1 /*
2 	This file is part of Warzone 2100.
3 	Copyright (C) 1999-2004  Eidos Interactive
4 	Copyright (C) 2005-2020  Warzone 2100 Project
5 
6 	Warzone 2100 is free software; you can redistribute it and/or modify
7 	it under the terms of the GNU General Public License as published by
8 	the Free Software Foundation; either version 2 of the License, or
9 	(at your option) any later version.
10 
11 	Warzone 2100 is distributed in the hope that it will be useful,
12 	but WITHOUT ANY WARRANTY; without even the implied warranty of
13 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 	GNU General Public License for more details.
15 
16 	You should have received a copy of the GNU General Public License
17 	along with Warzone 2100; if not, write to the Free Software
18 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 /**
21  * @file droid.c
22  *
23  * Droid method functions.
24  *
25  */
26 #include "lib/framework/frame.h"
27 #include "lib/framework/math_ext.h"
28 #include "lib/framework/geometry.h"
29 #include "lib/framework/strres.h"
30 
31 #include "lib/gamelib/gtime.h"
32 #include "lib/ivis_opengl/piematrix.h"
33 #include "lib/ivis_opengl/ivisdef.h"
34 #include "lib/sound/audio.h"
35 #include "lib/sound/audio_id.h"
36 #include "lib/netplay/netplay.h"
37 
38 #include "objects.h"
39 #include "loop.h"
40 #include "visibility.h"
41 #include "map.h"
42 #include "droid.h"
43 #include "hci.h"
44 #include "power.h"
45 #include "effects.h"
46 #include "feature.h"
47 #include "action.h"
48 #include "order.h"
49 #include "move.h"
50 #include "geometry.h"
51 #include "display.h"
52 #include "console.h"
53 #include "component.h"
54 #include "lighting.h"
55 #include "multiplay.h"
56 #include "warcam.h"
57 #include "display3d.h"
58 #include "group.h"
59 #include "text.h"
60 #include "cmddroid.h"
61 #include "fpath.h"
62 #include "projectile.h"
63 #include "mission.h"
64 #include "levels.h"
65 #include "transporter.h"
66 #include "selection.h"
67 #include "difficulty.h"
68 #include "edit3d.h"
69 #include "scores.h"
70 #include "research.h"
71 #include "combat.h"
72 #include "template.h"
73 #include "qtscript.h"
74 
75 #define DEFAULT_RECOIL_TIME	(GAME_TICKS_PER_SEC/4)
76 #define	DROID_DAMAGE_SPREAD	(16 - rand()%32)
77 #define	DROID_REPAIR_SPREAD	(20 - rand()%40)
78 
79 // store the experience of recently recycled droids
80 static std::priority_queue<int> recycled_experience[MAX_PLAYERS];
81 
82 /** Height the transporter hovers at above the terrain. */
83 #define TRANSPORTER_HOVER_HEIGHT	10
84 
85 // the structure that was last hit
86 DROID	*psLastDroidHit;
87 
88 //determines the best IMD to draw for the droid - A TEMP MEASURE!
89 static void groupConsoleInformOfSelection(UDWORD groupNumber);
90 static void groupConsoleInformOfCreation(UDWORD groupNumber);
91 static void groupConsoleInformOfCentering(UDWORD groupNumber);
92 
93 static void droidUpdateDroidSelfRepair(DROID *psRepairDroid);
94 static UDWORD calcDroidBaseBody(DROID *psDroid);
95 
cancelBuild(DROID * psDroid)96 void cancelBuild(DROID *psDroid)
97 {
98 	if (psDroid->order.type == DORDER_NONE || psDroid->order.type == DORDER_PATROL || psDroid->order.type == DORDER_HOLD || psDroid->order.type == DORDER_SCOUT || psDroid->order.type == DORDER_GUARD)
99 	{
100 		objTrace(psDroid->id, "Droid build action cancelled");
101 		psDroid->order.psObj = nullptr;
102 		psDroid->action = DACTION_NONE;
103 		setDroidActionTarget(psDroid, nullptr, 0);
104 		return;  // Don't cancel orders.
105 	}
106 
107 	if (orderDroidList(psDroid))
108 	{
109 		objTrace(psDroid->id, "Droid build order cancelled - changing to next order");
110 	}
111 	else
112 	{
113 		objTrace(psDroid->id, "Droid build order cancelled");
114 		psDroid->action = DACTION_NONE;
115 		psDroid->order = DroidOrder(DORDER_NONE);
116 		setDroidActionTarget(psDroid, nullptr, 0);
117 
118 		// The droid has no more build orders, so halt in place rather than clumping around the build objective
119 		moveStopDroid(psDroid);
120 
121 		triggerEventDroidIdle(psDroid);
122 	}
123 }
124 
droidBodyUpgrade(DROID * psDroid)125 static void droidBodyUpgrade(DROID *psDroid)
126 {
127 	const int factor = 10000; // use big numbers to scare away rounding errors
128 	int prev = psDroid->originalBody;
129 	psDroid->originalBody = calcDroidBaseBody(psDroid);
130 	int increase = psDroid->originalBody * factor / prev;
131 	psDroid->body = MIN(psDroid->originalBody, (psDroid->body * increase) / factor + 1);
132 	if (isTransporter(psDroid))
133 	{
134 		for (DROID *psCurr = psDroid->psGroup->psList; psCurr != nullptr; psCurr = psCurr->psGrpNext)
135 		{
136 			if (psCurr != psDroid)
137 			{
138 				droidBodyUpgrade(psCurr);
139 			}
140 		}
141 	}
142 }
143 
144 // initialise droid module
droidInit()145 bool droidInit()
146 {
147 	for (int i = 0; i < MAX_PLAYERS; i++)
148 	{
149 		recycled_experience[i] = std::priority_queue <int>(); // clear it
150 	}
151 	psLastDroidHit = nullptr;
152 
153 	return true;
154 }
155 
droidReloadBar(const BASE_OBJECT * psObj,const WEAPON * psWeap,int weapon_slot)156 int droidReloadBar(const BASE_OBJECT *psObj, const WEAPON *psWeap, int weapon_slot)
157 {
158 	WEAPON_STATS *psStats;
159 	bool bSalvo;
160 	int firingStage, interval;
161 
162 	if (psWeap->nStat == 0)	// no weapon
163 	{
164 		return -1;
165 	}
166 	psStats = asWeaponStats + psWeap->nStat;
167 
168 	/* Justifiable only when greater than a one second reload or intra salvo time  */
169 	bSalvo = (psStats->upgrade[psObj->player].numRounds > 1);
170 	if ((bSalvo && psStats->upgrade[psObj->player].reloadTime > GAME_TICKS_PER_SEC)
171 	    || psStats->upgrade[psObj->player].firePause > GAME_TICKS_PER_SEC
172 	    || (psObj->type == OBJ_DROID && isVtolDroid((const DROID *)psObj)))
173 	{
174 		if (psObj->type == OBJ_DROID && isVtolDroid((const DROID *)psObj))
175 		{
176 			//deal with VTOLs
177 			firingStage = getNumAttackRuns((const DROID *)psObj, weapon_slot) - ((const DROID *)psObj)->asWeaps[weapon_slot].usedAmmo;
178 
179 			//compare with max value
180 			interval = getNumAttackRuns((const DROID *)psObj, weapon_slot);
181 		}
182 		else
183 		{
184 			firingStage = gameTime - psWeap->lastFired;
185 			interval = bSalvo ? weaponReloadTime(psStats, psObj->player) : weaponFirePause(psStats, psObj->player);
186 		}
187 		if (firingStage < interval && interval > 0)
188 		{
189 			return PERCENT(firingStage, interval);
190 		}
191 		return 100;
192 	}
193 	return -1;
194 }
195 
196 #define UNIT_LOST_DELAY	(5*GAME_TICKS_PER_SEC)
197 /* Deals damage to a droid
198  * \param psDroid droid to deal damage to
199  * \param damage amount of damage to deal
200  * \param weaponClass the class of the weapon that deals the damage
201  * \param weaponSubClass the subclass of the weapon that deals the damage
202  * \param angle angle of impact (from the damage dealing projectile in relation to this droid)
203  * \return > 0 when the dealt damage destroys the droid, < 0 when the droid survives
204  *
205  */
droidDamage(DROID * psDroid,unsigned damage,WEAPON_CLASS weaponClass,WEAPON_SUBCLASS weaponSubClass,unsigned impactTime,bool isDamagePerSecond,int minDamage)206 int32_t droidDamage(DROID *psDroid, unsigned damage, WEAPON_CLASS weaponClass, WEAPON_SUBCLASS weaponSubClass, unsigned impactTime, bool isDamagePerSecond, int minDamage)
207 {
208 	int32_t relativeDamage;
209 
210 	CHECK_DROID(psDroid);
211 
212 	// VTOLs (and transporters in MP) on the ground take triple damage
213 	if ((isVtolDroid(psDroid) || (isTransporter(psDroid) && bMultiPlayer)) && (psDroid->sMove.Status == MOVEINACTIVE))
214 	{
215 		damage *= 3;
216 	}
217 
218 	relativeDamage = objDamage(psDroid, damage, psDroid->originalBody, weaponClass, weaponSubClass, isDamagePerSecond, minDamage);
219 
220 	if (relativeDamage > 0)
221 	{
222 		// reset the attack level
223 		if (secondaryGetState(psDroid, DSO_ATTACK_LEVEL) == DSS_ALEV_ATTACKED)
224 		{
225 			secondarySetState(psDroid, DSO_ATTACK_LEVEL, DSS_ALEV_ALWAYS);
226 		}
227 		// Now check for auto return on droid's secondary orders (i.e. return on medium/heavy damage)
228 		secondaryCheckDamageLevel(psDroid);
229 
230 		CHECK_DROID(psDroid);
231 	}
232 	else if (relativeDamage < 0)
233 	{
234 		// Droid destroyed
235 		debug(LOG_ATTACK, "droid (%d): DESTROYED", psDroid->id);
236 
237 		// Deal with score increase/decrease and messages to the player
238 		if (psDroid->player == selectedPlayer)
239 		{
240 			// TRANSLATORS:	Refers to the loss of a single unit, known by its name
241 			CONPRINTF(_("%s Lost!"), objInfo(psDroid));
242 			scoreUpdateVar(WD_UNITS_LOST);
243 			audio_QueueTrackMinDelayPos(ID_SOUND_UNIT_DESTROYED, UNIT_LOST_DELAY,
244 			                            psDroid->pos.x, psDroid->pos.y, psDroid->pos.z);
245 		}
246 		else
247 		{
248 			scoreUpdateVar(WD_UNITS_KILLED);
249 		}
250 
251 		// Do we have a dying animation?
252 		if (psDroid->sDisplay.imd->objanimpie[ANIM_EVENT_DYING] && psDroid->animationEvent != ANIM_EVENT_DYING)
253 		{
254 			bool useDeathAnimation = true;
255 			//Babas should not burst into flames from non-heat weapons
256 			if (psDroid->droidType == DROID_PERSON)
257 			{
258 				if (weaponClass == WC_HEAT)
259 				{
260 					// NOTE: 3 types of screams are available ID_SOUND_BARB_SCREAM - ID_SOUND_BARB_SCREAM3
261 					audio_PlayObjDynamicTrack(psDroid, ID_SOUND_BARB_SCREAM + (rand() % 3), nullptr);
262 				}
263 				else
264 				{
265 					useDeathAnimation = false;
266 				}
267 			}
268 			if (useDeathAnimation)
269 			{
270 				debug(LOG_DEATH, "%s droid %d (%p) is starting death animation", objInfo(psDroid), (int)psDroid->id, static_cast<void *>(psDroid));
271 				psDroid->timeAnimationStarted = gameTime;
272 				psDroid->animationEvent = ANIM_EVENT_DYING;
273 			}
274 		}
275 		// Otherwise use the default destruction animation
276 		if (psDroid->animationEvent != ANIM_EVENT_DYING)
277 		{
278 			debug(LOG_DEATH, "%s droid %d (%p) is toast", objInfo(psDroid), (int)psDroid->id, static_cast<void *>(psDroid));
279 			// This should be sent even if multi messages are turned off, as the group message that was
280 			// sent won't contain the destroyed droid
281 			if (bMultiPlayer && !bMultiMessages)
282 			{
283 				bMultiMessages = true;
284 				destroyDroid(psDroid, impactTime);
285 				bMultiMessages = false;
286 			}
287 			else
288 			{
289 				destroyDroid(psDroid, impactTime);
290 			}
291 		}
292 	}
293 
294 	return relativeDamage;
295 }
296 
DROID(uint32_t id,unsigned player)297 DROID::DROID(uint32_t id, unsigned player)
298 	: BASE_OBJECT(OBJ_DROID, id, player)
299 	, droidType(DROID_ANY)
300 	, psGroup(nullptr)
301 	, psGrpNext(nullptr)
302 	, secondaryOrder(DSS_ARANGE_LONG | DSS_REPLEV_NEVER | DSS_ALEV_ALWAYS | DSS_HALT_GUARD)
303 	, secondaryOrderPending(DSS_ARANGE_LONG | DSS_REPLEV_NEVER | DSS_ALEV_ALWAYS | DSS_HALT_GUARD)
304 	, secondaryOrderPendingCount(0)
305 	, action(DACTION_NONE)
306 	, actionPos(0, 0)
307 {
308 	memset(aName, 0, sizeof(aName));
309 	memset(asBits, 0, sizeof(asBits));
310 	pos = Vector3i(0, 0, 0);
311 	rot = Vector3i(0, 0, 0);
312 	order.type = DORDER_NONE;
313 	order.pos = Vector2i(0, 0);
314 	order.pos2 = Vector2i(0, 0);
315 	order.direction = 0;
316 	order.psObj = nullptr;
317 	order.psStats = nullptr;
318 	sMove.Status = MOVEINACTIVE;
319 	listSize = 0;
320 	listPendingBegin = 0;
321 	iAudioID = NO_SOUND;
322 	group = UBYTE_MAX;
323 	psBaseStruct = nullptr;
324 	sDisplay.frameNumber = 0;	// it was never drawn before
325 	for (unsigned vPlayer = 0; vPlayer < MAX_PLAYERS; ++vPlayer)
326 	{
327 		visible[vPlayer] = hasSharedVision(vPlayer, player) ? UINT8_MAX : 0;
328 	}
329 	memset(seenThisTick, 0, sizeof(seenThisTick));
330 	periodicalDamageStart = 0;
331 	periodicalDamage = 0;
332 	sDisplay.screenX = OFF_SCREEN;
333 	sDisplay.screenY = OFF_SCREEN;
334 	sDisplay.screenR = 0;
335 	sDisplay.imd = nullptr;
336 	illumination = UBYTE_MAX;
337 	resistance = ACTION_START_TIME;	// init the resistance to indicate no EW performed on this droid
338 	lastFrustratedTime = 0;		// make sure we do not start the game frustrated
339 }
340 
341 /* DROID::~DROID: release all resources associated with a droid -
342  * should only be called by objmem - use vanishDroid preferably
343  */
~DROID()344 DROID::~DROID()
345 {
346 	// Make sure to get rid of some final references in the sound code to this object first
347 	// In BASE_OBJECT::~BASE_OBJECT() is too late for this, since some callbacks require us to still be a DROID.
348 	audio_RemoveObj(this);
349 
350 	DROID *psDroid = this;
351 	DROID	*psCurr, *pNextGroupDroid = nullptr;
352 
353 	if (isTransporter(psDroid))
354 	{
355 		if (psDroid->psGroup)
356 		{
357 			//free all droids associated with this Transporter
358 			for (psCurr = psDroid->psGroup->psList; psCurr != nullptr && psCurr != psDroid; psCurr = pNextGroupDroid)
359 			{
360 				pNextGroupDroid = psCurr->psGrpNext;
361 				delete psCurr;
362 			}
363 		}
364 	}
365 
366 	fpathRemoveDroidData(psDroid->id);
367 
368 	// leave the current group if any
369 	if (psDroid->psGroup)
370 	{
371 		psDroid->psGroup->remove(psDroid);
372 	}
373 }
374 
copy_experience_queue(int player)375 std::priority_queue<int> copy_experience_queue(int player)
376 {
377 	return recycled_experience[player];
378 }
379 
add_to_experience_queue(int player,int value)380 void add_to_experience_queue(int player, int value)
381 {
382 	recycled_experience[player].push(value);
383 }
384 
385 // recycle a droid (retain it's experience and some of it's cost)
recycleDroid(DROID * psDroid)386 void recycleDroid(DROID *psDroid)
387 {
388 	CHECK_DROID(psDroid);
389 
390 	// store the droids kills
391 	if (psDroid->experience > 0)
392 	{
393 		recycled_experience[psDroid->player].push(psDroid->experience);
394 	}
395 
396 	// return part of the cost of the droid
397 	int cost = calcDroidPower(psDroid);
398 	cost = (cost / 2) * psDroid->body / psDroid->originalBody;
399 	addPower(psDroid->player, (UDWORD)cost);
400 
401 	// hide the droid
402 	memset(psDroid->visible, 0, sizeof(psDroid->visible));
403 
404 	if (psDroid->psGroup)
405 	{
406 		psDroid->psGroup->remove(psDroid);
407 	}
408 
409 	triggerEvent(TRIGGER_OBJECT_RECYCLED, psDroid);
410 	vanishDroid(psDroid);
411 
412 	Vector3i position = psDroid->pos.xzy();
413 	addEffect(&position, EFFECT_EXPLOSION, EXPLOSION_TYPE_DISCOVERY, false, nullptr, false, gameTime - deltaGameTime + 1);
414 
415 	CHECK_DROID(psDroid);
416 }
417 
418 
removeDroidBase(DROID * psDel)419 bool removeDroidBase(DROID *psDel)
420 {
421 	CHECK_DROID(psDel);
422 
423 	if (isDead(psDel))
424 	{
425 		// droid has already been killed, quit
426 		syncDebug("droid already dead");
427 		return true;
428 	}
429 
430 	syncDebugDroid(psDel, '#');
431 
432 	//kill all the droids inside the transporter
433 	if (isTransporter(psDel))
434 	{
435 		if (psDel->psGroup)
436 		{
437 			//free all droids associated with this Transporter
438 			DROID *psNext;
439 			for (auto psCurr = psDel->psGroup->psList; psCurr != nullptr && psCurr != psDel; psCurr = psNext)
440 			{
441 				psNext = psCurr->psGrpNext;
442 
443 				/* add droid to droid list then vanish it - hope this works! - GJ */
444 				addDroid(psCurr, apsDroidLists);
445 				vanishDroid(psCurr);
446 			}
447 		}
448 	}
449 
450 	// leave the current group if any
451 	if (psDel->psGroup)
452 	{
453 		psDel->psGroup->remove(psDel);
454 		psDel->psGroup = nullptr;
455 	}
456 
457 	/* Put Deliv. Pts back into world when a command droid dies */
458 	if (psDel->droidType == DROID_COMMAND)
459 	{
460 		for (auto psStruct = apsStructLists[psDel->player]; psStruct; psStruct = psStruct->psNext)
461 		{
462 			// alexl's stab at a right answer.
463 			if (StructIsFactory(psStruct)
464 			    && psStruct->pFunctionality->factory.psCommander == psDel)
465 			{
466 				assignFactoryCommandDroid(psStruct, nullptr);
467 			}
468 		}
469 	}
470 
471 	// Check to see if constructor droid currently trying to find a location to build
472 	if (psDel->player == selectedPlayer && psDel->selected && isConstructionDroid(psDel))
473 	{
474 		// If currently trying to build, kill off the placement
475 		if (tryingToGetLocation())
476 		{
477 			int numSelectedConstructors = 0;
478 			for (DROID *psDroid = apsDroidLists[psDel->player]; psDroid != nullptr; psDroid = psDroid->psNext)
479 			{
480 				numSelectedConstructors += psDroid->selected && isConstructionDroid(psDroid);
481 			}
482 			if (numSelectedConstructors <= 1)  // If we were the last selected construction droid.
483 			{
484 				kill3DBuilding();
485 			}
486 		}
487 	}
488 
489 	if (psDel->player == selectedPlayer)
490 	{
491 		intRefreshScreen();
492 	}
493 
494 	killDroid(psDel);
495 	return true;
496 }
497 
removeDroidFX(DROID * psDel,unsigned impactTime)498 static void removeDroidFX(DROID *psDel, unsigned impactTime)
499 {
500 	Vector3i pos;
501 
502 	CHECK_DROID(psDel);
503 
504 	// only display anything if the droid is visible
505 	if (!psDel->visible[selectedPlayer])
506 	{
507 		return;
508 	}
509 
510 	if (psDel->animationEvent != ANIM_EVENT_DYING)
511 	{
512 		compPersonToBits(psDel);
513 	}
514 
515 	/* if baba then squish */
516 	if (psDel->droidType == DROID_PERSON)
517 	{
518 		// The barbarian has been run over ...
519 		audio_PlayStaticTrack(psDel->pos.x, psDel->pos.y, ID_SOUND_BARB_SQUISH);
520 	}
521 	else
522 	{
523 		destroyFXDroid(psDel, impactTime);
524 		pos.x = psDel->pos.x;
525 		pos.z = psDel->pos.y;
526 		pos.y = psDel->pos.z;
527 		if (psDel->droidType == DROID_SUPERTRANSPORTER)
528 		{
529 			addEffect(&pos, EFFECT_EXPLOSION, EXPLOSION_TYPE_LARGE, false, nullptr, 0, impactTime);
530 		}
531 		else
532 		{
533 			addEffect(&pos, EFFECT_DESTRUCTION, DESTRUCTION_TYPE_DROID, false, nullptr, 0, impactTime);
534 		}
535 		audio_PlayStaticTrack(psDel->pos.x, psDel->pos.y, ID_SOUND_EXPLOSION);
536 	}
537 }
538 
destroyDroid(DROID * psDel,unsigned impactTime)539 bool destroyDroid(DROID *psDel, unsigned impactTime)
540 {
541 	ASSERT(gameTime - deltaGameTime < impactTime, "Expected %u < %u, gameTime = %u, bad impactTime", gameTime - deltaGameTime, impactTime, gameTime);
542 
543 	if (psDel->lastHitWeapon == WSC_LAS_SAT)		// darken tile if lassat.
544 	{
545 		UDWORD width, breadth, mapX, mapY;
546 		MAPTILE	*psTile;
547 
548 		mapX = map_coord(psDel->pos.x);
549 		mapY = map_coord(psDel->pos.y);
550 		for (width = mapX - 1; width <= mapX + 1; width++)
551 		{
552 			for (breadth = mapY - 1; breadth <= mapY + 1; breadth++)
553 			{
554 				psTile = mapTile(width, breadth);
555 				if (TEST_TILE_VISIBLE(selectedPlayer, psTile))
556 				{
557 					psTile->illumination /= 2;
558 				}
559 			}
560 		}
561 	}
562 
563 	removeDroidFX(psDel, impactTime);
564 	removeDroidBase(psDel);
565 	psDel->died = impactTime;
566 	return true;
567 }
568 
vanishDroid(DROID * psDel)569 void vanishDroid(DROID *psDel)
570 {
571 	removeDroidBase(psDel);
572 }
573 
574 /* Remove a droid from the List so doesn't update or get drawn etc
575 TAKE CARE with removeDroid() - usually want droidRemove since it deal with grid code*/
576 //returns false if the droid wasn't removed - because it died!
droidRemove(DROID * psDroid,DROID * pList[MAX_PLAYERS])577 bool droidRemove(DROID *psDroid, DROID *pList[MAX_PLAYERS])
578 {
579 	CHECK_DROID(psDroid);
580 
581 	if (isDead(psDroid))
582 	{
583 		// droid has already been killed, quit
584 		return false;
585 	}
586 
587 	// leave the current group if any - not if its a Transporter droid
588 	if (!isTransporter(psDroid) && psDroid->psGroup)
589 	{
590 		psDroid->psGroup->remove(psDroid);
591 		psDroid->psGroup = nullptr;
592 	}
593 
594 	// reset the baseStruct
595 	setDroidBase(psDroid, nullptr);
596 
597 	removeDroid(psDroid, pList);
598 
599 	if (psDroid->player == selectedPlayer)
600 	{
601 		intRefreshScreen();
602 	}
603 
604 	return true;
605 }
606 
_syncDebugDroid(const char * function,DROID const * psDroid,char ch)607 void _syncDebugDroid(const char *function, DROID const *psDroid, char ch)
608 {
609 	if (psDroid->type != OBJ_DROID) {
610 		ASSERT(false, "%c Broken psDroid->type %u!", ch, psDroid->type);
611 		syncDebug("Broken psDroid->type %u!", psDroid->type);
612 	}
613 	int list[] =
614 	{
615 		ch,
616 
617 		(int)psDroid->id,
618 
619 		psDroid->player,
620 		psDroid->pos.x, psDroid->pos.y, psDroid->pos.z,
621 		psDroid->rot.direction, psDroid->rot.pitch, psDroid->rot.roll,
622 		(int)psDroid->order.type, psDroid->order.pos.x, psDroid->order.pos.y, psDroid->listSize,
623 		(int)psDroid->action,
624 		(int)psDroid->secondaryOrder,
625 		(int)psDroid->body,
626 		(int)psDroid->sMove.Status,
627 		psDroid->sMove.speed, psDroid->sMove.moveDir,
628 		psDroid->sMove.pathIndex, (int)psDroid->sMove.asPath.size(),
629 		psDroid->sMove.src.x, psDroid->sMove.src.y, psDroid->sMove.target.x, psDroid->sMove.target.y, psDroid->sMove.destination.x, psDroid->sMove.destination.y,
630 		psDroid->sMove.bumpDir, (int)psDroid->sMove.bumpTime, (int)psDroid->sMove.lastBump, (int)psDroid->sMove.pauseTime, psDroid->sMove.bumpPos.x, psDroid->sMove.bumpPos.y, (int)psDroid->sMove.shuffleStart,
631 		(int)psDroid->experience,
632 	};
633 	_syncDebugIntList(function, "%c droid%d = p%d;pos(%d,%d,%d),rot(%d,%d,%d),order%d(%d,%d)^%d,action%d,secondaryOrder%X,body%d,sMove(status%d,speed%d,moveDir%d,path%d/%d,src(%d,%d),target(%d,%d),destination(%d,%d),bump(%d,%d,%d,%d,(%d,%d),%d)),exp%u", list, ARRAY_SIZE(list));
634 }
635 
636 /* The main update routine for all droids */
droidUpdate(DROID * psDroid)637 void droidUpdate(DROID *psDroid)
638 {
639 	Vector3i        dv;
640 	UDWORD          percentDamage, emissionInterval;
641 	BASE_OBJECT     *psBeingTargetted = nullptr;
642 	unsigned        i;
643 
644 	CHECK_DROID(psDroid);
645 
646 #ifdef DEBUG
647 	// Check that we are (still) in the sensor list
648 	if (psDroid->droidType == DROID_SENSOR)
649 	{
650 		BASE_OBJECT	*psSensor;
651 
652 		for (psSensor = apsSensorList[0]; psSensor; psSensor = psSensor->psNextFunc)
653 		{
654 			if (psSensor == (BASE_OBJECT *)psDroid)
655 			{
656 				break;
657 			}
658 		}
659 		ASSERT(psSensor == (BASE_OBJECT *)psDroid, "%s(%p) not in sensor list!",
660 		       droidGetName(psDroid), static_cast<void *>(psDroid));
661 	}
662 #endif
663 
664 	syncDebugDroid(psDroid, '<');
665 
666 	if (psDroid->flags.test(OBJECT_FLAG_DIRTY))
667 	{
668 		visTilesUpdate(psDroid);
669 		droidBodyUpgrade(psDroid);
670 		psDroid->flags.set(OBJECT_FLAG_DIRTY, false);
671 	}
672 
673 	// Save old droid position, update time.
674 	psDroid->prevSpacetime = getSpacetime(psDroid);
675 	psDroid->time = gameTime;
676 	for (i = 0; i < MAX(1, psDroid->numWeaps); ++i)
677 	{
678 		psDroid->asWeaps[i].prevRot = psDroid->asWeaps[i].rot;
679 	}
680 
681 	if (psDroid->animationEvent != ANIM_EVENT_NONE)
682 	{
683 		iIMDShape *imd = psDroid->sDisplay.imd->objanimpie[psDroid->animationEvent];
684 		if (imd && imd->objanimcycles > 0 && gameTime > psDroid->timeAnimationStarted + imd->objanimtime * imd->objanimcycles)
685 		{
686 			// Done animating (animation is defined by body - other components should follow suit)
687 			if (psDroid->animationEvent == ANIM_EVENT_DYING)
688 			{
689 				debug(LOG_DEATH, "%s (%d) died to burn anim (died=%d)", objInfo(psDroid), (int)psDroid->id, (int)psDroid->died);
690 				destroyDroid(psDroid, gameTime);
691 				return;
692 			}
693 			psDroid->animationEvent = ANIM_EVENT_NONE;
694 		}
695 	}
696 	else if (psDroid->animationEvent == ANIM_EVENT_DYING)
697 	{
698 		return; // rest below is irrelevant if dead
699 	}
700 
701 	// ai update droid
702 	aiUpdateDroid(psDroid);
703 
704 	// Update the droids order.
705 	orderUpdateDroid(psDroid);
706 
707 	// update the action of the droid
708 	actionUpdateDroid(psDroid);
709 
710 	syncDebugDroid(psDroid, 'M');
711 
712 	// update the move system
713 	moveUpdateDroid(psDroid);
714 
715 	/* Only add smoke if they're visible */
716 	if ((psDroid->visible[selectedPlayer]) && psDroid->droidType != DROID_PERSON)
717 	{
718 		// need to clip this value to prevent overflow condition
719 		percentDamage = 100 - clip<UDWORD>(PERCENT(psDroid->body, psDroid->originalBody), 0, 100);
720 
721 		// Is there any damage?
722 		if (percentDamage >= 25)
723 		{
724 			if (percentDamage >= 100)
725 			{
726 				percentDamage = 99;
727 			}
728 
729 			emissionInterval = CALC_DROID_SMOKE_INTERVAL(percentDamage);
730 
731 			uint32_t effectTime = std::max(gameTime - deltaGameTime + 1, psDroid->lastEmission + emissionInterval);
732 			if (gameTime >= effectTime)
733 			{
734 				dv.x = psDroid->pos.x + DROID_DAMAGE_SPREAD;
735 				dv.z = psDroid->pos.y + DROID_DAMAGE_SPREAD;
736 				dv.y = psDroid->pos.z;
737 
738 				dv.y += (psDroid->sDisplay.imd->max.y * 2);
739 				addEffect(&dv, EFFECT_SMOKE, SMOKE_TYPE_DRIFTING_SMALL, false, nullptr, 0, effectTime);
740 				psDroid->lastEmission = effectTime;
741 			}
742 		}
743 	}
744 
745 	// -----------------
746 	/* Are we a sensor droid or a command droid? Show where we target for selectedPlayer. */
747 	if (psDroid->player == selectedPlayer && (psDroid->droidType == DROID_SENSOR || psDroid->droidType == DROID_COMMAND))
748 	{
749 		/* If we're attacking or sensing (observing), then... */
750 		if ((psBeingTargetted = orderStateObj(psDroid, DORDER_ATTACK))
751 		    || (psBeingTargetted = orderStateObj(psDroid, DORDER_OBSERVE)))
752 		{
753 			psBeingTargetted->flags.set(OBJECT_FLAG_TARGETED, true);
754 		}
755 		else if (secondaryGetState(psDroid, DSO_HALTTYPE) != DSS_HALT_PURSUE &&
756 		    psDroid->psActionTarget[0] != nullptr &&
757 		    validTarget(psDroid, psDroid->psActionTarget[0], 0) &&
758 		    (psDroid->action == DACTION_ATTACK ||
759 		    psDroid->action == DACTION_OBSERVE ||
760 	         orderState(psDroid, DORDER_HOLD)))
761 		{
762 			psBeingTargetted = psDroid->psActionTarget[0];
763 			psBeingTargetted->flags.set(OBJECT_FLAG_TARGETED, true);
764 		}
765 	}
766 	// -----------------
767 
768 	// See if we can and need to self repair.
769 	if (!isVtolDroid(psDroid) && psDroid->body < psDroid->originalBody && psDroid->asBits[COMP_REPAIRUNIT] != 0 && selfRepairEnabled(psDroid->player))
770 	{
771 		droidUpdateDroidSelfRepair(psDroid);
772 	}
773 
774 	/* Update the fire damage data */
775 	if (psDroid->periodicalDamageStart != 0 && psDroid->periodicalDamageStart != gameTime - deltaGameTime)  // -deltaGameTime, since projectiles are updated after droids.
776 	{
777 		// The periodicalDamageStart has been set, but is not from the previous tick, so we must be out of the fire.
778 		psDroid->periodicalDamage = 0;  // Reset periodical damage done this tick.
779 		if (psDroid->periodicalDamageStart + BURN_TIME < gameTime)
780 		{
781 			// Finished periodical damaging.
782 			psDroid->periodicalDamageStart = 0;
783 		}
784 		else
785 		{
786 			// do hardcoded burn damage (this damage automatically applied after periodical damage finished)
787 			droidDamage(psDroid, BURN_DAMAGE, WC_HEAT, WSC_FLAME, gameTime - deltaGameTime / 2 + 1, true, BURN_MIN_DAMAGE);
788 		}
789 	}
790 
791 	// At this point, the droid may be dead due to periodical damage or hardcoded burn damage.
792 	if (isDead(psDroid))
793 	{
794 		return;
795 	}
796 
797 	calcDroidIllumination(psDroid);
798 
799 	// Check the resistance level of the droid
800 	if ((psDroid->id + gameTime) / 833 != (psDroid->id + gameTime - deltaGameTime) / 833)
801 	{
802 		// Zero resistance means not currently been attacked - ignore these
803 		if (psDroid->resistance && psDroid->resistance < droidResistance(psDroid))
804 		{
805 			// Increase over time if low
806 			psDroid->resistance++;
807 		}
808 	}
809 
810 	syncDebugDroid(psDroid, '>');
811 
812 	CHECK_DROID(psDroid);
813 }
814 
815 /* See if a droid is next to a structure */
droidNextToStruct(DROID * psDroid,STRUCTURE * psStruct)816 static bool droidNextToStruct(DROID *psDroid, STRUCTURE *psStruct)
817 {
818 	CHECK_DROID(psDroid);
819 
820 	auto pos = map_coord(psDroid->pos);
821 	int minX = std::max(pos.x - 1, 0);
822 	int minY = std::max(pos.y - 1, 0);
823 	int maxX = std::min(pos.x + 1, mapWidth);
824 	int maxY = std::min(pos.y + 1, mapHeight);
825 	for (int y = minY; y <= maxY; ++y)
826 	{
827 		for (int x = minX; x <= maxX; ++x)
828 		{
829 			if (TileHasStructure(mapTile(x, y)) &&
830 			    getTileStructure(x, y) == psStruct)
831 			{
832 				return true;
833 			}
834 		}
835 	}
836 
837 	return false;
838 }
839 
droidCheckBuildStillInProgress(void * psObj)840 static bool droidCheckBuildStillInProgress(void *psObj)
841 {
842 	if (psObj == nullptr)
843 	{
844 		return false;
845 	}
846 
847 	auto psDroid = (DROID *)psObj;
848 	CHECK_DROID(psDroid);
849 
850 	return !psDroid->died && psDroid->action == DACTION_BUILD;
851 }
852 
droidBuildStartAudioCallback(void * psObj)853 static bool droidBuildStartAudioCallback(void *psObj)
854 {
855 	auto psDroid = (DROID *)psObj;
856 
857 	if (psDroid != nullptr && psDroid->visible[selectedPlayer])
858 	{
859 		audio_PlayObjDynamicTrack(psDroid, ID_SOUND_CONSTRUCTION_LOOP, droidCheckBuildStillInProgress);
860 	}
861 
862 	return true;
863 }
864 
865 
866 /* Set up a droid to build a structure - returns true if successful */
droidStartBuild(DROID * psDroid)867 DroidStartBuild droidStartBuild(DROID *psDroid)
868 {
869 	STRUCTURE *psStruct = nullptr;
870 	ASSERT_OR_RETURN(DroidStartBuildFailed, psDroid != nullptr, "Bad Droid");
871 	CHECK_DROID(psDroid);
872 
873 	/* See if we are starting a new structure */
874 	if (psDroid->order.psObj == nullptr &&
875 	    (psDroid->order.type == DORDER_BUILD ||
876 	     psDroid->order.type == DORDER_LINEBUILD))
877 	{
878 		STRUCTURE_STATS *psStructStat = psDroid->order.psStats;
879 
880 		ItemAvailability ia = (ItemAvailability)apStructTypeLists[psDroid->player][psStructStat - asStructureStats];
881 		if (ia != AVAILABLE && ia != REDUNDANT)
882 		{
883 			ASSERT(false, "Cannot build \"%s\" for player %d.", psStructStat->name.toUtf8().c_str(), psDroid->player);
884 			cancelBuild(psDroid);
885 			objTrace(psDroid->id, "DroidStartBuildFailed: not researched");
886 			return DroidStartBuildFailed;
887 		}
888 
889 		//need to check structLimits have not been exceeded
890 		if (psStructStat->curCount[psDroid->player] >= psStructStat->upgrade[psDroid->player].limit)
891 		{
892 			cancelBuild(psDroid);
893 			objTrace(psDroid->id, "DroidStartBuildFailed: structure limits");
894 			return DroidStartBuildFailed;
895 		}
896 		// Can't build on burning oil derricks.
897 		if (psStructStat->type == REF_RESOURCE_EXTRACTOR && fireOnLocation(psDroid->order.pos.x, psDroid->order.pos.y))
898 		{
899 			// Don't cancel build, since we can wait for it to stop burning.
900 			objTrace(psDroid->id, "DroidStartBuildPending: burning");
901 			return DroidStartBuildPending;
902 		}
903 		//ok to build
904 		psStruct = buildStructureDir(psStructStat, psDroid->order.pos.x, psDroid->order.pos.y, psDroid->order.direction, psDroid->player, false);
905 		if (!psStruct)
906 		{
907 			cancelBuild(psDroid);
908 			objTrace(psDroid->id, "DroidStartBuildFailed: buildStructureDir failed");
909 			return DroidStartBuildFailed;
910 		}
911 		psStruct->body = (psStruct->body + 9) / 10;  // Structures start at 10% health. Round up.
912 	}
913 	else
914 	{
915 		/* Check the structure is still there to build (joining a partially built struct) */
916 		psStruct = castStructure(psDroid->order.psObj);
917 		if (psStruct == nullptr)
918 		{
919 			psStruct = castStructure(worldTile(psDroid->actionPos)->psObject);
920 		}
921 		if (psStruct && !droidNextToStruct(psDroid, psStruct))
922 		{
923 			/* Nope - stop building */
924 			debug(LOG_NEVER, "not next to structure");
925 			objTrace(psDroid->id, "DroidStartBuildSuccess: not next to structure");
926 		}
927 	}
928 
929 	//check structure not already built, and we still 'own' it
930 	if (psStruct)
931 	{
932 		if (psStruct->status != SS_BUILT && aiCheckAlliances(psStruct->player, psDroid->player))
933 		{
934 			psDroid->actionStarted = gameTime;
935 			psDroid->actionPoints = 0;
936 			setDroidTarget(psDroid, psStruct);
937 			setDroidActionTarget(psDroid, psStruct, 0);
938 			objTrace(psDroid->id, "DroidStartBuild: set target");
939 		}
940 
941 		if (psStruct->visible[selectedPlayer])
942 		{
943 			audio_PlayObjStaticTrackCallback(psDroid, ID_SOUND_CONSTRUCTION_START, droidBuildStartAudioCallback);
944 		}
945 	}
946 	CHECK_DROID(psDroid);
947 
948 	objTrace(psDroid->id, "DroidStartBuildSuccess");
949 	return DroidStartBuildSuccess;
950 }
951 
droidAddWeldSound(Vector3i iVecEffect)952 static void droidAddWeldSound(Vector3i iVecEffect)
953 {
954 	int iAudioID = ID_SOUND_CONSTRUCTION_1 + (rand() % 4);
955 
956 	audio_PlayStaticTrack(iVecEffect.x, iVecEffect.z, iAudioID);
957 }
958 
addConstructorEffect(STRUCTURE * psStruct)959 static void addConstructorEffect(STRUCTURE *psStruct)
960 {
961 	if ((ONEINTEN) && (psStruct->visible[selectedPlayer]))
962 	{
963 		/* This needs fixing - it's an arse effect! */
964 		const Vector2i size = psStruct->size() * TILE_UNITS / 4;
965 		Vector3i temp;
966 		temp.x = psStruct->pos.x + ((rand() % (2 * size.x)) - size.x);
967 		temp.y = map_TileHeight(map_coord(psStruct->pos.x), map_coord(psStruct->pos.y)) + (psStruct->sDisplay.imd->max.y / 6);
968 		temp.z = psStruct->pos.y + ((rand() % (2 * size.y)) - size.y);
969 		if (rand() % 2)
970 		{
971 			droidAddWeldSound(temp);
972 		}
973 	}
974 }
975 
976 /* Update a construction droid while it is building
977    returns true while building continues */
droidUpdateBuild(DROID * psDroid)978 bool droidUpdateBuild(DROID *psDroid)
979 {
980 	CHECK_DROID(psDroid);
981 	ASSERT_OR_RETURN(false, psDroid->action == DACTION_BUILD, "%s (order %s) has wrong action for construction: %s",
982 	                 droidGetName(psDroid), getDroidOrderName(psDroid->order.type), getDroidActionName(psDroid->action));
983 
984 	STRUCTURE *psStruct = castStructure(psDroid->order.psObj);
985 	if (psStruct == nullptr)
986 	{
987 		// Target missing, stop trying to build it.
988 		psDroid->action = DACTION_NONE;
989 		return false;
990 	}
991 
992 	ASSERT_OR_RETURN(false, psStruct->type == OBJ_STRUCTURE, "target is not a structure");
993 	ASSERT_OR_RETURN(false, psDroid->asBits[COMP_CONSTRUCT] < numConstructStats, "Invalid construct pointer for unit");
994 
995 	// First check the structure hasn't been completed by another droid
996 	if (psStruct->status == SS_BUILT)
997 	{
998 		// Check if line order build is completed, or we are not carrying out a line order build
999 		if (psDroid->order.type != DORDER_LINEBUILD ||
1000 		    map_coord(psDroid->order.pos) == map_coord(psDroid->order.pos2))
1001 		{
1002 			cancelBuild(psDroid);
1003 		}
1004 		else
1005 		{
1006 			psDroid->action = DACTION_NONE;	// make us continue line build
1007 			setDroidTarget(psDroid, nullptr);
1008 			setDroidActionTarget(psDroid, nullptr, 0);
1009 		}
1010 		return false;
1011 	}
1012 
1013 	// make sure we still 'own' the building in question
1014 	if (!aiCheckAlliances(psStruct->player, psDroid->player))
1015 	{
1016 		cancelBuild(psDroid);		// stop what you are doing fool it isn't ours anymore!
1017 		return false;
1018 	}
1019 
1020 	unsigned constructPoints = constructorPoints(asConstructStats + psDroid->
1021 	                                    asBits[COMP_CONSTRUCT], psDroid->player);
1022 
1023 	unsigned pointsToAdd = constructPoints * (gameTime - psDroid->actionStarted) /
1024 	              GAME_TICKS_PER_SEC;
1025 
1026 	structureBuild(psStruct, psDroid, pointsToAdd - psDroid->actionPoints, constructPoints);
1027 
1028 	//store the amount just added
1029 	psDroid->actionPoints = pointsToAdd;
1030 
1031 	addConstructorEffect(psStruct);
1032 
1033 	return true;
1034 }
1035 
droidUpdateDemolishing(DROID * psDroid)1036 bool droidUpdateDemolishing(DROID *psDroid)
1037 {
1038 	CHECK_DROID(psDroid);
1039 
1040 	ASSERT_OR_RETURN(false, psDroid->action == DACTION_DEMOLISH, "unit is not demolishing");
1041 	STRUCTURE *psStruct = (STRUCTURE *)psDroid->order.psObj;
1042 	ASSERT_OR_RETURN(false, psStruct->type == OBJ_STRUCTURE, "target is not a structure");
1043 
1044 	int constructRate = 5 * constructorPoints(asConstructStats + psDroid->asBits[COMP_CONSTRUCT], psDroid->player);
1045 	int pointsToAdd = gameTimeAdjustedAverage(constructRate);
1046 
1047 	structureDemolish(psStruct, psDroid, pointsToAdd);
1048 
1049 	addConstructorEffect(psStruct);
1050 
1051 	CHECK_DROID(psDroid);
1052 
1053 	return true;
1054 }
1055 
droidStartAction(DROID * psDroid)1056 void droidStartAction(DROID *psDroid)
1057 {
1058 	psDroid->actionStarted = gameTime;
1059 	psDroid->actionPoints  = 0;
1060 }
1061 
1062 /*continue restoring a structure*/
droidUpdateRestore(DROID * psDroid)1063 bool droidUpdateRestore(DROID *psDroid)
1064 {
1065 	CHECK_DROID(psDroid);
1066 
1067 	ASSERT_OR_RETURN(false, psDroid->action == DACTION_RESTORE, "Unit is not restoring");
1068 	STRUCTURE *psStruct = (STRUCTURE *)psDroid->order.psObj;
1069 	ASSERT_OR_RETURN(false, psStruct->type == OBJ_STRUCTURE, "Target is not a structure");
1070 	ASSERT_OR_RETURN(false, psDroid->asWeaps[0].nStat > 0, "Droid doesn't have any weapons");
1071 
1072 	unsigned compIndex = psDroid->asWeaps[0].nStat;
1073 	ASSERT_OR_RETURN(false, compIndex < numWeaponStats, "Invalid range referenced for numWeaponStats, %u > %u", compIndex, numWeaponStats);
1074 	WEAPON_STATS *psStats = &asWeaponStats[compIndex];
1075 
1076 	ASSERT_OR_RETURN(false, psStats->weaponSubClass == WSC_ELECTRONIC, "unit's weapon is not EW");
1077 
1078 	unsigned restorePoints = calcDamage(weaponDamage(psStats, psDroid->player),
1079 	                           psStats->weaponEffect, (BASE_OBJECT *)psStruct);
1080 
1081 	unsigned pointsToAdd = restorePoints * (gameTime - psDroid->actionStarted) /
1082 	              GAME_TICKS_PER_SEC;
1083 
1084 	psStruct->resistance = (SWORD)(psStruct->resistance + (pointsToAdd - psDroid->actionPoints));
1085 
1086 	//store the amount just added
1087 	psDroid->actionPoints = pointsToAdd;
1088 
1089 	CHECK_DROID(psDroid);
1090 
1091 	/* check if structure is restored */
1092 	if (psStruct->resistance < (SDWORD)structureResistance(psStruct->
1093 	        pStructureType, psStruct->player))
1094 	{
1095 		return true;
1096 	}
1097 	else
1098 	{
1099 		addConsoleMessage(_("Structure Restored") , DEFAULT_JUSTIFY, SYSTEM_MESSAGE);
1100 		psStruct->resistance = (UWORD)structureResistance(psStruct->pStructureType,
1101 		                       psStruct->player);
1102 		return false;
1103 	}
1104 }
1105 
1106 // Declared in weapondef.h.
getRecoil(WEAPON const & weapon)1107 int getRecoil(WEAPON const &weapon)
1108 {
1109 	if (weapon.nStat != 0)
1110 	{
1111 		// We have a weapon.
1112 		if (graphicsTime >= weapon.lastFired && graphicsTime < weapon.lastFired + DEFAULT_RECOIL_TIME)
1113 		{
1114 			int recoilTime = graphicsTime - weapon.lastFired;
1115 			int recoilAmount = DEFAULT_RECOIL_TIME / 2 - abs(recoilTime - DEFAULT_RECOIL_TIME / 2);
1116 			int maxRecoil = asWeaponStats[weapon.nStat].recoilValue;  // Max recoil is 1/10 of this value.
1117 			return maxRecoil * recoilAmount / (DEFAULT_RECOIL_TIME / 2 * 10);
1118 		}
1119 		// Recoil effect is over.
1120 	}
1121 	return 0;
1122 }
1123 
1124 
droidUpdateRepair(DROID * psDroid)1125 bool droidUpdateRepair(DROID *psDroid)
1126 {
1127 	CHECK_DROID(psDroid);
1128 
1129 	ASSERT_OR_RETURN(false, psDroid->action == DACTION_REPAIR, "unit does not have repair order");
1130 	STRUCTURE *psStruct = (STRUCTURE *)psDroid->psActionTarget[0];
1131 
1132 	ASSERT_OR_RETURN(false, psStruct->type == OBJ_STRUCTURE, "target is not a structure");
1133 	int iRepairRate = constructorPoints(asConstructStats + psDroid->asBits[COMP_CONSTRUCT], psDroid->player);
1134 
1135 	/* add points to structure */
1136 	structureRepair(psStruct, psDroid, iRepairRate);
1137 
1138 	/* if not finished repair return true else complete repair and return false */
1139 	if (psStruct->body < structureBody(psStruct))
1140 	{
1141 		return true;
1142 	}
1143 	else
1144 	{
1145 		objTrace(psDroid->id, "Repaired of %s all done with %u", objInfo(psStruct), iRepairRate);
1146 		return false;
1147 	}
1148 }
1149 
1150 /*Updates a Repair Droid working on a damaged droid*/
droidUpdateDroidRepairBase(DROID * psRepairDroid,DROID * psDroidToRepair)1151 static bool droidUpdateDroidRepairBase(DROID *psRepairDroid, DROID *psDroidToRepair)
1152 {
1153 	CHECK_DROID(psRepairDroid);
1154 
1155 	int iRepairRateNumerator = repairPoints(asRepairStats + psRepairDroid->asBits[COMP_REPAIRUNIT], psRepairDroid->player);
1156 	int iRepairRateDenominator = 1;
1157 
1158 	//if self repair then add repair points depending on the time delay for the stat
1159 	if (psRepairDroid == psDroidToRepair)
1160 	{
1161 		iRepairRateNumerator *= GAME_TICKS_PER_SEC;
1162 		iRepairRateDenominator *= (asRepairStats + psRepairDroid->asBits[COMP_REPAIRUNIT])->time;
1163 	}
1164 
1165 	int iPointsToAdd = gameTimeAdjustedAverage(iRepairRateNumerator, iRepairRateDenominator);
1166 
1167 	psDroidToRepair->body = clip<UDWORD>(psDroidToRepair->body + iPointsToAdd, 0, psDroidToRepair->originalBody);
1168 
1169 	/* add plasma repair effect whilst being repaired */
1170 	if ((ONEINFIVE) && (psDroidToRepair->visible[selectedPlayer]))
1171 	{
1172 		Vector3i iVecEffect = (psDroidToRepair->pos + Vector3i(DROID_REPAIR_SPREAD, DROID_REPAIR_SPREAD, rand() % 8)).xzy();
1173 		effectGiveAuxVar(90 + rand() % 20);
1174 		addEffect(&iVecEffect, EFFECT_EXPLOSION, EXPLOSION_TYPE_LASER, false, nullptr, 0, gameTime - deltaGameTime + 1 + rand() % deltaGameTime);
1175 		droidAddWeldSound(iVecEffect);
1176 	}
1177 
1178 	CHECK_DROID(psRepairDroid);
1179 
1180 	/* if not finished repair return true else complete repair and return false */
1181 	return psDroidToRepair->body < psDroidToRepair->originalBody;
1182 }
1183 
droidUpdateDroidRepair(DROID * psRepairDroid)1184 bool droidUpdateDroidRepair(DROID *psRepairDroid)
1185 {
1186 	ASSERT_OR_RETURN(false, psRepairDroid->action == DACTION_DROIDREPAIR, "Unit does not have unit repair order");
1187 	ASSERT_OR_RETURN(false, psRepairDroid->asBits[COMP_REPAIRUNIT] != 0, "Unit does not have a repair turret");
1188 
1189 	DROID *psDroidToRepair = (DROID *)psRepairDroid->psActionTarget[0];
1190 	ASSERT_OR_RETURN(false, psDroidToRepair->type == OBJ_DROID, "Target is not a unit");
1191 
1192 	return droidUpdateDroidRepairBase(psRepairDroid, psDroidToRepair);
1193 }
1194 
droidUpdateDroidSelfRepair(DROID * psRepairDroid)1195 static void droidUpdateDroidSelfRepair(DROID *psRepairDroid)
1196 {
1197 	droidUpdateDroidRepairBase(psRepairDroid, psRepairDroid);
1198 }
1199 
1200 // return whether a droid is IDF
idfDroid(DROID * psDroid)1201 bool idfDroid(DROID *psDroid)
1202 {
1203 	//add Cyborgs
1204 	//if (psDroid->droidType != DROID_WEAPON)
1205 	if (!(psDroid->droidType == DROID_WEAPON || psDroid->droidType == DROID_CYBORG ||
1206 	      psDroid->droidType == DROID_CYBORG_SUPER))
1207 	{
1208 		return false;
1209 	}
1210 
1211 	return !proj_Direct(psDroid->asWeaps[0].nStat + asWeaponStats);
1212 }
1213 
1214 /* Return the type of a droid */
droidType(DROID * psDroid)1215 DROID_TYPE droidType(DROID *psDroid)
1216 {
1217 	return psDroid->droidType;
1218 }
1219 
1220 /* Return the type of a droid from it's template */
droidTemplateType(const DROID_TEMPLATE * psTemplate)1221 DROID_TYPE droidTemplateType(const DROID_TEMPLATE *psTemplate)
1222 {
1223 	DROID_TYPE type = DROID_DEFAULT;
1224 
1225 	if (psTemplate->droidType == DROID_PERSON ||
1226 	    psTemplate->droidType == DROID_CYBORG ||
1227 	    psTemplate->droidType == DROID_CYBORG_SUPER ||
1228 	    psTemplate->droidType == DROID_CYBORG_CONSTRUCT ||
1229 	    psTemplate->droidType == DROID_CYBORG_REPAIR ||
1230 	    psTemplate->droidType == DROID_TRANSPORTER ||
1231 	    psTemplate->droidType == DROID_SUPERTRANSPORTER)
1232 	{
1233 		type = psTemplate->droidType;
1234 	}
1235 	else if (psTemplate->asParts[COMP_BRAIN] != 0)
1236 	{
1237 		type = DROID_COMMAND;
1238 	}
1239 	else if ((asSensorStats + psTemplate->asParts[COMP_SENSOR])->location == LOC_TURRET)
1240 	{
1241 		type = DROID_SENSOR;
1242 	}
1243 	else if ((asECMStats + psTemplate->asParts[COMP_ECM])->location == LOC_TURRET)
1244 	{
1245 		type = DROID_ECM;
1246 	}
1247 	else if (psTemplate->asParts[COMP_CONSTRUCT] != 0)
1248 	{
1249 		type = DROID_CONSTRUCT;
1250 	}
1251 	else if ((asRepairStats + psTemplate->asParts[COMP_REPAIRUNIT])->location == LOC_TURRET)
1252 	{
1253 		type = DROID_REPAIR;
1254 	}
1255 	else if (psTemplate->asWeaps[0] != 0)
1256 	{
1257 		type = DROID_WEAPON;
1258 	}
1259 	/* with more than weapon is still a DROID_WEAPON */
1260 	else if (psTemplate->numWeaps > 1)
1261 	{
1262 		type = DROID_WEAPON;
1263 	}
1264 
1265 	return type;
1266 }
1267 
1268 template <typename F, typename G>
calcSum(const uint8_t (& asParts)[DROID_MAXCOMP],int numWeaps,const uint32_t (& asWeaps)[MAX_WEAPONS],F func,G propulsionFunc)1269 static unsigned calcSum(const uint8_t (&asParts)[DROID_MAXCOMP], int numWeaps, const uint32_t (&asWeaps)[MAX_WEAPONS], F func, G propulsionFunc)
1270 {
1271 	unsigned sum =
1272 		func(asBrainStats    [asParts[COMP_BRAIN]]) +
1273 		func(asSensorStats   [asParts[COMP_SENSOR]]) +
1274 		func(asECMStats      [asParts[COMP_ECM]]) +
1275 		func(asRepairStats   [asParts[COMP_REPAIRUNIT]]) +
1276 		func(asConstructStats[asParts[COMP_CONSTRUCT]]) +
1277 		propulsionFunc(asBodyStats[asParts[COMP_BODY]], asPropulsionStats[asParts[COMP_PROPULSION]]);
1278 	for (int i = 0; i < numWeaps; ++i)
1279 	{
1280 		sum += func(asWeaponStats[asWeaps[i]]);
1281 	}
1282 	return sum;
1283 }
1284 
1285 template <typename F, typename G>
calcUpgradeSum(const uint8_t (& asParts)[DROID_MAXCOMP],int numWeaps,const uint32_t (& asWeaps)[MAX_WEAPONS],int player,F func,G propulsionFunc)1286 static unsigned calcUpgradeSum(const uint8_t (&asParts)[DROID_MAXCOMP], int numWeaps, const uint32_t (&asWeaps)[MAX_WEAPONS], int player, F func, G propulsionFunc)
1287 {
1288 	unsigned sum =
1289 		func(asBrainStats    [asParts[COMP_BRAIN]].upgrade[player]) +
1290 		func(asSensorStats   [asParts[COMP_SENSOR]].upgrade[player]) +
1291 		func(asECMStats      [asParts[COMP_ECM]].upgrade[player]) +
1292 		func(asRepairStats   [asParts[COMP_REPAIRUNIT]].upgrade[player]) +
1293 		func(asConstructStats[asParts[COMP_CONSTRUCT]].upgrade[player]) +
1294 		propulsionFunc(asBodyStats[asParts[COMP_BODY]].upgrade[player], asPropulsionStats[asParts[COMP_PROPULSION]].upgrade[player]);
1295 	for (int i = 0; i < numWeaps; ++i)
1296 	{
1297 		// asWeaps[i] > 0 check only needed for droids, not templates.
1298 		if (asWeaps[i] > 0)
1299 		{
1300 			sum += func(asWeaponStats[asWeaps[i]].upgrade[player]);
1301 		}
1302 	}
1303 	return sum;
1304 }
1305 
1306 struct FilterDroidWeaps
1307 {
FilterDroidWeapsFilterDroidWeaps1308 	FilterDroidWeaps(unsigned numWeaps, const WEAPON (&asWeaps)[MAX_WEAPONS])
1309 	{
1310 		std::transform(asWeaps, asWeaps + numWeaps, this->asWeaps, [](const WEAPON &weap) {
1311 			return weap.nStat;
1312 		});
1313 		this->numWeaps = std::remove_if(this->asWeaps, this->asWeaps + numWeaps, [](uint32_t stat) {
1314 			return stat == 0;
1315 		}) - this->asWeaps;
1316 	}
1317 
1318 	unsigned numWeaps;
1319 	uint32_t asWeaps[MAX_WEAPONS];
1320 };
1321 
1322 template <typename F, typename G>
calcSum(const DROID_TEMPLATE * psTemplate,F func,G propulsionFunc)1323 static unsigned calcSum(const DROID_TEMPLATE *psTemplate, F func, G propulsionFunc)
1324 {
1325 	return calcSum(psTemplate->asParts, psTemplate->numWeaps, psTemplate->asWeaps, func, propulsionFunc);
1326 }
1327 
1328 template <typename F, typename G>
calcSum(const DROID * psDroid,F func,G propulsionFunc)1329 static unsigned calcSum(const DROID *psDroid, F func, G propulsionFunc)
1330 {
1331 	FilterDroidWeaps f = {psDroid->numWeaps, psDroid->asWeaps};
1332 	return calcSum(psDroid->asBits, f.numWeaps, f.asWeaps, func, propulsionFunc);
1333 }
1334 
1335 template <typename F, typename G>
calcUpgradeSum(const DROID_TEMPLATE * psTemplate,int player,F func,G propulsionFunc)1336 static unsigned calcUpgradeSum(const DROID_TEMPLATE *psTemplate, int player, F func, G propulsionFunc)
1337 {
1338 	return calcUpgradeSum(psTemplate->asParts, psTemplate->numWeaps, psTemplate->asWeaps, player, func, propulsionFunc);
1339 }
1340 
1341 template <typename F, typename G>
calcUpgradeSum(const DROID * psDroid,int player,F func,G propulsionFunc)1342 static unsigned calcUpgradeSum(const DROID *psDroid, int player, F func, G propulsionFunc)
1343 {
1344 	FilterDroidWeaps f = {psDroid->numWeaps, psDroid->asWeaps};
1345 	return calcUpgradeSum(psDroid->asBits, f.numWeaps, f.asWeaps, player, func, propulsionFunc);
1346 }
1347 
1348 /* Calculate the weight of a droid from it's template */
calcDroidWeight(const DROID_TEMPLATE * psTemplate)1349 UDWORD calcDroidWeight(const DROID_TEMPLATE *psTemplate)
1350 {
1351 	return calcSum(psTemplate, [](COMPONENT_STATS const &stat) {
1352 		return stat.weight;
1353 	}, [](BODY_STATS const &bodyStat, PROPULSION_STATS const &propStat) {
1354 		// Propulsion weight is a percentage of the body weight.
1355 		return bodyStat.weight * (100 + propStat.weight) / 100;
1356 	});
1357 }
1358 
1359 template <typename T>
calcBody(T * obj,int player)1360 static uint32_t calcBody(T *obj, int player)
1361 {
1362 	int hitpoints = calcUpgradeSum(obj, player, [](COMPONENT_STATS::UPGRADE const &upgrade) {
1363 		return upgrade.hitpoints;
1364 	}, [](BODY_STATS::UPGRADE const &bodyUpgrade, PROPULSION_STATS::UPGRADE const &propUpgrade) {
1365 		// propulsion hitpoints can be a percentage of the body's hitpoints
1366 		return bodyUpgrade.hitpoints * (100 + propUpgrade.hitpointPctOfBody) / 100 + propUpgrade.hitpoints;
1367 	});
1368 
1369 	int hitpointPct = calcUpgradeSum(obj, player, [](COMPONENT_STATS::UPGRADE const &upgrade) {
1370 		return upgrade.hitpointPct - 100;
1371 	}, [](BODY_STATS::UPGRADE const &bodyUpgrade, PROPULSION_STATS::UPGRADE const &propUpgrade) {
1372 		return bodyUpgrade.hitpointPct - 100 + propUpgrade.hitpointPct - 100;
1373 	});
1374 
1375 	// Final adjustment based on the hitpoint modifier
1376 	return hitpoints * (100 + hitpointPct) / 100;
1377 }
1378 
1379 // Calculate the body points of a droid from its template
calcTemplateBody(const DROID_TEMPLATE * psTemplate,UBYTE player)1380 UDWORD calcTemplateBody(const DROID_TEMPLATE *psTemplate, UBYTE player)
1381 {
1382 	if (psTemplate == nullptr)
1383 	{
1384 		ASSERT(false, "null template");
1385 		return 0;
1386 	}
1387 
1388 	return calcBody(psTemplate, player);
1389 }
1390 
1391 // Calculate the base body points of a droid with upgrades
calcDroidBaseBody(DROID * psDroid)1392 static UDWORD calcDroidBaseBody(DROID *psDroid)
1393 {
1394 	return calcBody(psDroid, psDroid->player);
1395 }
1396 
1397 
1398 /* Calculate the base speed of a droid from it's template */
calcDroidBaseSpeed(const DROID_TEMPLATE * psTemplate,UDWORD weight,UBYTE player)1399 UDWORD calcDroidBaseSpeed(const DROID_TEMPLATE *psTemplate, UDWORD weight, UBYTE player)
1400 {
1401 	unsigned speed = asPropulsionTypes[asPropulsionStats[psTemplate->asParts[COMP_PROPULSION]].propulsionType].powerRatioMult *
1402 		         bodyPower(&asBodyStats[psTemplate->asParts[COMP_BODY]], player) / MAX(1, weight);
1403 
1404 	// reduce the speed of medium/heavy VTOLs
1405 	if (asPropulsionStats[psTemplate->asParts[COMP_PROPULSION]].propulsionType == PROPULSION_TYPE_LIFT)
1406 	{
1407 		if (asBodyStats[psTemplate->asParts[COMP_BODY]].size == SIZE_HEAVY)
1408 		{
1409 			speed /= 4;
1410 		}
1411 		else if (asBodyStats[psTemplate->asParts[COMP_BODY]].size == SIZE_MEDIUM)
1412 		{
1413 			speed = speed * 3 / 4;
1414 		}
1415 	}
1416 
1417 	// applies the engine output bonus if output > weight
1418 	if (asBodyStats[psTemplate->asParts[COMP_BODY]].base.power > weight)
1419 	{
1420 		speed = speed * 3 / 2;
1421 	}
1422 
1423 	return speed;
1424 }
1425 
1426 
1427 /* Calculate the speed of a droid over a terrain */
calcDroidSpeed(UDWORD baseSpeed,UDWORD terrainType,UDWORD propIndex,UDWORD level)1428 UDWORD calcDroidSpeed(UDWORD baseSpeed, UDWORD terrainType, UDWORD propIndex, UDWORD level)
1429 {
1430 	PROPULSION_STATS const &propulsion = asPropulsionStats[propIndex];
1431 
1432 	// Factor in terrain
1433 	unsigned speed = baseSpeed * getSpeedFactor(terrainType, propulsion.propulsionType) / 100;
1434 
1435 	// Need to ensure doesn't go over the max speed possible for this propulsion
1436 	speed = std::min(speed, propulsion.maxSpeed);
1437 
1438 	// Factor in experience
1439 	speed *= 100 + EXP_SPEED_BONUS * level;
1440 	speed /= 100;
1441 
1442 	return speed;
1443 }
1444 
1445 template <typename T>
calcBuild(T * obj)1446 static uint32_t calcBuild(T *obj)
1447 {
1448 	return calcSum(obj, [](COMPONENT_STATS const &stat) {
1449 		return stat.buildPoints;
1450 	}, [](BODY_STATS const &bodyStat, PROPULSION_STATS const &propStat) {
1451 		// Propulsion power points are a percentage of the body's build points.
1452 		return bodyStat.buildPoints * (100 + propStat.buildPoints) / 100;
1453 	});
1454 }
1455 
1456 /* Calculate the points required to build the template - used to calculate time*/
calcTemplateBuild(const DROID_TEMPLATE * psTemplate)1457 UDWORD calcTemplateBuild(const DROID_TEMPLATE *psTemplate)
1458 {
1459 	return calcBuild(psTemplate);
1460 }
1461 
calcDroidPoints(DROID * psDroid)1462 UDWORD calcDroidPoints(DROID *psDroid)
1463 {
1464 	return calcBuild(psDroid);
1465 }
1466 
1467 template <typename T>
calcPower(const T * obj)1468 static uint32_t calcPower(const T *obj)
1469 {
1470 	ASSERT_NOT_NULLPTR_OR_RETURN(0, obj);
1471 	return calcSum(obj, [](COMPONENT_STATS const &stat) {
1472 		return stat.buildPower;
1473 	}, [](BODY_STATS const &bodyStat, PROPULSION_STATS const &propStat) {
1474 		// Propulsion power points are a percentage of the body's power points.
1475 		return bodyStat.buildPower * (100 + propStat.buildPower) / 100;
1476 	});
1477 }
1478 
1479 /* Calculate the power points required to build/maintain a template */
calcTemplatePower(const DROID_TEMPLATE * psTemplate)1480 UDWORD calcTemplatePower(const DROID_TEMPLATE *psTemplate)
1481 {
1482 	return calcPower(psTemplate);
1483 }
1484 
1485 
1486 /* Calculate the power points required to build/maintain a droid */
calcDroidPower(const DROID * psDroid)1487 UDWORD calcDroidPower(const DROID *psDroid)
1488 {
1489 	return calcPower(psDroid);
1490 }
1491 
1492 //Builds an instance of a Droid - the x/y passed in are in world coords.
reallyBuildDroid(const DROID_TEMPLATE * pTemplate,Position pos,UDWORD player,bool onMission,Rotation rot)1493 DROID *reallyBuildDroid(const DROID_TEMPLATE *pTemplate, Position pos, UDWORD player, bool onMission, Rotation rot)
1494 {
1495 	// Don't use this assertion in single player, since droids can finish building while on an away mission
1496 	ASSERT(!bMultiPlayer || worldOnMap(pos.x, pos.y), "the build locations are not on the map");
1497 
1498 	DROID *psDroid = new DROID(generateSynchronisedObjectId(), player);
1499 	droidSetName(psDroid, getStatsName(pTemplate));
1500 
1501 	// Set the droids type
1502 	psDroid->droidType = droidTemplateType(pTemplate);  // Is set again later to the same thing, in droidSetBits.
1503 	psDroid->pos = pos;
1504 	psDroid->rot = rot;
1505 
1506 	//don't worry if not on homebase cos not being drawn yet
1507 	if (!onMission)
1508 	{
1509 		//set droid height
1510 		psDroid->pos.z = map_Height(psDroid->pos.x, psDroid->pos.y);
1511 	}
1512 
1513 	if (isTransporter(psDroid) || psDroid->droidType == DROID_COMMAND)
1514 	{
1515 		DROID_GROUP *psGrp = grpCreate();
1516 		psGrp->add(psDroid);
1517 	}
1518 
1519 	// find the highest stored experience
1520 	// Unless game time is stopped, then we're hopefully loading a game and
1521 	// don't want to use up recycled experience for the droids we just loaded.
1522 	if (!gameTimeIsStopped() &&
1523 	    (psDroid->droidType != DROID_CONSTRUCT) &&
1524 	    (psDroid->droidType != DROID_CYBORG_CONSTRUCT) &&
1525 	    (psDroid->droidType != DROID_REPAIR) &&
1526 	    (psDroid->droidType != DROID_CYBORG_REPAIR) &&
1527 	    !isTransporter(psDroid) &&
1528 	    !recycled_experience[psDroid->player].empty())
1529 	{
1530 		psDroid->experience = recycled_experience[psDroid->player].top();
1531 		recycled_experience[psDroid->player].pop();
1532 	}
1533 	else
1534 	{
1535 		psDroid->experience = 0;
1536 	}
1537 	psDroid->kills = 0;
1538 
1539 	droidSetBits(pTemplate, psDroid);
1540 
1541 	//calculate the droids total weight
1542 	psDroid->weight = calcDroidWeight(pTemplate);
1543 
1544 	// Initialise the movement stuff
1545 	psDroid->baseSpeed = calcDroidBaseSpeed(pTemplate, psDroid->weight, (UBYTE)player);
1546 
1547 	initDroidMovement(psDroid);
1548 
1549 	//allocate 'easy-access' data!
1550 	psDroid->body = calcDroidBaseBody(psDroid); // includes upgrades
1551 	ASSERT(psDroid->body > 0, "Invalid number of hitpoints");
1552 	psDroid->originalBody = psDroid->body;
1553 
1554 	/* Set droid's initial illumination */
1555 	psDroid->sDisplay.imd = BODY_IMD(psDroid, psDroid->player);
1556 
1557 	//don't worry if not on homebase cos not being drawn yet
1558 	if (!onMission)
1559 	{
1560 		/* People always stand upright */
1561 		if (psDroid->droidType != DROID_PERSON)
1562 		{
1563 			updateDroidOrientation(psDroid);
1564 		}
1565 		visTilesUpdate(psDroid);
1566 	}
1567 
1568 	/* transporter-specific stuff */
1569 	if (isTransporter(psDroid))
1570 	{
1571 		//add transporter launch button if selected player and not a reinforcable situation
1572 		if (player == selectedPlayer && !missionCanReEnforce())
1573 		{
1574 			(void)intAddTransporterLaunch(psDroid);
1575 		}
1576 
1577 		//set droid height to be above the terrain
1578 		psDroid->pos.z += TRANSPORTER_HOVER_HEIGHT;
1579 
1580 		/* reset halt secondary order from guard to hold */
1581 		secondarySetState(psDroid, DSO_HALTTYPE, DSS_HALT_HOLD);
1582 	}
1583 
1584 	if (player == selectedPlayer)
1585 	{
1586 		scoreUpdateVar(WD_UNITS_BUILT);
1587 	}
1588 
1589 	// Avoid droid appearing to jump or turn on spawn.
1590 	psDroid->prevSpacetime.pos = psDroid->pos;
1591 	psDroid->prevSpacetime.rot = psDroid->rot;
1592 
1593 	debug(LOG_LIFE, "created droid for player %d, droid = %p, id=%d (%s): position: x(%d)y(%d)z(%d)", player, static_cast<void *>(psDroid), (int)psDroid->id, psDroid->aName, psDroid->pos.x, psDroid->pos.y, psDroid->pos.z);
1594 
1595 	return psDroid;
1596 }
1597 
buildDroid(DROID_TEMPLATE * pTemplate,UDWORD x,UDWORD y,UDWORD player,bool onMission,const INITIAL_DROID_ORDERS * initialOrders,Rotation rot)1598 DROID *buildDroid(DROID_TEMPLATE *pTemplate, UDWORD x, UDWORD y, UDWORD player, bool onMission, const INITIAL_DROID_ORDERS *initialOrders, Rotation rot)
1599 {
1600 	// ajl. droid will be created, so inform others
1601 	if (bMultiMessages)
1602 	{
1603 		// Only sends if it's ours, otherwise the owner should send the message.
1604 		SendDroid(pTemplate, x, y, player, generateNewObjectId(), initialOrders);
1605 		return nullptr;
1606 	}
1607 	else
1608 	{
1609 		return reallyBuildDroid(pTemplate, Position(x, y, 0), player, onMission, rot);
1610 	}
1611 }
1612 
1613 //initialises the droid movement model
initDroidMovement(DROID * psDroid)1614 void initDroidMovement(DROID *psDroid)
1615 {
1616 	psDroid->sMove.asPath.clear();
1617 	psDroid->sMove.pathIndex = 0;
1618 }
1619 
1620 // Set the asBits in a DROID structure given it's template.
droidSetBits(const DROID_TEMPLATE * pTemplate,DROID * psDroid)1621 void droidSetBits(const DROID_TEMPLATE *pTemplate, DROID *psDroid)
1622 {
1623 	psDroid->droidType = droidTemplateType(pTemplate);
1624 	psDroid->numWeaps = pTemplate->numWeaps;
1625 	psDroid->body = calcTemplateBody(pTemplate, psDroid->player);
1626 	psDroid->originalBody = psDroid->body;
1627 	psDroid->expectedDamageDirect = 0;  // Begin life optimistically.
1628 	psDroid->expectedDamageIndirect = 0;  // Begin life optimistically.
1629 	psDroid->time = gameTime - deltaGameTime + 1;         // Start at beginning of tick.
1630 	psDroid->prevSpacetime.time = psDroid->time - 1;  // -1 for interpolation.
1631 
1632 	//create the droids weapons
1633 	for (int inc = 0; inc < MAX_WEAPONS; inc++)
1634 	{
1635 		psDroid->psActionTarget[inc] = nullptr;
1636 		psDroid->asWeaps[inc].lastFired = 0;
1637 		psDroid->asWeaps[inc].shotsFired = 0;
1638 		// no weapon (could be a construction droid for example)
1639 		// this is also used to check if a droid has a weapon, so zero it
1640 		psDroid->asWeaps[inc].nStat = 0;
1641 		psDroid->asWeaps[inc].ammo = 0;
1642 		psDroid->asWeaps[inc].rot.direction = 0;
1643 		psDroid->asWeaps[inc].rot.pitch = 0;
1644 		psDroid->asWeaps[inc].rot.roll = 0;
1645 		psDroid->asWeaps[inc].prevRot = psDroid->asWeaps[inc].rot;
1646 		psDroid->asWeaps[inc].origin = ORIGIN_UNKNOWN;
1647 		if (inc < pTemplate->numWeaps)
1648 		{
1649 			psDroid->asWeaps[inc].nStat = pTemplate->asWeaps[inc];
1650 			psDroid->asWeaps[inc].ammo = (asWeaponStats + psDroid->asWeaps[inc].nStat)->upgrade[psDroid->player].numRounds;
1651 		}
1652 		psDroid->asWeaps[inc].usedAmmo = 0;
1653 	}
1654 	memcpy(psDroid->asBits, pTemplate->asParts, sizeof(psDroid->asBits));
1655 
1656 	switch (getPropulsionStats(psDroid)->propulsionType)  // getPropulsionStats(psDroid) only defined after psDroid->asBits[COMP_PROPULSION] is set.
1657 	{
1658 	case PROPULSION_TYPE_LIFT:
1659 		psDroid->blockedBits = AIR_BLOCKED;
1660 		break;
1661 	case PROPULSION_TYPE_HOVER:
1662 		psDroid->blockedBits = FEATURE_BLOCKED;
1663 		break;
1664 	case PROPULSION_TYPE_PROPELLOR:
1665 		psDroid->blockedBits = FEATURE_BLOCKED | LAND_BLOCKED;
1666 		break;
1667 	default:
1668 		psDroid->blockedBits = FEATURE_BLOCKED | WATER_BLOCKED;
1669 		break;
1670 	}
1671 }
1672 
1673 
1674 // Sets the parts array in a template given a droid.
templateSetParts(const DROID * psDroid,DROID_TEMPLATE * psTemplate)1675 void templateSetParts(const DROID *psDroid, DROID_TEMPLATE *psTemplate)
1676 {
1677 	psTemplate->numWeaps = 0;
1678 	psTemplate->droidType = psDroid->droidType;
1679 	for (int inc = 0; inc < MAX_WEAPONS; inc++)
1680 	{
1681 		//this should fix the NULL weapon stats for empty weaponslots
1682 		psTemplate->asWeaps[inc] = 0;
1683 		if (psDroid->asWeaps[inc].nStat > 0)
1684 		{
1685 			psTemplate->numWeaps += 1;
1686 			psTemplate->asWeaps[inc] = psDroid->asWeaps[inc].nStat;
1687 		}
1688 	}
1689 	memcpy(psTemplate->asParts, psDroid->asBits, sizeof(psDroid->asBits));
1690 }
1691 
1692 /* Make all the droids for a certain player a member of a specific group */
assignDroidsToGroup(UDWORD playerNumber,UDWORD groupNumber,bool clearGroup)1693 void assignDroidsToGroup(UDWORD	playerNumber, UDWORD groupNumber, bool clearGroup)
1694 {
1695 	DROID	*psDroid;
1696 	bool	bAtLeastOne = false;
1697 	FLAG_POSITION	*psFlagPos;
1698 
1699 	if (groupNumber < UBYTE_MAX)
1700 	{
1701 		/* Run through all the droids */
1702 		for (psDroid = apsDroidLists[playerNumber]; psDroid != nullptr; psDroid = psDroid->psNext)
1703 		{
1704 			/* Clear out the old ones */
1705 			if (clearGroup && psDroid->group == groupNumber)
1706 			{
1707 				psDroid->group = UBYTE_MAX;
1708 			}
1709 
1710 			/* Only assign the currently selected ones */
1711 			if (psDroid->selected)
1712 			{
1713 				/* Set them to the right group - they can only be a member of one group */
1714 				psDroid->group = (UBYTE)groupNumber;
1715 				bAtLeastOne = true;
1716 			}
1717 		}
1718 	}
1719 	if (bAtLeastOne)
1720 	{
1721 		//clear the Deliv Point if one
1722 		for (psFlagPos = apsFlagPosLists[selectedPlayer]; psFlagPos;
1723 		     psFlagPos = psFlagPos->psNext)
1724 		{
1725 			psFlagPos->selected = false;
1726 		}
1727 		groupConsoleInformOfCreation(groupNumber);
1728 		secondarySetAverageGroupState(selectedPlayer, groupNumber);
1729 	}
1730 }
1731 
1732 
activateGroupAndMove(UDWORD playerNumber,UDWORD groupNumber)1733 bool activateGroupAndMove(UDWORD playerNumber, UDWORD groupNumber)
1734 {
1735 	DROID	*psDroid, *psCentreDroid = nullptr;
1736 	bool selected = false;
1737 	FLAG_POSITION	*psFlagPos;
1738 
1739 	if (groupNumber < UBYTE_MAX)
1740 	{
1741 		for (psDroid = apsDroidLists[playerNumber]; psDroid != nullptr; psDroid = psDroid->psNext)
1742 		{
1743 			/* Wipe out the ones in the wrong group */
1744 			if (psDroid->selected && psDroid->group != groupNumber)
1745 			{
1746 				DeSelectDroid(psDroid);
1747 			}
1748 			/* Get the right ones */
1749 			if (psDroid->group == groupNumber)
1750 			{
1751 				SelectDroid(psDroid);
1752 				psCentreDroid = psDroid;
1753 			}
1754 		}
1755 
1756 		/* There was at least one in the group */
1757 		if (psCentreDroid)
1758 		{
1759 			//clear the Deliv Point if one
1760 			for (psFlagPos = apsFlagPosLists[selectedPlayer]; psFlagPos;
1761 			     psFlagPos = psFlagPos->psNext)
1762 			{
1763 				psFlagPos->selected = false;
1764 			}
1765 
1766 			selected = true;
1767 			if (getWarCamStatus())
1768 			{
1769 				camToggleStatus();			 // messy - fix this
1770 				processWarCam(); //odd, but necessary
1771 				camToggleStatus();				// messy - FIXME
1772 			}
1773 			else
1774 			{
1775 				/* Centre display on him if warcam isn't active */
1776 				setViewPos(map_coord(psCentreDroid->pos.x), map_coord(psCentreDroid->pos.y), true);
1777 			}
1778 		}
1779 	}
1780 
1781 	if (selected)
1782 	{
1783 		groupConsoleInformOfCentering(groupNumber);
1784 	}
1785 
1786 	return selected;
1787 }
1788 
activateGroup(UDWORD playerNumber,UDWORD groupNumber)1789 bool activateGroup(UDWORD playerNumber, UDWORD groupNumber)
1790 {
1791 	DROID	*psDroid;
1792 	bool selected = false;
1793 	FLAG_POSITION	*psFlagPos;
1794 
1795 	if (groupNumber < UBYTE_MAX)
1796 	{
1797 		for (psDroid = apsDroidLists[playerNumber]; psDroid; psDroid = psDroid->psNext)
1798 		{
1799 			/* Wipe out the ones in the wrong group */
1800 			if (psDroid->selected && psDroid->group != groupNumber)
1801 			{
1802 				DeSelectDroid(psDroid);
1803 			}
1804 			/* Get the right ones */
1805 			if (psDroid->group == groupNumber)
1806 			{
1807 				SelectDroid(psDroid);
1808 				selected = true;
1809 			}
1810 		}
1811 	}
1812 
1813 	if (selected)
1814 	{
1815 		//clear the Deliv Point if one
1816 		for (psFlagPos = apsFlagPosLists[selectedPlayer]; psFlagPos;
1817 		     psFlagPos = psFlagPos->psNext)
1818 		{
1819 			psFlagPos->selected = false;
1820 		}
1821 		groupConsoleInformOfSelection(groupNumber);
1822 	}
1823 	return selected;
1824 }
1825 
groupConsoleInformOfSelection(UDWORD groupNumber)1826 void	groupConsoleInformOfSelection(UDWORD groupNumber)
1827 {
1828 	unsigned int num_selected = selNumSelected(selectedPlayer);
1829 
1830 	CONPRINTF(ngettext("Group %u selected - %u Unit", "Group %u selected - %u Units", num_selected), groupNumber, num_selected);
1831 }
1832 
groupConsoleInformOfCreation(UDWORD groupNumber)1833 void	groupConsoleInformOfCreation(UDWORD groupNumber)
1834 {
1835 	if (!getWarCamStatus())
1836 	{
1837 		unsigned int num_selected = selNumSelected(selectedPlayer);
1838 
1839 		CONPRINTF(ngettext("%u unit assigned to Group %u", "%u units assigned to Group %u", num_selected), num_selected, groupNumber);
1840 	}
1841 
1842 }
1843 
groupConsoleInformOfCentering(UDWORD groupNumber)1844 void	groupConsoleInformOfCentering(UDWORD groupNumber)
1845 {
1846 	unsigned int num_selected = selNumSelected(selectedPlayer);
1847 
1848 	if (!getWarCamStatus())
1849 	{
1850 		CONPRINTF(ngettext("Centered on Group %u - %u Unit", "Centered on Group %u - %u Units", num_selected), groupNumber, num_selected);
1851 	}
1852 	else
1853 	{
1854 		CONPRINTF(ngettext("Aligning with Group %u - %u Unit", "Aligning with Group %u - %u Units", num_selected), groupNumber, num_selected);
1855 	}
1856 }
1857 
1858 /**
1859  * calculate muzzle base location in 3d world
1860  */
calcDroidMuzzleBaseLocation(const DROID * psDroid,Vector3i * muzzle,int weapon_slot)1861 bool calcDroidMuzzleBaseLocation(const DROID *psDroid, Vector3i *muzzle, int weapon_slot)
1862 {
1863 	const iIMDShape *psBodyImd = BODY_IMD(psDroid, psDroid->player);
1864 
1865 	CHECK_DROID(psDroid);
1866 
1867 	if (psBodyImd && psBodyImd->nconnectors)
1868 	{
1869 		Vector3i barrel(0, 0, 0);
1870 
1871 		Affine3F af;
1872 
1873 		af.Trans(psDroid->pos.x, -psDroid->pos.z, psDroid->pos.y);
1874 
1875 		//matrix = the center of droid
1876 		af.RotY(psDroid->rot.direction);
1877 		af.RotX(psDroid->rot.pitch);
1878 		af.RotZ(-psDroid->rot.roll);
1879 		af.Trans(psBodyImd->connectors[weapon_slot].x, -psBodyImd->connectors[weapon_slot].z,
1880 		         -psBodyImd->connectors[weapon_slot].y);//note y and z flipped
1881 
1882 		*muzzle = (af * barrel).xzy();
1883 		muzzle->z = -muzzle->z;
1884 	}
1885 	else
1886 	{
1887 		*muzzle = psDroid->pos + Vector3i(0, 0, psDroid->sDisplay.imd->max.y);
1888 	}
1889 
1890 	CHECK_DROID(psDroid);
1891 
1892 	return true;
1893 }
1894 
1895 /**
1896  * calculate muzzle tip location in 3d world
1897  */
calcDroidMuzzleLocation(const DROID * psDroid,Vector3i * muzzle,int weapon_slot)1898 bool calcDroidMuzzleLocation(const DROID *psDroid, Vector3i *muzzle, int weapon_slot)
1899 {
1900 	const iIMDShape *psBodyImd = BODY_IMD(psDroid, psDroid->player);
1901 
1902 	CHECK_DROID(psDroid);
1903 
1904 	if (psBodyImd && psBodyImd->nconnectors)
1905 	{
1906 		char debugStr[250], debugLen = 0;  // Each "(%d,%d,%d)" uses up to 34 bytes, for very large values. So 250 isn't exaggerating.
1907 
1908 		Vector3i barrel(0, 0, 0);
1909 		const iIMDShape *psWeaponImd = nullptr, *psMountImd = nullptr;
1910 
1911 		if (psDroid->asWeaps[weapon_slot].nStat)
1912 		{
1913 			psMountImd = WEAPON_MOUNT_IMD(psDroid, weapon_slot);
1914 			psWeaponImd = WEAPON_IMD(psDroid, weapon_slot);
1915 		}
1916 
1917 		Affine3F af;
1918 
1919 		af.Trans(psDroid->pos.x, -psDroid->pos.z, psDroid->pos.y);
1920 
1921 		//matrix = the center of droid
1922 		af.RotY(psDroid->rot.direction);
1923 		af.RotX(psDroid->rot.pitch);
1924 		af.RotZ(-psDroid->rot.roll);
1925 		af.Trans(psBodyImd->connectors[weapon_slot].x, -psBodyImd->connectors[weapon_slot].z,
1926 		         -psBodyImd->connectors[weapon_slot].y);//note y and z flipped
1927 		debugLen += sprintf(debugStr + debugLen, "connect:body[%d]=(%d,%d,%d)", weapon_slot, psBodyImd->connectors[weapon_slot].x, -psBodyImd->connectors[weapon_slot].z, -psBodyImd->connectors[weapon_slot].y);
1928 
1929 		//matrix = the weapon[slot] mount on the body
1930 		af.RotY(psDroid->asWeaps[weapon_slot].rot.direction);  // +ve anticlockwise
1931 
1932 		// process turret mount
1933 		if (psMountImd && psMountImd->nconnectors)
1934 		{
1935 			af.Trans(psMountImd->connectors->x, -psMountImd->connectors->z, -psMountImd->connectors->y);
1936 			debugLen += sprintf(debugStr + debugLen, ",turret=(%d,%d,%d)", psMountImd->connectors->x, -psMountImd->connectors->z, -psMountImd->connectors->y);
1937 		}
1938 
1939 		//matrix = the turret connector for the gun
1940 		af.RotX(psDroid->asWeaps[weapon_slot].rot.pitch);      // +ve up
1941 
1942 		//process the gun
1943 		if (psWeaponImd && psWeaponImd->nconnectors)
1944 		{
1945 			unsigned int connector_num = 0;
1946 
1947 			// which barrel is firing if model have multiple muzzle connectors?
1948 			if (psDroid->asWeaps[weapon_slot].shotsFired && (psWeaponImd->nconnectors > 1))
1949 			{
1950 				// shoot first, draw later - substract one shot to get correct results
1951 				connector_num = (psDroid->asWeaps[weapon_slot].shotsFired - 1) % (psWeaponImd->nconnectors);
1952 			}
1953 
1954 			barrel = Vector3i(psWeaponImd->connectors[connector_num].x,
1955 			                  -psWeaponImd->connectors[connector_num].z,
1956 			                  -psWeaponImd->connectors[connector_num].y);
1957 			debugLen += sprintf(debugStr + debugLen, ",barrel[%u]=(%d,%d,%d)", connector_num, psWeaponImd->connectors[connector_num].x, -psWeaponImd->connectors[connector_num].y, -psWeaponImd->connectors[connector_num].z);
1958 		}
1959 
1960 		*muzzle = (af * barrel).xzy();
1961 		muzzle->z = -muzzle->z;
1962 		sprintf(debugStr + debugLen, ",muzzle=(%d,%d,%d)", muzzle->x, muzzle->y, muzzle->z);
1963 
1964 		syncDebug("%s", debugStr);
1965 	}
1966 	else
1967 	{
1968 		*muzzle = psDroid->pos + Vector3i(0, 0, psDroid->sDisplay.imd->max.y);
1969 	}
1970 
1971 	CHECK_DROID(psDroid);
1972 
1973 	return true;
1974 }
1975 
1976 struct rankMap
1977 {
1978 	unsigned int kills;          // required minimum amount of kills to reach this rank
1979 	unsigned int commanderKills; // required minimum amount of kills for a commander (or sensor) to reach this rank
1980 	const char  *name;           // name of this rank
1981 };
1982 
getDroidLevel(const DROID * psDroid)1983 unsigned int getDroidLevel(const DROID *psDroid)
1984 {
1985 	unsigned int numKills = psDroid->experience / 65536;
1986 	unsigned int i;
1987 
1988 	// Search through the array of ranks until one is found
1989 	// which requires more kills than the droid has.
1990 	// Then fall back to the previous rank.
1991 	const BRAIN_STATS *psStats = getBrainStats(psDroid);
1992 	auto &vec = psStats->upgrade[psDroid->player].rankThresholds;
1993 	for (i = 1; i < vec.size(); ++i)
1994 	{
1995 		if (numKills < vec.at(i))
1996 		{
1997 			return i - 1;
1998 		}
1999 	}
2000 
2001 	// If the criteria of the last rank are met, then select the last one
2002 	return vec.size() - 1;
2003 }
2004 
getDroidEffectiveLevel(const DROID * psDroid)2005 UDWORD getDroidEffectiveLevel(const DROID *psDroid)
2006 {
2007 	UDWORD level = getDroidLevel(psDroid);
2008 	UDWORD cmdLevel = 0;
2009 
2010 	// get commander level
2011 	if (hasCommander(psDroid))
2012 	{
2013 		cmdLevel = cmdGetCommanderLevel(psDroid);
2014 
2015 		// Commanders boost units' effectiveness just by being assigned to it
2016 		level++;
2017 	}
2018 
2019 	return MAX(level, cmdLevel);
2020 }
2021 
getDroidLevelName(const DROID * psDroid)2022 const char *getDroidLevelName(const DROID *psDroid)
2023 {
2024 	const BRAIN_STATS *psStats = getBrainStats(psDroid);
2025 	return PE_("rank", psStats->rankNames[getDroidLevel(psDroid)].c_str());
2026 }
2027 
getNumDroidsForLevel(uint32_t player,UDWORD level)2028 UDWORD	getNumDroidsForLevel(uint32_t player, UDWORD level)
2029 {
2030 	DROID	*psDroid;
2031 	UDWORD	count;
2032 
2033 	ASSERT_OR_RETURN(0, player < MAX_PLAYERS, "invalid player idx: %" PRIu32 "", player);
2034 
2035 	for (psDroid = apsDroidLists[player], count = 0;
2036 	     psDroid; psDroid = psDroid->psNext)
2037 	{
2038 		if (getDroidLevel(psDroid) == level)
2039 		{
2040 			count++;
2041 		}
2042 	}
2043 
2044 	return count;
2045 }
2046 
2047 // Get the name of a droid from it's DROID structure.
2048 //
droidGetName(const DROID * psDroid)2049 const char *droidGetName(const DROID *psDroid)
2050 {
2051 	ASSERT_NOT_NULLPTR_OR_RETURN("", psDroid);
2052 	return psDroid->aName;
2053 }
2054 
2055 //
2056 // Set the name of a droid in it's DROID structure.
2057 //
2058 // - only possible on the PC where you can adjust the names,
2059 //
droidSetName(DROID * psDroid,const char * pName)2060 void droidSetName(DROID *psDroid, const char *pName)
2061 {
2062 	sstrcpy(psDroid->aName, pName);
2063 }
2064 
2065 // ////////////////////////////////////////////////////////////////////////////
2066 // returns true when no droid on x,y square.
noDroid(UDWORD x,UDWORD y)2067 bool noDroid(UDWORD x, UDWORD y)
2068 {
2069 	unsigned int i;
2070 
2071 	// check each droid list
2072 	for (i = 0; i < MAX_PLAYERS; ++i)
2073 	{
2074 		const DROID *psDroid;
2075 		for (psDroid = apsDroidLists[i]; psDroid; psDroid = psDroid->psNext)
2076 		{
2077 			if (map_coord(psDroid->pos.x) == x
2078 			    && map_coord(psDroid->pos.y) == y)
2079 			{
2080 				return false;
2081 			}
2082 		}
2083 	}
2084 	return true;
2085 }
2086 
2087 // ////////////////////////////////////////////////////////////////////////////
2088 // returns true when at most one droid on x,y square.
oneDroidMax(UDWORD x,UDWORD y)2089 static bool oneDroidMax(UDWORD x, UDWORD y)
2090 {
2091 	UDWORD i;
2092 	bool bFound = false;
2093 	DROID *pD;
2094 	// check each droid list
2095 	for (i = 0; i < MAX_PLAYERS; i++)
2096 	{
2097 		for (pD = apsDroidLists[i]; pD ; pD = pD->psNext)
2098 		{
2099 			if (map_coord(pD->pos.x) == x
2100 			    && map_coord(pD->pos.y) == y)
2101 			{
2102 				if (bFound)
2103 				{
2104 					return false;
2105 				}
2106 
2107 				bFound = true;//first droid on this square so continue
2108 			}
2109 		}
2110 	}
2111 	return true;
2112 }
2113 
2114 // ////////////////////////////////////////////////////////////////////////////
2115 // returns true if it's a sensible place to put that droid.
sensiblePlace(SDWORD x,SDWORD y,PROPULSION_TYPE propulsion)2116 static bool sensiblePlace(SDWORD x, SDWORD y, PROPULSION_TYPE propulsion)
2117 {
2118 	// not too near the edges.
2119 	if ((x < TOO_NEAR_EDGE) || (x > (SDWORD)(mapWidth - TOO_NEAR_EDGE)))
2120 	{
2121 		return false;
2122 	}
2123 	if ((y < TOO_NEAR_EDGE) || (y > (SDWORD)(mapHeight - TOO_NEAR_EDGE)))
2124 	{
2125 		return false;
2126 	}
2127 
2128 	// not on a blocking tile.
2129 	if (fpathBlockingTile(x, y, propulsion))
2130 	{
2131 		return false;
2132 	}
2133 
2134 	return true;
2135 }
2136 
2137 // ------------------------------------------------------------------------------------
2138 // Should stop things being placed in inaccessible areas? Assume wheeled propulsion.
zonedPAT(UDWORD x,UDWORD y)2139 bool	zonedPAT(UDWORD x, UDWORD y)
2140 {
2141 	return sensiblePlace(x, y, PROPULSION_TYPE_WHEELED) && noDroid(x, y);
2142 }
2143 
canFitDroid(UDWORD x,UDWORD y)2144 static bool canFitDroid(UDWORD x, UDWORD y)
2145 {
2146 	return sensiblePlace(x, y, PROPULSION_TYPE_WHEELED) && oneDroidMax(x, y);
2147 }
2148 
2149 /// find a tile for which the function will return true
pickATileGen(UDWORD * x,UDWORD * y,UBYTE numIterations,bool (* function)(UDWORD x,UDWORD y))2150 bool	pickATileGen(UDWORD *x, UDWORD *y, UBYTE numIterations,
2151                      bool (*function)(UDWORD x, UDWORD y))
2152 {
2153 	return pickATileGenThreat(x, y, numIterations, -1, -1, function);
2154 }
2155 
pickATileGen(Vector2i * pos,unsigned numIterations,bool (* function)(UDWORD x,UDWORD y))2156 bool pickATileGen(Vector2i *pos, unsigned numIterations, bool (*function)(UDWORD x, UDWORD y))
2157 {
2158 	UDWORD x = pos->x, y = pos->y;
2159 	bool ret = pickATileGenThreat(&x, &y, numIterations, -1, -1, function);
2160 	*pos = Vector2i(x, y);
2161 	return ret;
2162 }
2163 
ThreatInRange(SDWORD player,SDWORD range,SDWORD rangeX,SDWORD rangeY,bool bVTOLs)2164 static bool ThreatInRange(SDWORD player, SDWORD range, SDWORD rangeX, SDWORD rangeY, bool bVTOLs)
2165 {
2166 	UDWORD				i, structType;
2167 	STRUCTURE			*psStruct;
2168 	DROID				*psDroid;
2169 
2170 	const int tx = map_coord(rangeX);
2171 	const int ty = map_coord(rangeY);
2172 
2173 	for (i = 0; i < MAX_PLAYERS; i++)
2174 	{
2175 		if ((alliances[player][i] == ALLIANCE_FORMED) || (i == player))
2176 		{
2177 			continue;
2178 		}
2179 
2180 		//check structures
2181 		for (psStruct = apsStructLists[i]; psStruct; psStruct = psStruct->psNext)
2182 		{
2183 			if (psStruct->visible[player] || psStruct->born == 2)	// if can see it or started there
2184 			{
2185 				if (psStruct->status == SS_BUILT)
2186 				{
2187 					structType = psStruct->pStructureType->type;
2188 
2189 					switch (structType)		//dangerous to get near these structures
2190 					{
2191 					case REF_DEFENSE:
2192 					case REF_CYBORG_FACTORY:
2193 					case REF_FACTORY:
2194 					case REF_VTOL_FACTORY:
2195 					case REF_REARM_PAD:
2196 
2197 						if (range < 0
2198 						    || world_coord(hypotf(tx - map_coord(psStruct->pos.x), ty - map_coord(psStruct->pos.y))) < range)	//enemy in range
2199 						{
2200 							return true;
2201 						}
2202 
2203 						break;
2204 					}
2205 				}
2206 			}
2207 		}
2208 
2209 		//check droids
2210 		for (psDroid = apsDroidLists[i]; psDroid; psDroid = psDroid->psNext)
2211 		{
2212 			if (psDroid->visible[player])		//can see this droid?
2213 			{
2214 				if (!objHasWeapon((BASE_OBJECT *)psDroid))
2215 				{
2216 					continue;
2217 				}
2218 
2219 				//if VTOLs are excluded, skip them
2220 				if (!bVTOLs && ((asPropulsionStats[psDroid->asBits[COMP_PROPULSION]].propulsionType == PROPULSION_TYPE_LIFT) || isTransporter(psDroid)))
2221 				{
2222 					continue;
2223 				}
2224 
2225 				if (range < 0
2226 				    || world_coord(hypotf(tx - map_coord(psDroid->pos.x), ty - map_coord(psDroid->pos.y))) < range)	//enemy in range
2227 				{
2228 					return true;
2229 				}
2230 			}
2231 		}
2232 	}
2233 
2234 	return false;
2235 }
2236 
2237 /// find a tile for which the passed function will return true without any threat in the specified range
pickATileGenThreat(UDWORD * x,UDWORD * y,UBYTE numIterations,SDWORD threatRange,SDWORD player,bool (* function)(UDWORD x,UDWORD y))2238 bool	pickATileGenThreat(UDWORD *x, UDWORD *y, UBYTE numIterations, SDWORD threatRange,
2239                            SDWORD player, bool (*function)(UDWORD x, UDWORD y))
2240 {
2241 	SDWORD		i, j;
2242 	SDWORD		startX, endX, startY, endY;
2243 	UDWORD		passes;
2244 	Vector3i	origin(world_coord(*x), world_coord(*y), 0);
2245 
2246 	ASSERT_OR_RETURN(false, *x < mapWidth, "x coordinate is off-map for pickATileGen");
2247 	ASSERT_OR_RETURN(false, *y < mapHeight, "y coordinate is off-map for pickATileGen");
2248 
2249 	if (function(*x, *y) && ((threatRange <= 0) || (!ThreatInRange(player, threatRange, *x, *y, false))))	//TODO: vtol check really not needed?
2250 	{
2251 		return (true);
2252 	}
2253 
2254 	/* Initial box dimensions and set iteration count to zero */
2255 	startX = endX = *x;	startY = endY = *y;	passes = 0;
2256 
2257 	/* Keep going until we get a tile or we exceed distance */
2258 	while (passes < numIterations)
2259 	{
2260 		/* Process whole box */
2261 		for (i = startX; i <= endX; i++)
2262 		{
2263 			for (j = startY; j <= endY; j++)
2264 			{
2265 				/* Test only perimeter as internal tested previous iteration */
2266 				if (i == startX || i == endX || j == startY || j == endY)
2267 				{
2268 					Vector3i newPos(world_coord(i), world_coord(j), 0);
2269 
2270 					/* Good enough? */
2271 					if (function(i, j)
2272 					    && fpathCheck(origin, newPos, PROPULSION_TYPE_WHEELED)
2273 					    && ((threatRange <= 0) || (!ThreatInRange(player, threatRange, world_coord(i), world_coord(j), false))))
2274 					{
2275 						/* Set exit conditions and get out NOW */
2276 						*x = i;	*y = j;
2277 						return true;
2278 					}
2279 				}
2280 			}
2281 		}
2282 		/* Expand the box out in all directions - off map handled by tileAcceptable */
2283 		startX--; startY--;	endX++;	endY++;	passes++;
2284 	}
2285 	/* If we got this far, then we failed - passed in values will be unchanged */
2286 	return false;
2287 
2288 }
2289 
2290 /// find a tile for a wheeled droid with only one other droid present
pickHalfATile(UDWORD * x,UDWORD * y,UBYTE numIterations)2291 PICKTILE pickHalfATile(UDWORD *x, UDWORD *y, UBYTE numIterations)
2292 {
2293 	return pickATileGen(x, y, numIterations, canFitDroid) ? FREE_TILE : NO_FREE_TILE;
2294 }
2295 
2296 /* Looks through the players list of droids to see if any of them are
2297 building the specified structure - returns true if finds one*/
checkDroidsBuilding(STRUCTURE * psStructure)2298 bool checkDroidsBuilding(STRUCTURE *psStructure)
2299 {
2300 	DROID				*psDroid;
2301 
2302 	for (psDroid = apsDroidLists[psStructure->player]; psDroid != nullptr; psDroid =
2303 	         psDroid->psNext)
2304 	{
2305 		//check DORDER_BUILD, HELP_BUILD is handled the same
2306 		BASE_OBJECT *const psStruct = orderStateObj(psDroid, DORDER_BUILD);
2307 		if ((STRUCTURE *)psStruct == psStructure)
2308 		{
2309 			return true;
2310 		}
2311 	}
2312 	return false;
2313 }
2314 
2315 /* Looks through the players list of droids to see if any of them are
2316 demolishing the specified structure - returns true if finds one*/
checkDroidsDemolishing(STRUCTURE * psStructure)2317 bool checkDroidsDemolishing(STRUCTURE *psStructure)
2318 {
2319 	DROID				*psDroid;
2320 
2321 	for (psDroid = apsDroidLists[psStructure->player]; psDroid != nullptr; psDroid =
2322 	         psDroid->psNext)
2323 	{
2324 		//check DORDER_DEMOLISH
2325 		BASE_OBJECT *const psStruct = orderStateObj(psDroid, DORDER_DEMOLISH);
2326 		if ((STRUCTURE *)psStruct == psStructure)
2327 		{
2328 			return true;
2329 		}
2330 	}
2331 	return false;
2332 }
2333 
2334 
nextModuleToBuild(STRUCTURE const * psStruct,int lastOrderedModule)2335 int nextModuleToBuild(STRUCTURE const *psStruct, int lastOrderedModule)
2336 {
2337 	int order = 0;
2338 	UDWORD	i = 0;
2339 
2340 	ASSERT_OR_RETURN(0, psStruct != nullptr && psStruct->pStructureType != nullptr, "Invalid structure pointer");
2341 
2342 	int next = psStruct->status == SS_BUILT ? 1 : 0; // If complete, next is one after the current number of modules, otherwise next is the one we're working on.
2343 	int max;
2344 	switch (psStruct->pStructureType->type)
2345 	{
2346 	case REF_POWER_GEN:
2347 		//check room for one more!
2348 		max = std::max<int>(psStruct->capacity + next, lastOrderedModule + 1);
2349 		if (max <= 1)
2350 		{
2351 			i = powerModuleStat;
2352 			order = max;
2353 		}
2354 		break;
2355 	case REF_FACTORY:
2356 	case REF_VTOL_FACTORY:
2357 		//check room for one more!
2358 		max = std::max<int>(psStruct->capacity + next, lastOrderedModule + 1);
2359 		if (max <= NUM_FACTORY_MODULES)
2360 		{
2361 			i = factoryModuleStat;
2362 			order = max;
2363 		}
2364 		break;
2365 	case REF_RESEARCH:
2366 		//check room for one more!
2367 		max = std::max<int>(psStruct->capacity + next, lastOrderedModule + 1);
2368 		if (max <= 1)
2369 		{
2370 			i = researchModuleStat;
2371 			order = max;  // Research modules are weird. Build one, get three free.
2372 		}
2373 		break;
2374 	default:
2375 		//no other structures can have modules attached
2376 		break;
2377 	}
2378 
2379 	if (order)
2380 	{
2381 		// Check availability of Module
2382 		if (!((i < numStructureStats) &&
2383 		      (apStructTypeLists[psStruct->player][i] == AVAILABLE)))
2384 		{
2385 			order = 0;
2386 		}
2387 	}
2388 
2389 	return order;
2390 }
2391 
2392 /*Deals with building a module - checking if any droid is currently doing this
2393  - if so, helping to build the current one*/
setUpBuildModule(DROID * psDroid)2394 void setUpBuildModule(DROID *psDroid)
2395 {
2396 	Vector2i tile = map_coord(psDroid->order.pos);
2397 
2398 	//check not another Truck started
2399 	STRUCTURE *psStruct = getTileStructure(tile.x, tile.y);
2400 	if (psStruct)
2401 	{
2402 		// if a droid is currently building, or building is in progress of being built/upgraded the droid's order should be DORDER_HELPBUILD
2403 		if (checkDroidsBuilding(psStruct) || !psStruct->status)
2404 		{
2405 			//set up the help build scenario
2406 			psDroid->order.type = DORDER_HELPBUILD;
2407 			setDroidTarget(psDroid, (BASE_OBJECT *)psStruct);
2408 			if (droidStartBuild(psDroid))
2409 			{
2410 				psDroid->action = DACTION_BUILD;
2411 				return;
2412 			}
2413 		}
2414 		else
2415 		{
2416 			if (nextModuleToBuild(psStruct, -1) > 0)
2417 			{
2418 				//no other droids building so just start it off
2419 				if (droidStartBuild(psDroid))
2420 				{
2421 					psDroid->action = DACTION_BUILD;
2422 					return;
2423 				}
2424 			}
2425 		}
2426 	}
2427 	cancelBuild(psDroid);
2428 }
2429 
2430 /* Just returns true if the droid's present body points aren't as high as the original*/
droidIsDamaged(const DROID * psDroid)2431 bool	droidIsDamaged(const DROID *psDroid)
2432 {
2433 	if (psDroid->body < psDroid->originalBody)
2434 	{
2435 		return (true);
2436 	}
2437 	else
2438 	{
2439 		return (false);
2440 	}
2441 }
2442 
2443 
getDroidResourceName(char const * pName)2444 char const *getDroidResourceName(char const *pName)
2445 {
2446 	/* See if the name has a string resource associated with it by trying
2447 	 * to get the string resource.
2448 	 */
2449 	return strresGetString(psStringRes, pName);
2450 }
2451 
2452 
2453 /*checks to see if an electronic warfare weapon is attached to the droid*/
electronicDroid(const DROID * psDroid)2454 bool electronicDroid(const DROID *psDroid)
2455 {
2456 	CHECK_DROID(psDroid);
2457 
2458 	//use slot 0 for now
2459 	if (psDroid->numWeaps > 0 && asWeaponStats[psDroid->asWeaps[0].nStat].weaponSubClass == WSC_ELECTRONIC)
2460 	{
2461 		return true;
2462 	}
2463 
2464 	if (psDroid->droidType == DROID_COMMAND && psDroid->psGroup && psDroid->psGroup->psCommander == psDroid)
2465 	{
2466 		// if a commander has EW units attached it is electronic
2467 		for (const DROID *psCurr = psDroid->psGroup->psList; psCurr; psCurr = psCurr->psGrpNext)
2468 		{
2469 			if (psDroid != psCurr && electronicDroid(psCurr))
2470 			{
2471 				return true;
2472 			}
2473 		}
2474 	}
2475 
2476 	return false;
2477 }
2478 
2479 /*checks to see if the droid is currently being repaired by another*/
droidUnderRepair(const DROID * psDroid)2480 bool droidUnderRepair(const DROID *psDroid)
2481 {
2482 	CHECK_DROID(psDroid);
2483 
2484 	//droid must be damaged
2485 	if (droidIsDamaged(psDroid))
2486 	{
2487 		//look thru the list of players droids to see if any are repairing this droid
2488 		for (const DROID *psCurr = apsDroidLists[psDroid->player]; psCurr != nullptr; psCurr = psCurr->psNext)
2489 		{
2490 			if ((psCurr->droidType == DROID_REPAIR || psCurr->droidType ==
2491 			     DROID_CYBORG_REPAIR) && psCurr->action ==
2492 			    DACTION_DROIDREPAIR && psCurr->order.psObj == psDroid)
2493 			{
2494 				return true;
2495 			}
2496 		}
2497 	}
2498 	return false;
2499 }
2500 
2501 //count how many Command Droids exist in the world at any one moment
checkCommandExist(UBYTE player)2502 UBYTE checkCommandExist(UBYTE player)
2503 {
2504 	UBYTE	quantity = 0;
2505 
2506 	for (DROID *psDroid = apsDroidLists[player]; psDroid != nullptr; psDroid = psDroid->psNext)
2507 	{
2508 		if (psDroid->droidType == DROID_COMMAND)
2509 		{
2510 			quantity++;
2511 		}
2512 	}
2513 	return quantity;
2514 }
2515 
isTransporter(DROID_TYPE type)2516 static inline bool isTransporter(DROID_TYPE type)
2517 {
2518 	return type == DROID_TRANSPORTER || type == DROID_SUPERTRANSPORTER;
2519 }
2520 
isTransporter(DROID const * psDroid)2521 bool isTransporter(DROID const *psDroid)
2522 {
2523 	return isTransporter(psDroid->droidType);
2524 }
2525 
isTransporter(DROID_TEMPLATE const * psTemplate)2526 bool isTransporter(DROID_TEMPLATE const *psTemplate)
2527 {
2528 	return isTransporter(psTemplate->droidType);
2529 }
2530 
2531 //access functions for vtols
isVtolDroid(const DROID * psDroid)2532 bool isVtolDroid(const DROID *psDroid)
2533 {
2534 	return asPropulsionStats[psDroid->asBits[COMP_PROPULSION]].propulsionType == PROPULSION_TYPE_LIFT
2535 	       && !isTransporter(psDroid);
2536 }
2537 
2538 /* returns true if the droid has lift propulsion and is moving */
isFlying(const DROID * psDroid)2539 bool isFlying(const DROID *psDroid)
2540 {
2541 	return (asPropulsionStats + psDroid->asBits[COMP_PROPULSION])->propulsionType == PROPULSION_TYPE_LIFT
2542 	       && (psDroid->sMove.Status != MOVEINACTIVE || isTransporter(psDroid));
2543 }
2544 
2545 /* returns true if it's a VTOL weapon droid which has completed all runs */
vtolEmpty(const DROID * psDroid)2546 bool vtolEmpty(const DROID *psDroid)
2547 {
2548 	CHECK_DROID(psDroid);
2549 
2550 	if (!isVtolDroid(psDroid))
2551 	{
2552 		return false;
2553 	}
2554 	if (psDroid->droidType != DROID_WEAPON)
2555 	{
2556 		return false;
2557 	}
2558 
2559 	for (int i = 0; i < psDroid->numWeaps; i++)
2560 	{
2561 		if (asWeaponStats[psDroid->asWeaps[i].nStat].vtolAttackRuns > 0 &&
2562 		    psDroid->asWeaps[i].usedAmmo < getNumAttackRuns(psDroid, i))
2563 		{
2564 			return false;
2565 		}
2566 	}
2567 
2568 	return true;
2569 }
2570 
2571 /* returns true if it's a VTOL weapon droid which still has full ammo */
vtolFull(const DROID * psDroid)2572 bool vtolFull(const DROID *psDroid)
2573 {
2574 	CHECK_DROID(psDroid);
2575 
2576 	if (!isVtolDroid(psDroid))
2577 	{
2578 		return false;
2579 	}
2580 	if (psDroid->droidType != DROID_WEAPON)
2581 	{
2582 		return false;
2583 	}
2584 
2585 	for (int i = 0; i < psDroid->numWeaps; i++)
2586 	{
2587 		if (asWeaponStats[psDroid->asWeaps[i].nStat].vtolAttackRuns > 0 &&
2588 		    psDroid->asWeaps[i].usedAmmo > 0)
2589 		{
2590 			return false;
2591 		}
2592 	}
2593 
2594 	return true;
2595 }
2596 
2597 // true if a vtol is waiting to be rearmed by a particular rearm pad
vtolReadyToRearm(DROID * psDroid,STRUCTURE * psStruct)2598 bool vtolReadyToRearm(DROID *psDroid, STRUCTURE *psStruct)
2599 {
2600 	CHECK_DROID(psDroid);
2601 
2602 	if (!isVtolDroid(psDroid) || psDroid->action != DACTION_WAITFORREARM)
2603 	{
2604 		return false;
2605 	}
2606 
2607 	// If a unit has been ordered to rearm make sure it goes to the correct base
2608 	STRUCTURE *psRearmPad = castStructure(orderStateObj(psDroid, DORDER_REARM));
2609 	if (psRearmPad && psRearmPad != psStruct && !vtolOnRearmPad(psRearmPad, psDroid))
2610 	{
2611 		// target rearm pad is clear - let it go there
2612 		objTrace(psDroid->id, "rearm pad at %d,%d won't snatch us - we already have another available at %d,%d", psStruct->pos.x / TILE_UNITS, psStruct->pos.y / TILE_UNITS, psRearmPad->pos.x / TILE_UNITS, psRearmPad->pos.y / TILE_UNITS);
2613 		return false;
2614 	}
2615 
2616 	if (vtolHappy(psDroid) && vtolOnRearmPad(psStruct, psDroid))
2617 	{
2618 		// there is a vtol on the pad and this vtol is already rearmed
2619 		// don't bother shifting the other vtol off
2620 		objTrace(psDroid->id, "rearm pad at %d,%d won't snatch us - we're rearmed and pad is busy", psStruct->pos.x / TILE_UNITS, psStruct->pos.y / TILE_UNITS);
2621 		return false;
2622 	}
2623 
2624 	STRUCTURE *psTarget = castStructure(psDroid->psActionTarget[0]);
2625 	if (psTarget && psTarget->pFunctionality->rearmPad.psObj == psDroid)
2626 	{
2627 		// vtol is rearming at a different base, leave it alone
2628 		objTrace(psDroid->id, "rearm pad at %d,%d won't snatch us - we already are snatched by %d,%d", psStruct->pos.x / TILE_UNITS, psStruct->pos.y / TILE_UNITS, psTarget->pos.x / TILE_UNITS, psTarget->pos.y / TILE_UNITS);
2629 		return false;
2630 	}
2631 
2632 	return true;
2633 }
2634 
2635 // true if a vtol droid currently returning to be rearmed
vtolRearming(const DROID * psDroid)2636 bool vtolRearming(const DROID *psDroid)
2637 {
2638 	CHECK_DROID(psDroid);
2639 
2640 	if (!isVtolDroid(psDroid))
2641 	{
2642 		return false;
2643 	}
2644 	if (psDroid->droidType != DROID_WEAPON)
2645 	{
2646 		return false;
2647 	}
2648 
2649 	if (psDroid->action == DACTION_MOVETOREARM ||
2650 	    psDroid->action == DACTION_WAITFORREARM ||
2651 	    psDroid->action == DACTION_MOVETOREARMPOINT ||
2652 	    psDroid->action == DACTION_WAITDURINGREARM)
2653 	{
2654 		return true;
2655 	}
2656 
2657 	return false;
2658 }
2659 
2660 // true if a droid is currently attacking
droidAttacking(const DROID * psDroid)2661 bool droidAttacking(const DROID *psDroid)
2662 {
2663 	CHECK_DROID(psDroid);
2664 
2665 	//what about cyborgs?
2666 	if (!(psDroid->droidType == DROID_WEAPON || psDroid->droidType == DROID_CYBORG ||
2667 	      psDroid->droidType == DROID_CYBORG_SUPER))
2668 	{
2669 		return false;
2670 	}
2671 
2672 	if (psDroid->action == DACTION_ATTACK ||
2673 	    psDroid->action == DACTION_MOVETOATTACK ||
2674 	    psDroid->action == DACTION_ROTATETOATTACK ||
2675 	    psDroid->action == DACTION_VTOLATTACK ||
2676 	    psDroid->action == DACTION_MOVEFIRE)
2677 	{
2678 		return true;
2679 	}
2680 
2681 	return false;
2682 }
2683 
2684 // see if there are any other vtols attacking the same target
2685 // but still rearming
allVtolsRearmed(const DROID * psDroid)2686 bool allVtolsRearmed(const DROID *psDroid)
2687 {
2688 	CHECK_DROID(psDroid);
2689 
2690 	// ignore all non vtols
2691 	if (!isVtolDroid(psDroid))
2692 	{
2693 		return true;
2694 	}
2695 
2696 	bool stillRearming = false;
2697 	for (const DROID *psCurr = apsDroidLists[psDroid->player]; psCurr; psCurr = psCurr->psNext)
2698 	{
2699 		if (vtolRearming(psCurr) &&
2700 		    psCurr->order.type == psDroid->order.type &&
2701 		    psCurr->order.psObj == psDroid->order.psObj)
2702 		{
2703 			stillRearming = true;
2704 			break;
2705 		}
2706 	}
2707 
2708 	return !stillRearming;
2709 }
2710 
2711 
2712 /*returns a count of the base number of attack runs for the weapon attached to the droid*/
getNumAttackRuns(const DROID * psDroid,int weapon_slot)2713 UWORD   getNumAttackRuns(const DROID *psDroid, int weapon_slot)
2714 {
2715 	ASSERT_OR_RETURN(0, isVtolDroid(psDroid), "not a VTOL Droid");
2716 	// if weapon is a salvo weapon, then number of shots that can be fired = vtolAttackRuns * numRounds
2717 	if (asWeaponStats[psDroid->asWeaps[weapon_slot].nStat].upgrade[psDroid->player].reloadTime)
2718 	{
2719 		return asWeaponStats[psDroid->asWeaps[weapon_slot].nStat].upgrade[psDroid->player].numRounds
2720 		       * asWeaponStats[psDroid->asWeaps[weapon_slot].nStat].vtolAttackRuns;
2721 	}
2722 	return asWeaponStats[psDroid->asWeaps[weapon_slot].nStat].vtolAttackRuns;
2723 }
2724 
2725 /*Checks a vtol for being fully armed and fully repaired to see if ready to
2726 leave reArm pad */
vtolHappy(const DROID * psDroid)2727 bool vtolHappy(const DROID *psDroid)
2728 {
2729 	CHECK_DROID(psDroid);
2730 
2731 	ASSERT_OR_RETURN(false, isVtolDroid(psDroid), "not a VTOL droid");
2732 
2733 	if (psDroid->body < psDroid->originalBody)
2734 	{
2735 		// VTOLs with less health than their original aren't happy
2736 		return false;
2737 	}
2738 
2739 	if (psDroid->droidType != DROID_WEAPON)
2740 	{
2741 		// Not an armed droid, so don't check the (non-existent) weapons
2742 		return true;
2743 	}
2744 
2745 	/* NOTE: Previous code (r5410) returned false if a droid had no weapon,
2746 	 *       which IMO isn't correct, but might be expected behaviour. I'm
2747 	 *       also not sure if weapon droids (see the above droidType check)
2748 	 *       can even have zero weapons. -- Giel
2749 	 */
2750 	ASSERT_OR_RETURN(false, psDroid->numWeaps > 0, "VTOL weapon droid without weapons found!");
2751 
2752 	//check full complement of ammo
2753 	for (int i = 0; i < psDroid->numWeaps; ++i)
2754 	{
2755 		if (asWeaponStats[psDroid->asWeaps[i].nStat].vtolAttackRuns > 0
2756 		    && psDroid->asWeaps[i].usedAmmo != 0)
2757 		{
2758 			return false;
2759 		}
2760 	}
2761 
2762 	return true;
2763 }
2764 
2765 /*checks if the droid is a VTOL droid and updates the attack runs as required*/
updateVtolAttackRun(DROID * psDroid,int weapon_slot)2766 void updateVtolAttackRun(DROID *psDroid, int weapon_slot)
2767 {
2768 	if (isVtolDroid(psDroid))
2769 	{
2770 		if (psDroid->numWeaps > 0)
2771 		{
2772 			if (asWeaponStats[psDroid->asWeaps[weapon_slot].nStat].vtolAttackRuns > 0)
2773 			{
2774 				++psDroid->asWeaps[weapon_slot].usedAmmo;
2775 				if (psDroid->asWeaps[weapon_slot].usedAmmo == getNumAttackRuns(psDroid, weapon_slot))
2776 				{
2777 					psDroid->asWeaps[weapon_slot].ammo = 0;
2778 				}
2779 				//quick check doesn't go over limit
2780 				ASSERT(psDroid->asWeaps[weapon_slot].usedAmmo < UWORD_MAX, "too many attack runs");
2781 			}
2782 		}
2783 	}
2784 }
2785 
2786 //assign rearmPad to the VTOL
assignVTOLPad(DROID * psNewDroid,STRUCTURE * psReArmPad)2787 void assignVTOLPad(DROID *psNewDroid, STRUCTURE *psReArmPad)
2788 {
2789 	ASSERT_OR_RETURN(, isVtolDroid(psNewDroid), "%s is not a VTOL droid", objInfo(psNewDroid));
2790 	ASSERT_OR_RETURN(,  psReArmPad->type == OBJ_STRUCTURE && psReArmPad->pStructureType->type == REF_REARM_PAD,
2791 	                 "%s cannot rearm", objInfo(psReArmPad));
2792 
2793 	setDroidBase(psNewDroid, psReArmPad);
2794 }
2795 
2796 /*compares the droid sensor type with the droid weapon type to see if the
2797 FIRE_SUPPORT order can be assigned*/
droidSensorDroidWeapon(const BASE_OBJECT * psObj,const DROID * psDroid)2798 bool droidSensorDroidWeapon(const BASE_OBJECT *psObj, const DROID *psDroid)
2799 {
2800 	const SENSOR_STATS	*psStats = nullptr;
2801 	int compIndex;
2802 
2803 	CHECK_DROID(psDroid);
2804 
2805 	if (!psObj || !psDroid)
2806 	{
2807 		return false;
2808 	}
2809 
2810 	//first check if the object is a droid or a structure
2811 	if ((psObj->type != OBJ_DROID) &&
2812 	    (psObj->type != OBJ_STRUCTURE))
2813 	{
2814 		return false;
2815 	}
2816 	//check same player
2817 	if (psObj->player != psDroid->player)
2818 	{
2819 		return false;
2820 	}
2821 	//check obj is a sensor droid/structure
2822 	switch (psObj->type)
2823 	{
2824 	case OBJ_DROID:
2825 		if (((const DROID *)psObj)->droidType != DROID_SENSOR &&
2826 		    ((const DROID *)psObj)->droidType != DROID_COMMAND)
2827 		{
2828 			return false;
2829 		}
2830 		compIndex = ((const DROID *)psObj)->asBits[COMP_SENSOR];
2831 		ASSERT_OR_RETURN(false, compIndex < numSensorStats, "Invalid range referenced for numSensorStats, %d > %d", compIndex, numSensorStats);
2832 		psStats = asSensorStats + compIndex;
2833 		break;
2834 	case OBJ_STRUCTURE:
2835 		psStats = ((const STRUCTURE *)psObj)->pStructureType->pSensor;
2836 		if ((psStats == nullptr) ||
2837 		    (psStats->location != LOC_TURRET))
2838 		{
2839 			return false;
2840 		}
2841 		break;
2842 	default:
2843 		break;
2844 	}
2845 
2846 	//check droid is a weapon droid - or Cyborg!!
2847 	if (!(psDroid->droidType == DROID_WEAPON || psDroid->droidType ==
2848 	      DROID_CYBORG || psDroid->droidType == DROID_CYBORG_SUPER))
2849 	{
2850 		return false;
2851 	}
2852 
2853 	//finally check the right droid/sensor combination
2854 	// check vtol droid with commander
2855 	if ((isVtolDroid(psDroid) || !proj_Direct(asWeaponStats + psDroid->asWeaps[0].nStat)) &&
2856 	    psObj->type == OBJ_DROID && ((const DROID *)psObj)->droidType == DROID_COMMAND)
2857 	{
2858 		return true;
2859 	}
2860 
2861 	//check vtol droid with vtol sensor
2862 	if (isVtolDroid(psDroid) && psDroid->asWeaps[0].nStat > 0)
2863 	{
2864 		if (psStats->type == VTOL_INTERCEPT_SENSOR || psStats->type == VTOL_CB_SENSOR || psStats->type == SUPER_SENSOR /*|| psStats->type == RADAR_DETECTOR_SENSOR*/)
2865 		{
2866 			return true;
2867 		}
2868 		return false;
2869 	}
2870 
2871 	// Check indirect weapon droid with standard/CB/radar detector sensor
2872 	if (!proj_Direct(asWeaponStats + psDroid->asWeaps[0].nStat))
2873 	{
2874 		if (psStats->type == STANDARD_SENSOR ||	psStats->type == INDIRECT_CB_SENSOR || psStats->type == SUPER_SENSOR /*|| psStats->type == RADAR_DETECTOR_SENSOR*/)
2875 		{
2876 			return true;
2877 		}
2878 		return false;
2879 	}
2880 	return false;
2881 }
2882 
2883 // return whether a droid has a CB sensor on it
cbSensorDroid(const DROID * psDroid)2884 bool cbSensorDroid(const DROID *psDroid)
2885 {
2886 	if (psDroid->droidType != DROID_SENSOR)
2887 	{
2888 		return false;
2889 	}
2890 	if (asSensorStats[psDroid->asBits[COMP_SENSOR]].type == VTOL_CB_SENSOR
2891 	    || asSensorStats[psDroid->asBits[COMP_SENSOR]].type == INDIRECT_CB_SENSOR)
2892 	{
2893 		return true;
2894 	}
2895 
2896 	return false;
2897 }
2898 
2899 // return whether a droid has a standard sensor on it (standard, VTOL strike, or wide spectrum)
standardSensorDroid(const DROID * psDroid)2900 bool standardSensorDroid(const DROID *psDroid)
2901 {
2902 	if (psDroid->droidType != DROID_SENSOR)
2903 	{
2904 		return false;
2905 	}
2906 	if (asSensorStats[psDroid->asBits[COMP_SENSOR]].type == VTOL_INTERCEPT_SENSOR
2907 	    || asSensorStats[psDroid->asBits[COMP_SENSOR]].type == STANDARD_SENSOR
2908 	    || asSensorStats[psDroid->asBits[COMP_SENSOR]].type == SUPER_SENSOR)
2909 	{
2910 		return true;
2911 	}
2912 
2913 	return false;
2914 }
2915 
2916 // ////////////////////////////////////////////////////////////////////////////
2917 // Give a droid from one player to another - used in Electronic Warfare and multiplayer.
2918 // Got to destroy the droid and build another since there are too many complications otherwise.
2919 // Returns the droid created.
giftSingleDroid(DROID * psD,UDWORD to,bool electronic)2920 DROID *giftSingleDroid(DROID *psD, UDWORD to, bool electronic)
2921 {
2922 	CHECK_DROID(psD);
2923 	ASSERT_OR_RETURN(nullptr, !isDead(psD), "Cannot gift dead unit");
2924 	ASSERT_OR_RETURN(psD, psD->player != to, "Cannot gift to self");
2925 
2926 	// Check unit limits (multiplayer only)
2927 	syncDebug("Limits: %u/%d %u/%d %u/%d", getNumDroids(to), getMaxDroids(to), getNumConstructorDroids(to), getMaxConstructors(to), getNumCommandDroids(to), getMaxCommanders(to));
2928 	if (bMultiPlayer
2929 	    && ((int)getNumDroids(to) >= getMaxDroids(to)
2930 	        || ((psD->droidType == DROID_CYBORG_CONSTRUCT || psD->droidType == DROID_CONSTRUCT)
2931 	            && (int)getNumConstructorDroids(to) >= getMaxConstructors(to))
2932 	        || (psD->droidType == DROID_COMMAND && (int)getNumCommandDroids(to) >= getMaxCommanders(to))))
2933 	{
2934 		if (to == selectedPlayer || psD->player == selectedPlayer)
2935 		{
2936 			CONPRINTF("%s", _("Unit transfer failed -- unit limits exceeded"));
2937 		}
2938 		return nullptr;
2939 	}
2940 
2941 	// electronic or campaign will destroy and recreate the droid.
2942 	if (electronic || !bMultiPlayer)
2943 	{
2944 		DROID_TEMPLATE sTemplate;
2945 		DROID *psNewDroid;
2946 
2947 		templateSetParts(psD, &sTemplate);	// create a template based on the droid
2948 		sTemplate.name = WzString::fromUtf8(psD->aName);	// copy the name across
2949 		// update score
2950 		if (psD->player == selectedPlayer && to != selectedPlayer && !bMultiPlayer)
2951 		{
2952 			scoreUpdateVar(WD_UNITS_LOST);
2953 		}
2954 		// make the old droid vanish (but is not deleted until next tick)
2955 		adjustDroidCount(psD, -1);
2956 		vanishDroid(psD);
2957 		// create a new droid
2958 		psNewDroid = reallyBuildDroid(&sTemplate, Position(psD->pos.x, psD->pos.y, 0), to, false, psD->rot);
2959 		ASSERT_OR_RETURN(nullptr, psNewDroid, "Unable to build unit");
2960 
2961 		addDroid(psNewDroid, apsDroidLists);
2962 		adjustDroidCount(psNewDroid, 1);
2963 
2964 		psNewDroid->body = clip((psD->body*psNewDroid->originalBody + psD->originalBody/2)/std::max(psD->originalBody, 1u), 1u, psNewDroid->originalBody);
2965 		psNewDroid->experience = psD->experience;
2966 		psNewDroid->kills = psD->kills;
2967 
2968 		if (!(psNewDroid->droidType == DROID_PERSON || cyborgDroid(psNewDroid) || isTransporter(psNewDroid)))
2969 		{
2970 			updateDroidOrientation(psNewDroid);
2971 		}
2972 
2973 		triggerEventObjectTransfer(psNewDroid, psD->player);
2974 		return psNewDroid;
2975 	}
2976 
2977 	int oldPlayer = psD->player;
2978 
2979 	// reset the assigned state of units attached to a leader
2980 	for (DROID *psCurr = apsDroidLists[oldPlayer]; psCurr != nullptr; psCurr = psCurr->psNext)
2981 	{
2982 		BASE_OBJECT	*psLeader;
2983 
2984 		if (hasCommander(psCurr))
2985 		{
2986 			psLeader = (BASE_OBJECT *)psCurr->psGroup->psCommander;
2987 		}
2988 		else
2989 		{
2990 			//psLeader can be either a droid or a structure
2991 			psLeader = orderStateObj(psCurr, DORDER_FIRESUPPORT);
2992 		}
2993 
2994 		if (psLeader && psLeader->id == psD->id)
2995 		{
2996 			psCurr->selected = false;
2997 			orderDroid(psCurr, DORDER_STOP, ModeQueue);
2998 		}
2999 	}
3000 
3001 	visRemoveVisibility((BASE_OBJECT *)psD);
3002 	psD->selected = false;
3003 
3004 	adjustDroidCount(psD, -1);
3005 	scriptRemoveObject(psD); //Remove droid from any script groups
3006 
3007 	if (droidRemove(psD, apsDroidLists))
3008 	{
3009 		psD->player	= to;
3010 
3011 		addDroid(psD, apsDroidLists);
3012 		adjustDroidCount(psD, 1);
3013 
3014 		// the new player may have different default sensor/ecm/repair components
3015 		if ((asSensorStats + psD->asBits[COMP_SENSOR])->location == LOC_DEFAULT)
3016 		{
3017 			if (psD->asBits[COMP_SENSOR] != aDefaultSensor[psD->player])
3018 			{
3019 				psD->asBits[COMP_SENSOR] = aDefaultSensor[psD->player];
3020 			}
3021 		}
3022 		if ((asECMStats + psD->asBits[COMP_ECM])->location == LOC_DEFAULT)
3023 		{
3024 			if (psD->asBits[COMP_ECM] != aDefaultECM[psD->player])
3025 			{
3026 				psD->asBits[COMP_ECM] = aDefaultECM[psD->player];
3027 			}
3028 		}
3029 		if ((asRepairStats + psD->asBits[COMP_REPAIRUNIT])->location == LOC_DEFAULT)
3030 		{
3031 			if (psD->asBits[COMP_REPAIRUNIT] != aDefaultRepair[psD->player])
3032 			{
3033 				psD->asBits[COMP_REPAIRUNIT] = aDefaultRepair[psD->player];
3034 			}
3035 		}
3036 	}
3037 	else
3038 	{
3039 		// if we couldn't remove it, then get rid of it.
3040 		return nullptr;
3041 	}
3042 
3043 	// Update visibility
3044 	visTilesUpdate((BASE_OBJECT*)psD);
3045 
3046 	// check through the players, and our allies, list of droids to see if any are targetting it
3047 	for (unsigned int i = 0; i < MAX_PLAYERS; ++i)
3048 	{
3049 		if (!aiCheckAlliances(i, to))
3050 		{
3051 			continue;
3052 		}
3053 
3054 		for (DROID *psCurr = apsDroidLists[i]; psCurr != nullptr; psCurr = psCurr->psNext)
3055 		{
3056 			if (psCurr->order.psObj == psD || psCurr->psActionTarget[0] == psD)
3057 			{
3058 				orderDroid(psCurr, DORDER_STOP, ModeQueue);
3059 				break;
3060 			}
3061 			for (unsigned iWeap = 0; iWeap < psCurr->numWeaps; ++iWeap)
3062 			{
3063 				if (psCurr->psActionTarget[iWeap] == psD)
3064 				{
3065 					orderDroid(psCurr, DORDER_STOP, ModeImmediate);
3066 					break;
3067 				}
3068 			}
3069 			// check through order list
3070 			orderClearTargetFromDroidList(psCurr, (BASE_OBJECT *)psD);
3071 		}
3072 	}
3073 
3074 	for (unsigned int i = 0; i < MAX_PLAYERS; ++i)
3075 	{
3076 		if (!aiCheckAlliances(i, to))
3077 		{
3078 			continue;
3079 		}
3080 
3081 		// check through the players list, and our allies, of structures to see if any are targetting it
3082 		for (STRUCTURE *psStruct = apsStructLists[i]; psStruct != nullptr; psStruct = psStruct->psNext)
3083 		{
3084 			if (psStruct->psTarget[0] == psD)
3085 			{
3086 				setStructureTarget(psStruct, nullptr, 0, ORIGIN_UNKNOWN);
3087 			}
3088 		}
3089 	}
3090 
3091 	triggerEventObjectTransfer(psD, oldPlayer);
3092 	return psD;
3093 }
3094 
3095 /*calculates the electronic resistance of a droid based on its experience level*/
droidResistance(const DROID * psDroid)3096 SWORD   droidResistance(const DROID *psDroid)
3097 {
3098 	CHECK_DROID(psDroid);
3099 	const BODY_STATS *psStats = asBodyStats + psDroid->asBits[COMP_BODY];
3100 	int resistance = psDroid->experience / (65536 / MAX(1, psStats->upgrade[psDroid->player].resistance));
3101 	// ensure resistance is a base minimum
3102 	resistance = MAX(resistance, psStats->upgrade[psDroid->player].resistance);
3103 	return MIN(resistance, INT16_MAX);
3104 }
3105 
3106 /*this is called to check the weapon is 'allowed'. Check if VTOL, the weapon is
3107 direct fire. Also check numVTOLattackRuns for the weapon is not zero - return
3108 true if valid weapon*/
3109 /* this will be buggy if the droid being checked has both AA weapon and non-AA weapon
3110 Cannot think of a solution without adding additional return value atm.
3111 */
checkValidWeaponForProp(DROID_TEMPLATE * psTemplate)3112 bool checkValidWeaponForProp(DROID_TEMPLATE *psTemplate)
3113 {
3114 	PROPULSION_STATS	*psPropStats;
3115 
3116 	//check propulsion stat for vtol
3117 	psPropStats = asPropulsionStats + psTemplate->asParts[COMP_PROPULSION];
3118 
3119 	ASSERT_OR_RETURN(false, psPropStats != nullptr, "invalid propulsion stats pointer");
3120 
3121 	// if there are no weapons, then don't even bother continuing
3122 	if (psTemplate->numWeaps == 0)
3123 	{
3124 		return false;
3125 	}
3126 
3127 	if (asPropulsionTypes[psPropStats->propulsionType].travel == AIR)
3128 	{
3129 		//check weapon stat for indirect
3130 		if (!proj_Direct(asWeaponStats + psTemplate->asWeaps[0])
3131 		    || !asWeaponStats[psTemplate->asWeaps[0]].vtolAttackRuns)
3132 		{
3133 			return false;
3134 		}
3135 	}
3136 	else
3137 	{
3138 		// VTOL weapons do not go on non-AIR units.
3139 		if (asWeaponStats[psTemplate->asWeaps[0]].vtolAttackRuns)
3140 		{
3141 			return false;
3142 		}
3143 	}
3144 
3145 	//also checks that there is no other system component
3146 	if (psTemplate->asParts[COMP_BRAIN] != 0
3147 	    && asWeaponStats[psTemplate->asWeaps[0]].weaponSubClass != WSC_COMMAND)
3148 	{
3149 		assert(false);
3150 		return false;
3151 	}
3152 
3153 	return true;
3154 }
3155 
3156 // Check if a droid can be selected.
isSelectable(DROID const * psDroid)3157 bool isSelectable(DROID const *psDroid)
3158 {
3159 	if (psDroid->flags.test(OBJECT_FLAG_UNSELECTABLE))
3160 	{
3161 		return false;
3162 	}
3163 
3164 	// we shouldn't ever control the transporter in SP games
3165 	if (isTransporter(psDroid) && !bMultiPlayer)
3166 	{
3167 		return false;
3168 	}
3169 
3170 	return true;
3171 }
3172 
3173 // Select a droid and do any necessary housekeeping.
3174 //
SelectDroid(DROID * psDroid)3175 void SelectDroid(DROID *psDroid)
3176 {
3177 	if (!isSelectable(psDroid))
3178 	{
3179 		return;
3180 	}
3181 
3182 	psDroid->selected = true;
3183 	intRefreshScreen();
3184 	triggerEventSelected();
3185 	jsDebugSelected(psDroid);
3186 }
3187 
3188 // De-select a droid and do any necessary housekeeping.
3189 //
DeSelectDroid(DROID * psDroid)3190 void DeSelectDroid(DROID *psDroid)
3191 {
3192 	psDroid->selected = false;
3193 	intRefreshScreen();
3194 	triggerEventSelected();
3195 }
3196 
3197 /** Callback function for stopped audio tracks
3198  *  Sets the droid's current track id to NO_SOUND
3199  *  \return true on success, false on failure
3200  */
droidAudioTrackStopped(void * psObj)3201 bool droidAudioTrackStopped(void *psObj)
3202 {
3203 	DROID	*psDroid;
3204 
3205 	psDroid = (DROID *)psObj;
3206 	if (psDroid == nullptr)
3207 	{
3208 		debug(LOG_ERROR, "droid pointer invalid");
3209 		return false;
3210 	}
3211 
3212 	if (psDroid->type != OBJ_DROID || psDroid->died)
3213 	{
3214 		return false;
3215 	}
3216 
3217 	psDroid->iAudioID = NO_SOUND;
3218 	return true;
3219 }
3220 
3221 /*returns true if droid type is one of the Cyborg types*/
cyborgDroid(const DROID * psDroid)3222 bool cyborgDroid(const DROID *psDroid)
3223 {
3224 	return (psDroid->droidType == DROID_CYBORG
3225 	        || psDroid->droidType == DROID_CYBORG_CONSTRUCT
3226 	        || psDroid->droidType == DROID_CYBORG_REPAIR
3227 	        || psDroid->droidType == DROID_CYBORG_SUPER);
3228 }
3229 
isConstructionDroid(DROID const * psDroid)3230 bool isConstructionDroid(DROID const *psDroid)
3231 {
3232 	return psDroid->droidType == DROID_CONSTRUCT || psDroid->droidType == DROID_CYBORG_CONSTRUCT;
3233 }
3234 
isConstructionDroid(BASE_OBJECT const * psObject)3235 bool isConstructionDroid(BASE_OBJECT const *psObject)
3236 {
3237 	return isDroid(psObject) && isConstructionDroid(castDroid(psObject));
3238 }
3239 
droidOnMap(const DROID * psDroid)3240 bool droidOnMap(const DROID *psDroid)
3241 {
3242 	if (psDroid->died == NOT_CURRENT_LIST || isTransporter(psDroid)
3243 	    || psDroid->pos.x == INVALID_XY || psDroid->pos.y == INVALID_XY || missionIsOffworld()
3244 	    || mapHeight == 0)
3245 	{
3246 		// Off world or on a transport or is a transport or in mission list, or on a mission, or no map - ignore
3247 		return true;
3248 	}
3249 	return worldOnMap(psDroid->pos.x, psDroid->pos.y);
3250 }
3251 
3252 /** Teleport a droid to a new position on the map */
droidSetPosition(DROID * psDroid,int x,int y)3253 void droidSetPosition(DROID *psDroid, int x, int y)
3254 {
3255 	psDroid->pos.x = x;
3256 	psDroid->pos.y = y;
3257 	psDroid->pos.z = map_Height(psDroid->pos.x, psDroid->pos.y);
3258 	initDroidMovement(psDroid);
3259 	visTilesUpdate((BASE_OBJECT *)psDroid);
3260 }
3261 
3262 /** Check validity of a droid. Crash hard if it fails. */
checkDroid(const DROID * droid,const char * const location,const char * function,const int recurse)3263 void checkDroid(const DROID *droid, const char *const location, const char *function, const int recurse)
3264 {
3265 	if (recurse < 0)
3266 	{
3267 		return;
3268 	}
3269 
3270 	ASSERT_HELPER(droid != nullptr, location, function, "CHECK_DROID: NULL pointer");
3271 	ASSERT_HELPER(droid->type == OBJ_DROID, location, function, "CHECK_DROID: Not droid (type %d)", (int)droid->type);
3272 	ASSERT_HELPER(droid->numWeaps <= MAX_WEAPONS, location, function, "CHECK_DROID: Bad number of droid weapons %d", (int)droid->numWeaps);
3273 	ASSERT_HELPER((unsigned)droid->listSize <= droid->asOrderList.size() && (unsigned)droid->listPendingBegin <= droid->asOrderList.size(), location, function, "CHECK_DROID: Bad number of droid orders %d %d %d", (int)droid->listSize, (int)droid->listPendingBegin, (int)droid->asOrderList.size());
3274 	ASSERT_HELPER(droid->player < MAX_PLAYERS, location, function, "CHECK_DROID: Bad droid owner %d", (int)droid->player);
3275 	ASSERT_HELPER(droidOnMap(droid), location, function, "CHECK_DROID: Droid off map");
3276 	ASSERT_HELPER(droid->body <= droid->originalBody, location, function, "CHECK_DROID: More body points (%u) than original body points (%u).", (unsigned)droid->body, (unsigned)droid->originalBody);
3277 
3278 	for (int i = 0; i < MAX_WEAPONS; ++i)
3279 	{
3280 		ASSERT_HELPER(droid->asWeaps[i].lastFired <= gameTime, location, function, "CHECK_DROID: Bad last fired time for turret %u", i);
3281 	}
3282 }
3283 
droidSqDist(DROID * psDroid,BASE_OBJECT * psObj)3284 int droidSqDist(DROID *psDroid, BASE_OBJECT *psObj)
3285 {
3286 	PROPULSION_STATS *psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION];
3287 
3288 	if (!fpathCheck(psDroid->pos, psObj->pos, psPropStats->propulsionType))
3289 	{
3290 		return -1;
3291 	}
3292 	return objPosDiffSq(psDroid->pos, psObj->pos);
3293 }
3294