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