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