/* =========================================================================== Wolfenstein: Enemy Territory GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Wolfenstein: Enemy Territory GPL Source Code (“Wolf ET Source Code”). Wolf ET Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Wolf ET Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Wolf ET Source Code. If not, see . In addition, the Wolf: ET Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Wolf ET Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ /* * name: g_weapon.c * * desc: perform the server side effects of a weapon firing * */ #include "g_local.h" vec3_t forward, right, up; vec3_t muzzleEffect; vec3_t muzzleTrace; // forward dec void Bullet_Fire( gentity_t *ent, float spread, int damage, qboolean distance_falloff ); qboolean Bullet_Fire_Extended( gentity_t *source, gentity_t *attacker, vec3_t start, vec3_t end, float spread, int damage, qboolean distance_falloff ); int G_GetWeaponDamage( int weapon ); // JPW qboolean G_WeaponIsExplosive( meansOfDeath_t mod ) { switch ( mod ) { case MOD_GRENADE_LAUNCHER: case MOD_GRENADE_PINEAPPLE: case MOD_PANZERFAUST: case MOD_LANDMINE: case MOD_GPG40: case MOD_M7: case MOD_ARTY: case MOD_AIRSTRIKE: case MOD_MORTAR: case MOD_SATCHEL: case MOD_DYNAMITE: // map entity based explosions case MOD_GRENADE: case MOD_MAPMORTAR: case MOD_MAPMORTAR_SPLASH: case MOD_EXPLOSIVE: case MOD_TELEFRAG: // Gordon: yes this _SHOULD_ be here, kthxbye case MOD_CRUSH: return qtrue; default: return qfalse; } } int G_GetWeaponClassForMOD( meansOfDeath_t mod ) { switch ( mod ) { case MOD_GRENADE_LAUNCHER: case MOD_GRENADE_PINEAPPLE: case MOD_PANZERFAUST: case MOD_LANDMINE: case MOD_GPG40: case MOD_M7: case MOD_ARTY: case MOD_AIRSTRIKE: case MOD_MORTAR: // map entity based explosions case MOD_GRENADE: case MOD_MAPMORTAR: case MOD_MAPMORTAR_SPLASH: case MOD_EXPLOSIVE: return 0; case MOD_SATCHEL: return 1; case MOD_DYNAMITE: return 2; default: return -1; } } #define NUM_NAILSHOTS 10 /* ====================================================================== KNIFE/GAUNTLET (NOTE: gauntlet is now the Zombie melee) ====================================================================== */ #define KNIFE_DIST 48 // Let's use the same angle between function we've used before extern float sAngleBetweenVectors( vec3_t a, vec3_t b ); /* ============== Weapon_Knife ============== */ void Weapon_Knife( gentity_t *ent ) { trace_t tr; gentity_t *traceEnt, *tent; int damage, mod; vec3_t pforward, eforward; vec3_t end; mod = MOD_KNIFE; AngleVectors( ent->client->ps.viewangles, forward, right, up ); CalcMuzzlePoint( ent, ent->s.weapon, forward, right, up, muzzleTrace ); VectorMA( muzzleTrace, KNIFE_DIST, forward, end ); G_HistoricalTrace( ent, &tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT ); if ( tr.surfaceFlags & SURF_NOIMPACT ) { return; } // no contact if ( tr.fraction == 1.0f ) { return; } if ( tr.entityNum >= MAX_CLIENTS ) { // world brush or non-player entity (no blood) tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS ); } else { // other player tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); } tent->s.otherEntityNum = tr.entityNum; tent->s.eventParm = DirToByte( tr.plane.normal ); tent->s.weapon = ent->s.weapon; tent->s.clientNum = ent->r.ownerNum; if ( tr.entityNum == ENTITYNUM_WORLD ) { // don't worry about doing any damage return; } traceEnt = &g_entities[ tr.entityNum ]; if ( !( traceEnt->takedamage ) ) { return; } damage = G_GetWeaponDamage( ent->s.weapon ); // JPW // default knife damage for frontal attacks /* CHECK WITH PAUL */ if ( ent->client->sess.playerType == PC_COVERTOPS ) { damage *= 2; // Watch it - you could hurt someone with that thing! } if ( traceEnt->client ) { AngleVectors( ent->client->ps.viewangles, pforward, NULL, NULL ); AngleVectors( traceEnt->client->ps.viewangles, eforward, NULL, NULL ); if ( DotProduct( eforward, pforward ) > 0.6f ) { // from behind(-ish) damage = 100; // enough to drop a 'normal' (100 health) human with one jab mod = MOD_KNIFE; // rain - only do this if they have a positive health if ( traceEnt->health > 0 && ent->client->sess.skill[SK_MILITARY_INTELLIGENCE_AND_SCOPED_WEAPONS] >= 4 ) { damage = traceEnt->health; } } } G_Damage( traceEnt, ent, ent, vec3_origin, tr.endpos, ( damage + rand() % 5 ), 0, mod ); } // JPW NERVE //make it TR_LINEAR so it doesnt chew bandwidth... void MagicSink( gentity_t *self ) { self->clipmask = 0; self->r.contents = 0; self->nextthink = level.time + 4000; self->think = G_FreeEntity; self->s.pos.trType = TR_LINEAR; self->s.pos.trTime = level.time; VectorCopy( self->r.currentOrigin, self->s.pos.trBase ); VectorSet( self->s.pos.trDelta, 0, 0, -5 ); } /* ====================== Weapon_Class_Special class-specific in multiplayer ====================== */ // JPW NERVE void Weapon_Medic( gentity_t *ent ) { gitem_t *item; gentity_t *ent2; vec3_t velocity, offset; vec3_t angles,mins,maxs; vec3_t tosspos, viewpos; trace_t tr; if ( level.time - ent->client->ps.classWeaponTime > level.medicChargeTime[ent->client->sess.sessionTeam - 1] ) { ent->client->ps.classWeaponTime = level.time - level.medicChargeTime[ent->client->sess.sessionTeam - 1]; } if ( ent->client->sess.skill[SK_FIRST_AID] >= 2 ) { ent->client->ps.classWeaponTime += level.medicChargeTime[ent->client->sess.sessionTeam - 1] * 0.15; } else { ent->client->ps.classWeaponTime += level.medicChargeTime[ent->client->sess.sessionTeam - 1] * 0.25; } item = BG_FindItemForClassName( "item_health" ); VectorCopy( ent->client->ps.viewangles, angles ); // clamp pitch if ( angles[PITCH] < -30 ) { angles[PITCH] = -30; } else if ( angles[PITCH] > 30 ) { angles[PITCH] = 30; } AngleVectors( angles, velocity, NULL, NULL ); VectorScale( velocity, 64, offset ); offset[2] += ent->client->ps.viewheight / 2; VectorScale( velocity, 75, velocity ); velocity[2] += 50 + crandom() * 25; VectorCopy( muzzleEffect, tosspos ); VectorMA( tosspos, 48, forward, tosspos ); VectorCopy( ent->client->ps.origin, viewpos ); VectorSet( mins, -( ITEM_RADIUS + 8 ), -( ITEM_RADIUS + 8 ), 0 ); VectorSet( maxs, ( ITEM_RADIUS + 8 ), ( ITEM_RADIUS + 8 ), 2 * ( ITEM_RADIUS + 8 ) ); trap_EngineerTrace( &tr, viewpos, mins, maxs, tosspos, ent->s.number, MASK_MISSILESHOT ); if ( tr.startsolid ) { // Arnout: this code is a bit more solid than the previous code VectorCopy( forward, viewpos ); VectorNormalizeFast( viewpos ); VectorMA( ent->r.currentOrigin, -24.f, viewpos, viewpos ); trap_EngineerTrace( &tr, viewpos, mins, maxs, tosspos, ent->s.number, MASK_MISSILESHOT ); VectorCopy( tr.endpos, tosspos ); } else if ( tr.fraction < 1 ) { // oops, bad launch spot VectorCopy( tr.endpos, tosspos ); SnapVectorTowards( tosspos, viewpos ); } ent2 = LaunchItem( item, tosspos, velocity, ent->s.number ); ent2->think = MagicSink; ent2->nextthink = level.time + 30000; // ent2->timestamp = level.time + 31200; ent2->parent = ent; // JPW NERVE so we can score properly later //ent2->count = 20; } /* ========== G_PlaceTripmine ========== */ void G_PlaceTripmine( gentity_t* ent ) { vec3_t start, end; trace_t trace; gentity_t* bomb; vec3_t forward; VectorCopy( ent->client->ps.origin, start ); start[2] += ent->client->ps.viewheight; AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); VectorMA( start, 64, forward, end ); trap_Trace( &trace, start, NULL, NULL, end, ent->s.number, MASK_SHOT ); bomb = G_Spawn(); bomb->r.svFlags = SVF_BROADCAST; bomb->s.eType = ET_BOMB; bomb->s.eFlags = 0; bomb->s.weapon = WP_TRIPMINE; bomb->parent = ent; bomb->think = G_TripMinePrime; bomb->nextthink = level.time + 2000; bomb->splashDamage = 300; bomb->splashRadius = 300; bomb->methodOfDeath = MOD_TRIPMINE; bomb->splashMethodOfDeath = MOD_TRIPMINE; bomb->r.contents = CONTENTS_CORPSE; // (player can walk through) VectorSet( bomb->r.mins, -12, -12, 0 ); VectorCopy( bomb->r.mins, bomb->r.absmin ); VectorSet( bomb->r.maxs, 12, 12, 20 ); VectorCopy( bomb->r.maxs, bomb->r.absmax ); VectorMA( trace.endpos, 1, trace.plane.normal, start ); G_SetOrigin( bomb, start ); G_SetAngle( bomb, vec3_origin ); VectorCopy( trace.plane.normal, bomb->s.origin2 ); trap_LinkEntity( bomb ); } /*void Weapon_SatchelCharge(gentity_t *ent) { gitem_t *item; gentity_t *ent2; vec3_t velocity, org, offset; vec3_t angles,mins,maxs; trace_t tr; item = BG_FindItem("Satchel Charge"); VectorCopy( ent->client->ps.viewangles, angles ); // clamp pitch if ( angles[PITCH] < -30 ) angles[PITCH] = -30; else if ( angles[PITCH] > 30 ) angles[PITCH] = 30; AngleVectors( angles, velocity, NULL, NULL ); VectorScale( velocity, 64, offset); offset[2] += ent->client->ps.viewheight/2; VectorScale( velocity, 75, velocity ); velocity[2] += 50 + crandom() * 25; VectorAdd(ent->client->ps.origin,offset,org); VectorSet( mins, -ITEM_RADIUS, -ITEM_RADIUS, 0 ); VectorSet( maxs, ITEM_RADIUS, ITEM_RADIUS, 2*ITEM_RADIUS ); trap_Trace (&tr, ent->client->ps.origin, mins, maxs, org, ent->s.number, MASK_SOLID); VectorCopy( tr.endpos, org ); ent2 = LaunchItem( item, org, velocity, ent->s.number ); ent2->think = MagicSink; ent2->timestamp = level.time + 4000; ent2->parent = ent; ent2->s.eType = ET_MISSILE; ent2->methodOfDeath = MOD_SATCHEL; ent2->splashMethodOfDeath = MOD_SATCHEL; ent2->s.weapon = WP_SATCHEL; ent2->touch = 0; } */ // JPW NERVE /* ================== Weapon_MagicAmmo ================== */ void Weapon_MagicAmmo( gentity_t *ent ) { gitem_t *item; gentity_t *ent2; vec3_t velocity, offset; vec3_t tosspos, viewpos; vec3_t angles,mins,maxs; trace_t tr; if ( level.time - ent->client->ps.classWeaponTime > level.lieutenantChargeTime[ent->client->sess.sessionTeam - 1] ) { ent->client->ps.classWeaponTime = level.time - level.lieutenantChargeTime[ent->client->sess.sessionTeam - 1]; } if ( ent->client->sess.skill[SK_SIGNALS] >= 1 ) { ent->client->ps.classWeaponTime += level.lieutenantChargeTime[ent->client->sess.sessionTeam - 1] * 0.15; } else { ent->client->ps.classWeaponTime += level.lieutenantChargeTime[ent->client->sess.sessionTeam - 1] * 0.25; } item = BG_FindItem( ent->client->sess.skill[SK_SIGNALS] >= 1 ? "Mega Ammo Pack" : "Ammo Pack" ); VectorCopy( ent->client->ps.viewangles, angles ); // clamp pitch if ( angles[PITCH] < -30 ) { angles[PITCH] = -30; } else if ( angles[PITCH] > 30 ) { angles[PITCH] = 30; } AngleVectors( angles, velocity, NULL, NULL ); VectorScale( velocity, 64, offset ); offset[2] += ent->client->ps.viewheight / 2; VectorScale( velocity, 75, velocity ); velocity[2] += 50 + crandom() * 25; VectorCopy( muzzleEffect, tosspos ); VectorMA( tosspos, 48, forward, tosspos ); VectorCopy( ent->client->ps.origin, viewpos ); VectorSet( mins, -( ITEM_RADIUS + 8 ), -( ITEM_RADIUS + 8 ), 0 ); VectorSet( maxs, ( ITEM_RADIUS + 8 ), ( ITEM_RADIUS + 8 ), 2 * ( ITEM_RADIUS + 8 ) ); trap_EngineerTrace( &tr, viewpos, mins, maxs, tosspos, ent->s.number, MASK_MISSILESHOT ); if ( tr.startsolid ) { // Arnout: this code is a bit more solid than the previous code VectorCopy( forward, viewpos ); VectorNormalizeFast( viewpos ); VectorMA( ent->r.currentOrigin, -24.f, viewpos, viewpos ); trap_EngineerTrace( &tr, viewpos, mins, maxs, tosspos, ent->s.number, MASK_MISSILESHOT ); VectorCopy( tr.endpos, tosspos ); } else if ( tr.fraction < 1 ) { // oops, bad launch spot VectorCopy( tr.endpos, tosspos ); SnapVectorTowards( tosspos, viewpos ); } ent2 = LaunchItem( item, tosspos, velocity, ent->s.number ); ent2->think = MagicSink; ent2->nextthink = level.time + 30000; // ent2->timestamp = level.time + 31200; ent2->parent = ent; if ( ent->client->sess.skill[SK_SIGNALS] >= 1 ) { ent2->count = 2; ent2->s.density = 2; } else { ent2->count = 1; ent2->s.density = 1; } } // jpw // START - Mad Doc - TDF // took this out of Weapon_Syringe so we can use it from other places qboolean ReviveEntity( gentity_t *ent, gentity_t *traceEnt ) { vec3_t org; trace_t tr; int healamt, headshot, oldweapon,oldweaponstate,oldclasstime = 0; qboolean usedSyringe = qfalse; // DHM - Nerve int ammo[MAX_WEAPONS]; // JPW NERVE total amount of ammo int ammoclip[MAX_WEAPONS]; // JPW NERVE ammo in clip int weapons[MAX_WEAPONS / ( sizeof( int ) * 8 )]; // JPW NERVE 64 bits for weapons held // gentity_t *traceEnt, gentity_t *te; // heal the dude // copy some stuff out that we'll wanna restore VectorCopy( traceEnt->client->ps.origin, org ); headshot = traceEnt->client->ps.eFlags & EF_HEADSHOT; if ( ent->client->sess.skill[SK_FIRST_AID] >= 3 ) { healamt = traceEnt->client->ps.stats[STAT_MAX_HEALTH]; } else { healamt = traceEnt->client->ps.stats[STAT_MAX_HEALTH] * 0.5; } oldweapon = traceEnt->client->ps.weapon; oldweaponstate = traceEnt->client->ps.weaponstate; // keep class special weapon time to keep them from exploiting revives oldclasstime = traceEnt->client->ps.classWeaponTime; memcpy( ammo,traceEnt->client->ps.ammo,sizeof( int ) * MAX_WEAPONS ); memcpy( ammoclip,traceEnt->client->ps.ammoclip,sizeof( int ) * MAX_WEAPONS ); memcpy( weapons,traceEnt->client->ps.weapons,sizeof( int ) * ( MAX_WEAPONS / ( sizeof( int ) * 8 ) ) ); ClientSpawn( traceEnt, qtrue ); traceEnt->client->ps.stats[STAT_PLAYER_CLASS] = traceEnt->client->sess.playerType; memcpy( traceEnt->client->ps.ammo,ammo,sizeof( int ) * MAX_WEAPONS ); memcpy( traceEnt->client->ps.ammoclip,ammoclip,sizeof( int ) * MAX_WEAPONS ); memcpy( traceEnt->client->ps.weapons,weapons,sizeof( int ) * ( MAX_WEAPONS / ( sizeof( int ) * 8 ) ) ); if ( headshot ) { traceEnt->client->ps.eFlags |= EF_HEADSHOT; } traceEnt->client->ps.weapon = oldweapon; traceEnt->client->ps.weaponstate = oldweaponstate; // set idle animation on weapon traceEnt->client->ps.weapAnim = ( ( traceEnt->client->ps.weapAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | PM_IdleAnimForWeapon( traceEnt->client->ps.weapon ); traceEnt->client->ps.classWeaponTime = oldclasstime; traceEnt->health = healamt; VectorCopy( org,traceEnt->s.origin ); VectorCopy( org,traceEnt->r.currentOrigin ); VectorCopy( org,traceEnt->client->ps.origin ); trap_Trace( &tr, traceEnt->client->ps.origin, traceEnt->client->ps.mins, traceEnt->client->ps.maxs, traceEnt->client->ps.origin, traceEnt->s.number, MASK_PLAYERSOLID ); if ( tr.allsolid ) { traceEnt->client->ps.pm_flags |= PMF_DUCKED; } traceEnt->r.contents = CONTENTS_CORPSE; trap_LinkEntity( ent ); // DHM - Nerve :: Let the person being revived know about it trap_SendServerCommand( traceEnt - g_entities, va( "cp \"You have been revived by [lof]%s[lon] [lof]%s!\n\"", ent->client->sess.sessionTeam == TEAM_ALLIES ? rankNames_Allies[ ent->client->sess.rank ] : rankNames_Axis[ ent->client->sess.rank ], ent->client->pers.netname ) ); traceEnt->props_frame_state = ent->s.number; // DHM - Nerve :: Mark that the medicine was indeed dispensed usedSyringe = qtrue; // sound te = G_TempEntity( traceEnt->r.currentOrigin, EV_GENERAL_SOUND ); te->s.eventParm = G_SoundIndex( "sound/misc/vo_revive.wav" ); // Xian -- This was gay and I always hated it. if ( g_fastres.integer > 0 ) { BG_AnimScriptEvent( &traceEnt->client->ps, traceEnt->client->pers.character->animModelInfo, ANIM_ET_JUMP, qfalse, qtrue ); } else { // DHM - Nerve :: Play revive animation BG_AnimScriptEvent( &traceEnt->client->ps, traceEnt->client->pers.character->animModelInfo, ANIM_ET_REVIVE, qfalse, qtrue ); traceEnt->client->ps.pm_flags |= PMF_TIME_LOCKPLAYER; traceEnt->client->ps.pm_time = 2100; } // Tell the caller if we actually used a syringe return usedSyringe; } // END - Mad Doc // JPW NERVE Weapon_Syringe: /* ====================== Weapon_Syringe shoot the syringe, do the old lazarus bit ====================== */ void Weapon_Syringe( gentity_t *ent ) { vec3_t end; trace_t tr; qboolean usedSyringe = qfalse; // DHM - Nerve gentity_t *traceEnt; AngleVectors( ent->client->ps.viewangles, forward, right, up ); CalcMuzzlePointForActivate( ent, forward, right, up, muzzleTrace ); VectorMA( muzzleTrace, 48, forward, end ); // CH_ACTIVATE_DIST //VectorMA (muzzleTrace, -16, forward, muzzleTrace); // DHM - Back up the start point in case medic is // right on top of intended revivee. trap_Trace( &tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT ); if ( tr.startsolid ) { VectorMA( muzzleTrace, 8, forward, end ); // CH_ACTIVATE_DIST trap_Trace( &tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT ); } if ( tr.fraction < 1.0 ) { traceEnt = &g_entities[ tr.entityNum ]; if ( traceEnt->client != NULL ) { if ( ( traceEnt->client->ps.pm_type == PM_DEAD ) && ( traceEnt->client->sess.sessionTeam == ent->client->sess.sessionTeam ) ) { // Mad Doc - TDF moved all the revive stuff into its own function usedSyringe = ReviveEntity( ent, traceEnt ); // OSP - syringe "hit" if ( g_gamestate.integer == GS_PLAYING ) { ent->client->sess.aWeaponStats[WS_SYRINGE].hits++; } if ( ent && ent->client ) { G_LogPrintf( "Medic_Revive: %d %d\n", ent - g_entities, traceEnt - g_entities ); // OSP } if ( !traceEnt->isProp ) { // Gordon: flag for if they were teamkilled or not AddScore( ent, WOLF_MEDIC_BONUS ); // JPW NERVE props to the medic for the swift and dexterous bit o healitude G_AddSkillPoints( ent, SK_FIRST_AID, 4.f ); G_DebugAddSkillPoints( ent, SK_FIRST_AID, 4.f, "reviving a player" ); } // Arnout: calculate ranks to update numFinalDead arrays. Have to do it manually as addscore has an early out if ( g_gametype.integer == GT_WOLF_LMS ) { CalculateRanks(); } } } } // DHM - Nerve :: If the medicine wasn't used, give back the ammo if ( !usedSyringe ) { ent->client->ps.ammoclip[BG_FindClipForWeapon( WP_MEDIC_SYRINGE )] += 1; } } // jpw /* ====================== Weapon_AdrenalineSyringe Hmmmm. Needles. With stuff in it. Woooo. ====================== */ void Weapon_AdrenalineSyringe( gentity_t *ent ) { ent->client->ps.powerups[PW_ADRENALINE] = level.time + 10000; } void G_ExplodeMissile( gentity_t *ent ); void DynaSink( gentity_t* self ); // Arnout: crude version of G_RadiusDamage to see if the dynamite can damage a func_constructible int EntsThatRadiusCanDamage( vec3_t origin, float radius, int *damagedList ) { float dist; gentity_t *ent; int entityList[MAX_GENTITIES]; int numListedEntities; vec3_t mins, maxs; vec3_t v; int i, e; float boxradius; vec3_t dest; trace_t tr; vec3_t midpoint; int numDamaged = 0; if ( radius < 1 ) { radius = 1; } boxradius = 1.41421356 * radius; // radius * sqrt(2) for bounding box enlargement -- // bounding box was checking against radius / sqrt(2) if collision is along box plane for ( i = 0 ; i < 3 ; i++ ) { mins[i] = origin[i] - boxradius; maxs[i] = origin[i] + boxradius; } numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for ( e = 0 ; e < numListedEntities ; e++ ) { ent = &g_entities[entityList[ e ]]; if ( !ent->r.bmodel ) { VectorSubtract( ent->r.currentOrigin,origin,v ); } else { for ( i = 0 ; i < 3 ; i++ ) { if ( origin[i] < ent->r.absmin[i] ) { v[i] = ent->r.absmin[i] - origin[i]; } else if ( origin[i] > ent->r.absmax[i] ) { v[i] = origin[i] - ent->r.absmax[i]; } else { v[i] = 0; } } } dist = VectorLength( v ); if ( dist >= radius ) { continue; } if ( CanDamage( ent, origin ) ) { damagedList[numDamaged++] = entityList[e]; } else { VectorAdd( ent->r.absmin, ent->r.absmax, midpoint ); VectorScale( midpoint, 0.5, midpoint ); VectorCopy( midpoint, dest ); trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); if ( tr.fraction < 1.0 ) { VectorSubtract( dest,origin,dest ); dist = VectorLength( dest ); if ( dist < radius * 0.2f ) { // closer than 1/4 dist damagedList[numDamaged++] = entityList[e]; } } } } return( numDamaged ); } void G_LandminePrime( gentity_t *self ); extern void explosive_indicator_think( gentity_t *ent ); #define MIN_BLOCKINGWARNING_INTERVAL 5000 static void MakeTemporarySolid( gentity_t *ent ) { if ( ent->entstate == STATE_UNDERCONSTRUCTION ) { ent->clipmask = ent->realClipmask; ent->r.contents = ent->realContents; if ( !ent->realNonSolidBModel ) { ent->s.eFlags &= ~EF_NONSOLID_BMODEL; } } trap_LinkEntity( ent ); } static void UndoTemporarySolid( gentity_t *ent ) { ent->entstate = STATE_UNDERCONSTRUCTION; ent->s.powerups = STATE_UNDERCONSTRUCTION; ent->realClipmask = ent->clipmask; ent->clipmask = 0; ent->realContents = ent->r.contents; ent->r.contents = 0; if ( ent->s.eFlags & EF_NONSOLID_BMODEL ) { ent->realNonSolidBModel = qtrue; } else { ent->s.eFlags |= EF_NONSOLID_BMODEL; } trap_LinkEntity( ent ); } // handleBlockingEnts = kill players, return flags, remove entities // warnBlockingPlayers = warn any players that are in the constructible area static void HandleEntsThatBlockConstructible( gentity_t *constructor, gentity_t *constructible, qboolean handleBlockingEnts, qboolean warnBlockingPlayers ) { // check if something blocks us int constructibleList[MAX_GENTITIES]; int entityList[MAX_GENTITIES]; int blockingList[MAX_GENTITIES]; int constructibleEntities = 0; int listedEntities, e; int blockingEntities = 0; gentity_t *check, *block; // backup... int constructibleModelindex = constructible->s.modelindex; int constructibleClipmask = constructible->clipmask; int constructibleContents = constructible->r.contents; int constructibleNonSolidBModel = ( constructible->s.eFlags & EF_NONSOLID_BMODEL ); trap_SetBrushModel( constructible, va( "*%i", constructible->s.modelindex2 ) ); // ...and restore constructible->clipmask = constructibleClipmask; constructible->r.contents = constructibleContents; if ( !constructibleNonSolidBModel ) { constructible->s.eFlags &= ~EF_NONSOLID_BMODEL; } trap_LinkEntity( constructible ); // store our origin VectorCopy( constructible->r.absmin, constructible->s.origin2 ); VectorAdd( constructible->r.absmax, constructible->s.origin2, constructible->s.origin2 ); VectorScale( constructible->s.origin2, 0.5, constructible->s.origin2 ); // get all the entities that make up the constructible if ( constructible->track && constructible->track[0] ) { vec3_t mins, maxs; VectorCopy( constructible->r.absmin, mins ); VectorCopy( constructible->r.absmax, maxs ); check = NULL; while ( 1 ) { check = G_Find( check, FOFS( track ), constructible->track ); if ( check == constructible ) { continue; } if ( !check ) { break; } if ( constructible->count2 ) { if ( check->partofstage != constructible->grenadeFired ) { continue; } } // get the bounding box of all entities in the constructible together AddPointToBounds( check->r.absmin, mins, maxs ); AddPointToBounds( check->r.absmax, mins, maxs ); constructibleList[constructibleEntities++] = check->s.number; } listedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); // make our constructible entities solid so we can check against them //trap_LinkEntity( constructible ); MakeTemporarySolid( constructible ); for ( e = 0; e < constructibleEntities; e++ ) { check = &g_entities[constructibleList[e]]; //trap_LinkEntity( check ); MakeTemporarySolid( check ); } } else { // Gordon: changed * to abs* listedEntities = trap_EntitiesInBox( constructible->r.absmin, constructible->r.absmax, entityList, MAX_GENTITIES ); // make our constructible solid so we can check against it //trap_LinkEntity( constructible ); MakeTemporarySolid( constructible ); } for ( e = 0; e < listedEntities; e++ ) { check = &g_entities[entityList[e]]; // ignore everything but items, players and missiles (grenades too) if ( check->s.eType != ET_MISSILE && check->s.eType != ET_ITEM && check->s.eType != ET_PLAYER && !check->physicsObject ) { continue; } // remove any corpses, this includes dynamite if ( check->r.contents == CONTENTS_CORPSE ) { blockingList[blockingEntities++] = entityList[e]; continue; } // FIXME : dynamite seems to test out of position? // see if the entity is in a solid now if ( ( block = G_TestEntityPosition( check ) ) == NULL ) { continue; } // the entity is blocked and it is a player, then warn the player if ( warnBlockingPlayers && check->s.eType == ET_PLAYER ) { if ( ( level.time - check->client->lastConstructibleBlockingWarnTime ) >= MIN_BLOCKINGWARNING_INTERVAL ) { trap_SendServerCommand( check->s.number, "cp \"Warning, leave the construction area...\" 1" ); // Gordon: store the entity num to warn the bot check->client->lastConstructibleBlockingWarnEnt = constructible - g_entities; check->client->lastConstructibleBlockingWarnTime = level.time; } // unlink our entities again /*trap_UnlinkEntity( constructible ); if( constructible->track && constructible->track[0] ) { for( e = 0; e < constructibleEntities; e++ ) { check = &g_entities[constructibleList[e]]; trap_UnlinkEntity( check ); } } return;*/ } blockingList[blockingEntities++] = entityList[e]; } // undo the temporary solid for our entities UndoTemporarySolid( constructible ); if ( constructible->track && constructible->track[0] ) { for ( e = 0; e < constructibleEntities; e++ ) { check = &g_entities[constructibleList[e]]; //trap_UnlinkEntity( check ); UndoTemporarySolid( check ); } } if ( handleBlockingEnts ) { for ( e = 0; e < blockingEntities; e++ ) { block = &g_entities[blockingList[e]]; if ( block->client || block->s.eType == ET_CORPSE ) { G_Damage( block, constructible, constructor, NULL, NULL, 9999, DAMAGE_NO_PROTECTION, MOD_CRUSH_CONSTRUCTION ); } else if ( block->s.eType == ET_ITEM && block->item->giType == IT_TEAM ) { // see if it's a critical entity, one that we can't just simply kill (basically flags) Team_DroppedFlagThink( block ); } else { // remove the landmine from both teamlists if ( block->s.eType == ET_MISSILE && block->methodOfDeath == MOD_LANDMINE ) { mapEntityData_t *mEnt; if ( ( mEnt = G_FindMapEntityData( &mapEntityData[0], block - g_entities ) ) != NULL ) { G_FreeMapEntityData( &mapEntityData[0], mEnt ); } if ( ( mEnt = G_FindMapEntityData( &mapEntityData[1], block - g_entities ) ) != NULL ) { G_FreeMapEntityData( &mapEntityData[1], mEnt ); } } // just get rid of it G_TempEntity( block->s.origin, EV_ITEM_POP ); G_FreeEntity( block ); } } } if ( constructibleModelindex ) { trap_SetBrushModel( constructible, va( "*%i", constructibleModelindex ) ); // ...and restore constructible->clipmask = constructibleClipmask; constructible->r.contents = constructibleContents; if ( !constructibleNonSolidBModel ) { constructible->s.eFlags &= ~EF_NONSOLID_BMODEL; } trap_LinkEntity( constructible ); } else { constructible->s.modelindex = 0; //constructible->clipmask = constructibleClipmask; //constructible->r.contents = constructibleContents; //if( !constructibleNonSolidBModel ) // constructible->s.eFlags &= ~EF_NONSOLID_BMODEL; trap_LinkEntity( constructible ); } } #define CONSTRUCT_POSTDECAY_TIME 500 // !! NOTE !!: if the conditions here of a buildable constructible change, then BotIsConstructible() must reflect those changes // returns qfalse when it couldn't build static qboolean TryConstructing( gentity_t *ent ) { gentity_t *check; gentity_t *constructible = ent->client->touchingTOI->target_ent; int i; // no construction during prematch if ( level.warmupTime ) { return( qfalse ); } // see if we are in a trigger_objective_info targetting multiple func_constructibles if ( constructible->s.eType == ET_CONSTRUCTIBLE && ent->client->touchingTOI->chain ) { gentity_t *otherconstructible = NULL; // use the target that has the same team as the player if ( constructible->s.teamNum != ent->client->sess.sessionTeam ) { constructible = ent->client->touchingTOI->chain; } otherconstructible = constructible->chain; // make sure the other constructible isn't built/underconstruction/something if ( otherconstructible->s.angles2[0] || otherconstructible->s.angles2[1] || ( otherconstructible->count2 && otherconstructible->grenadeFired ) ) { return( qfalse ); } } // see if we are in a trigger_objective_info targetting a func_constructible if ( constructible->s.eType == ET_CONSTRUCTIBLE && constructible->s.teamNum == ent->client->sess.sessionTeam ) { if ( constructible->s.angles2[0] >= 250 ) { // have to do this so we don't score multiple times return( qfalse ); } if ( constructible->s.angles2[1] != 0 ) { return( qfalse ); } // Check if we can construct - updates the classWeaponTime as well if ( !ReadyToConstruct( ent, constructible, qtrue ) ) { return qtrue; } // try to start building if ( constructible->s.angles2[0] <= 0 ) { // wait a bit, this prevents network spam if ( level.time - constructible->lastHintCheckTime < CONSTRUCT_POSTDECAY_TIME ) { return( qtrue ); // likely will come back soon - so override other plier bits anyway } // Gordon: are we scripted only? if ( !( ent->spawnflags & CONSTRUCTIBLE_AAS_SCRIPTED ) ) { if ( !( ent->spawnflags & CONSTRUCTIBLE_NO_AAS_BLOCKING ) ) { // RF, if we are blocking AAS areas when built, then clear AAS blocking so we can set it again after the stage has been increased if ( constructible->spawnflags & CONSTRUCTIBLE_BLOCK_PATHS_WHEN_BUILD ) { G_SetAASBlockingEntity( ent, AAS_AREA_ENABLED ); } } } // swap brushmodels if staged if ( constructible->count2 ) { constructible->grenadeFired++; constructible->s.modelindex2 = constructible->conbmodels[constructible->grenadeFired - 1]; //trap_SetBrushModel( constructible, va( "*%i", constructible->conbmodels[constructible->grenadeFired-1] ) ); } G_SetEntState( constructible, STATE_UNDERCONSTRUCTION ); if ( !constructible->count2 ) { // call script G_Script_ScriptEvent( constructible, "buildstart", "final" ); constructible->s.frame = 1; } else { if ( constructible->grenadeFired == constructible->count2 ) { G_Script_ScriptEvent( constructible, "buildstart", "final" ); constructible->s.frame = constructible->grenadeFired; } else { switch ( constructible->grenadeFired ) { case 1: G_Script_ScriptEvent( constructible, "buildstart", "stage1" ); constructible->s.frame = 1; break; case 2: G_Script_ScriptEvent( constructible, "buildstart", "stage2" ); constructible->s.frame = 2; break; case 3: G_Script_ScriptEvent( constructible, "buildstart", "stage3" ); constructible->s.frame = 3; break; } } } { vec3_t mid; gentity_t* te; VectorAdd( constructible->parent->r.absmin, constructible->parent->r.absmax, mid ); VectorScale( mid, 0.5f, mid ); te = G_TempEntity( mid, EV_GENERAL_SOUND ); te->s.eventParm = G_SoundIndex( "sound/world/build.wav" ); } // Play sound /* if( constructible->parent->spawnflags & 8 ) { constructible->parent->s.loopSound = G_SoundIndex( va( "sound/world/build_stage%i.wav", constructible->s.frame ) ); } else { constructible->s.loopSound = G_SoundIndex( va( "sound/world/build_stage%i.wav", constructible->s.frame ) ); }*/ if ( ent->client->touchingTOI->chain && ent->client->touchingTOI->count2 ) { // find the constructible indicator and change team mapEntityData_t *mEnt; mapEntityData_Team_t *teamList; gentity_t *indicator = &g_entities[ent->client->touchingTOI->count2]; indicator->s.teamNum = constructible->s.teamNum; // update the map for the other team teamList = indicator->s.teamNum == TEAM_AXIS ? &mapEntityData[1] : &mapEntityData[0]; // inversed if ( ( mEnt = G_FindMapEntityData( teamList, indicator - g_entities ) ) != NULL ) { G_FreeMapEntityData( teamList, mEnt ); } } if ( !constructible->count2 || constructible->grenadeFired == 1 ) { // link in if we just started building G_UseEntity( constructible, ent->client->touchingTOI, ent ); } // setup our think function for decaying constructible->think = func_constructible_underconstructionthink; constructible->nextthink = level.time + FRAMETIME; G_PrintClientSpammyCenterPrint( ent - g_entities, "Constructing..." ); } // Give health until it is full, don't continue constructible->s.angles2[0] += ( 255.f / ( constructible->constructibleStats.duration / (float)FRAMETIME ) ); if ( constructible->s.angles2[0] >= 250 ) { constructible->s.angles2[0] = 0; HandleEntsThatBlockConstructible( ent, constructible, qtrue, qfalse ); } else { constructible->lastHintCheckTime = level.time; HandleEntsThatBlockConstructible( ent, constructible, qfalse, qtrue ); return( qtrue ); // properly constructed } //trap_SendServerCommand( ent-g_entities, "cp \"Job's done!\" 1"); // eeeh no point in doing this twice //HandleEntsThatBlockConstructible( ent, constructible, qtrue, qfalse ); if ( constructible->count2 ) { // backup... //int constructibleModelindex = constructible->s.modelindex; int constructibleClipmask = constructible->clipmask; int constructibleContents = constructible->r.contents; int constructibleNonSolidBModel = ( constructible->s.eFlags & EF_NONSOLID_BMODEL ); constructible->s.modelindex2 = 0; trap_SetBrushModel( constructible, va( "*%i", constructible->conbmodels[constructible->grenadeFired - 1] ) ); // ...and restore constructible->clipmask = constructibleClipmask; constructible->r.contents = constructibleContents; if ( !constructibleNonSolidBModel ) { constructible->s.eFlags &= ~EF_NONSOLID_BMODEL; } if ( constructible->grenadeFired == constructible->count2 ) { constructible->s.angles2[1] = 1; } } else { // backup... //int constructibleModelindex = constructible->s.modelindex; int constructibleClipmask = constructible->clipmask; int constructibleContents = constructible->r.contents; int constructibleNonSolidBModel = ( constructible->s.eFlags & EF_NONSOLID_BMODEL ); constructible->s.modelindex2 = 0; trap_SetBrushModel( constructible, constructible->model ); // ...and restore constructible->clipmask = constructibleClipmask; constructible->r.contents = constructibleContents; if ( !constructibleNonSolidBModel ) { constructible->s.eFlags &= ~EF_NONSOLID_BMODEL; } constructible->s.angles2[1] = 1; } // Gordon: removing messages for now /* if( ent->client->touchingTOI->spawnflags & 4 ) { // MESSAGE_OVERRIDE gentity_t* pm = G_PopupMessage( PM_CONSTRUCTION ); pm->s.effect3Time = G_StringIndex( ent->client->touchingTOI->spawnitem ); pm->s.effect2Time = TEAM_AXIS; pm->s.density = -1; // -1 = built (custom msg) } else { gentity_t* pm = G_PopupMessage( PM_CONSTRUCTION ); pm->s.density = ent->client->sess.sessionTeam; pm->s.effect2Time = 0; // 0 = built pm->s.effect3Time = ent->client->touchingTOI->s.teamNum; }*/ AddScore( ent, constructible->accuracy ); // give drop score to guy who built it G_AddSkillPoints( ent, SK_EXPLOSIVES_AND_CONSTRUCTION, constructible->constructibleStats.constructxpbonus ); G_DebugAddSkillPoints( ent, SK_EXPLOSIVES_AND_CONSTRUCTION, constructible->constructibleStats.constructxpbonus, "finishing a construction" ); // unlink the objective info to get rid of the indicator for now // Arnout: don't unlink, we still want the location popup. Instead, constructible_indicator_think got changed to free // the indicator when the constructible is constructed // if( constructible->parent ) // trap_UnlinkEntity( constructible->parent ); G_SetEntState( constructible, STATE_DEFAULT ); // make destructable if ( !( constructible->spawnflags & 2 ) ) { constructible->takedamage = qtrue; constructible->health = constructible->sound1to2; } // Stop thinking constructible->think = NULL; constructible->nextthink = 0; if ( !constructible->count2 ) { // call script G_Script_ScriptEvent( constructible, "built", "final" ); } else { if ( constructible->grenadeFired == constructible->count2 ) { G_Script_ScriptEvent( constructible, "built", "final" ); } else { switch ( constructible->grenadeFired ) { case 1: G_Script_ScriptEvent( constructible, "built", "stage1" ); break; case 2: G_Script_ScriptEvent( constructible, "built", "stage2" ); break; case 3: G_Script_ScriptEvent( constructible, "built", "stage3" ); break; } } } // Stop sound if ( constructible->parent->spawnflags & 8 ) { constructible->parent->s.loopSound = 0; } else { constructible->s.loopSound = 0; } //ent->client->ps.classWeaponTime = level.time; // Out of "ammo" // if not invulnerable and dynamite-able, create a 'destructable' marker for the other team if ( !( constructible->spawnflags & CONSTRUCTIBLE_INVULNERABLE ) && ( constructible->constructibleStats.weaponclass >= 1 ) ) { if ( !constructible->count2 || constructible->grenadeFired == 1 ) { gentity_t* tent = NULL; gentity_t *e; e = G_Spawn(); e->r.svFlags = SVF_BROADCAST; e->classname = "explosive_indicator"; e->s.pos.trType = TR_STATIONARY; e->s.eType = ET_EXPLOSIVE_INDICATOR; while ( ( tent = G_Find( tent, FOFS( target ), constructible->targetname ) ) != NULL ) { if ( tent->s.eType == ET_OID_TRIGGER ) { if ( tent->spawnflags & 8 ) { e->s.eType = ET_TANK_INDICATOR; } } } // Find the trigger_objective_info that targets us (if not set before) { gentity_t* tent = NULL; while ( ( tent = G_Find( tent, FOFS( target ), constructible->targetname ) ) != NULL ) { if ( ( tent->s.eType == ET_OID_TRIGGER ) ) { e->parent = tent; } } } if ( constructible->spawnflags & AXIS_CONSTRUCTIBLE ) { e->s.teamNum = TEAM_AXIS; } else if ( constructible->spawnflags & ALLIED_CONSTRUCTIBLE ) { e->s.teamNum = TEAM_ALLIES; } e->s.modelindex2 = ent->client->touchingTOI->s.teamNum; e->r.ownerNum = constructible->s.number; e->think = explosive_indicator_think; e->nextthink = level.time + FRAMETIME; e->s.effect1Time = constructible->constructibleStats.weaponclass; if ( constructible->parent->tagParent ) { e->tagParent = constructible->parent->tagParent; Q_strncpyz( e->tagName, constructible->parent->tagName, MAX_QPATH ); } else { VectorCopy( constructible->r.absmin, e->s.pos.trBase ); VectorAdd( constructible->r.absmax, e->s.pos.trBase, e->s.pos.trBase ); VectorScale( e->s.pos.trBase, 0.5, e->s.pos.trBase ); } SnapVector( e->s.pos.trBase ); trap_LinkEntity( e ); } else { // find our marker and update it's coordinates for ( i = 0, check = g_entities; i < level.num_entities; i++, check++ ) { if ( check->s.eType != ET_EXPLOSIVE_INDICATOR && check->s.eType != ET_TANK_INDICATOR && check->s.eType != ET_TANK_INDICATOR_DEAD ) { continue; } if ( check->r.ownerNum == constructible->s.number ) { // found it! if ( constructible->parent->tagParent ) { check->tagParent = constructible->parent->tagParent; Q_strncpyz( check->tagName, constructible->parent->tagName, MAX_QPATH ); } else { VectorCopy( constructible->r.absmin, check->s.pos.trBase ); VectorAdd( constructible->r.absmax, check->s.pos.trBase, check->s.pos.trBase ); VectorScale( check->s.pos.trBase, 0.5, check->s.pos.trBase ); SnapVector( check->s.pos.trBase ); } trap_LinkEntity( check ); break; } } } } // Gordon: are we scripted only? if ( !( ent->spawnflags & CONSTRUCTIBLE_AAS_SCRIPTED ) ) { if ( !( ent->spawnflags & CONSTRUCTIBLE_NO_AAS_BLOCKING ) ) { // RF, a stage has been completed, either enable or disable AAS areas appropriately if ( !( constructible->spawnflags & CONSTRUCTIBLE_BLOCK_PATHS_WHEN_BUILD ) ) { // builing creates AAS paths // Gordon: HACK from ryan // if( !constructible->count2 || ( constructible->grenadeFired == constructible->count2 ) ) { // completely built, enable paths G_SetAASBlockingEntity( constructible, AAS_AREA_ENABLED ); } } else { // builing blocks AAS paths G_SetAASBlockingEntity( constructible, AAS_AREA_DISABLED ); } } } return( qtrue ); // building } return( qfalse ); } void AutoBuildConstruction( gentity_t* constructible ) { int i; gentity_t* check; HandleEntsThatBlockConstructible( NULL, constructible, qtrue, qfalse ); if ( constructible->count2 ) { // backup... //int constructibleModelindex = constructible->s.modelindex; int constructibleClipmask = constructible->clipmask; int constructibleContents = constructible->r.contents; int constructibleNonSolidBModel = ( constructible->s.eFlags & EF_NONSOLID_BMODEL ); constructible->s.modelindex2 = 0; trap_SetBrushModel( constructible, va( "*%i", constructible->conbmodels[constructible->grenadeFired - 1] ) ); // ...and restore constructible->clipmask = constructibleClipmask; constructible->r.contents = constructibleContents; if ( !constructibleNonSolidBModel ) { constructible->s.eFlags &= ~EF_NONSOLID_BMODEL; } if ( constructible->grenadeFired == constructible->count2 ) { constructible->s.angles2[1] = 1; } } else { // backup... //int constructibleModelindex = constructible->s.modelindex; int constructibleClipmask = constructible->clipmask; int constructibleContents = constructible->r.contents; int constructibleNonSolidBModel = ( constructible->s.eFlags & EF_NONSOLID_BMODEL ); constructible->s.modelindex2 = 0; trap_SetBrushModel( constructible, constructible->model ); // ...and restore constructible->clipmask = constructibleClipmask; constructible->r.contents = constructibleContents; if ( !constructibleNonSolidBModel ) { constructible->s.eFlags &= ~EF_NONSOLID_BMODEL; } constructible->s.angles2[1] = 1; } // unlink the objective info to get rid of the indicator for now // Arnout: don't unlink, we still want the location popup. Instead, constructible_indicator_think got changed to free // the indicator when the constructible is constructed // if( constructible->parent ) // trap_UnlinkEntity( constructible->parent ); G_SetEntState( constructible, STATE_DEFAULT ); // make destructable if ( !( constructible->spawnflags & CONSTRUCTIBLE_INVULNERABLE ) ) { constructible->takedamage = qtrue; constructible->health = constructible->constructibleStats.health; } // Stop thinking constructible->think = NULL; constructible->nextthink = 0; if ( !constructible->count2 ) { // call script G_Script_ScriptEvent( constructible, "built", "final" ); } else { if ( constructible->grenadeFired == constructible->count2 ) { G_Script_ScriptEvent( constructible, "built", "final" ); } else { switch ( constructible->grenadeFired ) { case 1: G_Script_ScriptEvent( constructible, "built", "stage1" ); break; case 2: G_Script_ScriptEvent( constructible, "built", "stage2" ); break; case 3: G_Script_ScriptEvent( constructible, "built", "stage3" ); break; } } } // Stop sound if ( constructible->parent->spawnflags & 8 ) { constructible->parent->s.loopSound = 0; } else { constructible->s.loopSound = 0; } //ent->client->ps.classWeaponTime = level.time; // Out of "ammo" // if not invulnerable and dynamite-able, create a 'destructable' marker for the other team if ( !( constructible->spawnflags & CONSTRUCTIBLE_INVULNERABLE ) && ( constructible->constructibleStats.weaponclass >= 1 ) ) { if ( !constructible->count2 || constructible->grenadeFired == 1 ) { gentity_t* tent = NULL; gentity_t *e; e = G_Spawn(); e->r.svFlags = SVF_BROADCAST; e->classname = "explosive_indicator"; e->s.pos.trType = TR_STATIONARY; e->s.eType = ET_EXPLOSIVE_INDICATOR; while ( ( tent = G_Find( tent, FOFS( target ), constructible->targetname ) ) != NULL ) { if ( ( tent->s.eType == ET_OID_TRIGGER ) ) { if ( tent->spawnflags & 8 ) { e->s.eType = ET_TANK_INDICATOR; } } } // Find the trigger_objective_info that targets us (if not set before) { gentity_t* tent = NULL; while ( ( tent = G_Find( tent, FOFS( target ), constructible->targetname ) ) != NULL ) { if ( ( tent->s.eType == ET_OID_TRIGGER ) ) { e->parent = tent; } } } if ( constructible->spawnflags & AXIS_CONSTRUCTIBLE ) { e->s.teamNum = TEAM_AXIS; } else if ( constructible->spawnflags & ALLIED_CONSTRUCTIBLE ) { e->s.teamNum = TEAM_ALLIES; } e->s.modelindex2 = constructible->parent->s.teamNum == TEAM_AXIS ? TEAM_ALLIES : TEAM_AXIS; e->r.ownerNum = constructible->s.number; e->think = explosive_indicator_think; e->nextthink = level.time + FRAMETIME; e->s.effect1Time = constructible->constructibleStats.weaponclass; if ( constructible->parent->tagParent ) { e->tagParent = constructible->parent->tagParent; Q_strncpyz( e->tagName, constructible->parent->tagName, MAX_QPATH ); } else { VectorCopy( constructible->r.absmin, e->s.pos.trBase ); VectorAdd( constructible->r.absmax, e->s.pos.trBase, e->s.pos.trBase ); VectorScale( e->s.pos.trBase, 0.5, e->s.pos.trBase ); } SnapVector( e->s.pos.trBase ); trap_LinkEntity( e ); } else { // find our marker and update it's coordinates for ( i = 0, check = g_entities; i < level.num_entities; i++, check++ ) { if ( check->s.eType != ET_EXPLOSIVE_INDICATOR && check->s.eType != ET_TANK_INDICATOR && check->s.eType != ET_TANK_INDICATOR_DEAD ) { continue; } if ( check->r.ownerNum == constructible->s.number ) { // found it! if ( constructible->parent->tagParent ) { check->tagParent = constructible->parent->tagParent; Q_strncpyz( check->tagName, constructible->parent->tagName, MAX_QPATH ); } else { VectorCopy( constructible->r.absmin, check->s.pos.trBase ); VectorAdd( constructible->r.absmax, check->s.pos.trBase, check->s.pos.trBase ); VectorScale( check->s.pos.trBase, 0.5, check->s.pos.trBase ); SnapVector( check->s.pos.trBase ); } trap_LinkEntity( check ); break; } } } } // Gordon: are we scripted only? if ( !( constructible->spawnflags & CONSTRUCTIBLE_AAS_SCRIPTED ) ) { if ( !( constructible->spawnflags & CONSTRUCTIBLE_NO_AAS_BLOCKING ) ) { // RF, a stage has been completed, either enable or disable AAS areas appropriately if ( !( constructible->spawnflags & CONSTRUCTIBLE_BLOCK_PATHS_WHEN_BUILD ) ) { // builing creates AAS paths if ( !constructible->count2 || ( constructible->grenadeFired == constructible->count2 ) ) { // completely built, enable paths G_SetAASBlockingEntity( constructible, AAS_AREA_ENABLED ); } } else { // builing blocks AAS paths G_SetAASBlockingEntity( constructible, AAS_AREA_DISABLED ); } } } } qboolean G_LandmineTriggered( gentity_t* ent ) { switch ( ent->s.teamNum ) { case TEAM_AXIS + 8: case TEAM_ALLIES + 8: return qtrue; } return qfalse; } qboolean G_LandmineArmed( gentity_t* ent ) { switch ( ent->s.teamNum ) { case TEAM_AXIS: case TEAM_ALLIES: return qtrue; } return qfalse; } qboolean G_LandmineUnarmed( gentity_t* ent ) { return ( !G_LandmineArmed( ent ) && !G_LandmineTriggered( ent ) ); } team_t G_LandmineTeam( gentity_t* ent ) { return ( ent->s.teamNum % 4 ); } qboolean G_LandmineSpotted( gentity_t* ent ) { return ent->s.modelindex2 ? qtrue : qfalse; } void trap_EngineerTrace( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask ) { G_TempTraceIgnorePlayersAndBodies(); trap_Trace( results, start, mins, maxs, end, passEntityNum, contentmask ); G_ResetTempTraceIgnoreEnts(); } // DHM - Nerve void Weapon_Engineer( gentity_t *ent ) { trace_t tr; gentity_t *traceEnt, *hit; vec3_t mins, maxs; // JPW NERVE int i, num, touch[MAX_GENTITIES], scored = 0; // JPW NERVE int dynamiteDropTeam; vec3_t end; vec3_t origin; // DHM - Nerve :: Can't heal an MG42 if you're using one! if ( ent->client->ps.persistant[PERS_HWEAPON_USE] ) { return; } if ( ent->client->touchingTOI ) { if ( TryConstructing( ent ) ) { return; } } AngleVectors( ent->client->ps.viewangles, forward, right, up ); VectorCopy( ent->client->ps.origin, muzzleTrace ); muzzleTrace[2] += ent->client->ps.viewheight; VectorMA( muzzleTrace, 64, forward, end ); // CH_BREAKABLE_DIST trap_EngineerTrace( &tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT | CONTENTS_TRIGGER ); if ( tr.surfaceFlags & SURF_NOIMPACT ) { return; } // no contact if ( tr.fraction == 1.0f ) { return; } if ( tr.entityNum == ENTITYNUM_NONE || tr.entityNum == ENTITYNUM_WORLD ) { return; } traceEnt = &g_entities[ tr.entityNum ]; if ( G_EmplacedGunIsRepairable( traceEnt, ent ) ) { // "Ammo" for this weapon is time based if ( ent->client->ps.classWeaponTime + level.engineerChargeTime[ent->client->sess.sessionTeam - 1] < level.time ) { ent->client->ps.classWeaponTime = level.time - level.engineerChargeTime[ent->client->sess.sessionTeam - 1]; } if ( ent->client->sess.skill[SK_EXPLOSIVES_AND_CONSTRUCTION] >= 3 ) { ent->client->ps.classWeaponTime += .66f * 150; } else { ent->client->ps.classWeaponTime += 150; } if ( ent->client->ps.classWeaponTime > level.time ) { ent->client->ps.classWeaponTime = level.time; return; // Out of "ammo" } if ( traceEnt->health >= 255 ) { traceEnt->s.frame = 0; if ( traceEnt->mg42BaseEnt > 0 ) { g_entities[ traceEnt->mg42BaseEnt ].health = MG42_MULTIPLAYER_HEALTH; g_entities[ traceEnt->mg42BaseEnt ].takedamage = qtrue; traceEnt->health = 0; } else { traceEnt->health = MG42_MULTIPLAYER_HEALTH; } G_LogPrintf( "Repair: %d\n", ent - g_entities ); // OSP if ( traceEnt->sound3to2 != ent->client->sess.sessionTeam ) { AddScore( ent, WOLF_REPAIR_BONUS ); // JPW NERVE props to the E for the fixin' G_AddSkillPoints( ent, SK_EXPLOSIVES_AND_CONSTRUCTION, 3.f ); G_DebugAddSkillPoints( ent, SK_EXPLOSIVES_AND_CONSTRUCTION, 3.f, "repairing a MG42" ); } traceEnt->takedamage = qtrue; traceEnt->s.eFlags &= ~EF_SMOKING; trap_SendServerCommand( ent - g_entities, "cp \"You have repaired the MG!\n\"" ); G_AddEvent( ent, EV_MG42_FIXED, 0 ); } else { traceEnt->health += 3; } } else { trap_EngineerTrace( &tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT ); if ( tr.surfaceFlags & SURF_NOIMPACT ) { return; } if ( tr.fraction == 1.0f ) { return; } if ( tr.entityNum == ENTITYNUM_NONE || tr.entityNum == ENTITYNUM_WORLD ) { return; } traceEnt = &g_entities[ tr.entityNum ]; if ( traceEnt->methodOfDeath == MOD_LANDMINE ) { trace_t tr2; vec3_t base; vec3_t tr_down = {0, 0, 16}; VectorSubtract( traceEnt->s.pos.trBase, tr_down, base ); trap_EngineerTrace( &tr2, traceEnt->s.pos.trBase, NULL, NULL, base, traceEnt->s.number, MASK_SHOT ); // ydnar: added "surfaceparm landmine" (SURF_LANDMINE) support //% if(!(tr2.surfaceFlags & (SURF_GRASS | SURF_SNOW | SURF_GRAVEL)) || if ( !( tr2.surfaceFlags & SURF_LANDMINE ) || ( tr2.entityNum != ENTITYNUM_WORLD && ( !g_entities[tr2.entityNum].inuse || g_entities[tr2.entityNum].s.eType != ET_CONSTRUCTIBLE ) ) ) { trap_SendServerCommand( ent - g_entities, "cp \"Landmine cannot be armed here...\" 1" ); G_FreeEntity( traceEnt ); Add_Ammo( ent, WP_LANDMINE, 1, qfalse ); // rain - #202 - give back the correct charge amount if ( ent->client->sess.skill[SK_EXPLOSIVES_AND_CONSTRUCTION] >= 3 ) { ent->client->ps.classWeaponTime -= .33f * level.engineerChargeTime[ent->client->sess.sessionTeam - 1]; } else { ent->client->ps.classWeaponTime -= .5f * level.engineerChargeTime[ent->client->sess.sessionTeam - 1]; } ent->client->sess.aWeaponStats[WS_LANDMINE].atts--; return; //bani // rain - #384 - check landmine team so that enemy mines can be disarmed // even if you're using all of yours :x } else if ( G_CountTeamLandmines( ent->client->sess.sessionTeam ) >= MAX_TEAM_LANDMINES && G_LandmineTeam( traceEnt ) == ent->client->sess.sessionTeam ) { if ( G_LandmineUnarmed( traceEnt ) ) { // rain - should be impossible now // if ( G_LandmineTeam( traceEnt ) != ent->client->sess.sessionTeam ) // return; trap_SendServerCommand( ent - g_entities, "cp \"Your team has too many landmines placed...\" 1" ); G_FreeEntity( traceEnt ); Add_Ammo( ent, WP_LANDMINE, 1, qfalse ); // rain - #202 - give back the correct charge amount if ( ent->client->sess.skill[SK_EXPLOSIVES_AND_CONSTRUCTION] >= 3 ) { ent->client->ps.classWeaponTime -= .33f * level.engineerChargeTime[ent->client->sess.sessionTeam - 1]; } else { ent->client->ps.classWeaponTime -= .5f * level.engineerChargeTime[ent->client->sess.sessionTeam - 1]; } ent->client->sess.aWeaponStats[WS_LANDMINE].atts--; return; } //bani - #471 else { goto evilbanigoto; } } else { if ( G_LandmineUnarmed( traceEnt ) ) { // Opposing team cannot accidentally arm it if ( G_LandmineTeam( traceEnt ) != ent->client->sess.sessionTeam ) { return; } G_PrintClientSpammyCenterPrint( ent - g_entities, "Arming landmine..." ); // Give health until it is full, don't continue if ( ent->client->sess.skill[SK_EXPLOSIVES_AND_CONSTRUCTION] >= 2 ) { traceEnt->health += 24; } else { traceEnt->health += 12; } if ( traceEnt->health >= 250 ) { //traceEnt->health = 255; trap_SendServerCommand( ent - g_entities, "cp \"Landmine armed...\" 1" ); } else { return; } traceEnt->r.contents = 0; // (player can walk through) trap_LinkEntity( traceEnt ); // Don't allow disarming for sec (so guy that WAS arming doesn't start disarming it! traceEnt->timestamp = level.time + 1000; traceEnt->health = 0; traceEnt->s.teamNum = ent->client->sess.sessionTeam; traceEnt->s.modelindex2 = 0; traceEnt->nextthink = level.time + 2000; traceEnt->think = G_LandminePrime; } else { //bani - #471 evilbanigoto: if ( traceEnt->timestamp > level.time ) { return; } if ( traceEnt->health >= 250 ) { // have to do this so we don't score multiple times return; } if ( ent->client->sess.skill[SK_EXPLOSIVES_AND_CONSTRUCTION] >= 2 ) { traceEnt->health += 6; } else { traceEnt->health += 3; } G_PrintClientSpammyCenterPrint( ent - g_entities, "Defusing landmine" ); if ( traceEnt->health >= 250 ) { /* traceEnt->health = 255; traceEnt->think = G_FreeEntity; traceEnt->nextthink = level.time + FRAMETIME;*/ trap_SendServerCommand( ent - g_entities, "cp \"Landmine defused...\" 1" ); Add_Ammo( ent, WP_LANDMINE, 1, qfalse ); if ( G_LandmineTeam( traceEnt ) != ent->client->sess.sessionTeam ) { G_AddSkillPoints( ent, SK_EXPLOSIVES_AND_CONSTRUCTION, 4.f ); G_DebugAddSkillPoints( ent, SK_EXPLOSIVES_AND_CONSTRUCTION, 4.f, "defusing an enemy landmine" ); } // update our map /*{ // if it's an enemy mine, update both teamlists int teamNum; mapEntityData_t *mEnt; mapEntityData_Team_t *teamList; teamNum = traceEnt->s.teamNum % 4; teamList = ent->client->sess.sessionTeam == TEAM_AXIS ? &mapEntityData[0] : &mapEntityData[1]; if((mEnt = G_FindMapEntityData(teamList, traceEnt-g_entities)) != NULL) { G_FreeMapEntityData( teamList, mEnt ); } teamList = ent->client->sess.sessionTeam == TEAM_AXIS ? &mapEntityData[1] : &mapEntityData[0]; // inverted if((mEnt = G_FindMapEntityData(teamList, traceEnt-g_entities)) != NULL) { if( teamNum != ent->client->sess.sessionTeam ) { G_FreeMapEntityData( teamList, mEnt ); } else { // mEnt->type = ME_LANDMINE; // set it back to this as it might have been set to 'about to explode'. mEnt->entNum = -1; } } }*/ { mapEntityData_t *mEnt; if ( ( mEnt = G_FindMapEntityData( &mapEntityData[0], traceEnt - g_entities ) ) != NULL ) { G_FreeMapEntityData( &mapEntityData[0], mEnt ); } if ( ( mEnt = G_FindMapEntityData( &mapEntityData[1], traceEnt - g_entities ) ) != NULL ) { G_FreeMapEntityData( &mapEntityData[1], mEnt ); } G_FreeEntity( traceEnt ); } } else { return; } } } } else if ( traceEnt->methodOfDeath == MOD_SATCHEL ) { if ( traceEnt->health >= 250 ) { // have to do this so we don't score multiple times return; } // Give health until it is full, don't continue traceEnt->health += 3; G_PrintClientSpammyCenterPrint( ent - g_entities, "Disarming satchel charge..." ); if ( traceEnt->health >= 250 ) { traceEnt->health = 255; traceEnt->think = G_FreeEntity; traceEnt->nextthink = level.time + FRAMETIME; //bani - consistency with dynamite defusing G_PrintClientSpammyCenterPrint( ent - g_entities, "Satchel charge disarmed..." ); G_AddSkillPoints( ent, SK_EXPLOSIVES_AND_CONSTRUCTION, 6.f ); G_DebugAddSkillPoints( ent, SK_EXPLOSIVES_AND_CONSTRUCTION, 6.f, "disarming satchel charge" ); } else { return; } //bani - no tripmine... #if 0 } else if ( traceEnt->methodOfDeath == MOD_TRIPMINE ) { // Give health until it is full, don't continue traceEnt->health += 3; G_PrintClientSpammyCenterPrint( ent - g_entities, "Disarming tripmine..." ); if ( traceEnt->health >= 250 ) { traceEnt->health = 255; traceEnt->think = G_FreeEntity; traceEnt->nextthink = level.time + FRAMETIME; Add_Ammo( ent, WP_TRIPMINE, 1, qfalse ); } else { return; } #endif } else if ( traceEnt->methodOfDeath == MOD_DYNAMITE ) { // Not armed if ( traceEnt->s.teamNum >= 4 ) { //bani qboolean friendlyObj = qfalse; qboolean enemyObj = qfalse; // Opposing team cannot accidentally arm it if ( ( traceEnt->s.teamNum - 4 ) != ent->client->sess.sessionTeam ) { return; } G_PrintClientSpammyCenterPrint( ent - g_entities, "Arming dynamite..." ); // Give health until it is full, don't continue if ( ent->client->sess.skill[SK_EXPLOSIVES_AND_CONSTRUCTION] >= 2 ) { traceEnt->health += 14; } else { traceEnt->health += 7; } { int entityList[MAX_GENTITIES]; int numListedEntities; int e; vec3_t org; VectorCopy( traceEnt->r.currentOrigin, org ); org[2] += 4; // move out of ground G_TempTraceIgnorePlayersAndBodies(); numListedEntities = EntsThatRadiusCanDamage( org, traceEnt->splashRadius, entityList ); G_ResetTempTraceIgnoreEnts(); for ( e = 0; e < numListedEntities; e++ ) { hit = &g_entities[entityList[ e ]]; if ( hit->s.eType != ET_CONSTRUCTIBLE ) { continue; } // invulnerable if ( hit->spawnflags & CONSTRUCTIBLE_INVULNERABLE || ( hit->parent && hit->parent->spawnflags & 8 ) ) { continue; } if ( !G_ConstructionIsPartlyBuilt( hit ) ) { continue; } // is it a friendly constructible if ( hit->s.teamNum == traceEnt->s.teamNum - 4 ) { //bani // G_FreeEntity( traceEnt ); // trap_SendServerCommand( ent-g_entities, "cp \"You cannot arm dynamite near a friendly construction!\" 1"); // return; friendlyObj = qtrue; } } } VectorCopy( traceEnt->r.currentOrigin, origin ); SnapVector( origin ); VectorAdd( origin, traceEnt->r.mins, mins ); VectorAdd( origin, traceEnt->r.maxs, maxs ); num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); VectorAdd( origin, traceEnt->r.mins, mins ); VectorAdd( origin, traceEnt->r.maxs, maxs ); for ( i = 0 ; i < num ; i++ ) { hit = &g_entities[touch[i]]; if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) { continue; } if ( ( hit->s.eType == ET_OID_TRIGGER ) ) { if ( !( hit->spawnflags & ( AXIS_OBJECTIVE | ALLIED_OBJECTIVE ) ) ) { continue; } // Arnout - only if it targets a func_explosive if ( hit->target_ent && Q_stricmp( hit->target_ent->classname, "func_explosive" ) ) { continue; } if ( ( ( hit->spawnflags & AXIS_OBJECTIVE ) && ( ent->client->sess.sessionTeam == TEAM_AXIS ) ) || ( ( hit->spawnflags & ALLIED_OBJECTIVE ) && ( ent->client->sess.sessionTeam == TEAM_ALLIES ) ) ) { //bani // G_FreeEntity( traceEnt ); // trap_SendServerCommand( ent-g_entities, "cp \"You cannot arm dynamite near a friendly objective!\" 1"); // return; friendlyObj = qtrue; } //bani if ( ( ( hit->spawnflags & AXIS_OBJECTIVE ) && ( ent->client->sess.sessionTeam == TEAM_ALLIES ) ) || ( ( hit->spawnflags & ALLIED_OBJECTIVE ) && ( ent->client->sess.sessionTeam == TEAM_AXIS ) ) ) { enemyObj = qtrue; } } } //bani if ( friendlyObj && !enemyObj ) { G_FreeEntity( traceEnt ); trap_SendServerCommand( ent - g_entities, "cp \"You cannot arm dynamite near a friendly objective!\" 1" ); return; } if ( traceEnt->health >= 250 ) { traceEnt->health = 255; } else { return; } // Don't allow disarming for sec (so guy that WAS arming doesn't start disarming it! traceEnt->timestamp = level.time + 1000; traceEnt->health = 5; // set teamnum so we can check it for drop/defuse exploit traceEnt->s.teamNum = ent->client->sess.sessionTeam; // For dynamic light pulsing traceEnt->s.effect1Time = level.time; // ARM IT! traceEnt->nextthink = level.time + 30000; traceEnt->think = G_ExplodeMissile; // Gordon: moved down here to prevent two prints when dynamite IS near objective trap_SendServerCommand( ent - g_entities, "cp \"Dynamite is now armed with a 30 second timer!\" 1" ); // check if player is in trigger objective field // NERVE - SMF - made this the actual bounding box of dynamite instead of range, also must snap origin to line up properly VectorCopy( traceEnt->r.currentOrigin, origin ); SnapVector( origin ); VectorAdd( origin, traceEnt->r.mins, mins ); VectorAdd( origin, traceEnt->r.maxs, maxs ); num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); for ( i = 0 ; i < num ; i++ ) { hit = &g_entities[touch[i]]; if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) { continue; } if ( ( hit->s.eType == ET_OID_TRIGGER ) ) { if ( !( hit->spawnflags & ( AXIS_OBJECTIVE | ALLIED_OBJECTIVE ) ) ) { continue; } // Arnout - only if it targets a func_explosive if ( hit->target_ent && Q_stricmp( hit->target_ent->classname, "func_explosive" ) ) { continue; } if ( hit->spawnflags & AXIS_OBJECTIVE ) { if ( ent->client->sess.sessionTeam == TEAM_ALLIES ) { // transfer score info if this is a bomb scoring objective traceEnt->accuracy = hit->accuracy; } } else if ( hit->spawnflags & ALLIED_OBJECTIVE ) { if ( ent->client->sess.sessionTeam == TEAM_AXIS ) { // ditto other team traceEnt->accuracy = hit->accuracy; } } // rain - spawnflags 128 = disabled (#309) if ( !( hit->spawnflags & 128 ) && ( ( ( hit->spawnflags & AXIS_OBJECTIVE ) && ( ent->client->sess.sessionTeam == TEAM_ALLIES ) ) || ( ( hit->spawnflags & ALLIED_OBJECTIVE ) && ( ent->client->sess.sessionTeam == TEAM_AXIS ) ) ) ) { gentity_t* pm = G_PopupMessage( PM_DYNAMITE ); pm->s.effect2Time = 0; pm->s.effect3Time = hit->s.teamNum; pm->s.teamNum = ent->client->sess.sessionTeam; G_Script_ScriptEvent( hit, "dynamited", "" ); if ( !( hit->spawnflags & OBJECTIVE_DESTROYED ) ) { AddScore( traceEnt->parent, WOLF_DYNAMITE_PLANT ); // give drop score to guy who dropped it if ( traceEnt->parent && traceEnt->parent->client ) { G_LogPrintf( "Dynamite_Plant: %d\n", traceEnt->parent - g_entities ); // OSP } traceEnt->parent = ent; // give explode score to guy who armed it } //bani - fix #238 traceEnt->etpro_misc_1 |= 1; } //bani // i = num; return; //bani - bail out here because primary obj's take precendence over constructibles } } //bani - reordered this check so its AFTER the primary obj check // Arnout - first see if the dynamite is planted near a constructable object that can be destroyed { int entityList[MAX_GENTITIES]; int numListedEntities; int e; vec3_t org; VectorCopy( traceEnt->r.currentOrigin, org ); org[2] += 4; // move out of ground G_TempTraceIgnorePlayersAndBodies(); numListedEntities = EntsThatRadiusCanDamage( org, traceEnt->splashRadius, entityList ); G_ResetTempTraceIgnoreEnts(); for ( e = 0; e < numListedEntities; e++ ) { hit = &g_entities[entityList[ e ]]; if ( hit->s.eType != ET_CONSTRUCTIBLE ) { continue; } // invulnerable if ( hit->spawnflags & CONSTRUCTIBLE_INVULNERABLE ) { continue; } if ( !G_ConstructionIsPartlyBuilt( hit ) ) { continue; } // is it a friendly constructible if ( hit->s.teamNum == traceEnt->s.teamNum ) { //bani - er, didnt we just pass this check earlier? // G_FreeEntity( traceEnt ); // trap_SendServerCommand( ent-g_entities, "cp \"You cannot arm dynamite near a friendly construction!\" 1"); // return; continue; } // not dynamite-able if ( hit->constructibleStats.weaponclass < 1 ) { continue; } if ( hit->parent ) { gentity_t* pm = G_PopupMessage( PM_DYNAMITE ); pm->s.effect2Time = 0; // 0 = planted pm->s.effect3Time = hit->parent->s.teamNum; pm->s.teamNum = ent->client->sess.sessionTeam; G_Script_ScriptEvent( hit, "dynamited", "" ); if ( ( !( hit->parent->spawnflags & OBJECTIVE_DESTROYED ) ) && hit->s.teamNum && ( hit->s.teamNum == ent->client->sess.sessionTeam ) ) { // ==, as it's inverse AddScore( traceEnt->parent, WOLF_DYNAMITE_PLANT ); // give drop score to guy who dropped it if ( traceEnt->parent && traceEnt->parent->client ) { G_LogPrintf( "Dynamite_Plant: %d\n", traceEnt->parent - g_entities ); // OSP } traceEnt->parent = ent; // give explode score to guy who armed it } //bani - fix #238 traceEnt->etpro_misc_1 |= 1; } return; } } } else { if ( traceEnt->timestamp > level.time ) { return; } if ( traceEnt->health >= 248 ) { // have to do this so we don't score multiple times return; } dynamiteDropTeam = traceEnt->s.teamNum; // set this here since we wack traceent later but want teamnum for scoring if ( ent->client->sess.skill[SK_EXPLOSIVES_AND_CONSTRUCTION] >= 2 ) { traceEnt->health += 6; } else { traceEnt->health += 3; } G_PrintClientSpammyCenterPrint( ent - g_entities, "Defusing dynamite..." ); if ( traceEnt->health >= 248 ) { //bani qboolean defusedObj = qfalse; traceEnt->health = 255; // Need some kind of event/announcement here // Add_Ammo( ent, WP_DYNAMITE, 1, qtrue ); traceEnt->think = G_FreeEntity; traceEnt->nextthink = level.time + FRAMETIME; VectorCopy( traceEnt->r.currentOrigin, origin ); SnapVector( origin ); VectorAdd( origin, traceEnt->r.mins, mins ); VectorAdd( origin, traceEnt->r.maxs, maxs ); num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); // don't report if not disarming *enemy* dynamite in field /* if (dynamiteDropTeam == ent->client->sess.sessionTeam) return;*/ //bani - eh, why was this commented out? it makes sense, and prevents a sploit. if ( dynamiteDropTeam == ent->client->sess.sessionTeam ) { return; } for ( i = 0 ; i < num ; i++ ) { hit = &g_entities[touch[i]]; if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) { continue; } if ( ( hit->s.eType == ET_OID_TRIGGER ) ) { if ( !( hit->spawnflags & ( AXIS_OBJECTIVE | ALLIED_OBJECTIVE ) ) ) { continue; } // rain - spawnflags 128 = disabled (#309) if ( hit->spawnflags & 128 ) { continue; } //bani - prevent plant/defuse exploit near a/h cabinets or non-destroyable locations (bank doors on goldrush) if ( !hit->target_ent || hit->target_ent->s.eType != ET_EXPLOSIVE ) { continue; } if ( ent->client->sess.sessionTeam == TEAM_AXIS ) { if ( ( hit->spawnflags & AXIS_OBJECTIVE ) && ( !scored ) ) { AddScore( ent,WOLF_DYNAMITE_DIFFUSE ); G_AddSkillPoints( ent, SK_EXPLOSIVES_AND_CONSTRUCTION, 6.f ); G_DebugAddSkillPoints( ent, SK_EXPLOSIVES_AND_CONSTRUCTION, 6.f, "defusing enemy dynamite" ); scored++; } if ( hit->target_ent ) { G_Script_ScriptEvent( hit->target_ent, "defused", "" ); } { gentity_t* pm = G_PopupMessage( PM_DYNAMITE ); pm->s.effect2Time = 1; // 1 = defused pm->s.effect3Time = hit->s.teamNum; pm->s.teamNum = ent->client->sess.sessionTeam; } // trap_SendServerCommand(-1, "cp \"Axis engineer disarmed the Dynamite!\n\""); //bani defusedObj = qtrue; } else { // TEAM_ALLIES if ( ( hit->spawnflags & ALLIED_OBJECTIVE ) && ( !scored ) ) { AddScore( ent,WOLF_DYNAMITE_DIFFUSE ); G_AddSkillPoints( ent, SK_EXPLOSIVES_AND_CONSTRUCTION, 6.f ); G_DebugAddSkillPoints( ent, SK_EXPLOSIVES_AND_CONSTRUCTION, 6.f, "defusing enemy dynamite" ); scored++; hit->spawnflags &= ~OBJECTIVE_DESTROYED; // "re-activate" objective since it wasn't destroyed } if ( hit->target_ent ) { G_Script_ScriptEvent( hit->target_ent, "defused", "" ); } { gentity_t* pm = G_PopupMessage( PM_DYNAMITE ); pm->s.effect2Time = 1; // 1 = defused pm->s.effect3Time = hit->s.teamNum; pm->s.teamNum = ent->client->sess.sessionTeam; } // trap_SendServerCommand(-1, "cp \"Allied engineer disarmed the Dynamite!\n\""); //bani defusedObj = qtrue; } } } //bani - prevent multiple messages here if ( defusedObj ) { return; } //bani - reordered this check so its AFTER the primary obj check // Gordon - first see if the dynamite was planted near a constructable object that would have been destroyed { int entityList[MAX_GENTITIES]; int numListedEntities; int e; vec3_t org; VectorCopy( traceEnt->r.currentOrigin, org ); org[2] += 4; // move out of ground numListedEntities = EntsThatRadiusCanDamage( org, traceEnt->splashRadius, entityList ); for ( e = 0; e < numListedEntities; e++ ) { hit = &g_entities[entityList[ e ]]; if ( hit->s.eType != ET_CONSTRUCTIBLE ) { continue; } // not completely build yet - NOTE: don't do this, in case someone places dynamite before construction is complete //if( hit->s.angles2[0] < 255 ) // continue; // invulnerable if ( hit->spawnflags & CONSTRUCTIBLE_INVULNERABLE ) { continue; } // not dynamite-able if ( hit->constructibleStats.weaponclass < 1 ) { continue; } // we got somthing to destroy if ( ent->client->sess.sessionTeam == TEAM_AXIS ) { if ( hit->s.teamNum == TEAM_AXIS && ( !scored ) ) { AddScore( ent,WOLF_DYNAMITE_DIFFUSE ); if ( ent && ent->client ) { G_LogPrintf( "Dynamite_Diffuse: %d\n", ent - g_entities ); // OSP } G_AddSkillPoints( ent, SK_EXPLOSIVES_AND_CONSTRUCTION, 6.f ); G_DebugAddSkillPoints( ent, SK_EXPLOSIVES_AND_CONSTRUCTION, 6.f, "defusing enemy dynamite" ); scored++; } G_Script_ScriptEvent( hit, "defused", "" ); { gentity_t* pm = G_PopupMessage( PM_DYNAMITE ); pm->s.effect2Time = 1; // 1 = defused pm->s.effect3Time = hit->parent->s.teamNum; pm->s.teamNum = ent->client->sess.sessionTeam; } // trap_SendServerCommand(-1, "cp \"Axis engineer disarmed the Dynamite!\" 2"); } else { // TEAM_ALLIES if ( hit->s.teamNum == TEAM_ALLIES && ( !scored ) ) { AddScore( ent,WOLF_DYNAMITE_DIFFUSE ); if ( ent && ent->client ) { G_LogPrintf( "Dynamite_Diffuse: %d\n", ent - g_entities ); // OSP } G_AddSkillPoints( ent, SK_EXPLOSIVES_AND_CONSTRUCTION, 6.f ); G_DebugAddSkillPoints( ent, SK_EXPLOSIVES_AND_CONSTRUCTION, 6.f, "defusing enemy dynamite" ); scored++; } G_Script_ScriptEvent( hit, "defused", "" ); { gentity_t* pm = G_PopupMessage( PM_DYNAMITE ); pm->s.effect2Time = 1; // 1 = defused pm->s.effect3Time = hit->parent->s.teamNum; pm->s.teamNum = ent->client->sess.sessionTeam; } // trap_SendServerCommand(-1, "cp \"Allied engineer disarmed the Dynamite!\" 2"); } return; } } } // jpw } } } } // JPW NERVE -- launch airstrike as line of bombs mostly-perpendicular to line of grenade travel // (close air support should *always* drop parallel to friendly lines, tho accidents do happen) extern void G_ExplodeMissile( gentity_t *ent ); void G_AirStrikeExplode( gentity_t *self ) { self->r.svFlags &= ~SVF_NOCLIENT; self->r.svFlags |= SVF_BROADCAST; self->think = G_ExplodeMissile; self->nextthink = level.time + 50; } qboolean G_AvailableAirstrikes( gentity_t* ent ) { if ( ent->client->sess.sessionTeam == TEAM_AXIS ) { if ( level.axisBombCounter >= 60 * 1000 ) { return qfalse; } } else { if ( level.alliedBombCounter >= 60 * 1000 ) { return qfalse; } } return qtrue; } void G_AddAirstrikeToCounters( gentity_t* ent ) { int max = min( 6, 2 * ( ceil( g_heavyWeaponRestriction.integer * G_TeamCount( ent, -1 ) * 0.1f * 10 * 0.01f ) ) ); if ( ent->client->sess.sessionTeam == TEAM_AXIS ) { level.axisBombCounter += ( ( 60 * 1000 ) / (float)max ); } else { level.alliedBombCounter += ( ( 60 * 1000 ) / (float)max ); } } #define NUMBOMBS 10 #define BOMBSPREAD 150 extern void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message, qboolean localize ); void weapon_checkAirStrikeThink1( gentity_t *ent ) { if ( !weapon_checkAirStrike( ent ) ) { ent->think = G_ExplodeMissile; ent->nextthink = level.time + 1000; return; } ent->think = weapon_callAirStrike; ent->nextthink = level.time + 1500; } void weapon_checkAirStrikeThink2( gentity_t *ent ) { if ( !weapon_checkAirStrike( ent ) ) { ent->think = G_ExplodeMissile; ent->nextthink = level.time + 1000; return; } ent->think = weapon_callSecondPlane; ent->nextthink = level.time + 500; } void weapon_callSecondPlane( gentity_t *ent ) { gentity_t* te; te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND ); te->s.eventParm = G_SoundIndex( "sound/weapons/airstrike/airstrike_plane.wav" ); te->r.svFlags |= SVF_BROADCAST; ent->nextthink = level.time + 1000; ent->think = weapon_callAirStrike; } qboolean weapon_checkAirStrike( gentity_t *ent ) { if ( ent->s.teamNum == TEAM_AXIS ) { level.numActiveAirstrikes[0]++; } else { level.numActiveAirstrikes[1]++; } // cancel the airstrike if FF off and player joined spec // FIXME: this is a stupid workaround. Just store the parent team in the enitity itself and use that - no need to look up the parent if ( !g_friendlyFire.integer && ent->parent->client && ent->parent->client->sess.sessionTeam == TEAM_SPECTATOR ) { ent->splashDamage = 0; // no damage ent->think = G_ExplodeMissile; ent->nextthink = level.time + crandom() * 50; ent->active = qfalse; if ( ent->s.teamNum == TEAM_AXIS ) { level.numActiveAirstrikes[0]--; } else { level.numActiveAirstrikes[1]--; } return qfalse; // do nothing, don't hurt anyone } if ( ent->s.teamNum == TEAM_AXIS ) { if ( level.numActiveAirstrikes[0] > 6 || !G_AvailableAirstrikes( ent->parent ) ) { G_SayTo( ent->parent, ent->parent, 2, COLOR_YELLOW, "HQ: ", "All available planes are already en-route.", qtrue ); G_GlobalClientEvent( EV_AIRSTRIKEMESSAGE, 0, ent->parent - g_entities ); /* te = G_TempEntity( ent->parent->s.pos.trBase, EV_GLOBAL_CLIENT_SOUND ); te->s.eventParm = G_SoundIndex( "axis_hq_airstrike_denied" ); te->s.teamNum = ent->parent->s.clientNum;*/ // te->s.effect1Time = 1; // don't buffer ent->active = qfalse; if ( ent->s.teamNum == TEAM_AXIS ) { level.numActiveAirstrikes[0]--; } else { level.numActiveAirstrikes[1]--; } return qfalse; } } else { if ( level.numActiveAirstrikes[1] > 6 || !G_AvailableAirstrikes( ent->parent ) ) { G_SayTo( ent->parent, ent->parent, 2, COLOR_YELLOW, "HQ: ", "All available planes are already en-route.", qtrue ); G_GlobalClientEvent( EV_AIRSTRIKEMESSAGE, 0, ent->parent - g_entities ); /* te = G_TempEntity( ent->parent->s.pos.trBase, EV_GLOBAL_CLIENT_SOUND ); te->s.eventParm = G_SoundIndex( "allies_hq_airstrike_denied" ); te->s.teamNum = ent->parent->s.clientNum;*/ // te->s.effect1Time = 1; // don't buffer ent->active = qfalse; if ( ent->s.teamNum == TEAM_AXIS ) { level.numActiveAirstrikes[0]--; } else { level.numActiveAirstrikes[1]--; } return qfalse; } } return qtrue; } void G_RailTrail( vec_t* start, vec_t* end ); void weapon_callAirStrike( gentity_t *ent ) { int i, j; vec3_t bombaxis, lookaxis, pos, bomboffset, fallaxis, temp, dir, skypoint; gentity_t *bomb; trace_t tr; float traceheight, bottomtraceheight; VectorCopy( ent->s.pos.trBase,bomboffset ); bomboffset[2] += 4096.f; // turn off smoke grenade ent->think = G_ExplodeMissile; ent->nextthink = level.time + 950 + NUMBOMBS * 100 + crandom() * 50; // 950 offset is for aircraft flyby ent->active = qtrue; G_AddAirstrikeToCounters( ent->parent ); { gentity_t* te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND ); te->s.eventParm = G_SoundIndex( "sound/weapons/airstrike/airstrike_plane.wav" ); te->r.svFlags |= SVF_BROADCAST; } trap_Trace( &tr, ent->s.pos.trBase, NULL, NULL, bomboffset, ent->s.number, MASK_SHOT ); if ( ( tr.fraction < 1.0 ) && ( !( tr.surfaceFlags & SURF_NOIMPACT ) ) ) { //SURF_SKY)) ) { // JPW NERVE changed for trenchtoast foggie prollem G_SayTo( ent->parent, ent->parent, 2, COLOR_YELLOW, "Pilot: ", "Aborting, can't see target.", qtrue ); G_GlobalClientEvent( EV_AIRSTRIKEMESSAGE, 1, ent->parent - g_entities ); /* te = G_TempEntity( ent->parent->s.pos.trBase, EV_GLOBAL_CLIENT_SOUND ); if ( ent->s.teamNum == TEAM_ALLIES ) { te->s.eventParm = G_SoundIndex( "allies_hq_airstrike_abort" ); } else { te->s.eventParm = G_SoundIndex( "axis_hq_airstrike_abort" ); } te->s.teamNum = ent->parent->s.clientNum;*/ // te->s.effect1Time = 1; // don't buffer if ( ent->s.teamNum == TEAM_AXIS ) { level.numActiveAirstrikes[0]--; } else { level.numActiveAirstrikes[1]--; } ent->active = qfalse; return; } G_GlobalClientEvent( EV_AIRSTRIKEMESSAGE, 2, ent->parent - g_entities ); /* te = G_TempEntity( ent->parent->s.pos.trBase, EV_GLOBAL_CLIENT_SOUND ); if ( ent->parent->client->sess.sessionTeam == TEAM_ALLIES ) { te->s.eventParm = G_SoundIndex( "allies_hq_airstrike" ); } else { te->s.eventParm = G_SoundIndex( "axis_hq_airstrike" ); } te->s.teamNum = ent->parent->s.clientNum;*/ // te->s.effect1Time = 1; // don't buffer VectorCopy( tr.endpos, bomboffset ); VectorCopy( tr.endpos, skypoint ); traceheight = bomboffset[2]; bottomtraceheight = traceheight - 8192.f; VectorSubtract( ent->s.pos.trBase, ent->parent->client->ps.origin, lookaxis ); lookaxis[2] = 0; VectorNormalize( lookaxis ); dir[0] = 0; dir[1] = 0; dir[2] = crandom(); // generate either up or down vector VectorNormalize( dir ); // which adds randomness to pass direction below for ( j = 0; j < ent->count; j++ ) { RotatePointAroundVector( bombaxis, dir, lookaxis, 90 + crandom() * 30 ); // munge the axis line a bit so it's not totally perpendicular VectorNormalize( bombaxis ); VectorCopy( bombaxis, pos ); VectorScale( pos,(float)( -.5f * BOMBSPREAD * NUMBOMBS ), pos ); VectorAdd( ent->s.pos.trBase, pos, pos ); // first bomb position VectorScale( bombaxis, BOMBSPREAD, bombaxis ); // bomb drop direction offset for ( i = 0; i < NUMBOMBS; i++ ) { bomb = G_Spawn(); bomb->nextthink = level.time + i * 100 + crandom() * 50 + 1000 + ( j * 2000 ); // 1000 for aircraft flyby, other term for tumble stagger bomb->think = G_AirStrikeExplode; bomb->s.eType = ET_MISSILE; bomb->r.svFlags = SVF_NOCLIENT; bomb->s.weapon = WP_SMOKE_MARKER; // might wanna change this bomb->r.ownerNum = ent->s.number; bomb->parent = ent->parent; bomb->s.teamNum = ent->s.teamNum; bomb->damage = 400; // maybe should un-hard-code these? bomb->splashDamage = 400; // Gordon: for explosion type bomb->accuracy = 2; bomb->classname = "air strike"; bomb->splashRadius = 400; bomb->methodOfDeath = MOD_AIRSTRIKE; bomb->splashMethodOfDeath = MOD_AIRSTRIKE; bomb->clipmask = MASK_MISSILESHOT; bomb->s.pos.trType = TR_STATIONARY; // was TR_GRAVITY, might wanna go back to this and drop from height //bomb->s.pos.trTime = level.time; // move a bit on the very first frame bomboffset[0] = crandom() * .5f * BOMBSPREAD; bomboffset[1] = crandom() * .5f * BOMBSPREAD; bomboffset[2] = 0.f; VectorAdd( pos, bomboffset, bomb->s.pos.trBase ); VectorCopy( bomb->s.pos.trBase, bomboffset ); // make sure bombs fall "on top of" nonuniform scenery bomboffset[2] = traceheight; VectorCopy( bomboffset, fallaxis ); fallaxis[2] = bottomtraceheight; trap_Trace( &tr, bomboffset, NULL, NULL, fallaxis, ent - g_entities, bomb->clipmask ); if ( tr.fraction != 1.0 ) { VectorCopy( tr.endpos,bomb->s.pos.trBase ); // Snap origin! VectorMA( bomb->s.pos.trBase, 2.f, tr.plane.normal, temp ); SnapVectorTowards( bomb->s.pos.trBase, temp ); // save net bandwidth // G_RailTrail( skypoint, bomb->s.pos.trBase ); trap_TraceNoEnts( &tr, skypoint, NULL, NULL, bomb->s.pos.trBase, 0, CONTENTS_SOLID ); if ( tr.fraction < 1.f ) { G_FreeEntity( bomb ); // move pos for next bomb VectorAdd( pos, bombaxis, pos ); continue; } } VectorCopy( bomb->s.pos.trBase, bomb->r.currentOrigin ); // move pos for next bomb VectorAdd( pos, bombaxis, pos ); } } } // JPW NERVE -- sound effect for spotter round, had to do this as half-second bomb warning void artilleryThink_real( gentity_t *ent ) { ent->freeAfterEvent = qtrue; trap_LinkEntity( ent ); { int sfx = rand() % 3; switch ( sfx ) { case 0: G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( "sound/weapons/artillery/artillery_fly_1.wav" ) ); break; case 1: G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( "sound/weapons/artillery/artillery_fly_2.wav" ) ); break; case 2: G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( "sound/weapons/artillery/artillery_fly_3.wav" ) ); break; } } } void artilleryThink( gentity_t *ent ) { ent->think = artilleryThink_real; ent->nextthink = level.time + 100; ent->r.svFlags = SVF_BROADCAST; } // JPW NERVE -- makes smoke disappear after a bit (just unregisters stuff) void artilleryGoAway( gentity_t *ent ) { ent->freeAfterEvent = qtrue; trap_LinkEntity( ent ); } // JPW NERVE -- generates some smoke debris void artillerySpotterThink( gentity_t *ent ) { gentity_t *bomb; vec3_t tmpdir; int i; ent->think = G_ExplodeMissile; ent->nextthink = level.time + 1; SnapVector( ent->s.pos.trBase ); for ( i = 0; i < 7; i++ ) { bomb = G_Spawn(); bomb->s.eType = ET_MISSILE; bomb->r.svFlags = 0; bomb->r.ownerNum = ent->s.number; bomb->parent = ent; bomb->s.teamNum = ent->s.teamNum; bomb->nextthink = level.time + 1000 + random() * 300; bomb->classname = "WP"; // WP == White Phosphorous, so we can check for bounce noise in grenade bounce routine bomb->damage = 000; // maybe should un-hard-code these? bomb->splashDamage = 000; bomb->splashRadius = 000; bomb->s.weapon = WP_SMOKETRAIL; bomb->think = artilleryGoAway; bomb->s.eFlags |= EF_BOUNCE; bomb->clipmask = MASK_MISSILESHOT; bomb->s.pos.trType = TR_GRAVITY; // was TR_GRAVITY, might wanna go back to this and drop from height bomb->s.pos.trTime = level.time; // move a bit on the very first frame bomb->s.otherEntityNum2 = ent->s.otherEntityNum2; VectorCopy( ent->s.pos.trBase, bomb->s.pos.trBase ); tmpdir[0] = crandom(); tmpdir[1] = crandom(); tmpdir[2] = 1; VectorNormalize( tmpdir ); tmpdir[2] = 1; // extra up VectorScale( tmpdir, 500 + random() * 500, tmpdir ); VectorCopy( tmpdir,bomb->s.pos.trDelta ); SnapVector( bomb->s.pos.trDelta ); // save net bandwidth VectorCopy( ent->s.pos.trBase, bomb->s.pos.trBase ); VectorCopy( ent->s.pos.trBase, bomb->r.currentOrigin ); } } void G_GlobalClientEvent( int event, int param, int client ) { gentity_t* tent = G_TempEntity( vec3_origin, event ); tent->s.density = param; tent->r.singleClient = client; tent->r.svFlags = SVF_SINGLECLIENT | SVF_BROADCAST; } /* ================== Weapon_Artillery ================== */ void Weapon_Artillery( gentity_t *ent ) { trace_t trace; int i, count; vec3_t muzzlePoint, end, bomboffset, pos, fallaxis; float traceheight, bottomtraceheight; gentity_t *bomb, *bomb2; if ( ent->client->ps.stats[STAT_PLAYER_CLASS] != PC_FIELDOPS ) { G_Printf( "not a fieldops, you can't shoot this!\n" ); return; } // TAT - 10/27/2002 - moved energy check into a func, so I can use same check in bot code if ( !ReadyToCallArtillery( ent ) ) { return; } if ( ent->client->sess.sessionTeam == TEAM_AXIS ) { if ( !G_AvailableAirstrikes( ent ) ) { G_SayTo( ent, ent, 2, COLOR_YELLOW, "Fire Mission: ", "Insufficient fire support.", qtrue ); ent->active = qfalse; G_GlobalClientEvent( EV_ARTYMESSAGE, 0, ent - g_entities ); /* te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_CLIENT_SOUND ); te->s.eventParm = G_SoundIndex( "axis_hq_airstrike_denied" ); te->s.teamNum = ent-g_entities;*/ return; } } else { if ( !G_AvailableAirstrikes( ent ) ) { G_SayTo( ent, ent, 2, COLOR_YELLOW, "Fire Mission: ", "Insufficient fire support.", qtrue ); ent->active = qfalse; G_GlobalClientEvent( EV_ARTYMESSAGE, 0, ent - g_entities ); /* te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_CLIENT_SOUND ); te->s.eventParm = G_SoundIndex( "allies_hq_airstrike_denied" ); te->s.teamNum = ent-g_entities;*/ return; } } AngleVectors( ent->client->ps.viewangles, forward, right, up ); VectorCopy( ent->r.currentOrigin, muzzlePoint ); muzzlePoint[2] += ent->client->ps.viewheight; VectorMA( muzzlePoint, 8192, forward, end ); trap_Trace( &trace, muzzlePoint, NULL, NULL, end, ent->s.number, MASK_SHOT ); if ( trace.surfaceFlags & SURF_NOIMPACT ) { return; } VectorCopy( trace.endpos,pos ); VectorCopy( pos,bomboffset ); bomboffset[2] += 4096; trap_Trace( &trace, pos, NULL, NULL, bomboffset, ent->s.number, MASK_SHOT ); if ( ( trace.fraction < 1.0 ) && ( !( trace.surfaceFlags & SURF_NOIMPACT ) ) ) { // JPW NERVE was SURF_SKY)) ) { G_SayTo( ent, ent, 2, COLOR_YELLOW, "Fire Mission: ", "Aborting, can't see target.", qtrue ); G_GlobalClientEvent( EV_ARTYMESSAGE, 1, ent - g_entities ); /* te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_CLIENT_SOUND ); if ( ent->client->sess.sessionTeam == TEAM_ALLIES ) { te->s.eventParm = G_SoundIndex( "allies_hq_ffe_abort" ); } else { te->s.eventParm = G_SoundIndex( "axis_hq_ffe_abort" ); } te->s.teamNum = ent->s.clientNum;*/ // te->s.effect1Time = 1; // don't buffer return; } G_AddAirstrikeToCounters( ent ); G_SayTo( ent, ent, 2, COLOR_YELLOW, "Fire Mission: ", "Firing for effect!", qtrue ); G_GlobalClientEvent( EV_ARTYMESSAGE, 2, ent - g_entities ); /* te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_CLIENT_SOUND ); if ( ent->client->sess.sessionTeam == TEAM_ALLIES ) { te->s.eventParm = G_SoundIndex( "allies_hq_ffe" ); } else { te->s.eventParm = G_SoundIndex( "axis_hq_ffe" ); } te->s.teamNum = ent->s.clientNum;*/ // te->s.effect1Time = 1; // don't buffer VectorCopy( trace.endpos, bomboffset ); traceheight = bomboffset[2]; bottomtraceheight = traceheight - 8192; // "spotter" round (i == 0) // i == 1->4 is regular explosives if ( ent->client->sess.skill[SK_SIGNALS] >= 3 ) { count = 9; } else { count = 5; } for ( i = 0; i < count; i++ ) { bomb = G_Spawn(); bomb->think = G_AirStrikeExplode; bomb->s.eType = ET_MISSILE; bomb->r.svFlags = SVF_NOCLIENT; bomb->s.weapon = WP_ARTY; // might wanna change this bomb->r.ownerNum = ent->s.number; bomb->s.clientNum = ent->s.number; bomb->parent = ent; bomb->s.teamNum = ent->client->sess.sessionTeam; if ( i == 0 ) { bomb->nextthink = level.time + 5000; bomb->r.svFlags = SVF_BROADCAST; bomb->classname = "props_explosion"; // was "air strike" bomb->damage = 0; // maybe should un-hard-code these? bomb->splashDamage = 90; bomb->splashRadius = 50; bomb->count = 7; bomb->count2 = 1000; bomb->delay = 300; bomb->s.otherEntityNum2 = 1; // first bomb bomb->think = artillerySpotterThink; } else { if ( ent->client->sess.skill[SK_SIGNALS] >= 3 ) { bomb->nextthink = level.time + 8950 + 2000 * i + crandom() * 800; } else { bomb->nextthink = level.time + 8950 + 2000 * i + crandom() * 800; } // Gordon: for explosion type bomb->accuracy = 2; bomb->classname = "air strike"; bomb->damage = 0; bomb->splashDamage = 400; bomb->splashRadius = 400; } bomb->methodOfDeath = MOD_ARTY; bomb->splashMethodOfDeath = MOD_ARTY; bomb->clipmask = MASK_MISSILESHOT; bomb->s.pos.trType = TR_STATIONARY; // was TR_GRAVITY, might wanna go back to this and drop from height bomb->s.pos.trTime = level.time; // move a bit on the very first frame if ( i ) { // spotter round is always dead on (OK, unrealistic but more fun) bomboffset[0] = crandom() * 250; bomboffset[1] = crandom() * 250; } else { bomboffset[0] = crandom() * 50; // was 0; changed per id request to prevent spotter round assassinations bomboffset[1] = crandom() * 50; // was 0; } bomboffset[2] = 0; VectorAdd( pos,bomboffset,bomb->s.pos.trBase ); VectorCopy( bomb->s.pos.trBase,bomboffset ); // make sure bombs fall "on top of" nonuniform scenery bomboffset[2] = traceheight; VectorCopy( bomboffset, fallaxis ); fallaxis[2] = bottomtraceheight; trap_Trace( &trace, bomboffset, NULL, NULL, fallaxis, ent->s.number, MASK_SHOT ); if ( trace.fraction != 1.0 ) { VectorCopy( trace.endpos,bomb->s.pos.trBase ); } bomb->s.pos.trDelta[0] = 0; // might need to change this bomb->s.pos.trDelta[1] = 0; bomb->s.pos.trDelta[2] = 0; SnapVector( bomb->s.pos.trDelta ); // save net bandwidth VectorCopy( bomb->s.pos.trBase, bomb->r.currentOrigin ); // build arty falling sound effect in front of bomb drop bomb2 = G_Spawn(); bomb2->think = artilleryThink; bomb2->s.eType = ET_MISSILE; bomb2->r.svFlags = SVF_NOCLIENT; bomb2->r.ownerNum = ent->s.number; bomb2->parent = ent; bomb2->s.teamNum = ent->s.teamNum; bomb2->damage = 0; bomb2->nextthink = bomb->nextthink - 600; bomb2->classname = "air strike"; bomb2->clipmask = MASK_MISSILESHOT; bomb2->s.pos.trType = TR_STATIONARY; // was TR_GRAVITY, might wanna go back to this and drop from height bomb2->s.pos.trTime = level.time; // move a bit on the very first frame VectorCopy( bomb->s.pos.trBase,bomb2->s.pos.trBase ); VectorCopy( bomb->s.pos.trDelta,bomb2->s.pos.trDelta ); VectorCopy( bomb->s.pos.trBase,bomb2->r.currentOrigin ); } if ( ent->client->sess.skill[SK_SIGNALS] >= 2 ) { if ( level.time - ent->client->ps.classWeaponTime > level.lieutenantChargeTime[ent->client->sess.sessionTeam - 1] ) { ent->client->ps.classWeaponTime = level.time - level.lieutenantChargeTime[ent->client->sess.sessionTeam - 1]; } ent->client->ps.classWeaponTime += 0.66f * level.lieutenantChargeTime[ent->client->sess.sessionTeam - 1]; } else { ent->client->ps.classWeaponTime = level.time; } // OSP -- weapon stats #ifndef DEBUG_STATS if ( g_gamestate.integer == GS_PLAYING ) #endif ent->client->sess.aWeaponStats[WS_ARTILLERY].atts++; } #define SMOKEBOMB_GROWTIME 1000 #define SMOKEBOMB_SMOKETIME 15000 #define SMOKEBOMB_POSTSMOKETIME 2000 // xkan, 11/25/2002 - increases postsmoke time from 2000->32000, this way, the entity // is still around while the smoke is around, so we can check if it blocks bot's vision // Arnout: eeeeeh this is wrong. 32 seconds is way too long. Also - we shouldn't be // rendering the grenade anymore after the smoke stops and definately not send it to the client // xkan, 12/06/2002 - back to the old value 2000, now that it looks like smoke disappears more // quickly void weapon_smokeBombExplode( gentity_t *ent ) { int lived = 0; if ( !ent->grenadeExplodeTime ) { ent->grenadeExplodeTime = level.time; } lived = level.time - ent->grenadeExplodeTime; ent->nextthink = level.time + FRAMETIME; if ( lived < SMOKEBOMB_GROWTIME ) { // Just been thrown, increase radius ent->s.effect1Time = 16 + lived * ( ( 640.f - 16.f ) / (float)SMOKEBOMB_GROWTIME ); } else if ( lived < SMOKEBOMB_SMOKETIME + SMOKEBOMB_GROWTIME ) { // Smoking ent->s.effect1Time = 640; } else if ( lived < SMOKEBOMB_SMOKETIME + SMOKEBOMB_GROWTIME + SMOKEBOMB_POSTSMOKETIME ) { // Dying out ent->s.effect1Time = -1; } else { // Poof and it's gone G_FreeEntity( ent ); } } gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity, int ownerNum ); // jpw /* ====================================================================== MACHINEGUN ====================================================================== */ /* ====================== SnapVectorTowards Round a vector to integers for more efficient network transmission, but make sure that it rounds towards a given point rather than blindly truncating. This prevents it from truncating into a wall. ====================== */ // (SA) modified so it doesn't have trouble with negative locations (quadrant problems) // (this was causing some problems with bullet marks appearing since snapping // too far off the target surface causes the the distance between the transmitted impact // point and the actual hit surface larger than the mark radius. (so nothing shows) ) void SnapVectorTowards( vec3_t v, vec3_t to ) { int i; for ( i = 0 ; i < 3 ; i++ ) { if ( to[i] <= v[i] ) { // v[i] = (int)v[i]; v[i] = floor( v[i] ); } else { // v[i] = (int)v[i] + 1; v[i] = ceil( v[i] ); } } } // JPW // mechanism allows different weapon damage for single/multiplayer; we want "balanced" weapons // in multiplayer but don't want to alter the existing single-player damage items that have already // been changed // // KLUDGE/FIXME: also modded #defines below to become macros that call this fn for minimal impact elsewhere // int G_GetWeaponDamage( int weapon ) { switch ( weapon ) { default: return 1; case WP_KNIFE: return 10; case WP_STEN: return 14; case WP_CARBINE: case WP_GARAND: case WP_KAR98: case WP_K43: return 34; case WP_FG42: return 15; case WP_LUGER: case WP_SILENCER: case WP_AKIMBO_LUGER: case WP_AKIMBO_SILENCEDLUGER: case WP_COLT: case WP_SILENCED_COLT: case WP_AKIMBO_COLT: case WP_AKIMBO_SILENCEDCOLT: case WP_THOMPSON: case WP_MP40: case WP_MOBILE_MG42: case WP_MOBILE_MG42_SET: return 18; case WP_FG42SCOPE: return 30; case WP_GARAND_SCOPE: case WP_K43_SCOPE: return 50; case WP_SMOKE_MARKER: return 140; // just enough to kill somebody standing on it case WP_MAPMORTAR: case WP_GRENADE_LAUNCHER: case WP_GRENADE_PINEAPPLE: case WP_GPG40: case WP_M7: case WP_LANDMINE: case WP_SATCHEL: return 250; case WP_TRIPMINE: return 300; case WP_PANZERFAUST: case WP_MORTAR_SET: case WP_DYNAMITE: return 400; } } float G_GetWeaponSpread( int weapon ) { switch ( weapon ) { case WP_LUGER: case WP_SILENCER: case WP_AKIMBO_LUGER: case WP_AKIMBO_SILENCEDLUGER: return 600; case WP_COLT: case WP_SILENCED_COLT: case WP_AKIMBO_COLT: case WP_AKIMBO_SILENCEDCOLT: return 600; case WP_MP40: case WP_THOMPSON: return 400; case WP_STEN: return 200; case WP_FG42SCOPE: return 200; case WP_FG42: return 500; case WP_GARAND: case WP_CARBINE: case WP_KAR98: case WP_K43: return 250; case WP_GARAND_SCOPE: case WP_K43_SCOPE: return 700; case WP_MOBILE_MG42: case WP_MOBILE_MG42_SET: return 2500; } G_Printf( "shouldn't ever get here (weapon %d)\n", weapon ); // jpw return 0; // shouldn't get here } #define LUGER_SPREAD G_GetWeaponSpread( WP_LUGER ) #define LUGER_DAMAGE G_GetWeaponDamage( WP_LUGER ) // JPW #define SILENCER_DAMAGE G_GetWeaponDamage( WP_SILENCER ) #define SILENCER_SPREAD G_GetWeaponSpread( WP_SILENCER ) #define AKIMBO_LUGER_DAMAGE G_GetWeaponDamage( WP_AKIMBO_LUGER ) #define AKIMBO_LUGER_SPREAD G_GetWeaponSpread( WP_AKIMBO_LUGER ) #define AKIMBO_SILENCEDLUGER_DAMAGE G_GetWeaponDamage( WP_AKIMBO_SILENCEDLUGER ) #define AKIMBO_SILENCEDLUGER_SPREAD G_GetWeaponSpread( WP_AKIMBO_SILENCEDLUGER ) #define COLT_SPREAD G_GetWeaponSpread( WP_COLT ) #define COLT_DAMAGE G_GetWeaponDamage( WP_COLT ) // JPW #define SILENCED_COLT_DAMAGE G_GetWeaponDamage( WP_SILENCED_COLT ) #define SILENCED_COLT_SPREAD G_GetWeaponSpread( WP_SILENCED_COLT ) #define AKIMBO_COLT_DAMAGE G_GetWeaponDamage( WP_AKIMBO_COLT ) #define AKIMBO_COLT_SPREAD G_GetWeaponSpread( WP_AKIMBO_COLT ) #define AKIMBO_SILENCEDCOLT_DAMAGE G_GetWeaponDamage( WP_AKIMBO_SILENCEDCOLT ) #define AKIMBO_SILENCEDCOLT_SPREAD G_GetWeaponSpread( WP_AKIMBO_SILENCEDCOLT ) #define MP40_SPREAD G_GetWeaponSpread( WP_MP40 ) #define MP40_DAMAGE G_GetWeaponDamage( WP_MP40 ) // JPW #define THOMPSON_SPREAD G_GetWeaponSpread( WP_THOMPSON ) #define THOMPSON_DAMAGE G_GetWeaponDamage( WP_THOMPSON ) // JPW #define STEN_SPREAD G_GetWeaponSpread( WP_STEN ) #define STEN_DAMAGE G_GetWeaponDamage( WP_STEN ) // JPW #define GARAND_SPREAD G_GetWeaponSpread( WP_GARAND ) #define GARAND_DAMAGE G_GetWeaponDamage( WP_GARAND ) // JPW #define KAR98_SPREAD G_GetWeaponSpread( WP_KAR98 ) #define KAR98_DAMAGE G_GetWeaponDamage( WP_KAR98 ) #define CARBINE_SPREAD G_GetWeaponSpread( WP_CARBINE ) #define CARBINE_DAMAGE G_GetWeaponDamage( WP_CARBINE ) #define KAR98_GREN_DAMAGE G_GetWeaponDamage( WP_GREN_KAR98 ) #define MOBILE_MG42_SPREAD G_GetWeaponSpread( WP_MOBILE_MG42 ) #define MOBILE_MG42_DAMAGE G_GetWeaponDamage( WP_MOBILE_MG42 ) #define FG42_SPREAD G_GetWeaponSpread( WP_FG42 ) #define FG42_DAMAGE G_GetWeaponDamage( WP_FG42 ) // JPW #define FG42SCOPE_SPREAD G_GetWeaponSpread( WP_FG42SCOPE ) #define FG42SCOPE_DAMAGE G_GetWeaponDamage( WP_FG42SCOPE ) // JPW #define K43_SPREAD G_GetWeaponSpread( WP_K43 ) #define K43_DAMAGE G_GetWeaponDamage( WP_K43 ) #define GARANDSCOPE_SPREAD G_GetWeaponSpread( WP_GARAND_SCOPE ) #define GARANDSCOPE_DAMAGE G_GetWeaponDamage( WP_GARAND_SCOPE ) #define K43SCOPE_SPREAD G_GetWeaponSpread( WP_K43_SCOPE ) #define K43SCOPE_DAMAGE G_GetWeaponDamage( WP_K43_SCOPE ) void RubbleFlagCheck( gentity_t *ent, trace_t tr ) { qboolean is_valid = qfalse; int type = 0; // (SA) moving client-side return; if ( tr.surfaceFlags & SURF_RUBBLE || tr.surfaceFlags & SURF_GRAVEL ) { is_valid = qtrue; type = 4; } else if ( tr.surfaceFlags & SURF_METAL ) { //----(SA) removed // is_valid = qtrue; // type = 2; } else if ( tr.surfaceFlags & SURF_WOOD ) { is_valid = qtrue; type = 1; } if ( is_valid && ent->client && ( ent->client->ps.persistant[PERS_HWEAPON_USE] ) ) { if ( rand() % 100 > 75 ) { gentity_t *sfx; vec3_t start; vec3_t dir; sfx = G_Spawn(); sfx->s.density = type; VectorCopy( tr.endpos, start ); VectorCopy( muzzleTrace, dir ); VectorNegate( dir, dir ); G_SetOrigin( sfx, start ); G_SetAngle( sfx, dir ); G_AddEvent( sfx, EV_SHARD, DirToByte( dir ) ); sfx->think = G_FreeEntity; sfx->nextthink = level.time + 1000; sfx->s.frame = 3 + ( rand() % 3 ) ; trap_LinkEntity( sfx ); } } } /* ============== EmitterCheck see if a new particle emitter should be created at the bullet impact point ============== */ void EmitterCheck( gentity_t *ent, gentity_t *attacker, trace_t *tr ) { gentity_t *tent; vec3_t origin; VectorCopy( tr->endpos, origin ); SnapVectorTowards( tr->endpos, attacker->s.origin ); if ( Q_stricmp( ent->classname, "func_explosive" ) == 0 ) { } else if ( Q_stricmp( ent->classname, "func_leaky" ) == 0 ) { tent = G_TempEntity( origin, EV_EMITTER ); VectorCopy( origin, tent->s.origin ); tent->s.time = 1234; tent->s.density = 9876; VectorCopy( tr->plane.normal, tent->s.origin2 ); } } /* ============== Bullet_Endpos find target end position for bullet trace based on entities weapon and accuracy ============== */ void Bullet_Endpos( gentity_t *ent, float spread, vec3_t *end ) { float r, u; qboolean randSpread = qtrue; int dist = 8192; r = crandom() * spread; u = crandom() * spread; if ( BG_IsScopedWeapon( ent->s.weapon ) ) { // aim dir already accounted for sway of scoped weapons in CalcMuzzlePoints() dist *= 2; randSpread = qfalse; } VectorMA( muzzleTrace, dist, forward, *end ); if ( randSpread ) { VectorMA( *end, r, right, *end ); VectorMA( *end, u, up, *end ); } } /* ============== Bullet_Fire ============== */ void Bullet_Fire( gentity_t *ent, float spread, int damage, qboolean distance_falloff ) { vec3_t end; // Gordon: skill thing should be here Arnout! switch ( ent->s.weapon ) { // light weapons case WP_LUGER: case WP_COLT: case WP_MP40: case WP_THOMPSON: case WP_STEN: case WP_SILENCER: case WP_SILENCED_COLT: if ( ent->client->sess.skill[SK_LIGHT_WEAPONS] >= 4 ) { spread *= .65f; } break; } Bullet_Endpos( ent, spread, &end ); G_HistoricalTraceBegin( ent ); Bullet_Fire_Extended( ent, ent, muzzleTrace, end, spread, damage, distance_falloff ); G_HistoricalTraceEnd( ent ); } /* ============== Bullet_Fire_Extended A modified Bullet_Fire with more parameters. The original Bullet_Fire still passes through here and functions as it always has. uses for this include shooting through entities (windows, doors, other players, etc.) and reflecting bullets ============== */ qboolean Bullet_Fire_Extended( gentity_t *source, gentity_t *attacker, vec3_t start, vec3_t end, float spread, int damage, qboolean distance_falloff ) { trace_t tr; gentity_t *tent; gentity_t *traceEnt; qboolean hitClient = qfalse; qboolean reducedDamage = qfalse; qboolean waslinked = qfalse; //bani - prevent shooting ourselves in the head when prone, firing through a breakable if ( g_entities[ attacker->s.number ].client && g_entities[ attacker->s.number ].r.linked == qtrue ) { g_entities[ attacker->s.number ].r.linked = qfalse; waslinked = qtrue; } G_Trace( source, &tr, start, NULL, NULL, end, source->s.number, MASK_SHOT ); //bani - prevent shooting ourselves in the head when prone, firing through a breakable if ( waslinked == qtrue ) { g_entities[ attacker->s.number ].r.linked = qtrue; } // bullet debugging using Q3A's railtrail if ( g_debugBullets.integer & 1 ) { tent = G_TempEntity( start, EV_RAILTRAIL ); VectorCopy( tr.endpos, tent->s.origin2 ); tent->s.otherEntityNum2 = attacker->s.number; } RubbleFlagCheck( attacker, tr ); traceEnt = &g_entities[ tr.entityNum ]; EmitterCheck( traceEnt, attacker, &tr ); // snap the endpos to integers, but nudged towards the line SnapVectorTowards( tr.endpos, start ); if ( distance_falloff ) { vec_t dist; vec3_t shotvec; float scale; //VectorSubtract( tr.endpos, start, shotvec ); VectorSubtract( tr.endpos, muzzleTrace, shotvec ); dist = VectorLengthSquared( shotvec ); #if DO_BROKEN_DISTANCEFALLOFF // ~~~___---___ if ( dist > Square( 1500.f ) ) { reducedDamage = qtrue; if ( dist > Square( 2500.f ) ) { damage *= 0.5f; } else { float scale = 1.f - 0.5f * ( Square( 1000.f ) / ( dist - Square( 1000.f ) ) ); damage *= scale; } } #else // ~~~---______ // zinx - start at 100% at 1500 units (and before), // and go to 50% at 2500 units (and after) // Square(1500) to Square(2500) -> 0.0 to 1.0 scale = ( dist - Square( 1500.f ) ) / ( Square( 2500.f ) - Square( 1500.f ) ); // 0.0 to 1.0 -> 0.0 to 0.5 scale *= 0.5f; // 0.0 to 0.5 -> 1.0 to 0.5 scale = 1.0f - scale; // And, finally, cap it. reducedDamage = qtrue; if ( scale >= 1.0f ) { scale = 1.0f; reducedDamage = qfalse; } else if ( scale < 0.5f ) { scale = 0.5f; } damage *= scale; #endif } // send bullet impact if ( traceEnt->takedamage && traceEnt->client /*&& !(traceEnt->flags & FL_DEFENSE_GUARD)*/ ) { tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH ); tent->s.eventParm = traceEnt->s.number; if ( AccuracyHit( traceEnt, attacker ) ) { hitClient = qtrue; } if ( g_debugBullets.integer >= 2 ) { // show hit player bb gentity_t *bboxEnt; vec3_t b1, b2; VectorCopy( traceEnt->r.currentOrigin, b1 ); VectorCopy( traceEnt->r.currentOrigin, b2 ); VectorAdd( b1, traceEnt->r.mins, b1 ); VectorAdd( b2, traceEnt->r.maxs, b2 ); bboxEnt = G_TempEntity( b1, EV_RAILTRAIL ); VectorCopy( b2, bboxEnt->s.origin2 ); bboxEnt->s.dmgFlags = 1; // ("type") } } else { trace_t tr2; // Ridah, bullet impact should reflect off surface vec3_t reflect; float dot; if ( g_debugBullets.integer <= -2 ) { // show hit thing bb gentity_t *bboxEnt; vec3_t b1, b2; VectorCopy( traceEnt->r.currentOrigin, b1 ); VectorCopy( traceEnt->r.currentOrigin, b2 ); VectorAdd( b1, traceEnt->r.mins, b1 ); VectorAdd( b2, traceEnt->r.maxs, b2 ); bboxEnt = G_TempEntity( b1, EV_RAILTRAIL ); VectorCopy( b2, bboxEnt->s.origin2 ); bboxEnt->s.dmgFlags = 1; // ("type") } tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_WALL ); G_Trace( source, &tr2, start, NULL, NULL, end, source->s.number, MASK_WATER | MASK_SHOT ); if ( ( tr.entityNum != tr2.entityNum && tr2.fraction != 1 ) ) { vec3_t v; VectorSubtract( tr.endpos, start, v ); tent->s.origin2[0] = ( 8192 * tr2.fraction ) / VectorLength( v ); } else { tent->s.origin2[0] = 0; } dot = DotProduct( forward, tr.plane.normal ); VectorMA( forward, -2 * dot, tr.plane.normal, reflect ); VectorNormalize( reflect ); tent->s.eventParm = DirToByte( reflect ); tent->s.otherEntityNum2 = ENTITYNUM_NONE; } tent->s.otherEntityNum = attacker->s.number; if ( traceEnt->takedamage ) { G_Damage( traceEnt, attacker, attacker, forward, tr.endpos, damage, ( distance_falloff ? DAMAGE_DISTANCEFALLOFF : 0 ), GetAmmoTableData( attacker->s.weapon )->mod ); // allow bullets to "pass through" func_explosives if they break by taking another simultanious shot if ( traceEnt->s.eType == ET_EXPLOSIVE ) { if ( traceEnt->health <= damage ) { // start new bullet at position this hit the bmodel and continue to the end position (ignoring shot-through bmodel in next trace) // spread = 0 as this is an extension of an already spread shot return Bullet_Fire_Extended( traceEnt, attacker, tr.endpos, end, 0, damage, distance_falloff ); } } } return hitClient; } /* ====================================================================== GRENADE LAUNCHER 700 has been the standard direction multiplier in fire_grenade() ====================================================================== */ extern void G_ExplodeMissilePoisonGas( gentity_t *ent ); gentity_t *weapon_gpg40_fire( gentity_t *ent, int grenType ) { gentity_t *m /*, *te*/; // JPW NERVE trace_t tr; vec3_t viewpos; // float upangle = 0, pitch; // start with level throwing and adjust based on angle vec3_t tosspos; //bani - to prevent nade-through-teamdoor sploit vec3_t orig_viewpos; AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); VectorCopy( muzzleEffect, tosspos ); // check for valid start spot (so you don't throw through or get stuck in a wall) VectorCopy( ent->s.pos.trBase, viewpos ); viewpos[2] += ent->client->ps.viewheight; VectorCopy( viewpos, orig_viewpos ); //bani - to prevent nade-through-teamdoor sploit VectorMA( viewpos, 32, forward, viewpos ); //bani - to prevent nade-through-teamdoor sploit trap_Trace( &tr, orig_viewpos, tv( -4.f, -4.f, 0.f ), tv( 4.f, 4.f, 6.f ), viewpos, ent->s.number, MASK_MISSILESHOT ); if ( tr.fraction < 1 ) { // oops, bad launch spot ) { VectorCopy( tr.endpos, tosspos ); SnapVectorTowards( tosspos, orig_viewpos ); } else { trap_Trace( &tr, viewpos, tv( -4.f, -4.f, 0.f ), tv( 4.f, 4.f, 6.f ), tosspos, ent->s.number, MASK_MISSILESHOT ); if ( tr.fraction < 1 ) { // oops, bad launch spot VectorCopy( tr.endpos, tosspos ); SnapVectorTowards( tosspos, viewpos ); } } VectorScale( forward, 2000, forward ); m = fire_grenade( ent, tosspos, forward, grenType ); m->damage = 0; // Ridah, return the grenade so we can do some prediction before deciding if we really want to throw it or not return m; } gentity_t *weapon_mortar_fire( gentity_t *ent, int grenType ) { gentity_t *m; trace_t tr; vec3_t launchPos, testPos; vec3_t angles; VectorCopy( ent->client->ps.viewangles, angles ); angles[PITCH] -= 60.f; /* if( angles[PITCH] < -89.f ) angles[PITCH] = -89.f;*/ AngleVectors( angles, forward, NULL, NULL ); VectorCopy( muzzleEffect, launchPos ); // check for valid start spot (so you don't throw through or get stuck in a wall) VectorMA( launchPos, 32, forward, testPos ); // Gordon: hack so i can do inverse trajectory calcs easily :p if ( G_IsSinglePlayerGame() && ent->r.svFlags & SVF_BOT ) { /* forward[0] *= 3000; forward[1] *= 3000; forward[2] *= 3000;*/ VectorCopy( ent->gDelta, forward ); } else { forward[0] *= 3000 * 1.1f; forward[1] *= 3000 * 1.1f; forward[2] *= 1500 * 1.1f; } trap_Trace( &tr, testPos, tv( -4.f, -4.f, 0.f ), tv( 4.f, 4.f, 6.f ), launchPos, ent->s.number, MASK_MISSILESHOT ); if ( tr.fraction < 1 ) { // oops, bad launch spot VectorCopy( tr.endpos, launchPos ); SnapVectorTowards( launchPos, testPos ); } m = fire_grenade( ent, launchPos, forward, grenType ); return m; } gentity_t *weapon_grenadelauncher_fire( gentity_t *ent, int grenType ) { gentity_t *m; trace_t tr; vec3_t viewpos; float upangle = 0, pitch; // start with level throwing and adjust based on angle vec3_t tosspos; qboolean underhand = qtrue; pitch = ent->s.apos.trBase[0]; // JPW NERVE -- smoke grenades always overhand if ( pitch >= 0 ) { forward[2] += 0.5f; // Used later in underhand boost pitch = 1.3f; } else { pitch = -pitch; pitch = min( pitch, 30 ); pitch /= 30.f; pitch = 1 - pitch; forward[2] += ( pitch * 0.5f ); // Used later in underhand boost pitch *= 0.3f; pitch += 1.f; } VectorNormalizeFast( forward ); // make sure forward is normalized upangle = -( ent->s.apos.trBase[0] ); // this will give between -90 / 90 upangle = min( upangle, 50 ); upangle = max( upangle, -50 ); // now clamped to -50 / 50 (don't allow firing straight up/down) upangle = upangle / 100.0f; // -0.5 / 0.5 upangle += 0.5f; // 0.0 / 1.0 if ( upangle < .1 ) { upangle = .1; } // pineapples are not thrown as far as mashers // Gordon: um, no? if ( grenType == WP_GRENADE_LAUNCHER ) { upangle *= 900; } else if ( grenType == WP_GRENADE_PINEAPPLE ) { upangle *= 900; } else if ( grenType == WP_SMOKE_MARKER ) { upangle *= 900; } else if ( grenType == WP_SMOKE_BOMB ) { upangle *= 900; } else { // WP_DYNAMITE // Gordon: or WP_LANDMINE / WP_SATCHEL upangle *= 400; } VectorCopy( muzzleEffect, tosspos ); if ( underhand ) { // move a little bit more away from the player (so underhand tosses don't get caught on nearby lips) VectorMA( muzzleEffect, 8, forward, tosspos ); tosspos[2] -= 8; // lower origin for the underhand throw upangle *= pitch; SnapVector( tosspos ); } VectorScale( forward, upangle, forward ); // check for valid start spot (so you don't throw through or get stuck in a wall) VectorCopy( ent->s.pos.trBase, viewpos ); viewpos[2] += ent->client->ps.viewheight; if ( grenType == WP_DYNAMITE || grenType == WP_SATCHEL ) { trap_Trace( &tr, viewpos, tv( -12.f, -12.f, 0.f ), tv( 12.f, 12.f, 20.f ), tosspos, ent->s.number, MASK_MISSILESHOT ); } else if ( grenType == WP_LANDMINE ) { trap_Trace( &tr, viewpos, tv( -16.f, -16.f, 0.f ), tv( 16.f, 16.f, 16.f ), tosspos, ent->s.number, MASK_MISSILESHOT ); } else { trap_Trace( &tr, viewpos, tv( -4.f, -4.f, 0.f ), tv( 4.f, 4.f, 6.f ), tosspos, ent->s.number, MASK_MISSILESHOT ); } if ( tr.startsolid ) { // Arnout: this code is a bit more solid than the previous code VectorCopy( forward, viewpos ); VectorNormalizeFast( viewpos ); VectorMA( ent->r.currentOrigin, -24.f, viewpos, viewpos ); if ( grenType == WP_DYNAMITE || grenType == WP_SATCHEL ) { trap_Trace( &tr, viewpos, tv( -12.f, -12.f, 0.f ), tv( 12.f, 12.f, 20.f ), tosspos, ent->s.number, MASK_MISSILESHOT ); } else if ( grenType == WP_LANDMINE ) { trap_Trace( &tr, viewpos, tv( -16.f, -16.f, 0.f ), tv( 16.f, 16.f, 16.f ), tosspos, ent->s.number, MASK_MISSILESHOT ); } else { trap_Trace( &tr, viewpos, tv( -4.f, -4.f, 0.f ), tv( 4.f, 4.f, 6.f ), tosspos, ent->s.number, MASK_MISSILESHOT ); } VectorCopy( tr.endpos, tosspos ); } else if ( tr.fraction < 1 ) { // oops, bad launch spot VectorCopy( tr.endpos, tosspos ); SnapVectorTowards( tosspos, viewpos ); } m = fire_grenade( ent, tosspos, forward, grenType ); m->damage = 0; // Ridah, grenade's don't explode on contact if ( grenType == WP_LANDMINE ) { if ( ent->client->sess.sessionTeam == TEAM_AXIS ) { // store team so we can generate red or blue smoke m->s.otherEntityNum2 = 1; } else { m->s.otherEntityNum2 = 0; } } // Arnout: override for smoke gren if ( grenType == WP_SMOKE_BOMB ) { m->s.effect1Time = 16; m->think = weapon_smokeBombExplode; } // JPW NERVE if ( grenType == WP_SMOKE_MARKER ) { m->s.teamNum = ent->client->sess.sessionTeam; // store team so we can generate red or blue smoke if ( ent->client->sess.skill[SK_SIGNALS] >= 3 ) { m->count = 2; m->nextthink = level.time + 3500; m->think = weapon_checkAirStrikeThink2; } else { m->count = 1; m->nextthink = level.time + 2500; m->think = weapon_checkAirStrikeThink1; } } // jpw //----(SA) adjust for movement of character. TODO: Probably comment in later, but only for forward/back not strafing //VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics // Ridah, return the grenade so we can do some prediction before deciding if we really want to throw it or not return m; } /* ====================================================================== ROCKET ====================================================================== */ void Weapon_Panzerfaust_Fire( gentity_t *ent ) { gentity_t *m; m = fire_rocket( ent, muzzleEffect, forward ); // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics } /* ====================================================================== SPEARGUN ====================================================================== */ /*void Weapon_Speargun_Fire (gentity_t *ent) { gentity_t *m; m = fire_speargun (ent, muzzleEffect, forward); }*/ /* ====================================================================== LIGHTNING GUN ====================================================================== */ void G_BurnMeGood( gentity_t *self, gentity_t *body ) { // add the new damage body->flameQuota += 5; body->flameQuotaTime = level.time; // JPW NERVE -- yet another flamethrower damage model, trying to find a feels-good damage combo that isn't overpowered if ( body->lastBurnedFrameNumber != level.framenum ) { G_Damage( body, self->parent, self->parent, vec3_origin, self->r.currentOrigin, 5, 0, MOD_FLAMETHROWER ); // was 2 dmg in release ver, hit avg. 2.5 times per frame body->lastBurnedFrameNumber = level.framenum; } // jpw // make em burn if ( body->client && ( body->health <= 0 || body->flameQuota > 0 ) ) { // JPW NERVE was > FLAME_THRESHOLD if ( body->s.onFireEnd < level.time ) { body->s.onFireStart = level.time; } body->s.onFireEnd = level.time + FIRE_FLASH_TIME; body->flameBurnEnt = self->r.ownerNum; // add to playerState for client-side effect body->client->ps.onFireStart = level.time; } } // TTimo - for traces calls static vec3_t flameChunkMins = {-4, -4, -4}; static vec3_t flameChunkMaxs = { 4, 4, 4}; void Weapon_FlamethrowerFire( gentity_t *ent ) { gentity_t *traceEnt; vec3_t start; vec3_t trace_start; vec3_t trace_end; trace_t trace; VectorCopy( ent->r.currentOrigin, start ); start[2] += ent->client->ps.viewheight; VectorCopy( start, trace_start ); VectorMA( start, -8, forward, start ); VectorMA( start, 10, right, start ); VectorMA( start, -6, up, start ); // prevent flame thrower cheat, run & fire while aiming at the ground, don't get hurt // 72 total box height, 18 xy -> 77 trace radius (from view point towards the ground) is enough to cover the area around the feet VectorMA( trace_start, 77.0, forward, trace_end ); trap_Trace( &trace, trace_start, flameChunkMins, flameChunkMaxs, trace_end, ent->s.number, MASK_SHOT | MASK_WATER ); if ( trace.fraction != 1.0 ) { // additional checks to filter out false positives if ( trace.endpos[2] > ( ent->r.currentOrigin[2] + ent->r.mins[2] - 8 ) && trace.endpos[2] < ent->r.currentOrigin[2] ) { // trigger in a 21 radius around origin trace_start[0] -= trace.endpos[0]; trace_start[1] -= trace.endpos[1]; if ( trace_start[0] * trace_start[0] + trace_start[1] * trace_start[1] < 441 ) { // set self in flames G_BurnMeGood( ent, ent ); } } } traceEnt = fire_flamechunk( ent, start, forward ); } //====================================================================== /* ============== AddLean add leaning offset ============== */ void AddLean( gentity_t *ent, vec3_t point ) { if ( ent->client ) { if ( ent->client->ps.leanf ) { vec3_t right; AngleVectors( ent->client->ps.viewangles, NULL, right, NULL ); VectorMA( point, ent->client->ps.leanf, right, point ); } } } /* =============== AccuracyHit =============== */ qboolean AccuracyHit( gentity_t *target, gentity_t *attacker ) { if ( !target->takedamage ) { return qfalse; } if ( !attacker ) { return qfalse; } if ( target == attacker ) { return qfalse; } if ( !target->client ) { return qfalse; } if ( !attacker->client ) { return qfalse; } if ( target->client->ps.stats[STAT_HEALTH] <= 0 ) { return qfalse; } if ( OnSameTeam( target, attacker ) ) { return qfalse; } return qtrue; } /* =============== CalcMuzzlePoint set muzzle location relative to pivoting eye =============== */ void CalcMuzzlePoint( gentity_t *ent, int weapon, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) { VectorCopy( ent->r.currentOrigin, muzzlePoint ); muzzlePoint[2] += ent->client->ps.viewheight; // Ridah, this puts the start point outside the bounding box, isn't necessary // VectorMA( muzzlePoint, 14, forward, muzzlePoint ); // done. // Ridah, offset for more realistic firing from actual gun position //----(SA) modified switch ( weapon ) // Ridah, changed this so I can predict weapons { case WP_PANZERFAUST: VectorMA( muzzlePoint,10,right,muzzlePoint ); break; case WP_DYNAMITE: case WP_GRENADE_PINEAPPLE: case WP_GRENADE_LAUNCHER: case WP_SATCHEL: case WP_SMOKE_BOMB: VectorMA( muzzlePoint, 20, right, muzzlePoint ); break; case WP_AKIMBO_COLT: case WP_AKIMBO_SILENCEDCOLT: case WP_AKIMBO_LUGER: case WP_AKIMBO_SILENCEDLUGER: VectorMA( muzzlePoint, -6, right, muzzlePoint ); VectorMA( muzzlePoint, -4, up, muzzlePoint ); break; default: VectorMA( muzzlePoint, 6, right, muzzlePoint ); VectorMA( muzzlePoint, -4, up, muzzlePoint ); break; } // done. // (SA) actually, this is sort of moot right now since // you're not allowed to fire when leaning. Leave in // in case we decide to enable some lean-firing. // (SA) works with gl now //AddLean(ent, muzzlePoint); // snap to integer coordinates for more efficient network bandwidth usage SnapVector( muzzlePoint ); } // Rafael - for activate void CalcMuzzlePointForActivate( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) { VectorCopy( ent->s.pos.trBase, muzzlePoint ); muzzlePoint[2] += ent->client->ps.viewheight; AddLean( ent, muzzlePoint ); // snap to integer coordinates for more efficient network bandwidth usage SnapVector( muzzlePoint ); } // done. // Ridah void CalcMuzzlePoints( gentity_t *ent, int weapon ) { vec3_t viewang; VectorCopy( ent->client->ps.viewangles, viewang ); { // non ai's take into account scoped weapon 'sway' (just another way aimspread is visualized/utilized) float spreadfrac, phase; if ( BG_IsScopedWeapon( weapon ) ) { float pitchAmp, yawAmp; float pitchMinAmp, yawMinAmp; spreadfrac = ent->client->currentAimSpreadScale; if ( weapon == WP_FG42SCOPE ) { pitchAmp = 4 * ZOOM_PITCH_AMPLITUDE; yawAmp = 4 * ZOOM_YAW_AMPLITUDE; pitchMinAmp = 4 * ZOOM_PITCH_MIN_AMPLITUDE; yawMinAmp = 4 * ZOOM_YAW_MIN_AMPLITUDE; } else { pitchAmp = ZOOM_PITCH_AMPLITUDE; yawAmp = ZOOM_YAW_AMPLITUDE; pitchMinAmp = ZOOM_PITCH_MIN_AMPLITUDE; yawMinAmp = ZOOM_YAW_MIN_AMPLITUDE; } // rotate 'forward' vector by the sway phase = level.time / 1000.0 * ZOOM_PITCH_FREQUENCY * M_PI * 2; viewang[PITCH] += ZOOM_PITCH_AMPLITUDE * sin( phase ) * ( spreadfrac + pitchMinAmp ); phase = level.time / 1000.0 * ZOOM_YAW_FREQUENCY * M_PI * 2; viewang[YAW] += ZOOM_YAW_AMPLITUDE * sin( phase ) * ( spreadfrac + yawMinAmp ); } } // set aiming directions AngleVectors( viewang, forward, right, up ); //----(SA) modified the muzzle stuff so that weapons that need to fire down a perfect trace // straight out of the camera (SP5, Mauser right now) can have that accuracy, but // weapons that need an offset effect (bazooka/grenade/etc.) can still look like // they came out of the weap. CalcMuzzlePointForActivate( ent, forward, right, up, muzzleTrace ); CalcMuzzlePoint( ent, weapon, forward, right, up, muzzleEffect ); } qboolean G_PlayerCanBeSeenByOthers( gentity_t *ent ) { int i; gentity_t *ent2; vec3_t pos[3]; VectorCopy( ent->client->ps.origin, pos[0] ); pos[0][2] += ent->client->ps.mins[2]; VectorCopy( ent->client->ps.origin, pos[1] ); VectorCopy( ent->client->ps.origin, pos[2] ); pos[2][2] += ent->client->ps.maxs[2]; for ( i = 0, ent2 = g_entities; i < level.maxclients; i++, ent2++ ) { if ( !ent2->inuse || ent2 == ent ) { continue; } if ( ent2->client->sess.sessionTeam == TEAM_SPECTATOR ) { continue; } if ( ent2->health <= 0 || ent2->client->sess.sessionTeam == ent->client->sess.sessionTeam ) { continue; } if ( ent2->client->ps.eFlags & EF_ZOOMING ) { G_SetupFrustum_ForBinoculars( ent2 ); } else { G_SetupFrustum( ent2 ); } if ( G_VisibleFromBinoculars( ent2, ent, pos[0] ) || G_VisibleFromBinoculars( ent2, ent, pos[1] ) || G_VisibleFromBinoculars( ent2, ent, pos[2] ) ) { return qtrue; } } return qfalse; } /* =============== FireWeapon =============== */ void FireWeapon( gentity_t *ent ) { float aimSpreadScale; int shots = 1; // ydnar: dead guys don't fire guns if ( ent->client->ps.pm_type == PM_DEAD ) { return; } // Rafael mg42 if ( ent->client->ps.persistant[PERS_HWEAPON_USE] && ent->active ) { return; } // Ridah, need to call this for AI prediction also CalcMuzzlePoints( ent, ent->s.weapon ); if ( g_userAim.integer ) { aimSpreadScale = ent->client->currentAimSpreadScale; // Ridah, add accuracy factor for AI aimSpreadScale += 0.15f; // (SA) just adding a temp /maximum/ accuracy for player (this will be re-visited in greater detail :) if ( aimSpreadScale > 1 ) { aimSpreadScale = 1.0f; // still cap at 1.0 } } else { aimSpreadScale = 1.0; } if ( ( ent->client->ps.eFlags & EF_ZOOMING ) && ( ent->client->ps.stats[STAT_KEYS] & ( 1 << INV_BINOCS ) ) ) { if ( ent->client->sess.playerType == PC_FIELDOPS ) { if ( !( ent->client->ps.leanf ) ) { Weapon_Artillery( ent ); } return; } } if ( ent->client->ps.groundEntityNum == ENTITYNUM_NONE ) { aimSpreadScale = 2.0f; } // covert ops disguise handling if ( ent->client->ps.powerups[PW_OPS_DISGUISED] && ent->s.weapon != WP_SMOKE_BOMB && ent->s.weapon != WP_SATCHEL && ent->s.weapon != WP_SATCHEL_DET ) { if ( !( ent->s.weapon == WP_KNIFE || ent->s.weapon == WP_STEN || ent->s.weapon == WP_SILENCER || ent->s.weapon == WP_SILENCED_COLT || ent->s.weapon == WP_AKIMBO_SILENCEDCOLT || ent->s.weapon == WP_AKIMBO_SILENCEDLUGER || ent->s.weapon == WP_K43 || ent->s.weapon == WP_K43_SCOPE || ent->s.weapon == WP_GARAND || ent->s.weapon == WP_GRENADE_LAUNCHER || ent->s.weapon == WP_GRENADE_PINEAPPLE || ent->s.weapon == WP_GARAND_SCOPE ) ) { ent->client->ps.powerups[PW_OPS_DISGUISED] = 0; } else if ( G_PlayerCanBeSeenByOthers( ent ) ) { ent->client->ps.powerups[PW_OPS_DISGUISED] = 0; } } // fire the specific weapon switch ( ent->s.weapon ) { case WP_KNIFE: Weapon_Knife( ent ); break; // NERVE - SMF case WP_MEDKIT: Weapon_Medic( ent ); break; case WP_PLIERS: Weapon_Engineer( ent ); break; case WP_SMOKE_MARKER: if ( level.time - ent->client->ps.classWeaponTime > level.lieutenantChargeTime[ent->client->sess.sessionTeam - 1] ) { ent->client->ps.classWeaponTime = level.time - level.lieutenantChargeTime[ent->client->sess.sessionTeam - 1]; } if ( ent->client->sess.skill[SK_SIGNALS] >= 2 ) { ent->client->ps.classWeaponTime += .66f * level.lieutenantChargeTime[ent->client->sess.sessionTeam - 1]; } else { ent->client->ps.classWeaponTime = level.time; } weapon_grenadelauncher_fire( ent, WP_SMOKE_MARKER ); break; // -NERVE - SMF case WP_MEDIC_SYRINGE: Weapon_Syringe( ent ); break; case WP_MEDIC_ADRENALINE: ent->client->ps.classWeaponTime = level.time; Weapon_AdrenalineSyringe( ent ); break; case WP_AMMO: Weapon_MagicAmmo( ent ); break; case WP_LUGER: Bullet_Fire( ent, LUGER_SPREAD * aimSpreadScale, LUGER_DAMAGE, qtrue ); break; case WP_SILENCER: Bullet_Fire( ent, SILENCER_SPREAD * aimSpreadScale, SILENCER_DAMAGE, qtrue ); break; case WP_AKIMBO_LUGER: Bullet_Fire( ent, AKIMBO_LUGER_SPREAD * aimSpreadScale, AKIMBO_LUGER_DAMAGE, qtrue ); break; case WP_AKIMBO_SILENCEDLUGER: Bullet_Fire( ent, AKIMBO_SILENCEDLUGER_SPREAD * aimSpreadScale, AKIMBO_SILENCEDLUGER_DAMAGE, qtrue ); break; case WP_COLT: Bullet_Fire( ent, COLT_SPREAD * aimSpreadScale, COLT_DAMAGE, qtrue ); break; case WP_SILENCED_COLT: Bullet_Fire( ent, SILENCED_COLT_SPREAD * aimSpreadScale, SILENCED_COLT_DAMAGE, qtrue ); break; case WP_AKIMBO_COLT: Bullet_Fire( ent, AKIMBO_COLT_SPREAD * aimSpreadScale, AKIMBO_COLT_DAMAGE, qtrue ); break; case WP_AKIMBO_SILENCEDCOLT: Bullet_Fire( ent, AKIMBO_SILENCEDCOLT_SPREAD * aimSpreadScale, AKIMBO_SILENCEDCOLT_DAMAGE, qtrue ); break; case WP_KAR98: aimSpreadScale = 1.0f; Bullet_Fire( ent, KAR98_SPREAD * aimSpreadScale, KAR98_DAMAGE, qfalse ); break; case WP_CARBINE: aimSpreadScale = 1.0f; Bullet_Fire( ent, CARBINE_SPREAD * aimSpreadScale, CARBINE_DAMAGE, qfalse ); break; case WP_FG42SCOPE: Bullet_Fire( ent, FG42SCOPE_SPREAD * aimSpreadScale, FG42SCOPE_DAMAGE, qfalse ); break; case WP_FG42: Bullet_Fire( ent, FG42_SPREAD * aimSpreadScale, FG42_DAMAGE, qtrue ); break; case WP_GARAND_SCOPE: Bullet_Fire( ent, GARANDSCOPE_SPREAD * aimSpreadScale, GARANDSCOPE_DAMAGE, qfalse ); break; case WP_GARAND: aimSpreadScale = 1.0f; Bullet_Fire( ent, GARAND_SPREAD * aimSpreadScale, GARAND_DAMAGE, qfalse ); break; case WP_SATCHEL_DET: if ( G_ExplodeSatchels( ent ) ) { ent->client->ps.ammo[WP_SATCHEL_DET] = 0; ent->client->ps.ammoclip[WP_SATCHEL_DET] = 0; ent->client->ps.ammoclip[WP_SATCHEL] = 1; G_AddEvent( ent, EV_NOAMMO, 0 ); } break; case WP_TRIPMINE: G_PlaceTripmine( ent ); break; case WP_MOBILE_MG42_SET: Bullet_Fire( ent, MOBILE_MG42_SPREAD * 0.05f * aimSpreadScale, MOBILE_MG42_DAMAGE, qfalse ); break; case WP_MOBILE_MG42: if ( ent->client->ps.pm_flags & PMF_DUCKED || ent->client->ps.eFlags & EF_PRONE ) { Bullet_Fire( ent, MOBILE_MG42_SPREAD * 0.6f * aimSpreadScale, MOBILE_MG42_DAMAGE, qfalse ); } else { Bullet_Fire( ent, MOBILE_MG42_SPREAD * aimSpreadScale, MOBILE_MG42_DAMAGE, qfalse ); } break; case WP_K43_SCOPE: Bullet_Fire( ent, K43SCOPE_SPREAD * aimSpreadScale, K43SCOPE_DAMAGE, qfalse ); break; case WP_K43: aimSpreadScale = 1.0; Bullet_Fire( ent, K43_SPREAD * aimSpreadScale, K43_DAMAGE, qfalse ); break; case WP_STEN: Bullet_Fire( ent, STEN_SPREAD * aimSpreadScale, STEN_DAMAGE, qtrue ); break; case WP_MP40: Bullet_Fire( ent, MP40_SPREAD * aimSpreadScale, MP40_DAMAGE, qtrue ); break; case WP_THOMPSON: Bullet_Fire( ent, THOMPSON_SPREAD * aimSpreadScale, THOMPSON_DAMAGE, qtrue ); break; case WP_PANZERFAUST: if ( level.time - ent->client->ps.classWeaponTime > level.soldierChargeTime[ent->client->sess.sessionTeam - 1] ) { ent->client->ps.classWeaponTime = level.time - level.soldierChargeTime[ent->client->sess.sessionTeam - 1]; } if ( ent->client->sess.skill[SK_HEAVY_WEAPONS] >= 1 ) { ent->client->ps.classWeaponTime += .66f * level.soldierChargeTime[ent->client->sess.sessionTeam - 1]; } else { ent->client->ps.classWeaponTime = level.time; } Weapon_Panzerfaust_Fire( ent ); if ( ent->client ) { vec3_t forward; AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); VectorMA( ent->client->ps.velocity, -64, forward, ent->client->ps.velocity ); } break; case WP_GPG40: case WP_M7: if ( level.time - ent->client->ps.classWeaponTime > level.engineerChargeTime[ent->client->sess.sessionTeam - 1] ) { ent->client->ps.classWeaponTime = level.time - level.engineerChargeTime[ent->client->sess.sessionTeam - 1]; } ent->client->ps.classWeaponTime += .5f * level.engineerChargeTime[ent->client->sess.sessionTeam - 1]; weapon_gpg40_fire( ent, ent->s.weapon ); break; case WP_MORTAR_SET: if ( level.time - ent->client->ps.classWeaponTime > level.soldierChargeTime[ent->client->sess.sessionTeam - 1] ) { ent->client->ps.classWeaponTime = level.time - level.soldierChargeTime[ent->client->sess.sessionTeam - 1]; } if ( ent->client->sess.skill[SK_HEAVY_WEAPONS] >= 1 ) { ent->client->ps.classWeaponTime += .5f * ( 1 - 0.3f ) * level.soldierChargeTime[ent->client->sess.sessionTeam - 1]; } else { ent->client->ps.classWeaponTime += .5f * level.soldierChargeTime[ent->client->sess.sessionTeam - 1]; } weapon_mortar_fire( ent, ent->s.weapon ); break; case WP_GRENADE_LAUNCHER: case WP_GRENADE_PINEAPPLE: case WP_DYNAMITE: case WP_LANDMINE: case WP_SATCHEL: case WP_SMOKE_BOMB: if ( ent->s.weapon == WP_SMOKE_BOMB || ent->s.weapon == WP_SATCHEL ) { if ( level.time - ent->client->ps.classWeaponTime > level.covertopsChargeTime[ent->client->sess.sessionTeam - 1] ) { ent->client->ps.classWeaponTime = level.time - level.covertopsChargeTime[ent->client->sess.sessionTeam - 1]; } if ( ent->client->sess.skill[SK_MILITARY_INTELLIGENCE_AND_SCOPED_WEAPONS] >= 2 ) { ent->client->ps.classWeaponTime += .66f * level.covertopsChargeTime[ent->client->sess.sessionTeam - 1]; } else { ent->client->ps.classWeaponTime = level.time; } } if ( ent->s.weapon == WP_LANDMINE ) { if ( level.time - ent->client->ps.classWeaponTime > level.engineerChargeTime[ent->client->sess.sessionTeam - 1] ) { ent->client->ps.classWeaponTime = level.time - level.engineerChargeTime[ent->client->sess.sessionTeam - 1]; } if ( ent->client->sess.skill[SK_EXPLOSIVES_AND_CONSTRUCTION] >= 3 ) { // rain - bug #202 - use 33%, not 66%, when upgraded. // do not penalize the happy fun engineer. ent->client->ps.classWeaponTime += .33f * level.engineerChargeTime[ent->client->sess.sessionTeam - 1]; } else { ent->client->ps.classWeaponTime += .5f * level.engineerChargeTime[ent->client->sess.sessionTeam - 1]; } } if ( ent->s.weapon == WP_DYNAMITE ) { if ( level.time - ent->client->ps.classWeaponTime > level.engineerChargeTime[ent->client->sess.sessionTeam - 1] ) { ent->client->ps.classWeaponTime = level.time - level.engineerChargeTime[ent->client->sess.sessionTeam - 1]; } if ( ent->client->sess.skill[SK_EXPLOSIVES_AND_CONSTRUCTION] >= 3 ) { ent->client->ps.classWeaponTime += .66f * level.engineerChargeTime[ent->client->sess.sessionTeam - 1]; } else { ent->client->ps.classWeaponTime = level.time; } } weapon_grenadelauncher_fire( ent, ent->s.weapon ); break; case WP_FLAMETHROWER: // RF, this is done client-side only now // Gordon: um, no it isnt? Weapon_FlamethrowerFire( ent ); break; case WP_MAPMORTAR: break; default: break; } // OSP #ifndef DEBUG_STATS if ( g_gamestate.integer == GS_PLAYING ) #endif ent->client->sess.aWeaponStats[BG_WeapStatForWeapon( ent->s.weapon )].atts += shots; } // // IsSilencedWeapon // // Description: Is the specified weapon a silenced weapon? // Written: 12/26/2002 // qboolean IsSilencedWeapon ( // The type of weapon in question. Is it silenced? int weaponType ) { // Local Variables //////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // Return true for any of the silenced types switch ( weaponType ) { case WP_SILENCED_COLT: case WP_STEN: case WP_SILENCER: return qtrue; }; // Otherwise, not silenced return qfalse; } // // IsSilencedWeapon //