jedi-academy/codemp/game/g_vehicleTurret.c
2013-04-23 15:21:39 +10:00

444 lines
14 KiB
C

#include "g_headers.h"
#include "bg_vehicles.h"
#include "b_local.h"
#include "../ghoul2/G2.h"
extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
extern void WP_CalcVehMuzzle(gentity_t *ent, int muzzleNum);
extern gentity_t *WP_FireVehicleWeapon( gentity_t *ent, vec3_t start, vec3_t dir, vehWeaponInfo_t *vehWeapon, qboolean alt_fire, qboolean isTurretWeap );
extern void G_VehMuzzleFireFX( gentity_t *ent, gentity_t *broadcaster, int muzzlesFired );
//-----------------------------------------------------
void VEH_TurretCheckFire( Vehicle_t *pVeh,
gentity_t *parent,
//gentity_t *turretEnemy,
turretStats_t *turretStats,
vehWeaponInfo_t *vehWeapon,
int turretNum, int curMuzzle )
{
// if it's time to fire and we have an enemy, then gun 'em down! pushDebounce time controls next fire time
if ( pVeh->m_iMuzzleTag[curMuzzle] == -1 )
{//invalid muzzle?
return;
}
if ( pVeh->m_iMuzzleWait[curMuzzle] >= level.time )
{//can't fire yet
return;
}
if ( pVeh->turretStatus[turretNum].ammo < vehWeapon->iAmmoPerShot )
{//no ammo, can't fire
return;
}
//if ( turretEnemy )
{
//FIXME: check to see if I'm aiming generally where I want to
int nextMuzzle = 0, muzzlesFired = (1<<curMuzzle);
gentity_t *missile;
WP_CalcVehMuzzle( parent, curMuzzle );
//FIXME: some variation in fire dir
missile = WP_FireVehicleWeapon( parent, pVeh->m_vMuzzlePos[curMuzzle], pVeh->m_vMuzzleDir[curMuzzle], vehWeapon, (turretNum!=0), qtrue );
//play the weapon's muzzle effect if we have one
G_VehMuzzleFireFX(parent, missile, muzzlesFired );
//take the ammo away
pVeh->turretStatus[turretNum].ammo -= vehWeapon->iAmmoPerShot;
//toggle to the next muzzle on this turret, if there is one
nextMuzzle = ((curMuzzle+1)==pVeh->m_pVehicleInfo->turret[turretNum].iMuzzle[0])?pVeh->m_pVehicleInfo->turret[turretNum].iMuzzle[1]:pVeh->m_pVehicleInfo->turret[turretNum].iMuzzle[0];
if ( nextMuzzle )
{//a valid muzzle to toggle to
pVeh->turretStatus[turretNum].nextMuzzle = nextMuzzle-1;//-1 because you type muzzles 1-10 in the .veh file
}
//add delay to the next muzzle so it doesn't fire right away on the next frame
pVeh->m_iMuzzleWait[pVeh->turretStatus[turretNum].nextMuzzle] = level.time + turretStats->iDelay;
}
}
void VEH_TurretAnglesToEnemy( Vehicle_t *pVeh, int curMuzzle, float fSpeed, gentity_t *turretEnemy, qboolean bAILead, vec3_t desiredAngles )
{
vec3_t enemyDir, org;
VectorCopy( turretEnemy->r.currentOrigin, org );
if ( bAILead )
{//we want to lead them a bit
vec3_t diff, velocity;
float dist;
VectorSubtract( org, pVeh->m_vMuzzlePos[curMuzzle], diff );
dist = VectorNormalize( diff );
if ( turretEnemy->client )
{
VectorCopy( turretEnemy->client->ps.velocity, velocity );
}
else
{
VectorCopy( turretEnemy->s.pos.trDelta, velocity );
}
VectorMA( org, (dist/fSpeed), velocity, org );
}
//FIXME: this isn't quite right, it's aiming from the muzzle, not the center of the turret...
VectorSubtract( org, pVeh->m_vMuzzlePos[curMuzzle], enemyDir );
//Get the desired absolute, world angles to our target
vectoangles( enemyDir, desiredAngles );
}
//-----------------------------------------------------
qboolean VEH_TurretAim( Vehicle_t *pVeh,
gentity_t *parent,
gentity_t *turretEnemy,
turretStats_t *turretStats,
vehWeaponInfo_t *vehWeapon,
int turretNum, int curMuzzle, vec3_t desiredAngles )
//-----------------------------------------------------
{
vec3_t curAngles, addAngles, newAngles, yawAngles, pitchAngles;
float aimCorrect = qfalse;
WP_CalcVehMuzzle( parent, curMuzzle );
//get the current absolute angles of the turret right now
vectoangles( pVeh->m_vMuzzleDir[curMuzzle], curAngles );
//subtract out the vehicle's angles to get the relative alignment
AnglesSubtract( curAngles, pVeh->m_vOrientation, curAngles );
if ( turretEnemy )
{
aimCorrect = qtrue;
// ...then we'll calculate what new aim adjustments we should attempt to make this frame
// Aim at enemy
VEH_TurretAnglesToEnemy( pVeh, curMuzzle, vehWeapon->fSpeed, turretEnemy, turretStats->bAILead, desiredAngles );
}
//subtract out the vehicle's angles to get the relative desired alignment
AnglesSubtract( desiredAngles, pVeh->m_vOrientation, desiredAngles );
//Now clamp the desired relative angles
//clamp yaw
desiredAngles[YAW] = AngleNormalize180( desiredAngles[YAW] );
if ( pVeh->m_pVehicleInfo->turret[turretNum].yawClampLeft
&& desiredAngles[YAW] > pVeh->m_pVehicleInfo->turret[turretNum].yawClampLeft )
{
aimCorrect = qfalse;
desiredAngles[YAW] = pVeh->m_pVehicleInfo->turret[turretNum].yawClampLeft;
}
if ( pVeh->m_pVehicleInfo->turret[turretNum].yawClampRight
&& desiredAngles[YAW] < pVeh->m_pVehicleInfo->turret[turretNum].yawClampRight )
{
aimCorrect = qfalse;
desiredAngles[YAW] = pVeh->m_pVehicleInfo->turret[turretNum].yawClampRight;
}
//clamp pitch
desiredAngles[PITCH] = AngleNormalize180( desiredAngles[PITCH] );
if ( pVeh->m_pVehicleInfo->turret[turretNum].pitchClampDown
&& desiredAngles[PITCH] > pVeh->m_pVehicleInfo->turret[turretNum].pitchClampDown )
{
aimCorrect = qfalse;
desiredAngles[PITCH] = pVeh->m_pVehicleInfo->turret[turretNum].pitchClampDown;
}
if ( pVeh->m_pVehicleInfo->turret[turretNum].pitchClampUp
&& desiredAngles[PITCH] < pVeh->m_pVehicleInfo->turret[turretNum].pitchClampUp )
{
aimCorrect = qfalse;
desiredAngles[PITCH] = pVeh->m_pVehicleInfo->turret[turretNum].pitchClampUp;
}
//Now get the offset we want from our current relative angles
AnglesSubtract( desiredAngles, curAngles, addAngles );
//Now cap the addAngles for our fTurnSpeed
if ( addAngles[PITCH] > turretStats->fTurnSpeed )
{
//aimCorrect = qfalse;//???
addAngles[PITCH] = turretStats->fTurnSpeed;
}
else if ( addAngles[PITCH] < -turretStats->fTurnSpeed )
{
//aimCorrect = qfalse;//???
addAngles[PITCH] = -turretStats->fTurnSpeed;
}
if ( addAngles[YAW] > turretStats->fTurnSpeed )
{
//aimCorrect = qfalse;//???
addAngles[YAW] = turretStats->fTurnSpeed;
}
else if ( addAngles[YAW] < -turretStats->fTurnSpeed )
{
//aimCorrect = qfalse;//???
addAngles[YAW] = -turretStats->fTurnSpeed;
}
//Now add the additional angles back in to our current relative angles
//FIXME: add some AI aim error randomness...?
newAngles[PITCH] = AngleNormalize180( curAngles[PITCH]+addAngles[PITCH] );
newAngles[YAW] = AngleNormalize180( curAngles[YAW]+addAngles[YAW] );
//Now set the bone angles to the new angles
//set yaw
if ( turretStats->yawBone )
{
VectorClear( yawAngles );
yawAngles[turretStats->yawAxis] = newAngles[YAW];
NPC_SetBoneAngles( parent, turretStats->yawBone, yawAngles );
}
//set pitch
if ( turretStats->pitchBone )
{
VectorClear( pitchAngles );
pitchAngles[turretStats->pitchAxis] = newAngles[PITCH];
NPC_SetBoneAngles( parent, turretStats->pitchBone, pitchAngles );
}
//force muzzle to recalc next check
pVeh->m_iMuzzleTime[curMuzzle] = 0;
return aimCorrect;
}
//-----------------------------------------------------
static qboolean VEH_TurretFindEnemies( Vehicle_t *pVeh,
gentity_t *parent,
turretStats_t *turretStats,
int turretNum, int curMuzzle )
//-----------------------------------------------------
{
qboolean found = qfalse;
int i, count;
float bestDist = turretStats->fAIRange * turretStats->fAIRange;
float enemyDist;
vec3_t enemyDir, org, org2;
qboolean foundClient = qfalse;
gentity_t *entity_list[MAX_GENTITIES], *target, *bestTarget = NULL;
WP_CalcVehMuzzle( parent, curMuzzle );
VectorCopy( pVeh->m_vMuzzlePos[curMuzzle], org2 );
count = G_RadiusList( org2, turretStats->fAIRange, parent, qtrue, entity_list );
for ( i = 0; i < count; i++ )
{
trace_t tr;
target = entity_list[i];
if ( target == parent
|| !target->takedamage
|| target->health <= 0
|| ( target->flags & FL_NOTARGET ))
{
continue;
}
if ( !target->client )
{// only attack clients
if ( !(target->flags&FL_BBRUSH)//not a breakable brush
|| !target->takedamage//is a bbrush, but invincible
|| (target->NPC_targetname&&parent->targetname&&Q_stricmp(target->NPC_targetname,parent->targetname)!=0) )//not in invicible bbrush, but can only be broken by an NPC that is not me
{
if ( target->s.weapon == WP_TURRET
&& target->classname
&& Q_strncmp( "misc_turret", target->classname, 11 ) == 0 )
{//these guys we want to shoot at
}
else
{
continue;
}
}
//else: we will shoot at bbrushes!
}
else if ( target->client->sess.sessionTeam == TEAM_SPECTATOR )
{
continue;
}
if ( target == ((gentity_t*)pVeh->m_pPilot)
|| target->r.ownerNum == parent->s.number )
{//don't get angry at my pilot or passengers?
continue;
}
if ( parent->client
&& parent->client->sess.sessionTeam )
{
if ( target->client )
{
if ( target->client->sess.sessionTeam == parent->client->sess.sessionTeam )
{
// A bot/client/NPC we don't want to shoot
continue;
}
}
else if ( target->teamnodmg == parent->client->sess.sessionTeam )
{//some other entity that's allied with us
continue;
}
}
if ( !trap_InPVS( org2, target->r.currentOrigin ))
{
continue;
}
VectorCopy( target->r.currentOrigin, org );
trap_Trace( &tr, org2, NULL, NULL, org, parent->s.number, MASK_SHOT );
if ( tr.entityNum == target->s.number
|| (!tr.allsolid && !tr.startsolid && tr.fraction == 1.0 ) )
{
// Only acquire if have a clear shot, Is it in range and closer than our best?
VectorSubtract( target->r.currentOrigin, org2, enemyDir );
enemyDist = VectorLengthSquared( enemyDir );
if ( enemyDist < bestDist || (target->client && !foundClient))// all things equal, keep current
{
bestTarget = target;
bestDist = enemyDist;
found = qtrue;
if ( target->client )
{//prefer clients over non-clients
foundClient = qtrue;
}
}
}
}
if ( found )
{
pVeh->turretStatus[turretNum].enemyEntNum = bestTarget->s.number;
}
return found;
}
void VEH_TurretObeyPassengerControl( Vehicle_t *pVeh, gentity_t *parent, int turretNum )
{
turretStats_t *turretStats = &pVeh->m_pVehicleInfo->turret[turretNum];
gentity_t *passenger = (gentity_t *)pVeh->m_ppPassengers[turretStats->passengerNum-1];
if ( passenger && passenger->client && passenger->health > 0 )
{//a valid, living passenger client
vehWeaponInfo_t *vehWeapon = &g_vehWeaponInfo[turretStats->iWeapon];
int curMuzzle = pVeh->turretStatus[turretNum].nextMuzzle;
vec3_t aimAngles;
VectorCopy( passenger->client->ps.viewangles, aimAngles );
VEH_TurretAim( pVeh, parent, NULL, turretStats, vehWeapon, turretNum, curMuzzle, aimAngles );
if ( (passenger->client->pers.cmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)) )
{//he's pressing an attack button, so fire!
VEH_TurretCheckFire( pVeh, parent, turretStats, vehWeapon, turretNum, curMuzzle );
}
}
}
void VEH_TurretThink( Vehicle_t *pVeh, gentity_t *parent, int turretNum )
//-----------------------------------------------------
{
qboolean doAim = qfalse;
float enemyDist, rangeSq;
vec3_t enemyDir;
turretStats_t *turretStats = &pVeh->m_pVehicleInfo->turret[turretNum];
vehWeaponInfo_t *vehWeapon = NULL;
gentity_t *turretEnemy = NULL;
int curMuzzle = 0;//?
if ( !turretStats || !turretStats->iAmmoMax )
{//not a valid turret
return;
}
if ( turretStats->passengerNum
&& pVeh->m_iNumPassengers >= turretStats->passengerNum )
{//the passenger that has control of this turret is on the ship
VEH_TurretObeyPassengerControl( pVeh, parent, turretNum );
return;
}
else if ( !turretStats->bAI )//try AI
{//this turret does not think on its own.
return;
}
//okay, so it has AI, but still don't think if there's no pilot!
if ( !pVeh->m_pPilot )
{
return;
}
vehWeapon = &g_vehWeaponInfo[turretStats->iWeapon];
rangeSq = (turretStats->fAIRange*turretStats->fAIRange);
curMuzzle = pVeh->turretStatus[turretNum].nextMuzzle;
if ( pVeh->turretStatus[turretNum].enemyEntNum < ENTITYNUM_WORLD )
{
turretEnemy = &g_entities[pVeh->turretStatus[turretNum].enemyEntNum];
if ( turretEnemy->health < 0
|| !turretEnemy->inuse
|| turretEnemy == ((gentity_t*)pVeh->m_pPilot)//enemy became my pilot///?
|| turretEnemy == parent
|| turretEnemy->r.ownerNum == parent->s.number // a passenger?
|| ( turretEnemy->client && turretEnemy->client->sess.sessionTeam == TEAM_SPECTATOR ) )
{//don't keep going after spectators, pilot, self, dead people, etc.
turretEnemy = NULL;
pVeh->turretStatus[turretNum].enemyEntNum = ENTITYNUM_NONE;
}
}
if ( pVeh->turretStatus[turretNum].enemyHoldTime < level.time )
{
if ( VEH_TurretFindEnemies( pVeh, parent, turretStats, turretNum, curMuzzle ) )
{
turretEnemy = &g_entities[pVeh->turretStatus[turretNum].enemyEntNum];
doAim = qtrue;
}
else if ( parent->enemy && parent->enemy->s.number < ENTITYNUM_WORLD )
{
if ( g_gametype.integer < GT_TEAM
|| !OnSameTeam( parent->enemy, parent ) )
{//either not in a team game or the enemy isn't on the same team
turretEnemy = parent->enemy;
doAim = qtrue;
}
}
if ( turretEnemy )
{//found one
if ( turretEnemy->client )
{//hold on to clients for a min of 3 seconds
pVeh->turretStatus[turretNum].enemyHoldTime = level.time + 3000;
}
else
{//hold less
pVeh->turretStatus[turretNum].enemyHoldTime = level.time + 500;
}
}
}
if ( turretEnemy != NULL )
{
if ( turretEnemy->health > 0 )
{
// enemy is alive
WP_CalcVehMuzzle( parent, curMuzzle );
VectorSubtract( turretEnemy->r.currentOrigin, pVeh->m_vMuzzlePos[curMuzzle], enemyDir );
enemyDist = VectorLengthSquared( enemyDir );
if ( enemyDist < rangeSq )
{
// was in valid radius
if ( trap_InPVS( pVeh->m_vMuzzlePos[curMuzzle], turretEnemy->r.currentOrigin ) )
{
// Every now and again, check to see if we can even trace to the enemy
trace_t tr;
vec3_t start, end;
VectorCopy( pVeh->m_vMuzzlePos[curMuzzle], start );
VectorCopy( turretEnemy->r.currentOrigin, end );
trap_Trace( &tr, start, NULL, NULL, end, parent->s.number, MASK_SHOT );
if ( tr.entityNum == turretEnemy->s.number
|| (!tr.allsolid && !tr.startsolid ) )
{
doAim = qtrue; // Can see our enemy
}
}
}
}
}
if ( doAim )
{
vec3_t aimAngles;
if ( VEH_TurretAim( pVeh, parent, turretEnemy, turretStats, vehWeapon, turretNum, curMuzzle, aimAngles ) )
{
VEH_TurretCheckFire( pVeh, parent, /*turretEnemy,*/ turretStats, vehWeapon, turretNum, curMuzzle );
}
}
}