Vegastrike 0.5.1 rc1  1.0
Original sources for Vegastrike Evolved
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
autodocking.cpp
Go to the documentation of this file.
1 // -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*-
2 
3 #include <cassert>
4 #include <iterator>
5 #include <boost/optional.hpp>
6 #include "cmd/unit_generic.h"
7 #include "cmd/unit_util.h"
8 #include "cmd/ai/navigation.h"
9 #include "autodocking.h"
10 
11 #include "flykeyboard.h"
12 #include "in.h"
13 #include "in_kb_data.h"
14 
15 namespace
16 {
17 
18 // Find waypoints if we can travel through all of them.
19 boost::optional<size_t> FindWaypoint(Unit *player,
20  const std::vector<DockingPorts>& dockingPorts,
21  size_t port)
22 {
23  if (!dockingPorts[port].IsConnected())
24  return port;
25 
26  // Subsequent waypoints belong to this port and are listed from closest
27  // to farthest.
28  size_t i = port + 1;
29  for (; i < dockingPorts.size(); ++i)
30  {
31  const DockingPorts& waypoint = dockingPorts[i];
32  if (waypoint.IsDockable()) // No further waypoints
33  break;
34  // Do not use docking port if one of the waypoints are too small
35  if (waypoint.GetRadius() < player->rSize())
36  return boost::optional<size_t>();
37  }
38  return i - 1;
39 }
40 
41 // Find suitable docking port and associated waypoints.
43  Unit *station)
44 {
45  // FIXME: Prefer outside docking ports (because they are more safe to travel to)
46  // FIXME: Ensure line-of-sight to first point
47  // FIXME: Start at the closest waypoint (and skip those before it)
48 
49  const std::vector<DockingPorts>& dockingPorts = station->DockingPortLocations();
50 
51  typedef std::pair<size_t, size_t> PortRange;
52  boost::optional<PortRange> candidate;
53  float shortestDistance = std::numeric_limits<float>::max();
54  const bool isPlanet = station->isPlanet();
55  for (size_t i = 0; i < dockingPorts.size(); ++i)
56  {
57  if (dockingPorts[i].IsOccupied())
58  continue;
59 
60  // Auto-dockable ports must be marked as connected (even if they have
61  // no associated waypoints)
62  if (!dockingPorts[i].IsConnected())
63  continue;
64 
65  // Does our ship fit into the docking port?
66  if (dockingPorts[i].GetRadius() < player->rSize())
67  continue;
68 
69  QVector dockingPosition = Transform(station->GetTransformation(),
70  dockingPorts[i].GetPosition().Cast());
71  float distance = (dockingPosition - player->Position()).Magnitude();
72  if (shortestDistance > distance)
73  {
74  if (isPlanet)
75  {
76  shortestDistance = distance;
77  candidate = PortRange(i, i);
78  }
79  else
80  {
81  boost::optional<size_t> waypoint = FindWaypoint(player, dockingPorts, i);
82  if (waypoint)
83  {
84  shortestDistance = distance;
85  candidate = PortRange(i, *waypoint);
86  }
87  }
88  }
89  }
90 
92  if (candidate)
93  {
94  assert(candidate->first <= candidate->second);
95  for (size_t i = candidate->first; i <= candidate->second; ++i)
96  {
97  result.push_front(i);
98  }
99  }
100  return result;
101 }
102 
103 } // anonymous namespace
104 
105 namespace Orders
106 {
107 
109  : Order(MOVEMENT | FACING, SLOCATION),
110  state(&AutoDocking::InitialState),
111  target(destination)
112 {
113 }
114 
116 {
117  Unit *player = GetParent();
118  Unit *station = target.GetUnit();
119  // Exit if either the ship or the station has been destroyed
120  if (player == NULL || station == NULL)
121  {
122  done = true;
123  }
124  else
125  {
126  (this->*state)(player, station);
127  }
128 }
129 
130 bool AutoDocking::CanDock(Unit *player, Unit *station)
131 {
132  if (!station->IsCleared(player))
133  {
134  return false;
135  }
136  else if (UnitUtil::isCapitalShip(player))
137  {
138  // A capital ship must align one of its docking ports with one of the
139  // the station's docking ports. This docking script cannot do that.
140  return false;
141  }
142  else if (!UnitUtil::isDockableUnit(station))
143  {
144  return false;
145  }
146  else if (!UnitUtil::isCloseEnoughToDock(player, station))
147  {
148  return false;
149  }
150  else if (FindDockingPort(player, station).empty())
151  {
152  return false;
153  }
154  return true;
155 }
156 
157 void AutoDocking::EndState(Unit *player, Unit *station)
158 {
159  player->autopilotactive = false;
160  done = true;
161 }
162 
163 void AutoDocking::AbortState(Unit *player, Unit *station)
164 {
165  EraseOrders();
166  state = &AutoDocking::EndState;
167 
168  // Safety: full stop on abort
169  KBData kbdata;
170  FlyByKeyboard::StopKey(kbdata, PRESS);
171 }
172 
173 void AutoDocking::InitialState(Unit *player, Unit *station)
174 {
175  if (CanDock(player, station))
176  {
178  }
179  else
180  {
181  state = &AutoDocking::AbortState;
182  }
183 }
184 
185 void AutoDocking::SelectionState(Unit *player, Unit *station)
186 {
187  EraseOrders();
188 
189  dockingPath = FindDockingPort(player, station);
190  if (dockingPath.empty())
191  {
192  state = &AutoDocking::AbortState;
193  return;
194  }
195 
196  // Enqueue the waypoints from the farthest to the closest.
197  for (DockingPath::const_iterator it = dockingPath.begin(); it != dockingPath.end(); ++it)
198  {
199  EnqueuePort(player, station, *it);
200  }
201 
203 }
204 
205 void AutoDocking::ApproachState(Unit *player, Unit *station)
206 {
207  assert(!dockingPath.empty());
208 
209  // Move to docking port
210  if (station->DockingPortLocations()[dockingPath.back()].IsOccupied())
211  {
212  // Another ship has docked at our port. Find a new port.
214  }
215  else if (station->CanDockWithMe(player) == dockingPath.back())
216  {
217  state = &AutoDocking::DockingState;
218  }
219  else
220  {
221  // FIXME: Request clearance X times with fixed interval to keep capital ship immobile
222  Order::Execute();
223  }
224 }
225 
226 void AutoDocking::DockingState(Unit *player, Unit *station)
227 {
228  assert(!dockingPath.empty());
229 
230  player->ForceDock(station, dockingPath.back());
231  state = &AutoDocking::DockedState;
232 }
233 
234 void AutoDocking::DockedState(Unit *player, Unit *station)
235 {
236  EraseOrders();
238 }
239 
240 void AutoDocking::UndockingState(Unit *player, Unit *station)
241 {
242  assert(!dockingPath.empty());
243 
244  state = &AutoDocking::EndState;
245 
246  // Enqueue undocking path if docked at inner port
247  if (station->DockingPortLocations()[dockingPath.back()].IsInside())
248  {
249  DockingPath::reverse_iterator it = dockingPath.rbegin();
250  ++it; // Skip the docking port itself
251  for (; it != dockingPath.rend(); ++it)
252  {
253  EnqueuePort(player, station, *it);
255  }
256  }
257 }
258 
259 void AutoDocking::DepartureState(Unit *player, Unit *station)
260 {
261  Order::Execute();
262  if (Done())
263  {
264  EraseOrders();
265  done = false;
266  state = &AutoDocking::EndState;
267  }
268 }
269 
271 {
272  eraseType(FACING);
274 }
275 
276 void AutoDocking::EnqueuePort(Unit *player, Unit *station, size_t port)
277 {
278  // Set the coordinates for the docking port
279  const float turningSpeed = 1;
280  // If accuracy is too low then the ship does not always turn precisely.
281  // An incorrect heading may cause it to fly into corridor walls.
282  // If accuracy is too high then the ship turns precisely, but it does
283  // not terminate because it is trying to reach a higher accuracy than
284  // it can. This causes the ship to freeze.
285  const unsigned char accuracy = 7; // Higher means more accurate
286  const bool useAfterburner = false;
287  const bool terminateAfterUse = true;
288 
289  const DockingPorts& currentPort = station->DockingPortLocations()[port];
290  QVector position = Transform(station->GetTransformation(),
291  currentPort.GetPosition().Cast());
292 
293  Order *facing = new ChangeHeading(position, accuracy, turningSpeed, terminateAfterUse);
294  Order *movement = new MoveTo(position, useAfterburner, accuracy, terminateAfterUse);
295  if (currentPort.IsInside())
296  {
297  // Perform facing and movement sequentially when navigating inside the station
298  const unsigned int blockedDuringFacing = MOVEMENT;
299  EnqueueOrder(new Sequence(player, facing, blockedDuringFacing));
300  const unsigned int blockedDuringMovement = FACING;
301  EnqueueOrder(new Sequence(player, movement, blockedDuringMovement));
302  }
303  else
304  {
305  // Perform facing and movement simultaneously outside the station
306  EnqueueOrder(new Join(player, facing, movement));
307  }
308 }
309 
310 } // namespace Orders