Starshatter_Open
Open source Starshatter engine
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
StarshipAI.cpp
Go to the documentation of this file.
1 /* Project Starshatter 5.0
2  Destroyer Studios LLC
3  Copyright © 1997-2007. All Rights Reserved.
4 
5  SUBSYSTEM: Stars.exe
6  FILE: StarshipAI.cpp
7  AUTHOR: John DiCamillo
8 
9 
10  OVERVIEW
11  ========
12  Starship (low-level) Artificial Intelligence class
13 */
14 
15 #include "MemDebug.h"
16 #include "StarshipAI.h"
17 #include "StarshipTacticalAI.h"
18 #include "Ship.h"
19 #include "ShipDesign.h"
20 #include "Element.h"
21 #include "Mission.h"
22 #include "Instruction.h"
23 #include "RadioMessage.h"
24 #include "Contact.h"
25 #include "WeaponGroup.h"
26 #include "Drive.h"
27 #include "Sim.h"
28 #include "StarSystem.h"
29 #include "FlightComp.h"
30 #include "Farcaster.h"
31 #include "QuantumDrive.h"
32 
33 #include "Game.h"
34 #include "Random.h"
35 
36 // +----------------------------------------------------------------------+
37 
39 : ShipAI(s), sub_select_time(0), subtarget(0), tgt_point_defense(false)
40 {
41  ai_type = STARSHIP;
42 
43  // signifies this ship is a dead hulk:
44  if (ship && ship->Design()->auto_roll < 0) {
45  Point torque(rand()-16000, rand()-16000, rand()-16000);
46  torque.Normalize();
47  torque *= ship->Mass() / 10;
48 
49  ship->SetFLCSMode(0);
50  if (ship->GetFLCS())
51  ship->GetFLCS()->PowerOff();
52 
53  ship->ApplyTorque(torque);
54  ship->SetVelocity(RandomDirection() * Random(20, 50));
55 
56  for (int i = 0; i < 64; i++) {
57  Weapon* w = ship->GetWeaponByIndex(i+1);
58  if (w)
59  w->DrainPower(0);
60  else
61  break;
62  }
63  }
64 
65  else {
66  tactical = new(__FILE__,__LINE__) StarshipTacticalAI(this);
67  }
68 
69  sub_select_time = Game::GameTime() + (DWORD) Random(0, 2000);
71 }
72 
73 
74 // +--------------------------------------------------------------------+
75 
77 { }
78 
79 // +--------------------------------------------------------------------+
80 
81 void
83 {
84  distance = 0;
85 
86  int order = ship->GetRadioOrders()->Action();
87 
88  if (order == RadioMessage::QUANTUM_TO ||
89  order == RadioMessage::FARCAST_TO) {
90 
93  return;
94  }
95 
96  bool hold = order == RadioMessage::WEP_HOLD ||
97  order == RadioMessage::FORM_UP;
98 
99  bool form = hold ||
100  (!order && !target) ||
101  (farcaster);
102 
103  // if not the element leader, stay in formation:
104  if (form && element_index > 1) {
105  ship->SetDirectorInfo(Game::GetText("ai.formation"));
106 
107  if (navpt && navpt->Action() == Instruction::LAUNCH) {
109  }
110  else {
111  navpt = 0;
113  }
114 
115  // transform into camera coords:
117  return;
118  }
119 
120  // under orders?
121  bool directed = false;
122  double threat_level = 0;
123  double support_level = 1;
124  Ship* ward = ship->GetWard();
125 
126  if (tactical) {
128  threat_level = tactical->ThreatLevel();
129  support_level = tactical->SupportLevel();
130  }
131 
132  // threat processing:
133  if (hold || !directed && threat_level >= 2*support_level) {
134 
135  // seek support:
136  if (support) {
137  double d_support = Point(support->Location() - ship->Location()).length();
138  if (d_support > 35e3) {
139  ship->SetDirectorInfo(Game::GetText("ai.regroup"));
142  return;
143  }
144  }
145 
146  // run away:
147  else if (threat && threat != target) {
148  ship->SetDirectorInfo(Game::GetText("ai.retreat"));
149  obj_w = ship->Location() + Point(ship->Location() - threat->Location()) * 100;
151  return;
152  }
153  }
154 
155  // weapons hold:
156  if (hold) {
157  if (navpt) {
158  ship->SetDirectorInfo(Game::GetText("ai.seek-navpt"));
160  }
161 
162  else if (patrol) {
163  ship->SetDirectorInfo(Game::GetText("ai.patrol"));
165  }
166 
167  else {
168  ship->SetDirectorInfo(Game::GetText("ai.holding"));
169  objective = Point();
170  }
171  }
172 
173  // normal processing:
174  else if (target) {
175  ship->SetDirectorInfo(Game::GetText("ai.seek-target"));
177  }
178 
179  else if (patrol) {
180  ship->SetDirectorInfo(Game::GetText("ai.patrol"));
182  }
183 
184  else if (ward) {
185  ship->SetDirectorInfo(Game::GetText("ai.seek-ward"));
187  }
188 
189  else if (navpt) {
190  ship->SetDirectorInfo(Game::GetText("ai.seek-navpt"));
192  }
193 
194  else if (rumor) {
195  ship->SetDirectorInfo(Game::GetText("ai.search"));
197  }
198 
199  else {
200  objective = Point();
201  }
202 
203  // transform into camera coords:
205 }
206 
207 // +--------------------------------------------------------------------+
208 
209 void
211 {
212  // signifies this ship is a dead hulk:
213  if (ship && ship->Design()->auto_roll < 0) {
214  ship->SetDirectorInfo(Game::GetText("ai.dead"));
215  return;
216  }
217 
218  accumulator.Clear();
219  magnitude = 0;
220 
221  hold = false;
222  if ((ship->GetElement() && ship->GetElement()->GetHoldTime() > 0) ||
223  (navpt && navpt->Status() == Instruction::COMPLETE && navpt->HoldTime() > 0))
224  hold = true;
225 
227 
228  if (!ship->GetDirectorInfo()) {
229  if (target)
230  ship->SetDirectorInfo(Game::GetText("ai.seek-target"));
231  else if (ship->GetWard())
232  ship->SetDirectorInfo(Game::GetText("ai.seek-ward"));
233  else
234  ship->SetDirectorInfo(Game::GetText("ai.patrol"));
235  }
236 
237  if (farcaster && distance < 25e3) {
239  }
240  else {
242 
243  if (!other && !hold)
245  }
246 
247  HelmControl();
248  ThrottleControl();
249  FireControl();
250  AdjustDefenses();
251 }
252 
253 // +--------------------------------------------------------------------+
254 
255 void
257 {
258  // signifies this ship is a dead hulk:
259  if (ship && ship->Design()->auto_roll < 0) {
260  return;
261  }
262 
263  double trans_x = 0;
264  double trans_y = 0;
265  double trans_z = 0;
266 
267  bool station_keeping = distance < 0;
268 
269  if (station_keeping) {
270  accumulator.brake = 1;
271  accumulator.stop = 1;
272 
273  ship->SetHelmPitch(0);
274  }
275 
276  else {
277  Element* elem = ship->GetElement();
278 
279  Ship* ward = ship->GetWard();
280  Ship* s_threat = 0;
281  if (threat && threat->Class() >= ship->Class())
282  s_threat = threat;
283 
284  if (other || target || ward || s_threat || navpt || patrol || farcaster || element_index > 1) {
286 
287  if (elem->Type() == Mission::FLIGHT_OPS) {
288  ship->SetHelmPitch(0);
289 
290  if (ship->NumInbound() > 0) {
292  }
293  }
294 
295  else if (accumulator.pitch > 60*DEGREES) {
297  }
298 
299  else if (accumulator.pitch < -60*DEGREES) {
300  ship->SetHelmPitch(-60*DEGREES);
301  }
302 
303  else {
305  }
306 
307  }
308  else {
309  ship->SetHelmPitch(0);
310  }
311  }
312 
313  ship->SetTransX(trans_x);
314  ship->SetTransY(trans_y);
315  ship->SetTransZ(trans_z);
316 
317  ship->ExecFLCSFrame();
318 }
319 
320 void
322 {
323  // signifies this ship is a dead hulk:
324  if (ship && ship->Design()->auto_roll < 0) {
325  return;
326  }
327 
328  // station keeping:
329 
330  if (distance < 0) {
331  old_throttle = 0;
332  throttle = 0;
333 
334  ship->SetThrottle(0);
335 
336  if (ship->GetFLCS())
337  ship->GetFLCS()->FullStop();
338 
339  return;
340  }
341 
342  // normal throttle processing:
343 
344  double ship_speed = ship->Velocity() * ship->Heading();
345  double brakes = 0;
346  Ship* ward = ship->GetWard();
347  Ship* s_threat = 0;
348 
349  if (threat && threat->Class() >= ship->Class())
350  s_threat = threat;
351 
352  if (target || s_threat) { // target pursuit, or retreat
353  throttle = 100;
354 
355  if (target && distance < 50e3) {
356  double closing_speed = ship_speed;
357 
358  if (target) {
359  Point delta = target->Location() - ship->Location();
360  delta.Normalize();
361 
362  closing_speed = ship->Velocity() * delta;
363  }
364 
365  if (closing_speed > 300) {
366  throttle = 30;
367  brakes = 0.25;
368  }
369  }
370 
371  throttle *= (1 - accumulator.brake);
372 
373  if (throttle < 1 && ship->GetFLCS() != 0)
374  ship->GetFLCS()->FullStop();
375  }
376 
377  else if (ward) { // escort, match speed of ward
378  double speed = ward->Velocity().length();
380 
381  if (speed == 0) {
382  double d = (ship->Location() - ward->Location()).length();
383 
384  if (d > 30e3)
385  speed = (d - 30e3) / 100;
386  }
387 
388  if (speed > 0) {
389  if (ship_speed > speed) {
390  throttle = old_throttle - 1;
391  brakes = 0.2;
392  }
393  else if (ship_speed < speed - 10) {
394  throttle = old_throttle + 1;
395  }
396  }
397  else {
398  throttle = 0;
399  brakes = 0.5;
400  }
401  }
402 
403  else if (patrol || farcaster) { // seek patrol point
404  throttle = 100;
405 
406  if (distance < 10 * ship_speed) {
407  if (ship->Velocity().length() > 200)
408  throttle = 5;
409  else
410  throttle = 50;
411  }
412  }
413 
414  else if (navpt) { // lead only, get speed from navpt
415  double speed = navpt->Speed();
417 
418  if (hold) {
419  throttle = 0;
420  brakes = 1;
421  }
422 
423  else {
424  if (speed <= 0)
425  speed = 300;
426 
427  if (ship_speed > speed) {
428  if (throttle > 0 && old_throttle > 1)
429  throttle = old_throttle - 1;
430 
431  brakes = 0.25;
432  }
433  else if (ship_speed < speed - 10) {
434  throttle = old_throttle + 1;
435  }
436  }
437  }
438 
439  else if (element_index > 1) { // wingman
440  Ship* lead = ship->GetElement()->GetShip(1);
441  double lv = lead->Velocity().length();
442  double sv = ship_speed;
443  double dv = lv-sv;
444  double dt = 0;
445 
446  if (dv > 0) dt = dv * 1e-2 * seconds;
447  else if (dv < 0) dt = dv * 1e-2 * seconds;
448 
449  throttle = old_throttle + dt;
450  }
451 
452  else {
453  throttle = 0;
454  }
455 
458 
459  if (ship_speed > 1 && brakes > 0)
460  ship->SetTransY(-brakes * ship->Design()->trans_y);
461 
462  else if (throttle > 10 && (ship->GetEMCON() < 2 || ship->GetFuelLevel() < 10))
464 }
465 
466 // +--------------------------------------------------------------------+
467 
468 Steer
470 {
471  if (navpt) {
472  SimRegion* self_rgn = ship->GetRegion();
473  SimRegion* nav_rgn = navpt->Region();
474  QuantumDrive* qdrive = ship->GetQuantumDrive();
475 
476  if (self_rgn && !nav_rgn) {
477  nav_rgn = self_rgn;
478  navpt->SetRegion(nav_rgn);
479  }
480 
481  bool use_farcaster = self_rgn != nav_rgn &&
482  (navpt->Farcast() ||
483  !qdrive ||
484  !qdrive->IsPowerOn() ||
485  qdrive->Status() < System::DEGRADED
486  );
487 
488  if (use_farcaster) {
489  if (!farcaster) {
490  ListIter<Ship> s = self_rgn->Ships();
491  while (++s && !farcaster) {
492  if (s->GetFarcaster()) {
493  const Ship* dest = s->GetFarcaster()->GetDest();
494  if (dest && dest->GetRegion() == nav_rgn) {
495  farcaster = s->GetFarcaster();
496  }
497  }
498  }
499  }
500 
501  if (farcaster) {
502  if (farcaster->GetShip()->GetRegion() != self_rgn)
504 
505  obj_w = farcaster->EndPoint();
506  distance = Point(obj_w - ship->Location()).length();
507 
508  if (distance < 1000)
509  farcaster = 0;
510  }
511  }
512  else if (self_rgn != nav_rgn) {
514 
515  if (q) {
518  q->Engage();
519  }
520  }
521  }
522  }
523 
524  return ShipAI::SeekTarget();
525 }
526 
527 // +--------------------------------------------------------------------+
528 
529 Steer
531 {
532  if (!ship || ship->Velocity().length() < 25)
533  return Steer();
534 
535  return ShipAI::AvoidCollision();
536 }
537 
538 // +--------------------------------------------------------------------+
539 
540 void
542 {
543  // identify unknown contacts:
544  if (identify) {
545  if (fabs(ship->GetHelmHeading() - ship->CompassHeading()) < 10*DEGREES) {
546  Contact* contact = ship->FindContact(target);
547 
548  if (contact && !contact->ActLock()) {
549  if (!ship->GetProbe()) {
550  ship->LaunchProbe();
551  }
552  }
553  }
554 
555  return;
556  }
557 
558  // investigate last known location of enemy ship:
559  if (rumor && !target && ship->GetProbeLauncher() && !ship->GetProbe()) {
560  // is rumor in basket?
561  Point rmr = Transform(rumor->Location());
562  rmr.Normalize();
563 
564  double dx = fabs(rmr.x);
565  double dy = fabs(rmr.y);
566 
567  if (dx < 10*DEGREES && dy < 10*DEGREES && rmr.z > 0) {
568  ship->LaunchProbe();
569  }
570  }
571 
572  // Corvettes and Frigates are anti-air platforms. They need to
573  // target missile threats even when the threat is aimed at another
574  // friendly ship. Forward facing weapons must be on auto fire,
575  // while lateral and aft facing weapons are set to point defense.
576 
577  if (ship->Class() == Ship::CORVETTE || ship->Class() == Ship::FRIGATE) {
579  while (++iter) {
580  WeaponGroup* group = iter.value();
581 
582  ListIter<Weapon> w_iter = group->GetWeapons();
583  while (++w_iter) {
584  Weapon* weapon = w_iter.value();
585 
586  double az = weapon->GetAzimuth();
587  if (fabs(az) < 45*DEGREES) {
588  weapon->SetFiringOrders(Weapon::AUTO);
589  weapon->SetTarget(target, 0);
590  }
591 
592  else {
594  }
595  }
596  }
597  }
598 
599  // All other starships are free to engage ship targets. Weapon
600  // fire control is managed by the type of weapon.
601 
602  else {
603  System* subtgt = SelectSubtarget();
604 
606  while (++iter) {
607  WeaponGroup* weapon = iter.value();
608 
609  if (weapon->GetDesign()->target_type & Ship::DROPSHIPS) { // anti-air weapon?
611  }
612  else if (weapon->IsDrone()) { // torpedoes
614  weapon->SetTarget(target, 0);
615 
616  if (target && target->GetRegion() == ship->GetRegion()) {
617  Point delta = target->Location() - ship->Location();
618  double range = delta.length();
619 
620  if (range < weapon->GetDesign()->max_range * 0.9 &&
622  weapon->SetFiringOrders(Weapon::AUTO);
623 
624  else if (range < weapon->GetDesign()->max_range * 0.5)
625  weapon->SetFiringOrders(Weapon::AUTO);
626  }
627  }
628  else { // anti-ship weapon
629  weapon->SetFiringOrders(Weapon::AUTO);
630  weapon->SetTarget(target, subtgt);
631  weapon->SetSweep(subtgt ? Weapon::SWEEP_NONE : Weapon::SWEEP_TIGHT);
632  }
633  }
634  }
635 }
636 
637 // +--------------------------------------------------------------------+
638 
639 System*
641 {
642  if (Game::GameTime() - sub_select_time < 2345)
643  return subtarget;
644 
645  subtarget = 0;
646 
647  if (!target || target->Type() != SimObject::SIM_SHIP || GetAILevel() < 1)
648  return subtarget;
649 
650  Ship* tgt_ship = (Ship*) target;
651 
652  if (!tgt_ship->IsStarship())
653  return subtarget;
654 
655  Weapon* subtgt = 0;
656  double dist = 50e3;
657  Point svec = ship->Location() - tgt_ship->Location();
658 
660 
661  // first pass: turrets
662  ListIter<WeaponGroup> g_iter = tgt_ship->Weapons();
663  while (++g_iter) {
664  WeaponGroup* g = g_iter.value();
665 
666  if (g->GetDesign() && g->GetDesign()->turret_model) {
667  ListIter<Weapon> w_iter = g->GetWeapons();
668  while (++w_iter) {
669  Weapon* w = w_iter.value();
670 
671  if (w->Availability() < 35)
672  continue;
673 
674  if (w->GetAimVector() * svec < 0)
675  continue;
676 
677  if (w->GetTurret()) {
678  Point tloc = w->GetTurret()->Location();
679  Point delta = tloc - ship->Location();
680  double dlen = delta.length();
681 
682  if (dlen < dist) {
683  subtgt = w;
684  dist = dlen;
685  }
686  }
687  }
688  }
689  }
690 
691  // second pass: major weapons
692  if (!subtgt) {
693  g_iter.reset();
694  while (++g_iter) {
695  WeaponGroup* g = g_iter.value();
696 
697  if (g->GetDesign() && !g->GetDesign()->turret_model) {
698  ListIter<Weapon> w_iter = g->GetWeapons();
699  while (++w_iter) {
700  Weapon* w = w_iter.value();
701 
702  if (w->Availability() < 35)
703  continue;
704 
705  if (w->GetAimVector() * svec < 0)
706  continue;
707 
708  Point tloc = w->MountLocation();
709  Point delta = tloc - ship->Location();
710  double dlen = delta.length();
711 
712  if (dlen < dist) {
713  subtgt = w;
714  dist = dlen;
715  }
716  }
717  }
718  }
719  }
720 
721  subtarget = subtgt;
722  return subtarget;
723 }
724 
725 // +--------------------------------------------------------------------+
726 
727 bool
729 {
730  if (Game::GameTime() - point_defense_time < 3500)
731  return tgt_point_defense;
732 
733  tgt_point_defense = false;
734 
735  if (!target || target->Type() != SimObject::SIM_SHIP || GetAILevel() < 2)
736  return tgt_point_defense;
737 
738  Ship* tgt_ship = (Ship*) target;
739 
740  if (!tgt_ship->IsStarship())
741  return tgt_point_defense;
742 
743  Weapon* subtgt = 0;
744  Point svec = ship->Location() - tgt_ship->Location();
745 
747 
748  // first pass: turrets
749  ListIter<WeaponGroup> g_iter = tgt_ship->Weapons();
750  while (++g_iter && !tgt_point_defense) {
751  WeaponGroup* g = g_iter.value();
752 
753  if (g->CanTarget(1)) {
754  ListIter<Weapon> w_iter = g->GetWeapons();
755  while (++w_iter && !tgt_point_defense) {
756  Weapon* w = w_iter.value();
757 
758  if (w->Availability() > 35 && w->GetAimVector() * svec > 0)
759  tgt_point_defense = true;
760  }
761  }
762  }
763 
764  return tgt_point_defense;
765 }
766 
767 
768 // +--------------------------------------------------------------------+
769 
770 Point
772 {
773  return point - self->Location();
774 }
775 
776 Steer
777 StarshipAI::Seek(const Point& point)
778 {
779  // the point is in relative world coordinates
780  // x: distance east(-) / west(+)
781  // y: altitude down(-) / up(+)
782  // z: distance north(-) / south(+)
783 
784  Steer result;
785 
786  result.yaw = atan2(point.x, point.z) + PI;
787 
788  double adjacent = sqrt(point.x*point.x + point.z*point.z);
789  if (fabs(point.y) > ship->Radius() && adjacent > ship->Radius())
790  result.pitch = atan(point.y / adjacent);
791 
792  if (!_finite(result.yaw))
793  result.yaw = 0;
794 
795  if (!_finite(result.pitch))
796  result.pitch = 0;
797 
798  return result;
799 }
800 
801 Steer
802 StarshipAI::Flee(const Point& point)
803 {
804  Steer result = Seek(point);
805  result.yaw += PI;
806  return result;
807 }
808 
809 Steer
810 StarshipAI::Avoid(const Point& point, float radius)
811 {
812  Steer result = Seek(point);
813 
814  if (point * ship->BeamLine() > 0)
815  result.yaw -= PI/2;
816  else
817  result.yaw += PI/2;
818 
819  return result;
820 }
821 
822