1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3 #include "UnitDef.h"
4 #include "Unit.h"
5 #include "UnitHandler.h"
6 #include "UnitDefHandler.h"
7 #include "UnitLoader.h"
8 #include "UnitTypes/Building.h"
9 #include "UnitTypes/TransportUnit.h"
10 #include "Scripts/NullUnitScript.h"
11 #include "Scripts/UnitScriptFactory.h"
12 #include "Scripts/CobInstance.h" // for TAANG2RAD
13
14 #include "CommandAI/CommandAI.h"
15 #include "CommandAI/FactoryCAI.h"
16 #include "CommandAI/AirCAI.h"
17 #include "CommandAI/BuilderCAI.h"
18 #include "CommandAI/CommandAI.h"
19 #include "CommandAI/FactoryCAI.h"
20 #include "CommandAI/MobileCAI.h"
21 #include "CommandAI/TransportCAI.h"
22
23 #include "ExternalAI/EngineOutHandler.h"
24 #include "Game/GameHelper.h"
25 #include "Game/GameSetup.h"
26 #include "Game/GlobalUnsynced.h"
27 #include "Game/SelectedUnitsHandler.h"
28 #include "Game/Players/Player.h"
29 #include "Map/Ground.h"
30 #include "Map/MapInfo.h"
31 #include "Map/ReadMap.h"
32
33 #include "Rendering/Models/IModelParser.h"
34 #include "Rendering/GroundFlash.h"
35
36 #include "Sim/Units/Groups/Group.h"
37 #include "Sim/Misc/AirBaseHandler.h"
38 #include "Sim/Features/Feature.h"
39 #include "Sim/Features/FeatureHandler.h"
40 #include "Sim/Misc/GlobalConstants.h"
41 #include "Sim/Misc/CollisionVolume.h"
42 #include "Sim/Misc/LosHandler.h"
43 #include "Sim/Misc/QuadField.h"
44 #include "Sim/Misc/RadarHandler.h"
45 #include "Sim/Misc/TeamHandler.h"
46 #include "Sim/Misc/Wind.h"
47 #include "Sim/Misc/ModInfo.h"
48 #include "Sim/MoveTypes/MoveDefHandler.h"
49 #include "Sim/MoveTypes/MoveType.h"
50 #include "Sim/MoveTypes/MoveTypeFactory.h"
51 #include "Sim/MoveTypes/ScriptMoveType.h"
52 #include "Sim/Projectiles/FlareProjectile.h"
53 #include "Sim/Projectiles/WeaponProjectiles/MissileProjectile.h"
54 #include "Sim/Weapons/Weapon.h"
55 #include "Sim/Weapons/WeaponDefHandler.h"
56 #include "Sim/Weapons/WeaponLoader.h"
57 #include "System/EventBatchHandler.h"
58 #include "System/EventHandler.h"
59 #include "System/Log/ILog.h"
60 #include "System/Matrix44f.h"
61 #include "System/myMath.h"
62 #include "System/creg/STL_List.h"
63 #include "System/Sound/ISoundChannels.h"
64 #include "System/Sync/SyncedPrimitive.h"
65 #include "System/Sync/SyncTracer.h"
66
67 // See end of source for member bindings
68 //////////////////////////////////////////////////////////////////////
69 // Construction/Destruction
70 //////////////////////////////////////////////////////////////////////
71
72 //! info: SlowUpdate runs each 16th GameFrame (:= twice per 32GameFrames) (a second has GAME_SPEED=30 gameframes!)
73 float CUnit::empDecline = 2.0f * (float)UNIT_SLOWUPDATE_RATE / (float)GAME_SPEED / 40.0f;
74 bool CUnit::spawnFeature = true;
75
76 float CUnit::expMultiplier = 1.0f;
77 float CUnit::expPowerScale = 1.0f;
78 float CUnit::expHealthScale = 0.7f;
79 float CUnit::expReloadScale = 0.4f;
80 float CUnit::expGrade = 0.0f;
81
82
CUnit()83 CUnit::CUnit() : CSolidObject(),
84 unitDef(NULL),
85 soloBuilder(NULL),
86 lastAttacker(NULL),
87 attackTarget(NULL),
88 transporter(NULL),
89
90 moveType(NULL),
91 prevMoveType(NULL),
92
93 commandAI(NULL),
94 group(NULL),
95
96 shieldWeapon(NULL),
97 stockpileWeapon(NULL),
98
99 localModel(NULL),
100 script(NULL),
101 lastAttackedPiece(NULL),
102 los(NULL),
103
104 fpsControlPlayer(NULL),
105 myTrack(NULL),
106 myIcon(NULL),
107
108 losStatus(teamHandler->ActiveAllyTeams(), 0),
109
110 attackPos(ZeroVector),
111 deathSpeed(ZeroVector),
112 lastMuzzleFlameDir(UpVector),
113 flankingBonusDir(RgtVector),
114 posErrorVector(ZeroVector),
115 posErrorDelta(ZeroVector),
116
117 unitDefID(-1),
118 featureDefID(-1),
119
120 upright(true),
121 travel(0.0f),
122 travelPeriod(0.0f),
123 power(100.0f),
124 maxHealth(100.0f),
125 paralyzeDamage(0.0f),
126 captureProgress(0.0f),
127 experience(0.0f),
128 limExperience(0.0f),
129 neutral(false),
130 beingBuilt(true),
131 lastNanoAdd(gs->frameNum),
132 lastFlareDrop(0),
133 repairAmount(0.0f),
134 loadingTransportId(-1),
135 buildProgress(0.0f),
136 groundLevelled(true),
137 terraformLeft(0.0f),
138 realLosRadius(0),
139 realAirLosRadius(0),
140
141 inBuildStance(false),
142 useHighTrajectory(false),
143 dontUseWeapons(false),
144 dontFire(false),
145 deathScriptFinished(false),
146 delayedWreckLevel(-1),
147 restTime(0),
148 outOfMapTime(0),
149 reloadSpeed(1.0f),
150 maxRange(0.0f),
151 haveTarget(false),
152 haveManualFireRequest(false),
153 lastMuzzleFlameSize(0.0f),
154 armorType(0),
155 category(0),
156 mapSquare(-1),
157 losRadius(0),
158 airLosRadius(0),
159 lastLosUpdate(0),
160 losHeight(0.0f),
161 radarHeight(0.0f),
162 radarRadius(0),
163 sonarRadius(0),
164 jammerRadius(0),
165 sonarJamRadius(0),
166 seismicRadius(0),
167 seismicSignature(0.0f),
168 hasRadarCapacity(false),
169 oldRadarPos(0, 0),
170 hasRadarPos(false),
171 stealth(false),
172 sonarStealth(false),
173 condUseMetal(0.0f),
174 condUseEnergy(0.0f),
175 condMakeMetal(0.0f),
176 condMakeEnergy(0.0f),
177 uncondUseMetal(0.0f),
178 uncondUseEnergy(0.0f),
179 uncondMakeMetal(0.0f),
180 uncondMakeEnergy(0.0f),
181 metalUse(0.0f),
182 energyUse(0.0f),
183 metalMake(0.0f),
184 energyMake(0.0f),
185 metalUseI(0.0f),
186 energyUseI(0.0f),
187 metalMakeI(0.0f),
188 energyMakeI(0.0f),
189 metalUseold(0.0f),
190 energyUseold(0.0f),
191 metalMakeold(0.0f),
192 energyMakeold(0.0f),
193 energyTickMake(0.0f),
194 metalExtract(0.0f),
195 metalCost(100.0f),
196 energyCost(0.0f),
197 buildTime(100.0f),
198 metalStorage(0.0f),
199 energyStorage(0.0f),
200 harvestStorage(0.0f),
201 lastAttackedPieceFrame(-1),
202 lastAttackFrame(-200),
203 lastFireWeapon(0),
204 recentDamage(0.0f),
205 userAttackGround(false),
206 fireState(FIRESTATE_FIREATWILL),
207 moveState(MOVESTATE_MANEUVER),
208 activated(false),
209 isDead(false),
210 fallSpeed(0.2f),
211 flankingBonusMode(0),
212 flankingBonusMobility(10.0f),
213 flankingBonusMobilityAdd(0.01f),
214 flankingBonusAvgDamage(1.4f),
215 flankingBonusDifDamage(0.5f),
216 armoredState(false),
217 armoredMultiple(1.0f),
218 curArmorMultiple(1.0f),
219 nextPosErrorUpdate(1),
220 wantCloak(false),
221 scriptCloak(0),
222 cloakTimeout(128),
223 curCloakTimeout(gs->frameNum),
224 isCloaked(false),
225 decloakDistance(0.0f),
226 lastTerrainType(-1),
227 curTerrainType(0),
228 selfDCountdown(0),
229 currentFuel(0.0f),
230 alphaThreshold(0.1f),
231 cegDamage(1),
232 noDraw(false),
233 noMinimap(false),
234 leaveTracks(false),
235 isSelected(false),
236 isIcon(false),
237 iconRadius(0.0f),
238 lodCount(0),
239 currentLOD(0),
240
241 lastDrawFrame(-30),
242 lastUnitUpdate(0),
243
244 stunned(false)
245 {
246 }
247
~CUnit()248 CUnit::~CUnit()
249 {
250 // clean up if we are still under movectrl here
251 DisableScriptMoveType();
252
253 if (delayedWreckLevel >= 0) {
254 // NOTE: could also do this in Update() or even in CUnitKilledCB()
255 // where we wouldn't need deathSpeed, but not in KillUnit() since
256 // we have to wait for deathScriptFinished (but we want the delay
257 // in frames between CUnitKilledCB() and the CreateWreckage() call
258 // to be as short as possible to prevent position jumps)
259 FeatureLoadParams params = {featureHandler->GetFeatureDefByID(featureDefID), unitDef, pos, deathSpeed, -1, team, -1, heading, buildFacing, 0};
260 featureHandler->CreateWreckage(params, delayedWreckLevel - 1, true);
261 }
262
263 if (unitDef->isAirBase) {
264 airBaseHandler->DeregisterAirBase(this);
265 }
266
267 #ifdef TRACE_SYNC
268 tracefile << "Unit died: ";
269 tracefile << pos.x << " " << pos.y << " " << pos.z << " " << id << "\n";
270 #endif
271
272 if (fpsControlPlayer != NULL) {
273 fpsControlPlayer->StopControllingUnit();
274 assert(fpsControlPlayer == NULL);
275 }
276
277 if (activated && unitDef->targfac) {
278 radarHandler->IncreaseAllyTeamRadarErrorSize(allyteam);
279 }
280
281 SetMetalStorage(0);
282 SetEnergyStorage(0);
283
284 delete commandAI; commandAI = NULL;
285 delete moveType; moveType = NULL;
286 delete prevMoveType; prevMoveType = NULL;
287
288 // not all unit deletions run through KillUnit(),
289 // but we always want to call this for ourselves
290 UnBlock();
291
292 // Remove us from our group, if we were in one
293 SetGroup(NULL);
294
295 if (script != &CNullUnitScript::value) {
296 delete script;
297 script = NULL;
298 }
299 // ScriptCallback may reference weapons, so delete the script first
300 for (std::vector<CWeapon*>::const_iterator wi = weapons.begin(); wi != weapons.end(); ++wi) {
301 delete *wi;
302 }
303
304 quadField->RemoveUnit(this);
305 losHandler->DelayedFreeInstance(los);
306 los = NULL;
307 radarHandler->RemoveUnit(this);
308
309 modelParser->DeleteLocalModel(localModel);
310 }
311
312
SetMetalStorage(float newStorage)313 void CUnit::SetMetalStorage(float newStorage)
314 {
315 teamHandler->Team(team)->metalStorage -= metalStorage;
316 metalStorage = newStorage;
317 teamHandler->Team(team)->metalStorage += metalStorage;
318 }
319
320
SetEnergyStorage(float newStorage)321 void CUnit::SetEnergyStorage(float newStorage)
322 {
323 teamHandler->Team(team)->energyStorage -= energyStorage;
324 energyStorage = newStorage;
325 teamHandler->Team(team)->energyStorage += energyStorage;
326 }
327
328
329
PreInit(const UnitLoadParams & params)330 void CUnit::PreInit(const UnitLoadParams& params)
331 {
332 // if this is < 0, we get a random ID from UnitHandler
333 id = params.unitID;
334 unitDefID = (params.unitDef)->id;
335 featureDefID = -1;
336
337 objectDef = params.unitDef;
338 unitDef = params.unitDef;
339
340 {
341 const FeatureDef* wreckFeatureDef = featureHandler->GetFeatureDef(unitDef->wreckName);
342
343 if (wreckFeatureDef != NULL) {
344 featureDefID = wreckFeatureDef->id;
345 }
346 }
347
348 team = params.teamID;
349 allyteam = teamHandler->AllyTeam(team);
350
351 buildFacing = std::abs(params.facing) % NUM_FACINGS;
352 xsize = ((buildFacing & 1) == 0) ? unitDef->xsize : unitDef->zsize;
353 zsize = ((buildFacing & 1) == 1) ? unitDef->xsize : unitDef->zsize;
354
355 // copy the UnitDef volume instance
356 // NOTE: gets deleted in ~CSolidObject
357 model = unitDef->LoadModel();
358 localModel = new LocalModel(model);
359 collisionVolume = new CollisionVolume(unitDef->collisionVolume);
360 modelParser->CreateLocalModel(localModel);
361
362 if (collisionVolume->DefaultToSphere())
363 collisionVolume->InitSphere(model->radius);
364 if (collisionVolume->DefaultToFootPrint())
365 collisionVolume->InitBox(float3(xsize * SQUARE_SIZE, model->height, zsize * SQUARE_SIZE));
366
367 mapSquare = CGround::GetSquare((params.pos).cClampInMap());
368
369 heading = GetHeadingFromFacing(buildFacing);
370 frontdir = GetVectorFromHeading(heading);
371 updir = UpVector;
372 rightdir = frontdir.cross(updir);
373 upright = unitDef->upright;
374
375 SetVelocity(params.speed);
376 Move((params.pos).cClampInMap(), false);
377 SetMidAndAimPos(model->relMidPos, model->relMidPos, true);
378 SetRadiusAndHeight(model);
379 UpdateDirVectors(!upright);
380 UpdateMidAndAimPos();
381
382 unitHandler->AddUnit(this);
383 quadField->MovedUnit(this);
384
385 hasRadarPos = false;
386
387 losStatus[allyteam] = LOS_ALL_MASK_BITS | LOS_INLOS | LOS_INRADAR | LOS_PREVLOS | LOS_CONTRADAR;
388
389 #ifdef TRACE_SYNC
390 tracefile << "[" << __FUNCTION__ << "] id: " << id << ", name: " << unitDef->name << " ";
391 tracefile << "pos: <" << pos.x << ", " << pos.y << ", " << pos.z << ">\n";
392 #endif
393
394 ASSERT_SYNCED(pos);
395
396 blockMap = (unitDef->GetYardMap().empty())? NULL: &unitDef->GetYardMap()[0];
397 footprint = int2(unitDef->xsize, unitDef->zsize);
398
399 beingBuilt = params.beingBuilt;
400 mass = (beingBuilt)? mass: unitDef->mass;
401 crushResistance = unitDef->crushResistance;
402 power = unitDef->power;
403 maxHealth = unitDef->health;
404 health = beingBuilt? 0.1f: unitDef->health;
405 losHeight = unitDef->losHeight;
406 radarHeight = unitDef->radarHeight;
407 metalCost = unitDef->metal;
408 energyCost = unitDef->energy;
409 buildTime = unitDef->buildTime;
410 currentFuel = unitDef->maxFuel;
411 armoredMultiple = std::max(0.0001f, unitDef->armoredMultiple); // armored multiple of 0 will crash spring
412 armorType = unitDef->armorType;
413 category = unitDef->category;
414 leaveTracks = unitDef->decalDef.leaveTrackDecals;
415
416 tooltip = unitDef->humanName + " - " + unitDef->tooltip;
417
418
419 // sensor parameters
420 realLosRadius = int(unitDef->losRadius);
421 realAirLosRadius = int(unitDef->airLosRadius);
422
423 radarRadius = unitDef->radarRadius / (SQUARE_SIZE * 8);
424 sonarRadius = unitDef->sonarRadius / (SQUARE_SIZE * 8);
425 jammerRadius = unitDef->jammerRadius / (SQUARE_SIZE * 8);
426 sonarJamRadius = unitDef->sonarJamRadius / (SQUARE_SIZE * 8);
427 seismicRadius = unitDef->seismicRadius / (SQUARE_SIZE * 8);
428 seismicSignature = unitDef->seismicSignature;
429 hasRadarCapacity =
430 (radarRadius > 0.0f) || (sonarRadius > 0.0f) ||
431 (jammerRadius > 0.0f) || (sonarJamRadius > 0.0f) ||
432 (seismicRadius > 0.0f);
433 stealth = unitDef->stealth;
434 sonarStealth = unitDef->sonarStealth;
435
436 // can be overridden by cloak orders during construction
437 wantCloak |= unitDef->startCloaked;
438 decloakDistance = unitDef->decloakDistance;
439 cloakTimeout = unitDef->cloakTimeout;
440
441
442 flankingBonusMode = unitDef->flankingBonusMode;
443 flankingBonusDir = unitDef->flankingBonusDir;
444 flankingBonusMobility = unitDef->flankingBonusMobilityAdd * 1000;
445 flankingBonusMobilityAdd = unitDef->flankingBonusMobilityAdd;
446 flankingBonusAvgDamage = (unitDef->flankingBonusMax + unitDef->flankingBonusMin) * 0.5f;
447 flankingBonusDifDamage = (unitDef->flankingBonusMax - unitDef->flankingBonusMin) * 0.5f;
448
449 useHighTrajectory = (unitDef->highTrajectoryType == 1);
450
451 energyTickMake =
452 unitDef->energyMake +
453 unitDef->tidalGenerator * mapInfo->map.tidalStrength;
454
455 moveType = MoveTypeFactory::GetMoveType(this, unitDef);
456 script = CUnitScriptFactory::CreateScript("scripts/" + unitDef->scriptName, this);
457 }
458
459
PostInit(const CUnit * builder)460 void CUnit::PostInit(const CUnit* builder)
461 {
462 weaponLoader->LoadWeapons(this);
463 // Call initializing script functions
464 script->Create();
465
466 // all units are blocking (ie. register on the blocking-map
467 // when not flying) except mines, since their position would
468 // be given away otherwise by the PF, etc.
469 // NOTE: this does mean that mines can be stacked indefinitely
470 // (an extra yardmap character would be needed to prevent this)
471 immobile = unitDef->IsImmobileUnit();
472
473 UpdateCollidableStateBit(CSolidObject::CSTATE_BIT_SOLIDOBJECTS, unitDef->collidable && (!immobile || !unitDef->canKamikaze));
474 Block();
475
476 if (unitDef->windGenerator > 0.0f) {
477 wind.AddUnit(this);
478 }
479
480 UpdateTerrainType();
481 UpdatePhysicalState(0.1f);
482 UpdatePosErrorParams(true, true);
483
484 if (unitDef->floatOnWater && IsInWater()) {
485 Move(UpVector * (std::max(CGround::GetHeightReal(pos.x, pos.z), -unitDef->waterline) - pos.y), true);
486 }
487
488 if (unitDef->canmove || unitDef->builder) {
489 if (unitDef->moveState <= MOVESTATE_NONE) {
490 if (builder != NULL) {
491 // always inherit our builder's movestate
492 // if none, set CUnit's default (maneuver)
493 moveState = builder->moveState;
494 }
495 } else {
496 // use our predefined movestate
497 moveState = unitDef->moveState;
498 }
499
500 Command c(CMD_MOVE_STATE, 0, moveState);
501 commandAI->GiveCommand(c);
502 }
503
504 if (commandAI->CanChangeFireState()) {
505 if (unitDef->fireState <= FIRESTATE_NONE) {
506 if (builder != NULL && dynamic_cast<CFactoryCAI*>(builder->commandAI) != NULL) {
507 // inherit our builder's firestate (if it is a factory)
508 // if no builder, CUnit's default (fire-at-will) is set
509 fireState = builder->fireState;
510 }
511 } else {
512 // use our predefined firestate
513 fireState = unitDef->fireState;
514 }
515
516 Command c(CMD_FIRE_STATE, 0, fireState);
517 commandAI->GiveCommand(c);
518 }
519
520 // Lua might call SetUnitHealth within UnitCreated
521 // and trigger FinishedBuilding before we get to it
522 const bool preBeingBuilt = beingBuilt;
523
524 // these must precede UnitFinished from FinishedBuilding
525 eventHandler.UnitCreated(this, builder);
526 eoh->UnitCreated(*this, builder);
527
528 if (!preBeingBuilt && !beingBuilt) {
529 // skip past the gradual build-progression
530 FinishedBuilding(true);
531 }
532 }
533
534
535
536
ForcedMove(const float3 & newPos)537 void CUnit::ForcedMove(const float3& newPos)
538 {
539 UnBlock();
540 Move(newPos - pos, true);
541 Block();
542
543 eventHandler.UnitMoved(this);
544
545 quadField->MovedUnit(this);
546 losHandler->MoveUnit(this, false);
547 radarHandler->MoveUnit(this);
548 }
549
550
551
GetErrorVector(int allyteam) const552 float3 CUnit::GetErrorVector(int allyteam) const
553 {
554 if (teamHandler->Ally(allyteam, this->allyteam) || (losStatus[allyteam] & LOS_INLOS)) {
555 // it's one of our own, or it's in LOS, so don't add an error
556 return ZeroVector;
557 }
558 if (gameSetup->ghostedBuildings && (losStatus[allyteam] & LOS_PREVLOS) && unitDef->IsImmobileUnit()) {
559 // this is a ghosted building, so don't add an error
560 return ZeroVector;
561 }
562
563 if ((losStatus[allyteam] & LOS_INRADAR) != 0) {
564 return (posErrorVector * radarHandler->GetAllyTeamRadarErrorSize(allyteam));
565 } else {
566 return (posErrorVector * radarHandler->GetBaseRadarErrorSize() * 2.0f);
567 }
568 }
569
UpdatePosErrorParams(bool updateError,bool updateDelta)570 void CUnit::UpdatePosErrorParams(bool updateError, bool updateDelta)
571 {
572 if (updateError) {
573 // every frame, magnitude of error increases
574 // error-direction is fixed until next delta
575 posErrorVector += posErrorDelta;
576 }
577
578 if (updateDelta) {
579 if ((--nextPosErrorUpdate) <= 0) {
580 float3 newPosError = gs->randVector();
581 newPosError.y *= 0.2f;
582
583 if (posErrorVector.dot(newPosError) < 0.0f) {
584 newPosError = -newPosError;
585 }
586
587 posErrorDelta = (newPosError - posErrorVector) * (1.0f / 256.0f);
588 nextPosErrorUpdate = UNIT_SLOWUPDATE_RATE;
589 }
590 }
591 }
592
Drop(const float3 & parentPos,const float3 & parentDir,CUnit * parent)593 void CUnit::Drop(const float3& parentPos, const float3& parentDir, CUnit* parent)
594 {
595 // drop unit from position
596 fallSpeed = unitDef->unitFallSpeed > 0 ? unitDef->unitFallSpeed : parent->unitDef->fallSpeed;
597
598 speed.y = 0.0f;
599 frontdir = parentDir;
600 frontdir.y = 0.0f;
601
602 Move(UpVector * ((parentPos.y - height) - pos.y), true);
603 UpdateMidAndAimPos();
604 SetPhysicalStateBit(CSolidObject::PSTATE_BIT_FALLING);
605
606 // start parachute animation
607 script->Falling();
608 }
609
610
EnableScriptMoveType()611 void CUnit::EnableScriptMoveType()
612 {
613 if (UsingScriptMoveType())
614 return;
615
616 prevMoveType = moveType;
617 moveType = new CScriptMoveType(this);
618 }
619
DisableScriptMoveType()620 void CUnit::DisableScriptMoveType()
621 {
622 if (!UsingScriptMoveType())
623 return;
624
625 delete moveType;
626 moveType = prevMoveType;
627 prevMoveType = NULL;
628
629 // ensure unit does not try to move back to the
630 // position it was at when MoveCtrl was enabled
631 // FIXME: prevent the issuing of extra commands?
632 if (moveType != NULL) {
633 moveType->SetGoal(moveType->oldPos = pos);
634 moveType->StopMoving();
635 }
636 }
637
638
Update()639 void CUnit::Update()
640 {
641 ASSERT_SYNCED(pos);
642
643 UpdatePhysicalState(0.1f);
644 UpdatePosErrorParams(true, false);
645
646 if (beingBuilt)
647 return;
648
649 if (travelPeriod != 0.0f) {
650 travel += speed.w;
651 travel = math::fmod(travel, travelPeriod);
652 }
653
654 recentDamage *= 0.9f;
655 flankingBonusMobility += flankingBonusMobilityAdd;
656
657 if (IsStunned()) {
658 // leave the pad if reserved
659 moveType->UnreservePad(moveType->GetReservedPad());
660
661 // paralyzed weapons shouldn't reload
662 for (std::vector<CWeapon*>::iterator wi = weapons.begin(); wi != weapons.end(); ++wi) {
663 ++(*wi)->reloadStatus;
664 }
665 return;
666 }
667
668 restTime++;
669 outOfMapTime = (pos.IsInBounds())? 0: outOfMapTime + 1;
670
671 if (!dontUseWeapons) {
672 for (std::vector<CWeapon*>::iterator wi = weapons.begin(); wi != weapons.end(); ++wi) {
673 (*wi)->Update();
674 }
675 }
676 }
677
UpdateResources()678 void CUnit::UpdateResources()
679 {
680 metalMake = metalMakeI + metalMakeold;
681 metalUse = metalUseI + metalUseold;
682 energyMake = energyMakeI + energyMakeold;
683 energyUse = energyUseI + energyUseold;
684
685 metalMakeold = metalMakeI;
686 metalUseold = metalUseI;
687 energyMakeold = energyMakeI;
688 energyUseold = energyUseI;
689
690 metalMakeI = metalUseI = energyMakeI = energyUseI = 0.0f;
691 }
692
SetLosStatus(int at,unsigned short newStatus)693 void CUnit::SetLosStatus(int at, unsigned short newStatus)
694 {
695 const unsigned short currStatus = losStatus[at];
696 const unsigned short diffBits = (currStatus ^ newStatus);
697
698 // add to the state before running the callins
699 //
700 // note that is not symmetric: UnitEntered* and
701 // UnitLeft* are after-the-fact events, yet the
702 // Left* call-ins would still see the old state
703 // without first clearing the IN{LOS, RADAR} bit
704 losStatus[at] |= newStatus;
705
706 if (diffBits) {
707 if (diffBits & LOS_INLOS) {
708 if (newStatus & LOS_INLOS) {
709 eventHandler.UnitEnteredLos(this, at);
710 eoh->UnitEnteredLos(*this, at);
711 } else {
712 // clear before sending the event
713 losStatus[at] &= ~LOS_INLOS;
714
715 eventHandler.UnitLeftLos(this, at);
716 eoh->UnitLeftLos(*this, at);
717 }
718 }
719
720 if (diffBits & LOS_INRADAR) {
721 if (newStatus & LOS_INRADAR) {
722 eventHandler.UnitEnteredRadar(this, at);
723 eoh->UnitEnteredRadar(*this, at);
724 } else {
725 // clear before sending the event
726 losStatus[at] &= ~LOS_INRADAR;
727
728 eventHandler.UnitLeftRadar(this, at);
729 eoh->UnitLeftRadar(*this, at);
730 }
731 }
732 }
733
734 // remove from the state after running the callins
735 losStatus[at] &= newStatus;
736 }
737
738
CalcLosStatus(int at)739 unsigned short CUnit::CalcLosStatus(int at)
740 {
741 const unsigned short currStatus = losStatus[at];
742
743 unsigned short newStatus = currStatus;
744 unsigned short mask = ~(currStatus >> 8);
745
746 if (losHandler->InLos(this, at)) {
747 if (!beingBuilt) {
748 newStatus |= (mask & (LOS_INLOS | LOS_INRADAR |
749 LOS_PREVLOS | LOS_CONTRADAR));
750 } else {
751 // we are being built, do not set LOS_PREVLOS
752 // since we do not want ghosts for nanoframes
753 newStatus |= (mask & (LOS_INLOS | LOS_INRADAR));
754 newStatus &= ~(mask & (LOS_PREVLOS | LOS_CONTRADAR));
755 }
756 }
757 else if (radarHandler->InRadar(this, at)) {
758 newStatus |= (mask & LOS_INRADAR);
759 newStatus &= ~(mask & LOS_INLOS);
760 }
761 else {
762 newStatus &= ~(mask & (LOS_INLOS | LOS_INRADAR | LOS_CONTRADAR));
763 }
764
765 return newStatus;
766 }
767
768
UpdateLosStatus(int at)769 inline void CUnit::UpdateLosStatus(int at)
770 {
771 const unsigned short currStatus = losStatus[at];
772 if ((currStatus & LOS_ALL_MASK_BITS) == LOS_ALL_MASK_BITS) {
773 return; // no need to update, all changes are masked
774 }
775 SetLosStatus(at, CalcLosStatus(at));
776 }
777
778
SetStunned(bool stun)779 void CUnit::SetStunned(bool stun) {
780 stunned = stun;
781
782 if (moveType->progressState == AMoveType::Active) {
783 if (stunned) {
784 script->StopMoving();
785 } else {
786 script->StartMoving(moveType->IsReversing());
787 }
788 }
789 }
790
791
SlowUpdate()792 void CUnit::SlowUpdate()
793 {
794 UpdatePosErrorParams(false, true);
795
796 for (int at = 0; at < teamHandler->ActiveAllyTeams(); ++at) {
797 UpdateLosStatus(at);
798 }
799
800 DoWaterDamage();
801
802 if (health < 0.0f) {
803 KillUnit(NULL, false, true);
804 return;
805 }
806
807 repairAmount = 0.0f;
808
809 if (paralyzeDamage > 0.0f) {
810 // NOTE: the paralysis degradation-rate has to vary, because
811 // when units are paralyzed based on their current health (in
812 // DoDamage) we potentially start decaying from a lower damage
813 // level and would otherwise be de-paralyzed more quickly than
814 // specified by <paralyzeTime>
815 paralyzeDamage -= ((modInfo.paralyzeOnMaxHealth? maxHealth: health) * 0.5f * CUnit::empDecline);
816 paralyzeDamage = std::max(paralyzeDamage, 0.0f);
817 }
818
819 UpdateResources();
820
821 if (IsStunned()) {
822 // call this because we can be pushed into a different quad while stunned
823 // which would make us invulnerable to most non-/small-AOE weapon impacts
824 static_cast<AMoveType*>(moveType)->SlowUpdate();
825
826 const bool b0 = (paralyzeDamage <= (modInfo.paralyzeOnMaxHealth? maxHealth: health));
827 const bool b1 = (transporter == NULL || transporter->unitDef->isFirePlatform);
828
829 // de-stun only if we are not (still) inside a non-firebase transport
830 if (b0 && b1) {
831 SetStunned(false);
832 }
833
834 SlowUpdateCloak(true);
835 return;
836 }
837
838 if (selfDCountdown > 0) {
839 if ((selfDCountdown -= 1) == 0) {
840 // avoid unfinished buildings making an explosion
841 if (!beingBuilt) {
842 KillUnit(NULL, true, false);
843 } else {
844 KillUnit(NULL, false, true);
845 }
846 return;
847 }
848 if ((selfDCountdown & 1) && (team == gu->myTeam) && !gu->spectating) {
849 LOG("%s: self-destruct in %is", unitDef->humanName.c_str(), (selfDCountdown >> 1) + 1);
850 }
851 }
852
853 if (beingBuilt) {
854 if (modInfo.constructionDecay && (lastNanoAdd < (gs->frameNum - modInfo.constructionDecayTime))) {
855 float buildDecay = buildTime * modInfo.constructionDecaySpeed;
856
857 buildDecay = 1.0f / std::max(0.001f, buildDecay);
858 buildDecay = std::min(buildProgress, buildDecay);
859
860 health = std::max(0.0f, health - maxHealth * buildDecay);
861 buildProgress -= buildDecay;
862
863 AddMetal(metalCost * buildDecay, false);
864
865 if (health <= 0.0f || buildProgress <= 0.0f) {
866 KillUnit(NULL, false, true);
867 }
868 }
869
870 ScriptDecloak(false);
871 return;
872 }
873
874 // below is stuff that should not be run while being built
875 commandAI->SlowUpdate();
876 moveType->SlowUpdate();
877
878 // FIXME: scriptMakeMetal ...?
879 AddMetal(uncondMakeMetal);
880 AddEnergy(uncondMakeEnergy);
881 UseMetal(uncondUseMetal);
882 UseEnergy(uncondUseEnergy);
883 if (activated) {
884 if (UseMetal(condUseMetal)) {
885 AddEnergy(condMakeEnergy);
886 }
887 if (UseEnergy(condUseEnergy)) {
888 AddMetal(condMakeMetal);
889 }
890 }
891
892 AddMetal(unitDef->metalMake * 0.5f);
893 if (activated) {
894 if (UseEnergy(unitDef->energyUpkeep * 0.5f)) {
895 AddMetal(unitDef->makesMetal * 0.5f);
896 if (unitDef->extractsMetal > 0.0f) {
897 AddMetal(metalExtract * 0.5f);
898 }
899 }
900 UseMetal(unitDef->metalUpkeep * 0.5f);
901
902 if (unitDef->windGenerator > 0.0f) {
903 if (wind.GetCurrentStrength() > unitDef->windGenerator) {
904 AddEnergy(unitDef->windGenerator * 0.5f);
905 } else {
906 AddEnergy(wind.GetCurrentStrength() * 0.5f);
907 }
908 }
909 }
910 AddEnergy(energyTickMake * 0.5f);
911
912 if (health < maxHealth) {
913 if (restTime > unitDef->idleTime) {
914 health += unitDef->idleAutoHeal;
915 }
916
917 health += unitDef->autoHeal;
918 health = std::min(health, maxHealth);
919 }
920
921 SlowUpdateCloak(false);
922
923 if (unitDef->canKamikaze) {
924 if (fireState >= FIRESTATE_FIREATWILL) {
925 std::vector<int> nearbyUnits;
926 if (unitDef->kamikazeUseLOS) {
927 CGameHelper::GetEnemyUnits(pos, unitDef->kamikazeDist, allyteam, nearbyUnits);
928 } else {
929 CGameHelper::GetEnemyUnitsNoLosTest(pos, unitDef->kamikazeDist, allyteam, nearbyUnits);
930 }
931
932 for (std::vector<int>::const_iterator it = nearbyUnits.begin(); it != nearbyUnits.end(); ++it) {
933 const CUnit* victim = unitHandler->GetUnitUnsafe(*it);
934 const float3 dif = pos - victim->pos;
935
936 if (dif.SqLength() < Square(unitDef->kamikazeDist)) {
937 if (victim->speed.dot(dif) <= 0) {
938 //! self destruct when we start moving away from the target, this should maximize the damage
939 KillUnit(NULL, true, false);
940 return;
941 }
942 }
943 }
944 }
945
946 if (
947 (attackTarget && (attackTarget->pos.SqDistance(pos) < Square(unitDef->kamikazeDist)))
948 || (userAttackGround && (attackPos.SqDistance(pos)) < Square(unitDef->kamikazeDist))
949 ) {
950 KillUnit(NULL, true, false);
951 return;
952 }
953 }
954
955 SlowUpdateWeapons();
956
957 if (moveType->progressState == AMoveType::Active) {
958 if (seismicSignature && !GetTransporter()) {
959 DoSeismicPing((int)seismicSignature);
960 }
961 }
962
963 CalculateTerrainType();
964 UpdateTerrainType();
965 }
966
SlowUpdateWeapons()967 void CUnit::SlowUpdateWeapons() {
968 if (weapons.empty())
969 return;
970
971 haveTarget = false;
972
973 if (dontFire)
974 return;
975
976 for (vector<CWeapon*>::iterator wi = weapons.begin(); wi != weapons.end(); ++wi) {
977 CWeapon* w = *wi;
978
979 w->SlowUpdate();
980
981 // NOTE:
982 // pass w->haveUserTarget so we do not interfere with
983 // user targets; w->haveUserTarget can only be true if
984 // either 1) ::AttackUnit was called with a (non-NULL)
985 // target-unit which the CAI did *not* auto-select, or
986 // 2) ::AttackGround was called with any user-selected
987 // position and all checks succeeded
988 if (haveManualFireRequest == (unitDef->canManualFire && w->weaponDef->manualfire)) {
989 if (attackTarget != NULL) {
990 w->AttackUnit(attackTarget, w->haveUserTarget);
991 } else if (userAttackGround) {
992 // this implies a user-order
993 w->AttackGround(attackPos, true);
994 }
995 }
996
997 if (lastAttacker == NULL)
998 continue;
999 if ((lastAttackFrame + 200) <= gs->frameNum)
1000 continue;
1001 if (w->targetType != Target_None)
1002 continue;
1003 if (fireState == FIRESTATE_HOLDFIRE)
1004 continue;
1005
1006 // return fire at our last attacker if allowed
1007 w->AttackUnit(lastAttacker, false);
1008 }
1009 }
1010
1011
1012
GetFlankingDamageBonus(const float3 & attackDir)1013 float CUnit::GetFlankingDamageBonus(const float3& attackDir)
1014 {
1015 float flankingBonus = 1.0f;
1016
1017 if (flankingBonusMode <= 0)
1018 return flankingBonus;
1019
1020 if (flankingBonusMode == 1) {
1021 // mode 1 = global coordinates, mobile
1022 flankingBonusDir += (attackDir * flankingBonusMobility);
1023 flankingBonusDir.Normalize();
1024 flankingBonusMobility = 0.0f;
1025 flankingBonus = (flankingBonusAvgDamage - attackDir.dot(flankingBonusDir) * flankingBonusDifDamage);
1026 } else {
1027 float3 adirRelative;
1028 adirRelative.x = attackDir.dot(rightdir);
1029 adirRelative.y = attackDir.dot(updir);
1030 adirRelative.z = attackDir.dot(frontdir);
1031
1032 if (flankingBonusMode == 2) {
1033 // mode 2 = unit coordinates, mobile
1034 flankingBonusDir += (adirRelative * flankingBonusMobility);
1035 flankingBonusDir.Normalize();
1036 flankingBonusMobility = 0.0f;
1037 }
1038
1039 // modes 2 and 3 both use this; 3 is unit coordinates, immobile
1040 flankingBonus = (flankingBonusAvgDamage - adirRelative.dot(flankingBonusDir) * flankingBonusDifDamage);
1041 }
1042
1043 return flankingBonus;
1044 }
1045
DoWaterDamage()1046 void CUnit::DoWaterDamage()
1047 {
1048 if (mapInfo->water.damage <= 0.0f)
1049 return;
1050 if (!pos.IsInBounds())
1051 return;
1052 // note: hovercraft could also use a negative waterline
1053 // ("hoverline"?) to avoid being damaged but that would
1054 // confuse GMTPathController --> damage must be removed
1055 // via UnitPreDamaged if not wanted
1056 if (!IsInWater())
1057 return;
1058
1059 DoDamage(DamageArray(mapInfo->water.damage), ZeroVector, NULL, -DAMAGE_EXTSOURCE_WATER, -1);
1060 }
1061
1062
1063
AddUnitDamageStats(CUnit * unit,float damage,bool dealt)1064 static void AddUnitDamageStats(CUnit* unit, float damage, bool dealt)
1065 {
1066 if (unit == NULL)
1067 return;
1068
1069 CTeam* team = teamHandler->Team(unit->team);
1070 TeamStatistics* stats = team->currentStats;
1071
1072 if (dealt) {
1073 stats->damageDealt += damage;
1074 } else {
1075 stats->damageReceived += damage;
1076 }
1077 }
1078
DoDamage(const DamageArray & damages,const float3 & impulse,CUnit * attacker,int weaponDefID,int projectileID)1079 void CUnit::DoDamage(
1080 const DamageArray& damages,
1081 const float3& impulse,
1082 CUnit* attacker,
1083 int weaponDefID,
1084 int projectileID
1085 ) {
1086 if (isDead)
1087 return;
1088 if (IsCrashing() || IsInVoid())
1089 return;
1090
1091 float baseDamage = damages[armorType];
1092 float experienceMod = expMultiplier;
1093 float impulseMult = 1.0f;
1094
1095 const bool isParalyzer = (damages.paralyzeDamageTime != 0);
1096
1097 if (baseDamage > 0.0f) {
1098 if (attacker != NULL) {
1099 SetLastAttacker(attacker);
1100
1101 // FIXME -- not the impulse direction?
1102 baseDamage *= GetFlankingDamageBonus((attacker->pos - pos).SafeNormalize());
1103 }
1104
1105 baseDamage *= curArmorMultiple;
1106 restTime = 0; // bleeding != resting
1107 }
1108
1109 if (eventHandler.UnitPreDamaged(this, attacker, baseDamage, weaponDefID, projectileID, isParalyzer, &baseDamage, &impulseMult)) {
1110 return;
1111 }
1112
1113 script->HitByWeapon(-(float3(impulse * impulseMult)).SafeNormalize2D(), weaponDefID, /*inout*/ baseDamage);
1114 ApplyImpulse((impulse * impulseMult) / mass);
1115
1116 if (!isParalyzer) {
1117 // real damage
1118 if (baseDamage > 0.0f) {
1119 // do not log overkill damage (so nukes etc do not inflate values)
1120 AddUnitDamageStats(attacker, Clamp(maxHealth - health, 0.0f, baseDamage), true);
1121 AddUnitDamageStats(this, Clamp(maxHealth - health, 0.0f, baseDamage), false);
1122
1123 health -= baseDamage;
1124 } else { // healing
1125 health -= baseDamage;
1126 health = std::min(health, maxHealth);
1127
1128 if (health > paralyzeDamage && !modInfo.paralyzeOnMaxHealth) {
1129 SetStunned(false);
1130 }
1131 }
1132 } else {
1133 // paralyzation damage (adds reduced experience for the attacker)
1134 experienceMod *= 0.1f;
1135
1136 // paralyzeDamage may not get higher than baseHealth * (paralyzeTime + 1),
1137 // which means the unit will be destunned after <paralyzeTime> seconds.
1138 // (maximum paralyzeTime of all paralyzer weapons which recently hit it ofc)
1139 //
1140 // rate of paralysis-damage reduction is lower if the unit has less than
1141 // maximum health to ensure stun-time is always equal to <paralyzeTime>
1142 const float baseHealth = (modInfo.paralyzeOnMaxHealth? maxHealth: health);
1143 const float paralysisDecayRate = baseHealth * CUnit::empDecline;
1144 const float sumParalysisDamage = paralysisDecayRate * damages.paralyzeDamageTime;
1145 const float maxParalysisDamage = std::max(baseHealth + sumParalysisDamage - paralyzeDamage, 0.0f);
1146
1147 if (baseDamage > 0.0f) {
1148 // clamp the dealt paralysis-damage to [0, maxParalysisDamage]
1149 baseDamage = Clamp(baseDamage, 0.0f, maxParalysisDamage);
1150
1151 // no attacker gains experience from a stunned target
1152 experienceMod *= (1 - IsStunned());
1153 // increase the current level of paralysis-damage
1154 paralyzeDamage += baseDamage;
1155
1156 if (paralyzeDamage >= baseHealth) {
1157 SetStunned(true);
1158 }
1159 } else {
1160 // no experience from healing a non-stunned target
1161 experienceMod *= (paralyzeDamage > 0.0f);
1162 // decrease ("heal") the current level of paralysis-damage
1163 paralyzeDamage += baseDamage;
1164 paralyzeDamage = std::max(paralyzeDamage, 0.0f);
1165
1166 if (paralyzeDamage <= baseHealth) {
1167 SetStunned(false);
1168 }
1169 }
1170 }
1171
1172 recentDamage += baseDamage;
1173
1174 eventHandler.UnitDamaged(this, attacker, baseDamage, weaponDefID, projectileID, isParalyzer);
1175 eoh->UnitDamaged(*this, attacker, baseDamage, weaponDefID, projectileID, isParalyzer);
1176
1177 #ifdef TRACE_SYNC
1178 tracefile << "Damage: ";
1179 tracefile << id << " " << baseDamage << "\n";
1180 #endif
1181
1182 if (baseDamage > 0.0f) {
1183 if ((attacker != NULL) && !teamHandler->Ally(allyteam, attacker->allyteam)) {
1184 const float scaledExpMod = 0.1f * experienceMod * (power / attacker->power);
1185 const float scaledDamage = std::max(0.0f, (baseDamage + std::min(0.0f, health))) / maxHealth;
1186 // alternative
1187 // scaledDamage = (max(healthPreDamage, 0) - max(health, 0)) / maxHealth
1188
1189 // FIXME: why is experience added a second time when health <= 0.0f?
1190 attacker->AddExperience(scaledExpMod * scaledDamage);
1191 }
1192 }
1193
1194 if (health <= 0.0f) {
1195 KillUnit(attacker, false, false);
1196
1197 if (!isDead) { return; }
1198 if (beingBuilt) { return; }
1199 if (attacker == NULL) { return; }
1200
1201 if (!teamHandler->Ally(allyteam, attacker->allyteam)) {
1202 attacker->AddExperience(expMultiplier * 0.1f * (power / attacker->power));
1203 teamHandler->Team(attacker->team)->currentStats->unitsKilled++;
1204 }
1205 }
1206 }
1207
1208
1209
ApplyImpulse(const float3 & impulse)1210 void CUnit::ApplyImpulse(const float3& impulse) {
1211 if (GetTransporter() != NULL) {
1212 // transfer impulse to unit transporting us, scaled by its mass
1213 // assume we came here straight from DoDamage, not LuaSyncedCtrl
1214 GetTransporter()->ApplyImpulse((impulse * mass) / (GetTransporter()->mass));
1215 return;
1216 }
1217
1218 const float3& groundNormal = CGround::GetNormal(pos.x, pos.z);
1219 const float groundImpulseScale = std::min(0.0f, impulse.dot(groundNormal));
1220 const float3 modImpulse = impulse - (groundNormal * groundImpulseScale * IsOnGround());
1221
1222 if (moveType->CanApplyImpulse(modImpulse)) {
1223 CSolidObject::ApplyImpulse(modImpulse);
1224 }
1225 }
1226
1227
1228
1229 /******************************************************************************/
1230 /******************************************************************************/
1231
GetTransformMatrix(const bool synced,const bool error) const1232 CMatrix44f CUnit::GetTransformMatrix(const bool synced, const bool error) const
1233 {
1234 float3 interPos = synced ? pos : drawPos;
1235
1236 if (error && !synced && !gu->spectatingFullView) {
1237 interPos += GetErrorVector(gu->myAllyTeam);
1238 }
1239
1240 return CMatrix44f(interPos, -rightdir, updir, frontdir);
1241 }
1242
GetCollisionVolume(const LocalModelPiece * lmp) const1243 const CollisionVolume* CUnit::GetCollisionVolume(const LocalModelPiece* lmp) const {
1244 if (lmp == NULL)
1245 return collisionVolume;
1246 if (!collisionVolume->DefaultToPieceTree())
1247 return collisionVolume;
1248
1249 return (lmp->GetCollisionVolume());
1250 }
1251
1252
1253
1254 /******************************************************************************/
1255 /******************************************************************************/
1256
ChangeSensorRadius(int * valuePtr,int newValue)1257 void CUnit::ChangeSensorRadius(int* valuePtr, int newValue)
1258 {
1259 radarHandler->RemoveUnit(this);
1260
1261 *valuePtr = newValue;
1262
1263 if (newValue != 0) {
1264 hasRadarCapacity = true;
1265 } else if (hasRadarCapacity) {
1266 hasRadarCapacity = (radarRadius > 0.0f) || (jammerRadius > 0.0f) ||
1267 (sonarRadius > 0.0f) || (sonarJamRadius > 0.0f) ||
1268 (seismicRadius > 0.0f);
1269 }
1270
1271 radarHandler->MoveUnit(this);
1272 }
1273
1274
AddExperience(float exp)1275 void CUnit::AddExperience(float exp)
1276 {
1277 if (exp == 0.0f)
1278 return;
1279
1280 assert(exp > 0.0f);
1281 const float oldExp = experience;
1282 experience += exp;
1283
1284 const float oldLimExp = limExperience;
1285 limExperience = experience / (experience + 1.0f);
1286
1287 if (expGrade != 0.0f) {
1288 const int oldGrade = (int)(oldLimExp / expGrade);
1289 const int newGrade = (int)(limExperience / expGrade);
1290 if (oldGrade != newGrade) {
1291 eventHandler.UnitExperience(this, oldExp);
1292 }
1293 }
1294
1295 if (expPowerScale > 0.0f) {
1296 power = unitDef->power * (1.0f + (limExperience * expPowerScale));
1297 }
1298 if (expReloadScale > 0.0f) {
1299 reloadSpeed = (1.0f + (limExperience * expReloadScale));
1300 }
1301 if (expHealthScale > 0.0f) {
1302 const float oldMaxHealth = maxHealth;
1303
1304 maxHealth = std::max(0.1f, unitDef->health * (1.0f + (limExperience * expHealthScale)));
1305 health = health * (maxHealth / oldMaxHealth);
1306 }
1307 }
1308
1309
DoSeismicPing(float pingSize)1310 void CUnit::DoSeismicPing(float pingSize)
1311 {
1312 float rx = gs->randFloat();
1313 float rz = gs->randFloat();
1314
1315 if (!(losStatus[gu->myAllyTeam] & LOS_INLOS) &&
1316 radarHandler->InSeismicDistance(this, gu->myAllyTeam)) {
1317
1318 const float3 err(radarHandler->GetAllyTeamRadarErrorSize(gu->myAllyTeam) * (0.5f - rx), 0.0f,
1319 radarHandler->GetAllyTeamRadarErrorSize(gu->myAllyTeam) * (0.5f - rz));
1320
1321 new CSeismicGroundFlash(pos + err, 30, 15, 0, pingSize, 1, float3(0.8f, 0.0f, 0.0f));
1322 }
1323 for (int a = 0; a < teamHandler->ActiveAllyTeams(); ++a) {
1324 if (radarHandler->InSeismicDistance(this, a)) {
1325 const float3 err(radarHandler->GetAllyTeamRadarErrorSize(a) * (0.5f - rx), 0.0f,
1326 radarHandler->GetAllyTeamRadarErrorSize(a) * (0.5f - rz));
1327 const float3 pingPos = (pos + err);
1328 eventHandler.UnitSeismicPing(this, a, pingPos, pingSize);
1329 eoh->SeismicPing(a, *this, pingPos, pingSize);
1330 }
1331 }
1332 }
1333
1334
ChangeLos(int losRad,int airRad)1335 void CUnit::ChangeLos(int losRad, int airRad)
1336 {
1337 losHandler->FreeInstance(los);
1338 los = NULL;
1339 losRadius = losRad;
1340 airLosRadius = airRad;
1341 losHandler->MoveUnit(this, false);
1342 }
1343
1344
ChangeTeam(int newteam,ChangeType type)1345 bool CUnit::ChangeTeam(int newteam, ChangeType type)
1346 {
1347 if (isDead)
1348 return false;
1349
1350 // do not allow unit count violations due to team swapping
1351 // (this includes unit captures)
1352 if (unitHandler->unitsByDefs[newteam][unitDef->id].size() >= unitDef->maxThisUnit)
1353 return false;
1354
1355 if (!eventHandler.AllowUnitTransfer(this, newteam, type == ChangeCaptured))
1356 return false;
1357
1358 // do not allow old player to keep controlling the unit
1359 if (fpsControlPlayer != NULL) {
1360 fpsControlPlayer->StopControllingUnit();
1361 assert(fpsControlPlayer == NULL);
1362 }
1363
1364 const int oldteam = team;
1365
1366 selectedUnitsHandler.RemoveUnit(this);
1367 SetGroup(NULL);
1368
1369 eventHandler.UnitTaken(this, oldteam, newteam);
1370 eoh->UnitCaptured(*this, oldteam, newteam);
1371
1372 quadField->RemoveUnit(this);
1373 quads.clear();
1374 losHandler->FreeInstance(los);
1375 los = 0;
1376 radarHandler->RemoveUnit(this);
1377
1378 if (unitDef->isAirBase) {
1379 airBaseHandler->DeregisterAirBase(this);
1380 }
1381
1382 if (type == ChangeGiven) {
1383 teamHandler->Team(oldteam)->RemoveUnit(this, CTeam::RemoveGiven);
1384 teamHandler->Team(newteam)->AddUnit(this, CTeam::AddGiven);
1385 } else {
1386 teamHandler->Team(oldteam)->RemoveUnit(this, CTeam::RemoveCaptured);
1387 teamHandler->Team(newteam)->AddUnit(this, CTeam::AddCaptured);
1388 }
1389
1390 if (!beingBuilt) {
1391 teamHandler->Team(oldteam)->metalStorage -= metalStorage;
1392 teamHandler->Team(oldteam)->energyStorage -= energyStorage;
1393
1394 teamHandler->Team(newteam)->metalStorage += metalStorage;
1395 teamHandler->Team(newteam)->energyStorage += energyStorage;
1396 }
1397
1398
1399 team = newteam;
1400 allyteam = teamHandler->AllyTeam(newteam);
1401 neutral = false;
1402
1403 unitHandler->unitsByDefs[oldteam][unitDef->id].erase(this);
1404 unitHandler->unitsByDefs[newteam][unitDef->id].insert(this);
1405
1406 for (int at = 0; at < teamHandler->ActiveAllyTeams(); ++at) {
1407 if (teamHandler->Ally(at, allyteam)) {
1408 SetLosStatus(at, LOS_ALL_MASK_BITS | LOS_INLOS | LOS_INRADAR | LOS_PREVLOS | LOS_CONTRADAR);
1409 } else {
1410 // re-calc LOS status
1411 losStatus[at] = 0;
1412 UpdateLosStatus(at);
1413 }
1414 }
1415
1416 losHandler->MoveUnit(this, false);
1417 quadField->MovedUnit(this);
1418 radarHandler->MoveUnit(this);
1419
1420 if (unitDef->isAirBase) {
1421 airBaseHandler->RegisterAirBase(this);
1422 }
1423
1424 eventHandler.UnitGiven(this, oldteam, newteam);
1425 eoh->UnitGiven(*this, oldteam, newteam);
1426
1427 // reset states and clear the queues
1428 if (!teamHandler->AlliedTeams(oldteam, newteam))
1429 ChangeTeamReset();
1430
1431 return true;
1432 }
1433
1434
ChangeTeamReset()1435 void CUnit::ChangeTeamReset()
1436 {
1437 // stop friendly units shooting at us
1438 const CObject::TDependenceMap& listeners = GetAllListeners();
1439 std::vector<CUnit *> alliedunits;
1440 for (CObject::TDependenceMap::const_iterator li = listeners.begin(); li != listeners.end(); ++li) {
1441 for (CObject::TSyncSafeSet::const_iterator di = li->second.begin(); di != li->second.end(); ++di) {
1442 CUnit* u = dynamic_cast<CUnit*>(*di);
1443 if (u != NULL && teamHandler->AlliedTeams(team, u->team))
1444 alliedunits.push_back(u);
1445 }
1446 }
1447 for (std::vector<CUnit*>::const_iterator ui = alliedunits.begin(); ui != alliedunits.end(); ++ui) {
1448 (*ui)->StopAttackingAllyTeam(allyteam);
1449 }
1450 // and stop shooting at friendly ally teams
1451 for (int t = 0; t < teamHandler->ActiveAllyTeams(); ++t) {
1452 if (teamHandler->Ally(t, allyteam))
1453 StopAttackingAllyTeam(t);
1454 }
1455
1456 // clear the commands (newUnitCommands for factories)
1457 Command c(CMD_STOP);
1458 commandAI->GiveCommand(c);
1459
1460 // clear the build commands for factories
1461 CFactoryCAI* facAI = dynamic_cast<CFactoryCAI*>(commandAI);
1462 if (facAI) {
1463 const unsigned char options = RIGHT_MOUSE_KEY; // clear option
1464 CCommandQueue& buildCommands = facAI->commandQue;
1465 CCommandQueue::iterator it;
1466 std::vector<Command> clearCommands;
1467 clearCommands.reserve(buildCommands.size());
1468 for (it = buildCommands.begin(); it != buildCommands.end(); ++it) {
1469 clearCommands.push_back(Command(it->GetID(), options));
1470 }
1471 for (int i = 0; i < (int)clearCommands.size(); i++) {
1472 facAI->GiveCommand(clearCommands[i]);
1473 }
1474 }
1475
1476 //FIXME reset to unitdef defaults
1477
1478 // deactivate to prevent the old give metal maker trick
1479 // TODO remove, because it is *A specific
1480 c = Command(CMD_ONOFF, 0, 0); // always off
1481 commandAI->GiveCommand(c);
1482
1483 // reset repeat state
1484 c = Command(CMD_REPEAT, 0, 0);
1485 commandAI->GiveCommand(c);
1486
1487 // reset cloak state
1488 if (unitDef->canCloak) {
1489 c = Command(CMD_CLOAK, 0, 0); // always off
1490 commandAI->GiveCommand(c);
1491 }
1492 // reset move state
1493 if (unitDef->canmove || unitDef->builder) {
1494 c = Command(CMD_MOVE_STATE, 0, MOVESTATE_MANEUVER);
1495 commandAI->GiveCommand(c);
1496 }
1497 // reset fire state
1498 if (commandAI->CanChangeFireState()) {
1499 c = Command(CMD_FIRE_STATE, 0, FIRESTATE_FIREATWILL);
1500 commandAI->GiveCommand(c);
1501 }
1502 // reset trajectory state
1503 if (unitDef->highTrajectoryType > 1) {
1504 c = Command(CMD_TRAJECTORY, 0, 0);
1505 commandAI->GiveCommand(c);
1506 }
1507 }
1508
1509
IsIdle() const1510 bool CUnit::IsIdle() const
1511 {
1512 if (beingBuilt)
1513 return false;
1514
1515 if (!commandAI->commandQue.empty())
1516 return false;
1517
1518 return true;
1519 }
1520
1521
AttackUnit(CUnit * targetUnit,bool isUserTarget,bool wantManualFire,bool fpsMode)1522 bool CUnit::AttackUnit(CUnit* targetUnit, bool isUserTarget, bool wantManualFire, bool fpsMode)
1523 {
1524 bool ret = false;
1525
1526 haveManualFireRequest = wantManualFire;
1527 userAttackGround = false;
1528
1529 if (attackTarget != NULL) {
1530 DeleteDeathDependence(attackTarget, DEPENDENCE_TARGET);
1531 }
1532
1533 attackPos = ZeroVector;
1534 attackTarget = targetUnit;
1535
1536 if (targetUnit != NULL) {
1537 AddDeathDependence(targetUnit, DEPENDENCE_TARGET);
1538 }
1539
1540 for (std::vector<CWeapon*>::iterator wi = weapons.begin(); wi != weapons.end(); ++wi) {
1541 CWeapon* w = *wi;
1542
1543 // isUserTarget is true if this target was selected by the
1544 // user as opposed to automatically by the unit's commandAI
1545 //
1546 // NOTE: "&&" because we have a separate userAttackGround (!)
1547 w->targetType = Target_None;
1548 w->haveUserTarget = (targetUnit != NULL && isUserTarget);
1549
1550 if (targetUnit == NULL)
1551 continue;
1552
1553 if ((wantManualFire == (unitDef->canManualFire && w->weaponDef->manualfire)) || fpsMode) {
1554 ret |= (w->AttackUnit(targetUnit, isUserTarget));
1555 }
1556 }
1557
1558 return ret;
1559 }
1560
AttackGround(const float3 & pos,bool isUserTarget,bool wantManualFire,bool fpsMode)1561 bool CUnit::AttackGround(const float3& pos, bool isUserTarget, bool wantManualFire, bool fpsMode)
1562 {
1563 bool ret = false;
1564
1565 // remember whether this was a user-order for SlowUpdateWeapons
1566 // (because CCommandAI does not keep calling us, but ::SUW does)
1567 haveManualFireRequest = wantManualFire;
1568 userAttackGround = isUserTarget;
1569
1570 if (attackTarget != NULL) {
1571 DeleteDeathDependence(attackTarget, DEPENDENCE_TARGET);
1572 }
1573
1574 attackPos = pos;
1575 attackTarget = NULL;
1576
1577 for (std::vector<CWeapon*>::iterator wi = weapons.begin(); wi != weapons.end(); ++wi) {
1578 CWeapon* w = *wi;
1579
1580 w->targetType = Target_None;
1581 w->haveUserTarget = false; // this should be false for ground-attack commands
1582
1583 if ((wantManualFire == (unitDef->canManualFire && w->weaponDef->manualfire)) || fpsMode) {
1584 ret |= (w->AttackGround(pos, isUserTarget));
1585 }
1586 }
1587
1588 return ret;
1589 }
1590
1591
1592
SetLastAttacker(CUnit * attacker)1593 void CUnit::SetLastAttacker(CUnit* attacker)
1594 {
1595 assert(attacker != NULL);
1596
1597 if (teamHandler->AlliedTeams(team, attacker->team)) {
1598 return;
1599 }
1600 if (lastAttacker) {
1601 DeleteDeathDependence(lastAttacker, DEPENDENCE_ATTACKER);
1602 }
1603
1604 lastAttackFrame = gs->frameNum;
1605 lastAttacker = attacker;
1606
1607 AddDeathDependence(attacker, DEPENDENCE_ATTACKER);
1608 }
1609
DependentDied(CObject * o)1610 void CUnit::DependentDied(CObject* o)
1611 {
1612 if (o == attackTarget) { attackTarget = NULL; }
1613 if (o == soloBuilder) { soloBuilder = NULL; }
1614 if (o == transporter) { transporter = NULL; }
1615 if (o == lastAttacker) { lastAttacker = NULL; }
1616
1617 incomingMissiles.remove(static_cast<CMissileProjectile*>(o));
1618
1619 CSolidObject::DependentDied(o);
1620 }
1621
1622
1623
UpdatePhysicalState(float eps)1624 void CUnit::UpdatePhysicalState(float eps)
1625 {
1626 const bool inAir = IsInAir();
1627 const bool inWater = IsInWater();
1628
1629 CSolidObject::UpdatePhysicalState(eps);
1630
1631 if (IsInAir() != inAir) {
1632 if (IsInAir()) {
1633 eventHandler.UnitEnteredAir(this);
1634 } else {
1635 eventHandler.UnitLeftAir(this);
1636 }
1637 }
1638 if (IsInWater() != inWater) {
1639 if (IsInWater()) {
1640 eventHandler.UnitEnteredWater(this);
1641 } else {
1642 eventHandler.UnitLeftWater(this);
1643 }
1644 }
1645 }
1646
UpdateTerrainType()1647 void CUnit::UpdateTerrainType()
1648 {
1649 if (curTerrainType != lastTerrainType) {
1650 script->SetSFXOccupy(curTerrainType);
1651 lastTerrainType = curTerrainType;
1652 }
1653 }
1654
CalculateTerrainType()1655 void CUnit::CalculateTerrainType()
1656 {
1657 enum {
1658 SFX_TERRAINTYPE_NONE = 0,
1659 SFX_TERRAINTYPE_WATER_A = 1,
1660 SFX_TERRAINTYPE_WATER_B = 2,
1661 SFX_TERRAINTYPE_LAND = 4,
1662 };
1663
1664 // optimization: there's only about one unit that actually needs this information
1665 // ==> why are we even bothering with it? the callin parameter barely makes sense
1666 if (!script->HasSetSFXOccupy())
1667 return;
1668
1669 if (GetTransporter() != NULL) {
1670 curTerrainType = SFX_TERRAINTYPE_NONE;
1671 return;
1672 }
1673
1674 const float height = CGround::GetApproximateHeight(pos.x, pos.z);
1675
1676 // water
1677 if (height < -5.0f) {
1678 if (upright)
1679 curTerrainType = SFX_TERRAINTYPE_WATER_B;
1680 else
1681 curTerrainType = SFX_TERRAINTYPE_WATER_A;
1682 }
1683 // shore
1684 else if (height < 0.0f) {
1685 if (upright)
1686 curTerrainType = SFX_TERRAINTYPE_WATER_A;
1687 }
1688 // land (or air)
1689 else {
1690 curTerrainType = SFX_TERRAINTYPE_LAND;
1691 }
1692 }
1693
1694
SetGroup(CGroup * newGroup,bool fromFactory)1695 bool CUnit::SetGroup(CGroup* newGroup, bool fromFactory)
1696 {
1697 // factory is not necessarily selected
1698 if (fromFactory && !selectedUnitsHandler.AutoAddBuiltUnitsToFactoryGroup())
1699 return false;
1700
1701 if (group != NULL) {
1702 group->RemoveUnit(this);
1703 }
1704
1705 group = newGroup;
1706
1707 if (group) {
1708 if (!group->AddUnit(this)){
1709 // group did not accept us
1710 group = NULL;
1711 return false;
1712 } else {
1713 // add unit to the set of selected units iff its new group is already selected
1714 // and (user wants the unit to be auto-selected or the unit is not newly built)
1715 if (selectedUnitsHandler.IsGroupSelected(group->id) && (selectedUnitsHandler.AutoAddBuiltUnitsToSelectedGroup() || !fromFactory)) {
1716 selectedUnitsHandler.AddUnit(this);
1717 }
1718 }
1719 }
1720
1721 return true;
1722 }
1723
1724
AddBuildPower(CUnit * builder,float amount)1725 bool CUnit::AddBuildPower(CUnit* builder, float amount)
1726 {
1727 // stop decaying on building AND reclaim
1728 lastNanoAdd = gs->frameNum;
1729
1730 CTeam* builderTeam = teamHandler->Team(builder->team);
1731
1732 if (amount >= 0.0f) {
1733 // build or repair
1734 if (!beingBuilt && (health >= maxHealth))
1735 return false;
1736
1737 if (beingBuilt) {
1738 // build
1739 const float step = std::min(amount / buildTime, 1.0f - buildProgress);
1740 const float metalCostStep = metalCost * step;
1741 const float energyCostStep = energyCost * step;
1742
1743 if (builderTeam->metal < metalCostStep || builderTeam->energy < energyCostStep) {
1744 // update the energy and metal required counts
1745 builderTeam->metalPull += metalCostStep;
1746 builderTeam->energyPull += energyCostStep;
1747 return false;
1748 }
1749
1750 if (!eventHandler.AllowUnitBuildStep(builder, this, step))
1751 return false;
1752
1753 if (builder->UseMetal(metalCostStep)) {
1754 // FIXME eventHandler.AllowUnitBuildStep() may have changed the storages!!! so the checks can be invalid!
1755 // TODO add a builder->UseResources(SResources(metalCostStep, energyCostStep))
1756 if (builder->UseEnergy(energyCostStep)) {
1757 health += (maxHealth * step);
1758 health = std::min(health, maxHealth);
1759 buildProgress += step;
1760
1761 if (buildProgress >= 1.0f) {
1762 FinishedBuilding(false);
1763 }
1764 } else {
1765 // refund already-deducted metal if *energy* cost cannot be
1766 builder->UseMetal(-metalCostStep);
1767 }
1768 }
1769
1770 return true;
1771 }
1772 else if (health < maxHealth) {
1773 // repair
1774 const float step = std::min(amount / buildTime, 1.0f - (health / maxHealth));
1775 const float energyUse = (energyCost * step);
1776 const float energyUseScaled = energyUse * modInfo.repairEnergyCostFactor;
1777
1778 if ((builderTeam->energy < energyUseScaled)) {
1779 // update the energy and metal required counts
1780 builderTeam->energyPull += energyUseScaled;
1781 return false;
1782 }
1783
1784 if (!eventHandler.AllowUnitBuildStep(builder, this, step))
1785 return false;
1786
1787 if (!builder->UseEnergy(energyUseScaled)) {
1788 // UseEnergy already increases the team's pull when it returns false!
1789 // builderTeam->energyPull += energyUseScaled;
1790 return false;
1791 }
1792
1793 repairAmount += amount;
1794 health += (maxHealth * step);
1795 health = std::min(health, maxHealth);
1796
1797 return true;
1798 }
1799 } else {
1800 // reclaim
1801 if (isDead || IsCrashing())
1802 return false;
1803
1804 const float step = std::max(amount / buildTime, -buildProgress);
1805 const float energyRefundStep = energyCost * step;
1806 const float metalRefundStep = metalCost * step;
1807 const float metalRefundStepScaled = metalRefundStep * modInfo.reclaimUnitEfficiency;
1808 const float energyRefundStepScaled = energyRefundStep * modInfo.reclaimUnitEnergyCostFactor;
1809
1810 if (builderTeam->energy < -energyRefundStepScaled) {
1811 builderTeam->energyPull += -energyRefundStepScaled;
1812 return false;
1813 }
1814
1815 if (!eventHandler.AllowUnitBuildStep(builder, this, step))
1816 return false;
1817
1818 restTime = 0;
1819
1820 if (!AllowedReclaim(builder)) {
1821 builder->DependentDied(this);
1822 return false;
1823 }
1824
1825 if (!builder->UseEnergy(-energyRefundStepScaled)) {
1826 // UseEnergy already increases the team's pull when it returns false!
1827 // builderTeam->energyPull += energyRefundStepScaled;
1828 return false;
1829 }
1830
1831 health += (maxHealth * step);
1832 buildProgress += (step * int(beingBuilt) * int(modInfo.reclaimUnitMethod == 0));
1833
1834 if (modInfo.reclaimUnitMethod == 0) {
1835 // gradual reclamation of invested metal
1836 if (!builder->AddHarvestedMetal(-metalRefundStepScaled)) {
1837 eventHandler.UnitHarvestStorageFull(this);
1838 return false;
1839 }
1840 // turn reclaimee into nanoframe (even living units)
1841 beingBuilt = true;
1842 } else {
1843 // lump reclamation of invested metal
1844 if (buildProgress <= 0.0f || health <= 0.0f) {
1845 builder->AddHarvestedMetal((metalCost * buildProgress) * modInfo.reclaimUnitEfficiency);
1846 }
1847 }
1848
1849 if (buildProgress <= 0.0f || health <= 0.0f) {
1850 KillUnit(NULL, false, true);
1851 return false;
1852 }
1853
1854 return true;
1855 }
1856
1857 return false;
1858 }
1859
1860
FinishedBuilding(bool postInit)1861 void CUnit::FinishedBuilding(bool postInit)
1862 {
1863 if (!beingBuilt && !postInit) {
1864 return;
1865 }
1866
1867 beingBuilt = false;
1868 buildProgress = 1.0f;
1869 mass = unitDef->mass;
1870
1871 if (soloBuilder) {
1872 DeleteDeathDependence(soloBuilder, DEPENDENCE_BUILDER);
1873 soloBuilder = NULL;
1874 }
1875
1876 ChangeLos(realLosRadius, realAirLosRadius);
1877
1878 if (unitDef->activateWhenBuilt) {
1879 Activate();
1880 }
1881 SetMetalStorage(unitDef->metalStorage);
1882 SetEnergyStorage(unitDef->energyStorage);
1883
1884
1885 // Sets the frontdir in sync with heading.
1886 frontdir = GetVectorFromHeading(heading) + float3(0, frontdir.y, 0);
1887
1888 if (unitDef->isAirBase) {
1889 airBaseHandler->RegisterAirBase(this);
1890 }
1891
1892 eventHandler.UnitFinished(this);
1893 eoh->UnitFinished(*this);
1894
1895 if (unitDef->isFeature && CUnit::spawnFeature) {
1896 FeatureLoadParams p = {featureHandler->GetFeatureDefByID(featureDefID), NULL, pos, ZeroVector, -1, team, allyteam, heading, buildFacing, 0};
1897 CFeature* f = featureHandler->CreateWreckage(p, 0, false);
1898
1899 if (f != NULL) {
1900 f->blockHeightChanges = true;
1901 }
1902
1903 UnBlock();
1904 KillUnit(NULL, false, true);
1905 }
1906 }
1907
1908
KillUnit(CUnit * attacker,bool selfDestruct,bool reclaimed,bool showDeathSequence)1909 void CUnit::KillUnit(CUnit* attacker, bool selfDestruct, bool reclaimed, bool showDeathSequence)
1910 {
1911 if (isDead) { return; }
1912 if (IsCrashing() && !beingBuilt) { return; }
1913
1914 isDead = true;
1915 deathSpeed = speed;
1916
1917 // TODO: add UnitPreDestroyed, call these later
1918 eventHandler.UnitDestroyed(this, attacker);
1919 eoh->UnitDestroyed(*this, attacker);
1920
1921 // Will be called in the destructor again, but this can not hurt
1922 SetGroup(NULL);
1923
1924 blockHeightChanges = false;
1925
1926 if (unitDef->windGenerator > 0.0f) {
1927 wind.DelUnit(this);
1928 }
1929
1930 if (showDeathSequence && (!reclaimed && !beingBuilt)) {
1931 const WeaponDef* wd = (selfDestruct)? unitDef->selfdExpWeaponDef: unitDef->deathExpWeaponDef;
1932
1933 if (wd != NULL) {
1934 CGameHelper::ExplosionParams params = {
1935 pos,
1936 ZeroVector,
1937 wd->damages,
1938 wd,
1939 this, // owner
1940 NULL, // hitUnit
1941 NULL, // hitFeature
1942 wd->craterAreaOfEffect,
1943 wd->damageAreaOfEffect,
1944 wd->edgeEffectiveness,
1945 wd->explosionSpeed,
1946 wd->damages[0] > 500? 1.0f: 2.0f, // gfxMod
1947 false, // impactOnly
1948 false, // ignoreOwner
1949 true, // damageGround
1950 -1u // projectileID
1951 };
1952
1953 helper->Explosion(params);
1954 }
1955
1956 if (selfDestruct) {
1957 recentDamage += (maxHealth * 2.0f);
1958 }
1959
1960 // start running the unit's kill-script
1961 script->Killed();
1962 } else {
1963 deathScriptFinished = true;
1964 }
1965
1966 if (!deathScriptFinished) {
1967 // put the unit in a pseudo-zombie state until Killed finishes
1968 SetVelocity(ZeroVector);
1969 SetStunned(true);
1970
1971 paralyzeDamage = 100.0f * maxHealth;
1972 health = std::max(health, 0.0f);
1973 }
1974 }
1975
AllowedReclaim(CUnit * builder) const1976 bool CUnit::AllowedReclaim(CUnit* builder) const
1977 {
1978 // Don't allow the reclaim if the unit is finished and we arent allowed to reclaim it
1979 if (!beingBuilt) {
1980 if (allyteam == builder->allyteam) {
1981 if ((team != builder->team) && (!modInfo.reclaimAllowAllies)) return false;
1982 } else {
1983 if (!modInfo.reclaimAllowEnemies) return false;
1984 }
1985 }
1986
1987 return true;
1988 }
1989
UseMetal(float metal)1990 bool CUnit::UseMetal(float metal)
1991 {
1992 if (metal < 0.0f) {
1993 AddMetal(-metal);
1994 return true;
1995 }
1996
1997 CTeam* myTeam = teamHandler->Team(team);
1998 myTeam->metalPull += metal;
1999
2000 if (myTeam->UseMetal(metal)) {
2001 metalUseI += metal;
2002 return true;
2003 }
2004
2005 return false;
2006 }
2007
AddMetal(float metal,bool useIncomeMultiplier)2008 void CUnit::AddMetal(float metal, bool useIncomeMultiplier)
2009 {
2010 if (metal < 0.0f) {
2011 UseMetal(-metal);
2012 return;
2013 }
2014
2015 metalMakeI += metal;
2016 teamHandler->Team(team)->AddMetal(metal, useIncomeMultiplier);
2017 }
2018
2019
UseEnergy(float energy)2020 bool CUnit::UseEnergy(float energy)
2021 {
2022 if (energy < 0.0f) {
2023 AddEnergy(-energy);
2024 return true;
2025 }
2026
2027 CTeam* myTeam = teamHandler->Team(team);
2028 myTeam->energyPull += energy;
2029
2030 if (myTeam->UseEnergy(energy)) {
2031 energyUseI += energy;
2032 return true;
2033 }
2034
2035 return false;
2036 }
2037
AddEnergy(float energy,bool useIncomeMultiplier)2038 void CUnit::AddEnergy(float energy, bool useIncomeMultiplier)
2039 {
2040 if (energy < 0.0f) {
2041 UseEnergy(-energy);
2042 return;
2043 }
2044 energyMakeI += energy;
2045 teamHandler->Team(team)->AddEnergy(energy, useIncomeMultiplier);
2046 }
2047
2048
AddHarvestedMetal(float metal)2049 bool CUnit::AddHarvestedMetal(float metal)
2050 {
2051 if (unitDef->harvestStorage <= 0.0f) {
2052 AddMetal(metal, false);
2053 return true;
2054 }
2055
2056 if (harvestStorage >= unitDef->harvestStorage)
2057 return false;
2058
2059 //FIXME what do with exceeding metal?
2060 harvestStorage = std::min(harvestStorage + metal, unitDef->harvestStorage);
2061 return true;
2062 }
2063
2064
2065
Activate()2066 void CUnit::Activate()
2067 {
2068 if (activated)
2069 return;
2070
2071 activated = true;
2072 script->Activate();
2073
2074 if (unitDef->targfac) {
2075 radarHandler->DecreaseAllyTeamRadarErrorSize(allyteam);
2076 }
2077
2078 radarHandler->MoveUnit(this);
2079
2080 if (losStatus[gu->myAllyTeam] & LOS_INLOS) {
2081 Channels::General->PlayRandomSample(unitDef->sounds.activate, this);
2082 }
2083 }
2084
Deactivate()2085 void CUnit::Deactivate()
2086 {
2087 if (!activated)
2088 return;
2089
2090 activated = false;
2091 script->Deactivate();
2092
2093 if (unitDef->targfac) {
2094 radarHandler->IncreaseAllyTeamRadarErrorSize(allyteam);
2095 }
2096
2097 radarHandler->RemoveUnit(this);
2098
2099 if (losStatus[gu->myAllyTeam] & LOS_INLOS) {
2100 Channels::General->PlayRandomSample(unitDef->sounds.deactivate, this);
2101 }
2102 }
2103
2104
2105
UpdateWind(float x,float z,float strength)2106 void CUnit::UpdateWind(float x, float z, float strength)
2107 {
2108 const float windHeading = ClampRad(GetHeadingFromVectorF(-x, -z) - heading * TAANG2RAD);
2109 const float windStrength = std::min(strength, unitDef->windGenerator);
2110
2111 script->WindChanged(windHeading, windStrength);
2112 }
2113
2114
IncomingMissile(CMissileProjectile * missile)2115 void CUnit::IncomingMissile(CMissileProjectile* missile)
2116 {
2117 if (!unitDef->canDropFlare)
2118 return;
2119
2120 incomingMissiles.push_back(missile);
2121 AddDeathDependence(missile, DEPENDENCE_INCOMING);
2122
2123 if (lastFlareDrop >= (gs->frameNum - unitDef->flareReloadTime * GAME_SPEED))
2124 return;
2125
2126 new CFlareProjectile(pos, speed, this, (int) (gs->frameNum + unitDef->flareDelay * (1 + gs->randFloat()) * 15));
2127 lastFlareDrop = gs->frameNum;
2128 }
2129
2130
2131
TempHoldFire(int cmdID)2132 void CUnit::TempHoldFire(int cmdID)
2133 {
2134 if (weapons.empty())
2135 return;
2136 if (!eventHandler.AllowBuilderHoldFire(this, cmdID))
2137 return;
2138
2139 // block the SlowUpdateWeapons cycle
2140 dontFire = true;
2141
2142 // clear current target (if any)
2143 AttackUnit(NULL, false, false);
2144 }
2145
2146
2147
PostLoad()2148 void CUnit::PostLoad()
2149 {
2150 //HACK:Initializing after load
2151 unitDef = unitDefHandler->GetUnitDefByID(unitDefID); // strange. creg should handle this by itself already, but it doesn't
2152 objectDef = unitDef;
2153 model = unitDef->LoadModel();
2154 localModel = new LocalModel(model);
2155 modelParser->CreateLocalModel(localModel);
2156 blockMap = (unitDef->GetYardMap().empty())? NULL: &unitDef->GetYardMap()[0];
2157
2158 SetMidAndAimPos(model->relMidPos, model->relMidPos, true);
2159 SetRadiusAndHeight(model);
2160 UpdateDirVectors(!upright);
2161 UpdateMidAndAimPos();
2162
2163 // FIXME: how to handle other script types (e.g. Lua) here?
2164 script = CUnitScriptFactory::CreateScript("scripts/" + unitDef->scriptName, this);
2165
2166 // Call initializing script functions
2167 script->Create();
2168 script->SetSFXOccupy(curTerrainType);
2169
2170 if (unitDef->windGenerator > 0.0f) {
2171 wind.AddUnit(this);
2172 }
2173
2174 if (activated) {
2175 script->Activate();
2176 }
2177
2178 (eventBatchHandler->GetUnitCreatedDestroyedBatch()).enqueue(EventBatchHandler::UD(this, isCloaked));
2179
2180 }
2181
2182
StopAttackingAllyTeam(int ally)2183 void CUnit::StopAttackingAllyTeam(int ally)
2184 {
2185 if (lastAttacker != NULL && lastAttacker->allyteam == ally) {
2186 DeleteDeathDependence(lastAttacker, DEPENDENCE_ATTACKER);
2187 lastAttacker = NULL;
2188 }
2189 if (attackTarget != NULL && attackTarget->allyteam == ally)
2190 AttackUnit(NULL, false, false);
2191
2192 commandAI->StopAttackingAllyTeam(ally);
2193 for (std::vector<CWeapon*>::iterator it = weapons.begin(); it != weapons.end(); ++it) {
2194 (*it)->StopAttackingAllyTeam(ally);
2195 }
2196 }
2197
2198
GetNewCloakState(bool stunCheck)2199 bool CUnit::GetNewCloakState(bool stunCheck) {
2200 if (stunCheck) {
2201 if (IsStunned() && isCloaked && scriptCloak <= 3) {
2202 return false;
2203 }
2204
2205 return isCloaked;
2206 }
2207
2208 if (scriptCloak >= 3) {
2209 return true;
2210 }
2211
2212 if (wantCloak || (scriptCloak >= 1)) {
2213 const CUnit* closestEnemy = CGameHelper::GetClosestEnemyUnitNoLosTest(NULL, midPos, decloakDistance, allyteam, unitDef->decloakSpherical, false);
2214 const float cloakCost = (Square(speed.w) > 0.2f)? unitDef->cloakCostMoving: unitDef->cloakCost;
2215
2216 if (decloakDistance > 0.0f && closestEnemy != NULL) {
2217 curCloakTimeout = gs->frameNum + cloakTimeout;
2218 return false;
2219 }
2220
2221 if (isCloaked || (gs->frameNum >= curCloakTimeout)) {
2222 return ((scriptCloak >= 2) || UseEnergy(cloakCost * 0.5f));
2223 }
2224 }
2225
2226 return false;
2227 }
SlowUpdateCloak(bool stunCheck)2228 void CUnit::SlowUpdateCloak(bool stunCheck)
2229 {
2230 const bool oldCloak = isCloaked;
2231 const bool newCloak = GetNewCloakState(stunCheck);
2232
2233 if (oldCloak != newCloak) {
2234 if (newCloak) {
2235 eventHandler.UnitCloaked(this);
2236 } else {
2237 eventHandler.UnitDecloaked(this);
2238 }
2239 }
2240
2241 isCloaked = newCloak;
2242 }
2243
ScriptDecloak(bool updateCloakTimeOut)2244 void CUnit::ScriptDecloak(bool updateCloakTimeOut)
2245 {
2246 if (scriptCloak <= 2) {
2247 if (isCloaked) {
2248 isCloaked = false;
2249 eventHandler.UnitDecloaked(this);
2250 }
2251
2252 if (updateCloakTimeOut) {
2253 curCloakTimeout = gs->frameNum + cloakTimeout;
2254 }
2255 }
2256 }
2257
2258 CR_BIND_DERIVED(CUnit, CSolidObject, )
2259 CR_REG_METADATA(CUnit, (
2260 CR_MEMBER(unitDef),
2261 CR_MEMBER(unitDefID),
2262 CR_MEMBER(featureDefID),
2263
2264 CR_MEMBER(modParams),
2265 CR_MEMBER(modParamsMap),
2266
2267 CR_MEMBER(upright),
2268
2269 CR_MEMBER(deathSpeed),
2270
2271 CR_MEMBER(travel),
2272 CR_MEMBER(travelPeriod),
2273
2274 CR_MEMBER(power),
2275
2276 CR_MEMBER(maxHealth),
2277 CR_MEMBER(paralyzeDamage),
2278 CR_MEMBER(captureProgress),
2279 CR_MEMBER(experience),
2280 CR_MEMBER(limExperience),
2281
2282 CR_MEMBER(neutral),
2283
2284 CR_MEMBER(soloBuilder),
2285 CR_MEMBER(beingBuilt),
2286 CR_MEMBER(lastNanoAdd),
2287 CR_MEMBER(repairAmount),
2288 CR_MEMBER(transporter),
2289 CR_MEMBER(loadingTransportId),
2290 CR_MEMBER(buildProgress),
2291 CR_MEMBER(groundLevelled),
2292 CR_MEMBER(terraformLeft),
2293 CR_MEMBER(realLosRadius),
2294 CR_MEMBER(realAirLosRadius),
2295
2296 CR_MEMBER(losStatus),
2297
2298 CR_MEMBER(inBuildStance),
2299 CR_MEMBER(useHighTrajectory),
2300
2301 CR_MEMBER(dontUseWeapons),
2302 CR_MEMBER(dontFire),
2303
2304 CR_MEMBER(deathScriptFinished),
2305 CR_MEMBER(delayedWreckLevel),
2306
2307 CR_MEMBER(restTime),
2308 CR_MEMBER(outOfMapTime),
2309
2310 CR_MEMBER(weapons),
2311 CR_MEMBER(shieldWeapon),
2312 CR_MEMBER(stockpileWeapon),
2313 CR_MEMBER(reloadSpeed),
2314 CR_MEMBER(maxRange),
2315
2316 CR_MEMBER(haveTarget),
2317 CR_MEMBER(haveManualFireRequest),
2318
2319 CR_MEMBER(lastMuzzleFlameSize),
2320 CR_MEMBER(lastMuzzleFlameDir),
2321
2322 CR_MEMBER(armorType),
2323 CR_MEMBER(category),
2324
2325 CR_MEMBER(quads),
2326 CR_MEMBER(los),
2327
2328 CR_MEMBER(mapSquare),
2329
2330 CR_MEMBER(losRadius),
2331 CR_MEMBER(airLosRadius),
2332 CR_MEMBER(lastLosUpdate),
2333
2334 CR_MEMBER(losHeight),
2335 CR_MEMBER(radarHeight),
2336
2337 CR_MEMBER(radarRadius),
2338 CR_MEMBER(sonarRadius),
2339 CR_MEMBER(jammerRadius),
2340 CR_MEMBER(sonarJamRadius),
2341 CR_MEMBER(seismicRadius),
2342 CR_MEMBER(seismicSignature),
2343 CR_MEMBER(hasRadarCapacity),
2344 CR_MEMBER(radarSquares),
2345 CR_MEMBER(oldRadarPos),
2346 CR_MEMBER(hasRadarPos),
2347 CR_MEMBER(stealth),
2348 CR_MEMBER(sonarStealth),
2349
2350 CR_MEMBER(moveType),
2351 CR_MEMBER(prevMoveType),
2352
2353 // CR_MEMBER(fpsControlPlayer),
2354 CR_MEMBER(commandAI),
2355 CR_MEMBER(group),
2356
2357
2358 //CR_MEMBER(localModel), //
2359 // CR_MEMBER(script),
2360
2361 CR_MEMBER(condUseMetal),
2362 CR_MEMBER(condUseEnergy),
2363 CR_MEMBER(condMakeMetal),
2364 CR_MEMBER(condMakeEnergy),
2365 CR_MEMBER(uncondUseMetal),
2366 CR_MEMBER(uncondUseEnergy),
2367 CR_MEMBER(uncondMakeMetal),
2368 CR_MEMBER(uncondMakeEnergy),
2369
2370 CR_MEMBER(metalUse),
2371 CR_MEMBER(energyUse),
2372 CR_MEMBER(metalMake),
2373 CR_MEMBER(energyMake),
2374
2375 CR_MEMBER(metalUseI),
2376 CR_MEMBER(energyUseI),
2377 CR_MEMBER(metalMakeI),
2378 CR_MEMBER(energyMakeI),
2379 CR_MEMBER(metalUseold),
2380 CR_MEMBER(energyUseold),
2381 CR_MEMBER(metalMakeold),
2382 CR_MEMBER(energyMakeold),
2383 CR_MEMBER(energyTickMake),
2384
2385 CR_MEMBER(metalExtract),
2386
2387 CR_MEMBER(metalCost),
2388 CR_MEMBER(energyCost),
2389 CR_MEMBER(buildTime),
2390
2391 CR_MEMBER(metalStorage),
2392 CR_MEMBER(energyStorage),
2393 CR_MEMBER(harvestStorage),
2394
2395 CR_MEMBER(lastAttacker),
2396 CR_MEMBER(lastAttackedPiece),
2397 CR_MEMBER(lastAttackedPieceFrame),
2398 CR_MEMBER(lastAttackFrame),
2399 CR_MEMBER(lastFireWeapon),
2400 CR_MEMBER(recentDamage),
2401
2402 CR_MEMBER(attackTarget),
2403 CR_MEMBER(attackPos),
2404
2405 CR_MEMBER(userAttackGround),
2406
2407 CR_MEMBER(fireState),
2408 CR_MEMBER(moveState),
2409
2410 CR_MEMBER(activated),
2411
2412 CR_MEMBER(isDead),
2413 CR_MEMBER(fallSpeed),
2414
2415 CR_MEMBER(flankingBonusMode),
2416 CR_MEMBER(flankingBonusDir),
2417 CR_MEMBER(flankingBonusMobility),
2418 CR_MEMBER(flankingBonusMobilityAdd),
2419 CR_MEMBER(flankingBonusAvgDamage),
2420 CR_MEMBER(flankingBonusDifDamage),
2421
2422 CR_MEMBER(armoredState),
2423 CR_MEMBER(armoredMultiple),
2424 CR_MEMBER(curArmorMultiple),
2425
2426 CR_MEMBER(posErrorVector),
2427 CR_MEMBER(posErrorDelta),
2428 CR_MEMBER(nextPosErrorUpdate),
2429
2430 CR_MEMBER(wantCloak),
2431 CR_MEMBER(scriptCloak),
2432 CR_MEMBER(cloakTimeout),
2433 CR_MEMBER(curCloakTimeout),
2434 CR_MEMBER(isCloaked),
2435 CR_MEMBER(decloakDistance),
2436
2437 CR_MEMBER(lastTerrainType),
2438 CR_MEMBER(curTerrainType),
2439
2440 CR_MEMBER(selfDCountdown),
2441
2442 CR_IGNORED(myTrack),
2443 CR_IGNORED(myIcon),
2444
2445 CR_MEMBER(incomingMissiles),
2446 CR_MEMBER(lastFlareDrop),
2447
2448 CR_MEMBER(currentFuel),
2449
2450 CR_MEMBER(alphaThreshold),
2451 CR_MEMBER(cegDamage),
2452
2453 CR_MEMBER_UN(noDraw),
2454 CR_MEMBER_UN(noMinimap),
2455 CR_MEMBER_UN(leaveTracks),
2456
2457 CR_MEMBER_UN(isSelected),
2458 CR_MEMBER_UN(isIcon),
2459 CR_MEMBER(iconRadius),
2460
2461 CR_MEMBER_UN(lodCount),
2462 CR_MEMBER_UN(currentLOD),
2463 CR_MEMBER_UN(lastDrawFrame),
2464 CR_MEMBER(lastUnitUpdate),
2465
2466 CR_MEMBER_UN(tooltip),
2467
2468 CR_MEMBER(stunned),
2469
2470 // CR_MEMBER(expMultiplier),
2471 // CR_MEMBER(expPowerScale),
2472 // CR_MEMBER(expHealthScale),
2473 // CR_MEMBER(expReloadScale),
2474 // CR_MEMBER(expGrade),
2475
2476 // CR_MEMBER(empDecline),
2477 // CR_MEMBER(spawnFeature),
2478
2479 // CR_MEMBER(model),
2480
2481 CR_POSTLOAD(PostLoad)
2482 ))
2483