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 		&currentBank,
1214 		&currentPitch,
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