mirror of
https://github.com/ioquake/jedi-academy.git
synced 2024-11-28 23:12:04 +00:00
444 lines
14 KiB
C
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 );
|
|
}
|
|
}
|
|
}
|