1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 #include "GameHelper.h"
4 
5 #include "Camera.h"
6 #include "GameSetup.h"
7 #include "Game/GlobalUnsynced.h"
8 #include "Lua/LuaUI.h"
9 #include "Map/Ground.h"
10 #include "Map/MapDamage.h"
11 #include "Map/ReadMap.h"
12 #include "Rendering/Models/3DModel.h"
13 #include "Sim/Features/Feature.h"
14 #include "Sim/Features/FeatureDef.h"
15 #include "Sim/Misc/CollisionHandler.h"
16 #include "Sim/Misc/CollisionVolume.h"
17 #include "Sim/Misc/DamageArray.h"
18 #include "Sim/Misc/GeometricObjects.h"
19 #include "Sim/Misc/GroundBlockingObjectMap.h"
20 #include "Sim/Misc/LosHandler.h"
21 #include "Sim/Misc/QuadField.h"
22 #include "Sim/Misc/TeamHandler.h"
23 #include "Sim/Misc/RadarHandler.h"
24 #include "Sim/Misc/ModInfo.h"
25 #include "Sim/MoveTypes/MoveDefHandler.h"
26 #include "Sim/MoveTypes/MoveMath/MoveMath.h"
27 #include "Sim/Projectiles/ExplosionGenerator.h"
28 #include "Sim/Projectiles/ExplosionListener.h"
29 #include "Sim/Projectiles/Projectile.h"
30 #include "Sim/Units/CommandAI/MobileCAI.h"
31 #include "Sim/Units/UnitTypes/Factory.h"
32 #include "Sim/Units/BuildInfo.h"
33 #include "Sim/Units/UnitDef.h"
34 #include "Sim/Units/Unit.h"
35 #include "Sim/Units/UnitHandler.h"
36 #include "Sim/Weapons/WeaponDefHandler.h"
37 #include "Sim/Weapons/Weapon.h"
38 #include "System/EventHandler.h"
39 #include "System/myMath.h"
40 #include "System/Sound/ISoundChannels.h"
41 #include "System/Sync/SyncTracer.h"
42 
43 #define NUM_WAITING_DAMAGE_LISTS 128
44 
45 //////////////////////////////////////////////////////////////////////
46 // Construction/Destruction
47 //////////////////////////////////////////////////////////////////////
48 
49 CGameHelper* helper;
50 
51 
CGameHelper()52 CGameHelper::CGameHelper()
53 {
54 	stdExplosionGenerator = new CStdExplosionGenerator();
55 	waitingDamageLists.resize(NUM_WAITING_DAMAGE_LISTS);
56 }
57 
~CGameHelper()58 CGameHelper::~CGameHelper()
59 {
60 	for (unsigned int n = 0; n < waitingDamageLists.size(); ++n) {
61 		std::list<WaitingDamage*>& wd = waitingDamageLists[n];
62 
63 		while (!wd.empty()) {
64 			delete wd.back();
65 			wd.pop_back();
66 		}
67 	}
68 
69 	waitingDamageLists.clear();
70 	delete stdExplosionGenerator;
71 }
72 
73 
74 
75 //////////////////////////////////////////////////////////////////////
76 // Explosions/Damage
77 //////////////////////////////////////////////////////////////////////
78 
CalcImpulseScale(const DamageArray & damages,const float expDistanceMod)79 float CGameHelper::CalcImpulseScale(const DamageArray& damages, const float expDistanceMod)
80 {
81 	// limit the impulse to prevent later FP overflow
82 	// (several weapons have _default_ damage values in the order of 1e4,
83 	// which make the simulation highly unstable because they can impart
84 	// speeds of several thousand elmos/frame to units and throw them far
85 	// outside the map)
86 	// DamageArray::operator* scales damage multipliers,
87 	// not the impulse factor --> need to scale manually
88 	// by it for impulse
89 	const float impulseDmgMult = (damages.GetDefaultDamage() + damages.impulseBoost);
90 	const float rawImpulseScale = damages.impulseFactor * expDistanceMod * impulseDmgMult;
91 
92 	return Clamp(rawImpulseScale, -MAX_EXPLOSION_IMPULSE, MAX_EXPLOSION_IMPULSE);
93 }
94 
DoExplosionDamage(CUnit * unit,CUnit * owner,const float3 & expPos,const float expRadius,const float expSpeed,const float expEdgeEffect,const bool ignoreOwner,const DamageArray & damages,const int weaponDefID,const int projectileID)95 void CGameHelper::DoExplosionDamage(
96 	CUnit* unit,
97 	CUnit* owner,
98 	const float3& expPos,
99 	const float expRadius,
100 	const float expSpeed,
101 	const float expEdgeEffect,
102 	const bool ignoreOwner,
103 	const DamageArray& damages,
104 	const int weaponDefID,
105 	const int projectileID
106 ) {
107 	assert(unit != NULL);
108 
109 	if (ignoreOwner && (unit == owner))
110 		return;
111 
112 	const LocalModelPiece* lap = unit->GetLastAttackedPiece(gs->frameNum);
113 	const CollisionVolume* vol = unit->GetCollisionVolume(lap);
114 
115 	const float3& lapPos = (lap != NULL && vol == lap->GetCollisionVolume())? lap->GetAbsolutePos(): ZeroVector;
116 	const float3& volPos = vol->GetWorldSpacePos(unit, lapPos);
117 
118 	// linear damage falloff with distance
119 	const float expDist = vol->GetPointSurfaceDistance(unit, lap, expPos);
120 	const float expRim = expDist * expEdgeEffect;
121 
122 	// return early if (distance > radius)
123 	if (expDist >= expRadius)
124 		return;
125 
126 	// expEdgeEffect should be in [0, 1], so expRadius >= expDist >= expDist*expEdgeEffect
127 	assert(expRadius >= expRim);
128 
129 	// expMod will also be in [0, 1], no negatives
130 	// TODO: damage attenuation for underwater units from surface explosions?
131 	const float expDistanceMod = (expRadius - expDist) / (expRadius + 0.01f - expRim);
132 	const float modImpulseScale = CalcImpulseScale(damages, expDistanceMod);
133 
134 	// NOTE: if an explosion occurs right underneath a
135 	// unit's map footprint, it might cause damage even
136 	// if the unit's collision volume is greatly offset
137 	// (because CQuadField coverage is based exclusively
138 	// on unit->radius, so the DoDamage() iteration will
139 	// include units that should not be touched)
140 
141 	const float3 impulseDir = (volPos - expPos).SafeNormalize();
142 	const float3 expImpulse = impulseDir * modImpulseScale;
143 
144 	const DamageArray expDamages = damages * expDistanceMod;
145 
146 	if (expDist < (expSpeed * DIRECT_EXPLOSION_DAMAGE_SPEED_SCALE)) {
147 		// damage directly
148 		unit->DoDamage(expDamages, expImpulse, owner, weaponDefID, projectileID);
149 	} else {
150 		// damage later
151 		WaitingDamage* wd = new WaitingDamage((owner? owner->id: -1), unit->id, expDamages, expImpulse, weaponDefID, projectileID);
152 		waitingDamageLists[(gs->frameNum + int(expDist / expSpeed) - 3) & 127].push_front(wd);
153 	}
154 }
155 
DoExplosionDamage(CFeature * feature,CUnit * owner,const float3 & expPos,const float expRadius,const float expEdgeEffect,const DamageArray & damages,const int weaponDefID,const int projectileID)156 void CGameHelper::DoExplosionDamage(
157 	CFeature* feature,
158 	CUnit* owner,
159 	const float3& expPos,
160 	const float expRadius,
161 	const float expEdgeEffect,
162 	const DamageArray& damages,
163 	const int weaponDefID,
164 	const int projectileID
165 ) {
166 	assert(feature != NULL);
167 
168 	const CollisionVolume* vol = feature->GetCollisionVolume(NULL);
169 	const float3& volPos = vol->GetWorldSpacePos(feature, ZeroVector);
170 
171 	const float expDist = vol->GetPointSurfaceDistance(feature, NULL, expPos);
172 	const float expRim = expDist * expEdgeEffect;
173 
174 	if (expDist >= expRadius)
175 		return;
176 
177 	assert(expRadius >= expRim);
178 
179 	const float expDistanceMod = (expRadius - expDist) / (expRadius + 0.01f - expRim);
180 	const float modImpulseScale = CalcImpulseScale(damages, expDistanceMod);
181 
182 	const float3 impulseDir = (volPos - expPos).SafeNormalize();
183 	const float3 expImpulse = impulseDir * modImpulseScale;
184 
185 	feature->DoDamage(damages * expDistanceMod, expImpulse, owner, weaponDefID, projectileID);
186 }
187 
188 
189 
DamageObjectsInExplosionRadius(const ExplosionParams & params,const float expRad,const int weaponDefID)190 void CGameHelper::DamageObjectsInExplosionRadius(
191 	const ExplosionParams& params,
192 	const float expRad,
193 	const int weaponDefID
194 ) {
195 	static ObjectCache cache;
196 
197 	if (cache.Empty())
198 		cache.Init(unitHandler->MaxUnits(), unitHandler->MaxUnits());
199 
200 	std::vector<CUnit*>& units = cache.GetUnits();
201 	std::vector<CFeature*>& features = cache.GetFeatures();
202 
203 	const unsigned int oldNumUnits = *(cache.GetNumUnitsPtr());
204 	const unsigned int oldNumFeatures = *(cache.GetNumFeaturesPtr());
205 
206 	quadField->GetUnitsAndFeaturesColVol(params.pos, expRad, units, features, cache.GetNumUnitsPtr(), cache.GetNumFeaturesPtr());
207 
208 	const unsigned int newNumUnits = *(cache.GetNumUnitsPtr());
209 	const unsigned int newNumFeatures = *(cache.GetNumFeaturesPtr());
210 
211 	// damage all units within the explosion radius
212 	// NOTE:
213 	//   this can recursively trigger ::Explosion() again
214 	//   which would overwrite our object cache if we did
215 	//   not keep track of end-markers --> certain objects
216 	//   would not be damaged AT ALL (!)
217 	for (unsigned int n = oldNumUnits; n < newNumUnits; n++) {
218 		DoExplosionDamage(units[n], params.owner, params.pos, expRad, params.explosionSpeed, params.edgeEffectiveness, params.ignoreOwner, params.damages, weaponDefID, params.projectileID);
219 	}
220 
221 	// damage all features within the explosion radius
222 	for (unsigned int n = oldNumFeatures; n < newNumFeatures; n++) {
223 		DoExplosionDamage(features[n], params.owner, params.pos, expRad, params.edgeEffectiveness, params.damages, weaponDefID, params.projectileID);
224 	}
225 
226 	cache.Reset(oldNumUnits, oldNumFeatures);
227 }
228 
Explosion(const ExplosionParams & params)229 void CGameHelper::Explosion(const ExplosionParams& params) {
230 	const DamageArray& damages = params.damages;
231 
232 	// if weaponDef is NULL, this is a piece-explosion
233 	// (implicit damage-type -DAMAGE_EXPLOSION_DEBRIS)
234 	const WeaponDef* weaponDef = params.weaponDef;
235 
236 	const int weaponDefID = (weaponDef != NULL)? weaponDef->id: -CSolidObject::DAMAGE_EXPLOSION_DEBRIS;
237 	const int explosionID = (weaponDef != NULL)? weaponDef->impactExplosionGeneratorID: CExplosionGeneratorHandler::EXPGEN_ID_STANDARD;
238 
239 
240 	const float craterAOE = std::max(1.0f, params.craterAreaOfEffect);
241 	const float damageAOE = std::max(1.0f, params.damageAreaOfEffect);
242 
243 	const float realHeight = CGround::GetHeightReal(params.pos);
244 	const float altitude = (params.pos).y - realHeight;
245 
246 	// NOTE: event triggers before damage is applied to objects
247 	const bool noGfx = eventHandler.Explosion(weaponDefID, params.projectileID, params.pos, params.owner);
248 
249 	if (luaUI != NULL) {
250 		if (weaponDef != NULL && weaponDef->cameraShake > 0.0f) {
251 			luaUI->ShockFront(params.pos, weaponDef->cameraShake, damageAOE);
252 		}
253 	}
254 
255 	if (params.impactOnly) {
256 		if (params.hitUnit != NULL) {
257 			DoExplosionDamage(
258 				params.hitUnit,
259 				params.owner,
260 				params.pos,
261 				damageAOE,
262 				params.explosionSpeed,
263 				params.edgeEffectiveness,
264 				params.ignoreOwner,
265 				params.damages,
266 				weaponDefID,
267 				params.projectileID
268 			);
269 		}
270 
271 		if (params.hitFeature != NULL) {
272 			DoExplosionDamage(
273 				params.hitFeature,
274 				params.owner,
275 				params.pos,
276 				damageAOE,
277 				params.edgeEffectiveness,
278 				params.damages,
279 				weaponDefID,
280 				params.projectileID
281 			);
282 		}
283 	} else {
284 		DamageObjectsInExplosionRadius(params, damageAOE, weaponDefID);
285 
286 		// deform the map if the explosion was above-ground
287 		// (but had large enough radius to touch the ground)
288 		if (altitude >= -1.0f) {
289 			if (params.damageGround && !mapDamage->disabled && (craterAOE > altitude) && (damages.craterMult > 0.0f)) {
290 				// limit the depth somewhat
291 				const float craterDepth = damages.GetDefaultDamage() * (1.0f - (altitude / craterAOE));
292 				const float damageDepth = std::min(craterAOE * 10.0f, craterDepth);
293 				const float craterStrength = (damageDepth + damages.craterBoost) * damages.craterMult;
294 				const float craterRadius = craterAOE - altitude;
295 
296 				mapDamage->Explosion(params.pos, craterStrength, craterRadius);
297 			}
298 		}
299 	}
300 
301 	if (!noGfx) {
302 		explGenHandler->GenExplosion(
303 			explosionID,
304 			params.pos,
305 			params.dir,
306 			damages.GetDefaultDamage(),
307 			damageAOE,
308 			params.gfxMod,
309 			params.owner,
310 			params.hitUnit
311 		);
312 	}
313 
314 	CExplosionEvent explosionEvent(params.pos, damages.GetDefaultDamage(), damageAOE, weaponDef);
315 	CExplosionCreator::FireExplosionEvent(explosionEvent);
316 
317 	if (weaponDef != NULL) {
318 		const GuiSoundSet& soundSet = weaponDef->hitSound;
319 
320 		const unsigned int soundFlags = CCustomExplosionGenerator::GetFlagsFromHeight(params.pos.y, altitude);
321 		const unsigned int soundMask = CCustomExplosionGenerator::SPW_WATER | CCustomExplosionGenerator::SPW_UNDERWATER;
322 
323 		const int soundNum = ((soundFlags & soundMask) != 0);
324 		const int soundID = soundSet.getID(soundNum);
325 
326 		if (soundID > 0) {
327 			Channels::Battle->PlaySample(soundID, params.pos, soundSet.getVolume(soundNum));
328 		}
329 	}
330 }
331 
332 
333 
334 //////////////////////////////////////////////////////////////////////
335 // Spatial unit queries
336 //////////////////////////////////////////////////////////////////////
337 
338 /**
339  * @brief Generic spatial unit query.
340  *
341  * Filter should implement two methods:
342  *  - bool Team(int allyTeam): returns true if this allyteam should be considered
343  *  - bool Unit(const CUnit*): returns true if the unit should be returned
344  *
345  * Query should implement three methods:
346  *  - float3 GetPos(): returns the center of the (circular) search area
347  *  - float GetRadius(): returns the radius of the search area
348  *  - void AddUnit(const CUnit*): add the unit to the result
349  *
350  * The area as returned by Query is approximate; exact circular filtering
351  * should be implemented in the Query object if desired.
352  * (It isn't necessary for e.g. GetClosest** methods.)
353  */
354 template<typename TFilter, typename TQuery>
QueryUnits(TFilter filter,TQuery & query)355 static inline void QueryUnits(TFilter filter, TQuery& query)
356 {
357 	const vector<int> &quads = quadField->GetQuads(query.pos, query.radius);
358 
359 	const int tempNum = gs->tempNum++;
360 
361 	for (vector<int>::const_iterator qi = quads.begin(); qi != quads.end(); ++qi) {
362 		const CQuadField::Quad& quad = quadField->GetQuad(*qi);
363 		for (int t = 0; t < teamHandler->ActiveAllyTeams(); ++t) {
364 			if (!filter.Team(t)) {
365 				continue;
366 			}
367 			std::list<CUnit*>::const_iterator ui;
368 			const std::list<CUnit*>& allyTeamUnits = quad.teamUnits[t];
369 			for (ui = allyTeamUnits.begin(); ui != allyTeamUnits.end(); ++ui) {
370 				if ((*ui)->tempNum != tempNum) {
371 					(*ui)->tempNum = tempNum;
372 					if (filter.Unit(*ui)) {
373 						query.AddUnit(*ui);
374 					}
375 				}
376 			}
377 		}
378 	}
379 }
380 
381 
382 namespace {
383 	namespace Filter {
384 
385 		/**
386 		 * Base class for Filter::Friendly and Filter::Enemy.
387 		 */
388 		struct Base
389 		{
390 			const int searchAllyteam;
Base__anonc34641d40111::Filter::Base391 			Base(int at) : searchAllyteam(at) {}
392 		};
393 
394 		/**
395 		 * Look for friendly units only.
396 		 * All units are included by default.
397 		 */
398 		struct Friendly : public Base
399 		{
400 		public:
Friendly__anonc34641d40111::Filter::Friendly401 			Friendly(const CUnit* exclUnit, int allyTeam) : Base(allyTeam), excludeUnit(exclUnit) {}
Team__anonc34641d40111::Filter::Friendly402 			bool Team(int allyTeam) { return teamHandler->Ally(searchAllyteam, allyTeam); }
Unit__anonc34641d40111::Filter::Friendly403 			bool Unit(const CUnit* unit) { return (unit != excludeUnit); }
404 		protected:
405 			const CUnit* excludeUnit;
406 		};
407 
408 		/**
409 		 * Look for enemy units only.
410 		 * All units are included by default.
411 		 */
412 		struct Enemy : public Base
413 		{
414 		public:
Enemy__anonc34641d40111::Filter::Enemy415 			Enemy(const CUnit* exclUnit, int allyTeam) : Base(allyTeam), excludeUnit(exclUnit) {}
Team__anonc34641d40111::Filter::Enemy416 			bool Team(int allyTeam) { return !teamHandler->Ally(searchAllyteam, allyTeam); }
Unit__anonc34641d40111::Filter::Enemy417 			bool Unit(const CUnit* unit) { return (unit != excludeUnit && !unit->IsNeutral()); }
418 		protected:
419 			const CUnit* excludeUnit;
420 		};
421 
422 		/**
423 		 * Look for enemy units which are in LOS/Radar only.
424 		 */
425 		struct Enemy_InLos : public Enemy
426 		{
Enemy_InLos__anonc34641d40111::Filter::Enemy_InLos427 			Enemy_InLos(const CUnit* exclUnit, int allyTeam) : Enemy(exclUnit, allyTeam) {}
Unit__anonc34641d40111::Filter::Enemy_InLos428 			bool Unit(const CUnit* u) {
429 				return (u->losStatus[searchAllyteam] & (LOS_INLOS | LOS_INRADAR) && Enemy::Unit(u));
430 			}
431 		};
432 
433 		/**
434 		 * Look for enemy aircraft which are in LOS/Radar only.
435 		 */
436 		struct EnemyAircraft : public Enemy_InLos
437 		{
EnemyAircraft__anonc34641d40111::Filter::EnemyAircraft438 			EnemyAircraft(const CUnit* exclUnit, int allyTeam) : Enemy_InLos(exclUnit, allyTeam) {}
Unit__anonc34641d40111::Filter::EnemyAircraft439 			bool Unit(const CUnit* u) {
440 				return (u->unitDef->canfly && !u->IsCrashing() && Enemy_InLos::Unit(u));
441 			}
442 		};
443 
444 		/**
445 		 * Look for units of any team. Enemy units must be in LOS/Radar.
446 		 *
447 		 * NOT SYNCED
448 		 */
449 		struct Friendly_All_Plus_Enemy_InLos_NOT_SYNCED
450 		{
Team__anonc34641d40111::Filter::Friendly_All_Plus_Enemy_InLos_NOT_SYNCED451 			bool Team(int) const { return true; }
Unit__anonc34641d40111::Filter::Friendly_All_Plus_Enemy_InLos_NOT_SYNCED452 			bool Unit(const CUnit* u) const {
453 				return (u->allyteam == gu->myAllyTeam) ||
454 					   (u->losStatus[gu->myAllyTeam] & (LOS_INLOS | LOS_INRADAR)) ||
455 					   gu->spectatingFullView;
456 			}
457 		};
458 
459 		/**
460 		 * Delegates filtering to CMobileCAI::IsValidTarget.
461 		 *
462 		 * This is necessary in CMobileCAI and CAirCAI so they can select the closest
463 		 * enemy unit which they consider a valid target.
464 		 *
465 		 * Without the valid target condition, units don't attack anything if an
466 		 * the nearest enemy is an invalid target. (e.g. noChaseCategory)
467 		 */
468 		struct Enemy_InLos_ValidTarget : public Enemy_InLos
469 		{
470 			const CMobileCAI* const cai;
471 
Enemy_InLos_ValidTarget__anonc34641d40111::Filter::Enemy_InLos_ValidTarget472 			Enemy_InLos_ValidTarget(int at, const CMobileCAI* cai) :
473 				Enemy_InLos(NULL, at), cai(cai) {}
474 
Unit__anonc34641d40111::Filter::Enemy_InLos_ValidTarget475 			bool Unit(const CUnit* u) {
476 				return Enemy_InLos::Unit(u) && cai->IsValidTarget(u);
477 			}
478 		};
479 
480 	} // end of namespace Filter
481 
482 
483 	namespace Query {
484 
485 		/**
486 		 * Base class for Query objects, containing the basic methods needed by
487 		 * QueryUnits which defined the search area.
488 		 */
489 		struct Base
490 		{
491 			const float3& pos;
492 			const float radius;
493 			const float sqRadius;
Base__anonc34641d40111::Query::Base494 			Base(const float3& pos, float searchRadius) :
495 				pos(pos), radius(searchRadius), sqRadius(searchRadius * searchRadius) {}
496 		};
497 
498 		/**
499 		 * Return the closest unit.
500 		 */
501 		struct ClosestUnit : public Base
502 		{
503 		protected:
504 			float closeSqDist;
505 			CUnit* closeUnit;
506 
507 		public:
ClosestUnit__anonc34641d40111::Query::ClosestUnit508 			ClosestUnit(const float3& pos, float searchRadius) :
509 				Base(pos, searchRadius), closeSqDist(sqRadius), closeUnit(NULL) {}
510 
AddUnit__anonc34641d40111::Query::ClosestUnit511 			void AddUnit(CUnit* u) {
512 				const float sqDist = (pos - u->midPos).SqLength2D();
513 				if (sqDist <= closeSqDist) {
514 					closeSqDist = sqDist;
515 					closeUnit = u;
516 				}
517 			}
518 
GetClosestUnit__anonc34641d40111::Query::ClosestUnit519 			CUnit* GetClosestUnit() const { return closeUnit; }
520 		};
521 
522 		/**
523 		 * Return the closest unit, using GetUnitErrorPos
524 		 * instead of the unit's actual position.
525 		 *
526 		 * NOT SYNCED
527 		 */
528 		struct ClosestUnit_ErrorPos_NOT_SYNCED : public ClosestUnit
529 		{
ClosestUnit_ErrorPos_NOT_SYNCED__anonc34641d40111::Query::ClosestUnit_ErrorPos_NOT_SYNCED530 			ClosestUnit_ErrorPos_NOT_SYNCED(const float3& pos, float searchRadius) :
531 				ClosestUnit(pos, searchRadius) {}
532 
AddUnit__anonc34641d40111::Query::ClosestUnit_ErrorPos_NOT_SYNCED533 			void AddUnit(CUnit* u) {
534 				float3 unitPos;
535 				if (gu->spectatingFullView) {
536 					unitPos = u->midPos;
537 				} else {
538 					unitPos = u->GetErrorPos(gu->myAllyTeam);
539 				}
540 				const float sqDist = (pos - unitPos).SqLength2D();
541 				if (sqDist <= closeSqDist) {
542 					closeSqDist = sqDist;
543 					closeUnit = u;
544 				}
545 			}
546 		};
547 
548 		/**
549 		 * Returns the closest unit (3D) which may have LOS on the search position.
550 		 * LOS is spherical in the context of this query. Whether the unit actually has
551 		 * LOS depends on nearby obstacles.
552 		 *
553 		 * Search area just needs to touch the unit's radius: this query includes the
554 		 * target unit's radius.
555 		 *
556 		 * If canBeBlind is true then the LOS test is skipped.
557 		 */
558 		struct ClosestUnit_InLos : public Base
559 		{
560 		protected:
561 			float closeDist;
562 			CUnit* closeUnit;
563 			const bool canBeBlind;
564 
565 		public:
ClosestUnit_InLos__anonc34641d40111::Query::ClosestUnit_InLos566 			ClosestUnit_InLos(const float3& pos, float searchRadius, bool canBeBlind) :
567 				Base(pos, searchRadius + unitHandler->MaxUnitRadius()),
568 				closeDist(searchRadius), closeUnit(NULL), canBeBlind(canBeBlind) {}
569 
AddUnit__anonc34641d40111::Query::ClosestUnit_InLos570 			void AddUnit(CUnit* u) {
571 				// FIXME: use volumeBoundingRadius?
572 				// (more for consistency than need)
573 				const float dist = pos.distance(u->midPos) - u->radius;
574 
575 				if (dist <= closeDist &&
576 					(canBeBlind || u->losRadius * losHandler->losDiv > dist)) {
577 					closeDist = dist;
578 					closeUnit = u;
579 				}
580 			}
581 
GetClosestUnit__anonc34641d40111::Query::ClosestUnit_InLos582 			CUnit* GetClosestUnit() const { return closeUnit; }
583 		};
584 
585 		/**
586 		 * Returns the closest unit (2D) which may have LOS on the search position.
587 		 * Whether it actually has LOS depends on nearby obstacles.
588 		 *
589 		 * If canBeBlind is true then the LOS test is skipped.
590 		 */
591 		struct ClosestUnit_InLos_Cylinder : public ClosestUnit
592 		{
593 			const bool canBeBlind;
594 
ClosestUnit_InLos_Cylinder__anonc34641d40111::Query::ClosestUnit_InLos_Cylinder595 			ClosestUnit_InLos_Cylinder(const float3& pos, float searchRadius, bool canBeBlind) :
596 				ClosestUnit(pos, searchRadius), canBeBlind(canBeBlind) {}
597 
AddUnit__anonc34641d40111::Query::ClosestUnit_InLos_Cylinder598 			void AddUnit(CUnit* u) {
599 				const float sqDist = (pos - u->midPos).SqLength2D();
600 
601 				if (sqDist <= closeSqDist &&
602 					(canBeBlind || Square(u->losRadius * losHandler->losDiv) > sqDist)) {
603 					closeSqDist = sqDist;
604 					closeUnit = u;
605 				}
606 			}
607 		};
608 
609 		/**
610 		 * Return the unitIDs of all units exactly within the search area.
611 		 */
612 		struct AllUnitsById : public Base
613 		{
614 		protected:
615 			vector<int>& found;
616 
617 		public:
AllUnitsById__anonc34641d40111::Query::AllUnitsById618 			AllUnitsById(const float3& pos, float searchRadius, vector<int>& found) :
619 				Base(pos, searchRadius), found(found) {}
620 
AddUnit__anonc34641d40111::Query::AllUnitsById621 			void AddUnit(CUnit* u) {
622 				if ((pos - u->midPos).SqLength2D() <= sqRadius) {
623 					found.push_back(u->id);
624 				}
625 			}
626 		};
627 
628 	} // end of namespace Query
629 } // end of namespace
630 
631 // Use this instead of unit->tempNum here, because it requires a mutex lock that will deadlock if luaRules is invoked simultaneously.
632 // Not the cleanest solution, but faster than e.g. a std::set, and this function is called quite frequently.
633 static int tempTargetUnits[MAX_UNITS] = {0};
634 static int targetTempNum = 2;
635 
GenerateWeaponTargets(const CWeapon * weapon,const CUnit * lastTargetUnit,std::multimap<float,CUnit * > & targets)636 void CGameHelper::GenerateWeaponTargets(const CWeapon* weapon, const CUnit* lastTargetUnit, std::multimap<float, CUnit*>& targets)
637 {
638 	const CUnit* attacker = weapon->owner;
639 	const float radius    = weapon->range;
640 	const float3& pos     = attacker->pos;
641 	const float heightMod = weapon->heightMod;
642 	const float aHeight   = weapon->weaponPos.y;
643 
644 	const WeaponDef* weaponDef = weapon->weaponDef;
645 
646 	// how much damage the weapon deals over 1 second
647 	const float secDamage = weaponDef->damages.GetDefaultDamage() * weapon->salvoSize / weapon->reloadTime * GAME_SPEED;
648 	const bool paralyzer  = (weaponDef->damages.paralyzeDamageTime != 0);
649 
650 	const std::vector<int>& quads = quadField->GetQuads(pos, radius + (aHeight - std::max(0.0f, readMap->GetInitMinHeight())) * heightMod);
651 
652 	const int tempNum = targetTempNum++;
653 
654 	typedef std::vector<int>::const_iterator VectorIt;
655 	typedef std::list<CUnit*>::const_iterator ListIt;
656 
657 	for (VectorIt qi = quads.begin(); qi != quads.end(); ++qi) {
658 		for (int t = 0; t < teamHandler->ActiveAllyTeams(); ++t) {
659 			if (teamHandler->Ally(attacker->allyteam, t)) {
660 				continue;
661 			}
662 
663 			const std::list<CUnit*>& allyTeamUnits = quadField->GetQuad(*qi).teamUnits[t];
664 
665 			for (ListIt ui = allyTeamUnits.begin(); ui != allyTeamUnits.end(); ++ui) {
666 				CUnit* targetUnit = *ui;
667 				float targetPriority = 1.0f;
668 
669 				if (!(targetUnit->category & weapon->onlyTargetCategory)) {
670 					continue;
671 				}
672 				if (targetUnit->GetTransporter() != NULL) {
673 					if (!modInfo.targetableTransportedUnits)
674 						continue;
675 					// the transportee might be "hidden" below terrain, in which case we can't target it
676 					if (targetUnit->pos.y < CGround::GetHeightReal(targetUnit->pos.x, targetUnit->pos.z))
677 						continue;
678 				}
679 				if (tempTargetUnits[targetUnit->id] == tempNum) {
680 					continue;
681 				}
682 
683 				tempTargetUnits[targetUnit->id] = tempNum;
684 
685 				if (targetUnit->IsUnderWater() && !weaponDef->waterweapon) {
686 					continue;
687 				}
688 				if (targetUnit->isDead) {
689 					continue;
690 				}
691 
692 				float3 targPos;
693 				const unsigned short targetLOSState = targetUnit->losStatus[attacker->allyteam];
694 
695 				if (targetLOSState & LOS_INLOS) {
696 					targPos = targetUnit->aimPos;
697 				} else if (targetLOSState & LOS_INRADAR) {
698 					targPos = targetUnit->aimPos + (targetUnit->posErrorVector * radarHandler->GetAllyTeamRadarErrorSize(attacker->allyteam));
699 					targetPriority *= 10.0f;
700 				} else {
701 					continue;
702 				}
703 
704 				const float modRange = radius + (aHeight - targPos.y) * heightMod;
705 
706 				if ((pos - targPos).SqLength2D() > modRange * modRange) {
707 					continue;
708 				}
709 
710 				const float dist2D = (pos - targPos).Length2D();
711 				const float rangeMul = (dist2D * weaponDef->proximityPriority + modRange * 0.4f + 100.0f);
712 				const float damageMul = weaponDef->damages[targetUnit->armorType] * targetUnit->curArmorMultiple;
713 
714 				targetPriority *= rangeMul;
715 
716 				if (targetLOSState & LOS_INLOS) {
717 					targetPriority *= (secDamage + targetUnit->health);
718 
719 					if (targetUnit == lastTargetUnit) {
720 						targetPriority *= weapon->avoidTarget ? 10.0f : 0.4f;
721 					}
722 
723 					if (paralyzer && targetUnit->paralyzeDamage > (modInfo.paralyzeOnMaxHealth? targetUnit->maxHealth: targetUnit->health)) {
724 						targetPriority *= 4.0f;
725 					}
726 
727 					if (weapon->hasTargetWeight) {
728 						targetPriority *= weapon->TargetWeight(targetUnit);
729 					}
730 				} else {
731 					targetPriority *= (secDamage + 10000.0f);
732 				}
733 
734 				if (targetLOSState & LOS_PREVLOS) {
735 					targetPriority /= (damageMul * targetUnit->power * (0.7f + gs->randFloat() * 0.6f));
736 
737 					if (targetUnit->category & weapon->badTargetCategory) {
738 						targetPriority *= 100.0f;
739 					}
740 					if (targetUnit->IsCrashing()) {
741 						targetPriority *= 1000.0f;
742 					}
743 				}
744 
745 				if (!eventHandler.AllowWeaponTarget(attacker->id, targetUnit->id, weapon->weaponNum, weaponDef->id, &targetPriority)) {
746 					continue;
747 				}
748 
749 				targets.insert(std::pair<float, CUnit*>(targetPriority, targetUnit));
750 			}
751 		}
752 	}
753 
754 #ifdef TRACE_SYNC
755 	{
756 		tracefile << "[GenerateWeaponTargets] attackerID, attackRadius: " << attacker->id << ", " << radius << " ";
757 
758 		for (std::multimap<float, CUnit*>::const_iterator ti = targets.begin(); ti != targets.end(); ++ti)
759 			tracefile << "\tpriority: " << (ti->first) <<  ", targetID: " << (ti->second)->id <<  " ";
760 
761 		tracefile << "\n";
762 	}
763 #endif
764 }
765 
GetClosestUnit(const float3 & pos,float searchRadius)766 CUnit* CGameHelper::GetClosestUnit(const float3& pos, float searchRadius)
767 {
768 	Query::ClosestUnit_ErrorPos_NOT_SYNCED q(pos, searchRadius);
769 	QueryUnits(Filter::Friendly_All_Plus_Enemy_InLos_NOT_SYNCED(), q);
770 	return q.GetClosestUnit();
771 }
772 
GetClosestEnemyUnit(const CUnit * excludeUnit,const float3 & pos,float searchRadius,int searchAllyteam)773 CUnit* CGameHelper::GetClosestEnemyUnit(const CUnit* excludeUnit, const float3& pos, float searchRadius, int searchAllyteam)
774 {
775 	Query::ClosestUnit q(pos, searchRadius);
776 	QueryUnits(Filter::Enemy_InLos(excludeUnit, searchAllyteam), q);
777 	return q.GetClosestUnit();
778 }
779 
GetClosestValidTarget(const float3 & pos,float searchRadius,int searchAllyteam,const CMobileCAI * cai)780 CUnit* CGameHelper::GetClosestValidTarget(const float3& pos, float searchRadius, int searchAllyteam, const CMobileCAI* cai)
781 {
782 	Query::ClosestUnit q(pos, searchRadius);
783 	QueryUnits(Filter::Enemy_InLos_ValidTarget(searchAllyteam, cai), q);
784 	return q.GetClosestUnit();
785 }
786 
GetClosestEnemyUnitNoLosTest(const CUnit * excludeUnit,const float3 & pos,float searchRadius,int searchAllyteam,bool sphere,bool canBeBlind)787 CUnit* CGameHelper::GetClosestEnemyUnitNoLosTest(
788 	const CUnit* excludeUnit,
789 	const float3& pos,
790 	float searchRadius,
791 	int searchAllyteam,
792 	bool sphere,
793 	bool canBeBlind
794 ) {
795 	if (sphere) {
796 		// includes target radius
797 		Query::ClosestUnit_InLos q(pos, searchRadius, canBeBlind);
798 		QueryUnits(Filter::Enemy(excludeUnit, searchAllyteam), q);
799 		return q.GetClosestUnit();
800 	} else {
801 		// cylinder (doesn't include target radius)
802 		Query::ClosestUnit_InLos_Cylinder q(pos, searchRadius, canBeBlind);
803 		QueryUnits(Filter::Enemy(excludeUnit, searchAllyteam), q);
804 		return q.GetClosestUnit();
805 	}
806 }
807 
GetClosestFriendlyUnit(const CUnit * excludeUnit,const float3 & pos,float searchRadius,int searchAllyteam)808 CUnit* CGameHelper::GetClosestFriendlyUnit(const CUnit* excludeUnit, const float3& pos, float searchRadius, int searchAllyteam)
809 {
810 	Query::ClosestUnit q(pos, searchRadius);
811 	QueryUnits(Filter::Friendly(excludeUnit, searchAllyteam), q);
812 	return q.GetClosestUnit();
813 }
814 
GetClosestEnemyAircraft(const CUnit * excludeUnit,const float3 & pos,float searchRadius,int searchAllyteam)815 CUnit* CGameHelper::GetClosestEnemyAircraft(const CUnit* excludeUnit, const float3& pos, float searchRadius, int searchAllyteam)
816 {
817 	Query::ClosestUnit q(pos, searchRadius);
818 	QueryUnits(Filter::EnemyAircraft(excludeUnit, searchAllyteam), q);
819 	return q.GetClosestUnit();
820 }
821 
GetEnemyUnits(const float3 & pos,float searchRadius,int searchAllyteam,vector<int> & found)822 void CGameHelper::GetEnemyUnits(const float3& pos, float searchRadius, int searchAllyteam, vector<int> &found)
823 {
824 	Query::AllUnitsById q(pos, searchRadius, found);
825 	QueryUnits(Filter::Enemy_InLos(NULL, searchAllyteam), q);
826 }
827 
GetEnemyUnitsNoLosTest(const float3 & pos,float searchRadius,int searchAllyteam,vector<int> & found)828 void CGameHelper::GetEnemyUnitsNoLosTest(const float3& pos, float searchRadius, int searchAllyteam, vector<int> &found)
829 {
830 	Query::AllUnitsById q(pos, searchRadius, found);
831 	QueryUnits(Filter::Enemy(NULL, searchAllyteam), q);
832 }
833 
834 
835 //////////////////////////////////////////////////////////////////////
836 // Miscellaneous (i.e. not yet categorized)
837 //////////////////////////////////////////////////////////////////////
838 
BuggerOff(float3 pos,float radius,bool spherical,bool forced,int teamId,CUnit * excludeUnit)839 void CGameHelper::BuggerOff(float3 pos, float radius, bool spherical, bool forced, int teamId, CUnit* excludeUnit)
840 {
841 	const std::vector<CUnit*> &units = quadField->GetUnitsExact(pos, radius + SQUARE_SIZE, spherical);
842 	const int allyTeamId = teamHandler->AllyTeam(teamId);
843 
844 	for (std::vector<CUnit*>::const_iterator ui = units.begin(); ui != units.end(); ++ui) {
845 		CUnit* u = *ui;
846 
847 		// don't send BuggerOff commands to enemy units
848 		const int uAllyTeamId = u->allyteam;
849 		const bool allied = (
850 				teamHandler->Ally(uAllyTeamId,  allyTeamId) ||
851 				teamHandler->Ally(allyTeamId, uAllyTeamId));
852 
853 		if ((u != excludeUnit) && allied && ((!u->unitDef->pushResistant && !u->UsingScriptMoveType()) || forced)) {
854 			u->commandAI->BuggerOff(pos, radius + SQUARE_SIZE);
855 		}
856 	}
857 }
858 
859 
Pos2BuildPos(const BuildInfo & buildInfo,bool synced)860 float3 CGameHelper::Pos2BuildPos(const BuildInfo& buildInfo, bool synced)
861 {
862 	float3 pos;
863 
864 	if (buildInfo.GetXSize() & 2)
865 		pos.x = math::floor((buildInfo.pos.x              ) / (SQUARE_SIZE * 2)) * SQUARE_SIZE * 2 + SQUARE_SIZE;
866 	else
867 		pos.x = math::floor((buildInfo.pos.x + SQUARE_SIZE) / (SQUARE_SIZE * 2)) * SQUARE_SIZE * 2;
868 
869 	if (buildInfo.GetZSize() & 2)
870 		pos.z = math::floor((buildInfo.pos.z              ) / (SQUARE_SIZE * 2)) * SQUARE_SIZE * 2 + SQUARE_SIZE;
871 	else
872 		pos.z = math::floor((buildInfo.pos.z + SQUARE_SIZE) / (SQUARE_SIZE * 2)) * SQUARE_SIZE * 2;
873 
874 	pos.y = CGameHelper::GetBuildHeight(pos, buildInfo.def, synced);
875 	return pos;
876 }
877 
878 
879 struct SearchOffset {
880 	int dx,dy;
881 	int qdist; // dx*dx+dy*dy
882 };
SearchOffsetComparator(const SearchOffset & a,const SearchOffset & b)883 static bool SearchOffsetComparator (const SearchOffset& a, const SearchOffset& b)
884 {
885 	return a.qdist < b.qdist;
886 }
GetSearchOffsetTable(int radius)887 static const vector<SearchOffset>& GetSearchOffsetTable (int radius)
888 {
889 	static vector <SearchOffset> searchOffsets;
890 	unsigned int size = radius*radius*4;
891 	if (size > searchOffsets.size()) {
892 		searchOffsets.resize (size);
893 
894 		for (int y = 0; y < radius*2; y++)
895 			for (int x = 0; x < radius*2; x++)
896 			{
897 				SearchOffset& i = searchOffsets[y*radius*2 + x];
898 
899 				i.dx = x - radius;
900 				i.dy = y - radius;
901 				i.qdist = i.dx*i.dx + i.dy*i.dy;
902 			}
903 
904 		std::sort(searchOffsets.begin(), searchOffsets.end(), SearchOffsetComparator);
905 	}
906 
907 	return searchOffsets;
908 }
909 
910 //! only used by the AI callback of the same name
ClosestBuildSite(int team,const UnitDef * unitDef,float3 pos,float searchRadius,int minDist,int facing)911 float3 CGameHelper::ClosestBuildSite(int team, const UnitDef* unitDef, float3 pos, float searchRadius, int minDist, int facing)
912 {
913 	if (unitDef == NULL) {
914 		return -RgtVector;
915 	}
916 
917 	CFeature* feature = NULL;
918 
919 	const int allyTeam = teamHandler->AllyTeam(team);
920 	const int endr = (int) (searchRadius / (SQUARE_SIZE * 2));
921 	const vector<SearchOffset>& ofs = GetSearchOffsetTable(endr);
922 
923 	for (int so = 0; so < endr * endr * 4; so++) {
924 		const float x = pos.x + ofs[so].dx * SQUARE_SIZE * 2;
925 		const float z = pos.z + ofs[so].dy * SQUARE_SIZE * 2;
926 
927 		BuildInfo bi(unitDef, float3(x, 0.0f, z), facing);
928 		bi.pos = Pos2BuildPos(bi, false);
929 
930 		if (CGameHelper::TestUnitBuildSquare(bi, feature, allyTeam, false) && (!feature || feature->allyteam != allyTeam)) {
931 			const int xs = (int) (x / SQUARE_SIZE);
932 			const int zs = (int) (z / SQUARE_SIZE);
933 			const int xsize = bi.GetXSize();
934 			const int zsize = bi.GetZSize();
935 
936 			bool good = true;
937 
938 			int z2Min = std::max(       0, zs - (zsize    ) / 2 - minDist);
939 			int z2Max = std::min(gs->mapy, zs + (zsize + 1) / 2 + minDist);
940 			int x2Min = std::max(       0, xs - (xsize    ) / 2 - minDist);
941 			int x2Max = std::min(gs->mapx, xs + (xsize + 1) / 2 + minDist);
942 
943 			// check for nearby blocking features
944 			for (int z2 = z2Min; z2 < z2Max; ++z2) {
945 				for (int x2 = x2Min; x2 < x2Max; ++x2) {
946 					CSolidObject* solObj = groundBlockingObjectMap->GroundBlockedUnsafe(z2 * gs->mapx + x2);
947 
948 					if (solObj && solObj->immobile && !dynamic_cast<CFeature*>(solObj)) {
949 						good = false;
950 						break;
951 					}
952 				}
953 			}
954 
955 			if (good) {
956 				z2Min = std::max(       0, zs - (zsize    ) / 2 - minDist - 2);
957 				z2Max = std::min(gs->mapy, zs + (zsize + 1) / 2 + minDist + 2);
958 				x2Min = std::max(       0, xs - (xsize    ) / 2 - minDist - 2);
959 				x2Max = std::min(gs->mapx, xs + (xsize + 1) / 2 + minDist + 2);
960 
961 				// check for nearby factories with open yards
962 				for (int z2 = z2Min; z2 < z2Max; ++z2) {
963 					for (int x2 = x2Min; x2 < x2Max; ++x2) {
964 						CSolidObject* solObj = groundBlockingObjectMap->GroundBlockedUnsafe(z2 * gs->mapx + x2);
965 
966 						if (solObj == NULL)
967 							continue;
968 						if (!solObj->immobile)
969 							continue;
970 						if (dynamic_cast<CFactory*>(solObj) == NULL)
971 							continue;
972 						if (!static_cast<CFactory*>(solObj)->opening)
973 							continue;
974 
975 						good = false;
976 						break;
977 					}
978 				}
979 			}
980 
981 			if (good) {
982 				return bi.pos;
983 			}
984 		}
985 	}
986 
987 	return -RgtVector;
988 }
989 
990 // find the reference height for a build-position
991 // against which to compare all footprint squares
GetBuildHeight(const float3 & pos,const UnitDef * unitdef,bool synced)992 float CGameHelper::GetBuildHeight(const float3& pos, const UnitDef* unitdef, bool synced)
993 {
994 	// we are not going to terraform the ground for mobile units
995 	// (so we do not care about maxHeightDif constraints either)
996 	// TODO: maybe respect waterline if <pos> is in water
997 	if (!unitdef->IsImmobileUnit())
998 		return (std::max(pos.y, CGround::GetHeightReal(pos.x, pos.z, synced)));
999 
1000 	const float* orgHeightMap = readMap->GetOriginalHeightMapSynced();
1001 	const float* curHeightMap = readMap->GetCornerHeightMapSynced();
1002 
1003 	#ifdef USE_UNSYNCED_HEIGHTMAP
1004 	if (!synced) {
1005 		orgHeightMap = readMap->GetCornerHeightMapUnsynced();
1006 		curHeightMap = readMap->GetCornerHeightMapUnsynced();
1007 	}
1008 	#endif
1009 
1010 	const float maxDifHgt = unitdef->maxHeightDif;
1011 
1012 	float minHgt = readMap->GetCurrMinHeight();
1013 	float maxHgt = readMap->GetCurrMaxHeight();
1014 
1015 	unsigned int numBorderSquares = 0;
1016 	float sumBorderSquareHeight = 0.0f;
1017 
1018 	static const int xsize = 1;
1019 	static const int zsize = 1;
1020 
1021 	// top-left footprint corner (sans clamping)
1022 	const int px = (pos.x - (xsize * (SQUARE_SIZE >> 1))) / SQUARE_SIZE;
1023 	const int pz = (pos.z - (zsize * (SQUARE_SIZE >> 1))) / SQUARE_SIZE;
1024 	// top-left and bottom-right footprint corner (clamped)
1025 	const int x1 = std::min(gs->mapx, std::max(0, px));
1026 	const int z1 = std::min(gs->mapy, std::max(0, pz));
1027 	const int x2 = std::max(0, std::min(gs->mapx, x1 + xsize));
1028 	const int z2 = std::max(0, std::min(gs->mapy, z1 + zsize));
1029 
1030 	for (int x = x1; x <= x2; x++) {
1031 		for (int z = z1; z <= z2; z++) {
1032 			const float sqOrgHgt = orgHeightMap[z * gs->mapxp1 + x];
1033 			const float sqCurHgt = curHeightMap[z * gs->mapxp1 + x];
1034 			const float sqMinHgt = std::min(sqCurHgt, sqOrgHgt);
1035 			const float sqMaxHgt = std::max(sqCurHgt, sqOrgHgt);
1036 
1037 			if (x == x1 || x == x2 || z == z1 || z == z2) {
1038 				sumBorderSquareHeight += sqCurHgt;
1039 				numBorderSquares += 1;
1040 			}
1041 
1042 			// restrict the range of [minH, maxH] to
1043 			// the minimum and maximum square height
1044 			// within the footprint
1045 			if (minHgt < (sqMinHgt - maxDifHgt)) { minHgt = sqMinHgt - maxDifHgt; }
1046 			if (maxHgt > (sqMaxHgt + maxDifHgt)) { maxHgt = sqMaxHgt + maxDifHgt; }
1047 		}
1048 	}
1049 
1050 	// find the average height of the footprint-border squares
1051 	float avgHgt = sumBorderSquareHeight / numBorderSquares;
1052 
1053 	// and clamp it to [minH, maxH] if necessary
1054 	if (avgHgt < minHgt && minHgt < maxHgt) { avgHgt = (minHgt + 0.01f); }
1055 	if (avgHgt > maxHgt && maxHgt > minHgt) { avgHgt = (maxHgt - 0.01f); }
1056 
1057 	if (unitdef->floatOnWater && avgHgt < 0.0f)
1058 		avgHgt = -unitdef->waterline;
1059 
1060 	return avgHgt;
1061 }
1062 
1063 
TestUnitBuildSquare(const BuildInfo & buildInfo,CFeature * & feature,int allyteam,bool synced,std::vector<float3> * canbuildpos,std::vector<float3> * featurepos,std::vector<float3> * nobuildpos,const std::vector<Command> * commands)1064 CGameHelper::BuildSquareStatus CGameHelper::TestUnitBuildSquare(
1065 	const BuildInfo& buildInfo,
1066 	CFeature*& feature,
1067 	int allyteam,
1068 	bool synced,
1069 	std::vector<float3>* canbuildpos,
1070 	std::vector<float3>* featurepos,
1071 	std::vector<float3>* nobuildpos,
1072 	const std::vector<Command>* commands)
1073 {
1074 	feature = NULL;
1075 
1076 	const int xsize = buildInfo.GetXSize();
1077 	const int zsize = buildInfo.GetZSize();
1078 
1079 	const float3 pos = buildInfo.pos;
1080 
1081 	const int x1 = int(pos.x / SQUARE_SIZE) - (xsize >> 1), x2 = x1 + xsize;
1082 	const int z1 = int(pos.z / SQUARE_SIZE) - (zsize >> 1), z2 = z1 + zsize;
1083 
1084 	const int2 xrange = int2(x1, x2);
1085 	const int2 zrange = int2(z1, z2);
1086 
1087 	const MoveDef* moveDef = (buildInfo.def->pathType != -1U) ? moveDefHandler->GetMoveDefByPathType(buildInfo.def->pathType) : NULL;
1088 	/*const S3DModel* model =*/ buildInfo.def->LoadModel();
1089 
1090 	const float buildHeight = GetBuildHeight(pos, buildInfo.def, synced);
1091 	// const float modelHeight = (model != NULL) ? math::fabs(model->height) : 10.0f;
1092 
1093 	BuildSquareStatus canBuild = BUILDSQUARE_OPEN;
1094 
1095 	if (buildInfo.def->needGeo) {
1096 		canBuild = BUILDSQUARE_BLOCKED;
1097 
1098 		const std::vector<CFeature*>& features = quadField->GetFeaturesExact(pos, std::max(xsize, zsize) * 6);
1099 
1100 		const int mindx = xsize * (SQUARE_SIZE >> 1) - (SQUARE_SIZE >> 1);
1101 		const int mindz = zsize * (SQUARE_SIZE >> 1) - (SQUARE_SIZE >> 1);
1102 
1103 		// look for a nearby geothermal feature if we need one
1104 		for (std::vector<CFeature*>::const_iterator fi = features.begin(); fi != features.end(); ++fi) {
1105 			if (!(*fi)->def->geoThermal)
1106 				continue;
1107 
1108 			const float dx = math::fabs((*fi)->pos.x - pos.x);
1109 			const float dz = math::fabs((*fi)->pos.z - pos.z);
1110 
1111 			if (dx < mindx && dz < mindz) {
1112 				canBuild = BUILDSQUARE_OPEN;
1113 				break;
1114 			}
1115 		}
1116 	}
1117 
1118 	if (commands != NULL) {
1119 		// this is only called in unsynced context (ShowUnitBuildSquare)
1120 		assert(!synced);
1121 
1122 		for (int z = z1; z < z2; z++) {
1123 			for (int x = x1; x < x2; x++) {
1124 				BuildSquareStatus tbs = TestBuildSquare(float3(x * SQUARE_SIZE, buildHeight, z * SQUARE_SIZE), xrange, zrange, buildInfo.def, moveDef, feature, gu->myAllyTeam, synced);
1125 
1126 				if (tbs != BUILDSQUARE_BLOCKED) {
1127 					// test if build-position overlaps a queued command
1128 					for (std::vector<Command>::const_iterator ci = commands->begin(); ci != commands->end(); ++ci) {
1129 						BuildInfo bc(*ci);
1130 						if (std::max(bc.pos.x - x * SQUARE_SIZE - SQUARE_SIZE, x * SQUARE_SIZE - bc.pos.x) * 2 < bc.GetXSize() * SQUARE_SIZE &&
1131 							std::max(bc.pos.z - z * SQUARE_SIZE - SQUARE_SIZE, z * SQUARE_SIZE - bc.pos.z) * 2 < bc.GetZSize() * SQUARE_SIZE) {
1132 							tbs = BUILDSQUARE_BLOCKED;
1133 							break;
1134 						}
1135 					}
1136 				}
1137 
1138 				switch (tbs) {
1139 					case BUILDSQUARE_OPEN:
1140 						canbuildpos->push_back(float3(x * SQUARE_SIZE, buildHeight, z * SQUARE_SIZE));
1141 						break;
1142 					case BUILDSQUARE_RECLAIMABLE:
1143 					case BUILDSQUARE_OCCUPIED:
1144 						featurepos->push_back(float3(x * SQUARE_SIZE, buildHeight, z * SQUARE_SIZE));
1145 						break;
1146 					case BUILDSQUARE_BLOCKED:
1147 						nobuildpos->push_back(float3(x * SQUARE_SIZE, buildHeight, z * SQUARE_SIZE));
1148 						break;
1149 				}
1150 
1151 				canBuild = std::min(canBuild, tbs);
1152 			}
1153 		}
1154 	} else {
1155 		// this can be called in either context
1156 		for (int z = z1; z < z2; z++) {
1157 			for (int x = x1; x < x2; x++) {
1158 				canBuild = std::min(canBuild, TestBuildSquare(float3(x * SQUARE_SIZE, buildHeight, z * SQUARE_SIZE), xrange, zrange, buildInfo.def, moveDef, feature, allyteam, synced));
1159 
1160 				if (canBuild == BUILDSQUARE_BLOCKED) {
1161 					return BUILDSQUARE_BLOCKED;
1162 				}
1163 			}
1164 		}
1165 	}
1166 
1167 	return canBuild;
1168 }
1169 
TestBuildSquare(const float3 & pos,const int2 & xrange,const int2 & zrange,const UnitDef * unitDef,const MoveDef * moveDef,CFeature * & feature,int allyteam,bool synced)1170 CGameHelper::BuildSquareStatus CGameHelper::TestBuildSquare(
1171 	const float3& pos,
1172 	const int2& xrange,
1173 	const int2& zrange,
1174 	const UnitDef* unitDef,
1175 	const MoveDef* moveDef,
1176 	CFeature*& feature,
1177 	int allyteam,
1178 	bool synced
1179 ) {
1180 	if (!pos.IsInBounds())
1181 		return BUILDSQUARE_BLOCKED;
1182 
1183 	const int yardxpos = int(pos.x + (SQUARE_SIZE >> 1)) / SQUARE_SIZE;
1184 	const int yardypos = int(pos.z + (SQUARE_SIZE >> 1)) / SQUARE_SIZE;
1185 
1186 	const float groundHeight = CGround::GetHeightReal(pos.x, pos.z, synced);
1187 
1188 	BuildSquareStatus ret = BUILDSQUARE_OPEN;
1189 	CSolidObject* so = groundBlockingObjectMap->GroundBlocked(yardxpos, yardypos);
1190 
1191 	if (so != NULL) {
1192 		CFeature* f = dynamic_cast<CFeature*>(so);
1193 		CUnit* u = dynamic_cast<CUnit*>(so);
1194 
1195 		// blocking-map can lag behind because it is not updated every frame
1196 		assert(true || (so->pos.x >= xrange.x && so->pos.x <= xrange.y));
1197 		assert(true || (so->pos.z >= zrange.x && so->pos.z <= zrange.y));
1198 
1199 		if (f != NULL) {
1200 			if ((allyteam < 0) || f->IsInLosForAllyTeam(allyteam)) {
1201 				if (!f->def->reclaimable) {
1202 					ret = BUILDSQUARE_BLOCKED;
1203 				} else {
1204 					ret = BUILDSQUARE_RECLAIMABLE;
1205 					feature = f;
1206 				}
1207 			}
1208 		} else if (u == NULL || (allyteam < 0) || (u->losStatus[allyteam] & LOS_INLOS)) {
1209 			if (so->immobile) {
1210 				ret = BUILDSQUARE_BLOCKED;
1211 			} else {
1212 				ret = BUILDSQUARE_OCCUPIED;
1213 			}
1214 		}
1215 
1216 		if (ret == BUILDSQUARE_BLOCKED || ret == BUILDSQUARE_OCCUPIED) {
1217 			// if the to-be-buildee has a MoveDef, test if <so> would block it
1218 			// note:
1219 			//   <so> might be another new buildee and if that happens to be located
1220 			//   on sloped ground, then so->pos.y will equal Builder::StartBuild -->
1221 			//   ::Pos2BuildPos --> ::GetBuildHeight which can differ from the actual
1222 			//   ground height at so->pos (s.t. !so->IsOnGround() and the object will
1223 			//   be non-blocking)
1224 			//   fixed: no longer true for mobile units
1225 			#if 0
1226 			if (synced) {
1227 				so->PushPhysicalStateBit(CSolidObject::PSTATE_BIT_ONGROUND);
1228 				so->UpdatePhysicalStateBit(CSolidObject::PSTATE_BIT_ONGROUND, (math::fabs(so->pos.y - groundHeight) <= 0.5f));
1229 			}
1230 			#endif
1231 
1232 			if (moveDef != NULL && CMoveMath::IsNonBlocking(*moveDef, so, NULL)) {
1233 				ret = BUILDSQUARE_OPEN;
1234 			}
1235 
1236 			#if 0
1237 			if (synced) {
1238 				so->PopPhysicalStateBit(CSolidObject::PSTATE_BIT_ONGROUND);
1239 			}
1240 			#endif
1241 		}
1242 
1243 		if (ret == BUILDSQUARE_BLOCKED) {
1244 			return ret;
1245 		}
1246 	}
1247 
1248 	// check maxHeightDif constraint (structures only)
1249 	//
1250 	// if we are capable of floating, only test local
1251 	// height difference IF terrain is above sea-level
1252 	if (unitDef->IsImmobileUnit()) {
1253 		if (!unitDef->floatOnWater || groundHeight > 0.0f) {
1254 			const float* orgHeightMap = readMap->GetOriginalHeightMapSynced();
1255 			const float* curHeightMap = readMap->GetCornerHeightMapSynced();
1256 
1257 			#ifdef USE_UNSYNCED_HEIGHTMAP
1258 			if (!synced) {
1259 				orgHeightMap = readMap->GetCornerHeightMapUnsynced();
1260 				curHeightMap = readMap->GetCornerHeightMapUnsynced();
1261 			}
1262 			#endif
1263 
1264 			const int sqx = pos.x / SQUARE_SIZE;
1265 			const int sqz = pos.z / SQUARE_SIZE;
1266 
1267 			// FIXME: we do not want to use maxHeightDif for a MOBILE unit!
1268 			const float orgHgt = orgHeightMap[sqz * gs->mapxp1 + sqx];
1269 			const float curHgt = curHeightMap[sqz * gs->mapxp1 + sqx];
1270 			const float difHgt = unitDef->maxHeightDif;
1271 
1272 			if (pos.y > std::max(orgHgt + difHgt, curHgt + difHgt)) { return BUILDSQUARE_BLOCKED; }
1273 			if (pos.y < std::min(orgHgt - difHgt, curHgt - difHgt)) { return BUILDSQUARE_BLOCKED; }
1274 		}
1275 	}
1276 
1277 	if (!unitDef->CheckTerrainConstraints(moveDef, groundHeight))
1278 		ret = BUILDSQUARE_BLOCKED;
1279 
1280 	return ret;
1281 }
1282 
1283 /**
1284  * Returns a build Command that intersects the ray described by pos and dir from
1285  * the command queues of the units 'units' on team number 'team'.
1286  * @brief returns a build Command that intersects the ray described by pos and dir
1287  * @return the build Command, or a Command with id 0 if none is found
1288  */
GetBuildCommand(const float3 & pos,const float3 & dir)1289 Command CGameHelper::GetBuildCommand(const float3& pos, const float3& dir) {
1290 	float3 tempF1 = pos;
1291 
1292 	CCommandQueue::iterator ci;
1293 
1294 	const std::list<CUnit*>& units = unitHandler->activeUnits;
1295 	      std::list<CUnit*>::const_iterator ui;
1296 
1297 	for (ui = units.begin(); ui != units.end(); ++ui) {
1298 		const CUnit* unit = *ui;
1299 
1300 		if (unit->team != gu->myTeam) {
1301 			continue;
1302 		}
1303 
1304 		ci = unit->commandAI->commandQue.begin();
1305 
1306 		for (; ci != unit->commandAI->commandQue.end(); ++ci) {
1307 			const Command& cmd = *ci;
1308 
1309 			if (cmd.GetID() < 0 && cmd.params.size() >= 3) {
1310 				BuildInfo bi(cmd);
1311 				tempF1 = pos + dir * ((bi.pos.y - pos.y) / dir.y) - bi.pos;
1312 
1313 				if (bi.def && (bi.GetXSize() / 2) * SQUARE_SIZE > math::fabs(tempF1.x) && (bi.GetZSize() / 2) * SQUARE_SIZE > math::fabs(tempF1.z)) {
1314 					return cmd;
1315 				}
1316 			}
1317 		}
1318 	}
1319 
1320 	Command c(CMD_STOP);
1321 	return c;
1322 }
1323 
1324 
1325 
1326 
Update()1327 void CGameHelper::Update()
1328 {
1329 	std::list<WaitingDamage*>& wdList = waitingDamageLists[gs->frameNum & 127];
1330 
1331 	while (!wdList.empty()) {
1332 		WaitingDamage* wd = wdList.back();
1333 		wdList.pop_back();
1334 
1335 		CUnit* attackee = unitHandler->units[wd->target];
1336 		CUnit* attacker = (wd->attacker == -1)? NULL: unitHandler->units[wd->attacker];
1337 
1338 		if (attackee != NULL)
1339 			attackee->DoDamage(wd->damage, wd->impulse, attacker, wd->weaponID, wd->projectileID);
1340 
1341 		delete wd;
1342 	}
1343 }
1344