1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3 #include "AAirMoveType.h"
4 #include "MoveMath/MoveMath.h"
5
6 #include "Map/Ground.h"
7 #include "Map/MapInfo.h"
8 #include "Sim/Misc/GlobalSynced.h"
9 #include "Sim/Misc/QuadField.h"
10 #include "Sim/Units/Unit.h"
11 #include "Sim/Units/UnitDef.h"
12 #include "Sim/Units/CommandAI/CommandAI.h"
13 #include "Sim/Units/Scripts/UnitScript.h"
14
CR_BIND_DERIVED_INTERFACE(AAirMoveType,AMoveType)15 CR_BIND_DERIVED_INTERFACE(AAirMoveType, AMoveType)
16
17 CR_REG_METADATA(AAirMoveType, (
18 CR_ENUM_MEMBER(aircraftState),
19 CR_ENUM_MEMBER(padStatus),
20
21 CR_MEMBER(oldGoalPos),
22 CR_MEMBER(reservedLandingPos),
23
24 CR_MEMBER(wantedHeight),
25 CR_MEMBER(orgWantedHeight),
26
27 CR_MEMBER(accRate),
28 CR_MEMBER(decRate),
29 CR_MEMBER(altitudeRate),
30
31 CR_MEMBER(collide),
32 CR_MEMBER(useSmoothMesh),
33 CR_MEMBER(autoLand),
34
35 CR_MEMBER(lastColWarning),
36 CR_MEMBER(reservedPad),
37
38 CR_MEMBER(lastColWarningType),
39 CR_MEMBER(lastFuelUpdateFrame)
40 ))
41
42 AAirMoveType::AAirMoveType(CUnit* unit):
43 AMoveType(unit),
44 aircraftState(AIRCRAFT_LANDED),
45 padStatus(PAD_STATUS_FLYING),
46
47 oldGoalPos((unit != NULL)? unit->pos : ZeroVector),
48 reservedLandingPos(-1.0f, -1.0f, -1.0f),
49
50 wantedHeight(80.0f),
51 orgWantedHeight(0.0f),
52
53 accRate(1.0f),
54 decRate(1.0f),
55 altitudeRate(3.0f),
56
57 collide(true),
58 useSmoothMesh(false),
59 autoLand(true),
60
61 lastColWarning(NULL),
62 reservedPad(NULL),
63
64 lastColWarningType(0),
65 lastFuelUpdateFrame(gs->frameNum)
66 {
67 // same as {Ground, HoverAir}MoveType::accRate
68 accRate = std::max(0.01f, unit->unitDef->maxAcc);
69 decRate = std::max(0.01f, unit->unitDef->maxDec);
70 altitudeRate = std::max(0.01f, unit->unitDef->verticalSpeed);
71
72 useHeading = false;
73 }
74
~AAirMoveType()75 AAirMoveType::~AAirMoveType()
76 {
77 // NOTE:
78 // this calls Takeoff and (indirectly) SetState,
79 // so neither of these must be pure virtuals (!)
80 UnreservePad(reservedPad);
81 }
82
UseSmoothMesh() const83 bool AAirMoveType::UseSmoothMesh() const {
84 if (!useSmoothMesh)
85 return false;
86
87 const bool onTransportMission =
88 !owner->commandAI->commandQue.empty() &&
89 ((owner->commandAI->commandQue.front().GetID() == CMD_LOAD_UNITS) || (owner->commandAI->commandQue.front().GetID() == CMD_UNLOAD_UNIT));
90 const bool repairing = reservedPad ? padStatus >= PAD_STATUS_LANDING : false;
91 const bool forceDisableSmooth = repairing || onTransportMission || (aircraftState != AIRCRAFT_FLYING);
92 return !forceDisableSmooth;
93 }
94
95
ReservePad(CAirBaseHandler::LandingPad * lp)96 void AAirMoveType::ReservePad(CAirBaseHandler::LandingPad* lp) {
97 oldGoalPos = goalPos;
98 orgWantedHeight = wantedHeight;
99
100 assert(reservedPad == NULL);
101
102 AddDeathDependence(lp, DEPENDENCE_LANDINGPAD);
103 SetGoal(lp->GetUnit()->pos);
104
105 reservedPad = lp;
106 padStatus = PAD_STATUS_FLYING;
107
108 Takeoff();
109 }
110
UnreservePad(CAirBaseHandler::LandingPad * lp)111 void AAirMoveType::UnreservePad(CAirBaseHandler::LandingPad* lp)
112 {
113 if (lp == NULL)
114 return;
115
116 assert(reservedPad == lp);
117
118 DeleteDeathDependence(reservedPad, DEPENDENCE_LANDINGPAD);
119 airBaseHandler->LeaveLandingPad(reservedPad);
120
121 reservedPad = NULL;
122 padStatus = PAD_STATUS_FLYING;
123
124 goalPos = oldGoalPos;
125 wantedHeight = orgWantedHeight;
126 SetState(AIRCRAFT_TAKEOFF);
127 }
128
DependentDied(CObject * o)129 void AAirMoveType::DependentDied(CObject* o) {
130 if (o == lastColWarning) {
131 lastColWarning = NULL;
132 lastColWarningType = 0;
133 }
134
135 if (o == reservedPad) {
136 if (aircraftState != AIRCRAFT_CRASHING) {
137 // change state only if not crashing
138 SetState(AIRCRAFT_TAKEOFF);
139
140 goalPos = oldGoalPos;
141 wantedHeight = orgWantedHeight;
142 padStatus = PAD_STATUS_FLYING;
143 }
144 reservedPad = NULL;
145 }
146 }
147
Update()148 bool AAirMoveType::Update() {
149 // NOTE: useHeading is never true by default for aircraft (AAirMoveType
150 // forces it to false, TransportUnit::{Attach,Detach}Unit manipulate it
151 // specifically for HoverAirMoveType's)
152 if (useHeading) {
153 SetState(AIRCRAFT_TAKEOFF);
154 }
155
156 if (owner->beingBuilt) {
157 // while being built, MoveType::SlowUpdate is not
158 // called so UpdateFuel will not be either --> do
159 // it here to prevent a drop in fuel level as soon
160 // as unit finishes construction
161 UpdateFuel(false);
162 }
163
164 // this return value is never used
165 return (useHeading = false);
166 }
167
UpdateLanded()168 void AAirMoveType::UpdateLanded()
169 {
170 // while an aircraft is being built we do not adjust its
171 // position, because the builder might be a tall platform
172 // we also do nothing if the aircraft is preparing to land
173 // or has already landed on a repair-pad
174 if (owner->beingBuilt)
175 return;
176 if (padStatus != PAD_STATUS_FLYING)
177 return;
178
179 // when an aircraft transitions to the landed state it
180 // is on the ground, but the terrain can be raised (or
181 // lowered) later and we do not want to end up hovering
182 // in mid-air or sink below it
183 // let gravity do the job instead of teleporting
184 const float minHeight = owner->unitDef->canSubmerge?
185 CGround::GetHeightReal(owner->pos.x, owner->pos.z):
186 CGround::GetHeightAboveWater(owner->pos.x, owner->pos.z);
187 const float curHeight = owner->pos.y;
188
189 if (curHeight > minHeight) {
190 if (curHeight > 0.0f) {
191 owner->speed.y += mapInfo->map.gravity;
192 } else {
193 owner->speed.y = mapInfo->map.gravity;
194 }
195 } else {
196 owner->speed.y = 0.0f;
197 }
198
199 owner->SetVelocityAndSpeed(owner->speed + owner->GetDragAccelerationVec(float4(0.0f, 0.0f, 0.0f, 0.1f)));
200 owner->Move(UpVector * (std::max(curHeight, minHeight) - owner->pos.y), true);
201 owner->Move(owner->speed, true);
202 // match the terrain normal
203 owner->UpdateDirVectors(owner->IsOnGround());
204 owner->UpdateMidAndAimPos();
205 }
206
UpdateFuel(bool slowUpdate)207 void AAirMoveType::UpdateFuel(bool slowUpdate) {
208 if (owner->unitDef->maxFuel <= 0.0f)
209 return;
210
211 // lastFuelUpdateFrame must be updated even when early-outing
212 // otherwise fuel level will jump after a period of not using
213 // any (eg. when on a pad or after being built)
214 if (!slowUpdate) {
215 lastFuelUpdateFrame = gs->frameNum;
216 return;
217 }
218 if (aircraftState == AIRCRAFT_LANDED) {
219 lastFuelUpdateFrame = gs->frameNum;
220 return;
221 }
222 if (padStatus == PAD_STATUS_ARRIVED) {
223 lastFuelUpdateFrame = gs->frameNum;
224 return;
225 }
226
227 owner->currentFuel = std::max(0.0f, owner->currentFuel - (float(gs->frameNum - lastFuelUpdateFrame) / GAME_SPEED));
228 lastFuelUpdateFrame = gs->frameNum;
229 }
230
231
232
CheckForCollision()233 void AAirMoveType::CheckForCollision()
234 {
235 if (!collide)
236 return;
237
238 const SyncedFloat3& pos = owner->midPos;
239 const SyncedFloat3& forward = owner->frontdir;
240
241 const float3 midTestPos = pos + forward * 121.0f;
242 const std::vector<CUnit*>& others = quadField->GetUnitsExact(midTestPos, 115.0f);
243
244 float dist = 200.0f;
245
246 if (lastColWarning) {
247 DeleteDeathDependence(lastColWarning, DEPENDENCE_LASTCOLWARN);
248 lastColWarning = NULL;
249 lastColWarningType = 0;
250 }
251
252 for (std::vector<CUnit*>::const_iterator ui = others.begin(); ui != others.end(); ++ui) {
253 const CUnit* unit = *ui;
254
255 if (unit == owner || !unit->unitDef->canfly) {
256 continue;
257 }
258
259 const SyncedFloat3& op = unit->midPos;
260 const float3 dif = op - pos;
261 const float3 forwardDif = forward * (forward.dot(dif));
262
263 if (forwardDif.SqLength() >= (dist * dist)) {
264 continue;
265 }
266
267 const float3 ortoDif = dif - forwardDif;
268 const float frontLength = forwardDif.Length();
269 // note: radii are multiplied by two
270 const float minOrtoDif = (unit->radius + owner->radius) * 2.0f + frontLength * 0.1f + 10;
271
272 if (ortoDif.SqLength() < (minOrtoDif * minOrtoDif)) {
273 dist = frontLength;
274 lastColWarning = const_cast<CUnit*>(unit);
275 }
276 }
277
278 if (lastColWarning != NULL) {
279 lastColWarningType = 2;
280 AddDeathDependence(lastColWarning, DEPENDENCE_LASTCOLWARN);
281 return;
282 }
283
284 for (std::vector<CUnit*>::const_iterator ui = others.begin(); ui != others.end(); ++ui) {
285 if (*ui == owner)
286 continue;
287 if (((*ui)->midPos - pos).SqLength() < (dist * dist)) {
288 lastColWarning = *ui;
289 }
290 }
291
292 if (lastColWarning != NULL) {
293 lastColWarningType = 1;
294 AddDeathDependence(lastColWarning, DEPENDENCE_LASTCOLWARN);
295 }
296 }
297
298
CanLandOnPad(const float3 & padPos) const299 bool AAirMoveType::CanLandOnPad(const float3& padPos) const {
300 // once distance to pad becomes smaller than current braking distance, switch states
301 // (but do not allow state-switch until the aircraft is heading ~directly toward pad)
302 // braking distance is 0.5*a*t*t where t is v/a --> 0.5*a*((v*v)/(a*a)) --> 0.5*v*v*(1/a)
303 // FIXME:
304 // apply N-frame lookahead when deciding to switch state for strafing aircraft
305 // (see comments in StrafeAirMoveType::UpdateLanding, overshooting prevention)
306 // the lookahead is roughly based on the time to descend to pad-target altitude
307 const float3 flatFrontDir = (float3(owner->frontdir)).Normalize2D();
308
309 const float brakingDistSq = Square(0.5f * owner->speed.SqLength2D() / decRate);
310 const float descendDistSq = Square(maxSpeed * ((owner->pos.y - padPos.y) / altitudeRate)) * owner->unitDef->IsStrafingAirUnit();
311
312 if (padPos.SqDistance2D(owner->pos) > std::max(brakingDistSq, descendDistSq))
313 return false;
314
315 if (owner->unitDef->IsStrafingAirUnit()) {
316 // horizontal guide
317 if (flatFrontDir.dot((padPos - owner->pos).SafeNormalize2D()) < 0.985f)
318 return false;
319 // vertical guide
320 if (flatFrontDir.dot((padPos - owner->pos).SafeNormalize()) < 0.707f)
321 return false;
322 }
323
324 return true;
325 }
326
HaveLandedOnPad(const float3 & padPos)327 bool AAirMoveType::HaveLandedOnPad(const float3& padPos) {
328 const AircraftState landingState = GetLandingState();
329 const float landingPadHeight = CGround::GetHeightAboveWater(padPos.x, padPos.z);
330
331 reservedLandingPos = padPos;
332 wantedHeight = padPos.y - landingPadHeight;
333
334 const float3 padVector = (padPos - owner->pos).SafeNormalize2D();
335 const float curOwnerAltitude = (owner->pos.y - landingPadHeight);
336 const float minOwnerAltitude = (wantedHeight + 1.0f);
337
338 if (aircraftState != landingState)
339 SetState(landingState);
340 if (aircraftState == AIRCRAFT_LANDED)
341 return true;
342
343 if (curOwnerAltitude <= minOwnerAltitude) {
344 // deal with overshooting aircraft: "tractor" them in
345 // once they descend down to the landing pad altitude
346 // this is a no-op for HAMT planes which do not apply
347 // speed updates at this point
348 owner->SetVelocityAndSpeed((owner->speed + (padVector * accRate)) * XZVector);
349 }
350
351 if (Square(owner->rightdir.y) < 0.01f && owner->frontdir.dot(padVector) > 0.01f) {
352 owner->frontdir = padVector;
353 owner->rightdir = owner->frontdir.cross(UpVector);
354 owner->updir = owner->rightdir.cross(owner->frontdir);
355
356 owner->SetHeadingFromDirection();
357 }
358
359 if (curOwnerAltitude > minOwnerAltitude)
360 return false;
361
362 return ((owner->pos.SqDistance2D(padPos) <= 1.0f || owner->unitDef->IsHoveringAirUnit()));
363 }
364
MoveToRepairPad()365 bool AAirMoveType::MoveToRepairPad() {
366 CUnit* airBase = reservedPad->GetUnit();
367
368 if (airBase->beingBuilt || airBase->IsStunned()) {
369 // pad became inoperable after being reserved
370 DependentDied(airBase);
371 return false;
372 } else {
373 const float3& relPadPos = airBase->script->GetPiecePos(reservedPad->GetPiece());
374 const float3 absPadPos = airBase->GetObjectSpacePos(relPadPos);
375
376 switch (padStatus) {
377 case PAD_STATUS_FLYING: {
378 // flying toward pad
379 if (aircraftState != AIRCRAFT_FLYING && aircraftState != AIRCRAFT_TAKEOFF) {
380 SetState(AIRCRAFT_FLYING);
381 }
382
383 if (CanLandOnPad(goalPos = absPadPos)) {
384 padStatus = PAD_STATUS_LANDING;
385 }
386 } break;
387
388 case PAD_STATUS_LANDING: {
389 // landing on pad
390 if (HaveLandedOnPad(goalPos = absPadPos)) {
391 padStatus = PAD_STATUS_ARRIVED;
392 }
393 } break;
394
395 case PAD_STATUS_ARRIVED: {
396 if (aircraftState != AIRCRAFT_LANDED) {
397 SetState(AIRCRAFT_LANDED);
398 }
399
400 owner->SetVelocityAndSpeed(ZeroVector);
401 owner->Move(absPadPos, false);
402 owner->UpdateMidAndAimPos(); // needed here?
403 owner->AddBuildPower(airBase, airBase->unitDef->buildSpeed / GAME_SPEED);
404
405 owner->currentFuel += (owner->unitDef->maxFuel / (GAME_SPEED * owner->unitDef->refuelTime));
406 owner->currentFuel = std::min(owner->unitDef->maxFuel, owner->currentFuel);
407
408 if (owner->health >= owner->maxHealth - 1.0f && owner->currentFuel >= owner->unitDef->maxFuel) {
409 // repaired and filled up, leave the pad
410 UnreservePad(reservedPad);
411 }
412 } break;
413 }
414 }
415
416 return true;
417 }
418