/* =========================================================================== 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 . =========================================================================== */ #include "g_local.h" #include "g_functions.h" #include "wp_saber.h" #include "bg_local.h" #include "../cgame/cg_local.h" #include "b_local.h" #ifdef _DEBUG #include #endif //_DEBUG extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f ); qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ); extern qboolean PM_SaberInParry( int move ); extern qboolean PM_SaberInReflect( int move ); extern qboolean PM_SaberInIdle( int move ); extern qboolean PM_SaberInAttack( int move ); extern qboolean PM_SaberInTransitionAny( int move ); extern qboolean PM_SaberInSpecialAttack( int anim ); //------------------------------------------------------------------------- void G_MissileBounceEffect( gentity_t *ent, vec3_t org, vec3_t dir, qboolean hitWorld ) { //FIXME: have an EV_BOUNCE_MISSILE event that checks the s.weapon and does the appropriate effect switch( ent->s.weapon ) { case WP_BOWCASTER: if ( hitWorld ) { G_PlayEffect( "bowcaster/bounce_wall", org, dir ); } else { G_PlayEffect( "bowcaster/deflect", ent->currentOrigin, dir ); } break; case WP_BLASTER: case WP_BRYAR_PISTOL: case WP_BLASTER_PISTOL: G_PlayEffect( "blaster/deflect", ent->currentOrigin, dir ); break; default: { gentity_t *tent = G_TempEntity( org, EV_GRENADE_BOUNCE ); VectorCopy( dir, tent->pos1 ); tent->s.weapon = ent->s.weapon; } break; } } void G_MissileReflectEffect( gentity_t *ent, vec3_t org, vec3_t dir ) { //FIXME: have an EV_BOUNCE_MISSILE event that checks the s.weapon and does the appropriate effect switch( ent->s.weapon ) { case WP_BOWCASTER: G_PlayEffect( "bowcaster/deflect", ent->currentOrigin, dir ); break; case WP_BLASTER: case WP_BRYAR_PISTOL: case WP_BLASTER_PISTOL: default: G_PlayEffect( "blaster/deflect", ent->currentOrigin, dir ); break; } } //------------------------------------------------------------------------- static void G_MissileStick( gentity_t *missile, gentity_t *other, trace_t *tr ) { if ( other->NPC || !Q_stricmp( other->classname, "misc_model_breakable" )) { // we bounce off of NPC's and misc model breakables because sticking to them requires too much effort vec3_t velocity; int hitTime = level.previousTime + ( level.time - level.previousTime ) * tr->fraction; EvaluateTrajectoryDelta( &missile->s.pos, hitTime, velocity ); float dot = DotProduct( velocity, tr->plane.normal ); G_SetOrigin( missile, tr->endpos ); VectorMA( velocity, -1.6f * dot, tr->plane.normal, missile->s.pos.trDelta ); VectorMA( missile->s.pos.trDelta, 10, tr->plane.normal, missile->s.pos.trDelta ); missile->s.pos.trTime = level.time - 10; // move a bit on the first frame // check for stop if ( tr->entityNum >= 0 && tr->entityNum < ENTITYNUM_WORLD && tr->plane.normal[2] > 0.7 && missile->s.pos.trDelta[2] < 40 ) //this can happen even on very slightly sloped walls, so changed it from > 0 to > 0.7 { missile->nextthink = level.time + 100; } else { // fall till we hit the ground missile->s.pos.trType = TR_GRAVITY; } return; // don't stick yet } if ( missile->e_TouchFunc != touchF_NULL ) { GEntity_TouchFunc( missile, other, tr ); } G_AddEvent( missile, EV_MISSILE_STICK, 0 ); if ( other->s.eType == ET_MOVER || other->e_DieFunc == dieF_funcBBrushDie || other->e_DieFunc == dieF_funcGlassDie) { // movers and breakable brushes need extra info...so sticky missiles can ride lifts and blow up when the thing they are attached to goes away. missile->s.groundEntityNum = tr->entityNum; } } /* ================ G_ReflectMissile Reflect the missile roughly back at it's owner ================ */ extern gentity_t *Jedi_FindEnemyInCone( gentity_t *self, gentity_t *fallback, float minDot ); void G_ReflectMissile( gentity_t *ent, gentity_t *missile, vec3_t forward ) { vec3_t bounce_dir; int i; float speed; qboolean reflected = qfalse; gentity_t *owner = ent; if ( ent->owner ) { owner = ent->owner; } //save the original speed speed = VectorNormalize( missile->s.pos.trDelta ); if ( ent && owner && owner->client && !owner->client->ps.saberInFlight && (owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 || (owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_1&&!Q_irand( 0, 3 )) ) ) {//if high enough defense skill and saber in-hand (100% at level 3, 25% at level 2, 0% at level 1), reflections are perfectly deflected toward an enemy gentity_t *enemy; if ( owner->enemy && Q_irand( 0, 3 ) ) {//toward current enemy 75% of the time enemy = owner->enemy; } else {//find another enemy enemy = Jedi_FindEnemyInCone( owner, owner->enemy, 0.3f ); } if ( enemy ) { vec3_t bullseye; CalcEntitySpot( enemy, SPOT_HEAD, bullseye ); bullseye[0] += Q_irand( -4, 4 ); bullseye[1] += Q_irand( -4, 4 ); bullseye[2] += Q_irand( -16, 4 ); VectorSubtract( bullseye, missile->currentOrigin, bounce_dir ); VectorNormalize( bounce_dir ); if ( !PM_SaberInParry( owner->client->ps.saberMove ) && !PM_SaberInReflect( owner->client->ps.saberMove ) && !PM_SaberInIdle( owner->client->ps.saberMove ) ) {//a bit more wild if ( PM_SaberInAttack( owner->client->ps.saberMove ) || PM_SaberInTransitionAny( owner->client->ps.saberMove ) || PM_SaberInSpecialAttack( owner->client->ps.torsoAnim ) ) {//moderately more wild for ( i = 0; i < 3; i++ ) { bounce_dir[i] += Q_flrand( -0.2f, 0.2f ); } } else {//mildly more wild for ( i = 0; i < 3; i++ ) { bounce_dir[i] += Q_flrand( -0.1f, 0.1f ); } } } VectorNormalize( bounce_dir ); reflected = qtrue; } } if ( !reflected ) { if ( missile->owner && missile->s.weapon != WP_SABER ) {//bounce back at them if you can VectorSubtract( missile->owner->currentOrigin, missile->currentOrigin, bounce_dir ); VectorNormalize( bounce_dir ); } else { vec3_t missile_dir; VectorSubtract( ent->currentOrigin, missile->currentOrigin, missile_dir ); VectorCopy( missile->s.pos.trDelta, bounce_dir ); VectorScale( bounce_dir, DotProduct( forward, missile_dir ), bounce_dir ); VectorNormalize( bounce_dir ); } if ( owner->s.weapon == WP_SABER && owner->client ) {//saber if ( owner->client->ps.saberInFlight ) {//reflecting off a thrown saber is totally wild for ( i = 0; i < 3; i++ ) { bounce_dir[i] += Q_flrand( -0.8f, 0.8f ); } } else if ( owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] <= FORCE_LEVEL_1 ) {// at level 1 for ( i = 0; i < 3; i++ ) { bounce_dir[i] += Q_flrand( -0.4f, 0.4f ); } } else {// at level 2 for ( i = 0; i < 3; i++ ) { bounce_dir[i] += Q_flrand( -0.2f, 0.2f ); } } if ( !PM_SaberInParry( owner->client->ps.saberMove ) && !PM_SaberInReflect( owner->client->ps.saberMove ) && !PM_SaberInIdle( owner->client->ps.saberMove ) ) {//a bit more wild if ( PM_SaberInAttack( owner->client->ps.saberMove ) || PM_SaberInTransitionAny( owner->client->ps.saberMove ) || PM_SaberInSpecialAttack( owner->client->ps.torsoAnim ) ) {//really wild for ( i = 0; i < 3; i++ ) { bounce_dir[i] += Q_flrand( -0.3f, 0.3f ); } } else {//mildly more wild for ( i = 0; i < 3; i++ ) { bounce_dir[i] += Q_flrand( -0.1f, 0.1f ); } } } } else {//some other kind of reflection for ( i = 0; i < 3; i++ ) { bounce_dir[i] += Q_flrand( -0.2f, 0.2f ); } } } VectorNormalize( bounce_dir ); VectorScale( bounce_dir, speed, missile->s.pos.trDelta ); #ifdef _DEBUG assert( !Q_isnan(missile->s.pos.trDelta[0])&&!Q_isnan(missile->s.pos.trDelta[1])&&!Q_isnan(missile->s.pos.trDelta[2])); #endif// _DEBUG missile->s.pos.trTime = level.time - 10; // move a bit on the very first frame VectorCopy( missile->currentOrigin, missile->s.pos.trBase ); if ( missile->s.weapon != WP_SABER ) {//you are mine, now! if ( !missile->lastEnemy ) {//remember who originally shot this missile missile->lastEnemy = missile->owner; } missile->owner = owner; } if ( missile->s.weapon == WP_ROCKET_LAUNCHER ) {//stop homing missile->e_ThinkFunc = thinkF_NULL; } } /* ================ G_BounceRollMissile ================ */ void G_BounceRollMissile( gentity_t *ent, trace_t *trace ) { vec3_t velocity, normal; float dot, speedXY, velocityZ, normalZ; int hitTime; // reflect the velocity on the trace plane hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction; EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity ); //Do horizontal //FIXME: Need to roll up, down slopes velocityZ = velocity[2]; velocity[2] = 0; speedXY = VectorLength( velocity );//friction VectorCopy( trace->plane.normal, normal ); normalZ = normal[2]; normal[2] = 0; dot = DotProduct( velocity, normal ); VectorMA( velocity, -2*dot, normal, ent->s.pos.trDelta ); //now do the z reflection //FIXME: Bobbles when it stops VectorSet( velocity, 0, 0, velocityZ ); VectorSet( normal, 0, 0, normalZ ); dot = DotProduct( velocity, normal )*-1; if ( dot > 10 ) { ent->s.pos.trDelta[2] = dot*0.3f;//not very bouncy } else { ent->s.pos.trDelta[2] = 0; } // check for stop if ( speedXY <= 0 ) { G_SetOrigin( ent, trace->endpos ); VectorCopy( ent->currentAngles, ent->s.apos.trBase ); VectorClear( ent->s.apos.trDelta ); ent->s.apos.trType = TR_STATIONARY; return; } //FIXME: rolling needs to match direction VectorCopy( ent->currentAngles, ent->s.apos.trBase ); VectorCopy( ent->s.pos.trDelta, ent->s.apos.trDelta ); //remember this spot VectorCopy( trace->endpos, ent->currentOrigin ); ent->s.pos.trTime = hitTime - 10; VectorCopy( ent->currentOrigin, ent->s.pos.trBase ); //VectorCopy( trace->plane.normal, ent->pos1 ); } /* ================ G_BounceMissile ================ */ void G_BounceMissile( gentity_t *ent, trace_t *trace ) { vec3_t velocity; float dot; int hitTime; // reflect the velocity on the trace plane hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction; EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity ); dot = DotProduct( velocity, trace->plane.normal ); VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta ); if ( ent->s.eFlags & EF_BOUNCE_SHRAPNEL ) { VectorScale( ent->s.pos.trDelta, 0.25f, ent->s.pos.trDelta ); ent->s.pos.trType = TR_GRAVITY; // check for stop if ( trace->plane.normal[2] > 0.7 && ent->s.pos.trDelta[2] < 40 ) //this can happen even on very slightly sloped walls, so changed it from > 0 to > 0.7 { G_SetOrigin( ent, trace->endpos ); ent->nextthink = level.time + 100; return; } } else if ( ent->s.eFlags & EF_BOUNCE_HALF ) { VectorScale( ent->s.pos.trDelta, 0.5, ent->s.pos.trDelta ); // check for stop if ( trace->plane.normal[2] > 0.7 && ent->s.pos.trDelta[2] < 40 ) //this can happen even on very slightly sloped walls, so changed it from > 0 to > 0.7 { if ( ent->s.weapon == WP_THERMAL ) {//roll when you "stop" ent->s.pos.trType = TR_INTERPOLATE; } else { G_SetOrigin( ent, trace->endpos ); ent->nextthink = level.time + 500; return; } } if ( ent->s.weapon == WP_THERMAL ) { ent->has_bounced = qtrue; } } #if 0 // OLD--this looks so wrong. It looked wrong in EF. It just must be wrong. VectorAdd( ent->currentOrigin, trace->plane.normal, ent->currentOrigin); ent->s.pos.trTime = level.time - 10; #else // NEW--It would seem that we want to set our trBase to the trace endpos // and set the trTime to the actual time of impact.... VectorAdd( trace->endpos, trace->plane.normal, ent->currentOrigin ); if ( hitTime >= level.time ) {//trace fraction must have been 1 ent->s.pos.trTime = level.time - 10; } else { ent->s.pos.trTime = hitTime - 10; // this is kinda dumb hacking, but it pushes the missile away from the impact plane a bit } #endif VectorCopy( ent->currentOrigin, ent->s.pos.trBase ); VectorCopy( trace->plane.normal, ent->pos1 ); if ( ent->s.weapon != WP_SABER && ent->s.weapon != WP_THERMAL && ent->e_clThinkFunc != clThinkF_CG_Limb && ent->e_ThinkFunc != thinkF_LimbThink ) {//not a saber, bouncing thermal or limb //now you can damage the guy you came from ent->owner = NULL; } } /* ================ G_MissileImpact ================ */ void NoghriGasCloudThink( gentity_t *self ) { self->nextthink = level.time + FRAMETIME; AddSightEvent( self->owner, self->currentOrigin, 200, AEL_DANGER, 50 ); if ( self->fx_time < level.time ) { vec3_t up = {0,0,1}; G_PlayEffect( "noghri_stick/gas_cloud", self->currentOrigin, up ); self->fx_time = level.time + 250; } if ( level.time - self->s.time <= 2500 ) { if ( !Q_irand( 0, 3-g_spskill->integer ) ) { G_RadiusDamage( self->currentOrigin, self->owner, Q_irand( 1, 4 ), self->splashRadius, self->owner, self->splashMethodOfDeath ); } } if ( level.time - self->s.time > 3000 ) { G_FreeEntity( self ); } } void G_SpawnNoghriGasCloud( gentity_t *ent ) {//FIXME: force-pushable/dispersable? ent->freeAfterEvent = qfalse; ent->e_TouchFunc = touchF_NULL; //ent->s.loopSound = G_SoundIndex( "sound/weapons/noghri/smoke.wav" ); //G_SoundOnEnt( ent, CHAN_AUTO, "sound/weapons/noghri/smoke.wav" ); G_SetOrigin( ent, ent->currentOrigin ); ent->e_ThinkFunc = thinkF_NoghriGasCloudThink; ent->nextthink = level.time + FRAMETIME; vec3_t up = {0,0,1}; G_PlayEffect( "noghri_stick/gas_cloud", ent->currentOrigin, up ); ent->fx_time = level.time + 250; ent->s.time = level.time; } extern void laserTrapStick( gentity_t *ent, vec3_t endpos, vec3_t normal ); extern qboolean W_AccuracyLoggableWeapon( int weapon, qboolean alt_fire, int mod ); void G_MissileImpacted( gentity_t *ent, gentity_t *other, vec3_t impactPos, vec3_t normal, int hitLoc=HL_NONE ) { // impact damage if ( other->takedamage ) { // FIXME: wrong damage direction? if ( ent->damage ) { vec3_t velocity; EvaluateTrajectoryDelta( &ent->s.pos, level.time, velocity ); if ( VectorLength( velocity ) == 0 ) { velocity[2] = 1; // stepped on a grenade } int damage = ent->damage; if( other->client ) { class_t npc_class = other->client->NPC_class; // If we are a robot and we aren't currently doing the full body electricity... if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE || npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 || npc_class == CLASS_REMOTE || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 || //npc_class == CLASS_PROTOCOL ||//no protocol, looks odd npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY ) { // special droid only behaviors if ( other->client->ps.powerups[PW_SHOCKED] < level.time + 100 ) { // ... do the effect for a split second for some more feedback other->s.powerups |= ( 1 << PW_SHOCKED ); other->client->ps.powerups[PW_SHOCKED] = level.time + 450; } //FIXME: throw some sparks off droids,too } } G_Damage( other, ent, ent->owner, velocity, impactPos, damage, ent->dflags, ent->methodOfDeath, hitLoc); if ( ent->s.weapon == WP_DEMP2 ) {//a hit with demp2 decloaks saboteurs if ( other && other->client && other->client->NPC_class == CLASS_SABOTEUR ) {//FIXME: make this disabled cloak hold for some amount of time? Saboteur_Decloak( other, Q_irand( 3000, 10000 ) ); if ( ent->methodOfDeath == MOD_DEMP2_ALT ) {//direct hit with alt disabled cloak forever if ( other->NPC ) {//permanently disable the saboteur's cloak other->NPC->aiFlags &= ~NPCAI_SHIELDS; } } } } } } // is it cheaper in bandwidth to just remove this ent and create a new // one, rather than changing the missile into the explosion? //G_FreeEntity(ent); if ( (other->takedamage && other->client ) || (ent->s.weapon == WP_FLECHETTE && other->contents&CONTENTS_LIGHTSABER) ) { G_AddEvent( ent, EV_MISSILE_HIT, DirToByte( normal ) ); ent->s.otherEntityNum = other->s.number; } else { G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( normal ) ); ent->s.otherEntityNum = other->s.number; } VectorCopy( normal, ent->pos1 ); if ( ent->owner )//&& ent->owner->s.number == 0 ) { //Add the event AddSoundEvent( ent->owner, ent->currentOrigin, 256, AEL_SUSPICIOUS, qfalse, qtrue ); AddSightEvent( ent->owner, ent->currentOrigin, 512, AEL_DISCOVERED, 75 ); } ent->freeAfterEvent = qtrue; // change over to a normal entity right at the point of impact ent->s.eType = ET_GENERAL; //SnapVectorTowards( trace->endpos, ent->s.pos.trBase ); // save net bandwidth VectorCopy( impactPos, ent->s.pos.trBase ); G_SetOrigin( ent, impactPos ); // splash damage (doesn't apply to person directly hit) if ( ent->splashDamage ) { G_RadiusDamage( impactPos, ent->owner, ent->splashDamage, ent->splashRadius, other, ent->splashMethodOfDeath ); } if ( ent->s.weapon == WP_NOGHRI_STICK ) { G_SpawnNoghriGasCloud( ent ); } gi.linkentity( ent ); } //------------------------------------------------ static void G_MissileAddAlerts( gentity_t *ent ) { //Add the event if ( ent->s.weapon == WP_THERMAL && ((ent->delay-level.time) < 2000 || ent->s.pos.trType == TR_INTERPOLATE) ) {//a thermal about to explode or rolling if ( (ent->delay-level.time) < 500 ) {//half a second before it explodes! AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER_GREAT, qfalse, qtrue ); AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER_GREAT, 20 ); } else {//2 seconds until it explodes or it's rolling AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, qfalse, qtrue ); AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, 20 ); } } else { AddSoundEvent( ent->owner, ent->currentOrigin, 128, AEL_DISCOVERED ); AddSightEvent( ent->owner, ent->currentOrigin, 256, AEL_DISCOVERED, 40 ); } } //------------------------------------------------------ void G_MissileImpact( gentity_t *ent, trace_t *trace, int hitLoc=HL_NONE ) { gentity_t *other; vec3_t diff; other = &g_entities[trace->entityNum]; if ( other == ent ) { assert(0&&"missile hit itself!!!"); return; } if ( trace->plane.normal[0] == 0.0f && trace->plane.normal[1] == 0.0f && trace->plane.normal[2] == 0.0f ) {//model moved into missile in flight probably... trace->plane.normal[0] = -ent->s.pos.trDelta[0]; trace->plane.normal[1] = -ent->s.pos.trDelta[1]; trace->plane.normal[2] = -ent->s.pos.trDelta[2]; VectorNormalize(trace->plane.normal); } if ( ent->owner && (other->takedamage||other->client) ) { if ( !ent->lastEnemy || ent->lastEnemy == ent->owner ) {//a missile that was not reflected or, if so, still is owned by original owner if( LogAccuracyHit( other, ent->owner ) ) { ent->owner->client->ps.persistant[PERS_ACCURACY_HITS]++; } if ( ent->owner->client && !ent->owner->s.number ) { if ( W_AccuracyLoggableWeapon( ent->s.weapon, qfalse, ent->methodOfDeath ) ) { ent->owner->client->sess.missionStats.hits++; } } } } // check for bounce //OR: if the surfaceParm is has a reflect property (magnetic shielding) and the missile isn't an exploding missile qboolean bounce = (qboolean)( (!other->takedamage && (ent->s.eFlags&(EF_BOUNCE|EF_BOUNCE_HALF))) || (((trace->surfaceFlags&SURF_FORCEFIELD)||(other->flags&FL_SHIELDED))&&!ent->splashDamage&&!ent->splashRadius&&ent->s.weapon != WP_NOGHRI_STICK) ); if ( ent->dflags & DAMAGE_HEAVY_WEAP_CLASS ) { // heavy class missiles generally never bounce. bounce = qfalse; } if ( other->flags & (FL_DMG_BY_HEAVY_WEAP_ONLY | FL_SHIELDED )) { // Dumb assumption, but I guess we must be a shielded ion_cannon?? We should probably verify // if it's an ion_cannon that's Heavy Weapon only, we don't want to make it shielded do we...? if ( (!strcmp( "misc_ion_cannon", other->classname )) && (other->flags & FL_SHIELDED) ) { // Anything will bounce off of us. bounce = qtrue; // Not exactly the debounce time, but rather the impact time for the shield effect...play effect for 1 second other->painDebounceTime = level.time + 1000; } } if ( ent->s.weapon == WP_DEMP2 ) { // demp2 shots can never bounce bounce = qfalse; // in fact, alt-charge shots will not call the regular impact functions if ( ent->alt_fire ) { // detonate at the trace end VectorCopy( trace->endpos, ent->currentOrigin ); VectorCopy( trace->plane.normal, ent->pos1 ); DEMP2_AltDetonate( ent ); return; } } if ( bounce ) { // Check to see if there is a bounce count if ( ent->bounceCount ) { // decrement number of bounces and then see if it should be done bouncing if ( !(--ent->bounceCount) ) { // He (or she) will bounce no more (after this current bounce, that is). ent->s.eFlags &= ~( EF_BOUNCE | EF_BOUNCE_HALF ); } } if ( other->NPC ) { G_Damage( other, ent, ent->owner, ent->currentOrigin, ent->s.pos.trDelta, 0, DAMAGE_NO_DAMAGE, MOD_UNKNOWN ); } G_BounceMissile( ent, trace ); if ( ent->owner )//&& ent->owner->s.number == 0 ) { G_MissileAddAlerts( ent ); } G_MissileBounceEffect( ent, trace->endpos, trace->plane.normal, (qboolean)(trace->entityNum==ENTITYNUM_WORLD) ); return; } // I would glom onto the EF_BOUNCE code section above, but don't feel like risking breaking something else if ( (!other->takedamage && ( ent->s.eFlags&(EF_BOUNCE_SHRAPNEL) ) ) || ((trace->surfaceFlags&SURF_FORCEFIELD)&&!ent->splashDamage&&!ent->splashRadius) ) { if ( !(other->contents&CONTENTS_LIGHTSABER) || g_spskill->integer <= 0//on easy, it reflects all shots || (g_spskill->integer == 1 && ent->s.weapon != WP_FLECHETTE && ent->s.weapon != WP_DEMP2 )//on medium it won't reflect flechette or demp shots || (g_spskill->integer >= 2 && ent->s.weapon != WP_FLECHETTE && ent->s.weapon != WP_DEMP2 && ent->s.weapon != WP_BOWCASTER && ent->s.weapon != WP_REPEATER )//on hard it won't reflect flechette, demp, repeater or bowcaster shots ) { G_BounceMissile( ent, trace ); if ( --ent->bounceCount < 0 ) { ent->s.eFlags &= ~EF_BOUNCE_SHRAPNEL; } G_MissileBounceEffect( ent, trace->endpos, trace->plane.normal, (qboolean)(trace->entityNum==ENTITYNUM_WORLD) ); return; } } if ( (!other->takedamage || (other->client && other->health <= 0)) && ent->s.weapon == WP_THERMAL && !ent->alt_fire ) {//rolling thermal det - FIXME: make this an eFlag like bounce & stick!!! //G_BounceRollMissile( ent, trace ); if ( ent->owner )//&& ent->owner->s.number == 0 ) { G_MissileAddAlerts( ent ); } //gi.linkentity( ent ); return; } // check for sticking if ( ent->s.eFlags & EF_MISSILE_STICK ) { if ( ent->owner )//&& ent->owner->s.number == 0 ) { //Add the event if ( ent->s.weapon == WP_TRIP_MINE ) { AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius/2, AEL_DISCOVERED/*AEL_DANGER*/, qfalse, qtrue ); AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DISCOVERED/*AEL_DANGER*/, 60 ); /* AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, qfalse, qtrue ); AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, 60 ); */ } else { AddSoundEvent( ent->owner, ent->currentOrigin, 128, AEL_DISCOVERED, qfalse, qtrue ); AddSightEvent( ent->owner, ent->currentOrigin, 256, AEL_DISCOVERED, 10 ); } } G_MissileStick( ent, other, trace ); return; } extern bool WP_DoingMoronicForcedAnimationForForcePowers(gentity_t *ent); // check for hitting a lightsaber if ( other->contents & CONTENTS_LIGHTSABER ) { if ( other->owner && !other->owner->s.number && other->owner->client ) { other->owner->client->sess.missionStats.saberBlocksCnt++; } if ( ( g_spskill->integer <= 0//on easy, it reflects all shots || (g_spskill->integer == 1 && ent->s.weapon != WP_FLECHETTE && ent->s.weapon != WP_DEMP2 )//on medium it won't reflect flechette or demp shots || (g_spskill->integer >= 2 && ent->s.weapon != WP_FLECHETTE && ent->s.weapon != WP_DEMP2 && ent->s.weapon != WP_BOWCASTER && ent->s.weapon != WP_REPEATER )//on hard it won't reflect flechette, demp, repeater or bowcaster shots ) && (!ent->splashDamage || !ent->splashRadius) //this would be cool, though, to "bat" the thermal det away... && ent->s.weapon != WP_NOGHRI_STICK )//gas bomb, don't reflect { //FIXME: take other's owner's FP_SABER_DEFENSE into account here somehow? if ( !other->owner || !other->owner->client || other->owner->client->ps.saberInFlight || (InFront( ent->currentOrigin, other->owner->currentOrigin, other->owner->client->ps.viewangles, SABER_REFLECT_MISSILE_CONE ) && !WP_DoingMoronicForcedAnimationForForcePowers(other)) )//other->owner->s.number != 0 || {//Jedi cannot block shots from behind! int blockChance = 0; switch ( other->owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) {//level 1 reflects 50% of the time, level 2 reflects 75% of the time case FORCE_LEVEL_3: blockChance = 10; break; case FORCE_LEVEL_2: blockChance = 3; break; case FORCE_LEVEL_1: blockChance = 1; break; } if ( blockChance && (other->owner->client->ps.forcePowersActive&(1<owner->client->ps.forcePowerLevel[FP_SPEED]*2; } if ( Q_irand( 0, blockChance ) ) { VectorSubtract(ent->currentOrigin, other->currentOrigin, diff); VectorNormalize(diff); G_ReflectMissile( other, ent, diff); if ( other->owner && other->owner->client ) { other->owner->client->ps.saberEventFlags |= SEF_DEFLECTED; } //do the effect VectorCopy( ent->s.pos.trDelta, diff ); VectorNormalize( diff ); G_MissileReflectEffect( ent, trace->endpos, trace->plane.normal ); return; } } } else {//still do the bounce effect G_MissileReflectEffect( ent, trace->endpos, trace->plane.normal ); } } G_MissileImpacted( ent, other, trace->endpos, trace->plane.normal, hitLoc ); } /* ================ G_ExplodeMissile Explode a missile without an impact ================ */ void G_ExplodeMissile( gentity_t *ent ) { vec3_t dir; vec3_t origin; EvaluateTrajectory( &ent->s.pos, level.time, origin ); SnapVector( origin ); G_SetOrigin( ent, origin ); // we don't have a valid direction, so just point straight up dir[0] = dir[1] = 0; dir[2] = 1; if ( ent->owner )//&& ent->owner->s.number == 0 ) { //Add the event AddSoundEvent( ent->owner, ent->currentOrigin, 256, AEL_DISCOVERED, qfalse, qtrue );//FIXME: are we on ground or not? AddSightEvent( ent->owner, ent->currentOrigin, 512, AEL_DISCOVERED, 100 ); } /* ent->s.eType = ET_GENERAL; G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( dir ) ); ent->freeAfterEvent = qtrue;*/ // splash damage if ( ent->splashDamage ) { G_RadiusDamage( ent->currentOrigin, ent->owner, ent->splashDamage, ent->splashRadius, NULL , ent->splashMethodOfDeath ); } G_FreeEntity(ent); //gi.linkentity( ent ); } void G_RunStuckMissile( gentity_t *ent ) { if ( ent->takedamage ) { if ( ent->s.groundEntityNum >= 0 && ent->s.groundEntityNum < ENTITYNUM_WORLD ) { gentity_t *other = &g_entities[ent->s.groundEntityNum]; if ( (!VectorCompare( vec3_origin, other->s.pos.trDelta ) && other->s.pos.trType != TR_STATIONARY) || (!VectorCompare( vec3_origin, other->s.apos.trDelta ) && other->s.apos.trType != TR_STATIONARY) ) {//thing I stuck to is moving or rotating now, kill me G_Damage( ent, other, other, NULL, NULL, 99999, 0, MOD_CRUSH ); return; } } } // check think function G_RunThink( ent ); } /* ================== G_GroundTrace ================== */ int G_GroundTrace( gentity_t *ent, pml_t *pPml ) { vec3_t point; trace_t trace; point[0] = ent->currentOrigin[0]; point[1] = ent->currentOrigin[1]; point[2] = ent->currentOrigin[2] - 0.25; gi.trace ( &trace, ent->currentOrigin, ent->mins, ent->maxs, point, ent->s.number, ent->clipmask, (EG2_Collision)0, 0 ); pPml->groundTrace = trace; // do something corrective if the trace starts in a solid... if ( trace.allsolid ) { pPml->groundPlane = qfalse; pPml->walking = qfalse; return ENTITYNUM_NONE; } // if the trace didn't hit anything, we are in free fall if ( trace.fraction == 1.0 ) { pPml->groundPlane = qfalse; pPml->walking = qfalse; return ENTITYNUM_NONE; } // check if getting thrown off the ground if ( ent->s.pos.trDelta[2] > 0 && DotProduct( ent->s.pos.trDelta, trace.plane.normal ) > 10 ) { pPml->groundPlane = qfalse; pPml->walking = qfalse; return ENTITYNUM_NONE; } // slopes that are too steep will not be considered onground if ( trace.plane.normal[2] < MIN_WALK_NORMAL ) { pPml->groundPlane = qtrue; pPml->walking = qfalse; return ENTITYNUM_NONE; } pPml->groundPlane = qtrue; pPml->walking = qtrue; /* if ( ent->s.groundEntityNum == ENTITYNUM_NONE ) { // just hit the ground } */ return trace.entityNum; } void G_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) { float backoff; float change; int i; backoff = DotProduct (in, normal); if ( backoff < 0 ) { backoff *= overbounce; } else { backoff /= overbounce; } for ( i=0 ; i<3 ; i++ ) { change = normal[i]*backoff; out[i] = in[i] - change; } } /* ================== G_RollMissile reworking the rolling object code, still needs to stop bobbling up & down, need to get roll angles right, and need to maybe make the transfer of velocity happen on impacts? Also need bounce sound for bounces off a floor. Also need to not bounce as much off of enemies Also gets stuck inside thrower if looking down when thrown ================== */ #define MAX_CLIP_PLANES 5 #define BUMPCLIP 1.5f void G_RollMissile( gentity_t *ent ) { int bumpcount, numbumps; vec3_t dir; float d; int numplanes; vec3_t planes[MAX_CLIP_PLANES]; vec3_t primal_velocity; vec3_t clipVelocity; int i, j, k; trace_t trace; vec3_t end; float time_left; float into; vec3_t endVelocity; vec3_t endClipVelocity; pml_t objPML; float bounceAmt = BUMPCLIP; gentity_t *hitEnt = NULL; memset( &objPML, 0, sizeof( objPML ) ); G_GroundTrace( ent, &objPML ); objPML.frametime = (level.time - level.previousTime)*0.001; numbumps = 4; VectorCopy ( ent->s.pos.trDelta, primal_velocity ); VectorCopy( ent->s.pos.trDelta, endVelocity ); endVelocity[2] -= g_gravity->value * objPML.frametime; ent->s.pos.trDelta[2] = ( ent->s.pos.trDelta[2] + endVelocity[2] ) * 0.5; primal_velocity[2] = endVelocity[2]; if ( objPML.groundPlane ) {//FIXME: never happens! // slide along the ground plane G_ClipVelocity( ent->s.pos.trDelta, objPML.groundTrace.plane.normal, ent->s.pos.trDelta, BUMPCLIP ); VectorScale( ent->s.pos.trDelta, 0.9f, ent->s.pos.trDelta ); } time_left = objPML.frametime; // never turn against the ground plane if ( objPML.groundPlane ) { numplanes = 1; VectorCopy( objPML.groundTrace.plane.normal, planes[0] ); } else { numplanes = 0; } // never turn against original velocity /* VectorNormalize2( ent->s.pos.trDelta, planes[numplanes] ); numplanes++; */ for ( bumpcount = 0; bumpcount < numbumps; bumpcount++ ) { // calculate position we are trying to move to VectorMA( ent->currentOrigin, time_left, ent->s.pos.trDelta, end ); // see if we can make it there gi.trace ( &trace, ent->currentOrigin, ent->mins, ent->maxs, end, ent->s.number, ent->clipmask, G2_RETURNONHIT, 10 ); //TEMP HACK: //had to move this up above the trace.allsolid check now that for some reason ghoul2 impacts tell me I'm allsolid?! //this needs to be fixed, really if ( trace.entityNum < ENTITYNUM_WORLD ) {//hit another ent hitEnt = &g_entities[trace.entityNum]; if ( hitEnt && (hitEnt->takedamage || (hitEnt->contents&CONTENTS_LIGHTSABER) ) ) { G_MissileImpact( ent, &trace ); if ( ent->s.eType == ET_GENERAL ) {//exploded return; } } } if ( trace.allsolid ) { // entity is completely trapped in another solid //FIXME: this happens a lot now when we hit a G2 ent... WTF? ent->s.pos.trDelta[2] = 0; // don't build up falling damage, but allow sideways acceleration return;// qtrue; } if ( trace.fraction > 0 ) { // actually covered some distance VectorCopy( trace.endpos, ent->currentOrigin ); } if ( trace.fraction == 1 ) { break; // moved the entire distance } //pm->ps->pm_flags |= PMF_BUMPED; // save entity for contact //PM_AddTouchEnt( trace.entityNum ); //Hit it /* if ( PM_ClientImpact( trace.entityNum, qtrue ) ) { continue; } */ time_left -= time_left * trace.fraction; if ( numplanes >= MAX_CLIP_PLANES ) { // this shouldn't really happen VectorClear( ent->s.pos.trDelta ); return;// qtrue; } // // if this is the same plane we hit before, nudge velocity // out along it, which fixes some epsilon issues with // non-axial planes // for ( i = 0 ; i < numplanes ; i++ ) { if ( DotProduct( trace.plane.normal, planes[i] ) > 0.99 ) { VectorAdd( trace.plane.normal, ent->s.pos.trDelta, ent->s.pos.trDelta ); break; } } if ( i < numplanes ) { continue; } VectorCopy( trace.plane.normal, planes[numplanes] ); numplanes++; // // modify velocity so it parallels all of the clip planes // if ( &g_entities[trace.entityNum] != NULL && g_entities[trace.entityNum].client ) {//hit a person, bounce off much less bounceAmt = OVERCLIP; } else { bounceAmt = BUMPCLIP; } // find a plane that it enters for ( i = 0 ; i < numplanes ; i++ ) { into = DotProduct( ent->s.pos.trDelta, planes[i] ); if ( into >= 0.1 ) { continue; // move doesn't interact with the plane } // see how hard we are hitting things if ( -into > pml.impactSpeed ) { pml.impactSpeed = -into; } // slide along the plane G_ClipVelocity( ent->s.pos.trDelta, planes[i], clipVelocity, bounceAmt ); // slide along the plane G_ClipVelocity( endVelocity, planes[i], endClipVelocity, bounceAmt ); // see if there is a second plane that the new move enters for ( j = 0 ; j < numplanes ; j++ ) { if ( j == i ) { continue; } if ( DotProduct( clipVelocity, planes[j] ) >= 0.1 ) { continue; // move doesn't interact with the plane } // try clipping the move to the plane G_ClipVelocity( clipVelocity, planes[j], clipVelocity, bounceAmt ); G_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, bounceAmt ); // see if it goes back into the first clip plane if ( DotProduct( clipVelocity, planes[i] ) >= 0 ) { continue; } // slide the original velocity along the crease CrossProduct (planes[i], planes[j], dir); VectorNormalize( dir ); d = DotProduct( dir, ent->s.pos.trDelta ); VectorScale( dir, d, clipVelocity ); CrossProduct (planes[i], planes[j], dir); VectorNormalize( dir ); d = DotProduct( dir, endVelocity ); VectorScale( dir, d, endClipVelocity ); // see if there is a third plane the the new move enters for ( k = 0 ; k < numplanes ; k++ ) { if ( k == i || k == j ) { continue; } if ( DotProduct( clipVelocity, planes[k] ) >= 0.1 ) { continue; // move doesn't interact with the plane } // stop dead at a triple plane interaction VectorClear( ent->s.pos.trDelta ); return;// qtrue; } } // if we have fixed all interactions, try another move VectorCopy( clipVelocity, ent->s.pos.trDelta ); VectorCopy( endClipVelocity, endVelocity ); break; } VectorScale( endVelocity, 0.975f, endVelocity ); } VectorCopy( endVelocity, ent->s.pos.trDelta ); // don't change velocity if in a timer (FIXME: is this correct?) /* if ( pm->ps->pm_time ) { VectorCopy( primal_velocity, ent->s.pos.trDelta ); } */ return;// ( bumpcount != 0 ); } /* ================ G_RunMissile ================ */ void G_MoverTouchPushTriggers( gentity_t *ent, vec3_t oldOrg ); void G_RunMissile( gentity_t *ent ) { vec3_t oldOrg; trace_t tr; int trHitLoc=HL_NONE; if ( (ent->s.eFlags&EF_HELD_BY_SAND_CREATURE) ) {//in a sand creature's mouth if ( ent->activator ) { mdxaBone_t boltMatrix; // Getting the bolt here //in hand vec3_t scAngles = {0}; scAngles[YAW] = ent->activator->currentAngles[YAW]; gi.G2API_GetBoltMatrix( ent->activator->ghoul2, ent->activator->playerModel, ent->activator->gutBolt, &boltMatrix, scAngles, ent->activator->currentOrigin, (cg.time?cg.time:level.time), NULL, ent->activator->s.modelScale ); // Storing ent position, bolt position, and bolt axis gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent->currentOrigin ); G_SetOrigin( ent, ent->currentOrigin ); } // check think function G_RunThink( ent ); return; } VectorCopy( ent->currentOrigin, oldOrg ); // get current position if ( ent->s.pos.trType == TR_INTERPOLATE ) {//rolling missile? //FIXME: WTF?!! Sticks to stick missiles? //FIXME: they stick inside the player G_RollMissile( ent ); if ( ent->s.eType != ET_GENERAL ) {//didn't explode VectorCopy( ent->currentOrigin, ent->s.pos.trBase ); gi.trace( &tr, oldOrg, ent->mins, ent->maxs, ent->currentOrigin, ent->s.number, ent->clipmask, G2_RETURNONHIT, 10 ); if ( VectorCompare( ent->s.pos.trDelta, vec3_origin ) ) { //VectorCopy( ent->currentAngles, ent->s.apos.trBase ); VectorClear( ent->s.apos.trDelta ); } else { vec3_t ang, fwdDir, rtDir; float speed; ent->s.apos.trType = TR_INTERPOLATE; VectorSet( ang, 0, ent->s.apos.trBase[1], 0 ); AngleVectors( ang, fwdDir, rtDir, NULL ); speed = VectorLength( ent->s.pos.trDelta )*4; //HMM, this works along an axis-aligned dir, but not along diagonals //This is because when roll gets to 90, pitch becomes yaw, and vice-versa //Maybe need to just set the angles directly? ent->s.apos.trDelta[0] = DotProduct( fwdDir, ent->s.pos.trDelta ); ent->s.apos.trDelta[1] = 0;//never spin! ent->s.apos.trDelta[2] = DotProduct( rtDir, ent->s.pos.trDelta ); VectorNormalize( ent->s.apos.trDelta ); VectorScale( ent->s.apos.trDelta, speed, ent->s.apos.trDelta ); ent->s.apos.trTime = level.previousTime; } } } else { vec3_t origin; EvaluateTrajectory( &ent->s.pos, level.time, origin ); // trace a line from the previous position to the current position, // ignoring interactions with the missile owner gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin, ent->owner ? ent->owner->s.number : ent->s.number, ent->clipmask, G2_COLLIDE, 10 ); if ( tr.entityNum != ENTITYNUM_NONE ) { gentity_t *other = &g_entities[tr.entityNum]; // check for hitting a lightsaber if ( other->contents & CONTENTS_LIGHTSABER ) {//hit a lightsaber bbox if ( other->owner && other->owner->client && !other->owner->client->ps.saberInFlight && ( Q_irand( 0, (other->owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]*other->owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) ) == 0 || !InFront( ent->currentOrigin, other->owner->currentOrigin, other->owner->client->ps.viewangles, SABER_REFLECT_MISSILE_CONE ) ) )//other->owner->s.number == 0 && {//Jedi cannot block shots from behind! //re-trace from here, ignoring the lightsaber gi.trace( &tr, tr.endpos, ent->mins, ent->maxs, origin, tr.entityNum, ent->clipmask, G2_RETURNONHIT, 10 ); } } } VectorCopy( tr.endpos, ent->currentOrigin ); } // get current angles VectorMA( ent->s.apos.trBase, (level.time - ent->s.apos.trTime) * 0.001, ent->s.apos.trDelta, ent->s.apos.trBase ); //FIXME: Rolling things hitting G2 polys is weird /////////////////////////////////////////////////////// //? if ( tr.fraction != 1 ) { // did we hit or go near a Ghoul2 model? // qboolean hitModel = qfalse; for (int i=0; i < MAX_G2_COLLISIONS; i++) { if (tr.G2CollisionMap[i].mEntityNum == -1) { break; } CCollisionRecord &coll = tr.G2CollisionMap[i]; gentity_t *hitEnt = &g_entities[coll.mEntityNum]; // process collision records here... // make sure we only do this once, not for all the entrance wounds we might generate if ((coll.mFlags & G2_FRONTFACE)/* && !(hitModel)*/ && hitEnt->health) { if (trHitLoc==HL_NONE) { G_GetHitLocFromSurfName( &g_entities[coll.mEntityNum], gi.G2API_GetSurfaceName( &g_entities[coll.mEntityNum].ghoul2[coll.mModelIndex], coll.mSurfaceIndex ), &trHitLoc, coll.mCollisionPosition, NULL, NULL, ent->methodOfDeath ); } break; // NOTE: the way this whole section was working, it would only get inside of this IF once anyway, might as well break out now } } } ///////////////////////////////////////////////////////// if ( tr.startsolid ) { tr.fraction = 0; } gi.linkentity( ent ); if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) ) {//stuck missiles should check some special stuff G_RunStuckMissile( ent ); return; } // check think function G_RunThink( ent ); if ( ent->s.eType != ET_MISSILE ) { return; // exploded } if ( ent->mass ) { G_MoverTouchPushTriggers( ent, oldOrg ); } /* if ( !(ent->s.eFlags & EF_TELEPORT_BIT) ) { G_MoverTouchTeleportTriggers( ent, oldOrg ); if ( ent->s.eFlags & EF_TELEPORT_BIT ) {//was teleported return; } } else { ent->s.eFlags &= ~EF_TELEPORT_BIT; } */ AddSightEvent( ent->owner, ent->currentOrigin, 512, AEL_DISCOVERED, 75 );//wakes them up when see a shot passes in front of them if ( !Q_irand( 0, 10 ) ) {//not so often... if ( ent->splashDamage && ent->splashRadius ) {//I'm an exploder, let people around me know danger is coming if ( ent->s.weapon == WP_TRIP_MINE ) {//??? } else { if ( ent->s.weapon == WP_ROCKET_LAUNCHER && ent->e_ThinkFunc == thinkF_rocketThink ) {//homing rocket- run like hell! AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius, AEL_DANGER_GREAT, 50 ); } else { AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius, AEL_DANGER, 50 ); } AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius, AEL_DANGER ); } } else {//makes them run from near misses AddSightEvent( ent->owner, ent->currentOrigin, 48, AEL_DANGER, 50 ); } } if ( tr.fraction == 1 ) { if ( ent->s.weapon == WP_THERMAL && ent->s.pos.trType == TR_INTERPOLATE ) {//a rolling thermal that didn't hit anything G_MissileAddAlerts( ent ); } return; } // never explode or bounce on sky if ( tr.surfaceFlags & SURF_NOIMPACT ) { G_FreeEntity( ent ); return; } G_MissileImpact( ent, &tr, trHitLoc ); }