Starshatter_Open
Open source Starshatter engine
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
FighterAI.cpp
Go to the documentation of this file.
1 /* Project Starshatter 4.5
2  Destroyer Studios LLC
3  Copyright © 1997-2004. All Rights Reserved.
4 
5  SUBSYSTEM: Stars.exe
6  FILE: FighterAI.cpp
7  AUTHOR: John DiCamillo
8 
9 
10  OVERVIEW
11  ========
12  Fighter (low-level) Artificial Intelligence class
13 */
14 
15 #include "MemDebug.h"
16 #include "FighterAI.h"
17 #include "FighterTacticalAI.h"
18 #include "Ship.h"
19 #include "Shot.h"
20 #include "Sensor.h"
21 #include "Element.h"
22 #include "ShipDesign.h"
23 #include "Instruction.h"
24 #include "Weapon.h"
25 #include "WeaponGroup.h"
26 #include "Drive.h"
27 #include "QuantumDrive.h"
28 #include "Farcaster.h"
29 #include "FlightComp.h"
30 #include "FlightDeck.h"
31 #include "Hangar.h"
32 #include "Sim.h"
33 #include "StarSystem.h"
34 #include "RadioMessage.h"
35 #include "RadioTraffic.h"
36 
37 #include "Game.h"
38 
39 static const double TIME_TO_DOCK = 30;
40 
41 // +----------------------------------------------------------------------+
42 
44 : ShipAI(s), brakes(0), drop_state(0), jink_time(0), evading(false),
45 decoy_missile(0), missile_time(0), terrain_warning(false), inbound(0),
46 rtb_code(0), form_up(false), over_threshold(false), time_to_dock(0),
47 go_manual(false)
48 {
49  ai_type = FIGHTER;
50  seek_gain = 22;
51  seek_damp = 0.55;
52  brakes = 0;
53  z_shift = 0;
54 
55  tactical = new(__FILE__,__LINE__) FighterTacticalAI(this);
56 }
57 
58 
59 // +--------------------------------------------------------------------+
60 
62 { }
63 
64 // +--------------------------------------------------------------------+
65 
66 static double frame_time = 0;
67 
68 void
70 {
71  if (!ship) return;
72 
73  evading = false;
74  inbound = ship->GetInbound();
75  missile_time -= s;
76 
77  int order = 0;
78 
79  if (navpt)
80  order = navpt->Action();
81 
82  if (inbound) {
83  form_up = false;
84  rtb_code = 1;
85 
86  // CHEAT LANDING:
87  if (inbound->Final() && time_to_dock > 0) {
88  FlightDeck* deck = inbound->GetDeck();
89  if (deck) {
90  Point dst = deck->EndPoint();
91  Point approach = deck->StartPoint() - dst;
92 
93  const Ship* carrier = deck->GetCarrier();
94 
95  Camera landing_cam;
96  landing_cam.Clone(carrier->Cam());
97  landing_cam.Yaw(deck->Azimuth());
98 
99  if (time_to_dock > TIME_TO_DOCK/2) {
100  double lr, lp, lw;
101  double sr, sp, sw;
102 
103  landing_cam.Orientation().ComputeEulerAngles(lr, lp, lw);
104  ship->Cam().Orientation().ComputeEulerAngles(sr, sp, sw);
105 
106  double nr = sr + s*(lr-sr);
107  double np = sp + s*(lp-sp);
108  double nw = sw + s*(lw-sw)*0.5;
109 
110  Camera work;
111  work.Aim(nr,np,nw);
112  landing_cam.Clone(work);
113  }
114 
115  ship->CloneCam(landing_cam);
116  ship->MoveTo(dst + approach * (time_to_dock / TIME_TO_DOCK));
117  ship->SetVelocity(carrier->Velocity() + ship->Heading() * 50);
118  ship->SetThrottle(50);
119  ship->ExecFLCSFrame();
120 
121  time_to_dock -= s;
122 
123  if (time_to_dock <= 0) {
124  deck->Dock(ship);
125  time_to_dock = 0;
126  }
127 
128  return;
129  }
130  }
131 
132  else if (ship->GetFlightPhase() == Ship::DOCKING) {
133  // deal with (pathological) moving carrier deck:
134 
135  FlightDeck* deck = inbound->GetDeck();
136  if (deck) {
137  Point dst = deck->EndPoint();
138 
139  if (ship->IsAirborne()) {
140  double alt = dst.y;
141  dst = ship->Location();
142  dst.y = alt;
143  }
144 
145  const Ship* carrier = deck->GetCarrier();
146 
147  Camera landing_cam;
148  landing_cam.Clone(carrier->Cam());
149  landing_cam.Yaw(deck->Azimuth());
150 
151  ship->CloneCam(landing_cam);
152  ship->MoveTo(dst);
153 
154  if (!ship->IsAirborne()) {
155  ship->SetVelocity(carrier->Velocity());
156  }
157  else {
158  Point taxi(landing_cam.vpn());
159  ship->SetVelocity(taxi * 95);
160  }
161 
162  ship->SetThrottle(0);
163  ship->ExecFLCSFrame();
164  }
165 
166  return;
167  }
168  }
169  else {
170  Instruction* orders = ship->GetRadioOrders();
171 
172  if (orders &&
173  (orders->Action() == RadioMessage::WEP_HOLD ||
174  orders->Action() == RadioMessage::FORM_UP)) {
175  form_up = true;
176  rtb_code = 0;
177  }
178  else {
179  form_up = false;
180  }
181  }
182 
183  if (!target && order != Instruction::STRIKE)
185 
186  ShipAI::ExecFrame(s); // this must be the last line of this method
187 
188  // IT IS NOT SAFE TO PLACE CODE HERE
189  // if this class decides to break orbit,
190  // this object will be deleted during
191  // ShipAI::ExecFrame() (which calls
192  // FighterAI::Navigator() - see below)
193 }
194 
195 // +--------------------------------------------------------------------+
196 
197 void
199 {
200  distance = 0;
201 
202  // ALWAYS complete initial launch navpt:
203  if (!navpt) {
206  navpt = 0;
207  }
208 
209  if (navpt && navpt->Action() == Instruction::LAUNCH) {
210  if (navpt->Status() != Instruction::COMPLETE) {
212 
213  // transform into camera coords:
215  ship->SetDirectorInfo(Game::GetText("ai.launch"));
216  return;
217  }
218  else {
219  navpt = 0;
220  }
221  }
222 
223  // runway takeoff:
224  else if (takeoff) {
225  obj_w = ship->Location() + ship->Heading() * 10e3;
226  obj_w.y = ship->Location().y + 2e3;
227 
228  // transform into camera coords:
230  ship->SetDirectorInfo(Game::GetText("ai.takeoff"));
231  return;
232  }
233 
234  // approaching a carrier or runway:
235  else if (inbound) {
236  FlightDeck* deck = inbound->GetDeck();
237 
238  if (!deck) {
239  objective = Point();
240  return;
241  }
242 
243  // initial approach
244  if (inbound->Approach() > 0 || !inbound->Cleared()) {
246 
247  distance = (obj_w - ship->Location()).length();
248 
249  // transform into camera coords:
251  ship->SetDirectorInfo(Game::GetText("ai.inbound"));
252 
253  return;
254  }
255 
256  // final approach
257  else {
258  ship->SetDirectorInfo(Game::GetText("ai.finals"));
259 
260  obj_w = deck->StartPoint();
261  if (inbound->Final()) {
262  obj_w = deck->EndPoint();
263 
264  if (deck->OverThreshold(ship)) {
265  obj_w = deck->MountLocation();
266  over_threshold = true;
267  }
268  }
269 
270  distance = (obj_w - ship->Location()).length();
271 
272  // transform into camera coords:
274 
275  return;
276  }
277  }
278 
279  // not inbound yet, check for RTB order:
280  else {
281  Instruction* orders = (Instruction*) ship->GetRadioOrders();
282  int action = 0;
283 
284  if (orders)
285  action = orders->Action();
286 
287  if (navpt && !action) {
289  if (distance < 5e3) {
290  action = navpt->Action();
291  }
292  }
293 
294  if (action == RadioMessage::RTB ||
295  action == RadioMessage::DOCK_WITH) {
296 
297  Ship* controller = ship->GetController();
298 
299  if (orders && orders->Action() == RadioMessage::DOCK_WITH && orders->GetTarget()) {
300  controller = (Ship*) orders->GetTarget();
301  }
302 
303  else if (navpt && navpt->Action() == RadioMessage::DOCK_WITH && navpt->GetTarget()) {
304  controller = (Ship*) navpt->GetTarget();
305  }
306 
307  ReturnToBase(controller);
308 
309  if (rtb_code)
310  return;
311  }
312  }
313 
315 }
316 
317 void
319 {
320  rtb_code = 0;
321 
322  if (controller) {
323  SimRegion* self_rgn = ship->GetRegion();
324  SimRegion* rtb_rgn = controller->GetRegion();
325 
326  if (self_rgn && !rtb_rgn) {
327  rtb_rgn = self_rgn;
328  }
329 
330  if (self_rgn && rtb_rgn && self_rgn != rtb_rgn) {
331  // is the carrier in orbit above us
332  // (or on the ground below us)?
333 
334  if (rtb_rgn->GetOrbitalRegion()->Primary() ==
335  self_rgn->GetOrbitalRegion()->Primary()) {
336 
337  Point npt = rtb_rgn->Location() - self_rgn->Location();
338  obj_w = npt.OtherHand();
339 
340  // distance from self to navpt:
341  distance = Point(obj_w - ship->Location()).length();
342 
343  // transform into camera coords:
345 
346  if (rtb_rgn->IsAirSpace()) {
347  drop_state = -1;
348  }
349  else if (rtb_rgn->IsOrbital()) {
350  drop_state = 1;
351  }
352 
353  rtb_code = 2;
354  }
355 
356  // try to find a jumpgate that will take us home:
357  else {
358  QuantumDrive* qdrive = ship->GetQuantumDrive();
359  bool use_farcaster = !qdrive ||
360  !qdrive->IsPowerOn() ||
361  qdrive->Status() < System::DEGRADED;
362 
363  if (use_farcaster) {
364  if (!farcaster) {
365  ListIter<Ship> s = self_rgn->Ships();
366  while (++s && !farcaster) {
367  if (s->GetFarcaster()) {
368  const Ship* dest = s->GetFarcaster()->GetDest();
369  if (dest && dest->GetRegion() == rtb_rgn) {
370  farcaster = s->GetFarcaster();
371  }
372  }
373  }
374  }
375 
376  if (farcaster) {
377  Point apt = farcaster->ApproachPoint(0);
378  Point npt = farcaster->StartPoint();
379  double r1 = (ship->Location() - npt).length();
380 
381  if (r1 > 50e3) {
382  obj_w = apt;
383  distance = r1;
385  }
386 
387  else {
388  double r2 = (ship->Location() - apt).length();
389  double r3 = (npt - apt).length();
390 
391  if (r1+r2 < 1.2*r3) {
392  obj_w = npt;
393  distance = r1;
395  }
396  else {
397  obj_w = apt;
398  distance = r2;
400  }
401  }
402 
403  rtb_code = 3;
404  }
405 
406  // can't find a way back home, ignore the RTB order:
407  else {
409  rtb_code = 0;
410  return;
411  }
412  }
413  else if (qdrive) {
414  if (qdrive->ActiveState() == QuantumDrive::ACTIVE_READY) {
415  qdrive->SetDestination(rtb_rgn, controller->Location());
416  qdrive->Engage();
417  }
418 
419  rtb_code = 3;
420  }
421  }
422  }
423 
424  else {
425  obj_w = controller->Location();
426 
427  distance = (obj_w - ship->Location()).length();
428 
429  // transform into camera coords:
431  ship->SetDirectorInfo(Game::GetText("ai.return-to-base"));
432 
433  rtb_code = 1;
434  }
435  }
436 }
437 
438 // +--------------------------------------------------------------------+
439 
440 void
442 {
443  SimRegion* self_rgn = ship->GetRegion();
444  SimRegion* nav_rgn = navpt->Region();
445 
446  if (self_rgn && !nav_rgn) {
447  nav_rgn = self_rgn;
448  navpt->SetRegion(nav_rgn);
449  }
450 
451  if (self_rgn && nav_rgn && self_rgn != nav_rgn) {
452  if (nav_rgn->GetOrbitalRegion()->Primary() ==
453  self_rgn->GetOrbitalRegion()->Primary()) {
454 
455  Point npt = nav_rgn->Location() - self_rgn->Location();
456  obj_w = npt.OtherHand();
457 
458  // distance from self to navpt:
459  distance = Point(obj_w - ship->Location()).length();
460 
461  // transform into camera coords:
463 
464  if (nav_rgn->IsAirSpace()) {
465  drop_state = -1;
466  }
467  else if (nav_rgn->IsOrbital()) {
468  drop_state = 1;
469  }
470 
471  return;
472  }
473 
474  else {
476 
477  if (q) {
480  q->Engage();
481  return;
482  }
483  }
484  }
485  }
486 
488 }
489 
490 // +--------------------------------------------------------------------+
491 
492 Point
494 {
495  if (ship) {
496  WeaponDesign* wep_design = ship->GetPrimaryDesign();
497 
498  if (target && wep_design) {
499  Point aim_vec = ship->Heading();
500  aim_vec.Normalize();
501 
502  Point shot_vel = ship->Velocity() + aim_vec * wep_design->speed;
503  return shot_vel - target->Velocity();
504  }
505 
506  else if (target) {
507  return ship->Velocity() - target->Velocity();
508  }
509 
510  else {
511  return ship->Velocity();
512  }
513  }
514 
515  return Point(1,0,0);
516 }
517 
518 // +--------------------------------------------------------------------+
519 
520 void
522 {
523  go_manual = false;
524 
525  if (takeoff) {
526  accumulator.Clear();
527  magnitude = 0;
528  brakes = 0;
529  z_shift = 0;
530 
532  HelmControl();
533  ThrottleControl();
534  ship->ExecFLCSFrame();
535  return;
536  }
537 
538  Element* elem = ship->GetElement();
539 
540  if (elem) {
541  Ship* lead = elem->GetShip(1);
542 
543  if (lead && lead != ship) {
544  if (lead->IsDropping() && !ship->IsDropping()) {
545  ship->DropOrbit();
546  // careful: this object has just been deleted!
547  return;
548  }
549 
550  if (lead->IsAttaining() && !ship->IsAttaining()) {
551  ship->MakeOrbit();
552  // careful: this object has just been deleted!
553  return;
554  }
555  }
556 
557  else {
558  if (drop_state < 0) {
559  ship->DropOrbit();
560  // careful: this object has just been deleted!
561  return;
562  }
563 
564  if (drop_state > 0) {
565  ship->MakeOrbit();
566  // careful: this object has just been deleted!
567  return;
568  }
569  }
570  }
571 
572  int order = 0;
573 
574  if (navpt)
575  order = navpt->Action();
576 
577  if (rtb_code == 1 && navpt && navpt->Status() < Instruction::SKIPPED &&
578  !inbound && distance < 35e3) { // (this should be distance to the ship)
579 
580  if (order == Instruction::RTB) {
581  Ship* controller = ship->GetController();
582  Hangar* hangar = controller ? controller->GetHangar() : 0;
583 
584  if (hangar && hangar->CanStow(ship)) {
585  for (int i = 0; i < elem->NumShips(); i++) {
586  Ship* s = elem->GetShip(i+1);
587 
588  if (s && s->GetDirector() && s->GetDirector()->Type() >= ShipAI::FIGHTER)
590  }
591 
592  if (element_index == 1)
594  }
595 
596  else {
597  if (element_index == 1) {
598  ::Print("WARNING: FighterAI NAVPT RTB, but no controller or hangar found for ship '%s'\n", ship->Name());
600  }
601  }
602  }
603 
604  else {
605  Ship* dock_target = (Ship*) navpt->GetTarget();
606  if (dock_target) {
607  for (int i = 0; i < elem->NumShips(); i++) {
608  Ship* s = elem->GetShip(i+1);
609 
610  if (s) {
611  RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(dock_target, s, RadioMessage::CALL_INBOUND);
613  }
614  }
615 
616  if (element_index == 1)
618  }
619 
620  else {
621  if (element_index == 1) {
622  ::Print("WARNING: FighterAI NAVPT DOCK, but no dock target found for ship '%s'\n", ship->Name());
624  }
625  }
626  }
627  }
628 
629  if (target)
630  ship->SetDirectorInfo(Game::GetText("ai.seek-target"));
631 
632  accumulator.Clear();
633  magnitude = 0;
634  brakes = 0;
635  z_shift = 0;
636 
637  hold = false;
638  if ((ship->GetElement() && ship->GetElement()->GetHoldTime() > 0) ||
639  (navpt && navpt->Status() == Instruction::COMPLETE && navpt->HoldTime() > 0))
640  hold = true;
641 
642  if (ship->MissionClock() < 10000) {
643  if (ship->IsAirborne())
645  }
646 
647  else if ((farcaster && distance < 20e3) || (inbound && inbound->Final())) {
649  }
650 
651  else {
652  if (!ship->IsAirborne() || ship->AltitudeAGL() > 100)
653  ship->RaiseGear();
654 
656  Steer avoid = AvoidCollision();
657 
658  if (other && inbound && inbound->GetDeck() && inbound->Cleared()) {
659  if (other != (SimObject*) inbound->GetDeck()->GetCarrier())
660  Accumulate(avoid);
661  }
662  else {
663  Accumulate(avoid);
664  }
665 
666  if (!too_close && !hold && !terrain_warning) {
669  }
670  }
671 
672  HelmControl();
673  ThrottleControl();
674  FireControl();
675  AdjustDefenses();
676 
677  ship->ExecFLCSFrame();
678 }
679 
680 // +--------------------------------------------------------------------+
681 
682 void
684 {
685  Camera* cam = ((Camera*) &(ship->Cam()));
686  Point vrt = cam->vrt();
687  double deflection = vrt.y;
688  double theta = 0;
689  bool formation = element_index > 1;
690  bool station_keeping = distance < 0;
691  bool inverted = cam->vup().y < -0.5;
692  Ship* ward = ship->GetWard();
693 
694  if (takeoff || inbound || station_keeping)
695  formation = false;
696 
697  if (takeoff || navpt || farcaster || patrol || inbound || rtb_code || target || ward || threat || formation) {
698  // are we being asked to flee?
699  if (fabs(accumulator.yaw) == 1.0 && accumulator.pitch == 0.0) {
700  accumulator.pitch = -0.7f;
701  accumulator.yaw *= 0.25f;
702 
703  if (ship->IsAirborne() && ship->GetFlightModel() == 0)
704  accumulator.pitch = -0.45f;
705 
706  // low ai -> lower turning rate
707  accumulator.pitch += 0.1f * (2-ai_level);
708  }
709 
710  ship->ApplyRoll((float) (accumulator.yaw * -0.7));
711  ship->ApplyYaw((float) (accumulator.yaw * 0.2));
712 
713  if (fabs(accumulator.yaw) > 0.5 && fabs(accumulator.pitch) < 0.1)
714  accumulator.pitch -= 0.1f;
715 
716  ship->ApplyPitch((float) accumulator.pitch);
717  }
718 
719  else {
720  ship->SetDirectorInfo(Game::GetText("ai.station-keeping"));
721  station_keeping = true;
722 
723  // go into a slow orbit if airborne:
724  if (ship->IsAirborne() && ship->Class() < Ship::LCA) {
725  accumulator.brake = 0.2;
726  accumulator.stop = 0;
727 
728  double compass_pitch = ship->CompassPitch();
729  double desired_bank = -PI/4;
730  double current_bank = asin(deflection);
731  double theta = desired_bank - current_bank;
732  ship->ApplyRoll(theta);
733 
734  double coord_pitch = compass_pitch - 0.2 * fabs(current_bank);
735  ship->ApplyPitch(coord_pitch);
736  }
737  else {
738  accumulator.brake = 1;
739  accumulator.stop = 1;
740  }
741  }
742 
743  // if not turning, roll to orient with world coords:
744  if (ship->Design()->auto_roll > 0) {
745  if (fabs(accumulator.pitch) < 0.1 && fabs(accumulator.yaw) < 0.25) {
746  // zolon spiral behavior:
747  if (ship->Design()->auto_roll > 1) {
748  if ((element_index + (ship->MissionClock()>>10)) & 0x4)
749  ship->ApplyRoll( 0.60);
750  else
751  ship->ApplyRoll(-0.35);
752  }
753 
754  // normal behavior - roll to upright:
755  else if (fabs(deflection) > 0.1 || inverted) {
756  double theta = asin(deflection/vrt.length()) * 0.5;
757  ship->ApplyRoll(-theta);
758  }
759  }
760  }
761 
762  // if not otherwise occupied, pitch to orient with world coords:
763  if (station_keeping && (!ship->IsAirborne() || ship->Class() < Ship::LCA)) {
764  Point heading = ship->Heading();
765  double pitch_deflection = heading.y;
766 
767  if (fabs(pitch_deflection) > 0.05) {
768  double rho = asin(pitch_deflection) * 3;
769  ship->ApplyPitch(rho);
770  }
771  }
772 
773  ship->SetTransX(0);
774  ship->SetTransY(0);
777 }
778 
779 void
781 {
782  Element* elem = ship->GetElement();
783  double ship_speed = ship->Velocity() * ship->Heading();
784  double desired = 1000;
785  bool formation = element_index > 1;
786  bool station_keeping = distance < 0;
787  bool augmenter = false;
788  Ship* ward = ship->GetWard();
789 
790  if (inbound || station_keeping)
791  formation = false;
792 
793  // LAUNCH / TAKEOFF
794  if (ship->MissionClock() < 10000) {
795  formation = false;
796  throttle = 100;
797  brakes = 0;
798  }
799 
800  // STATION KEEPING
801  else if (station_keeping) {
802  // go into a slow orbit if airborne:
803  if (ship->IsAirborne() && ship->Class() < Ship::LCA) {
804  throttle = 30;
805  brakes = 0;
806  }
807  else {
808  throttle = 0;
809  brakes = 1;
810  }
811  }
812 
813  // TRY TO STAY AIRBORNE, YES?
814  else if (ship->IsAirborne() && ship_speed < 250 && ship->Class() < Ship::LCA) {
815  throttle = 100;
816  brakes = 0;
817 
818  if (ship_speed < 200)
819  augmenter = true;
820  }
821 
822  // INBOUND
823  else if (inbound) {
824  double carrier_speed = inbound->GetDeck()->GetCarrier()->Velocity().length();
825  desired = 250 + carrier_speed;
826 
827  if (distance > 25.0e3)
828  desired = 750 + carrier_speed;
829 
830  else if (ship->IsAirborne())
831  desired = 300;
832 
833  else if (inbound->Final())
834  desired = 75 + carrier_speed;
835 
836  throttle = 0;
837 
838  // holding short?
839  if (inbound->Approach() == 0 && !inbound->Cleared() &&
840  distance < 2000 && !ship->IsAirborne())
841  desired = 0;
842 
843  if (ship_speed > desired+5)
844  brakes = 0.25;
845 
846  else if (ship->IsAirborne() || Ship::GetFlightModel() > 0) {
847  throttle = old_throttle + 1;
848  }
849 
850  else if (ship_speed < 0.85 * desired) {
851  throttle = 100;
852 
853  if (ship_speed < 0 && ship->GetFuelLevel() > 10)
854  augmenter = true;
855  }
856 
857  else if (ship_speed < desired-5) {
858  throttle = 30;
859  }
860  }
861 
862  else if (rtb_code || farcaster) {
863  desired = 750;
864 
865  if (threat || threat_missile) {
866  throttle = 100;
867 
868  if (!threat_missile && ship->GetFuelLevel() > 15)
869  augmenter = true;
870  }
871 
872  else {
873  throttle = 0;
874 
875  if (ship_speed > desired+5)
876  brakes = 0.25;
877 
878  else if (Ship::GetFlightModel() > 0) {
879  throttle = old_throttle + 1;
880  }
881 
882  else if (ship_speed < 0.85 * desired) {
883  throttle = 100;
884 
885  if (ship_speed < 0 && ship->GetFuelLevel() > 10)
886  augmenter = true;
887  }
888 
889  else if (ship_speed < desired-5) {
890  throttle = 30;
891  }
892  }
893  }
894 
895  // RUN AWAY!!!
896  else if (evading) {
897  throttle = 100;
898 
899  if (!threat_missile && ship->GetFuelLevel() > 15)
900  augmenter = true;
901  }
902 
903  // PATROL AND FORMATION
904  else if (!navpt && !target && !ward) {
905  if (!elem || !formation) { // element lead
906  if (patrol) {
907  desired = 250;
908 
909  if (distance > 10e3)
910  desired = 750;
911 
912  if (ship_speed > desired+5) {
913  brakes = 0.25;
914  throttle = old_throttle - 5;
915  }
916 
917  else if (ship_speed < 0.85 * desired) {
918  throttle = 100;
919 
920  if (ship_speed < 0 && ship->GetFuelLevel() > 10)
921  augmenter = true;
922  }
923 
924  else if (ship_speed < desired-5)
925  throttle = old_throttle + 5;
926  }
927 
928  else {
929  throttle = 35;
930 
931  if (threat)
932  throttle = 100;
933 
935 
936  if (brakes > 0.1)
937  throttle = 0;
938  }
939  }
940 
941  else { // wingman
942  Ship* lead = elem->GetShip(1);
943  double zone = ship->Radius() * 3;
944 
945  if (lead)
946  desired = lead->Velocity() * lead->Heading();
947 
948  if (fabs(slot_dist) < distance/4) // try to prevent porpoising
950 
951  else if (slot_dist > zone*2) {
952  throttle = 100;
953 
954  if (objective.z > 10e3 && ship_speed < desired && ship->GetFuelLevel() > 25)
955  augmenter = true;
956  }
957 
958  else if (slot_dist > zone)
959  throttle = lead->Throttle() + 10;
960 
961  else if (slot_dist < -zone*2) {
962  throttle = old_throttle - 10;
963  brakes = 1;
964  }
965 
966  else if (slot_dist < -zone) {
968  brakes = 0.5;
969  }
970 
971  else if (lead) {
972  double lv = lead->Velocity().length();
973  double sv = ship_speed;
974  double dv = lv-sv;
975  double dt = 0;
976 
977  if (dv > 0) dt = dv * 1e-5 * frame_time;
978  else if (dv < 0) dt = dv * 1e-2 * frame_time;
979 
980  throttle = old_throttle + dt;
981  }
982 
983  else {
985  }
986  }
987  }
988 
989  // TARGET/WARD/NAVPOINT SEEKING
990  else {
992 
993  if (target) {
994  desired = 1250;
995 
996  if (ai_level < 1) {
997  throttle = 70;
998  }
999 
1000  else if (ship->IsAirborne()) {
1001  throttle = 100;
1002 
1003  if (!threat_missile && fabs(objective.z) > 6e3 && ship->GetFuelLevel() > 25)
1004  augmenter = true;
1005  }
1006 
1007  else {
1008  throttle = 100;
1009 
1010  if (objective.z > 20e3 && ship_speed < desired && ship->GetFuelLevel() > 35)
1011  augmenter = true;
1012 
1013  else if (objective.z > 0 && objective.z < 10e3)
1014  throttle = 50;
1015  }
1016  }
1017 
1018  else if (ward) {
1019  double d = (ship->Location() - ward->Location()).length();
1020 
1021  if (d > 5000) {
1022  if (ai_level < 1)
1023  throttle = 50;
1024  else
1025  throttle = 80;
1026  }
1027  else {
1028  double speed = ward->Velocity().length();
1029 
1030  if (speed > 0) {
1031  if (ship_speed > speed) {
1032  throttle = old_throttle - 5;
1033  brakes = 0.25;
1034  }
1035  else if (ship_speed < speed - 10) {
1036  throttle = old_throttle + 1;
1037  }
1038  }
1039  }
1040  }
1041 
1042  else if (navpt) {
1043  desired = navpt->Speed();
1044 
1045  if (hold) {
1046  // go into a slow orbit if airborne:
1047  if (ship->IsAirborne() && ship->Class() < Ship::LCA) {
1048  throttle = 25;
1049  brakes = 0;
1050  }
1051  else {
1052  throttle = 0;
1053  brakes = 1;
1054  }
1055  }
1056 
1057  else if (desired > 0) {
1058  if (ship_speed > desired) {
1059  throttle = old_throttle - 5;
1060  brakes = 0.25;
1061  }
1062 
1063  else if (ship_speed < 0.85 * desired) {
1064  throttle = 100;
1065 
1066  if ((ship->IsAirborne() || ship_speed < 0.35 * desired) && ship->GetFuelLevel() > 30)
1067  augmenter = true;
1068  }
1069 
1070  else if (ship_speed < desired - 10) {
1071  throttle = old_throttle + 1;
1072  }
1073 
1074  else if (Ship::GetFlightModel() > 0) {
1076  }
1077  }
1078  }
1079 
1080  else {
1081  throttle = 0;
1082  brakes = 1;
1083  }
1084  }
1085 
1086  if (ship->IsAirborne() && throttle < 20 && ship->Class() < Ship::LCA)
1087  throttle = 20;
1088  else if (ship->Design()->auto_roll > 1 && throttle < 5)
1089  throttle = 5;
1090  else if (throttle < 0)
1091  throttle = 0;
1092 
1094  ship->SetThrottle((int) throttle);
1095  ship->SetAugmenter(augmenter);
1096 
1097  if (accumulator.stop && ship->GetFLCS() != 0)
1098  ship->GetFLCS()->FullStop();
1099 
1100  else if (ship_speed > 1 && brakes > 0)
1102 
1103  else if (throttle > 10 && (ship->GetEMCON() < 2 || ship->GetFuelLevel() < 10))
1105 }
1106 
1107 // +--------------------------------------------------------------------+
1108 
1109 Steer
1111 {
1112  Steer avoid;
1113 
1114  terrain_warning = false;
1115 
1116  if (!ship || !ship->GetRegion() || !ship->GetRegion()->IsActive() ||
1118  return avoid;
1119 
1120  if (ship->IsAirborne() && ship->GetFlightPhase() == Ship::ACTIVE) {
1121  // too high?
1122  if (ship->AltitudeMSL() > 25e3) {
1123  if (!navpt || (navpt->Region() == ship->GetRegion() && navpt->Location().z < 27e3)) {
1124  terrain_warning = true;
1125  ship->SetDirectorInfo(Game::GetText("ai.too-high"));
1126 
1127  // where will we be?
1128  Point selfpt = ship->Location() + ship->Velocity() + Point(0, -15e3, 0);
1129 
1130  // transform into camera coords:
1131  Point obj = Transform(selfpt);
1132 
1133  // head down!
1134  avoid = Seek(obj);
1135  }
1136  }
1137 
1138  // too low?
1139  else if (ship->AltitudeAGL() < 2500) {
1140  terrain_warning = true;
1141  ship->SetDirectorInfo(Game::GetText("ai.too-low"));
1142 
1143  // way too low?
1144  if (ship->AltitudeAGL() < 1500) {
1145  ship->SetDirectorInfo(Game::GetText("ai.way-too-low"));
1146  target = 0;
1147  drop_time = 5;
1148  }
1149 
1150  // where will we be?
1151  Point selfpt = ship->Location() + ship->Velocity() + Point(0, 10e3, 0);
1152 
1153  // transform into camera coords:
1154  Point obj = Transform(selfpt);
1155 
1156  // pull up!
1157  avoid = Seek(obj);
1158  }
1159  }
1160 
1161  return avoid;
1162 }
1163 
1164 // +--------------------------------------------------------------------+
1165 
1166 Steer
1168 {
1169  if (ship->GetFlightPhase() < Ship::ACTIVE)
1170  return Seek(objective);
1171 
1172  Ship* ward = ship->GetWard();
1173 
1174  if ((!target && !ward && !navpt && !farcaster && !patrol && !inbound && !rtb_code) || ship->MissionClock() < 10000) {
1175  if (element_index > 1) {
1176  // break formation if threatened:
1177  if (threat_missile)
1178  return Steer();
1179 
1180  else if (threat && !form_up)
1181  return Steer();
1182 
1183  // otherwise, keep in formation:
1184  return SeekFormationSlot();
1185  }
1186  else {
1187  return Steer();
1188  }
1189  }
1190 
1191  if (patrol) {
1192  Steer result = Seek(objective);
1193  ship->SetDirectorInfo(Game::GetText("ai.seek-patrol-point"));
1194 
1195  if (distance < 10 * self->Radius()) {
1196  patrol = 0;
1197  result.brake = 1;
1198  result.stop = 1;
1199  }
1200 
1201  return result;
1202  }
1203 
1204  if (inbound) {
1205  Steer result = Seek(objective);
1206 
1207  if (over_threshold && objective.z < 0) {
1208  result = Steer();
1209  result.brake = 1;
1210  result.stop = 1;
1211  }
1212  else {
1213  ship->SetDirectorInfo(Game::GetText("ai.seek-inbound"));
1214 
1215  // approach legs:
1216  if (inbound->Approach() > 0) {
1217  if (distance < 20 * self->Radius())
1219  }
1220 
1221  // marshall point and finals:
1222  else {
1223  if (inbound->Cleared() && distance < 10 * self->Radius()) {
1224  if (!inbound->Final()) {
1225  time_to_dock = TIME_TO_DOCK;
1226 
1227  FlightDeck* deck = inbound->GetDeck();
1228  if (deck) {
1229  double total_dist = Point(deck->EndPoint() - deck->StartPoint()).length();
1230  double current_dist = Point(deck->EndPoint() - ship->Location()).length();
1231 
1232  time_to_dock *= (current_dist / total_dist);
1233  }
1234 
1236  }
1237 
1238  inbound->SetFinal(true);
1239  ship->LowerGear();
1240  result.brake = 1;
1241  result.stop = 1;
1242  }
1243 
1244  else if (!inbound->Cleared() && distance < 2000) {
1245  ship->SetDirectorInfo(Game::GetText("ai.hold-final"));
1246  result = Steer();
1247  result.brake = 1;
1248  result.stop = 1;
1249  }
1250  }
1251  }
1252 
1253  return result;
1254  }
1255 
1256  else if (rtb_code) {
1257  return Seek(objective);
1258  }
1259 
1260  SimObject* tgt = target;
1261 
1262  if (ward && !tgt)
1263  tgt = ward;
1264 
1265  if (tgt && too_close == tgt->Identity()) {
1266  drop_time = 4;
1267  return Steer();
1268  }
1269 
1270  else if (navpt && navpt->Action() == Instruction::LAUNCH) {
1271  ship->SetDirectorInfo(Game::GetText("ai.launch"));
1272  return Seek(objective);
1273  }
1274 
1275  else if (farcaster) {
1276  // wingmen should
1277  if (element_index > 1)
1278  return SeekFormationSlot();
1279 
1280  ship->SetDirectorInfo(Game::GetText("ai.seek-farcaster"));
1281  return Seek(objective);
1282  }
1283 
1284  else if (drop_time > 0) {
1285  return Steer();
1286  }
1287 
1288  if (tgt) {
1289  double basis = self->Radius() + tgt->Radius();
1290  double gap = distance - basis;
1291 
1292  // target behind:
1293  if (objective.z < 0) {
1294  // leave some room for an attack run:
1295  if (gap < 8000) {
1296  Steer s;
1297 
1298  s.pitch = -0.1;
1299  if (objective.x > 0) s.yaw = 0.1;
1300  else s.yaw = -0.1;
1301 
1302  return s;
1303  }
1304 
1305  // start the attack run:
1306  else {
1307  return Seek(objective);
1308  }
1309  }
1310 
1311  // target in front:
1312  else {
1313  if (tgt->Type() == SimObject::SIM_SHIP) {
1314  Ship* tgt_ship = (Ship*) tgt;
1315 
1316  // capital target strike:
1317  if (tgt_ship->IsStatic()) {
1318  if (gap < 2500)
1319  return Flee(objective);
1320  }
1321 
1322  else if (tgt_ship->IsStarship()) {
1323  if (gap < 1000)
1324  return Flee(objective);
1325 
1326  else if (ship->GetFlightModel() == Ship::FM_STANDARD && gap < 20e3)
1327  go_manual = true;
1328  }
1329  }
1330 
1331  // fighter melee:
1332  if (tgt->Velocity() * ship->Velocity() < 0) {
1333  // head-to-head pass:
1334  if (gap < 1250)
1335  return Flee(objective);
1336  }
1337 
1338  else if (gap < 250) {
1339  return Steer();
1340  }
1341 
1342  ship->SetDirectorInfo(Game::GetText("ai.seek-target"));
1343  return Seek(objective);
1344  }
1345  }
1346 
1347  if (navpt) {
1348  ship->SetDirectorInfo(Game::GetText("ai.seek-navpt"));
1349  }
1350 
1351  return Seek(objective);
1352 }
1353 
1354 // +--------------------------------------------------------------------+
1355 
1356 Steer
1358 {
1359  Steer s;
1360 
1361  // advance memory pipeline:
1362  az[2] = az[1]; az[1] = az[0];
1363  el[2] = el[1]; el[1] = el[0];
1364 
1365  Element* elem = ship->GetElement();
1366  Ship* lead = elem->GetShip(1);
1367 
1368  if (lead) {
1369  SimRegion* self_rgn = ship->GetRegion();
1370  SimRegion* lead_rgn = lead->GetRegion();
1371 
1372  if (self_rgn != lead_rgn) {
1373  QuantumDrive* qdrive = ship->GetQuantumDrive();
1374  bool use_farcaster = !qdrive ||
1375  !qdrive->IsPowerOn() ||
1376  qdrive->Status() < System::DEGRADED;
1377 
1378  if (use_farcaster) {
1379  FindObjectiveFarcaster(self_rgn, lead_rgn);
1380  }
1381 
1382  else if (qdrive) {
1383  if (qdrive->ActiveState() == QuantumDrive::ACTIVE_READY) {
1384  qdrive->SetDestination(lead_rgn, lead->Location());
1385  qdrive->Engage();
1386  }
1387  }
1388  }
1389  }
1390 
1391  // do station keeping?
1392  if (distance < ship->Radius() * 10 && lead->Velocity().length() < 50) {
1393  distance = -1;
1394  return s;
1395  }
1396 
1397  // approach
1398  if (objective.z > ship->Radius() * -4) {
1399  az[0] = atan2(fabs(objective.x), objective.z) * 50;
1400  el[0] = atan2(fabs(objective.y), objective.z) * 50;
1401 
1402  if (objective.x < 0) az[0] = -az[0];
1403  if (objective.y > 0) el[0] = -el[0];
1404 
1405  s.yaw = az[0] - seek_damp * (az[1] + az[2] * 0.5);
1406  s.pitch = el[0] - seek_damp * (el[1] + el[2] * 0.5);
1407  }
1408 
1409  // reverse
1410  else {
1411  if (objective.x > 0) s.yaw = 1.0f;
1412  else s.yaw = -1.0f;
1413 
1414  s.pitch = -objective.y * 0.5f;
1415  }
1416 
1417  seeking = 1;
1418  ship->SetDirectorInfo(Game::GetText("ai.seek-formation"));
1419 
1420  return s;
1421 }
1422 
1423 // +--------------------------------------------------------------------+
1424 
1425 Steer
1426 FighterAI::Seek(const Point& point)
1427 {
1428  Steer s;
1429 
1430  // advance memory pipeline:
1431  az[2] = az[1]; az[1] = az[0];
1432  el[2] = el[1]; el[1] = el[0];
1433 
1434  // approach
1435  if (point.z > 0.0f) {
1436  az[0] = atan2(fabs(point.x), point.z) * seek_gain;
1437  el[0] = atan2(fabs(point.y), point.z) * seek_gain;
1438 
1439  if (point.x < 0) az[0] = -az[0];
1440  if (point.y > 0) el[0] = -el[0];
1441 
1442  s.yaw = az[0] - seek_damp * (az[1] + az[2] * 0.5);
1443  s.pitch = el[0] - seek_damp * (el[1] + el[2] * 0.5);
1444 
1445  // pull up:
1446  if (ship->IsAirborne() && point.y > 5e3)
1447  s.pitch = -1.0f;
1448  }
1449 
1450  // reverse
1451  else {
1452  if (ship->IsAirborne()) {
1453  // pull up:
1454  if (point.y > 5e3) {
1455  s.pitch = -1.0f;
1456  }
1457 
1458  // head down:
1459  else if (point.y < -5e3) {
1460  s.pitch = 1.0f;
1461  }
1462 
1463  // level turn:
1464  else {
1465  if (point.x > 0) s.yaw = 1.0f;
1466  else s.yaw = -1.0f;
1467 
1468  s.brake = 0.5f;
1469  }
1470  }
1471 
1472  else {
1473  if (point.x > 0) s.yaw = 1.0f;
1474  else s.yaw = -1.0f;
1475  }
1476  }
1477 
1478  seeking = 1;
1479 
1480  return s;
1481 }
1482 
1483 // +--------------------------------------------------------------------+
1484 
1485 Steer
1487 {
1488  // MISSILE THREAT REACTION:
1489  if (threat_missile) {
1490  evading = true;
1491  SetTarget(0);
1492  drop_time = 3 * (3-ai_level);
1493 
1494  // dropped a decoy for this missile yet?
1495  if (decoy_missile != threat_missile) {
1496  ship->FireDecoy();
1498  }
1499 
1500  // beam the missile
1501  ship->SetDirectorInfo(Game::GetText("ai.evade-missile"));
1502 
1503  Point beam_line = threat_missile->Velocity().cross(Point(0,1,0));
1504  beam_line.Normalize();
1505  beam_line *= 1e6;
1506 
1507  Point evade_p;
1508  Point evade_w1 = threat_missile->Location() + beam_line;
1509  Point evade_w2 = threat_missile->Location() - beam_line;
1510 
1511  double d1 = Point(evade_w1 - ship->Location()).length();
1512  double d2 = Point(evade_w2 - ship->Location()).length();
1513 
1514  if (d1 > d2)
1515  evade_p = Transform(evade_w1);
1516  else
1517  evade_p = Transform(evade_w2);
1518 
1519  return Seek(evade_p);
1520  }
1521 
1522  // GENERAL THREAT EVASION:
1523  if (threat && !form_up) {
1524  double threat_range = 20e3;
1525 
1526  Ship* threat_ship = (Ship*) threat;
1527  double threat_dist = Point(threat->Location() - ship->Location()).length();
1528 
1529  if (threat_ship->IsStarship()) {
1530  threat_range = CalcDefensePerimeter(threat_ship);
1531  }
1532 
1533  if (threat_dist <= threat_range) {
1534  ship->SetDirectorInfo(Game::GetText("ai.evade-threat"));
1535 
1536  if (ship->IsAirborne()) {
1537  evading = true;
1538  Point beam_line = threat->Velocity().cross(Point(0,1,0));
1539  beam_line.Normalize();
1540  beam_line *= threat_range;
1541 
1542  Point evade_w = threat->Location() + beam_line;
1543  Point evade_p = Transform(evade_w);
1544 
1545  return Seek(evade_p);
1546  }
1547 
1548  else if (threat_ship->IsStarship()) {
1549  evading = true;
1550 
1551  if (target == threat_ship && threat_dist < threat_range / 4) {
1552  SetTarget(0);
1553  drop_time = 5;
1554  }
1555 
1556  if (!target) {
1557  ship->SetDirectorInfo(Game::GetText("ai.evade-starship"));
1558 
1559  // flee for three seconds:
1560  if ((ship->MissionClock() & 3) != 3) {
1561  return Flee(Transform(threat->Location()));
1562  }
1563 
1564  // jink for one second:
1565  else {
1566  if (Game::GameTime() - jink_time > 1500) {
1568  jink = Point(rand() - 16384,
1569  rand() - 16384,
1570  rand() - 16384) * 15e3;
1571  }
1572 
1573  Point evade_w = ship->Location() + jink;
1574  Point evade_p = Transform(evade_w);
1575 
1576  return Seek(evade_p);
1577  }
1578  }
1579 
1580  else {
1581  ship->SetDirectorInfo(Game::GetText("ai.evade-and-seek"));
1582 
1583  // seek for three seconds:
1584  if ((ship->MissionClock() & 3) < 3) {
1585  return Steer(); // no evasion
1586  }
1587 
1588  // jink for one second:
1589  else {
1590  if (Game::GameTime() - jink_time > 1000) {
1592  jink = Point(rand() - 16384,
1593  rand() - 16384,
1594  rand() - 16384);
1595  }
1596 
1597  Point evade_w = target->Location() + jink;
1598  Point evade_p = Transform(evade_w);
1599 
1600  return Seek(evade_p);
1601  }
1602  }
1603  }
1604 
1605  else {
1606  evading = true;
1607 
1608  if (target == threat) {
1609  if (target->Type() == SimObject::SIM_SHIP) {
1610  Ship* tgt_ship = (Ship*) target;
1611  if (tgt_ship->GetTrigger(0)) {
1612  SetTarget(0);
1613  drop_time = 3;
1614  }
1615  }
1616  }
1617 
1618  else if (target && threat_dist < threat_range / 2) {
1619  SetTarget(0);
1620  drop_time = 3;
1621  }
1622 
1623  if (target)
1624  ship->SetDirectorInfo(Game::GetText("ai.evade-and-seek"));
1625  else
1626  ship->SetDirectorInfo(Game::GetText("ai.random-evade"));
1627 
1628  // beam the threat
1629  Point beam_line = threat->Velocity().cross(Point(0,1,0));
1630  beam_line.Normalize();
1631  beam_line *= 1e6;
1632 
1633  Point evade_p;
1634  Point evade_w1 = threat->Location() + beam_line;
1635  Point evade_w2 = threat->Location() - beam_line;
1636 
1637  double d1 = Point(evade_w1 - ship->Location()).length();
1638  double d2 = Point(evade_w2 - ship->Location()).length();
1639 
1640  if (d1 > d2)
1641  evade_p = Transform(evade_w1);
1642  else
1643  evade_p = Transform(evade_w2);
1644 
1645  if (!target) {
1646  DWORD jink_rate = 400 + 200 * (3-ai_level);
1647 
1648  if (Game::GameTime() - jink_time > jink_rate) {
1650  jink = Point(rand() - 16384,
1651  rand() - 16384,
1652  rand() - 16384) * 2000;
1653  }
1654 
1655  evade_p += jink;
1656  }
1657 
1658  Steer steer = Seek(evade_p);
1659 
1660  if (target)
1661  return steer / 4;
1662 
1663  return steer;
1664  }
1665  }
1666  }
1667 
1668  return Steer();
1669 }
1670 
1671 // +--------------------------------------------------------------------+
1672 
1673 void
1675 {
1676  // if nothing to shoot at, forget it:
1677  if (!target || target->Integrity() < 1)
1678  return;
1679 
1680  // if the objective is a navpt or landing bay (not a target), then don't shoot!
1682  return;
1683 
1684  // object behind us, or too close:
1685  if (objective.z < 0 || distance < 4 * self->Radius())
1686  return;
1687 
1688  // compute the firing cone:
1689  double cross_section = 2 * target->Radius() / distance;
1690  double gun_basket = cross_section * 2;
1691 
1692  Weapon* primary = ship->GetPrimary();
1693  Weapon* secondary = ship->GetSecondary();
1694  const WeaponDesign* dsgn_primary = 0;
1695  const WeaponDesign* dsgn_secondary = 0;
1696  bool use_primary = true;
1697  Ship* tgt_ship = 0;
1698 
1699  if (target->Type() == SimObject::SIM_SHIP) {
1700  tgt_ship = (Ship*) target;
1701 
1702  if (tgt_ship->InTransition())
1703  return;
1704  }
1705 
1706  if (primary) {
1707  dsgn_primary = primary->Design();
1708 
1709  if (dsgn_primary->aim_az_max > 5*DEGREES && distance > dsgn_primary->max_range/2)
1710  gun_basket = cross_section * 4;
1711 
1712  gun_basket *= (3-ai_level);
1713 
1714  if (tgt_ship) {
1715  if (!primary->CanTarget(tgt_ship->Class()))
1716  use_primary = false;
1717 
1718  /*** XXX NEED TO SUBTARGET SYSTEMS IF TARGET IS STARSHIP...
1719  else if (tgt_ship->ShieldStrength() > 10)
1720  use_primary = false;
1721  ***/
1722  }
1723 
1724  if (use_primary) {
1725  // is target in the basket?
1726  double dx = fabs(objective.x / distance);
1727  double dy = fabs(objective.y / distance);
1728 
1729  if (primary->GetFiringOrders() == Weapon::MANUAL &&
1730  dx < gun_basket && dy < gun_basket &&
1731  distance > dsgn_primary->min_range &&
1732  distance < dsgn_primary->max_range &&
1733  !primary->IsBlockedFriendly())
1734  {
1735  ship->FirePrimary();
1736  }
1737  }
1738  }
1739 
1740  if (secondary && secondary->GetFiringOrders() == Weapon::MANUAL) {
1741  dsgn_secondary = secondary->Design();
1742 
1743  if (missile_time <= 0 && secondary->Ammo() && !secondary->IsBlockedFriendly()) {
1744  if (secondary->Locked() || !dsgn_secondary->self_aiming) {
1745  // is target in basket?
1746  Point tgt = AimTransform(target->Location());
1747  double tgt_range = tgt.Normalize();
1748 
1749  int factor = 2-ai_level;
1750  double s_range = 0.5 + 0.2 * factor;
1751  double s_basket = 0.3 + 0.2 * factor;
1752  double extra_time = 10 * factor * factor + 5;
1753 
1754  if (!dsgn_secondary->self_aiming)
1755  s_basket *= 0.33;
1756 
1757  if (tgt_ship) {
1758  if (tgt_ship->Class() == Ship::MINE) {
1759  extra_time = 10;
1760  s_range = 0.75;
1761  }
1762 
1763  else if (!tgt_ship->IsDropship()) {
1764  extra_time = 0.5 * factor + 0.5;
1765  s_range = 0.9;
1766  }
1767  }
1768 
1769  // is target in decent range?
1770  if (tgt_range < secondary->Design()->max_range * s_range) {
1771  double dx = fabs(tgt.x);
1772  double dy = fabs(tgt.y);
1773 
1774  if (dx < s_basket && dy < s_basket && tgt.z > 0) {
1775  if (ship->FireSecondary()) {
1776  missile_time = secondary->Design()->salvo_delay + extra_time;
1777 
1778  if (Game::GameTime() - last_call_time > 6000) {
1779  // call fox:
1780  int call = RadioMessage::FOX_3; // A2A
1781 
1782  if (secondary->CanTarget(Ship::GROUND_UNITS)) // AGM
1783  call = RadioMessage::FOX_1;
1784 
1785  else if (secondary->CanTarget(Ship::DESTROYER)) // ASM
1786  call = RadioMessage::FOX_2;
1787 
1790  }
1791  }
1792  }
1793  }
1794  }
1795  }
1796  }
1797 }
1798 
1799 // +--------------------------------------------------------------------+
1800 
1801 double
1803 {
1804  double perimeter = 15e3;
1805 
1806  if (starship) {
1807  ListIter<WeaponGroup> g_iter = starship->Weapons();
1808  while (++g_iter) {
1809  WeaponGroup* group = g_iter.value();
1810 
1811  ListIter<Weapon> w_iter = group->GetWeapons();
1812  while (++w_iter) {
1813  Weapon* weapon = w_iter.value();
1814 
1815  if (weapon->Ammo() &&
1816  weapon->GetTarget() == ship &&
1817  !weapon->IsBlockedFriendly()) {
1818 
1819  double range = weapon->Design()->max_range * 1.2;
1820  if (range > perimeter)
1821  perimeter = range;
1822  }
1823  }
1824  }
1825  }
1826 
1827  return perimeter;
1828 }
1829 
1830 
1831