Starshatter_Open
Open source Starshatter engine
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
TacticalAI.cpp
Go to the documentation of this file.
1 /* Project Starshatter 4.5
2  Destroyer Studios LLC
3  Copyright (C) 1997-2004. All Rights Reserved.
4 
5  SUBSYSTEM: Stars.exe
6  FILE: TacticalAI.cpp
7  AUTHOR: John DiCamillo
8 
9 
10  OVERVIEW
11  ========
12  Generic Ship Tactical Level AI class
13 */
14 
15 #include "MemDebug.h"
16 #include "TacticalAI.h"
17 #include "ShipAI.h"
18 #include "CarrierAI.h"
19 #include "Ship.h"
20 #include "ShipDesign.h"
21 #include "Element.h"
22 #include "Instruction.h"
23 #include "RadioMessage.h"
24 #include "RadioTraffic.h"
25 #include "Contact.h"
26 #include "WeaponGroup.h"
27 #include "Drive.h"
28 #include "Hangar.h"
29 #include "Sim.h"
30 #include "Shot.h"
31 #include "Drone.h"
32 #include "StarSystem.h"
33 
34 #include "Game.h"
35 #include "Random.h"
36 
37 // +----------------------------------------------------------------------+
38 
39 static int exec_time_seed = 0;
40 
41 // +----------------------------------------------------------------------+
42 
44 : ship(0), ship_ai(0), carrier_ai(0), navpt(0), orders(0),
45 action(0), threat_level(0), support_level(1),
46 directed_tgtid(0)
47 {
48  if (ai) {
49  ship_ai = ai;
50  ship = ai->GetShip();
51 
52  Sim* sim = Sim::GetSim();
53 
54  if (ship && ship->GetHangar() && ship->GetCommandAILevel() > 0 &&
55  ship != sim->GetPlayerShip())
56  carrier_ai = new(__FILE__,__LINE__) CarrierAI(ship, ship_ai->GetAILevel());
57  }
58 
59  agression = 0;
60  roe = FLEXIBLE;
61  element_index = 1;
62  exec_time = exec_time_seed;
63  exec_time_seed += 17;
64 }
65 
67 {
68  delete carrier_ai;
69 }
70 
71 // +--------------------------------------------------------------------+
72 
73 void
75 {
76  const int exec_period = 1000;
77 
78  if (!ship || !ship_ai)
79  return;
80 
83 
84  if ((int) Game::GameTime() - exec_time > exec_period) {
86 
87  CheckOrders();
88  SelectTarget();
89  FindThreat();
90  FindSupport();
91 
92  if (element_index > 1) {
93  int formation = 0;
94 
95  if (orders && orders->Formation() >= 0)
96  formation = orders->Formation();
97 
98  else if (navpt)
99  formation = navpt->Formation();
100 
101  FindFormationSlot(formation);
102  }
103 
105 
106  if (carrier_ai)
107  carrier_ai->ExecFrame(secs);
108 
109  exec_time += exec_period;
110  }
111 }
112 
113 // +--------------------------------------------------------------------+
114 
115 void
117 {
118  directed_tgtid = 0;
119 
120  if (CheckShipOrders())
121  return;
122 
123  if (CheckFlightPlan())
124  return;
125 
126  if (CheckObjectives())
127  return;
128 }
129 
130 // +--------------------------------------------------------------------+
131 
132 bool
134 {
135  return ProcessOrders();
136 }
137 
138 // +--------------------------------------------------------------------+
139 
140 bool
142 {
143  bool processed = false;
144  Ship* ward = 0;
145  Element* elem = ship->GetElement();
146 
147  if (elem) {
148  Instruction* obj = elem->GetTargetObjective();
149 
150  if (obj) {
151  ship_ai->ClearPatrol();
152 
153  if (obj->Action()) {
154  switch (obj->Action()) {
156  case Instruction::STRIKE:
158  {
159  SimObject* tgt = obj->GetTarget();
160  if (tgt && tgt->Type() == SimObject::SIM_SHIP) {
161  roe = DIRECTED;
162  SelectTargetDirected((Ship*) tgt);
163  }
164  }
165  break;
166 
167  case Instruction::DEFEND:
168  case Instruction::ESCORT:
169  {
170  SimObject* tgt = obj->GetTarget();
171  if (tgt && tgt->Type() == SimObject::SIM_SHIP) {
172  roe = DEFENSIVE;
173  ward = (Ship*) tgt;
174  }
175  }
176  break;
177 
178  default:
179  break;
180  }
181  }
182 
183  orders = obj;
184  processed = true;
185  }
186  }
187 
188  ship_ai->SetWard(ward);
189  return processed;
190 }
191 
192 // +--------------------------------------------------------------------+
193 
194 bool
196 {
197  if (ship_ai)
198  ship_ai->ClearPatrol();
199 
200  if (orders && orders->EMCON() > 0) {
201  int desired_emcon = orders->EMCON();
202 
204  desired_emcon = 3;
205 
206  if (ship->GetEMCON() != desired_emcon)
207  ship->SetEMCON(desired_emcon);
208  }
209 
210  if (orders && orders->Action()) {
211  switch (orders->Action()) {
215  {
216  bool tgt_ok = false;
217  SimObject* tgt = orders->GetTarget();
218 
219  if (tgt && tgt->Type() == SimObject::SIM_SHIP) {
220  Ship* tgt_ship = (Ship*) tgt;
221 
222  if (CanTarget(tgt_ship)) {
223  roe = DIRECTED;
224  SelectTargetDirected((Ship*) tgt);
225 
228  ship_ai->SetNavPoint(0);
229 
230  tgt_ok = true;
231  }
232  }
233 
234  if (!tgt_ok)
236  }
237  break;
238 
241  {
242  SimObject* tgt = orders->GetTarget();
243  if (tgt && tgt->Type() == SimObject::SIM_SHIP) {
244  roe = DEFENSIVE;
245  ship_ai->SetWard((Ship*) tgt);
246  ship_ai->SetNavPoint(0);
247  }
248  else {
250  }
251  }
252  break;
253 
255  roe = AGRESSIVE;
256  ship_ai->DropTarget(0.1);
257  break;
258 
261  roe = NONE;
262  ship_ai->DropTarget(5);
263  break;
264 
268  ship_ai->SetNavPoint(0);
269  ship_ai->DropTarget(Random(5, 10));
270  break;
271 
272  case RadioMessage::RTB:
274  roe = NONE;
275 
276  ship_ai->DropTarget(10);
277 
278  if (!ship->GetInbound()) {
279  RadioMessage* msg = 0;
280  Ship* controller = ship->GetController();
281 
283  controller = (Ship*) orders->GetTarget();
284  }
285 
286  if (!controller) {
287  Element* elem = ship->GetElement();
288  if (elem && elem->GetCommander()) {
289  Element* cmdr = elem->GetCommander();
290  controller = cmdr->GetShip(1);
291  }
292  }
293 
294  if (controller && controller->GetHangar() &&
295  controller->GetHangar()->CanStow(ship)) {
296  SimRegion* self_rgn = ship->GetRegion();
297  SimRegion* rtb_rgn = controller->GetRegion();
298 
299  if (self_rgn == rtb_rgn) {
300  double range = Point(controller->Location() - ship->Location()).length();
301 
302  if (range < 50e3) {
303  msg = new(__FILE__,__LINE__) RadioMessage(controller, ship, RadioMessage::CALL_INBOUND);
305  }
306  }
307  }
308  else {
310  }
311 
312  ship_ai->SetNavPoint(0);
313  }
314  break;
315 
318  roe = NONE;
319  ship_ai->DropTarget(10);
320  break;
321 
322  }
323 
324  action = orders->Action();
325  return true;
326  }
327 
328  // if we had an action before, this must be a "cancel orders"
329  else if (action) {
331  }
332 
333  return false;
334 }
335 
336 void
338 {
339  action = 0;
340  roe = FLEXIBLE;
341 
342  if (ship_ai)
343  ship_ai->DropTarget(0.1);
344 
345  if (ship)
347 
348 }
349 
350 // +--------------------------------------------------------------------+
351 
352 bool
354 {
355  Ship* ward = 0;
356 
357  // Find next Instruction:
359 
360  roe = FLEXIBLE;
361 
362  if (navpt) {
363  switch (navpt->Action()) {
364  case Instruction::LAUNCH:
365  case Instruction::DOCK:
366  case Instruction::RTB: roe = NONE;
367  break;
368 
370  break;
371 
372  case Instruction::DEFEND:
374  break;
375 
377  roe = DIRECTED;
378  break;
379 
380  case Instruction::RECON:
381  case Instruction::STRIKE:
383  break;
384 
385  case Instruction::PATROL:
387  break;
388 
389  default: break;
390  }
391 
392  if (roe == DEFENSIVE) {
393  SimObject* tgt = navpt->GetTarget();
394 
395  if (tgt && tgt->Type() == SimObject::SIM_SHIP)
396  ward = (Ship*) tgt;
397  }
398 
399 
400  if (navpt->EMCON() > 0) {
401  int desired_emcon = navpt->EMCON();
402 
404  desired_emcon = 3;
405 
406  if (ship->GetEMCON() != desired_emcon)
407  ship->SetEMCON(desired_emcon);
408  }
409  }
410 
411  if (ship_ai)
412  ship_ai->SetWard(ward);
413 
414  return (navpt != 0);
415 }
416 
417 // +--------------------------------------------------------------------+
418 
419 void
421 {
422  if (!ship) {
423  roe = NONE;
424  return;
425  }
426 
427  // unarmed vessels should never engage an enemy:
428  if (ship->Weapons().size() < 1)
429  roe = NONE;
430 
431  SimObject* target = ship_ai->GetTarget();
432  SimObject* ward = ship_ai->GetWard();
433 
434  // if not allowed to engage, drop and return:
435  if (roe == NONE) {
436  if (target)
437  ship_ai->DropTarget();
438  return;
439  }
440 
441  // if we have abandoned our ward, drop and return:
442  if (ward && roe != AGRESSIVE) {
443  double d = (ward->Location() - ship->Location()).length();
444  double safe_zone = 50e3;
445 
446  if (target) {
447  if (ship->IsStarship())
448  safe_zone = 100e3;
449 
450  if (d > safe_zone) {
451  ship_ai->DropTarget();
452  return;
453  }
454  }
455  else {
456  if (d > safe_zone) {
457  return;
458  }
459  }
460  }
461 
462  // already have a target, keep it:
463  if (target) {
464  if (target->Life()) {
465  CheckTarget();
466 
467  // frigates need to be ready to abandon ship-type targets
468  // in favor of drone-type targets, others should just go
469  // with what they have:
470  if (ship->Class() != Ship::CORVETTE && ship->Class() != Ship::FRIGATE)
471  return;
472 
473  // in case the check decided to drop the target:
474  target = ship_ai->GetTarget();
475  }
476 
477  // if the old target is dead, forget it:
478  else {
479  ship_ai->DropTarget();
480  target = 0;
481  }
482  }
483 
484  // if not allowed to acquire, forget it:
485  if (ship_ai->DropTime() > 0)
486  return;
487 
488  if (roe == DIRECTED) {
489  if (target && target->Type() == SimObject::SIM_SHIP)
490  SelectTargetDirected((Ship*) target);
491  else if (navpt && navpt->GetTarget() && navpt->GetTarget()->Type() == SimObject::SIM_SHIP)
493  else
495  }
496 
497  else {
499 
500  // don't switch one ship target for another...
501  if (ship->Class() == Ship::CORVETTE || ship->Class() == Ship::FRIGATE) {
502  SimObject* potential_target = ship_ai->GetTarget();
503  if (target && potential_target && target != potential_target) {
504  if (target->Type() == SimObject::SIM_SHIP &&
505  potential_target->Type() == SimObject::SIM_SHIP) {
506 
507  ship_ai->SetTarget(target);
508  }
509  }
510  }
511  }
512 }
513 
514 // +--------------------------------------------------------------------+
515 
516 void
518 {
519  Ship* potential_target = tgt;
520 
521  // try to target one of the element's objectives
522  // (if it shows up in the contact list)
523 
524  if (!tgt) {
525  Element* elem = ship->GetElement();
526 
527  if (elem) {
528  Instruction* objective = elem->GetTargetObjective();
529 
530  if (objective) {
531  SimObject* obj_sim_obj = objective->GetTarget();
532  Ship* obj_tgt = 0;
533 
534  if (obj_sim_obj && obj_sim_obj->Type() == SimObject::SIM_SHIP)
535  obj_tgt = (Ship*) obj_sim_obj;
536 
537  if (obj_tgt) {
538  ListIter<Contact> contact = ship->ContactList();
539  while (++contact && !potential_target) {
540  Ship* test = contact->GetShip();
541 
542  if (obj_tgt == test) {
543  potential_target = test;
544  }
545  }
546  }
547  }
548  }
549  }
550 
551  if (!CanTarget(potential_target))
552  potential_target = 0;
553 
554  ship_ai->SetTarget(potential_target);
555 
556  if (tgt && tgt == ship_ai->GetTarget())
557  directed_tgtid = tgt->Identity();
558  else
559  directed_tgtid = 0;
560 }
561 
562 // +--------------------------------------------------------------------+
563 
564 bool
566 {
567  bool result = false;
568 
569  if (tgt && !tgt->InTransition()) {
570  if (tgt->IsRogue() || tgt->GetIFF() != ship->GetIFF())
571  result = true;
572  }
573 
574  return result;
575 }
576 
577 // +--------------------------------------------------------------------+
578 
579 void
581 {
582  // NON-COMBATANTS do not pick targets of opportunity:
583  if (ship->GetIFF() == 0)
584  return;
585 
586  SimObject* potential_target = 0;
587 
588  // pick the closest combatant ship with a different IFF code:
589  double target_dist = ship->Design()->commit_range;
590 
591  SimObject* ward = ship_ai->GetWard();
592 
593  // FRIGATES are primarily anti-air platforms, but may
594  // also attack smaller starships:
595 
596  if (ship->Class() == Ship::CORVETTE || ship->Class() == Ship::FRIGATE) {
597  Ship* current_ship_target = 0;
598  Shot* current_shot_target = 0;
599 
600  // if we are escorting a larger warship, it is good to attack
601  // the same target as our ward:
602 
603  if (ward) {
604  Ship* s = (Ship*) ward;
605 
606  if (s->Class() > ship->Class()) {
607  SimObject* obj = s->GetTarget();
608 
609  if (obj && obj->Type() == SimObject::SIM_SHIP) {
610  current_ship_target = (Ship*) obj;
611  target_dist = (ship->Location() - obj->Location()).length();
612  }
613  }
614  }
615 
616  ListIter<Contact> contact = ship->ContactList();
617  while (++contact) {
618  Ship* c_ship = contact->GetShip();
619  Shot* c_shot = contact->GetShot();
620 
621  if (!c_ship && !c_shot)
622  continue;
623 
624  int c_iff = contact->GetIFF(ship);
625  bool rogue = c_ship && c_ship->IsRogue();
626  bool tgt_ok = c_iff > 0 &&
627  c_iff != ship->GetIFF() &&
628  c_iff < 1000;
629 
630  if (rogue || tgt_ok) {
631  if (c_ship && c_ship != ship && !c_ship->InTransition()) {
632  if (c_ship->Class() < Ship::DESTROYER ||
633  (c_ship->Class() >= Ship::MINE && c_ship->Class() <= Ship::SWACS)) {
634  // found an enemy, check distance:
635  double dist = (ship->Location() - c_ship->Location()).length();
636 
637  if (dist < 0.75 * target_dist &&
638  (!current_ship_target || c_ship->Class() <= current_ship_target->Class())) {
639  current_ship_target = c_ship;
640  target_dist = dist;
641  }
642  }
643  }
644 
645  else if (c_shot) {
646  // found an enemy shot, is there enough time to engage?
647  if (c_shot->GetEta() < 3)
648  continue;
649 
650  // found an enemy shot, check distance:
651  double dist = (ship->Location() - c_shot->Location()).length();
652 
653  if (!current_shot_target) {
654  current_shot_target = c_shot;
655  target_dist = dist;
656  }
657 
658  // is this shot a better target than the one we've found?
659  else {
660  Ship* ward = ship_ai->GetWard();
661 
662  if ((c_shot->IsTracking(ward) || c_shot->IsTracking(ship)) &&
663  (!current_shot_target->IsTracking(ward) ||
664  !current_shot_target->IsTracking(ship))) {
665  current_shot_target = c_shot;
666  target_dist = dist;
667  }
668  else if (dist < target_dist) {
669  current_shot_target = c_shot;
670  target_dist = dist;
671  }
672  }
673  }
674  }
675  }
676 
677  if (current_shot_target)
678  potential_target = current_shot_target;
679  else
680  potential_target = current_ship_target;
681  }
682 
683  // ALL OTHER SHIP CLASSES ignore fighters and only engage
684  // other starships:
685 
686  else {
687  List<Ship> ward_threats;
688 
689  ListIter<Contact> contact = ship->ContactList();
690  while (++contact) {
691  Ship* c_ship = contact->GetShip();
692 
693  if (!c_ship)
694  continue;
695 
696  int c_iff = contact->GetIFF(ship);
697  bool rogue = c_ship->IsRogue();
698  bool tgt_ok = c_ship != ship &&
699  c_iff > 0 &&
700  c_iff != ship->GetIFF() &&
701  !c_ship->InTransition();
702 
703  if (rogue || tgt_ok) {
704  if (c_ship->IsStarship() || c_ship->IsStatic()) {
705  // found an enemy, check distance:
706  double dist = (ship->Location() - c_ship->Location()).length();
707 
708  if (dist < 0.75 * target_dist) {
709  potential_target = c_ship;
710  target_dist = dist;
711  }
712 
713  if (ward && c_ship->IsTracking(ward)) {
714  ward_threats.append(c_ship);
715  }
716  }
717  }
718  }
719 
720  // if this ship is protecting a ward,
721  // prefer targets that are threatening that ward:
722  if (potential_target && ward_threats.size() && !ward_threats.contains((Ship*)potential_target)) {
723  target_dist *= 2;
724 
725  ListIter<Ship> iter = ward_threats;
726  while (++iter) {
727  Ship* threat = iter.value();
728 
729  double dist = (ward->Location() - threat->Location()).length();
730 
731  if (dist < target_dist) {
732  potential_target = threat;
733  target_dist = dist;
734  }
735  }
736  }
737  }
738 
739  if (ship->Class() != Ship::CARRIER)
740  ship_ai->SetTarget(potential_target);
741 }
742 
743 // +--------------------------------------------------------------------+
744 
745 void
747 {
748  SimObject* tgt = ship_ai->GetTarget();
749 
750  if (!tgt) return;
751 
752  if (tgt->GetRegion() != ship->GetRegion()) {
753  ship_ai->DropTarget();
754  return;
755  }
756 
757  if (tgt->Type() == SimObject::SIM_SHIP) {
758  Ship* target = (Ship*) tgt;
759 
760  // has the target joined our side?
761  if (target->GetIFF() == ship->GetIFF() && !target->IsRogue()) {
762  ship_ai->DropTarget();
763  return;
764  }
765 
766  // is the target already jumping/breaking/dying?
767  if (target->InTransition()) {
768  ship_ai->DropTarget();
769  return;
770  }
771 
772  // have we been ordered to pursue the target?
773  if (directed_tgtid) {
774  if (directed_tgtid != target->Identity()) {
775  ship_ai->DropTarget();
776  }
777 
778  return;
779  }
780 
781  // can we catch the target?
782  if (target->Design()->vlimit <= ship->Design()->vlimit ||
783  ship->Velocity().length() <= ship->Design()->vlimit)
784  return;
785 
786  // is the target now out of range?
787  WeaponDesign* wep_dsn = ship->GetPrimaryDesign();
788  if (!wep_dsn)
789  return;
790 
791  // compute the "give up" range:
792  double drop_range = 3 * wep_dsn->max_range;
793  if (drop_range > 0.75 * ship->Design()->commit_range)
794  drop_range = 0.75 * ship->Design()->commit_range;
795 
796  double range = Point(target->Location() - ship->Location()).length();
797  if (range < drop_range)
798  return;
799 
800  // is the target closing or separating?
801  Point delta = (target->Location() + target->Velocity()) -
802  (ship->Location() + ship->Velocity());
803 
804  if (delta.length() < range)
805  return;
806 
807  ship_ai->DropTarget();
808  }
809 
810  else if (tgt->Type() == SimObject::SIM_DRONE) {
811  Drone* drone = (Drone*) tgt;
812 
813  // is the target still a threat?
814  if (drone->GetEta() < 1 || drone->GetTarget() == 0)
815  ship_ai->DropTarget();
816  }
817 }
818 
819 // +--------------------------------------------------------------------+
820 
821 void
823 {
824  // pick the closest contact on Threat Warning System:
825  Ship* threat = 0;
826  Shot* threat_missile = 0;
827  Ship* rumor = 0;
828  double threat_dist = 1e9;
829  const DWORD THREAT_REACTION_TIME = 1000; // 1 second
830 
832 
833  while (++iter) {
834  Contact* contact = iter.value();
835 
836  if (contact->Threat(ship) &&
837  (Game::GameTime() - contact->AcquisitionTime()) > THREAT_REACTION_TIME) {
838 
839  if (contact->GetShot()) {
840  threat_missile = contact->GetShot();
841  rumor = (Ship*) threat_missile->Owner();
842  }
843  else {
844  double rng = contact->Range(ship);
845 
846  Ship* c_ship = contact->GetShip();
847  if (c_ship && !c_ship->InTransition() &&
848  c_ship->Class() != Ship::FREIGHTER &&
849  c_ship->Class() != Ship::FARCASTER) {
850 
851  if (c_ship->GetTarget() == ship) {
852  if (!threat || c_ship->Class() > threat->Class()) {
853  threat = c_ship;
854  threat_dist = 0;
855  }
856  }
857  else if (rng < threat_dist) {
858  threat = c_ship;
859  threat_dist = rng;
860  }
861  }
862  }
863  }
864  }
865 
866  if (rumor && !rumor->InTransition()) {
867  iter.reset();
868 
869  while (++iter) {
870  if (iter->GetShip() == rumor) {
871  rumor = 0;
872  ship_ai->ClearRumor();
873  break;
874  }
875  }
876  }
877  else {
878  rumor = 0;
879  ship_ai->ClearRumor();
880  }
881 
882  ship_ai->SetRumor(rumor);
883  ship_ai->SetThreat(threat);
884  ship_ai->SetThreatMissile(threat_missile);
885 }
886 
887 // +--------------------------------------------------------------------+
888 
889 void
891 {
892  if (!ship_ai->GetThreat()) {
893  ship_ai->SetSupport(0);
894  return;
895  }
896 
897  // pick the biggest friendly contact in the sector:
898  Ship* support = 0;
899  double support_dist = 1e9;
900 
901  ListIter<Contact> contact = ship->ContactList();
902 
903  while (++contact) {
904  if (contact->GetShip() && contact->GetIFF(ship) == ship->GetIFF()) {
905  Ship* c_ship = contact->GetShip();
906 
907  if (c_ship != ship && c_ship->Class() >= ship->Class() && !c_ship->InTransition()) {
908  if (!support || c_ship->Class() > support->Class())
909  support = c_ship;
910  }
911  }
912  }
913 
914  ship_ai->SetSupport(support);
915 }
916 
917 // +--------------------------------------------------------------------+
918 
919 void
921 {
922  // find the formation delta:
923  int s = element_index - 1;
924  Point delta(10*s, 0, 10*s);
925 
926  // diamond:
927  if (formation == Instruction::DIAMOND) {
928  switch (element_index) {
929  case 2: delta = Point( 10, 0, -12); break;
930  case 3: delta = Point(-10, 0, -12); break;
931  case 4: delta = Point( 0, 0, -24); break;
932  }
933  }
934 
935  // spread:
936  if (formation == Instruction::SPREAD) {
937  switch (element_index) {
938  case 2: delta = Point( 15, 0, 0); break;
939  case 3: delta = Point(-15, 0, 0); break;
940  case 4: delta = Point(-30, 0, 0); break;
941  }
942  }
943 
944  // box:
945  if (formation == Instruction::BOX) {
946  switch (element_index) {
947  case 2: delta = Point(15, 0, 0); break;
948  case 3: delta = Point( 0, -1, -15); break;
949  case 4: delta = Point(15, -1, -15); break;
950  }
951  }
952 
953  // trail:
954  if (formation == Instruction::TRAIL) {
955  delta = Point(0, 0, -15*s);
956  }
957 
958  ship_ai->SetFormationDelta(delta * ship->Radius() * 2);
959 }