Starshatter_Open
Open source Starshatter engine
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
FighterTacticalAI.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: FighterTacticalAI.cpp
7  AUTHOR: John DiCamillo
8 
9 
10  OVERVIEW
11  ========
12  Fighter-specific mid-level (tactical) AI class
13 */
14 
15 #include "MemDebug.h"
16 #include "FighterTacticalAI.h"
17 #include "ShipAI.h"
18 #include "Ship.h"
19 #include "ShipDesign.h"
20 #include "Shot.h"
21 #include "Element.h"
22 #include "Instruction.h"
23 #include "RadioMessage.h"
24 #include "Sensor.h"
25 #include "Contact.h"
26 #include "WeaponGroup.h"
27 #include "Drive.h"
28 #include "Sim.h"
29 #include "StarSystem.h"
30 
31 #include "Game.h"
32 
33 // +----------------------------------------------------------------------+
34 
35 const int WINCHESTER_FIGHTER = 0;
36 const int WINCHESTER_ASSAULT = 1;
37 const int WINCHESTER_STRIKE = 2;
38 const int WINCHESTER_STATIC = 3;
39 
40 // +----------------------------------------------------------------------+
41 
43 : TacticalAI(ai), secondary_selection_time(0)
44 {
45  for (int i = 0; i < 4; i++)
46  winchester[i] = false;
47 
48  ai_level = ai->GetAILevel();
49 
50  switch (ai_level) {
51  default:
52  case 2: THREAT_REACTION_TIME = 1000; break;
53  case 1: THREAT_REACTION_TIME = 3000; break;
54  case 0: THREAT_REACTION_TIME = 6000; break;
55  }
56 }
57 
58 
59 // +--------------------------------------------------------------------+
60 
62 { }
63 
64 // +--------------------------------------------------------------------+
65 
66 bool
68 {
70 
71  int order = Instruction::PATROL;
72  roe = FLEXIBLE;
73 
74  if (navpt) {
75  order = navpt->Action();
76 
77  switch (order) {
79  case Instruction::DOCK:
80  case Instruction::RTB: roe = NONE;
81  break;
82 
85  if (element_index > 1)
86  roe = DEFENSIVE;
87  break;
88 
91  break;
92 
94  if (element_index > 1)
95  roe = DEFENSIVE;
96  else
97  roe = DIRECTED;
98  break;
99 
100  case Instruction::RECON:
101  case Instruction::STRIKE:
103  break;
104 
105  case Instruction::PATROL:
107  break;
108 
109  default: break;
110  }
111 
112  if (order == Instruction::STRIKE) {
114 
115  if (IsStrikeComplete(navpt)) {
117  }
118  }
119 
120  else if (order == Instruction::ASSAULT) {
121  if (ship->GetSensorMode() == Sensor::GM)
123 
124  if (IsStrikeComplete(navpt)) {
126  }
127  }
128 
129  else {
130  if (ship->GetSensorMode() == Sensor::GM)
132  }
133  }
134 
135  switch (roe) {
136  case NONE: ship->SetDirectorInfo(Game::GetText("ai.none")); break;
137  case SELF_DEFENSIVE: ship->SetDirectorInfo(Game::GetText("ai.self-defensive")); break;
138  case DEFENSIVE: ship->SetDirectorInfo(Game::GetText("ai.defensive")); break;
139  case DIRECTED: ship->SetDirectorInfo(Game::GetText("ai.directed")); break;
140  case FLEXIBLE: ship->SetDirectorInfo(Game::GetText("ai.flexible")); break;
141  default: ship->SetDirectorInfo(Game::GetText("ai.default")); break;
142  }
143 
144  return (navpt != 0);
145 }
146 
147 // +--------------------------------------------------------------------+
148 
149 void
151 {
153 
154  SimObject* target = ship_ai->GetTarget();
155 
156  if (target && (target->Type() == SimObject::SIM_SHIP) &&
158  SelectSecondaryForTarget((Ship*) target);
160  }
161 }
162 
163 // +--------------------------------------------------------------------+
164 
165 void
167 {
168  Ship* potential_target = tgt;
169 
170  if (!tgt) {
171  // try to target one of the element's objectives
172  // (if it shows up in the contact list)
173 
174  Element* elem = ship->GetElement();
175 
176  if (elem) {
177  Instruction* objective = elem->GetTargetObjective();
178 
179  if (objective) {
180  SimObject* obj_sim_obj = objective->GetTarget();
181  Ship* obj_tgt = 0;
182 
183  if (obj_sim_obj && obj_sim_obj->Type() == SimObject::SIM_SHIP)
184  obj_tgt = (Ship*) obj_sim_obj;
185 
186  if (obj_tgt && ship->FindContact(obj_tgt))
187  potential_target = obj_tgt;
188  }
189  }
190  }
191 
192  if (!CanTarget(potential_target))
193  potential_target = 0;
194 
195  ship_ai->SetTarget(potential_target);
196  SelectSecondaryForTarget(potential_target);
197 }
198 
199 // +--------------------------------------------------------------------+
200 
201 void
203 {
204  // NON-COMBATANTS do not pick targets of opportunity:
205  if (ship->GetIFF() == 0)
206  return;
207 
208  Ship* potential_target = 0;
209  Shot* current_shot_target = 0;
210 
211  // pick the closest combatant ship with a different IFF code:
212  double target_dist = 1.0e15;
213  double min_dist = 5.0e3;
214 
215  // FIGHTERS are primarily anti-air platforms, but may
216  // also attack smaller starships:
217 
218  Ship* ward = 0;
219  if (element_index > 1)
220  ward = ship->GetLeader();
221 
222  // commit range for patrol/sweep is 80 Km
223  // (about 2 minutes for a fighter at max speed)
224  if (roe == FLEXIBLE || roe == AGRESSIVE)
225  target_dist = ship->Design()->commit_range;
226 
227  if (roe < FLEXIBLE)
228  target_dist = 0.5 * ship->Design()->commit_range;
229 
230  int class_limit = Ship::LCA;
231 
232  if (ship->Class() == Ship::ATTACK)
233  class_limit = Ship::DESTROYER;
234 
235  ListIter<Contact> c_iter = ship->ContactList();
236  while (++c_iter) {
237  Contact* contact = c_iter.value();
238  Ship* c_ship = contact->GetShip();
239  Shot* c_shot = contact->GetShot();
240  int c_iff = contact->GetIFF(ship);
241  bool rogue = false;
242 
243  if (c_ship)
244  rogue = c_ship->IsRogue();
245 
246  if (!rogue && (c_iff <= 0 || c_iff == ship->GetIFF() || c_iff == 1000))
247  continue;
248 
249  // reasonable target?
250  if (c_ship && c_ship->Class() <= class_limit && !c_ship->InTransition()) {
251  if (!rogue) {
252  SimObject* ttgt = c_ship->GetTarget();
253 
254  // if we are self-defensive, is this contact engaging us?
255  if (roe == SELF_DEFENSIVE && ttgt != ship)
256  continue;
257 
258  // if we are defending, is this contact engaging us or our ward?
259  if (roe == DEFENSIVE && ttgt != ship && ttgt != ward)
260  continue;
261  }
262 
263  // found an enemy, check distance:
264  double dist = (ship->Location() - c_ship->Location()).length();
265 
266  if (dist < 0.75 * target_dist) {
267 
268  // if on patrol, check target distance from navpoint:
269  if (roe == FLEXIBLE && navpt) {
270  double ndist = (navpt->Location().OtherHand() - c_ship->Location()).length();
271  if (ndist > 80e3)
272  continue;
273  }
274 
275  potential_target = c_ship;
276  target_dist = dist;
277  }
278  }
279 
280  else if (c_shot && c_shot->IsDrone()) {
281  // found an enemy shot, do we have enough time to engage?
282  if (c_shot->GetEta() < 10)
283  continue;
284 
285  // found an enemy shot, check distance:
286  double dist = (ship->Location() - c_shot->Location()).length();
287 
288  if (!current_shot_target) {
289  current_shot_target = c_shot;
290  target_dist = dist;
291  }
292 
293  // is this shot a better target than the one we've found?
294  else {
295  Ship* ward = ship_ai->GetWard();
296 
297  if ((c_shot->IsTracking(ward) && !current_shot_target->IsTracking(ward)) ||
298  (dist < target_dist)) {
299  current_shot_target = c_shot;
300  target_dist = dist;
301  }
302  }
303  }
304  }
305 
306  if (current_shot_target) {
307  ship_ai->SetTarget(current_shot_target);
308  }
309  else {
310  ship_ai->SetTarget(potential_target);
311  SelectSecondaryForTarget(potential_target);
312  }
313 }
314 
315 // +--------------------------------------------------------------------+
316 
317 int
319 {
320  weps.clear();
321 
322  if (tgt) {
324  while (++iter) {
325  WeaponGroup* w = iter.value();
326 
327  if (w->Ammo() && w->CanTarget(tgt->Class()))
328  weps.append(w);
329  }
330  }
331 
332  return weps.size();
333 }
334 
335 void
337 {
338  if (tgt) {
339  int wix = WINCHESTER_FIGHTER;
340 
341  if (tgt->IsGroundUnit()) wix = WINCHESTER_STRIKE;
342  else if (tgt->IsStatic()) wix = WINCHESTER_STATIC;
343  else if (tgt->IsStarship()) wix = WINCHESTER_ASSAULT;
344 
345  WeaponGroup* best = 0;
346  List<WeaponGroup> weps;
347 
348  if (ListSecondariesForTarget(tgt, weps)) {
349  winchester[wix] = false;
350 
351  // select best weapon for the job:
352  double range = (ship->Location() - tgt->Location()).length();
353  double best_range = 0;
354  double best_damage = 0;
355 
356  ListIter<WeaponGroup> iter = weps;
357  while (++iter) {
358  WeaponGroup* w = iter.value();
359 
360  if (!best) {
361  best = w;
362 
363  WeaponDesign* d = best->GetDesign();
364  best_range = d->max_range;
365  best_damage = d->damage * d->ripple_count;
366 
367  if (best_range < range)
368  best = 0;
369  }
370 
371  else {
372  WeaponDesign* d = w->GetDesign();
373  double w_range = d->max_range;
374  double w_damage = d->damage * d->ripple_count;
375 
376  if (w_range > range) {
377  if (w_range < best_range || w_damage > best_damage)
378  best = w;
379  }
380  }
381  }
382 
383  // now cycle weapons until you pick the best one:
384  WeaponGroup* current_missile = ship->GetSecondaryGroup();
385 
386  if (current_missile && best && current_missile != best) {
387  ship->CycleSecondary();
389 
390  while (m != current_missile && m != best) {
391  ship->CycleSecondary();
392  m = ship->GetSecondaryGroup();
393  }
394  }
395  }
396 
397  else {
398  winchester[wix] = true;
399 
400  // if we have NO weapons that can hit this target,
401  // just drop it:
402 
403  Weapon* primary = ship->GetPrimary();
404  if (!primary || !primary->CanTarget(tgt->Class())) {
405  ship_ai->DropTarget(3);
406  ship->DropTarget();
407  }
408  }
409 
410  if (tgt->IsGroundUnit())
412 
413  else if (ship->GetSensorMode() == Sensor::GM)
415  }
416 }
417 
418 // +--------------------------------------------------------------------+
419 
420 void
422 {
423  // find the formation delta:
424  int s = element_index - 1;
425  Point delta(5*s, 0, -5*s);
426 
427  // diamond:
428  if (formation == Instruction::DIAMOND) {
429  switch (element_index) {
430  case 2: delta = Point( 12, -1, -10); break;
431  case 3: delta = Point(-12, -1, -10); break;
432  case 4: delta = Point( 0, -2, -20); break;
433  }
434  }
435 
436  // spread:
437  if (formation == Instruction::SPREAD) {
438  switch (element_index) {
439  case 2: delta = Point( 15, 0, 0); break;
440  case 3: delta = Point(-15, 0, 0); break;
441  case 4: delta = Point(-30, 0, 0); break;
442  }
443  }
444 
445  // box:
446  if (formation == Instruction::BOX) {
447  switch (element_index) {
448  case 2: delta = Point(15, 0, 0); break;
449  case 3: delta = Point( 0, -2, -20); break;
450  case 4: delta = Point(15, -2, -20); break;
451  }
452  }
453 
454  // trail:
455  if (formation == Instruction::TRAIL) {
456  delta = Point(0, s, -20*s);
457  }
458 
459  ship_ai->SetFormationDelta(delta * ship->Radius() * 2);
460 }
461 
462 // +--------------------------------------------------------------------+
463 
464 void
466 {
467  // pick the closest contact on Threat Warning System:
468  Ship* threat_ship = 0;
469  Shot* threat_missile = 0;
470  double threat_dist = 1e9;
471 
472  ListIter<Contact> c_iter = ship->ContactList();
473 
474  while (++c_iter) {
475  Contact* contact = c_iter.value();
476 
477  if (contact->Threat(ship) &&
479 
480  double rng = contact->Range(ship);
481 
482  if (contact->GetShot()) {
483  threat_missile = contact->GetShot();
484  }
485 
486  else if (rng < threat_dist && contact->GetShip()) {
487  Ship* candidate = contact->GetShip();
488 
489  if (candidate->InTransition())
490  continue;
491 
492  if (candidate->IsStarship() && rng < 50e3) {
493  threat_ship = candidate;
494  threat_dist = rng;
495  }
496 
497  else if (candidate->IsDropship() && rng < 25e3) {
498  threat_ship = candidate;
499  threat_dist = rng;
500  }
501 
502  // static and ground units:
503  else if (rng < 30e3) {
504  threat_ship = candidate;
505  threat_dist = rng;
506  }
507  }
508  }
509  }
510 
511  ship_ai->SetThreat(threat_ship);
512  ship_ai->SetThreatMissile(threat_missile);
513 }
514 
515 // +--------------------------------------------------------------------+
516 
517 bool
519 {
520  // wingmen can not call a halt to a strike:
521  if (!ship || element_index > 1)
522  return false;
523 
524  // if there's nothing to shoot at, we must be done:
525  if (!instr || !instr->GetTarget() || instr->GetTarget()->Life() == 0 ||
526  instr->GetTarget()->Type() != SimObject::SIM_SHIP)
527  return true;
528 
529  // break off strike only when ALL weapons are expended:
530  // (remember to check all relevant wingmen)
531  Element* element = ship->GetElement();
532  Ship* target = (Ship*) instr->GetTarget();
533 
534  if (!element)
535  return true;
536 
537  for (int i = 0; i < element->NumShips(); i++) {
538  Ship* s = element->GetShip(i+1);
539 
540  if (!s || s->Integrity() < 25) // || (s->Location() - target->Location()).length() > 250e3)
541  continue;
542 
543  ListIter<WeaponGroup> g_iter = s->Weapons();
544  while (++g_iter) {
545  WeaponGroup* w = g_iter.value();
546 
547  if (w->Ammo() && w->CanTarget(target->Class())) {
548  ListIter<Weapon> w_iter = w->GetWeapons();
549 
550  while (++w_iter) {
551  Weapon* weapon = w_iter.value();
552 
553  if (weapon->Status() > System::CRITICAL)
554  return false;
555  }
556  }
557  }
558  }
559 
560  // still here? we must be done!
561  return true;
562 }