1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 
4 #include "Factory.h"
5 #include "Game/GameHelper.h"
6 #include "Game/WaitCommandsAI.h"
7 #include "Map/Ground.h"
8 #include "Map/ReadMap.h"
9 #include "Sim/Misc/GroundBlockingObjectMap.h"
10 #include "Sim/Misc/QuadField.h"
11 #include "Sim/Misc/TeamHandler.h"
12 #include "Sim/Projectiles/ProjectileHandler.h"
13 #include "Sim/Units/Scripts/UnitScript.h"
14 #include "Sim/Units/CommandAI/CommandAI.h"
15 #include "Sim/Units/CommandAI/FactoryCAI.h"
16 #include "Sim/Units/CommandAI/MobileCAI.h"
17 #include "Sim/Units/UnitHandler.h"
18 #include "Sim/Units/UnitLoader.h"
19 #include "Sim/Units/UnitDefHandler.h"
20 #include "System/EventHandler.h"
21 #include "System/Matrix44f.h"
22 #include "System/myMath.h"
23 #include "System/Sound/ISoundChannels.h"
24 #include "System/Sync/SyncTracer.h"
25 
26 #include "Game/GlobalUnsynced.h"
27 
28 CR_BIND_DERIVED(CFactory, CBuilding, )
29 
30 CR_REG_METADATA(CFactory, (
31 	CR_MEMBER(buildSpeed),
32 	CR_MEMBER(opening),
33 	CR_MEMBER(curBuild),
34 	CR_MEMBER(nextBuildUnitDefID),
35 	CR_MEMBER(lastBuildUpdateFrame),
36 	CR_MEMBER(curBuildDef),
37 	CR_MEMBER(curBuild),
38 	//CR_MEMBER(finishedBuildFunc), FIXME is pointer
39 	CR_MEMBER(finishedBuildCommand),
40 	CR_MEMBER(nanoPieceCache),
41 	CR_POSTLOAD(PostLoad)
42 ))
43 
44 //////////////////////////////////////////////////////////////////////
45 // Construction/Destruction
46 //////////////////////////////////////////////////////////////////////
47 
CFactory()48 CFactory::CFactory():
49 	buildSpeed(100.0f),
50 	opening(false),
51 	curBuildDef(NULL),
52 	curBuild(NULL),
53 	nextBuildUnitDefID(-1),
54 	lastBuildUpdateFrame(-1),
55 	finishedBuildFunc(NULL)
56 {
57 }
58 
59 
60 
PostLoad()61 void CFactory::PostLoad()
62 {
63 	curBuildDef = unitDefHandler->GetUnitDefByID(nextBuildUnitDefID);
64 
65 	if (opening) {
66 		script->Activate();
67 	}
68 	if (curBuild) {
69 		script->StartBuilding();
70 	}
71 }
72 
KillUnit(CUnit * attacker,bool selfDestruct,bool reclaimed,bool showDeathSequence)73 void CFactory::KillUnit(CUnit* attacker, bool selfDestruct, bool reclaimed, bool showDeathSequence)
74 {
75 	if (curBuild != NULL) {
76 		curBuild->KillUnit(NULL, false, true);
77 		curBuild = NULL;
78 	}
79 
80 	CUnit::KillUnit(attacker, selfDestruct, reclaimed, showDeathSequence);
81 }
82 
PreInit(const UnitLoadParams & params)83 void CFactory::PreInit(const UnitLoadParams& params)
84 {
85 	unitDef = params.unitDef;
86 	buildSpeed = unitDef->buildSpeed / TEAM_SLOWUPDATE_RATE;
87 
88 	CBuilding::PreInit(params);
89 }
90 
91 
92 
CalcBuildPos(int buildPiece)93 float3 CFactory::CalcBuildPos(int buildPiece)
94 {
95 	const float3 relBuildPos = script->GetPiecePos((buildPiece < 0)? script->QueryBuildInfo() : buildPiece);
96 	const float3 absBuildPos = this->GetObjectSpacePos(relBuildPos);
97 	return absBuildPos;
98 }
99 
100 
101 
Update()102 void CFactory::Update()
103 {
104 	nanoPieceCache.Update();
105 
106 	if (beingBuilt) {
107 		// factory is under construction, cannot build anything yet
108 		CUnit::Update();
109 
110 		#if 1
111 		// this can happen if we started being reclaimed *while* building a
112 		// unit, in which case our buildee can either be allowed to finish
113 		// construction (by assisting builders) or has to be killed --> the
114 		// latter is easier
115 		if (curBuild != NULL) {
116 			StopBuild();
117 		}
118 		#endif
119 
120 		return;
121 	}
122 
123 
124 	if (curBuildDef != NULL) {
125 		if (!opening && !IsStunned()) {
126 			if (groundBlockingObjectMap->CanOpenYard(this)) {
127 				script->Activate();
128 				groundBlockingObjectMap->OpenBlockingYard(this);
129 				opening = true;
130 
131 				// make sure the idle-check does not immediately trigger
132 				// (scripts have 7 seconds to set inBuildStance to true)
133 				lastBuildUpdateFrame = gs->frameNum;
134 			}
135 		}
136 
137 		if (opening && inBuildStance && !IsStunned()) {
138 			StartBuild(curBuildDef);
139 		}
140 	}
141 
142 	if (curBuild != NULL) {
143 		UpdateBuild(curBuild);
144 		FinishBuild(curBuild);
145 	}
146 
147 	const bool wantClose = (!IsStunned() && opening && (gs->frameNum >= (lastBuildUpdateFrame + GAME_SPEED * 7)));
148 	const bool closeYard = (wantClose && curBuild == NULL && groundBlockingObjectMap->CanCloseYard(this));
149 
150 	if (closeYard) {
151 		// close the factory after inactivity
152 		groundBlockingObjectMap->CloseBlockingYard(this);
153 		opening = false;
154 		script->Deactivate();
155 	}
156 
157 	CBuilding::Update();
158 }
159 
160 /*
161 void CFactory::SlowUpdate(void)
162 {
163 	// this (ancient) code was intended to keep vicinity around factories clear
164 	// so units could exit more easily among crowds of assisting builders, etc.
165 	// it is unneeded now that units can flow / push through a non-moving crowd
166 	// (so we no longer have to override CBuilding::SlowUpdate either)
167 	if (transporter == NULL) {
168 		CGameHelper::BuggerOff(pos - float3(0.01f, 0, 0.02f), radius, true, true, team, NULL);
169 	}
170 
171 	CBuilding::SlowUpdate();
172 }
173 */
174 
175 
176 
StartBuild(const UnitDef * buildeeDef)177 void CFactory::StartBuild(const UnitDef* buildeeDef) {
178 	const float3& buildPos = CalcBuildPos();
179 	const bool blocked = groundBlockingObjectMap->GroundBlocked(buildPos, this);
180 
181 	// wait until buildPos is no longer blocked (eg. by a previous buildee)
182 	//
183 	// it might rarely be the case that a unit got stuck inside the factory
184 	// or died right after completion and left some wreckage, but that is up
185 	// to players to fix (we no longer broadcast BuggerOff directives, since
186 	// those are indiscriminate and ineffective)
187 	if (blocked)
188 		return;
189 
190 	UnitLoadParams buildeeParams = {buildeeDef, this, buildPos, ZeroVector, -1, team, buildFacing, true, false};
191 	CUnit* buildee = unitLoader->LoadUnit(buildeeParams);
192 
193 	if (!unitDef->canBeAssisted) {
194 		buildee->soloBuilder = this;
195 		buildee->AddDeathDependence(this, DEPENDENCE_BUILDER);
196 	}
197 
198 	AddDeathDependence(buildee, DEPENDENCE_BUILD);
199 	script->StartBuilding();
200 
201 	// set curBuildDef to NULL to indicate construction
202 	// has started, otherwise we would keep being called
203 	curBuild = buildee;
204 	curBuildDef = NULL;
205 
206 	if (losStatus[gu->myAllyTeam] & LOS_INLOS) {
207 		Channels::General->PlayRandomSample(unitDef->sounds.build, buildPos);
208 	}
209 }
210 
UpdateBuild(CUnit * buildee)211 void CFactory::UpdateBuild(CUnit* buildee) {
212 	if (IsStunned())
213 		return;
214 
215 	// factory not under construction and
216 	// nanolathing unit: continue building
217 	lastBuildUpdateFrame = gs->frameNum;
218 
219 	// buildPiece is the rotating platform
220 	const int buildPiece = script->QueryBuildInfo();
221 	const float3& buildPos = CalcBuildPos(buildPiece);
222 	const CMatrix44f& buildPieceMat = script->GetPieceMatrix(buildPiece);
223 	const int buildPieceHeading = GetHeadingFromVector(buildPieceMat[2], buildPieceMat[10]); //! x.z, z.z
224 
225 	float3 buildeePos = buildPos;
226 
227 	// rotate unit nanoframe with platform
228 	buildee->heading = (-buildPieceHeading + GetHeadingFromFacing(buildFacing)) & (SPRING_CIRCLE_DIVS - 1);
229 
230 	if (buildee->unitDef->floatOnWater && (buildeePos.y <= 0.0f))
231 		buildeePos.y = -buildee->unitDef->waterline;
232 
233 	buildee->Move(buildeePos, false);
234 	buildee->UpdateDirVectors(false);
235 	buildee->UpdateMidAndAimPos();
236 
237 	const CCommandQueue& queue = commandAI->commandQue;
238 
239 	if (!queue.empty() && (queue.front().GetID() == CMD_WAIT)) {
240 		buildee->AddBuildPower(this, 0.0f);
241 	} else {
242 		if (buildee->AddBuildPower(this, buildSpeed)) {
243 			CreateNanoParticle();
244 		}
245 	}
246 }
247 
FinishBuild(CUnit * buildee)248 void CFactory::FinishBuild(CUnit* buildee) {
249 	if (buildee->beingBuilt) { return; }
250 	if (unitDef->fullHealthFactory && buildee->health < buildee->maxHealth) { return; }
251 
252 	{
253 		if (group && buildee->group == 0) {
254 			buildee->SetGroup(group, true);
255 		}
256 	}
257 
258 	const CCommandAI* bcai = buildee->commandAI;
259 	// if not idle, the buildee already has user orders
260 	const bool buildeeIdle = (bcai->commandQue.empty());
261 	const bool buildeeMobile = (dynamic_cast<const CMobileCAI*>(bcai) != NULL);
262 
263 	if (buildeeIdle || buildeeMobile) {
264 		AssignBuildeeOrders(buildee);
265 		waitCommandsAI.AddLocalUnit(buildee, this);
266 	}
267 
268 	// inform our commandAI
269 	finishedBuildFunc(this, finishedBuildCommand);
270 	finishedBuildFunc = NULL;
271 
272 	eventHandler.UnitFromFactory(buildee, this, !buildeeIdle);
273 	StopBuild();
274 }
275 
276 
277 
QueueBuild(const UnitDef * buildeeDef,const Command & buildCmd,FinishBuildCallBackFunc buildFunc)278 unsigned int CFactory::QueueBuild(const UnitDef* buildeeDef, const Command& buildCmd, FinishBuildCallBackFunc buildFunc)
279 {
280 	assert(!beingBuilt);
281 	assert(buildeeDef != NULL);
282 
283 	if (finishedBuildFunc != NULL)
284 		return FACTORY_KEEP_BUILD_ORDER;
285 	if (curBuild != NULL)
286 		return FACTORY_KEEP_BUILD_ORDER;
287 	if (unitHandler->unitsByDefs[team][buildeeDef->id].size() >= buildeeDef->maxThisUnit)
288 		return FACTORY_SKIP_BUILD_ORDER;
289 	if (teamHandler->Team(team)->AtUnitLimit())
290 		return FACTORY_KEEP_BUILD_ORDER;
291 	if (!eventHandler.AllowUnitCreation(buildeeDef, this, NULL))
292 		return FACTORY_SKIP_BUILD_ORDER;
293 
294 	finishedBuildFunc = buildFunc;
295 	finishedBuildCommand = buildCmd;
296 	curBuildDef = buildeeDef;
297 	nextBuildUnitDefID = buildeeDef->id;
298 
299 	// signal that the build-order was accepted (queued)
300 	return FACTORY_NEXT_BUILD_ORDER;
301 }
302 
StopBuild()303 void CFactory::StopBuild()
304 {
305 	// cancel a build-in-progress
306 	script->StopBuilding();
307 
308 	if (curBuild) {
309 		if (curBuild->beingBuilt) {
310 			AddMetal(curBuild->metalCost * curBuild->buildProgress, false);
311 			curBuild->KillUnit(NULL, false, true);
312 		}
313 		DeleteDeathDependence(curBuild, DEPENDENCE_BUILD);
314 	}
315 
316 	curBuild = NULL;
317 	curBuildDef = NULL;
318 	finishedBuildFunc = NULL;
319 }
320 
DependentDied(CObject * o)321 void CFactory::DependentDied(CObject* o)
322 {
323 	if (o == curBuild) {
324 		curBuild = NULL;
325 		StopBuild();
326 	}
327 
328 	CUnit::DependentDied(o);
329 }
330 
331 
332 
SendToEmptySpot(CUnit * unit)333 void CFactory::SendToEmptySpot(CUnit* unit)
334 {
335 	const float searchRadius = radius * 4.0f + unit->radius * 4.0f;
336 	const int numSteps = 36;
337 
338 	const float3 exitPos = pos + frontdir*(radius + unit->radius);
339 	float3 foundPos = pos + frontdir * searchRadius;
340 	const float3 tempPos = foundPos;
341 
342 	for (int x = 0; x < numSteps; ++x) {
343 		const float a = searchRadius * math::cos(x * PI / (numSteps * 0.5f));
344 		const float b = searchRadius * math::sin(x * PI / (numSteps * 0.5f));
345 
346 		float3 testPos = pos + frontdir * a + rightdir * b;
347 
348 		if (!testPos.IsInBounds())
349 			continue;
350 		// don't pick spots behind the factory (because
351 		// units will want to path through it when open
352 		// which slows down production)
353 		if ((testPos - pos).dot(frontdir) < 0.0f)
354 			continue;
355 
356 		testPos.y = CGround::GetHeightAboveWater(testPos.x, testPos.z);
357 
358 		if (quadField->GetSolidsExact(testPos, unit->radius * 1.5f, 0xFFFFFFFF, CSolidObject::CSTATE_BIT_SOLIDOBJECTS).empty()) {
359 			foundPos = testPos; break;
360 		}
361 	}
362 
363 	if (foundPos == tempPos) {
364 		// no empty spot found, pick one randomly so units do not pile up even more
365 		// also make sure not to loop forever if we happen to be facing a map border
366 		foundPos.y = 0.0f;
367 
368 		do {
369 			const float x = ((gs->randInt() * 1.0f) / RANDINT_MAX) * numSteps;
370 			const float a = searchRadius * math::cos(x * PI / (numSteps * 0.5f));
371 			const float b = searchRadius * math::sin(x * PI / (numSteps * 0.5f));
372 
373 			foundPos.x = pos.x + frontdir.x * a + rightdir.x * b;
374 			foundPos.z = pos.z + frontdir.z * a + rightdir.z * b;
375 			foundPos.y += 1.0f;
376 		} while ((!foundPos.IsInBounds() || (foundPos - pos).dot(frontdir) < 0.0f) && (foundPos.y < 100.0f));
377 
378 		foundPos.y = CGround::GetHeightAboveWater(foundPos.x, foundPos.z);
379 	}
380 
381 	// first queue a temporary waypoint outside the factory
382 	// (otherwise units will try to turn before exiting when
383 	// foundPos lies behind exit and cause jams / get stuck)
384 	// we assume this temporary point is not itself blocked
385 	//
386 	// NOTE:
387 	//   MobileCAI::AutoGenerateTarget inserts a _third_
388 	//   command when |foundPos - tempPos| >= 100 elmos,
389 	//   because MobileCAI::FinishCommand only updates
390 	//   lastUserGoal for non-internal orders --> the
391 	//   final order given here should not be internal
392 	//   (and should also be more than CMD_CANCEL_DIST
393 	//   elmos distant from foundPos)
394 	//
395 	if (!unit->unitDef->canfly && exitPos.IsInBounds()) {
396 		Command c0(CMD_MOVE, SHIFT_KEY, exitPos);
397 		unit->commandAI->GiveCommand(c0);
398 	}
399 	Command c1(CMD_MOVE, SHIFT_KEY, foundPos);
400 	unit->commandAI->GiveCommand(c1);
401 }
402 
AssignBuildeeOrders(CUnit * unit)403 void CFactory::AssignBuildeeOrders(CUnit* unit) {
404 	CCommandAI* unitCAI = unit->commandAI;
405 	CCommandQueue& unitCmdQue = unitCAI->commandQue;
406 
407 	const CFactoryCAI* factoryCAI = static_cast<CFactoryCAI*>(commandAI);
408 	const CCommandQueue& factoryCmdQue = factoryCAI->newUnitCommands;
409 
410 	if (factoryCmdQue.empty() && unitCmdQue.empty()) {
411 		SendToEmptySpot(unit);
412 		return;
413 	}
414 
415 	Command c(CMD_MOVE);
416 
417 	if (!unit->unitDef->canfly) {
418 		// HACK: when a factory has a rallypoint set far enough away
419 		// to trigger the non-admissable path estimators, we want to
420 		// avoid units getting stuck inside by issuing them an extra
421 		// move-order. However, this order can *itself* cause the PF
422 		// system to consider the path blocked if the extra waypoint
423 		// falls within the factory's confines, so use a wide berth.
424 		const float xs = unitDef->xsize * SQUARE_SIZE * 0.5f;
425 		const float zs = unitDef->zsize * SQUARE_SIZE * 0.5f;
426 
427 		float tmpDst = 2.0f;
428 		float3 tmpPos = unit->pos + (frontdir * this->radius * tmpDst);
429 
430 		if (buildFacing == FACING_NORTH || buildFacing == FACING_SOUTH) {
431 			while ((tmpPos.z >= unit->pos.z - zs && tmpPos.z <= unit->pos.z + zs)) {
432 				tmpDst += 0.5f;
433 				tmpPos = unit->pos + (frontdir * this->radius * tmpDst);
434 			}
435 		} else {
436 			while ((tmpPos.x >= unit->pos.x - xs && tmpPos.x <= unit->pos.x + xs)) {
437 				tmpDst += 0.5f;
438 				tmpPos = unit->pos + (frontdir * this->radius * tmpDst);
439 			}
440 		}
441 
442 		c.PushPos(tmpPos.cClampInBounds());
443 	} else {
444 		// dummy rallypoint for aircraft
445 		c.PushPos(unit->pos);
446 	}
447 
448 	if (unitCmdQue.empty()) {
449 		unitCAI->GiveCommand(c);
450 
451 		// copy factory orders for new unit
452 		for (CCommandQueue::const_iterator ci = factoryCmdQue.begin(); ci != factoryCmdQue.end(); ++ci) {
453 			c = *ci;
454 			c.options |= SHIFT_KEY;
455 
456 			if (c.GetID() == CMD_MOVE) {
457 				const float3 p1 = c.GetPos(0);
458 				const float3 p2 = float3(p1.x + gs->randFloat() * TWOPI, p1.y, p1.z + gs->randFloat() * TWOPI);
459 				// apply a small amount of random jitter to move commands
460 				// such that new units do not all share the same goal-pos
461 				// and start forming a "trail" back to the factory exit
462 				c.SetPos(0, p2);
463 			}
464 
465 			unitCAI->GiveCommand(c);
466 		}
467 	} else {
468 		unitCmdQue.push_front(c);
469 	}
470 }
471 
472 
473 
ChangeTeam(int newTeam,ChangeType type)474 bool CFactory::ChangeTeam(int newTeam, ChangeType type)
475 {
476 	StopBuild();
477 	return (CBuilding::ChangeTeam(newTeam, type));
478 }
479 
480 
CreateNanoParticle(bool highPriority)481 void CFactory::CreateNanoParticle(bool highPriority)
482 {
483 	const int modelNanoPiece = nanoPieceCache.GetNanoPiece(script);
484 
485 	if (localModel == NULL || !localModel->HasPiece(modelNanoPiece))
486 		return;
487 
488 	const float3 relNanoFirePos = localModel->GetRawPiecePos(modelNanoPiece);
489 	const float3 nanoPos = this->GetObjectSpacePos(relNanoFirePos);
490 
491 	// unsynced
492 	projectileHandler->AddNanoParticle(nanoPos, curBuild->midPos, unitDef, team, highPriority);
493 }
494