1 #include "Explosion_Control.h"
2 
3 #include "AI.h"
4 #include "Action_Items.h"
5 #include "Animation_Control.h"
6 #include "Campaign_Types.h"
7 #include "ContentManager.h"
8 #include "Debug.h"
9 #include "Directories.h"
10 #include "End_Game.h"
11 #include "FOV.h"
12 #include "FileMan.h"
13 #include "Font_Control.h"
14 #include "GameInstance.h"
15 #include "GameSettings.h"
16 #include "Game_Clock.h"
17 #include "Handle_Doors.h"
18 #include "Handle_Items.h"
19 #include "Handle_UI.h"
20 #include "Interactive_Tiles.h"
21 #include "Interface.h"
22 #include "Interface_Dialogue.h"
23 #include "Isometric_Utils.h"
24 #include "Items.h"
25 #include "Keys.h"
26 #include "LightEffects.h"
27 #include "Lighting.h"
28 #include "LoadSaveData.h"
29 #include "LoadSaveExplosionType.h"
30 #include "Logger.h"
31 #include "Map_Information.h"
32 #include "MemMan.h"
33 #include "Message.h"
34 #include "Morale.h"
35 #include "OppList.h"
36 #include "Overhead.h"
37 #include "PathAI.h"
38 #include "Pits.h"
39 #include "Quests.h"
40 #include "Random.h"
41 #include "RenderWorld.h"
42 #include "Render_Fun.h"
43 #include "Rotting_Corpses.h"
44 #include "SamSiteModel.h"
45 #include "SaveLoadMap.h"
46 #include "Smell.h"
47 #include "SmokeEffects.h"
48 #include "Soldier_Add.h"
49 #include "Soldier_Control.h"
50 #include "Soldier_Create.h"
51 #include "Soldier_Macros.h"
52 #include "Soldier_Profile.h"
53 #include "Soldier_Tile.h"
54 #include "Sound_Control.h"
55 #include "StrategicMap.h"
56 #include "Structure.h"
57 #include "Structure_Wrap.h"
58 #include "TileDat.h"
59 #include "TileDef.h"
60 #include "Tile_Animation.h"
61 #include "Timer_Control.h"
62 #include "Weapons.h"
63 #include "WorldDat.h"
64 #include "WorldDef.h"
65 #include "WorldMan.h"
66 #include "World_Items.h"
67 
68 struct ExplosionInfo
69 {
70 	const char* blast_anim;
71 	SoundID     sound;
72 	UINT8       blast_speed;
73 	UINT8       transparent_key_frame;
74 	UINT8       damage_key_frame;
75 };
76 
77 static const ExplosionInfo explosion_info[] =
78 {
79 	{ "",                           EXPLOSION_1,        0,  0,  0 },
80 	{ TILECACHEDIR "/zgrav_d.sti",  EXPLOSION_1,       80, 17,  3 },
81 	{ TILECACHEDIR "/zgrav_c.sti",  EXPLOSION_BLAST_2, 80, 28,  5 },
82 	{ TILECACHEDIR "/zgrav_b.sti",  EXPLOSION_BLAST_2, 80, 24,  5 },
83 	{ TILECACHEDIR "/shckwave.sti", EXPLOSION_1,       20,  1,  5 },
84 	{ TILECACHEDIR "/wat_exp.sti",  AIR_ESCAPING_1,    80,  1, 18 },
85 	{ TILECACHEDIR "/tear_exp.sti", AIR_ESCAPING_1,    80,  1, 18 },
86 	{ TILECACHEDIR "/tear_exp.sti", AIR_ESCAPING_1,    80,  1, 18 },
87 	{ TILECACHEDIR "/must_exp.sti", AIR_ESCAPING_1,    80,  1, 18 }
88 };
89 
90 
91 #define BOMB_QUEUE_DELAY (1000 + Random( 500 ) )
92 
93 #define MAX_BOMB_QUEUE 40
94 static ExplosionQueueElement gExplosionQueue[MAX_BOMB_QUEUE];
95 UINT8                        gubElementsOnExplosionQueue = 0;
96 BOOLEAN                      gfExplosionQueueActive      = FALSE;
97 
98 static BOOLEAN      gfExplosionQueueMayHaveChangedSight = FALSE;
99 static SOLDIERTYPE* gPersonToSetOffExplosions           = 0;
100 
101 #define NUM_EXPLOSION_SLOTS 100
102 static EXPLOSIONTYPE gExplosionData[NUM_EXPLOSION_SLOTS];
103 
104 Observable<INT16, INT16, INT8, INT16, STRUCTURE*, UINT32, BOOLEAN_S*> BeforeStructureDamaged = {};
105 Observable<INT16, INT16, INT8, INT16, STRUCTURE*, UINT8, BOOLEAN> OnStructureDamaged = {};
106 
GetFreeExplosion(void)107 static EXPLOSIONTYPE* GetFreeExplosion(void)
108 {
109 	FOR_EACH(EXPLOSIONTYPE, e, gExplosionData)
110 	{
111 		if (!e->fAllocated) return e;
112 	}
113 	return NULL;
114 }
115 
116 
117 static void GenerateExplosionFromExplosionPointer(EXPLOSIONTYPE* pExplosion);
118 
119 
120 // GENERATE EXPLOSION
InternalIgniteExplosion(SOLDIERTYPE * const owner,const INT16 sX,const INT16 sY,const INT16 sZ,const INT16 sGridNo,const UINT16 usItem,const BOOLEAN fLocate,const INT8 bLevel)121 void InternalIgniteExplosion(SOLDIERTYPE* const owner, const INT16 sX, const INT16 sY, const INT16 sZ, const INT16 sGridNo, const UINT16 usItem, const BOOLEAN fLocate, const INT8 bLevel)
122 {
123 	// Double check that we are using an explosive!
124 	if ( !( GCM->getItem(usItem)->isExplosive() ) )
125 	{
126 		return;
127 	}
128 
129 	// Increment attack counter...
130 
131 	if (gubElementsOnExplosionQueue == 0)
132 	{
133 		// single explosion, disable sight until the end, and set flag
134 		// to check sight at end of attack
135 
136 		gTacticalStatus.uiFlags |= (DISALLOW_SIGHT | CHECK_SIGHT_AT_END_OF_ATTACK);
137 	}
138 
139 
140 	gTacticalStatus.ubAttackBusyCount++;
141 	SLOGD("Incrementing Attack: Explosion gone off, Count now %d", gTacticalStatus.ubAttackBusyCount);
142 
143 	EXPLOSIONTYPE* const e = GetFreeExplosion();
144 	if (e == NULL) return;
145 
146 	e->owner         = owner;
147 	e->ubTypeID      = Explosive[GCM->getItem(usItem)->getClassIndex()].ubAnimationID;
148 	e->usItem        = usItem;
149 	e->sX            = sX;
150 	e->sY            = sY;
151 	e->sZ            = sZ;
152 	e->sGridNo       = sGridNo;
153 	e->bLevel        = bLevel;
154 	e->fAllocated    = TRUE;
155 	e->sCurrentFrame = 0;
156 	GenerateExplosionFromExplosionPointer(e);
157 
158 	if (fLocate) LocateGridNo(sGridNo);
159 }
160 
161 
IgniteExplosion(SOLDIERTYPE * const owner,const INT16 z,const INT16 sGridNo,const UINT16 item,const INT8 level)162 void IgniteExplosion(SOLDIERTYPE* const owner, const INT16 z, const INT16 sGridNo, const UINT16 item, const INT8 level)
163 {
164 	INT16 x;
165 	INT16 y;
166 	ConvertGridNoToCenterCellXY(sGridNo, &x, &y);
167 	InternalIgniteExplosion(owner, x, y, z, sGridNo, item, TRUE, level);
168 }
169 
170 
IgniteExplosionXY(SOLDIERTYPE * const owner,const INT16 sX,const INT16 sY,const INT16 sZ,const INT16 sGridNo,const UINT16 usItem,const INT8 bLevel)171 void IgniteExplosionXY(SOLDIERTYPE* const owner, const INT16 sX, const INT16 sY, const INT16 sZ, const INT16 sGridNo, const UINT16 usItem, const INT8 bLevel)
172 {
173 	InternalIgniteExplosion(owner, sX, sY, sZ, sGridNo, usItem, TRUE, bLevel);
174 }
175 
176 
GenerateExplosionFromExplosionPointer(EXPLOSIONTYPE * pExplosion)177 static void GenerateExplosionFromExplosionPointer(EXPLOSIONTYPE* pExplosion)
178 {
179 	UINT8			ubTerrainType;
180 
181 	ANITILE_PARAMS	AniParams;
182 
183 	// Assign param values
184 	const INT16 sX      = pExplosion->sX;
185 	const INT16 sY      = pExplosion->sY;
186 	INT16       sZ      = pExplosion->sZ;
187 	const INT16 sGridNo = pExplosion->sGridNo;
188 	const INT16 bLevel  = pExplosion->bLevel;
189 
190 	// If Z value given is 0 and bLevel > 0, make z heigher
191 	if ( sZ == 0 && bLevel > 0 )
192 	{
193 		sZ = ROOF_LEVEL_HEIGHT;
194 	}
195 
196 	pExplosion->light = NULL;
197 
198 	// OK, if we are over water.... use water explosion...
199 	ubTerrainType = GetTerrainType( sGridNo );
200 
201 	const ExplosionInfo* inf = &explosion_info[pExplosion->ubTypeID];
202 
203 	// Setup explosion!
204 	AniParams = ANITILE_PARAMS{};
205 
206 	AniParams.sGridNo							= sGridNo;
207 	AniParams.ubLevelID						= ANI_TOPMOST_LEVEL;
208 	AniParams.sDelay              = inf->blast_speed;
209 	AniParams.sStartFrame					= pExplosion->sCurrentFrame;
210 	AniParams.uiFlags             = ANITILE_FORWARD | ANITILE_EXPLOSION;
211 
212 	if ( ubTerrainType == LOW_WATER || ubTerrainType == MED_WATER || ubTerrainType == DEEP_WATER )
213 	{
214 		// Change type to water explosion...
215 		inf = &explosion_info[WATER_BLAST];
216 		AniParams.uiFlags						|= ANITILE_ALWAYS_TRANSLUCENT;
217 	}
218 
219 	if ( sZ < WALL_HEIGHT )
220 	{
221 		AniParams.uiFlags |= ANITILE_NOZBLITTER;
222 	}
223 
224 	AniParams.sX = sX;
225 	AniParams.sY = sY;
226 	AniParams.sZ = sZ;
227 
228 	AniParams.ubKeyFrame1     = inf->transparent_key_frame;
229 	AniParams.uiKeyFrame1Code = ANI_KEYFRAME_BEGIN_TRANSLUCENCY;
230 
231 	AniParams.ubKeyFrame2     = inf->damage_key_frame;
232 	AniParams.uiKeyFrame2Code = ANI_KEYFRAME_BEGIN_DAMAGE;
233 	AniParams.v.explosion     = pExplosion;
234 
235 	AniParams.zCachedFile = inf->blast_anim;
236 	CreateAnimationTile( &AniParams );
237 
238 	//  set light source....
239 	if (pExplosion->light == NULL)
240 	{
241 		// DO ONLY IF WE'RE AT A GOOD LEVEL
242 		if ( ubAmbientLightLevel >= MIN_AMB_LEVEL_FOR_MERC_LIGHTS )
243 		{
244 			LIGHT_SPRITE* const l = LightSpriteCreate("L-R04.LHT");
245 			pExplosion->light = l;
246 			if (l != NULL)
247 			{
248 				LightSpritePower(l, TRUE);
249 				LightSpritePosition(l, sX / CELL_X_SIZE, sY / CELL_Y_SIZE);
250 			}
251 		}
252 	}
253 
254 	SoundID uiSoundID = inf->sound;
255 	if ( uiSoundID == EXPLOSION_1 )
256 	{
257 		// Randomize
258 		if ( Random( 2 ) == 0 )
259 		{
260 			uiSoundID = EXPLOSION_ALT_BLAST_1;
261 		}
262 	}
263 
264 	PlayLocationJA2Sample(sGridNo, uiSoundID, HIGHVOLUME, 1);
265 }
266 
267 
UpdateExplosionFrame(EXPLOSIONTYPE * const e,const INT16 sCurrentFrame)268 void UpdateExplosionFrame(EXPLOSIONTYPE* const e, const INT16 sCurrentFrame)
269 {
270 	Assert(gExplosionData <= e && e < endof(gExplosionData));
271 	Assert(e->fAllocated);
272 
273 	e->sCurrentFrame = sCurrentFrame;
274 }
275 
276 
RemoveExplosionData(EXPLOSIONTYPE * const e)277 void RemoveExplosionData(EXPLOSIONTYPE* const e)
278 {
279 	Assert(gExplosionData <= e && e < endof(gExplosionData));
280 	Assert(e->fAllocated);
281 
282 	e->fAllocated = FALSE;
283 	if (e->light != NULL) LightSpriteDestroy(e->light);
284 }
285 
286 
HandleFencePartnerCheck(INT16 sStructGridNo)287 static void HandleFencePartnerCheck(INT16 sStructGridNo)
288 {
289 	STRUCTURE *pFenceStructure, *pFenceBaseStructure;
290 	INT8		bFenceDestructionPartner = -1;
291 
292 	pFenceStructure = FindStructure( sStructGridNo, STRUCTURE_FENCE );
293 
294 	if ( pFenceStructure )
295 	{
296 		// How does our explosion partner look?
297 		if ( pFenceStructure->pDBStructureRef->pDBStructure->bDestructionPartner < 0 )
298 		{
299 			// Find level node.....
300 			pFenceBaseStructure = FindBaseStructure( pFenceStructure );
301 
302 			// Get LEVELNODE for struct and remove!
303 			LEVELNODE* const pFenceNode = FindLevelNodeBasedOnStructure(pFenceBaseStructure);
304 
305 			// Get type from index...
306 			const UINT32 uiFenceType = GetTileType(pFenceNode->usIndex);
307 
308 			bFenceDestructionPartner = -1 * ( pFenceBaseStructure->pDBStructureRef->pDBStructure->bDestructionPartner );
309 
310 			// Get new index
311 			UINT16 usTileIndex = GetTileIndexFromTypeSubIndex(uiFenceType, bFenceDestructionPartner);
312 
313 			ApplyMapChangesToMapTempFile app;
314 			// Remove it!
315 			RemoveStructFromLevelNode( pFenceBaseStructure->sGridNo, pFenceNode );
316 			// Add it!
317 			AddStructToHead( pFenceBaseStructure->sGridNo, (UINT16)( usTileIndex ) );
318 		}
319 	}
320 }
321 
322 
ReplaceWall(GridNo const grid_no,UINT8 const orientation,INT16 const sub_idx)323 static void ReplaceWall(GridNo const grid_no, UINT8 const orientation, INT16 const sub_idx)
324 {
325 	STRUCTURE* const wall_struct = GetWallStructOfSameOrientationAtGridno(grid_no, orientation);
326 	if (!wall_struct || !(wall_struct->fFlags & STRUCTURE_WALL)) return;
327 
328 	LEVELNODE* const node    = FindLevelNodeBasedOnStructure(wall_struct);
329 	UINT16     const new_idx = GetTileIndexFromTypeSubIndex(gTileDatabase[node->usIndex].fType, sub_idx);
330 	ApplyMapChangesToMapTempFile app;
331 	RemoveStructFromLevelNode(grid_no, node);
332 	AddWallToStructLayer(grid_no, new_idx, TRUE);
333 }
334 
335 
RemoveOnWall(GridNo const grid_no,StructureFlags const flags,STRUCTURE * next)336 static STRUCTURE* RemoveOnWall(GridNo const grid_no, StructureFlags const flags, STRUCTURE* next)
337 {
338 	while (STRUCTURE* const attached = FindStructure(grid_no, flags))
339 	{
340 		STRUCTURE* const base = FindBaseStructure(attached);
341 		if (!base)
342 		{ // Error!
343 			SLOGW("Problems removing structure attached to wall at %d", grid_no);
344 			break;
345 		}
346 
347 		while (next && next->usStructureID == base->usStructureID)
348 		{ // The next structure will also be deleted, so skip past it
349 			next = next->pNext;
350 		}
351 
352 		LEVELNODE* const node = FindLevelNodeBasedOnStructure(base);
353 		ApplyMapChangesToMapTempFile app;
354 		RemoveStructFromLevelNode(base->sGridNo, node);
355 	}
356 	return next;
357 }
358 
359 
ExplosiveDamageStructureAtGridNo(STRUCTURE * const pCurrent,STRUCTURE ** const ppNextCurrent,INT16 const grid_no,INT16 const wound_amt,UINT32 const uiDist,BOOLEAN * const pfRecompileMovementCosts,BOOLEAN const only_walls,SOLDIERTYPE * const owner,INT8 const level)360 static bool ExplosiveDamageStructureAtGridNo(STRUCTURE* const pCurrent, STRUCTURE** const ppNextCurrent, INT16 const grid_no, INT16 const wound_amt, UINT32 const uiDist, BOOLEAN* const pfRecompileMovementCosts, BOOLEAN const only_walls, SOLDIERTYPE* const owner, INT8 const level)
361 {
362 	BOOLEAN_S skipDamage = false;
363 	BeforeStructureDamaged(gWorldSectorX, gWorldSectorY, gbWorldSectorZ, grid_no, pCurrent, uiDist, &skipDamage);
364 	if (skipDamage)
365 	{
366 		return true;
367 	}
368 
369 	// ATE: Continue if we are only looking for walls
370 	if (only_walls && !(pCurrent->fFlags & STRUCTURE_WALLSTUFF)) return true;
371 
372 	if (level > 0) return true;
373 
374 	// Is this a corpse?
375 	if (pCurrent->fFlags & STRUCTURE_CORPSE && gGameSettings.fOptions[TOPTION_BLOOD_N_GORE] && wound_amt > 10)
376 	{
377 		// Spray corpse in a fine mist
378 		if (uiDist <= 1)
379 		{ // Remove corpse
380 			VaporizeCorpse(grid_no, pCurrent->usStructureID);
381 		}
382 	}
383 	else if (!(pCurrent->fFlags & STRUCTURE_PERSON))
384 	{
385 		// Damage structure!
386 		INT16 const sX = CenterX(grid_no);
387 		INT16 const sY = CenterY(grid_no);
388 		INT8 bDamageReturnVal = DamageStructure(pCurrent, wound_amt, STRUCTURE_DAMAGE_EXPLOSION, grid_no, sX, sY, NULL);
389 		if (bDamageReturnVal == STRUCTURE_NOT_DAMAGED) return true;
390 
391 		BOOLEAN fDestroyed = (bDamageReturnVal == STRUCTURE_DESTROYED);
392 		OnStructureDamaged(gWorldSectorX, gWorldSectorY, gbWorldSectorZ, grid_no, pCurrent, wound_amt, fDestroyed);
393 
394 		STRUCTURE* const base         = FindBaseStructure(pCurrent);
395 		GridNo     const base_grid_no = base->sGridNo;
396 
397 		// if the structure is openable, destroy all items there
398 		if (base->fFlags & STRUCTURE_OPENABLE && !(base->fFlags & STRUCTURE_DOOR))
399 		{
400 			RemoveAllUnburiedItems(base_grid_no, level);
401 		}
402 
403 		bool const is_explosive = pCurrent->fFlags & STRUCTURE_EXPLOSIVE;
404 
405 		// Get LEVELNODE for struct and remove!
406 		LEVELNODE* const node = FindLevelNodeBasedOnStructure(base);
407 
408 		INT8 const orig_destruction_partner = base->pDBStructureRef->pDBStructure->bDestructionPartner;
409 		/* ATE: if we have completely destroyed a structure, and this structure
410 		 * should have a in-between explosion partner, make damage code 2 - which
411 		 * means only damaged - the normal explosion spreading will cause it do use
412 		 * the proper pieces */
413 		if (bDamageReturnVal == STRUCTURE_DESTROYED && orig_destruction_partner < 0)
414 		{
415 			bDamageReturnVal = STRUCTURE_DAMAGED;
416 		}
417 
418 		INT8    destruction_partner = -1;
419 		BOOLEAN fContinue           = FALSE;
420 		if (bDamageReturnVal == STRUCTURE_DESTROYED)
421 		{
422 			fContinue = TRUE;
423 		}
424 		// Check for a damaged looking graphic
425 		else if (bDamageReturnVal == STRUCTURE_DAMAGED)
426 		{
427 			if (orig_destruction_partner < 0)
428 			{
429 				// We swap to another graphic!
430 				// It's -ve and 1-based, change to +ve, 1 based
431 				destruction_partner = -orig_destruction_partner;
432 				fContinue           = 2;
433 			}
434 		}
435 
436 		if (fContinue == 0) return true;
437 
438 		// Remove the beast!
439 		while (*ppNextCurrent && (*ppNextCurrent)->usStructureID == pCurrent->usStructureID)
440 		{ // The next structure will also be deleted, so skip past it
441 			*ppNextCurrent = (*ppNextCurrent)->pNext;
442 		}
443 
444 		// Replace with explosion debris if there are any....
445 		// ( and there already sin;t explosion debris there.... )
446 		if (orig_destruction_partner > 0)
447 		{
448 			// Alrighty add!
449 
450 			// Add to every gridno structure is in
451 			UINT8                     const n_tiles = base->pDBStructureRef->pDBStructure->ubNumberOfTiles;
452 			DB_STRUCTURE_TILE* const* const ppTile  = base->pDBStructureRef->ppTile;
453 
454 			destruction_partner = orig_destruction_partner;
455 
456 			// OK, destrcution index is , as default, the partner, until we go over the first set of explsion
457 			// debris...
458 			UINT16 const tile_idx = destruction_partner >= 40 ?
459 				GetTileIndexFromTypeSubIndex(SECONDEXPLDEBRIS, destruction_partner - 40) :
460 				GetTileIndexFromTypeSubIndex(FIRSTEXPLDEBRIS,  destruction_partner);
461 
462 			// Free all the non-base tiles; the base tile is at pointer 0
463 			for (UINT8 ubLoop = BASE_TILE; ubLoop != n_tiles; ++ubLoop)
464 			{
465 				if (ppTile[ubLoop]->fFlags & TILE_ON_ROOF) continue;
466 
467 				/* There might be two structures in this tile, one on each level, but
468 				 * we just want to delete one on each pass */
469 				GridNo const struct_grid_no = base_grid_no + ppTile[ubLoop]->sPosRelToBase;
470 				if (TypeRangeExistsInObjectLayer(struct_grid_no, FIRSTEXPLDEBRIS, SECONDEXPLDEBRIS)) continue;
471 
472 				ApplyMapChangesToMapTempFile app;
473 				AddObjectToHead(struct_grid_no, tile_idx + Random(3));
474 			}
475 
476 			// If we are a wall, add debris for the other side
477 			if (pCurrent->fFlags & STRUCTURE_WALLSTUFF)
478 			{
479 				switch (pCurrent->ubWallOrientation)
480 				{
481 					case OUTSIDE_TOP_LEFT:
482 					case INSIDE_TOP_LEFT:
483 					{
484 						GridNo const struct_grid_no = NewGridNo(base_grid_no, DirectionInc(SOUTH));
485 						if (!TypeRangeExistsInObjectLayer(struct_grid_no, FIRSTEXPLDEBRIS, SECONDEXPLDEBRIS))
486 						{
487 							ApplyMapChangesToMapTempFile app;
488 							AddObjectToHead(struct_grid_no, tile_idx + Random(3));
489 						}
490 						break;
491 					}
492 
493 					case OUTSIDE_TOP_RIGHT:
494 					case INSIDE_TOP_RIGHT:
495 					{
496 						GridNo const struct_grid_no = NewGridNo(base_grid_no, DirectionInc(EAST));
497 						if (!TypeRangeExistsInObjectLayer(struct_grid_no, FIRSTEXPLDEBRIS, SECONDEXPLDEBRIS))
498 						{
499 							ApplyMapChangesToMapTempFile app;
500 							AddObjectToHead(struct_grid_no, tile_idx + Random(3));
501 						}
502 						break;
503 					}
504 				}
505 			}
506 		}
507 		// Else look for fences, walk along them to change to destroyed pieces
508 		else if (pCurrent->fFlags & STRUCTURE_FENCE)
509 		{
510 			// walk along based on orientation
511 			switch (pCurrent->ubWallOrientation)
512 			{
513 				case OUTSIDE_TOP_RIGHT:
514 				case INSIDE_TOP_RIGHT:
515 					HandleFencePartnerCheck(NewGridNo(base_grid_no, DirectionInc(SOUTH)));
516 					HandleFencePartnerCheck(NewGridNo(base_grid_no, DirectionInc(NORTH)));
517 					break;
518 
519 				case OUTSIDE_TOP_LEFT:
520 				case INSIDE_TOP_LEFT:
521 					HandleFencePartnerCheck(NewGridNo(base_grid_no, DirectionInc(EAST)));
522 					HandleFencePartnerCheck(NewGridNo(base_grid_no, DirectionInc(WEST)));
523 					break;
524 			}
525 		}
526 
527 		// OK, Check if this is a wall, then search and change other walls based on this
528 		if (pCurrent->fFlags & STRUCTURE_WALLSTUFF)
529 		{
530 			/* ATE
531 			 * Remove any decals in tile
532 			 * Use tile database for this as apposed to stuct data */
533 			RemoveAllStructsOfTypeRange(base_grid_no, FIRSTWALLDECAL, FOURTHWALLDECAL);
534 			RemoveAllStructsOfTypeRange(base_grid_no, FIFTHWALLDECAL, EIGTHWALLDECAL);
535 
536 			/* Based on orientation, go either x or y dir, check for wall in both _ve
537 			 * and -ve directions and if found, then replace */
538 			switch (UINT8 const orientation = pCurrent->ubWallOrientation)
539 			{
540 				case OUTSIDE_TOP_LEFT:
541 				case INSIDE_TOP_LEFT:
542 					ReplaceWall(NewGridNo(base_grid_no, DirectionInc(WEST)), orientation, orientation == OUTSIDE_TOP_LEFT ? 48 : 52);
543 					ReplaceWall(NewGridNo(base_grid_no, DirectionInc(EAST)), orientation, orientation == OUTSIDE_TOP_LEFT ? 49 : 53);
544 
545 					// look for attached structures in same tile
546 					*ppNextCurrent = RemoveOnWall(base_grid_no, STRUCTURE_ON_LEFT_WALL, *ppNextCurrent);
547 
548 					// Move in SOUTH, looking for attached structures to remove
549 					RemoveOnWall(NewGridNo(base_grid_no, DirectionInc(SOUTH)), STRUCTURE_ON_LEFT_WALL, 0);
550 					break;
551 
552 				case OUTSIDE_TOP_RIGHT:
553 				case INSIDE_TOP_RIGHT:
554 					ReplaceWall(NewGridNo(base_grid_no, DirectionInc(NORTH)), orientation, orientation == OUTSIDE_TOP_RIGHT ? 51 : 55);
555 					ReplaceWall(NewGridNo(base_grid_no, DirectionInc(SOUTH)), orientation, orientation == OUTSIDE_TOP_RIGHT ? 50 : 54);
556 
557 					// looking for attached structures to remove in base tile
558 					*ppNextCurrent = RemoveOnWall(base_grid_no, STRUCTURE_ON_RIGHT_WALL, *ppNextCurrent);
559 
560 					// Move in EAST, looking for attached structures to remove
561 					RemoveOnWall(NewGridNo(base_grid_no, DirectionInc(EAST)), STRUCTURE_ON_RIGHT_WALL, 0);
562 					break;
563 			}
564 		}
565 
566 		// We need to remove the water from the fountain
567 		// Lots of HARD CODING HERE :(
568 		UINT32 const tile_type = GetTileType(node->usIndex);
569 		// Check if we are a fountain!
570 		if (gTilesets[giCurrentTilesetID].zTileSurfaceFilenames[tile_type].compare("fount1.sti", ST::case_insensitive) == 0)
571 		{ // Remove water
572 			ApplyMapChangesToMapTempFile app;
573 			UINT16 sNewIndex;
574 			sNewIndex = GetTileIndexFromTypeSubIndex(tile_type, 1);
575 			RemoveStruct(base_grid_no, sNewIndex);
576 			RemoveStruct(base_grid_no, sNewIndex);
577 			sNewIndex = GetTileIndexFromTypeSubIndex(tile_type, 2);
578 			RemoveStruct(base_grid_no, sNewIndex);
579 			RemoveStruct(base_grid_no, sNewIndex);
580 			sNewIndex = GetTileIndexFromTypeSubIndex(tile_type, 3);
581 			RemoveStruct(base_grid_no, sNewIndex);
582 			RemoveStruct(base_grid_no, sNewIndex);
583 		}
584 
585 		// Remove any interactive tiles we could be over
586 		BeginCurInteractiveTileCheck();
587 
588 		if (pCurrent->fFlags & STRUCTURE_WALLSTUFF)
589 		{
590 			RecompileLocalMovementCostsForWall(base_grid_no, base->ubWallOrientation);
591 		}
592 
593 		{ ApplyMapChangesToMapTempFile app;
594 			RemoveStructFromLevelNode(base_grid_no, node);
595 		}
596 
597 		// If we are to swap structures, do it now
598 		if (fContinue == 2)
599 		{ // We have a levelnode, get new index for new graphic
600 			UINT16 const tile_idx = GetTileIndexFromTypeSubIndex(tile_type, destruction_partner);
601 			ApplyMapChangesToMapTempFile app;
602 			AddStructToHead(base_grid_no, tile_idx);
603 		}
604 
605 		// Rerender world!
606 		// Reevaluate world movement costs, redundancy!
607 		gTacticalStatus.uiFlags |= NOHIDE_REDUNDENCY;
608 		// For the next render loop, re-evaluate redundent tiles
609 		InvalidateWorldRedundency();
610 		SetRenderFlags(RENDER_FLAG_FULL);
611 		// Movement costs!
612 		*pfRecompileMovementCosts = TRUE;
613 
614 		// Make secondary explosion if eplosive....
615 		if (is_explosive)
616 		{
617 			InternalIgniteExplosion(owner, CenterX(base_grid_no), CenterY(base_grid_no), 0, base_grid_no, STRUCTURE_EXPLOSION, FALSE, level);
618 		}
619 
620 		if (fContinue == 2) return false;
621 	}
622 
623 	return true;
624 }
625 
626 
ExplosiveDamageGridNo(const INT16 sGridNo,const INT16 sWoundAmt,const UINT32 uiDist,BOOLEAN * const pfRecompileMovementCosts,const BOOLEAN fOnlyWalls,INT8 bMultiStructSpecialFlag,SOLDIERTYPE * const owner,const INT8 bLevel)627 static void ExplosiveDamageGridNo(const INT16 sGridNo, const INT16 sWoundAmt, const UINT32 uiDist, BOOLEAN* const pfRecompileMovementCosts, const BOOLEAN fOnlyWalls, INT8 bMultiStructSpecialFlag, SOLDIERTYPE* const owner, const INT8 bLevel)
628 {
629 	STRUCTURE *pCurrent, *pNextCurrent, *pStructure;
630 	STRUCTURE *pBaseStructure;
631 	INT16     sDesiredLevel;
632 	UINT8     ubLoop, ubLoop2;
633 	INT16     sNewGridNo, sNewGridNo2;
634 	BOOLEAN   fToBreak = FALSE;
635 	BOOLEAN   fMultiStructure = FALSE;
636 	BOOLEAN   fMultiStructSpecialFlag = FALSE;
637 	BOOLEAN   fExplodeDamageReturn = FALSE;
638 
639 	std::vector<DB_STRUCTURE_TILE*> ppTile;
640 	GridNo              sBaseGridNo     = NOWHERE; // XXX HACK000E
641 	UINT8               ubNumberOfTiles = 0;       // XXX HACK000E
642 
643 	// Based on distance away, damage any struct at this gridno
644 	// OK, loop through structures and damage!
645 	pCurrent =  gpWorldLevelData[ sGridNo ].pStructureHead;
646 	sDesiredLevel  = STRUCTURE_ON_GROUND;
647 
648 	// This code gets a little hairy because
649 	// (1) we might need to destroy the currently-examined structure
650 	while (pCurrent != NULL)
651 	{
652 		// ATE: These are for the chacks below for multi-structs....
653 		pBaseStructure = FindBaseStructure( pCurrent );
654 
655 		if ( pBaseStructure )
656 		{
657 			sBaseGridNo = pBaseStructure->sGridNo;
658 			ubNumberOfTiles = pBaseStructure->pDBStructureRef->pDBStructure->ubNumberOfTiles;
659 			fMultiStructure = ( ( pBaseStructure->fFlags & STRUCTURE_MULTI ) != 0 );
660 			ppTile.assign(pBaseStructure->pDBStructureRef->ppTile, pBaseStructure->pDBStructureRef->ppTile + ubNumberOfTiles);
661 
662 			if ( bMultiStructSpecialFlag == -1 )
663 			{
664 				// Set it!
665 				bMultiStructSpecialFlag = ( ( pBaseStructure->fFlags & STRUCTURE_SPECIAL ) != 0 );
666 			}
667 
668 			if ( pBaseStructure->fFlags & STRUCTURE_EXPLOSIVE )
669 			{
670 				// ATE: Set hit points to zero....
671 				pBaseStructure->ubHitPoints = 0;
672 			}
673 		}
674 		else
675 		{
676 			fMultiStructure = FALSE;
677 		}
678 
679 		pNextCurrent = pCurrent->pNext;
680 
681 		// Check level!
682 		if (pCurrent->sCubeOffset == sDesiredLevel )
683 		{
684 			fExplodeDamageReturn = ExplosiveDamageStructureAtGridNo(pCurrent, &pNextCurrent, sGridNo, sWoundAmt, uiDist, pfRecompileMovementCosts, fOnlyWalls, owner, bLevel);
685 
686 			if ( !fExplodeDamageReturn )
687 			{
688 				fToBreak = TRUE;
689 			}
690 		}
691 
692 		// OK, for multi-structs...
693 		// AND we took damage...
694 		if ( fMultiStructure && !fOnlyWalls && fExplodeDamageReturn == 0 )
695 		{
696 			// ATE: Don't after first attack...
697 			if ( uiDist > 1 )
698 			{
699 				return;
700 			}
701 
702 			{
703 
704 				for ( ubLoop = BASE_TILE; ubLoop < ubNumberOfTiles; ubLoop++)
705 				{
706 					sNewGridNo = sBaseGridNo + ppTile[ubLoop]->sPosRelToBase;
707 
708 					// look in adjacent tiles
709 					for ( ubLoop2 = 0; ubLoop2 < NUM_WORLD_DIRECTIONS; ubLoop2++ )
710 					{
711 						sNewGridNo2 = NewGridNo( sNewGridNo, DirectionInc( ubLoop2 ) );
712 						if ( sNewGridNo2 != sNewGridNo && sNewGridNo2 != sGridNo )
713 						{
714 							pStructure = FindStructure( sNewGridNo2, STRUCTURE_MULTI );
715 							if ( pStructure )
716 							{
717 								fMultiStructSpecialFlag = ( ( pStructure->fFlags & STRUCTURE_SPECIAL ) != 0 );
718 
719 								if ( bMultiStructSpecialFlag == fMultiStructSpecialFlag )
720 								{
721 									// If we just damaged it, use same damage value....
722 									ExplosiveDamageGridNo(sNewGridNo2, sWoundAmt, uiDist, pfRecompileMovementCosts, fOnlyWalls, bMultiStructSpecialFlag, owner, bLevel);
723 
724 									InternalIgniteExplosion(owner, CenterX(sNewGridNo2), CenterY(sNewGridNo2), 0, sNewGridNo2, RDX, FALSE, bLevel);
725 
726 									fToBreak = TRUE;
727 								}
728 							}
729 						}
730 					}
731 				}
732 			}
733 			if ( fToBreak )
734 			{
735 				break;
736 			}
737 		}
738 
739 		if ( pBaseStructure )
740 		{
741 			ppTile.clear();
742 		}
743 
744 		pCurrent = pNextCurrent;
745 	}
746 }
747 
748 
DamageSoldierFromBlast(SOLDIERTYPE * const pSoldier,SOLDIERTYPE * const owner,const INT16 sBombGridNo,const INT16 sWoundAmt,const INT16 sBreathAmt,const UINT32 uiDist,const UINT16 usItem)749 static BOOLEAN DamageSoldierFromBlast(SOLDIERTYPE* const pSoldier, SOLDIERTYPE* const owner, const INT16 sBombGridNo, const INT16 sWoundAmt, const INT16 sBreathAmt, const UINT32 uiDist, const UINT16 usItem)
750 {
751 	INT16 sNewWoundAmt = 0;
752 	UINT8		ubDirection;
753 
754 	if (!pSoldier->bActive || !pSoldier->bInSector || !pSoldier->bLife )
755 		return( FALSE );
756 
757 	if ( pSoldier->ubMiscSoldierFlags & SOLDIER_MISC_HURT_BY_EXPLOSION )
758 	{
759 		// don't want to damage the guy twice
760 		return( FALSE );
761 	}
762 
763 	// Direction to center of explosion
764 	ubDirection = (UINT8)GetDirectionFromGridNo( sBombGridNo, pSoldier );
765 
766 	// Increment attack counter...
767 	gTacticalStatus.ubAttackBusyCount++;
768 	SLOGD("Incrementing Attack: Explosion dishing out damage, Count now %d", gTacticalStatus.ubAttackBusyCount);
769 
770 	sNewWoundAmt = sWoundAmt - __min( sWoundAmt, 35 ) * ArmourVersusExplosivesPercent( pSoldier ) / 100;
771 	if ( sNewWoundAmt < 0 )
772 	{
773 		sNewWoundAmt = 0;
774 	}
775 	EVENT_SoldierGotHit(pSoldier, usItem, sNewWoundAmt, sBreathAmt, ubDirection, uiDist, owner, 0, ANIM_CROUCH, sBombGridNo);
776 
777 	pSoldier->ubMiscSoldierFlags |= SOLDIER_MISC_HURT_BY_EXPLOSION;
778 
779 	if (owner != NULL && owner->bTeam == OUR_TEAM && pSoldier->bTeam != OUR_TEAM)
780 	{
781 		ProcessImplicationsOfPCAttack(owner, pSoldier, REASON_EXPLOSION);
782 	}
783 
784 	return( TRUE );
785 }
786 
787 
DishOutGasDamage(SOLDIERTYPE * const pSoldier,EXPLOSIVETYPE const * const pExplosive,INT16 const sSubsequent,BOOLEAN const fRecompileMovementCosts,INT16 sWoundAmt,INT16 sBreathAmt,SOLDIERTYPE * const owner)788 BOOLEAN DishOutGasDamage(SOLDIERTYPE* const pSoldier, EXPLOSIVETYPE const* const pExplosive, INT16 const sSubsequent, BOOLEAN const fRecompileMovementCosts, INT16 sWoundAmt, INT16 sBreathAmt, SOLDIERTYPE* const owner)
789 {
790 	INT8 bPosOfMask = NO_SLOT;
791 
792 	if (!pSoldier->bActive || !pSoldier->bInSector || !pSoldier->bLife || AM_A_ROBOT( pSoldier ) )
793 	{
794 		return( fRecompileMovementCosts );
795 	}
796 
797 	if ( pExplosive->ubType == EXPLOSV_CREATUREGAS )
798 	{
799 		if ( pSoldier->uiStatusFlags & SOLDIER_MONSTER )
800 		{
801 			// unaffected by own gas effects
802 			return( fRecompileMovementCosts );
803 		}
804 		if ( sSubsequent && pSoldier->fHitByGasFlags & HIT_BY_CREATUREGAS )
805 		{
806 			// already affected by creature gas this turn
807 			return( fRecompileMovementCosts );
808 		}
809 	}
810 	else // no gas mask help from creature attacks
811 	// ATE/CJC: gas stuff
812 	{
813 		if ( pExplosive->ubType == EXPLOSV_TEARGAS )
814 		{
815 			if ( AM_A_ROBOT( pSoldier ) )
816 			{
817 				return( fRecompileMovementCosts );
818 			}
819 
820 			// ignore whether subsequent or not if hit this turn
821 			if ( pSoldier->fHitByGasFlags & HIT_BY_TEARGAS )
822 			{
823 				// already affected by creature gas this turn
824 				return( fRecompileMovementCosts );
825 			}
826 		}
827 		else if ( pExplosive->ubType == EXPLOSV_MUSTGAS )
828 		{
829 			if ( AM_A_ROBOT( pSoldier ) )
830 			{
831 				return( fRecompileMovementCosts );
832 			}
833 
834 			if ( sSubsequent && pSoldier->fHitByGasFlags & HIT_BY_MUSTARDGAS )
835 			{
836 				// already affected by creature gas this turn
837 				return( fRecompileMovementCosts );
838 			}
839 
840 		}
841 
842 		if ( pSoldier->inv[ HEAD1POS ].usItem == GASMASK && pSoldier->inv[ HEAD1POS ].bStatus[0] >= USABLE )
843 		{
844 			bPosOfMask = HEAD1POS;
845 		}
846 		else if ( pSoldier->inv[ HEAD2POS ].usItem == GASMASK && pSoldier->inv[ HEAD2POS ].bStatus[0] >= USABLE )
847 		{
848 			bPosOfMask = HEAD2POS;
849 		}
850 
851 		if ( bPosOfMask != NO_SLOT  )
852 		{
853 			if ( pSoldier->inv[ bPosOfMask ].bStatus[0] < GASMASK_MIN_STATUS )
854 			{
855 				// GAS MASK reduces breath loss by its work% (it leaks if not at least 70%)
856 				sBreathAmt = ( sBreathAmt * ( 100 - pSoldier->inv[ bPosOfMask ].bStatus[0] ) ) / 100;
857 				if ( sBreathAmt > 500 )
858 				{
859 					// if at least 500 of breath damage got through
860 					// the soldier within the blast radius is gassed for at least one
861 					// turn, possibly more if it's tear gas (which hangs around a while)
862 					pSoldier->uiStatusFlags |= SOLDIER_GASSED;
863 				}
864 
865 				if ( pSoldier->uiStatusFlags & SOLDIER_PC )
866 				{
867 
868 					if ( sWoundAmt > 1 )
869 					{
870 						pSoldier->inv[ bPosOfMask ].bStatus[0] -= (INT8) Random( 4 );
871 						sWoundAmt = ( sWoundAmt * ( 100 -  pSoldier->inv[ bPosOfMask ].bStatus[0] ) ) / 100;
872 					}
873 					else if ( sWoundAmt == 1 )
874 					{
875 						pSoldier->inv[ bPosOfMask ].bStatus[0] -= (INT8) Random( 2 );
876 					}
877 				}
878 			}
879 			else
880 			{
881 				sBreathAmt = 0;
882 				if ( sWoundAmt > 0 )
883 				{
884 					if ( sWoundAmt == 1 )
885 					{
886 						pSoldier->inv[ bPosOfMask ].bStatus[0] -= (INT8) Random( 2 );
887 					}
888 					else
889 					{
890 						// use up gas mask
891 						pSoldier->inv[ bPosOfMask ].bStatus[0] -= (INT8) Random( 4 );
892 					}
893 				}
894 				sWoundAmt = 0;
895 			}
896 
897 		}
898 	}
899 
900 	if ( sWoundAmt != 0 || sBreathAmt != 0 )
901 	{
902 		switch( pExplosive->ubType )
903 		{
904 			case EXPLOSV_CREATUREGAS:
905 				pSoldier->fHitByGasFlags |= HIT_BY_CREATUREGAS;
906 				break;
907 			case EXPLOSV_TEARGAS:
908 				pSoldier->fHitByGasFlags |= HIT_BY_TEARGAS;
909 				break;
910 			case EXPLOSV_MUSTGAS:
911 				pSoldier->fHitByGasFlags |= HIT_BY_MUSTARDGAS;
912 				break;
913 			default:
914 				break;
915 		}
916 		// a gas effect, take damage directly...
917 		SoldierTakeDamage(pSoldier, sWoundAmt, sBreathAmt, TAKE_DAMAGE_GAS, NULL);
918 		if ( pSoldier->bLife >= CONSCIOUSNESS )
919 		{
920 			DoMercBattleSound(pSoldier, BATTLE_SOUND_HIT1);
921 		}
922 
923 		if (owner != NULL && owner->bTeam == OUR_TEAM && pSoldier->bTeam != OUR_TEAM)
924 		{
925 			ProcessImplicationsOfPCAttack(owner, pSoldier, REASON_EXPLOSION);
926 		}
927 	}
928 	return( fRecompileMovementCosts );
929 }
930 
931 
932 static void HandleBuldingDestruction(INT16 sGridNo, const SOLDIERTYPE* owner);
933 
934 
935 // Spreads the effects of explosions...
ExpAffect(const INT16 sBombGridNo,const INT16 sGridNo,const UINT32 uiDist,const UINT16 usItem,SOLDIERTYPE * const owner,const INT16 sSubsequent,BOOLEAN * const pfMercHit,const INT8 bLevel,const SMOKEEFFECT * const smoke)936 static BOOLEAN ExpAffect(const INT16 sBombGridNo, const INT16 sGridNo, const UINT32 uiDist, const UINT16 usItem, SOLDIERTYPE* const owner, const INT16 sSubsequent, BOOLEAN* const pfMercHit, const INT8 bLevel, const SMOKEEFFECT* const smoke)
937 {
938 	INT16 sWoundAmt = 0, sBreathAmt = 0, sStructDmgAmt;
939 	BOOLEAN fRecompileMovementCosts = FALSE;
940 	BOOLEAN fSmokeEffect=FALSE;
941 	BOOLEAN fStunEffect = FALSE;
942 	SmokeEffectKind bSmokeEffectType = NO_SMOKE_EFFECT;
943 	BOOLEAN	fBlastEffect = TRUE;
944 	INT16		sNewGridNo;
945 	UINT32	uiRoll;
946 
947 	if ( sSubsequent == BLOOD_SPREAD_EFFECT )
948 	{
949 		fSmokeEffect = FALSE;
950 		fBlastEffect = FALSE;
951 	}
952 	else
953 	{
954 		// Turn off blast effect if some types of items...
955 		switch( usItem )
956 		{
957 			case MUSTARD_GRENADE:
958 				fSmokeEffect = TRUE;
959 				bSmokeEffectType = MUSTARDGAS_SMOKE_EFFECT;
960 				fBlastEffect = FALSE;
961 				break;
962 
963 			case TEARGAS_GRENADE:
964 			case GL_TEARGAS_GRENADE:
965 			case BIG_TEAR_GAS:
966 				fSmokeEffect = TRUE;
967 				bSmokeEffectType = TEARGAS_SMOKE_EFFECT;
968 				fBlastEffect = FALSE;
969 				break;
970 
971 			case SMOKE_GRENADE:
972 			case GL_SMOKE_GRENADE:
973 				fSmokeEffect = TRUE;
974 				bSmokeEffectType = NORMAL_SMOKE_EFFECT;
975 				fBlastEffect = FALSE;
976 				break;
977 
978 			case STUN_GRENADE:
979 			case GL_STUN_GRENADE:
980 				fStunEffect = TRUE;
981 				break;
982 
983 			case SMALL_CREATURE_GAS:
984 			case LARGE_CREATURE_GAS:
985 			case VERY_SMALL_CREATURE_GAS:
986 				fSmokeEffect = TRUE;
987 				bSmokeEffectType = CREATURE_SMOKE_EFFECT;
988 				fBlastEffect = FALSE;
989 				break;
990 		}
991 	}
992 
993 
994 	// OK, here we:
995 	// Get explosive data from table
996 	EXPLOSIVETYPE const* const pExplosive = &Explosive[GCM->getItem(usItem)->getClassIndex()];
997 
998 	uiRoll = PreRandom( 100 );
999 
1000 	// Calculate wound amount
1001 	sWoundAmt = pExplosive->ubDamage + (INT16) ( (pExplosive->ubDamage * uiRoll) / 100 );
1002 
1003 	// Calculate breath amount ( if stun damage applicable )
1004 	sBreathAmt = ( pExplosive->ubStunDamage * 100 ) + (INT16) ( ( ( pExplosive->ubStunDamage / 2 ) * 100 * uiRoll ) / 100 ) ;
1005 
1006 	// ATE: Make sure guys get pissed at us!
1007 	HandleBuldingDestruction(sGridNo, owner);
1008 
1009 
1010 	if ( fBlastEffect )
1011 	{
1012 		// lower effects for distance away from center of explosion
1013 		// If radius is 3, damage % is (100)/66/33/17
1014 		// If radius is 5, damage % is (100)/80/60/40/20/10
1015 		// If radius is 8, damage % is (100)/88/75/63/50/37/25/13/6
1016 
1017 		if ( pExplosive->ubRadius == 0 )
1018 		{
1019 			// leave as is, has to be at range 0 here
1020 		}
1021 		else if (uiDist < pExplosive->ubRadius)
1022 		{
1023 			// if radius is 5, go down by 5ths ~ 20%
1024 			sWoundAmt -= (INT16)  (sWoundAmt * uiDist / pExplosive->ubRadius );
1025 			sBreathAmt -= (INT16) (sBreathAmt * uiDist / pExplosive->ubRadius );
1026 		}
1027 		else
1028 		{
1029 			// at the edge of the explosion, do half the previous damage
1030 			sWoundAmt = (INT16) ( (sWoundAmt / pExplosive->ubRadius) / 2);
1031 			sBreathAmt = (INT16) ( (sBreathAmt / pExplosive->ubRadius) / 2);
1032 		}
1033 
1034 		if (sWoundAmt < 0)
1035 			sWoundAmt = 0;
1036 
1037 		if (sBreathAmt < 0)
1038 			sBreathAmt = 0;
1039 
1040 		// damage structures
1041 		if ( uiDist <= __max( 1, (UINT32) (pExplosive->ubDamage / 30) ) )
1042 		{
1043 			if ( GCM->getItem(usItem)->isGrenade() )
1044 			{
1045 				sStructDmgAmt = sWoundAmt / 3;
1046 			}
1047 			else // most explosives
1048 			{
1049 				sStructDmgAmt = sWoundAmt;
1050 			}
1051 
1052 			ExplosiveDamageGridNo(sGridNo, sStructDmgAmt, uiDist, &fRecompileMovementCosts, FALSE, -1, owner, bLevel);
1053 
1054 			// ATE: Look for damage to walls ONLY for next two gridnos
1055 			sNewGridNo = NewGridNo( sGridNo, DirectionInc( NORTH ) );
1056 
1057 			if ( GridNoOnVisibleWorldTile( sNewGridNo ) )
1058 			{
1059 				ExplosiveDamageGridNo(sNewGridNo, sStructDmgAmt, uiDist, &fRecompileMovementCosts, TRUE, -1, owner, bLevel);
1060 			}
1061 
1062 			// ATE: Look for damage to walls ONLY for next two gridnos
1063 			sNewGridNo = NewGridNo( sGridNo, DirectionInc( WEST ) );
1064 
1065 			if ( GridNoOnVisibleWorldTile( sNewGridNo ) )
1066 			{
1067 				ExplosiveDamageGridNo(sNewGridNo, sStructDmgAmt, uiDist, &fRecompileMovementCosts, TRUE, -1, owner, bLevel);
1068 			}
1069 		}
1070 
1071 		// Add burn marks to ground randomly....
1072 		if ( Random( 50 ) < 15 && uiDist == 1 )
1073 		{
1074 			//if (!TypeRangeExistsInObjectLayer(sGridNo, FIRSTEXPLDEBRIS, SECONDEXPLDEBRIS))
1075 			//{
1076 			//	UINT16 usTileIndex = GetTileIndexFromTypeSubIndex(SECONDEXPLDEBRIS, Random(10) + 1);
1077 			//	AddObjectToHead( sGridNo, usTileIndex );
1078 			//	SetRenderFlags(RENDER_FLAG_FULL);
1079 			//}
1080 		}
1081 
1082 		// NB radius can be 0 so cannot divide it by 2 here
1083 		if (!fStunEffect && (uiDist * 2 <= pExplosive->ubRadius)  )
1084 		{
1085 			const ITEM_POOL* pItemPool = GetItemPool(sGridNo, bLevel);
1086 
1087 			while( pItemPool )
1088 			{
1089 				const ITEM_POOL* pItemPoolNext = pItemPool->pNext;
1090 
1091 				WORLDITEM& wi = GetWorldItem(pItemPool->iItemIndex);
1092 				if (DamageItemOnGround(&wi.o, sGridNo, bLevel, sWoundAmt * 2, owner))
1093 				{
1094 					// item was destroyed
1095 					RemoveItemFromPool(wi);
1096 				}
1097 				pItemPool = pItemPoolNext;
1098 			}
1099 		}
1100 	}
1101 	else if ( fSmokeEffect )
1102 	{
1103 		// If tear gar, determine turns to spread.....
1104 		if ( sSubsequent == ERASE_SPREAD_EFFECT )
1105 		{
1106 			RemoveSmokeEffectFromTile( sGridNo, bLevel );
1107 		}
1108 		else if ( sSubsequent != REDO_SPREAD_EFFECT )
1109 		{
1110 			AddSmokeEffectToTile(smoke, bSmokeEffectType, sGridNo, bLevel);
1111 		}
1112 	}
1113 	else
1114 	{
1115 		// Drop blood ....
1116 		// Get blood quantity....
1117 		InternalDropBlood(sGridNo, 0, HUMAN, __max(MAXBLOODQUANTITY - uiDist * 2 /* XXX always >= 0, because uiDist is unsigned */, 0), 1);
1118 	}
1119 
1120 	if ( sSubsequent != ERASE_SPREAD_EFFECT && sSubsequent != BLOOD_SPREAD_EFFECT )
1121 	{
1122 		// if an explosion effect....
1123 		if ( fBlastEffect )
1124 		{
1125 			// don't hurt anyone who is already dead & waiting to be removed
1126 			SOLDIERTYPE* const tgt = WhoIsThere2(sGridNo, bLevel);
1127 			if (tgt != NULL)
1128 			{
1129 				DamageSoldierFromBlast(tgt, owner, sBombGridNo, sWoundAmt, sBreathAmt, uiDist, usItem);
1130 			}
1131 
1132 			if ( bLevel == 1 )
1133 			{
1134 				SOLDIERTYPE* const tgt_below = WhoIsThere2(sGridNo, 0);
1135 				if (tgt_below != NULL)
1136 				{
1137 					if ( (sWoundAmt / 2) > 20 )
1138 					{
1139 						// debris damage!
1140 						const INT16 breath = sBreathAmt / 2 - 20 > 0 ? Random(sBreathAmt / 2 - 20) : 1;
1141 						DamageSoldierFromBlast(tgt_below, owner, sBombGridNo, Random(sWoundAmt / 2 - 20), breath, uiDist, usItem);
1142 					}
1143 				}
1144 			}
1145 		}
1146 		else
1147 		{
1148 			SOLDIERTYPE* const pSoldier = WhoIsThere2(sGridNo, bLevel);
1149 			if (pSoldier == NULL) return fRecompileMovementCosts;
1150 			// someone is here, and they're gonna get hurt
1151 
1152 			fRecompileMovementCosts = DishOutGasDamage(pSoldier, pExplosive, sSubsequent, fRecompileMovementCosts, sWoundAmt, sBreathAmt, owner);
1153 /*
1154 			if (!pSoldier->bActive || !pSoldier->bInSector || !pSoldier->bLife || AM_A_ROBOT( pSoldier ) )
1155 			{
1156 				return( fRecompileMovementCosts );
1157 			}
1158 
1159 			if ( pExplosive->ubType == EXPLOSV_CREATUREGAS )
1160 			{
1161 				if ( pSoldier->uiStatusFlags & SOLDIER_MONSTER )
1162 				{
1163 					// unaffected by own gas effects
1164 					return( fRecompileMovementCosts );
1165 				}
1166 				if ( sSubsequent && pSoldier->fHitByGasFlags & HIT_BY_CREATUREGAS )
1167 				{
1168 					// already affected by creature gas this turn
1169 					return( fRecompileMovementCosts );
1170 				}
1171 			}
1172 			else // no gas mask help from creature attacks
1173 				// ATE/CJC: gas stuff
1174 				{
1175 					INT8 bPosOfMask = NO_SLOT;
1176 
1177 
1178 				if ( pExplosive->ubType == EXPLOSV_TEARGAS )
1179 				{
1180 					// ignore whether subsequent or not if hit this turn
1181 					if ( pSoldier->fHitByGasFlags & HIT_BY_TEARGAS )
1182 					{
1183 						// already affected by creature gas this turn
1184 						return( fRecompileMovementCosts );
1185 					}
1186 				}
1187 				else if ( pExplosive->ubType == EXPLOSV_MUSTGAS )
1188 				{
1189 					if ( sSubsequent && pSoldier->fHitByGasFlags & HIT_BY_MUSTARDGAS )
1190 					{
1191 						// already affected by creature gas this turn
1192 						return( fRecompileMovementCosts );
1193 					}
1194 
1195 				}
1196 
1197 				if ( sSubsequent && pSoldier->fHitByGasFlags & HIT_BY_CREATUREGAS )
1198 				{
1199 					// already affected by creature gas this turn
1200 					return( fRecompileMovementCosts );
1201 				}
1202 
1203 
1204 				if ( pSoldier->inv[ HEAD1POS ].usItem == GASMASK && pSoldier->inv[ HEAD1POS ].bStatus[0] >= USABLE )
1205 				{
1206 					bPosOfMask = HEAD1POS;
1207 				}
1208 				else if ( pSoldier->inv[ HEAD2POS ].usItem == GASMASK && pSoldier->inv[ HEAD2POS ].bStatus[0] >= USABLE )
1209 				{
1210 					bPosOfMask = HEAD2POS;
1211 				}
1212 
1213 				if ( bPosOfMask != NO_SLOT  )
1214 				{
1215 					if ( pSoldier->inv[ bPosOfMask ].bStatus[0] < GASMASK_MIN_STATUS )
1216 					{
1217 						// GAS MASK reduces breath loss by its work% (it leaks if not at least 70%)
1218 						sBreathAmt = ( sBreathAmt * ( 100 - pSoldier->inv[ bPosOfMask ].bStatus[0] ) ) / 100;
1219 						if ( sBreathAmt > 500 )
1220 						{
1221 							// if at least 500 of breath damage got through
1222 							// the soldier within the blast radius is gassed for at least one
1223 							// turn, possibly more if it's tear gas (which hangs around a while)
1224 							pSoldier->uiStatusFlags |= SOLDIER_GASSED;
1225 						}
1226 
1227 						if ( sWoundAmt > 1 )
1228 						{
1229 							pSoldier->inv[ bPosOfMask ].bStatus[0] -= (INT8) Random( 4 );
1230 							sWoundAmt = ( sWoundAmt * ( 100 -  pSoldier->inv[ bPosOfMask ].bStatus[0] ) ) / 100;
1231 						}
1232 						else if ( sWoundAmt == 1 )
1233 						{
1234 							pSoldier->inv[ bPosOfMask ].bStatus[0] -= (INT8) Random( 2 );
1235 						}
1236 					}
1237 					else
1238 					{
1239 						sBreathAmt = 0;
1240 						if ( sWoundAmt > 0 )
1241 						{
1242 							if ( sWoundAmt == 1 )
1243 							{
1244 								pSoldier->inv[ bPosOfMask ].bStatus[0] -= (INT8) Random( 2 );
1245 							}
1246 							else
1247 							{
1248 								// use up gas mask
1249 								pSoldier->inv[ bPosOfMask ].bStatus[0] -= (INT8) Random( 4 );
1250 							}
1251 						}
1252 						sWoundAmt = 0;
1253 					}
1254 
1255 				}
1256 			}
1257 
1258 			if ( sWoundAmt != 0 || sBreathAmt != 0 )
1259 			{
1260 				switch( pExplosive->ubType )
1261 				{
1262 					case EXPLOSV_CREATUREGAS:
1263 						pSoldier->fHitByGasFlags |= HIT_BY_CREATUREGAS;
1264 						break;
1265 					case EXPLOSV_TEARGAS:
1266 						pSoldier->fHitByGasFlags |= HIT_BY_TEARGAS;
1267 						break;
1268 					case EXPLOSV_MUSTGAS:
1269 						pSoldier->fHitByGasFlags |= HIT_BY_MUSTARDGAS;
1270 						break;
1271 					default:
1272 						break;
1273 				}
1274 				// a gas effect, take damage directly...
1275 				SoldierTakeDamage(pSoldier, sWoundAmt, sBreathAmt, TAKE_DAMAGE_GAS, NULL);
1276 				if ( pSoldier->bLife >= CONSCIOUSNESS )
1277 				{
1278 					DoMercBattleSound(pSoldier, BATTLE_SOUND_HIT1);
1279 				}
1280 			}
1281 			*/
1282 		}
1283 
1284 		(*pfMercHit) = TRUE;
1285 	}
1286 
1287 	return( fRecompileMovementCosts );
1288 }
1289 
1290 
GetRayStopInfo(UINT32 uiNewSpot,UINT8 ubDir,INT8 bLevel,BOOLEAN fSmokeEffect,INT32 uiCurRange,INT32 * piMaxRange,UINT8 * pubKeepGoing)1291 static void GetRayStopInfo(UINT32 uiNewSpot, UINT8 ubDir, INT8 bLevel, BOOLEAN fSmokeEffect, INT32 uiCurRange, INT32* piMaxRange, UINT8* pubKeepGoing)
1292 {
1293 	INT8      bStructHeight;
1294 	UINT8     ubMovementCost;
1295 	INT8      Blocking, BlockingTemp;
1296 	BOOLEAN   fTravelCostObs = FALSE;
1297 	UINT32    uiRangeReduce;
1298 	INT16     sNewGridNo;
1299 	STRUCTURE *pBlockingStructure;
1300 	BOOLEAN   fBlowWindowSouth = FALSE;
1301 	BOOLEAN   fReduceRay = TRUE;
1302 
1303 	ubMovementCost = gubWorldMovementCosts[ uiNewSpot ][ ubDir ][ bLevel ];
1304 
1305 	if ( IS_TRAVELCOST_DOOR( ubMovementCost ) )
1306 	{
1307 		ubMovementCost = DoorTravelCost( NULL, uiNewSpot, ubMovementCost, FALSE, NULL );
1308 		// If we have hit a wall, STOP HERE
1309 		if (ubMovementCost >= TRAVELCOST_BLOCKED)
1310 		{
1311 			fTravelCostObs  = TRUE;
1312 		}
1313 	}
1314 	else
1315 	{
1316 		// If we have hit a wall, STOP HERE
1317 		if ( ubMovementCost == TRAVELCOST_WALL )
1318 		{
1319 			// We have an obstacle here..
1320 			fTravelCostObs = TRUE;
1321 		}
1322 	}
1323 
1324 
1325 	Blocking = GetBlockingStructureInfo( (INT16)uiNewSpot, ubDir, 0, bLevel, &bStructHeight, &pBlockingStructure, TRUE );
1326 
1327 	if ( pBlockingStructure )
1328 	{
1329 		if ( pBlockingStructure->fFlags & STRUCTURE_CAVEWALL )
1330 		{
1331 			// block completely!
1332 			fTravelCostObs = TRUE;
1333 		}
1334 		else if ( pBlockingStructure->pDBStructureRef->pDBStructure->ubDensity <= 15 )
1335 		{
1336 		// not stopped
1337 		fTravelCostObs = FALSE;
1338 		fReduceRay = FALSE;
1339 		}
1340 	}
1341 
1342 	if ( fTravelCostObs )
1343 	{
1344 
1345 		if ( fSmokeEffect )
1346 		{
1347 			if ( Blocking == BLOCKING_TOPRIGHT_OPEN_WINDOW || Blocking == BLOCKING_TOPLEFT_OPEN_WINDOW )
1348 			{
1349 				// If open, fTravelCostObs set to false and reduce range....
1350 				fTravelCostObs = FALSE;
1351 				// Range will be reduced below...
1352 			}
1353 
1354 			if ( fTravelCostObs )
1355 			{
1356 				// ATE: For windows, check to the west and north for a broken window, as movement costs
1357 				// will override there...
1358 				sNewGridNo = NewGridNo( (INT16)uiNewSpot, DirectionInc( WEST ) );
1359 
1360 				BlockingTemp = GetBlockingStructureInfo( (INT16)sNewGridNo, ubDir, 0, bLevel, &bStructHeight, &pBlockingStructure, TRUE );
1361 				if ( BlockingTemp == BLOCKING_TOPRIGHT_OPEN_WINDOW || BlockingTemp == BLOCKING_TOPLEFT_OPEN_WINDOW )
1362 				{
1363 					// If open, fTravelCostObs set to false and reduce range....
1364 					fTravelCostObs = FALSE;
1365 					// Range will be reduced below...
1366 				}
1367 				if ( pBlockingStructure && pBlockingStructure->pDBStructureRef->pDBStructure->ubDensity <= 15 )
1368 				{
1369 					fTravelCostObs = FALSE;
1370 					fReduceRay = FALSE;
1371 				}
1372 			}
1373 
1374 			if ( fTravelCostObs )
1375 			{
1376 				sNewGridNo = NewGridNo( (INT16)uiNewSpot, DirectionInc( NORTH ) );
1377 
1378 				BlockingTemp = GetBlockingStructureInfo( (INT16)sNewGridNo, ubDir, 0, bLevel, &bStructHeight, &pBlockingStructure, TRUE );
1379 				if ( BlockingTemp == BLOCKING_TOPRIGHT_OPEN_WINDOW || BlockingTemp == BLOCKING_TOPLEFT_OPEN_WINDOW )
1380 				{
1381 					// If open, fTravelCostObs set to false and reduce range....
1382 					fTravelCostObs = FALSE;
1383 					// Range will be reduced below...
1384 				}
1385 				if ( pBlockingStructure && pBlockingStructure->pDBStructureRef->pDBStructure->ubDensity <= 15 )
1386 				{
1387 					fTravelCostObs = FALSE;
1388 					fReduceRay = FALSE;
1389 				}
1390 			}
1391 
1392 		}
1393 		else
1394 		{
1395 			// We are a blast effect....
1396 
1397 			// ATE: explode windows!!!!
1398 			if ( Blocking == BLOCKING_TOPLEFT_WINDOW || Blocking == BLOCKING_TOPRIGHT_WINDOW )
1399 			{
1400 				// Explode!
1401 				if ( ubDir == SOUTH || ubDir == SOUTHEAST || ubDir == SOUTHWEST )
1402 				{
1403 					fBlowWindowSouth = TRUE;
1404 				}
1405 
1406 				if ( pBlockingStructure != NULL )
1407 				{
1408 					WindowHit( (INT16)uiNewSpot, pBlockingStructure->usStructureID, fBlowWindowSouth, TRUE );
1409 				}
1410 			}
1411 
1412 			// ATE: For windows, check to the west and north for a broken window, as movement costs
1413 			// will override there...
1414 			sNewGridNo = NewGridNo( (INT16)uiNewSpot, DirectionInc( WEST ) );
1415 
1416 			BlockingTemp = GetBlockingStructureInfo( (INT16)sNewGridNo, ubDir, 0, bLevel, &bStructHeight, &pBlockingStructure , TRUE );
1417 			if ( pBlockingStructure && pBlockingStructure->pDBStructureRef->pDBStructure->ubDensity <= 15 )
1418 			{
1419 				fTravelCostObs = FALSE;
1420 				fReduceRay = FALSE;
1421 			}
1422 			if ( BlockingTemp == BLOCKING_TOPRIGHT_WINDOW || BlockingTemp == BLOCKING_TOPLEFT_WINDOW )
1423 			{
1424 				if ( pBlockingStructure != NULL )
1425 				{
1426 					WindowHit( sNewGridNo, pBlockingStructure->usStructureID, FALSE, TRUE );
1427 				}
1428 			}
1429 
1430 			sNewGridNo = NewGridNo( (INT16)uiNewSpot, DirectionInc( NORTH ) );
1431 			BlockingTemp = GetBlockingStructureInfo( (INT16)sNewGridNo, ubDir, 0, bLevel, &bStructHeight, &pBlockingStructure, TRUE );
1432 
1433 			if ( pBlockingStructure && pBlockingStructure->pDBStructureRef->pDBStructure->ubDensity <= 15 )
1434 			{
1435 				fTravelCostObs = FALSE;
1436 				fReduceRay = FALSE;
1437 			}
1438 			if ( BlockingTemp == BLOCKING_TOPRIGHT_WINDOW || BlockingTemp == BLOCKING_TOPLEFT_WINDOW )
1439 			{
1440 				if ( pBlockingStructure != NULL )
1441 				{
1442 					WindowHit( sNewGridNo, pBlockingStructure->usStructureID, FALSE, TRUE );
1443 				}
1444 			}
1445 		}
1446 	}
1447 
1448 	// Have we hit things like furniture, etc?
1449 	if ( Blocking != NOTHING_BLOCKING && !fTravelCostObs )
1450 	{
1451 		// ATE: Tall things should blaock all; Default wall/door height is 4
1452 		if ( bStructHeight > 4 )
1453 		{
1454 			(*pubKeepGoing) = FALSE;
1455 		}
1456 		else
1457 		{
1458 			// If we are smoke, reduce range variably....
1459 			if ( fReduceRay )
1460 			{
1461 				if ( fSmokeEffect )
1462 				{
1463 					switch( bStructHeight )
1464 					{
1465 						case 3:
1466 							uiRangeReduce = 2;
1467 							break;
1468 						case 2:
1469 							uiRangeReduce = 1;
1470 							break;
1471 						default:
1472 							uiRangeReduce = 0;
1473 							break;
1474 					}
1475 				}
1476 				else
1477 				{
1478 					uiRangeReduce = 2;
1479 				}
1480 
1481 				(*piMaxRange) -= uiRangeReduce;
1482 			}
1483 
1484 			if ( uiCurRange <= (*piMaxRange) )
1485 			{
1486 				(*pubKeepGoing) = TRUE;
1487 			}
1488 			else
1489 			{
1490 				(*pubKeepGoing) = FALSE;
1491 			}
1492 		}
1493 	}
1494 	else
1495 	{
1496 		if ( fTravelCostObs )
1497 		{
1498 			(*pubKeepGoing) = FALSE;
1499 		}
1500 		else
1501 		{
1502 			(*pubKeepGoing) = TRUE;
1503 		}
1504 	}
1505 
1506 	if (bLevel == 1)
1507 	{
1508 		// We check for roof-level and structure to prevent smoke spreading over roof
1509 		STRUCTURE * pStructure = FindStructure( uiNewSpot, STRUCTURE_ROOF );
1510 		if (pStructure == NULL)
1511 		{
1512 			// no structure found therefore we can't spread
1513 			(*pubKeepGoing) = FALSE;
1514 		}
1515 	}
1516 }
1517 
1518 
SpreadEffect(const INT16 sGridNo,const UINT8 ubRadius,const UINT16 usItem,SOLDIERTYPE * const owner,const BOOLEAN fSubsequent,const INT8 bLevel,const SMOKEEFFECT * const smoke)1519 void SpreadEffect(const INT16 sGridNo, const UINT8 ubRadius, const UINT16 usItem, SOLDIERTYPE* const owner, const BOOLEAN fSubsequent, const INT8 bLevel, const SMOKEEFFECT* const smoke)
1520 {
1521 	INT32   uiNewSpot, uiTempSpot, uiBranchSpot, branchCnt;
1522 	INT32   uiTempRange, ubBranchRange;
1523 	UINT8   ubDir,ubBranchDir, ubKeepGoing;
1524 	INT16   sRange;
1525 	BOOLEAN fRecompileMovement = FALSE;
1526 	BOOLEAN fAnyMercHit = FALSE;
1527 	BOOLEAN fSmokeEffect = FALSE;
1528 
1529 	switch( usItem )
1530 	{
1531 		case MUSTARD_GRENADE:
1532 		case TEARGAS_GRENADE:
1533 		case GL_TEARGAS_GRENADE:
1534 		case BIG_TEAR_GAS:
1535 		case SMOKE_GRENADE:
1536 		case GL_SMOKE_GRENADE:
1537 		case SMALL_CREATURE_GAS:
1538 		case LARGE_CREATURE_GAS:
1539 		case VERY_SMALL_CREATURE_GAS:
1540 			fSmokeEffect = TRUE;
1541 			break;
1542 	}
1543 
1544 	// Set values for recompile region to optimize area we need to recompile for MPs
1545 	gsRecompileAreaTop = sGridNo / WORLD_COLS;
1546 	gsRecompileAreaLeft = sGridNo % WORLD_COLS;
1547 	gsRecompileAreaRight = gsRecompileAreaLeft;
1548 	gsRecompileAreaBottom = gsRecompileAreaTop;
1549 
1550 	// multiply range by 2 so we can correctly calculate approximately round explosion regions
1551 	sRange = ubRadius * 2;
1552 
1553 	// first, affect main spot
1554 	if (ExpAffect(sGridNo, sGridNo, 0, usItem, owner, fSubsequent, &fAnyMercHit, bLevel, smoke))
1555 	{
1556 		fRecompileMovement = TRUE;
1557 	}
1558 
1559 
1560 	for (ubDir = NORTH; ubDir <= NORTHWEST; ubDir++ )
1561 	{
1562 		uiTempSpot = sGridNo;
1563 
1564 		uiTempRange = sRange;
1565 
1566 		INT32 cnt;
1567 		if (ubDir & 1)
1568 		{
1569 			cnt = 3;
1570 		}
1571 		else
1572 		{
1573 			cnt = 2;
1574 		}
1575 		while( cnt <= uiTempRange) // end of range loop
1576 		{
1577 			// move one tile in direction
1578 			uiNewSpot = NewGridNo( (INT16)uiTempSpot, DirectionInc( ubDir ) );
1579 
1580 			// see if this was a different spot & if we should be able to reach
1581 			// this spot
1582 			if (uiNewSpot == uiTempSpot)
1583 			{
1584 				ubKeepGoing = FALSE;
1585 			}
1586 			else
1587 			{
1588 				// Check if struct is a tree, etc and reduce range...
1589 				GetRayStopInfo( uiNewSpot, ubDir, bLevel, fSmokeEffect, cnt, &uiTempRange, &ubKeepGoing );
1590 			}
1591 
1592 			if (ubKeepGoing)
1593 			{
1594 				uiTempSpot = uiNewSpot;
1595 
1596 				SLOGD("Explosion affects %d", uiNewSpot);
1597 				// ok, do what we do here...
1598 				if (ExpAffect(sGridNo, uiNewSpot, cnt / 2, usItem, owner, fSubsequent, &fAnyMercHit, bLevel, smoke))
1599 				{
1600 					fRecompileMovement = TRUE;
1601 				}
1602 
1603 				// how far should we branch out here?
1604 				ubBranchRange = (UINT8)( sRange - cnt );
1605 
1606 				if ( ubBranchRange )
1607 				{
1608 					// ok, there's a branch here. Mark where we start this branch.
1609 					uiBranchSpot = uiNewSpot;
1610 
1611 					// figure the branch direction - which is one dir clockwise
1612 					ubBranchDir = (ubDir + 1) % 8;
1613 
1614 					if (ubBranchDir & 1)
1615 					{
1616 						branchCnt = 3;
1617 					}
1618 					else
1619 					{
1620 						branchCnt = 2;
1621 					}
1622 
1623 					while( branchCnt <= ubBranchRange) // end of range loop
1624 					{
1625 						ubKeepGoing = TRUE;
1626 						uiNewSpot = NewGridNo( (INT16)uiBranchSpot, DirectionInc(ubBranchDir));
1627 
1628 						if (uiNewSpot != uiBranchSpot)
1629 						{
1630 							// Check if struct is a tree, etc and reduce range...
1631 							GetRayStopInfo( uiNewSpot, ubBranchDir, bLevel, fSmokeEffect, branchCnt, &ubBranchRange, &ubKeepGoing );
1632 
1633 							if ( ubKeepGoing )
1634 							{
1635 								// ok, do what we do here
1636 								SLOGD("Explosion affects %d", uiNewSpot);
1637 								if (ExpAffect(sGridNo, uiNewSpot, (INT16)((cnt + branchCnt) / 2), usItem, owner, fSubsequent, &fAnyMercHit, bLevel, smoke))
1638 								{
1639 									fRecompileMovement = TRUE;
1640 								}
1641 								uiBranchSpot = uiNewSpot;
1642 							}
1643 							//else
1644 							{
1645 								// check if it's ANY door, and if so, affect that spot so it's damaged
1646 							//	if (RealDoorAt(uiNewSpot))
1647 							//	{
1648 							//      	ExpAffect(sGridNo,uiNewSpot,cnt,ubReason,fSubsequent);
1649 							//	}
1650 								// blocked, break out of the the sub-branch loop
1651 							//	break;
1652 							}
1653 						}
1654 
1655 						if (ubBranchDir & 1)
1656 						{
1657 							branchCnt += 3;
1658 						}
1659 						else
1660 						{
1661 							branchCnt += 2;
1662 						}
1663 
1664 					}
1665 				} // end of if a branch to do
1666 
1667 			}
1668 			else // at edge, or tile blocks further spread in that direction
1669 			{
1670 				break;
1671 			}
1672 
1673 			if (ubDir & 1)
1674 			{
1675 				cnt += 3;
1676 			}
1677 			else
1678 			{
1679 				cnt += 2;
1680 			}
1681 		}
1682 
1683 	} // end of dir loop
1684 
1685 	// Recompile movement costs...
1686 	if ( fRecompileMovement )
1687 	{
1688 		// DO wireframes as well
1689 		SetRecalculateWireFrameFlagRadius(sGridNo, ubRadius);
1690 		CalculateWorldWireFrameTiles( FALSE );
1691 
1692 		RecompileLocalMovementCostsInAreaWithFlags();
1693 		RecompileLocalMovementCostsFromRadius( sGridNo, MAX_DISTANCE_EXPLOSIVE_CAN_DESTROY_STRUCTURES );
1694 
1695 		// if anything has been done to change movement costs and this is a potential POW situation, check
1696 		// paths for POWs
1697 		if ( gWorldSectorX == 13 && gWorldSectorY == MAP_ROW_I )
1698 		{
1699 			DoPOWPathChecks();
1700 		}
1701 
1702 	}
1703 
1704 	// do sight checks if something damaged or smoke stuff involved
1705 	if ( fRecompileMovement || fSmokeEffect )
1706 	{
1707 		if ( gubElementsOnExplosionQueue )
1708 		{
1709 			gfExplosionQueueMayHaveChangedSight	= TRUE;
1710 		}
1711 	}
1712 
1713 	gsRecompileAreaTop = 0;
1714 	gsRecompileAreaLeft = 0;
1715 	gsRecompileAreaRight = 0;
1716 	gsRecompileAreaBottom = 0;
1717 
1718 	if (fAnyMercHit)
1719 	{
1720 		// reset explosion hit flag so we can damage mercs again
1721 		FOR_EACH_MERC(i) (*i)->ubMiscSoldierFlags &= ~SOLDIER_MISC_HURT_BY_EXPLOSION;
1722 	}
1723 
1724 	if ( fSubsequent != BLOOD_SPREAD_EFFECT )
1725 	{
1726 		MakeNoise(NULL, sGridNo, bLevel, Explosive[GCM->getItem(usItem)->getClassIndex()].ubVolume, NOISE_EXPLOSION);
1727 	}
1728 }
1729 
1730 
SpreadEffectSmoke(const SMOKEEFFECT * const s,const BOOLEAN subsequent,const INT8 level)1731 void SpreadEffectSmoke(const SMOKEEFFECT* const s, const BOOLEAN subsequent, const INT8 level)
1732 {
1733 	SpreadEffect(s->sGridNo, s->ubRadius, s->usItem, s->owner, subsequent, level, s);
1734 }
1735 
1736 
ToggleActionItemsByFrequency(INT8 bFrequency)1737 static void ToggleActionItemsByFrequency(INT8 bFrequency)
1738 {
1739 	// Go through all the bombs in the world, and look for remote ones
1740 	CFOR_EACH_WORLD_BOMB(wb)
1741 	{
1742 		OBJECTTYPE& o = GetWorldItem(wb.iItemIndex).o;
1743 		if (o.bDetonatorType == BOMB_REMOTE)
1744 		{
1745 			// Found a remote bomb, so check to see if it has the same frequency
1746 			if (o.bFrequency == bFrequency)
1747 			{
1748 				// toggle its active flag
1749 				if (o.fFlags & OBJECT_DISABLED_BOMB)
1750 				{
1751 					o.fFlags &= ~OBJECT_DISABLED_BOMB;
1752 				}
1753 				else
1754 				{
1755 					o.fFlags |= OBJECT_DISABLED_BOMB;
1756 				}
1757 			}
1758 		}
1759 	}
1760 }
1761 
1762 
TogglePressureActionItemsInGridNo(INT16 sGridNo)1763 static void TogglePressureActionItemsInGridNo(INT16 sGridNo)
1764 {
1765 	// Go through all the bombs in the world, and look for remote ones
1766 	CFOR_EACH_WORLD_BOMB(wb)
1767 	{
1768 		WORLDITEM& wi = GetWorldItem(wb.iItemIndex);
1769 		if (wi.sGridNo != sGridNo) continue;
1770 
1771 		OBJECTTYPE& o = wi.o;
1772 		if (o.bDetonatorType == BOMB_PRESSURE)
1773 		{
1774 			// Found a pressure item
1775 			// toggle its active flag
1776 			if (o.fFlags & OBJECT_DISABLED_BOMB)
1777 			{
1778 				o.fFlags &= ~OBJECT_DISABLED_BOMB;
1779 			}
1780 			else
1781 			{
1782 				o.fFlags |= OBJECT_DISABLED_BOMB;
1783 			}
1784 		}
1785 	}
1786 }
1787 
1788 
HookerInRoom(UINT8 ubRoom)1789 static BOOLEAN HookerInRoom(UINT8 ubRoom)
1790 {
1791 	FOR_EACH_IN_TEAM(s, CIV_TEAM)
1792 	{
1793 		if (!s->bInSector)                 continue;
1794 		if (s->bLife < OKLIFE)             continue;
1795 		if (!s->bNeutral)                  continue;
1796 		if (s->ubBodyType != MINICIV)      continue;
1797 		if (GetRoom(s->sGridNo) != ubRoom) continue;
1798 		return TRUE;
1799 	}
1800 	return FALSE;
1801 }
1802 
PerformItemAction(INT16 sGridNo,OBJECTTYPE * pObj)1803 static void PerformItemAction(INT16 sGridNo, OBJECTTYPE* pObj)
1804 {
1805 	STRUCTURE * pStructure;
1806 
1807 	switch( pObj->bActionValue )
1808 	{
1809 		case ACTION_ITEM_OPEN_DOOR:
1810 			pStructure = FindStructure( sGridNo, STRUCTURE_ANYDOOR );
1811 			if (pStructure)
1812 			{
1813 				if (pStructure->fFlags & STRUCTURE_OPEN)
1814 				{
1815 					// it's already open - this MIGHT be an error but probably not
1816 					// because we are basically just ensuring that the door is open
1817 				}
1818 				else
1819 				{
1820 					if (pStructure->fFlags & STRUCTURE_BASE_TILE)
1821 					{
1822 						HandleDoorChangeFromGridNo( NULL, sGridNo, FALSE );
1823 					}
1824 					else
1825 					{
1826 						HandleDoorChangeFromGridNo( NULL, pStructure->sBaseGridNo, FALSE );
1827 					}
1828 					gfExplosionQueueMayHaveChangedSight = TRUE;
1829 				}
1830 			}
1831 			else
1832 			{
1833 				// error message here
1834 				SLOGW("Action item to open door in gridno %d but there is none!", sGridNo );
1835 			}
1836 			break;
1837 		case ACTION_ITEM_CLOSE_DOOR:
1838 			pStructure = FindStructure( sGridNo, STRUCTURE_ANYDOOR );
1839 			if (pStructure)
1840 			{
1841 				if (pStructure->fFlags & STRUCTURE_OPEN)
1842 				{
1843 					if (pStructure->fFlags & STRUCTURE_BASE_TILE)
1844 					{
1845 						HandleDoorChangeFromGridNo( NULL, sGridNo , FALSE );
1846 					}
1847 					else
1848 					{
1849 						HandleDoorChangeFromGridNo( NULL, pStructure->sBaseGridNo, FALSE );
1850 					}
1851 					gfExplosionQueueMayHaveChangedSight = TRUE;
1852 				}
1853 				else
1854 				{
1855 					// it's already closed - this MIGHT be an error but probably not
1856 					// because we are basically just ensuring that the door is closed
1857 				}
1858 			}
1859 			else
1860 			{
1861 				// error message here
1862 				SLOGW("Action item to close door in gridno %d but there is none!", sGridNo );
1863 			}
1864 			break;
1865 		case ACTION_ITEM_TOGGLE_DOOR:
1866 			pStructure = FindStructure( sGridNo, STRUCTURE_ANYDOOR );
1867 			if (pStructure)
1868 			{
1869 				if (pStructure->fFlags & STRUCTURE_BASE_TILE)
1870 				{
1871 					HandleDoorChangeFromGridNo( NULL, sGridNo, FALSE );
1872 				}
1873 				else
1874 				{
1875 					HandleDoorChangeFromGridNo( NULL, pStructure->sBaseGridNo , FALSE );
1876 				}
1877 				gfExplosionQueueMayHaveChangedSight = TRUE;
1878 			}
1879 			else
1880 			{
1881 				// error message here
1882 				SLOGW("Action item to toggle door in gridno %d but there is none!", sGridNo );
1883 			}
1884 			break;
1885 		case ACTION_ITEM_UNLOCK_DOOR:
1886 			{
1887 				DOOR * pDoor;
1888 
1889 				pDoor = FindDoorInfoAtGridNo( sGridNo );
1890 				if ( pDoor )
1891 				{
1892 					pDoor->fLocked = FALSE;
1893 				}
1894 			}
1895 			break;
1896 		case ACTION_ITEM_TOGGLE_LOCK:
1897 			{
1898 				DOOR * pDoor;
1899 
1900 				pDoor = FindDoorInfoAtGridNo( sGridNo );
1901 				if ( pDoor )
1902 				{
1903 					if ( pDoor->fLocked )
1904 					{
1905 						pDoor->fLocked = FALSE;
1906 					}
1907 					else
1908 					{
1909 						pDoor->fLocked = TRUE;
1910 					}
1911 				}
1912 			}
1913 			break;
1914 		case ACTION_ITEM_UNTRAP_DOOR:
1915 			{
1916 				DOOR * pDoor;
1917 
1918 				pDoor = FindDoorInfoAtGridNo( sGridNo );
1919 				if ( pDoor )
1920 				{
1921 					pDoor->ubTrapLevel = 0;
1922 					pDoor->ubTrapID = NO_TRAP;
1923 				}
1924 			}
1925 			break;
1926 		case ACTION_ITEM_SMALL_PIT:
1927 			Add3X3Pit( sGridNo );
1928 			SearchForOtherMembersWithinPitRadiusAndMakeThemFall( sGridNo, 1 );
1929 			break;
1930 		case ACTION_ITEM_LARGE_PIT:
1931 			Add5X5Pit( sGridNo );
1932 			SearchForOtherMembersWithinPitRadiusAndMakeThemFall( sGridNo, 2 );
1933 			break;
1934 		case ACTION_ITEM_TOGGLE_ACTION1:
1935 			ToggleActionItemsByFrequency( FIRST_MAP_PLACED_FREQUENCY + 1 );
1936 			break;
1937 		case ACTION_ITEM_TOGGLE_ACTION2:
1938 			ToggleActionItemsByFrequency( FIRST_MAP_PLACED_FREQUENCY + 2 );
1939 			break;
1940 		case ACTION_ITEM_TOGGLE_ACTION3:
1941 			ToggleActionItemsByFrequency( FIRST_MAP_PLACED_FREQUENCY + 3 );
1942 			break;
1943 		case ACTION_ITEM_TOGGLE_ACTION4:
1944 			ToggleActionItemsByFrequency( FIRST_MAP_PLACED_FREQUENCY + 4 );
1945 			break;
1946 		case ACTION_ITEM_TOGGLE_PRESSURE_ITEMS:
1947 			TogglePressureActionItemsInGridNo( sGridNo );
1948 			break;
1949 		case ACTION_ITEM_ENTER_BROTHEL:
1950 			// JA2Gold: Disable brothel tracking
1951 			/*
1952 			if ( ! (gTacticalStatus.uiFlags & INCOMBAT) )
1953 			{
1954 				const SOLDIERTYPE* const tgt = WhoIsThere2(sGridNo, 0);
1955 				if (tgt != NULL && tgt->bTeam == OUR_TEAM)
1956 				{
1957 					if (tgt->sOldGridNo == sGridNo + DirectionInc(SOUTH))
1958 					{
1959 						gMercProfiles[ MADAME ].bNPCData2++;
1960 
1961 						SetFactTrue( FACT_PLAYER_USED_BROTHEL );
1962 						SetFactTrue( FACT_PLAYER_PASSED_GOON );
1963 
1964 						// If we for any reason trigger Madame's record 34 then we don't bother to do
1965 						// anything else
1966 
1967 						// Billy always moves back on a timer so that the player has a chance to sneak
1968 						// someone else through
1969 
1970 						// Madame's quote about female mercs should therefore not be made on a timer
1971 
1972 						if ( gMercProfiles[ MADAME ].bNPCData2 > 2 )
1973 						{
1974 							// more than 2 entering brothel
1975 							TriggerNPCRecord( MADAME, 35 );
1976 							return;
1977 						}
1978 
1979 						if ( gMercProfiles[ MADAME ].bNPCData2 == gMercProfiles[ MADAME ].bNPCData )
1980 						{
1981 							// full # of mercs who paid have entered brothel
1982 							// have Billy block the way again
1983 							SetCustomizableTimerCallbackAndDelay( 2000, BillyBlocksDoorCallback, FALSE );
1984 							//TriggerNPCRecord( BILLY, 6 );
1985 						}
1986 						else if ( gMercProfiles[ MADAME ].bNPCData2 > gMercProfiles[ MADAME ].bNPCData )
1987 						{
1988 							// more than full # of mercs who paid have entered brothel
1989 							// have Billy block the way again?
1990 							if ( CheckFact( FACT_PLAYER_FORCED_WAY_INTO_BROTHEL, 0 ) )
1991 							{
1992 								// player already did this once!
1993 								TriggerNPCRecord( MADAME, 35 );
1994 								return;
1995 							}
1996 							else
1997 							{
1998 								SetCustomizableTimerCallbackAndDelay( 2000, BillyBlocksDoorCallback, FALSE );
1999 								SetFactTrue( FACT_PLAYER_FORCED_WAY_INTO_BROTHEL );
2000 								TriggerNPCRecord( MADAME, 34 );
2001 							}
2002 						}
2003 
2004 						if (gMercProfiles[tgt->ubProfile].bSex == FEMALE)
2005 						{
2006 							// woman walking into brothel
2007 							TriggerNPCRecordImmediately( MADAME, 33 );
2008 						}
2009 
2010 					}
2011 					else
2012 					{
2013 						// someone wants to leave the brothel
2014 						TriggerNPCRecord( BILLY, 5 );
2015 					}
2016 
2017 				}
2018 
2019 			}
2020 			*/
2021 			break;
2022 		case ACTION_ITEM_EXIT_BROTHEL:
2023 			// JA2Gold: Disable brothel tracking
2024 			/*
2025 			if ( ! (gTacticalStatus.uiFlags & INCOMBAT) )
2026 			{
2027 				const SOLDIERTYPE* const tgt = WhoIsThere2(sGridNo, 0);
2028 				if (tgt != NULL && tgt->bTeam == OUR_TEAM && tgt->sOldGridNo == sGridNo + DirectionInc(NORTH))
2029 				{
2030 					gMercProfiles[ MADAME ].bNPCData2--;
2031 					if ( gMercProfiles[ MADAME ].bNPCData2 == 0 )
2032 					{
2033 						// reset paid #
2034 						gMercProfiles[ MADAME ].bNPCData = 0;
2035 					}
2036 					// Billy should move back to block the door again
2037 					gsTempActionGridNo = sGridNo;
2038 					SetCustomizableTimerCallbackAndDelay( 1000, DelayedBillyTriggerToBlockOnExit, TRUE );
2039 				}
2040 			}
2041 			*/
2042 			break;
2043 		case ACTION_ITEM_KINGPIN_ALARM:
2044 			PlayLocationJA2Sample(sGridNo, KLAXON_ALARM, MIDVOLUME, 5);
2045 			CallAvailableKingpinMenTo( sGridNo );
2046 
2047 			gTacticalStatus.fCivGroupHostile[ KINGPIN_CIV_GROUP ] = CIV_GROUP_HOSTILE;
2048 
2049 			{
2050 				FOR_EACH_IN_TEAM(civ, CIV_TEAM)
2051 				{
2052 					if (civ->bInSector && civ->ubCivilianGroup == KINGPIN_CIV_GROUP)
2053 					{
2054 						for (UINT8 ubID2 = gTacticalStatus.Team[OUR_TEAM].bFirstID; ubID2 <= gTacticalStatus.Team[OUR_TEAM].bLastID; ++ubID2)
2055 						{
2056 							if (civ->bOppList[ubID2] == SEEN_CURRENTLY)
2057 							{
2058 								MakeCivHostile(civ, 2);
2059 							}
2060 						}
2061 					}
2062 				}
2063 
2064 				if ( ! (gTacticalStatus.uiFlags & INCOMBAT) )
2065 				{
2066 					EnterCombatMode( CIV_TEAM );
2067 				}
2068 			}
2069 
2070 			// now zap this object so it won't activate again
2071 			pObj->fFlags &= (~OBJECT_DISABLED_BOMB);
2072 			break;
2073 		case ACTION_ITEM_SEX:
2074 			if ( ! (gTacticalStatus.uiFlags & INCOMBAT) )
2075 			{
2076 				OBJECTTYPE DoorCloser;
2077 				INT16		sTeleportSpot;
2078 				INT16		sDoorSpot;
2079 				UINT8		ubDirection;
2080 
2081 				SOLDIERTYPE* tgt = WhoIsThere2(sGridNo, 0);
2082 				if (tgt != NULL)
2083 					if (tgt->bTeam == OUR_TEAM)
2084 					{
2085 						UINT8 const room     = GetRoom(sGridNo);
2086 						UINT8 const old_room = GetRoom(tgt->sOldGridNo);
2087 						if (room != NO_ROOM && old_room != NO_ROOM && old_room != room)
2088 						{
2089 							// also require there to be a miniskirt civ in the room
2090 							if (HookerInRoom(room))
2091 							{
2092 
2093 								// stop the merc...
2094 								EVENT_StopMerc(tgt);
2095 
2096 								switch( sGridNo )
2097 								{
2098 									case 13414:
2099 										sDoorSpot = 13413;
2100 										sTeleportSpot = 13413;
2101 										break;
2102 									case 11174:
2103 										sDoorSpot = 11173;
2104 										sTeleportSpot = 11173;
2105 										break;
2106 									case 12290:
2107 										sDoorSpot = 12290;
2108 										sTeleportSpot = 12291;
2109 										break;
2110 
2111 									default:
2112 
2113 										sDoorSpot = NOWHERE;
2114 										sTeleportSpot = NOWHERE;
2115 
2116 
2117 								}
2118 
2119 								if ( sDoorSpot != NOWHERE && sTeleportSpot != NOWHERE )
2120 								{
2121 									// close the door...
2122 									DoorCloser.bActionValue = ACTION_ITEM_CLOSE_DOOR;
2123 									PerformItemAction( sDoorSpot, &DoorCloser );
2124 
2125 									// have sex
2126 									HandleNPCDoAction( 0, NPC_ACTION_SEX, 0 );
2127 
2128 									// move the merc outside of the room again
2129 									sTeleportSpot = FindGridNoFromSweetSpotWithStructData(tgt, STANDING, sTeleportSpot, 2, &ubDirection, FALSE);
2130 									ChangeSoldierState(tgt, STANDING, 0, TRUE);
2131 									TeleportSoldier(*tgt, sTeleportSpot, false);
2132 
2133 									HandleMoraleEvent(tgt, MORALE_SEX, gWorldSectorX, gWorldSectorY, gbWorldSectorZ);
2134 									FatigueCharacter(*tgt);
2135 									FatigueCharacter(*tgt);
2136 									FatigueCharacter(*tgt);
2137 									FatigueCharacter(*tgt);
2138 									DirtyMercPanelInterface(tgt, DIRTYLEVEL1);
2139 								}
2140 							}
2141 
2142 						}
2143 						break;
2144 					}
2145 			}
2146 			break;
2147 		case ACTION_ITEM_REVEAL_ROOM:
2148 			{
2149 				UINT8 ubRoom;
2150 				if ( InAHiddenRoom( sGridNo, &ubRoom ) )
2151 				{
2152 					RemoveRoomRoof( sGridNo, ubRoom, NULL );
2153 				}
2154 			}
2155 			break;
2156 		case ACTION_ITEM_LOCAL_ALARM:
2157 			MakeNoise(NULL, sGridNo, 0, 30, NOISE_SILENT_ALARM);
2158 			break;
2159 		case ACTION_ITEM_GLOBAL_ALARM:
2160 			CallAvailableEnemiesTo( sGridNo );
2161 			break;
2162 		case ACTION_ITEM_BLOODCAT_ALARM:
2163 			CallAvailableTeamEnemiesTo( sGridNo, CREATURE_TEAM );
2164 			break;
2165 		case ACTION_ITEM_KLAXON:
2166 			PlayLocationJA2Sample(sGridNo, KLAXON_ALARM, MIDVOLUME, 5);
2167 			break;
2168 		case ACTION_ITEM_MUSEUM_ALARM:
2169 			PlayLocationJA2Sample(sGridNo, KLAXON_ALARM, MIDVOLUME, 5);
2170 			CallEldinTo( sGridNo );
2171 			break;
2172 		default:
2173 			// error message here
2174 			SLOGW("Action item with invalid action in gridno %d!", sGridNo );
2175 			break;
2176 	}
2177 }
2178 
2179 
AddBombToQueue(UINT32 const world_bomb_idx,UINT32 const timestamp)2180 static void AddBombToQueue(UINT32 const world_bomb_idx, UINT32 const timestamp)
2181 {
2182 	if (gubElementsOnExplosionQueue == MAX_BOMB_QUEUE) return; // XXX exception?
2183 
2184 	ExplosionQueueElement& e = gExplosionQueue[gubElementsOnExplosionQueue++];
2185 	e.uiWorldBombIndex = world_bomb_idx;
2186 	e.uiTimeStamp      = timestamp;
2187 	e.fExists          = TRUE;
2188 
2189 	if (!gfExplosionQueueActive)
2190 	{
2191 		gfExplosionQueueActive   = TRUE;
2192 		guiPendingOverrideEvent  = LU_BEGINUILOCK; // Lock UI
2193 		gTacticalStatus.uiFlags |= DISALLOW_SIGHT; // Disable sight
2194 	}
2195 }
2196 
2197 
HandleExplosionQueue()2198 void HandleExplosionQueue()
2199 {
2200 	if (!gfExplosionQueueActive) return;
2201 
2202 	UINT32 const now = GetJA2Clock();
2203 	for (UINT32 i = 0; i != gubElementsOnExplosionQueue; ++i)
2204 	{
2205 		ExplosionQueueElement& e = gExplosionQueue[i];
2206 		if (!e.fExists)          continue;
2207 		if (now < e.uiTimeStamp) continue;
2208 
2209 		// Set off this bomb now.
2210 		WORLDITEM&  wi     = GetWorldItem(gWorldBombs[e.uiWorldBombIndex].iItemIndex);
2211 		OBJECTTYPE& o      = wi.o;
2212 		INT16 const gridno = wi.sGridNo;
2213 		UINT8 const level  = wi.ubLevel;
2214 
2215 		if (o.usItem == ACTION_ITEM && o.bActionValue != ACTION_ITEM_BLOW_UP)
2216 		{
2217 			PerformItemAction(gridno, &o);
2218 		}
2219 		else if (o.usBombItem == TRIP_KLAXON)
2220 		{
2221 			PlayLocationJA2Sample(gridno, KLAXON_ALARM, MIDVOLUME, 5);
2222 			CallAvailableEnemiesTo(gridno);
2223 		}
2224 		else if (o.usBombItem == TRIP_FLARE)
2225 		{
2226 			NewLightEffect(gridno, LIGHT_FLARE_MARK_1);
2227 			RemoveItemFromPool(wi);
2228 		}
2229 		else
2230 		{
2231 			gfExplosionQueueMayHaveChangedSight = TRUE;
2232 
2233 			/* Remove the item first to prevent the explosion from detonating it a
2234 			 * second time. */
2235 			RemoveItemFromPool(wi);
2236 
2237 			// Make sure no one thinks there is a bomb here any more
2238 			UINT16& flags = gpWorldLevelData[gridno].uiFlags;
2239 			if (flags & MAPELEMENT_PLAYER_MINE_PRESENT)
2240 			{
2241 				RemoveBlueFlag(gridno, level);
2242 			}
2243 			flags &= ~MAPELEMENT_ENEMY_MINE_PRESENT;
2244 
2245 			// Bomb objects only store the side who placed the bomb.
2246 			SOLDIERTYPE* const owner = o.ubBombOwner > 1 ? ID2SOLDIER(o.ubBombOwner - 2) : 0;
2247 			IgniteExplosion(owner, 0, gridno, o.usBombItem, level);
2248 		}
2249 
2250 		e.fExists = FALSE;
2251 	}
2252 
2253 	/* See if we can reduce the # of elements on the queue that we have recorded.
2254 	 * Easier to do it at this time rather than in the loop above. */
2255 	while (gubElementsOnExplosionQueue > 0 && !gExplosionQueue[gubElementsOnExplosionQueue - 1].fExists)
2256 	{
2257 		--gubElementsOnExplosionQueue;
2258 	}
2259 
2260 	TacticalStatusType& ts = gTacticalStatus;
2261 	if (gubElementsOnExplosionQueue == 0 &&
2262 			(!gPersonToSetOffExplosions || ts.ubAttackBusyCount == 0))
2263 	{ // Turn off explosion queue
2264 		ts.uiFlags &= ~DISALLOW_SIGHT; // Re-enable sight
2265 
2266 		SOLDIERTYPE* const s = gPersonToSetOffExplosions;
2267 		if (s && !(s->uiStatusFlags & SOLDIER_PC))
2268 		{
2269 			FreeUpNPCFromPendingAction(s);
2270 		}
2271 
2272 		if (gfExplosionQueueMayHaveChangedSight)
2273 		{
2274 			// Set variable so we may at least have someone to resolve interrupts against
2275 			gInterruptProvoker = s;
2276 			AllTeamsLookForAll(TRUE);
2277 
2278 			// call fov code
2279 			FOR_EACH_IN_TEAM(s, OUR_TEAM)
2280 			{
2281 				if (s->bInSector) RevealRoofsAndItems(s, FALSE);
2282 			}
2283 
2284 			gfExplosionQueueMayHaveChangedSight = FALSE;
2285 			gPersonToSetOffExplosions           = 0;
2286 		}
2287 
2288 		if (!(ts.uiFlags & INCOMBAT) || ts.ubCurrentTeam == OUR_TEAM)
2289 		{ // Don't end UI lock when it's a computer turn
2290 			guiPendingOverrideEvent = LU_ENDUILOCK;
2291 		}
2292 
2293 		gfExplosionQueueActive = FALSE;
2294 	}
2295 }
2296 
2297 
DecayBombTimers(void)2298 void DecayBombTimers( void )
2299 {
2300 	UINT32				uiWorldBombIndex;
2301 	UINT32				uiTimeStamp;
2302 
2303 	uiTimeStamp = GetJA2Clock();
2304 
2305 	// Go through all the bombs in the world, and look for timed ones
2306 	Assert(gWorldBombs.size() <= UINT32_MAX);
2307 	for (uiWorldBombIndex = 0; uiWorldBombIndex < static_cast<UINT32>(gWorldBombs.size()); uiWorldBombIndex++)
2308 	{
2309 		if (gWorldBombs[uiWorldBombIndex].fExists)
2310 		{
2311 			OBJECTTYPE& o = GetWorldItem(gWorldBombs[uiWorldBombIndex].iItemIndex).o;
2312 			if (o.bDetonatorType == BOMB_TIMED && !(o.fFlags & OBJECT_DISABLED_BOMB))
2313 			{
2314 				// Found a timed bomb, so decay its delay value and see if it goes off
2315 				o.bDelay--;
2316 				if (o.bDelay == 0)
2317 				{
2318 					// put this bomb on the queue
2319 					AddBombToQueue( uiWorldBombIndex, uiTimeStamp );
2320 					// ATE: CC black magic....
2321 					if (o.ubBombOwner > 1)
2322 					{
2323 						gPersonToSetOffExplosions = &GetMan(o.ubBombOwner - 2);
2324 					}
2325 					else
2326 					{
2327 						gPersonToSetOffExplosions = NULL;
2328 					}
2329 
2330 					if (o.usItem != ACTION_ITEM || o.bActionValue == ACTION_ITEM_BLOW_UP)
2331 					{
2332 						uiTimeStamp += BOMB_QUEUE_DELAY;
2333 					}
2334 				}
2335 			}
2336 		}
2337 	}
2338 }
2339 
2340 
SetOffBombsByFrequency(SOLDIERTYPE * const s,const INT8 bFrequency)2341 void SetOffBombsByFrequency(SOLDIERTYPE* const s, const INT8 bFrequency)
2342 {
2343 	UINT32 uiWorldBombIndex;
2344 	UINT32 uiTimeStamp;
2345 
2346 	uiTimeStamp = GetJA2Clock();
2347 
2348 	// Go through all the bombs in the world, and look for remote ones
2349 	Assert(gWorldBombs.size() <= UINT32_MAX);
2350 	for (uiWorldBombIndex = 0; uiWorldBombIndex < static_cast<UINT32>(gWorldBombs.size()); uiWorldBombIndex++)
2351 	{
2352 		if (gWorldBombs[uiWorldBombIndex].fExists)
2353 		{
2354 			OBJECTTYPE const& o = GetWorldItem(gWorldBombs[uiWorldBombIndex].iItemIndex).o;
2355 			if (o.bDetonatorType == BOMB_REMOTE && !(o.fFlags & OBJECT_DISABLED_BOMB))
2356 			{
2357 				// Found a remote bomb, so check to see if it has the same frequency
2358 				if (o.bFrequency == bFrequency)
2359 				{
2360 					gPersonToSetOffExplosions = s;
2361 
2362 					// put this bomb on the queue
2363 					AddBombToQueue( uiWorldBombIndex, uiTimeStamp );
2364 					if (o.usItem != ACTION_ITEM || o.bActionValue == ACTION_ITEM_BLOW_UP)
2365 					{
2366 						uiTimeStamp += BOMB_QUEUE_DELAY;
2367 					}
2368 				}
2369 			}
2370 		}
2371 	}
2372 }
2373 
2374 
SetOffPanicBombs(SOLDIERTYPE * const s,const INT8 bPanicTrigger)2375 void SetOffPanicBombs(SOLDIERTYPE* const s, const INT8 bPanicTrigger)
2376 {
2377 	// need to turn off gridnos & flags in gTacticalStatus
2378 	gTacticalStatus.sPanicTriggerGridNo[ bPanicTrigger ] = NOWHERE;
2379 	if ( (gTacticalStatus.sPanicTriggerGridNo[0] == NOWHERE) &&
2380 		(gTacticalStatus.sPanicTriggerGridNo[1] == NOWHERE) &&
2381 		(gTacticalStatus.sPanicTriggerGridNo[2] == NOWHERE) )
2382 	{
2383 		gTacticalStatus.fPanicFlags &= ~(PANIC_TRIGGERS_HERE);
2384 	}
2385 
2386 	switch( bPanicTrigger )
2387 	{
2388 		case 0:
2389 			SetOffBombsByFrequency(s, PANIC_FREQUENCY);
2390 			gTacticalStatus.fPanicFlags &= ~(PANIC_BOMBS_HERE);
2391 			break;
2392 
2393 		case 1: SetOffBombsByFrequency(s, PANIC_FREQUENCY_2); break;
2394 		case 2: SetOffBombsByFrequency(s, PANIC_FREQUENCY_3); break;
2395 
2396 		default:
2397 			break;
2398 
2399 	}
2400 
2401 	if ( gTacticalStatus.fPanicFlags )
2402 	{
2403 		// find a new "closest one"
2404 		MakeClosestEnemyChosenOne();
2405 	}
2406 }
2407 
2408 
SetOffBombsInGridNo(SOLDIERTYPE * const s,const INT16 sGridNo,const BOOLEAN fAllBombs,const INT8 bLevel)2409 BOOLEAN SetOffBombsInGridNo(SOLDIERTYPE* const s, const INT16 sGridNo, const BOOLEAN fAllBombs, const INT8 bLevel)
2410 {
2411 	UINT32  uiWorldBombIndex;
2412 	UINT32  uiTimeStamp;
2413 	BOOLEAN fFoundMine = FALSE;
2414 
2415 	uiTimeStamp = GetJA2Clock();
2416 
2417 	// Go through all the bombs in the world, and look for mines at this location
2418 	Assert(gWorldBombs.size() <= UINT32_MAX);
2419 	for (uiWorldBombIndex = 0; uiWorldBombIndex < static_cast<UINT32>(gWorldBombs.size()); uiWorldBombIndex++)
2420 	{
2421 		if (!gWorldBombs[uiWorldBombIndex].fExists) continue;
2422 
2423 		WORLDITEM const& wi = GetWorldItem(gWorldBombs[uiWorldBombIndex].iItemIndex);
2424 		if (wi.sGridNo != sGridNo || wi.ubLevel != bLevel) continue;
2425 
2426 		OBJECTTYPE const& o = wi.o;
2427 		if (!(o.fFlags & OBJECT_DISABLED_BOMB))
2428 		{
2429 			if (fAllBombs || o.bDetonatorType == BOMB_PRESSURE)
2430 			{
2431 				if (!fAllBombs && s->bTeam != OUR_TEAM)
2432 				{
2433 					// ignore this unless it is a mine, etc which would have to have been placed by the
2434 					// player, seeing as how the others are all marked as known to the AI.
2435 					if (o.usItem != MINE && o.usItem != TRIP_FLARE && o.usItem != TRIP_KLAXON)
2436 					{
2437 						continue;
2438 					}
2439 				}
2440 
2441 				// player and militia ignore bombs set by player
2442 				if (o.ubBombOwner > 1 &&
2443 						(s->bTeam == OUR_TEAM || s->bTeam == MILITIA_TEAM))
2444 				{
2445 					continue;
2446 				}
2447 
2448 				if (o.usItem == SWITCH)
2449 				{
2450 					// send out a signal to detonate other bombs, rather than this which
2451 					// isn't a bomb but a trigger
2452 					SetOffBombsByFrequency(s, o.bFrequency);
2453 				}
2454 				else
2455 				{
2456 					gPersonToSetOffExplosions = s;
2457 
2458 					// put this bomb on the queue
2459 					AddBombToQueue( uiWorldBombIndex, uiTimeStamp );
2460 					if (o.usItem != ACTION_ITEM || o.bActionValue == ACTION_ITEM_BLOW_UP)
2461 					{
2462 						uiTimeStamp += BOMB_QUEUE_DELAY;
2463 					}
2464 
2465 					if (o.usBombItem != NOTHING && GCM->getItem(o.usBombItem)->isExplosive())
2466 					{
2467 						fFoundMine = TRUE;
2468 					}
2469 
2470 				}
2471 			}
2472 		}
2473 	}
2474 	return( fFoundMine );
2475 }
2476 
2477 
ActivateSwitchInGridNo(SOLDIERTYPE * const s,const INT16 sGridNo)2478 void ActivateSwitchInGridNo(SOLDIERTYPE* const s, const INT16 sGridNo)
2479 {
2480 	// Go through all the bombs in the world, and look for mines at this location
2481 	CFOR_EACH_WORLD_BOMB(wb)
2482 	{
2483 		WORLDITEM const& wi = GetWorldItem(wb.iItemIndex);
2484 		if (wi.sGridNo != sGridNo) continue;
2485 
2486 		OBJECTTYPE const& o = wi.o;
2487 		if (o.usItem == SWITCH && !(o.fFlags & OBJECT_DISABLED_BOMB) && o.bDetonatorType == BOMB_SWITCH)
2488 		{
2489 			// send out a signal to detonate other bombs, rather than this which
2490 			// isn't a bomb but a trigger
2491 
2492 			// first set attack busy count to 0 in case of a lingering a.b.c. problem...
2493 			gTacticalStatus.ubAttackBusyCount = 0;
2494 
2495 			SetOffBombsByFrequency(s, o.bFrequency);
2496 		}
2497 	}
2498 }
2499 
2500 
SaveExplosionTableToSaveGameFile(HWFILE const hFile)2501 void SaveExplosionTableToSaveGameFile(HWFILE const hFile)
2502 {
2503 	UINT32 uiExplosionCount=0;
2504 	UINT32 uiCnt;
2505 
2506 
2507 	//
2508 	// Explosion queue Info
2509 	//
2510 
2511 
2512 	//Write the number of explosion queues
2513 	FileWrite(hFile, &gubElementsOnExplosionQueue, sizeof(gubElementsOnExplosionQueue));
2514 	FileSeek(hFile, 3, FILE_SEEK_FROM_CURRENT);
2515 
2516 	//loop through and add all the explosions
2517 	FOR_EACH(ExplosionQueueElement const, i, gExplosionQueue)
2518 	{
2519 		ExplosionQueueElement const& e = *i;
2520 		BYTE  data[12];
2521 		DataWriter d{data};
2522 		INJ_U32( d, e.uiWorldBombIndex)
2523 		INJ_U32( d, e.uiTimeStamp)
2524 		INJ_U8(  d, e.fExists)
2525 		INJ_SKIP(d, 3)
2526 		Assert(d.getConsumed() == lengthof(data));
2527 		FileWrite(hFile, data, sizeof(data));
2528 	}
2529 
2530 	//
2531 	//	Explosion Data
2532 	//
2533 
2534 	//loop through and count all the active explosions
2535 	uiExplosionCount = 0;
2536 	for( uiCnt=0; uiCnt< NUM_EXPLOSION_SLOTS; uiCnt++)
2537 	{
2538 		if( gExplosionData[ uiCnt ].fAllocated )
2539 		{
2540 			uiExplosionCount++;
2541 		}
2542 	}
2543 
2544 	//Save the number of explosions
2545 	FileWrite(hFile, &uiExplosionCount, sizeof(UINT32));
2546 
2547 	//loop through and count all the active explosions
2548 	for( uiCnt=0; uiCnt< NUM_EXPLOSION_SLOTS; uiCnt++)
2549 	{
2550 		const EXPLOSIONTYPE* const e = &gExplosionData[uiCnt];
2551 		if (e->fAllocated)
2552 		{
2553 			InjectExplosionTypeIntoFile(hFile, e);
2554 		}
2555 	}
2556 }
2557 
2558 
LoadExplosionTableFromSavedGameFile(HWFILE const hFile)2559 void LoadExplosionTableFromSavedGameFile(HWFILE const hFile)
2560 {
2561 	//
2562 	// Explosion Queue
2563 	//
2564 
2565 	//Read the number of explosions queue's
2566 	FileRead(hFile, &gubElementsOnExplosionQueue, sizeof(gubElementsOnExplosionQueue));
2567 	FileSeek(hFile, 3, FILE_SEEK_FROM_CURRENT);
2568 
2569 	//loop through read all the active explosions fro the file
2570 	FOR_EACH(ExplosionQueueElement, i, gExplosionQueue)
2571 	{
2572 		BYTE  data[12];
2573 		FileRead(hFile, data, sizeof(data));
2574 		DataReader d{data};
2575 		ExplosionQueueElement& e = *i;
2576 		EXTR_U32( d, e.uiWorldBombIndex)
2577 		EXTR_U32( d, e.uiTimeStamp)
2578 		EXTR_U8(  d, e.fExists)
2579 		EXTR_SKIP(d, 3)
2580 		Assert(d.getConsumed() == lengthof(data));
2581 	}
2582 
2583 	//
2584 	//	Explosion Data
2585 	//
2586 
2587 	//Load the number of explosions
2588 	UINT32 num_explosions;
2589 	FileRead(hFile, &num_explosions, sizeof(num_explosions));
2590 
2591 	//loop through and load all the active explosions
2592 	const EXPLOSIONTYPE* const end = gExplosionData + num_explosions;
2593 	for (EXPLOSIONTYPE* e = gExplosionData; e != end; ++e)
2594 	{
2595 		ExtractExplosionTypeFromFile(hFile, e);
2596 		GenerateExplosionFromExplosionPointer(e);
2597 	}
2598 }
2599 
2600 // loop through civ team and find
2601 // anybody who is an NPC and
2602 // see if they get angry
HandleBuldingDestruction(const INT16 sGridNo,const SOLDIERTYPE * const owner)2603 static void HandleBuldingDestruction(const INT16 sGridNo, const SOLDIERTYPE* const owner)
2604 {
2605 	if (owner == NULL || owner->bTeam != OUR_TEAM) return;
2606 
2607 	FOR_EACH_IN_TEAM(pSoldier, CIV_TEAM)
2608 	{
2609 		if (pSoldier->bInSector && pSoldier->bLife && pSoldier->bNeutral)
2610 		{
2611 			if ( pSoldier->ubProfile != NO_PROFILE )
2612 			{
2613 				// ignore if the player is fighting the enemy here and this is a good guy
2614 				if (IsTeamActive(ENEMY_TEAM) && gMercProfiles[pSoldier->ubProfile].ubMiscFlags3 & PROFILE_MISC_FLAG3_GOODGUY)
2615 				{
2616 					continue;
2617 				}
2618 
2619 				if ( DoesNPCOwnBuilding( pSoldier, sGridNo ) )
2620 				{
2621 					MakeNPCGrumpyForMinorOffense(pSoldier, owner);
2622 				}
2623 			}
2624 		}
2625 	}
2626 }
2627 
2628 
FindActiveTimedBomb(void)2629 static INT32 FindActiveTimedBomb(void)
2630 {
2631 	// Go through all the bombs in the world, and look for timed ones
2632 	FOR_EACH_WORLD_BOMB(wb)
2633 	{
2634 		OBJECTTYPE const& o = GetWorldItem(wb.iItemIndex).o;
2635 		if (o.bDetonatorType != BOMB_TIMED || o.fFlags & OBJECT_DISABLED_BOMB) continue;
2636 
2637 		return wb.iItemIndex;
2638 	}
2639 	return -1;
2640 }
2641 
2642 
ActiveTimedBombExists(void)2643 BOOLEAN ActiveTimedBombExists(void)
2644 {
2645 	return gfWorldLoaded && FindActiveTimedBomb() != -1;
2646 }
2647 
2648 
RemoveAllActiveTimedBombs(void)2649 void RemoveAllActiveTimedBombs(void)
2650 {
2651 	for (;;)
2652 	{
2653 		const INT32 item_idx = FindActiveTimedBomb();
2654 		if (item_idx == -1) break;
2655 		RemoveItemFromWorld(item_idx);
2656 	}
2657 }
2658