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