1 #include "Directories.h"
2 #include "Font_Control.h"
3 #include "Lighting.h"
4 #include "Overhead.h"
5 #include "Soldier_Find.h"
6 #include "TileDef.h"
7 #include "VObject.h"
8 #include "Debug.h"
9 #include "Soldier_Control.h"
10 #include "Weapons.h"
11 #include "Handle_Items.h"
12 #include "WorldDef.h"
13 #include "Rotting_Corpses.h"
14 #include "Tile_Cache.h"
15 #include "Isometric_Utils.h"
16 #include "Animation_Control.h"
17 #include "Utilities.h"
18 #include "Game_Clock.h"
19 #include "Soldier_Create.h"
20 #include "RenderWorld.h"
21 #include "Soldier_Add.h"
22 #include "StrategicMap.h"
23 #include "LOS.h"
24 #include "Structure.h"
25 #include "Message.h"
26 #include "Sound_Control.h"
27 #include "PathAI.h"
28 #include "Random.h"
29 #include "Dialogue_Control.h"
30 #include "Items.h"
31 #include "Smell.h"
32 #include "World_Items.h"
33 #include "Explosion_Control.h"
34 #include "GameSettings.h"
35 #include "Interface_Items.h"
36 #include "Soldier_Profile.h"
37 #include "Soldier_Macros.h"
38 #include "Keys.h"
39 #include "Render_Fun.h"
40 #include "Strategic.h"
41 #include "QArray.h"
42 #include "Interface.h"
43 #include "MemMan.h"
44 #include "WorldMan.h"
45
46 #include "FileMan.h"
47 #include "ContentManager.h"
48 #include "GameInstance.h"
49 #include "policy/GamePolicy.h"
50
51 #define CORPSE_WARNING_MAX 5
52 #define CORPSE_WARNING_DIST 5
53
54 #define CORPSE_INDEX_OFFSET 10000
55
56 //#define DELAY_UNTIL_ROTTING ( 1 * NUM_SEC_IN_DAY )
57 #define DELAY_UNTIL_ROTTING ( 1 * NUM_SEC_IN_DAY / 60 )
58 #define DELAY_UNTIL_DONE_ROTTING ( 3 * NUM_SEC_IN_DAY / 60 )
59
60 #define MAX_NUM_CROWS 6
61
62
63 // When adding a corpse, add struct data...
64 static const char* const zCorpseFilenames[NUM_CORPSES] =
65 {
66 "",
67 ANIMSDIR "/corpses/s_d_jfk.sti",
68 ANIMSDIR "/corpses/s_d_bck.sti",
69 ANIMSDIR "/corpses/s_d_fwd.sti",
70 ANIMSDIR "/corpses/s_d_dhd.sti",
71 ANIMSDIR "/corpses/s_d_prn.sti",
72 ANIMSDIR "/corpses/s_d_wtr.sti",
73 ANIMSDIR "/corpses/s_d_fall.sti",
74 ANIMSDIR "/corpses/s_d_fallf.sti",
75
76 ANIMSDIR "/corpses/m_d_jfk.sti",
77 ANIMSDIR "/corpses/m_d_bck.sti",
78 ANIMSDIR "/corpses/m_d_fwd.sti",
79 ANIMSDIR "/corpses/m_d_dhd.sti",
80 ANIMSDIR "/corpses/m_d_prn.sti",
81 ANIMSDIR "/corpses/s_d_wtr.sti",
82 ANIMSDIR "/corpses/m_d_fall.sti",
83 ANIMSDIR "/corpses/m_d_fallf.sti",
84
85 ANIMSDIR "/corpses/f_d_jfk.sti",
86 ANIMSDIR "/corpses/f_d_bck.sti",
87 ANIMSDIR "/corpses/f_d_fwd.sti",
88 ANIMSDIR "/corpses/f_d_dhd.sti",
89 ANIMSDIR "/corpses/f_d_prn.sti",
90 ANIMSDIR "/corpses/s_d_wtr.sti",
91 ANIMSDIR "/corpses/f_d_fall.sti",
92 ANIMSDIR "/corpses/f_d_fallf.sti",
93
94 // Civs....
95 ANIMSDIR "/corpses/m_dead1.sti",
96 ANIMSDIR "/corpses/k_dead2.sti",
97 ANIMSDIR "/corpses/h_dead2.sti",
98 ANIMSDIR "/corpses/ft_dead1.sti",
99 ANIMSDIR "/corpses/s_dead1.sti",
100 ANIMSDIR "/corpses/w_dead1.sti",
101 ANIMSDIR "/corpses/cp_dead1.sti",
102 ANIMSDIR "/corpses/m_dead2.sti",
103 ANIMSDIR "/corpses/k_dead1.sti",
104 ANIMSDIR "/corpses/h_dead1.sti",
105
106 ANIMSDIR "/corpses/ft_dead2.sti",
107 ANIMSDIR "/corpses/s_dead2.sti",
108 ANIMSDIR "/corpses/w_dead2.sti",
109 ANIMSDIR "/corpses/cp_dead2.sti",
110 ANIMSDIR "/corpses/ct_dead.sti",
111 ANIMSDIR "/corpses/cw_dead1.sti",
112 ANIMSDIR "/corpses/mn_dead2.sti",
113 ANIMSDIR "/corpses/i_dead1.sti",
114 ANIMSDIR "/corpses/l_dead1.sti",
115
116 ANIMSDIR "/corpses/p_decomp2.sti",
117 ANIMSDIR "/corpses/tk_wrek.sti",
118 ANIMSDIR "/corpses/tk2_wrek.sti",
119 ANIMSDIR "/corpses/hm_wrek.sti",
120 ANIMSDIR "/corpses/ic_wrek.sti",
121 ANIMSDIR "/corpses/qn_dead.sti",
122 ANIMSDIR "/corpses/j_dead.sti",
123 ANIMSDIR "/corpses/s_burnt.sti",
124 ANIMSDIR "/corpses/s_expld.sti",
125 };
126
127
128 // When adding a corpse, add struct data...
129 static const char* const zNoBloodCorpseFilenames[NUM_CORPSES] =
130 {
131 "",
132 ANIMSDIR "/corpses/m_d_jfk_nb.sti",
133 ANIMSDIR "/corpses/s_d_bck_nb.sti",
134 ANIMSDIR "/corpses/s_d_fwd_nb.sti",
135 ANIMSDIR "/corpses/s_d_dhd_nb.sti",
136 ANIMSDIR "/corpses/s_d_prn_nb.sti",
137 ANIMSDIR "/corpses/s_d_wtr.sti",
138 ANIMSDIR "/corpses/s_d_fall_nb.sti",
139 ANIMSDIR "/corpses/s_d_fallf_nb.sti",
140
141 ANIMSDIR "/corpses/m_d_jfk_nb.sti",
142 ANIMSDIR "/corpses/m_d_bck_nb.sti",
143 ANIMSDIR "/corpses/m_d_fwd_nb.sti",
144 ANIMSDIR "/corpses/m_d_dhd_nb.sti",
145 ANIMSDIR "/corpses/m_d_prn_nb.sti",
146 ANIMSDIR "/corpses/s_d_wtr.sti",
147 ANIMSDIR "/corpses/m_d_fall_nb.sti",
148 ANIMSDIR "/corpses/m_d_fallf_nb.sti",
149
150 ANIMSDIR "/corpses/f_d_jfk_nb.sti",
151 ANIMSDIR "/corpses/f_d_bck_nb.sti",
152 ANIMSDIR "/corpses/f_d_fwd_nb.sti",
153 ANIMSDIR "/corpses/f_d_dhd_nb.sti",
154 ANIMSDIR "/corpses/f_d_prn_nb.sti",
155 ANIMSDIR "/corpses/s_d_wtr.sti",
156 ANIMSDIR "/corpses/f_d_fall_nb.sti",
157 ANIMSDIR "/corpses/f_d_fallf_nb.sti",
158
159 // Civs....
160 ANIMSDIR "/corpses/m_dead1_nb.sti",
161 ANIMSDIR "/corpses/k_dead2_nb.sti",
162 ANIMSDIR "/corpses/h_dead2_nb.sti",
163 ANIMSDIR "/corpses/ft_dead1_nb.sti",
164 ANIMSDIR "/corpses/s_dead1_nb.sti",
165 ANIMSDIR "/corpses/w_dead1_nb.sti",
166 ANIMSDIR "/corpses/cp_dead1_nb.sti",
167 ANIMSDIR "/corpses/m_dead2_nb.sti",
168 ANIMSDIR "/corpses/k_dead1_nb.sti",
169 ANIMSDIR "/corpses/h_dead1_nb.sti",
170
171 ANIMSDIR "/corpses/ft_dead2_nb.sti",
172 ANIMSDIR "/corpses/s_dead2_nb.sti",
173 ANIMSDIR "/corpses/w_dead2_nb.sti",
174 // The following line used to reference the non-existing animation
175 // cp_dead2_nb.sti.
176 ANIMSDIR "/corpses/cp_dead2.sti",
177 ANIMSDIR "/corpses/ct_dead.sti",
178 ANIMSDIR "/corpses/cw_dead1.sti",
179 ANIMSDIR "/corpses/mn_dead2.sti",
180 ANIMSDIR "/corpses/i_dead1.sti",
181 ANIMSDIR "/corpses/l_dead1.sti",
182 ANIMSDIR "/corpses/p_decomp2.sti",
183
184 ANIMSDIR "/corpses/tk_wrek.sti",
185 ANIMSDIR "/corpses/tk2_wrek.sti",
186 ANIMSDIR "/corpses/hm_wrek.sti",
187 ANIMSDIR "/corpses/ic_wrek.sti",
188 ANIMSDIR "/corpses/qn_dead.sti",
189 ANIMSDIR "/corpses/j_dead.sti",
190 ANIMSDIR "/corpses/s_burnt.sti",
191 ANIMSDIR "/corpses/s_expld.sti",
192 };
193
194 UINT8 gb4DirectionsFrom8[8] =
195 {
196 0, // NORTH
197 0, // NE
198 0, // E
199 0, // SE
200 1, // S
201 0, // SW,
202 2, // W,
203 0 // NW
204 };
205
206
207 static const UINT8 gb2DirectionsFrom8[8] =
208 {
209 0, // NORTH
210 7, // NE
211 7, // E
212 7, // SE
213 0, // S
214 7, // SW,
215 7, // W,
216 7 // NW
217 };
218
219
220 static const BOOLEAN gbCorpseValidForDecapitation[NUM_CORPSES] =
221 {
222 0,
223 0,
224 1,
225 1,
226 1,
227 1,
228 1,
229 1,
230 1,
231
232 0,
233 1,
234 1,
235 1,
236 1,
237 1,
238 1,
239 1,
240
241 0,
242 1,
243 1,
244 1,
245 1,
246 1,
247 1,
248 1,
249
250 // Civs....
251 1,
252 1,
253 1,
254 1,
255 1,
256 1,
257 1,
258 1,
259 1,
260 1,
261
262 1,
263 1,
264 1,
265 1,
266 0,
267 0,
268 0,
269 0,
270 0,
271 1,
272
273 0,
274 0,
275 0,
276 0,
277 0,
278 0,
279 0,
280 0,
281 };
282
283
284 static const INT8 gDecapitatedCorpse[NUM_CORPSES] =
285 {
286 0,
287 SMERC_JFK,
288 SMERC_JFK,
289 SMERC_JFK,
290 SMERC_JFK,
291 SMERC_JFK,
292 SMERC_JFK,
293 SMERC_JFK,
294 SMERC_JFK,
295
296 MMERC_JFK,
297 MMERC_JFK,
298 MMERC_JFK,
299 MMERC_JFK,
300 MMERC_JFK,
301 MMERC_JFK,
302 MMERC_JFK,
303 MMERC_JFK,
304
305 FMERC_JFK,
306 FMERC_JFK,
307 FMERC_JFK,
308 FMERC_JFK,
309 FMERC_JFK,
310 FMERC_JFK,
311 FMERC_JFK,
312 FMERC_JFK,
313
314 // Civs....
315 0,
316 0,
317 0,
318 0,
319 0,
320 0,
321 0,
322 0,
323 0,
324 0,
325
326 0,
327 0,
328 0,
329 0,
330 0,
331 0,
332 0,
333 0,
334 0,
335 0,
336
337 0,
338 0,
339 0,
340 0,
341 0,
342 0,
343 0,
344 0,
345 };
346
347
348
349 ROTTING_CORPSE gRottingCorpse[ MAX_ROTTING_CORPSES ];
350 INT32 giNumRottingCorpse = 0;
351
352
GetFreeRottingCorpse(void)353 static ROTTING_CORPSE* GetFreeRottingCorpse(void)
354 {
355 for (ROTTING_CORPSE* c = gRottingCorpse; c != gRottingCorpse + giNumRottingCorpse; ++c)
356 {
357 if (!c->fActivated) return c;
358 }
359 if (giNumRottingCorpse < MAX_ROTTING_CORPSES)
360 {
361 return &gRottingCorpse[giNumRottingCorpse++];
362 }
363 return NULL;
364 }
365
366
GetCorpseStructIndex(const ROTTING_CORPSE_DEFINITION * pCorpseDef,BOOLEAN fForImage)367 UINT16 GetCorpseStructIndex(const ROTTING_CORPSE_DEFINITION* pCorpseDef, BOOLEAN fForImage)
368 {
369 INT8 bDirection;
370
371 switch( pCorpseDef->ubType )
372 {
373 case QUEEN_MONSTER_DEAD:
374 case BURNT_DEAD:
375 case EXPLODE_DEAD:
376
377 bDirection = 0;
378 break;
379
380 case ICECREAM_DEAD:
381 case HUMMER_DEAD:
382
383 // OK , these have 2 directions....
384 bDirection = gb2DirectionsFrom8[ pCorpseDef->bDirection ];
385 if (fForImage) bDirection = OneCDirection(bDirection);
386 break;
387
388 case SMERC_FALL:
389 case SMERC_FALLF:
390 case MMERC_FALL:
391 case MMERC_FALLF:
392 case FMERC_FALL:
393 case FMERC_FALLF:
394
395 // OK , these have 4 directions....
396 bDirection = gb4DirectionsFrom8[ pCorpseDef->bDirection ];
397
398 if ( fForImage )
399 {
400 bDirection = OneCDirection(bDirection);
401 }
402 break;
403
404 default:
405
406 // Uses 8
407 bDirection = pCorpseDef->bDirection;
408 if (fForImage) bDirection = OneCDirection(bDirection);
409 break;
410 }
411
412 return( bDirection );
413 }
414
415
416 static void CreateCorpsePalette(ROTTING_CORPSE*);
417
418
AddRottingCorpse(ROTTING_CORPSE_DEFINITION * const pCorpseDef)419 ROTTING_CORPSE* AddRottingCorpse(ROTTING_CORPSE_DEFINITION* const pCorpseDef)
420 try
421 {
422 if (pCorpseDef->sGridNo == NOWHERE) return NULL;
423 if (pCorpseDef->ubType == NO_CORPSE) return NULL;
424
425 ROTTING_CORPSE* const c = GetFreeRottingCorpse();
426 if (c == NULL) return NULL;
427
428 // Copy elements in
429 c->def = *pCorpseDef;
430
431 // If we are a soecial type...
432 AnimationFlags uiDirectionUseFlag;
433 switch (pCorpseDef->ubType)
434 {
435 case SMERC_FALL:
436 case SMERC_FALLF:
437 case MMERC_FALL:
438 case MMERC_FALLF:
439 case FMERC_FALL:
440 case FMERC_FALLF: uiDirectionUseFlag = ANITILE_USE_4DIRECTION_FOR_START_FRAME; break;
441 default: uiDirectionUseFlag = ANITILE_USE_DIRECTION_FOR_START_FRAME; break;
442 }
443
444 if (!(gTacticalStatus.uiFlags & LOADING_SAVED_GAME))
445 {
446 // OK, AS WE ADD, CHECK FOR TOD AND DECAY APPROPRIATELY
447 if (GetWorldTotalMin() - c->def.uiTimeOfDeath > DELAY_UNTIL_ROTTING &&
448 c->def.ubType < ROTTING_STAGE2 &&
449 c->def.ubType <= FMERC_FALLF)
450 {
451 // Rott!
452 c->def.ubType = ROTTING_STAGE2;
453 }
454
455 // If time of death is a few days, now, don't add at all!
456 if (GetWorldTotalMin() - c->def.uiTimeOfDeath > DELAY_UNTIL_DONE_ROTTING) return NULL;
457 }
458
459 // Check if on roof or not...
460 AnimationLevel const ubLevelID = (c->def.bLevel == 0 ? ANI_STRUCT_LEVEL : ANI_ONROOF_LEVEL);
461
462 ANITILE_PARAMS AniParams;
463 AniParams = ANITILE_PARAMS{};
464 AniParams.sGridNo = c->def.sGridNo;
465 AniParams.ubLevelID = ubLevelID;
466 AniParams.sDelay = 150;
467 AniParams.sStartFrame = 0;
468 AniParams.uiFlags = ANITILE_PAUSED | ANITILE_OPTIMIZEFORSLOWMOVING | ANITILE_ANIMATE_Z | ANITILE_ERASEITEMFROMSAVEBUFFFER | uiDirectionUseFlag;
469 AniParams.sX = CenterX(c->def.sGridNo);
470 AniParams.sY = CenterY(c->def.sGridNo);
471 AniParams.sZ = c->def.sHeightAdjustment;
472 AniParams.v.user.uiData3 = c->def.bDirection;
473
474 if (!gGameSettings.fOptions[TOPTION_BLOOD_N_GORE])
475 {
476 AniParams.zCachedFile = zNoBloodCorpseFilenames[c->def.ubType];
477 }
478 else
479 {
480 AniParams.zCachedFile = zCorpseFilenames[c->def.ubType];
481 }
482
483 ANITILE* const ani = CreateAnimationTile(&AniParams);
484 c->pAniTile = ani;
485
486 LEVELNODE* const n = ani->pLevelNode;
487 const LEVELNODE* const land = gpWorldLevelData[c->def.sGridNo].pLandHead;
488
489 // Set flag and index values
490 n->uiFlags |= LEVELNODE_ROTTINGCORPSE;
491 n->ubShadeLevel = land->ubShadeLevel;
492 n->ubSumLights = land->ubSumLights;
493 n->ubMaxLights = land->ubMaxLights;
494 n->ubNaturalShadeLevel = land->ubNaturalShadeLevel;
495
496 // Get palette and create palettes and do substitutions
497 CreateCorpsePalette(c);
498
499 c->fActivated = TRUE;
500 ani->v.user.uiData = CORPSE2ID(c);
501 c->def.ubAIWarningValue = CORPSE_WARNING_MAX;
502
503 SetRenderFlags(RENDER_FLAG_FULL);
504
505 if (c->def.usFlags & ROTTING_CORPSE_VEHICLE)
506 {
507 ani->uiFlags |= ANITILE_FORWARD | ANITILE_LOOPING;
508 ani->uiFlags &= ~ANITILE_PAUSED;
509 }
510
511 InvalidateWorldRedundency();
512
513 // OK, loop through gridnos for corpse and remove blood.....
514
515 // Get root filename... this removes path and extension
516 // Used to find struct data for this corpse...
517 ST::string zFilename(FileMan::getFileNameWithoutExt(AniParams.zCachedFile));
518
519 // Add structure data.....
520 CheckForAndAddTileCacheStructInfo(n, c->def.sGridNo, ani->sCachedTileID, GetCorpseStructIndex(pCorpseDef, TRUE));
521
522 const STRUCTURE_FILE_REF* const pStructureFileRef = GetCachedTileStructureRefFromFilename(zFilename.c_str());
523 if (pStructureFileRef != NULL)
524 {
525 const UINT16 usStructIndex = GetCorpseStructIndex(pCorpseDef, TRUE);
526 const DB_STRUCTURE_REF* const pDBStructureRef = &pStructureFileRef->pDBStructureRef[usStructIndex];
527 for (UINT8 ubLoop = 0; ubLoop < pDBStructureRef->pDBStructure->ubNumberOfTiles; ++ubLoop)
528 {
529 DB_STRUCTURE_TILE* const* const ppTile = pDBStructureRef->ppTile;
530 const INT16 sTileGridNo = pCorpseDef->sGridNo + ppTile[ubLoop]->sPosRelToBase;
531 RemoveBlood(sTileGridNo, pCorpseDef->bLevel);
532 }
533 }
534
535 return c;
536 }
537 catch (...) { return 0; }
538
539
FreeCorpsePalettes(ROTTING_CORPSE * pCorpse)540 static void FreeCorpsePalettes(ROTTING_CORPSE* pCorpse)
541 {
542 INT32 cnt;
543
544 for ( cnt = 0; cnt < NUM_CORPSE_SHADES; cnt++ )
545 {
546 if ( pCorpse->pShades[ cnt ] != NULL )
547 {
548 delete[] pCorpse->pShades[ cnt ];
549 pCorpse->pShades[ cnt ] = NULL;
550 }
551 }
552 }
553
554
555 static void RemoveCorpse(ROTTING_CORPSE* c);
556
557
RemoveCorpses()558 void RemoveCorpses( )
559 {
560 FOR_EACH_ROTTING_CORPSE(c) RemoveCorpse(c);
561 giNumRottingCorpse = 0;
562 }
563
564
RemoveCorpse(ROTTING_CORPSE * const c)565 static void RemoveCorpse(ROTTING_CORPSE* const c)
566 {
567 Assert(gRottingCorpse <= c && c < endof(gRottingCorpse));
568 Assert(c->fActivated);
569
570 c->fActivated = FALSE;
571 DeleteAniTile(c->pAniTile);
572 FreeCorpsePalettes(c);
573 }
574
575
CreateCorpsePalette(ROTTING_CORPSE * const c)576 static void CreateCorpsePalette(ROTTING_CORPSE* const c)
577 {
578 char const* const substitution =
579 c->def.ubType == ROTTING_STAGE2 ? "" :
580 c->def.usFlags & ROTTING_CORPSE_USE_CAMO_PALETTE ? ANIMSDIR "/camo.COL" :
581 GetBodyTypePaletteSubstitution(0, c->def.ubBodyType);
582
583 const SGPPaletteEntry* pal;
584 SGPPaletteEntry tmp_pal[256];
585 if (!substitution)
586 {
587 // Use palette from HVOBJECT, then use substitution for pants, etc
588 memcpy(tmp_pal, gpTileCache[c->pAniTile->sCachedTileID].pImagery->vo->Palette(), sizeof(tmp_pal));
589 SetPaletteReplacement(tmp_pal, c->def.HeadPal);
590 SetPaletteReplacement(tmp_pal, c->def.VestPal);
591 SetPaletteReplacement(tmp_pal, c->def.PantsPal);
592 SetPaletteReplacement(tmp_pal, c->def.SkinPal);
593 pal = tmp_pal;
594 }
595 else if (substitution[0] != '\0' && CreateSGPPaletteFromCOLFile(tmp_pal, substitution))
596 {
597 pal = tmp_pal;
598 }
599 else
600 {
601 // Use palette from hvobject
602 pal = gpTileCache[c->pAniTile->sCachedTileID].pImagery->vo->Palette();
603 }
604
605 CreateBiasedShadedPalettes(c->pShades, pal);
606 }
607
608
TurnSoldierIntoCorpse(SOLDIERTYPE & s)609 BOOLEAN TurnSoldierIntoCorpse(SOLDIERTYPE& s)
610 {
611 ROTTING_CORPSE_DEFINITION Corpse;
612 UINT8 ubType;
613 UINT16 usItemFlags = 0; //WORLD_ITEM_DONTRENDER;
614 UINT8 ubNumGoo;
615 INT16 sNewGridNo;
616 OBJECTTYPE ItemObject;
617
618
619 if (s.sGridNo == NOWHERE)
620 {
621 return( FALSE );
622 }
623
624 // ATE: Change to fix crash when item in hand
625 if (gpItemPointer && gpItemPointerSoldier == &s)
626 {
627 CancelItemPointer( );
628 }
629
630 // Setup some values!
631 Corpse = ROTTING_CORPSE_DEFINITION{};
632 Corpse.ubBodyType = s.ubBodyType;
633 Corpse.sGridNo = s.sGridNo;
634 Corpse.bLevel = s.bLevel;
635 Corpse.ubProfile = s.ubProfile;
636
637 if ( Corpse.bLevel > 0 )
638 {
639 Corpse.sHeightAdjustment = s.sHeightAdjustment - WALL_HEIGHT;
640 }
641
642 Corpse.HeadPal = s.HeadPal;
643 Corpse.VestPal = s.VestPal;
644 Corpse.SkinPal = s.SkinPal;
645 Corpse.PantsPal = s.PantsPal;
646
647 if (s.bCamo != 0)
648 {
649 Corpse.usFlags |= ROTTING_CORPSE_USE_CAMO_PALETTE;
650 }
651
652 // Determine corpse type!
653 ubType = gubAnimSurfaceCorpseID[s.ubBodyType][s.usAnimState];
654
655 Corpse.bDirection = s.bDirection;
656
657 // If we are a vehicle.... only use 1 direction....
658 if (s.uiStatusFlags & SOLDIER_VEHICLE)
659 {
660 Corpse.usFlags |= ROTTING_CORPSE_VEHICLE;
661
662 if (s.ubBodyType != ICECREAMTRUCK && s.ubBodyType != HUMVEE)
663 {
664 Corpse.bDirection = 7;
665 }
666 else
667 {
668 Corpse.bDirection = gb2DirectionsFrom8[ Corpse.bDirection ];
669 }
670 }
671
672 if ( ubType == QUEEN_MONSTER_DEAD || ubType == BURNT_DEAD || ubType == EXPLODE_DEAD )
673 {
674 Corpse.bDirection = 7;
675 }
676
677
678 // ATE: If bDirection, get opposite
679 //if ( ubType == SMERC_FALLF || ubType == MMERC_FALLF || ubType == FMERC_FALLF )
680 //{
681 // Corpse.bDirection = OppositeDirection(Corpse.bDirection);
682 //}
683
684 // Set time of death
685 Corpse.uiTimeOfDeath = GetWorldTotalMin( );
686
687 // If corpse is not valid. make items visible
688 if (ubType == NO_CORPSE && s.bTeam != OUR_TEAM)
689 {
690 usItemFlags &= (~WORLD_ITEM_DONTRENDER );
691 }
692
693
694 // ATE: If the queen is killed, she should
695 // make items visible because it ruins end sequence....
696 Visibility const bVisible = s.bTeam == OUR_TEAM ||
697 s.ubProfile == QUEEN ?
698 VISIBLE : INVISIBLE;
699
700 // Not for a robot...
701 if (AM_A_ROBOT(&s))
702 {
703
704 }
705 else if ( ubType == QUEEN_MONSTER_DEAD )
706 {
707 gTacticalStatus.fLockItemLocators = FALSE;
708
709 ubNumGoo = 6 - ( gGameOptions.ubDifficultyLevel - DIF_LEVEL_EASY );
710
711 sNewGridNo = s.sGridNo + WORLD_COLS * 2;
712
713 for (INT32 cnt = 0; cnt < ubNumGoo; ++cnt)
714 {
715 CreateItem( JAR_QUEEN_CREATURE_BLOOD, 100, &ItemObject );
716
717 AddItemToPool(sNewGridNo, &ItemObject, bVisible, s.bLevel, usItemFlags, -1);
718 }
719 }
720 else
721 {
722 // OK, Place what objects this guy was carrying on the ground!
723 FOR_EACH_SOLDIER_INV_SLOT(pObj, s)
724 {
725 if ( pObj->usItem != NOTHING )
726 {
727 // Check if it's supposed to be dropped
728 if (!(pObj->fFlags & OBJECT_UNDROPPABLE)
729 || (s.bTeam == OUR_TEAM)
730 || gamepolicy(f_drop_everything))
731 {
732 // and make sure that it really is a droppable item type
733 if ( !(GCM->getItem(pObj->usItem)->getFlags() & ITEM_DEFAULT_UNDROPPABLE) )
734 {
735 ReduceAmmoDroppedByNonPlayerSoldiers(s, *pObj);
736 Visibility vis = gamepolicy(f_all_dropped_visible) ? VISIBLE : bVisible;
737 AddItemToPool(s.sGridNo, pObj, vis, s.bLevel, usItemFlags, -1);
738 }
739 }
740 }
741 }
742
743 DropKeysInKeyRing(s, s.sGridNo, s.bLevel, bVisible, false, 0, false);
744 }
745
746 // Make team look for items
747 AllSoldiersLookforItems();
748
749 // If not a player, you can completely remove soldiertype
750 // otherwise, just remove their graphic
751 if (s.bTeam != OUR_TEAM)
752 {
753 // Remove merc!
754 // ATE: Remove merc slot first - will disappear if no corpse data found!
755 TacticalRemoveSoldier(s);
756 }
757 else
758 {
759 RemoveSoldierFromGridNo(s);
760 }
761
762 if (ubType == NO_CORPSE)
763 {
764 return( TRUE );
765 }
766
767 // Set type
768 Corpse.ubType = ubType;
769 ROTTING_CORPSE* const added_corpse = AddRottingCorpse(&Corpse);
770
771 // If this is our guy......make visible...
772 //if (s.bTeam == OUR_TEAM)
773 {
774 added_corpse->def.bVisible = 1;
775 }
776
777 return( TRUE );
778 }
779
780
FindNearestRottingCorpse(SOLDIERTYPE * pSoldier)781 INT16 FindNearestRottingCorpse( SOLDIERTYPE *pSoldier )
782 {
783 INT32 uiLowestRange = 999999;
784 INT16 sLowestGridNo = NOWHERE;
785
786 // OK, loop through our current listing of bodies
787 CFOR_EACH_ROTTING_CORPSE(c)
788 {
789 // Check rotting state
790 if (c->def.ubType == ROTTING_STAGE2)
791 {
792 const INT32 uiRange = GetRangeInCellCoordsFromGridNoDiff(pSoldier->sGridNo, c->def.sGridNo);
793 if (uiRange < uiLowestRange)
794 {
795 sLowestGridNo = c->def.sGridNo;
796 uiLowestRange = uiRange;
797 }
798 }
799 }
800
801 return( sLowestGridNo );
802 }
803
804
AddCrowToCorpse(ROTTING_CORPSE * pCorpse)805 static void AddCrowToCorpse(ROTTING_CORPSE* pCorpse)
806 {
807 SOLDIERCREATE_STRUCT MercCreateStruct;
808 INT8 bBodyType = CROW;
809
810 // No crows inside :(
811 if (GetRoom(pCorpse->def.sGridNo) != NO_ROOM) return;
812
813 // Put him flying over corpse pisition
814 MercCreateStruct = SOLDIERCREATE_STRUCT{};
815 MercCreateStruct.ubProfile = NO_PROFILE;
816 MercCreateStruct.sSectorX = gWorldSectorX;
817 MercCreateStruct.sSectorY = gWorldSectorY;
818 MercCreateStruct.bSectorZ = gbWorldSectorZ;
819 MercCreateStruct.bBodyType = bBodyType;
820 MercCreateStruct.bDirection = SOUTH;
821 MercCreateStruct.bTeam = CIV_TEAM;
822 MercCreateStruct.sInsertionGridNo = pCorpse->def.sGridNo;
823 RandomizeNewSoldierStats( &MercCreateStruct );
824
825 SOLDIERTYPE* const pSoldier = TacticalCreateSoldier(MercCreateStruct);
826 if (pSoldier != NULL)
827 {
828 // Setup action data to point back to corpse....
829 pSoldier->uiPendingActionData1 = CORPSE2ID(pCorpse);
830 pSoldier->sPendingActionData2 = pCorpse->def.sGridNo;
831
832 pCorpse->def.bNumServicingCrows++;
833
834 const INT16 sGridNo = FindRandomGridNoFromSweetSpot(pSoldier, pCorpse->def.sGridNo, 2);
835 if ( sGridNo != NOWHERE )
836 {
837 pSoldier->ubStrategicInsertionCode = INSERTION_CODE_GRIDNO;
838 pSoldier->usStrategicInsertionData = sGridNo;
839
840 pSoldier->sInsertionGridNo = sGridNo;
841 pSoldier->sDesiredHeight = 0;
842
843 AddSoldierToSector(pSoldier);
844
845 // Change to fly animation
846 //sGridNo = FindRandomGridNoFromSweetSpot(pSoldier, pCorpse->def.sGridNo, 5);
847 //pSoldier->usUIMovementMode = CROW_FLY;
848 //EVENT_GetNewSoldierPath( pSoldier, sGridNo, pSoldier->usUIMovementMode );
849 }
850 else
851 {
852 TacticalRemoveSoldier(*pSoldier);
853 }
854
855 }
856
857 }
858
HandleCrowLeave(SOLDIERTYPE * pSoldier)859 void HandleCrowLeave( SOLDIERTYPE *pSoldier )
860 {
861 // Check if this crow is still referencing the same corpse...
862 ROTTING_CORPSE* const pCorpse = ID2CORPSE(pSoldier->uiPendingActionData1);
863
864 // Double check grindo...
865 if ( pSoldier->sPendingActionData2 == pCorpse->def.sGridNo )
866 {
867 // We have a match
868 // Adjust crow servicing count...
869 pCorpse->def.bNumServicingCrows--;
870
871 if ( pCorpse->def.bNumServicingCrows < 0 )
872 {
873 pCorpse->def.bNumServicingCrows = 0;
874 }
875 }
876 }
877
878
HandleCrowFlyAway(SOLDIERTYPE * pSoldier)879 void HandleCrowFlyAway( SOLDIERTYPE *pSoldier )
880 {
881 // Set desired height
882 pSoldier->sDesiredHeight = 100;
883
884 // Change to fly animation
885 const INT16 sGridNo = FindRandomGridNoFromSweetSpot(pSoldier, pSoldier->sGridNo, 5);
886 pSoldier->usUIMovementMode = CROW_FLY;
887 SendGetNewSoldierPathEvent(pSoldier, sGridNo);
888 }
889
890
HandleRottingCorpses()891 void HandleRottingCorpses( )
892 {
893 ROTTING_CORPSE *pCorpse;
894 INT8 bNumCrows = 0;
895 UINT32 uiChosenCorpseID;
896
897 // ATE: If it's too late, don't!
898 if( NightTime() )
899 {
900 return;
901 }
902
903 if ( gbWorldSectorZ > 0 )
904 {
905 return;
906 }
907
908 // ATE: Check for multiple crows.....
909 // Couint how many we have now...
910 CFOR_EACH_IN_TEAM(s, CIV_TEAM)
911 {
912 if (s->bInSector &&
913 s->bLife >= OKLIFE &&
914 !(s->uiStatusFlags & SOLDIER_GASSED) &&
915 s->ubBodyType == CROW)
916 {
917 bNumCrows++;
918 }
919 }
920
921 // Once population gets down to 0, we can add more again....
922 if ( bNumCrows == 0 )
923 {
924 gTacticalStatus.fDontAddNewCrows = FALSE;
925 }
926
927 if ( gTacticalStatus.fDontAddNewCrows )
928 {
929 return;
930 }
931
932 if ( bNumCrows >= gTacticalStatus.ubNumCrowsPossible )
933 {
934 gTacticalStatus.fDontAddNewCrows = TRUE;
935 return;
936 }
937
938 if (IsTeamActive(CREATURE_TEAM))
939 {
940 // don't add any crows while there are predators around
941 return;
942 }
943
944 // Pick one to attact a crow...
945 uiChosenCorpseID = Random( giNumRottingCorpse );
946
947 pCorpse = &(gRottingCorpse[ uiChosenCorpseID ] );
948
949 if ( pCorpse->fActivated )
950 {
951 if ( !( pCorpse->def.usFlags & ROTTING_CORPSE_VEHICLE ) )
952 {
953 if ( pCorpse->def.ubType == ROTTING_STAGE2 )
954 {
955 if ( GridNoOnScreen( pCorpse->def.sGridNo ) )
956 {
957 return;
958 }
959
960 AddCrowToCorpse( pCorpse );
961 AddCrowToCorpse( pCorpse );
962 }
963 }
964 }
965 }
966
967
FindCorpseBasedOnStructure(GridNo const grid_no,STRUCTURE * const structure)968 static ROTTING_CORPSE* FindCorpseBasedOnStructure(GridNo const grid_no, STRUCTURE* const structure)
969 {
970 for (LEVELNODE const* i = gpWorldLevelData[grid_no].pStructHead; i; i = i->pNext)
971 {
972 if (i->pStructureData != structure) continue;
973 return ID2CORPSE(i->pAniTile->v.user.uiData);
974 }
975 return 0;
976 }
977
978
CorpseHit(INT16 sGridNo,UINT16 usStructureID)979 void CorpseHit( INT16 sGridNo, UINT16 usStructureID )
980 {
981 #if 0
982 STRUCTURE *pStructure, *pBaseStructure;
983 ROTTING_CORPSE *pCorpse = NULL;
984 INT16 sBaseGridNo;
985
986 pStructure = FindStructureByID( sGridNo, usStructureID );
987
988 // Get base....
989 pBaseStructure = FindBaseStructure( pStructure );
990
991 // Find base gridno...
992 sBaseGridNo = pBaseStructure->sGridNo;
993
994 // Get corpse ID.....
995 pCorpse = FindCorpseBasedOnStructure( sBaseGridNo, pBaseStructure );
996
997 if ( pCorpse == NULL )
998 {
999 SLOGD("Bullet hit corpse but corpse cannot be found at: %d", sBaseGridNo );
1000 return;
1001 }
1002
1003 // Twitch the bugger...
1004 SLOGD("Corpse hit" );
1005
1006 if ( GridNoOnScreen( sBaseGridNo ) )
1007 {
1008 // Twitch....
1009 // Set frame...
1010 SetAniTileFrame(pCorpse->pAniTile, 1);
1011
1012 // Go reverse...
1013 pCorpse->pAniTile->uiFlags |= ( ANITILE_BACKWARD | ANITILE_PAUSE_AFTER_LOOP );
1014
1015 // Turn off pause...
1016 pCorpse->pAniTile->uiFlags &= (~ANITILE_PAUSED);
1017 }
1018
1019 // PLay a sound....
1020 PlayLocationJA2Sample(sGridNo, BULLET_IMPACT_2, MIDVOLUME, 1);
1021 #endif
1022 }
1023
1024
VaporizeCorpse(INT16 sGridNo,UINT16 usStructureID)1025 void VaporizeCorpse( INT16 sGridNo, UINT16 usStructureID )
1026 {
1027 STRUCTURE *pStructure, *pBaseStructure;
1028 ROTTING_CORPSE *pCorpse = NULL;
1029 INT16 sBaseGridNo;
1030 ANITILE_PARAMS AniParams;
1031
1032 pStructure = FindStructureByID( sGridNo, usStructureID );
1033
1034 // Get base....
1035 pBaseStructure = FindBaseStructure( pStructure );
1036
1037 // Find base gridno...
1038 sBaseGridNo = pBaseStructure->sGridNo;
1039
1040 // Get corpse ID.....
1041 pCorpse = FindCorpseBasedOnStructure( sBaseGridNo, pBaseStructure );
1042
1043 if ( pCorpse == NULL )
1044 {
1045 SLOGW("Vaporize corpse but corpse cannot be found at: %d", sBaseGridNo );
1046 return;
1047 }
1048
1049 if ( pCorpse->def.usFlags & ROTTING_CORPSE_VEHICLE )
1050 {
1051 return;
1052 }
1053
1054 if ( GridNoOnScreen( sBaseGridNo ) )
1055 {
1056 // Add explosion
1057 AniParams = ANITILE_PARAMS{};
1058 AniParams.sGridNo = sBaseGridNo;
1059 AniParams.ubLevelID = ANI_STRUCT_LEVEL;
1060 AniParams.sDelay = (INT16)( 80 );
1061 AniParams.sStartFrame = 0;
1062 AniParams.uiFlags = ANITILE_FORWARD;
1063 AniParams.sX = CenterX( sBaseGridNo );
1064 AniParams.sY = CenterY( sBaseGridNo );
1065 AniParams.sZ = (INT16)pCorpse->def.sHeightAdjustment;
1066 AniParams.zCachedFile = TILECACHEDIR "/gen_blow.sti";
1067 CreateAnimationTile( &AniParams );
1068
1069 RemoveCorpse(pCorpse);
1070 SetRenderFlags( RENDER_FLAG_FULL );
1071
1072 if ( pCorpse->def.bLevel == 0 )
1073 {
1074 // Set some blood......
1075 SpreadEffect(sBaseGridNo, 2, 0, NULL, BLOOD_SPREAD_EFFECT, 0, NULL);
1076 }
1077 }
1078
1079 // PLay a sound....
1080 PlayLocationJA2Sample(sGridNo, BODY_EXPLODE_1, HIGHVOLUME, 1);
1081 }
1082
1083
FindNearestAvailableGridNoForCorpse(ROTTING_CORPSE_DEFINITION * pDef,INT8 ubRadius)1084 INT16 FindNearestAvailableGridNoForCorpse( ROTTING_CORPSE_DEFINITION *pDef, INT8 ubRadius )
1085 {
1086 INT16 sSweetGridNo;
1087 INT16 sTop, sBottom;
1088 INT16 sLeft, sRight;
1089 INT16 cnt1, cnt2, cnt3;
1090 INT16 sGridNo;
1091 INT32 uiRange, uiLowestRange = 999999;
1092 INT16 sLowestGridNo=0;
1093 INT32 leftmost;
1094 BOOLEAN fFound = FALSE;
1095 SOLDIERTYPE soldier;
1096 UINT8 ubSaveNPCAPBudget;
1097 UINT8 ubSaveNPCDistLimit;
1098 STRUCTURE_FILE_REF *pStructureFileRef = NULL;
1099 UINT8 ubBestDirection=0;
1100 BOOLEAN fSetDirection = FALSE;
1101
1102 cnt3 = 0;
1103
1104 // Get root filename... this removes path and extension
1105 // Used to find struct data for this corpse...
1106 ST::string zFilename(FileMan::getFileNameWithoutExt(zCorpseFilenames[pDef->ubType]));
1107
1108 pStructureFileRef = GetCachedTileStructureRefFromFilename( zFilename.c_str() );
1109
1110 sSweetGridNo = pDef->sGridNo;
1111
1112
1113 //Save AI pathing vars. changing the distlimit restricts how
1114 //far away the pathing will consider.
1115 ubSaveNPCAPBudget = gubNPCAPBudget;
1116 ubSaveNPCDistLimit = gubNPCDistLimit;
1117 gubNPCAPBudget = 0;
1118 gubNPCDistLimit = ubRadius;
1119
1120 //create dummy soldier, and use the pathing to determine which nearby slots are
1121 //reachable.
1122 soldier = SOLDIERTYPE{};
1123 soldier.bTeam = 1;
1124 soldier.sGridNo = sSweetGridNo;
1125
1126 sTop = ubRadius;
1127 sBottom = -ubRadius;
1128 sLeft = - ubRadius;
1129 sRight = ubRadius;
1130
1131 //clear the mapelements of potential residue MAPELEMENT_REACHABLE flags
1132 //in the square region.
1133 for( cnt1 = sBottom; cnt1 <= sTop; cnt1++ )
1134 {
1135 for( cnt2 = sLeft; cnt2 <= sRight; cnt2++ )
1136 {
1137 sGridNo = sSweetGridNo + (WORLD_COLS * cnt1) + cnt2;
1138 if ( sGridNo >=0 && sGridNo < WORLD_MAX )
1139 {
1140 gpWorldLevelData[ sGridNo ].uiFlags &= (~MAPELEMENT_REACHABLE);
1141 }
1142 }
1143 }
1144
1145 //Now, find out which of these gridnos are reachable
1146 //(use the fake soldier and the pathing settings)
1147 FindBestPath( &soldier, NOWHERE, 0, WALKING, COPYREACHABLE, 0 );
1148
1149 uiLowestRange = 999999;
1150
1151 for( cnt1 = sBottom; cnt1 <= sTop; cnt1++ )
1152 {
1153 leftmost = ( ( sSweetGridNo + ( WORLD_COLS * cnt1 ) )/ WORLD_COLS ) * WORLD_COLS;
1154
1155 for( cnt2 = sLeft; cnt2 <= sRight; cnt2++ )
1156 {
1157 sGridNo = sSweetGridNo + ( WORLD_COLS * cnt1 ) + cnt2;
1158 if (sGridNo >=0 && sGridNo < WORLD_MAX && sGridNo >= leftmost &&
1159 sGridNo < (leftmost + WORLD_COLS) &&
1160 gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REACHABLE && !Water(sGridNo))
1161 {
1162 // Go on sweet stop
1163 if ( NewOKDestination( &soldier, sGridNo, TRUE, soldier.bLevel ) )
1164 {
1165 BOOLEAN fDirectionFound = FALSE;
1166 BOOLEAN fCanSetDirection = FALSE;
1167
1168 // Check each struct in each direction
1169 if ( pStructureFileRef == NULL )
1170 {
1171 fDirectionFound = TRUE;
1172 }
1173 else
1174 {
1175 for( cnt3 = 0; cnt3 < 8; cnt3++ )
1176 {
1177 if (OkayToAddStructureToWorld(sGridNo, pDef->bLevel, &pStructureFileRef->pDBStructureRef[OneCDirection(cnt3)], INVALID_STRUCTURE_ID))
1178 {
1179 fDirectionFound = TRUE;
1180 fCanSetDirection = TRUE;
1181 break;
1182 }
1183 }
1184 }
1185
1186 if ( fDirectionFound )
1187 {
1188 uiRange = GetRangeInCellCoordsFromGridNoDiff( sSweetGridNo, sGridNo );
1189
1190 if ( uiRange < uiLowestRange )
1191 {
1192 if ( fCanSetDirection )
1193 {
1194 ubBestDirection = (UINT8)cnt3;
1195 fSetDirection = TRUE;
1196 }
1197 sLowestGridNo = sGridNo;
1198 uiLowestRange = uiRange;
1199 fFound = TRUE;
1200 }
1201 }
1202
1203 }
1204 }
1205 }
1206 }
1207 gubNPCAPBudget = ubSaveNPCAPBudget;
1208 gubNPCDistLimit = ubSaveNPCDistLimit;
1209 if ( fFound )
1210 {
1211 if ( fSetDirection )
1212 {
1213 pDef->bDirection = ubBestDirection;
1214 }
1215
1216 return sLowestGridNo;
1217 }
1218 return NOWHERE;
1219 }
1220
1221
IsValidDecapitationCorpse(const ROTTING_CORPSE * const c)1222 BOOLEAN IsValidDecapitationCorpse(const ROTTING_CORPSE* const c)
1223 {
1224 if (c->def.fHeadTaken) return FALSE;
1225 return gbCorpseValidForDecapitation[c->def.ubType];
1226 }
1227
1228
GetCorpseAtGridNo(INT16 sGridNo,INT8 bLevel)1229 ROTTING_CORPSE *GetCorpseAtGridNo( INT16 sGridNo, INT8 bLevel )
1230 {
1231 STRUCTURE *pStructure, *pBaseStructure;
1232
1233 pStructure = FindStructure( sGridNo, STRUCTURE_CORPSE );
1234
1235 if ( pStructure != NULL )
1236 {
1237 // Get base....
1238 pBaseStructure = FindBaseStructure( pStructure );
1239
1240 if ( pBaseStructure != NULL )
1241 {
1242 return( FindCorpseBasedOnStructure( pBaseStructure->sGridNo, pBaseStructure ) );
1243 }
1244 }
1245
1246 return( NULL );
1247 }
1248
1249
DecapitateCorpse(const INT16 sGridNo,const INT8 bLevel)1250 void DecapitateCorpse(const INT16 sGridNo, const INT8 bLevel)
1251 {
1252 OBJECTTYPE Object;
1253 ROTTING_CORPSE *pCorpse;
1254 ROTTING_CORPSE_DEFINITION CorpseDef;
1255
1256 pCorpse = GetCorpseAtGridNo( sGridNo, bLevel );
1257
1258 if ( pCorpse == NULL )
1259 {
1260 return;
1261 }
1262
1263 if ( IsValidDecapitationCorpse( pCorpse ) )
1264 {
1265 // Decapitate.....
1266 // Copy corpse definition...
1267 CorpseDef = pCorpse->def;
1268
1269 // Add new one...
1270 CorpseDef.ubType = gDecapitatedCorpse[ CorpseDef.ubType ];
1271
1272 pCorpse->def.fHeadTaken = TRUE;
1273
1274 if ( CorpseDef.ubType != 0 )
1275 {
1276 RemoveCorpse(pCorpse);
1277 AddRottingCorpse( &CorpseDef );
1278 }
1279
1280 // Add head item.....
1281
1282 // Pick the head based on profile type...
1283 UINT16 head_index;
1284 switch (pCorpse->def.ubProfile)
1285 {
1286 case CHRIS: head_index = HEAD_2; break;
1287 case T_REX: head_index = HEAD_3; break;
1288 case SLAY: head_index = HEAD_4; break;
1289 case DRUGGIST: head_index = HEAD_5; break;
1290 case ANNIE: head_index = HEAD_6; break;
1291 case TIFFANY: head_index = HEAD_7; break;
1292 default: head_index = HEAD_1; break;
1293 }
1294
1295 CreateItem(head_index, 100, &Object);
1296 AddItemToPool( sGridNo, &Object, INVISIBLE, 0, 0, 0 );
1297
1298 // All teams lok for this...
1299 NotifySoldiersToLookforItems( );
1300 }
1301
1302 }
1303
1304
GetBloodFromCorpse(SOLDIERTYPE * pSoldier)1305 void GetBloodFromCorpse( SOLDIERTYPE *pSoldier )
1306 {
1307 const ROTTING_CORPSE* const pCorpse = ID2CORPSE(pSoldier->uiPendingActionData4);
1308 INT8 bObjSlot;
1309 OBJECTTYPE Object;
1310
1311 bObjSlot = FindObj( pSoldier, JAR );
1312
1313 // What kind of corpse ami I?
1314 switch( pCorpse->def.ubType )
1315 {
1316 case ADULTMONSTER_DEAD:
1317 case INFANTMONSTER_DEAD:
1318
1319 // Can get creature blood....
1320 CreateItem( JAR_CREATURE_BLOOD, 100, &Object );
1321 break;
1322
1323 case QUEEN_MONSTER_DEAD:
1324 CreateItem( JAR_QUEEN_CREATURE_BLOOD, 100, &Object );
1325 break;
1326
1327 default:
1328
1329 CreateItem( JAR_HUMAN_BLOOD, 100, &Object );
1330 break;
1331
1332 }
1333
1334 if ( bObjSlot != NO_SLOT )
1335 {
1336 SwapObjs( &(pSoldier->inv[ bObjSlot ] ), &Object );
1337 }
1338 }
1339
1340
ReduceAmmoDroppedByNonPlayerSoldiers(SOLDIERTYPE const & s,OBJECTTYPE & o)1341 void ReduceAmmoDroppedByNonPlayerSoldiers(SOLDIERTYPE const& s, OBJECTTYPE& o)
1342 {
1343 if (s.bTeam == OUR_TEAM) return;
1344 if (GCM->getItem(o.usItem)->getItemClass() != IC_AMMO) return;
1345
1346 // Don't drop all the clips, just a random # of them between 1 and how
1347 // many there are
1348 o.ubNumberOfObjects = 1 + Random(o.ubNumberOfObjects);
1349 o.ubWeight = CalculateObjectWeight(&o);
1350 }
1351
1352
LookForAndMayCommentOnSeeingCorpse(SOLDIERTYPE * pSoldier,INT16 sGridNo,UINT8 ubLevel)1353 void LookForAndMayCommentOnSeeingCorpse( SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 ubLevel )
1354 {
1355 ROTTING_CORPSE *pCorpse;
1356 INT8 bToleranceThreshold = 0;
1357
1358 if ( QuoteExp_HeadShotOnly[ pSoldier->ubProfile ] == 1 )
1359 {
1360 return;
1361 }
1362
1363 pCorpse = GetCorpseAtGridNo( sGridNo, ubLevel );
1364
1365 if ( pCorpse == NULL )
1366 {
1367 return;
1368 }
1369
1370 if ( pCorpse->def.ubType != ROTTING_STAGE2 )
1371 {
1372 return;
1373 }
1374
1375 // If servicing qrows, tolerance is now 1
1376 if ( pCorpse->def.bNumServicingCrows > 0 )
1377 {
1378 bToleranceThreshold++;
1379 }
1380
1381 // Check tolerance
1382 if ( pSoldier->bCorpseQuoteTolerance <= bToleranceThreshold )
1383 {
1384 // Say quote...
1385 TacticalCharacterDialogue( pSoldier, QUOTE_HEADSHOT );
1386
1387 BeginMultiPurposeLocator(sGridNo, ubLevel);
1388
1389 // Reset values....
1390 pSoldier->bCorpseQuoteTolerance = (INT8)( Random(3) + 1 );
1391
1392 // 50% chance of adding 1 to other mercs....
1393 if ( Random( 2 ) == 1 )
1394 {
1395 FOR_EACH_IN_TEAM(s, OUR_TEAM)
1396 {
1397 // ATE: Ok, lets check for some basic things here!
1398 if (s->bLife >= OKLIFE && s->sGridNo != NOWHERE && s->bInSector)
1399 {
1400 ++s->bCorpseQuoteTolerance;
1401 }
1402 }
1403 }
1404 }
1405 }
1406
1407
GetGridNoOfCorpseGivenProfileID(const UINT8 ubProfileID)1408 INT16 GetGridNoOfCorpseGivenProfileID(const UINT8 ubProfileID)
1409 {
1410 // Loop through all corpses....
1411 CFOR_EACH_ROTTING_CORPSE(c)
1412 {
1413 if (c->def.ubProfile == ubProfileID) return c->def.sGridNo;
1414 }
1415 return NOWHERE;
1416 }
1417
1418
DecayRottingCorpseAIWarnings(void)1419 void DecayRottingCorpseAIWarnings(void)
1420 {
1421 FOR_EACH_ROTTING_CORPSE(c)
1422 {
1423 if (c->def.ubAIWarningValue > 0) --c->def.ubAIWarningValue;
1424 }
1425 }
1426
1427
GetNearestRottingCorpseAIWarning(const INT16 sGridNo)1428 UINT8 GetNearestRottingCorpseAIWarning(const INT16 sGridNo)
1429 {
1430 UINT8 ubHighestWarning = 0;
1431 CFOR_EACH_ROTTING_CORPSE(c)
1432 {
1433 if (c->def.ubAIWarningValue > 0 &&
1434 PythSpacesAway(sGridNo, c->def.sGridNo) <= CORPSE_WARNING_DIST &&
1435 c->def.ubAIWarningValue > ubHighestWarning)
1436 {
1437 ubHighestWarning = c->def.ubAIWarningValue;
1438 }
1439 }
1440 return ubHighestWarning;
1441 }
1442