#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<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 ); } } }