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