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