// Copyright (C) 1999-2000 Id Software, Inc. // // g_weapon.c // perform the server side effects of a weapon firing #include "g_local.h" #include "be_aas.h" #include "bg_saga.h" #include "../ghoul2/G2.h" #include "q_shared.h" static float s_quadFactor; static vec3_t forward, vright, up; static vec3_t muzzle; // Bryar Pistol //-------- #define BRYAR_PISTOL_VEL 1600 #define BRYAR_PISTOL_DAMAGE 10 #define BRYAR_CHARGE_UNIT 200.0f // bryar charging gives us one more unit every 200ms--if you change this, you'll have to do the same in bg_pmove #define BRYAR_ALT_SIZE 1.0f // E11 Blaster //--------- #define BLASTER_SPREAD 1.6f//1.2f #define BLASTER_VELOCITY 2300 #define BLASTER_DAMAGE 20 // Tenloss Disruptor //---------- #define DISRUPTOR_MAIN_DAMAGE 30 //40 #define DISRUPTOR_MAIN_DAMAGE_SIEGE 50 #define DISRUPTOR_NPC_MAIN_DAMAGE_CUT 0.25f #define DISRUPTOR_ALT_DAMAGE 100 //125 #define DISRUPTOR_NPC_ALT_DAMAGE_CUT 0.2f #define DISRUPTOR_ALT_TRACES 3 // can go through a max of 3 damageable(sp?) entities #define DISRUPTOR_CHARGE_UNIT 50.0f // distruptor charging gives us one more unit every 50ms--if you change this, you'll have to do the same in bg_pmove // Wookiee Bowcaster //---------- #define BOWCASTER_DAMAGE 50 #define BOWCASTER_VELOCITY 1300 #define BOWCASTER_SPLASH_DAMAGE 0 #define BOWCASTER_SPLASH_RADIUS 0 #define BOWCASTER_SIZE 2 #define BOWCASTER_ALT_SPREAD 5.0f #define BOWCASTER_VEL_RANGE 0.3f #define BOWCASTER_CHARGE_UNIT 200.0f // bowcaster charging gives us one more unit every 200ms--if you change this, you'll have to do the same in bg_pmove // Heavy Repeater //---------- #define REPEATER_SPREAD 1.4f #define REPEATER_DAMAGE 14 #define REPEATER_VELOCITY 1600 #define REPEATER_ALT_SIZE 3 // half of bbox size #define REPEATER_ALT_DAMAGE 60 #define REPEATER_ALT_SPLASH_DAMAGE 60 #define REPEATER_ALT_SPLASH_RADIUS 128 #define REPEATER_ALT_SPLASH_RAD_SIEGE 80 #define REPEATER_ALT_VELOCITY 1100 // DEMP2 //---------- #define DEMP2_DAMAGE 35 #define DEMP2_VELOCITY 1800 #define DEMP2_SIZE 2 // half of bbox size #define DEMP2_ALT_DAMAGE 8 //12 // does 12, 36, 84 at each of the 3 charge levels. #define DEMP2_CHARGE_UNIT 700.0f // demp2 charging gives us one more unit every 700ms--if you change this, you'll have to do the same in bg_weapons #define DEMP2_ALT_RANGE 4096 #define DEMP2_ALT_SPLASHRADIUS 256 // Golan Arms Flechette //--------- #define FLECHETTE_SHOTS 5 #define FLECHETTE_SPREAD 4.0f #define FLECHETTE_DAMAGE 12//15 #define FLECHETTE_VEL 3500 #define FLECHETTE_SIZE 1 #define FLECHETTE_MINE_RADIUS_CHECK 256 #define FLECHETTE_ALT_DAMAGE 60 #define FLECHETTE_ALT_SPLASH_DAM 60 #define FLECHETTE_ALT_SPLASH_RAD 128 // Personal Rocket Launcher //--------- #define ROCKET_VELOCITY 900 #define ROCKET_DAMAGE 100 #define ROCKET_SPLASH_DAMAGE 100 #define ROCKET_SPLASH_RADIUS 160 #define ROCKET_SIZE 3 #define ROCKET_ALT_THINK_TIME 100 // Concussion Rifle //--------- //primary //man, this thing is too absurdly powerful. having to //slash the values way down from sp. #define CONC_VELOCITY 3000 #define CONC_DAMAGE 75 //150 #define CONC_NPC_DAMAGE_EASY 40 #define CONC_NPC_DAMAGE_NORMAL 80 #define CONC_NPC_DAMAGE_HARD 100 #define CONC_SPLASH_DAMAGE 40 //50 #define CONC_SPLASH_RADIUS 200 //300 //alt #define CONC_ALT_DAMAGE 25 //100 #define CONC_ALT_NPC_DAMAGE_EASY 20 #define CONC_ALT_NPC_DAMAGE_MEDIUM 35 #define CONC_ALT_NPC_DAMAGE_HARD 50 // Stun Baton //-------------- #define STUN_BATON_DAMAGE 20 #define STUN_BATON_ALT_DAMAGE 20 #define STUN_BATON_RANGE 8 // Melee //-------------- #define MELEE_SWING1_DAMAGE 10 #define MELEE_SWING2_DAMAGE 12 #define MELEE_RANGE 8 // ATST Main Gun //-------------- #define ATST_MAIN_VEL 4000 // #define ATST_MAIN_DAMAGE 25 // #define ATST_MAIN_SIZE 3 // make it easier to hit things // ATST Side Gun //--------------- #define ATST_SIDE_MAIN_DAMAGE 75 #define ATST_SIDE_MAIN_VELOCITY 1300 #define ATST_SIDE_MAIN_NPC_DAMAGE_EASY 30 #define ATST_SIDE_MAIN_NPC_DAMAGE_NORMAL 40 #define ATST_SIDE_MAIN_NPC_DAMAGE_HARD 50 #define ATST_SIDE_MAIN_SIZE 4 #define ATST_SIDE_MAIN_SPLASH_DAMAGE 10 // yeah, pretty small, either zero out or make it worth having? #define ATST_SIDE_MAIN_SPLASH_RADIUS 16 // yeah, pretty small, either zero out or make it worth having? #define ATST_SIDE_ALT_VELOCITY 1100 #define ATST_SIDE_ALT_NPC_VELOCITY 600 #define ATST_SIDE_ALT_DAMAGE 130 #define ATST_SIDE_ROCKET_NPC_DAMAGE_EASY 30 #define ATST_SIDE_ROCKET_NPC_DAMAGE_NORMAL 50 #define ATST_SIDE_ROCKET_NPC_DAMAGE_HARD 90 #define ATST_SIDE_ALT_SPLASH_DAMAGE 130 #define ATST_SIDE_ALT_SPLASH_RADIUS 200 #define ATST_SIDE_ALT_ROCKET_SIZE 5 #define ATST_SIDE_ALT_ROCKET_SPLASH_SCALE 0.5f // scales splash for NPC's extern qboolean G_BoxInBounds( vec3_t point, vec3_t mins, vec3_t maxs, vec3_t boundsMins, vec3_t boundsMaxs ); extern qboolean G_HeavyMelee( gentity_t *attacker ); extern void Jedi_Decloak( gentity_t *self ); static void WP_FireEmplaced( gentity_t *ent, qboolean altFire ); void laserTrapStick( gentity_t *ent, vec3_t endpos, vec3_t normal ); void touch_NULL( gentity_t *ent, gentity_t *other, trace_t *trace ) { } void laserTrapExplode( gentity_t *self ); void RocketDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod); extern vmCvar_t g_vehAutoAimLead; //We should really organize weapon data into tables or parse from the ext data so we have accurate info for this, float WP_SpeedOfMissileForWeapon( int wp, qboolean alt_fire ) { return 500; } //----------------------------------------------------------------------------- void W_TraceSetStart( gentity_t *ent, vec3_t start, vec3_t mins, vec3_t maxs ) //----------------------------------------------------------------------------- { //make sure our start point isn't on the other side of a wall trace_t tr; vec3_t entMins; vec3_t entMaxs; vec3_t eyePoint; VectorAdd( ent->r.currentOrigin, ent->r.mins, entMins ); VectorAdd( ent->r.currentOrigin, ent->r.maxs, entMaxs ); if ( G_BoxInBounds( start, mins, maxs, entMins, entMaxs ) ) { return; } if ( !ent->client ) { return; } VectorCopy( ent->s.pos.trBase, eyePoint); eyePoint[2] += ent->client->ps.viewheight; trap_Trace( &tr, eyePoint, mins, maxs, start, ent->s.number, MASK_SOLID|CONTENTS_SHOTCLIP ); if ( tr.startsolid || tr.allsolid ) { return; } if ( tr.fraction < 1.0f ) { VectorCopy( tr.endpos, start ); } } /* ---------------------------------------------- PLAYER WEAPONS ---------------------------------------------- */ /* ====================================================================== BRYAR PISTOL ====================================================================== */ //---------------------------------------------- static void WP_FireBryarPistol( gentity_t *ent, qboolean altFire ) //--------------------------------------------------------- { int damage = BRYAR_PISTOL_DAMAGE; int count; gentity_t *missile = CreateMissile( muzzle, forward, BRYAR_PISTOL_VEL, 10000, ent, altFire ); missile->classname = "bryar_proj"; missile->s.weapon = WP_BRYAR_PISTOL; if ( altFire ) { float boxSize = 0; count = ( level.time - ent->client->ps.weaponChargeTime ) / BRYAR_CHARGE_UNIT; if ( count < 1 ) { count = 1; } else if ( count > 5 ) { count = 5; } if (count > 1) { damage *= (count*1.7); } else { damage *= (count*1.5); } missile->s.generic1 = count; // The missile will then render according to the charge level. boxSize = BRYAR_ALT_SIZE*(count*0.5); VectorSet( missile->r.maxs, boxSize, boxSize, boxSize ); VectorSet( missile->r.mins, -boxSize, -boxSize, -boxSize ); } missile->damage = damage; missile->dflags = DAMAGE_DEATH_KNOCKBACK; if (altFire) { missile->methodOfDeath = MOD_BRYAR_PISTOL_ALT; } else { missile->methodOfDeath = MOD_BRYAR_PISTOL; } missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; // we don't want it to bounce forever missile->bounceCount = 8; } /* ====================================================================== GENERIC ====================================================================== */ //--------------------------------------------------------- void WP_FireTurretMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire, int damage, int velocity, int mod, gentity_t *ignore ) //--------------------------------------------------------- { gentity_t *missile; missile = CreateMissile( start, dir, velocity, 10000, ent, altFire ); missile->classname = "generic_proj"; missile->s.weapon = WP_TURRET; missile->damage = damage; missile->dflags = DAMAGE_DEATH_KNOCKBACK; missile->methodOfDeath = mod; missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; if (ignore) { missile->passThroughNum = ignore->s.number+1; } // we don't want it to bounce forever missile->bounceCount = 8; } //Currently only the seeker drone uses this, but it might be useful for other things as well. //--------------------------------------------------------- void WP_FireGenericBlasterMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire, int damage, int velocity, int mod ) //--------------------------------------------------------- { gentity_t *missile; missile = CreateMissile( start, dir, velocity, 10000, ent, altFire ); missile->classname = "generic_proj"; missile->s.weapon = WP_BRYAR_PISTOL; missile->damage = damage; missile->dflags = DAMAGE_DEATH_KNOCKBACK; missile->methodOfDeath = mod; missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; // we don't want it to bounce forever missile->bounceCount = 8; } /* ====================================================================== BLASTER ====================================================================== */ //--------------------------------------------------------- void WP_FireBlasterMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire ) //--------------------------------------------------------- { int velocity = BLASTER_VELOCITY; int damage = BLASTER_DAMAGE; gentity_t *missile; if (ent->s.eType == ET_NPC) { //animent damage = 10; } missile = CreateMissile( start, dir, velocity, 10000, ent, altFire ); missile->classname = "blaster_proj"; missile->s.weapon = WP_BLASTER; missile->damage = damage; missile->dflags = DAMAGE_DEATH_KNOCKBACK; missile->methodOfDeath = MOD_BLASTER; missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; // we don't want it to bounce forever missile->bounceCount = 8; } //--------------------------------------------------------- void WP_FireTurboLaserMissile( gentity_t *ent, vec3_t start, vec3_t dir ) //--------------------------------------------------------- { int velocity = ent->mass; //FIXME: externalize gentity_t *missile; missile = CreateMissile( start, dir, velocity, 10000, ent, qfalse ); //use a custom shot effect missile->s.otherEntityNum2 = ent->genericValue14; //use a custom impact effect missile->s.emplacedOwner = ent->genericValue15; missile->classname = "turbo_proj"; missile->s.weapon = WP_TURRET; missile->damage = ent->damage; //FIXME: externalize missile->splashDamage = ent->splashDamage; //FIXME: externalize missile->splashRadius = ent->splashRadius; //FIXME: externalize missile->dflags = DAMAGE_DEATH_KNOCKBACK; missile->methodOfDeath = MOD_TARGET_LASER;//MOD_TURBLAST; //count as a heavy weap missile->splashMethodOfDeath = MOD_TARGET_LASER;//MOD_TURBLAST;// ?SPLASH; missile->clipmask = MASK_SHOT; // we don't want it to bounce forever missile->bounceCount = 8; //set veh as cgame side owner for purpose of fx overrides missile->s.owner = ent->s.number; //don't let them last forever missile->think = G_FreeEntity; missile->nextthink = level.time + 5000;//at 20000 speed, that should be more than enough } //--------------------------------------------------------- void WP_FireEmplacedMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire, gentity_t *ignore ) //--------------------------------------------------------- { int velocity = BLASTER_VELOCITY; int damage = BLASTER_DAMAGE; gentity_t *missile; missile = CreateMissile( start, dir, velocity, 10000, ent, altFire ); missile->classname = "emplaced_gun_proj"; missile->s.weapon = WP_TURRET;//WP_EMPLACED_GUN; missile->activator = ignore; missile->damage = damage; missile->dflags = (DAMAGE_DEATH_KNOCKBACK|DAMAGE_HEAVY_WEAP_CLASS); missile->methodOfDeath = MOD_VEHICLE; missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; if (ignore) { missile->passThroughNum = ignore->s.number+1; } // we don't want it to bounce forever missile->bounceCount = 8; } //--------------------------------------------------------- static void WP_FireBlaster( gentity_t *ent, qboolean altFire ) //--------------------------------------------------------- { vec3_t dir, angs; vectoangles( forward, angs ); if ( altFire ) { // add some slop to the alt-fire direction angs[PITCH] += crandom() * BLASTER_SPREAD; angs[YAW] += crandom() * BLASTER_SPREAD; } AngleVectors( angs, dir, NULL, NULL ); // FIXME: if temp_org does not have clear trace to inside the bbox, don't shoot! WP_FireBlasterMissile( ent, muzzle, dir, altFire ); } int G_GetHitLocation(gentity_t *target, vec3_t ppoint); /* ====================================================================== DISRUPTOR ====================================================================== */ //--------------------------------------------------------- static void WP_DisruptorMainFire( gentity_t *ent ) //--------------------------------------------------------- { int damage = DISRUPTOR_MAIN_DAMAGE; qboolean render_impact = qtrue; vec3_t start, end; trace_t tr; gentity_t *traceEnt, *tent; float shotRange = 8192; int ignore, traces; if ( g_gametype.integer == GT_SIEGE ) { damage = DISRUPTOR_MAIN_DAMAGE_SIEGE; } memset(&tr, 0, sizeof(tr)); //to shut the compiler up VectorCopy( ent->client->ps.origin, start ); start[2] += ent->client->ps.viewheight;//By eyes VectorMA( start, shotRange, forward, end ); ignore = ent->s.number; traces = 0; while ( traces < 10 ) {//need to loop this in case we hit a Jedi who dodges the shot if (d_projectileGhoul2Collision.integer) { trap_G2Trace( &tr, start, NULL, NULL, end, ignore, MASK_SHOT, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_THICK|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer ); } else { trap_Trace( &tr, start, NULL, NULL, end, ignore, MASK_SHOT ); } traceEnt = &g_entities[tr.entityNum]; if (d_projectileGhoul2Collision.integer && traceEnt->inuse && traceEnt->client) { //g2 collision checks -rww if (traceEnt->inuse && traceEnt->client && traceEnt->ghoul2) { //since we used G2TRFLAG_GETSURFINDEX, tr.surfaceFlags will actually contain the index of the surface on the ghoul2 model we collided with. traceEnt->client->g2LastSurfaceHit = tr.surfaceFlags; traceEnt->client->g2LastSurfaceTime = level.time; } if (traceEnt->ghoul2) { tr.surfaceFlags = 0; //clear the surface flags after, since we actually care about them in here. } } if (traceEnt && traceEnt->client && traceEnt->client->ps.duelInProgress && traceEnt->client->ps.duelIndex != ent->s.number) { VectorCopy( tr.endpos, start ); ignore = tr.entityNum; traces++; continue; } if ( Jedi_DodgeEvasion( traceEnt, ent, &tr, G_GetHitLocation(traceEnt, tr.endpos) ) ) {//act like we didn't even hit him VectorCopy( tr.endpos, start ); ignore = tr.entityNum; traces++; continue; } else if (traceEnt && traceEnt->client && traceEnt->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= FORCE_LEVEL_3) { if (WP_SaberCanBlock(traceEnt, tr.endpos, 0, MOD_DISRUPTOR, qtrue, 0)) { //broadcast and stop the shot because it was blocked gentity_t *te = NULL; tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_MAIN_SHOT ); VectorCopy( muzzle, tent->s.origin2 ); tent->s.eventParm = ent->s.number; te = G_TempEntity( tr.endpos, EV_SABER_BLOCK ); VectorCopy(tr.endpos, te->s.origin); VectorCopy(tr.plane.normal, te->s.angles); if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2]) { te->s.angles[1] = 1; } te->s.eventParm = 0; te->s.weapon = 0;//saberNum te->s.legsAnim = 0;//bladeNum return; } } else if ( (traceEnt->flags&FL_SHIELDED) ) {//stopped cold return; } //a Jedi is not dodging this shot break; } if ( tr.surfaceFlags & SURF_NOIMPACT ) { render_impact = qfalse; } // always render a shot beam, doing this the old way because I don't much feel like overriding the effect. tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_MAIN_SHOT ); VectorCopy( muzzle, tent->s.origin2 ); tent->s.eventParm = ent->s.number; traceEnt = &g_entities[tr.entityNum]; if ( render_impact ) { if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage ) { if ( traceEnt->client && LogAccuracyHit( traceEnt, ent )) { ent->client->accuracy_hits++; } G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NORMAL, MOD_DISRUPTOR ); tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_HIT ); tent->s.eventParm = DirToByte( tr.plane.normal ); if (traceEnt->client) { tent->s.weapon = 1; } } else { // Hmmm, maybe don't make any marks on things that could break tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_MISS ); tent->s.eventParm = DirToByte( tr.plane.normal ); tent->s.weapon = 1; } } } qboolean G_CanDisruptify(gentity_t *ent) { if (!ent || !ent->inuse || !ent->client || ent->s.eType != ET_NPC || ent->s.NPC_class != CLASS_VEHICLE || !ent->m_pVehicle) { //not vehicle return qtrue; } if (ent->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL) { //animal is only type that can be disintigeiteigerated return qtrue; } //don't do it to any other veh return qfalse; } //--------------------------------------------------------- void WP_DisruptorAltFire( gentity_t *ent ) //--------------------------------------------------------- { int damage = 0, skip; qboolean render_impact = qtrue; vec3_t start, end; vec3_t muzzle2; trace_t tr; gentity_t *traceEnt, *tent; float shotRange = 8192.0f; int i; int count, maxCount = 60; int traces = DISRUPTOR_ALT_TRACES; qboolean fullCharge = qfalse; damage = DISRUPTOR_ALT_DAMAGE-30; VectorCopy( muzzle, muzzle2 ); // making a backup copy if (ent->client) { VectorCopy( ent->client->ps.origin, start ); start[2] += ent->client->ps.viewheight;//By eyes count = ( level.time - ent->client->ps.weaponChargeTime ) / DISRUPTOR_CHARGE_UNIT; if ( g_gametype.integer == GT_SIEGE ) {//maybe a full alt-charge should be a *bit* more dangerous in Siege mode? //maxCount = ceil((200.0f-(float)damage)/2.0f);//cap at 200 damage total maxCount = 200;//the previous line ALWAYS evaluated to 135 - was that on purpose? } } else { VectorCopy( ent->r.currentOrigin, start ); start[2] += 24; count = ( 100 ) / DISRUPTOR_CHARGE_UNIT; } count *= 2; if ( count < 1 ) { count = 1; } else if ( count >= maxCount ) { count = maxCount; fullCharge = qtrue; } // more powerful charges go through more things if ( count < 10 ) { traces = 1; } else if ( count < 20 ) { traces = 2; } damage += count; skip = ent->s.number; for (i = 0; i < traces; i++ ) { VectorMA( start, shotRange, forward, end ); if (d_projectileGhoul2Collision.integer) { trap_G2Trace( &tr, start, NULL, NULL, end, skip, MASK_SHOT, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_THICK|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer ); } else { trap_Trace( &tr, start, NULL, NULL, end, skip, MASK_SHOT ); } traceEnt = &g_entities[tr.entityNum]; if (d_projectileGhoul2Collision.integer && traceEnt->inuse && traceEnt->client) { //g2 collision checks -rww if (traceEnt->inuse && traceEnt->client && traceEnt->ghoul2) { //since we used G2TRFLAG_GETSURFINDEX, tr.surfaceFlags will actually contain the index of the surface on the ghoul2 model we collided with. traceEnt->client->g2LastSurfaceHit = tr.surfaceFlags; traceEnt->client->g2LastSurfaceTime = level.time; } if (traceEnt->ghoul2) { tr.surfaceFlags = 0; //clear the surface flags after, since we actually care about them in here. } } if ( tr.surfaceFlags & SURF_NOIMPACT ) { render_impact = qfalse; } if (traceEnt && traceEnt->client && traceEnt->client->ps.duelInProgress && traceEnt->client->ps.duelIndex != ent->s.number) { skip = tr.entityNum; VectorCopy(tr.endpos, start); continue; } if (Jedi_DodgeEvasion(traceEnt, ent, &tr, G_GetHitLocation(traceEnt, tr.endpos))) { skip = tr.entityNum; VectorCopy(tr.endpos, start); continue; } else if (traceEnt && traceEnt->client && traceEnt->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= FORCE_LEVEL_3) { if (WP_SaberCanBlock(traceEnt, tr.endpos, 0, MOD_DISRUPTOR_SNIPER, qtrue, 0)) { //broadcast and stop the shot because it was blocked gentity_t *te = NULL; tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_SHOT ); VectorCopy( muzzle, tent->s.origin2 ); tent->s.shouldtarget = fullCharge; tent->s.eventParm = ent->s.number; te = G_TempEntity( tr.endpos, EV_SABER_BLOCK ); VectorCopy(tr.endpos, te->s.origin); VectorCopy(tr.plane.normal, te->s.angles); if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2]) { te->s.angles[1] = 1; } te->s.eventParm = 0; te->s.weapon = 0;//saberNum te->s.legsAnim = 0;//bladeNum return; } } // always render a shot beam, doing this the old way because I don't much feel like overriding the effect. tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_SHOT ); VectorCopy( muzzle, tent->s.origin2 ); tent->s.shouldtarget = fullCharge; tent->s.eventParm = ent->s.number; // If the beam hits a skybox, etc. it would look foolish to add impact effects if ( render_impact ) { if ( traceEnt->takedamage && traceEnt->client ) { tent->s.otherEntityNum = traceEnt->s.number; // Create a simple impact type mark tent = G_TempEntity(tr.endpos, EV_MISSILE_MISS); tent->s.eventParm = DirToByte(tr.plane.normal); tent->s.eFlags |= EF_ALT_FIRING; if ( LogAccuracyHit( traceEnt, ent )) { if (ent->client) { ent->client->accuracy_hits++; } } } else { if ( traceEnt->r.svFlags & SVF_GLASS_BRUSH || traceEnt->takedamage || traceEnt->s.eType == ET_MOVER ) { if ( traceEnt->takedamage ) { G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_DISRUPTOR_SNIPER ); tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_HIT ); tent->s.eventParm = DirToByte( tr.plane.normal ); } } else { // Hmmm, maybe don't make any marks on things that could break tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_MISS ); tent->s.eventParm = DirToByte( tr.plane.normal ); } break; // and don't try any more traces } if ( (traceEnt->flags&FL_SHIELDED) ) {//stops us cold break; } if ( traceEnt->takedamage ) { vec3_t preAng; int preHealth = traceEnt->health; int preLegs = 0; int preTorso = 0; if (traceEnt->client) { preLegs = traceEnt->client->ps.legsAnim; preTorso = traceEnt->client->ps.torsoAnim; VectorCopy(traceEnt->client->ps.viewangles, preAng); } G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_DISRUPTOR_SNIPER ); if (traceEnt->client && preHealth > 0 && traceEnt->health <= 0 && fullCharge && G_CanDisruptify(traceEnt)) { //was killed by a fully charged sniper shot, so disintegrate VectorCopy(preAng, traceEnt->client->ps.viewangles); traceEnt->client->ps.eFlags |= EF_DISINTEGRATION; VectorCopy(tr.endpos, traceEnt->client->ps.lastHitLoc); traceEnt->client->ps.legsAnim = preLegs; traceEnt->client->ps.torsoAnim = preTorso; traceEnt->r.contents = 0; VectorClear(traceEnt->client->ps.velocity); } tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_HIT ); tent->s.eventParm = DirToByte( tr.plane.normal ); if (traceEnt->client) { tent->s.weapon = 1; } } } else // not rendering impact, must be a skybox or other similar thing? { break; // don't try anymore traces } // Get ready for an attempt to trace through another person VectorCopy( tr.endpos, muzzle ); VectorCopy( tr.endpos, start ); skip = tr.entityNum; } } //--------------------------------------------------------- static void WP_FireDisruptor( gentity_t *ent, qboolean altFire ) //--------------------------------------------------------- { if (!ent || !ent->client || ent->client->ps.zoomMode != 1) { //do not ever let it do the alt fire when not zoomed altFire = qfalse; } if (ent && ent->s.eType == ET_NPC && !ent->client) { //special case for animents WP_DisruptorAltFire( ent ); return; } if ( altFire ) { WP_DisruptorAltFire( ent ); } else { WP_DisruptorMainFire( ent ); } } /* ====================================================================== BOWCASTER ====================================================================== */ static void WP_BowcasterAltFire( gentity_t *ent ) { int damage = BOWCASTER_DAMAGE; gentity_t *missile = CreateMissile( muzzle, forward, BOWCASTER_VELOCITY, 10000, ent, qfalse); missile->classname = "bowcaster_proj"; missile->s.weapon = WP_BOWCASTER; VectorSet( missile->r.maxs, BOWCASTER_SIZE, BOWCASTER_SIZE, BOWCASTER_SIZE ); VectorScale( missile->r.maxs, -1, missile->r.mins ); missile->damage = damage; missile->dflags = DAMAGE_DEATH_KNOCKBACK; missile->methodOfDeath = MOD_BOWCASTER; missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; missile->flags |= FL_BOUNCE; missile->bounceCount = 3; } //--------------------------------------------------------- static void WP_BowcasterMainFire( gentity_t *ent ) //--------------------------------------------------------- { int damage = BOWCASTER_DAMAGE, count; float vel; vec3_t angs, dir; gentity_t *missile; int i; if (!ent->client) { count = 1; } else { count = ( level.time - ent->client->ps.weaponChargeTime ) / BOWCASTER_CHARGE_UNIT; } if ( count < 1 ) { count = 1; } else if ( count > 5 ) { count = 5; } if ( !(count & 1 )) { // if we aren't odd, knock us down a level count--; } //scale the damage down based on how many are about to be fired if (count <= 1) { damage = 50; } else if (count == 2) { damage = 45; } else if (count == 3) { damage = 40; } else if (count == 4) { damage = 35; } else { damage = 30; } for (i = 0; i < count; i++ ) { // create a range of different velocities vel = BOWCASTER_VELOCITY * ( crandom() * BOWCASTER_VEL_RANGE + 1.0f ); vectoangles( forward, angs ); // add some slop to the alt-fire direction angs[PITCH] += crandom() * BOWCASTER_ALT_SPREAD * 0.2f; angs[YAW] += ((i+0.5f) * BOWCASTER_ALT_SPREAD - count * 0.5f * BOWCASTER_ALT_SPREAD ); AngleVectors( angs, dir, NULL, NULL ); missile = CreateMissile( muzzle, dir, vel, 10000, ent, qtrue ); missile->classname = "bowcaster_alt_proj"; missile->s.weapon = WP_BOWCASTER; VectorSet( missile->r.maxs, BOWCASTER_SIZE, BOWCASTER_SIZE, BOWCASTER_SIZE ); VectorScale( missile->r.maxs, -1, missile->r.mins ); missile->damage = damage; missile->dflags = DAMAGE_DEATH_KNOCKBACK; missile->methodOfDeath = MOD_BOWCASTER; missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; // we don't want it to bounce missile->bounceCount = 0; } } //--------------------------------------------------------- static void WP_FireBowcaster( gentity_t *ent, qboolean altFire ) //--------------------------------------------------------- { if ( altFire ) { WP_BowcasterAltFire( ent ); } else { WP_BowcasterMainFire( ent ); } } /* ====================================================================== REPEATER ====================================================================== */ //--------------------------------------------------------- static void WP_RepeaterMainFire( gentity_t *ent, vec3_t dir ) //--------------------------------------------------------- { int damage = REPEATER_DAMAGE; gentity_t *missile = CreateMissile( muzzle, dir, REPEATER_VELOCITY, 10000, ent, qfalse ); missile->classname = "repeater_proj"; missile->s.weapon = WP_REPEATER; missile->damage = damage; missile->dflags = DAMAGE_DEATH_KNOCKBACK; missile->methodOfDeath = MOD_REPEATER; missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; // we don't want it to bounce forever missile->bounceCount = 8; } //--------------------------------------------------------- static void WP_RepeaterAltFire( gentity_t *ent ) //--------------------------------------------------------- { int damage = REPEATER_ALT_DAMAGE; gentity_t *missile = CreateMissile( muzzle, forward, REPEATER_ALT_VELOCITY, 10000, ent, qtrue ); missile->classname = "repeater_alt_proj"; missile->s.weapon = WP_REPEATER; VectorSet( missile->r.maxs, REPEATER_ALT_SIZE, REPEATER_ALT_SIZE, REPEATER_ALT_SIZE ); VectorScale( missile->r.maxs, -1, missile->r.mins ); missile->s.pos.trType = TR_GRAVITY; missile->s.pos.trDelta[2] += 40.0f; //give a slight boost in the upward direction missile->damage = damage; missile->dflags = DAMAGE_DEATH_KNOCKBACK; missile->methodOfDeath = MOD_REPEATER_ALT; missile->splashMethodOfDeath = MOD_REPEATER_ALT_SPLASH; missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; missile->splashDamage = REPEATER_ALT_SPLASH_DAMAGE; if ( g_gametype.integer == GT_SIEGE ) // we've been having problems with this being too hyper-potent because of it's radius { missile->splashRadius = REPEATER_ALT_SPLASH_RAD_SIEGE; } else { missile->splashRadius = REPEATER_ALT_SPLASH_RADIUS; } // we don't want it to bounce forever missile->bounceCount = 8; } //--------------------------------------------------------- static void WP_FireRepeater( gentity_t *ent, qboolean altFire ) //--------------------------------------------------------- { vec3_t dir, angs; vectoangles( forward, angs ); if ( altFire ) { WP_RepeaterAltFire( ent ); } else { // add some slop to the alt-fire direction angs[PITCH] += crandom() * REPEATER_SPREAD; angs[YAW] += crandom() * REPEATER_SPREAD; AngleVectors( angs, dir, NULL, NULL ); WP_RepeaterMainFire( ent, dir ); } } /* ====================================================================== DEMP2 ====================================================================== */ static void WP_DEMP2_MainFire( gentity_t *ent ) { int damage = DEMP2_DAMAGE; gentity_t *missile = CreateMissile( muzzle, forward, DEMP2_VELOCITY, 10000, ent, qfalse); missile->classname = "demp2_proj"; missile->s.weapon = WP_DEMP2; VectorSet( missile->r.maxs, DEMP2_SIZE, DEMP2_SIZE, DEMP2_SIZE ); VectorScale( missile->r.maxs, -1, missile->r.mins ); missile->damage = damage; missile->dflags = DAMAGE_DEATH_KNOCKBACK; missile->methodOfDeath = MOD_DEMP2; missile->clipmask = MASK_SHOT; // we don't want it to ever bounce missile->bounceCount = 0; } static gentity_t *ent_list[MAX_GENTITIES]; void DEMP2_AltRadiusDamage( gentity_t *ent ) { float frac = ( level.time - ent->genericValue5 ) / 800.0f; // / 1600.0f; // synchronize with demp2 effect float dist, radius, fact; gentity_t *gent; int iEntityList[MAX_GENTITIES]; gentity_t *entityList[MAX_GENTITIES]; gentity_t *myOwner = NULL; int numListedEntities, i, e; vec3_t mins, maxs; vec3_t v, dir; if (ent->r.ownerNum >= 0 && ent->r.ownerNum < /*MAX_CLIENTS ... let npc's/shooters use it*/MAX_GENTITIES) { myOwner = &g_entities[ent->r.ownerNum]; } if (!myOwner || !myOwner->inuse || !myOwner->client) { ent->think = G_FreeEntity; ent->nextthink = level.time; return; } frac *= frac * frac; // yes, this is completely ridiculous...but it causes the shell to grow slowly then "explode" at the end radius = frac * 200.0f; // 200 is max radius...the model is aprox. 100 units tall...the fx draw code mults. this by 2. fact = ent->count*0.6; if (fact < 1) { fact = 1; } radius *= fact; for ( i = 0 ; i < 3 ; i++ ) { mins[i] = ent->r.currentOrigin[i] - radius; maxs[i] = ent->r.currentOrigin[i] + radius; } numListedEntities = trap_EntitiesInBox( mins, maxs, iEntityList, MAX_GENTITIES ); i = 0; while (i < numListedEntities) { entityList[i] = &g_entities[iEntityList[i]]; i++; } for ( e = 0 ; e < numListedEntities ; e++ ) { gent = entityList[ e ]; if ( !gent || !gent->takedamage || !gent->r.contents ) { continue; } // find the distance from the edge of the bounding box for ( i = 0 ; i < 3 ; i++ ) { if ( ent->r.currentOrigin[i] < gent->r.absmin[i] ) { v[i] = gent->r.absmin[i] - ent->r.currentOrigin[i]; } else if ( ent->r.currentOrigin[i] > gent->r.absmax[i] ) { v[i] = ent->r.currentOrigin[i] - gent->r.absmax[i]; } else { v[i] = 0; } } // shape is an ellipsoid, so cut vertical distance in half` v[2] *= 0.5f; dist = VectorLength( v ); if ( dist >= radius ) { // shockwave hasn't hit them yet continue; } if (dist+(16*ent->count) < ent->genericValue6) { // shockwave has already hit this thing... continue; } VectorCopy( gent->r.currentOrigin, v ); VectorSubtract( v, ent->r.currentOrigin, dir); // push the center of mass higher than the origin so players get knocked into the air more dir[2] += 12; if (gent != myOwner) { G_Damage( gent, myOwner, myOwner, dir, ent->r.currentOrigin, ent->damage, DAMAGE_DEATH_KNOCKBACK, ent->splashMethodOfDeath ); if ( gent->takedamage && gent->client ) { if ( gent->client->ps.electrifyTime < level.time ) {//electrocution effect if (gent->s.eType == ET_NPC && gent->s.NPC_class == CLASS_VEHICLE && gent->m_pVehicle && (gent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER || gent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER)) { //do some extra stuff to speeders/walkers gent->client->ps.electrifyTime = level.time + Q_irand( 3000, 4000 ); } else if ( gent->s.NPC_class != CLASS_VEHICLE || (gent->m_pVehicle && gent->m_pVehicle->m_pVehicleInfo->type != VH_FIGHTER) ) {//don't do this to fighters gent->client->ps.electrifyTime = level.time + Q_irand( 300, 800 ); } } if ( gent->client->ps.powerups[PW_CLOAKED] ) {//disable cloak temporarily Jedi_Decloak( gent ); gent->client->cloakToggleTime = level.time + Q_irand( 3000, 10000 ); } } } } // store the last fraction so that next time around we can test against those things that fall between that last point and where the current shockwave edge is ent->genericValue6 = radius; if ( frac < 1.0f ) { // shock is still happening so continue letting it expand ent->nextthink = level.time + 50; } else { //don't just leave the entity around ent->think = G_FreeEntity; ent->nextthink = level.time; } } //--------------------------------------------------------- void DEMP2_AltDetonate( gentity_t *ent ) //--------------------------------------------------------- { gentity_t *efEnt; G_SetOrigin( ent, ent->r.currentOrigin ); if (!ent->pos1[0] && !ent->pos1[1] && !ent->pos1[2]) { //don't play effect with a 0'd out directional vector ent->pos1[1] = 1; } //Let's just save ourself some bandwidth and play both the effect and sphere spawn in 1 event efEnt = G_PlayEffect( EFFECT_EXPLOSION_DEMP2ALT, ent->r.currentOrigin, ent->pos1 ); if (efEnt) { efEnt->s.weapon = ent->count*2; } ent->genericValue5 = level.time; ent->genericValue6 = 0; ent->nextthink = level.time + 50; ent->think = DEMP2_AltRadiusDamage; ent->s.eType = ET_GENERAL; // make us a missile no longer } //--------------------------------------------------------- static void WP_DEMP2_AltFire( gentity_t *ent ) //--------------------------------------------------------- { int damage = DEMP2_ALT_DAMAGE; int count, origcount; float fact; vec3_t start, end; trace_t tr; gentity_t *missile; VectorCopy( muzzle, start ); VectorMA( start, DEMP2_ALT_RANGE, forward, end ); count = ( level.time - ent->client->ps.weaponChargeTime ) / DEMP2_CHARGE_UNIT; origcount = count; if ( count < 1 ) { count = 1; } else if ( count > 3 ) { count = 3; } fact = count*0.8; if (fact < 1) { fact = 1; } damage *= fact; if (!origcount) { //this was just a tap-fire damage = 1; } trap_Trace( &tr, start, NULL, NULL, end, ent->s.number, MASK_SHOT); missile = G_Spawn(); G_SetOrigin(missile, tr.endpos); //In SP the impact actually travels as a missile based on the trace fraction, but we're //just going to be instant. -rww VectorCopy( tr.plane.normal, missile->pos1 ); missile->count = count; missile->classname = "demp2_alt_proj"; missile->s.weapon = WP_DEMP2; missile->think = DEMP2_AltDetonate; missile->nextthink = level.time; missile->splashDamage = missile->damage = damage; missile->splashMethodOfDeath = missile->methodOfDeath = MOD_DEMP2; missile->splashRadius = DEMP2_ALT_SPLASHRADIUS; missile->r.ownerNum = ent->s.number; missile->dflags = DAMAGE_DEATH_KNOCKBACK; missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; // we don't want it to ever bounce missile->bounceCount = 0; } //--------------------------------------------------------- static void WP_FireDEMP2( gentity_t *ent, qboolean altFire ) //--------------------------------------------------------- { if ( altFire ) { WP_DEMP2_AltFire( ent ); } else { WP_DEMP2_MainFire( ent ); } } /* ====================================================================== FLECHETTE ====================================================================== */ //--------------------------------------------------------- static void WP_FlechetteMainFire( gentity_t *ent ) //--------------------------------------------------------- { vec3_t fwd, angs; gentity_t *missile; int i; for (i = 0; i < FLECHETTE_SHOTS; i++ ) { vectoangles( forward, angs ); if (i != 0) { //do nothing on the first shot, it will hit the crosshairs angs[PITCH] += crandom() * FLECHETTE_SPREAD; angs[YAW] += crandom() * FLECHETTE_SPREAD; } AngleVectors( angs, fwd, NULL, NULL ); missile = CreateMissile( muzzle, fwd, FLECHETTE_VEL, 10000, ent, qfalse); missile->classname = "flech_proj"; missile->s.weapon = WP_FLECHETTE; VectorSet( missile->r.maxs, FLECHETTE_SIZE, FLECHETTE_SIZE, FLECHETTE_SIZE ); VectorScale( missile->r.maxs, -1, missile->r.mins ); missile->damage = FLECHETTE_DAMAGE; missile->dflags = DAMAGE_DEATH_KNOCKBACK; missile->methodOfDeath = MOD_FLECHETTE; missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; // we don't want it to bounce forever missile->bounceCount = Q_irand(5,8); missile->flags |= FL_BOUNCE_SHRAPNEL; } } //--------------------------------------------------------- void prox_mine_think( gentity_t *ent ) //--------------------------------------------------------- { int count, i; qboolean blow = qfalse; // if it isn't time to auto-explode, do a small proximity check if ( ent->delay > level.time ) { count = G_RadiusList( ent->r.currentOrigin, FLECHETTE_MINE_RADIUS_CHECK, ent, qtrue, ent_list ); for ( i = 0; i < count; i++ ) { if ( ent_list[i]->client && ent_list[i]->health > 0 && ent->activator && ent_list[i]->s.number != ent->activator->s.number ) { blow = qtrue; break; } } } else { // well, we must die now blow = qtrue; } if ( blow ) { ent->think = laserTrapExplode; ent->nextthink = level.time + 200; } else { // we probably don't need to do this thinking logic very often...maybe this is fast enough? ent->nextthink = level.time + 500; } } //----------------------------------------------------------------------------- static void WP_TraceSetStart( gentity_t *ent, vec3_t start, vec3_t mins, vec3_t maxs ) //----------------------------------------------------------------------------- { //make sure our start point isn't on the other side of a wall trace_t tr; vec3_t entMins; vec3_t entMaxs; VectorAdd( ent->r.currentOrigin, ent->r.mins, entMins ); VectorAdd( ent->r.currentOrigin, ent->r.maxs, entMaxs ); if ( G_BoxInBounds( start, mins, maxs, entMins, entMaxs ) ) { return; } if ( !ent->client ) { return; } trap_Trace( &tr, ent->client->ps.origin, mins, maxs, start, ent->s.number, MASK_SOLID|CONTENTS_SHOTCLIP ); if ( tr.startsolid || tr.allsolid ) { return; } if ( tr.fraction < 1.0f ) { VectorCopy( tr.endpos, start ); } } void WP_ExplosiveDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod) { laserTrapExplode(self); } //---------------------------------------------- void WP_flechette_alt_blow( gentity_t *ent ) //---------------------------------------------- { ent->s.pos.trDelta[0] = 1; ent->s.pos.trDelta[1] = 0; ent->s.pos.trDelta[2] = 0; laserTrapExplode(ent); } //------------------------------------------------------------------------------ static void WP_CreateFlechetteBouncyThing( vec3_t start, vec3_t fwd, gentity_t *self ) //------------------------------------------------------------------------------ { gentity_t *missile = CreateMissile( start, fwd, 700 + random() * 700, 1500 + random() * 2000, self, qtrue ); missile->think = WP_flechette_alt_blow; missile->activator = self; missile->s.weapon = WP_FLECHETTE; missile->classname = "flech_alt"; missile->mass = 4; // How 'bout we give this thing a size... VectorSet( missile->r.mins, -3.0f, -3.0f, -3.0f ); VectorSet( missile->r.maxs, 3.0f, 3.0f, 3.0f ); missile->clipmask = MASK_SHOT; missile->touch = touch_NULL; // normal ones bounce, alt ones explode on impact missile->s.pos.trType = TR_GRAVITY; missile->flags |= FL_BOUNCE_HALF; missile->s.eFlags |= EF_ALT_FIRING; missile->bounceCount = 50; missile->damage = FLECHETTE_ALT_DAMAGE; missile->dflags = 0; missile->splashDamage = FLECHETTE_ALT_SPLASH_DAM; missile->splashRadius = FLECHETTE_ALT_SPLASH_RAD; missile->r.svFlags = SVF_USE_CURRENT_ORIGIN; missile->methodOfDeath = MOD_FLECHETTE_ALT_SPLASH; missile->splashMethodOfDeath = MOD_FLECHETTE_ALT_SPLASH; VectorCopy( start, missile->pos2 ); } //--------------------------------------------------------- static void WP_FlechetteAltFire( gentity_t *self ) //--------------------------------------------------------- { vec3_t dir, fwd, start, angs; int i; vectoangles( forward, angs ); VectorCopy( muzzle, start ); WP_TraceSetStart( self, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall for ( i = 0; i < 2; i++ ) { VectorCopy( angs, dir ); dir[PITCH] -= random() * 4 + 8; // make it fly upwards dir[YAW] += crandom() * 2; AngleVectors( dir, fwd, NULL, NULL ); WP_CreateFlechetteBouncyThing( start, fwd, self ); } } //--------------------------------------------------------- static void WP_FireFlechette( gentity_t *ent, qboolean altFire ) //--------------------------------------------------------- { if ( altFire ) { //WP_FlechetteProxMine( ent ); WP_FlechetteAltFire(ent); } else { WP_FlechetteMainFire( ent ); } } /* ====================================================================== ROCKET LAUNCHER ====================================================================== */ //--------------------------------------------------------- void rocketThink( gentity_t *ent ) //--------------------------------------------------------- { vec3_t newdir, targetdir, up={0,0,1}, right; vec3_t org; float dot, dot2, dis; int i; float vel = (ent->spawnflags&1)?ent->speed:ROCKET_VELOCITY; if ( ent->genericValue1 && ent->genericValue1 < level.time ) {//time's up, we're done, remove us if ( ent->genericValue2 ) {//explode when die RocketDie( ent, &g_entities[ent->r.ownerNum], &g_entities[ent->r.ownerNum], 0, MOD_UNKNOWN ); } else {//just remove when die G_FreeEntity( ent ); } return; } if ( !ent->enemy || !ent->enemy->client || ent->enemy->health <= 0 || ent->enemy->client->ps.powerups[PW_CLOAKED] ) {//no enemy or enemy not a client or enemy dead or enemy cloaked if ( !ent->genericValue1 ) {//doesn't have its own self-kill time ent->nextthink = level.time + 10000; ent->think = G_FreeEntity; } return; } if ( (ent->spawnflags&1) ) {//vehicle rocket if ( ent->enemy->client && ent->enemy->client->NPC_class == CLASS_VEHICLE ) {//tracking another vehicle if ( ent->enemy->client->ps.speed+4000 > vel ) { vel = ent->enemy->client->ps.speed+4000; } } } if ( ent->enemy && ent->enemy->inuse ) { float newDirMult = ent->angle?ent->angle*2.0f:1.0f; float oldDirMult = ent->angle?(1.0f-ent->angle)*2.0f:1.0f; VectorCopy( ent->enemy->r.currentOrigin, org ); org[2] += (ent->enemy->r.mins[2] + ent->enemy->r.maxs[2]) * 0.5f; VectorSubtract( org, ent->r.currentOrigin, targetdir ); VectorNormalize( targetdir ); // Now the rocket can't do a 180 in space, so we'll limit the turn to about 45 degrees. dot = DotProduct( targetdir, ent->movedir ); if ( (ent->spawnflags&1) ) {//vehicle rocket if ( ent->radius > -1.0f ) {//can lose the lock if DotProduct drops below this number if ( dot < ent->radius ) {//lost the lock!!! //HMM... maybe can re-lock on if they come in front again? /* //OR: should it stop trying to lock altogether? if ( ent->genericValue1 ) {//have a timelimit, set next think to that ent->nextthink = ent->genericValue1; if ( ent->genericValue2 ) {//explode when die ent->think = G_ExplodeMissile; } else { ent->think = G_FreeEntity; } } else { ent->think = NULL; ent->nextthink = -1; } */ return; } } } // a dot of 1.0 means right-on-target. if ( dot < 0.0f ) { // Go in the direction opposite, start a 180. CrossProduct( ent->movedir, up, right ); dot2 = DotProduct( targetdir, right ); if ( dot2 > 0 ) { // Turn 45 degrees right. VectorMA( ent->movedir, 0.4f*newDirMult, right, newdir ); } else { // Turn 45 degrees left. VectorMA( ent->movedir, -0.4f*newDirMult, right, newdir ); } // Yeah we've adjusted horizontally, but let's split the difference vertically, so we kinda try to move towards it. newdir[2] = ( (targetdir[2]*newDirMult) + (ent->movedir[2]*oldDirMult) ) * 0.5; // let's also slow down a lot vel *= 0.5f; } else if ( dot < 0.70f ) { // Still a bit off, so we turn a bit softer VectorMA( ent->movedir, 0.5f*newDirMult, targetdir, newdir ); } else { // getting close, so turn a bit harder VectorMA( ent->movedir, 0.9f*newDirMult, targetdir, newdir ); } // add crazy drunkenness for (i = 0; i < 3; i++ ) { newdir[i] += crandom() * ent->random * 0.25f; } // decay the randomness ent->random *= 0.9f; if ( ent->enemy->client && ent->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) {//tracking a client who's on the ground, aim at the floor...? // Try to crash into the ground if we get close enough to do splash damage dis = Distance( ent->r.currentOrigin, org ); if ( dis < 128 ) { // the closer we get, the more we push the rocket down, heh heh. newdir[2] -= (1.0f - (dis / 128.0f)) * 0.6f; } } VectorNormalize( newdir ); VectorScale( newdir, vel * 0.5f, ent->s.pos.trDelta ); VectorCopy( newdir, ent->movedir ); SnapVector( ent->s.pos.trDelta ); // save net bandwidth VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); ent->s.pos.trTime = level.time; } ent->nextthink = level.time + ROCKET_ALT_THINK_TIME; // Nothing at all spectacular happened, continue. return; } extern void G_ExplodeMissile( gentity_t *ent ); void RocketDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod) { self->die = 0; self->r.contents = 0; G_ExplodeMissile( self ); self->think = G_FreeEntity; self->nextthink = level.time; } //--------------------------------------------------------- static void WP_FireRocket( gentity_t *ent, qboolean altFire ) //--------------------------------------------------------- { int damage = ROCKET_DAMAGE; int vel = ROCKET_VELOCITY; int dif = 0; float rTime; gentity_t *missile; if ( altFire ) { vel *= 0.5f; } missile = CreateMissile( muzzle, forward, vel, 10000, ent, altFire ); if (ent->client && ent->client->ps.rocketLockIndex != ENTITYNUM_NONE) { float lockTimeInterval = ((g_gametype.integer==GT_SIEGE)?2400.0f:1200.0f)/16.0f; rTime = ent->client->ps.rocketLockTime; if (rTime == -1) { rTime = ent->client->ps.rocketLastValidTime; } dif = ( level.time - rTime ) / lockTimeInterval; if (dif < 0) { dif = 0; } //It's 10 even though it locks client-side at 8, because we want them to have a sturdy lock first, and because there's a slight difference in time between server and client if ( dif >= 10 && rTime != -1 ) { missile->enemy = &g_entities[ent->client->ps.rocketLockIndex]; if (missile->enemy && missile->enemy->client && missile->enemy->health > 0 && !OnSameTeam(ent, missile->enemy)) { //if enemy became invalid, died, or is on the same team, then don't seek it missile->angle = 0.5f; missile->think = rocketThink; missile->nextthink = level.time + ROCKET_ALT_THINK_TIME; } } ent->client->ps.rocketLockIndex = ENTITYNUM_NONE; ent->client->ps.rocketLockTime = 0; ent->client->ps.rocketTargetTime = 0; } missile->classname = "rocket_proj"; missile->s.weapon = WP_ROCKET_LAUNCHER; // Make it easier to hit things VectorSet( missile->r.maxs, ROCKET_SIZE, ROCKET_SIZE, ROCKET_SIZE ); VectorScale( missile->r.maxs, -1, missile->r.mins ); missile->damage = damage; missile->dflags = DAMAGE_DEATH_KNOCKBACK; if (altFire) { missile->methodOfDeath = MOD_ROCKET_HOMING; missile->splashMethodOfDeath = MOD_ROCKET_HOMING_SPLASH; } else { missile->methodOfDeath = MOD_ROCKET; missile->splashMethodOfDeath = MOD_ROCKET_SPLASH; } //===testing being able to shoot rockets out of the air================================== missile->health = 10; missile->takedamage = qtrue; missile->r.contents = MASK_SHOT; missile->die = RocketDie; //===testing being able to shoot rockets out of the air================================== missile->clipmask = MASK_SHOT; missile->splashDamage = ROCKET_SPLASH_DAMAGE; missile->splashRadius = ROCKET_SPLASH_RADIUS; // we don't want it to ever bounce missile->bounceCount = 0; } /* ====================================================================== THERMAL DETONATOR ====================================================================== */ #define TD_DAMAGE 70 //only do 70 on a direct impact #define TD_SPLASH_RAD 128 #define TD_SPLASH_DAM 90 #define TD_VELOCITY 900 #define TD_MIN_CHARGE 0.15f #define TD_TIME 3000//6000 #define TD_ALT_TIME 3000 #define TD_ALT_DAMAGE 60//100 #define TD_ALT_SPLASH_RAD 128 #define TD_ALT_SPLASH_DAM 50//90 #define TD_ALT_VELOCITY 600 #define TD_ALT_MIN_CHARGE 0.15f #define TD_ALT_TIME 3000 void thermalThinkStandard(gentity_t *ent); //--------------------------------------------------------- void thermalDetonatorExplode( gentity_t *ent ) //--------------------------------------------------------- { if ( !ent->count ) { G_Sound( ent, CHAN_WEAPON, G_SoundIndex( "sound/weapons/thermal/warning.wav" ) ); ent->count = 1; ent->genericValue5 = level.time + 500; ent->think = thermalThinkStandard; ent->nextthink = level.time; ent->r.svFlags |= SVF_BROADCAST;//so everyone hears/sees the explosion? } else { vec3_t origin; vec3_t dir={0,0,1}; BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); origin[2] += 8; SnapVector( origin ); G_SetOrigin( ent, origin ); ent->s.eType = ET_GENERAL; G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( dir ) ); ent->freeAfterEvent = qtrue; if (G_RadiusDamage( ent->r.currentOrigin, ent->parent, ent->splashDamage, ent->splashRadius, ent, ent, ent->splashMethodOfDeath)) { g_entities[ent->r.ownerNum].client->accuracy_hits++; } trap_LinkEntity( ent ); } } void thermalThinkStandard(gentity_t *ent) { if (ent->genericValue5 < level.time) { ent->think = thermalDetonatorExplode; ent->nextthink = level.time; return; } G_RunObject(ent); ent->nextthink = level.time; } //--------------------------------------------------------- gentity_t *WP_FireThermalDetonator( gentity_t *ent, qboolean altFire ) //--------------------------------------------------------- { gentity_t *bolt; vec3_t dir, start; float chargeAmount = 1.0f; // default of full charge VectorCopy( forward, dir ); VectorCopy( muzzle, start ); bolt = G_Spawn(); bolt->physicsObject = qtrue; bolt->classname = "thermal_detonator"; bolt->think = thermalThinkStandard; bolt->nextthink = level.time; bolt->touch = touch_NULL; // How 'bout we give this thing a size... VectorSet( bolt->r.mins, -3.0f, -3.0f, -3.0f ); VectorSet( bolt->r.maxs, 3.0f, 3.0f, 3.0f ); bolt->clipmask = MASK_SHOT; W_TraceSetStart( ent, start, bolt->r.mins, bolt->r.maxs );//make sure our start point isn't on the other side of a wall if ( ent->client ) { chargeAmount = level.time - ent->client->ps.weaponChargeTime; } // get charge amount chargeAmount = chargeAmount / (float)TD_VELOCITY; if ( chargeAmount > 1.0f ) { chargeAmount = 1.0f; } else if ( chargeAmount < TD_MIN_CHARGE ) { chargeAmount = TD_MIN_CHARGE; } // normal ones bounce, alt ones explode on impact bolt->genericValue5 = level.time + TD_TIME; // How long 'til she blows bolt->s.pos.trType = TR_GRAVITY; bolt->parent = ent; bolt->r.ownerNum = ent->s.number; VectorScale( dir, TD_VELOCITY * chargeAmount, bolt->s.pos.trDelta ); if ( ent->health >= 0 ) { bolt->s.pos.trDelta[2] += 120; } if ( !altFire ) { bolt->flags |= FL_BOUNCE_HALF; } bolt->s.loopSound = G_SoundIndex( "sound/weapons/thermal/thermloop.wav" ); bolt->s.loopIsSoundset = qfalse; bolt->damage = TD_DAMAGE; bolt->dflags = 0; bolt->splashDamage = TD_SPLASH_DAM; bolt->splashRadius = TD_SPLASH_RAD; bolt->s.eType = ET_MISSILE; bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_THERMAL; bolt->methodOfDeath = MOD_THERMAL; bolt->splashMethodOfDeath = MOD_THERMAL_SPLASH; bolt->s.pos.trTime = level.time; // move a bit on the very first frame VectorCopy( start, bolt->s.pos.trBase ); SnapVector( bolt->s.pos.trDelta ); // save net bandwidth VectorCopy (start, bolt->r.currentOrigin); VectorCopy( start, bolt->pos2 ); bolt->bounceCount = -5; return bolt; } gentity_t *WP_DropThermal( gentity_t *ent ) { AngleVectors( ent->client->ps.viewangles, forward, vright, up ); return (WP_FireThermalDetonator( ent, qfalse )); } //--------------------------------------------------------- qboolean WP_LobFire( gentity_t *self, vec3_t start, vec3_t target, vec3_t mins, vec3_t maxs, int clipmask, vec3_t velocity, qboolean tracePath, int ignoreEntNum, int enemyNum, float minSpeed, float maxSpeed, float idealSpeed, qboolean mustHit ) //--------------------------------------------------------- { //for the galak mech NPC float targetDist, shotSpeed, speedInc = 100, travelTime, impactDist, bestImpactDist = Q3_INFINITE;//fireSpeed, vec3_t targetDir, shotVel, failCase; trace_t trace; trajectory_t tr; qboolean blocked; int elapsedTime, skipNum, timeStep = 500, hitCount = 0, maxHits = 7; vec3_t lastPos, testPos; gentity_t *traceEnt; if ( !idealSpeed ) { idealSpeed = 300; } else if ( idealSpeed < speedInc ) { idealSpeed = speedInc; } shotSpeed = idealSpeed; skipNum = (idealSpeed-speedInc)/speedInc; if ( !minSpeed ) { minSpeed = 100; } if ( !maxSpeed ) { maxSpeed = 900; } while ( hitCount < maxHits ) { VectorSubtract( target, start, targetDir ); targetDist = VectorNormalize( targetDir ); VectorScale( targetDir, shotSpeed, shotVel ); travelTime = targetDist/shotSpeed; shotVel[2] += travelTime * 0.5 * g_gravity.value; if ( !hitCount ) {//save the first (ideal) one as the failCase (fallback value) if ( !mustHit ) {//default is fine as a return value VectorCopy( shotVel, failCase ); } } if ( tracePath ) {//do a rough trace of the path blocked = qfalse; VectorCopy( start, tr.trBase ); VectorCopy( shotVel, tr.trDelta ); tr.trType = TR_GRAVITY; tr.trTime = level.time; travelTime *= 1000.0f; VectorCopy( start, lastPos ); //This may be kind of wasteful, especially on long throws... use larger steps? Divide the travelTime into a certain hard number of slices? Trace just to apex and down? for ( elapsedTime = timeStep; elapsedTime < floor(travelTime)+timeStep; elapsedTime += timeStep ) { if ( (float)elapsedTime > travelTime ) {//cap it elapsedTime = floor( travelTime ); } BG_EvaluateTrajectory( &tr, level.time + elapsedTime, testPos ); trap_Trace( &trace, lastPos, mins, maxs, testPos, ignoreEntNum, clipmask ); if ( trace.allsolid || trace.startsolid ) { blocked = qtrue; break; } if ( trace.fraction < 1.0f ) {//hit something if ( trace.entityNum == enemyNum ) {//hit the enemy, that's perfect! break; } else if ( trace.plane.normal[2] > 0.7 && DistanceSquared( trace.endpos, target ) < 4096 )//hit within 64 of desired location, should be okay {//close enough! break; } else {//FIXME: maybe find the extents of this brush and go above or below it on next try somehow? impactDist = DistanceSquared( trace.endpos, target ); if ( impactDist < bestImpactDist ) { bestImpactDist = impactDist; VectorCopy( shotVel, failCase ); } blocked = qtrue; //see if we should store this as the failCase if ( trace.entityNum < ENTITYNUM_WORLD ) {//hit an ent traceEnt = &g_entities[trace.entityNum]; if ( traceEnt && traceEnt->takedamage && !OnSameTeam( self, traceEnt ) ) {//hit something breakable, so that's okay //we haven't found a clear shot yet so use this as the failcase VectorCopy( shotVel, failCase ); } } break; } } if ( elapsedTime == floor( travelTime ) ) {//reached end, all clear break; } else { //all clear, try next slice VectorCopy( testPos, lastPos ); } } if ( blocked ) {//hit something, adjust speed (which will change arc) hitCount++; shotSpeed = idealSpeed + ((hitCount-skipNum) * speedInc);//from min to max (skipping ideal) if ( hitCount >= skipNum ) {//skip ideal since that was the first value we tested shotSpeed += speedInc; } } else {//made it! break; } } else {//no need to check the path, go with first calc break; } } if ( hitCount >= maxHits ) {//NOTE: worst case scenario, use the one that impacted closest to the target (or just use the first try...?) VectorCopy( failCase, velocity ); return qfalse; } VectorCopy( shotVel, velocity ); return qtrue; } /* ====================================================================== LASER TRAP / TRIP MINE ====================================================================== */ #define LT_DAMAGE 100 #define LT_SPLASH_RAD 256.0f #define LT_SPLASH_DAM 105 #define LT_VELOCITY 900.0f #define LT_SIZE 1.5f #define LT_ALT_TIME 2000 #define LT_ACTIVATION_DELAY 1000 #define LT_DELAY_TIME 50 void laserTrapExplode( gentity_t *self ) { vec3_t v; self->takedamage = qfalse; if (self->activator) { G_RadiusDamage( self->r.currentOrigin, self->activator, self->splashDamage, self->splashRadius, self, self, MOD_TRIP_MINE_SPLASH/*MOD_LT_SPLASH*/ ); } if (self->s.weapon != WP_FLECHETTE) { G_AddEvent( self, EV_MISSILE_MISS, 0); } VectorCopy(self->s.pos.trDelta, v); //Explode outward from the surface if (self->s.time == -2) { v[0] = 0; v[1] = 0; v[2] = 0; } if (self->s.weapon == WP_FLECHETTE) { G_PlayEffect(EFFECT_EXPLOSION_FLECHETTE, self->r.currentOrigin, v); } else { G_PlayEffect(EFFECT_EXPLOSION_TRIPMINE, self->r.currentOrigin, v); } self->think = G_FreeEntity; self->nextthink = level.time; } void laserTrapDelayedExplode( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) { self->enemy = attacker; self->think = laserTrapExplode; self->nextthink = level.time + FRAMETIME; self->takedamage = qfalse; if ( attacker && !attacker->s.number ) { //less damage when shot by player self->splashDamage /= 3; self->splashRadius /= 3; } } void touchLaserTrap( gentity_t *ent, gentity_t *other, trace_t *trace ) { if (other && other->s.number < ENTITYNUM_WORLD) { //just explode if we hit any entity. This way we don't have things happening like tripmines floating //in the air after getting stuck to a moving door if ( ent->activator != other ) { ent->touch = 0; ent->nextthink = level.time + FRAMETIME; ent->think = laserTrapExplode; VectorCopy(trace->plane.normal, ent->s.pos.trDelta); } } else { ent->touch = 0; if (trace->entityNum != ENTITYNUM_NONE) { ent->enemy = &g_entities[trace->entityNum]; } laserTrapStick(ent, trace->endpos, trace->plane.normal); } } void proxMineThink(gentity_t *ent) { int i = 0; gentity_t *cl; gentity_t *owner = NULL; if (ent->r.ownerNum < ENTITYNUM_WORLD) { owner = &g_entities[ent->r.ownerNum]; } ent->nextthink = level.time; if (ent->genericValue15 < level.time || !owner || !owner->inuse || !owner->client || owner->client->pers.connected != CON_CONNECTED) { //time to die! ent->think = laserTrapExplode; return; } while (i < MAX_CLIENTS) { //eh, just check for clients, don't care about anyone else... cl = &g_entities[i]; if (cl->inuse && cl->client && cl->client->pers.connected == CON_CONNECTED && owner != cl && cl->client->sess.sessionTeam != TEAM_SPECTATOR && cl->client->tempSpectate < level.time && cl->health > 0) { if (!OnSameTeam(owner, cl) || g_friendlyFire.integer) { //not on the same team, or friendly fire is enabled vec3_t v; VectorSubtract(ent->r.currentOrigin, cl->client->ps.origin, v); if (VectorLength(v) < (ent->splashRadius/2.0f)) { ent->think = laserTrapExplode; return; } } } i++; } } void laserTrapThink ( gentity_t *ent ) { gentity_t *traceEnt; vec3_t end; trace_t tr; //just relink it every think trap_LinkEntity(ent); //turn on the beam effect if ( !(ent->s.eFlags&EF_FIRING) ) {//arm me G_Sound( ent, CHAN_WEAPON, G_SoundIndex( "sound/weapons/laser_trap/warning.wav" ) ); ent->s.eFlags |= EF_FIRING; } ent->think = laserTrapThink; ent->nextthink = level.time + FRAMETIME; // Find the main impact point VectorMA ( ent->s.pos.trBase, 1024, ent->movedir, end ); trap_Trace ( &tr, ent->r.currentOrigin, NULL, NULL, end, ent->s.number, MASK_SHOT); traceEnt = &g_entities[ tr.entityNum ]; ent->s.time = -1; //let all clients know to draw a beam from this guy if ( traceEnt->client || tr.startsolid ) { //go boom ent->touch = 0; ent->nextthink = level.time + LT_DELAY_TIME; ent->think = laserTrapExplode; } } void laserTrapStick( gentity_t *ent, vec3_t endpos, vec3_t normal ) { G_SetOrigin( ent, endpos ); VectorCopy( normal, ent->pos1 ); VectorClear( ent->s.apos.trDelta ); // This will orient the object to face in the direction of the normal VectorCopy( normal, ent->s.pos.trDelta ); //VectorScale( normal, -1, ent->s.pos.trDelta ); ent->s.pos.trTime = level.time; //This does nothing, cg_missile makes assumptions about direction of travel controlling angles vectoangles( normal, ent->s.apos.trBase ); VectorClear( ent->s.apos.trDelta ); ent->s.apos.trType = TR_STATIONARY; VectorCopy( ent->s.apos.trBase, ent->s.angles ); VectorCopy( ent->s.angles, ent->r.currentAngles ); G_Sound( ent, CHAN_WEAPON, G_SoundIndex( "sound/weapons/laser_trap/stick.wav" ) ); if ( ent->count ) {//a tripwire //add draw line flag VectorCopy( normal, ent->movedir ); ent->think = laserTrapThink; ent->nextthink = level.time + LT_ACTIVATION_DELAY;//delay the activation ent->touch = touch_NULL; //make it shootable ent->takedamage = qtrue; ent->health = 5; ent->die = laserTrapDelayedExplode; //shove the box through the wall VectorSet( ent->r.mins, -LT_SIZE*2, -LT_SIZE*2, -LT_SIZE*2 ); VectorSet( ent->r.maxs, LT_SIZE*2, LT_SIZE*2, LT_SIZE*2 ); //so that the owner can blow it up with projectiles ent->r.svFlags |= SVF_OWNERNOTSHARED; } else { ent->touch = touchLaserTrap; ent->think = proxMineThink;//laserTrapExplode; ent->genericValue15 = level.time + 30000; //auto-explode after 30 seconds. ent->nextthink = level.time + LT_ALT_TIME; // How long 'til she blows //make it shootable ent->takedamage = qtrue; ent->health = 5; ent->die = laserTrapDelayedExplode; //shove the box through the wall VectorSet( ent->r.mins, -LT_SIZE*2, -LT_SIZE*2, -LT_SIZE*2 ); VectorSet( ent->r.maxs, LT_SIZE*2, LT_SIZE*2, LT_SIZE*2 ); //so that the owner can blow it up with projectiles ent->r.svFlags |= SVF_OWNERNOTSHARED; if ( !(ent->s.eFlags&EF_FIRING) ) {//arm me G_Sound( ent, CHAN_WEAPON, G_SoundIndex( "sound/weapons/laser_trap/warning.wav" ) ); ent->s.eFlags |= EF_FIRING; ent->s.time = -1; ent->s.bolt2 = 1; } } } void TrapThink(gentity_t *ent) { //laser trap think ent->nextthink = level.time + 50; G_RunObject(ent); } void CreateLaserTrap( gentity_t *laserTrap, vec3_t start, gentity_t *owner ) { //create a laser trap entity laserTrap->classname = "laserTrap"; laserTrap->flags |= FL_BOUNCE_HALF; laserTrap->s.eFlags |= EF_MISSILE_STICK; laserTrap->splashDamage = LT_SPLASH_DAM; laserTrap->splashRadius = LT_SPLASH_RAD; laserTrap->damage = LT_DAMAGE; laserTrap->methodOfDeath = MOD_TRIP_MINE_SPLASH; laserTrap->splashMethodOfDeath = MOD_TRIP_MINE_SPLASH; laserTrap->s.eType = ET_GENERAL; laserTrap->r.svFlags = SVF_USE_CURRENT_ORIGIN; laserTrap->s.weapon = WP_TRIP_MINE; laserTrap->s.pos.trType = TR_GRAVITY; laserTrap->r.contents = MASK_SHOT; laserTrap->parent = owner; laserTrap->activator = owner; laserTrap->r.ownerNum = owner->s.number; VectorSet( laserTrap->r.mins, -LT_SIZE, -LT_SIZE, -LT_SIZE ); VectorSet( laserTrap->r.maxs, LT_SIZE, LT_SIZE, LT_SIZE ); laserTrap->clipmask = MASK_SHOT; laserTrap->s.solid = 2; laserTrap->s.modelindex = G_ModelIndex( "models/weapons2/laser_trap/laser_trap_w.glm" ); laserTrap->s.modelGhoul2 = 1; laserTrap->s.g2radius = 40; laserTrap->s.genericenemyindex = owner->s.number+MAX_GENTITIES; laserTrap->health = 1; laserTrap->s.time = 0; laserTrap->s.pos.trTime = level.time; // move a bit on the very first frame VectorCopy( start, laserTrap->s.pos.trBase ); SnapVector( laserTrap->s.pos.trBase ); // save net bandwidth SnapVector( laserTrap->s.pos.trDelta ); // save net bandwidth VectorCopy (start, laserTrap->r.currentOrigin); laserTrap->s.apos.trType = TR_GRAVITY; laserTrap->s.apos.trTime = level.time; laserTrap->s.apos.trBase[YAW] = rand()%360; laserTrap->s.apos.trBase[PITCH] = rand()%360; laserTrap->s.apos.trBase[ROLL] = rand()%360; if (rand()%10 < 5) { laserTrap->s.apos.trBase[YAW] = -laserTrap->s.apos.trBase[YAW]; } VectorCopy( start, laserTrap->pos2 ); laserTrap->touch = touchLaserTrap; laserTrap->think = TrapThink; laserTrap->nextthink = level.time + 50; } void WP_PlaceLaserTrap( gentity_t *ent, qboolean alt_fire ) { gentity_t *laserTrap; gentity_t *found = NULL; vec3_t dir, start; int trapcount = 0; int foundLaserTraps[MAX_GENTITIES]; int trapcount_org; int lowestTimeStamp; int removeMe; int i; foundLaserTraps[0] = ENTITYNUM_NONE; VectorCopy( forward, dir ); VectorCopy( muzzle, start ); laserTrap = G_Spawn(); //limit to 10 placed at any one time //see how many there are now while ( (found = G_Find( found, FOFS(classname), "laserTrap" )) != NULL ) { if ( found->parent != ent ) { continue; } foundLaserTraps[trapcount++] = found->s.number; } //now remove first ones we find until there are only 9 left found = NULL; trapcount_org = trapcount; lowestTimeStamp = level.time; while ( trapcount > 9 ) { removeMe = -1; for ( i = 0; i < trapcount_org; i++ ) { if ( foundLaserTraps[i] == ENTITYNUM_NONE ) { continue; } found = &g_entities[foundLaserTraps[i]]; if ( laserTrap && found->setTime < lowestTimeStamp ) { removeMe = i; lowestTimeStamp = found->setTime; } } if ( removeMe != -1 ) { //remove it... or blow it? if ( &g_entities[foundLaserTraps[removeMe]] == NULL ) { break; } else { G_FreeEntity( &g_entities[foundLaserTraps[removeMe]] ); } foundLaserTraps[removeMe] = ENTITYNUM_NONE; trapcount--; } else { break; } } //now make the new one CreateLaserTrap( laserTrap, start, ent ); //set player-created-specific fields laserTrap->setTime = level.time;//remember when we placed it if (!alt_fire) {//tripwire laserTrap->count = 1; } //move it laserTrap->s.pos.trType = TR_GRAVITY; if (alt_fire) { VectorScale( dir, 512, laserTrap->s.pos.trDelta ); } else { VectorScale( dir, 256, laserTrap->s.pos.trDelta ); } trap_LinkEntity(laserTrap); } /* ====================================================================== DET PACK ====================================================================== */ void VectorNPos(vec3_t in, vec3_t out) { if (in[0] < 0) { out[0] = -in[0]; } else { out[0] = in[0]; } if (in[1] < 0) { out[1] = -in[1]; } else { out[1] = in[1]; } if (in[2] < 0) { out[2] = -in[2]; } else { out[2] = in[2]; } } void DetPackBlow(gentity_t *self); void charge_stick (gentity_t *self, gentity_t *other, trace_t *trace) { gentity_t *tent; if ( other && (other->flags&FL_BBRUSH) && other->s.pos.trType == TR_STATIONARY && other->s.apos.trType == TR_STATIONARY ) {//a perfectly still breakable brush, let us attach directly to it! self->target_ent = other;//remember them when we blow up } else if ( other && other->s.number < ENTITYNUM_WORLD && other->s.eType == ET_MOVER && trace->plane.normal[2] > 0 ) {//stick to it? self->s.groundEntityNum = other->s.number; } else if (other && other->s.number < ENTITYNUM_WORLD && (other->client || !other->s.weapon)) { //hit another entity that is not stickable, "bounce" off vec3_t vNor, tN; VectorCopy(trace->plane.normal, vNor); VectorNormalize(vNor); VectorNPos(self->s.pos.trDelta, tN); self->s.pos.trDelta[0] += vNor[0]*(tN[0]*(((float)Q_irand(1, 10))*0.1)); self->s.pos.trDelta[1] += vNor[1]*(tN[1]*(((float)Q_irand(1, 10))*0.1)); self->s.pos.trDelta[2] += vNor[1]*(tN[2]*(((float)Q_irand(1, 10))*0.1)); vectoangles(vNor, self->s.angles); vectoangles(vNor, self->s.apos.trBase); self->touch = charge_stick; return; } else if (other && other->s.number < ENTITYNUM_WORLD) { //hit an entity that we just want to explode on (probably another projectile or something) vec3_t v; self->touch = 0; self->think = 0; self->nextthink = 0; self->takedamage = qfalse; VectorClear(self->s.apos.trDelta); self->s.apos.trType = TR_STATIONARY; G_RadiusDamage( self->r.currentOrigin, self->parent, self->splashDamage, self->splashRadius, self, self, MOD_DET_PACK_SPLASH ); VectorCopy(trace->plane.normal, v); VectorCopy(v, self->pos2); self->count = -1; G_PlayEffect(EFFECT_EXPLOSION_DETPACK, self->r.currentOrigin, v); self->think = G_FreeEntity; self->nextthink = level.time; return; } //if we get here I guess we hit hte world so we can stick to it self->touch = 0; self->think = DetPackBlow; self->nextthink = level.time + 30000; VectorClear(self->s.apos.trDelta); self->s.apos.trType = TR_STATIONARY; self->s.pos.trType = TR_STATIONARY; VectorCopy( self->r.currentOrigin, self->s.origin ); VectorCopy( self->r.currentOrigin, self->s.pos.trBase ); VectorClear( self->s.pos.trDelta ); VectorClear( self->s.apos.trDelta ); VectorNormalize(trace->plane.normal); vectoangles(trace->plane.normal, self->s.angles); VectorCopy(self->s.angles, self->r.currentAngles ); VectorCopy(self->s.angles, self->s.apos.trBase); VectorCopy(trace->plane.normal, self->pos2); self->count = -1; G_Sound(self, CHAN_WEAPON, G_SoundIndex("sound/weapons/detpack/stick.wav")); tent = G_TempEntity( self->r.currentOrigin, EV_MISSILE_MISS ); tent->s.weapon = 0; tent->parent = self; tent->r.ownerNum = self->s.number; //so that the owner can blow it up with projectiles self->r.svFlags |= SVF_OWNERNOTSHARED; } void DetPackBlow(gentity_t *self) { vec3_t v; self->pain = 0; self->die = 0; self->takedamage = qfalse; if ( self->target_ent ) {//we were attached to something, do *direct* damage to it! G_Damage( self->target_ent, self, &g_entities[self->r.ownerNum], v, self->r.currentOrigin, self->damage, 0, MOD_DET_PACK_SPLASH ); } G_RadiusDamage( self->r.currentOrigin, self->parent, self->splashDamage, self->splashRadius, self, self, MOD_DET_PACK_SPLASH ); v[0] = 0; v[1] = 0; v[2] = 1; if (self->count == -1) { VectorCopy(self->pos2, v); } G_PlayEffect(EFFECT_EXPLOSION_DETPACK, self->r.currentOrigin, v); self->think = G_FreeEntity; self->nextthink = level.time; } void DetPackPain(gentity_t *self, gentity_t *attacker, int damage) { self->think = DetPackBlow; self->nextthink = level.time + Q_irand(50, 100); self->takedamage = qfalse; } void DetPackDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod) { self->think = DetPackBlow; self->nextthink = level.time + Q_irand(50, 100); self->takedamage = qfalse; } void drop_charge (gentity_t *self, vec3_t start, vec3_t dir) { gentity_t *bolt; VectorNormalize (dir); bolt = G_Spawn(); bolt->classname = "detpack"; bolt->nextthink = level.time + FRAMETIME; bolt->think = G_RunObject; bolt->s.eType = ET_GENERAL; bolt->s.g2radius = 100; bolt->s.modelGhoul2 = 1; bolt->s.modelindex = G_ModelIndex("models/weapons2/detpack/det_pack_proj.glm"); bolt->parent = self; bolt->r.ownerNum = self->s.number; bolt->damage = 100; bolt->splashDamage = 200; bolt->splashRadius = 200; bolt->methodOfDeath = MOD_DET_PACK_SPLASH; bolt->splashMethodOfDeath = MOD_DET_PACK_SPLASH; bolt->clipmask = MASK_SHOT; bolt->s.solid = 2; bolt->r.contents = MASK_SHOT; bolt->touch = charge_stick; bolt->physicsObject = qtrue; bolt->s.genericenemyindex = self->s.number+MAX_GENTITIES; //rww - so client prediction knows we own this and won't hit it VectorSet( bolt->r.mins, -2, -2, -2 ); VectorSet( bolt->r.maxs, 2, 2, 2 ); bolt->health = 1; bolt->takedamage = qtrue; bolt->pain = DetPackPain; bolt->die = DetPackDie; bolt->s.weapon = WP_DET_PACK; bolt->setTime = level.time; G_SetOrigin(bolt, start); bolt->s.pos.trType = TR_GRAVITY; VectorCopy( start, bolt->s.pos.trBase ); VectorScale(dir, 300, bolt->s.pos.trDelta ); bolt->s.pos.trTime = level.time; bolt->s.apos.trType = TR_GRAVITY; bolt->s.apos.trTime = level.time; bolt->s.apos.trBase[YAW] = rand()%360; bolt->s.apos.trBase[PITCH] = rand()%360; bolt->s.apos.trBase[ROLL] = rand()%360; if (rand()%10 < 5) { bolt->s.apos.trBase[YAW] = -bolt->s.apos.trBase[YAW]; } vectoangles(dir, bolt->s.angles); VectorCopy(bolt->s.angles, bolt->s.apos.trBase); VectorSet(bolt->s.apos.trDelta, 300, 0, 0 ); bolt->s.apos.trTime = level.time; trap_LinkEntity(bolt); } void BlowDetpacks(gentity_t *ent) { gentity_t *found = NULL; if ( ent->client->ps.hasDetPackPlanted ) { while ( (found = G_Find( found, FOFS(classname), "detpack") ) != NULL ) {//loop through all ents and blow the crap out of them! if ( found->parent == ent ) { VectorCopy( found->r.currentOrigin, found->s.origin ); found->think = DetPackBlow; found->nextthink = level.time + 100 + random() * 200; G_Sound( found, CHAN_BODY, G_SoundIndex("sound/weapons/detpack/warning.wav") ); } } ent->client->ps.hasDetPackPlanted = qfalse; } } qboolean CheatsOn(void) { if ( !g_cheats.integer ) { return qfalse; } return qtrue; } void WP_DropDetPack( gentity_t *ent, qboolean alt_fire ) { gentity_t *found = NULL; int trapcount = 0; int foundDetPacks[MAX_GENTITIES] = {ENTITYNUM_NONE}; int trapcount_org; int lowestTimeStamp; int removeMe; int i; if ( !ent || !ent->client ) { return; } //limit to 10 placed at any one time //see how many there are now while ( (found = G_Find( found, FOFS(classname), "detpack" )) != NULL ) { if ( found->parent != ent ) { continue; } foundDetPacks[trapcount++] = found->s.number; } //now remove first ones we find until there are only 9 left found = NULL; trapcount_org = trapcount; lowestTimeStamp = level.time; while ( trapcount > 9 ) { removeMe = -1; for ( i = 0; i < trapcount_org; i++ ) { if ( foundDetPacks[i] == ENTITYNUM_NONE ) { continue; } found = &g_entities[foundDetPacks[i]]; if ( found->setTime < lowestTimeStamp ) { removeMe = i; lowestTimeStamp = found->setTime; } } if ( removeMe != -1 ) { //remove it... or blow it? if ( &g_entities[foundDetPacks[removeMe]] == NULL ) { break; } else { if (!CheatsOn()) { //Let them have unlimited if cheats are enabled G_FreeEntity( &g_entities[foundDetPacks[removeMe]] ); } } foundDetPacks[removeMe] = ENTITYNUM_NONE; trapcount--; } else { break; } } if ( alt_fire ) { BlowDetpacks(ent); } else { AngleVectors( ent->client->ps.viewangles, forward, vright, up ); CalcMuzzlePoint( ent, forward, vright, up, muzzle ); VectorNormalize( forward ); VectorMA( muzzle, -4, forward, muzzle ); drop_charge( ent, muzzle, forward ); ent->client->ps.hasDetPackPlanted = qtrue; } } #pragma warning(disable : 4701) //local variable may be used without having been initialized static void WP_FireConcussionAlt( gentity_t *ent ) {//a rail-gun-like beam int damage = CONC_ALT_DAMAGE, skip, traces = DISRUPTOR_ALT_TRACES; qboolean render_impact = qtrue; vec3_t start, end; vec3_t muzzle2, dir; trace_t tr; gentity_t *traceEnt, *tent; float shotRange = 8192.0f; qboolean hitDodged = qfalse; vec3_t shot_mins, shot_maxs; int i; //Shove us backwards for half a second VectorMA( ent->client->ps.velocity, -200, forward, ent->client->ps.velocity ); ent->client->ps.groundEntityNum = ENTITYNUM_NONE; if ( (ent->client->ps.pm_flags&PMF_DUCKED) ) {//hunkered down ent->client->ps.pm_time = 100; } else { ent->client->ps.pm_time = 250; } // ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK|PMF_TIME_NOFRICTION; //FIXME: only if on ground? So no "rocket jump"? Or: (see next FIXME) //FIXME: instead, set a forced ucmd backmove instead of this sliding VectorCopy( muzzle, muzzle2 ); // making a backup copy VectorCopy( muzzle, start ); WP_TraceSetStart( ent, start, vec3_origin, vec3_origin ); skip = ent->s.number; // if ( ent->client && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time ) // { // // in overcharge mode, so doing double damage // damage *= 2; // } //Make it a little easier to hit guys at long range VectorSet( shot_mins, -1, -1, -1 ); VectorSet( shot_maxs, 1, 1, 1 ); for ( i = 0; i < traces; i++ ) { VectorMA( start, shotRange, forward, end ); //NOTE: if you want to be able to hit guys in emplaced guns, use "G2_COLLIDE, 10" instead of "G2_RETURNONHIT, 0" //alternately, if you end up hitting an emplaced_gun that has a sitter, just redo this one trace with the "G2_COLLIDE, 10" to see if we it the sitter //gi.trace( &tr, start, NULL, NULL, end, skip, MASK_SHOT, G2_COLLIDE, 10 );//G2_RETURNONHIT, 0 ); if (d_projectileGhoul2Collision.integer) { trap_G2Trace( &tr, start, shot_mins, shot_maxs, end, skip, MASK_SHOT, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer ); } else { trap_Trace( &tr, start, shot_mins, shot_maxs, end, skip, MASK_SHOT ); } traceEnt = &g_entities[tr.entityNum]; if (d_projectileGhoul2Collision.integer && traceEnt->inuse && traceEnt->client) { //g2 collision checks -rww if (traceEnt->inuse && traceEnt->client && traceEnt->ghoul2) { //since we used G2TRFLAG_GETSURFINDEX, tr.surfaceFlags will actually contain the index of the surface on the ghoul2 model we collided with. traceEnt->client->g2LastSurfaceHit = tr.surfaceFlags; traceEnt->client->g2LastSurfaceTime = level.time; } if (traceEnt->ghoul2) { tr.surfaceFlags = 0; //clear the surface flags after, since we actually care about them in here. } } if ( tr.surfaceFlags & SURF_NOIMPACT ) { render_impact = qfalse; } if ( tr.entityNum == ent->s.number ) { // should never happen, but basically we don't want to consider a hit to ourselves? // Get ready for an attempt to trace through another person VectorCopy( tr.endpos, muzzle2 ); VectorCopy( tr.endpos, start ); skip = tr.entityNum; #ifdef _DEBUG Com_Printf( "BAD! Concussion gun shot somehow traced back and hit the owner!\n" ); #endif continue; } // always render a shot beam, doing this the old way because I don't much feel like overriding the effect. //NOTE: let's just draw one beam at the end //tent = G_TempEntity( tr.endpos, EV_CONC_ALT_SHOT ); //tent->svFlags |= SVF_BROADCAST; //VectorCopy( muzzle2, tent->s.origin2 ); if ( tr.fraction >= 1.0f ) { // draw the beam but don't do anything else break; } if ( traceEnt->s.weapon == WP_SABER )//&& traceEnt->NPC {//FIXME: need a more reliable way to know we hit a jedi? hitDodged = Jedi_DodgeEvasion( traceEnt, ent, &tr, HL_NONE ); //acts like we didn't even hit him } if ( !hitDodged ) { if ( render_impact ) { if (( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage ) || !Q_stricmp( traceEnt->classname, "misc_model_breakable" ) || traceEnt->s.eType == ET_MOVER ) { qboolean noKnockBack; // Create a simple impact type mark that doesn't last long in the world //G_PlayEffectID( G_EffectIndex( "concussion/alt_hit" ), tr.endpos, tr.plane.normal ); //no no no if ( traceEnt->client && LogAccuracyHit( traceEnt, ent )) {//NOTE: hitting multiple ents can still get you over 100% accuracy ent->client->accuracy_hits++; } noKnockBack = (traceEnt->flags&FL_NO_KNOCKBACK);//will be set if they die, I want to know if it was on *before* they died if ( traceEnt && traceEnt->client && traceEnt->client->NPC_class == CLASS_GALAKMECH ) {//hehe G_Damage( traceEnt, ent, ent, forward, tr.endpos, 10, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_CONC_ALT ); break; } G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_CONC_ALT ); //do knockback and knockdown manually if ( traceEnt->client ) {//only if we hit a client vec3_t pushDir; VectorCopy( forward, pushDir ); if ( pushDir[2] < 0.2f ) { pushDir[2] = 0.2f; }//hmm, re-normalize? nah... /* if ( !noKnockBack ) {//knock-backable G_Throw( traceEnt, pushDir, 200 ); } */ if ( traceEnt->health > 0 ) {//alive //if ( G_HasKnockdownAnims( traceEnt ) ) if (!noKnockBack && !traceEnt->localAnimIndex && traceEnt->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN && BG_KnockDownable(&traceEnt->client->ps)) //just check for humanoids.. {//knock-downable //G_Knockdown( traceEnt, ent, pushDir, 400, qtrue ); vec3_t plPDif; float pStr; //cap it and stuff, base the strength and whether or not we can knockdown on the distance //from the shooter to the target VectorSubtract(traceEnt->client->ps.origin, ent->client->ps.origin, plPDif); pStr = 500.0f-VectorLength(plPDif); if (pStr < 150.0f) { pStr = 150.0f; } if (pStr > 200.0f) { traceEnt->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; traceEnt->client->ps.forceHandExtendTime = level.time + 1100; traceEnt->client->ps.forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim } traceEnt->client->ps.otherKiller = ent->s.number; traceEnt->client->ps.otherKillerTime = level.time + 5000; traceEnt->client->ps.otherKillerDebounceTime = level.time + 100; traceEnt->client->otherKillerMOD = MOD_UNKNOWN; traceEnt->client->otherKillerVehWeapon = 0; traceEnt->client->otherKillerWeaponType = WP_NONE; traceEnt->client->ps.velocity[0] += pushDir[0]*pStr; traceEnt->client->ps.velocity[1] += pushDir[1]*pStr; traceEnt->client->ps.velocity[2] = pStr; } } } if ( traceEnt->s.eType == ET_MOVER ) {//stop the traces on any mover break; } } else { // we only make this mark on things that can't break or move // tent = G_TempEntity(tr.endpos, EV_MISSILE_MISS); // tent->s.eventParm = DirToByte(tr.plane.normal); // tent->s.eFlags |= EF_ALT_FIRING; //tent->svFlags |= SVF_BROADCAST; //eh? why broadcast? // VectorCopy( tr.plane.normal, tent->pos1 ); //mmm..no..don't do this more than once for no reason whatsoever. break; // hit solid, but doesn't take damage, so stop the shot...we _could_ allow it to shoot through walls, might be cool? } } else // not rendering impact, must be a skybox or other similar thing? { break; // don't try anymore traces } } // Get ready for an attempt to trace through another person VectorCopy( tr.endpos, muzzle2 ); VectorCopy( tr.endpos, start ); skip = tr.entityNum; hitDodged = qfalse; } //just draw one beam all the way to the end // tent = G_TempEntity( tr.endpos, EV_CONC_ALT_SHOT ); // tent->svFlags |= SVF_BROADCAST; //again, why broadcast? // tent = G_TempEntity(tr.endpos, EV_MISSILE_MISS); // tent->s.eventParm = DirToByte(tr.plane.normal); // tent->s.eFlags |= EF_ALT_FIRING; // VectorCopy( muzzle, tent->s.origin2 ); // now go along the trail and make sight events VectorSubtract( tr.endpos, muzzle, dir ); // shotDist = VectorNormalize( dir ); //let's pack all this junk into a single tempent, and send it off. tent = G_TempEntity(tr.endpos, EV_CONC_ALT_IMPACT); tent->s.eventParm = DirToByte(tr.plane.normal); tent->s.owner = ent->s.number; VectorCopy(dir, tent->s.angles); VectorCopy(muzzle, tent->s.origin2); VectorCopy(forward, tent->s.angles2); #if 0 //yuck //FIXME: if shoot *really* close to someone, the alert could be way out of their FOV for ( dist = 0; dist < shotDist; dist += 64 ) { //FIXME: on a really long shot, this could make a LOT of alerts in one frame... VectorMA( muzzle, dist, dir, spot ); AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 ); //FIXME: creates *way* too many effects, make it one effect somehow? G_PlayEffectID( G_EffectIndex( "concussion/alt_ring" ), spot, actualAngles ); } //FIXME: spawn a temp ent that continuously spawns sight alerts here? And 1 sound alert to draw their attention? VectorMA( start, shotDist-4, forward, spot ); AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 ); G_PlayEffectID( G_EffectIndex( "concussion/altmuzzle_flash" ), muzzle, forward ); #endif } #pragma warning(default : 4701) //local variable may be used without having been initialized static void WP_FireConcussion( gentity_t *ent ) {//a fast rocket-like projectile vec3_t start; int damage = CONC_DAMAGE; float vel = CONC_VELOCITY; gentity_t *missile; //hold us still for a bit //ent->client->ps.pm_time = 300; //ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; //add viewkick // if ( ent->s.number < MAX_CLIENTS//player only // && !cg.renderingThirdPerson )//gives an advantage to being in 3rd person, but would look silly otherwise // {//kick the view back // cg.kick_angles[PITCH] = Q_flrand( -10, -15 ); // cg.kick_time = level.time; // } //mm..yeah..this needs some reworking for mp VectorCopy( muzzle, start ); WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall missile = CreateMissile( start, forward, vel, 10000, ent, qfalse ); missile->classname = "conc_proj"; missile->s.weapon = WP_CONCUSSION; missile->mass = 10; // Make it easier to hit things VectorSet( missile->r.maxs, ROCKET_SIZE, ROCKET_SIZE, ROCKET_SIZE ); VectorScale( missile->r.maxs, -1, missile->r.mins ); missile->damage = damage; missile->dflags = DAMAGE_EXTRA_KNOCKBACK; missile->methodOfDeath = MOD_CONC; missile->splashMethodOfDeath = MOD_CONC; missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; missile->splashDamage = CONC_SPLASH_DAMAGE; missile->splashRadius = CONC_SPLASH_RADIUS; // we don't want it to ever bounce missile->bounceCount = 0; } //--------------------------------------------------------- // FireStunBaton //--------------------------------------------------------- void WP_FireStunBaton( gentity_t *ent, qboolean alt_fire ) { gentity_t *tr_ent; trace_t tr; vec3_t mins, maxs, end; vec3_t muzzleStun; if (!ent->client) { VectorCopy(ent->r.currentOrigin, muzzleStun); muzzleStun[2] += 8; } else { VectorCopy(ent->client->ps.origin, muzzleStun); muzzleStun[2] += ent->client->ps.viewheight-6; } VectorMA(muzzleStun, 20.0f, forward, muzzleStun); VectorMA(muzzleStun, 4.0f, vright, muzzleStun); VectorMA( muzzleStun, STUN_BATON_RANGE, forward, end ); VectorSet( maxs, 6, 6, 6 ); VectorScale( maxs, -1, mins ); trap_Trace ( &tr, muzzleStun, mins, maxs, end, ent->s.number, MASK_SHOT ); if ( tr.entityNum >= ENTITYNUM_WORLD ) { return; } tr_ent = &g_entities[tr.entityNum]; if (tr_ent && tr_ent->takedamage && tr_ent->client) { //see if either party is involved in a duel if (tr_ent->client->ps.duelInProgress && tr_ent->client->ps.duelIndex != ent->s.number) { return; } if (ent->client && ent->client->ps.duelInProgress && ent->client->ps.duelIndex != tr_ent->s.number) { return; } } if ( tr_ent && tr_ent->takedamage ) { G_PlayEffect( EFFECT_STUNHIT, tr.endpos, tr.plane.normal ); G_Sound( tr_ent, CHAN_WEAPON, G_SoundIndex( va("sound/weapons/melee/punch%d", Q_irand(1, 4)) ) ); G_Damage( tr_ent, ent, ent, forward, tr.endpos, STUN_BATON_DAMAGE, (DAMAGE_NO_KNOCKBACK|DAMAGE_HALF_ABSORB), MOD_STUN_BATON ); if (tr_ent->client) { //if it's a player then use the shock effect if ( tr_ent->client->NPC_class == CLASS_VEHICLE ) {//not on vehicles if ( !tr_ent->m_pVehicle || tr_ent->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL || tr_ent->m_pVehicle->m_pVehicleInfo->type == VH_FLIER ) {//can zap animals tr_ent->client->ps.electrifyTime = level.time + Q_irand( 3000, 4000 ); } } else { tr_ent->client->ps.electrifyTime = level.time + 700; } } } } //--------------------------------------------------------- // FireMelee //--------------------------------------------------------- void WP_FireMelee( gentity_t *ent, qboolean alt_fire ) { gentity_t *tr_ent; trace_t tr; vec3_t mins, maxs, end; vec3_t muzzlePunch; if (ent->client && ent->client->ps.torsoAnim == BOTH_MELEE2) { //right if (ent->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM)) { return; } } else { //left if (ent->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM)) { return; } } if (!ent->client) { VectorCopy(ent->r.currentOrigin, muzzlePunch); muzzlePunch[2] += 8; } else { VectorCopy(ent->client->ps.origin, muzzlePunch); muzzlePunch[2] += ent->client->ps.viewheight-6; } VectorMA(muzzlePunch, 20.0f, forward, muzzlePunch); VectorMA(muzzlePunch, 4.0f, vright, muzzlePunch); VectorMA( muzzlePunch, MELEE_RANGE, forward, end ); VectorSet( maxs, 6, 6, 6 ); VectorScale( maxs, -1, mins ); trap_Trace ( &tr, muzzlePunch, mins, maxs, end, ent->s.number, MASK_SHOT ); if (tr.entityNum != ENTITYNUM_NONE) { //hit something tr_ent = &g_entities[tr.entityNum]; G_Sound( ent, CHAN_AUTO, G_SoundIndex( va("sound/weapons/melee/punch%d", Q_irand(1, 4)) ) ); if (tr_ent->takedamage && tr_ent->client) { //special duel checks if (tr_ent->client->ps.duelInProgress && tr_ent->client->ps.duelIndex != ent->s.number) { return; } if (ent->client && ent->client->ps.duelInProgress && ent->client->ps.duelIndex != tr_ent->s.number) { return; } } if ( tr_ent->takedamage ) { //damage them, do more damage if we're in the second right hook int dmg = MELEE_SWING1_DAMAGE; if (ent->client && ent->client->ps.torsoAnim == BOTH_MELEE2) { //do a tad bit more damage on the second swing dmg = MELEE_SWING2_DAMAGE; } if ( G_HeavyMelee( ent ) ) { //2x damage for heavy melee class dmg *= 2; } G_Damage( tr_ent, ent, ent, forward, tr.endpos, dmg, DAMAGE_NO_ARMOR, MOD_MELEE ); } } } //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// /* ====================== 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; } } } //====================================================================== /* =============== 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) { 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 rwwFIXMEFIXME: Since ghoul2 models are on server and properly updated now, it may be reasonable to base muzzle point off actual weapon bolt point. The down side would be that it does not necessarily look alright from a first person perspective. =============== */ void CalcMuzzlePoint ( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) { int weapontype; vec3_t muzzleOffPoint; weapontype = ent->s.weapon; VectorCopy( ent->s.pos.trBase, muzzlePoint ); VectorCopy(WP_MuzzlePoint[weapontype], muzzleOffPoint); if (weapontype > WP_NONE && weapontype < WP_NUM_WEAPONS) { // Use the table to generate the muzzlepoint; { // Crouching. Use the add-to-Z method to adjust vertically. VectorMA(muzzlePoint, muzzleOffPoint[0], forward, muzzlePoint); VectorMA(muzzlePoint, muzzleOffPoint[1], right, muzzlePoint); muzzlePoint[2] += ent->client->ps.viewheight + muzzleOffPoint[2]; } } // 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 ); } extern void G_MissileImpact( gentity_t *ent, trace_t *trace ); void WP_TouchVehMissile( gentity_t *ent, gentity_t *other, trace_t *trace ) { trace_t myTrace; memcpy( (void *)&myTrace, (void *)trace, sizeof(myTrace) ); if ( other ) { myTrace.entityNum = other->s.number; } G_MissileImpact( ent, &myTrace ); } void WP_CalcVehMuzzle(gentity_t *ent, int muzzleNum) { Vehicle_t *pVeh = ent->m_pVehicle; mdxaBone_t boltMatrix; vec3_t vehAngles; assert(pVeh); if (pVeh->m_iMuzzleTime[muzzleNum] == level.time) { //already done for this frame, don't need to do it again return; } //Uh... how about we set this, hunh...? :) pVeh->m_iMuzzleTime[muzzleNum] = level.time; VectorCopy( ent->client->ps.viewangles, vehAngles ); if ( pVeh->m_pVehicleInfo && (pVeh->m_pVehicleInfo->type == VH_ANIMAL ||pVeh->m_pVehicleInfo->type == VH_WALKER ||pVeh->m_pVehicleInfo->type == VH_SPEEDER) ) { vehAngles[PITCH] = vehAngles[ROLL] = 0; } trap_G2API_GetBoltMatrix_NoRecNoRot(ent->ghoul2, 0, pVeh->m_iMuzzleTag[muzzleNum], &boltMatrix, vehAngles, ent->client->ps.origin, level.time, NULL, ent->modelScale); BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, pVeh->m_vMuzzlePos[muzzleNum]); BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, pVeh->m_vMuzzleDir[muzzleNum]); } void WP_VehWeapSetSolidToOwner( gentity_t *self ) { self->r.svFlags |= SVF_OWNERNOTSHARED; if ( self->genericValue1 ) {//expire after a time if ( self->genericValue2 ) {//blow up when your lifetime is up self->think = G_ExplodeMissile;//FIXME: custom func? } else {//just remove yourself self->think = G_FreeEntity;//FIXME: custom func? } self->nextthink = level.time + self->genericValue1; } } #define VEH_HOMING_MISSILE_THINK_TIME 100 gentity_t *WP_FireVehicleWeapon( gentity_t *ent, vec3_t start, vec3_t dir, vehWeaponInfo_t *vehWeapon, qboolean alt_fire, qboolean isTurretWeap ) { gentity_t *missile = NULL; //FIXME: add some randomness...? Inherent inaccuracy stat of weapon? Pilot skill? if ( !vehWeapon ) {//invalid vehicle weapon return NULL; } else if ( vehWeapon->bIsProjectile ) {//projectile entity vec3_t mins, maxs; VectorSet( maxs, vehWeapon->fWidth/2.0f,vehWeapon->fWidth/2.0f,vehWeapon->fHeight/2.0f ); VectorScale( maxs, -1, mins ); //make sure our start point isn't on the other side of a wall WP_TraceSetStart( ent, start, mins, maxs ); //FIXME: CUSTOM MODEL? //QUERY: alt_fire true or not? Does it matter? missile = CreateMissile( start, dir, vehWeapon->fSpeed, 10000, ent, qfalse ); missile->classname = "vehicle_proj"; missile->s.genericenemyindex = ent->s.number+MAX_GENTITIES; missile->damage = vehWeapon->iDamage; missile->splashDamage = vehWeapon->iSplashDamage; missile->splashRadius = vehWeapon->fSplashRadius; //FIXME: externalize some of these properties? missile->dflags = DAMAGE_DEATH_KNOCKBACK; missile->clipmask = MASK_SHOT; //Maybe by checking flags...? if ( vehWeapon->bSaberBlockable ) { missile->clipmask |= CONTENTS_LIGHTSABER; } /* if ( (vehWeapon->iFlags&VWF_KNOCKBACK) ) { missile->dflags &= ~DAMAGE_DEATH_KNOCKBACK; } if ( (vehWeapon->iFlags&VWF_RADAR) ) { missile->s.eFlags |= EF_RADAROBJECT; } */ // Make it easier to hit things VectorCopy( mins, missile->r.mins ); VectorCopy( maxs, missile->r.maxs ); //some slightly different stuff for things with bboxes if ( vehWeapon->fWidth || vehWeapon->fHeight ) {//we assume it's a rocket-like thing missile->s.weapon = WP_ROCKET_LAUNCHER;//does this really matter? missile->methodOfDeath = MOD_VEHICLE;//MOD_ROCKET; missile->splashMethodOfDeath = MOD_VEHICLE;//MOD_ROCKET;// ?SPLASH; // we don't want it to ever bounce missile->bounceCount = 0; missile->mass = 10; } else {//a blaster-laser-like thing missile->s.weapon = WP_BLASTER;//does this really matter? missile->methodOfDeath = MOD_VEHICLE; //count as a heavy weap missile->splashMethodOfDeath = MOD_VEHICLE;// ?SPLASH; // we don't want it to bounce forever missile->bounceCount = 8; } if ( vehWeapon->bHasGravity ) {//TESTME: is this all we need to do? missile->s.weapon = WP_THERMAL;//does this really matter? missile->s.pos.trType = TR_GRAVITY; } if ( vehWeapon->bIonWeapon ) {//so it disables ship shields and sends them out of control missile->s.weapon = WP_DEMP2; } if ( vehWeapon->iHealth ) {//the missile can take damage /* //don't do this - ships hit them first and have no trace.plane.normal to bounce off it at and end up in the middle of the asteroid... missile->health = vehWeapon->iHealth; missile->takedamage = qtrue; missile->r.contents = MASK_SHOT; missile->die = RocketDie; */ } //pilot should own this projectile on server if we have a pilot if (ent->m_pVehicle && ent->m_pVehicle->m_pPilot) {//owned by vehicle pilot missile->r.ownerNum = ent->m_pVehicle->m_pPilot->s.number; } else {//owned by vehicle? missile->r.ownerNum = ent->s.number; } //set veh as cgame side owner for purpose of fx overrides missile->s.owner = ent->s.number; if ( alt_fire ) {//use the second weapon's iShotFX missile->s.eFlags |= EF_ALT_FIRING; } if ( isTurretWeap ) {//look for the turret weapon info on cgame side, not vehicle weapon info missile->s.weapon = WP_TURRET; } if ( vehWeapon->iLifeTime ) {//expire after a time if ( vehWeapon->bExplodeOnExpire ) {//blow up when your lifetime is up missile->think = G_ExplodeMissile;//FIXME: custom func? } else {//just remove yourself missile->think = G_FreeEntity;//FIXME: custom func? } missile->nextthink = level.time + vehWeapon->iLifeTime; } missile->s.otherEntityNum2 = (vehWeapon-&g_vehWeaponInfo[0]); missile->s.eFlags |= EF_JETPACK_ACTIVE; //homing if ( vehWeapon->fHoming ) {//homing missile if ( ent->client && ent->client->ps.rocketLockIndex != ENTITYNUM_NONE ) { int dif = 0; float rTime; rTime = ent->client->ps.rocketLockTime; if (rTime == -1) { rTime = ent->client->ps.rocketLastValidTime; } if ( !vehWeapon->iLockOnTime ) {//no minimum lock-on time dif = 10;//guaranteed lock-on } else { float lockTimeInterval = vehWeapon->iLockOnTime/16.0f; dif = ( level.time - rTime ) / lockTimeInterval; } if (dif < 0) { dif = 0; } //It's 10 even though it locks client-side at 8, because we want them to have a sturdy lock first, and because there's a slight difference in time between server and client if ( dif >= 10 && rTime != -1 ) { missile->enemy = &g_entities[ent->client->ps.rocketLockIndex]; if (missile->enemy && missile->enemy->client && missile->enemy->health > 0 && !OnSameTeam(ent, missile->enemy)) { //if enemy became invalid, died, or is on the same team, then don't seek it missile->spawnflags |= 1;//just to let it know it should be faster... missile->speed = vehWeapon->fSpeed; missile->angle = vehWeapon->fHoming; missile->radius = vehWeapon->fHomingFOV; //crap, if we have a lifetime, need to store that somewhere else on ent and have rocketThink func check it every frame... if ( vehWeapon->iLifeTime ) {//expire after a time missile->genericValue1 = level.time + vehWeapon->iLifeTime; missile->genericValue2 = (int)(vehWeapon->bExplodeOnExpire); } //now go ahead and use the rocketThink func missile->think = rocketThink;//FIXME: custom func? missile->nextthink = level.time + VEH_HOMING_MISSILE_THINK_TIME; missile->s.eFlags |= EF_RADAROBJECT;//FIXME: externalize if ( missile->enemy->s.NPC_class == CLASS_VEHICLE ) {//let vehicle know we've locked on to them missile->s.otherEntityNum = missile->enemy->s.number; } } } VectorCopy( dir, missile->movedir ); missile->random = 1.0f;//FIXME: externalize? } } if ( !vehWeapon->fSpeed ) {//a mine or something? if ( vehWeapon->iHealth ) {//the missile can take damage missile->health = vehWeapon->iHealth; missile->takedamage = qtrue; missile->r.contents = MASK_SHOT; missile->die = RocketDie; } //only do damage when someone touches us missile->s.weapon = WP_THERMAL;//does this really matter? G_SetOrigin( missile, start ); missile->touch = WP_TouchVehMissile; missile->s.eFlags |= EF_RADAROBJECT;//FIXME: externalize //crap, if we have a lifetime, need to store that somewhere else on ent and have rocketThink func check it every frame... if ( vehWeapon->iLifeTime ) {//expire after a time missile->genericValue1 = vehWeapon->iLifeTime; missile->genericValue2 = (int)(vehWeapon->bExplodeOnExpire); } //now go ahead and use the setsolidtoowner func missile->think = WP_VehWeapSetSolidToOwner; missile->nextthink = level.time + 3000; } } else {//traceline //FIXME: implement } return missile; } //custom routine to not waste tempents horribly -rww void G_VehMuzzleFireFX( gentity_t *ent, gentity_t *broadcaster, int muzzlesFired ) { Vehicle_t *pVeh = ent->m_pVehicle; gentity_t *b; if (!pVeh) { return; } if (!broadcaster) { //oh well. We will WASTE A TEMPENT. b = G_TempEntity( ent->client->ps.origin, EV_VEH_FIRE ); } else { //joy b = broadcaster; } //this guy owns it b->s.owner = ent->s.number; //this is the bitfield of all muzzles fired this time //NOTE: just need MAX_VEHICLE_MUZZLES bits for this... should be cool since it's currently 12 and we're sending it in 16 bits b->s.trickedentindex = muzzlesFired; if ( broadcaster ) { //add the event G_AddEvent( b, EV_VEH_FIRE, 0 ); } } void G_EstimateCamPos( vec3_t viewAngles, vec3_t cameraFocusLoc, float viewheight, float thirdPersonRange, float thirdPersonHorzOffset, float vertOffset, float pitchOffset, int ignoreEntNum, vec3_t camPos ) { int MASK_CAMERACLIP = (MASK_SOLID|CONTENTS_PLAYERCLIP); float CAMERA_SIZE = 4; vec3_t cameramins; vec3_t cameramaxs; vec3_t cameraFocusAngles, camerafwd, cameraup; vec3_t cameraIdealTarget, cameraCurTarget; vec3_t cameraIdealLoc, cameraCurLoc; vec3_t diff; vec3_t camAngles; vec3_t viewaxis[3]; trace_t trace; VectorSet( cameramins, -CAMERA_SIZE, -CAMERA_SIZE, -CAMERA_SIZE ); VectorSet( cameramaxs, CAMERA_SIZE, CAMERA_SIZE, CAMERA_SIZE ); VectorCopy( viewAngles, cameraFocusAngles ); cameraFocusAngles[PITCH] += pitchOffset; if ( !bg_fighterAltControl.integer ) {//clamp view pitch cameraFocusAngles[PITCH] = AngleNormalize180( cameraFocusAngles[PITCH] ); if (cameraFocusAngles[PITCH] > 80.0) { cameraFocusAngles[PITCH] = 80.0; } else if (cameraFocusAngles[PITCH] < -80.0) { cameraFocusAngles[PITCH] = -80.0; } } AngleVectors(cameraFocusAngles, camerafwd, NULL, cameraup); cameraFocusLoc[2] += viewheight; VectorCopy( cameraFocusLoc, cameraIdealTarget ); cameraIdealTarget[2] += vertOffset; //NOTE: on cgame, this uses the thirdpersontargetdamp value, we ignore that here VectorCopy( cameraIdealTarget, cameraCurTarget ); trap_Trace( &trace, cameraFocusLoc, cameramins, cameramaxs, cameraCurTarget, ignoreEntNum, MASK_CAMERACLIP ); if (trace.fraction < 1.0) { VectorCopy(trace.endpos, cameraCurTarget); } VectorMA(cameraIdealTarget, -(thirdPersonRange), camerafwd, cameraIdealLoc); //NOTE: on cgame, this uses the thirdpersoncameradamp value, we ignore that here VectorCopy( cameraIdealLoc, cameraCurLoc ); trap_Trace(&trace, cameraCurTarget, cameramins, cameramaxs, cameraCurLoc, ignoreEntNum, MASK_CAMERACLIP); if (trace.fraction < 1.0) { VectorCopy( trace.endpos, cameraCurLoc ); } VectorSubtract(cameraCurTarget, cameraCurLoc, diff); { float dist = VectorNormalize(diff); //under normal circumstances, should never be 0.00000 and so on. if ( !dist || (diff[0] == 0 || diff[1] == 0) ) {//must be hitting something, need some value to calc angles, so use cam forward VectorCopy( camerafwd, diff ); } } vectoangles(diff, camAngles); if ( thirdPersonHorzOffset != 0.0f ) { AnglesToAxis( camAngles, viewaxis ); VectorMA( cameraCurLoc, thirdPersonHorzOffset, viewaxis[1], cameraCurLoc ); } VectorCopy(cameraCurLoc, camPos); } void WP_GetVehicleCamPos( gentity_t *ent, gentity_t *pilot, vec3_t camPos ) { float thirdPersonHorzOffset = ent->m_pVehicle->m_pVehicleInfo->cameraHorzOffset; float thirdPersonRange = ent->m_pVehicle->m_pVehicleInfo->cameraRange; float pitchOffset = ent->m_pVehicle->m_pVehicleInfo->cameraPitchOffset; float vertOffset = ent->m_pVehicle->m_pVehicleInfo->cameraVertOffset; if ( ent->client->ps.hackingTime ) { thirdPersonHorzOffset += (((float)ent->client->ps.hackingTime)/MAX_STRAFE_TIME) * -80.0f; thirdPersonRange += fabs(((float)ent->client->ps.hackingTime)/MAX_STRAFE_TIME) * 100.0f; } if ( ent->m_pVehicle->m_pVehicleInfo->cameraPitchDependantVertOffset ) { if ( pilot->client->ps.viewangles[PITCH] > 0 ) { vertOffset = 130+pilot->client->ps.viewangles[PITCH]*-10; if ( vertOffset < -170 ) { vertOffset = -170; } } else if ( pilot->client->ps.viewangles[PITCH] < 0 ) { vertOffset = 130+pilot->client->ps.viewangles[PITCH]*-5; if ( vertOffset > 130 ) { vertOffset = 130; } } else { vertOffset = 30; } if ( pilot->client->ps.viewangles[PITCH] > 0 ) { pitchOffset = pilot->client->ps.viewangles[PITCH]*-0.75; } else if ( pilot->client->ps.viewangles[PITCH] < 0 ) { pitchOffset = pilot->client->ps.viewangles[PITCH]*-0.75; } else { pitchOffset = 0; } } //Control Scheme 3 Method: G_EstimateCamPos( ent->client->ps.viewangles, pilot->client->ps.origin, pilot->client->ps.viewheight, thirdPersonRange, thirdPersonHorzOffset, vertOffset, pitchOffset, pilot->s.number, camPos ); /* //Control Scheme 2 Method: G_EstimateCamPos( ent->m_pVehicle->m_vOrientation, ent->r.currentOrigin, pilot->client->ps.viewheight, thirdPersonRange, thirdPersonHorzOffset, vertOffset, pitchOffset, pilot->s.number, camPos ); */ } void WP_VehLeadCrosshairVeh( gentity_t *camTraceEnt, vec3_t newEnd, const vec3_t dir, const vec3_t shotStart, vec3_t shotDir ) { if ( g_vehAutoAimLead.integer ) { if ( camTraceEnt && camTraceEnt->client && camTraceEnt->client->NPC_class == CLASS_VEHICLE ) {//if the crosshair is on a vehicle, lead it float dot, distAdjust = DotProduct( camTraceEnt->client->ps.velocity, dir ); vec3_t predPos, predShotDir; if ( distAdjust > 500 || DistanceSquared( camTraceEnt->client->ps.origin, shotStart ) > 7000000 ) {//moving away from me at a decent speed and/or more than @2600 units away from me VectorMA( newEnd, distAdjust, dir, predPos ); VectorSubtract( predPos, shotStart, predShotDir ); VectorNormalize( predShotDir ); dot = DotProduct( predShotDir, shotDir ); if ( dot >= 0.75f ) {//if the new aim vector is no more than 23 degrees off the original one, go ahead and adjust the aim VectorCopy( predPos, newEnd ); } } } } VectorSubtract( newEnd, shotStart, shotDir ); VectorNormalize( shotDir ); } #define MAX_XHAIR_DIST_ACCURACY 20000.0f extern float g_cullDistance; extern int BG_VehTraceFromCamPos( trace_t *camTrace, bgEntity_t *bgEnt, const vec3_t entOrg, const vec3_t shotStart, const vec3_t end, vec3_t newEnd, vec3_t shotDir, float bestDist ); qboolean WP_VehCheckTraceFromCamPos( gentity_t *ent, const vec3_t shotStart, vec3_t shotDir ) { //FIXME: only if dynamicCrosshair and dynamicCrosshairPrecision is on! if ( !ent || !ent->m_pVehicle || !ent->m_pVehicle->m_pVehicleInfo || !ent->m_pVehicle->m_pPilot//not being driven || !((gentity_t*)ent->m_pVehicle->m_pPilot)->client//not being driven by a client...?!!! || (ent->m_pVehicle->m_pPilot->s.number >= MAX_CLIENTS) )//being driven, but not by a real client, no need to worry about crosshair { return qfalse; } if ( (ent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER && g_cullDistance > MAX_XHAIR_DIST_ACCURACY ) || ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER) { //FIRST: simulate the normal crosshair trace from the center of the veh straight forward trace_t trace; vec3_t dir, start, end; if ( ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER ) {//for some reason, the walker always draws the crosshair out from from the first muzzle point AngleVectors( ent->client->ps.viewangles, dir, NULL, NULL ); VectorCopy( ent->r.currentOrigin, start ); start[2] += ent->m_pVehicle->m_pVehicleInfo->height-DEFAULT_MINS_2-48; } else { vec3_t ang; if (ent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER) { VectorSet(ang, 0.0f, ent->m_pVehicle->m_vOrientation[1], 0.0f); } else { VectorCopy(ent->m_pVehicle->m_vOrientation, ang); } AngleVectors( ang, dir, NULL, NULL ); VectorCopy( ent->r.currentOrigin, start ); } VectorMA( start, g_cullDistance, dir, end ); trap_Trace( &trace, start, vec3_origin, vec3_origin, end, ent->s.number, CONTENTS_SOLID|CONTENTS_BODY ); if ( ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER ) {//just use the result of that one trace since walkers don't do the extra trace VectorSubtract( trace.endpos, shotStart, shotDir ); VectorNormalize( shotDir ); return qtrue; } else {//NOW do the trace from the camPos and compare with above trace trace_t extraTrace; vec3_t newEnd; int camTraceEntNum = BG_VehTraceFromCamPos( &extraTrace, (bgEntity_t *)ent, ent->r.currentOrigin, shotStart, end, newEnd, shotDir, (trace.fraction*g_cullDistance) ); if ( camTraceEntNum ) { WP_VehLeadCrosshairVeh( &g_entities[camTraceEntNum-1], newEnd, dir, shotStart, shotDir ); return qtrue; } } } return qfalse; } //--------------------------------------------------------- void FireVehicleWeapon( gentity_t *ent, qboolean alt_fire ) //--------------------------------------------------------- { Vehicle_t *pVeh = ent->m_pVehicle; int muzzlesFired = 0; gentity_t *missile = NULL; vehWeaponInfo_t *vehWeapon = NULL; qboolean clearRocketLockEntity = qfalse; if ( !pVeh ) { return; } if (pVeh->m_iRemovedSurfaces) { //can't fire when the thing is breaking apart return; } if (pVeh->m_pVehicleInfo->type == VH_WALKER && ent->client->ps.electrifyTime > level.time) { //don't fire while being electrocuted return; } // TODO?: If possible (probably not enough time), it would be nice if secondary fire was actually a mode switch/toggle // so that, for instance, an x-wing can have 4-gun fire, or individual muzzle fire. If you wanted a different weapon, you // would actually have to press the 2 key or something like that (I doubt I'd get a graphic for it anyways though). -AReis // If this is not the alternate fire, fire a normal blaster shot... if ( pVeh->m_pVehicleInfo && (pVeh->m_pVehicleInfo->type != VH_FIGHTER || (pVeh->m_ulFlags&VEH_WINGSOPEN)) ) // NOTE: Wings open also denotes that it has already launched. {//fighters can only fire when wings are open int weaponNum = 0, vehWeaponIndex = VEH_WEAPON_NONE; int delay = 1000; qboolean aimCorrect = qfalse; qboolean linkedFiring = qfalse; if ( !alt_fire ) { weaponNum = 0; } else { weaponNum = 1; } vehWeaponIndex = pVeh->m_pVehicleInfo->weapon[weaponNum].ID; if ( pVeh->weaponStatus[weaponNum].ammo <= 0 ) {//no ammo for this weapon if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS ) {// let the client know he's out of ammo int i; //but only if one of the vehicle muzzles is actually ready to fire this weapon for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ ) { if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex ) {//this muzzle doesn't match the weapon we're trying to use continue; } if ( pVeh->m_iMuzzleTag[i] != -1 && pVeh->m_iMuzzleWait[i] < level.time ) {//this one would have fired, send the no ammo message G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum ); break; } } } return; } delay = pVeh->m_pVehicleInfo->weapon[weaponNum].delay; aimCorrect = pVeh->m_pVehicleInfo->weapon[weaponNum].aimCorrect; if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 2//always linked || ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 1//optionally linkable && pVeh->weaponStatus[weaponNum].linked ) )//linked {//we're linking the primary or alternate weapons, so we'll do *all* the muzzles linkedFiring = qtrue; } if ( vehWeaponIndex <= VEH_WEAPON_BASE || vehWeaponIndex >= MAX_VEH_WEAPONS ) {//invalid vehicle weapon return; } else { int i, numMuzzles = 0, numMuzzlesReady = 0, cumulativeDelay = 0, cumulativeAmmo = 0; qboolean sentAmmoWarning = qfalse; vehWeapon = &g_vehWeaponInfo[vehWeaponIndex]; if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 2 ) {//always linked weapons don't accumulate delay, just use specified delay cumulativeDelay = delay; } //find out how many we've got for this weapon for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ ) { if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex ) {//this muzzle doesn't match the weapon we're trying to use continue; } if ( pVeh->m_iMuzzleTag[i] != -1 && pVeh->m_iMuzzleWait[i] < level.time ) { numMuzzlesReady++; } if ( pVeh->m_pVehicleInfo->weapMuzzle[pVeh->weaponStatus[weaponNum].nextMuzzle] != vehWeaponIndex ) {//Our designated next muzzle for this weapon isn't valid for this weapon (happens when ships fire for the first time) //set the next to this one pVeh->weaponStatus[weaponNum].nextMuzzle = i; } if ( linkedFiring ) { cumulativeAmmo += vehWeapon->iAmmoPerShot; if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable != 2 ) {//always linked weapons don't accumulate delay, just use specified delay cumulativeDelay += delay; } } numMuzzles++; } if ( linkedFiring ) {//firing all muzzles at once if ( numMuzzlesReady != numMuzzles ) {//can't fire all linked muzzles yet return; } else {//can fire all linked muzzles, check ammo if ( pVeh->weaponStatus[weaponNum].ammo < cumulativeAmmo ) {//can't fire, not enough ammo if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS ) {// let the client know he's out of ammo G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum ); } return; } } } for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ ) { if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex ) {//this muzzle doesn't match the weapon we're trying to use continue; } if ( !linkedFiring && i != pVeh->weaponStatus[weaponNum].nextMuzzle ) {//we're only firing one muzzle and this isn't it continue; } // Fire this muzzle. if ( pVeh->m_iMuzzleTag[i] != -1 && pVeh->m_iMuzzleWait[i] < level.time ) { vec3_t start, dir; if ( pVeh->weaponStatus[weaponNum].ammo < vehWeapon->iAmmoPerShot ) {//out of ammo! if ( !sentAmmoWarning ) { sentAmmoWarning = qtrue; if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS ) {// let the client know he's out of ammo G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum ); } } } else {//have enough ammo to shoot //do the firing WP_CalcVehMuzzle(ent, i); VectorCopy( pVeh->m_vMuzzlePos[i], start ); VectorCopy( pVeh->m_vMuzzleDir[i], dir ); if ( WP_VehCheckTraceFromCamPos( ent, start, dir ) ) {//auto-aim at whatever crosshair would be over from camera's point of view (if closer) } else if ( aimCorrect ) {//auto-aim the missile at the crosshair if there's anything there trace_t trace; vec3_t end; vec3_t ang; vec3_t fixedDir; if (pVeh->m_pVehicleInfo->type == VH_SPEEDER) { VectorSet(ang, 0.0f, pVeh->m_vOrientation[1], 0.0f); } else { VectorCopy(pVeh->m_vOrientation, ang); } AngleVectors( ang, fixedDir, NULL, NULL ); VectorMA( ent->r.currentOrigin, 32768, fixedDir, end ); //VectorMA( ent->r.currentOrigin, 8192, dir, end ); trap_Trace( &trace, ent->r.currentOrigin, vec3_origin, vec3_origin, end, ent->s.number, MASK_SHOT ); if ( trace.fraction < 1.0f && !trace.allsolid && !trace.startsolid ) { vec3_t newEnd; VectorCopy( trace.endpos, newEnd ); WP_VehLeadCrosshairVeh( &g_entities[trace.entityNum], newEnd, fixedDir, start, dir ); } } //play the weapon's muzzle effect if we have one //NOTE: just need MAX_VEHICLE_MUZZLES bits for this... should be cool since it's currently 12 and we're sending it in 16 bits muzzlesFired |= (1<fHoming ) {//clear the rocket lock entity *after* all muzzles have fired clearRocketLockEntity = qtrue; } } if ( linkedFiring ) {//we're linking the weapon, so continue on and fire all appropriate muzzles continue; } //else just firing one //take the ammo, set the next muzzle and set the delay on it if ( numMuzzles > 1 ) {//more than one, look for it int nextMuzzle = pVeh->weaponStatus[weaponNum].nextMuzzle; while ( 1 ) { nextMuzzle++; if ( nextMuzzle >= MAX_VEHICLE_MUZZLES ) { nextMuzzle = 0; } if ( nextMuzzle == pVeh->weaponStatus[weaponNum].nextMuzzle ) {//WTF? Wrapped without finding another valid one! break; } if ( pVeh->m_pVehicleInfo->weapMuzzle[nextMuzzle] == vehWeaponIndex ) {//this is the next muzzle for this weapon pVeh->weaponStatus[weaponNum].nextMuzzle = nextMuzzle; break; } } }//else, just stay on the one we just fired //set the delay on the next muzzle pVeh->m_iMuzzleWait[pVeh->weaponStatus[weaponNum].nextMuzzle] = level.time + delay; //take away the ammo pVeh->weaponStatus[weaponNum].ammo -= vehWeapon->iAmmoPerShot; //NOTE: in order to send the vehicle's ammo info to the client, we copy the ammo into the first 2 ammo slots on the vehicle NPC's client->ps.ammo array if ( pVeh->m_pParentEntity && ((gentity_t*)(pVeh->m_pParentEntity))->client ) { ((gentity_t*)(pVeh->m_pParentEntity))->client->ps.ammo[weaponNum] = pVeh->weaponStatus[weaponNum].ammo; } //done! //we'll get in here again next frame and try the next muzzle... //return; goto tryFire; } } //we went through all the muzzles, so apply the cumulative delay and ammo cost if ( cumulativeAmmo ) {//taking ammo one shot at a time //take the ammo pVeh->weaponStatus[weaponNum].ammo -= cumulativeAmmo; //NOTE: in order to send the vehicle's ammo info to the client, we copy the ammo into the first 2 ammo slots on the vehicle NPC's client->ps.ammo array if ( pVeh->m_pParentEntity && ((gentity_t*)(pVeh->m_pParentEntity))->client ) { ((gentity_t*)(pVeh->m_pParentEntity))->client->ps.ammo[weaponNum] = pVeh->weaponStatus[weaponNum].ammo; } } if ( cumulativeDelay ) {//we linked muzzles so we need to apply the cumulative delay now, to each of the linked muzzles for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ ) { if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex ) {//this muzzle doesn't match the weapon we're trying to use continue; } //apply the cumulative delay pVeh->m_iMuzzleWait[i] = level.time + cumulativeDelay; } } } } tryFire: if ( clearRocketLockEntity ) {//hmm, should probably clear that anytime any weapon fires? ent->client->ps.rocketLockIndex = ENTITYNUM_NONE; ent->client->ps.rocketLockTime = 0; ent->client->ps.rocketTargetTime = 0; } if ( vehWeapon && muzzlesFired > 0 ) { G_VehMuzzleFireFX(ent, missile, muzzlesFired ); } } /* =============== FireWeapon =============== */ #include "../namespace_begin.h" int BG_EmplacedView(vec3_t baseAngles, vec3_t angles, float *newYaw, float constraint); #include "../namespace_end.h" void FireWeapon( gentity_t *ent, qboolean altFire ) { if (ent->client->ps.powerups[PW_QUAD] ) { s_quadFactor = g_quadfactor.value; } else { s_quadFactor = 1; } // track shots taken for accuracy tracking. Grapple is not a weapon and gauntet is just not tracked if( ent->s.weapon != WP_SABER && ent->s.weapon != WP_STUN_BATON && ent->s.weapon != WP_MELEE ) { if( ent->s.weapon == WP_FLECHETTE ) { ent->client->accuracy_shots += FLECHETTE_SHOTS; } else { ent->client->accuracy_shots++; } } if ( ent && ent->client && ent->client->NPC_class == CLASS_VEHICLE ) { FireVehicleWeapon( ent, altFire ); return; } else { // set aiming directions if (ent->s.weapon == WP_EMPLACED_GUN && ent->client->ps.emplacedIndex) { //if using emplaced then base muzzle point off of gun position/angles gentity_t *emp = &g_entities[ent->client->ps.emplacedIndex]; if (emp->inuse) { float yaw; vec3_t viewAngCap; int override; VectorCopy(ent->client->ps.viewangles, viewAngCap); if (viewAngCap[PITCH] > 40) { viewAngCap[PITCH] = 40; } override = BG_EmplacedView(ent->client->ps.viewangles, emp->s.angles, &yaw, emp->s.origin2[0]); if (override) { viewAngCap[YAW] = yaw; } AngleVectors( viewAngCap, forward, vright, up ); } else { AngleVectors( ent->client->ps.viewangles, forward, vright, up ); } } else if (ent->s.number < MAX_CLIENTS && ent->client->ps.m_iVehicleNum && ent->s.weapon == WP_BLASTER) { //riding a vehicle...with blaster selected vec3_t vehTurnAngles; gentity_t *vehEnt = &g_entities[ent->client->ps.m_iVehicleNum]; if (vehEnt->inuse && vehEnt->client && vehEnt->m_pVehicle) { VectorCopy(vehEnt->m_pVehicle->m_vOrientation, vehTurnAngles); vehTurnAngles[PITCH] = ent->client->ps.viewangles[PITCH]; } else { VectorCopy(ent->client->ps.viewangles, vehTurnAngles); } if (ent->client->pers.cmd.rightmove > 0) { //shooting to right vehTurnAngles[YAW] -= 90.0f; } else if (ent->client->pers.cmd.rightmove < 0) { //shooting to left vehTurnAngles[YAW] += 90.0f; } AngleVectors( vehTurnAngles, forward, vright, up ); } else { AngleVectors( ent->client->ps.viewangles, forward, vright, up ); } CalcMuzzlePoint ( ent, forward, vright, up, muzzle ); // fire the specific weapon switch( ent->s.weapon ) { case WP_STUN_BATON: WP_FireStunBaton( ent, altFire ); break; case WP_MELEE: WP_FireMelee(ent, altFire); break; case WP_SABER: break; case WP_BRYAR_PISTOL: //if ( g_gametype.integer == GT_SIEGE ) if (1) {//allow alt-fire WP_FireBryarPistol( ent, altFire ); } else { WP_FireBryarPistol( ent, qfalse ); } break; case WP_CONCUSSION: if ( altFire ) { WP_FireConcussionAlt( ent ); } else { WP_FireConcussion( ent ); } break; case WP_BRYAR_OLD: WP_FireBryarPistol( ent, altFire ); break; case WP_BLASTER: WP_FireBlaster( ent, altFire ); break; case WP_DISRUPTOR: WP_FireDisruptor( ent, altFire ); break; case WP_BOWCASTER: WP_FireBowcaster( ent, altFire ); break; case WP_REPEATER: WP_FireRepeater( ent, altFire ); break; case WP_DEMP2: WP_FireDEMP2( ent, altFire ); break; case WP_FLECHETTE: WP_FireFlechette( ent, altFire ); break; case WP_ROCKET_LAUNCHER: WP_FireRocket( ent, altFire ); break; case WP_THERMAL: WP_FireThermalDetonator( ent, altFire ); break; case WP_TRIP_MINE: WP_PlaceLaserTrap( ent, altFire ); break; case WP_DET_PACK: WP_DropDetPack( ent, altFire ); break; case WP_EMPLACED_GUN: if (ent->client && ent->client->ewebIndex) { //specially handled by the e-web itself break; } WP_FireEmplaced( ent, altFire ); break; default: // assert(!"unknown weapon fire"); break; } } G_LogWeaponFire(ent->s.number, ent->s.weapon); } //--------------------------------------------------------- static void WP_FireEmplaced( gentity_t *ent, qboolean altFire ) //--------------------------------------------------------- { vec3_t dir, angs, gunpoint; vec3_t right; gentity_t *gun; int side; if (!ent->client) { return; } if (!ent->client->ps.emplacedIndex) { //shouldn't be using WP_EMPLACED_GUN if we aren't on an emplaced weapon return; } gun = &g_entities[ent->client->ps.emplacedIndex]; if (!gun->inuse || gun->health <= 0) { //gun was removed or killed, although we should never hit this check because we should have been forced off it already return; } VectorCopy(gun->s.origin, gunpoint); gunpoint[2] += 46; AngleVectors(ent->client->ps.viewangles, NULL, right, NULL); if (gun->genericValue10) { //fire out of the right cannon side VectorMA(gunpoint, 10.0f, right, gunpoint); side = 0; } else { //the left VectorMA(gunpoint, -10.0f, right, gunpoint); side = 1; } gun->genericValue10 = side; G_AddEvent(gun, EV_FIRE_WEAPON, side); vectoangles( forward, angs ); AngleVectors( angs, dir, NULL, NULL ); WP_FireEmplacedMissile( gun, gunpoint, dir, altFire, ent ); } #define EMPLACED_CANRESPAWN 1 //---------------------------------------------------------- /*QUAKED emplaced_gun (0 0 1) (-30 -20 8) (30 20 60) CANRESPAWN count - if CANRESPAWN spawnflag, decides how long it is before gun respawns (in ms) constraint - number of degrees gun is constrained from base angles on each side (default 60.0) showhealth - set to 1 to show health bar on this entity when crosshair is over it teamowner - crosshair shows green for this team, red for opposite team 0 - none 1 - red 2 - blue alliedTeam - team that can use this 0 - any 1 - red 2 - blue teamnodmg - team that turret does not take damage from or do damage to 0 - none 1 - red 2 - blue */ //---------------------------------------------------------- extern qboolean TryHeal(gentity_t *ent, gentity_t *target); //g_utils.c void emplaced_gun_use( gentity_t *self, gentity_t *other, trace_t *trace ) { vec3_t fwd1, fwd2; float dot; int oldWeapon; gentity_t *activator = other; float zoffset = 50; vec3_t anglesToOwner; vec3_t vLen; float ownLen; if ( self->health <= 0 ) { //gun is destroyed return; } if (self->activator) { //someone is already using me return; } if (!activator->client) { return; } if (activator->client->ps.emplacedTime > level.time) { //last use attempt still too recent return; } if (activator->client->ps.forceHandExtend != HANDEXTEND_NONE) { //don't use if busy doing something else return; } if (activator->client->ps.origin[2] > self->s.origin[2]+zoffset-8) { //can't use it from the top return; } if (activator->client->ps.pm_flags & PMF_DUCKED) { //must be standing return; } if (activator->client->ps.isJediMaster) { //jm can't use weapons return; } VectorSubtract(self->s.origin, activator->client->ps.origin, vLen); ownLen = VectorLength(vLen); if (ownLen > 64.0f) { //must be within 64 units of the gun to use at all return; } // Let's get some direction vectors for the user AngleVectors( activator->client->ps.viewangles, fwd1, NULL, NULL ); // Get the guns direction vector AngleVectors( self->pos1, fwd2, NULL, NULL ); dot = DotProduct( fwd1, fwd2 ); // Must be reasonably facing the way the gun points ( 110 degrees or so ), otherwise we don't allow to use it. if ( dot < -0.2f ) { goto tryHeal; } VectorSubtract(self->s.origin, activator->client->ps.origin, fwd1); VectorNormalize(fwd1); dot = DotProduct( fwd1, fwd2 ); //check the positioning in relation to the gun as well if ( dot < 0.6f ) { goto tryHeal; } self->genericValue1 = 1; oldWeapon = activator->s.weapon; // swap the users weapon with the emplaced gun activator->client->ps.weapon = self->s.weapon; activator->client->ps.weaponstate = WEAPON_READY; activator->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_EMPLACED_GUN ); activator->client->ps.emplacedIndex = self->s.number; self->s.emplacedOwner = activator->s.number; self->s.activeForcePass = NUM_FORCE_POWERS+1; // the gun will track which weapon we used to have self->s.weapon = oldWeapon; //user's new owner becomes the gun ent activator->r.ownerNum = self->s.number; self->activator = activator; VectorSubtract(self->r.currentOrigin, activator->client->ps.origin, anglesToOwner); vectoangles(anglesToOwner, anglesToOwner); return; tryHeal: //well, not in the right dir, try healing it instead... TryHeal(activator, self); } void emplaced_gun_realuse( gentity_t *self, gentity_t *other, gentity_t *activator ) { emplaced_gun_use(self, other, NULL); } //---------------------------------------------------------- void emplaced_gun_pain( gentity_t *self, gentity_t *attacker, int damage ) { self->s.health = self->health; if ( self->health <= 0 ) { //death effect.. for now taken care of on cgame } else { //if we have a pain behavior set then use it I guess G_ActivateBehavior( self, BSET_PAIN ); } } #define EMPLACED_GUN_HEALTH 800 //---------------------------------------------------------- void emplaced_gun_update(gentity_t *self) { vec3_t smokeOrg, puffAngle; int oldWeap; float ownLen = 0; if (self->health < 1 && !self->genericValue5) { //we are dead, set our respawn delay if we have one if (self->spawnflags & EMPLACED_CANRESPAWN) { self->genericValue5 = level.time + 4000 + self->count; } } else if (self->health < 1 && self->genericValue5 < level.time) { //we are dead, see if it's time to respawn self->s.time = 0; self->genericValue4 = 0; self->genericValue3 = 0; self->health = EMPLACED_GUN_HEALTH*0.4; self->s.health = self->health; } if (self->genericValue4 && self->genericValue4 < 2 && self->s.time < level.time) { //we have finished our warning (red flashing) effect, it's time to finish dying vec3_t explOrg; VectorSet( puffAngle, 0, 0, 1 ); VectorCopy(self->r.currentOrigin, explOrg); explOrg[2] += 16; //just use the detpack explosion effect G_PlayEffect(EFFECT_EXPLOSION_DETPACK, explOrg, puffAngle); self->genericValue3 = level.time + Q_irand(2500, 3500); G_RadiusDamage(self->r.currentOrigin, self, self->splashDamage, self->splashRadius, self, NULL, MOD_UNKNOWN); self->s.time = -1; self->genericValue4 = 2; } if (self->genericValue3 > level.time) { //see if we are freshly dead and should be smoking if (self->genericValue2 < level.time) { //is it time yet to spawn another smoke puff? VectorSet( puffAngle, 0, 0, 1 ); VectorCopy(self->r.currentOrigin, smokeOrg); smokeOrg[2] += 60; G_PlayEffect(EFFECT_SMOKE, smokeOrg, puffAngle); self->genericValue2 = level.time + Q_irand(250, 400); } } if (self->activator && self->activator->client && self->activator->inuse) { //handle updating current user vec3_t vLen; VectorSubtract(self->s.origin, self->activator->client->ps.origin, vLen); ownLen = VectorLength(vLen); if (!(self->activator->client->pers.cmd.buttons & BUTTON_USE) && self->genericValue1) { self->genericValue1 = 0; } if ((self->activator->client->pers.cmd.buttons & BUTTON_USE) && !self->genericValue1) { self->activator->client->ps.emplacedIndex = 0; self->activator->client->ps.saberHolstered = 0; self->nextthink = level.time + 50; return; } } if ((self->activator && self->activator->client) && (!self->activator->inuse || self->activator->client->ps.emplacedIndex != self->s.number || self->genericValue4 || ownLen > 64)) { //get the user off of me then self->activator->client->ps.stats[STAT_WEAPONS] &= ~(1<activator->client->ps.weapon; self->activator->client->ps.weapon = self->s.weapon; self->s.weapon = oldWeap; self->activator->r.ownerNum = ENTITYNUM_NONE; self->activator->client->ps.emplacedTime = level.time + 1000; self->activator->client->ps.emplacedIndex = 0; self->activator->client->ps.saberHolstered = 0; self->activator = NULL; self->s.activeForcePass = 0; } else if (self->activator && self->activator->client) { //make sure the user is using the emplaced gun weapon self->activator->client->ps.weapon = WP_EMPLACED_GUN; self->activator->client->ps.weaponstate = WEAPON_READY; } self->nextthink = level.time + 50; } //---------------------------------------------------------- void emplaced_gun_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { //set us up to flash and then explode if (self->genericValue4) { return; } self->genericValue4 = 1; self->s.time = level.time + 3000; self->genericValue5 = 0; } void SP_emplaced_gun( gentity_t *ent ) { const char *name = "models/map_objects/mp/turret_chair.glm"; vec3_t down; trace_t tr; //make sure our assets are precached RegisterItem( BG_FindItemForWeapon(WP_EMPLACED_GUN) ); ent->r.contents = CONTENTS_SOLID; ent->s.solid = SOLID_BBOX; ent->genericValue5 = 0; VectorSet( ent->r.mins, -30, -20, 8 ); VectorSet( ent->r.maxs, 30, 20, 60 ); VectorCopy(ent->s.origin, down); down[2] -= 1024; trap_Trace(&tr, ent->s.origin, ent->r.mins, ent->r.maxs, down, ent->s.number, MASK_SOLID); if (tr.fraction != 1 && !tr.allsolid && !tr.startsolid) { VectorCopy(tr.endpos, ent->s.origin); } ent->spawnflags |= 4; // deadsolid ent->health = EMPLACED_GUN_HEALTH; if (ent->spawnflags & EMPLACED_CANRESPAWN) { //make it somewhat easier to kill if it can respawn ent->health *= 0.4; } ent->maxHealth = ent->health; G_ScaleNetHealth(ent); ent->genericValue4 = 0; ent->takedamage = qtrue; ent->pain = emplaced_gun_pain; ent->die = emplaced_gun_die; // being caught in this thing when it blows would be really bad. ent->splashDamage = 80; ent->splashRadius = 128; // amount of ammo that this little poochie has G_SpawnInt( "count", "600", &ent->count ); G_SpawnFloat( "constraint", "60", &ent->s.origin2[0] ); ent->s.modelindex = G_ModelIndex( (char *)name ); ent->s.modelGhoul2 = 1; ent->s.g2radius = 110; //so the cgame knows for sure that we're an emplaced weapon ent->s.weapon = WP_EMPLACED_GUN; G_SetOrigin( ent, ent->s.origin ); // store base angles for later VectorCopy( ent->s.angles, ent->pos1 ); VectorCopy( ent->s.angles, ent->r.currentAngles ); VectorCopy( ent->s.angles, ent->s.apos.trBase ); ent->think = emplaced_gun_update; ent->nextthink = level.time + 50; ent->use = emplaced_gun_realuse; ent->r.svFlags |= SVF_PLAYER_USABLE; ent->s.pos.trType = TR_STATIONARY; ent->s.owner = MAX_CLIENTS+1; ent->s.shouldtarget = qtrue; //ent->s.teamowner = 0; trap_LinkEntity(ent); }