Starshatter_Open
Open source Starshatter engine
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
ShipAI.cpp
Go to the documentation of this file.
1 /* Project Starshatter 5.0
2  Destroyer Studios LLC
3  Copyright (C) 1997-2007. All Rights Reserved.
4 
5  SUBSYSTEM: Stars.exe
6  FILE: ShipAI.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 "ShipAI.h"
17 #include "TacticalAI.h"
18 #include "Ship.h"
19 #include "ShipDesign.h"
20 #include "Shot.h"
21 #include "Element.h"
22 #include "NavLight.h"
23 #include "Instruction.h"
24 #include "RadioMessage.h"
25 #include "RadioTraffic.h"
26 #include "Contact.h"
27 #include "WeaponGroup.h"
28 #include "Drive.h"
29 #include "Shield.h"
30 #include "Sim.h"
31 #include "Player.h"
32 #include "StarSystem.h"
33 #include "FlightComp.h"
34 #include "Farcaster.h"
35 #include "QuantumDrive.h"
36 #include "Debris.h"
37 #include "Asteroid.h"
38 
39 #include "Game.h"
40 #include "Random.h"
41 
42 // +----------------------------------------------------------------------+
43 
45 : SteerAI(s),
46 support(0), rumor(0), threat(0), threat_missile(0), drop_time(0),
47 too_close(0), navpt(0), patrol(0), engaged_ship_id(0),
48 bracket(false), identify(false), hold(false), takeoff(false),
49 throttle(0), old_throttle(0), element_index(1), splash_count(0),
50 tactical(0), farcaster(0), ai_level(2), last_avoid_time(0),
51 last_call_time(0)
52 {
53  ship = (Ship*) self;
54 
55  Sim* sim = Sim::GetSim();
56  Ship* pship = sim->GetPlayerShip();
57  int player_team = 1;
58 
59  if (pship)
60  player_team = pship->GetIFF();
61 
62  Player* player = Player::GetCurrentPlayer();
63  if (player) {
64  if (ship && ship->GetIFF() && ship->GetIFF() != player_team) {
65  ai_level = player->AILevel();
66  }
67  else if (player->AILevel() == 0) {
68  ai_level = 1;
69  }
70  }
71 
72  // evil alien ships are *always* smart:
73  if (ship && ship->GetIFF() > 1 && ship->Design()->auto_roll > 1) {
74  ai_level = 2;
75  }
76 }
77 
78 
79 // +--------------------------------------------------------------------+
80 
82 {
83  delete tactical;
84 }
85 
86 void
88 {
89  delete tactical;
90  tactical = 0;
91 }
92 
93 // +--------------------------------------------------------------------+
94 
95 Ship*
97 {
98  return ship->GetWard();
99 }
100 
101 void
103 {
104  if (s == ship->GetWard())
105  return;
106 
107  if (ship)
108  ship->SetWard(s);
109 
110  Point form = RandomDirection();
111  form.SwapYZ();
112 
113  if (fabs(form.x) < 0.5) {
114  if (form.x < 0)
115  form.x = -0.5;
116  else
117  form.x = 0.5;
118  }
119 
120  if (ship && ship->IsStarship()) {
121  form *= 30e3;
122  }
123  else {
124  form *= 15e3;
125  form.y = 500;
126  }
127 
128  SetFormationDelta(form);
129 }
130 
131 void
133 {
134  if (support == s)
135  return;
136 
137  support = s;
138 
139  if (support)
140  Observe(support);
141 }
142 
143 void
145 {
146  if (!s || rumor == s)
147  return;
148 
149  rumor = s;
150 
151  if (rumor)
152  Observe(rumor);
153 }
154 
155 void
157 {
158  rumor = 0;
159 }
160 
161 void
163 {
164  if (threat == s)
165  return;
166 
167  threat = s;
168 
169  if (threat)
170  Observe(threat);
171 }
172 
173 void
175 {
176  if (threat_missile == s)
177  return;
178 
179  threat_missile = s;
180 
181  if (threat_missile)
183 }
184 
185 bool
187 {
188  if (obj == support)
189  support = 0;
190 
191  if (obj == threat)
192  threat = 0;
193 
194  if (obj == threat_missile)
195  threat_missile = 0;
196 
197  if (obj == rumor)
198  rumor = 0;
199 
200  return SteerAI::Update(obj);
201 }
202 
203 const char*
205 {
206  static char name[64];
207  sprintf_s(name, "ShipAI(%s)", self->Name());
208  return name;
209 }
210 
211 // +--------------------------------------------------------------------+
212 
213 Point
215 {
216  return patrol_loc;
217 }
218 
219 void
221 {
222  patrol = 1;
223  patrol_loc = p;
224 }
225 
226 void
228 {
229  patrol = 0;
230 }
231 
232 // +--------------------------------------------------------------------+
233 
234 void
235 ShipAI::ExecFrame(double secs)
236 {
237  seconds = secs;
238 
239  if (drop_time > 0) drop_time -= seconds;
240  if (!ship) return;
241 
242  ship->SetDirectorInfo(" ");
243 
244  // check to make sure current navpt is still valid:
245  if (navpt)
247 
249  takeoff = true;
250 
251  if (takeoff) {
252  FindObjective();
253  Navigator();
254 
255  if (ship->MissionClock() > 10000)
256  takeoff = false;
257 
258  return;
259  }
260 
261  // initial assessment:
262  if (ship->MissionClock() < 5000)
263  return;
264 
266 
267  NavlightControl();
268  CheckTarget();
269 
270  if (tactical)
272 
273  if (target && target != ship->GetTarget()) {
275 
276  // if able to lock target, and target is a ship (not a shot)...
277  if (target == ship->GetTarget() && target->Type() == SimObject::SIM_SHIP) {
278 
279  // if this isn't the same ship we last called out:
280  if (target->Identity() != engaged_ship_id && Game::GameTime() - last_call_time > 10000) {
281  // call engaging:
282  RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(ship->GetElement(), ship, RadioMessage::CALL_ENGAGING);
283  msg->AddTarget(target);
286 
288  }
289  }
290  }
291 
292  else if (!target) {
293  target = ship->GetTarget();
294 
295  if (engaged_ship_id && !target) {
296  engaged_ship_id = 0;
297 
298  /***
299  *** XXX
300  *** Not the right place to make this decision.
301  ***
302  *** There is a brief wait between killing a target and
303  *** selecting a new one, so this message is sent after
304  *** every kill.
305  ***
306  *** Need to track when the entire element has been
307  *** put down.
308 
309  if (element_index == 1) {
310  RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(ship->GetElement(), ship, RadioMessage::RESUME_MISSION);
311  RadioTraffic::Transmit(msg);
312  }
313 
314  ***
315  ***/
316  }
317  }
318 
319  FindObjective();
320  Navigator();
321 }
322 
323 // +--------------------------------------------------------------------+
324 
325 Point
327 {
328  if (ship && target) {
329  if (ship->GetPrimaryDesign()) {
331  Point delta = target->Location() - ship->Location();
332 
333  // fighters need to aim the ship so that the guns will hit the target
334  if (guns->firing_cone < 10*DEGREES && guns->max_range <= delta.length()) {
335  Point aim_vec = ship->Heading();
336  aim_vec.Normalize();
337 
338  Point shot_vel = ship->Velocity() + aim_vec * guns->speed;
339  return shot_vel - target->Velocity();
340  }
341 
342  // ships with turreted weapons just need to worry about actual closing speed
343  else {
344  return ship->Velocity() - target->Velocity();
345  }
346  }
347 
348  else {
349  return ship->Velocity();
350  }
351  }
352 
353  return Point(1,0,0);
354 }
355 
356 // +--------------------------------------------------------------------+
357 
358 void
360 {
361  distance = 0;
362 
363  int order = ship->GetRadioOrders()->Action();
364 
365  if (order == RadioMessage::QUANTUM_TO ||
366  order == RadioMessage::FARCAST_TO) {
367 
370  return;
371  }
372 
373  bool form = (order == RadioMessage::WEP_HOLD) ||
374  (order == RadioMessage::FORM_UP) ||
375  (order == RadioMessage::MOVE_PATROL) ||
376  (order == RadioMessage::RTB) ||
377  (order == RadioMessage::DOCK_WITH) ||
378  (!order && !target) ||
379  (farcaster);
380 
381  Ship* ward = ship->GetWard();
382 
383  // if not the element leader, stay in formation:
384  if (form && element_index > 1) {
385  ship->SetDirectorInfo(Game::GetText("ai.formation"));
386 
387  if (navpt && navpt->Action() == Instruction::LAUNCH) {
389  }
390  else {
391  navpt = 0;
393  }
394 
395  // transform into camera coords:
397  return;
398  }
399 
400  // under orders?
401  bool directed = false;
402  if (tactical)
404 
405  // threat processing:
406  if (threat && !directed) {
407  double d_threat = Point(threat->Location() - ship->Location()).length();
408 
409  // seek support:
410  if (support) {
411  double d_support = Point(support->Location() - ship->Location()).length();
412  if (d_support > 35e3) {
413  ship->SetDirectorInfo(Game::GetText("ai.regroup"));
416  return;
417  }
418  }
419 
420  // run away:
421  else if (threat != target) {
422  ship->SetDirectorInfo(Game::GetText("ai.retreat"));
423  obj_w = ship->Location() + Point(ship->Location() - threat->Location()) * 100;
425  return;
426  }
427  }
428 
429  // normal processing:
430  if (target) {
431  ship->SetDirectorInfo(Game::GetText("ai.seek-target"));
434  }
435 
436  else if (patrol) {
437  ship->SetDirectorInfo(Game::GetText("ai.patrol"));
440  }
441 
442  else if (ward) {
443  ship->SetDirectorInfo(Game::GetText("ai.seek-ward"));
446  }
447 
448  else if (navpt && form) {
449  ship->SetDirectorInfo(Game::GetText("ai.seek-navpt"));
452  }
453 
454  else if (rumor) {
455  ship->SetDirectorInfo(Game::GetText("ai.search"));
458  }
459 
460  else {
461  obj_w = Point();
462  objective = Point();
463  }
464 }
465 
466 // +--------------------------------------------------------------------+
467 
468 void
470 {
471  if (!tgt) {
472  obj_w = Point();
473  return;
474  }
475 
476  navpt = 0; // this tells fire control that we are chasing a target,
477  // instead of a navpoint!
478 
479  Point cv = ClosingVelocity();
480  double cvl = cv.length();
481  double time = 0;
482 
483  if (cvl > 50) {
484  // distance from self to target:
485  distance = Point(tgt->Location() - self->Location()).length();
486 
487  // time to reach target:
488  time = distance / cvl;
489 
490  // where the target will be when we reach it:
491  if (time < 15) {
492  Point run_vec = tgt->Velocity();
493  obj_w = tgt->Location() + (run_vec * time);
494 
495 
496  if (time < 10)
497  obj_w += (tgt->Acceleration() * 0.33 * time * time);
498  }
499  else {
500  obj_w = tgt->Location();
501  }
502  }
503 
504  else {
505  obj_w = tgt->Location();
506  }
507 
508  distance = Point(obj_w - self->Location()).length();
509 
510  if (cvl > 50) {
511  time = distance / cvl;
512 
513  // where we will be when the target gets there:
514  if (time < 15) {
515  Point self_dest = self->Location() + cv * time;
516  Point err = obj_w - self_dest;
517 
518  obj_w += err;
519  }
520  }
521 
522  Point approach = obj_w - self->Location();
523  distance = approach.length();
524 
525  if (bracket && distance > 25e3) {
526  Point offset = approach.cross(Point(0,1,0));
527  offset.Normalize();
528  offset *= 15e3;
529 
530  Ship* s = (Ship*) self;
531  if (s->GetElementIndex() & 1)
532  obj_w -= offset;
533  else
534  obj_w += offset;
535  }
536 }
537 
538 // +--------------------------------------------------------------------+
539 
540 void
542 {
543  navpt = 0;
544 
545  Point npt = patrol_loc;
546  obj_w = npt;
547 
548  // distance from self to navpt:
549  distance = Point(obj_w - self->Location()).length();
550 
551  if (distance < 1000) {
553  ClearPatrol();
554  }
555 }
556 
557 // +--------------------------------------------------------------------+
558 
559 void
561 {
562  SimRegion* self_rgn = ship->GetRegion();
563  SimRegion* nav_rgn = navpt->Region();
564  QuantumDrive* qdrive = ship->GetQuantumDrive();
565 
566  if (!self_rgn)
567  return;
568 
569  if (!nav_rgn) {
570  nav_rgn = self_rgn;
571  navpt->SetRegion(nav_rgn);
572  }
573 
574  bool use_farcaster = self_rgn != nav_rgn &&
575  (navpt->Farcast() ||
576  !qdrive ||
577  !qdrive->IsPowerOn() ||
578  qdrive->Status() < System::DEGRADED
579  );
580 
581  if (use_farcaster) {
582  FindObjectiveFarcaster(self_rgn, nav_rgn);
583  }
584 
585  else {
586  if (farcaster) {
587  if (farcaster->GetShip()->GetRegion() != self_rgn)
589 
590  obj_w = farcaster->EndPoint();
591  }
592 
593  else {
594  // transform from starsystem to world coordinates:
595  Point npt = navpt->Region()->Location() + navpt->Location();
596 
597  SimRegion* active_region = ship->GetRegion();
598 
599  if (active_region)
600  npt -= active_region->Location();
601 
602  npt = npt.OtherHand();
603 
604  obj_w = npt;
605  }
606 
607  // distance from self to navpt:
608  distance = Point(obj_w - ship->Location()).length();
609 
610  if (farcaster && distance < 1000)
611  farcaster = 0;
612 
613  if (distance < 1000 || (navpt->Action() == Instruction::LAUNCH && distance > 25000))
615  }
616 }
617 
618 // +--------------------------------------------------------------------+
619 
620 void
622 {
623  Instruction* orders = ship->GetRadioOrders();
624  SimRegion* self_rgn = ship->GetRegion();
625  SimRegion* nav_rgn = orders->Region();
626  QuantumDrive* qdrive = ship->GetQuantumDrive();
627 
628  if (!self_rgn || !nav_rgn)
629  return;
630 
631  bool use_farcaster = self_rgn != nav_rgn &&
632  (orders->Farcast() ||
633  !qdrive ||
634  !qdrive->IsPowerOn() ||
635  qdrive->Status() < System::DEGRADED
636  );
637 
638  if (use_farcaster) {
639  FindObjectiveFarcaster(self_rgn, nav_rgn);
640  }
641 
642  else {
643  if (farcaster) {
644  if (farcaster->GetShip()->GetRegion() != self_rgn)
646 
647  obj_w = farcaster->EndPoint();
648  }
649 
650  else {
651  // transform from starsystem to world coordinates:
652  Point npt = orders->Region()->Location() + orders->Location();
653 
654  SimRegion* active_region = ship->GetRegion();
655 
656  if (active_region)
657  npt -= active_region->Location();
658 
659  npt = npt.OtherHand();
660 
661  obj_w = npt;
662 
663  if (qdrive && qdrive->ActiveState() == QuantumDrive::ACTIVE_READY) {
664  qdrive->SetDestination(nav_rgn, orders->Location());
665  qdrive->Engage();
666  return;
667  }
668  }
669 
670  // distance from self to navpt:
671  distance = Point(obj_w - ship->Location()).length();
672 
673  if (farcaster) {
674  if (distance < 1000) {
675  farcaster = 0;
677  }
678  }
679  else if (self_rgn == nav_rgn) {
681  }
682  }
683 }
684 
685 void
687 {
688  if (!farcaster) {
689  ListIter<Ship> s = src_rgn->Ships();
690  while (++s && !farcaster) {
691  if (s->GetFarcaster()) {
692  const Ship* dest = s->GetFarcaster()->GetDest();
693  if (dest && dest->GetRegion() == dst_rgn) {
694  farcaster = s->GetFarcaster();
695  }
696  }
697  }
698  }
699 
700  if (farcaster) {
701  Point apt = farcaster->ApproachPoint(0);
702  Point npt = farcaster->StartPoint();
703  double r1 = (ship->Location() - npt).length();
704 
705  if (r1 > 50e3) {
706  obj_w = apt;
707  distance = r1;
708  }
709 
710  else {
711  double r2 = (ship->Location() - apt).length();
712  double r3 = (npt - apt).length();
713 
714  if (r1+r2 < 1.2*r3) {
715  obj_w = npt;
716  distance = r1;
717  }
718  else {
719  obj_w = apt;
720  distance = r2;
721  }
722  }
723 
725  }
726 }
727 
728 // +--------------------------------------------------------------------+
729 
730 void
732 {
733  formation_delta = point;
734 }
735 
736 void
738 {
739  const double prediction = 5;
740 
741  // find the base position:
742  Element* elem = ship->GetElement();
743  Ship* lead = elem->GetShip(1);
744  Ship* ward = ship->GetWard();
745 
746  if (!lead || lead == ship) {
747  lead = ward;
748 
749  distance = (lead->Location() - self->Location()).length();
750  if (distance < 30e3 && lead->Velocity().length() < 50) {
751  obj_w = self->Location() + lead->Heading() * 1e6;
752  distance = -1;
753  return;
754  }
755  }
756 
757  obj_w = lead->Location() + lead->Velocity() * prediction;
758  Matrix m; m.Rotate(0, 0, lead->CompassHeading() - PI);
759  Point fd = formation_delta * m;
760  obj_w += fd;
761 
762  // try to avoid smacking into the ground...
763  if (ship->IsAirborne()) {
764  if (ship->AltitudeAGL() < 3000 || lead->AltitudeAGL() < 3000) {
765  obj_w.y += 500;
766  }
767  }
768 
769  Point dst_w = self->Location() + self->Velocity() * prediction;
770  Point dlt_w = obj_w - dst_w;
771 
772  distance = dlt_w.length();
773 
774  // get slot z distance:
775  dlt_w += ship->Location();
776  slot_dist = Transform(dlt_w).z;
777 
778  Director* lead_dir = lead->GetDirector();
779  if (lead_dir && (lead_dir->Type() == FIGHTER || lead_dir->Type() == STARSHIP)) {
780  ShipAI* lead_ai = (ShipAI*) lead_dir;
781  farcaster = lead_ai->GetFarcaster();
782  }
783  else {
784  Instruction* navpt = elem->GetNextNavPoint();
785  if (!navpt) {
786  farcaster = 0;
787  return;
788  }
789 
790  SimRegion* self_rgn = ship->GetRegion();
791  SimRegion* nav_rgn = navpt->Region();
792  QuantumDrive* qdrive = ship->GetQuantumDrive();
793 
794  if (self_rgn && !nav_rgn) {
795  nav_rgn = self_rgn;
796  navpt->SetRegion(nav_rgn);
797  }
798 
799  bool use_farcaster = self_rgn != nav_rgn &&
800  (navpt->Farcast() ||
801  !qdrive ||
802  !qdrive->IsPowerOn() ||
803  qdrive->Status() < System::DEGRADED
804  );
805 
806  if (use_farcaster) {
807  ListIter<Ship> s = self_rgn->Ships();
808  while (++s && !farcaster) {
809  if (s->GetFarcaster()) {
810  const Ship* dest = s->GetFarcaster()->GetDest();
811  if (dest && dest->GetRegion() == nav_rgn) {
812  farcaster = s->GetFarcaster();
813  }
814  }
815  }
816  }
817  else if (farcaster) {
818  if (farcaster->GetShip()->GetRegion() != self_rgn)
820 
821  obj_w = farcaster->EndPoint();
822  distance = Point(obj_w - ship->Location()).length();
823 
824  if (distance < 1000)
825  farcaster = 0;
826  }
827  }
828 }
829 
830 // +--------------------------------------------------------------------+
831 
832 void
833 ShipAI::Splash(const Ship* targ)
834 {
835  if (splash_count > 6)
836  splash_count = 4;
837 
838  // call splash:
840  splash_count++;
841 }
842 
843 // +--------------------------------------------------------------------+
844 
845 void
847 {
848  if (targ != target) {
849  bracket = false;
850  }
851 
852  SteerAI::SetTarget(targ, sub);
853 }
854 
855 void
856 ShipAI::DropTarget(double dtime)
857 {
858  SetTarget(0);
859  drop_time = dtime; // seconds until we can re-acquire
860 
861  ship->DropTarget();
862 }
863 
864 void
866 {
867  bracket = b;
868  identify = false;
869 }
870 
871 void
873 {
874  identify = i;
875  bracket = false;
876 }
877 
878 // +--------------------------------------------------------------------+
879 
880 void
882 {
883  accumulator.Clear();
884  magnitude = 0;
885 
886  hold = false;
887  if ((ship->GetElement() && ship->GetElement()->GetHoldTime() > 0) ||
888  (navpt && navpt->Status() == Instruction::COMPLETE && navpt->HoldTime() > 0))
889  hold = true;
890 
892 
893  if (target)
894  ship->SetDirectorInfo(Game::GetText("ai.seek-target"));
895  else if (rumor)
896  ship->SetDirectorInfo(Game::GetText("ai.seek-rumor"));
897  else
898  ship->SetDirectorInfo(Game::GetText("ai.none"));
899 
902 
903  if (!hold)
905 
906  HelmControl();
907  ThrottleControl();
908  FireControl();
909  AdjustDefenses();
910 }
911 
912 // +--------------------------------------------------------------------+
913 
914 void
916 {
917  double trans_x = 0;
918  double trans_y = 0;
919  double trans_z = 0;
920 
922 
923  if (fabs(accumulator.pitch) < 5*DEGREES || fabs(accumulator.pitch) > 45*DEGREES) {
924  trans_z = objective.y;
925  ship->SetHelmPitch(0);
926  }
927 
928  else {
930  }
931 
932  ship->SetTransX(trans_x);
933  ship->SetTransY(trans_y);
934  ship->SetTransZ(trans_z);
935 
936  ship->ExecFLCSFrame();
937 }
938 
939 /*****************************************
940 **
941 ** NOTE:
942 ** No one is really using this method.
943 ** It is overridden by both StarshipAI
944 ** and FighterAI.
945 **
946 *****************************************/
947 
948 void
950 {
951  if (navpt && !threat && !target) { // lead only, get speed from navpt
952  double speed = navpt->Speed();
953 
954  if (speed > 0)
955  throttle = speed / ship->VelocityLimit() * 100;
956  else
957  throttle = 50;
958  }
959 
960  else if (patrol && !threat && !target) { // lead only, get speed from navpt
961  double speed = 200;
962 
963  if (distance > 5000)
964  speed = 500;
965 
966  if (ship->Velocity().length() > speed)
967  throttle = 0;
968  else
969  throttle = 50;
970  }
971 
972  else {
973  if (threat || target || element_index < 2) { // element lead
974  throttle = 100;
975 
976  if (!threat && !target)
977  throttle = 50;
978 
979  if (accumulator.brake > 0) {
980  throttle *= (1 - accumulator.brake);
981  }
982  }
983 
984  else { // wingman
985  Ship* lead = ship->GetElement()->GetShip(1);
986  double lv = lead->Velocity().length();
987  double sv = ship->Velocity().length();
988  double dv = lv-sv;
989  double dt = 0;
990 
991  if (dv > 0) dt = dv * 1e-2 * seconds;
992  else if (dv < 0) dt = dv * 1e-2 * seconds;
993 
994  throttle = old_throttle + dt;
995  }
996  }
997 
999  ship->SetThrottle((int) throttle);
1000 }
1001 
1002 // +--------------------------------------------------------------------+
1003 
1004 void
1006 {
1007  Ship* leader = ship->GetLeader();
1008 
1009  if (leader && leader != ship) {
1010  bool navlight_enabled = false;
1011 
1012  if (leader->NavLights().size() > 0)
1013  navlight_enabled = leader->NavLights().at(0)->IsEnabled();
1014 
1015  for (int i = 0; i < ship->NavLights().size(); i++) {
1016  if (navlight_enabled)
1017  ship->NavLights().at(i)->Enable();
1018  else
1019  ship->NavLights().at(i)->Disable();
1020  }
1021  }
1022 }
1023 
1024 // +--------------------------------------------------------------------+
1025 
1026 Steer
1028 {
1029  Steer avoid;
1030  return avoid;
1031 }
1032 
1033 // +--------------------------------------------------------------------+
1034 
1035 Steer
1037 {
1038  Steer avoid;
1039 
1040  if (!ship || !ship->GetRegion() || !ship->GetRegion()->IsActive())
1041  return avoid;
1042 
1043  if (other && (other->Life() == 0 || other->Integrity() < 1)) {
1044  other = 0;
1045  last_avoid_time = 0; // check for a new obstacle immediately
1046  }
1047 
1048  if (!other && Game::GameTime() - last_avoid_time < 500)
1049  return avoid;
1050 
1051  brake = 0;
1052 
1053  // don't get closer than this:
1054  double avoid_dist = 5 * self->Radius();
1055 
1056  if (avoid_dist < 1e3) avoid_dist = 1e3;
1057  else if (avoid_dist > 12e3) avoid_dist = 12e3;
1058 
1059  // find the soonest potential collision,
1060  // ignore any that occur after this:
1061  double avoid_time = 15;
1062 
1063  if (ship->Design()->avoid_time > 0)
1064  avoid_time = ship->Design()->avoid_time;
1065  else if (ship->IsStarship())
1066  avoid_time *= 1.5;
1067 
1068  Point bearing = self->Velocity();
1069  bearing.Normalize();
1070 
1071  bool found = false;
1072  int num_contacts = ship->NumContacts();
1073  ListIter<Contact> contact = ship->ContactList();
1074 
1075  // check current obstacle first:
1076  if (other) {
1077  found = AvoidTestSingleObject(other, bearing, avoid_dist, avoid_time, avoid);
1078  }
1079 
1080  if (!found) {
1081  // avoid ships:
1082  while (++contact && !found) {
1083  Ship* c_ship = contact->GetShip();
1084 
1085  if (c_ship && c_ship != ship && c_ship->IsStarship()) {
1086  found = AvoidTestSingleObject(c_ship, bearing, avoid_dist, avoid_time, avoid);
1087  }
1088  }
1089 
1090  // also avoid large pieces of debris:
1091  if (!found) {
1092  ListIter<Debris> iter = ship->GetRegion()->Rocks();
1093  while (++iter && !found) {
1094  Debris* debris = iter.value();
1095 
1096  if (debris->Mass() > ship->Mass())
1097  found = AvoidTestSingleObject(debris, bearing, avoid_dist, avoid_time, avoid);
1098  }
1099  }
1100 
1101  // and asteroids:
1102  if (!found) {
1103  // give asteroids a wider berth -
1104  avoid_dist *= 8;
1105 
1106  ListIter<Asteroid> iter = ship->GetRegion()->Roids();
1107  while (++iter && !found) {
1108  Asteroid* roid = iter.value();
1109  found = AvoidTestSingleObject(roid, bearing, avoid_dist, avoid_time, avoid);
1110  }
1111 
1112  if (!found)
1113  avoid_dist /= 8;
1114  }
1115 
1116  // if found, steer to avoid:
1117  if (other) {
1118  avoid = Avoid(obstacle, (float) (ship->Radius() + other->Radius() + avoid_dist * 0.9));
1119  avoid.brake = brake;
1120 
1121  ship->SetDirectorInfo(Game::GetText("ai.avoid-collision"));
1122  }
1123  }
1124 
1126  return avoid;
1127 }
1128 
1129 bool
1131 const Point& bearing,
1132 double avoid_dist,
1133 double& avoid_time,
1134 Steer& avoid)
1135 {
1136  if (too_close == obj->Identity()) {
1137  double dist = (ship->Location() - obj->Location()).length();
1138  double closure = (ship->Velocity() - obj->Velocity()) * bearing;
1139 
1140  if (closure > 1 && dist < avoid_dist) {
1141  avoid = AvoidCloseObject(obj);
1142  return true;
1143  }
1144  else {
1145  too_close = 0;
1146  }
1147  }
1148 
1149  // will we get close?
1150  double time = ClosestApproachTime(ship->Location(), ship->Velocity(),
1151  obj->Location(), obj->Velocity());
1152 
1153  // already past the obstacle:
1154  if (time <= 0) {
1155  if (other == obj) other = 0;
1156  return false;
1157  }
1158 
1159  // how quickly could we collide?
1160  Point current_relation = ship->Location() - obj->Location();
1161  double current_distance = current_relation.length() - ship->Radius() - obj->Radius();
1162 
1163  // are we really far away?
1164  if (current_distance > 25e3) {
1165  if (other == obj) other = 0;
1166  return false;
1167  }
1168 
1169  // is the obstacle a farcaster?
1170  if (obj->Type() == SimObject::SIM_SHIP) {
1171  Ship* c_ship = (Ship*) obj;
1172 
1173  if (c_ship->GetFarcaster()) {
1174  // are we on a safe vector?
1175  Point dir = ship->Velocity();
1176  dir.Normalize();
1177 
1178  double angle_off = fabs(acos(dir * obj->Cam().vpn()));
1179 
1180  if (angle_off > 90*DEGREES)
1181  angle_off = 180*DEGREES - angle_off;
1182 
1183  if (angle_off < 35*DEGREES) {
1184  // will we pass through the center?
1185  Point d = ship->Location() + dir * (current_distance + ship->Radius() + obj->Radius());
1186  double err = (obj->Location() - d).length();
1187 
1188  if (err < 0.667 * obj->Radius()) {
1189  return false;
1190  }
1191  }
1192  }
1193  }
1194 
1195  // rate of closure:
1196  double closing_velocity = (ship->Velocity() - obj->Velocity()) * bearing;
1197 
1198  // are we too close already?
1199  if (current_distance < (avoid_dist * 0.35)) {
1200  if (closing_velocity > 1 || current_distance < ship->Radius()) {
1201  avoid = AvoidCloseObject(obj);
1202  return true;
1203  }
1204  }
1205 
1206  // too far away to worry about:
1207  double separation = (avoid_dist + obj->Radius());
1208  if ((current_distance-separation) / closing_velocity > avoid_time) {
1209  if (other == obj) other = 0;
1210  return false;
1211  }
1212 
1213  // where will we be?
1214  Point selfpt = ship->Location() + ship->Velocity() * time;
1215  Point testpt = obj->Location() + obj->Velocity() * time;
1216 
1217  // how close will we get?
1218  double dist = (selfpt - testpt).length()
1219  - ship->Radius()
1220  - obj->Radius();
1221 
1222  // that's too close:
1223  if (dist < avoid_dist) {
1224  if (dist < avoid_dist * 0.25 && time < avoid_time * 0.5) {
1225  avoid = AvoidCloseObject(obj);
1226  return true;
1227  }
1228 
1229  obstacle = Transform(testpt);
1230 
1231  if (obstacle.z > 0) {
1232  other = obj;
1233  avoid_time = time;
1234  brake = 0.5;
1235 
1236  Observe(other);
1237  }
1238  }
1239 
1240  // hysteresis:
1241  else if (other == obj && dist > avoid_dist * 1.25) {
1242  other = 0;
1243  }
1244 
1245  return false;
1246 }
1247 
1248 // +--------------------------------------------------------------------+
1249 
1250 Steer
1252 {
1253  too_close = obj->Identity();
1254  obstacle = Transform(obj->Location());
1255  other = obj;
1256 
1257  Observe(other);
1258 
1259  Steer avoid = Flee(obstacle);
1260  avoid.brake = 0.3;
1261 
1262  ship->SetDirectorInfo(Game::GetText("ai.avoid-collision"));
1263  return avoid;
1264 }
1265 
1266 // +--------------------------------------------------------------------+
1267 
1268 Steer
1270 {
1271  Ship* ward = ship->GetWard();
1272 
1273  if (!target && !ward && !navpt && !patrol) {
1274  if (element_index > 1) {
1275  // wingmen keep in formation:
1276  return Seek(objective);
1277  }
1278 
1279  if (farcaster) {
1280  return Seek(objective);
1281  }
1282 
1283  if (rumor) {
1284  return Seek(objective);
1285  }
1286 
1287  return Steer();
1288  }
1289 
1290  if (patrol) {
1291  Steer result = Seek(objective);
1292 
1293  if (distance < 2000) {
1294  result.brake = 1;
1295  }
1296 
1297  return result;
1298  }
1299 
1300  if (target && too_close == target->Identity()) {
1301  drop_time = 4;
1302  return Avoid(objective, 0.0f);
1303  }
1304  else if (drop_time > 0) {
1305  return Steer();
1306  }
1307 
1308  return Seek(objective);
1309 }
1310 
1311 // +--------------------------------------------------------------------+
1312 
1313 Steer
1315 {
1316  return Steer();
1317 }
1318 
1319 // +--------------------------------------------------------------------+
1320 
1321 void
1323 {
1324 }
1325 
1326 // +--------------------------------------------------------------------+
1327 
1328 void
1330 {
1331  Shield* shield = ship->GetShield();
1332 
1333  if (shield) {
1334  double desire = 50;
1335 
1336  if (threat_missile || threat)
1337  desire = 100;
1338 
1339  shield->SetPowerLevel(desire);
1340  }
1341 }
1342 
1343 // +--------------------------------------------------------------------+
1344 
1345 void
1347 {
1348  if (target) {
1349  if (target->Life() == 0)
1350  target = 0;
1351 
1352  else if (target->Type() == SimObject::SIM_SHIP) {
1353  Ship* tgt_ship = (Ship*) target;
1354 
1355  if (tgt_ship->GetIFF() == ship->GetIFF() && !tgt_ship->IsRogue())
1356  target = 0;
1357  }
1358  }
1359 }