/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena 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 2 of the License, or (at your option) any later version. Quake III Arena 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 Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // g_weapon.c // perform the server side effects of a weapon firing #include "g_local.h" static float s_quadFactor; static vec3_t forward, right, up; static vec3_t muzzle; #define NUM_NAILSHOTS 15 /* ================ G_BounceProjectile ================ */ void G_BounceProjectile(vec3_t start, vec3_t impact, vec3_t dir, vec3_t endout) { vec3_t v, newv; float dot; VectorSubtract(impact, start, v); dot = DotProduct(v, dir); VectorMA(v, -2 * dot, dir, newv); VectorNormalize(newv); VectorMA(impact, 8192, newv, endout); } /* ====================================================================== GAUNTLET ====================================================================== */ void Weapon_Gauntlet(gentity_t * ent) { } /* =============== CheckGauntletAttack =============== */ qboolean CheckGauntletAttack(gentity_t * ent) { trace_t tr; vec3_t end; gentity_t *tent; gentity_t *traceEnt; int damage; // set aiming directions AngleVectors(ent->client->ps.viewangles, forward, right, up); CalcMuzzlePoint(ent, forward, right, up, muzzle); VectorMA(muzzle, 32, forward, end); trap_Trace(&tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT); if(tr.surfaceFlags & SURF_NOIMPACT) { return qfalse; } traceEnt = &g_entities[tr.entityNum]; // send blood impact if(traceEnt->takedamage && traceEnt->client) { tent = G_TempEntity(tr.endpos, EV_MISSILE_HIT); tent->s.otherEntityNum = traceEnt->s.number; tent->s.eventParm = DirToByte(tr.plane.normal); tent->s.weapon = ent->s.weapon; } if(!traceEnt->takedamage) { return qfalse; } if(ent->client->ps.powerups[PW_QUAD]) { G_AddEvent(ent, EV_POWERUP_QUAD, 0); s_quadFactor = g_quadfactor.value; } else { s_quadFactor = 1; } #ifdef MISSIONPACK if(ent->client->persistantPowerup && ent->client->persistantPowerup->item && ent->client->persistantPowerup->item->giTag == PW_DOUBLER) { s_quadFactor *= 2; } #endif damage = 50 * s_quadFactor; G_Damage(traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_GAUNTLET); return qtrue; } /* ====================================================================== 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. ====================== */ 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]; } else { v[i] = (int)v[i] + 1; } } } #ifdef MISSIONPACK #define CHAINGUN_SPREAD 600 #endif #define MACHINEGUN_SPREAD 200 #define MACHINEGUN_DAMAGE 7 #define MACHINEGUN_TEAM_DAMAGE 5 // wimpier MG in teamplay void Bullet_Fire(gentity_t * ent, float spread, int damage) { trace_t tr; vec3_t end; #ifdef MISSIONPACK vec3_t impactpoint, bouncedir; #endif float r; float u; gentity_t *tent; gentity_t *traceEnt; int i, passent; damage *= s_quadFactor; r = random() * M_PI * 2.0f; u = sin(r) * crandom() * spread * 16; r = cos(r) * crandom() * spread * 16; VectorMA(muzzle, 8192 * 16, forward, end); VectorMA(end, r, right, end); VectorMA(end, u, up, end); passent = ent->s.number; for(i = 0; i < 10; i++) { trap_Trace(&tr, muzzle, NULL, NULL, end, passent, MASK_SHOT); if(tr.surfaceFlags & SURF_NOIMPACT) { return; } traceEnt = &g_entities[tr.entityNum]; // snap the endpos to integers, but nudged towards the line SnapVectorTowards(tr.endpos, muzzle); // send bullet impact if(traceEnt->takedamage && traceEnt->client) { tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_FLESH); tent->s.eventParm = traceEnt->s.number; if(LogAccuracyHit(traceEnt, ent)) { ent->client->accuracy_hits++; } } else { tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_WALL); tent->s.eventParm = DirToByte(tr.plane.normal); } tent->s.otherEntityNum = ent->s.number; if(traceEnt->takedamage) { #ifdef MISSIONPACK if(traceEnt->client && traceEnt->client->invulnerabilityTime > level.time) { if(G_InvulnerabilityEffect(traceEnt, forward, tr.endpos, impactpoint, bouncedir)) { G_BounceProjectile(muzzle, impactpoint, bouncedir, end); VectorCopy(impactpoint, muzzle); // the player can hit him/herself with the bounced rail passent = ENTITYNUM_NONE; } else { VectorCopy(tr.endpos, muzzle); passent = traceEnt->s.number; } continue; } else { #endif G_Damage(traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_MACHINEGUN); #ifdef MISSIONPACK } #endif } break; } } /* ====================================================================== BFG ====================================================================== */ void BFG_Fire(gentity_t * ent) { gentity_t *m; m = fire_bfg(ent, muzzle, forward); m->damage *= s_quadFactor; m->splashDamage *= s_quadFactor; // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics } /* ====================================================================== SHOTGUN ====================================================================== */ // DEFAULT_SHOTGUN_SPREAD and DEFAULT_SHOTGUN_COUNT are in bg_public.h, because // client predicts same spreads #define DEFAULT_SHOTGUN_DAMAGE 10 qboolean ShotgunPellet(vec3_t start, vec3_t end, gentity_t * ent) { trace_t tr; int damage, i, passent; gentity_t *traceEnt; #ifdef MISSIONPACK vec3_t impactpoint, bouncedir; #endif vec3_t tr_start, tr_end; passent = ent->s.number; VectorCopy(start, tr_start); VectorCopy(end, tr_end); for(i = 0; i < 10; i++) { trap_Trace(&tr, tr_start, NULL, NULL, tr_end, passent, MASK_SHOT); traceEnt = &g_entities[tr.entityNum]; // send bullet impact if(tr.surfaceFlags & SURF_NOIMPACT) { return qfalse; } if(traceEnt->takedamage) { damage = DEFAULT_SHOTGUN_DAMAGE * s_quadFactor; #ifdef MISSIONPACK if(traceEnt->client && traceEnt->client->invulnerabilityTime > level.time) { if(G_InvulnerabilityEffect(traceEnt, forward, tr.endpos, impactpoint, bouncedir)) { G_BounceProjectile(tr_start, impactpoint, bouncedir, tr_end); VectorCopy(impactpoint, tr_start); // the player can hit him/herself with the bounced rail passent = ENTITYNUM_NONE; } else { VectorCopy(tr.endpos, tr_start); passent = traceEnt->s.number; } continue; } else { G_Damage(traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_SHOTGUN); if(LogAccuracyHit(traceEnt, ent)) { return qtrue; } } #else G_Damage(traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_SHOTGUN); if(LogAccuracyHit(traceEnt, ent)) { return qtrue; } #endif } return qfalse; } return qfalse; } // this should match CG_ShotgunPattern void ShotgunPattern(vec3_t origin, vec3_t origin2, int seed, gentity_t * ent) { int i; float r, u; vec3_t end; vec3_t forward, right, up; int oldScore; qboolean hitClient = qfalse; // derive the right and up vectors from the forward vector, because // the client won't have any other information VectorNormalize2(origin2, forward); PerpendicularVector(right, forward); CrossProduct(forward, right, up); oldScore = ent->client->ps.persistant[PERS_SCORE]; // generate the "random" spread pattern for(i = 0; i < DEFAULT_SHOTGUN_COUNT; i++) { r = Q_crandom(&seed) * DEFAULT_SHOTGUN_SPREAD * 16; u = Q_crandom(&seed) * DEFAULT_SHOTGUN_SPREAD * 16; VectorMA(origin, 8192 * 16, forward, end); VectorMA(end, r, right, end); VectorMA(end, u, up, end); if(ShotgunPellet(origin, end, ent) && !hitClient) { hitClient = qtrue; ent->client->accuracy_hits++; } } } void weapon_supershotgun_fire(gentity_t * ent) { gentity_t *tent; // send shotgun blast tent = G_TempEntity(muzzle, EV_SHOTGUN); VectorScale(forward, 4096, tent->s.origin2); SnapVector(tent->s.origin2); tent->s.eventParm = rand() & 255; // seed for spread pattern tent->s.otherEntityNum = ent->s.number; ShotgunPattern(tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent); } /* ====================================================================== GRENADE LAUNCHER ====================================================================== */ void weapon_grenadelauncher_fire(gentity_t * ent) { gentity_t *m; // extra vertical velocity forward[2] += 0.2f; VectorNormalize(forward); m = fire_grenade(ent, muzzle, forward); m->damage *= s_quadFactor; m->splashDamage *= s_quadFactor; // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics } /* ====================================================================== ROCKET ====================================================================== */ void Weapon_RocketLauncher_Fire(gentity_t * ent) { gentity_t *m; m = fire_rocket(ent, muzzle, forward); m->damage *= s_quadFactor; m->splashDamage *= s_quadFactor; // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics } /* ====================================================================== PLASMA GUN ====================================================================== */ void Weapon_Plasmagun_Fire(gentity_t * ent) { gentity_t *m; m = fire_plasma(ent, muzzle, forward); m->damage *= s_quadFactor; m->splashDamage *= s_quadFactor; // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics } /* ====================================================================== RAILGUN ====================================================================== */ /* ================= weapon_railgun_fire ================= */ #define MAX_RAIL_HITS 4 void weapon_railgun_fire(gentity_t * ent) { vec3_t end; #ifdef MISSIONPACK vec3_t impactpoint, bouncedir; #endif trace_t trace; gentity_t *tent; gentity_t *traceEnt; int damage; int i; int hits; int unlinked; int passent; gentity_t *unlinkedEntities[MAX_RAIL_HITS]; damage = 100 * s_quadFactor; VectorMA(muzzle, 8192, forward, end); // trace only against the solids, so the railgun will go through people unlinked = 0; hits = 0; passent = ent->s.number; do { trap_Trace(&trace, muzzle, NULL, NULL, end, passent, MASK_SHOT); if(trace.entityNum >= ENTITYNUM_MAX_NORMAL) { break; } traceEnt = &g_entities[trace.entityNum]; if(traceEnt->takedamage) { #ifdef MISSIONPACK if(traceEnt->client && traceEnt->client->invulnerabilityTime > level.time) { if(G_InvulnerabilityEffect(traceEnt, forward, trace.endpos, impactpoint, bouncedir)) { G_BounceProjectile(muzzle, impactpoint, bouncedir, end); // snap the endpos to integers to save net bandwidth, but nudged towards the line SnapVectorTowards(trace.endpos, muzzle); // send railgun beam effect tent = G_TempEntity(trace.endpos, EV_RAILTRAIL); // set player number for custom colors on the railtrail tent->s.clientNum = ent->s.clientNum; VectorCopy(muzzle, tent->s.origin2); // move origin a bit to come closer to the drawn gun muzzle VectorMA(tent->s.origin2, 4, right, tent->s.origin2); VectorMA(tent->s.origin2, -1, up, tent->s.origin2); tent->s.eventParm = 255; // don't make the explosion at the end // VectorCopy(impactpoint, muzzle); // the player can hit him/herself with the bounced rail passent = ENTITYNUM_NONE; } } else { if(LogAccuracyHit(traceEnt, ent)) { hits++; } G_Damage(traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_RAILGUN); } #else if(LogAccuracyHit(traceEnt, ent)) { hits++; } G_Damage(traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_RAILGUN); #endif } if(trace.contents & CONTENTS_SOLID) { break; // we hit something solid enough to stop the beam } // unlink this entity, so the next trace will go past it trap_UnlinkEntity(traceEnt); unlinkedEntities[unlinked] = traceEnt; unlinked++; } while(unlinked < MAX_RAIL_HITS); // link back in any entities we unlinked for(i = 0; i < unlinked; i++) { trap_LinkEntity(unlinkedEntities[i]); } // the final trace endpos will be the terminal point of the rail trail // snap the endpos to integers to save net bandwidth, but nudged towards the line SnapVectorTowards(trace.endpos, muzzle); // send railgun beam effect tent = G_TempEntity(trace.endpos, EV_RAILTRAIL); // set player number for custom colors on the railtrail tent->s.clientNum = ent->s.clientNum; VectorCopy(muzzle, tent->s.origin2); // move origin a bit to come closer to the drawn gun muzzle VectorMA(tent->s.origin2, 4, right, tent->s.origin2); VectorMA(tent->s.origin2, -1, up, tent->s.origin2); // no explosion at end if SURF_NOIMPACT, but still make the trail if(trace.surfaceFlags & SURF_NOIMPACT) { tent->s.eventParm = 255; // don't make the explosion at the end } else { tent->s.eventParm = DirToByte(trace.plane.normal); } tent->s.clientNum = ent->s.clientNum; // give the shooter a reward sound if they have made two railgun hits in a row if(hits == 0) { // complete miss ent->client->accurateCount = 0; } else { // check for "impressive" reward sound ent->client->accurateCount += hits; if(ent->client->accurateCount >= 2) { ent->client->accurateCount -= 2; ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++; // add the sprite over the player's head ent->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP); ent->client->ps.eFlags |= EF_AWARD_IMPRESSIVE; ent->client->rewardTime = level.time + REWARD_SPRITE_TIME; } ent->client->accuracy_hits++; } } /* ====================================================================== GRAPPLING HOOK ====================================================================== */ void Weapon_GrapplingHook_Fire(gentity_t * ent) { if(!ent->client->fireHeld && !ent->client->hook) fire_grapple(ent, muzzle, forward); ent->client->fireHeld = qtrue; } void Weapon_HookFree(gentity_t * ent) { ent->parent->client->hook = NULL; ent->parent->client->ps.pm_flags &= ~PMF_GRAPPLE_PULL; G_FreeEntity(ent); } void Weapon_HookThink(gentity_t * ent) { if(ent->enemy) { vec3_t v, oldorigin; VectorCopy(ent->r.currentOrigin, oldorigin); v[0] = ent->enemy->r.currentOrigin[0] + (ent->enemy->r.mins[0] + ent->enemy->r.maxs[0]) * 0.5; v[1] = ent->enemy->r.currentOrigin[1] + (ent->enemy->r.mins[1] + ent->enemy->r.maxs[1]) * 0.5; v[2] = ent->enemy->r.currentOrigin[2] + (ent->enemy->r.mins[2] + ent->enemy->r.maxs[2]) * 0.5; SnapVectorTowards(v, oldorigin); // save net bandwidth G_SetOrigin(ent, v); } VectorCopy(ent->r.currentOrigin, ent->parent->client->ps.grapplePoint); } /* ====================================================================== LIGHTNING GUN ====================================================================== */ void Weapon_LightningFire(gentity_t * ent) { trace_t tr; vec3_t end; #ifdef MISSIONPACK vec3_t impactpoint, bouncedir; #endif gentity_t *traceEnt, *tent; int damage, i, passent; damage = 8 * s_quadFactor; passent = ent->s.number; for(i = 0; i < 10; i++) { VectorMA(muzzle, LIGHTNING_RANGE, forward, end); trap_Trace(&tr, muzzle, NULL, NULL, end, passent, MASK_SHOT); #ifdef MISSIONPACK // if not the first trace (the lightning bounced of an invulnerability sphere) if(i) { // add bounced off lightning bolt temp entity // the first lightning bolt is a cgame only visual // tent = G_TempEntity(muzzle, EV_LIGHTNINGBOLT); VectorCopy(tr.endpos, end); SnapVector(end); VectorCopy(end, tent->s.origin2); } #endif if(tr.entityNum == ENTITYNUM_NONE) { return; } traceEnt = &g_entities[tr.entityNum]; if(traceEnt->takedamage) { #ifdef MISSIONPACK if(traceEnt->client && traceEnt->client->invulnerabilityTime > level.time) { if(G_InvulnerabilityEffect(traceEnt, forward, tr.endpos, impactpoint, bouncedir)) { G_BounceProjectile(muzzle, impactpoint, bouncedir, end); VectorCopy(impactpoint, muzzle); VectorSubtract(end, impactpoint, forward); VectorNormalize(forward); // the player can hit him/herself with the bounced lightning passent = ENTITYNUM_NONE; } else { VectorCopy(tr.endpos, muzzle); passent = traceEnt->s.number; } continue; } else { G_Damage(traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_LIGHTNING); } #else G_Damage(traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_LIGHTNING); #endif } if(traceEnt->takedamage && traceEnt->client) { tent = G_TempEntity(tr.endpos, EV_MISSILE_HIT); tent->s.otherEntityNum = traceEnt->s.number; tent->s.eventParm = DirToByte(tr.plane.normal); tent->s.weapon = ent->s.weapon; if(LogAccuracyHit(traceEnt, ent)) { ent->client->accuracy_hits++; } } else if(!(tr.surfaceFlags & SURF_NOIMPACT)) { tent = G_TempEntity(tr.endpos, EV_MISSILE_MISS); tent->s.eventParm = DirToByte(tr.plane.normal); } break; } } #ifdef MISSIONPACK /* ====================================================================== NAILGUN ====================================================================== */ void Weapon_Nailgun_Fire(gentity_t * ent) { gentity_t *m; int count; for(count = 0; count < NUM_NAILSHOTS; count++) { m = fire_nail(ent, muzzle, forward, right, up); m->damage *= s_quadFactor; m->splashDamage *= s_quadFactor; } // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics } /* ====================================================================== PROXIMITY MINE LAUNCHER ====================================================================== */ void weapon_proxlauncher_fire(gentity_t * ent) { gentity_t *m; // extra vertical velocity forward[2] += 0.2f; VectorNormalize(forward); m = fire_prox(ent, muzzle, forward); m->damage *= s_quadFactor; m->splashDamage *= s_quadFactor; // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics } #endif //====================================================================== /* =============== LogAccuracyHit =============== */ qboolean LogAccuracyHit(gentity_t * target, gentity_t * attacker) { if(!target->takedamage) { 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, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint) { VectorCopy(ent->s.pos.trBase, muzzlePoint); muzzlePoint[2] += ent->client->ps.viewheight; VectorMA(muzzlePoint, 14, forward, muzzlePoint); // snap to integer coordinates for more efficient network bandwidth usage SnapVector(muzzlePoint); } /* =============== CalcMuzzlePointOrigin set muzzle location relative to pivoting eye =============== */ void CalcMuzzlePointOrigin(gentity_t * ent, vec3_t origin, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint) { VectorCopy(ent->s.pos.trBase, muzzlePoint); muzzlePoint[2] += ent->client->ps.viewheight; VectorMA(muzzlePoint, 14, forward, muzzlePoint); // snap to integer coordinates for more efficient network bandwidth usage SnapVector(muzzlePoint); } /* =============== FireWeapon =============== */ void FireWeapon(gentity_t * ent) { if(ent->client->ps.powerups[PW_QUAD]) { s_quadFactor = g_quadfactor.value; } else { s_quadFactor = 1; } #ifdef MISSIONPACK if(ent->client->persistantPowerup && ent->client->persistantPowerup->item && ent->client->persistantPowerup->item->giTag == PW_DOUBLER) { s_quadFactor *= 2; } #endif // track shots taken for accuracy tracking. Grapple is not a weapon and gauntet is just not tracked if(ent->s.weapon != WP_GRAPPLING_HOOK && ent->s.weapon != WP_GAUNTLET) { #ifdef MISSIONPACK if(ent->s.weapon == WP_NAILGUN) { ent->client->accuracy_shots += NUM_NAILSHOTS; } else { ent->client->accuracy_shots++; } #else ent->client->accuracy_shots++; #endif } // set aiming directions AngleVectors(ent->client->ps.viewangles, forward, right, up); CalcMuzzlePointOrigin(ent, ent->client->oldOrigin, forward, right, up, muzzle); // fire the specific weapon switch (ent->s.weapon) { case WP_GAUNTLET: Weapon_Gauntlet(ent); break; case WP_LIGHTNING: Weapon_LightningFire(ent); break; case WP_SHOTGUN: weapon_supershotgun_fire(ent); break; case WP_MACHINEGUN: if(g_gametype.integer != GT_TEAM) { Bullet_Fire(ent, MACHINEGUN_SPREAD, MACHINEGUN_DAMAGE); } else { Bullet_Fire(ent, MACHINEGUN_SPREAD, MACHINEGUN_TEAM_DAMAGE); } break; case WP_GRENADE_LAUNCHER: weapon_grenadelauncher_fire(ent); break; case WP_ROCKET_LAUNCHER: Weapon_RocketLauncher_Fire(ent); break; case WP_PLASMAGUN: Weapon_Plasmagun_Fire(ent); break; case WP_RAILGUN: weapon_railgun_fire(ent); break; case WP_BFG: BFG_Fire(ent); break; case WP_GRAPPLING_HOOK: Weapon_GrapplingHook_Fire(ent); break; #ifdef MISSIONPACK case WP_NAILGUN: Weapon_Nailgun_Fire(ent); break; case WP_PROX_LAUNCHER: weapon_proxlauncher_fire(ent); break; case WP_CHAINGUN: Bullet_Fire(ent, CHAINGUN_SPREAD, MACHINEGUN_DAMAGE); break; #endif default: // FIXME G_Error( "Bad ent->s.weapon" ); break; } } #ifdef MISSIONPACK /* =============== KamikazeRadiusDamage =============== */ static void KamikazeRadiusDamage(vec3_t origin, gentity_t * attacker, float damage, float radius) { float dist; gentity_t *ent; int entityList[MAX_GENTITIES]; int numListedEntities; vec3_t mins, maxs; vec3_t v; vec3_t dir; int i, e; if(radius < 1) { radius = 1; } for(i = 0; i < 3; i++) { mins[i] = origin[i] - radius; maxs[i] = origin[i] + radius; } numListedEntities = trap_EntitiesInBox(mins, maxs, entityList, MAX_GENTITIES); for(e = 0; e < numListedEntities; e++) { ent = &g_entities[entityList[e]]; if(!ent->takedamage) { continue; } // dont hit things we have already hit if(ent->kamikazeTime > level.time) { continue; } // find the distance from the edge of the bounding box for(i = 0; i < 3; i++) { if(origin[i] < ent->r.absmin[i]) { v[i] = ent->r.absmin[i] - origin[i]; } else if(origin[i] > ent->r.absmax[i]) { v[i] = origin[i] - ent->r.absmax[i]; } else { v[i] = 0; } } dist = VectorLength(v); if(dist >= radius) { continue; } // if( CanDamage (ent, origin) ) { VectorSubtract(ent->r.currentOrigin, origin, dir); // push the center of mass higher than the origin so players // get knocked into the air more dir[2] += 24; G_Damage(ent, NULL, attacker, dir, origin, damage, DAMAGE_RADIUS | DAMAGE_NO_TEAM_PROTECTION, MOD_KAMIKAZE); ent->kamikazeTime = level.time + 3000; // } } } /* =============== KamikazeShockWave =============== */ static void KamikazeShockWave(vec3_t origin, gentity_t * attacker, float damage, float push, float radius) { float dist; gentity_t *ent; int entityList[MAX_GENTITIES]; int numListedEntities; vec3_t mins, maxs; vec3_t v; vec3_t dir; int i, e; if(radius < 1) radius = 1; for(i = 0; i < 3; i++) { mins[i] = origin[i] - radius; maxs[i] = origin[i] + radius; } numListedEntities = trap_EntitiesInBox(mins, maxs, entityList, MAX_GENTITIES); for(e = 0; e < numListedEntities; e++) { ent = &g_entities[entityList[e]]; // dont hit things we have already hit if(ent->kamikazeShockTime > level.time) { continue; } // find the distance from the edge of the bounding box for(i = 0; i < 3; i++) { if(origin[i] < ent->r.absmin[i]) { v[i] = ent->r.absmin[i] - origin[i]; } else if(origin[i] > ent->r.absmax[i]) { v[i] = origin[i] - ent->r.absmax[i]; } else { v[i] = 0; } } dist = VectorLength(v); if(dist >= radius) { continue; } // if( CanDamage (ent, origin) ) { VectorSubtract(ent->r.currentOrigin, origin, dir); dir[2] += 24; G_Damage(ent, NULL, attacker, dir, origin, damage, DAMAGE_RADIUS | DAMAGE_NO_TEAM_PROTECTION, MOD_KAMIKAZE); // dir[2] = 0; VectorNormalize(dir); if(ent->client) { ent->client->ps.velocity[0] = dir[0] * push; ent->client->ps.velocity[1] = dir[1] * push; ent->client->ps.velocity[2] = 100; } ent->kamikazeShockTime = level.time + 3000; // } } } /* =============== KamikazeDamage =============== */ static void KamikazeDamage(gentity_t * self) { int i; float t; gentity_t *ent; vec3_t newangles; self->count += 100; if(self->count >= KAMI_SHOCKWAVE_STARTTIME) { // shockwave push back t = self->count - KAMI_SHOCKWAVE_STARTTIME; KamikazeShockWave(self->s.pos.trBase, self->activator, 25, 400, (int)(float)t * KAMI_SHOCKWAVE_MAXRADIUS / (KAMI_SHOCKWAVE_ENDTIME - KAMI_SHOCKWAVE_STARTTIME)); } // if(self->count >= KAMI_EXPLODE_STARTTIME) { // do our damage t = self->count - KAMI_EXPLODE_STARTTIME; KamikazeRadiusDamage(self->s.pos.trBase, self->activator, 400, (int)(float)t * KAMI_BOOMSPHERE_MAXRADIUS / (KAMI_IMPLODE_STARTTIME - KAMI_EXPLODE_STARTTIME)); } // either cycle or kill self if(self->count >= KAMI_SHOCKWAVE_ENDTIME) { G_FreeEntity(self); return; } self->nextthink = level.time + 100; // add earth quake effect newangles[0] = crandom() * 2; newangles[1] = crandom() * 2; newangles[2] = 0; for(i = 0; i < MAX_CLIENTS; i++) { ent = &g_entities[i]; if(!ent->inuse) continue; if(!ent->client) continue; if(ent->client->ps.groundEntityNum != ENTITYNUM_NONE) { ent->client->ps.velocity[0] += crandom() * 120; ent->client->ps.velocity[1] += crandom() * 120; ent->client->ps.velocity[2] = 30 + random() * 25; } ent->client->ps.delta_angles[0] += ANGLE2SHORT(newangles[0] - self->movedir[0]); ent->client->ps.delta_angles[1] += ANGLE2SHORT(newangles[1] - self->movedir[1]); ent->client->ps.delta_angles[2] += ANGLE2SHORT(newangles[2] - self->movedir[2]); } VectorCopy(newangles, self->movedir); } /* =============== G_StartKamikaze =============== */ void G_StartKamikaze(gentity_t * ent) { gentity_t *explosion; gentity_t *te; vec3_t snapped; // start up the explosion logic explosion = G_Spawn(); explosion->s.eType = ET_EVENTS + EV_KAMIKAZE; explosion->eventTime = level.time; if(ent->client) { VectorCopy(ent->s.pos.trBase, snapped); } else { VectorCopy(ent->activator->s.pos.trBase, snapped); } SnapVector(snapped); // save network bandwidth G_SetOrigin(explosion, snapped); explosion->classname = "kamikaze"; explosion->s.pos.trType = TR_STATIONARY; explosion->kamikazeTime = level.time; explosion->think = KamikazeDamage; explosion->nextthink = level.time + 100; explosion->count = 0; VectorClear(explosion->movedir); trap_LinkEntity(explosion); if(ent->client) { // explosion->activator = ent; // ent->s.eFlags &= ~EF_KAMIKAZE; // nuke the guy that used it G_Damage(ent, ent, ent, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_KAMIKAZE); } else { if(!strcmp(ent->activator->classname, "bodyque")) { explosion->activator = &g_entities[ent->activator->r.ownerNum]; } else { explosion->activator = ent->activator; } } // play global sound at all clients te = G_TempEntity(snapped, EV_GLOBAL_TEAM_SOUND); te->r.svFlags |= SVF_BROADCAST; te->s.eventParm = GTS_KAMIKAZE; } #endif