/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2002-2021 Q3Rally Team (Per Thormann - q3rally@gmail.com) This file is part of q3rally source code. q3rally 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. q3rally 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 q3rally; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // #include "g_local.h" #define MISSILE_PRESTEP_TIME 50 /* ================= Missile_Smooth_H ================= */ void Missile_Smooth_H( gentity_t *ent, vec3_t origin, trace_t *tr ) { VectorCopy( origin,ent->s.pos.trBase ); ent->s.pos.trTime = level.time; } /* ================ G_BounceMissile ================ */ void G_BounceMissile( gentity_t *ent, trace_t *trace ) { vec3_t velocity; float dot; int hitTime; // STONELANCE if ( ent->s.eFlags & EF_BOUNCE_NONE ){ ent->clipmask = MASK_SHOT; G_SetOrigin( ent, trace->endpos ); return; } // END // reflect the velocity on the trace plane hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction; BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity ); dot = DotProduct( velocity, trace->plane.normal ); VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta ); if ( ent->s.eFlags & EF_BOUNCE_HALF ) { VectorScale( ent->s.pos.trDelta, 0.65, ent->s.pos.trDelta ); // check for stop if ( trace->plane.normal[2] > 0.2 && VectorLength( ent->s.pos.trDelta ) < 40 ) { G_SetOrigin( ent, trace->endpos ); ent->s.time = level.time / 4; return; } } VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin); VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); ent->s.pos.trTime = level.time; } /* ================= G_ExplodeCluster ================= */ void G_ExplodeCluster( gentity_t *ent ){ vec3_t dir; VectorSet(dir, 0, 11, -66); fire_cluster_grenade2(ent->parent, ent->r.currentOrigin, dir); ent->parent->s.pos.trType = TR_GRAVITY; VectorSet(dir, 22, 22, -66); fire_cluster_grenade2(ent->parent, ent->r.currentOrigin, dir); VectorSet(dir, -22, 22, -66); fire_cluster_grenade2(ent->parent, ent->r.currentOrigin, dir); VectorSet(dir, 0, -11, -66); fire_cluster_grenade2(ent->parent, ent->r.currentOrigin, dir); VectorSet(dir, -11, -11, -66); fire_cluster_grenade2(ent->parent, ent->r.currentOrigin, dir); VectorSet(dir, 11, -11, -66); fire_cluster_grenade2(ent->parent, ent->r.currentOrigin, dir); } /* ================ G_ExplodeMissile Explode a missile without an impact ================ */ void G_ExplodeMissile( gentity_t *ent ) { vec3_t dir; vec3_t origin; ent->takedamage = qfalse; //TBB differs from q3a BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); SnapVector( origin ); G_SetOrigin( ent, origin ); // we don't have a valid direction, so just point straight up dir[0] = dir[1] = 0; dir[2] = 1; ent->s.eType = ET_GENERAL; G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( dir ) ); ent->freeAfterEvent = qtrue; // splash damage if ( ent->splashDamage ) { // STONELANCE CheckForOil(ent->r.currentOrigin, ent->splashRadius); // END if( G_RadiusDamage( ent->r.currentOrigin, ent->parent, ent->splashDamage, ent->splashRadius, ent , ent->splashMethodOfDeath ) ) { g_entities[ent->r.ownerNum].client->accuracy_hits++; } } //TBB - added this line for reference, Q3R { if (!strcmp(ent->classname, "fire_cluster_grenade")) G_ExplodeCluster( ent ); } //TBB - added this line for reference, Q3R trap_LinkEntity( ent ); } //Q3Rally Code Start /* ================ G_MissileDie Destroy a missile ================ */ void G_MissileDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { if (inflictor == self) return; self->takedamage = qfalse; self->think = G_ExplodeMissile; self->nextthink = level.time + 10; } /* ================= fire_flame ================= */ gentity_t *fire_flame (gentity_t *self, vec3_t start, vec3_t dir) { gentity_t *bolt; VectorNormalize (dir); bolt = G_Spawn(); bolt->classname = "flame"; bolt->nextthink = level.time + 1500; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_FLAME_THROWER; bolt->r.ownerNum = self->s.number; bolt->parent = self; bolt->damage = 30; bolt->splashDamage = 25; bolt->splashRadius = 45; bolt->methodOfDeath = MOD_FLAME_THROWER; bolt->splashMethodOfDeath = MOD_PLASMA_SPLASH; bolt->clipmask = MASK_SHOT; bolt->s.pos.trType = TR_LINEAR; bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;// move a bit on the very first frame VectorCopy( start, bolt->s.pos.trBase ); VectorScale( dir, 300, bolt->s.pos.trDelta ); SnapVector( bolt->s.pos.trDelta );// save net bandwidth VectorCopy (start, bolt->r.currentOrigin); return bolt; } //TBB /* ================= fire_cluster_flame ================= */ gentity_t *fire_cluster_flame (gentity_t *self, vec3_t start, vec3_t dir) { gentity_t *bolt; VectorNormalize (dir); bolt = G_Spawn(); bolt->classname = "fire_cluster_flame"; bolt->nextthink = level.time + 500; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_FLAME_THROWER; bolt->r.ownerNum = self->s.number; bolt->parent = self; bolt->damage = 10; bolt->splashDamage = 5; bolt->splashRadius = 25; bolt->methodOfDeath = MOD_FLAME_THROWER; bolt->splashMethodOfDeath = MOD_PLASMA_SPLASH; bolt->clipmask = MASK_SHOT; bolt->s.pos.trType = TR_LINEAR; bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;// move a bit on the very first frame VectorCopy( start, bolt->s.pos.trBase ); VectorScale( dir, 400, bolt->s.pos.trDelta ); SnapVector( bolt->s.pos.trDelta );// save net bandwidth VectorCopy (start, bolt->r.currentOrigin); return bolt; //.5s & 400u/s is 200units ahead } //TBB FIN //Q3Rally Code END #ifdef MISSIONPACK /* ================ ProximityMine_Explode ================ */ static void ProximityMine_Explode( gentity_t *mine ) { G_ExplodeMissile( mine ); // if the prox mine has a trigger free it if (mine->activator) { G_FreeEntity(mine->activator); mine->activator = NULL; } } /* ================ ProximityMine_Die ================ */ static void ProximityMine_Die( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { ent->think = ProximityMine_Explode; ent->nextthink = level.time + 1; } /* ================ ProximityMine_Trigger ================ */ void ProximityMine_Trigger( gentity_t *trigger, gentity_t *other, trace_t *trace ) { vec3_t v; gentity_t *mine; if( !other->client ) { return; } // trigger is a cube, do a distance test now to act as if it's a sphere VectorSubtract( trigger->s.pos.trBase, other->s.pos.trBase, v ); if( VectorLength( v ) > trigger->parent->splashRadius ) { return; } if ( g_gametype.integer >= GT_TEAM ) { // don't trigger same team mines if (trigger->parent->s.generic1 == other->client->sess.sessionTeam) { return; } } // ok, now check for ability to damage so we don't get triggered through walls, closed doors, etc... if( !CanDamage( other, trigger->s.pos.trBase ) ) { return; } // trigger the mine! mine = trigger->parent; mine->s.loopSound = 0; G_AddEvent( mine, EV_PROXIMITY_MINE_TRIGGER, 0 ); mine->nextthink = level.time + 500; G_FreeEntity( trigger ); } /* ================ ProximityMine_Activate ================ */ static void ProximityMine_Activate( gentity_t *ent ) { gentity_t *trigger; float r; ent->think = ProximityMine_Explode; ent->nextthink = level.time + g_proxMineTimeout.integer; ent->takedamage = qtrue; ent->health = 1; ent->die = ProximityMine_Die; ent->s.loopSound = G_SoundIndex( "sound/weapons/proxmine/wstbtick.wav" ); // build the proximity trigger trigger = G_Spawn (); trigger->classname = "proxmine_trigger"; r = ent->splashRadius; VectorSet( trigger->r.mins, -r, -r, -r ); VectorSet( trigger->r.maxs, r, r, r ); G_SetOrigin( trigger, ent->s.pos.trBase ); trigger->parent = ent; trigger->r.contents = CONTENTS_TRIGGER; trigger->touch = ProximityMine_Trigger; trap_LinkEntity (trigger); // set pointer to trigger so the entity can be freed when the mine explodes ent->activator = trigger; } /* ================ ProximityMine_ExplodeOnPlayer ================ */ static void ProximityMine_ExplodeOnPlayer( gentity_t *mine ) { gentity_t *player; player = mine->enemy; player->client->ps.eFlags &= ~EF_TICKING; if ( player->client->invulnerabilityTime > level.time ) { G_Damage( player, mine->parent, mine->parent, vec3_origin, mine->s.origin, 1000, DAMAGE_NO_KNOCKBACK, MOD_JUICED ); player->client->invulnerabilityTime = 0; G_TempEntity( player->client->ps.origin, EV_JUICED ); } else { G_SetOrigin( mine, player->s.pos.trBase ); // make sure the explosion gets to the client mine->r.svFlags &= ~SVF_NOCLIENT; mine->splashMethodOfDeath = MOD_PROXIMITY_MINE; G_ExplodeMissile( mine ); } } /* ================ ProximityMine_Player ================ */ static void ProximityMine_Player( gentity_t *mine, gentity_t *player ) { if( mine->s.eFlags & EF_NODRAW ) { return; } G_AddEvent( mine, EV_PROXIMITY_MINE_STICK, 0 ); if( player->s.eFlags & EF_TICKING ) { player->activator->splashDamage += mine->splashDamage; player->activator->splashRadius *= 1.50; mine->think = G_FreeEntity; mine->nextthink = level.time; return; } player->client->ps.eFlags |= EF_TICKING; player->activator = mine; mine->s.eFlags |= EF_NODRAW; mine->r.svFlags |= SVF_NOCLIENT; mine->s.pos.trType = TR_LINEAR; VectorClear( mine->s.pos.trDelta ); mine->enemy = player; mine->think = ProximityMine_ExplodeOnPlayer; if ( player->client->invulnerabilityTime > level.time ) { mine->nextthink = level.time + 2 * 1000; } else { mine->nextthink = level.time + 10 * 1000; } } #endif /* ================ G_MissileImpact ================ */ void G_MissileImpact( gentity_t *ent, trace_t *trace ) { gentity_t *other; qboolean hitClient = qfalse; #ifdef MISSIONPACK vec3_t forward, impactpoint, bouncedir; int eFlags; #endif other = &g_entities[trace->entityNum]; // STONELANCE - support for extra bboxes if (other->flags & FL_EXTRA_BBOX) other = &g_entities[other->r.ownerNum]; // END // check for bounce if ( !other->takedamage && // STONELANCE // ( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) ) { ( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF | EF_BOUNCE_NONE) ) ) { // END G_BounceMissile( ent, trace ); G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 ); return; } ent->takedamage = qfalse; #ifdef MISSIONPACK if ( other->takedamage ) { if ( ent->s.weapon != WP_PROX_LAUNCHER ) { if ( other->client && other->client->invulnerabilityTime > level.time ) { // VectorCopy( ent->s.pos.trDelta, forward ); VectorNormalize( forward ); if (G_InvulnerabilityEffect( other, forward, ent->s.pos.trBase, impactpoint, bouncedir )) { VectorCopy( bouncedir, trace->plane.normal ); eFlags = ent->s.eFlags & EF_BOUNCE_HALF; ent->s.eFlags &= ~EF_BOUNCE_HALF; G_BounceMissile( ent, trace ); ent->s.eFlags |= eFlags; } ent->target_ent = other; return; } } } #endif // impact damage if (other->takedamage) { // FIXME: wrong damage direction? if ( ent->damage ) { vec3_t velocity; if( LogAccuracyHit( other, &g_entities[ent->r.ownerNum] ) ) { g_entities[ent->r.ownerNum].client->accuracy_hits++; hitClient = qtrue; } BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, velocity ); if ( VectorLength( velocity ) == 0 ) { velocity[2] = 1; // stepped on a grenade } // STONELANCE /* G_Damage (other, ent, &g_entities[ent->r.ownerNum], velocity, ent->s.origin, ent->damage, 0, ent->methodOfDeath); */ if (ent->s.weapon == RWP_MINE){ G_Damage (other, ent, &g_entities[ent->s.otherEntityNum2], velocity, ent->s.origin, ent->damage, DAMAGE_WEAPON, ent->methodOfDeath); } else { G_Damage (other, ent, &g_entities[ent->r.ownerNum], velocity, ent->s.origin, ent->damage, DAMAGE_WEAPON, ent->methodOfDeath); } // END } } #ifdef MISSIONPACK if( ent->s.weapon == WP_PROX_LAUNCHER ) { if( ent->s.pos.trType != TR_GRAVITY ) { return; } // if it's a player, stick it on to them (flag them and remove this entity) if( other->s.eType == ET_PLAYER && other->health > 0 ) { ProximityMine_Player( ent, other ); return; } SnapVectorTowards( trace->endpos, ent->s.pos.trBase ); G_SetOrigin( ent, trace->endpos ); ent->s.pos.trType = TR_STATIONARY; VectorClear( ent->s.pos.trDelta ); G_AddEvent( ent, EV_PROXIMITY_MINE_STICK, trace->surfaceFlags ); ent->think = ProximityMine_Activate; ent->nextthink = level.time + 2000; vectoangles( trace->plane.normal, ent->s.angles ); ent->s.angles[0] += 90; // link the prox mine to the other entity ent->enemy = other; ent->die = ProximityMine_Die; VectorCopy(trace->plane.normal, ent->movedir); VectorSet(ent->r.mins, -4, -4, -4); VectorSet(ent->r.maxs, 4, 4, 4); trap_LinkEntity(ent); return; } #endif // STONELANCE - no more hook /* if (!strcmp(ent->classname, "hook")) { gentity_t *nent; vec3_t v; nent = G_Spawn(); if ( other->takedamage && other->client ) { G_AddEvent( nent, EV_MISSILE_HIT, DirToByte( trace->plane.normal ) ); nent->s.otherEntityNum = other->s.number; ent->enemy = other; v[0] = other->r.currentOrigin[0] + (other->r.mins[0] + other->r.maxs[0]) * 0.5; v[1] = other->r.currentOrigin[1] + (other->r.mins[1] + other->r.maxs[1]) * 0.5; v[2] = other->r.currentOrigin[2] + (other->r.mins[2] + other->r.maxs[2]) * 0.5; SnapVectorTowards( v, ent->s.pos.trBase ); // save net bandwidth } else { VectorCopy(trace->endpos, v); G_AddEvent( nent, EV_MISSILE_MISS, DirToByte( trace->plane.normal ) ); ent->enemy = NULL; } SnapVectorTowards( v, ent->s.pos.trBase ); // save net bandwidth nent->freeAfterEvent = qtrue; // change over to a normal entity right at the point of impact nent->s.eType = ET_GENERAL; ent->s.eType = ET_GRAPPLE; G_SetOrigin( ent, v ); G_SetOrigin( nent, v ); ent->think = Weapon_HookThink; ent->nextthink = level.time + FRAMETIME; ent->parent->client->ps.pm_flags |= PMF_GRAPPLE_PULL; VectorCopy( ent->r.currentOrigin, ent->parent->client->ps.grapplePoint); trap_LinkEntity( ent ); trap_LinkEntity( nent ); return; } */ // END // is it cheaper in bandwidth to just remove this ent and create a new // one, rather than changing the missile into the explosion? if ( other->takedamage && other->client ) { G_AddEvent( ent, EV_MISSILE_HIT, DirToByte( trace->plane.normal ) ); ent->s.otherEntityNum = other->s.number; } else if( trace->surfaceFlags & SURF_METALSTEPS ) { G_AddEvent( ent, EV_MISSILE_MISS_METAL, DirToByte( trace->plane.normal ) ); } else { G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( trace->plane.normal ) ); } ent->freeAfterEvent = qtrue; // change over to a normal entity right at the point of impact ent->s.eType = ET_GENERAL; SnapVectorTowards( trace->endpos, ent->s.pos.trBase ); // save net bandwidth G_SetOrigin( ent, trace->endpos ); // splash damage (doesn't apply to person directly hit) if ( ent->splashDamage ) { // STONELANCE CheckForOil(ent->r.currentOrigin, ent->splashRadius); // END if( G_RadiusDamage( trace->endpos, ent->parent, ent->splashDamage, ent->splashRadius, other, ent->splashMethodOfDeath ) ) { if( !hitClient ) { g_entities[ent->r.ownerNum].client->accuracy_hits++; } } } if (!strcmp(ent->classname,"fire_cluster_grenade")) { G_ExplodeCluster( ent ); } trap_LinkEntity( ent ); } /* ================ G_RunMissile ================ */ void G_RunMissile( gentity_t *ent ) { vec3_t origin; trace_t tr; int passent; // get current position BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); // if this missile bounced off an invulnerability sphere if ( ent->target_ent ) { passent = ent->target_ent->s.number; } #ifdef MISSIONPACK // prox mines that left the owner bbox will attach to anything, even the owner else if (ent->s.weapon == WP_PROX_LAUNCHER && ent->count) { passent = ENTITYNUM_NONE; } #endif else { // ignore interactions with the missile owner passent = ent->r.ownerNum; } // trace a line from the previous position to the current position trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, passent, ent->clipmask ); if ( tr.startsolid || tr.allsolid ) { // make sure the tr.entityNum is set to the entity we're stuck in trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, passent, ent->clipmask ); tr.fraction = 0; } else { VectorCopy( tr.endpos, ent->r.currentOrigin ); } Missile_Smooth_H(ent,origin,&tr); trap_LinkEntity( ent ); if ( tr.fraction != 1 ) { // never explode or bounce on sky if ( tr.surfaceFlags & SURF_NOIMPACT ) { // If grapple, reset owner if (ent->parent && ent->parent->client && ent->parent->client->hook == ent) { ent->parent->client->hook = NULL; } G_FreeEntity( ent ); return; } G_MissileImpact( ent, &tr ); if ( ent->s.eType != ET_MISSILE ) { return; // exploded } } #ifdef MISSIONPACK // if the prox mine wasn't yet outside the player body if (ent->s.weapon == WP_PROX_LAUNCHER && !ent->count) { // check if the prox mine is outside the owner bbox trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, ENTITYNUM_NONE, ent->clipmask ); if (!tr.startsolid || tr.entityNum != ent->r.ownerNum) { ent->count = 1; } } #endif // check think function after bouncing G_RunThink( ent ); } /* ================= fire_plasma ================= */ gentity_t *fire_plasma (gentity_t *self, vec3_t start, vec3_t dir) { gentity_t *bolt; VectorNormalize (dir); bolt = G_Spawn(); bolt->classname = "plasma"; bolt->nextthink = level.time + 10000; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_PLASMAGUN; bolt->r.ownerNum = self->s.number; bolt->parent = self; bolt->damage = 20; bolt->splashDamage = 15; bolt->splashRadius = 20; bolt->methodOfDeath = MOD_PLASMA; bolt->splashMethodOfDeath = MOD_PLASMA_SPLASH; bolt->clipmask = MASK_SHOT; bolt->target_ent = NULL; bolt->s.pos.trType = TR_LINEAR; bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame VectorCopy( start, bolt->s.pos.trBase ); // STONELANCE // VectorScale( dir, 2000, bolt->s.pos.trDelta ); VectorScale( dir, 4000, bolt->s.pos.trDelta ); // END SnapVector( bolt->s.pos.trDelta ); // save net bandwidth VectorCopy (start, bolt->r.currentOrigin); return bolt; } //============================================================================= //fire_plasma_bounce //============================================================================= gentity_t *fire_plasma_bounce (gentity_t *self, vec3_t start, vec3_t dir) { gentity_t *bolt; VectorNormalize (dir); bolt = G_Spawn(); bolt->classname = "plasma"; bolt->nextthink = level.time + 10000; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_PLASMAGUN; bolt->r.ownerNum = self->s.number; bolt->parent = self; bolt->damage = 20; bolt->splashDamage = 15; bolt->splashRadius = 20; bolt->methodOfDeath = MOD_PLASMA; bolt->splashMethodOfDeath = MOD_PLASMA_SPLASH; bolt->clipmask = MASK_SHOT; bolt->target_ent = NULL; bolt->s.eFlags = EF_BOUNCE; bolt->s.pos.trType = TR_LINEAR; bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame VectorCopy( start, bolt->s.pos.trBase ); VectorScale( dir, 2000, bolt->s.pos.trDelta ); SnapVector( bolt->s.pos.trDelta ); // save net bandwidth VectorCopy (start, bolt->r.currentOrigin); return bolt; } /* ================= fire_cluster_grenade ================= */ gentity_t *fire_cluster_grenade (gentity_t *self, vec3_t start, vec3_t dir) { gentity_t *bolt; VectorNormalize (dir); bolt = G_Spawn(); bolt->classname = "fire_cluster_grenade"; bolt->nextthink = level.time + 500; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_GRENADE_LAUNCHER; bolt->s.eFlags = EF_BOUNCE_HALF; bolt->r.ownerNum = self->s.number; bolt->parent = self; //bolt->damage = 100; //TBB - too high for short reload bolt->damage = 40; //TBB //bolt->splashDamage = 100; //TBB - too high for short reload bolt->splashDamage = 20; //TBB bolt->splashRadius = 150; bolt->methodOfDeath = MOD_GRENADE; bolt->splashMethodOfDeath = MOD_GRENADE_SPLASH; bolt->clipmask = MASK_SHOT; bolt->target_ent = NULL; bolt->s.pos.trType = TR_GRAVITY; bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame VectorCopy( start, bolt->s.pos.trBase ); VectorScale( dir, 1200, bolt->s.pos.trDelta ); //TBB - speed is 2000 in g_weapon.c SnapVector( bolt->s.pos.trDelta ); // save net bandwidth VectorCopy (start, bolt->r.currentOrigin); return bolt; } /* ====================== fire_cluster_grenade2 ====================== */ gentity_t *fire_cluster_grenade2 (gentity_t *self, vec3_t start, vec3_t dir) { gentity_t *bolt; VectorNormalize (dir); bolt = G_Spawn(); bolt->classname = "grenade"; //bolt->nextthink = level.time + 200; bolt->nextthink = level.time + 1200; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_GRENADE_LAUNCHER; //bolt->s.eFlags = EF_BOUNCE_HALF; bolt->s.eFlags = EF_BOUNCE_NONE; bolt->r.ownerNum = self->s.number; bolt->parent = self; //bolt->damage = 100; //TBB - too high for short reload bolt->damage = 80; //bolt->splashDamage = 80; //TBB - too high bolt->splashDamage = 60; //TBB bolt->splashRadius = 150; bolt->methodOfDeath = MOD_GRENADE; bolt->splashMethodOfDeath = MOD_GRENADE_SPLASH; bolt->clipmask = MASK_SHOT; bolt->target_ent = NULL; bolt->s.pos.trType = TR_GRAVITY; bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame VectorCopy( start, bolt->s.pos.trBase ); VectorScale( dir, 600, bolt->s.pos.trDelta ); SnapVector( bolt->s.pos.trDelta ); // save net bandwidth VectorCopy (start, bolt->r.currentOrigin); return bolt; } /* ================= fire_grenade ================= */ gentity_t *fire_grenade (gentity_t *self, vec3_t start, vec3_t dir) { gentity_t *bolt; VectorNormalize (dir); bolt = G_Spawn(); bolt->classname = "grenade"; bolt->nextthink = level.time + 2500; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_GRENADE_LAUNCHER; bolt->s.eFlags = EF_BOUNCE_HALF; bolt->r.ownerNum = self->s.number; bolt->parent = self; bolt->damage = 40; bolt->splashDamage = 20; bolt->splashRadius = 150; bolt->methodOfDeath = MOD_GRENADE; bolt->splashMethodOfDeath = MOD_GRENADE_SPLASH; bolt->clipmask = MASK_SHOT; bolt->target_ent = NULL; bolt->s.pos.trType = TR_GRAVITY; bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame VectorCopy( start, bolt->s.pos.trBase ); VectorScale( dir, 800, bolt->s.pos.trDelta ); SnapVector( bolt->s.pos.trDelta ); // save net bandwidth VectorCopy (start, bolt->r.currentOrigin); return bolt; } //============================================================================= /* ================= fire_bfg ================= */ gentity_t *fire_bfg (gentity_t *self, vec3_t start, vec3_t dir) { gentity_t *bolt; VectorNormalize (dir); bolt = G_Spawn(); bolt->classname = "bfg"; bolt->nextthink = level.time + 10000; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_BFG; bolt->r.ownerNum = self->s.number; bolt->parent = self; bolt->damage = 100; bolt->splashDamage = 100; bolt->splashRadius = 120; bolt->methodOfDeath = MOD_BFG; bolt->splashMethodOfDeath = MOD_BFG_SPLASH; bolt->clipmask = MASK_SHOT; bolt->target_ent = NULL; bolt->s.pos.trType = TR_LINEAR; bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame VectorCopy( start, bolt->s.pos.trBase ); // STONELANCE // VectorScale( dir, 2000, bolt->s.pos.trDelta ); VectorScale( dir, 4000, bolt->s.pos.trDelta ); // END SnapVector( bolt->s.pos.trDelta ); // save net bandwidth VectorCopy (start, bolt->r.currentOrigin); return bolt; } //============================================================================= /* ================= fire_rocket ================= */ gentity_t *fire_rocket (gentity_t *self, vec3_t start, vec3_t dir) { gentity_t *bolt; VectorNormalize (dir); bolt = G_Spawn(); bolt->classname = "rocket"; bolt->nextthink = level.time + 15000; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_ROCKET_LAUNCHER; bolt->r.ownerNum = self->s.number; bolt->parent = self; bolt->damage = 80; bolt->splashDamage = 60; bolt->splashRadius = 120; bolt->methodOfDeath = MOD_ROCKET; bolt->splashMethodOfDeath = MOD_ROCKET_SPLASH; bolt->clipmask = MASK_SHOT; bolt->target_ent = NULL; bolt->s.pos.trType = TR_LINEAR; bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame VectorCopy( start, bolt->s.pos.trBase ); VectorScale( dir, 1400, bolt->s.pos.trDelta ); SnapVector( bolt->s.pos.trDelta ); // save net bandwidth VectorCopy (start, bolt->r.currentOrigin); return bolt; } /* ================ G_HomingMissile ================ */ #define ROCKET_SPEED 650 void rocket_think( gentity_t *ent ) { gentity_t *target = NULL; gentity_t *rad = NULL; vec3_t dir, dir2, raddir, start; while ((rad = findradius(rad, ent->r.currentOrigin, 1000)) != NULL) { if (!rad->client) continue; if (rad == ent->parent) continue; if (rad->health <= 0) continue; if (rad->client->sess.sessionTeam == TEAM_SPECTATOR) continue; if ( (g_gametype.integer == GT_TEAM || g_gametype.integer == GT_CTF || g_gametype.integer == GT_DOMINATION ) && rad->client->sess.sessionTeam == rad->parent->client->sess.sessionTeam) continue; if (!visible (ent, rad)) continue; VectorSubtract(rad->r.currentOrigin, ent->r.currentOrigin, raddir); raddir[2] += 16; if ((target == NULL) || (VectorLength(raddir) < VectorLength(dir))) { target = rad; VectorCopy(raddir, dir); } } if (target != NULL) { VectorCopy( ent->r.currentOrigin, start ); VectorCopy( ent->r.currentAngles, dir2 ); VectorNormalize(dir); VectorScale(dir, 0.2, dir); VectorAdd(dir, dir2, dir); VectorNormalize(dir); VectorCopy( start, ent->s.pos.trBase ); VectorScale( dir, 400, ent->s.pos.trDelta ); SnapVector( ent->s.pos.trDelta ); VectorCopy (start, ent->r.currentOrigin); VectorCopy (dir, ent->r.currentAngles); } ent->nextthink = level.time + 100; } /* ================= fire_homing_rocket ================= */ gentity_t *fire_homing_rocket (gentity_t *self, vec3_t start, vec3_t dir) { gentity_t *bolt; VectorNormalize (dir); bolt = G_Spawn(); bolt->classname = "rocket"; bolt->nextthink = level.time + 1; bolt->think = rocket_think; bolt->health = 5; bolt->takedamage = qtrue; bolt->die = G_MissileDie; bolt->r.contents = CONTENTS_BODY; VectorSet(bolt->r.mins, -10, -3, 0); VectorCopy(bolt->r.mins, bolt->r.absmin); VectorSet(bolt->r.maxs, 10, 3, 6); VectorCopy(bolt->r.maxs, bolt->r.absmax); bolt->s.eType = ET_MISSILE; bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_ROCKET_LAUNCHER; bolt->r.ownerNum = self->s.number; bolt->parent = self; bolt->damage = 40; bolt->splashDamage = 60; bolt->splashRadius = 75; bolt->methodOfDeath = MOD_ROCKET; bolt->splashMethodOfDeath = MOD_ROCKET_SPLASH; bolt->clipmask = MASK_SHOT; bolt->target_ent = NULL; bolt->s.pos.trType = TR_LINEAR; bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame VectorCopy( start, bolt->s.pos.trBase ); VectorScale( dir, ROCKET_SPEED, bolt->s.pos.trDelta ); SnapVector( bolt->s.pos.trDelta ); // save net bandwidth VectorCopy (start, bolt->r.currentOrigin); return bolt; } //===================================== // fire mine //===================================== gentity_t *fire_mine( gentity_t *self, vec3_t start, vec3_t dir){ gentity_t *bolt; VectorNormalize (dir); bolt = G_Spawn(); bolt->classname = "mine"; bolt->nextthink = level.time + 7500; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = RWP_MINE; bolt->s.eFlags = EF_BOUNCE_NONE; // bolt->r.ownerNum = self->s.number; bolt->s.otherEntityNum2 = self->s.number; // want to be able to run over your own mines bolt->parent = self; bolt->damage = 100; bolt->splashDamage = 100; bolt->splashRadius = 150; bolt->methodOfDeath = MOD_MINE; bolt->splashMethodOfDeath = MOD_MINE_SPLASH; // bolt->clipmask = MASK_SHOT; bolt->clipmask = CONTENTS_SOLID; // dont activate until it stops moving // bolt->mass = 2; bolt->s.pos.trType = TR_GRAVITY; bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame VectorCopy( start, bolt->s.pos.trBase ); VectorMA( self->client->ps.velocity, 100, dir, bolt->s.pos.trDelta ); VectorScale(bolt->s.pos.trDelta, 0.4f, bolt->s.pos.trDelta); // VectorAdd( self->client->ps.velocity, bolt->s.pos.trDelta, bolt->s.pos.trDelta ); SnapVector( bolt->s.pos.trDelta ); // save net bandwidth VectorCopy (start, bolt->r.currentOrigin); return bolt; } // END #ifdef MISSIONPACK /* ================= fire_nail ================= */ #define NAILGUN_SPREAD 500 gentity_t *fire_nail( gentity_t *self, vec3_t start, vec3_t forward, vec3_t right, vec3_t up ) { gentity_t *bolt; vec3_t dir; vec3_t end; float r, u, scale; bolt = G_Spawn(); bolt->classname = "nail"; bolt->nextthink = level.time + 10000; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_NAILGUN; bolt->r.ownerNum = self->s.number; bolt->parent = self; bolt->damage = 20; bolt->methodOfDeath = MOD_NAIL; bolt->clipmask = MASK_SHOT; bolt->target_ent = NULL; bolt->s.pos.trType = TR_LINEAR; bolt->s.pos.trTime = level.time; VectorCopy( start, bolt->s.pos.trBase ); r = random() * M_PI * 2.0f; u = sin(r) * crandom() * NAILGUN_SPREAD * 16; r = cos(r) * crandom() * NAILGUN_SPREAD * 16; VectorMA( start, 8192 * 16, forward, end); VectorMA (end, r, right, end); VectorMA (end, u, up, end); VectorSubtract( end, start, dir ); VectorNormalize( dir ); scale = 555 + random() * 1800; VectorScale( dir, scale, bolt->s.pos.trDelta ); SnapVector( bolt->s.pos.trDelta ); VectorCopy( start, bolt->r.currentOrigin ); return bolt; } /* ================= fire_prox ================= */ gentity_t *fire_prox( gentity_t *self, vec3_t start, vec3_t dir ) { gentity_t *bolt; VectorNormalize (dir); bolt = G_Spawn(); bolt->classname = "prox mine"; bolt->nextthink = level.time + 3000; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_PROX_LAUNCHER; bolt->s.eFlags = 0; bolt->r.ownerNum = self->s.number; bolt->parent = self; bolt->damage = 0; bolt->splashDamage = 100; bolt->splashRadius = 150; bolt->methodOfDeath = MOD_PROXIMITY_MINE; bolt->splashMethodOfDeath = MOD_PROXIMITY_MINE; bolt->clipmask = MASK_SHOT; bolt->target_ent = NULL; // count is used to check if the prox mine left the player bbox // if count == 1 then the prox mine left the player bbox and can attack to it bolt->count = 0; //FIXME: we prolly wanna abuse another field bolt->s.generic1 = self->client->sess.sessionTeam; bolt->s.pos.trType = TR_GRAVITY; bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame VectorCopy( start, bolt->s.pos.trBase ); VectorScale( dir, 700, bolt->s.pos.trDelta ); SnapVector( bolt->s.pos.trDelta ); // save net bandwidth VectorCopy (start, bolt->r.currentOrigin); return bolt; } #endif