/*============================================================================= Name : GenericInterceptor.c Purpose : Specifics for the Generic Interceptor Created 6/30/1997 by gshaw Copyright Relic Entertainment, Inc. All rights reserved. =============================================================================*/ #include #include #include #include "types.h" #include "fastmath.h" #include "debug.h" #include "objtypes.h" #include "spaceobj.h" #include "collision.h" #include "physics.h" #include "universe.h" #include "GenericInterceptor.h" #include "statscript.h" #include "gun.h" #include "aiship.h" #include "aitrack.h" #include "mex.h" #include "soundevent.h" #include "flightman.h" #include "commandlayer.h" #include "univupdate.h" #include "tactics.h" #include "nis.h" #include "madlinkin.h" #include "madlinkindefs.h" #include "DefenseFighter.h" #include "randy.h" #include "battle.h" #ifdef gshaw //#define DEBUG_AIATTACK #endif #define FAKE_FLY_BY_DISTANCE_MUCH_BIGGER_THAN_NEEDED 15000.0f sdword FIGHTER_BREAK_ANGLE_MIN = 25; sdword FIGHTER_BREAK_ANGLE_MAX = 45; sdword FIGHTER_BREAK_VERTICAL_ANGLE_MIN = 10; sdword FIGHTER_BREAK_VERTICAL_ANGLE_MAX = 30; bool BombersUseBombingRun = FALSE; /* typedef struct { real32 firetime; real32 maxFlyAwayDist[NUM_TACTICS_TYPES][NUM_CLASSES+1]; real32 maxFlyAwayDistSquared[NUM_TACTICS_TYPES][NUM_CLASSES+1]; // use NUM_CLASSES entry as DefaultClass (so array len = NUM_CLASSES+1) real32 breakRange[NUM_TACTICS_TYPES][NUM_CLASSES+1]; real32 flyPastDist[NUM_TACTICS_TYPES][NUM_CLASSES+1]; real32 triggerHappy[NUM_TACTICS_TYPES][NUM_CLASSES+1]; real32 faceTargetAccuracy[NUM_TACTICS_TYPES][NUM_CLASSES+1]; real32 maxAttackTime[NUM_TACTICS_TYPES][NUM_CLASSES+1]; FlightManProb flightmanProb[NUM_TACTICS_TYPES][NUM_CLASSES+1]; } GenericInterceptorStatics; */ typedef struct //inherited Structure for Cloaked Fighter { real32 firetime; real32 maxFlyAwayDist[NUM_TACTICS_TYPES][NUM_CLASSES+1]; real32 maxFlyAwayDistSquared[NUM_TACTICS_TYPES][NUM_CLASSES+1]; // use NUM_CLASSES entry as DefaultClass (so array len = NUM_CLASSES+1) real32 breakRange[NUM_TACTICS_TYPES][NUM_CLASSES+1]; real32 flyPastDist[NUM_TACTICS_TYPES][NUM_CLASSES+1]; real32 triggerHappy[NUM_TACTICS_TYPES][NUM_CLASSES+1]; real32 faceTargetAccuracy[NUM_TACTICS_TYPES][NUM_CLASSES+1]; real32 maxAttackTime[NUM_TACTICS_TYPES][NUM_CLASSES+1]; FlightManProb flightmanProb[NUM_TACTICS_TYPES][NUM_CLASSES+1]; real32 CloakFuelBurnRate; real32 ForceDeCloakAtFuelPercent; real32 DeCloakFuelLevel; real32 CloakingTime; real32 DeCloakingTime; real32 VisibleState; real32 battleReCloakTime; } CloakedFighterStatics; GenericInterceptorStatics GenericInterceptorStatic; CloakedFighterStatics CloakedFighterStatic; GenericInterceptorStatics LightInterceptorStaticRace1; GenericInterceptorStatics LightInterceptorStaticRace2; GenericInterceptorStatics HeavyInterceptorStaticRace1; GenericInterceptorStatics HeavyInterceptorStaticRace2; GenericInterceptorStatics AttackBomberStaticRace1; GenericInterceptorStatics AttackBomberStaticRace2; CloakedFighterStatics CloakedFighterStaticRace1; CloakedFighterStatics CloakedFighterStaticRace2; GenericInterceptorStatics P1FighterStatic; GenericInterceptorStatics P2SwarmerStatic; GenericInterceptorStatics P2AdvanceSwarmerStatic; GenericInterceptorStatics TargetDroneStatic; scriptStructEntry LIStaticScriptTable[] = { { "gunsFireTime", scriptSetReal32CB, (udword) &(GenericInterceptorStatic.firetime), (udword) &(GenericInterceptorStatic) }, { "flightmanTurnaround", (void(*)(char *,char *,void*)) scriptSetFlightManTurnaroundCB, (udword) &(GenericInterceptorStatic.flightmanProb), (udword) &(GenericInterceptorStatic) }, { "flightmanAIP", (void(*)(char *,char *,void*))scriptSetFlightManAIPCB, (udword) &(GenericInterceptorStatic.flightmanProb), (udword) &(GenericInterceptorStatic) }, { "flightmanEvasiveBehind", (void(*)(char *,char *,void*))scriptSetFlightManEvasiveBehindCB, (udword) &(GenericInterceptorStatic.flightmanProb), (udword) &(GenericInterceptorStatic) }, { "flightmanEvasiveFront", (void(*)(char *,char *,void*))scriptSetFlightManEvasiveFrontCB, (udword) &(GenericInterceptorStatic.flightmanProb), (udword) &(GenericInterceptorStatic) }, { "flightmanEvasivePure", (void(*)(char *,char *,void*))scriptSetFlightManEvasivePureCB, (udword) &(GenericInterceptorStatic.flightmanProb), (udword) &(GenericInterceptorStatic) }, { "maxFlyAwayDist", scriptSetReal32CB_ARRAY, (udword) &(GenericInterceptorStatic.maxFlyAwayDist), (udword) &(GenericInterceptorStatic) }, { "breakRange", scriptSetReal32CB_ARRAY, (udword) &(GenericInterceptorStatic.breakRange), (udword) &(GenericInterceptorStatic) }, { "flyPastDist", scriptSetReal32CB_ARRAY, (udword) &(GenericInterceptorStatic.flyPastDist), (udword) &(GenericInterceptorStatic) }, { "triggerHappy", scriptSetCosAngCB_ARRAY, (udword) &(GenericInterceptorStatic.triggerHappy), (udword) &(GenericInterceptorStatic) }, { "faceTargetAccuracy", scriptSetCosAngCB_ARRAY, (udword) &(GenericInterceptorStatic.faceTargetAccuracy),(udword) &(GenericInterceptorStatic) }, { "maxAttackTime", scriptSetReal32CB_ARRAY, (udword) &(GenericInterceptorStatic.maxAttackTime), (udword) &(GenericInterceptorStatic) }, { NULL,NULL,0,0 } }; scriptStructEntry CloakedFighterStaticScriptTable[] = { { "CloakFuelBurnRate", scriptSetReal32CB, (udword) &(CloakedFighterStatic.CloakFuelBurnRate), (udword) &(CloakedFighterStatic) }, { "ForceDeCloakAtFuelPercent", scriptSetReal32CB, (udword) &(CloakedFighterStatic.ForceDeCloakAtFuelPercent), (udword) &(CloakedFighterStatic) }, { "CloakingTime", scriptSetReal32CB, (udword) &(CloakedFighterStatic.CloakingTime), (udword) &(CloakedFighterStatic) }, { "DeCloakingTime", scriptSetReal32CB, (udword) &(CloakedFighterStatic.DeCloakingTime), (udword) &(CloakedFighterStatic) }, { "VisibleState", scriptSetReal32CB, (udword) &(CloakedFighterStatic.VisibleState), (udword) &(CloakedFighterStatic) }, { "battleReCloakTime", scriptSetReal32CB, (udword) &(CloakedFighterStatic.battleReCloakTime), (udword) &(CloakedFighterStatic) }, { NULL,NULL,0,0 } }; void GenericInterceptorStaticInit(char *directory,char *filename,struct ShipStaticInfo *statinfo) { GenericInterceptorStatics *interceptorstat = NULL; udword i; TacticsType t; switch (statinfo->shiptype) { case LightInterceptor: interceptorstat = (statinfo->shiprace == R1) ? &LightInterceptorStaticRace1 : &LightInterceptorStaticRace2; break; case HeavyInterceptor: interceptorstat = (statinfo->shiprace == R1) ? &HeavyInterceptorStaticRace1 : &HeavyInterceptorStaticRace2; break; case AttackBomber: interceptorstat = (statinfo->shiprace == R1) ? &AttackBomberStaticRace1 : &AttackBomberStaticRace2; break; case CloakedFighter: interceptorstat = (statinfo->shiprace == R1) ? (GenericInterceptorStatics *)&CloakedFighterStaticRace1 : (GenericInterceptorStatics *)&CloakedFighterStaticRace2; break; case P1Fighter: interceptorstat = &P1FighterStatic; break; case P2Swarmer: interceptorstat = &P2SwarmerStatic; break; case P2AdvanceSwarmer: interceptorstat = &P2AdvanceSwarmerStatic; break; case TargetDrone: interceptorstat = &TargetDroneStatic; break; } dbgAssert(interceptorstat != NULL); statinfo->custstatinfo = interceptorstat; memset(interceptorstat,0,sizeof(*interceptorstat)); scriptSetStruct(directory,filename,LIStaticScriptTable,(ubyte *)interceptorstat); for(t = Evasive; t < NUM_TACTICS_TYPES; t++) for (i = 0; i < NUM_CLASSES + 1; i++) { interceptorstat->maxFlyAwayDistSquared[t][i] = interceptorstat->maxFlyAwayDist[t][i] * interceptorstat->maxFlyAwayDist[t][i]; } for(t = Evasive; t < NUM_TACTICS_TYPES; t++) for (i = 0; i < NUM_CLASSES; i++) { if (interceptorstat->flyPastDist[t][i] == 0.0f) { interceptorstat->maxFlyAwayDist[t][i] = interceptorstat->maxFlyAwayDist[t][NUM_CLASSES]; interceptorstat->breakRange[t][i] = interceptorstat->breakRange[t][NUM_CLASSES]; interceptorstat->flyPastDist[t][i] = interceptorstat->flyPastDist[t][NUM_CLASSES]; interceptorstat->triggerHappy[t][i] = interceptorstat->triggerHappy[t][NUM_CLASSES]; interceptorstat->faceTargetAccuracy[t][i] = interceptorstat->faceTargetAccuracy[t][NUM_CLASSES]; interceptorstat->maxAttackTime[t][i] = interceptorstat->maxAttackTime[t][NUM_CLASSES]; interceptorstat->flightmanProb[t][i] = interceptorstat->flightmanProb[t][NUM_CLASSES]; } } if (statinfo->shiptype == TargetDrone) for (t = Evasive; t < NUM_TACTICS_TYPES; t++) { statinfo->bulletRange[t] = 2500.0f; statinfo->bulletRangeSquared[t] = 2500.0f * 2500.0f; statinfo->minBulletRange[t] = 2500.0f; } } void GenericInterceptorInit(Ship *ship) { GenericInterceptorSpec *spec = (GenericInterceptorSpec *)ship->ShipSpecifics; spec->activeGun = 0; } bool GenericInterceptorCanFire(Ship *ship,SpaceObjRotImpTarg *target,vector *trajectory,real32 triggerHappy) { GenericInterceptorSpec *spec = (GenericInterceptorSpec *)ship->ShipSpecifics; GenericInterceptorStatics *interceptorstat = (GenericInterceptorStatics *)(((ShipStaticInfo *)ship->staticinfo)->custstatinfo); Gun *curgun; Gun *gun; vector shipheading; real32 dotprod; GunInfo *gunInfo = ship->gunInfo; sdword i; bool returnval; bool canUseGun; sdword pickgun; if (ship->shiptype == TargetDrone) { //target drones attack like interceptors, but don't have guns return FALSE; } if(ship->aistateattack == STATE_WHIPSTRAFE1) { triggerHappy = 0.0f; } if (bitTest(ship->flags, SOF_NISShip)) { triggerHappy = NIS_TriggerHappyAngle; } if(ship->staticinfo->madStatic != NULL) { //ship has meshanimations if(ship->staticinfo->madStatic->numGunOpenIndexes > 0) { //has gun opening animations if(ship->madGunStatus != MAD_STATUS_GUNS_OPEN && ship->madGunStatus != MAD_STATUS_GUNS_OPENING) { madOpenGunsShip(ship); return FALSE; } else if(ship->madGunStatus != MAD_STATUS_GUNS_OPEN) { return FALSE; } //otherwise, we're open..try to fire away! } } if ((universe.totaltimeelapsed - spec->lasttimefired) < interceptorstat->firetime) { return FALSE; // if time since last shot fired is less than the gun firetime, don't shoot. } for (i=0,canUseGun=FALSE,pickgun=spec->activeGun;inumGuns;i++) { if (gunCanShoot(ship, &gunInfo->guns[pickgun])) { canUseGun = TRUE; break; } pickgun++; if (pickgun>=gunInfo->numGuns) { pickgun = 0; } } if (canUseGun) { spec->activeGun = pickgun; dbgAssert(spec->activeGun < gunInfo->numGuns); gun = &gunInfo->guns[spec->activeGun]; switch (gun->gunstatic->guntype) { case GUN_Fixed: matGetVectFromMatrixCol3(shipheading,ship->rotinfo.coordsys); dotprod = vecDotProduct(*trajectory,shipheading); if (dotprod >= triggerHappy) { return TRUE; } else { return FALSE; } case GUN_Gimble: if (gunOrientGimbleGun(ship,gun,target)) { return TRUE; } else { return FALSE; } case GUN_NewGimble: // orient all other gimble guns as well for (i=0,curgun=&gunInfo->guns[0];inumGuns;i++,curgun++) { if (i == spec->activeGun) { dbgAssert(curgun == gun); returnval = gunOrientGimbleGun(ship,curgun,target); } else { gunOrientGimbleGun(ship,curgun,target); } } return returnval; default: dbgAssert(FALSE); return FALSE; } } else { return FALSE; } } void GenericInterceptorFire(Ship *ship,SpaceObjRotImpTarg *target) { GenericInterceptorSpec *spec = (GenericInterceptorSpec *)ship->ShipSpecifics; GunInfo *gunInfo = ship->gunInfo; spec->lasttimefired = universe.totaltimeelapsed; dbgAssert(spec->activeGun < gunInfo->numGuns); gunShoot(ship,&gunInfo->guns[spec->activeGun],target); spec->activeGun++; if (spec->activeGun >= gunInfo->numGuns) { spec->activeGun = 0; } } void setupWhipStrafe(Ship *ship,GenericInterceptorSpec *spec,Ship *target) { ShipStaticInfo *shipstaticinfo = (ShipStaticInfo *)ship->staticinfo; vector targetshipvec; vector tmpvec2; real32 zsqr,rsqr; real32 temp; uword whipstrafeflags; targetshipvec = target->posinfo.position; temp = target->staticinfo->staticheader.staticCollInfo.approxcollspheresize + shipstaticinfo->bulletRange[ship->tacticstype] * WHIPSTRAFE_BULLETRANGEOPTIMUM; if (ship->posinfo.position.z > targetshipvec.z) { whipstrafeflags = WHIPSTRAFE_ABOVE; targetshipvec.z += temp; } else { whipstrafeflags = 0; targetshipvec.z -= temp; } vecSub(tmpvec2,targetshipvec,ship->posinfo.position); zsqr = tmpvec2.z * tmpvec2.z; rsqr = tmpvec2.x * tmpvec2.x + tmpvec2.y * tmpvec2.y; if (zsqr <= rsqr*WHIPSTRAFE_MAXANGCOMPAREFACTOR) { // angle is appropriatley low, so continue vecMultiplyByScalar(tmpvec2,WHIPSTRAFE_FLYBYOVERSHOOT); vecAdd(spec->aivec,ship->posinfo.position,tmpvec2); spec->aiAIPflightman = TRUE; flightmanInit(ship,FLIGHTMAN_WHIP_STRAFE); dbgAssert(ship->flightman == FLIGHTMAN_WHIP_STRAFE); ship->flightmanState2 = (ubyte) whipstrafeflags; ship->aistateattack = STATE_WHIPSTRAFE1; spec->aitimekill = universe.totaltimeelapsed; } } #define TransitionToSTATE_REAPPROACH \ ship->aistateattack = STATE_REAPPROACH; \ spec->aiAIPflightman = FALSE; \ ship->aistate = 0; \ \ if ((ship->flightman == FLIGHTMAN_NULL) && (target->objtype == OBJ_ShipType) && (isCapitalShip((Ship *)target)) && (((ShipStaticInfo *)(target->staticinfo))->shipclass != CLASS_Mothership)) \ { \ if (flightmanTestRandom(&interceptorstat->flightmanProb[ship->tacticstype][targetIndex],FLIGHTMAN_AIP,FLIGHTMAN_WHIP_STRAFE)) \ { \ setupWhipStrafe(ship,spec,(Ship *)target); \ } \ } void GenericInterceptorPassiveAttack(Ship *ship,Ship *target,bool rotate) { ShipStaticInfo *shipstaticinfo = (ShipStaticInfo *)ship->staticinfo; GenericInterceptorStatics *interceptorstat = (GenericInterceptorStatics *)shipstaticinfo->custstatinfo; uword targetIndex = (uword)((ShipStaticInfo *)target->staticinfo)->shipclass; vector trajectory; real32 range; real32 dist,temp; //Commented out for now...was causing crashwith CloakedFighter //dbgAssert(shipstaticinfo->custshipheader.CustShipFire == GenericInterceptorFire); if (shipstaticinfo->gunStaticInfo) aishipGetTrajectoryWithVelPrediction(ship,(SpaceObjRotImpTarg *)target,shipstaticinfo->gunStaticInfo->gunstatics[0].bulletspeed,&trajectory); else aishipGetTrajectory(ship,(SpaceObjRotImpTarg *)target,&trajectory); dist = fsqrt(vecMagnitudeSquared(trajectory)); vecDivideByScalar(trajectory,dist,temp); range = RangeToTargetGivenDist(ship,(SpaceObjRotImpTarg *)target,dist); if ((rotate) & ((bool)shipstaticinfo->rotateToRetaliate)) { aitrackHeading(ship,&trajectory,FLYSHIP_ATTACKACCURACY); } if (range < shipstaticinfo->bulletRange[ship->tacticstype]) { if (GenericInterceptorCanFire(ship,(SpaceObjRotImpTarg *)target,&trajectory,interceptorstat->triggerHappy[ship->tacticstype][targetIndex])) { //GenericInterceptorFire(ship); shipstaticinfo->custshipheader.CustShipFire(ship,(SpaceObjRotImpTarg *)target); } } /* //check to see if we should back up! if(ship->tacticstype == Evasive) { command = getShipAndItsCommand(&universe.mainCommandLayer,ship); if(command != NULL) { if(command->ordertype.order == COMMAND_NULL) { //command group is doint nothing but passive attacking if(command->ordertype.attributes & COMMAND_IS_FORMATION) { if(command->selection->ShipPtr[0] != ship) return; //not leader..so return! so we only move leader } if(command->ordertype.attributes & COMMAND_IS_HOLDINGPATTERN || command->ordertype.attributes & COMMAND_IS_PROTECTING) { return; //if doing either of these things...we don't want to back up } } else { return; //if doing something else, we have to return } } vecScalarMultiply(trajectory,trajectory,-5000.0f); aishipFlyToPointAvoidingObjs(ship,&trajectory,AISHIP_FastAsPossible,0.0f); } */ } // works with DefenseFighter as well, even though it is not a generic interceptor FlightManProb *GenericInterceptorGetFlightManProb(Ship *ship,SpaceObjRotImpTarg *target) { ShipStaticInfo *shipstaticinfo = (ShipStaticInfo *)ship->staticinfo; uword targetIndex; if ((target != NULL) && (target->objtype == OBJ_ShipType)) { targetIndex = (uword)((ShipStaticInfo *)target->staticinfo)->shipclass; } else { targetIndex = (uword)NUM_CLASSES; } if (ship->shiptype == DefenseFighter) return &((DefenseFighterStatics *)shipstaticinfo->custstatinfo)->flightmanProb[ship->tacticstype][targetIndex]; else return &((GenericInterceptorStatics *)shipstaticinfo->custstatinfo)->flightmanProb[ship->tacticstype][targetIndex]; } void CalcRallyPoint(vector s, vector t, AttackBomberSpec *diveinfo) { s.z = t.z; vecSub(diveinfo->rallypoint, s, t); vecNormalize(&diveinfo->rallypoint); vecMultiplyByScalar(diveinfo->rallypoint, 700.0f); if (t.z < 0.0f) { diveinfo->rallypoint.z += 1000.0f; } else { diveinfo->rallypoint.z -= 1000.0f; } diveinfo->angle = -1.0f; } void GenericInterceptorAttack(Ship *ship,SpaceObjRotImpTarg *target,real32 maxdist) { GenericInterceptorSpec *spec = (GenericInterceptorSpec *)ship->ShipSpecifics; vector trajectory; //real32 tempreal; real32 dist; real32 range; real32 temp; ShipStaticInfo *shipstaticinfo = (ShipStaticInfo *)ship->staticinfo; GenericInterceptorStatics *interceptorstat = (GenericInterceptorStatics *)shipstaticinfo->custstatinfo; uword targetIndex; udword flighttodo; vector tmpvec; matrix tmpmat; sdword randeg; real32 randegf; udword flags; sdword curdistonly; vector rallypoint,dest; AttackBomberSpec *diveinfo; real32 sintheta, costheta; TypeOfFormation formtype = NO_FORMATION; if (target->objtype == OBJ_ShipType) { targetIndex = (uword)((ShipStaticInfo *)target->staticinfo)->shipclass; } else { targetIndex = (uword)NUM_CLASSES; } switch (ship->aistateattack) { case STATE_INIT: spec->aiAIPflightman = FALSE; if((ship->shiptype != AttackBomber) || (!BombersUseBombingRun) || (targetIndex > CLASS_Frigate) || ((target->objtype == OBJ_ShipType) && (((Ship *)target)->shiptype == Mothership) && (((Ship *)target)->shiprace == R1))) { ship->aistateattack = STATE_APPROACH; aishipGetTrajectory(ship,target,&trajectory); aishipFlyToShipAvoidingObjs(ship,target,AISHIP_PointInDirectionFlying + AISHIP_CarTurn,0.0f); range = RangeToTarget(ship,target,&trajectory); if (range < shipstaticinfo->bulletRange[ship->tacticstype]) { ship->aistateattack = STATE_KILL; spec->aitimekill = universe.totaltimeelapsed; } break; } ship->aistateattack = STATE_START_BOMBING_RUN; CalcRallyPoint(ship->posinfo.position, target->posinfo.position, (AttackBomberSpec *)ship->ShipSpecifics); // fall right into bombing run code case STATE_START_BOMBING_RUN: diveinfo = (AttackBomberSpec *)ship->ShipSpecifics; if(diveinfo->angle == -1.0f) { memcpy(&diveinfo->orig_point, &diveinfo->rallypoint, sizeof(vector)); diveinfo->angle = DEG_TO_RAD(45); sintheta = sin(diveinfo->angle); costheta = cos(diveinfo->angle); diveinfo->rallypoint.x = (diveinfo->orig_point.x * costheta) + (diveinfo->orig_point.y * sintheta); // rotate rallypoint X degrees clockwise diveinfo->rallypoint.y = (diveinfo->orig_point.y * costheta) - (diveinfo->orig_point.x * sintheta); } vecAdd(rallypoint, target->posinfo.position, diveinfo->rallypoint); vecSub(tmpvec, ship->posinfo.position, rallypoint); if(vecMagnitudeSquared(tmpvec) < 5625) // within 75 metres { if((gamerand() & 15) == 0) // 1 in 16 chance of starting bombing dive { ship->aistateattack = STATE_BOMBING_DIVE; diveinfo->rallypoint.x = -(diveinfo->rallypoint.x); diveinfo->rallypoint.y = -(diveinfo->rallypoint.y); diveinfo->rallypoint.z = -200.0f; diveinfo->bombingdelay = 15; } else { diveinfo->angle += DEG_TO_RAD(10); if(diveinfo->angle >= DEG_TO_RAD(360)) diveinfo->angle -= DEG_TO_RAD(360); sintheta = sin(diveinfo->angle); costheta = cos(diveinfo->angle); diveinfo->rallypoint.x = (diveinfo->orig_point.x * costheta) + (diveinfo->orig_point.y * sintheta); // rotate rallypoint X degrees clockwise diveinfo->rallypoint.y = (diveinfo->orig_point.y * costheta) - (diveinfo->orig_point.x * sintheta); } } else aishipFlyToPointAvoidingObjsWithVel(ship, &rallypoint, AISHIP_PointInDirectionFlying | AISHIP_DontFlyToObscuredPoints | AISHIP_FastAsPossible, -400.0f, &target->posinfo.velocity); break; case STATE_BOMBING_DIVE: diveinfo = (AttackBomberSpec *)ship->ShipSpecifics; vecAdd(rallypoint, target->posinfo.position, diveinfo->rallypoint); vecSub(tmpvec, ship->posinfo.position, rallypoint); if(vecMagnitudeSquared(tmpvec) < 5625) // within 75 metres ship->aistateattack = STATE_INIT; else aishipFlyToPointAvoidingObjsWithVel(ship, &rallypoint, AISHIP_PointInDirectionFlying | AISHIP_DontFlyToObscuredPoints | AISHIP_FastAsPossible, -400.0f, &target->posinfo.velocity); if(diveinfo->bombingdelay) diveinfo->bombingdelay--; else if (GenericInterceptorCanFire(ship,target,&trajectory,interceptorstat->triggerHappy[ship->tacticstype][targetIndex])) shipstaticinfo->custshipheader.CustShipFire(ship,target); break; case STATE_APPROACH: #ifdef DEBUG_AIATTACK dbgMessagef("\nShip %x STATE_APPROACH",(udword)ship); #endif aishipGetTrajectory(ship,target,&trajectory); aishipFlyToShipAvoidingObjs(ship,target,AISHIP_PointInDirectionFlying + AISHIP_CarTurn,0.0f); range = RangeToTarget(ship,target,&trajectory); if (range < shipstaticinfo->bulletRange[ship->tacticstype]) { ship->aistateattack = STATE_KILL; spec->aitimekill = universe.totaltimeelapsed; } break; case STATE_TURNAROUND: #ifdef DEBUG_AIATTACK dbgMessagef("\nShip %x STATE_TURNAROUND",(udword)ship); #endif if (flightmanExecute(ship)) { ship->tacticsTalk = 0; //reset var TransitionToSTATE_REAPPROACH } break; case STATE_REAPPROACH: #ifdef DEBUG_AIATTACK dbgMessagef("\nShip %x STATE_REAPPROACH",(udword)ship); #endif aishipGetTrajectory(ship,target,&trajectory); aishipFlyToShipAvoidingObjs(ship,target,AISHIP_PointInDirectionFlying,0.0f); range = RangeToTarget(ship,target,&trajectory); if (range < shipstaticinfo->bulletRange[ship->tacticstype]) { ship->aistateattack = STATE_KILL; spec->aitimekill = universe.totaltimeelapsed; } break; case STATE_WHIPSTRAFE1: #ifdef DEBUG_AIATTACK dbgMessagef("\nShip %x STATE_WHIPSTRAFE1",(udword)ship); #endif // dbgAssert(ship->flightman == FLIGHTMAN_WHIP_STRAFE); if (ship->flightman != FLIGHTMAN_WHIP_STRAFE) { dbgMessagef("\nWarning: Whip strafe cancelled"); ship->aistateattack = STATE_INIT; break; } aishipGetTrajectoryWithAngleCorrection(ship,target,&trajectory); dist = fsqrt(vecMagnitudeSquared(trajectory)); vecDivideByScalar(trajectory,dist,temp); range = RangeToTargetGivenDist(ship,target,dist); if (range < (shipstaticinfo->bulletRange[ship->tacticstype]*0.7f)) { aishipFlyToPointAvoidingObjs(ship,&spec->aivec,AISHIP_FastAsPossible,WHIPSTRAFE_MINVELOCITY); aitrackHeadingWithBank(ship,&trajectory,0.99999f /*interceptorstat->faceTargetAccuracy[targetIndex]*/,shipstaticinfo->sinbank); if (GenericInterceptorCanFire(ship,target,&trajectory,interceptorstat->triggerHappy[ship->tacticstype][targetIndex])) { //GenericInterceptorFire(ship); shipstaticinfo->custshipheader.CustShipFire(ship,target); } } else { aishipFlyToPointAvoidingObjs(ship,&spec->aivec,AISHIP_FastAsPossible | AISHIP_PointInDirectionFlying,WHIPSTRAFE_MINVELOCITY); } if ((MoveReachedDestinationVariable(ship,&spec->aivec,WHIPSTRAFE_REACHDESTINATIONRANGE) || ((universe.totaltimeelapsed - spec->aitimekill) > interceptorstat->maxAttackTime[ship->tacticstype][targetIndex]))) { ship->aistateattack = STATE_WHIPSTRAFE2; } break; case STATE_WHIPSTRAFE2: #ifdef DEBUG_AIATTACK dbgMessagef("\nShip %x STATE_WHIPSTRAFE2",(udword)ship); #endif // dbgAssert(ship->flightman == FLIGHTMAN_WHIP_STRAFE); if (ship->flightman != FLIGHTMAN_WHIP_STRAFE) { dbgMessagef("\nWarning: Whip strafe cancelled"); ship->aistateattack = STATE_INIT; break; } if (flightmanExecute(ship)) { ship->aistateattack = STATE_BREAK; vecSub(tmpvec,ship->posinfo.position,target->posinfo.position); tmpvec.z = 0.0f; vecNormalizeToLength(&tmpvec,interceptorstat->flyPastDist[ship->tacticstype][targetIndex]); randeg = randombetween(FIGHTER_BREAK_ANGLE_MIN,FIGHTER_BREAK_ANGLE_MAX); randegf = (real32) ((randeg & 1) ? randeg : -randeg); randegf = DEG_TO_RAD(randegf); matMakeRotAboutZ(&tmpmat,(real32)cos(randegf),(real32)sin(randegf)); matMultiplyMatByVec(&spec->aivec,&tmpmat,&tmpvec); vecAddTo(spec->aivec,target->posinfo.position); } break; case STATE_KILL: #ifdef DEBUG_AIATTACK dbgMessagef("\nShip %x STATE_KILL",(udword)ship); #endif if (shipstaticinfo->gunStaticInfo) aishipGetTrajectoryWithVelPrediction(ship,target,shipstaticinfo->gunStaticInfo->gunstatics[0].bulletspeed,&trajectory); else aishipGetTrajectory(ship,target,&trajectory); dist = fsqrt(vecMagnitudeSquared(trajectory)); vecDivideByScalar(trajectory,dist,temp); if (vecDotProduct(trajectory,target->posinfo.velocity) < 0) // never track a velocity of ship moving towards us, { // because we don't want to back up or slow down. aishipFlyToShipAvoidingObjs(ship,target,AISHIP_FastAsPossible,-shipstaticinfo->staticheader.maxvelocity); } else { aishipFlyToShipAvoidingObjsWithVel(ship,target,AISHIP_FastAsPossible,-shipstaticinfo->staticheader.maxvelocity,&target->posinfo.velocity); } if (ship->flightman == FLIGHTMAN_BARREL_ROLL || ship->flightman == FLIGHTMAN_SWARMER_BARRELROLL || ship->flightman == FLIGHTMAN_ROLL180) { flags = AITRACKHEADING_DONTROLL; if(flightmanExecute(ship)) ship->tacticsTalk = 0; //reset variable } else { flags = 0; if(ship->flightman == FLIGHTMAN_CELEB_FLIP || ship->flightman == FLIGHTMAN_BARRELROLL_OUT) { flightmanExecute(ship); break; } } aitrackHeadingWithBankFlags(ship,&trajectory,interceptorstat->faceTargetAccuracy[ship->tacticstype][targetIndex],shipstaticinfo->sinbank,flags); range = RangeToTargetGivenDist(ship,target,dist); if (range < shipstaticinfo->bulletRange[ship->tacticstype]) { if (GenericInterceptorCanFire(ship,target,&trajectory,interceptorstat->triggerHappy[ship->tacticstype][targetIndex])) { //GenericInterceptorFire(ship); shipstaticinfo->custshipheader.CustShipFire(ship,target); if ((ship->flightman == FLIGHTMAN_NULL) && (!spec->aiAIPflightman)) { spec->aiAIPflightman = TRUE; // only try to do barrel roll once, don't keep repeating random check if (flightmanTestRandom(&interceptorstat->flightmanProb[ship->tacticstype][targetIndex],FLIGHTMAN_AIP,FLIGHTMAN_BARREL_ROLL)) { bitSet(ship->tacticsTalk,TACTALK_BARRELROLL); flightmanInit(ship,FLIGHTMAN_BARREL_ROLL); } else if (flightmanTestRandom(&interceptorstat->flightmanProb[ship->tacticstype][targetIndex],FLIGHTMAN_AIP,FLIGHTMAN_CELEB_FLIP)) { flightmanInit(ship,FLIGHTMAN_CELEB_FLIP); } else if (flightmanTestRandom(&interceptorstat->flightmanProb[ship->tacticstype][targetIndex],FLIGHTMAN_AIP,FLIGHTMAN_SWARMER_BARRELROLL)) { flightmanInit(ship,FLIGHTMAN_SWARMER_BARRELROLL); } else if (flightmanTestRandom(&interceptorstat->flightmanProb[ship->tacticstype][targetIndex],FLIGHTMAN_AIP,FLIGHTMAN_ROLL180)) { flightmanInit(ship,FLIGHTMAN_ROLL180); } } } if ((range < interceptorstat->breakRange[ship->tacticstype][targetIndex])) // || ((universe.totaltimeelapsed - spec->aitimekill) > interceptorstat->maxAttackTime[ship->tacticstype][targetIndex])) { randeg = randombetween(FIGHTER_BREAK_ANGLE_MIN,FIGHTER_BREAK_ANGLE_MAX); if (flightmanTestRandom(&interceptorstat->flightmanProb[ship->tacticstype][targetIndex],FLIGHTMAN_AIP,FLIGHTMAN_BARRELROLL_OUT)) { ship->aistateattack = STATE_BARRELROLL_OUT; flightmanInit(ship,FLIGHTMAN_BARRELROLL_OUT); randegf = (real32) randeg; // always break left during barrelroll out } else { ship->aistateattack = STATE_BREAK; ship->tacticsTalk = 0; //reset variable if (targetIndex == CLASS_Fighter) randegf = (real32) randeg; // always break left for fighters so they avoid each other better else randegf = (real32) ((randeg & 1) ? randeg : -randeg); } randegf = DEG_TO_RAD(randegf); if ((ship->formationcommand) && (ship->tacticstype != Evasive)) formtype = ship->formationcommand->formation.formationtype; else formtype = NO_FORMATION; if ((target->objtype == OBJ_ShipType) && (((Ship *)target)->shiptype == Mothership) && (((Ship *)target)->shiprace == R1) ) { formtype = NO_FORMATION; // yet another R1 Mothership special consideration } if ( (abs(trajectory.z) >= 0.75) ) { //vecScalarMultiply(tmpvec,trajectory,interceptorstat->flyPastDist[ship->tacticstype][targetIndex]); vecScalarMultiply(tmpvec,trajectory,FAKE_FLY_BY_DISTANCE_MUCH_BIGGER_THAN_NEEDED); matMakeRotAboutY(&tmpmat,(real32)cos(randegf),(real32)sin(randegf)); matMultiplyMatByVec(&spec->aivec,&tmpmat,&tmpvec); } else if ( (formtype == DELTA_FORMATION) || (formtype == BROAD_FORMATION) ) { randeg = randombetween(FIGHTER_BREAK_VERTICAL_ANGLE_MIN,FIGHTER_BREAK_VERTICAL_ANGLE_MAX); randegf = (real32) randeg; randegf = DEG_TO_RAD(randegf); tmpvec.x = trajectory.x; tmpvec.y = trajectory.y; tmpvec.z = 0.0f; //vecNormalizeToLength(&tmpvec,interceptorstat->flyPastDist[ship->tacticstype][targetIndex]); vecNormalizeToLength(&tmpvec,FAKE_FLY_BY_DISTANCE_MUCH_BIGGER_THAN_NEEDED); if (abs(trajectory.x) > abs(trajectory.y)) { matMakeRotAboutY(&tmpmat,(real32)cos(randegf),(real32)sin(randegf)); } else { matMakeRotAboutX(&tmpmat,(real32)cos(randegf),(real32)sin(randegf)); } matMultiplyMatByVec(&spec->aivec,&tmpmat,&tmpvec); if (trajectory.z > 0.0f) { // attacking from below, so stay below if ((spec->aivec.z) > 0.0f) spec->aivec.z = -spec->aivec.z; } else { // attacking from above, so stay above if ((spec->aivec.z) < 0.0f) spec->aivec.z = -spec->aivec.z; } } else { //vecScalarMultiply(tmpvec,trajectory,interceptorstat->flyPastDist[ship->tacticstype][targetIndex]); vecScalarMultiply(tmpvec,trajectory,FAKE_FLY_BY_DISTANCE_MUCH_BIGGER_THAN_NEEDED); matMakeRotAboutZ(&tmpmat,(real32)cos(randegf),(real32)sin(randegf)); matMultiplyMatByVec(&spec->aivec,&tmpmat,&tmpvec); } #ifdef DEBUG_TACTICS if(tacticsOn) #endif { //slowly reduce verticle component so as to organize //things and reduce deadlyness of verticle attacks //tempreal = vecMagnitudeSquared(spec->aivec); //tempreal = fsqrt(tempreal); //spec->aivec.x *= tacticsInfo.InterceptorVerticalMultiplier; //vecNormalize(&spec->aivec); //vecScalarMultiply(spec->aivec,spec->aivec,tempreal); } vecAddTo(spec->aivec,target->posinfo.position); } } break; case STATE_BARRELROLL_OUT: if(flightmanExecute(ship)) ship->aistateattack = STATE_BREAK; break; case STATE_BREAK: #ifdef DEBUG_AIATTACK dbgMessagef("\nShip %x STATE_BREAK",(udword)ship); #endif aishipGetTrajectory(ship,target,&trajectory); dist = vecMagnitudeSquared(trajectory); curdistonly = TRUE; if(target->objtype == OBJ_ShipType) { if( ((ShipStaticInfo *)target->staticinfo)->shipclass <= CLASS_Frigate) { //ship is a frigate..so lets base our fly away distance on current distance ONLY! curdistonly = FALSE; } } if ((dist > interceptorstat->maxFlyAwayDistSquared[ship->tacticstype][targetIndex]) || ((MoveReachedDestinationVariable(ship,&spec->aivec,INTERCEPTORBREAK_TOLERANCE) && curdistonly)) ) { abortbreakafterall: matGetVectFromMatrixCol3(tmpvec,ship->rotinfo.coordsys); if (vecDotProduct(trajectory,tmpvec) > 0.0f) { flighttodo = FLIGHTMAN_DONOTHING; } else { flighttodo = flightmanGetRandom(&interceptorstat->flightmanProb[ship->tacticstype][targetIndex],FLIGHTMAN_TURNAROUND); bitSet(ship->tacticsTalk,TACTALK_FLIPTURN); } //flightmanInit(ship,flighttodo); flightmanInitFunc(ship,flighttodo,0); ship->aistateattack = STATE_TURNAROUND; } else { if(!curdistonly) { vecSub(dest,spec->aivec,target->posinfo.position); vecNormalize(&dest); vecScalarMultiply(dest,dest,20000.0f); vecAddTo(dest,target->posinfo.position); if(aishipFlyToPointAvoidingObjs(ship,&dest,AISHIP_DontFlyToObscuredPoints|AISHIP_ReturnImmedIfPointObscured|AISHIP_FastAsPossible|AISHIP_PointInDirectionFlying,INTERCEPTORBREAK_MINVELOCITY) & AISHIP_FLY_OBJECT_IN_WAY) { goto abortbreakafterall; } } else if (aishipFlyToPointAvoidingObjs(ship,&spec->aivec,AISHIP_DontFlyToObscuredPoints|AISHIP_ReturnImmedIfPointObscured|AISHIP_FastAsPossible|AISHIP_PointInDirectionFlying,INTERCEPTORBREAK_MINVELOCITY) & AISHIP_FLY_OBJECT_IN_WAY) { goto abortbreakafterall; } } break; default: dbgAssert(FALSE); break; } } void CloakedFighterStaticInit(char *directory,char *filename,struct ShipStaticInfo *statinfo) { CloakedFighterStatics *interceptorstat; GenericInterceptorStaticInit(directory,filename,statinfo); interceptorstat = (CloakedFighterStatics *)statinfo->custstatinfo; scriptSetStruct(directory,filename,CloakedFighterStaticScriptTable,(ubyte *)interceptorstat); //Set the fuel level at which ship decloaks and can no longer cloak interceptorstat->DeCloakFuelLevel = (interceptorstat->ForceDeCloakAtFuelPercent/100)*statinfo->maxfuel; interceptorstat->CloakingTime = 1 / interceptorstat->CloakingTime; } void CloakedFighterInit(Ship *ship) { CloakedFighterSpec *spec = (CloakedFighterSpec *)ship->ShipSpecifics; GenericInterceptorInit(ship); spec->CloakingStatus = 1.0f; //ship isn't cloaked spec->CloakLowWarning = FALSE; spec->ReCloak = FALSE; } /*----------------------------------------------------------------------------- Name : SpawnCloakingEffect Description : Create an effect for cloaking or decloaking Inputs : ship - ship that is (de)cloaking etgLOD - effect LOD Outputs : Return : ----------------------------------------------------------------------------*/ void SpawnCloakingEffect(Ship *ship, etglod *etgLOD) { etgeffectstatic *stat; sdword LOD; if (etgLOD != NULL) { LOD = ship->currentLOD; if (LOD >= etgLOD->nLevels) { stat = NULL; } else { stat = etgLOD->level[LOD]; } } else { stat = NULL; } #if ETG_DISABLEABLE if (stat != NULL && etgEffectsEnabled) #else if (stat != NULL) #endif { etgEffectCreate(stat, ship, NULL, NULL, NULL, ship->magnitudeSquared, EAF_Full, 3, ship->shiprace, ship->shiptype, ship->staticinfo->staticheader.staticCollInfo.collspheresize); } } bool CloakedFighterSpecialActivate(Ship *ship) { CloakedFighterSpec *spec = (CloakedFighterSpec *)ship->ShipSpecifics; CloakedFighterStatics *cloakedfighterstatics; cloakedfighterstatics = (CloakedFighterStatics *) ((ShipStaticInfo *)(ship->staticinfo))->custstatinfo; spec->ReCloak = FALSE; if(ship->fuel <= cloakedfighterstatics->DeCloakFuelLevel) { //ships fuel is below critical level, so don't let it cloak // speechEvent(ship, COMM_Cloak_InsufficientPower, 0); if (battleCanChatterAtThisTime(BCE_CloakingInsufficientPower, ship)) { battleChatterAttempt(SOUND_EVENT_DEFAULT, BCE_CloakingInsufficientPower, ship, SOUND_EVENT_DEFAULT); } return TRUE; } /* if(bitTest(ship->flags,SOF_Cloaked) || bitTest(ship->flags,SOF_Cloaking)) { bitSet(ship->flags,SOF_DeCloaking); //begin DeCloaking Process; bitClear(ship->flags,SOF_Cloaking); //clear in case we are cloaking right now } else { bitSet(ship->flags,SOF_Cloaking); //begin Cloaking Process bitClear(ship->flags,SOF_DeCloaking); //clear in case we are decloaking right now } */ if(bitTest(ship->flags,SOF_Cloaked)) { if(universe.curPlayerIndex != ship->playerowner->playerIndex) { ////////////////// //speechevent ENEMY fighters decloaking //needs to be dependant on camera n' stuff! //event num: COMM_F_Grp_Enemy_Fighters_Decloaking //battle chatter // if (battleCanChatterAtThisTime(BCE_COMM_F_Grp_Enemy_Fighters_Decloaking, ship)) //{ // battleChatterAttempt(SOUND_EVENT_DEFAULT, BCE_COMM_F_Grp_Enemy_Fighters_Decloaking, ship, SOUND_EVENT_DEFAULT); //} // } bitSet(ship->flags,SOF_DeCloaking); //begin DeCloaking Process; SpawnCloakingEffect(ship, etgSpecialPurposeEffectTable[EGT_CLOAK_OFF]); soundEvent(ship, Ship_CloakOff); } else { bitSet(ship->flags,SOF_Cloaking); //begin Cloaking Process SpawnCloakingEffect(ship, etgSpecialPurposeEffectTable[EGT_CLOAK_ON]); soundEvent(ship, Ship_CloakOn); // speechEvent(ship, COMM_Cloak_CloakingOn, 0); if (battleCanChatterAtThisTime(BCE_CloakingOn, ship)) { battleChatterAttempt(SOUND_EVENT_DEFAULT, BCE_CloakingOn, ship, SOUND_EVENT_DEFAULT); } } return TRUE; } void CloakedFighterFire(Ship *ship,SpaceObjRotImpTarg *target) { if(ship->shiptype == CloakedFighter) { if (bitTest(ship->flags,SOF_Cloaked) || bitTest(ship->flags,SOF_Cloaking)) { //ship is cloaked...make it decloak... bitClear(ship->flags,SOF_Cloaking); //if bastard is trying to cloak, bitSet(ship->flags,SOF_DeCloaking); //put an end to it pronto! ((CloakedFighterSpec *) ship->ShipSpecifics)->ReCloak = TRUE; ((CloakedFighterSpec *) ship->ShipSpecifics)->ReCloakTime = universe.totaltimeelapsed + ((CloakedFighterStatics *) ((ShipStaticInfo *)(ship->staticinfo))->custstatinfo)->battleReCloakTime; } } GenericInterceptorFire(ship,target); } void CloakedFighterHouseKeep(Ship *ship) { CloakedFighterSpec *spec = (CloakedFighterSpec *)ship->ShipSpecifics; CloakedFighterStatics *cloakedfighterstatics; cloakedfighterstatics = (CloakedFighterStatics *) ((ShipStaticInfo *)(ship->staticinfo))->custstatinfo; if(bitTest(ship->flags,SOF_Cloaking)) //ship is cloaking { //decrement Cloaking Status counter based on time elapsed /***** calculated the inverse of CloakingTime in CloakedFighterStaticInit and multiply instead of divide *****/ spec->CloakingStatus -= universe.phystimeelapsed*cloakedfighterstatics->CloakingTime; if(spec->CloakingStatus <= cloakedfighterstatics->VisibleState) { //if ship is now past the vissible threshold bitSet(ship->flags,SOF_Cloaked); //make it invisible to everything if(spec->CloakingStatus <= 0.0f) //if it is completly invisible stop decloaking { spec->CloakingStatus = 0.0f; //reset to 0.0 bitClear(ship->flags,SOF_Cloaking); //stop ship from 'cloaking' //Since ship is cloaked, must remove it from being targeted //Note: This probably will not let a person attack their own cloaked ships... shipHasJustCloaked(ship); //speechEvent(ship, COMM_Cloak_CloakingOn, 0); /* RemoveShipFromBeingTargeted(&universe.mainCommandLayer,ship,FALSE); if(ship->playerowner != universe.curPlayerPtr) { //if ship isn't players...remove it from camera stack ccRemoveShip(&universe.mainCameraCommand,ship); } speechEvent(ship, COMM_Cloak_CloakingOn, 0); */ } } } else if(bitTest(ship->flags,SOF_DeCloaking)) //Ship is decloaking { //Increment Cloaking Status counter based on time elapsed /***** calculated the inverse of CloakingTime in CloakedFighterStaticInit and multiply instead of divide *****/ spec->CloakingStatus += universe.phystimeelapsed*cloakedfighterstatics->CloakingTime; if(spec->CloakingStatus >= cloakedfighterstatics->VisibleState) { //ship is 'visible' since it is past visible threshold if(ship->flags & SOF_Cloaked) { bitClear(ship->flags, SOF_Cloaked); ship->shipDeCloakTime = universe.totaltimeelapsed; } if(spec->CloakingStatus >= 1.0f) //done decloaking { spec->CloakingStatus = 1.0f; bitClear(ship->flags,SOF_DeCloaking); spec->CloakLowWarning = FALSE; //reset flag if(spec->ReCloak == FALSE) { //only play speech when really decloaking for good // speechEvent(ship, COMM_Cloak_Decloak, 0); if (battleCanChatterAtThisTime(BCE_Decloaking, ship)) { battleChatterAttempt(SOUND_EVENT_DEFAULT, BCE_Decloaking, ship, SOUND_EVENT_DEFAULT); } } } } } //Now, lets charge the guy/gal fuel for using the cloaking device if(bitTest(ship->flags,SOF_Cloaked) || bitTest(ship->flags,SOF_Cloaking)) { //Ship is cloaked or cloaking subtract fuel ship->fuel -= cloakedfighterstatics->CloakFuelBurnRate*universe.phystimeelapsed; if(ship->fuel <= cloakedfighterstatics->DeCloakFuelLevel) { //if fuel reaches a certain level, DeCloak bitSet(ship->flags,SOF_DeCloaking); //begin DeCloaking Process; bitClear(ship->flags,SOF_Cloaking); //clear in case we are cloaking right now SpawnCloakingEffect(ship, etgSpecialPurposeEffectTable[EGT_CLOAK_OFF]); soundEvent(ship, Ship_CloakOff); } if ((ship->fuel <= cloakedfighterstatics->CloakFuelBurnRate * 10.0f) && !spec->CloakLowWarning) { spec->CloakLowWarning = TRUE; // speechEvent(ship, STAT_Cloak_CloakPowerLow, (sdword)(ship->fuel / cloakedfighterstatics->CloakFuelBurnRate)); if (battleCanChatterAtThisTime(BCE_CloakingPowerLow, ship)) { battleChatterAttempt(SOUND_EVENT_DEFAULT, BCE_CloakingPowerLow, ship, (sdword)(ship->fuel / cloakedfighterstatics->CloakFuelBurnRate)); } } } if(spec->ReCloak == TRUE) { if(universe.totaltimeelapsed > spec->ReCloakTime) { spec->ReCloak = FALSE; bitSet(ship->flags,SOF_Cloaking); soundEvent(ship, Ship_CloakOn); } } } bool InterceptorInRange(Ship *ship,SpaceObjRotImpTarg *target) { GenericInterceptorSpec *spec = (GenericInterceptorSpec *)ship->ShipSpecifics; GenericInterceptorStatics *interceptorstat = (GenericInterceptorStatics *)ship->staticinfo->custstatinfo; real32 range; uword targetIndex; vector trajectory; aishipGetTrajectory(ship,target,&trajectory); range = RangeToTarget(ship,target,&trajectory); if (target->objtype == OBJ_ShipType) { targetIndex = (uword)((ShipStaticInfo *)target->staticinfo)->shipclass; } else { targetIndex = (uword)NUM_CLASSES; } if ((range < interceptorstat->breakRange[ship->tacticstype][targetIndex])) return FALSE; //if((range > ship->staticinfo->bulletRange)) // return FALSE; return TRUE; } bool GenericInterceptorSpecialActivate(Ship *ship) { if(ship->shiptype == LightInterceptor) { if(!bitTest(ship->specialFlags,SPECIAL_SpeedBurst)) { bitSet(ship->specialFlags,SPECIAL_SpeedBurst); ship->speedBurstTime = universe.totaltimeelapsed+speedBurstDuration; } } return TRUE; } CustShipHeader GenericInterceptorHeader = { (ShipType)-1, sizeof(GenericInterceptorSpec), GenericInterceptorStaticInit, NULL, GenericInterceptorInit, NULL, GenericInterceptorAttack, GenericInterceptorFire, GenericInterceptorPassiveAttack, GenericInterceptorSpecialActivate, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; CustShipHeader CloakedFighterHeader = { (ShipType)-1, sizeof(CloakedFighterSpec), CloakedFighterStaticInit, NULL, CloakedFighterInit, NULL, GenericInterceptorAttack, CloakedFighterFire, GenericInterceptorPassiveAttack, CloakedFighterSpecialActivate, NULL, CloakedFighterHouseKeep, NULL, NULL, NULL, NULL, NULL, NULL }; CustShipHeader AttackBomberHeader = { (ShipType)-1, sizeof(AttackBomberSpec), GenericInterceptorStaticInit, NULL, GenericInterceptorInit, NULL, GenericInterceptorAttack, GenericInterceptorFire, GenericInterceptorPassiveAttack, GenericInterceptorSpecialActivate, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; CustShipHeader TargetDroneHeader = { (ShipType)-1, sizeof(GenericInterceptorSpec), GenericInterceptorStaticInit, NULL, GenericInterceptorInit, NULL, GenericInterceptorAttack, GenericInterceptorFire, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };