1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 #include "TransportUnit.h"
4 #include "Game/GameHelper.h"
5 #include "Game/SelectedUnitsHandler.h"
6 #include "Map/Ground.h"
7 #include "Sim/MoveTypes/MoveDefHandler.h"
8 #include "Sim/MoveTypes/HoverAirMoveType.h"
9 #include "Sim/MoveTypes/GroundMoveType.h"
10 #include "Sim/Units/Scripts/CobInstance.h"
11 #include "Sim/Units/CommandAI/CommandAI.h"
12 #include "Sim/Units/BuildInfo.h"
13 #include "Sim/Units/UnitDef.h"
14 #include "Sim/Units/UnitLoader.h"
15 #include "Sim/Units/UnitTypes/Building.h"
16 #include "Sim/Misc/DamageArray.h"
17 #include "Sim/Misc/LosHandler.h"
18 #include "Sim/Misc/ModInfo.h"
19 #include "Sim/Misc/TeamHandler.h"
20 #include "Sim/Misc/QuadField.h"
21 #include "System/EventHandler.h"
22 #include "System/myMath.h"
23 #include "System/creg/STL_List.h"
24 
25 CR_BIND_DERIVED(CTransportUnit, CUnit, )
26 
27 CR_REG_METADATA(CTransportUnit, (
28 	CR_MEMBER(transportedUnits),
29 	CR_MEMBER(transportCapacityUsed),
30 	CR_MEMBER(transportMassUsed),
31 	CR_RESERVED(16),
32 	CR_POSTLOAD(PostLoad)
33 ))
34 
35 CR_BIND(CTransportUnit::TransportedUnit,)
36 
37 CR_REG_METADATA_SUB(CTransportUnit,TransportedUnit,(
38 	CR_MEMBER(unit),
39 	CR_MEMBER(piece),
40 	CR_MEMBER(size),
41 	CR_MEMBER(mass),
42 	CR_RESERVED(8)
43 ))
44 
45 
46 
Update()47 void CTransportUnit::Update()
48 {
49 	CUnit::Update();
50 
51 	if (isDead)
52 		return;
53 
54 	for (std::list<TransportedUnit>::iterator ti = transportedUnits.begin(); ti != transportedUnits.end(); ++ti) {
55 		CUnit* transportee = ti->unit;
56 
57 		transportee->mapSquare = mapSquare;
58 
59 		float3 relPiecePos = ZeroVector;
60 		float3 absPiecePos = pos;
61 
62 		if (ti->piece >= 0) {
63 			relPiecePos = script->GetPiecePos(ti->piece);
64 			absPiecePos = this->GetObjectSpacePos(relPiecePos);
65 		}
66 
67 		if (unitDef->holdSteady) {
68 			// slave transportee orientation to piece
69 			if (ti->piece >= 0) {
70 				const CMatrix44f& transMat = GetTransformMatrix(true);
71 				const CMatrix44f& pieceMat = script->GetPieceMatrix(ti->piece);
72 				const CMatrix44f  slaveMat = transMat * pieceMat;
73 
74 				transportee->SetDirVectors(slaveMat);
75 			}
76 		} else {
77 			// slave transportee orientation to body
78 			transportee->heading  = heading;
79 			transportee->updir    = updir;
80 			transportee->frontdir = frontdir;
81 			transportee->rightdir = rightdir;
82 		}
83 
84 		transportee->Move(absPiecePos, false);
85 		transportee->UpdateMidAndAimPos();
86 		transportee->SetHeadingFromDirection();
87 
88 		// see ::AttachUnit
89 		if (transportee->IsStunned()) {
90 			quadField->MovedUnit(transportee);
91 		}
92 	}
93 }
94 
95 
DependentDied(CObject * o)96 void CTransportUnit::DependentDied(CObject* o)
97 {
98 	// a unit died while we were carrying it
99 	std::list<TransportedUnit>::iterator ti;
100 
101 	for (ti = transportedUnits.begin(); ti != transportedUnits.end(); ++ti) {
102 		if (ti->unit != o)
103 			continue;
104 
105 		transportCapacityUsed -= ti->size;
106 		transportMassUsed -= ti->mass;
107 		mass = Clamp(mass - ti->mass, CSolidObject::MINIMUM_MASS, CSolidObject::MAXIMUM_MASS);
108 
109 		transportedUnits.erase(ti);
110 		break;
111 	}
112 
113 	CUnit::DependentDied(o);
114 }
115 
116 
KillUnit(CUnit * attacker,bool selfDestruct,bool reclaimed,bool)117 void CTransportUnit::KillUnit(CUnit* attacker, bool selfDestruct, bool reclaimed, bool)
118 {
119 	if (!isDead) {
120 		// guard against recursive invocation via
121 		//     transportee->KillUnit
122 		//     helper->Explosion
123 		//     helper->DoExplosionDamage
124 		//     unit->DoDamage
125 		//     unit->KillUnit
126 		// in the case that unit == this
127 		isDead = true;
128 
129 		// ::KillUnit might be called multiple times while !deathScriptFinished,
130 		// but it makes no sense to kill/detach our transportees more than once
131 		std::list<TransportedUnit>::iterator ti;
132 
133 		for (ti = transportedUnits.begin(); ti != transportedUnits.end(); ++ti) {
134 			CUnit* transportee = ti->unit;
135 			assert(transportee != this);
136 
137 			if (transportee->isDead)
138 				continue;
139 
140 			transportee->SetTransporter(NULL);
141 			transportee->DeleteDeathDependence(this, DEPENDENCE_TRANSPORTER);
142 			transportee->UpdateVoidState(false);
143 
144 			if (!unitDef->releaseHeld) {
145 				if (!selfDestruct) {
146 					// we don't want transportees to leave a corpse
147 					transportee->DoDamage(DamageArray(1e6f), ZeroVector, NULL, -DAMAGE_EXTSOURCE_KILLED, -1);
148 				}
149 
150 				transportee->KillUnit(attacker, selfDestruct, reclaimed);
151 			} else {
152 				// NOTE: game's responsibility to deal with edge-cases now
153 				transportee->Move(transportee->pos.cClampInBounds(), false);
154 
155 				// if this transporter uses the piece-underneath-ground
156 				// method to "hide" transportees, place transportee near
157 				// the transporter's place of death
158 				if (transportee->pos.y < CGround::GetHeightReal(transportee->pos.x, transportee->pos.z)) {
159 					const float r1 = transportee->radius + radius;
160 					const float r2 = r1 * std::max(unitDef->unloadSpread, 1.0f);
161 
162 					// try to unload in a presently unoccupied spot
163 					// (if no such spot, unload on transporter wreck)
164 					for (int i = 0; i < 10; ++i) {
165 						float3 pos = transportee->pos;
166 						pos.x += (gs->randFloat() * 2.0f * r2 - r2);
167 						pos.z += (gs->randFloat() * 2.0f * r2 - r2);
168 						pos.y = CGround::GetHeightReal(pos.x, pos.z);
169 
170 						if (!pos.IsInBounds())
171 							continue;
172 
173 						if (quadField->GetSolidsExact(pos, transportee->radius + 2.0f, 0xFFFFFFFF, CSolidObject::CSTATE_BIT_SOLIDOBJECTS).empty()) {
174 							transportee->Move(pos, false);
175 							break;
176 						}
177 					}
178 				} else {
179 					if (transportee->unitDef->IsGroundUnit()) {
180 						transportee->SetPhysicalStateBit(CSolidObject::PSTATE_BIT_FLYING);
181 						transportee->SetPhysicalStateBit(CSolidObject::PSTATE_BIT_SKIDDING);
182 					}
183 				}
184 
185 				transportee->moveType->SlowUpdate();
186 				transportee->moveType->LeaveTransport();
187 
188 				// issue a move order so that units dropped from flying
189 				// transports won't try to return to their pick-up spot
190 				if (unitDef->canfly && transportee->unitDef->canmove) {
191 					transportee->commandAI->GiveCommand(Command(CMD_MOVE, transportee->pos));
192 				}
193 
194 				transportee->SetStunned(transportee->paralyzeDamage > (modInfo.paralyzeOnMaxHealth? transportee->maxHealth: transportee->health));
195 				transportee->SetVelocityAndSpeed(speed * (0.5f + 0.5f * gs->randFloat()));
196 
197 				eventHandler.UnitUnloaded(transportee, this);
198 			}
199 		}
200 
201 		transportedUnits.clear();
202 
203 		// make sure CUnit::KillUnit does not return early
204 		isDead = false;
205 	}
206 
207 	CUnit::KillUnit(attacker, selfDestruct, reclaimed);
208 }
209 
210 
211 
CanTransport(const CUnit * unit) const212 bool CTransportUnit::CanTransport(const CUnit* unit) const
213 {
214 	if (unit->GetTransporter() != NULL)
215 		return false;
216 	if (!unit->unitDef->transportByEnemy && !teamHandler->AlliedTeams(unit->team, team))
217 		return false;
218 	if (transportCapacityUsed >= unitDef->transportCapacity)
219 		return false;
220 	if (unit->unitDef->cantBeTransported)
221 		return false;
222 
223 	// don't transport cloaked enemies
224 	if (unit->isCloaked && !teamHandler->AlliedTeams(unit->team, team))
225 		return false;
226 
227 	if (unit->xsize > (unitDef->transportSize * SPRING_FOOTPRINT_SCALE))
228 		return false;
229 	if (unit->xsize < (unitDef->minTransportSize * SPRING_FOOTPRINT_SCALE))
230 		return false;
231 
232 	if (unit->mass >= CSolidObject::DEFAULT_MASS || unit->beingBuilt)
233 		return false;
234 	if (unit->mass < unitDef->minTransportMass)
235 		return false;
236 	if ((unit->mass + transportMassUsed) > unitDef->transportMass)
237 		return false;
238 
239 	if (!CanLoadUnloadAtPos(unit->pos, unit))
240 		return false;
241 
242 	// check if <unit> is already (in)directly transporting <this>
243 	const CTransportUnit* u = this;
244 
245 	while (u != NULL) {
246 		if (u == unit) {
247 			return false;
248 		}
249 		u = u->GetTransporter();
250 	}
251 
252 	return true;
253 }
254 
255 
AttachUnit(CUnit * unit,int piece)256 bool CTransportUnit::AttachUnit(CUnit* unit, int piece)
257 {
258 	assert(unit != this);
259 
260 	if (unit->GetTransporter() == this) {
261 		// assume we are already transporting this unit,
262 		// and just want to move it to a different piece
263 		// with script logic (this means the UnitLoaded
264 		// event is only sent once)
265 		std::list<TransportedUnit>::iterator transporteesIt;
266 
267 		for (transporteesIt = transportedUnits.begin(); transporteesIt != transportedUnits.end(); ++transporteesIt) {
268 			TransportedUnit& tu = *transporteesIt;
269 
270 			if (tu.unit == unit) {
271 				tu.piece = piece;
272 				break;
273 			}
274 		}
275 
276 		unit->UpdateVoidState(piece < 0);
277 		return false;
278 	} else {
279 		// handle transfers from another transport to us
280 		// (can still fail depending on CanTransport())
281 		if (unit->GetTransporter() != NULL) {
282 			unit->GetTransporter()->DetachUnit(unit);
283 		}
284 	}
285 
286 	// covers the case where unit->transporter != NULL
287 	if (!CanTransport(unit)) {
288 		return false;
289 	}
290 
291 	AddDeathDependence(unit, DEPENDENCE_TRANSPORTEE);
292 	unit->AddDeathDependence(this, DEPENDENCE_TRANSPORTER);
293 
294 	unit->SetTransporter(this);
295 	unit->loadingTransportId = -1;
296 	unit->SetStunned(!unitDef->isFirePlatform);
297 	unit->UpdateVoidState(piece < 0);
298 
299 	if (unit->IsStunned()) {
300 		// make sure unit does not fire etc in transport
301 		selectedUnitsHandler.RemoveUnit(unit);
302 	}
303 
304 	unit->UnBlock();
305 	losHandler->FreeInstance(unit->los);
306 	radarHandler->RemoveUnit(unit);
307 
308 	// do not remove unit from QF, otherwise projectiles
309 	// will not be able to connect with (ie. damage) it
310 	//
311 	// for NON-stunned transportees, QF position is kept
312 	// up-to-date by MoveType::SlowUpdate, otherwise by
313 	// ::Update
314 	//
315 	// quadField->RemoveUnit(unit);
316 
317 	unit->los = NULL;
318 
319 	if (dynamic_cast<CBuilding*>(unit) != NULL) {
320 		unitLoader->RestoreGround(unit);
321 	}
322 
323 	if (dynamic_cast<CHoverAirMoveType*>(moveType)) {
324 		unit->moveType->useHeading = false;
325 	}
326 
327 	TransportedUnit tu;
328 		tu.unit = unit;
329 		tu.piece = piece;
330 		tu.size = unit->xsize / SPRING_FOOTPRINT_SCALE;
331 		tu.mass = unit->mass;
332 
333 	transportCapacityUsed += tu.size;
334 	transportMassUsed += tu.mass;
335 	mass = Clamp(mass + tu.mass, CSolidObject::MINIMUM_MASS, CSolidObject::MAXIMUM_MASS);
336 
337 	transportedUnits.push_back(tu);
338 
339 	unit->moveType->StopMoving(true, true);
340 	unit->CalculateTerrainType();
341 	unit->UpdateTerrainType();
342 
343 	eventHandler.UnitLoaded(unit, this);
344 	commandAI->BuggerOff(pos, -1.0f);
345 	return true;
346 }
347 
348 
DetachUnitCore(CUnit * unit)349 bool CTransportUnit::DetachUnitCore(CUnit* unit)
350 {
351 	if (unit->GetTransporter() != this)
352 		return false;
353 
354 	std::list<TransportedUnit>::iterator ti;
355 
356 	for (ti = transportedUnits.begin(); ti != transportedUnits.end(); ++ti) {
357 		if (ti->unit != unit)
358 			continue;
359 
360 		this->DeleteDeathDependence(unit, DEPENDENCE_TRANSPORTEE);
361 		unit->DeleteDeathDependence(this, DEPENDENCE_TRANSPORTER);
362 		unit->SetTransporter(NULL);
363 
364 		if (dynamic_cast<CHoverAirMoveType*>(moveType)) {
365 			unit->moveType->useHeading = true;
366 		}
367 
368 		// de-stun detaching units in case we are not a fire-platform
369 		unit->SetStunned(unit->paralyzeDamage > (modInfo.paralyzeOnMaxHealth? unit->maxHealth: unit->health));
370 
371 		unit->moveType->SlowUpdate();
372 		unit->moveType->LeaveTransport();
373 
374 		if (CBuilding* building = dynamic_cast<CBuilding*>(unit))
375 			building->ForcedMove(building->pos);
376 
377 		transportCapacityUsed -= ti->size;
378 		transportMassUsed -= ti->mass;
379 		mass = Clamp(mass - ti->mass, CSolidObject::MINIMUM_MASS, CSolidObject::MAXIMUM_MASS);
380 
381 		transportedUnits.erase(ti);
382 
383 		unit->UpdateVoidState(false);
384 		unit->CalculateTerrainType();
385 		unit->UpdateTerrainType();
386 
387 		eventHandler.UnitUnloaded(unit, this);
388 		return true;
389 	}
390 
391 	return false;
392 }
393 
394 
DetachUnit(CUnit * unit)395 bool CTransportUnit::DetachUnit(CUnit* unit)
396 {
397 	if (DetachUnitCore(unit)) {
398 		unit->Block();
399 
400 		// erase command queue unless it's a wait command
401 		const CCommandQueue& queue = unit->commandAI->commandQue;
402 
403 		if (queue.empty() || (queue.front().GetID() != CMD_WAIT)) {
404 			unit->commandAI->GiveCommand(Command(CMD_STOP));
405 		}
406 
407 		return true;
408 	}
409 
410 	return false;
411 }
412 
413 
DetachUnitFromAir(CUnit * unit,const float3 & pos)414 bool CTransportUnit::DetachUnitFromAir(CUnit* unit, const float3& pos)
415 {
416 	if (DetachUnitCore(unit)) {
417 		unit->Drop(this->pos, this->frontdir, this);
418 
419 		// add an additional move command for after we land
420 		if (unit->unitDef->canmove) {
421 			unit->commandAI->GiveCommand(Command(CMD_MOVE, pos));
422 		}
423 
424 		return true;
425 	}
426 
427 	return false;
428 }
429 
430 
431 
CanLoadUnloadAtPos(const float3 & wantedPos,const CUnit * unit,float * wantedHeightPtr) const432 bool CTransportUnit::CanLoadUnloadAtPos(const float3& wantedPos, const CUnit* unit, float* wantedHeightPtr) const {
433 	bool canLoadUnload = false;
434 	float wantedHeight = GetTransporteeWantedHeight(wantedPos, unit, &canLoadUnload);
435 
436 	if (wantedHeightPtr != NULL)
437 		*wantedHeightPtr = wantedHeight;
438 
439 	return canLoadUnload;
440 }
441 
442 
443 
GetTransporteeWantedHeight(const float3 & wantedPos,const CUnit * unit,bool * allowedPos) const444 float CTransportUnit::GetTransporteeWantedHeight(const float3& wantedPos, const CUnit* unit, bool* allowedPos) const {
445 	bool isAllowedHeight = true;
446 
447 	float wantedHeight = unit->pos.y;
448 	float clampedHeight = wantedHeight;
449 
450 	const UnitDef* transporteeUnitDef = unit->unitDef;
451 	const MoveDef* transporteeMoveDef = unit->moveDef;
452 
453 	if (unit->GetTransporter() != NULL) {
454 		// if unit is being transported, set <clampedHeight>
455 		// to the altitude at which to UNload the transportee
456 		wantedHeight = CGround::GetHeightReal(wantedPos.x, wantedPos.z);
457 		isAllowedHeight = transporteeUnitDef->CheckTerrainConstraints(transporteeMoveDef, wantedHeight, &clampedHeight);
458 
459 		if (isAllowedHeight) {
460 			if (transporteeMoveDef != NULL) {
461 				// transportee is a mobile ground unit
462 				switch (transporteeMoveDef->speedModClass) {
463 					case MoveDef::Ship: {
464 						wantedHeight = std::max(-transporteeUnitDef->waterline, wantedHeight);
465 						clampedHeight = wantedHeight;
466 					} break;
467 					case MoveDef::Hover: {
468 						wantedHeight = std::max(0.0f, wantedHeight);
469 						clampedHeight = wantedHeight;
470 					} break;
471 					default: {
472 					} break;
473 				}
474 			} else {
475 				// transportee is a building or an airplane
476 				wantedHeight *= (1 - transporteeUnitDef->floatOnWater);
477 				clampedHeight = wantedHeight;
478 			}
479 		}
480 
481 		if (dynamic_cast<const CBuilding*>(unit) != NULL) {
482 			// for transported structures, <wantedPos> must be free/buildable
483 			// (note: TestUnitBuildSquare calls CheckTerrainConstraints again)
484 			BuildInfo bi(transporteeUnitDef, wantedPos, unit->buildFacing);
485 			bi.pos = CGameHelper::Pos2BuildPos(bi, true);
486 			CFeature* f = NULL;
487 
488 			if (isAllowedHeight && (!CGameHelper::TestUnitBuildSquare(bi, f, -1, true) || f != NULL))
489 				isAllowedHeight = false;
490 		}
491 	}
492 
493 
494 	float rawContactHeight = clampedHeight + unit->height;
495 	float modContactHeight = rawContactHeight;
496 
497 	// *we* must be capable of reaching the point-of-contact height
498 	// however this check fails for eg. ships that want to (un)load
499 	// land units on shore --> would require too many special cases
500 	// therefore restrict its use to transport aircraft
501 	if (this->moveDef == NULL) {
502 		isAllowedHeight &= unitDef->CheckTerrainConstraints(NULL, rawContactHeight, &modContactHeight);
503 	}
504 
505 	if (allowedPos != NULL) {
506 		*allowedPos = isAllowedHeight;
507 	}
508 
509 	return modContactHeight;
510 }
511 
GetTransporteeWantedHeading(const CUnit * unit) const512 short CTransportUnit::GetTransporteeWantedHeading(const CUnit* unit) const {
513 	if (unit->GetTransporter() == NULL)
514 		return unit->heading;
515 	if (dynamic_cast<CHoverAirMoveType*>(moveType) == NULL)
516 		return unit->heading;
517 	if (dynamic_cast<const CBuilding*>(unit) == NULL)
518 		return unit->heading;
519 
520 	// transported structures want to face a cardinal direction
521 	return (GetHeadingFromFacing(unit->buildFacing));
522 }
523 
524