/* =========================================================================== Copyright (C) 1999 - 2005, Id Software, Inc. Copyright (C) 2000 - 2013, Raven Software, Inc. Copyright (C) 2001 - 2013, Activision, Inc. Copyright (C) 2013 - 2015, OpenJK contributors This file is part of the OpenJK source code. OpenJK is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . =========================================================================== */ // g_weapon.c // perform the server side effects of a weapon firing #include "g_headers.h" #include "g_local.h" #include "g_functions.h" #include "anims.h" #include "b_local.h" #include "w_local.h" vec3_t wpFwd, wpVright, wpUp; vec3_t wpMuzzle; gentity_t *ent_list[MAX_GENTITIES]; // some naughty little things that are used cg side int g_rocketLockEntNum = ENTITYNUM_NONE; int g_rocketLockTime = 0; int g_rocketSlackTime = 0; // Weapon Helper Functions //----------------------------------------------------------------------------- void WP_TraceSetStart( const gentity_t *ent, vec3_t start, const vec3_t mins, const vec3_t maxs ) //----------------------------------------------------------------------------- { //make sure our start point isn't on the other side of a wall trace_t tr; vec3_t entMins, newstart; vec3_t entMaxs; VectorSet( entMaxs, 5, 5, 5 ); VectorScale( entMaxs, -1, entMins ); if ( !ent->client ) { return; } VectorCopy( ent->currentOrigin, newstart ); newstart[2] = start[2]; // force newstart to be on the same plane as the wpMuzzle ( start ) gi.trace( &tr, newstart, entMins, entMaxs, start, ent->s.number, MASK_SOLID|CONTENTS_SHOTCLIP, G2_NOCOLLIDE, 0 ); if ( tr.startsolid || tr.allsolid ) { // there is a problem here.. return; } if ( tr.fraction < 1.0f ) { VectorCopy( tr.endpos, start ); } } //----------------------------------------------------------------------------- gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire ) //----------------------------------------------------------------------------- { gentity_t *missile; missile = G_Spawn(); missile->nextthink = level.time + life; missile->e_ThinkFunc = thinkF_G_FreeEntity; missile->s.eType = ET_MISSILE; missile->owner = owner; missile->alt_fire = altFire; missile->s.pos.trType = TR_LINEAR; missile->s.pos.trTime = level.time;// - 10; // move a bit on the very first frame VectorCopy( org, missile->s.pos.trBase ); VectorScale( dir, vel, missile->s.pos.trDelta ); VectorCopy( org, missile->currentOrigin); gi.linkentity( missile ); return missile; } //----------------------------------------------------------------------------- void WP_Stick( gentity_t *missile, trace_t *trace, float fudge_distance ) //----------------------------------------------------------------------------- { vec3_t org, ang; // not moving or rotating missile->s.pos.trType = TR_STATIONARY; VectorClear( missile->s.pos.trDelta ); VectorClear( missile->s.apos.trDelta ); // so we don't stick into the wall VectorMA( trace->endpos, fudge_distance, trace->plane.normal, org ); G_SetOrigin( missile, org ); vectoangles( trace->plane.normal, ang ); G_SetAngles( missile, ang ); // I guess explode death wants me as the normal? // VectorCopy( trace->plane.normal, missile->pos1 ); gi.linkentity( missile ); } // This version shares is in the thinkFunc format //----------------------------------------------------------------------------- void WP_Explode( gentity_t *self ) //----------------------------------------------------------------------------- { gentity_t *attacker = self; vec3_t wpFwd; // stop chain reaction runaway loops self->takedamage = qfalse; self->s.loopSound = 0; // VectorCopy( self->currentOrigin, self->s.pos.trBase ); AngleVectors( self->s.angles, wpFwd, NULL, NULL ); if ( self->fxID > 0 ) { G_PlayEffect( self->fxID, self->currentOrigin, wpFwd ); } if ( self->owner ) { attacker = self->owner; } else if ( self->activator ) { attacker = self->activator; } if ( self->splashDamage > 0 && self->splashRadius > 0 ) { G_RadiusDamage( self->currentOrigin, attacker, self->splashDamage, self->splashRadius, attacker, MOD_EXPLOSIVE_SPLASH ); } if ( self->target ) { G_UseTargets( self, attacker ); } G_SetOrigin( self, self->currentOrigin ); self->nextthink = level.time + 50; self->e_ThinkFunc = thinkF_G_FreeEntity; } // We need to have a dieFunc, otherwise G_Damage won't actually make us die. I could modify G_Damage, but that entails too many changes //----------------------------------------------------------------------------- void WP_ExplosiveDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc ) //----------------------------------------------------------------------------- { self->enemy = attacker; if ( attacker && !attacker->s.number ) { // less damage when shot by player self->splashDamage /= 3; self->splashRadius /= 3; } self->s.eFlags &= ~EF_FIRING; // don't draw beam if we are dead WP_Explode( self ); } //--------------------------------------------------------- void AddLeanOfs(const gentity_t *const ent, vec3_t point) //--------------------------------------------------------- { if(ent->client) { if(ent->client->ps.leanofs) { vec3_t right; //add leaning offset AngleVectors(ent->client->ps.viewangles, NULL, right, NULL); VectorMA(point, (float)ent->client->ps.leanofs, right, point); } } } //--------------------------------------------------------- void SubtractLeanOfs(const gentity_t *const ent, vec3_t point) //--------------------------------------------------------- { if(ent->client) { if(ent->client->ps.leanofs) { vec3_t right; //add leaning offset AngleVectors( ent->client->ps.viewangles, NULL, right, NULL ); VectorMA( point, ent->client->ps.leanofs*-1, right, point ); } } } //--------------------------------------------------------- void ViewHeightFix(const gentity_t *const ent) //--------------------------------------------------------- { //FIXME: this is hacky and doesn't need to be here. Was only put here to make up //for the times a crouch anim would be used but not actually crouching. //When we start calcing eyepos (SPOT_HEAD) from the tag_eyes, we won't need //this (or viewheight at all?) if ( !ent ) return; if ( !ent->client || !ent->NPC ) return; if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) return;//dead if ( ent->client->ps.legsAnim == BOTH_CROUCH1IDLE || ent->client->ps.legsAnim == BOTH_CROUCH1 || ent->client->ps.legsAnim == BOTH_CROUCH1WALK ) { if ( ent->client->ps.viewheight!=ent->client->crouchheight + STANDARD_VIEWHEIGHT_OFFSET ) ent->client->ps.viewheight = ent->client->crouchheight + STANDARD_VIEWHEIGHT_OFFSET; } else { if ( ent->client->ps.viewheight!=ent->client->standheight + STANDARD_VIEWHEIGHT_OFFSET ) ent->client->ps.viewheight = ent->client->standheight + STANDARD_VIEWHEIGHT_OFFSET; } } qboolean W_AccuracyLoggableWeapon( int weapon, qboolean alt_fire, int mod ) { if ( mod != MOD_UNKNOWN ) { switch( mod ) { //standard weapons case MOD_BRYAR: case MOD_BRYAR_ALT: case MOD_BLASTER: case MOD_BLASTER_ALT: case MOD_DISRUPTOR: case MOD_SNIPER: case MOD_BOWCASTER: case MOD_BOWCASTER_ALT: case MOD_ROCKET: case MOD_ROCKET_ALT: return qtrue; break; //non-alt standard case MOD_REPEATER: case MOD_DEMP2: case MOD_FLECHETTE: return qtrue; break; //emplaced gun case MOD_EMPLACED: return qtrue; break; //atst case MOD_ENERGY: case MOD_EXPLOSIVE: if ( weapon == WP_ATST_MAIN || weapon == WP_ATST_SIDE ) { return qtrue; } break; } } else if ( weapon != WP_NONE ) { switch( weapon ) { case WP_BRYAR_PISTOL: case WP_BLASTER: case WP_DISRUPTOR: case WP_BOWCASTER: case WP_ROCKET_LAUNCHER: return qtrue; break; //non-alt standard case WP_REPEATER: case WP_DEMP2: case WP_FLECHETTE: if ( !alt_fire ) { return qtrue; } break; //emplaced gun case WP_EMPLACED_GUN: return qtrue; break; //atst case WP_ATST_MAIN: case WP_ATST_SIDE: return qtrue; break; } } return qfalse; } /* =============== LogAccuracyHit =============== */ qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ) { if( !target->takedamage ) { return qfalse; } if ( target == attacker ) { return qfalse; } if( !target->client ) { return qfalse; } if( !attacker->client ) { return qfalse; } if( target->client->ps.stats[STAT_HEALTH] <= 0 ) { return qfalse; } if ( OnSameTeam( target, attacker ) ) { return qfalse; } return qtrue; } //--------------------------------------------------------- void CalcMuzzlePoint( gentity_t *const ent, vec3_t wpFwd, vec3_t right, vec3_t wpUp, vec3_t muzzlePoint, float lead_in ) //--------------------------------------------------------- { vec3_t org; mdxaBone_t boltMatrix; if( !lead_in ) //&& ent->s.number != 0 {//Not players or melee if( ent->client ) { if ( ent->client->renderInfo.mPCalcTime >= level.time - FRAMETIME*2 ) {//Our muzz point was calced no more than 2 frames ago VectorCopy(ent->client->renderInfo.muzzlePoint, muzzlePoint); return; } } } VectorCopy( ent->currentOrigin, muzzlePoint ); switch( ent->s.weapon ) { case WP_BRYAR_PISTOL: ViewHeightFix(ent); muzzlePoint[2] += ent->client->ps.viewheight;//By eyes muzzlePoint[2] -= 16; VectorMA( muzzlePoint, 28, wpFwd, muzzlePoint ); VectorMA( muzzlePoint, 6, wpVright, muzzlePoint ); break; case WP_ROCKET_LAUNCHER: case WP_THERMAL: ViewHeightFix(ent); muzzlePoint[2] += ent->client->ps.viewheight;//By eyes muzzlePoint[2] -= 2; break; case WP_BLASTER: ViewHeightFix(ent); muzzlePoint[2] += ent->client->ps.viewheight;//By eyes muzzlePoint[2] -= 1; if ( ent->s.number == 0 ) VectorMA( muzzlePoint, 12, wpFwd, muzzlePoint ); // player, don't set this any lower otherwise the projectile will impact immediately when your back is to a wall else VectorMA( muzzlePoint, 2, wpFwd, muzzlePoint ); // NPC, don't set too far wpFwd otherwise the projectile can go through doors VectorMA( muzzlePoint, 1, wpVright, muzzlePoint ); break; case WP_SABER: if(ent->NPC!=NULL && (ent->client->ps.torsoAnim == TORSO_WEAPONREADY2 || ent->client->ps.torsoAnim == BOTH_ATTACK2))//Sniper pose { ViewHeightFix(ent); wpMuzzle[2] += ent->client->ps.viewheight;//By eyes } else { muzzlePoint[2] += 16; } VectorMA( muzzlePoint, 8, wpFwd, muzzlePoint ); VectorMA( muzzlePoint, 16, wpVright, muzzlePoint ); break; case WP_BOT_LASER: muzzlePoint[2] -= 16; // break; case WP_ATST_MAIN: if (ent->count > 0) { ent->count = 0; gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->handLBolt, &boltMatrix, ent->s.angles, ent->s.origin, (cg.time?cg.time:level.time), NULL, ent->s.modelScale ); } else { ent->count = 1; gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->handRBolt, &boltMatrix, ent->s.angles, ent->s.origin, (cg.time?cg.time:level.time), NULL, ent->s.modelScale ); } gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org ); VectorCopy(org,muzzlePoint); break; } AddLeanOfs(ent, muzzlePoint); } //--------------------------------------------------------- void FireWeapon( gentity_t *ent, qboolean alt_fire ) //--------------------------------------------------------- { float alert = 256; // track shots taken for accuracy tracking. ent->client->ps.persistant[PERS_ACCURACY_SHOTS]++; // set aiming directions if ( ent->s.weapon == WP_DISRUPTOR && alt_fire ) { if ( ent->NPC ) { //snipers must use the angles they actually did their shot trace with AngleVectors( ent->lastAngles, wpFwd, wpVright, wpUp ); } } else if ( ent->s.weapon == WP_ATST_SIDE || ent->s.weapon == WP_ATST_MAIN ) { vec3_t delta1, enemy_org1, muzzle1; vec3_t angleToEnemy1; VectorCopy( ent->client->renderInfo.muzzlePoint, muzzle1 ); if ( !ent->s.number ) {//player driving an AT-ST //SIGH... because we can't anticipate alt-fire, must calc muzzle here and now mdxaBone_t boltMatrix; int bolt; if ( ent->client->ps.weapon == WP_ATST_MAIN ) {//FIXME: alt_fire should fire both barrels, but slower? if ( ent->alt_fire ) { bolt = ent->handRBolt; } else { bolt = ent->handLBolt; } } else {// ATST SIDE weapons if ( ent->alt_fire ) { if ( gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], "head_light_blaster_cann" ) ) {//don't have it! return; } bolt = ent->genericBolt2; } else { if ( gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], "head_concussion_charger" ) ) {//don't have it! return; } bolt = ent->genericBolt1; } } vec3_t yawOnlyAngles = {0, ent->currentAngles[YAW], 0}; if ( ent->currentAngles[YAW] != ent->client->ps.legsYaw ) { yawOnlyAngles[YAW] = ent->client->ps.legsYaw; } gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, bolt, &boltMatrix, yawOnlyAngles, ent->currentOrigin, (cg.time?cg.time:level.time), NULL, ent->s.modelScale ); // work the matrix axis stuff into the original axis and origins used. gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent->client->renderInfo.muzzlePoint ); gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, ent->client->renderInfo.muzzleDir ); ent->client->renderInfo.mPCalcTime = level.time; AngleVectors( ent->client->ps.viewangles, wpFwd, wpVright, wpUp ); //CalcMuzzlePoint( ent, wpFwd, vright, wpUp, wpMuzzle, 0 ); } else if ( !ent->enemy ) {//an NPC with no enemy to auto-aim at VectorCopy( ent->client->renderInfo.muzzleDir, wpFwd ); } else {//NPC, auto-aim at enemy CalcEntitySpot( ent->enemy, SPOT_HEAD, enemy_org1 ); VectorSubtract (enemy_org1, muzzle1, delta1); vectoangles ( delta1, angleToEnemy1 ); AngleVectors (angleToEnemy1, wpFwd, wpVright, wpUp); } } else if ( ent->s.weapon == WP_BOT_LASER && ent->enemy ) { vec3_t delta1, enemy_org1, muzzle1; vec3_t angleToEnemy1; CalcEntitySpot( ent->enemy, SPOT_HEAD, enemy_org1 ); CalcEntitySpot( ent, SPOT_WEAPON, muzzle1 ); VectorSubtract (enemy_org1, muzzle1, delta1); vectoangles ( delta1, angleToEnemy1 ); AngleVectors (angleToEnemy1, wpFwd, wpVright, wpUp); } else { AngleVectors( ent->client->ps.viewangles, wpFwd, wpVright, wpUp ); } ent->alt_fire = alt_fire; CalcMuzzlePoint ( ent, wpFwd, wpVright, wpUp, wpMuzzle , 0); // fire the specific weapon switch( ent->s.weapon ) { // Player weapons //----------------- case WP_SABER: return; break; case WP_BRYAR_PISTOL: WP_FireBryarPistol( ent, alt_fire ); break; case WP_BLASTER: WP_FireBlaster( ent, alt_fire ); break; case WP_DISRUPTOR: alert = 50; // if you want it to alert enemies, remove this WP_FireDisruptor( ent, alt_fire ); break; case WP_BOWCASTER: WP_FireBowcaster( ent, alt_fire ); break; case WP_REPEATER: WP_FireRepeater( ent, alt_fire ); break; case WP_DEMP2: WP_FireDEMP2( ent, alt_fire ); break; case WP_FLECHETTE: WP_FireFlechette( ent, alt_fire ); break; case WP_ROCKET_LAUNCHER: WP_FireRocket( ent, alt_fire ); break; case WP_THERMAL: WP_FireThermalDetonator( ent, alt_fire ); break; case WP_TRIP_MINE: alert = 0; // if you want it to alert enemies, remove this WP_PlaceLaserTrap( ent, alt_fire ); break; case WP_DET_PACK: alert = 0; // if you want it to alert enemies, remove this WP_FireDetPack( ent, alt_fire ); break; case WP_BOT_LASER: WP_BotLaser( ent ); break; case WP_EMPLACED_GUN: // doesn't care about whether it's alt-fire or not. We can do an alt-fire if needed WP_EmplacedFire( ent ); break; case WP_MELEE: alert = 0; // if you want it to alert enemies, remove this WP_Melee( ent, qfalse ); break; case WP_ATST_MAIN: WP_ATSTMainFire( ent ); break; case WP_ATST_SIDE: // TEMP if ( alt_fire ) { // WP_FireRocket( ent, qfalse ); WP_ATSTSideAltFire(ent); } else { if ( ent->s.number == 0 && ent->client->ps.vehicleModel ) { WP_ATSTMainFire( ent ); } else { WP_ATSTSideFire(ent); } } break; case WP_TIE_FIGHTER: // TEMP WP_EmplacedFire( ent ); break; case WP_RAPID_FIRE_CONC: // TEMP if ( alt_fire ) { WP_FireRepeater( ent, alt_fire ); } else { WP_EmplacedFire( ent ); } break; case WP_STUN_BATON: WP_FireStunBaton( ent, alt_fire ); break; case WP_BLASTER_PISTOL: // enemy version WP_FireBryarPistol( ent, qfalse ); // never an alt-fire? break; default: return; break; } if ( !ent->s.number ) { if ( ent->s.weapon == WP_FLECHETTE || (ent->s.weapon == WP_BOWCASTER && !alt_fire) ) {//these can fire multiple shots, count them individually within the firing functions } else if ( W_AccuracyLoggableWeapon( ent->s.weapon, alt_fire, MOD_UNKNOWN ) ) { ent->client->sess.missionStats.shotsFired++; } } // We should probably just use this as a default behavior, in special cases, just set alert to false. if ( ent->s.number == 0 && alert > 0 ) { AddSoundEvent( ent, wpMuzzle, alert, AEL_DISCOVERED ); AddSightEvent( ent, wpMuzzle, alert*2, AEL_DISCOVERED, 20 ); } } // spawnflag #define EMPLACED_INACTIVE 1 #define EMPLACED_FACING 2 #define EMPLACED_VULNERABLE 4 //---------------------------------------------------------- /*QUAKED emplaced_gun (0 0 1) (-24 -24 0) (24 24 64) INACTIVE FACING VULNERABLE INACTIVE cannot be used until used by a target_activate FACING - player must be facing relatively in the same direction as the gun in order to use it VULNERABLE - allow the gun to take damage count - how much ammo to give this gun ( default 999 ) health - how much damage the gun can take before it blows ( default 250 ) delay - ONLY AFFECTS NPCs - time between shots ( default 200 on hardest setting ) wait - ONLY AFFECTS NPCs - time between bursts ( default 800 on hardest setting ) splashdamage - how much damage a blowing up gun deals ( default 80 ) splashradius - radius for exploding damage ( default 128 ) */ //---------------------------------------------------------- void emplaced_gun_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { vec3_t fwd1, fwd2; if ( self->health <= 0 ) { // can't use a dead gun. return; } if ( self->svFlags & SVF_INACTIVE ) { return; // can't use inactive gun } if ( !activator->client ) { return; // only a client can use it. } if ( self->activator ) { // someone is already in the gun. return; } // We'll just let the designers duke this one out....I mean, as to whether they even want to limit such a thing. if ( self->spawnflags & EMPLACED_FACING ) { // Let's get some direction vectors for the users AngleVectors( activator->client->ps.viewangles, fwd1, NULL, NULL ); // Get the guns direction vector AngleVectors( self->pos1, fwd2, NULL, NULL ); float dot = DotProduct( fwd1, fwd2 ); // Must be reasonably facing the way the gun points ( 90 degrees or so ), otherwise we don't allow to use it. if ( dot < 0.0f ) { return; } } // don't allow using it again for half a second if ( self->delay + 500 < level.time ) { int oldWeapon = activator->s.weapon; if ( oldWeapon == WP_SABER ) { self->alt_fire = activator->client->ps.saberActive; } // swap the users weapon with the emplaced gun and add the ammo the gun has to the player activator->client->ps.weapon = self->s.weapon; Add_Ammo( activator, WP_EMPLACED_GUN, self->count ); activator->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_EMPLACED_GUN ); // Allow us to point from one to the other activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it. self->activator = activator; if ( activator->weaponModel >= 0 ) { // rip that gun out of their hands.... gi.G2API_RemoveGhoul2Model( activator->ghoul2, activator->weaponModel ); activator->weaponModel = -1; } extern void ChangeWeapon( gentity_t *ent, int newWeapon ); if ( activator->NPC ) { if ( activator->weaponModel >= 0 ) { // rip that gun out of their hands.... gi.G2API_RemoveGhoul2Model( activator->ghoul2, activator->weaponModel ); activator->weaponModel = -1; // Doesn't work? // activator->maxs[2] += 35; // make it so you can potentially shoot their head // activator->s.radius += 10; // increase ghoul radius so we can collide with the enemy more accurately // gi.linkentity( activator ); } ChangeWeapon( activator, WP_EMPLACED_GUN ); } else if ( activator->s.number == 0 ) { // we don't want for it to draw the weapon select stuff cg.weaponSelect = WP_EMPLACED_GUN; CG_CenterPrint( "@INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.95 ); } // Since we move the activator inside of the gun, we reserve a solid spot where they were standing in order to be able to get back out without being in solid if ( self->nextTrain ) {//you never know G_FreeEntity( self->nextTrain ); } self->nextTrain = G_Spawn(); //self->nextTrain->classname = "emp_placeholder"; self->nextTrain->contents = CONTENTS_MONSTERCLIP|CONTENTS_PLAYERCLIP;//hmm... playerclip too now that we're doing it for NPCs? G_SetOrigin( self->nextTrain, activator->client->ps.origin ); VectorCopy( activator->mins, self->nextTrain->mins ); VectorCopy( activator->maxs, self->nextTrain->maxs ); gi.linkentity( self->nextTrain ); //need to inflate the activator's mins/maxs since the gunsit anim puts them outside of their bbox VectorSet( activator->mins, -24, -24, -24 ); VectorSet( activator->maxs, 24, 24, 40 ); // Move the activator into the center of the gun. For NPC's the only way the can get out of the gun is to die. VectorCopy( self->s.origin, activator->client->ps.origin ); activator->client->ps.origin[2] += 30; // move them up so they aren't standing in the floor gi.linkentity( activator ); // the gun will track which weapon we used to have self->s.weapon = oldWeapon; // Lock the player activator->client->ps.eFlags |= EF_LOCKED_TO_WEAPON; activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it. self->activator = activator; self->delay = level.time; // can't disconnect from the thing for half a second // Let the gun be considered an enemy self->svFlags |= SVF_NONNPC_ENEMY; self->noDamageTeam = activator->client->playerTeam; // FIXME: don't do this, we'll try and actually put the player in this beast // move the player to the center of the gun // activator->contents = 0; // VectorCopy( self->currentOrigin, activator->client->ps.origin ); SetClientViewAngle( activator, self->pos1 ); //FIXME: should really wait a bit after spawn and get this just once? self->waypoint = NAV_FindClosestWaypointForEnt( self, WAYPOINT_NONE ); #ifdef _DEBUG if ( self->waypoint == -1 ) { gi.Printf( S_COLOR_RED"ERROR: no waypoint for emplaced_gun %s at %s\n", self->targetname, vtos(self->currentOrigin) ); } #endif G_Sound( self, G_SoundIndex( "sound/weapons/emplaced/emplaced_mount.mp3" )); } } //---------------------------------------------------------- void emplaced_gun_pain( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, vec3_t point, int damage, int mod,int hitLoc ) { if ( self->health <= 0 ) { // play pain effect? } else { if ( self->paintarget ) { G_UseTargets2( self, self->activator, self->paintarget ); } // Don't do script if dead G_ActivateBehavior( self, BSET_PAIN ); } } //---------------------------------------------------------- void emplaced_blow( gentity_t *ent ) { ent->e_DieFunc = dieF_NULL; emplaced_gun_die( ent, ent->lastEnemy, ent->lastEnemy, 0, MOD_UNKNOWN ); } //---------------------------------------------------------- void emplaced_gun_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc ) { vec3_t org; // turn off any firing animations it may have been doing self->s.frame = self->startFrame = self->endFrame = 0; self->svFlags &= ~SVF_ANIMATING; self->health = 0; // self->s.weapon = WP_EMPLACED_GUN; // we need to be able to switch back to the old weapon self->takedamage = qfalse; self->lastEnemy = attacker; // we defer explosion so the player has time to get out if ( self->e_DieFunc ) { self->e_ThinkFunc = thinkF_emplaced_blow; self->nextthink = level.time + 3000; // don't blow for a couple of seconds return; } if ( self->activator && self->activator->client ) { if ( self->activator->NPC ) { vec3_t right; // radius damage seems to throw them, but add an extra bit to throw them away from the weapon AngleVectors( self->currentAngles, NULL, right, NULL ); VectorMA( self->activator->client->ps.velocity, 140, right, self->activator->client->ps.velocity ); self->activator->client->ps.velocity[2] = -100; // kill them self->activator->health = 0; self->activator->client->ps.stats[STAT_HEALTH] = 0; } // kill the players emplaced ammo, cheesy way to keep the gun from firing self->activator->client->ps.ammo[weaponData[WP_EMPLACED_GUN].ammoIndex] = 0; } self->e_PainFunc = painF_NULL; self->e_ThinkFunc = thinkF_NULL; if ( self->target ) { G_UseTargets( self, attacker ); } G_RadiusDamage( self->currentOrigin, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN ); // when the gun is dead, add some ugliness to it. vec3_t ugly; ugly[YAW] = 4; ugly[PITCH] = self->lastAngles[PITCH] * 0.8f + Q_flrand(-1.0f, 1.0f) * 6; ugly[ROLL] = Q_flrand(-1.0f, 1.0f) * 7; gi.G2API_SetBoneAnglesIndex( &self->ghoul2[self->playerModel], self->lowerLumbarBone, ugly, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 ); VectorCopy( self->currentOrigin, org ); org[2] += 20; G_PlayEffect( "emplaced/explode", org ); // create some persistent smoke by using a dynamically created fx runner gentity_t *ent = G_Spawn(); if ( ent ) { ent->delay = 200; ent->random = 100; ent->fxID = G_EffectIndex( "emplaced/dead_smoke" ); ent->e_ThinkFunc = thinkF_fx_runner_think; ent->nextthink = level.time + 50; // move up above the gun origin VectorCopy( self->currentOrigin, org ); org[2] += 35; G_SetOrigin( ent, org ); VectorCopy( org, ent->s.origin ); VectorSet( ent->s.angles, -90, 0, 0 ); // up G_SetAngles( ent, ent->s.angles ); gi.linkentity( ent ); } G_ActivateBehavior( self, BSET_DEATH ); } //---------------------------------------------------------- void SP_emplaced_gun( gentity_t *ent ) { char name[] = "models/map_objects/imp_mine/turret_chair.glm"; ent->svFlags |= SVF_PLAYER_USABLE; ent->contents = CONTENTS_BODY;//CONTENTS_SHOTCLIP|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP;//CONTENTS_SOLID; if ( ent->spawnflags & EMPLACED_INACTIVE ) { ent->svFlags |= SVF_INACTIVE; } VectorSet( ent->mins, -30, -30, -5 ); VectorSet( ent->maxs, 30, 30, 60 ); ent->takedamage = qtrue; if ( !( ent->spawnflags & EMPLACED_VULNERABLE )) { ent->flags |= FL_GODMODE; } ent->s.radius = 110; ent->spawnflags |= 4; // deadsolid ent->e_ThinkFunc = thinkF_NULL; ent->e_PainFunc = painF_emplaced_gun_pain; ent->e_DieFunc = dieF_emplaced_gun_die; G_EffectIndex( "emplaced/explode" ); G_EffectIndex( "emplaced/dead_smoke" ); G_SoundIndex( "sound/weapons/emplaced/emplaced_mount.mp3" ); G_SoundIndex( "sound/weapons/emplaced/emplaced_dismount.mp3" ); G_SoundIndex( "sound/weapons/emplaced/emplaced_move_lp.wav" ); // Set up our defaults and override with custom amounts as necessary G_SpawnInt( "count", "999", &ent->count ); G_SpawnInt( "health", "250", &ent->health ); G_SpawnInt( "splashDamage", "80", &ent->splashDamage ); G_SpawnInt( "splashRadius", "128", &ent->splashRadius ); G_SpawnFloat( "delay", "200", &ent->random ); // NOTE: spawning into a different field!! G_SpawnFloat( "wait", "800", &ent->wait ); ent->max_health = ent->health; ent->dflags |= DAMAGE_CUSTOM_HUD; // dumb, but we draw a custom hud ent->s.modelindex = G_ModelIndex( name ); ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, name, ent->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 ); // Activate our tags and bones ent->headBolt = gi.G2API_AddBolt( &ent->ghoul2[0], "*seat" ); ent->handLBolt = gi.G2API_AddBolt( &ent->ghoul2[0], "*flash01" ); ent->handRBolt = gi.G2API_AddBolt( &ent->ghoul2[0], "*flash02" ); ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "base_bone", qtrue ); ent->lowerLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[0], "swivel_bone", qtrue ); gi.G2API_SetBoneAngles( &ent->ghoul2[0], "swivel_bone", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0); RegisterItem( FindItemForWeapon( WP_EMPLACED_GUN )); ent->s.weapon = WP_EMPLACED_GUN; G_SetOrigin( ent, ent->s.origin ); G_SetAngles( ent, ent->s.angles ); VectorCopy( ent->s.angles, ent->lastAngles ); // store base angles for later VectorCopy( ent->s.angles, ent->pos1 ); ent->e_UseFunc = useF_emplaced_gun_use; gi.linkentity (ent); }