1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3
4 #include "HoverAirMoveType.h"
5 #include "Game/Players/Player.h"
6 #include "Map/Ground.h"
7 #include "Sim/Misc/GeometricObjects.h"
8 #include "Sim/Misc/GroundBlockingObjectMap.h"
9 #include "Sim/Misc/QuadField.h"
10 #include "Sim/Misc/ModInfo.h"
11 #include "Sim/Misc/SmoothHeightMesh.h"
12 #include "Sim/Projectiles/Unsynced/SmokeProjectile.h"
13 #include "Sim/Units/Scripts/UnitScript.h"
14 #include "Sim/Units/UnitDef.h"
15 #include "Sim/Units/UnitTypes/TransportUnit.h"
16 #include "Sim/Units/CommandAI/CommandAI.h"
17 #include "System/myMath.h"
18 #include "System/Matrix44f.h"
19 #include "System/Sync/HsiehHash.h"
20
21 CR_BIND_DERIVED(CHoverAirMoveType, AAirMoveType, (NULL))
22
23 CR_REG_METADATA(CHoverAirMoveType, (
24 CR_MEMBER(bankingAllowed),
25 CR_MEMBER(airStrafe),
26 CR_MEMBER(wantToStop),
27
28 CR_MEMBER(goalDistance),
29
30 CR_MEMBER(currentBank),
31 CR_MEMBER(currentPitch),
32
33 CR_MEMBER(turnRate),
34 CR_MEMBER(maxDrift),
35 CR_MEMBER(maxTurnAngle),
36
37 CR_MEMBER(wantedSpeed),
38 CR_MEMBER(deltaSpeed),
39
40 CR_MEMBER(circlingPos),
41 CR_MEMBER(randomWind),
42
43 CR_MEMBER(forceHeading),
44 CR_MEMBER(dontLand),
45
46 CR_MEMBER(wantedHeading),
47 CR_MEMBER(forceHeadingTo),
48
49 CR_MEMBER(waitCounter),
50 CR_MEMBER(lastMoveRate),
51
52 CR_RESERVED(32)
53 ))
54
55
56
IsUnitBusy(const CUnit * u)57 static bool IsUnitBusy(const CUnit* u) {
58 // queued move-commands or an active build-command mean unit has to stay airborne
59 return (u->commandAI->HasMoreMoveCommands() || u->commandAI->HasCommand(CMD_LOAD_UNITS) || u->commandAI->HasCommand(-1));
60 }
61
CHoverAirMoveType(CUnit * owner)62 CHoverAirMoveType::CHoverAirMoveType(CUnit* owner) :
63 AAirMoveType(owner),
64 flyState(FLY_CRUISING),
65
66 bankingAllowed(true),
67 airStrafe(owner->unitDef->airStrafe),
68 wantToStop(false),
69
70 goalDistance(1),
71
72 // we want to take off in direction of factory facing
73 currentBank(0),
74 currentPitch(0),
75
76 turnRate(1),
77 maxDrift(1.0f),
78 maxTurnAngle(math::cos(owner->unitDef->turnInPlaceAngleLimit * (PI / 180.0f)) * -1.0f),
79
80 wantedSpeed(ZeroVector),
81 deltaSpeed(ZeroVector),
82 circlingPos(ZeroVector),
83 randomWind(ZeroVector),
84
85 forceHeading(false),
86 dontLand(false),
87
88 wantedHeading(GetHeadingFromFacing(owner->buildFacing)),
89 forceHeadingTo(wantedHeading),
90
91 waitCounter(0),
92 lastMoveRate(0)
93 {
94 assert(owner != NULL);
95 assert(owner->unitDef != NULL);
96
97 turnRate = owner->unitDef->turnRate;
98
99 wantedHeight = owner->unitDef->wantedHeight + gs->randFloat() * 5.0f;
100 orgWantedHeight = wantedHeight;
101 dontLand = owner->unitDef->DontLand();
102 collide = owner->unitDef->collide;
103 bankingAllowed = owner->unitDef->bankingAllowed;
104 useSmoothMesh = owner->unitDef->useSmoothMesh;
105
106 // prevent weapons from being updated and firing while on the ground
107 owner->dontUseWeapons = true;
108 }
109
110
SetGoal(const float3 & pos,float distance)111 void CHoverAirMoveType::SetGoal(const float3& pos, float distance)
112 {
113 goalPos = pos;
114 oldGoalPos = pos;
115
116 // aircraft need some marginals to avoid uber stacking
117 // when lots of them are ordered to one place
118 maxDrift = std::max(16.0f, distance);
119 wantedHeight = orgWantedHeight;
120
121 forceHeading = false;
122 }
123
SetState(AircraftState newState)124 void CHoverAirMoveType::SetState(AircraftState newState)
125 {
126 // once in crashing, we should never change back into another state
127 if (aircraftState == AIRCRAFT_CRASHING && newState != AIRCRAFT_CRASHING)
128 return;
129
130 if (newState == aircraftState)
131 return;
132
133 // redundant SetState() call, we already landed and get command to switch into landing
134 if (aircraftState == AIRCRAFT_LANDED)
135 assert(newState != AIRCRAFT_LANDING);
136
137 if (newState == AIRCRAFT_LANDED) {
138 owner->dontUseWeapons = true;
139 owner->useAirLos = false;
140 } else {
141 owner->dontUseWeapons = false;
142 owner->useAirLos = true;
143 }
144
145 aircraftState = newState;
146
147 switch (aircraftState) {
148 case AIRCRAFT_CRASHING:
149 owner->SetPhysicalStateBit(CSolidObject::PSTATE_BIT_CRASHING);
150 break;
151 case AIRCRAFT_LANDED:
152 // FIXME already inform commandAI in AIRCRAFT_LANDING!
153 owner->commandAI->StopMove();
154
155 owner->Block();
156 owner->ClearPhysicalStateBit(CSolidObject::PSTATE_BIT_FLYING);
157 break;
158 case AIRCRAFT_LANDING:
159 owner->Deactivate();
160 break;
161 case AIRCRAFT_HOVERING: {
162 // when heading is forced by TCAI we are busy (un-)loading
163 // a unit and do not want wantedHeight to be tampered with
164 wantedHeight = mix(orgWantedHeight, wantedHeight, forceHeading);
165 wantedSpeed = ZeroVector;
166 } // fall through
167 default:
168 owner->Activate();
169 owner->UnBlock();
170 owner->SetPhysicalStateBit(CSolidObject::PSTATE_BIT_FLYING);
171
172 reservedLandingPos.x = -1.0f;
173 break;
174 }
175
176 // Cruise as default
177 // FIXME: RHS overrides FLY_ATTACKING and FLY_CIRCLING (via UpdateFlying-->ExecuteStop)
178 if (aircraftState == AIRCRAFT_FLYING || aircraftState == AIRCRAFT_HOVERING)
179 flyState = FLY_CRUISING;
180
181 owner->UpdatePhysicalStateBit(CSolidObject::PSTATE_BIT_MOVING, (aircraftState != AIRCRAFT_LANDED));
182 waitCounter = 0;
183 }
184
SetAllowLanding(bool allowLanding)185 void CHoverAirMoveType::SetAllowLanding(bool allowLanding)
186 {
187 dontLand = !allowLanding;
188
189 if (CanLand(false))
190 return;
191
192 if (aircraftState != AIRCRAFT_LANDED && aircraftState != AIRCRAFT_LANDING)
193 return;
194
195 // do not start hovering if still (un)loading a unit
196 if (forceHeading)
197 return;
198
199 SetState(AIRCRAFT_HOVERING);
200 }
201
StartMoving(float3 pos,float goalRadius)202 void CHoverAirMoveType::StartMoving(float3 pos, float goalRadius)
203 {
204 forceHeading = false;
205 wantToStop = false;
206 waitCounter = 0;
207
208 owner->SetPhysicalStateBit(CSolidObject::PSTATE_BIT_MOVING);
209
210 switch (aircraftState) {
211 case AIRCRAFT_LANDED:
212 SetState(AIRCRAFT_TAKEOFF);
213 break;
214 case AIRCRAFT_TAKEOFF:
215 SetState(AIRCRAFT_TAKEOFF);
216 break;
217 case AIRCRAFT_FLYING:
218 if (flyState != FLY_CRUISING) {
219 flyState = FLY_CRUISING;
220 }
221 break;
222 case AIRCRAFT_LANDING:
223 SetState(AIRCRAFT_TAKEOFF);
224 break;
225 case AIRCRAFT_HOVERING:
226 SetState(AIRCRAFT_FLYING);
227 break;
228 case AIRCRAFT_CRASHING:
229 break;
230 }
231
232 SetGoal(pos, goalRadius);
233 progressState = AMoveType::Active;
234 }
235
StartMoving(float3 pos,float goalRadius,float speed)236 void CHoverAirMoveType::StartMoving(float3 pos, float goalRadius, float speed)
237 {
238 StartMoving(pos, goalRadius);
239 }
240
KeepPointingTo(float3 pos,float distance,bool aggressive)241 void CHoverAirMoveType::KeepPointingTo(float3 pos, float distance, bool aggressive)
242 {
243 wantToStop = false;
244 forceHeading = false;
245 wantedHeight = orgWantedHeight;
246
247 // close in a little to avoid the command AI to override the pos constantly
248 distance -= 15;
249
250 // Ignore the exact same order
251 if ((aircraftState == AIRCRAFT_FLYING) && (flyState == FLY_CIRCLING || flyState == FLY_ATTACKING) && ((circlingPos - pos).SqLength2D() < 64) && (goalDistance == distance))
252 return;
253
254 circlingPos = pos;
255 goalDistance = distance;
256 goalPos = owner->pos;
257
258 SetState(AIRCRAFT_FLYING);
259
260 // FIXME:
261 // the FLY_ATTACKING state is broken (unknown how long this has been
262 // the case because <aggressive> was always false up until e7ae68df)
263 // aircraft fly *right up* to their target without stopping
264 // after fixing this, the "circling" behavior is now broken
265 // (waitCounter is always 0)
266 if (aggressive) {
267 flyState = FLY_ATTACKING;
268 } else {
269 flyState = FLY_CIRCLING;
270 }
271 }
272
ExecuteStop()273 void CHoverAirMoveType::ExecuteStop()
274 {
275 wantToStop = false;
276 wantedSpeed = ZeroVector;
277
278 switch (aircraftState) {
279 case AIRCRAFT_TAKEOFF: {
280 if (CanLand(IsUnitBusy(owner))) {
281 SetState(AIRCRAFT_LANDING);
282 // trick to land directly
283 waitCounter = GAME_SPEED;
284 break;
285 }
286 } // fall through
287 case AIRCRAFT_FLYING: {
288 goalPos = owner->pos;
289
290 if (CanLand(IsUnitBusy(owner))) {
291 SetState(AIRCRAFT_LANDING);
292 } else {
293 SetState(AIRCRAFT_HOVERING);
294 }
295 } break;
296
297 case AIRCRAFT_LANDING: {} break;
298 case AIRCRAFT_LANDED: {} break;
299 case AIRCRAFT_CRASHING: {} break;
300
301 case AIRCRAFT_HOVERING: {
302 if (CanLand(IsUnitBusy(owner))) {
303 // land immediately, otherwise keep hovering
304 SetState(AIRCRAFT_LANDING);
305 waitCounter = GAME_SPEED;
306 }
307 } break;
308 }
309 }
310
StopMoving(bool callScript,bool hardStop)311 void CHoverAirMoveType::StopMoving(bool callScript, bool hardStop)
312 {
313 // transports switch to landed state (via SetState which calls
314 // us) during pickup but must *not* be allowed to change their
315 // heading while "landed" (see TransportCAI)
316 forceHeading &= (aircraftState == AIRCRAFT_LANDED);
317 wantToStop = true;
318 wantedHeight = orgWantedHeight;
319
320 owner->ClearPhysicalStateBit(CSolidObject::PSTATE_BIT_MOVING);
321
322 if (progressState != AMoveType::Failed)
323 progressState = AMoveType::Done;
324 }
325
326
327
UpdateLanded()328 void CHoverAirMoveType::UpdateLanded()
329 {
330 AAirMoveType::UpdateLanded();
331
332 if (progressState != AMoveType::Failed)
333 progressState = AMoveType::Done;
334 }
335
UpdateTakeoff()336 void CHoverAirMoveType::UpdateTakeoff()
337 {
338 const float3& pos = owner->pos;
339
340 wantedSpeed = ZeroVector;
341 wantedHeight = orgWantedHeight;
342
343 UpdateAirPhysics();
344
345 const float altitude = owner->unitDef->canSubmerge?
346 (pos.y - CGround::GetHeightReal(pos.x, pos.z)):
347 (pos.y - CGround::GetHeightAboveWater(pos.x, pos.z));
348
349 if (altitude > orgWantedHeight * 0.8f) {
350 SetState(AIRCRAFT_FLYING);
351 }
352 }
353
354
355 // Move the unit around a bit..
UpdateHovering()356 void CHoverAirMoveType::UpdateHovering()
357 {
358 #if 0
359 #define NOZERO(x) std::max(x, 0.0001f)
360
361 float3 deltaVec = goalPos - owner->pos;
362 float3 deltaDir = float3(deltaVec.x, 0.0f, deltaVec.z);
363
364 const float driftSpeed = math::fabs(owner->unitDef->dlHoverFactor);
365 const float goalDistance = NOZERO(deltaDir.Length2D());
366 const float brakeDistance = 0.5f * owner->speed.SqLength2D() / decRate;
367
368 // move towards goal position if it's not immediately
369 // behind us when we have more waypoints to get to
370 // *** this behavior interferes with the loading procedure of transports ***
371 const bool b0 = (aircraftState != AIRCRAFT_LANDING && owner->commandAI->HasMoreMoveCommands());
372 const bool b1 = (goalDistance < brakeDistance && goalDistance > 1.0f);
373
374 if (b0 && b1 && dynamic_cast<CTransportUnit*>(owner) == NULL) {
375 deltaDir = owner->frontdir;
376 } else {
377 deltaDir *= smoothstep(0.0f, 20.0f, goalDistance) / goalDistance;
378 deltaDir -= owner->speed;
379 }
380
381 if (deltaDir.SqLength2D() > (maxSpeed * maxSpeed)) {
382 deltaDir *= (maxSpeed / NOZERO(math::sqrt(deltaDir.SqLength2D())));
383 }
384
385 // random movement (a sort of fake wind effect)
386 // random drift values are in range -0.5 ... 0.5
387 randomWind.x = randomWind.x * 0.9f + (gs->randFloat() - 0.5f) * 0.5f;
388 randomWind.z = randomWind.z * 0.9f + (gs->randFloat() - 0.5f) * 0.5f;
389
390 wantedSpeed = owner->speed + deltaDir;
391 wantedSpeed += (randomWind * driftSpeed * 0.5f);
392
393 UpdateAirPhysics();
394 #endif
395
396 #if 1
397 randomWind.x = randomWind.x * 0.9f + (gs->randFloat() - 0.5f) * 0.5f;
398 randomWind.z = randomWind.z * 0.9f + (gs->randFloat() - 0.5f) * 0.5f;
399
400 // randomly drift (but not too far from goal-position)
401 wantedSpeed = (randomWind * math::fabs(owner->unitDef->dlHoverFactor) * 0.5f);
402 wantedSpeed += (smoothstep(0.0f, 20.0f * 20.0f, (goalPos - owner->pos)) * (goalPos - owner->pos));
403
404 UpdateAirPhysics();
405 #endif
406 }
407
408
UpdateFlying()409 void CHoverAirMoveType::UpdateFlying()
410 {
411 const float3& pos = owner->pos;
412 // const float4& spd = owner->speed;
413
414 // Direction to where we would like to be
415 float3 goalVec = goalPos - pos;
416
417 owner->restTime = 0;
418
419 // don't change direction for waypoints we just flew over and missed slightly
420 if (flyState != FLY_LANDING && owner->commandAI->HasMoreMoveCommands()) {
421 float3 goalDir = goalVec;
422
423 if ((goalDir != ZeroVector) && (goalVec.dot(goalDir.UnsafeANormalize()) < 1.0f)) {
424 goalVec = owner->frontdir;
425 }
426 }
427
428 const float goalDistSq2D = goalVec.SqLength2D();
429 const float gHeight = UseSmoothMesh()?
430 std::max(smoothGround->GetHeight(pos.x, pos.z), CGround::GetApproximateHeight(pos.x, pos.z)):
431 CGround::GetHeightAboveWater(pos.x, pos.z);
432
433 const bool closeToGoal = (flyState == FLY_ATTACKING)?
434 (goalDistSq2D < ( 400.0f)):
435 (goalDistSq2D < (maxDrift * maxDrift)) && (math::fabs(gHeight + wantedHeight - pos.y) < maxDrift);
436
437 if (closeToGoal) {
438 switch (flyState) {
439 case FLY_CRUISING: {
440 // NOTE: should CMD_LOAD_ONTO be here?
441 const bool isTransporter = (dynamic_cast<CTransportUnit*>(owner) != NULL);
442 const bool hasLoadCmds = isTransporter &&
443 !owner->commandAI->commandQue.empty() &&
444 (owner->commandAI->commandQue.front().GetID() == CMD_LOAD_ONTO ||
445 owner->commandAI->commandQue.front().GetID() == CMD_LOAD_UNITS);
446 // [?] transport aircraft need some time to detect that they can pickup
447 const bool canLoad = isTransporter && (++waitCounter < ((GAME_SPEED << 1) - 5));
448 const bool isBusy = IsUnitBusy(owner);
449
450 if (!CanLand(isBusy) || (canLoad && hasLoadCmds)) {
451 wantedSpeed = ZeroVector;
452
453 if (isTransporter) {
454 if (waitCounter > (GAME_SPEED << 1)) {
455 wantedHeight = orgWantedHeight;
456 }
457
458 SetState(AIRCRAFT_HOVERING);
459 return;
460 } else {
461 if (!isBusy) {
462 wantToStop = true;
463
464 // NOTE:
465 // this is not useful, next frame UpdateFlying()
466 // will change it to _LANDING because wantToStop
467 // is now true
468 SetState(AIRCRAFT_HOVERING);
469 return;
470 }
471 }
472 } else {
473 wantedHeight = orgWantedHeight;
474
475 SetState(AIRCRAFT_LANDING);
476 return;
477 }
478 } break;
479
480 case FLY_CIRCLING: {
481 if ((++waitCounter) > ((GAME_SPEED * 3) + 10)) {
482 if (airStrafe) {
483 float3 relPos = pos - circlingPos;
484
485 if (relPos.x < 0.0001f && relPos.x > -0.0001f) {
486 relPos.x = 0.0001f;
487 }
488
489 static CMatrix44f rot(0.0f, fastmath::PI / 4.0f, 0.0f);
490
491 // make sure the point is on the circle, go there in a straight line
492 goalPos = circlingPos + (rot.Mul(relPos.Normalize2D()) * goalDistance);
493 }
494 waitCounter = 0;
495 }
496 } break;
497
498 case FLY_ATTACKING: {
499 if (airStrafe) {
500 float3 relPos = pos - circlingPos;
501
502 if (relPos.x < 0.0001f && relPos.x > -0.0001f) {
503 relPos.x = 0.0001f;
504 }
505
506 CMatrix44f rot;
507
508 if (gs->randFloat() > 0.5f) {
509 rot.RotateY(0.6f + gs->randFloat() * 0.6f);
510 } else {
511 rot.RotateY(-(0.6f + gs->randFloat() * 0.6f));
512 }
513
514 // Go there in a straight line
515 goalPos = circlingPos + (rot.Mul(relPos.Normalize2D()) * goalDistance);
516 }
517 } break;
518
519 case FLY_LANDING: {
520 } break;
521 }
522 }
523
524 // not "close" to goal yet, so keep going
525 // use 2D math since goal is on the ground
526 // but we are not
527 goalVec.y = 0.0f;
528
529 // if we are close to our goal, we should
530 // adjust speed st. we never overshoot it
531 // (by respecting current brake-distance)
532 const float curSpeed = owner->speed.Length2D();
533 const float brakeDist = (0.5f * curSpeed * curSpeed) / decRate;
534 const float goalDist = goalVec.Length() + 0.1f;
535 const float goalSpeed =
536 (maxSpeed ) * (goalDist > brakeDist) +
537 (curSpeed - decRate) * (goalDist <= brakeDist);
538
539 if (goalDist > goalSpeed) {
540 // update our velocity and heading so long as goal is still
541 // further away than the distance we can cover in one frame
542 // we must use a variable-size "dead zone" to avoid freezing
543 // in mid-air or oscillation behavior at very close distances
544 // NOTE:
545 // wantedSpeed is a vector, so even aircraft with turnRate=0
546 // are coincidentally able to reach any goal by side-strafing
547 wantedHeading = GetHeadingFromVector(goalVec.x, goalVec.z);
548 wantedSpeed = (goalVec / goalDist) * goalSpeed;
549 } else {
550 // switch to hovering (if !CanLand()))
551 if (flyState != FLY_ATTACKING) {
552 ExecuteStop();
553 }
554 }
555
556 // redundant, done in Update()
557 // UpdateHeading();
558 UpdateAirPhysics();
559
560 // Point toward goal or forward - unless we just passed it to get to another goal
561 if ((flyState == FLY_ATTACKING) || (flyState == FLY_CIRCLING)) {
562 goalVec = circlingPos - pos;
563 } else {
564 const bool b0 = (flyState != FLY_LANDING && (owner->commandAI->HasMoreMoveCommands()));
565 const bool b1 = (goalDist < brakeDist && goalDist > 1.0f);
566
567 if (b0 && b1) {
568 goalVec = owner->frontdir;
569 } else {
570 goalVec = goalPos - pos;
571 }
572 }
573
574 if (goalVec.SqLength2D() > 1.0f) {
575 // update heading again in case goalVec changed
576 wantedHeading = GetHeadingFromVector(goalVec.x, goalVec.z);
577 }
578 }
579
580
581
UpdateLanding()582 void CHoverAirMoveType::UpdateLanding()
583 {
584 const float3& pos = owner->pos;
585 const float4& spd = owner->speed;
586
587 // We want to land, and therefore cancel our speed first
588 wantedSpeed = ZeroVector;
589
590 // Hang around for a while so queued commands have a chance to take effect
591 if ((++waitCounter) < GAME_SPEED) {
592 UpdateAirPhysics();
593 return;
594 }
595
596 if (reservedLandingPos.x < 0.0f) {
597 if (CanLandAt(pos)) {
598 // found a landing spot
599 reservedLandingPos = pos;
600 goalPos = pos;
601
602 owner->Block();
603 owner->Deactivate();
604 owner->script->StopMoving();
605 } else {
606 if (goalPos.SqDistance2D(pos) < (30.0f * 30.0f)) {
607 // randomly pick another landing spot and try again
608 goalPos += (gs->randVector() * 300.0f);
609 goalPos.ClampInBounds();
610
611 // exact landing pos failed, make sure finishcommand is called anyway
612 progressState = AMoveType::Failed;
613 }
614
615 flyState = FLY_LANDING;
616 UpdateFlying();
617 return;
618 }
619 }
620
621 // We should wait until we actually have stopped smoothly
622 if (spd.SqLength2D() > 1.0f) {
623 UpdateFlying();
624 UpdateAirPhysics();
625 return;
626 }
627
628 // We have stopped, time to land
629 // NOTE: wantedHeight is interpreted as RELATIVE altitude
630 const float gh = CGround::GetHeightAboveWater(pos.x, pos.z);
631 const float gah = CGround::GetHeightReal(pos.x, pos.z);
632 float altitude = (wantedHeight = 0.0f);
633
634 // can we submerge and are we still above water?
635 if ((owner->unitDef->canSubmerge) && (gah < 0.0f)) {
636 altitude = pos.y - gah;
637 } else {
638 altitude = pos.y - gh;
639 }
640
641 UpdateAirPhysics();
642
643 // collision detection does not let us get
644 // closer to the ground than <radius> elmos
645 // (wrt. midPos.y)
646 if (altitude <= owner->radius) {
647 SetState(AIRCRAFT_LANDED);
648 }
649 }
650
651
652
UpdateHeading()653 void CHoverAirMoveType::UpdateHeading()
654 {
655 if (aircraftState == AIRCRAFT_TAKEOFF && !owner->unitDef->factoryHeadingTakeoff)
656 return;
657 // UpdateDirVectors() resets our up-vector but we
658 // might have residual pitch angle from attacking
659 // if (aircraftState == AIRCRAFT_LANDING)
660 // return;
661
662 SyncedSshort& heading = owner->heading;
663 const short deltaHeading = forceHeading?
664 (forceHeadingTo - heading):
665 (wantedHeading - heading);
666
667 if (deltaHeading > 0) {
668 heading += std::min(deltaHeading, short(turnRate));
669 } else {
670 heading += std::max(deltaHeading, short(-turnRate));
671 }
672
673 owner->UpdateDirVectors(owner->IsOnGround());
674 owner->UpdateMidAndAimPos();
675 }
676
UpdateBanking(bool noBanking)677 void CHoverAirMoveType::UpdateBanking(bool noBanking)
678 {
679 if (aircraftState != AIRCRAFT_FLYING && aircraftState != AIRCRAFT_HOVERING)
680 return;
681
682 if (!owner->upright) {
683 float wantedPitch = 0.0f;
684
685 if (aircraftState == AIRCRAFT_FLYING && flyState == FLY_ATTACKING && circlingPos.y < owner->pos.y) {
686 wantedPitch = (circlingPos.y - owner->pos.y) / circlingPos.distance(owner->pos);
687 }
688
689 currentPitch = mix(currentPitch, wantedPitch, 0.05f);
690 }
691
692 // always positive
693 const float bankLimit = std::min(1.0f, goalPos.SqDistance2D(owner->pos) * Square(0.15f));
694 float wantedBank = 0.0f;
695
696 SyncedFloat3& frontDir = owner->frontdir;
697 SyncedFloat3& upDir = owner->updir;
698 SyncedFloat3& rightDir3D = owner->rightdir;
699 SyncedFloat3 rightDir2D;
700
701 // pitching does not affect rightdir, but...
702 frontDir.y = currentPitch;
703 frontDir.Normalize();
704
705 // we want a flat right-vector to calculate wantedBank
706 rightDir2D = frontDir.cross(UpVector);
707
708 if (!noBanking && bankingAllowed)
709 wantedBank = rightDir2D.dot(deltaSpeed) / accRate * 0.5f;
710 if (Square(wantedBank) > bankLimit)
711 wantedBank = math::sqrt(bankLimit);
712
713 // Adjust our banking to the desired value
714 if (currentBank > wantedBank) {
715 currentBank -= std::min(0.03f, currentBank - wantedBank);
716 } else {
717 currentBank += std::min(0.03f, wantedBank - currentBank);
718 }
719
720
721 upDir = rightDir2D.cross(frontDir);
722 upDir = upDir * math::cos(currentBank) + rightDir2D * math::sin(currentBank);
723 upDir = upDir.Normalize();
724 rightDir3D = frontDir.cross(upDir);
725
726 // NOTE:
727 // heading might not be fully in sync with frontDir due to the
728 // vector<-->heading mapping not being 1:1 (such that heading
729 // != GetHeadingFromVector(frontDir)), therefore this call can
730 // cause owner->heading to change --> unwanted if forceHeading
731 //
732 // it is "safe" to skip because only frontDir.y is manipulated
733 // above so its xz-direction does not change, but the problem
734 // should really be fixed elsewhere
735 if (!forceHeading) {
736 owner->SetHeadingFromDirection();
737 }
738
739 owner->UpdateMidAndAimPos();
740 }
741
742
UpdateVerticalSpeed(const float4 & spd,float curRelHeight,float curVertSpeed) const743 void CHoverAirMoveType::UpdateVerticalSpeed(const float4& spd, float curRelHeight, float curVertSpeed) const
744 {
745 float wh = wantedHeight; // wanted RELATIVE height (altitude)
746 float ws = 0.0f; // wanted vertical speed
747
748 owner->SetVelocity((spd * XZVector) + (UpVector * curVertSpeed));
749
750 if (lastColWarningType == 2) {
751 const float3 dir = lastColWarning->midPos - owner->midPos;
752 const float3 sdir = lastColWarning->speed - spd;
753
754 if (spd.dot(dir + sdir * 20.0f) < 0.0f) {
755 if (lastColWarning->midPos.y > owner->pos.y) {
756 wh -= 30.0f;
757 } else {
758 wh += 50.0f;
759 }
760 }
761 }
762
763 if (curRelHeight < wh) {
764 ws = altitudeRate;
765
766 if ((spd.y > 0.0001f) && (((wh - curRelHeight) / spd.y) * accRate * 1.5f) < spd.y) {
767 ws = 0.0f;
768 }
769 } else {
770 ws = -altitudeRate;
771
772 if ((spd.y < -0.0001f) && (((wh - curRelHeight) / spd.y) * accRate * 0.7f) < -spd.y) {
773 ws = 0.0f;
774 }
775 }
776
777 ws *= (1 - owner->beingBuilt);
778 // note: don't want this in case unit is built on some raised platform?
779 wh *= (1 - owner->beingBuilt);
780
781 if (math::fabs(wh - curRelHeight) > 2.0f) {
782 if (spd.y > ws) {
783 owner->SetVelocity((spd * XZVector) + (UpVector * std::max(ws, spd.y - accRate * 1.5f)));
784 } else {
785 // accelerate upward faster if close to ground
786 owner->SetVelocity((spd * XZVector) + (UpVector * std::min(ws, spd.y + accRate * ((curRelHeight < 20.0f)? 2.0f: 0.7f))));
787 }
788 } else {
789 owner->SetVelocity((spd * XZVector) + (UpVector * spd.y * 0.95f));
790 }
791
792 // finally update w-component
793 owner->SetSpeed(spd);
794 }
795
796
UpdateAirPhysics()797 void CHoverAirMoveType::UpdateAirPhysics()
798 {
799 const float3& pos = owner->pos;
800 const float4& spd = owner->speed;
801
802 // copy vertical speed
803 const float yspeed = spd.y;
804
805 if (((gs->frameNum + owner->id) & 3) == 0) {
806 CheckForCollision();
807 }
808
809 // cancel out vertical speed, acc and dec are applied in xz-plane
810 owner->SetVelocity(spd * XZVector);
811
812 const float3 deltaSpeed = wantedSpeed - spd;
813 const float deltaDotSpeed = deltaSpeed.dot(spd);
814 const float deltaSpeedSq = deltaSpeed.SqLength();
815
816 if (deltaDotSpeed >= 0.0f) {
817 // accelerate
818 if (deltaSpeedSq < Square(accRate)) {
819 owner->SetVelocity(wantedSpeed);
820 } else {
821 if (deltaSpeedSq > 0.0f) {
822 owner->SetVelocity(spd + (deltaSpeed / math::sqrt(deltaSpeedSq) * accRate));
823 }
824 }
825 } else {
826 // deccelerate
827 if (deltaSpeedSq < Square(decRate)) {
828 owner->SetVelocity(wantedSpeed);
829 } else {
830 if (deltaSpeedSq > 0.0f) {
831 owner->SetVelocity(spd + (deltaSpeed / math::sqrt(deltaSpeedSq) * decRate));
832 }
833 }
834 }
835
836 // absolute and relative ground height at (pos.x, pos.z)
837 // if this aircraft uses the smoothmesh, these values are
838 // calculated with respect to that (for changing vertical
839 // speed, but not for ground collision)
840 float curAbsHeight = owner->unitDef->canSubmerge?
841 CGround::GetHeightReal(pos.x, pos.z):
842 CGround::GetHeightAboveWater(pos.x, pos.z);
843
844 // always stay above the actual terrain (therefore either the value of
845 // <midPos.y - radius> or pos.y must never become smaller than the real
846 // ground height)
847 // note: unlike StrafeAirMoveType, UpdateTakeoff and UpdateLanding call
848 // UpdateAirPhysics() so we ignore terrain while we are in those states
849 if (modInfo.allowAircraftToHitGround) {
850 const bool groundContact = (curAbsHeight > (owner->midPos.y - owner->radius));
851 const bool handleContact = (aircraftState != AIRCRAFT_LANDED && aircraftState != AIRCRAFT_TAKEOFF && padStatus == PAD_STATUS_FLYING);
852
853 if (groundContact && handleContact) {
854 owner->Move(UpVector * (curAbsHeight - (owner->midPos.y - owner->radius) + 0.01f), true);
855 }
856 }
857
858 if (UseSmoothMesh()) {
859 curAbsHeight = owner->unitDef->canSubmerge?
860 smoothGround->GetHeight(pos.x, pos.z):
861 smoothGround->GetHeightAboveWater(pos.x, pos.z);
862 }
863
864 // restore original vertical speed, then compute new
865 UpdateVerticalSpeed(spd, pos.y - curAbsHeight, yspeed);
866
867 if (modInfo.allowAircraftToLeaveMap || (pos + spd).IsInBounds()) {
868 owner->Move(spd, true);
869 }
870 }
871
872
UpdateMoveRate()873 void CHoverAirMoveType::UpdateMoveRate()
874 {
875 int curRate = 0;
876
877 // currentspeed is not used correctly for vertical movement, so compensate with this hax
878 if ((aircraftState == AIRCRAFT_LANDING) || (aircraftState == AIRCRAFT_TAKEOFF)) {
879 curRate = 1;
880 } else {
881 curRate = (owner->speed.w / maxSpeed) * 3;
882 curRate = std::max(0, std::min(curRate, 2));
883 }
884
885 if (curRate != lastMoveRate) {
886 owner->script->MoveRate(curRate);
887 lastMoveRate = curRate;
888 }
889 }
890
891
Update()892 bool CHoverAirMoveType::Update()
893 {
894 const float3 lastPos = owner->pos;
895 const float4 lastSpd = owner->speed;
896
897 AAirMoveType::Update();
898
899 if ((owner->IsStunned() && !owner->IsCrashing()) || owner->beingBuilt) {
900 wantedSpeed = ZeroVector;
901
902 UpdateAirPhysics();
903 return (HandleCollisions(collide && !owner->beingBuilt && (padStatus == PAD_STATUS_FLYING) && (aircraftState != AIRCRAFT_TAKEOFF)));
904 }
905
906 // allow us to stop if wanted (changes aircraft state)
907 if (wantToStop)
908 ExecuteStop();
909
910 if (aircraftState != AIRCRAFT_CRASHING) {
911 if (owner->UnderFirstPersonControl()) {
912 SetState(AIRCRAFT_FLYING);
913
914 const FPSUnitController& con = owner->fpsControlPlayer->fpsController;
915
916 const float3 forward = con.viewDir;
917 const float3 right = forward.cross(UpVector);
918 const float3 nextPos = lastPos + owner->speed;
919
920 float3 flatForward = forward;
921 flatForward.Normalize2D();
922
923 wantedSpeed = ZeroVector;
924
925 if (con.forward) wantedSpeed += flatForward;
926 if (con.back ) wantedSpeed -= flatForward;
927 if (con.right ) wantedSpeed += right;
928 if (con.left ) wantedSpeed -= right;
929
930 wantedSpeed.Normalize();
931 wantedSpeed *= maxSpeed;
932
933 if (!nextPos.IsInBounds()) {
934 owner->SetVelocityAndSpeed(ZeroVector);
935 }
936
937 UpdateAirPhysics();
938 wantedHeading = GetHeadingFromVector(flatForward.x, flatForward.z);
939 }
940
941 if (reservedPad != NULL) {
942 MoveToRepairPad();
943
944 if (padStatus >= PAD_STATUS_LANDING) {
945 flyState = FLY_LANDING;
946 }
947 }
948 }
949
950 switch (aircraftState) {
951 case AIRCRAFT_LANDED:
952 UpdateLanded();
953 break;
954 case AIRCRAFT_TAKEOFF:
955 UpdateTakeoff();
956 break;
957 case AIRCRAFT_FLYING:
958 UpdateFlying();
959 break;
960 case AIRCRAFT_LANDING:
961 UpdateLanding();
962 break;
963 case AIRCRAFT_HOVERING:
964 UpdateHovering();
965 break;
966 case AIRCRAFT_CRASHING: {
967 UpdateAirPhysics();
968
969 if ((CGround::GetHeightAboveWater(owner->pos.x, owner->pos.z) + 5.0f + owner->radius) > owner->pos.y) {
970 owner->ClearPhysicalStateBit(CSolidObject::PSTATE_BIT_CRASHING);
971 owner->KillUnit(NULL, true, false);
972 } else {
973 #define SPIN_DIR(o) ((o->id & 1) * 2 - 1)
974 wantedHeading = GetHeadingFromVector(owner->rightdir.x * SPIN_DIR(owner), owner->rightdir.z * SPIN_DIR(owner));
975 wantedHeight = 0.0f;
976 #undef SPIN_DIR
977 }
978
979 new CSmokeProjectile(owner, owner->midPos, gs->randVector() * 0.08f, 100 + gs->randFloat() * 50, 5, 0.2f, 0.4f);
980 } break;
981 }
982
983 if (lastSpd == ZeroVector && owner->speed != ZeroVector) { owner->script->StartMoving(false); }
984 if (lastSpd != ZeroVector && owner->speed == ZeroVector) { owner->script->StopMoving(); }
985
986 // Banking requires deltaSpeed.y = 0
987 deltaSpeed = owner->speed - lastSpd;
988 deltaSpeed.y = 0.0f;
989
990 // Turn and bank and move; update dirs
991 UpdateHeading();
992 UpdateBanking(aircraftState == AIRCRAFT_HOVERING);
993
994 return (HandleCollisions(collide && !owner->beingBuilt && (padStatus == PAD_STATUS_FLYING) && (aircraftState != AIRCRAFT_TAKEOFF)));
995 }
996
SlowUpdate()997 void CHoverAirMoveType::SlowUpdate()
998 {
999 UpdateFuel();
1000
1001 // HoverAirMoveType aircraft are controlled by AirCAI's,
1002 // but only MobileCAI's reserve pads so we need to do
1003 // this for ourselves
1004 if (reservedPad == NULL && aircraftState == AIRCRAFT_FLYING && WantsRepair()) {
1005 CAirBaseHandler::LandingPad* lp = airBaseHandler->FindAirBase(owner, owner->unitDef->minAirBasePower, true);
1006
1007 if (lp != NULL) {
1008 AAirMoveType::ReservePad(lp);
1009 }
1010 }
1011
1012 UpdateMoveRate();
1013 // note: NOT AAirMoveType::SlowUpdate
1014 AMoveType::SlowUpdate();
1015 }
1016
1017 /// Returns true if indicated position is a suitable landing spot
CanLandAt(const float3 & pos) const1018 bool CHoverAirMoveType::CanLandAt(const float3& pos) const
1019 {
1020 if (forceHeading)
1021 return true;
1022 if (!CanLand(false))
1023 return false;
1024 if (!pos.IsInBounds())
1025 return false;
1026
1027 const UnitDef* ud = owner->unitDef;
1028 const float gah = CGround::GetApproximateHeight(pos.x, pos.z);
1029
1030 if ((gah < 0.0f) && !(ud->floatOnWater || ud->canSubmerge)) {
1031 return false;
1032 }
1033
1034 const int2 mp = owner->GetMapPos(pos);
1035
1036 for (int z = mp.y; z < mp.y + owner->zsize; z++) {
1037 for (int x = mp.x; x < mp.x + owner->xsize; x++) {
1038 if (groundBlockingObjectMap->GroundBlocked(x, z, owner)) {
1039 return false;
1040 }
1041 }
1042 }
1043
1044 return true;
1045 }
1046
ForceHeading(short h)1047 void CHoverAirMoveType::ForceHeading(short h)
1048 {
1049 forceHeading = true;
1050 forceHeadingTo = h;
1051 }
1052
SetWantedAltitude(float altitude)1053 void CHoverAirMoveType::SetWantedAltitude(float altitude)
1054 {
1055 if (altitude == 0.0f) {
1056 wantedHeight = orgWantedHeight;
1057 } else {
1058 wantedHeight = altitude;
1059 }
1060 }
1061
SetDefaultAltitude(float altitude)1062 void CHoverAirMoveType::SetDefaultAltitude(float altitude)
1063 {
1064 wantedHeight = altitude;
1065 orgWantedHeight = altitude;
1066 }
1067
Takeoff()1068 void CHoverAirMoveType::Takeoff()
1069 {
1070 if (aircraftState == AAirMoveType::AIRCRAFT_LANDED) {
1071 SetState(AAirMoveType::AIRCRAFT_TAKEOFF);
1072 }
1073 if (aircraftState == AAirMoveType::AIRCRAFT_LANDING) {
1074 SetState(AAirMoveType::AIRCRAFT_FLYING);
1075 }
1076 }
1077
Land()1078 void CHoverAirMoveType::Land()
1079 {
1080 if (aircraftState == AAirMoveType::AIRCRAFT_HOVERING) {
1081 SetState(AAirMoveType::AIRCRAFT_FLYING); // switch to flying, it performs necessary checks to prepare for landing
1082 }
1083 }
1084
HandleCollisions(bool checkCollisions)1085 bool CHoverAirMoveType::HandleCollisions(bool checkCollisions)
1086 {
1087 const float3& pos = owner->pos;
1088
1089 if (pos != oldPos) {
1090 oldPos = pos;
1091
1092 bool hitBuilding = false;
1093
1094 // check for collisions if not on a pad, not being built, or not taking off
1095 // includes an extra condition for transports, which are exempt while loading
1096 if (!forceHeading && checkCollisions) {
1097 const vector<CUnit*>& nearUnits = quadField->GetUnitsExact(pos, owner->radius + 6);
1098
1099 for (vector<CUnit*>::const_iterator ui = nearUnits.begin(); ui != nearUnits.end(); ++ui) {
1100 CUnit* unit = *ui;
1101
1102 if (unit->transporter != NULL)
1103 continue;
1104
1105 const float sqDist = (pos - unit->pos).SqLength();
1106 const float totRad = owner->radius + unit->radius;
1107
1108 if (sqDist <= 0.1f || sqDist >= (totRad * totRad))
1109 continue;
1110
1111 const float dist = math::sqrt(sqDist);
1112 const float3 dif = (pos - unit->pos).Normalize();
1113
1114 if (unit->mass >= CSolidObject::DEFAULT_MASS || unit->immobile) {
1115 owner->Move(-dif * (dist - totRad), true);
1116 owner->SetVelocity(owner->speed * 0.99f);
1117
1118 hitBuilding = true;
1119 } else {
1120 const float part = owner->mass / (owner->mass + unit->mass);
1121 const float colSpeed = -owner->speed.dot(dif) + unit->speed.dot(dif);
1122
1123 owner->Move(-dif * (dist - totRad) * (1.0f - part), true);
1124 owner->SetVelocity(owner->speed + (dif * colSpeed * (1.0f - part)));
1125
1126 if (!unit->UsingScriptMoveType()) {
1127 unit->SetVelocityAndSpeed(unit->speed - (dif * colSpeed * (part)));
1128 unit->Move(dif * (dist - totRad) * (part), true);
1129 }
1130 }
1131 }
1132
1133 // update speed.w
1134 owner->SetSpeed(owner->speed);
1135 }
1136
1137 if (hitBuilding && owner->IsCrashing()) {
1138 owner->KillUnit(NULL, true, false);
1139 return true;
1140 }
1141
1142 if (pos.x < 0.0f) {
1143 owner->Move( RgtVector * 0.6f, true);
1144 } else if (pos.x > float3::maxxpos) {
1145 owner->Move(-RgtVector * 0.6f, true);
1146 }
1147
1148 if (pos.z < 0.0f) {
1149 owner->Move( FwdVector * 0.6f, true);
1150 } else if (pos.z > float3::maxzpos) {
1151 owner->Move(-FwdVector * 0.6f, true);
1152 }
1153
1154 return true;
1155 }
1156
1157 return false;
1158 }
1159
1160
1161
SetMemberValue(unsigned int memberHash,void * memberValue)1162 bool CHoverAirMoveType::SetMemberValue(unsigned int memberHash, void* memberValue) {
1163 // try the generic members first
1164 if (AMoveType::SetMemberValue(memberHash, memberValue))
1165 return true;
1166
1167 #define MEMBER_CHARPTR_HASH(memberName) HsiehHash(memberName, strlen(memberName), 0)
1168 #define MEMBER_LITERAL_HASH(memberName) HsiehHash(memberName, sizeof(memberName) - 1, 0)
1169
1170 #define DONTLAND_MEMBER_IDX 1
1171 #define WANTEDHEIGHT_MEMBER_IDX 0
1172
1173 static const unsigned int boolMemberHashes[] = {
1174 MEMBER_LITERAL_HASH( "collide"),
1175 MEMBER_LITERAL_HASH( "dontLand"),
1176 MEMBER_LITERAL_HASH( "airStrafe"),
1177 MEMBER_LITERAL_HASH( "useSmoothMesh"),
1178 MEMBER_LITERAL_HASH("bankingAllowed"),
1179 };
1180 static const unsigned int floatMemberHashes[] = {
1181 MEMBER_LITERAL_HASH( "wantedHeight"),
1182 MEMBER_LITERAL_HASH( "accRate"),
1183 MEMBER_LITERAL_HASH( "decRate"),
1184 MEMBER_LITERAL_HASH( "turnRate"),
1185 MEMBER_LITERAL_HASH( "altitudeRate"),
1186 MEMBER_LITERAL_HASH( "currentBank"),
1187 MEMBER_LITERAL_HASH( "currentPitch"),
1188 MEMBER_LITERAL_HASH( "maxDrift"),
1189 };
1190
1191 #undef MEMBER_CHARPTR_HASH
1192 #undef MEMBER_LITERAL_HASH
1193
1194
1195 // unordered_map etc. perform dynallocs, so KISS here
1196 bool* boolMemberPtrs[] = {
1197 &collide,
1198 &dontLand,
1199 &airStrafe,
1200
1201 &useSmoothMesh,
1202 &bankingAllowed
1203 };
1204 float* floatMemberPtrs[] = {
1205 &wantedHeight,
1206
1207 &accRate,
1208 &decRate,
1209
1210 &turnRate,
1211 &altitudeRate,
1212
1213 ¤tBank,
1214 ¤tPitch,
1215
1216 &maxDrift
1217 };
1218
1219 // special cases
1220 if (memberHash == boolMemberHashes[DONTLAND_MEMBER_IDX]) {
1221 SetAllowLanding(!(*(reinterpret_cast<bool*>(memberValue))));
1222 return true;
1223 }
1224 if (memberHash == floatMemberHashes[WANTEDHEIGHT_MEMBER_IDX]) {
1225 SetDefaultAltitude(*(reinterpret_cast<float*>(memberValue)));
1226 return true;
1227 }
1228
1229 // note: <memberHash> should be calculated via HsiehHash
1230 for (unsigned int n = 0; n < sizeof(boolMemberPtrs) / sizeof(boolMemberPtrs[0]); n++) {
1231 if (memberHash == boolMemberHashes[n]) {
1232 *(boolMemberPtrs[n]) = *(reinterpret_cast<bool*>(memberValue));
1233 return true;
1234 }
1235 }
1236
1237 for (unsigned int n = 0; n < sizeof(floatMemberPtrs) / sizeof(floatMemberPtrs[0]); n++) {
1238 if (memberHash == floatMemberHashes[n]) {
1239 *(floatMemberPtrs[n]) = *(reinterpret_cast<float*>(memberValue));
1240 return true;
1241 }
1242 }
1243
1244 return false;
1245 }
1246
1247