1 #include "Soldier_Control.h"
2 #include "AI.h"
3 #include "Animation_Cache.h"
4 #include "Animation_Control.h"
5 #include "Animation_Data.h"
6 #include "Arms_Dealer_Init.h"
7 #include "Boxing.h"
8 #include "Campaign.h"
9 #include "Campaign_Types.h"
10 #include "Civ_Quotes.h"
11 #include "ContentManager.h"
12 #include "Debug.h"
13 #include "Dialogue_Control.h"
14 #include "Directories.h"
15 #include "Drugs_And_Alcohol.h"
16 #include "Event_Pump.h"
17 #include "Explosion_Control.h"
18 #include "Faces.h"
19 #include "FileMan.h"
20 #include "Font_Control.h"
21 #include "GameInstance.h"
22 #include "GameSettings.h"
23 #include "Game_Clock.h"
24 #include "HImage.h"
25 #include "Handle_Doors.h"
26 #include "Handle_Items.h"
27 #include "Handle_UI.h"
28 #include "Interface.h"
29 #include "Interface_Dialogue.h"
30 #include "Isometric_Utils.h"
31 #include "Items.h"
32 #include "JAScreens.h"
33 #include "Keys.h"
34 #include "Lighting.h"
35 #include "Logger.h"
36 #include "MapScreen.h"
37 #include "Map_Information.h"
38 #include "Meanwhile.h"
39 #include "MercProfile.h"
40 #include "Message.h"
41 #include "Morale.h"
42 #include "NPC.h"
43 #include "OppList.h"
44 #include "Overhead.h"
45 #include "Overhead_Map.h"
46 #include "Overhead_Types.h"
47 #include "PathAI.h"
48 #include "Points.h"
49 #include "Quests.h"
50 #include "RT_Time_Defines.h"
51 #include "Random.h"
52 #include "RenderWorld.h"
53 #include "Render_Dirty.h"
54 #include "Render_Fun.h"
55 #include "Rotting_Corpses.h"
56 #include "ScreenIDs.h"
57 #include "SkillCheck.h"
58 #include "Smell.h"
59 #include "SmokeEffects.h"
60 #include "Soldier.h"
61 #include "Soldier_Ani.h"
62 #include "Soldier_Find.h"
63 #include "Soldier_Functions.h"
64 #include "Soldier_Macros.h"
65 #include "Soldier_Profile.h"
66 #include "Soldier_Tile.h"
67 #include "SoundMan.h"
68 #include "Sound_Control.h"
69 #include "Squads.h"
70 #include "Strategic.h"
71 #include "StrategicMap.h"
72 #include "Strategic_Merc_Handler.h"
73 #include "Strategic_Status.h"
74 #include "Structure.h"
75 #include "Structure_Wrap.h"
76 #include "Text.h"
77 #include "TileDef.h"
78 #include "Tile_Animation.h"
79 #include "Timer_Control.h"
80 #include "Utilities.h"
81 #include "VObject.h"
82 #include "Vehicles.h"
83 #include "WCheck.h"
84 #include "WeaponModels.h"
85 #include "Weapons.h"
86 #include "WorldMan.h"
87 #include "enums.h"
88 #include <cmath>
89 #include <stdexcept>
90 #include <string_theory/string>
91
92 #define PALETTEFILENAME BINARYDATADIR "/ja2pal.dat"
93
94 #define LOW_MORALE_BATTLE_SND_THREASHOLD 35
95
96
97 #define TURNING_FROM_PRONE_OFF 0
98 #define TURNING_FROM_PRONE_ON 1
99 #define TURNING_FROM_PRONE_START_UP_FROM_MOVE 2
100 #define TURNING_FROM_PRONE_ENDING_UP_FROM_MOVE 3
101
102 #define MIN_SUBSEQUENT_SNDS_DELAY 2000
103
104 // Enumerate extended directions
105 enum
106 {
107 EX_NORTH = 0,
108 EX_NORTHEAST = 4,
109 EX_EAST = 8,
110 EX_SOUTHEAST = 12,
111 EX_SOUTH = 16,
112 EX_SOUTHWEST = 20,
113 EX_WEST = 24,
114 EX_NORTHWEST = 28,
115 EX_NUM_WORLD_DIRECTIONS = 32,
116 EX_DIRECTION_IRRELEVANT
117 };
118
119 static void SetSoldierPersonalLightLevel(SOLDIERTYPE*);
120
Dir2ExtDir(const UINT8 dir)121 static UINT8 Dir2ExtDir(const UINT8 dir)
122 {
123 return dir * 4;
124 }
125
126
ExtOneCDirection(const UINT8 exdir)127 static UINT8 ExtOneCDirection(const UINT8 exdir)
128 {
129 return (exdir + 4) % EX_NUM_WORLD_DIRECTIONS;
130 }
131
132
133 struct BATTLESNDS_STRUCT
134 {
135 CHAR8 zName[20];
136 UINT8 ubRandomVal;
137 BOOLEAN fBadGuy;
138 BOOLEAN fDontAllowTwoInRow;
139 BOOLEAN fStopDialogue;
140 };
141
142
143 static const BATTLESNDS_STRUCT gBattleSndsData[] =
144 {
145 { "ok1", 2, 1, 1, 2 },
146 { "ok2", 0, 1, 1, 2 },
147 { "cool", 0, 0, 1, 0 },
148 { "curse", 0, 1, 1, 0 },
149 { "hit1", 2, 1, 1, 1 },
150 { "hit2", 0, 1, 1, 1 },
151 { "laugh", 0, 1, 1, 0 },
152 { "attn", 0, 0, 1, 0 },
153 { "die", 0, 1, 1, 1 },
154 { "humm", 0, 0, 1, 1 },
155 { "noth", 0, 0, 1, 1 },
156 { "gotit", 0, 0, 1, 1 },
157 { "lmok1", 2, 0, 1, 2 },
158 { "lmok2", 0, 0, 1, 2 },
159 { "lmattn", 0, 0, 1, 0 },
160 { "locked", 0, 0, 1, 0 },
161 { "enem", 0, 1, 1, 0 }
162 };
163
164
165 UINT8 bHealthStrRanges[] =
166 {
167 15,
168 30,
169 45,
170 60,
171 75,
172 90,
173 101
174 };
175
176
177 static const INT16 gsTerrainTypeSpeedModifiers[] =
178 {
179 5, // Flat ground
180 5, // Floor
181 5, // Paved road
182 5, // Dirt road
183 10, // LOW GRASS
184 15, // HIGH GRASS
185 20, // TRAIN TRACKS
186 20, // LOW WATER
187 25, // MID WATER
188 30 // DEEP WATER
189 };
190
191
192 struct PaletteSubRangeType
193 {
194 UINT8 ubStart;
195 UINT8 ubEnd;
196 };
197
198
199 // Palette ranges
200 static UINT32 guiNumPaletteSubRanges;
201 static PaletteSubRangeType* gpPaletteSubRanges;
202 // Palette replacements
203 static UINT32 guiNumReplacements;
204
205 BOOLEAN gfGetNewPathThroughPeople = FALSE;
206
207
208 UINT8* gubpNumReplacementsPerRange;
209 PaletteReplacementType* gpPalRep;
210
211
AdjustNoAPToFinishMove(SOLDIERTYPE * pSoldier,BOOLEAN fSet)212 void AdjustNoAPToFinishMove( SOLDIERTYPE *pSoldier, BOOLEAN fSet )
213 {
214 if ( pSoldier->ubBodyType == CROW )
215 {
216 return;
217 }
218
219 // Check if we are a vehicle first....
220 if ( pSoldier->uiStatusFlags & SOLDIER_VEHICLE )
221 {
222 // Turn off sound effects....
223 if ( fSet )
224 {
225 HandleVehicleMovementSound( pSoldier, FALSE );
226 }
227 }
228
229 pSoldier->fNoAPToFinishMove = fSet;
230
231 if ( !fSet )
232 {
233 // return reason to default value
234 pSoldier->ubReasonCantFinishMove = REASON_STOPPED_NO_APS;
235 }
236 }
237
238
IsCrowWithShadow(SOLDIERTYPE const & s)239 static bool IsCrowWithShadow(SOLDIERTYPE const& s)
240 {
241 return s.ubBodyType == CROW &&
242 s.usAnimState == CROW_FLY &&
243 s.pAniTile;
244 }
245
246
HandleCrowShadowVisibility(SOLDIERTYPE & s)247 void HandleCrowShadowVisibility(SOLDIERTYPE& s)
248 {
249 if (!IsCrowWithShadow(s)) return;
250 HideAniTile(s.pAniTile, s.bLastRenderVisibleValue == -1);
251 }
252
253
HandleCrowShadowNewGridNo(SOLDIERTYPE & s)254 static void HandleCrowShadowNewGridNo(SOLDIERTYPE& s)
255 {
256 if (s.ubBodyType != CROW) return;
257
258 if (s.pAniTile)
259 {
260 DeleteAniTile(s.pAniTile);
261 s.pAniTile = 0;
262 }
263
264 if (s.sGridNo == NOWHERE) return;
265 if (s.usAnimState != CROW_FLY) return;
266
267 ANITILE_PARAMS a;
268 a = ANITILE_PARAMS{};
269 a.sGridNo = s.sGridNo;
270 a.ubLevelID = ANI_SHADOW_LEVEL;
271 a.sDelay = s.sAniDelay;
272 a.sStartFrame = 0;
273 a.uiFlags = ANITILE_FORWARD | ANITILE_LOOPING | ANITILE_USE_DIRECTION_FOR_START_FRAME;
274 a.sX = s.sX;
275 a.sY = s.sY;
276 a.sZ = 0;
277 a.zCachedFile = TILECACHEDIR "/fly_shdw.sti";
278 a.v.user.uiData3 = s.bDirection;
279 s.pAniTile = CreateAnimationTile(&a);
280
281 HandleCrowShadowVisibility(s);
282 }
283
284
HandleCrowShadowRemoveGridNo(SOLDIERTYPE & s)285 static void HandleCrowShadowRemoveGridNo(SOLDIERTYPE& s)
286 {
287 if (!IsCrowWithShadow(s)) return;
288 DeleteAniTile(s.pAniTile);
289 s.pAniTile = 0;
290 }
291
292
HandleCrowShadowNewDirection(SOLDIERTYPE * const s)293 static void HandleCrowShadowNewDirection(SOLDIERTYPE* const s)
294 {
295 if (!IsCrowWithShadow(*s)) return;
296 s->pAniTile->v.user.uiData3 = s->bDirection;
297 }
298
299
HandleCrowShadowNewPosition(SOLDIERTYPE * const s)300 static void HandleCrowShadowNewPosition(SOLDIERTYPE* const s)
301 {
302 if (!IsCrowWithShadow(*s)) return;
303 s->pAniTile->sRelativeX = s->sX;
304 s->pAniTile->sRelativeY = s->sY;
305 }
306
307
308 static const UINT8 gubMaxActionPoints[] =
309 {
310 AP_MAXIMUM, // REGMALE
311 AP_MAXIMUM, // BIGMALE
312 AP_MAXIMUM, // STOCKYMALE
313 AP_MAXIMUM, // REGFEMALE
314 AP_MONSTER_MAXIMUM, // ADULTMONSTER
315 AP_MONSTER_MAXIMUM, // ADULTMONSTER
316 AP_MONSTER_MAXIMUM, // ADULTMONSTER
317 AP_MONSTER_MAXIMUM, // ADULTMONSTER
318 AP_MONSTER_MAXIMUM, // ADULTMONSTER
319 AP_MONSTER_MAXIMUM, // INFANT
320 AP_MONSTER_MAXIMUM, // QUEEN MONSTER
321 AP_MAXIMUM, // FATCIV
322 AP_MAXIMUM, // MANCIV
323 AP_MAXIMUM, // MINICIV
324 AP_MAXIMUM, // DRESSCIV
325 AP_MAXIMUM, // HAT KID
326 AP_MAXIMUM, // NOHAT KID
327 AP_MAXIMUM, // CRIPPLE
328 AP_MAXIMUM, // COW
329 AP_MAXIMUM, // CROW
330 AP_MAXIMUM, // BLOOD CAT
331 AP_MAXIMUM, // ROBOT1
332 AP_VEHICLE_MAXIMUM, // HUMVEE
333 AP_VEHICLE_MAXIMUM, // TANK1
334 AP_VEHICLE_MAXIMUM, // TANK2
335 AP_VEHICLE_MAXIMUM, // ELDORADO
336 AP_VEHICLE_MAXIMUM, // ICECREAMTRUCK
337 AP_VEHICLE_MAXIMUM // JEEP
338 };
339
340
CalcActionPoints(const SOLDIERTYPE * const pSold)341 INT8 CalcActionPoints(const SOLDIERTYPE* const pSold)
342 {
343 UINT8 ubPoints,ubMaxAPs;
344 INT8 bBandage;
345
346 // dead guys don't get any APs (they shouldn't be here asking for them!)
347 if (!pSold->bLife)
348 return(0);
349
350 // people with sleep dart drug who have collapsed get no APs
351 if ( (pSold->bSleepDrugCounter > 0) && pSold->bCollapsed )
352 return( 0 );
353
354 // Calculate merc's action points at 100% capability (range is 10 - 25)
355 // round fractions of .5 up (that's why the +20 before the division!
356 ubPoints = 5 + (((10 * EffectiveExpLevel( pSold ) +
357 3 * EffectiveAgility( pSold ) +
358 2 * pSold->bLifeMax +
359 2 * EffectiveDexterity( pSold ) ) + 20) / 40);
360
361 // Calculate bandage
362 bBandage = pSold->bLifeMax - pSold->bLife - pSold->bBleeding;
363
364 // If injured, reduce action points accordingly (by up to 2/3rds)
365 if (pSold->bLife < pSold->bLifeMax)
366 {
367 ubPoints -= (2 * ubPoints * (pSold->bLifeMax - pSold->bLife + (bBandage / 2))) /
368 (3 * pSold->bLifeMax);
369 }
370
371 // If tired, reduce action points accordingly (by up to 1/2)
372 if (pSold->bBreath < 100)
373 ubPoints -= (ubPoints * (100 - pSold->bBreath)) / 200;
374
375 if (pSold->sWeightCarriedAtTurnStart > 100)
376 {
377 ubPoints = (UINT8) ( ((UINT32)ubPoints) * 100 / pSold->sWeightCarriedAtTurnStart );
378 }
379
380 // If resulting APs are below our permitted minimum, raise them to it!
381 if (ubPoints < AP_MINIMUM)
382 ubPoints = AP_MINIMUM;
383
384 // make sure action points doesn't exceed the permitted maximum
385 ubMaxAPs = gubMaxActionPoints[ pSold->ubBodyType ];
386
387 // If resulting APs are below our permitted minimum, raise them to it!
388 if (ubPoints > ubMaxAPs)
389 ubPoints = ubMaxAPs;
390
391 if ( pSold->ubBodyType == BLOODCAT )
392 {
393 // use same as young monsters
394 ubPoints = (ubPoints * AP_YOUNG_MONST_FACTOR) / 10;
395 }
396 else if (pSold->uiStatusFlags & SOLDIER_MONSTER)
397 {
398 // young monsters get extra APs
399 if ( pSold->ubBodyType == YAF_MONSTER || pSold->ubBodyType == YAM_MONSTER ||
400 pSold->ubBodyType == INFANT_MONSTER )
401 {
402 ubPoints = (ubPoints * AP_YOUNG_MONST_FACTOR) / 10;
403 }
404
405 // if frenzied, female monsters get more APs! (for young females, cumulative!)
406 if (pSold->bFrenzied)
407 {
408
409 ubPoints = (ubPoints * AP_MONST_FRENZY_FACTOR) / 10;
410 }
411 }
412
413 // adjust APs for phobia situations
414 if ( pSold->ubProfile != NO_PROFILE )
415 {
416 if ((gMercProfiles[ pSold->ubProfile ].bPersonalityTrait == CLAUSTROPHOBIC) &&
417 (gbWorldSectorZ > 0))
418 {
419 ubPoints = (ubPoints * AP_CLAUSTROPHOBE) / 10;
420 }
421 else if (gMercProfiles[pSold->ubProfile].bPersonalityTrait == FEAR_OF_INSECTS &&
422 MercSeesCreature(*pSold))
423 {
424 ubPoints = (ubPoints * AP_AFRAID_OF_INSECTS) / 10;
425 }
426 }
427
428 // Adjusat APs due to drugs...
429 HandleAPEffectDueToDrugs( pSold, &ubPoints );
430
431 // If we are a vehicle, adjust APS...
432 if (pSold->uiStatusFlags & SOLDIER_VEHICLE) ubPoints += 35;
433
434 // if we are in boxing mode, adjust APs... THIS MUST BE LAST!
435 if ( gTacticalStatus.bBoxingState == BOXING || gTacticalStatus.bBoxingState == PRE_BOXING )
436 {
437 ubPoints /= 2;
438 }
439
440 return (ubPoints);
441 }
442
CalcNewActionPoints(SOLDIERTYPE * pSoldier)443 void CalcNewActionPoints( SOLDIERTYPE *pSoldier )
444 {
445 if ( gTacticalStatus.bBoxingState == BOXING || gTacticalStatus.bBoxingState == PRE_BOXING )
446 {
447 // if we are in boxing mode, carry 1/2 as many points
448 if (pSoldier->bActionPoints > MAX_AP_CARRIED / 2)
449 {
450 pSoldier->bActionPoints = MAX_AP_CARRIED / 2;
451 }
452 }
453 else
454 {
455 if (pSoldier->bActionPoints > MAX_AP_CARRIED)
456 {
457 pSoldier->bActionPoints = MAX_AP_CARRIED;
458 }
459 }
460
461 pSoldier->bActionPoints += CalcActionPoints( pSoldier);
462
463 // Don't max out if we are drugged....
464 if ( !GetDrugEffect( pSoldier, DRUG_TYPE_ADRENALINE ) )
465 {
466 pSoldier->bActionPoints = __min( pSoldier->bActionPoints, gubMaxActionPoints[ pSoldier->ubBodyType ] );
467 }
468
469 pSoldier->bInitialActionPoints = pSoldier->bActionPoints;
470 }
471
472
DoNinjaAttack(SOLDIERTYPE * pSoldier)473 void DoNinjaAttack( SOLDIERTYPE *pSoldier )
474 {
475 //UINT32 uiMercFlags;
476 UINT8 ubTDirection;
477 UINT8 ubTargetStance;
478
479 const SOLDIERTYPE* const pTSoldier = WhoIsThere2(pSoldier->sTargetGridNo, pSoldier->bLevel);
480 if (pTSoldier != NULL)
481 {
482 // Look at stance of target
483 ubTargetStance = gAnimControl[ pTSoldier->usAnimState ].ubEndHeight;
484
485 // Get his life...if < certain value, do finish!
486 if ( (pTSoldier->bLife <= 30 || pTSoldier->bBreath <= 30) && ubTargetStance != ANIM_PRONE )
487 {
488 // Do finish!
489 ChangeSoldierState( pSoldier, NINJA_SPINKICK, 0 , FALSE );
490 }
491 else
492 {
493 if ( ubTargetStance != ANIM_PRONE )
494 {
495 const UINT16 state = (Random(2) == 0 ? NINJA_LOWKICK : NINJA_PUNCH);
496 ChangeSoldierState(pSoldier, state, 0, FALSE);
497
498 // CHECK IF HE CAN SEE US, IF SO CHANGE DIRECTION
499 if ( pTSoldier->bOppList[ pSoldier->ubID ] == 0 && pTSoldier->bTeam != pSoldier->bTeam )
500 {
501 if ( !( pTSoldier->uiStatusFlags & ( SOLDIER_MONSTER | SOLDIER_ANIMAL | SOLDIER_VEHICLE ) ) )
502 {
503 ubTDirection = (UINT8)GetDirectionFromGridNo( pSoldier->sGridNo, pTSoldier );
504 SendSoldierSetDesiredDirectionEvent( pTSoldier, ubTDirection );
505 }
506 }
507 }
508 else
509 {
510 // CHECK OUR STANCE
511 if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight != ANIM_CROUCH )
512 {
513 // SET DESIRED STANCE AND SET PENDING ANIMATION
514 ChangeSoldierStance(pSoldier, ANIM_CROUCH);
515 pSoldier->usPendingAnimation = PUNCH_LOW;
516 }
517 else
518 {
519 // USE crouched one
520 // NEED TO CHANGE STANCE IF NOT CROUCHD!
521 EVENT_InitNewSoldierAnim( pSoldier, PUNCH_LOW, 0 , FALSE );
522 }
523 }
524 }
525 }
526
527 if (pSoldier->ubProfile == DR_Q)
528 {
529 // Play sound!
530
531 UINT32 volume = CalculateSpeechVolume(HIGHVOLUME);
532
533 // If we are an enemy.....reduce due to volume
534 if ( pSoldier->bTeam != OUR_TEAM )
535 {
536 volume = SoundVolume(volume, pSoldier->sGridNo);
537 }
538
539 const UINT32 pan = SoundDir(pSoldier->sGridNo);
540
541 const char* filename;
542 if ( pSoldier->usAnimState == NINJA_SPINKICK )
543 {
544 filename = BATTLESNDSDIR "/033_chop2.wav";
545 }
546 else
547 {
548 if ( Random( 2 ) == 0 )
549 {
550 filename = BATTLESNDSDIR "/033_chop3.wav";
551 }
552 else
553 {
554 filename = BATTLESNDSDIR "/033_chop1.wav";
555 }
556 }
557 const UINT32 uiSoundID = SoundPlay(filename, volume, pan, 1, NULL, NULL);
558
559 if ( uiSoundID != SOUND_ERROR )
560 {
561 pSoldier->uiBattleSoundID = uiSoundID;
562
563 if ( pSoldier->ubProfile != NO_PROFILE )
564 {
565 FACETYPE* const face = pSoldier->face;
566 if (face != NULL) ExternSetFaceTalking(*face, uiSoundID);
567 }
568 }
569 }
570 }
571
572
CreateSoldierCommon(SOLDIERTYPE & s)573 void CreateSoldierCommon(SOLDIERTYPE& s)
574 try
575 {
576 //if we are loading a saved game, we DO NOT want to reset the opplist,
577 //look for enemies, or say a dying commnet
578 if (!(gTacticalStatus.uiFlags & LOADING_SAVED_GAME))
579 {
580 // Set initial values for opplist!
581 InitSoldierOppList(s);
582 HandleSight(s, SIGHT_LOOK);
583
584 // Set some quote flags
585 s.fDyingComment = s.bLife < OKLIFE;
586 }
587
588 // ATE: Reset some timer flags...
589 s.uiTimeSameBattleSndDone = 0;
590 // ATE: Reset every time.....
591 s.fSoldierWasMoving = TRUE;
592 s.uiTuringSoundID = NO_SAMPLE;
593 s.uiTimeSinceLastBleedGrunt = 0;
594
595 if (s.ubBodyType == QUEENMONSTER)
596 {
597 s.iPositionSndID = NewPositionSnd(NOWHERE, &s, QUEEN_AMBIENT_NOISE);
598 }
599
600 // ANYTHING AFTER HERE CAN FAIL
601 if (IsOnOurTeam(s))
602 {
603 s.pKeyRing = new KEY_ON_RING[NUM_KEYS]{};
604 for (UINT32 i = 0; i < NUM_KEYS; ++i)
605 {
606 s.pKeyRing[i].ubKeyID = INVALID_KEY_NUMBER;
607 }
608 }
609 else
610 {
611 s.pKeyRing = NULL;
612 }
613
614 // Create frame cache
615 InitAnimationCache(s.ubID, &s.AnimCache);
616
617 UINT16 ani_state = s.usAnimState;
618 if (!(gTacticalStatus.uiFlags & LOADING_SAVED_GAME))
619 {
620 // Init new soldier state
621 // OFFSET FIRST ANIMATION FRAME FOR NEW MERCS
622 EVENT_InitNewSoldierAnim(&s, ani_state, ani_state == STANDING ? Random(10) : 0, TRUE);
623 }
624 else
625 {
626 /// if we don't have a world loaded, and are in a bad anim, goto standing.
627 UINT16 ani_code = s.usAniCode;
628 if (!gfWorldLoaded)
629 {
630 switch (ani_state)
631 {
632 case HOPFENCE:
633 case CLIMBDOWNROOF:
634 case FALLFORWARD_ROOF:
635 case FALLOFF:
636 case CLIMBUPROOF:
637 ani_state = STANDING;
638 ani_code = 0;
639 break;
640 }
641 }
642 EVENT_InitNewSoldierAnim(&s, ani_state, ani_code, TRUE);
643 }
644
645 CreateSoldierPalettes(s);
646 }
647 catch (...)
648 {
649 DeleteSoldier(s);
650 throw;
651 }
652
653
DeleteSoldier(SOLDIERTYPE & s)654 void DeleteSoldier(SOLDIERTYPE& s)
655 {
656 if (s.sGridNo != NOWHERE)
657 {
658 // Remove adjacency records
659 for (INT8 bDir = 0; bDir < NUM_WORLD_DIRECTIONS; ++bDir)
660 {
661 INT32 const iGridNo = s.sGridNo + DirIncrementer[bDir];
662 if (0 <= iGridNo && iGridNo < WORLD_MAX)
663 {
664 --gpWorldLevelData[iGridNo].ubAdjacentSoldierCnt;
665 }
666 }
667 }
668
669 if (s.pKeyRing)
670 {
671 delete[] s.pKeyRing;
672 s.pKeyRing = 0;
673 }
674
675 DeleteSoldierFace(&s);
676
677 FOR_EACH(UINT16*, i, s.pShades)
678 {
679 if (*i == NULL) continue;
680 delete[] *i;
681 *i = NULL;
682 }
683
684 if (s.effect_shade)
685 {
686 delete[] s.effect_shade;
687 s.effect_shade = 0;
688 }
689
690 FOR_EACH(UINT16*, i, s.pGlowShades)
691 {
692 if (*i == NULL) continue;
693 delete[] *i;
694 *i = NULL;
695 }
696
697 if (s.ubBodyType == QUEENMONSTER)
698 {
699 DeletePositionSnd(s.iPositionSndID);
700 }
701
702 // Free any animations we may have locked...
703 UnLoadCachedAnimationSurfaces(s.ubID, &s.AnimCache);
704 DeleteAnimationCache(s.ubID, &s.AnimCache);
705
706 DeleteSoldierLight(&s);
707 UnMarkMovementReserved(s);
708 if (!RemoveMercSlot(&s)) RemoveAwaySlot(&s);
709
710 s.bActive = FALSE;
711 }
712
713
CreateSoldierLight(SOLDIERTYPE * pSoldier)714 static BOOLEAN CreateSoldierLight(SOLDIERTYPE* pSoldier)
715 {
716 if ( pSoldier->bTeam != OUR_TEAM )
717 {
718 return( FALSE );
719 }
720
721 // DO ONLY IF WE'RE AT A GOOD LEVEL
722 if (pSoldier->light == NULL)
723 {
724 // ATE: Check for goggles in headpos....
725 char const* light_file = IsWearingHeadGear(*pSoldier, UVGOGGLES) ? "Light4" :
726 IsWearingHeadGear(*pSoldier, NIGHTGOGGLES) ? "Light3" :
727 "Light2";
728
729 LIGHT_SPRITE* const l = LightSpriteCreate(light_file);
730 pSoldier->light = l;
731 if (l == NULL)
732 {
733 SLOGD("Soldier: Failed loading light");
734 return FALSE;
735 }
736
737 l->uiFlags |= MERC_LIGHT;
738
739 if (pSoldier->bLevel != 0) LightSpriteRoofStatus(l, TRUE);
740 }
741
742 return( TRUE );
743 }
744
745
ReCreateSoldierLight(SOLDIERTYPE * const s)746 void ReCreateSoldierLight(SOLDIERTYPE* const s)
747 {
748 if (s->bTeam != OUR_TEAM) return;
749 if (!s->bActive) return;
750 if (!s->bInSector) return;
751
752 DeleteSoldierLight(s);
753 CreateSoldierLight(s);
754 }
755
756
DeleteSoldierLight(SOLDIERTYPE * const s)757 void DeleteSoldierLight(SOLDIERTYPE* const s)
758 {
759 if (s->light == NULL) return;
760
761 LightSpriteDestroy(s->light);
762 s->light = NULL;
763 }
764
765
766 // FUNCTIONS CALLED BY EVENT PUMP
767 /////////////////////////////////
768
ChangeSoldierState(SOLDIERTYPE * pSoldier,UINT16 usNewState,UINT16 usStartingAniCode,BOOLEAN fForce)769 void ChangeSoldierState(SOLDIERTYPE* pSoldier, UINT16 usNewState, UINT16 usStartingAniCode, BOOLEAN fForce)
770 {
771 EVENT_InitNewSoldierAnim(pSoldier, usNewState, usStartingAniCode, fForce);
772 }
773
774
775 // This function reevaluates the stance if the guy sees us!
ReevaluateEnemyStance(SOLDIERTYPE * pSoldier,UINT16 usAnimState)776 BOOLEAN ReevaluateEnemyStance( SOLDIERTYPE *pSoldier, UINT16 usAnimState )
777 {
778 // make the chosen one not turn to face us
779 if (OK_ENEMY_MERC(pSoldier) &&
780 pSoldier != gTacticalStatus.the_chosen_one &&
781 gAnimControl[usAnimState].ubEndHeight == ANIM_STAND &&
782 !(pSoldier->uiStatusFlags & SOLDIER_UNDERAICONTROL))
783 {
784 if ( pSoldier->fTurningFromPronePosition == TURNING_FROM_PRONE_OFF )
785 {
786 // If we are a queen and see enemies, goto ready
787 if ( pSoldier->ubBodyType == QUEENMONSTER )
788 {
789 if ( gAnimControl[ usAnimState ].uiFlags & ( ANIM_BREATH ) )
790 {
791 if ( pSoldier->bOppCnt > 0 )
792 {
793 EVENT_InitNewSoldierAnim( pSoldier, QUEEN_INTO_READY, 0 , TRUE );
794 return( TRUE );
795 }
796 }
797 }
798
799 // ATE: Don't do this if we're not a merc.....
800 if ( !IS_MERC_BODY_TYPE( pSoldier ) )
801 {
802 return( FALSE );
803 }
804
805 if ( gAnimControl[ usAnimState ].uiFlags & ( ANIM_MERCIDLE | ANIM_BREATH ) )
806 {
807 GridNo closest = NOWHERE;
808 if (pSoldier->bOppCnt > 0)
809 {
810 // Pick a guy this buddy sees and turn towards them!
811 INT16 sClosestDist = 10000;
812 CFOR_EACH_IN_TEAM(opp, OUR_TEAM)
813 {
814 if (pSoldier->bOppList[opp->ubID] == SEEN_CURRENTLY)
815 {
816 const GridNo gridno = opp->sGridNo;
817 const INT16 sDist = PythSpacesAway(pSoldier->sGridNo, gridno);
818 if (sDist < sClosestDist)
819 {
820 sClosestDist = sDist;
821 closest = gridno;
822 }
823 }
824 }
825
826 if (closest != NOWHERE)
827 {
828 // Change to fire ready animation
829 pSoldier->fDontChargeReadyAPs = TRUE;
830 return SoldierReadyWeapon(pSoldier, closest, FALSE);
831 }
832 }
833 }
834 }
835 }
836 return( FALSE );
837
838 }
839
840
CheckForFreeupFromHit(SOLDIERTYPE * pSoldier,UINT32 uiOldAnimFlags,UINT32 uiNewAnimFlags,UINT16 usOldAniState,UINT16 usNewState)841 static void CheckForFreeupFromHit(SOLDIERTYPE* pSoldier, UINT32 uiOldAnimFlags, UINT32 uiNewAnimFlags, UINT16 usOldAniState, UINT16 usNewState)
842 {
843 // THIS COULD POTENTIALLY CALL EVENT_INITNEWAnim() if the GUY was SUPPRESSED
844 // CHECK IF THE OLD ANIMATION WAS A HIT START THAT WAS NOT FOLLOWED BY A HIT FINISH
845 // IF SO, RELEASE ATTACKER FROM ATTACKING
846
847 // If old and new animations are the same, do nothing!
848 if ( usOldAniState == QUEEN_HIT && usNewState == QUEEN_HIT )
849 {
850 return;
851 }
852
853 if ( usOldAniState != usNewState && ( uiOldAnimFlags & ANIM_HITSTART ) && !( uiNewAnimFlags & ANIM_HITFINISH ) && !( uiNewAnimFlags & ANIM_IGNOREHITFINISH ) && !(pSoldier->uiStatusFlags & SOLDIER_TURNINGFROMHIT ) )
854 {
855 // Release attacker
856 SLOGD("Releasesoldierattacker, normal hit animation ended\n\
857 NEW: %s ( %d ) OLD: %s ( %d )",
858 gAnimControl[usNewState].zAnimStr, usNewState,
859 gAnimControl[usOldAniState].zAnimStr, pSoldier->usOldAniState);
860 ReleaseSoldiersAttacker( pSoldier );
861
862 //FREEUP GETTING HIT FLAG
863 pSoldier->fGettingHit = FALSE;
864
865 // ATE: if our guy, have 10% change of say damn, if still conscious...
866 if ( pSoldier->bTeam == OUR_TEAM && pSoldier->bLife >= OKLIFE )
867 {
868 if ( Random( 10 ) == 0 )
869 {
870 DoMercBattleSound(pSoldier, BATTLE_SOUND_CURSE1);
871 }
872 }
873 }
874
875 // CHECK IF WE HAVE FINSIHED A HIT WHILE DOWN
876 // OBLY DO THIS IF 1 ) We are dead already or 2 ) We are alive still
877 if ((uiOldAnimFlags & ANIM_HITWHENDOWN) && ((pSoldier->uiStatusFlags & SOLDIER_DEAD)
878 || pSoldier->bLife != 0))
879 {
880 // Release attacker
881 SLOGD(
882 "Releasesoldierattacker, animation of kill on the ground ended");
883 ReleaseSoldiersAttacker( pSoldier );
884
885 //FREEUP GETTING HIT FLAG
886 pSoldier->fGettingHit = FALSE;
887
888 if ( pSoldier->bLife == 0 )
889 {
890 //ATE: Set previous attacker's value!
891 // This is so that the killer can say their killed quote....
892 pSoldier->attacker = pSoldier->previous_attacker;
893 }
894 }
895 }
896
897
IsRifle(UINT16 const item_id)898 static bool IsRifle(UINT16 const item_id)
899 {
900 return item_id != NOTHING &&
901 item_id != ROCKET_LAUNCHER &&
902 GCM->getItem(item_id)->getItemClass() == IC_GUN &&
903 GCM->getItem(item_id)->isTwoHanded();
904 }
905
906
907 static void HandleAnimationProfile(SOLDIERTYPE&, UINT16 usAnimState, BOOLEAN fRemove);
908 static void SetSoldierLocatorOffsets(SOLDIERTYPE* pSoldier);
909
910
EVENT_InitNewSoldierAnim(SOLDIERTYPE * const pSoldier,UINT16 usNewState,UINT16 const usStartingAniCode,BOOLEAN const fForce)911 void EVENT_InitNewSoldierAnim(SOLDIERTYPE* const pSoldier, UINT16 usNewState, UINT16 const usStartingAniCode, BOOLEAN const fForce)
912 {
913 INT16 sAPCost = 0;
914 INT16 sBPCost = 0;
915 UINT32 uiOldAnimFlags;
916 UINT32 uiNewAnimFlags;
917 BOOLEAN fTryingToRestart = FALSE;
918
919 CHECKV(usNewState < NUMANIMATIONSTATES);
920
921 SoldierSP soldier = GetSoldier(pSoldier);
922
923 ///////////////////////////////////////////////////////////////////////
924 // DO SOME CHECKS ON OUR NEW ANIMATION!
925 /////////////////////////////////////////////////////////////////////
926
927 // If we are NOT loading a game, continue normally
928 if( !(gTacticalStatus.uiFlags & LOADING_SAVED_GAME ) )
929 {
930 // CHECK IF WE ARE TRYING TO INTURRUPT A SCRIPT WHICH WE DO NOT WANT INTERRUPTED!
931 if ( pSoldier->fInNonintAnim )
932 {
933 return;
934 }
935
936 if ( pSoldier->fRTInNonintAnim )
937 {
938 if ( !(gTacticalStatus.uiFlags & INCOMBAT) )
939 {
940 return;
941 }
942 else
943 {
944 pSoldier->fRTInNonintAnim = FALSE;
945 }
946 }
947
948
949 // Check if we can restart this animation if it's the same as our current!
950 if ( usNewState == pSoldier->usAnimState )
951 {
952 if ( ( gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_NORESTART ) && !fForce )
953 {
954 fTryingToRestart = TRUE;
955 }
956 }
957
958 // Check state, if we are not at the same height, set this ani as the pending one and
959 // change stance accordingly
960 // ATE: ONLY IF WE ARE STARTING AT START OF ANIMATION!
961 if ( usStartingAniCode == 0 )
962 {
963 if (gAnimControl[usNewState].ubHeight != gAnimControl[pSoldier->usAnimState].ubEndHeight &&
964 !(gAnimControl[usNewState].uiFlags & (ANIM_STANCECHANGEANIM | ANIM_IGNORE_AUTOSTANCE)))
965 {
966
967 // Check if we are going from crouched height to prone height, and adjust fast turning
968 // accordingly. Make guy turn while crouched THEN go into prone
969 if ((gAnimControl[usNewState].ubEndHeight == ANIM_PRONE &&
970 gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_CROUCH ) &&
971 !(gTacticalStatus.uiFlags & INCOMBAT))
972 {
973 pSoldier->fTurningUntilDone = TRUE;
974 pSoldier->ubPendingStanceChange = gAnimControl[ usNewState ].ubEndHeight;
975 pSoldier->usPendingAnimation = usNewState;
976 return;
977 }
978 // Check if we are in realtime and we are going from stand to crouch
979 else if (gAnimControl[usNewState].ubEndHeight == ANIM_CROUCH &&
980 gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_STAND &&
981 (gAnimControl[pSoldier->usAnimState].uiFlags & ANIM_MOVING) &&
982 !(gTacticalStatus.uiFlags & INCOMBAT))
983 {
984 pSoldier->ubDesiredHeight = gAnimControl[ usNewState ].ubEndHeight;
985 // Continue with this course of action IE: Do animation
986 // and skip from stand to crouch
987 }
988 // Check if we are in realtime and we are going from crouch to stand
989 else if (gAnimControl[usNewState].ubEndHeight == ANIM_STAND &&
990 gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_CROUCH &&
991 (gAnimControl[pSoldier->usAnimState].uiFlags & ANIM_MOVING) &&
992 !(gTacticalStatus.uiFlags & INCOMBAT) &&
993 pSoldier->usAnimState != HELIDROP)
994 {
995 pSoldier->ubDesiredHeight = gAnimControl[ usNewState ].ubEndHeight;
996 // Continue with this course of action IE: Do animation and
997 // skip from stand to crouch
998 }
999 else
1000 {
1001 // Set our next moving animation to be pending, after
1002 pSoldier->usPendingAnimation = usNewState;
1003 // Set new state to be animation to move to new stance
1004 ChangeSoldierStance(pSoldier, gAnimControl[usNewState].ubHeight);
1005 return;
1006 }
1007 }
1008 }
1009
1010 if ( usNewState == ADJACENT_GET_ITEM )
1011 {
1012 if ( pSoldier->ubPendingDirection != NO_PENDING_DIRECTION )
1013 {
1014 EVENT_SetSoldierDesiredDirectionForward(pSoldier, pSoldier->ubPendingDirection);
1015 pSoldier->ubPendingDirection = NO_PENDING_DIRECTION;
1016 pSoldier->usPendingAnimation = ADJACENT_GET_ITEM;
1017 pSoldier->fTurningUntilDone = TRUE;
1018 SoldierGotoStationaryStance( pSoldier );
1019 return;
1020 }
1021 }
1022
1023
1024 if ( usNewState == CLIMBUPROOF )
1025 {
1026 if ( pSoldier->ubPendingDirection != NO_PENDING_DIRECTION )
1027 {
1028 EVENT_SetSoldierDesiredDirectionForward(pSoldier, pSoldier->ubPendingDirection);
1029 pSoldier->ubPendingDirection = NO_PENDING_DIRECTION;
1030 pSoldier->usPendingAnimation = CLIMBUPROOF;
1031 pSoldier->fTurningUntilDone = TRUE;
1032 SoldierGotoStationaryStance( pSoldier );
1033 return;
1034 }
1035 }
1036
1037 if ( usNewState == CLIMBDOWNROOF )
1038 {
1039 if ( pSoldier->ubPendingDirection != NO_PENDING_DIRECTION )
1040 {
1041 EVENT_SetSoldierDesiredDirectionForward(pSoldier, pSoldier->ubPendingDirection);
1042 pSoldier->ubPendingDirection = NO_PENDING_DIRECTION;
1043 pSoldier->usPendingAnimation = CLIMBDOWNROOF;
1044 pSoldier->fTurningFromPronePosition = FALSE;
1045 pSoldier->fTurningUntilDone = TRUE;
1046 SoldierGotoStationaryStance( pSoldier );
1047 return;
1048 }
1049 }
1050
1051 // ATE: Don't raise/lower automatically if we are low on health,
1052 // as our gun looks lowered anyway....
1053 //if ( pSoldier->bLife > INJURED_CHANGE_THREASHOLD )
1054 {
1055 // Don't do some of this if we are a monster!
1056 // ATE: LOWER AIMATION IS GOOD, RAISE ONE HOWEVER MAY CAUSE PROBLEMS FOR AI....
1057 if (!(pSoldier->uiStatusFlags & SOLDIER_MONSTER) &&
1058 pSoldier->ubBodyType != ROBOTNOWEAPON && pSoldier->bTeam == OUR_TEAM)
1059 {
1060 // If this animation is a raise_weapon animation
1061 if ((gAnimControl[usNewState].uiFlags & ANIM_RAISE_WEAPON) &&
1062 !(gAnimControl[pSoldier->usAnimState].uiFlags & (ANIM_RAISE_WEAPON | ANIM_NOCHANGE_WEAPON)))
1063 {
1064 // We are told that we need to raise weapon
1065 // Do so only if
1066 // 1) We have a rifle in hand...
1067 if (IsRifle(pSoldier->inv[HANDPOS].usItem))
1068 {
1069 // Switch on height!
1070 switch( gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
1071 {
1072 case ANIM_STAND:
1073
1074 // 2) OK, all's fine... lower weapon first....
1075 pSoldier->usPendingAnimation = usNewState;
1076 // Set new state to be animation to move to new stance
1077 usNewState = RAISE_RIFLE;
1078 }
1079 }
1080 }
1081
1082 // If this animation is a lower_weapon animation
1083 if ((gAnimControl[usNewState].uiFlags & ANIM_LOWER_WEAPON) &&
1084 !( gAnimControl[pSoldier->usAnimState].uiFlags & (ANIM_LOWER_WEAPON | ANIM_NOCHANGE_WEAPON)))
1085 {
1086 // We are told that we need to lower weapon
1087 // Do so only if
1088 // 1) We have a rifle in hand...
1089 if (IsRifle(pSoldier->inv[HANDPOS].usItem))
1090 {
1091 // Switch on height!
1092 switch( gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
1093 {
1094 case ANIM_STAND:
1095
1096 // 2) OK, all's fine... lower weapon first....
1097 pSoldier->usPendingAnimation = usNewState;
1098 // Set new state to be animation to move to new stance
1099 usNewState = LOWER_RIFLE;
1100 }
1101 }
1102 }
1103 }
1104 }
1105
1106 // Are we cowering and are tyring to move, getup first...
1107 if (gAnimControl[usNewState].uiFlags & ANIM_MOVING &&
1108 pSoldier->usAnimState == COWERING &&
1109 gAnimControl[usNewState].ubEndHeight == ANIM_STAND)
1110 {
1111 pSoldier->usPendingAnimation = usNewState;
1112 // Set new state to be animation to move to new stance
1113 usNewState = END_COWER;
1114 }
1115
1116 // If we want to start swatting, put a pending animation
1117 if( pSoldier->usAnimState != START_SWAT && usNewState == SWATTING )
1118 {
1119 // Set new state to be animation to move to new stance
1120 usNewState = START_SWAT;
1121 }
1122
1123 if( pSoldier->usAnimState == SWATTING && usNewState == CROUCHING )
1124 {
1125 // Set new state to be animation to move to new stance
1126 usNewState = END_SWAT;
1127 }
1128
1129 if(pSoldier->usAnimState == WALKING && usNewState == STANDING &&
1130 pSoldier->bLife < INJURED_CHANGE_THREASHOLD &&
1131 pSoldier->ubBodyType <= REGFEMALE &&
1132 !MercInWater(pSoldier))
1133 {
1134 // Set new state to be animation to move to new stance
1135 usNewState = END_HURT_WALKING;
1136 }
1137
1138 // Check if we are an enemy, and we are in an animation what should be overriden
1139 // by if he sees us or not.
1140 if ( ReevaluateEnemyStance( pSoldier, usNewState ) )
1141 {
1142 return;
1143 }
1144
1145 // Alrighty, check if we should free buddy up!
1146 if ( usNewState == GIVING_AID )
1147 {
1148 UnSetUIBusy(pSoldier);
1149 }
1150
1151 // SUBSTITUDE VARIOUS REG ANIMATIONS WITH ODD BODY TYPES
1152 usNewState = SubstituteBodyTypeAnimation(pSoldier, usNewState);
1153
1154 // CHECK IF WE CAN DO THIS ANIMATION!
1155 if (!IsAnimationValidForBodyType(*pSoldier, usNewState)) return;
1156
1157 // OK, make guy transition if a big merc...
1158 if ( pSoldier->uiAnimSubFlags & SUB_ANIM_BIGGUYTHREATENSTANCE )
1159 {
1160 if (usNewState == KNEEL_DOWN &&
1161 pSoldier->usAnimState != BIGMERC_CROUCH_TRANS_INTO &&
1162 IsRifle(pSoldier->inv[HANDPOS].usItem))
1163 {
1164 usNewState = BIGMERC_CROUCH_TRANS_INTO;
1165 }
1166
1167 if (usNewState == KNEEL_UP &&
1168 pSoldier->usAnimState != BIGMERC_CROUCH_TRANS_OUTOF &&
1169 IsRifle(pSoldier->inv[HANDPOS].usItem))
1170 {
1171 usNewState = BIGMERC_CROUCH_TRANS_OUTOF;
1172 }
1173 }
1174
1175 // OK, if we have reverse set, do the side step!
1176 if ( pSoldier->bReverse )
1177 {
1178 if ( usNewState == WALKING || usNewState == RUNNING || usNewState == SWATTING )
1179 {
1180 // CHECK FOR SIDEWAYS!
1181 if ( pSoldier->bDirection == gPurpendicularDirection[ pSoldier->bDirection ][ pSoldier->ubPathingData[ pSoldier->ubPathIndex ] ] )
1182 {
1183 // We are perpendicular!
1184 usNewState = SIDE_STEP;
1185 }
1186 else
1187 {
1188 if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_CROUCH )
1189 {
1190 usNewState = SWAT_BACKWARDS;
1191 }
1192 else
1193 {
1194 // Here, change to opposite direction
1195 usNewState = WALK_BACKWARDS;
1196 }
1197 }
1198 }
1199 }
1200
1201 // ATE: Patch hole for breath collapse for roofs, fences
1202 if ( usNewState == CLIMBUPROOF || usNewState == CLIMBDOWNROOF || usNewState == HOPFENCE )
1203 {
1204 // Check for breath collapse if a given animation like
1205 if (CheckForBreathCollapse(*pSoldier) || pSoldier->bCollapsed)
1206 {
1207 // UNset UI
1208 UnSetUIBusy(pSoldier);
1209
1210 SoldierCollapse( pSoldier );
1211
1212 pSoldier->bBreathCollapsed = FALSE;
1213
1214 return;
1215
1216 }
1217 }
1218
1219 // If we are in water.....and trying to run, change to run
1220 if ( pSoldier->bOverTerrainType == LOW_WATER || pSoldier->bOverTerrainType == MED_WATER )
1221 {
1222 // Check animation
1223 // Change to walking
1224 if ( usNewState == RUNNING )
1225 {
1226 usNewState = WALKING;
1227 }
1228 }
1229
1230 // Turn off anipause flag for any anim!
1231 pSoldier->uiStatusFlags &= (~SOLDIER_PAUSEANIMOVE);
1232
1233 // Unset paused for no APs.....
1234 AdjustNoAPToFinishMove( pSoldier, FALSE );
1235
1236 if ( usNewState == CRAWLING && pSoldier->usDontUpdateNewGridNoOnMoveAnimChange == 1 )
1237 {
1238 if ( pSoldier->fTurningFromPronePosition != TURNING_FROM_PRONE_ENDING_UP_FROM_MOVE )
1239 {
1240 pSoldier->fTurningFromPronePosition = TURNING_FROM_PRONE_START_UP_FROM_MOVE;
1241 }
1242
1243 // ATE: IF we are starting to crawl, but have to getup to turn first......
1244 if ( pSoldier->fTurningFromPronePosition == TURNING_FROM_PRONE_START_UP_FROM_MOVE )
1245 {
1246 usNewState = PRONE_UP;
1247 pSoldier->fTurningFromPronePosition = TURNING_FROM_PRONE_ENDING_UP_FROM_MOVE;
1248 }
1249 }
1250
1251 // We are about to start moving
1252 // Handle buddy beginning to move...
1253 // check new gridno, etc
1254 // ATE: Added: Make check that old anim is not a moving one as well
1255 if ((gAnimControl[usNewState].uiFlags & ANIM_MOVING &&
1256 !(gAnimControl[pSoldier->usAnimState].uiFlags & ANIM_MOVING)) ||
1257 (gAnimControl[usNewState].uiFlags & ANIM_MOVING && fForce))
1258 {
1259 BOOLEAN fKeepMoving;
1260
1261 if ( usNewState == CRAWLING && pSoldier->usDontUpdateNewGridNoOnMoveAnimChange == LOCKED_NO_NEWGRIDNO )
1262 {
1263 // Turn off lock once we are crawling once...
1264 pSoldier->usDontUpdateNewGridNoOnMoveAnimChange = 1;
1265 }
1266
1267 // ATE: Additional check here if we have just been told to update animation ONLY, not goto gridno stuff...
1268 if ( !pSoldier->usDontUpdateNewGridNoOnMoveAnimChange )
1269 {
1270 if ( usNewState != SWATTING )
1271 {
1272 SLOGD("Handling New gridNo for %d: Old %s, New %s",
1273 pSoldier->ubID, gAnimControl[pSoldier->usAnimState].zAnimStr,
1274 gAnimControl[usNewState].zAnimStr);
1275
1276 if ( !( gAnimControl[ usNewState ].uiFlags & ANIM_SPECIALMOVE ) )
1277 {
1278 // Handle goto new tile...
1279 if ( HandleGotoNewGridNo( pSoldier, &fKeepMoving, TRUE, usNewState ) )
1280 {
1281 if ( !fKeepMoving )
1282 {
1283 return;
1284 }
1285
1286 // Make sure desy = zeroed out...
1287 // pSoldier->fPastXDest = pSoldier->fPastYDest = FALSE;
1288 }
1289 else
1290 {
1291 if ( pSoldier->bBreathCollapsed )
1292 {
1293 // UNset UI
1294 UnSetUIBusy(pSoldier);
1295
1296 SoldierCollapse( pSoldier );
1297
1298 pSoldier->bBreathCollapsed = FALSE;
1299 }
1300 return;
1301 }
1302 }
1303 else
1304 {
1305 // Change desired direction
1306 // Just change direction
1307 EVENT_InternalSetSoldierDestination( pSoldier, pSoldier->ubPathingData[ pSoldier->ubPathIndex ], FALSE, pSoldier->usAnimState );
1308 }
1309
1310 //check for services
1311 ReceivingSoldierCancelServices( pSoldier );
1312 GivingSoldierCancelServices( pSoldier );
1313
1314
1315 // Check if we are a vehicle, and start playing noise sound....
1316 if ( pSoldier->uiStatusFlags & SOLDIER_VEHICLE )
1317 {
1318 HandleVehicleMovementSound( pSoldier, TRUE );
1319 }
1320 }
1321 }
1322 }
1323 else
1324 {
1325 // Check for stopping movement noise...
1326 if ( pSoldier->uiStatusFlags & SOLDIER_VEHICLE )
1327 {
1328 HandleVehicleMovementSound( pSoldier, FALSE );
1329
1330 // If a vehicle, set hewight to 0
1331 SetSoldierHeight( pSoldier, (FLOAT)( 0 ) );
1332 }
1333
1334 }
1335
1336 // Reset to false always.....
1337 // ( Unless locked )
1338 if ( gAnimControl[ usNewState ].uiFlags & ANIM_MOVING )
1339 {
1340 if ( pSoldier->usDontUpdateNewGridNoOnMoveAnimChange != LOCKED_NO_NEWGRIDNO )
1341 {
1342 pSoldier->usDontUpdateNewGridNoOnMoveAnimChange = FALSE;
1343 }
1344 }
1345
1346 if ( fTryingToRestart )
1347 {
1348 return;
1349 }
1350
1351 }
1352
1353
1354 // ATE: If this is an AI guy.. unlock him!
1355 if ( gTacticalStatus.fEnemySightingOnTheirTurn )
1356 {
1357 if (gTacticalStatus.enemy_sighting_on_their_turn_enemy == pSoldier)
1358 {
1359 pSoldier->fPauseAllAnimation = FALSE;
1360 gTacticalStatus.fEnemySightingOnTheirTurn = FALSE;
1361 }
1362 }
1363
1364 ///////////////////////////////////////////////////////////////////////
1365 // HERE DOWN - WE HAVE MADE A DESCISION!
1366 /////////////////////////////////////////////////////////////////////
1367
1368 uiOldAnimFlags = gAnimControl[ pSoldier->usAnimState ].uiFlags;
1369 uiNewAnimFlags = gAnimControl[ usNewState ].uiFlags;
1370
1371
1372 // CHECKING IF WE HAVE A HIT FINISH BUT NO DEATH IS DONE WITH A SPECIAL ANI CODE
1373 // IN THE HIT FINSIH ANI SCRIPTS
1374
1375 // CHECKING IF WE HAVE FINISHED A DEATH ANIMATION IS DONE WITH A SPECIAL ANI CODE
1376 // IN THE DEATH SCRIPTS
1377
1378
1379 // CHECK IF THIS NEW STATE IS NON-INTERRUPTABLE
1380 // IF SO - SET NON-INT FLAG
1381 if ( uiNewAnimFlags & ANIM_NONINTERRUPT )
1382 {
1383 pSoldier->fInNonintAnim = TRUE;
1384 }
1385
1386 if ( uiNewAnimFlags & ANIM_RT_NONINTERRUPT )
1387 {
1388 pSoldier->fRTInNonintAnim = TRUE;
1389 }
1390
1391 // CHECK IF WE ARE NOT AIMING, IF NOT, RESET LAST TAGRET!
1392 if (!(gAnimControl[pSoldier->usAnimState].uiFlags & ANIM_FIREREADY) &&
1393 !(gAnimControl[usNewState].uiFlags & ANIM_FIREREADY))
1394 {
1395 // ATE: Also check for the transition anims to not reset this
1396 // this should have used a flag but we're out of them....
1397 if ( usNewState != READY_RIFLE_STAND && usNewState != READY_RIFLE_PRONE &&
1398 usNewState != READY_RIFLE_CROUCH && usNewState != ROBOT_SHOOT )
1399 {
1400 pSoldier->sLastTarget = NOWHERE;
1401 }
1402 }
1403
1404 // If a special move state, release np aps
1405 if ( ( gAnimControl[ usNewState ].uiFlags & ANIM_SPECIALMOVE ) )
1406 {
1407 AdjustNoAPToFinishMove( pSoldier, FALSE );
1408 }
1409
1410 // ATE: If not a moving animation - turn off reverse....
1411 if ( !( gAnimControl[ usNewState ].uiFlags & ANIM_MOVING ) )
1412 {
1413 pSoldier->bReverse = FALSE;
1414 }
1415
1416 // Do special things based on new state
1417 switch( usNewState )
1418 {
1419 case STANDING:
1420
1421 // Update desired height
1422 pSoldier->ubDesiredHeight = ANIM_STAND;
1423 break;
1424
1425 case CROUCHING:
1426
1427 // Update desired height
1428 pSoldier->ubDesiredHeight = ANIM_CROUCH;
1429 break;
1430
1431 case PRONE:
1432
1433 // Update desired height
1434 pSoldier->ubDesiredHeight = ANIM_PRONE;
1435 break;
1436
1437 case READY_RIFLE_STAND:
1438 case READY_RIFLE_PRONE:
1439 case READY_RIFLE_CROUCH:
1440 case READY_DUAL_STAND:
1441 case READY_DUAL_CROUCH:
1442 case READY_DUAL_PRONE:
1443
1444 // OK, get points to ready weapon....
1445 if ( !pSoldier->fDontChargeReadyAPs )
1446 {
1447 sAPCost = GetAPsToReadyWeapon( pSoldier, usNewState );
1448 DeductPoints( pSoldier, sAPCost, sBPCost );
1449 }
1450 else
1451 {
1452 pSoldier->fDontChargeReadyAPs = FALSE;
1453 }
1454 break;
1455
1456 case WALKING:
1457
1458 pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
1459 pSoldier->ubPendingActionAnimCount = 0;
1460 break;
1461
1462 case SWATTING:
1463
1464 pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
1465 pSoldier->ubPendingActionAnimCount = 0;
1466 break;
1467
1468 case CRAWLING:
1469
1470 // Turn off flag...
1471 pSoldier->fTurningFromPronePosition = TURNING_FROM_PRONE_OFF;
1472 pSoldier->ubPendingActionAnimCount = 0;
1473 pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
1474 break;
1475
1476 case RUNNING:
1477
1478 // Only if our previous is not running
1479 if ( pSoldier->usAnimState != RUNNING )
1480 {
1481 sAPCost = AP_START_RUN_COST;
1482 DeductPoints( pSoldier, sAPCost, sBPCost );
1483 }
1484 // Set pending action count to 0
1485 pSoldier->ubPendingActionAnimCount = 0;
1486 pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
1487 break;
1488
1489 case ADULTMONSTER_WALKING:
1490 pSoldier->ubPendingActionAnimCount = 0;
1491 break;
1492
1493 case ROBOT_WALK:
1494 pSoldier->ubPendingActionAnimCount = 0;
1495 break;
1496
1497 case KNEEL_UP:
1498 case KNEEL_DOWN:
1499 case BIGMERC_CROUCH_TRANS_INTO:
1500 case BIGMERC_CROUCH_TRANS_OUTOF:
1501
1502 if ( !pSoldier->fDontChargeAPsForStanceChange )
1503 {
1504 DeductPoints( pSoldier, AP_CROUCH, BP_CROUCH );
1505 }
1506 pSoldier->fDontChargeAPsForStanceChange = FALSE;
1507 break;
1508
1509 case PRONE_UP:
1510 case PRONE_DOWN:
1511
1512 // ATE: If we are NOT waiting for prone down...
1513 if ( pSoldier->fTurningFromPronePosition < TURNING_FROM_PRONE_START_UP_FROM_MOVE && !pSoldier->fDontChargeAPsForStanceChange )
1514 {
1515 // ATE: Don't do this if we are still 'moving'....
1516 if ( pSoldier->sGridNo == pSoldier->sFinalDestination || pSoldier->ubPathIndex == 0 )
1517 {
1518 DeductPoints( pSoldier, AP_PRONE, BP_PRONE );
1519 }
1520 }
1521 pSoldier->fDontChargeAPsForStanceChange = FALSE;
1522 break;
1523
1524 //Deduct points for stance change
1525 //sAPCost = GetAPsToChangeStance( pSoldier, gAnimControl[ usNewState ].ubEndHeight );
1526 //DeductPoints( pSoldier, sAPCost, 0 );
1527 //break;
1528
1529 case START_AID:
1530
1531 DeductPoints( pSoldier, AP_START_FIRST_AID, BP_START_FIRST_AID );
1532 break;
1533
1534 case CUTTING_FENCE:
1535 DeductPoints( pSoldier, AP_USEWIRECUTTERS, BP_USEWIRECUTTERS );
1536 break;
1537
1538 case PLANT_BOMB:
1539
1540 DeductPoints( pSoldier, AP_DROP_BOMB, 0 );
1541 break;
1542
1543 case STEAL_ITEM:
1544
1545 DeductPoints( pSoldier, AP_STEAL_ITEM, 0 );
1546 break;
1547
1548 case CROW_DIE:
1549
1550 // Delete shadow of crow....
1551 if ( pSoldier->pAniTile != NULL )
1552 {
1553 DeleteAniTile( pSoldier->pAniTile );
1554 pSoldier->pAniTile = NULL;
1555 }
1556 break;
1557
1558 case CROW_FLY:
1559 // Ate: startup a shadow ( if gridno is set )
1560 HandleCrowShadowNewGridNo(*pSoldier);
1561 break;
1562
1563 case CROW_EAT:
1564 // ATE: Make sure height level is 0....
1565 SetSoldierHeight( pSoldier, (FLOAT)(0) );
1566 HandleCrowShadowRemoveGridNo(*pSoldier);
1567 break;
1568
1569 case USE_REMOTE:
1570
1571 DeductPoints( pSoldier, AP_USE_REMOTE, 0 );
1572 break;
1573
1574 //case PUNCH:
1575
1576 //Deduct points for punching
1577 //sAPCost = MinAPsToAttack( pSoldier, pSoldier->sGridNo, FALSE );
1578 //DeductPoints( pSoldier, sAPCost, 0 );
1579 //break;
1580
1581 case HOPFENCE:
1582
1583 DeductPoints( pSoldier, AP_JUMPFENCE, BP_JUMPFENCE );
1584 break;
1585
1586 // Deduct aps for falling down....
1587 case FALLBACK_HIT_STAND:
1588 case FALLFORWARD_FROMHIT_STAND:
1589
1590 DeductPoints( pSoldier, AP_FALL_DOWN, BP_FALL_DOWN );
1591 break;
1592
1593 case FALLFORWARD_FROMHIT_CROUCH:
1594
1595 DeductPoints( pSoldier, (AP_FALL_DOWN/2), (BP_FALL_DOWN/2) );
1596 break;
1597
1598 case QUEEN_SWIPE:
1599
1600 // ATE: set damage counter...
1601 pSoldier->uiPendingActionData1 = 0;
1602 break;
1603
1604 case CLIMBDOWNROOF:
1605
1606 // disable sight
1607 gTacticalStatus.uiFlags |= DISALLOW_SIGHT;
1608
1609 DeductPoints( pSoldier, AP_CLIMBOFFROOF, BP_CLIMBOFFROOF );
1610 break;
1611
1612 case CLIMBUPROOF:
1613
1614 // disable sight
1615 gTacticalStatus.uiFlags |= DISALLOW_SIGHT;
1616
1617 DeductPoints( pSoldier, AP_CLIMBROOF, BP_CLIMBROOF );
1618 break;
1619
1620 case JUMP_OVER_BLOCKING_PERSON:
1621
1622 // Set path....
1623 {
1624 UINT16 usNewGridNo;
1625
1626 DeductPoints( pSoldier, AP_JUMP_OVER, BP_JUMP_OVER );
1627
1628 usNewGridNo = NewGridNo( (UINT16)pSoldier->sGridNo, DirectionInc( pSoldier->bDirection ) );
1629 usNewGridNo = NewGridNo( (UINT16)usNewGridNo, DirectionInc( pSoldier->bDirection ) );
1630
1631 pSoldier->ubPathDataSize = 0;
1632 pSoldier->ubPathIndex = 0;
1633 pSoldier->ubPathingData[ pSoldier->ubPathDataSize ] = pSoldier->bDirection;
1634 pSoldier->ubPathDataSize++;
1635 pSoldier->ubPathingData[ pSoldier->ubPathDataSize ] = pSoldier->bDirection;
1636 pSoldier->ubPathDataSize++;
1637 pSoldier->sFinalDestination = usNewGridNo;
1638 // Set direction
1639 EVENT_InternalSetSoldierDestination( pSoldier, pSoldier->ubPathingData[ pSoldier->ubPathIndex ], FALSE, JUMP_OVER_BLOCKING_PERSON );
1640 }
1641 break;
1642
1643
1644 case GENERIC_HIT_STAND:
1645 case GENERIC_HIT_CROUCH:
1646 case STANDING_BURST_HIT:
1647 case ADULTMONSTER_HIT:
1648 case ADULTMONSTER_DYING:
1649 case COW_HIT:
1650 case COW_DYING:
1651 case BLOODCAT_HIT:
1652 case BLOODCAT_DYING:
1653 case WATER_HIT:
1654 case WATER_DIE:
1655 case DEEP_WATER_HIT:
1656 case DEEP_WATER_DIE:
1657 case RIFLE_STAND_HIT:
1658 case LARVAE_HIT:
1659 case LARVAE_DIE:
1660 case QUEEN_HIT:
1661 case QUEEN_DIE:
1662 case INFANT_HIT:
1663 case INFANT_DIE:
1664 case CRIPPLE_HIT:
1665 case CRIPPLE_DIE:
1666 case CRIPPLE_DIE_FLYBACK:
1667 case ROBOTNW_HIT:
1668 case ROBOTNW_DIE:
1669
1670 // Set getting hit flag to TRUE
1671 pSoldier->fGettingHit = TRUE;
1672 break;
1673
1674 case CHARIOTS_OF_FIRE:
1675 case BODYEXPLODING:
1676
1677 // Merc on fire!
1678 pSoldier->uiPendingActionData1 = PlaySoldierJA2Sample(pSoldier, FIRE_ON_MERC, HIGHVOLUME, 5, TRUE);
1679 break;
1680 }
1681
1682 // Remove old animation profile
1683 HandleAnimationProfile(*pSoldier, pSoldier->usAnimState, TRUE);
1684
1685 // From animation control, set surface
1686 if (!SetSoldierAnimationSurface(pSoldier, usNewState))
1687 {
1688 return;
1689 }
1690
1691
1692 // Set state
1693 pSoldier->usOldAniState = pSoldier->usAnimState;
1694 pSoldier->sOldAniCode = pSoldier->usAniCode;
1695
1696 // Change state value!
1697 pSoldier->usAnimState = usNewState;
1698
1699 pSoldier->sZLevelOverride = -1;
1700
1701 if ( !( pSoldier->uiStatusFlags & SOLDIER_LOCKPENDINGACTIONCOUNTER ) )
1702 {
1703 //ATE Cancel ANY pending action...
1704 if ( pSoldier->ubPendingActionAnimCount > 0 && ( gAnimControl[ pSoldier->usOldAniState ].uiFlags & ANIM_MOVING ) )
1705 {
1706 // Do some special things for some actions
1707 switch( pSoldier->ubPendingAction )
1708 {
1709 case MERC_GIVEITEM:
1710 // Unset target as enaged
1711 GetMan(pSoldier->uiPendingActionData4).uiStatusFlags &= ~SOLDIER_ENGAGEDINACTION;
1712 break;
1713 }
1714 soldier->removePendingAction();
1715 }
1716 else
1717 {
1718 // Increment this for almost all animations except some movement ones...
1719 // That's because this represents ANY animation other than the one we began
1720 // when the pending action was started
1721 // ATE: Added to ignore this count if we are waiting for someone to move out of our way...
1722 if (usNewState != START_SWAT && usNewState != END_SWAT &&
1723 !(gAnimControl[usNewState].uiFlags & ANIM_NOCHANGE_PENDINGCOUNT) &&
1724 !pSoldier->fDelayedMovement &&
1725 !(pSoldier->uiStatusFlags & SOLDIER_ENGAGEDINACTION))
1726 {
1727 pSoldier->ubPendingActionAnimCount++;
1728 }
1729 }
1730 }
1731
1732 // Set new animation profile
1733 HandleAnimationProfile(*pSoldier, usNewState, FALSE);
1734
1735 // Reset some animation values
1736 pSoldier->fForceShade = FALSE;
1737
1738 // CHECK IF WE ARE AT AN IDLE ACTION
1739 #if 0
1740 if ( gAnimControl[ usNewState ].uiFlags & ANIM_IDLE )
1741 {
1742 pSoldier->bAction = ACTION_DONE;
1743 }
1744 else
1745 {
1746 pSoldier->bAction = ACTION_BUSY;
1747 }
1748 #endif
1749
1750 // Set current frame
1751 pSoldier->usAniCode = usStartingAniCode;
1752
1753 // ATE; For some animations that could use some variations, do so....
1754 if (usNewState == CHARIOTS_OF_FIRE || usNewState == BODYEXPLODING )
1755 {
1756 pSoldier->usAniCode = (UINT16)( Random( 10 ) );
1757 }
1758
1759 // ATE: Default to first frame....
1760 // Will get changed ( probably ) by AdjustToNextAnimationFrame()
1761 ConvertAniCodeToAniFrame( pSoldier, (INT16)( 0 ) );
1762
1763 // Set delay speed
1764 SetSoldierAniSpeed( pSoldier );
1765
1766 // Reset counters
1767 RESETTIMECOUNTER( pSoldier->UpdateCounter, pSoldier->sAniDelay );
1768
1769 // Adjust to new animation frame ( the first one )
1770 AdjustToNextAnimationFrame( pSoldier );
1771
1772 // Setup offset information for UI above guy
1773 SetSoldierLocatorOffsets( pSoldier );
1774
1775 // If we are certain animations, reload palette
1776 if ( usNewState == VEHICLE_DIE || usNewState == CHARIOTS_OF_FIRE || usNewState == BODYEXPLODING )
1777 {
1778 CreateSoldierPalettes(*pSoldier);
1779 }
1780
1781 // ATE: if the old animation was a movement, and new is not, play sound...
1782 // OK, play final footstep sound...
1783 if( !(gTacticalStatus.uiFlags & LOADING_SAVED_GAME ) )
1784 {
1785 if ( ( gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_STATIONARY ) &&
1786 ( gAnimControl[ pSoldier->usOldAniState ].uiFlags & ANIM_MOVING ) )
1787 {
1788 PlaySoldierFootstepSound( pSoldier );
1789 }
1790 }
1791
1792 // Free up from stance change
1793 FreeUpNPCFromStanceChange( pSoldier );
1794
1795 CheckForFreeupFromHit(pSoldier, uiOldAnimFlags, uiNewAnimFlags, pSoldier->usOldAniState, usNewState);
1796 }
1797
1798
InternalRemoveSoldierFromGridNo(SOLDIERTYPE & s,BOOLEAN const force)1799 static void InternalRemoveSoldierFromGridNo(SOLDIERTYPE& s, BOOLEAN const force)
1800 {
1801 if (s.sGridNo == NOWHERE) return;
1802
1803 if (!s.bInSector && !force) return;
1804
1805 // Remove from world (old pos)
1806 RemoveMerc(s.sGridNo, s, false);
1807 HandleAnimationProfile(s, s.usAnimState, TRUE);
1808
1809 // Remove records of this guy being adjacent
1810 for (INT8 dir = 0; dir < NUM_WORLD_DIRECTIONS; ++dir)
1811 {
1812 INT32 const grid_no = s.sGridNo + DirIncrementer[dir];
1813 if (grid_no < 0 || WORLD_MAX <= grid_no) continue;
1814
1815 --gpWorldLevelData[grid_no].ubAdjacentSoldierCnt;
1816 }
1817
1818 HandlePlacingRoofMarker(s, false, false);
1819
1820 UnMarkMovementReserved(s);
1821 HandleCrowShadowRemoveGridNo(s);
1822 s.sGridNo = NOWHERE;
1823 }
1824
1825
RemoveSoldierFromGridNo(SOLDIERTYPE & s)1826 void RemoveSoldierFromGridNo(SOLDIERTYPE& s)
1827 {
1828 InternalRemoveSoldierFromGridNo(s, FALSE);
1829 }
1830
1831
1832 static void SetSoldierGridNo(SOLDIERTYPE&, INT16 sNewGridNo, BOOLEAN fForceRemove);
1833
1834
EVENT_InternalSetSoldierPosition(SOLDIERTYPE * pSoldier,GridNo pos,FLOAT dNewXPos,FLOAT dNewYPos,SetSoldierPosFlags flags)1835 static void EVENT_InternalSetSoldierPosition(SOLDIERTYPE* pSoldier, GridNo pos, FLOAT dNewXPos, FLOAT dNewYPos, SetSoldierPosFlags flags)
1836 {
1837 // Not if we're dead!
1838 if ( ( pSoldier->uiStatusFlags & SOLDIER_DEAD ) )
1839 {
1840 return;
1841 }
1842
1843 if (!(flags & SSP_NO_DEST)) pSoldier->sDestination = pos;
1844 if (!(flags & SSP_NO_FINAL_DEST)) pSoldier->sFinalDestination = pos;
1845
1846 // Set New pos
1847 pSoldier->dXPos = dNewXPos;
1848 pSoldier->dYPos = dNewYPos;
1849
1850 pSoldier->sX = (INT16)dNewXPos;
1851 pSoldier->sY = (INT16)dNewYPos;
1852
1853 HandleCrowShadowNewPosition( pSoldier );
1854
1855 SetSoldierGridNo(*pSoldier, pos, (flags & SSP_FORCE_DELETE) != 0);
1856
1857 if ( !( pSoldier->uiStatusFlags & ( SOLDIER_DRIVER | SOLDIER_PASSENGER ) ) )
1858 {
1859 if ( gGameSettings.fOptions[ TOPTION_MERC_ALWAYS_LIGHT_UP ] )
1860 {
1861 PositionSoldierLight(pSoldier);
1862 }
1863 }
1864
1865 // ATE: Mirror calls if we are a vehicle ( for all our passengers )
1866 UpdateAllVehiclePassengersGridNo( pSoldier );
1867 }
1868
1869
EVENT_SetSoldierPosition(SOLDIERTYPE * const s,const GridNo pos,const SetSoldierPosFlags flags)1870 void EVENT_SetSoldierPosition(SOLDIERTYPE* const s, const GridNo pos, const SetSoldierPosFlags flags)
1871 {
1872 INT16 x;
1873 INT16 y;
1874 ConvertGridNoToCenterCellXY(pos, &x, &y);
1875 EVENT_InternalSetSoldierPosition(s, pos, x, y, flags);
1876 }
1877
1878
EVENT_SetSoldierPositionNoCenter(SOLDIERTYPE * const s,const GridNo pos,const SetSoldierPosFlags flags)1879 void EVENT_SetSoldierPositionNoCenter(SOLDIERTYPE* const s, const GridNo pos, const SetSoldierPosFlags flags)
1880 {
1881 INT16 x;
1882 INT16 y;
1883 ConvertGridNoToCellXY(pos, &x, &y);
1884 EVENT_InternalSetSoldierPosition(s, pos, x, y, flags);
1885 }
1886
1887
EVENT_SetSoldierPositionXY(SOLDIERTYPE * const s,const FLOAT x,const FLOAT y,const SetSoldierPosFlags flags)1888 void EVENT_SetSoldierPositionXY(SOLDIERTYPE* const s, const FLOAT x, const FLOAT y, const SetSoldierPosFlags flags)
1889 {
1890 EVENT_InternalSetSoldierPosition(s, GETWORLDINDEXFROMWORLDCOORDS(y, x), x, y, flags);
1891 }
1892
1893
SetSoldierHeight(SOLDIERTYPE * const s,FLOAT const new_height)1894 void SetSoldierHeight(SOLDIERTYPE* const s, FLOAT const new_height)
1895 {
1896 s->dHeightAdjustment = new_height;
1897 s->sHeightAdjustment = (INT16)new_height;
1898
1899 if (s->sHeightAdjustment > 0)
1900 {
1901 ApplyTranslucencyToWalls((INT16)(s->dXPos / CELL_X_SIZE), (INT16)(s->dYPos / CELL_Y_SIZE));
1902 s->bLevel = SECOND_LEVEL;
1903 }
1904 else
1905 {
1906 s->bLevel = FIRST_LEVEL;
1907 }
1908 }
1909
1910
SetSoldierGridNo(SOLDIERTYPE & s,GridNo new_grid_no,BOOLEAN const fForceRemove)1911 static void SetSoldierGridNo(SOLDIERTYPE& s, GridNo new_grid_no, BOOLEAN const fForceRemove)
1912 {
1913 // Not if we're dead!
1914 if (s.uiStatusFlags & SOLDIER_DEAD) return;
1915
1916 if (new_grid_no == s.sGridNo && s.pLevelNode) return;
1917
1918 // Check if we are moving AND this is our next dest gridno....
1919 if (gAnimControl[s.usAnimState].uiFlags & (ANIM_MOVING | ANIM_SPECIALMOVE))
1920 {
1921 if (!(gTacticalStatus.uiFlags & LOADING_SAVED_GAME))
1922 {
1923 if (new_grid_no != s.sDestination)
1924 {
1925 // This must be our new one, make it so
1926 new_grid_no = s.sDestination;
1927 }
1928
1929 // Now check this baby
1930 if (new_grid_no == s.sGridNo) return;
1931 }
1932 }
1933
1934 s.sOldGridNo = s.sGridNo;
1935
1936 if (s.ubBodyType == QUEENMONSTER)
1937 {
1938 SetPositionSndGridNo(s.iPositionSndID, new_grid_no);
1939 }
1940
1941 bool const in_vehicle = s.uiStatusFlags & (SOLDIER_DRIVER | SOLDIER_PASSENGER);
1942 if (!in_vehicle)
1943 {
1944 InternalRemoveSoldierFromGridNo(s, fForceRemove);
1945 }
1946
1947 s.sGridNo = new_grid_no;
1948
1949 // Check if our new gridno is valid, if not do not set!
1950 if (!GridNoOnVisibleWorldTile(new_grid_no)) return;
1951
1952 // Alrighty, update UI for this guy, if he's the selected guy
1953 if (GetSelectedMan() == &s && guiCurrentEvent == C_WAIT_FOR_CONFIRM)
1954 {
1955 // Update path!
1956 gfPlotNewMovement = TRUE;
1957 }
1958
1959 // Reset some flags for optimizations
1960 s.sWalkToAttackGridNo = NOWHERE;
1961
1962 // check for special code to close door
1963 if (s.bEndDoorOpenCode == 2)
1964 {
1965 s.bEndDoorOpenCode = 0;
1966 HandleDoorChangeFromGridNo(&s, s.sEndDoorOpenCodeData, FALSE);
1967 }
1968
1969 // Update buddy's strategic insertion code
1970 s.ubStrategicInsertionCode = INSERTION_CODE_GRIDNO;
1971 s.usStrategicInsertionData = new_grid_no;
1972
1973 // Remove this gridno as a reserved place!
1974 if (!in_vehicle) UnMarkMovementReserved(s);
1975
1976 if (s.sInitialGridNo == 0)
1977 {
1978 s.sInitialGridNo = new_grid_no;
1979 s.usPatrolGrid[0] = new_grid_no;
1980 }
1981
1982 // Add records of this guy being adjacent
1983 for (INT8 dir = 0; dir < NUM_WORLD_DIRECTIONS; ++dir)
1984 {
1985 ++gpWorldLevelData[new_grid_no + DirIncrementer[dir]].ubAdjacentSoldierCnt;
1986 }
1987
1988 if (!in_vehicle) DropSmell(s);
1989
1990 // Handle any special rendering situations
1991 s.sZLevelOverride = -1;
1992
1993 // If we are over a fence (hopping), make us higher!
1994 if (IsJumpableFencePresentAtGridno(new_grid_no))
1995 {
1996 s.sZLevelOverride = TOPMOST_Z_LEVEL;
1997 }
1998
1999 // Add merc at new pos
2000 if (!in_vehicle)
2001 {
2002 LEVELNODE* const n = AddMercToHead(new_grid_no, s, TRUE);
2003
2004 // If we are in the middle of climbing the roof!
2005 if (s.usAnimState == CLIMBUPROOF)
2006 {
2007 if (s.light) LightSpriteRoofStatus(s.light, TRUE);
2008 }
2009 else if (s.usAnimState == CLIMBDOWNROOF)
2010 {
2011 if (s.light) LightSpriteRoofStatus(s.light, FALSE);
2012 }
2013
2014 //JA2Gold: If the player wants the merc to cast the fake light AND it is night
2015 if (s.bTeam != OUR_TEAM || (gGameSettings.fOptions[TOPTION_MERC_CASTS_LIGHT] && NightTime()))
2016 {
2017 MAP_ELEMENT const& m = gpWorldLevelData[new_grid_no];
2018 LEVELNODE const& other = s.bLevel > 0 && m.pRoofHead ? *m.pRoofHead : *m.pLandHead;
2019 n->ubShadeLevel = other.ubShadeLevel;
2020 n->ubSumLights = other.ubSumLights;
2021 n->ubMaxLights = other.ubMaxLights;
2022 n->ubNaturalShadeLevel = other.ubNaturalShadeLevel;
2023 }
2024 else //The player DOESNT want the mercs to cast the fake lights
2025 {
2026 //Only light the soldier
2027 SetSoldierPersonalLightLevel(&s);
2028 }
2029
2030 HandleAnimationProfile(s, s.usAnimState, FALSE);
2031 HandleCrowShadowNewGridNo(s);
2032 }
2033
2034 INT8 const old_over_terrain_type = s.bOverTerrainType;
2035 s.bOverTerrainType = GetTerrainType(new_grid_no);
2036
2037 // Check that our animation is up to date!
2038 if (!in_vehicle)
2039 {
2040 BOOLEAN const in_water = MercInWater(&s);
2041
2042 // ATE: If ever in water make sure we walk afterwoods!
2043 if (in_water) s.usUIMovementMode = WALKING;
2044
2045 if (in_water != s.fPrevInWater)
2046 {
2047 // Update Animation data
2048 SetSoldierAnimationSurface(&s, s.usAnimState);
2049
2050 s.fPrevInWater = in_water;
2051
2052 // Update sound
2053 if (in_water)
2054 {
2055 PlaySoldierJA2Sample(&s, ENTER_WATER_1, MIDVOLUME, 1, TRUE);
2056 }
2057 else
2058 {
2059 // ATE: Check if we are going from water to land - if so, resume with
2060 // regular movement mode
2061 EVENT_InitNewSoldierAnim(&s, s.usUIMovementMode, 0, FALSE);
2062 }
2063 }
2064
2065 // OK, If we were not in deep water but we are now, handle deep animations!
2066 if (s.bOverTerrainType == DEEP_WATER && old_over_terrain_type != DEEP_WATER)
2067 {
2068 // Based on our current animation, change!
2069 switch (s.usAnimState)
2070 {
2071 case WALKING:
2072 case RUNNING:
2073 // In deep water, swim!
2074 // Make transition from low to deep
2075 EVENT_InitNewSoldierAnim(&s, LOW_TO_DEEP_WATER, 0, FALSE);
2076 s.usPendingAnimation = DEEP_WATER_SWIM;
2077 PlayLocationJA2Sample(new_grid_no, ENTER_DEEP_WATER_1, MIDVOLUME, 1);
2078 }
2079 }
2080
2081 // Damage water if in deep water
2082 if (s.bOverTerrainType == MED_WATER || s.bOverTerrainType == DEEP_WATER)
2083 {
2084 WaterDamage(s);
2085 }
2086
2087 // OK, If we were in deep water but we are NOT now, handle mid animations!
2088 if (s.bOverTerrainType != DEEP_WATER && old_over_terrain_type == DEEP_WATER)
2089 {
2090 // Make transition from low to deep
2091 EVENT_InitNewSoldierAnim(&s, DEEP_TO_LOW_WATER, 0, FALSE);
2092 s.usPendingAnimation = s.usUIMovementMode;
2093 }
2094 }
2095
2096 // Are we now standing in tear gas without a decently working gas mask?
2097 if (GetSmokeEffectOnTile(new_grid_no, s.bLevel) != NO_SMOKE_EFFECT &&
2098 (s.inv[HEAD1POS].usItem != GASMASK || s.inv[HEAD1POS].bStatus[0] < GASMASK_MIN_STATUS) &&
2099 (s.inv[HEAD2POS].usItem != GASMASK || s.inv[HEAD2POS].bStatus[0] < GASMASK_MIN_STATUS))
2100 {
2101 s.uiStatusFlags |= SOLDIER_GASSED;
2102 }
2103
2104 // Merc got to a new tile by "sneaking". Did we theoretically sneak past an enemy?
2105 if (s.bTeam == OUR_TEAM &&
2106 s.bStealthMode &&
2107 s.bOppCnt > 0) // opponents in sight
2108 {
2109 // Check each possible enemy
2110 CFOR_EACH_SOLDIER(enemy)
2111 {
2112 // If this guy is here and alive enough to be looking for us
2113 if (!enemy->bInSector) continue;
2114 if (enemy->bLife < OKLIFE) continue;
2115
2116 // No points for sneaking by the neutrals & friendlies
2117 if (enemy->bNeutral) continue;
2118 if (s.bSide == enemy->bSide) continue;
2119 if (enemy->ubBodyType == COW) continue;
2120 if (enemy->ubBodyType == CROW) continue;
2121
2122 // if we SEE this particular oppponent, and he DOESN'T see us and he COULD
2123 // see us
2124 if (s.bOppList[enemy->ubID] != SEEN_CURRENTLY) continue;
2125 if (enemy->bOppList[s.ubID] == SEEN_CURRENTLY) continue;
2126 if (PythSpacesAway(new_grid_no, enemy->sGridNo) >= DistanceVisible(enemy, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, new_grid_no, s.bLevel)) continue;
2127
2128 // AGILITY (5): Soldier snuck 1 square past unaware enemy
2129 StatChange(s, AGILAMT, 5, FROM_SUCCESS);
2130 // Keep looping, we'll give'em 1 point for EACH such enemy!
2131 }
2132 }
2133
2134 // Adjust speed based on terrain, etc
2135 SetSoldierAniSpeed(&s);
2136 }
2137
2138
2139 static UINT16 SelectFireAnimation(SOLDIERTYPE* pSoldier, UINT8 ubHeight);
2140
2141
EVENT_FireSoldierWeapon(SOLDIERTYPE * pSoldier,INT16 sTargetGridNo)2142 void EVENT_FireSoldierWeapon( SOLDIERTYPE *pSoldier, INT16 sTargetGridNo )
2143 {
2144 BOOLEAN fDoFireRightAway = FALSE;
2145
2146 // CANNOT BE SAME GRIDNO!
2147 if ( pSoldier->sGridNo == sTargetGridNo )
2148 {
2149 return;
2150 }
2151
2152 // Increment the number of people busy doing stuff because of an attack
2153 //if (gTacticalStatus.uiFlags & INCOMBAT)
2154 //{
2155 gTacticalStatus.ubAttackBusyCount++;
2156 SLOGD("Starting attack, attack count now %d",
2157 gTacticalStatus.ubAttackBusyCount);
2158 //}
2159
2160 // Set soldier's target gridno
2161 // This assignment was redundent because it's already set in
2162 // the actual event call
2163 pSoldier->sTargetGridNo = sTargetGridNo;
2164 //pSoldier->sLastTarget = sTargetGridNo;
2165 pSoldier->target = WhoIsThere2(sTargetGridNo, pSoldier->bTargetLevel);
2166
2167 if (GCM->getItem(pSoldier->inv[HANDPOS].usItem)->isGun())
2168 {
2169 if (pSoldier->bDoBurst)
2170 {
2171 // Set the TOTAL number of bullets to be fired
2172 // Can't shoot more bullets than we have in our magazine!
2173 pSoldier->bBulletsLeft = __min( GCM->getWeapon(pSoldier->inv[ pSoldier->ubAttackingHand].usItem)->ubShotsPerBurst, pSoldier->inv[ pSoldier->ubAttackingHand ].ubGunShotsLeft );
2174 }
2175 else if ( IsValidSecondHandShot( pSoldier ) )
2176 {
2177 // two-pistol attack - two bullets!
2178 pSoldier->bBulletsLeft = 2;
2179 }
2180 else
2181 {
2182 pSoldier->bBulletsLeft = 1;
2183 }
2184 if (pSoldier->inv[ pSoldier->ubAttackingHand ].ubGunAmmoType == AMMO_BUCKSHOT)
2185 {
2186 pSoldier->bBulletsLeft *= NUM_BUCKSHOT_PELLETS;
2187 }
2188 }
2189 SLOGD("Starting attack, bullets left %d", pSoldier->bBulletsLeft);
2190
2191 // Change to fire animation
2192 SoldierReadyWeapon(pSoldier, sTargetGridNo, FALSE);
2193
2194 // IF WE ARE AN NPC, SLIDE VIEW TO SHOW WHO IS SHOOTING
2195 {
2196 //if ( pSoldier->fDoSpread )
2197 //{
2198 // If we are spreading burst, goto right away!
2199 //EVENT_InitNewSoldierAnim( pSoldier, SelectFireAnimation( pSoldier, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ), 0, FALSE );
2200
2201 //}
2202
2203 // else
2204 {
2205 if (pSoldier->uiStatusFlags & SOLDIER_MONSTER )
2206 {
2207 // Force our direction!
2208 EVENT_SetSoldierDirection( pSoldier, pSoldier->bDesiredDirection );
2209 EVENT_InitNewSoldierAnim( pSoldier, SelectFireAnimation( pSoldier, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ), 0, FALSE );
2210 }
2211 else
2212 {
2213 // IF WE ARE IN REAl-TIME, FIRE IMMEDIATELY!
2214 if (!(gTacticalStatus.uiFlags & INCOMBAT))
2215 {
2216 //fDoFireRightAway = TRUE;
2217 }
2218
2219 // Check if our weapon has no intermediate anim...
2220 switch( pSoldier->inv[ HANDPOS ].usItem )
2221 {
2222 case ROCKET_LAUNCHER:
2223 case MORTAR:
2224 case GLAUNCHER:
2225 fDoFireRightAway = TRUE;
2226 break;
2227 }
2228
2229 if ( fDoFireRightAway )
2230 {
2231 // Set to true so we don't get toasted twice for APs..
2232 pSoldier->fDontUnsetLastTargetFromTurn = TRUE;
2233
2234 // Make sure we don't try and do fancy prone turning.....
2235 pSoldier->fTurningFromPronePosition = FALSE;
2236
2237 // Force our direction!
2238 EVENT_SetSoldierDirection( pSoldier, pSoldier->bDesiredDirection );
2239
2240 EVENT_InitNewSoldierAnim( pSoldier, SelectFireAnimation( pSoldier, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ), 0, FALSE );
2241 }
2242 else
2243 {
2244 // Set flag indicating we are about to shoot once destination direction is hit
2245 pSoldier->fTurningToShoot = TRUE;
2246
2247 if ( pSoldier->bTeam != OUR_TEAM && pSoldier->bVisible != -1)
2248 {
2249 LocateSoldier(pSoldier, DONTSETLOCATOR);
2250 }
2251 }
2252 }
2253 }
2254 }
2255 }
2256
2257 //gAnimControl[ pSoldier->usAnimState ].ubEndHeight
2258 // ChangeSoldierState( pSoldier, SHOOT_RIFLE_STAND, 0 , FALSE );
2259
2260
SelectFireAnimation(SOLDIERTYPE * pSoldier,UINT8 ubHeight)2261 static UINT16 SelectFireAnimation(SOLDIERTYPE* pSoldier, UINT8 ubHeight)
2262 {
2263 INT16 sDist;
2264 FLOAT dTargetX;
2265 FLOAT dTargetY;
2266 FLOAT dTargetZ;
2267 BOOLEAN fDoLowShot = FALSE;
2268
2269
2270 //Do different things if we are a monster
2271 if (pSoldier->uiStatusFlags & SOLDIER_MONSTER)
2272 {
2273 switch( pSoldier->ubBodyType )
2274 {
2275 case ADULTFEMALEMONSTER:
2276 case AM_MONSTER:
2277 case YAF_MONSTER:
2278 case YAM_MONSTER:
2279 return( MONSTER_SPIT_ATTACK );
2280
2281 case LARVAE_MONSTER:
2282 break;
2283
2284 case INFANT_MONSTER:
2285 return( INFANT_ATTACK );
2286
2287 case QUEENMONSTER:
2288 return( QUEEN_SPIT );
2289 }
2290 return( TRUE );
2291 }
2292
2293 if ( pSoldier->ubBodyType == ROBOTNOWEAPON )
2294 {
2295 if ( pSoldier->bDoBurst > 0 )
2296 {
2297 return( ROBOT_BURST_SHOOT );
2298 }
2299 else
2300 {
2301 return( ROBOT_SHOOT );
2302 }
2303 }
2304
2305 // Check for rocket laucncher....
2306 if ( pSoldier->inv[ HANDPOS ].usItem == ROCKET_LAUNCHER )
2307 {
2308 return( SHOOT_ROCKET );
2309 }
2310
2311 // Check for rocket laucncher....
2312 if ( pSoldier->inv[ HANDPOS ].usItem == MORTAR )
2313 {
2314 return( SHOOT_MORTAR );
2315 }
2316
2317 // Check for tank cannon
2318 if ( pSoldier->inv[ HANDPOS ].usItem == TANK_CANNON )
2319 {
2320 return( TANK_SHOOT );
2321 }
2322
2323 if ( pSoldier->ubBodyType == TANK_NW || pSoldier->ubBodyType == TANK_NE )
2324 {
2325 return( TANK_BURST );
2326 }
2327
2328 // Determine which animation to do...depending on stance and gun in hand...
2329 switch ( ubHeight )
2330 {
2331 case ANIM_STAND:
2332 // CHECK 2ND HAND!
2333 if ( IsValidSecondHandShot( pSoldier ) )
2334 {
2335 // Increment the number of people busy doing stuff because of an attack
2336 //gTacticalStatus.ubAttackBusyCount++;
2337 //SLOGD("Starting attack with 2 guns, attack count now %d", gTacticalStatus.ubAttackBusyCount);
2338
2339 return( SHOOT_DUAL_STAND );
2340 }
2341 else
2342 {
2343 // OK, while standing check distance away from target, and shoot low if we should!
2344 sDist = PythSpacesAway( pSoldier->sGridNo, pSoldier->sTargetGridNo );
2345
2346 //ATE: OK, SEE WERE WE ARE TARGETING....
2347 GetTargetWorldPositions( pSoldier, pSoldier->sTargetGridNo, &dTargetX, &dTargetY, &dTargetZ );
2348
2349 //CalculateSoldierZPos( pSoldier, FIRING_POS, &dFirerZ );
2350
2351 if ( sDist <= 2 && dTargetZ <= 100 )
2352 {
2353 fDoLowShot = TRUE;
2354 }
2355
2356 // ATE: Made distence away long for psitols such that they never use this....
2357 //if ( !(GCM->getItem(usItem)->isTwoHanded()) )
2358 //{
2359 // fDoLowShot = FALSE;
2360 //}
2361
2362 // Don't do any low shots if in water
2363 if ( MercInWater( pSoldier ) )
2364 {
2365 fDoLowShot = FALSE;
2366 }
2367
2368
2369 if ( pSoldier->bDoBurst > 0 )
2370 {
2371 if ( fDoLowShot )
2372 {
2373 return( FIRE_BURST_LOW_STAND );
2374 }
2375 else
2376 {
2377 return( STANDING_BURST );
2378 }
2379 }
2380 else
2381 {
2382 if ( fDoLowShot )
2383 {
2384 return( FIRE_LOW_STAND );
2385 }
2386 else
2387 {
2388 return( SHOOT_RIFLE_STAND );
2389 }
2390 }
2391 }
2392
2393 case ANIM_PRONE:
2394
2395 if ( pSoldier->bDoBurst > 0 )
2396 {
2397 //pSoldier->fBurstCompleted = FALSE;
2398 return( PRONE_BURST );
2399 }
2400 else
2401 {
2402 if ( IsValidSecondHandShot( pSoldier ) )
2403 {
2404 return( SHOOT_DUAL_PRONE );
2405 }
2406 else
2407 {
2408 return( SHOOT_RIFLE_PRONE );
2409 }
2410 }
2411
2412 case ANIM_CROUCH:
2413
2414 if ( IsValidSecondHandShot( pSoldier ) )
2415 {
2416 // Increment the number of people busy doing stuff because of an attack
2417 //gTacticalStatus.ubAttackBusyCount++;
2418 //SLOGD("Starting attack with 2 guns, attack count now %d", gTacticalStatus.ubAttackBusyCount);
2419
2420 return( SHOOT_DUAL_CROUCH );
2421 }
2422 else
2423 {
2424 if ( pSoldier->bDoBurst > 0 )
2425 {
2426 //pSoldier->fBurstCompleted = FALSE;
2427 return( CROUCHED_BURST );
2428 }
2429 else
2430 {
2431 return( SHOOT_RIFLE_CROUCH );
2432 }
2433 }
2434
2435 default:
2436 AssertMsg( FALSE, String( "SelectFireAnimation: ERROR - Invalid height %d", ubHeight ) );
2437 break;
2438 }
2439
2440
2441 // If here, an internal error has occured!
2442 Assert( FALSE );
2443 return ( 0 );
2444 }
2445
2446
GetMoveStateBasedOnStance(const SOLDIERTYPE * const s,const UINT8 ubStanceHeight)2447 UINT16 GetMoveStateBasedOnStance(const SOLDIERTYPE* const s, const UINT8 ubStanceHeight)
2448 {
2449 switch (ubStanceHeight)
2450 {
2451 case ANIM_STAND:
2452 return s->fUIMovementFast && !(s->uiStatusFlags & SOLDIER_VEHICLE) ?
2453 RUNNING : WALKING;
2454
2455 case ANIM_PRONE: return CRAWLING;
2456 case ANIM_CROUCH: return SWATTING;
2457
2458 default:
2459 AssertMsg(FALSE, String("GetMoveStateBasedOnStance: ERROR - Invalid height %d",
2460 ubStanceHeight));
2461 return 0;
2462 }
2463 }
2464
2465
SoldierReadyWeapon(SOLDIERTYPE * const pSoldier,const GridNo tgt_pos,const BOOLEAN fEndReady)2466 BOOLEAN SoldierReadyWeapon(SOLDIERTYPE* const pSoldier, const GridNo tgt_pos, const BOOLEAN fEndReady)
2467 {
2468 const INT16 sFacingDir = GetDirectionFromGridNo(tgt_pos, pSoldier);
2469 return InternalSoldierReadyWeapon(pSoldier, sFacingDir, fEndReady);
2470 }
2471
2472
2473 static void EVENT_InternalSetSoldierDesiredDirection(SOLDIERTYPE* pSoldier, UINT16 usNewDirection, BOOLEAN fInitalMove, UINT16 usAnimState);
2474
2475
InternalSoldierReadyWeapon(SOLDIERTYPE * pSoldier,UINT8 sFacingDir,BOOLEAN fEndReady)2476 BOOLEAN InternalSoldierReadyWeapon( SOLDIERTYPE *pSoldier, UINT8 sFacingDir, BOOLEAN fEndReady )
2477 {
2478 UINT16 usAnimState;
2479 BOOLEAN fReturnVal = FALSE;
2480
2481 // Handle monsters differently
2482 if (pSoldier->uiStatusFlags & SOLDIER_MONSTER)
2483 {
2484 if ( !fEndReady )
2485 {
2486 EVENT_SetSoldierDesiredDirection( pSoldier, sFacingDir );
2487 }
2488 return( FALSE );
2489 }
2490
2491 usAnimState = PickSoldierReadyAnimation( pSoldier, fEndReady );
2492
2493 if ( usAnimState != INVALID_ANIMATION )
2494 {
2495 EVENT_InitNewSoldierAnim( pSoldier, usAnimState, 0 , FALSE );
2496 fReturnVal = TRUE;
2497 }
2498
2499 if ( !fEndReady )
2500 {
2501 // Ready direction for new facing direction
2502 if ( usAnimState == INVALID_ANIMATION )
2503 {
2504 usAnimState = pSoldier->usAnimState;
2505 }
2506
2507 EVENT_InternalSetSoldierDesiredDirection( pSoldier, sFacingDir, FALSE, usAnimState );
2508
2509 // Check if facing dir is different from ours and change direction if so!
2510 //if ( sFacingDir != pSoldier->bDirection )
2511 //{
2512 // DeductPoints( pSoldier, AP_CHANGE_FACING, 0 );
2513 //}//
2514
2515 }
2516
2517 return( fReturnVal );
2518 }
2519
2520
PickSoldierReadyAnimation(SOLDIERTYPE * pSoldier,BOOLEAN fEndReady)2521 UINT16 PickSoldierReadyAnimation(SOLDIERTYPE* pSoldier, BOOLEAN fEndReady)
2522 {
2523
2524 // Invalid animation if nothing in our hands
2525 if ( pSoldier->inv[ HANDPOS ].usItem == NOTHING )
2526 {
2527 return( INVALID_ANIMATION );
2528 }
2529
2530 if ( pSoldier->bOverTerrainType == DEEP_WATER )
2531 {
2532 return( INVALID_ANIMATION );
2533 }
2534
2535 if ( pSoldier->ubBodyType == ROBOTNOWEAPON )
2536 {
2537 return( INVALID_ANIMATION );
2538 }
2539
2540 // Check if we have a gun.....
2541 if ( GCM->getItem(pSoldier->inv[ HANDPOS ].usItem)->getItemClass() != IC_GUN &&
2542 pSoldier->inv[ HANDPOS ].usItem != GLAUNCHER )
2543 {
2544 return( INVALID_ANIMATION );
2545 }
2546
2547 if ( pSoldier->inv[ HANDPOS ].usItem == ROCKET_LAUNCHER )
2548 {
2549 return( INVALID_ANIMATION );
2550 }
2551
2552 if ( pSoldier->ubBodyType == TANK_NW || pSoldier->ubBodyType == TANK_NE )
2553 {
2554 return( INVALID_ANIMATION );
2555 }
2556
2557 if ( fEndReady )
2558 {
2559 // IF our gun is already drawn, do not change animation, just direction
2560 if ( gAnimControl[ pSoldier->usAnimState ].uiFlags & ( ANIM_FIREREADY | ANIM_FIRE ))
2561 {
2562
2563 switch ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
2564 {
2565 case ANIM_STAND:
2566
2567 // CHECK 2ND HAND!
2568 if ( IsValidSecondHandShot( pSoldier ) )
2569 {
2570 return( END_DUAL_STAND );
2571 }
2572 else
2573 {
2574 return( END_RIFLE_STAND );
2575 }
2576
2577 case ANIM_PRONE:
2578
2579 if ( IsValidSecondHandShot( pSoldier ) )
2580 {
2581 return( END_DUAL_PRONE );
2582 }
2583 else
2584 {
2585 return( END_RIFLE_PRONE );
2586 }
2587
2588 case ANIM_CROUCH:
2589
2590 // CHECK 2ND HAND!
2591 if ( IsValidSecondHandShot( pSoldier ) )
2592 {
2593 return( END_DUAL_CROUCH );
2594 }
2595 else
2596 {
2597 return( END_RIFLE_CROUCH );
2598 }
2599
2600 }
2601
2602 }
2603 }
2604 else
2605 {
2606 // IF our gun is already drawn, do not change animation, just direction
2607 if ( !(gAnimControl[ pSoldier->usAnimState ].uiFlags & ( ANIM_FIREREADY | ANIM_FIRE ) ) )
2608 {
2609 switch ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
2610 {
2611 case ANIM_STAND:
2612
2613 // CHECK 2ND HAND!
2614 if ( IsValidSecondHandShot( pSoldier ) )
2615 {
2616 return( READY_DUAL_STAND );
2617 }
2618 else
2619 {
2620 return( READY_RIFLE_STAND );
2621 }
2622
2623 case ANIM_PRONE:
2624 // Go into crouch, turn, then go into prone again
2625 //ChangeSoldierStance( pSoldier, ANIM_CROUCH );
2626 //pSoldier->ubDesiredHeight = ANIM_PRONE;
2627 //ChangeSoldierState( pSoldier, PRONE_UP );
2628 if ( IsValidSecondHandShot( pSoldier ) )
2629 {
2630 return( READY_DUAL_PRONE );
2631 }
2632 else
2633 {
2634 return( READY_RIFLE_PRONE );
2635 }
2636
2637 case ANIM_CROUCH:
2638
2639 // CHECK 2ND HAND!
2640 if ( IsValidSecondHandShot( pSoldier ) )
2641 {
2642 return( READY_DUAL_CROUCH );
2643 }
2644 else
2645 {
2646 return( READY_RIFLE_CROUCH );
2647 }
2648 }
2649 }
2650 }
2651
2652 return( INVALID_ANIMATION );
2653 }
2654
2655
2656 static UINT8 CalcScreamVolume(SOLDIERTYPE* pSoldier, UINT8 ubCombinedLoss);
2657 static UINT32 SleepDartSuccumbChance(const SOLDIERTYPE* pSoldier);
2658 static void SoldierGotHitBlade(SOLDIERTYPE* pSoldier);
2659 static void SoldierGotHitExplosion(SOLDIERTYPE* pSoldier, UINT16 usWeaponIndex, UINT16 bDirection, UINT16 sRange);
2660 static void SoldierGotHitGunFire(SOLDIERTYPE* pSoldier, UINT16 bDirection, SOLDIERTYPE* att, UINT8 ubSpecial);
2661 static void SoldierGotHitPunch(SOLDIERTYPE* pSoldier);
2662
2663
2664 // ATE: THIS FUNCTION IS USED FOR ALL SOLDIER TAKE DAMAGE FUNCTIONS!
EVENT_SoldierGotHit(SOLDIERTYPE * pSoldier,const UINT16 usWeaponIndex,INT16 sDamage,INT16 sBreathLoss,const UINT16 bDirection,const UINT16 sRange,SOLDIERTYPE * const att,const UINT8 ubSpecial,const UINT8 ubHitLocation,const INT16 sLocationGrid)2665 void EVENT_SoldierGotHit(SOLDIERTYPE* pSoldier, const UINT16 usWeaponIndex, INT16 sDamage, INT16 sBreathLoss, const UINT16 bDirection, const UINT16 sRange, SOLDIERTYPE* const att, const UINT8 ubSpecial, const UINT8 ubHitLocation, const INT16 sLocationGrid)
2666 {
2667 UINT8 ubCombinedLoss, ubVolume, ubReason;
2668 SOLDIERTYPE *pNewSoldier;
2669
2670 ubReason = 0;
2671
2672 // ATE: If we have gotten hit, but are still in our attack animation, reduce count!
2673 switch ( pSoldier->usAnimState )
2674 {
2675 case SHOOT_ROCKET:
2676 case SHOOT_MORTAR:
2677 case THROW_ITEM:
2678 case LOB_ITEM:
2679 SLOGD(
2680 "Freeing up attacker - ATTACK ANIMATION %s ENDED BY HIT ANIMATION, Now %d",
2681 gAnimControl[pSoldier->usAnimState].zAnimStr, gTacticalStatus.ubAttackBusyCount);
2682 ReduceAttackBusyCount(pSoldier, FALSE);
2683 break;
2684 }
2685
2686 // DO STUFF COMMON FOR ALL TYPES
2687 if (att != NULL) att->bLastAttackHit = TRUE;
2688
2689 pSoldier->attacker = att;
2690
2691 if ( !( pSoldier->uiStatusFlags & SOLDIER_VEHICLE ) )
2692 {
2693 // Increment being attacked count
2694 pSoldier->bBeingAttackedCount++;
2695 }
2696
2697 // if defender is a vehicle, there will be no hit animation played!
2698 if ( !( pSoldier->uiStatusFlags & SOLDIER_VEHICLE ) )
2699 {
2700 // Increment the number of people busy doing stuff because of an attack (busy doing hit anim!)
2701 gTacticalStatus.ubAttackBusyCount++;
2702 SLOGD("Person got hit, attack count now %d",
2703 gTacticalStatus.ubAttackBusyCount);
2704 }
2705
2706 // ATE; Save hit location info...( for later anim determination stuff )
2707 pSoldier->ubHitLocation = ubHitLocation;
2708
2709 // handle morale for heavy damage attacks
2710 if ( sDamage > 25 )
2711 {
2712 if (att != NULL)
2713 {
2714 if (att->bTeam == OUR_TEAM)
2715 {
2716 HandleMoraleEvent(att, MORALE_DID_LOTS_OF_DAMAGE,
2717 att->sSectorX, att->sSectorY, att->bSectorZ);
2718 }
2719 }
2720 if (pSoldier->bTeam == OUR_TEAM)
2721 {
2722 HandleMoraleEvent(pSoldier, MORALE_TOOK_LOTS_OF_DAMAGE,
2723 pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ);
2724 }
2725 }
2726
2727 // SWITCH IN TYPE OF WEAPON
2728 if ( ubSpecial == FIRE_WEAPON_TOSSED_OBJECT_SPECIAL )
2729 {
2730 ubReason = TAKE_DAMAGE_OBJECT;
2731 }
2732 else if ( GCM->getItem(usWeaponIndex)->isTentacles() )
2733 {
2734 ubReason = TAKE_DAMAGE_TENTACLES;
2735 }
2736 else if ( GCM->getItem(usWeaponIndex)->getItemClass() & ( IC_GUN | IC_THROWING_KNIFE ) )
2737 {
2738 if ( ubSpecial == FIRE_WEAPON_SLEEP_DART_SPECIAL )
2739 {
2740 UINT32 uiChance;
2741
2742 // put the drug in!
2743 pSoldier->bSleepDrugCounter = 10;
2744
2745 uiChance = SleepDartSuccumbChance( pSoldier );
2746
2747 if ( PreRandom( 100 ) < uiChance )
2748 {
2749 // succumb to the drug!
2750 sBreathLoss = (INT16)( pSoldier->bBreathMax * 100 );
2751 }
2752
2753 }
2754 else if ( ubSpecial == FIRE_WEAPON_BLINDED_BY_SPIT_SPECIAL )
2755 {
2756 // blinded!!
2757 if ( pSoldier->bBlindedCounter == 0 )
2758 {
2759 // say quote
2760 if (pSoldier->uiStatusFlags & SOLDIER_PC)
2761 {
2762 TacticalCharacterDialogue( pSoldier, QUOTE_BLINDED );
2763 }
2764 DecayIndividualOpplist( pSoldier );
2765 }
2766 // will always increase counter by at least 1
2767 pSoldier->bBlindedCounter += (sDamage / 8) + 1;
2768
2769 // Dirty panel
2770 fInterfacePanelDirty = DIRTYLEVEL2;
2771 }
2772 sBreathLoss += BP_GET_HIT;
2773 ubReason = TAKE_DAMAGE_GUNFIRE;
2774 }
2775 else if ( GCM->getItem(usWeaponIndex)->isBlade() )
2776 {
2777 sBreathLoss = BP_GET_HIT;
2778 ubReason = TAKE_DAMAGE_BLADE;
2779 }
2780 else if ( GCM->getItem(usWeaponIndex)->isPunch() )
2781 {
2782 // damage from hand-to-hand is 1/4 normal, 3/4 breath.. the sDamage value
2783 // is actually how much breath we'll take away
2784 sBreathLoss = sDamage * 100;
2785 sDamage = sDamage / PUNCH_REAL_DAMAGE_PORTION;
2786 if ( AreInMeanwhile() && gCurrentMeanwhileDef.ubMeanwhileID == INTERROGATION )
2787 {
2788 sBreathLoss = 0;
2789 sDamage /= 2;
2790 }
2791 ubReason = TAKE_DAMAGE_HANDTOHAND;
2792 }
2793 else if ( GCM->getItem(usWeaponIndex)->isExplosive() )
2794 {
2795 if ( usWeaponIndex == STRUCTURE_EXPLOSION )
2796 {
2797 ubReason = TAKE_DAMAGE_STRUCTURE_EXPLOSION;
2798 }
2799 else
2800 {
2801 ubReason = TAKE_DAMAGE_EXPLOSION;
2802 }
2803 }
2804 else
2805 {
2806 SLOGW("Soldier Control: Weapon class not handled in SoldierGotHit( ) %d",
2807 usWeaponIndex);
2808 }
2809
2810
2811 // CJC: moved to after SoldierTakeDamage so that any quotes from the defender
2812 // will not be said if they are knocked out or killed
2813 if ( ubReason != TAKE_DAMAGE_TENTACLES && ubReason != TAKE_DAMAGE_OBJECT )
2814 {
2815 // OK, OK: THis is hairy, however, it's ness. because the normal freeup call uses the
2816 // attckers intended target, and here we want to use thier actual target....
2817
2818 // ATE: If it's from GUNFIRE damage, keep in mind bullets...
2819 if ( GCM->getItem(usWeaponIndex)->isGun())
2820 {
2821 pNewSoldier = FreeUpAttackerGivenTarget(pSoldier);
2822 }
2823 else
2824 {
2825 pNewSoldier = ReduceAttackBusyGivenTarget(pSoldier);
2826 }
2827
2828 if (pNewSoldier != NULL)
2829 {
2830 pSoldier = pNewSoldier;
2831 }
2832 SLOGD("Tried to free up attacker, attack count now %d",
2833 gTacticalStatus.ubAttackBusyCount);
2834 }
2835
2836
2837 // OK, If we are a vehicle.... damage vehicle...( people inside... )
2838 if ( pSoldier->uiStatusFlags & SOLDIER_VEHICLE )
2839 {
2840 SoldierTakeDamage(pSoldier, sDamage, sBreathLoss, ubReason, att);
2841 return;
2842 }
2843
2844 // DEDUCT LIFE
2845 ubCombinedLoss = SoldierTakeDamage(pSoldier, sDamage, sBreathLoss, ubReason, att);
2846
2847 // ATE: OK, Let's check our ASSIGNMENT state,
2848 // If anything other than on a squad or guard, make them guard....
2849 if ( pSoldier->bTeam == OUR_TEAM )
2850 {
2851 if ( pSoldier->bAssignment >= ON_DUTY && pSoldier->bAssignment != ASSIGNMENT_POW )
2852 {
2853 if( pSoldier->fMercAsleep )
2854 {
2855 pSoldier->fMercAsleep = FALSE;
2856 pSoldier -> fForcedToStayAwake = FALSE;
2857
2858 // refresh map screen
2859 fCharacterInfoPanelDirty = TRUE;
2860 fTeamPanelDirty = TRUE;
2861 }
2862
2863 AddCharacterToAnySquad( pSoldier );
2864 }
2865 }
2866
2867
2868 // SCREAM!!!!
2869 ubVolume = CalcScreamVolume( pSoldier, ubCombinedLoss );
2870
2871 // IF WE ARE AT A HIT_STOP ANIMATION
2872 // DO APPROPRIATE HITWHILE DOWN ANIMATION
2873 if ( !( gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_HITSTOP )
2874 || pSoldier->usAnimState != JFK_HITDEATH_STOP )
2875 {
2876 MakeNoise(pSoldier, pSoldier->sGridNo, pSoldier->bLevel, ubVolume, NOISE_SCREAM);
2877 }
2878
2879 // IAN ADDED THIS SAT JUNE 14th : HAVE TO SHOW VICTIM!
2880 if ((gTacticalStatus.uiFlags & INCOMBAT) &&
2881 pSoldier->bVisible != -1 && pSoldier->bTeam == OUR_TEAM)
2882 {
2883 LocateSoldier(pSoldier, DONTSETLOCATOR);
2884 }
2885
2886
2887 if ( GCM->getItem(usWeaponIndex)->isBlade() )
2888 {
2889 PlayLocationJA2Sample(pSoldier->sGridNo, KNIFE_IMPACT, MIDVOLUME, 1);
2890 }
2891 else
2892 {
2893 PlayLocationJA2Sample(pSoldier->sGridNo, SoundRange<BULLET_IMPACT_1, BULLET_IMPACT_3>(), MIDVOLUME, 1);
2894 }
2895
2896 // PLAY RANDOM GETTING HIT SOUND
2897 // ONLY IF WE ARE CONSCIOUS!
2898 if ( pSoldier->bLife >= CONSCIOUSNESS )
2899 {
2900 if ( pSoldier->ubBodyType == CROW )
2901 {
2902 // Exploding crow...
2903 PlayLocationJA2Sample(pSoldier->sGridNo, CROW_EXPLODE_1, HIGHVOLUME, 1);
2904 }
2905 else
2906 {
2907 // ATE: This is to disallow large amounts of smaples being played which is load!
2908 if ( pSoldier->fGettingHit && pSoldier->usAniCode != STANDING_BURST_HIT )
2909 {
2910
2911 }
2912 else
2913 {
2914 DoMercBattleSound(pSoldier, BATTLE_SOUND_HIT1);
2915 }
2916 }
2917 }
2918
2919 // CHECK FOR DOING HIT WHILE DOWN
2920 if ( ( gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_HITSTOP ) )
2921 {
2922 UINT16 state;
2923 switch (pSoldier->usAnimState)
2924 {
2925 case FLYBACKHIT_STOP: state = FALLBACK_DEATHTWICH; break;
2926 case STAND_FALLFORWARD_STOP: state = GENERIC_HIT_DEATHTWITCHNB; break;
2927 case JFK_HITDEATH_STOP: state = JFK_HITDEATH_TWITCHB; break;
2928 case FALLBACKHIT_STOP: state = FALLBACK_HIT_DEATHTWITCHNB; break;
2929 case PRONE_LAYFROMHIT_STOP: state = PRONE_HIT_DEATHTWITCHNB; break;
2930 case PRONE_HITDEATH_STOP: state = PRONE_HIT_DEATHTWITCHB; break;
2931 case FALLFORWARD_HITDEATH_STOP: state = GENERIC_HIT_DEATHTWITCHB; break;
2932 case FALLBACK_HITDEATH_STOP: state = FALLBACK_HIT_DEATHTWITCHB; break;
2933 case FALLOFF_DEATH_STOP: state = FALLOFF_TWITCHB; break;
2934 case FALLOFF_STOP: state = FALLOFF_TWITCHNB; break;
2935 case FALLOFF_FORWARD_DEATH_STOP: state = FALLOFF_FORWARD_TWITCHB; break;
2936 case FALLOFF_FORWARD_STOP: state = FALLOFF_FORWARD_TWITCHNB; break;
2937
2938 default:
2939 SLOGD("Death state %d has no death hit",
2940 pSoldier->usAnimState);
2941 return;
2942 }
2943 ChangeSoldierState(pSoldier, state, 0, FALSE);
2944 return;
2945 }
2946
2947 // Set goback to aim after hit flag!
2948 // Only if we were aiming!
2949 if ( gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_FIREREADY )
2950 {
2951 pSoldier->fGoBackToAimAfterHit = TRUE;
2952 }
2953
2954 // IF COWERING, PLAY SPECIFIC GENERIC HIT STAND...
2955 if ( pSoldier->uiStatusFlags & SOLDIER_COWERING )
2956 {
2957 if ( pSoldier->bLife == 0 || IS_MERC_BODY_TYPE( pSoldier ) )
2958 {
2959 EVENT_InitNewSoldierAnim( pSoldier, GENERIC_HIT_STAND, 0 , FALSE );
2960 }
2961 else
2962 {
2963 EVENT_InitNewSoldierAnim( pSoldier, CIV_COWER_HIT, 0 , FALSE );
2964 }
2965 return;
2966 }
2967
2968 // Change based on body type
2969 switch( pSoldier->ubBodyType )
2970 {
2971 case COW:
2972 EVENT_InitNewSoldierAnim( pSoldier, COW_HIT, 0 , FALSE );
2973 return;
2974
2975 case BLOODCAT:
2976 EVENT_InitNewSoldierAnim( pSoldier, BLOODCAT_HIT, 0 , FALSE );
2977 return;
2978
2979 case ADULTFEMALEMONSTER:
2980 case AM_MONSTER:
2981 case YAF_MONSTER:
2982 case YAM_MONSTER:
2983
2984 EVENT_InitNewSoldierAnim( pSoldier, ADULTMONSTER_HIT, 0 , FALSE );
2985 return;
2986
2987 case LARVAE_MONSTER:
2988 EVENT_InitNewSoldierAnim( pSoldier, LARVAE_HIT, 0 , FALSE );
2989 return;
2990
2991 case QUEENMONSTER:
2992 EVENT_InitNewSoldierAnim( pSoldier, QUEEN_HIT, 0 , FALSE );
2993 return;
2994
2995 case CRIPPLECIV:
2996
2997 {
2998 // OK, do some code here to allow the fact that poor buddy can be
2999 // thrown back if it's a big enough hit...
3000 EVENT_InitNewSoldierAnim( pSoldier, CRIPPLE_HIT, 0 , FALSE );
3001
3002 //pSoldier->bLife = 0;
3003 //EVENT_InitNewSoldierAnim( pSoldier, CRIPPLE_DIE_FLYBACK, 0 , FALSE );
3004
3005
3006 }
3007 return;
3008
3009 case ROBOTNOWEAPON:
3010 EVENT_InitNewSoldierAnim( pSoldier, ROBOTNW_HIT, 0 , FALSE );
3011 return;
3012
3013
3014 case INFANT_MONSTER:
3015 EVENT_InitNewSoldierAnim( pSoldier, INFANT_HIT, 0 , FALSE );
3016 return;
3017
3018 case CROW:
3019
3020 EVENT_InitNewSoldierAnim( pSoldier, CROW_DIE, 0 , FALSE );
3021 return;
3022
3023 //case FATCIV:
3024 case MANCIV:
3025 case MINICIV:
3026 case DRESSCIV:
3027 case HATKIDCIV:
3028 case KIDCIV:
3029
3030 // OK, if life is 0 and not set as dead ( this is a death hit... )
3031 if ( !( pSoldier->uiStatusFlags & SOLDIER_DEAD ) && pSoldier->bLife == 0 )
3032 {
3033 // Randomize death!
3034 if ( Random( 2 ) )
3035 {
3036 EVENT_InitNewSoldierAnim( pSoldier, CIV_DIE2, 0 , FALSE );
3037 return;
3038 }
3039 }
3040
3041 // IF here, go generic hit ALWAYS.....
3042 EVENT_InitNewSoldierAnim( pSoldier, GENERIC_HIT_STAND, 0 , FALSE );
3043 return;
3044 }
3045
3046 // If here, we are a merc, check if we are in water
3047 if ( pSoldier->bOverTerrainType == LOW_WATER )
3048 {
3049 EVENT_InitNewSoldierAnim( pSoldier, WATER_HIT, 0 , FALSE );
3050 return;
3051 }
3052 if ( pSoldier->bOverTerrainType == DEEP_WATER )
3053 {
3054 EVENT_InitNewSoldierAnim( pSoldier, DEEP_WATER_HIT, 0 , FALSE );
3055 return;
3056 }
3057
3058
3059 // SWITCH IN TYPE OF WEAPON
3060 if ( GCM->getItem(usWeaponIndex)->getItemClass() & ( IC_GUN | IC_THROWING_KNIFE ) )
3061 {
3062 SoldierGotHitGunFire(pSoldier, bDirection, att, ubSpecial);
3063 }
3064 if ( GCM->getItem(usWeaponIndex)->isBlade() )
3065 {
3066 SoldierGotHitBlade(pSoldier);
3067 }
3068 if ( GCM->getItem(usWeaponIndex)->isExplosive() || GCM->getItem(usWeaponIndex)->isTentacles() )
3069 {
3070 SoldierGotHitExplosion(pSoldier, usWeaponIndex, bDirection, sRange);
3071 }
3072 if ( GCM->getItem(usWeaponIndex)->isPunch() )
3073 {
3074 SoldierGotHitPunch(pSoldier);
3075 }
3076 }
3077
3078
CalcScreamVolume(SOLDIERTYPE * pSoldier,UINT8 ubCombinedLoss)3079 static UINT8 CalcScreamVolume(SOLDIERTYPE* pSoldier, UINT8 ubCombinedLoss)
3080 {
3081 // NB explosions are so loud they should drown out screams
3082 UINT8 ubVolume;
3083
3084 if (ubCombinedLoss < 1)
3085 {
3086 ubVolume = 1;
3087 }
3088 else
3089 {
3090 ubVolume = ubCombinedLoss;
3091 }
3092
3093 // Victim yells out in pain, making noise. Yelps are louder from greater
3094 // wounds, but softer for more experienced soldiers.
3095
3096 if (ubVolume > (10 - EffectiveExpLevel( pSoldier ) ))
3097 {
3098 ubVolume = 10 - EffectiveExpLevel( pSoldier );
3099 }
3100
3101 /*
3102 // the "Speck factor"... He's a whiner, and extra-sensitive to pain!
3103 if (ptr->trait == NERVOUS)
3104 ubVolume += 2;*/
3105
3106 #if 0 /* XXX unsigned < 0 ? */
3107 if (ubVolume < 0)
3108 {
3109 ubVolume = 0;
3110 }
3111 #endif
3112
3113 return( ubVolume );
3114 }
3115
3116
3117 static BOOLEAN SoldierCarriesTwoHandedWeapon(SOLDIERTYPE* pSoldier);
3118
3119
DoGenericHit(SOLDIERTYPE * pSoldier,UINT8 ubSpecial,INT16 bDirection)3120 static void DoGenericHit(SOLDIERTYPE* pSoldier, UINT8 ubSpecial, INT16 bDirection)
3121 {
3122 // Based on stance, select generic hit animation
3123 switch ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
3124 {
3125 case ANIM_STAND:
3126 // For now, check if we are affected by a burst
3127 // For now, if the weapon was a gun, special 1 == burst
3128 // ATE: Only do this for mercs!
3129 if ( ubSpecial == FIRE_WEAPON_BURST_SPECIAL && pSoldier->ubBodyType <= REGFEMALE )
3130 {
3131 //SetSoldierDesiredDirection( pSoldier, bDirection );
3132 EVENT_SetSoldierDirection( pSoldier, (INT8)bDirection );
3133 EVENT_SetSoldierDesiredDirection( pSoldier, pSoldier->bDirection );
3134
3135 EVENT_InitNewSoldierAnim( pSoldier, STANDING_BURST_HIT, 0 , FALSE );
3136 }
3137 else
3138 {
3139 // Check in hand for rifle
3140 if ( SoldierCarriesTwoHandedWeapon( pSoldier ) )
3141 {
3142 EVENT_InitNewSoldierAnim( pSoldier, RIFLE_STAND_HIT, 0 , FALSE );
3143 }
3144 else
3145 {
3146 EVENT_InitNewSoldierAnim( pSoldier, GENERIC_HIT_STAND, 0 , FALSE );
3147 }
3148 }
3149 break;
3150
3151 case ANIM_PRONE:
3152
3153 EVENT_InitNewSoldierAnim( pSoldier, GENERIC_HIT_PRONE, 0 , FALSE );
3154 break;
3155
3156 case ANIM_CROUCH:
3157 EVENT_InitNewSoldierAnim( pSoldier, GENERIC_HIT_CROUCH, 0 , FALSE );
3158 break;
3159
3160 }
3161 }
3162
3163
3164 static void ChangeToFlybackAnimation(SOLDIERTYPE* pSoldier, INT8 bDirection);
3165
3166
SoldierGotHitGunFire(SOLDIERTYPE * const pSoldier,const UINT16 bDirection,SOLDIERTYPE * const att,const UINT8 ubSpecial)3167 static void SoldierGotHitGunFire(SOLDIERTYPE* const pSoldier, const UINT16 bDirection, SOLDIERTYPE* const att, const UINT8 ubSpecial)
3168 {
3169 UINT16 usNewGridNo;
3170 BOOLEAN fBlownAway = FALSE;
3171 BOOLEAN fHeadHit = FALSE;
3172 BOOLEAN fFallenOver = FALSE;
3173
3174 // MAYBE CHANGE TO SPECIAL ANIMATION BASED ON VALUE SET BY DAMAGE CALCULATION CODE
3175 // ALL THESE ONLY WORK ON STANDING PEOPLE
3176 if (!(pSoldier->uiStatusFlags & SOLDIER_MONSTER) &&
3177 gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND)
3178 {
3179 if (gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND )
3180 {
3181 if (ubSpecial == FIRE_WEAPON_HEAD_EXPLODE_SPECIAL)
3182 {
3183 if ( gGameSettings.fOptions[ TOPTION_BLOOD_N_GORE ] )
3184 {
3185 if (SpacesAway(pSoldier->sGridNo, att->sGridNo) <= MAX_DISTANCE_FOR_MESSY_DEATH)
3186 {
3187 usNewGridNo = NewGridNo( (UINT16)pSoldier->sGridNo, DirectionInc( pSoldier->bDirection ) );
3188
3189 // CHECK OK DESTINATION!
3190 if ( OKFallDirection( pSoldier, usNewGridNo, pSoldier->bLevel, pSoldier->bDirection, JFK_HITDEATH ) )
3191 {
3192 usNewGridNo = NewGridNo( (UINT16)usNewGridNo, DirectionInc( pSoldier->bDirection ) );
3193
3194 if ( OKFallDirection( pSoldier, usNewGridNo, pSoldier->bLevel, pSoldier->bDirection, pSoldier->usAnimState ) )
3195 {
3196 fHeadHit = TRUE;
3197 }
3198 }
3199 }
3200 }
3201 }
3202 else if (ubSpecial == FIRE_WEAPON_CHEST_EXPLODE_SPECIAL)
3203 {
3204 if ( gGameSettings.fOptions[ TOPTION_BLOOD_N_GORE ] )
3205 {
3206 if (SpacesAway(pSoldier->sGridNo, att->sGridNo) <= MAX_DISTANCE_FOR_MESSY_DEATH)
3207 {
3208
3209 // possibly play torso explosion anim!
3210 if (pSoldier->bDirection == bDirection)
3211 {
3212 const UINT8 opp_dir = OppositeDirection(bDirection);
3213 usNewGridNo = NewGridNo(pSoldier->sGridNo, DirectionInc(opp_dir));
3214
3215 if (OKFallDirection(pSoldier, usNewGridNo, pSoldier->bLevel, opp_dir, FLYBACK_HIT))
3216 {
3217 usNewGridNo = NewGridNo(usNewGridNo, DirectionInc(opp_dir));
3218
3219 if (OKFallDirection(pSoldier, usNewGridNo, pSoldier->bLevel, opp_dir, pSoldier->usAnimState))
3220 {
3221 fBlownAway = TRUE;
3222 }
3223 }
3224 }
3225 }
3226 }
3227 }
3228 else if (ubSpecial == FIRE_WEAPON_LEG_FALLDOWN_SPECIAL)
3229 {
3230 // possibly play fall over anim!
3231 // this one is NOT restricted by distance
3232 if (IsValidStance( pSoldier, ANIM_PRONE ) )
3233 {
3234 // Can't be in water, or not standing
3235 if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND && !MercInWater( pSoldier ) )
3236 {
3237 fFallenOver = TRUE;
3238 ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, st_format_printf(gzLateLocalizedString[STR_LATE_20], pSoldier->name));
3239 }
3240 }
3241 }
3242 }
3243 }
3244
3245 // IF HERE AND GUY IS DEAD, RETURN!
3246 if ( pSoldier->uiStatusFlags & SOLDIER_DEAD )
3247 {
3248 SLOGD("Releasesoldierattacker, Dead soldier hit");
3249 ReleaseSoldiersAttacker( pSoldier );
3250 return;
3251 }
3252
3253 if ( fFallenOver )
3254 {
3255 SoldierCollapse( pSoldier );
3256 return;
3257 }
3258
3259 if ( fBlownAway )
3260 {
3261 // Only for mercs...
3262 if (pSoldier->ubBodyType <= REGFEMALE)
3263 {
3264 ChangeToFlybackAnimation( pSoldier, (INT8)bDirection );
3265 return;
3266 }
3267 }
3268
3269 if ( fHeadHit )
3270 {
3271 // Only for mercs ( or KIDS! )
3272 if (pSoldier->ubBodyType <= REGFEMALE || pSoldier->ubBodyType == HATKIDCIV || pSoldier->ubBodyType == KIDCIV)
3273 {
3274 EVENT_InitNewSoldierAnim( pSoldier, JFK_HITDEATH, 0 , FALSE );
3275 return;
3276 }
3277 }
3278
3279 DoGenericHit( pSoldier, ubSpecial, bDirection );
3280 }
3281
3282
SoldierGotHitExplosion(SOLDIERTYPE * const pSoldier,const UINT16 usWeaponIndex,const UINT16 bDirection,const UINT16 sRange)3283 static void SoldierGotHitExplosion(SOLDIERTYPE* const pSoldier, const UINT16 usWeaponIndex, const UINT16 bDirection, const UINT16 sRange)
3284 {
3285 INT16 sNewGridNo;
3286
3287 // IF HERE AND GUY IS DEAD, RETURN!
3288 if ( pSoldier->uiStatusFlags & SOLDIER_DEAD )
3289 {
3290 return;
3291 }
3292
3293 //check for services
3294 ReceivingSoldierCancelServices( pSoldier );
3295 GivingSoldierCancelServices( pSoldier );
3296
3297
3298 if ( gGameSettings.fOptions[ TOPTION_BLOOD_N_GORE ] )
3299 {
3300 if ( Explosive[ GCM->getItem(usWeaponIndex)->getClassIndex() ].ubRadius >= 3 &&
3301 pSoldier->bLife == 0 && gAnimControl[ pSoldier->usAnimState ].ubEndHeight != ANIM_PRONE )
3302 {
3303 if ( sRange >= 2 && sRange <= 4 )
3304 {
3305 DoMercBattleSound(pSoldier, BATTLE_SOUND_HIT1);
3306
3307 EVENT_InitNewSoldierAnim( pSoldier, CHARIOTS_OF_FIRE, 0 , FALSE );
3308 return;
3309 }
3310 else if ( sRange <= 1 )
3311 {
3312 DoMercBattleSound(pSoldier, BATTLE_SOUND_HIT1);
3313
3314 EVENT_InitNewSoldierAnim( pSoldier, BODYEXPLODING, 0 , FALSE );
3315 return;
3316 }
3317 }
3318 }
3319
3320 // If we can't fal back or such, so generic hit...
3321 if (pSoldier->ubBodyType > REGFEMALE)
3322 {
3323 DoGenericHit( pSoldier, 0, bDirection );
3324 return;
3325 }
3326
3327 // Based on stance, select generic hit animation
3328 switch ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
3329 {
3330 case ANIM_STAND:
3331 case ANIM_CROUCH:
3332
3333 EVENT_SetSoldierDirection( pSoldier, (INT8)bDirection );
3334 EVENT_SetSoldierDesiredDirection( pSoldier, pSoldier->bDirection );
3335
3336 // Check behind us!
3337 sNewGridNo = NewGridNo(pSoldier->sGridNo, DirectionInc(OppositeDirection(bDirection)));
3338
3339 if (OKFallDirection(pSoldier, sNewGridNo, pSoldier->bLevel, OppositeDirection(bDirection), FLYBACK_HIT))
3340 {
3341 ChangeToFallbackAnimation( pSoldier, (INT8)bDirection );
3342 }
3343 else
3344 {
3345 if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND )
3346 {
3347 BeginTyingToFall( pSoldier );
3348 EVENT_InitNewSoldierAnim( pSoldier, FALLFORWARD_FROMHIT_STAND, 0, FALSE );
3349 }
3350 else
3351 {
3352 SoldierCollapse( pSoldier );
3353 }
3354 }
3355 break;
3356
3357 case ANIM_PRONE:
3358
3359 SoldierCollapse( pSoldier );
3360 break;
3361 }
3362 }
3363
3364
SoldierGotHitBlade(SOLDIERTYPE * const pSoldier)3365 static void SoldierGotHitBlade(SOLDIERTYPE* const pSoldier)
3366 {
3367 // IF HERE AND GUY IS DEAD, RETURN!
3368 if ( pSoldier->uiStatusFlags & SOLDIER_DEAD )
3369 {
3370 return;
3371 }
3372
3373
3374 // Based on stance, select generic hit animation
3375 switch ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
3376 {
3377 case ANIM_STAND:
3378
3379 // Check in hand for rifle
3380 if ( SoldierCarriesTwoHandedWeapon( pSoldier ) )
3381 {
3382 EVENT_InitNewSoldierAnim( pSoldier, RIFLE_STAND_HIT, 0 , FALSE );
3383 }
3384 else
3385 {
3386 EVENT_InitNewSoldierAnim( pSoldier, GENERIC_HIT_STAND, 0 , FALSE );
3387 }
3388 break;
3389
3390 case ANIM_CROUCH:
3391 EVENT_InitNewSoldierAnim( pSoldier, GENERIC_HIT_CROUCH, 0 , FALSE );
3392 break;
3393
3394 case ANIM_PRONE:
3395 EVENT_InitNewSoldierAnim( pSoldier, GENERIC_HIT_PRONE, 0 , FALSE );
3396 break;
3397 }
3398 }
3399
3400
SoldierGotHitPunch(SOLDIERTYPE * const pSoldier)3401 static void SoldierGotHitPunch(SOLDIERTYPE* const pSoldier)
3402 {
3403
3404 // IF HERE AND GUY IS DEAD, RETURN!
3405 if ( pSoldier->uiStatusFlags & SOLDIER_DEAD )
3406 {
3407 return;
3408 }
3409
3410 // Based on stance, select generic hit animation
3411 switch ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
3412 {
3413 case ANIM_STAND:
3414 // Check in hand for rifle
3415 if ( SoldierCarriesTwoHandedWeapon( pSoldier ) )
3416 {
3417 EVENT_InitNewSoldierAnim( pSoldier, RIFLE_STAND_HIT, 0 , FALSE );
3418 }
3419 else
3420 {
3421 EVENT_InitNewSoldierAnim( pSoldier, GENERIC_HIT_STAND, 0 , FALSE );
3422 }
3423 break;
3424
3425 case ANIM_CROUCH:
3426 EVENT_InitNewSoldierAnim( pSoldier, GENERIC_HIT_CROUCH, 0 , FALSE );
3427 break;
3428
3429 case ANIM_PRONE:
3430 EVENT_InitNewSoldierAnim( pSoldier, GENERIC_HIT_PRONE, 0 , FALSE );
3431 break;
3432
3433 }
3434
3435 }
3436
3437
EVENT_InternalGetNewSoldierPath(SOLDIERTYPE * pSoldier,UINT16 sDestGridNo,UINT16 usMovementAnim,BOOLEAN fFromUI,BOOLEAN fForceRestartAnim)3438 BOOLEAN EVENT_InternalGetNewSoldierPath( SOLDIERTYPE *pSoldier, UINT16 sDestGridNo, UINT16 usMovementAnim, BOOLEAN fFromUI, BOOLEAN fForceRestartAnim )
3439 {
3440 INT32 iDest;
3441 BOOLEAN fContinue;
3442 UINT32 uiDist;
3443 UINT16 usAnimState;
3444 UINT16 usMoveAnimState = usMovementAnim;
3445 INT16 sMercGridNo;
3446 UINT8 ubPathingData[MAX_PATH_LIST_SIZE];
3447 //UINT8 ubPathingMaxDirection;
3448 BOOLEAN fAdvancePath = TRUE;
3449 UINT8 fFlags = 0;
3450
3451 // Ifd this code, make true if a player
3452 if ( fFromUI == 3 )
3453 {
3454 if ( pSoldier->bTeam == OUR_TEAM )
3455 {
3456 fFromUI = 1;
3457 }
3458 else
3459 {
3460 fFromUI = 0;
3461 }
3462 }
3463
3464 // ATE: if a civ, and from UI, and were cowering, remove from cowering
3465 if ( AM_AN_EPC( pSoldier ) && fFromUI )
3466 {
3467 if ( pSoldier->uiStatusFlags & SOLDIER_COWERING )
3468 {
3469 SetSoldierCowerState( pSoldier, FALSE );
3470 usMoveAnimState = WALKING;
3471 }
3472 }
3473
3474
3475 pSoldier->bGoodContPath = FALSE;
3476
3477 if ( pSoldier->fDelayedMovement )
3478 {
3479 if ( pSoldier->ubDelayedMovementFlags & DELAYED_MOVEMENT_FLAG_PATH_THROUGH_PEOPLE )
3480 {
3481 fFlags = PATH_THROUGH_PEOPLE;
3482 }
3483 else
3484 {
3485 fFlags = PATH_IGNORE_PERSON_AT_DEST;
3486 }
3487 pSoldier->fDelayedMovement = FALSE;
3488 }
3489
3490 if ( gfGetNewPathThroughPeople )
3491 {
3492 fFlags = PATH_THROUGH_PEOPLE;
3493 }
3494
3495 // ATE: Some stuff here for realtime, going through interface....
3496 if ((!( gTacticalStatus.uiFlags & INCOMBAT) &&
3497 ( gAnimControl[pSoldier->usAnimState].uiFlags & ANIM_MOVING) && fFromUI == 1)
3498 || fFromUI == 2)
3499 {
3500 if ( pSoldier->bCollapsed )
3501 {
3502 return( FALSE );
3503 }
3504
3505 sMercGridNo = pSoldier->sGridNo;
3506 pSoldier->sGridNo = pSoldier->sDestination;
3507
3508 // Check if path is good before copying it into guy's path...
3509 if ( FindBestPath( pSoldier, sDestGridNo, pSoldier->bLevel, pSoldier->usUIMovementMode, NO_COPYROUTE, fFlags ) == 0 )
3510 {
3511 // Set to old....
3512 pSoldier->sGridNo = sMercGridNo;
3513
3514 return( FALSE );
3515 }
3516
3517 uiDist = FindBestPath( pSoldier, sDestGridNo, pSoldier->bLevel, pSoldier->usUIMovementMode, COPYROUTE, fFlags );
3518
3519 pSoldier->sGridNo = sMercGridNo;
3520 pSoldier->sFinalDestination = sDestGridNo;
3521
3522 if ( uiDist > 0 )
3523 {
3524 // Add one to path data size....
3525 if ( fAdvancePath )
3526 {
3527 memcpy( ubPathingData, pSoldier->ubPathingData, sizeof( ubPathingData ) );
3528 //ubPathingMaxDirection = (UINT8)ubPathingData[ MAX_PATH_LIST_SIZE -1 ];
3529 memcpy( &(pSoldier->ubPathingData[1]), ubPathingData, sizeof( ubPathingData ) - sizeof( ubPathingData[0] ) );
3530
3531 // If we have reach the max, go back one sFinalDest....
3532 if ( pSoldier->ubPathDataSize == MAX_PATH_LIST_SIZE )
3533 {
3534 //pSoldier->sFinalDestination = NewGridNo(pSoldier->sFinalDestination, DirectionInc(OppositeDirection(ubPathingMaxDirection)));
3535 }
3536 else
3537 {
3538 pSoldier->ubPathDataSize++;
3539 }
3540 }
3541
3542 usMoveAnimState = pSoldier->usUIMovementMode;
3543
3544 if ( pSoldier->bOverTerrainType == DEEP_WATER )
3545 {
3546 usMoveAnimState = DEEP_WATER_SWIM;
3547 }
3548
3549 // Change animation only.... set value to NOT call any goto new gridno stuff.....
3550 if ( usMoveAnimState != pSoldier->usAnimState )
3551 {
3552 //
3553 pSoldier->usDontUpdateNewGridNoOnMoveAnimChange = TRUE;
3554
3555 EVENT_InitNewSoldierAnim( pSoldier, usMoveAnimState, 0, FALSE );
3556 }
3557
3558 return( TRUE );
3559 }
3560
3561 return( FALSE );
3562 }
3563
3564 // we can use the soldier's level here because we don't have pathing across levels right now...
3565 if (pSoldier->bPathStored)
3566 {
3567 fContinue = TRUE;
3568 }
3569 else
3570 {
3571 iDest = FindBestPath( pSoldier, sDestGridNo, pSoldier->bLevel, usMovementAnim, COPYROUTE, fFlags );
3572 fContinue = (iDest != 0);
3573 }
3574
3575 // Only if we can get a path here
3576 if ( fContinue )
3577 {
3578 // Debug messages
3579 SLOGD("Soldier %d: Get new path", pSoldier->ubID);
3580
3581 // Set final destination
3582 pSoldier->sFinalDestination = sDestGridNo;
3583 pSoldier->fPastXDest = 0;
3584 pSoldier->fPastYDest = 0;
3585
3586 // If true, we're OK, if not, WAIT for a guy to pass!
3587 // If we are in deep water, we can only swim!
3588 if ( pSoldier->bOverTerrainType == DEEP_WATER )
3589 {
3590 usMoveAnimState = DEEP_WATER_SWIM;
3591 }
3592
3593 // If we were aiming, end aim!
3594 usAnimState = PickSoldierReadyAnimation( pSoldier, TRUE );
3595
3596 // Add a pending animation first!
3597 // Only if we were standing!
3598 if ( usAnimState != INVALID_ANIMATION && gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND )
3599 {
3600 EVENT_InitNewSoldierAnim( pSoldier, usAnimState, 0, FALSE );
3601 pSoldier->usPendingAnimation = usMoveAnimState;
3602 }
3603 else
3604 {
3605 // Call local copy for change soldier state!
3606 EVENT_InitNewSoldierAnim( pSoldier, usMoveAnimState, 0, fForceRestartAnim );
3607
3608 }
3609
3610 // Change desired direction
3611 // ATE: Here we have a situation where in RT, we may have
3612 // gotten a new path, but we are alreayd moving.. so
3613 // at leasty change new dest. This will be redundent if the ANI is a totaly new one
3614
3615 return( TRUE );
3616 }
3617
3618 return( FALSE );
3619 }
3620
EVENT_GetNewSoldierPath(SOLDIERTYPE * pSoldier,UINT16 sDestGridNo,UINT16 usMovementAnim)3621 void EVENT_GetNewSoldierPath( SOLDIERTYPE *pSoldier, UINT16 sDestGridNo, UINT16 usMovementAnim )
3622 {
3623 // ATE: Default restart of animation to TRUE
3624 EVENT_InternalGetNewSoldierPath( pSoldier, sDestGridNo, usMovementAnim, FALSE, TRUE );
3625 }
3626
3627 // Change our state based on stance, to stop!
StopSoldier(SOLDIERTYPE * pSoldier)3628 void StopSoldier( SOLDIERTYPE *pSoldier )
3629 {
3630 ReceivingSoldierCancelServices( pSoldier );
3631 GivingSoldierCancelServices( pSoldier );
3632
3633 if ( !( gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_STATIONARY ) )
3634 {
3635 //SoldierGotoStationaryStance( pSoldier );
3636 EVENT_StopMerc(pSoldier);
3637 }
3638
3639 // Set desination
3640 pSoldier->sFinalDestination = pSoldier->sGridNo;
3641
3642 }
3643
SoldierGotoStationaryStance(SOLDIERTYPE * pSoldier)3644 void SoldierGotoStationaryStance( SOLDIERTYPE *pSoldier )
3645 {
3646 // ATE: This is to turn off fast movement, that us used to change movement mode
3647 // for ui display on stance changes....
3648 if ( pSoldier->bTeam == OUR_TEAM )
3649 {
3650 //pSoldier->fUIMovementFast = FALSE;
3651 }
3652
3653 // The queen, if she sees anybody, goes to ready, not normal breath....
3654 if ( pSoldier->ubBodyType == QUEENMONSTER )
3655 {
3656 if ( pSoldier->bOppCnt > 0 || pSoldier->bTeam == OUR_TEAM )
3657 {
3658 EVENT_InitNewSoldierAnim( pSoldier, QUEEN_READY, 0 , TRUE );
3659 return;
3660 }
3661 }
3662
3663 // Check if we are in deep water!
3664 if ( pSoldier->bOverTerrainType == DEEP_WATER )
3665 {
3666 // IN deep water, tred!
3667 EVENT_InitNewSoldierAnim( pSoldier, DEEP_WATER_TRED, 0 , FALSE );
3668 }
3669 else if (pSoldier->service_partner != NULL && pSoldier->bLife >= OKLIFE && pSoldier->bBreath > 0)
3670 {
3671 EVENT_InitNewSoldierAnim( pSoldier, GIVING_AID, 0 , FALSE );
3672 }
3673 else
3674 {
3675 // Change state back to stationary state for given height
3676 switch( gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
3677 {
3678 case ANIM_STAND:
3679
3680 // If we are cowering....goto cower state
3681 if ( pSoldier->uiStatusFlags & SOLDIER_COWERING )
3682 {
3683 EVENT_InitNewSoldierAnim( pSoldier, START_COWER, 0 , FALSE );
3684 }
3685 else
3686 {
3687 EVENT_InitNewSoldierAnim( pSoldier, STANDING, 0 , FALSE );
3688 }
3689 break;
3690
3691 case ANIM_CROUCH:
3692
3693 // If we are cowering....goto cower state
3694 if ( pSoldier->uiStatusFlags & SOLDIER_COWERING )
3695 {
3696 EVENT_InitNewSoldierAnim( pSoldier, COWERING, 0 , FALSE );
3697 }
3698 else
3699 {
3700 EVENT_InitNewSoldierAnim( pSoldier, CROUCHING, 0 , FALSE );
3701 }
3702 break;
3703
3704 case ANIM_PRONE:
3705 EVENT_InitNewSoldierAnim( pSoldier, PRONE, 0 , FALSE );
3706 break;
3707 }
3708
3709 }
3710
3711 }
3712
3713
3714 static UINT16 GetNewSoldierStateFromNewStance(SOLDIERTYPE* pSoldier, UINT8 ubDesiredStance);
3715
3716
ChangeSoldierStance(SOLDIERTYPE * pSoldier,UINT8 ubDesiredStance)3717 void ChangeSoldierStance( SOLDIERTYPE *pSoldier, UINT8 ubDesiredStance )
3718 {
3719 UINT16 usNewState;
3720
3721 // Check if they are the same!
3722 if ( ubDesiredStance == gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
3723 {
3724 // Free up from stance change
3725 FreeUpNPCFromStanceChange( pSoldier );
3726 return;
3727 }
3728
3729 // Set UI Busy
3730 SetUIBusy(pSoldier);
3731
3732 // ATE: If we are an NPC, cower....
3733 if ( pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= KIDCIV )
3734 {
3735 if ( ubDesiredStance == ANIM_STAND )
3736 {
3737 SetSoldierCowerState( pSoldier, FALSE );
3738 }
3739 else
3740 {
3741 SetSoldierCowerState( pSoldier, TRUE );
3742 }
3743 }
3744 else
3745 {
3746 usNewState = GetNewSoldierStateFromNewStance( pSoldier, ubDesiredStance );
3747
3748 // Set desired stance
3749 pSoldier->ubDesiredHeight = ubDesiredStance;
3750
3751 // Now change to appropriate animation
3752 EVENT_InitNewSoldierAnim( pSoldier, usNewState, 0 , FALSE );
3753 }
3754 }
3755
EVENT_InternalSetSoldierDestination(SOLDIERTYPE * pSoldier,UINT16 usNewDirection,BOOLEAN fFromMove,UINT16 usAnimState)3756 void EVENT_InternalSetSoldierDestination( SOLDIERTYPE *pSoldier, UINT16 usNewDirection, BOOLEAN fFromMove, UINT16 usAnimState )
3757 {
3758 UINT16 usNewGridNo;
3759 INT16 sXPos, sYPos;
3760
3761 // Get dest gridno, convert to center coords
3762 usNewGridNo = NewGridNo( (UINT16)pSoldier->sGridNo, DirectionInc( usNewDirection ) );
3763
3764 ConvertGridNoToCenterCellXY(usNewGridNo, &sXPos, &sYPos);
3765
3766 // Save new dest gridno, x, y
3767 pSoldier->sDestination = usNewGridNo;
3768 pSoldier->sDestXPos = sXPos;
3769 pSoldier->sDestYPos = sYPos;
3770
3771 pSoldier->bMovementDirection = (INT8)usNewDirection;
3772
3773
3774 // OK, ATE: If we are side_stepping, calculate a NEW desired direction....
3775 if ( pSoldier->bReverse && usAnimState == SIDE_STEP )
3776 {
3777 UINT8 ubPerpDirection;
3778
3779 // Get a new desired direction,
3780 ubPerpDirection = gPurpendicularDirection[ pSoldier->bDirection ][ usNewDirection ];
3781
3782 // CHange actual and desired direction....
3783 EVENT_SetSoldierDirection( pSoldier, ubPerpDirection );
3784 pSoldier->bDesiredDirection = pSoldier->bDirection;
3785 }
3786 else
3787 {
3788 if ( !( gAnimControl[ usAnimState ].uiFlags & ANIM_SPECIALMOVE ) )
3789 {
3790 EVENT_InternalSetSoldierDesiredDirection( pSoldier, usNewDirection, fFromMove, usAnimState );
3791 }
3792 }
3793 }
3794
3795
3796 // function to determine which direction a creature can turn in
MultiTiledTurnDirection(SOLDIERTYPE * pSoldier,INT8 bStartDirection,INT8 bDesiredDirection)3797 static INT8 MultiTiledTurnDirection(SOLDIERTYPE* pSoldier, INT8 bStartDirection, INT8 bDesiredDirection)
3798 {
3799 INT8 bTurningIncrement;
3800 INT8 bCurrentDirection;
3801 INT8 bLoop;
3802 UINT16 usStructureID, usAnimSurface;
3803 BOOLEAN fOk = FALSE;
3804
3805 // start by trying to turn in quickest direction
3806 bTurningIncrement = (INT8) QuickestDirection( bStartDirection, bDesiredDirection );
3807
3808 usAnimSurface = DetermineSoldierAnimationSurface( pSoldier, pSoldier->usUIMovementMode );
3809
3810 const STRUCTURE_FILE_REF* const pStructureFileRef = GetAnimationStructureRef(pSoldier, usAnimSurface, pSoldier->usUIMovementMode);
3811 if ( !pStructureFileRef )
3812 {
3813 // without structure data, well, assume quickest direction
3814 return( bTurningIncrement );
3815 }
3816
3817 // ATE: Only if we have a levelnode...
3818 if ( pSoldier->pLevelNode != NULL && pSoldier->pLevelNode->pStructureData != NULL )
3819 {
3820 usStructureID = pSoldier->pLevelNode->pStructureData->usStructureID;
3821 }
3822 else
3823 {
3824 usStructureID = INVALID_STRUCTURE_ID;
3825 }
3826
3827 bLoop = 0;
3828 bCurrentDirection = bStartDirection;
3829
3830 while( bLoop < 2 )
3831 {
3832 while( bCurrentDirection != bDesiredDirection )
3833 {
3834 bCurrentDirection += bTurningIncrement;
3835
3836 // did we wrap directions?
3837 if ( bCurrentDirection < 0 )
3838 {
3839 bCurrentDirection = (MAXDIR - 1);
3840 }
3841 else if ( bCurrentDirection >= MAXDIR )
3842 {
3843 bCurrentDirection = 0;
3844 }
3845
3846 // check to see if we can add creature in that direction
3847 fOk = OkayToAddStructureToWorld(pSoldier->sGridNo, pSoldier->bLevel, &pStructureFileRef->pDBStructureRef[OneCDirection(bCurrentDirection)], usStructureID);
3848 if (!fOk)
3849 {
3850 break;
3851 }
3852 }
3853
3854 if ( (bCurrentDirection == bDesiredDirection) && fOk )
3855 {
3856 // success!!
3857 return( bTurningIncrement );
3858 }
3859
3860 bLoop++;
3861 if ( bLoop < 2 )
3862 {
3863 // change direction of loop etc
3864 bCurrentDirection = bStartDirection;
3865 bTurningIncrement *= -1;
3866 }
3867 }
3868 // nothing found... doesn't matter much what we return
3869 return( bTurningIncrement );
3870 }
3871
3872
EVENT_InternalSetSoldierDesiredDirection(SOLDIERTYPE * const pSoldier,UINT16 usNewDirection,const BOOLEAN fInitalMove,const UINT16 usAnimState)3873 static void EVENT_InternalSetSoldierDesiredDirection(SOLDIERTYPE* const pSoldier, UINT16 usNewDirection, const BOOLEAN fInitalMove, const UINT16 usAnimState)
3874 {
3875 //if ( usAnimState == WALK_BACKWARDS )
3876 if (pSoldier->bReverse && usAnimState != SIDE_STEP) // XXX TODO0014
3877 {
3878 // OK, check if we are going to go in the exact opposite than our facing....
3879 usNewDirection = OppositeDirection(usNewDirection);
3880 }
3881
3882
3883 pSoldier->bDesiredDirection = (INT8)usNewDirection;
3884
3885 // If we are prone, goto crouched first!
3886 // ONly if we are stationary, and only if directions are differnet!
3887
3888 // ATE: If we are fNoAPsToFinnishMove, stop what we were doing and
3889 // reset flag.....
3890 if ( pSoldier->fNoAPToFinishMove && ( gAnimControl[ usAnimState ].uiFlags & ANIM_MOVING ) )
3891 {
3892 // ATE; Commented this out: NEVER, EVER, start a new anim from this function, as
3893 // an eternal loop will result....
3894 //SoldierGotoStationaryStance( pSoldier );
3895 // Reset flag!
3896 AdjustNoAPToFinishMove( pSoldier, FALSE );
3897 }
3898
3899 if ( pSoldier->bDesiredDirection != pSoldier->bDirection )
3900 {
3901 if (gAnimControl[usAnimState].uiFlags & (ANIM_BREATH | ANIM_OK_CHARGE_AP_FOR_TURN | ANIM_FIREREADY) &&
3902 !fInitalMove && !pSoldier->fDontChargeTurningAPs)
3903 {
3904 // Deduct points for initial turn!
3905 switch( gAnimControl[ usAnimState ].ubEndHeight )
3906 {
3907 // Now change to appropriate animation
3908 case ANIM_STAND:
3909 DeductPoints( pSoldier, AP_LOOK_STANDING, 0 );
3910 break;
3911
3912 case ANIM_CROUCH:
3913 DeductPoints( pSoldier, AP_LOOK_CROUCHED, 0 );
3914 break;
3915
3916 case ANIM_PRONE:
3917 DeductPoints( pSoldier, AP_LOOK_PRONE, 0 );
3918 break;
3919 }
3920
3921 }
3922
3923 pSoldier->fDontChargeTurningAPs = FALSE;
3924
3925 if ( fInitalMove )
3926 {
3927 if ( gAnimControl[ usAnimState ].ubHeight == ANIM_PRONE )
3928 {
3929 if ( pSoldier->fTurningFromPronePosition != TURNING_FROM_PRONE_ENDING_UP_FROM_MOVE )
3930 {
3931 pSoldier->fTurningFromPronePosition = TURNING_FROM_PRONE_START_UP_FROM_MOVE;
3932 }
3933 }
3934 }
3935
3936 if ( gAnimControl[ usAnimState ].uiFlags & ANIM_STATIONARY || pSoldier->fNoAPToFinishMove || fInitalMove )
3937 {
3938 if ( gAnimControl[ usAnimState ].ubHeight == ANIM_PRONE )
3939 {
3940 // Set this beasty of a flag to allow us to go back down to prone if we choose!
3941 // ATE: Alrighty, set flag to go back down only if we are not moving anywhere
3942 //if ( pSoldier->sDestination == pSoldier->sGridNo )
3943 if ( !fInitalMove )
3944 {
3945 pSoldier->fTurningFromPronePosition = TURNING_FROM_PRONE_ON;
3946
3947 // Set a pending animation to change stance first...
3948 ChangeSoldierStance(pSoldier, ANIM_CROUCH);
3949 }
3950 }
3951 }
3952 }
3953
3954 if ( pSoldier->bDesiredDirection != pSoldier->bDirection )
3955 {
3956 if ( pSoldier->uiStatusFlags & ( SOLDIER_VEHICLE ) || CREATURE_OR_BLOODCAT( pSoldier ) )
3957 {
3958 pSoldier->uiStatusFlags |= SOLDIER_PAUSEANIMOVE;
3959 }
3960 }
3961
3962
3963 if ( pSoldier->uiStatusFlags & SOLDIER_VEHICLE )
3964 {
3965 const UINT8 hires_desired_dir = Dir2ExtDir(pSoldier->bDesiredDirection);
3966 pSoldier->bTurningIncrement = ExtQuickestDirection(pSoldier->ubHiResDirection, hires_desired_dir);
3967 }
3968 else
3969 {
3970 if ( pSoldier->uiStatusFlags & SOLDIER_MULTITILE )
3971 {
3972 pSoldier->bTurningIncrement = (INT8) MultiTiledTurnDirection( pSoldier, pSoldier->bDirection, pSoldier->bDesiredDirection );
3973 }
3974 else
3975 {
3976 pSoldier->bTurningIncrement = (INT8) QuickestDirection( pSoldier->bDirection, pSoldier->bDesiredDirection );
3977 }
3978 }
3979
3980 }
3981
3982
EVENT_SetSoldierDesiredDirection(SOLDIERTYPE * pSoldier,UINT16 usNewDirection)3983 void EVENT_SetSoldierDesiredDirection( SOLDIERTYPE *pSoldier, UINT16 usNewDirection )
3984 {
3985 EVENT_InternalSetSoldierDesiredDirection( pSoldier, usNewDirection, FALSE, pSoldier->usAnimState );
3986 }
3987
3988
EVENT_SetSoldierDesiredDirectionForward(SOLDIERTYPE * const s,const UINT16 new_direction)3989 void EVENT_SetSoldierDesiredDirectionForward(SOLDIERTYPE* const s, const UINT16 new_direction)
3990 {
3991 s->bReverse = FALSE; // XXX TODO0014
3992 EVENT_SetSoldierDesiredDirection(s, new_direction);
3993 }
3994
3995
3996 static void AdjustForFastTurnAnimation(SOLDIERTYPE* pSoldier);
3997
3998
EVENT_SetSoldierDirection(SOLDIERTYPE * pSoldier,UINT16 usNewDirection)3999 void EVENT_SetSoldierDirection( SOLDIERTYPE *pSoldier, UINT16 usNewDirection )
4000 {
4001 // Remove old location data
4002 HandleAnimationProfile(*pSoldier, pSoldier->usAnimState, TRUE);
4003
4004 pSoldier->bDirection = (INT8)usNewDirection;
4005
4006 // Updated extended direction.....
4007 pSoldier->ubHiResDirection = Dir2ExtDir(pSoldier->bDirection);
4008
4009 // Add new stuff
4010 HandleAnimationProfile(*pSoldier, pSoldier->usAnimState, FALSE);
4011
4012 // If we are turning, we have chaanged our aim!
4013 if ( !pSoldier->fDontUnsetLastTargetFromTurn )
4014 {
4015 pSoldier->sLastTarget = NOWHERE;
4016 }
4017
4018 AdjustForFastTurnAnimation( pSoldier );
4019
4020 // Update structure info!
4021 //if ( pSoldier->uiStatusFlags & SOLDIER_MULTITILE )
4022 {
4023 UpdateMercStructureInfo( pSoldier );
4024 }
4025
4026 // Handle Profile data for hit locations
4027 HandleAnimationProfile(*pSoldier, pSoldier->usAnimState, TRUE);
4028
4029 HandleCrowShadowNewDirection( pSoldier );
4030
4031 // Change values!
4032 SetSoldierLocatorOffsets( pSoldier );
4033
4034 }
4035
4036
4037 static INT32 CheckBleeding(SOLDIERTYPE* pSoldier);
4038
4039
EVENT_BeginMercTurn(SOLDIERTYPE & s)4040 void EVENT_BeginMercTurn(SOLDIERTYPE& s)
4041 {
4042 // UnderFire now starts at 2 for "under fire this turn", down to 1 for "under
4043 // fire last turn", to 0
4044 if (s.bUnderFire != 0) --s.bUnderFire;
4045
4046 // ATE: Add decay effect for drugs
4047 HandleEndTurnDrugAdjustments(&s);
4048
4049 // ATE: Don't bleed if in auto bandage!
4050 if (!gTacticalStatus.fAutoBandageMode)
4051 {
4052 // Blood is not for the weak of heart, or mechanical
4053 if (!(s.uiStatusFlags & (SOLDIER_VEHICLE | SOLDIER_ROBOT)))
4054 {
4055 if (s.bBleeding != 0 || s.bLife < OKLIFE) // is he bleeding or dying?
4056 {
4057 INT32 const blood = CheckBleeding(&s); // check if he might lose another life point
4058 // ATE: Only if in sector
4059 if (blood != NOBLOOD && s.bInSector)
4060 {
4061 DropBlood(s, blood);
4062 }
4063 }
4064 }
4065 }
4066
4067 if (s.bLife == 0) return;
4068 // He is still alive (didn't bleed to death)
4069
4070 // Reduce the effects of any residual shock from past injuries by half
4071 s.bShock /= 2;
4072
4073 // If this person has heard a noise that hasn't been investigated
4074 if (s.sNoiseGridno != NOWHERE && s.ubNoiseVolume != 0)
4075 {
4076 // The volume of the noise "decays" by 1 point
4077 if (--s.ubNoiseVolume == 0)
4078 {
4079 // The volume has reached zero, forget about the noise
4080 s.sNoiseGridno = NOWHERE;
4081 }
4082 }
4083
4084 if (s.uiStatusFlags & SOLDIER_GASSED)
4085 {
4086 // Must get a gas mask or leave the gassed area to get over it
4087 if (IsWearingHeadGear(s, GASMASK) ||
4088 GetSmokeEffectOnTile(s.sGridNo, s.bLevel) == NO_SMOKE_EFFECT)
4089 {
4090 // Turn off gassed flag
4091 s.uiStatusFlags &= ~SOLDIER_GASSED;
4092 }
4093 }
4094
4095 if (s.bBlindedCounter > 0 && --s.bBlindedCounter == 0)
4096 {
4097 HandleSight(s, SIGHT_LOOK); // We can see
4098 fInterfacePanelDirty = DIRTYLEVEL2; // Dirty panel
4099 }
4100
4101 s.sWeightCarriedAtTurnStart = CalculateCarriedWeight(&s);
4102
4103 UnusedAPsToBreath(&s);
4104
4105 // Set flag back to normal, after reaching a certain statge
4106 if (s.bBreath > 80) s.usQuoteSaidFlags &= ~SOLDIER_QUOTE_SAID_LOW_BREATH;
4107 if (s.bBreath > 50) s.usQuoteSaidFlags &= ~SOLDIER_QUOTE_SAID_DROWNING;
4108
4109 if (s.ubTurnsUntilCanSayHeardNoise > 0) --s.ubTurnsUntilCanSayHeardNoise;
4110
4111 if (s.bInSector) CheckForBreathCollapse(s);
4112
4113 CalcNewActionPoints(&s);
4114
4115 s.bTilesMoved = 0;
4116
4117 if (s.bInSector)
4118 {
4119 BeginSoldierGetup(&s);
4120
4121 // CJC Nov 30: handle RT opplist decaying in another function which operates less often
4122 if (gTacticalStatus.uiFlags & INCOMBAT)
4123 {
4124 VerifyAndDecayOpplist(&s);
4125 if (s.uiXRayActivatedTime != 0) TurnOffXRayEffects(&s);
4126 }
4127
4128 if (s.bTeam == OUR_TEAM && s.ubProfile != NO_PROFILE)
4129 {
4130 switch (GetProfile(s.ubProfile).bPersonalityTrait)
4131 {
4132 case FEAR_OF_INSECTS:
4133 if (MercSeesCreature(s))
4134 {
4135 HandleMoraleEvent(&s, MORALE_INSECT_PHOBIC_SEES_CREATURE, s.sSectorX, s.sSectorY, s.bSectorZ);
4136 goto say_personality_quote;
4137 }
4138 break;
4139
4140 case CLAUSTROPHOBIC:
4141 if (gbWorldSectorZ > 0 && Random(6 - gbWorldSectorZ) == 0)
4142 {
4143 HandleMoraleEvent(&s, MORALE_CLAUSTROPHOBE_UNDERGROUND, s.sSectorX, s.sSectorY, s.bSectorZ);
4144 goto say_personality_quote;
4145 }
4146 break;
4147
4148 case NERVOUS:
4149 if (DistanceToClosestFriend(&s) > NERVOUS_RADIUS)
4150 {
4151 if (s.bMorale < 50)
4152 {
4153 HandleMoraleEvent(&s, MORALE_NERVOUS_ALONE, s.sSectorX, s.sSectorY, s.bSectorZ);
4154 goto say_personality_quote;
4155 }
4156 }
4157 else
4158 {
4159 if (s.bMorale > 45)
4160 { // Turn flag off, so that we say it every two turns
4161 s.usQuoteSaidFlags &= ~SOLDIER_QUOTE_SAID_PERSONALITY;
4162 }
4163 }
4164 break;
4165
4166 say_personality_quote:
4167 if (!(s.usQuoteSaidFlags & SOLDIER_QUOTE_SAID_PERSONALITY))
4168 {
4169 TacticalCharacterDialogue(&s, QUOTE_PERSONALITY_TRAIT);
4170 s.usQuoteSaidFlags |= SOLDIER_QUOTE_SAID_PERSONALITY;
4171 }
4172 break;
4173 }
4174 }
4175 }
4176
4177 // Reset quote flags for under heavy fire and close call
4178 s.usQuoteSaidFlags &= ~SOLDIER_QUOTE_SAID_BEING_PUMMELED;
4179 s.usQuoteSaidExtFlags &= ~SOLDIER_QUOTE_SAID_EXT_CLOSE_CALL;
4180 s.bNumHitsThisTurn = 0;
4181 s.ubSuppressionPoints = 0;
4182 s.fCloseCall = FALSE;
4183 s.ubMovementNoiseHeard = 0;
4184
4185 // If soldier has new APs, reset flags
4186 if (s.bActionPoints > 0)
4187 {
4188 s.bMoved = FALSE;
4189 s.bPassedLastInterrupt = FALSE;
4190 }
4191 }
4192
4193
ConvertAniCodeToAniFrame(SOLDIERTYPE * const s,UINT16 ani_frame)4194 BOOLEAN ConvertAniCodeToAniFrame(SOLDIERTYPE* const s, UINT16 ani_frame)
4195 {
4196 static UINT8 const gDirectionFrom8to2[] = { 0, 0, 1, 1, 0, 1, 1, 0 };
4197
4198 // Given ani code, adjust for facing direction
4199
4200 // get anim surface and determine # of frames
4201 UINT16 const anim_surface = GetSoldierAnimationSurface(s);
4202 CHECKF(anim_surface != INVALID_ANIMATION_SURFACE);
4203 AnimationSurfaceType const& as = gAnimSurfaceDatabase[anim_surface];
4204
4205 // Convert world direction into sprite direction
4206 UINT8 temp_dir = OneCDirection(s->bDirection);
4207
4208 // Check # of directions/surface, adjust if ness.
4209 switch (as.uiNumDirections)
4210 {
4211 case 1: temp_dir = 0; break;
4212 case 4: temp_dir /= 2; break;
4213 case 32: temp_dir = ExtOneCDirection(s->ubHiResDirection); break;
4214
4215 case 2:
4216 temp_dir = gDirectionFrom8to2[s->bDirection];
4217 break;
4218
4219 case 3:
4220 switch (s->bDirection)
4221 {
4222 case NORTHWEST: temp_dir = 1; break;
4223 case WEST: temp_dir = 0; break;
4224 case EAST: temp_dir = 2; break;
4225 }
4226 break;
4227 }
4228
4229 // If we are only one frame, ignore what the script is telling us!
4230 if (as.ubFlags & ANIM_DATA_FLAG_NOFRAMES) ani_frame = 0;
4231
4232 if (!as.hVideoObject)
4233 {
4234 ani_frame = 0;
4235 }
4236 else
4237 {
4238 ani_frame += as.uiNumFramesPerDir * temp_dir;
4239 if (ani_frame >= as.hVideoObject->SubregionCount())
4240 {
4241 // Debug msg here....
4242 SLOGW(
4243 "Wrong Number of frames per number of objects: %d vs %d, %s",
4244 as.uiNumFramesPerDir, as.hVideoObject->SubregionCount(),
4245 gAnimControl[s->usAnimState].zAnimStr);
4246 ani_frame = 0;
4247 }
4248 }
4249
4250 s->usAniFrame = ani_frame;
4251 return TRUE;
4252 }
4253
4254
TurnSoldier(SOLDIERTYPE * pSoldier)4255 void TurnSoldier( SOLDIERTYPE *pSoldier)
4256 {
4257 INT16 sDirection;
4258
4259 // If we are a vehicle... DON'T TURN!
4260 if ( pSoldier->uiStatusFlags & SOLDIER_VEHICLE )
4261 {
4262 if ( pSoldier->ubBodyType != TANK_NW && pSoldier->ubBodyType != TANK_NE )
4263 {
4264 return;
4265 }
4266 }
4267
4268 // We handle sight now....
4269 if ( pSoldier->uiStatusFlags & SOLDIER_LOOK_NEXT_TURNSOLDIER )
4270 {
4271 if ((gAnimControl[pSoldier->usAnimState].uiFlags & ANIM_STATIONARY &&
4272 pSoldier->usAnimState != CLIMBUPROOF && pSoldier->usAnimState != CLIMBDOWNROOF))
4273 {
4274 // HANDLE SIGHT!
4275 HandleSight(*pSoldier, SIGHT_LOOK | SIGHT_RADIO);
4276 }
4277 // Turn off!
4278 pSoldier->uiStatusFlags &= (~SOLDIER_LOOK_NEXT_TURNSOLDIER );
4279
4280 HandleSystemNewAISituation(pSoldier);
4281 }
4282
4283
4284 if ( pSoldier->fTurningToShoot )
4285 {
4286 if ( pSoldier->bDirection == pSoldier->bDesiredDirection )
4287 {
4288 if (((gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_FIREREADY ) &&
4289 !pSoldier->fTurningFromPronePosition ) || pSoldier->ubBodyType == ROBOTNOWEAPON ||
4290 pSoldier->ubBodyType == TANK_NW || pSoldier->ubBodyType == TANK_NE)
4291 {
4292 EVENT_InitNewSoldierAnim( pSoldier, SelectFireAnimation( pSoldier, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ), 0, FALSE );
4293 pSoldier->fTurningToShoot = FALSE;
4294
4295 // Save last target gridno!
4296 //pSoldier->sLastTarget = pSoldier->sTargetGridNo;
4297
4298 }
4299 // Else check if we are trying to shoot and once was prone, but am now crouched
4300 // because we needed to turn...
4301 else if ( pSoldier->fTurningFromPronePosition )
4302 {
4303 if ( IsValidStance( pSoldier, ANIM_PRONE ) )
4304 {
4305 ChangeSoldierStance(pSoldier, ANIM_PRONE);
4306 pSoldier->usPendingAnimation = SelectFireAnimation( pSoldier, ANIM_PRONE );
4307 }
4308 else
4309 {
4310 EVENT_InitNewSoldierAnim( pSoldier, SelectFireAnimation( pSoldier, ANIM_CROUCH ), 0, FALSE );
4311 }
4312 pSoldier->fTurningToShoot = FALSE;
4313 pSoldier->fTurningFromPronePosition = TURNING_FROM_PRONE_OFF;
4314 }
4315 }
4316 }
4317
4318 if ( pSoldier->fTurningUntilDone && ( pSoldier->ubPendingStanceChange != NO_PENDING_STANCE ) )
4319 {
4320 if ( pSoldier->bDirection == pSoldier->bDesiredDirection )
4321 {
4322 ChangeSoldierStance(pSoldier, pSoldier->ubPendingStanceChange);
4323 pSoldier->ubPendingStanceChange = NO_PENDING_STANCE;
4324 pSoldier->fTurningUntilDone = FALSE;
4325 }
4326 }
4327
4328 if ( pSoldier->fTurningUntilDone && ( pSoldier->usPendingAnimation != NO_PENDING_ANIMATION ) )
4329 {
4330 if ( pSoldier->bDirection == pSoldier->bDesiredDirection )
4331 {
4332 UINT16 usPendingAnimation;
4333
4334 usPendingAnimation = pSoldier->usPendingAnimation;
4335 pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
4336
4337 EVENT_InitNewSoldierAnim( pSoldier, usPendingAnimation, 0 , FALSE );
4338 pSoldier->fTurningUntilDone = FALSE;
4339 }
4340 }
4341
4342 // Don't do anything if we are at dest direction!
4343 if ( pSoldier->bDirection == pSoldier->bDesiredDirection )
4344 {
4345 if ( pSoldier->ubBodyType == TANK_NW || pSoldier->ubBodyType == TANK_NE )
4346 {
4347 if ( pSoldier->uiTuringSoundID != NO_SAMPLE )
4348 {
4349 SoundStop( pSoldier->uiTuringSoundID );
4350 pSoldier->uiTuringSoundID = NO_SAMPLE;
4351
4352 PlaySoldierJA2Sample(pSoldier, TURRET_STOP, HIGHVOLUME, 1, TRUE);
4353 }
4354 }
4355
4356 // Turn off!
4357 pSoldier->uiStatusFlags &= (~SOLDIER_LOOK_NEXT_TURNSOLDIER );
4358 pSoldier->fDontUnsetLastTargetFromTurn = FALSE;
4359
4360 // Unset ui busy if from ui
4361 if ( pSoldier->bTurningFromUI && ( pSoldier->fTurningFromPronePosition != 3 ) && ( pSoldier->fTurningFromPronePosition != 1 ) )
4362 {
4363 UnSetUIBusy(pSoldier);
4364 pSoldier->bTurningFromUI = FALSE;
4365 }
4366
4367 if ( pSoldier->uiStatusFlags & ( SOLDIER_VEHICLE ) || CREATURE_OR_BLOODCAT( pSoldier ) )
4368 {
4369 pSoldier->uiStatusFlags &= (~SOLDIER_PAUSEANIMOVE);
4370 }
4371
4372 FreeUpNPCFromTurning(pSoldier);
4373
4374 // Undo our flag for prone turning...
4375 // Else check if we are trying to shoot and once was prone, but am now crouched
4376 // because we needed to turn...
4377 if ( pSoldier->fTurningFromPronePosition == TURNING_FROM_PRONE_ON )
4378 {
4379 // ATE: Don't do this if we have something in our hands we are going to throw!
4380 if ( IsValidStance( pSoldier, ANIM_PRONE ) && pSoldier->pTempObject == NULL )
4381 {
4382 ChangeSoldierStance(pSoldier, ANIM_PRONE);
4383 }
4384 pSoldier->fTurningFromPronePosition = TURNING_FROM_PRONE_OFF;
4385 }
4386
4387 // If a special code, make guy crawl after stance change!
4388 if ( pSoldier->fTurningFromPronePosition == TURNING_FROM_PRONE_ENDING_UP_FROM_MOVE &&
4389 pSoldier->usAnimState != PRONE_UP && pSoldier->usAnimState != PRONE_DOWN )
4390 {
4391 if ( IsValidStance( pSoldier, ANIM_PRONE ) )
4392 {
4393 EVENT_InitNewSoldierAnim( pSoldier, CRAWLING, 0, FALSE );
4394 }
4395 }
4396
4397 if ( pSoldier->uiStatusFlags & SOLDIER_TURNINGFROMHIT )
4398 {
4399 if ( pSoldier->fGettingHit == 1 )
4400 {
4401 if (pSoldier->usPendingAnimation != FALLFORWARD_ROOF &&
4402 pSoldier->usPendingAnimation != FALLOFF &&
4403 pSoldier->usAnimState != FALLFORWARD_ROOF &&
4404 pSoldier->usAnimState != FALLOFF)
4405 {
4406 // Go back to original direction
4407 EVENT_SetSoldierDesiredDirection( pSoldier, (INT8)pSoldier->uiPendingActionData1 );
4408
4409 //SETUP GETTING HIT FLAG TO 2
4410 pSoldier->fGettingHit = 2;
4411 }
4412 else
4413 {
4414 pSoldier->uiStatusFlags &= (~SOLDIER_TURNINGFROMHIT );
4415 }
4416 }
4417 else if ( pSoldier->fGettingHit == 2 )
4418 {
4419 // Turn off
4420 pSoldier->uiStatusFlags &= (~SOLDIER_TURNINGFROMHIT );
4421
4422 // Release attacker
4423 SLOGD("Releasesoldierattacker, turning from hit animation ended");
4424 ReleaseSoldiersAttacker( pSoldier );
4425
4426 //FREEUP GETTING HIT FLAG
4427 pSoldier->fGettingHit = FALSE;
4428 }
4429 }
4430
4431 return;
4432 }
4433
4434 // IF WE ARE HERE, WE ARE IN THE PROCESS OF TURNING
4435
4436 // DOUBLE CHECK TO UNSET fNOAPs...
4437 if ( pSoldier->fNoAPToFinishMove )
4438 {
4439 AdjustNoAPToFinishMove( pSoldier, FALSE );
4440 }
4441
4442 // Do something different for vehicles....
4443 if ( pSoldier->uiStatusFlags & SOLDIER_VEHICLE )
4444 {
4445 // Get new direction
4446 sDirection = pSoldier->ubHiResDirection + pSoldier->bTurningIncrement;
4447 if (sDirection > 31)
4448 {
4449 sDirection = 0;
4450 }
4451 else
4452 {
4453 if ( sDirection < 0 )
4454 {
4455 sDirection = 31;
4456 }
4457 }
4458 pSoldier->ubHiResDirection = (UINT8)sDirection;
4459
4460 if ( pSoldier->ubBodyType == TANK_NW || pSoldier->ubBodyType == TANK_NE )
4461 {
4462 if ( pSoldier->uiTuringSoundID == NO_SAMPLE )
4463 {
4464 pSoldier->uiTuringSoundID = PlaySoldierJA2Sample(pSoldier, TURRET_MOVE, HIGHVOLUME, 100, TRUE);
4465 }
4466 }
4467
4468 if (sDirection % 4 != 0)
4469 {
4470 // We are not at the multiple of a 'cardinal' direction
4471 return;
4472 }
4473
4474 sDirection /= 4;
4475 }
4476 else
4477 {
4478 // Get new direction
4479 //sDirection = pSoldier->bDirection + QuickestDirection( pSoldier->bDirection, pSoldier->bDesiredDirection );
4480 sDirection = pSoldier->bDirection + pSoldier->bTurningIncrement;
4481 if (sDirection > 7)
4482 {
4483 sDirection = 0;
4484 }
4485 else
4486 {
4487 if ( sDirection < 0 )
4488 {
4489 sDirection = 7;
4490 }
4491 }
4492 }
4493
4494
4495 // CHECK FOR A VALID TURN DIRECTION
4496 // This is needed for prone animations as well as any multi-tiled structs
4497 if (OKToAddMercToWorld(pSoldier, (INT8)sDirection))
4498 {
4499 // Don't do this if we are walkoing off screen...
4500 if (gubWaitingForAllMercsToExitCode == WAIT_FOR_MERCS_TO_WALKOFF_SCREEN || gubWaitingForAllMercsToExitCode == WAIT_FOR_MERCS_TO_WALK_TO_GRIDNO)
4501 {
4502
4503 }
4504 else
4505 {
4506 // ATE: We should only do this if we are STATIONARY!
4507 if (gAnimControl[pSoldier->usAnimState].uiFlags & ANIM_STATIONARY)
4508 {
4509 pSoldier->uiStatusFlags |= SOLDIER_LOOK_NEXT_TURNSOLDIER;
4510 }
4511 // otherwise, it's handled next tile...
4512 }
4513
4514 EVENT_SetSoldierDirection(pSoldier, sDirection);
4515
4516 if (pSoldier->ubBodyType != LARVAE_MONSTER && !MercInWater(pSoldier) && pSoldier->bOverTerrainType != DIRT_ROAD && pSoldier->bOverTerrainType != PAVED_ROAD)
4517 {
4518 PlaySoldierFootstepSound(pSoldier);
4519 }
4520 }
4521 else
4522 {
4523 // Are we prone crawling?
4524 if (pSoldier->usAnimState == CRAWLING)
4525 {
4526 // OK, we want to getup, turn and go prone again....
4527 ChangeSoldierStance(pSoldier, ANIM_CROUCH);
4528 pSoldier->fTurningFromPronePosition = TURNING_FROM_PRONE_ENDING_UP_FROM_MOVE;
4529 }
4530 // If we are a creature, or multi-tiled, cancel AI action.....?
4531 else if (pSoldier->uiStatusFlags & SOLDIER_MULTITILE)
4532 {
4533 pSoldier->bDesiredDirection = pSoldier->bDirection;
4534 }
4535 }
4536 }
4537
4538
4539 static const UINT8 gRedGlowR[]=
4540 {
4541 0, // Normal shades
4542 25,
4543 50,
4544 75,
4545 100,
4546 125,
4547 150,
4548 175,
4549 200,
4550 225,
4551
4552 0, // For gray palettes
4553 25,
4554 50,
4555 75,
4556 100,
4557 125,
4558 150,
4559 175,
4560 200,
4561 225,
4562
4563 };
4564
4565
4566 #if 0
4567 static const UINT8 gOrangeGlowR[]=
4568 {
4569 0, // Normal shades
4570 20,
4571 40,
4572 60,
4573 80,
4574 100,
4575 120,
4576 140,
4577 160,
4578 180,
4579
4580 0, // For gray palettes
4581 20,
4582 40,
4583 60,
4584 80,
4585 100,
4586 120,
4587 140,
4588 160,
4589 180,
4590 };
4591 #endif
4592
4593 static const UINT8 gOrangeGlowR[]=
4594 {
4595 0, // Normal shades
4596 25,
4597 50,
4598 75,
4599 100,
4600 125,
4601 150,
4602 175,
4603 200,
4604 225,
4605
4606 0, // For gray palettes
4607 25,
4608 50,
4609 75,
4610 100,
4611 125,
4612 150,
4613 175,
4614 200,
4615 225,
4616
4617 };
4618
4619
4620 #if 0
4621 static const UINT8 gOrangeGlowG[]=
4622 {
4623 0, // Normal shades
4624 5,
4625 10,
4626 25,
4627 30,
4628 35,
4629 40,
4630 45,
4631 50,
4632 55,
4633
4634 0, // For gray palettes
4635 5,
4636 10,
4637 25,
4638 30,
4639 35,
4640 40,
4641 45,
4642 50,
4643 55,
4644 };
4645 #endif
4646
4647 static const UINT8 gOrangeGlowG[]=
4648 {
4649 0, // Normal shades
4650 20,
4651 40,
4652 60,
4653 80,
4654 100,
4655 120,
4656 140,
4657 160,
4658 180,
4659
4660 0, // For gray palettes
4661 20,
4662 40,
4663 60,
4664 80,
4665 100,
4666 120,
4667 140,
4668 160,
4669 180,
4670
4671 };
4672
4673
4674 static UINT16* CreateEnemyGlow16BPPPalette(const SGPPaletteEntry* pPalette, UINT32 rscale, UINT32 gscale);
4675 static UINT16* CreateEnemyGreyGlow16BPPPalette(const SGPPaletteEntry* pPalette, UINT32 rscale, UINT32 gscale);
4676
4677
CreateSoldierPalettes(SOLDIERTYPE & s)4678 void CreateSoldierPalettes(SOLDIERTYPE& s)
4679 {
4680 // --- TAKE FROM CURRENT ANIMATION HVOBJECT!
4681 UINT16 const anim_surface = GetSoldierAnimationSurface(&s);
4682 if (anim_surface == INVALID_ANIMATION_SURFACE)
4683 {
4684 throw std::runtime_error("Palette creation failed, soldier has invalid animation");
4685 }
4686
4687 SGPPaletteEntry tmp_pal[256];
4688 std::fill_n(tmp_pal, 256, SGPPaletteEntry{});
4689
4690 SGPPaletteEntry const* pal;
4691 char const* const substitution = GetBodyTypePaletteSubstitution(&s, s.ubBodyType);
4692 if (!substitution)
4693 {
4694 // ATE: here we want to use the breath cycle for the palette.....
4695 UINT16 const palette_anim_surface = LoadSoldierAnimationSurface(s, STANDING);
4696 if (palette_anim_surface != INVALID_ANIMATION_SURFACE)
4697 {
4698 // Use palette from HVOBJECT, then use substitution for pants, etc
4699 memcpy(tmp_pal, gAnimSurfaceDatabase[palette_anim_surface].hVideoObject->Palette(), sizeof(*tmp_pal) * 256);
4700
4701 // Substitute based on head, etc
4702 SetPaletteReplacement(tmp_pal, s.HeadPal);
4703 SetPaletteReplacement(tmp_pal, s.VestPal);
4704 SetPaletteReplacement(tmp_pal, s.PantsPal);
4705 SetPaletteReplacement(tmp_pal, s.SkinPal);
4706 }
4707 pal = tmp_pal;
4708 }
4709 else if (substitution[0] != '\0' && CreateSGPPaletteFromCOLFile(tmp_pal, substitution))
4710 {
4711 pal = tmp_pal;
4712 }
4713 else
4714 {
4715 // Use palette from hvobject
4716 pal = gAnimSurfaceDatabase[anim_surface].hVideoObject->Palette();
4717 }
4718
4719
4720 for (INT32 i = 0; i < NUM_SOLDIER_SHADES; ++i)
4721 {
4722 if (s.pShades[i])
4723 {
4724 delete[] s.pShades[i];
4725 s.pShades[i] = 0;
4726 }
4727 }
4728
4729 if (s.effect_shade)
4730 {
4731 delete[] s.effect_shade;
4732 s.effect_shade = 0;
4733 }
4734
4735 for (INT32 i = 0; i < 20; ++i)
4736 {
4737 if (s.pGlowShades[i])
4738 {
4739 delete[] s.pGlowShades[i];
4740 s.pGlowShades[i] = 0;
4741 }
4742 }
4743
4744
4745 CreateBiasedShadedPalettes(s.pShades, pal);
4746
4747 s.effect_shade = Create16BPPPaletteShaded(pal, 100, 100, 100, TRUE);
4748
4749 // Build shades for glowing visible bad guy
4750
4751 // First do visible guy
4752 s.pGlowShades[0] = Create16BPPPaletteShaded(pal, 255, 255, 255, FALSE);
4753 for (INT32 i = 1; i < 10; ++i)
4754 {
4755 s.pGlowShades[i] = CreateEnemyGlow16BPPPalette(pal, gRedGlowR[i], 0);
4756 }
4757
4758 // Now for gray guy...
4759 s.pGlowShades[10] = Create16BPPPaletteShaded(pal, 100, 100, 100, TRUE);
4760 for (INT32 i = 11; i < 19; ++i)
4761 {
4762 s.pGlowShades[i] = CreateEnemyGreyGlow16BPPPalette(pal, gRedGlowR[i], 0);
4763 }
4764 s.pGlowShades[19] = CreateEnemyGreyGlow16BPPPalette(pal, gRedGlowR[18], 0);
4765
4766 // ATE: OK, piggyback on the shades we are not using for 2 colored lighting....
4767 // ORANGE, VISIBLE GUY
4768 s.pShades[20] = Create16BPPPaletteShaded(pal, 255, 255, 255, FALSE);
4769 for (INT32 i = 21; i < 30; ++i)
4770 {
4771 s.pShades[i] = CreateEnemyGlow16BPPPalette(pal, gOrangeGlowR[i - 20], gOrangeGlowG[i - 20]);
4772 }
4773
4774 // ORANGE, GREY GUY
4775 s.pShades[30] = Create16BPPPaletteShaded(pal, 100, 100, 100, TRUE);
4776 for (INT32 i = 31; i < 39; ++i)
4777 {
4778 s.pShades[i] = CreateEnemyGreyGlow16BPPPalette(pal, gOrangeGlowR[i - 20], gOrangeGlowG[i - 20]);
4779 }
4780 s.pShades[39] = CreateEnemyGreyGlow16BPPPalette(pal, gOrangeGlowR[18], gOrangeGlowG[18]);
4781 }
4782
4783
AdjustAniSpeed(SOLDIERTYPE * pSoldier)4784 static void AdjustAniSpeed(SOLDIERTYPE* pSoldier)
4785 {
4786 if ( ( gTacticalStatus.uiFlags & SLOW_ANIMATION ) )
4787 {
4788 if ( gTacticalStatus.bRealtimeSpeed == -1 )
4789 {
4790 pSoldier->sAniDelay = 10000;
4791 }
4792 else
4793 {
4794 pSoldier->sAniDelay = pSoldier->sAniDelay * ( 1 * gTacticalStatus.bRealtimeSpeed / 2 );
4795 }
4796 }
4797
4798
4799 RESETTIMECOUNTER( pSoldier->UpdateCounter, pSoldier->sAniDelay );
4800 }
4801
4802
CalculateSoldierAniSpeed(SOLDIERTYPE * pSoldier,SOLDIERTYPE * pStatsSoldier)4803 static void CalculateSoldierAniSpeed(SOLDIERTYPE* pSoldier, SOLDIERTYPE* pStatsSoldier)
4804 {
4805 UINT32 uiTerrainDelay;
4806 UINT32 uiSpeed = 0;
4807
4808 INT8 bBreathDef, bLifeDef, bAgilDef;
4809 INT8 bAdditional = 0;
4810
4811 // for those animations which have a speed of zero, we have to calculate it
4812 // here. Some animation, such as water-movement, have an ADDITIONAL speed
4813 switch( pSoldier->usAnimState )
4814 {
4815 case PRONE:
4816 case STANDING:
4817
4818 pSoldier->sAniDelay = ( pStatsSoldier->bBreath * 2 ) + (100 - pStatsSoldier->bLife );
4819
4820 // Limit it!
4821 if ( pSoldier->sAniDelay < 40 )
4822 {
4823 pSoldier->sAniDelay = 40;
4824 }
4825 AdjustAniSpeed( pSoldier );
4826 return;
4827
4828 case CROUCHING:
4829
4830 pSoldier->sAniDelay = ( pStatsSoldier->bBreath * 2 ) + ( (100 - pStatsSoldier->bLife ) );
4831
4832 // Limit it!
4833 if ( pSoldier->sAniDelay < 40 )
4834 {
4835 pSoldier->sAniDelay = 40;
4836 }
4837 AdjustAniSpeed( pSoldier );
4838 return;
4839
4840 case WALKING:
4841
4842 // Adjust based on body type
4843 bAdditional = (UINT8)( gubAnimWalkSpeeds[ pStatsSoldier->ubBodyType ].sSpeed );
4844 if ( bAdditional < 0 )
4845 bAdditional = 0;
4846 break;
4847
4848 case RUNNING:
4849
4850 // Adjust based on body type
4851 bAdditional = (UINT8)gubAnimRunSpeeds[ pStatsSoldier->ubBodyType ].sSpeed;
4852 if ( bAdditional < 0 )
4853 bAdditional = 0;
4854 break;
4855
4856 case SWATTING:
4857
4858 // Adjust based on body type
4859 if ( pStatsSoldier->ubBodyType <= REGFEMALE )
4860 {
4861 bAdditional = (UINT8)gubAnimSwatSpeeds[ pStatsSoldier->ubBodyType ].sSpeed;
4862 if ( bAdditional < 0 )
4863 bAdditional = 0;
4864 }
4865 break;
4866
4867 case CRAWLING:
4868
4869 // Adjust based on body type
4870 if ( pStatsSoldier->ubBodyType <= REGFEMALE )
4871 {
4872 bAdditional = (UINT8)gubAnimCrawlSpeeds[ pStatsSoldier->ubBodyType ].sSpeed;
4873 if ( bAdditional < 0 )
4874 bAdditional = 0;
4875 }
4876 break;
4877
4878 case READY_RIFLE_STAND:
4879
4880 // Raise rifle based on aim vs non-aim.
4881 if ( pSoldier->bAimTime == 0 )
4882 {
4883 // Quick shot
4884 pSoldier->sAniDelay = 70;
4885 }
4886 else
4887 {
4888 pSoldier->sAniDelay = 150;
4889 }
4890 AdjustAniSpeed( pSoldier );
4891 return;
4892 }
4893
4894
4895 // figure out movement speed (terrspeed)
4896 if ( gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_MOVING )
4897 {
4898 uiSpeed = gsTerrainTypeSpeedModifiers[ pStatsSoldier->bOverTerrainType ];
4899
4900 uiTerrainDelay = uiSpeed;
4901 }
4902 else
4903 {
4904 uiTerrainDelay = 40; // standing still
4905 }
4906
4907 bBreathDef = 50 - ( pStatsSoldier->bBreath / 2 );
4908
4909 if ( bBreathDef > 30 )
4910 bBreathDef = 30;
4911
4912 bAgilDef = 50 - ( EffectiveAgility( pStatsSoldier ) / 4 );
4913 bLifeDef = 50 - ( pStatsSoldier->bLife / 2 );
4914
4915 uiTerrainDelay += ( bLifeDef + bBreathDef + bAgilDef + bAdditional );
4916
4917 pSoldier->sAniDelay = (INT16)uiTerrainDelay;
4918
4919 // If a moving animation and w/re on drugs, increase speed....
4920 if ( gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_MOVING )
4921 {
4922 if ( GetDrugEffect( pSoldier, DRUG_TYPE_ADRENALINE ) )
4923 {
4924 pSoldier->sAniDelay = pSoldier->sAniDelay / 2;
4925 }
4926 }
4927
4928 // MODIFTY NOW BASED ON REAL-TIME, ETC
4929 // Adjust speed, make twice as fast if in turn-based!
4930 if (gTacticalStatus.uiFlags & INCOMBAT)
4931 {
4932 pSoldier->sAniDelay = pSoldier->sAniDelay / 2;
4933 }
4934
4935 // MODIFY IF REALTIME COMBAT
4936 if ( !( gTacticalStatus.uiFlags & INCOMBAT ) )
4937 {
4938 // ATE: If realtime, and stealth mode...
4939 if ( pStatsSoldier->bStealthMode )
4940 {
4941 pSoldier->sAniDelay = (INT16)( pSoldier->sAniDelay * 2 );
4942 }
4943
4944 //pSoldier->sAniDelay = pSoldier->sAniDelay * ( 1 * gTacticalStatus.bRealtimeSpeed / 2 );
4945 }
4946 }
4947
4948
SetSoldierAniSpeed(SOLDIERTYPE * pSoldier)4949 void SetSoldierAniSpeed(SOLDIERTYPE* pSoldier)
4950 {
4951 SOLDIERTYPE *pStatsSoldier;
4952
4953
4954 // ATE: If we are an enemy and are not visible......
4955 // Set speed to 0
4956 if ((gTacticalStatus.uiFlags & INCOMBAT) || gTacticalStatus.fAutoBandageMode)
4957 {
4958 if ( ( ( pSoldier->bVisible == -1 && pSoldier->bVisible == pSoldier->bLastRenderVisibleValue ) ||
4959 gTacticalStatus.fAutoBandageMode ) && pSoldier->usAnimState != MONSTER_UP )
4960 {
4961 pSoldier->sAniDelay = 0;
4962 RESETTIMECOUNTER( pSoldier->UpdateCounter, pSoldier->sAniDelay );
4963 return;
4964 }
4965 }
4966
4967 // Default stats soldier to same as normal soldier.....
4968 pStatsSoldier = pSoldier;
4969
4970 if ( pSoldier->fUseMoverrideMoveSpeed )
4971 {
4972 pStatsSoldier = &GetMan(pSoldier->bOverrideMoveSpeed);
4973 }
4974
4975 // Only calculate if set to zero
4976 if ( ( pSoldier->sAniDelay = gAnimControl[ pSoldier->usAnimState ].sSpeed ) == 0 )
4977 {
4978 CalculateSoldierAniSpeed( pSoldier, pStatsSoldier );
4979 }
4980
4981 AdjustAniSpeed( pSoldier );
4982
4983 if (_KeyDown(SDLK_SPACE))
4984 {
4985 //pSoldier->sAniDelay = 1000;
4986 }
4987
4988 }
4989
4990
4991 ///////////////////////////////////////////////////////
4992 //PALETTE REPLACEMENT FUNCTIONS
4993 ///////////////////////////////////////////////////////
LoadPaletteData()4994 void LoadPaletteData()
4995 {
4996 UINT32 cnt, cnt2;
4997
4998 AutoSGPFile hFile(GCM->openGameResForReading(PALETTEFILENAME));
4999
5000 // Read # of types
5001 FileRead(hFile, &guiNumPaletteSubRanges, sizeof(guiNumPaletteSubRanges));
5002
5003 // Malloc!
5004 gpPaletteSubRanges = new PaletteSubRangeType[guiNumPaletteSubRanges]{};
5005 gubpNumReplacementsPerRange = new UINT8[guiNumPaletteSubRanges]{};
5006
5007 // Read # of types for each!
5008 for ( cnt = 0; cnt < guiNumPaletteSubRanges; cnt++ )
5009 {
5010 FileRead(hFile, &gubpNumReplacementsPerRange[cnt], sizeof(UINT8));
5011 }
5012
5013 // Loop for each one, read in data
5014 for ( cnt = 0; cnt < guiNumPaletteSubRanges; cnt++ )
5015 {
5016 FileRead(hFile, &gpPaletteSubRanges[cnt].ubStart, sizeof(UINT8));
5017 FileRead(hFile, &gpPaletteSubRanges[cnt].ubEnd, sizeof(UINT8));
5018 }
5019
5020
5021 // Read # of palettes
5022 FileRead(hFile, &guiNumReplacements, sizeof(guiNumReplacements));
5023
5024 // Malloc!
5025 gpPalRep = new PaletteReplacementType[guiNumReplacements]{};
5026
5027 // Read!
5028 for ( cnt = 0; cnt < guiNumReplacements; cnt++ )
5029 {
5030 // type
5031 FileRead(hFile, &gpPalRep[cnt].ubType, sizeof(gpPalRep[cnt].ubType));
5032
5033 ST::char_buffer buf{PaletteRepID_LENGTH, '\0'};
5034 FileRead(hFile, buf.data(), buf.size() * sizeof(char));
5035 gpPalRep[cnt].ID = ST::string(buf.c_str(), ST_AUTO_SIZE, ST::substitute_invalid);
5036
5037 // # entries
5038 FileRead(hFile, &gpPalRep[cnt].ubPaletteSize, sizeof(gpPalRep[cnt].ubPaletteSize));
5039
5040 SGPPaletteEntry* const Pal = new SGPPaletteEntry[gpPalRep[cnt].ubPaletteSize]{};
5041 gpPalRep[cnt].rgb = Pal;
5042
5043 for( cnt2 = 0; cnt2 < gpPalRep[ cnt ].ubPaletteSize; cnt2++ )
5044 {
5045 FileRead(hFile, &Pal[cnt2].r, sizeof(Pal[cnt2].r));
5046 FileRead(hFile, &Pal[cnt2].g, sizeof(Pal[cnt2].g));
5047 FileRead(hFile, &Pal[cnt2].b, sizeof(Pal[cnt2].b));
5048 }
5049
5050 }
5051 }
5052
5053
SetPaletteReplacement(SGPPaletteEntry * p8BPPPalette,const ST::string & aPalRep)5054 void SetPaletteReplacement(SGPPaletteEntry* p8BPPPalette, const ST::string& aPalRep)
5055 {
5056 UINT32 cnt2;
5057 UINT8 ubType;
5058
5059 const UINT8 ubPalIndex = GetPaletteRepIndexFromID(aPalRep);
5060
5061 // Get range type
5062 ubType = gpPalRep[ ubPalIndex ].ubType;
5063
5064 for ( cnt2 = gpPaletteSubRanges[ ubType ].ubStart; cnt2 <= gpPaletteSubRanges[ ubType ].ubEnd; cnt2++ )
5065 {
5066 p8BPPPalette[cnt2] = gpPalRep[ubPalIndex].rgb[cnt2 - gpPaletteSubRanges[ubType].ubStart];
5067 }
5068 }
5069
5070
DeletePaletteData()5071 void DeletePaletteData()
5072 {
5073 UINT32 cnt;
5074
5075 // Free!
5076 if ( gpPaletteSubRanges != NULL )
5077 {
5078 delete[] gpPaletteSubRanges;
5079 gpPaletteSubRanges = NULL;
5080 }
5081
5082 if ( gubpNumReplacementsPerRange != NULL )
5083 {
5084 delete[] gubpNumReplacementsPerRange;
5085 gubpNumReplacementsPerRange = NULL;
5086 }
5087
5088
5089 for ( cnt = 0; cnt < guiNumReplacements; cnt++ )
5090 {
5091 if (gpPalRep[cnt].rgb != NULL) delete[] gpPalRep[cnt].rgb;
5092 }
5093
5094 // Free
5095 if ( gpPalRep != NULL )
5096 {
5097 delete[] gpPalRep;
5098 gpPalRep = NULL;
5099 }
5100 }
5101
5102
GetPaletteRepIndexFromID(const ST::string & pal_rep)5103 UINT8 GetPaletteRepIndexFromID(const ST::string& pal_rep)
5104 {
5105 // Check if type exists
5106 for (UINT32 i = 0; i < guiNumReplacements; ++i)
5107 {
5108 if (pal_rep.compare(gpPalRep[i].ID) == 0) return i;
5109 }
5110
5111 throw std::logic_error("Invalid Palette Replacement ID given");
5112 }
5113
5114
GetNewSoldierStateFromNewStance(SOLDIERTYPE * pSoldier,UINT8 ubDesiredStance)5115 static UINT16 GetNewSoldierStateFromNewStance(SOLDIERTYPE* pSoldier, UINT8 ubDesiredStance)
5116 {
5117 UINT16 usNewState;
5118 INT8 bCurrentHeight;
5119
5120 bCurrentHeight = ( ubDesiredStance - gAnimControl[ pSoldier->usAnimState ].ubEndHeight );
5121
5122 // Now change to appropriate animation
5123
5124 switch( bCurrentHeight )
5125 {
5126 case ANIM_STAND - ANIM_CROUCH:
5127 usNewState = KNEEL_UP;
5128 break;
5129 case ANIM_CROUCH - ANIM_STAND:
5130 usNewState = KNEEL_DOWN;
5131 break;
5132
5133 case ANIM_STAND - ANIM_PRONE:
5134 usNewState = PRONE_UP;
5135 break;
5136 case ANIM_PRONE - ANIM_STAND:
5137 usNewState = KNEEL_DOWN;
5138 break;
5139
5140 case ANIM_CROUCH - ANIM_PRONE:
5141 usNewState = PRONE_UP;
5142 break;
5143 case ANIM_PRONE - ANIM_CROUCH:
5144 usNewState = PRONE_DOWN;
5145 break;
5146
5147 default:
5148
5149 // Cannot get here unless ub desired stance is bogus
5150 SLOGD(
5151 "GetNewSoldierStateFromNewStance bogus ubDesiredStance value %d",
5152 ubDesiredStance);
5153 usNewState = pSoldier->usAnimState;
5154 }
5155
5156 return( usNewState );
5157 }
5158
5159
MoveMercFacingDirection(SOLDIERTYPE * pSoldier,BOOLEAN fReverse,FLOAT dMovementDist)5160 void MoveMercFacingDirection( SOLDIERTYPE *pSoldier, BOOLEAN fReverse, FLOAT dMovementDist )
5161 {
5162 FLOAT dAngle = (FLOAT)0;
5163
5164 // Determine which direction we are in
5165 switch( pSoldier->bDirection )
5166 {
5167 case NORTH:
5168 dAngle = (FLOAT)( -1 * PI );
5169 break;
5170
5171 case NORTHEAST:
5172 dAngle = (FLOAT)( PI * .75 );
5173 break;
5174
5175 case EAST:
5176 dAngle = (FLOAT)( PI / 2 );
5177 break;
5178
5179 case SOUTHEAST:
5180 dAngle = (FLOAT)( PI / 4 );
5181 break;
5182
5183 case SOUTH:
5184 dAngle = (FLOAT)0;
5185 break;
5186
5187 case SOUTHWEST:
5188 //dAngle = (FLOAT)( PI * -.25 );
5189 dAngle = (FLOAT)-0.786;
5190 break;
5191
5192 case WEST:
5193 dAngle = (FLOAT) ( PI *-.5 );
5194 break;
5195
5196 case NORTHWEST:
5197 dAngle = (FLOAT) ( PI * -.75 );
5198 break;
5199
5200 }
5201
5202 if ( fReverse )
5203 {
5204 dMovementDist = dMovementDist * -1;
5205 }
5206
5207 MoveMerc( pSoldier, dMovementDist, dAngle, FALSE );
5208 }
5209
5210
5211 static void InternalReceivingSoldierCancelServices(SOLDIERTYPE* pSoldier, BOOLEAN fPlayEndAnim);
5212
5213
BeginSoldierClimbUpRoof(SOLDIERTYPE * const s)5214 void BeginSoldierClimbUpRoof(SOLDIERTYPE* const s)
5215 {
5216 UINT8 direction;
5217 if (!FindHigherLevel(s, &direction)) return;
5218
5219 if (!EnoughPoints(s, GetAPsToClimbRoof(s, FALSE), 0, TRUE)) return;
5220
5221 if (s->bTeam == OUR_TEAM) SetUIBusy(s);
5222
5223 s->sTempNewGridNo = NewGridNo(s->sGridNo, DirectionInc(direction));
5224 s->ubPendingDirection = direction;
5225 EVENT_InitNewSoldierAnim(s, CLIMBUPROOF, 0, FALSE);
5226 InternalReceivingSoldierCancelServices(s, FALSE);
5227 InternalGivingSoldierCancelServices(s, FALSE);
5228 }
5229
5230
BeginSoldierClimbWindow(SOLDIERTYPE * const s)5231 void BeginSoldierClimbWindow(SOLDIERTYPE* const s)
5232 {
5233 if(!IsFacingClimableWindow(s)) return;
5234
5235 s->sTempNewGridNo = NewGridNo(s->sGridNo, DirectionInc(s->bDirection));
5236 s->fDontChargeTurningAPs = TRUE;
5237 //EVENT_SetSoldierDesiredDirectionForward(s, direction);
5238 s->fTurningUntilDone = TRUE;
5239 // ATE: Reset flag to go back to prone
5240 s->fTurningFromPronePosition = TURNING_FROM_PRONE_OFF;
5241 //s->usPendingAnimation = HOPFENCE;
5242 DeductPoints( s, AP_JUMPFENCE, BP_JUMPFENCE );
5243 TeleportSoldier( *s, s->sTempNewGridNo, TRUE );
5244 }
5245
BeginSoldierClimbFence(SOLDIERTYPE * const s)5246 void BeginSoldierClimbFence(SOLDIERTYPE* const s)
5247 {
5248 UINT8 direction;
5249 if (!FindFenceJumpDirection(s, &direction)) return;
5250
5251 s->sTempNewGridNo = NewGridNo(s->sGridNo, DirectionInc(direction));
5252 s->fDontChargeTurningAPs = TRUE;
5253 EVENT_SetSoldierDesiredDirectionForward(s, direction);
5254 s->fTurningUntilDone = TRUE;
5255 // ATE: Reset flag to go back to prone
5256 s->fTurningFromPronePosition = TURNING_FROM_PRONE_OFF;
5257 s->usPendingAnimation = HOPFENCE;
5258 }
5259
5260
SleepDartSuccumbChance(const SOLDIERTYPE * pSoldier)5261 static UINT32 SleepDartSuccumbChance(const SOLDIERTYPE* pSoldier)
5262 {
5263 UINT32 uiChance;
5264 INT8 bEffectiveStrength;
5265
5266 // figure out base chance of succumbing,
5267 bEffectiveStrength = EffectiveStrength( pSoldier );
5268
5269 if (bEffectiveStrength > 90)
5270 {
5271 uiChance = 110 - bEffectiveStrength;
5272 }
5273 else if (bEffectiveStrength > 80)
5274 {
5275 uiChance = 120 - bEffectiveStrength;
5276 }
5277 else if (bEffectiveStrength > 70)
5278 {
5279 uiChance = 130 - bEffectiveStrength;
5280 }
5281 else
5282 {
5283 uiChance = 140 - bEffectiveStrength;
5284 }
5285
5286 // add in a bonus based on how long it's been since shot... highest chance at the beginning
5287 uiChance += (10 - pSoldier->bSleepDrugCounter);
5288
5289 return( uiChance );
5290 }
5291
BeginSoldierGetup(SOLDIERTYPE * pSoldier)5292 void BeginSoldierGetup( SOLDIERTYPE *pSoldier )
5293 {
5294 // RETURN IF WE ARE BEING SERVICED
5295 if ( pSoldier->ubServiceCount > 0 )
5296 {
5297 return;
5298 }
5299
5300 // ATE: Don't getup if we are in a meanwhile
5301 if ( AreInMeanwhile( ) )
5302 {
5303 return;
5304 }
5305
5306 if ( pSoldier->bCollapsed )
5307 {
5308 if ( pSoldier->bLife >= OKLIFE && pSoldier->bBreath >= OKBREATH && (pSoldier->bSleepDrugCounter == 0) )
5309 {
5310 // get up you hoser!
5311
5312 pSoldier->bCollapsed = FALSE;
5313 pSoldier->bTurnsCollapsed = 0;
5314
5315 if ( IS_MERC_BODY_TYPE( pSoldier ) )
5316 {
5317 switch( pSoldier->usAnimState )
5318 {
5319 case FALLOFF_FORWARD_STOP:
5320 case PRONE_LAYFROMHIT_STOP:
5321 case STAND_FALLFORWARD_STOP:
5322 ChangeSoldierStance( pSoldier, ANIM_CROUCH );
5323 break;
5324
5325 case FALLBACKHIT_STOP:
5326 case FALLOFF_STOP:
5327 case FLYBACKHIT_STOP:
5328 case FALLBACK_HIT_STAND:
5329 case FALLOFF:
5330 case FLYBACK_HIT:
5331
5332 // ROLL OVER
5333 EVENT_InitNewSoldierAnim( pSoldier, ROLLOVER, 0 , FALSE );
5334 break;
5335
5336 default:
5337
5338 ChangeSoldierStance( pSoldier, ANIM_CROUCH );
5339 break;
5340 }
5341 }
5342 else
5343 {
5344 EVENT_InitNewSoldierAnim( pSoldier, END_COWER, 0 , FALSE );
5345 }
5346 }
5347 else
5348 {
5349 pSoldier->bTurnsCollapsed++;
5350 if ( (gTacticalStatus.bBoxingState == BOXING) && (pSoldier->uiStatusFlags & SOLDIER_BOXER) )
5351 {
5352 if (pSoldier->bTurnsCollapsed > 1)
5353 {
5354 // We have a winnah! But it isn't this boxer!
5355 EndBoxingMatch( pSoldier );
5356 }
5357 }
5358 }
5359 }
5360 else if ( pSoldier->bSleepDrugCounter > 0 )
5361 {
5362 UINT32 uiChance;
5363
5364 uiChance = SleepDartSuccumbChance( pSoldier );
5365
5366 if ( PreRandom( 100 ) < uiChance )
5367 {
5368 // succumb to the drug!
5369 DeductPoints( pSoldier, 0, (INT16)( pSoldier->bBreathMax * 100 ) );
5370 SoldierCollapse( pSoldier );
5371 }
5372 }
5373
5374 if ( pSoldier->bSleepDrugCounter > 0 )
5375 {
5376 pSoldier->bSleepDrugCounter--;
5377 }
5378 }
5379
5380
5381 static void HandleSoldierTakeDamageFeedback(SOLDIERTYPE* pSoldier);
5382
5383
HandleTakeDamageDeath(SOLDIERTYPE * pSoldier,UINT8 bOldLife,UINT8 ubReason)5384 static void HandleTakeDamageDeath(SOLDIERTYPE* pSoldier, UINT8 bOldLife, UINT8 ubReason)
5385 {
5386 switch( ubReason )
5387 {
5388 case TAKE_DAMAGE_BLOODLOSS:
5389 case TAKE_DAMAGE_ELECTRICITY:
5390 case TAKE_DAMAGE_GAS:
5391
5392 if ( pSoldier->bInSector )
5393 {
5394 if ( pSoldier->bVisible != -1 )
5395 {
5396 if ( ubReason != TAKE_DAMAGE_BLOODLOSS )
5397 {
5398 DoMercBattleSound( pSoldier, BATTLE_SOUND_DIE1 );
5399 pSoldier->fDeadSoundPlayed = TRUE;
5400 }
5401 }
5402
5403 if ( ( ubReason == TAKE_DAMAGE_ELECTRICITY ) && pSoldier->bLife < OKLIFE )
5404 {
5405 pSoldier->fInNonintAnim = FALSE;
5406 }
5407
5408 // Check for < OKLIFE
5409 if ( pSoldier->bLife < OKLIFE && pSoldier->bLife != 0 && !pSoldier->bCollapsed)
5410 {
5411 SoldierCollapse( pSoldier );
5412 }
5413
5414 // THis is for the die animation that will be happening....
5415 if ( pSoldier->bLife == 0 )
5416 {
5417 pSoldier->fDoingExternalDeath = TRUE;
5418 }
5419
5420 // Check if he is dead....
5421 CheckForAndHandleSoldierDyingNotFromHit( pSoldier );
5422
5423 }
5424
5425 HandleSoldierTakeDamageFeedback(pSoldier);
5426
5427 if (fInMapMode || !pSoldier->bInSector)
5428 {
5429 if ( pSoldier->bLife == 0 && !( pSoldier->uiStatusFlags & SOLDIER_DEAD ) )
5430 {
5431 StrategicHandlePlayerTeamMercDeath(*pSoldier);
5432
5433 DoMercBattleSound( pSoldier, BATTLE_SOUND_DIE1 );
5434 pSoldier->fDeadSoundPlayed = TRUE;
5435
5436 // ATE: DO death sound
5437 PlayJA2Sample(DOORCR_1, HIGHVOLUME, 1, MIDDLEPAN);
5438 PlayJA2Sample(HEADCR_1, HIGHVOLUME, 1, MIDDLEPAN);
5439 }
5440 }
5441 break;
5442 }
5443
5444 if ( ubReason == TAKE_DAMAGE_ELECTRICITY )
5445 {
5446 if ( pSoldier->bLife >= OKLIFE )
5447 {
5448 SLOGD("Freeing up attacker from electricity damage");
5449 ReleaseSoldiersAttacker( pSoldier );
5450 }
5451 }
5452 }
5453
5454
5455 static FLOAT CalcSoldierNextBleed(SOLDIERTYPE* pSoldier);
5456
5457
SoldierTakeDamage(SOLDIERTYPE * const pSoldier,INT16 sLifeDeduct,INT16 sBreathLoss,const UINT8 ubReason,SOLDIERTYPE * const attacker)5458 UINT8 SoldierTakeDamage(SOLDIERTYPE* const pSoldier, INT16 sLifeDeduct, INT16 sBreathLoss, const UINT8 ubReason, SOLDIERTYPE* const attacker)
5459 {
5460 INT8 bOldLife;
5461 UINT8 ubCombinedLoss;
5462 INT8 bBandage;
5463 INT16 sAPCost;
5464 UINT8 ubBlood;
5465
5466
5467 pSoldier->ubLastDamageReason = ubReason;
5468
5469
5470 // CJC Jan 21 99: add check to see if we are hurting an enemy in an enemy-controlled
5471 // sector; if so, this is a sign of player activity
5472 switch ( pSoldier->bTeam )
5473 {
5474 case ENEMY_TEAM:
5475 // if we're in the wilderness this always counts
5476 if (StrategicMap[CALCULATE_STRATEGIC_INDEX(gWorldSectorX, gWorldSectorY)].fEnemyControlled ||
5477 SectorInfo[SECTOR(gWorldSectorX, gWorldSectorY)].ubTraversability[THROUGH_STRATEGIC_MOVE] != TOWN)
5478 {
5479 // update current day of activity!
5480 UpdateLastDayOfPlayerActivity( (UINT16) GetWorldDay() );
5481 }
5482 break;
5483 case CREATURE_TEAM:
5484 // always a sign of activity?
5485 UpdateLastDayOfPlayerActivity( (UINT16) GetWorldDay() );
5486 break;
5487 case CIV_TEAM:
5488 if (pSoldier->ubCivilianGroup == KINGPIN_CIV_GROUP &&
5489 gubQuest[QUEST_RESCUE_MARIA] == QUESTINPROGRESS && gTacticalStatus.bBoxingState == NOT_BOXING)
5490 {
5491 const SOLDIERTYPE* const pMaria = FindSoldierByProfileID(MARIA);
5492 if (pMaria && pMaria->bInSector)
5493 {
5494 SetFactTrue( FACT_MARIA_ESCAPE_NOTICED );
5495 }
5496 }
5497 break;
5498 default:
5499 break;
5500 }
5501
5502 // Deduct life!, Show damage if we want!
5503 bOldLife = pSoldier->bLife;
5504
5505 // OK, If we are a vehicle.... damage vehicle...( people inside... )
5506 if ( pSoldier->uiStatusFlags & SOLDIER_VEHICLE )
5507 {
5508 if ( TANK( pSoldier ) )
5509 {
5510 //sLifeDeduct = (sLifeDeduct * 2) / 3;
5511 }
5512 else
5513 {
5514 if ( ubReason == TAKE_DAMAGE_GUNFIRE )
5515 {
5516 sLifeDeduct /= 3;
5517 }
5518 else if ( ubReason == TAKE_DAMAGE_EXPLOSION && sLifeDeduct > 50 )
5519 {
5520 // boom!
5521 sLifeDeduct *= 2;
5522 }
5523 }
5524
5525 VehicleTakeDamage(pSoldier->bVehicleID, ubReason, sLifeDeduct, pSoldier->sGridNo, attacker);
5526 HandleTakeDamageDeath( pSoldier, bOldLife, ubReason );
5527 return( 0 );
5528 }
5529
5530 // ATE: If we are elloit being attacked in a meanwhile...
5531 if ( pSoldier->uiStatusFlags & SOLDIER_NPC_SHOOTING )
5532 {
5533 // Almost kill but not quite.....
5534 sLifeDeduct = ( pSoldier->bLife - 1 );
5535 // Turn off
5536 pSoldier->uiStatusFlags &= ( ~SOLDIER_NPC_SHOOTING );
5537 }
5538
5539 // CJC: make sure Elliot doesn't bleed to death!
5540 if ( ubReason == TAKE_DAMAGE_BLOODLOSS && AreInMeanwhile() )
5541 {
5542 return( 0 );
5543 }
5544
5545
5546 // Calculate bandage
5547 bBandage = pSoldier->bLifeMax - pSoldier->bLife - pSoldier->bBleeding;
5548
5549 if( guiCurrentScreen == MAP_SCREEN )
5550 {
5551 fReDrawFace = TRUE;
5552 }
5553
5554 if ( CREATURE_OR_BLOODCAT( pSoldier ) )
5555 {
5556 INT16 sReductionFactor = 0;
5557
5558 if ( pSoldier->ubBodyType == BLOODCAT )
5559 {
5560 sReductionFactor = 2;
5561 }
5562 else if (pSoldier->uiStatusFlags & SOLDIER_MONSTER)
5563 {
5564 switch( pSoldier->ubBodyType )
5565 {
5566 case LARVAE_MONSTER:
5567 case INFANT_MONSTER:
5568 sReductionFactor = 1;
5569 break;
5570 case YAF_MONSTER:
5571 case YAM_MONSTER:
5572 sReductionFactor = 4;
5573 break;
5574 case ADULTFEMALEMONSTER:
5575 case AM_MONSTER:
5576 sReductionFactor = 6;
5577 break;
5578 case QUEENMONSTER:
5579 // increase with range!
5580 if (attacker == NULL)
5581 {
5582 sReductionFactor = 8;
5583 }
5584 else
5585 {
5586 sReductionFactor = 4 + PythSpacesAway(attacker->sGridNo, pSoldier->sGridNo) / 2;
5587 }
5588 break;
5589 }
5590 }
5591
5592 if ( ubReason == TAKE_DAMAGE_EXPLOSION )
5593 {
5594 sReductionFactor /= 4;
5595 }
5596 if ( sReductionFactor > 1 )
5597 {
5598 sLifeDeduct = (sLifeDeduct + (sReductionFactor / 2 ) ) / sReductionFactor;
5599 }
5600 else if ( ubReason == TAKE_DAMAGE_EXPLOSION )
5601 {
5602 // take at most 2/3rds
5603 sLifeDeduct = (sLifeDeduct * 2) / 3;
5604 }
5605
5606 // reduce breath loss to a smaller degree, except for the queen...
5607 if ( pSoldier->ubBodyType == QUEENMONSTER )
5608 {
5609 // in fact, reduce breath loss by MORE!
5610 sReductionFactor = __min( sReductionFactor, 8 );
5611 sReductionFactor *= 2;
5612 }
5613 else
5614 {
5615 sReductionFactor /= 2;
5616 }
5617 if ( sReductionFactor > 1 )
5618 {
5619 sBreathLoss = (sBreathLoss + (sReductionFactor / 2 ) ) / sReductionFactor;
5620 }
5621 }
5622
5623 if (sLifeDeduct > pSoldier->bLife)
5624 {
5625 pSoldier->bLife = 0;
5626 }
5627 else
5628 {
5629 // Decrease Health
5630 pSoldier->bLife -= sLifeDeduct;
5631 }
5632
5633 // ATE: Put some logic in here to allow enemies to die quicker.....
5634 // Are we an enemy?
5635 if ( pSoldier->bSide != OUR_TEAM && !pSoldier->bNeutral && pSoldier->ubProfile == NO_PROFILE )
5636 {
5637 // ATE: Give them a chance to fall down...
5638 if ( pSoldier->bLife > 0 && pSoldier->bLife < ( OKLIFE - 1 ) )
5639 {
5640 // Are we taking damage from bleeding?
5641 if ( ubReason == TAKE_DAMAGE_BLOODLOSS )
5642 {
5643 // Fifty-fifty chance to die now!
5644 if ( Random( 2 ) == 0 || gTacticalStatus.Team[ pSoldier->bTeam ].bMenInSector == 1 )
5645 {
5646 // Kill!
5647 pSoldier->bLife = 0;
5648 }
5649 }
5650 else
5651 {
5652 // OK, see how far we are..
5653 if ( pSoldier->bLife < ( OKLIFE - 3 ) )
5654 {
5655 // Kill!
5656 pSoldier->bLife = 0;
5657 }
5658 }
5659 }
5660 }
5661
5662 pSoldier->sDamage += sLifeDeduct;
5663
5664 // Truncate life
5665 if ( pSoldier->bLife < 0 )
5666 {
5667 pSoldier->bLife = 0;
5668 }
5669
5670
5671 // Calculate damage to our items if from an explosion!
5672 if ( ubReason == TAKE_DAMAGE_EXPLOSION || ubReason == TAKE_DAMAGE_STRUCTURE_EXPLOSION)
5673 {
5674 CheckEquipmentForDamage( pSoldier, sLifeDeduct );
5675 }
5676
5677
5678
5679 // Calculate bleeding
5680 if ( ubReason != TAKE_DAMAGE_GAS && !AM_A_ROBOT( pSoldier ) )
5681 {
5682 if ( ubReason == TAKE_DAMAGE_HANDTOHAND )
5683 {
5684 if ( sLifeDeduct > 0 )
5685 {
5686 // HTH does 1 pt bleeding per hit
5687 pSoldier->bBleeding = pSoldier->bBleeding + 1;
5688 }
5689 }
5690 else
5691 {
5692 pSoldier->bBleeding = pSoldier->bLifeMax - ( pSoldier->bLife + bBandage );
5693 }
5694
5695 }
5696
5697 // Deduct breath AND APs!
5698 sAPCost = (sLifeDeduct / AP_GET_WOUNDED_DIVISOR); // + fallCost;
5699
5700 // ATE: if the robot, do not deduct
5701 if ( !AM_A_ROBOT( pSoldier ) )
5702 {
5703 DeductPoints( pSoldier, sAPCost, sBreathLoss );
5704 }
5705
5706 ubCombinedLoss = (UINT8) sLifeDeduct / 10 + sBreathLoss / 2000;
5707
5708 // Add shock
5709 if ( !AM_A_ROBOT( pSoldier ) )
5710 {
5711 pSoldier->bShock += ubCombinedLoss;
5712 }
5713
5714 // start the stopwatch - the blood is gushing!
5715 pSoldier->dNextBleed = CalcSoldierNextBleed( pSoldier );
5716
5717 if ( pSoldier->bInSector && pSoldier->bVisible != -1 )
5718 {
5719 // If we are already dead, don't show damage!
5720 if (bOldLife != 0 && sLifeDeduct != 0 && sLifeDeduct < 1000)
5721 {
5722 // Display damage
5723
5724 // Set Damage display counter
5725 pSoldier->fDisplayDamage = TRUE;
5726 pSoldier->bDisplayDamageCount = 0;
5727
5728 if ( pSoldier->ubBodyType == QUEENMONSTER )
5729 {
5730 pSoldier->sDamageX = 0;
5731 pSoldier->sDamageY = 0;
5732 }
5733 else
5734 {
5735 pSoldier->sDamageX = pSoldier->sBoundingBoxOffsetX;
5736 pSoldier->sDamageY = pSoldier->sBoundingBoxOffsetY;
5737 }
5738 }
5739 }
5740
5741 // OK, if here, let's see if we should drop our weapon....
5742 if ( ubReason != TAKE_DAMAGE_BLOODLOSS && !(AM_A_ROBOT( pSoldier )) )
5743 {
5744 INT16 sTestOne, sTestTwo, sChanceToDrop;
5745
5746 sTestOne = EffectiveStrength( pSoldier );
5747 sTestTwo = ( 2 * ( __max( sLifeDeduct, ( sBreathLoss / 100 ) ) ) );
5748
5749 const SOLDIERTYPE* const attacker = pSoldier->attacker;
5750 if (attacker != NULL && attacker->ubBodyType == BLOODCAT)
5751 {
5752 // bloodcat boost, let them make people drop items more
5753 sTestTwo += 20;
5754 }
5755
5756 // If damage > effective strength....
5757 sChanceToDrop = ( __max( 0, ( sTestTwo - sTestOne ) ) );
5758
5759 // ATE: Increase odds of NOT dropping an UNDROPPABLE OBJECT
5760 if ( ( pSoldier->inv[ HANDPOS ].fFlags & OBJECT_UNDROPPABLE ) )
5761 {
5762 sChanceToDrop -= 30;
5763 }
5764 SLOGD(
5765 "Chance To Drop Weapon: str: %d Dam: %d Chance: %d",
5766 sTestOne, sTestTwo, sChanceToDrop );
5767
5768 if ( Random( 100 ) < (UINT16) sChanceToDrop )
5769 {
5770 // OK, drop item in main hand...
5771 if ( pSoldier->inv[ HANDPOS ].usItem != NOTHING )
5772 {
5773 if ( !( pSoldier->inv[ HANDPOS ].fFlags & OBJECT_UNDROPPABLE ) )
5774 {
5775 // ATE: if our guy, make visible....
5776 Visibility const bVisible = pSoldier->bTeam == OUR_TEAM ?
5777 VISIBLE : INVISIBLE;
5778 AddItemToPool( pSoldier->sGridNo, &(pSoldier->inv[ HANDPOS ]), bVisible, pSoldier->bLevel, 0, -1 );
5779 DeleteObj( &(pSoldier->inv[HANDPOS]) );
5780 }
5781 }
5782 }
5783 }
5784
5785 // Drop some blood!
5786 // decide blood amt, if any
5787 ubBlood = ( sLifeDeduct / BLOODDIVISOR);
5788 if ( ubBlood > MAXBLOODQUANTITY )
5789 {
5790 ubBlood = MAXBLOODQUANTITY;
5791 }
5792
5793 if ( !( pSoldier->uiStatusFlags & ( SOLDIER_VEHICLE | SOLDIER_ROBOT ) ) )
5794 {
5795 if ( ubBlood != 0 )
5796 {
5797 if ( pSoldier->bInSector )
5798 {
5799 DropBlood(*pSoldier, ubBlood);
5800 }
5801 }
5802 }
5803
5804 //Set UI Flag for unconscious, if it's our own guy!
5805 if ( pSoldier->bTeam == OUR_TEAM )
5806 {
5807 if ( pSoldier->bLife < OKLIFE && pSoldier->bLife > 0 && bOldLife >= OKLIFE )
5808 {
5809 fInterfacePanelDirty = DIRTYLEVEL2;
5810 }
5811 }
5812
5813 if ( pSoldier->bInSector )
5814 {
5815 CheckForBreathCollapse(*pSoldier);
5816 }
5817
5818 // EXPERIENCE CLASS GAIN (combLoss): Getting wounded in battle
5819
5820 DirtyMercPanelInterface( pSoldier, DIRTYLEVEL1 );
5821
5822
5823 if (attacker != NULL)
5824 {
5825 // don't give exp for hitting friends!
5826 if (attacker->bTeam == OUR_TEAM && pSoldier->bTeam != OUR_TEAM)
5827 {
5828 if ( ubReason == TAKE_DAMAGE_EXPLOSION )
5829 {
5830 // EXPLOSIVES GAIN (combLoss): Causing wounds in battle
5831 StatChange(*attacker, EXPLODEAMT, 10 * ubCombinedLoss, FROM_FAILURE);
5832 }
5833 /*
5834 else if ( ubReason == TAKE_DAMAGE_GUNFIRE )
5835 {
5836 // MARKSMANSHIP GAIN (combLoss): Causing wounds in battle
5837 StatChange(*attacker, MARKAMT, 5 * ubCombinedLoss, FALSE);
5838 }*/
5839 }
5840 }
5841
5842 if (IsOnOurTeam(*pSoldier))
5843 {
5844 // EXPERIENCE GAIN: Took some damage
5845 StatChange(*pSoldier, EXPERAMT, 5 * ubCombinedLoss, FROM_FAILURE);
5846
5847 // Check for quote
5848 if ( !(pSoldier->usQuoteSaidFlags & SOLDIER_QUOTE_SAID_BEING_PUMMELED ) )
5849 {
5850 // Check attacker!
5851 if (attacker != NULL && attacker != pSoldier)
5852 {
5853 pSoldier->bNumHitsThisTurn++;
5854
5855 if ( (pSoldier->bNumHitsThisTurn >= 3) && ( pSoldier->bLife - pSoldier->bOldLife > 20 ) )
5856 {
5857 if ( Random(100) < (UINT16)((40 * ( pSoldier->bNumHitsThisTurn - 2))))
5858 {
5859 DelayedTacticalCharacterDialogue( pSoldier, QUOTE_TAKEN_A_BREATING );
5860 pSoldier->usQuoteSaidFlags |= SOLDIER_QUOTE_SAID_BEING_PUMMELED;
5861 pSoldier->bNumHitsThisTurn = 0;
5862 }
5863 }
5864 }
5865 }
5866 }
5867
5868 if (attacker != NULL && attacker->bTeam == OUR_TEAM && pSoldier->ubProfile != NO_PROFILE &&
5869 MercProfile(pSoldier->ubProfile).isNPCorRPC())
5870 {
5871 gMercProfiles[pSoldier->ubProfile].ubMiscFlags |= PROFILE_MISC_FLAG_WOUNDEDBYPLAYER;
5872 if (pSoldier->ubProfile == PACOS)
5873 {
5874 SetFactTrue( FACT_PACOS_KILLED );
5875 }
5876 }
5877
5878 HandleTakeDamageDeath( pSoldier, bOldLife, ubReason );
5879
5880 // Check if we are < unconscious, and shutup if so! also wipe sight
5881 if (pSoldier->bLife < CONSCIOUSNESS) ShutupaYoFace(pSoldier->face);
5882
5883 if ( pSoldier->bLife < OKLIFE )
5884 {
5885 DecayIndividualOpplist( pSoldier );
5886 }
5887
5888
5889 return( ubCombinedLoss );
5890 }
5891
5892
InternalDoMercBattleSound(SOLDIERTYPE * s,BattleSound battle_snd_id,INT8 const bSpecialCode)5893 BOOLEAN InternalDoMercBattleSound(SOLDIERTYPE* s, BattleSound battle_snd_id, INT8 const bSpecialCode)
5894 {
5895 CHECKF (battle_snd_id < NUM_MERC_BATTLE_SOUNDS);
5896
5897 if (s->uiStatusFlags & SOLDIER_VEHICLE)
5898 {
5899 // Pick a passenger from vehicle....
5900 s = PickRandomPassengerFromVehicle(s);
5901 if (!s) return FALSE;
5902 }
5903
5904 // Are we mute?
5905 if (s->uiStatusFlags & SOLDIER_MUTE) return FALSE;
5906
5907 // If we are a creature, etc, pick a better sound...
5908 SoundID sub_snd;
5909 switch (battle_snd_id)
5910 {
5911 case BATTLE_SOUND_HIT1:
5912 case BATTLE_SOUND_HIT2:
5913 switch (s->ubBodyType)
5914 {
5915 case COW: sub_snd = COW_HIT_SND; break;
5916 case YAF_MONSTER:
5917 case YAM_MONSTER:
5918 case ADULTFEMALEMONSTER:
5919 case AM_MONSTER: sub_snd = Random(2) == 0 ? ACR_DIE_PART1 : ACR_LUNGE; break;
5920 case INFANT_MONSTER: sub_snd = BCR_SHRIEK; break;
5921 case QUEENMONSTER: sub_snd = LQ_SHRIEK; break;
5922 case LARVAE_MONSTER: sub_snd = BCR_SHRIEK; break;
5923 case BLOODCAT: sub_snd = BLOODCAT_HIT_1; break;
5924 case ROBOTNOWEAPON: sub_snd = SoundRange<S_METAL_IMPACT1, S_METAL_IMPACT2>(); break;
5925
5926 default: goto no_sub;
5927 }
5928 break;
5929
5930 case BATTLE_SOUND_DIE1:
5931 switch (s->ubBodyType)
5932 {
5933 case COW: sub_snd = COW_DIE_SND; break;
5934 case YAF_MONSTER:
5935 case YAM_MONSTER:
5936 case ADULTFEMALEMONSTER:
5937 case AM_MONSTER: sub_snd = CREATURE_FALL_PART_2; break;
5938 case INFANT_MONSTER: sub_snd = BCR_DYING; break;
5939 case LARVAE_MONSTER: sub_snd = LCR_RUPTURE; break;
5940 case QUEENMONSTER: sub_snd = LQ_DYING; break;
5941 case BLOODCAT: sub_snd = BLOODCAT_DIE_1; break;
5942
5943 case ROBOTNOWEAPON:
5944 sub_snd = EXPLOSION_1;
5945 PlayJA2Sample(ROBOT_DEATH, HIGHVOLUME, 1, MIDDLEPAN);
5946 break;
5947
5948 default: goto no_sub;
5949 }
5950 break;
5951
5952 default:
5953 // OK. any other sound, not hits, robot makes a beep
5954 switch (s->ubBodyType)
5955 {
5956 case ROBOTNOWEAPON:
5957 sub_snd = battle_snd_id == BATTLE_SOUND_ATTN1 ?
5958 ROBOT_GREETING : ROBOT_BEEP;
5959 break;
5960
5961 default: goto no_sub;
5962 }
5963 break;
5964 }
5965
5966 if (guiCurrentScreen != GAME_SCREEN)
5967 {
5968 PlayJA2Sample(sub_snd, HIGHVOLUME, 1, MIDDLEPAN);
5969 }
5970 else
5971 {
5972 PlayLocationJA2Sample(s->sGridNo, sub_snd, CalculateSpeechVolume(HIGHVOLUME), 1);
5973 }
5974 return TRUE;
5975
5976 no_sub:
5977 BATTLESNDS_STRUCT const* battle_snd = &gBattleSndsData[battle_snd_id];
5978
5979 // Check if this is the same one we just played and we are below the min delay
5980 if (s->bOldBattleSnd == battle_snd_id &&
5981 battle_snd->fDontAllowTwoInRow &&
5982 GetJA2Clock() - s->uiTimeSameBattleSndDone < MIN_SUBSEQUENT_SNDS_DELAY)
5983 {
5984 return TRUE;
5985 }
5986
5987 // If a battle snd is STILL playing....
5988 if (SoundIsPlaying(s->uiBattleSoundID))
5989 {
5990 // If this is not crucial, skip it
5991 if (battle_snd->fStopDialogue != 1) return TRUE;
5992
5993 // Stop playing original
5994 SoundStop(s->uiBattleSoundID);
5995 }
5996
5997 // If we are talking now....
5998 if (IsMercSayingDialogue(s->ubProfile))
5999 {
6000 switch (battle_snd->fStopDialogue)
6001 {
6002 case 1: DialogueAdvanceSpeech(); break; // Stop dialogue
6003 case 2: return TRUE; // Skip battle snd
6004 }
6005 }
6006
6007 // Save this one we're doing...
6008 s->bOldBattleSnd = battle_snd_id;
6009 s->uiTimeSameBattleSndDone = GetJA2Clock();
6010
6011 // Adjust based on morale...
6012 if (s->bMorale < LOW_MORALE_BATTLE_SND_THREASHOLD)
6013 {
6014 switch (battle_snd_id)
6015 {
6016 case BATTLE_SOUND_OK1: battle_snd_id = BATTLE_SOUND_LOWMARALE_OK1; break;
6017 case BATTLE_SOUND_ATTN1: battle_snd_id = BATTLE_SOUND_LOWMARALE_ATTN1; break;
6018 default:
6019 break;
6020 }
6021 }
6022
6023 //if the sound to be played is a confirmation, check to see if we are to play it
6024 if (battle_snd_id == BATTLE_SOUND_OK1 &&
6025 gGameSettings.fOptions[TOPTION_MUTE_CONFIRMATIONS])
6026 {
6027 return TRUE;
6028 }
6029
6030 // Randomize between sounds, if appropriate
6031 if (battle_snd->ubRandomVal != 0)
6032 {
6033 battle_snd_id = static_cast<BattleSound>(battle_snd_id + Random(battle_snd->ubRandomVal));
6034 battle_snd = &gBattleSndsData[battle_snd_id];
6035 }
6036
6037 char basename[16];
6038 if (s->ubProfile != NO_PROFILE)
6039 {
6040 sprintf(basename, "%03d", s->ubProfile);
6041 }
6042 else
6043 {
6044 // Check if we can play this!
6045 if (!battle_snd->fBadGuy) return FALSE;
6046
6047 char const* const prefix = s->ubBodyType == HATKIDCIV ||
6048 s->ubBodyType == KIDCIV ? "kid" : "bad";
6049 sprintf(basename, "%s%d", prefix, s->ubBattleSoundID);
6050 }
6051
6052 SGPFILENAME filename;
6053 sprintf(filename, BATTLESNDSDIR "/%s_%s.wav", basename, battle_snd->zName);
6054
6055 if (!GCM->doesGameResExists(filename))
6056 {
6057 if (battle_snd_id == BATTLE_SOUND_DIE1)
6058 {
6059 // The "die" sound filenames differs between profiles and languages
6060 sprintf(filename, BATTLESNDSDIR "/%s_dying.wav", basename);
6061 if (GCM->doesGameResExists(filename)) goto file_exists;
6062 }
6063
6064 if (s->ubProfile == NO_PROFILE) return FALSE;
6065
6066 // Generic replacement voices
6067 char const prefix = s->ubBodyType == REGFEMALE ? 'f' : 'm';
6068 sprintf(filename, BATTLESNDSDIR "/%c_%s.wav", prefix, battle_snd->zName);
6069 }
6070 file_exists:;
6071
6072 // ATE: Reduce volume for OK sounds...
6073 // (Only for all-moves or multi-selection cases)
6074 UINT32 const base_volume = bSpecialCode == BATTLE_SND_LOWER_VOLUME ? MIDVOLUME : HIGHVOLUME;
6075 UINT32 volume = CalculateSpeechVolume(base_volume);
6076
6077 // If we are an enemy.....reduce due to volume
6078 if (s->bTeam != OUR_TEAM)
6079 {
6080 volume = SoundVolume(volume, s->sGridNo);
6081 }
6082
6083 UINT32 const pan = SoundDir(s->sGridNo);
6084 UINT32 const uiSoundID = SoundPlay(filename, volume, pan, 1, NULL, NULL);
6085 if (uiSoundID == SOUND_ERROR) return FALSE;
6086 s->uiBattleSoundID = uiSoundID;
6087
6088 if (s->ubProfile != NO_PROFILE)
6089 {
6090 FACETYPE* const face = s->face;
6091 if (face) ExternSetFaceTalking(*face, uiSoundID);
6092 }
6093
6094 return TRUE;
6095 }
6096
6097
MakeCharacterDialogueEventDoBattleSound(SOLDIERTYPE & s,BattleSound const sound,UINT32 const delay)6098 void MakeCharacterDialogueEventDoBattleSound(SOLDIERTYPE& s, BattleSound const sound, UINT32 const delay)
6099 {
6100 class CharacterDialogueEventDoBattleSound : public CharacterDialogueEvent
6101 {
6102 public:
6103 CharacterDialogueEventDoBattleSound(SOLDIERTYPE& s, BattleSound const sound, UINT32 const delay) :
6104 CharacterDialogueEvent(s),
6105 sound_(sound),
6106 time_stamp_(GetJA2Clock()),
6107 delay_(delay)
6108 {}
6109
6110 bool Execute()
6111 {
6112 // ATE: If a battle sound, and delay value was given, set time stamp now
6113 if (delay_ != 0 && GetJA2Clock() - time_stamp_ < delay_) return true;
6114
6115 if (!MayExecute()) return true;
6116
6117 InternalDoMercBattleSound(&soldier_, sound_, 0);
6118 return false;
6119 }
6120
6121 private:
6122 BattleSound const sound_;
6123 UINT32 const time_stamp_;
6124 UINT32 const delay_;
6125 };
6126
6127 DialogueEvent::Add(new CharacterDialogueEventDoBattleSound(s, sound, delay));
6128 }
6129
6130
DoMercBattleSound(SOLDIERTYPE * const s,BattleSound const battle_snd_id)6131 BOOLEAN DoMercBattleSound(SOLDIERTYPE* const s, BattleSound const battle_snd_id)
6132 {
6133 // We WANT to play some RIGHT AWAY or merc is not saying anything right now
6134 if (gBattleSndsData[battle_snd_id].fStopDialogue == 1 ||
6135 s->ubProfile == NO_PROFILE ||
6136 InOverheadMap() ||
6137 !IsMercSayingDialogue(s->ubProfile))
6138 {
6139 return InternalDoMercBattleSound(s, battle_snd_id, 0);
6140 }
6141
6142 // OK, queue it up otherwise!
6143 MakeCharacterDialogueEventDoBattleSound(*s, battle_snd_id, 0);
6144 return TRUE;
6145 }
6146
6147
CheckSoldierHitRoof(SOLDIERTYPE * pSoldier)6148 BOOLEAN CheckSoldierHitRoof( SOLDIERTYPE *pSoldier )
6149 {
6150 // Check if we are near a lower level
6151 UINT8 bNewDirection;
6152 BOOLEAN fReturnVal = FALSE;
6153 INT16 sNewGridNo;
6154 // Default to true
6155 BOOLEAN fDoForwards = TRUE;
6156
6157 if ( pSoldier->bLife >= OKLIFE )
6158 {
6159 return( FALSE );
6160 }
6161
6162 if (FindLowerLevel(pSoldier, &bNewDirection))
6163 {
6164 // ONly if standing!
6165 if ( gAnimControl[ pSoldier->usAnimState ].ubHeight == ANIM_STAND )
6166 {
6167 // We are near a lower level.
6168 // Use opposite direction
6169 bNewDirection = OppositeDirection(bNewDirection);
6170
6171 // Alrighty, let's not blindly change here, look at whether the dest gridno is good!
6172 sNewGridNo = NewGridNo(pSoldier->sGridNo, DirectionInc(OppositeDirection(bNewDirection)));
6173 if ( !NewOKDestination( pSoldier, sNewGridNo, TRUE, 0 ) )
6174 {
6175 return( FALSE );
6176 }
6177 sNewGridNo = NewGridNo(sNewGridNo, DirectionInc(OppositeDirection(bNewDirection)));
6178 if ( !NewOKDestination( pSoldier, sNewGridNo, TRUE, 0 ) )
6179 {
6180 return( FALSE );
6181 }
6182
6183 // Are wee near enough to fall forwards....
6184 if (pSoldier->bDirection == OneCDirection(bNewDirection) ||
6185 pSoldier->bDirection == TwoCDirection(bNewDirection) ||
6186 pSoldier->bDirection == bNewDirection ||
6187 pSoldier->bDirection == OneCCDirection(bNewDirection) ||
6188 pSoldier->bDirection == TwoCCDirection(bNewDirection))
6189 {
6190 // Do backwards...
6191 fDoForwards = FALSE;
6192 }
6193
6194 // If we are facing the opposite direction, fall backwards
6195 // ATE: Make this more usefull...
6196 if ( fDoForwards )
6197 {
6198 pSoldier->sTempNewGridNo = NewGridNo( (UINT16)pSoldier->sGridNo, DirectionInc( OppositeDirection( bNewDirection ) ) );
6199 pSoldier->sTempNewGridNo = NewGridNo( (UINT16)pSoldier->sTempNewGridNo, DirectionInc( OppositeDirection( bNewDirection ) ) );
6200 EVENT_SetSoldierDesiredDirection(pSoldier, OppositeDirection(bNewDirection));
6201 pSoldier->fTurningUntilDone = TRUE;
6202 pSoldier->usPendingAnimation = FALLFORWARD_ROOF;
6203 //EVENT_InitNewSoldierAnim( pSoldier, FALLFORWARD_ROOF, 0 , FALSE );
6204
6205 // Deduct hitpoints/breath for falling!
6206 SoldierTakeDamage(pSoldier, 100, 5000, TAKE_DAMAGE_FALLROOF, NULL);
6207
6208 fReturnVal = TRUE;
6209
6210 }
6211 else
6212 {
6213
6214 pSoldier->sTempNewGridNo = NewGridNo( (UINT16)pSoldier->sGridNo, DirectionInc( OppositeDirection( bNewDirection ) ) );
6215 pSoldier->sTempNewGridNo = NewGridNo( (UINT16)pSoldier->sTempNewGridNo, DirectionInc( OppositeDirection( bNewDirection ) ) );
6216 EVENT_SetSoldierDesiredDirection( pSoldier, bNewDirection );
6217 pSoldier->fTurningUntilDone = TRUE;
6218 pSoldier->usPendingAnimation = FALLOFF;
6219
6220 // Deduct hitpoints/breath for falling!
6221 SoldierTakeDamage(pSoldier, 100, 5000, TAKE_DAMAGE_FALLROOF, NULL);
6222
6223 fReturnVal = TRUE;
6224 }
6225 }
6226 }
6227
6228 return( fReturnVal );
6229 }
6230
6231
BeginSoldierClimbDownRoof(SOLDIERTYPE * const s)6232 void BeginSoldierClimbDownRoof(SOLDIERTYPE* const s)
6233 {
6234 UINT8 direction;
6235 if (!FindLowerLevel(s, &direction)) return;
6236
6237 if (!EnoughPoints(s, GetAPsToClimbRoof(s, TRUE), 0, TRUE)) return;
6238
6239 if (s->bTeam == OUR_TEAM) SetUIBusy(s);
6240
6241 s->sTempNewGridNo = NewGridNo(s->sGridNo, DirectionInc(direction));
6242 s->ubPendingDirection = TwoCDirection(direction);
6243 EVENT_InitNewSoldierAnim(s, CLIMBDOWNROOF, 0, FALSE);
6244 InternalReceivingSoldierCancelServices(s, FALSE);
6245 InternalGivingSoldierCancelServices(s, FALSE);
6246 }
6247
6248
MoveMerc(SOLDIERTYPE * pSoldier,FLOAT dMovementChange,FLOAT dAngle,BOOLEAN fCheckRange)6249 void MoveMerc( SOLDIERTYPE *pSoldier, FLOAT dMovementChange, FLOAT dAngle, BOOLEAN fCheckRange )
6250 {
6251 FLOAT dDeltaPos;
6252 FLOAT dXPos , dYPos;
6253 BOOLEAN fStop = FALSE;
6254
6255 // Find delta Movement for X pos
6256 dDeltaPos = (FLOAT) (dMovementChange * sin( dAngle ));
6257
6258 // Find new position
6259 dXPos = pSoldier->dXPos + dDeltaPos;
6260
6261 if ( fCheckRange )
6262 {
6263 fStop = FALSE;
6264
6265 switch( pSoldier->bMovementDirection )
6266 {
6267 case NORTHEAST:
6268 case EAST:
6269 case SOUTHEAST:
6270
6271 if ( dXPos >= pSoldier->sDestXPos )
6272 {
6273 fStop = TRUE;
6274 }
6275 break;
6276
6277 case NORTHWEST:
6278 case WEST:
6279 case SOUTHWEST:
6280
6281 if ( dXPos <= pSoldier->sDestXPos )
6282 {
6283 fStop = TRUE;
6284 }
6285 break;
6286
6287 case NORTH:
6288 case SOUTH:
6289
6290 fStop = TRUE;
6291 break;
6292
6293 }
6294
6295 if ( fStop )
6296 {
6297 //dXPos = pSoldier->sDestXPos;
6298 pSoldier->fPastXDest = TRUE;
6299
6300 if ( pSoldier->sGridNo == pSoldier->sFinalDestination )
6301 {
6302 dXPos = pSoldier->sDestXPos;
6303 }
6304 }
6305 }
6306
6307 // Find delta Movement for Y pos
6308 dDeltaPos = (FLOAT) (dMovementChange * cos( dAngle ));
6309
6310 // Find new pos
6311 dYPos = pSoldier->dYPos + dDeltaPos;
6312
6313 if ( fCheckRange )
6314 {
6315 fStop = FALSE;
6316
6317 switch( pSoldier->bMovementDirection )
6318 {
6319 case NORTH:
6320 case NORTHEAST:
6321 case NORTHWEST:
6322
6323 if ( dYPos <= pSoldier->sDestYPos )
6324 {
6325 fStop = TRUE;
6326 }
6327 break;
6328
6329 case SOUTH:
6330 case SOUTHWEST:
6331 case SOUTHEAST:
6332
6333 if ( dYPos >= pSoldier->sDestYPos )
6334 {
6335 fStop = TRUE;
6336 }
6337 break;
6338
6339 case EAST:
6340 case WEST:
6341
6342 fStop = TRUE;
6343 break;
6344
6345 }
6346
6347 if ( fStop )
6348 {
6349 //dYPos = pSoldier->sDestYPos;
6350 pSoldier->fPastYDest = TRUE;
6351
6352 if ( pSoldier->sGridNo == pSoldier->sFinalDestination )
6353 {
6354 dYPos = pSoldier->sDestYPos;
6355 }
6356 }
6357 }
6358
6359 // OK, set new position
6360 EVENT_SetSoldierPositionXY(pSoldier, dXPos, dYPos, SSP_NO_DEST | SSP_NO_FINAL_DEST);
6361 }
6362
6363
GetDirectionFromGridNo(const INT16 sGridNo,const SOLDIERTYPE * const s)6364 INT16 GetDirectionFromGridNo(const INT16 sGridNo, const SOLDIERTYPE* const s)
6365 {
6366 return GetDirectionToGridNoFromGridNo(s->sGridNo, sGridNo);
6367 }
6368
6369
GetDirectionToGridNoFromGridNo(INT16 sGridNoDest,INT16 sGridNoSrc)6370 INT16 GetDirectionToGridNoFromGridNo( INT16 sGridNoDest, INT16 sGridNoSrc )
6371 {
6372 INT16 sXPos2, sYPos2;
6373 INT16 sXPos, sYPos;
6374
6375 ConvertGridNoToXY( sGridNoSrc, &sXPos, &sYPos );
6376 ConvertGridNoToXY( sGridNoDest, &sXPos2, &sYPos2 );
6377
6378 return( atan8( sXPos2, sYPos2, sXPos, sYPos ) );
6379
6380 }
6381
6382
6383 #if 0
6384 UINT8 atan8( INT16 x1, INT16 y1, INT16 x2, INT16 y2 )
6385 {
6386 static int trig[8] = { 2, 3, 4, 5, 6, 7, 8, 1 };
6387 // returned values are N=1, NE=2, E=3, SE=4, S=5, SW=6, W=7, NW=8
6388 double dx=(x2-x1);
6389 double dy=(y2-y1);
6390 double a;
6391 int i,k;
6392 if (dx==0)
6393 dx=0.00390625; // 1/256th
6394 #define PISLICES (8)
6395 a=(atan2(dy,dx) + PI/PISLICES)/(PI/(PISLICES/2));
6396 i=(int)a;
6397 if (a>0)
6398 k=i; else
6399 if (a<0)
6400 k=i+(PISLICES-1); else
6401 k=0;
6402 return(trig[k]);
6403 }
6404 #endif
6405
6406 //#if 0
atan8(INT16 sXPos,INT16 sYPos,INT16 sXPos2,INT16 sYPos2)6407 UINT8 atan8( INT16 sXPos, INT16 sYPos, INT16 sXPos2, INT16 sYPos2 )
6408 {
6409 DOUBLE test_x = sXPos2 - sXPos;
6410 DOUBLE test_y = sYPos2 - sYPos;
6411 UINT8 mFacing = WEST;
6412 DOUBLE angle;
6413
6414 if ( test_x == 0 )
6415 {
6416 test_x = 0.04;
6417 }
6418
6419 angle = atan2( test_x, test_y );
6420
6421 do
6422 {
6423 if ( angle >=-PI*.375 && angle <= -PI*.125 )
6424 {
6425 mFacing = SOUTHWEST;
6426 break;
6427 }
6428
6429 if ( angle <= PI*.375 && angle >= PI*.125 )
6430 {
6431 mFacing = SOUTHEAST;
6432 break;
6433 }
6434
6435 if ( angle >=PI*.623 && angle <= PI*.875 )
6436 {
6437 mFacing = NORTHEAST;
6438 break;
6439 }
6440
6441 if ( angle <=-PI*.623 && angle >= -PI*.875 )
6442 {
6443 mFacing = NORTHWEST;
6444 break;
6445 }
6446
6447 if ( angle >-PI*0.125 && angle < PI*0.125 )
6448 {
6449 mFacing = SOUTH;
6450 }
6451 if ( angle > PI*0.375 && angle < PI*0.623 )
6452 {
6453 mFacing = EAST;
6454 }
6455 if ( ( angle > PI*0.875 && angle <= PI ) || ( angle > -PI && angle < -PI*0.875 ) )
6456 {
6457 mFacing = NORTH;
6458 }
6459 if ( angle > -PI*0.623 && angle < -PI*0.375 )
6460 {
6461 mFacing = WEST;
6462 }
6463
6464 } while( FALSE );
6465
6466 return( mFacing );
6467 }
6468
6469
AdjustForFastTurnAnimation(SOLDIERTYPE * pSoldier)6470 static void AdjustForFastTurnAnimation(SOLDIERTYPE* pSoldier)
6471 {
6472
6473 // CHECK FOR FASTTURN ANIMATIONS
6474 // ATE: Mod: Only fastturn for OUR guys!
6475 if ( gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_FASTTURN &&
6476 pSoldier->bTeam == OUR_TEAM && !( pSoldier->uiStatusFlags & SOLDIER_TURNINGFROMHIT ) )
6477 {
6478 if ( pSoldier->bDirection != pSoldier->bDesiredDirection )
6479 {
6480 pSoldier->sAniDelay = FAST_TURN_ANIM_SPEED;
6481 }
6482 else
6483 {
6484 SetSoldierAniSpeed( pSoldier );
6485 }
6486 }
6487 }
6488
6489
SendSoldierSetDesiredDirectionEvent(const SOLDIERTYPE * pSoldier,UINT16 usDesiredDirection)6490 void SendSoldierSetDesiredDirectionEvent(const SOLDIERTYPE* pSoldier, UINT16 usDesiredDirection)
6491 {
6492 // Sent event for position update
6493 EV_S_SETDESIREDDIRECTION SSetDesiredDirection;
6494
6495 SSetDesiredDirection.usSoldierID = pSoldier->ubID;
6496 SSetDesiredDirection.usDesiredDirection = usDesiredDirection;
6497 SSetDesiredDirection.uiUniqueId = pSoldier -> uiUniqueSoldierIdValue;
6498
6499 AddGameEvent(SSetDesiredDirection, 0);
6500 }
6501
6502
SendGetNewSoldierPathEvent(SOLDIERTYPE * const pSoldier,UINT16 const sDestGridNo)6503 void SendGetNewSoldierPathEvent(SOLDIERTYPE* const pSoldier, UINT16 const sDestGridNo)
6504 {
6505 EV_S_GETNEWPATH SGetNewPath;
6506
6507 SGetNewPath.usSoldierID = pSoldier->ubID;
6508 SGetNewPath.sDestGridNo = sDestGridNo;
6509 SGetNewPath.usMovementAnim = pSoldier->usUIMovementMode;
6510 SGetNewPath.uiUniqueId = pSoldier -> uiUniqueSoldierIdValue;
6511
6512 AddGameEvent(SGetNewPath, 0);
6513 }
6514
6515
SendBeginFireWeaponEvent(SOLDIERTYPE * pSoldier,INT16 sTargetGridNo)6516 void SendBeginFireWeaponEvent( SOLDIERTYPE *pSoldier, INT16 sTargetGridNo )
6517 {
6518 EV_S_BEGINFIREWEAPON SBeginFireWeapon;
6519
6520 SBeginFireWeapon.usSoldierID = pSoldier->ubID;
6521 SBeginFireWeapon.sTargetGridNo = sTargetGridNo;
6522 SBeginFireWeapon.bTargetLevel = pSoldier->bTargetLevel;
6523 SBeginFireWeapon.bTargetCubeLevel = pSoldier->bTargetCubeLevel;
6524 SBeginFireWeapon.uiUniqueId = pSoldier -> uiUniqueSoldierIdValue;
6525
6526 AddGameEvent(SBeginFireWeapon, 0);
6527 }
6528
6529 // This function just encapolates the check for turnbased and having an attacker in the first place
ReleaseSoldiersAttacker(SOLDIERTYPE * pSoldier)6530 void ReleaseSoldiersAttacker( SOLDIERTYPE *pSoldier )
6531 {
6532 INT32 cnt;
6533 UINT8 ubNumToFree;
6534
6535 //if (gTacticalStatus.uiFlags & INCOMBAT)
6536 {
6537 // ATE: Removed...
6538 //if (pSoldier->attacker != NULL)
6539 {
6540 // JA2 Gold
6541 // set next-to-previous attacker, so long as this isn't a repeat attack
6542 if (pSoldier->previous_attacker != pSoldier->attacker)
6543 {
6544 pSoldier->next_to_previous_attacker = pSoldier->previous_attacker;
6545 }
6546
6547 // get previous attacker id
6548 pSoldier->previous_attacker = pSoldier->attacker;
6549
6550 // Copy BeingAttackedCount here....
6551 ubNumToFree = pSoldier->bBeingAttackedCount;
6552 // Zero it out BEFORE, as supression may increase it again...
6553 pSoldier->bBeingAttackedCount = 0;
6554
6555 for ( cnt = 0; cnt < ubNumToFree; cnt++ )
6556 {
6557 SLOGD("Freeing up attacker of %d (attacker is %d)\n\
6558 releasesoldierattacker num to free is %d",
6559 pSoldier->ubID, SOLDIER2ID(pSoldier->attacker), ubNumToFree);
6560 ReduceAttackBusyCount(pSoldier->attacker, FALSE);
6561 }
6562
6563 // ATE: Set to NOBODY if this person is NOT dead
6564 // otherise, we keep it so the kill can be awarded!
6565 if ( pSoldier->bLife != 0 && pSoldier->ubBodyType != QUEENMONSTER )
6566 {
6567 pSoldier->attacker = NULL;
6568 }
6569 }
6570 }
6571 }
6572
6573
MercInWater(const SOLDIERTYPE * pSoldier)6574 BOOLEAN MercInWater(const SOLDIERTYPE* pSoldier)
6575 {
6576 // Our water texture , for now is of a given type
6577 if ( pSoldier->bOverTerrainType == LOW_WATER || pSoldier->bOverTerrainType == MED_WATER ||
6578 pSoldier->bOverTerrainType == DEEP_WATER )
6579 {
6580 return( TRUE );
6581 }
6582 else
6583 {
6584 return( FALSE );
6585 }
6586 }
6587
6588
ReviveSoldier(SOLDIERTYPE * pSoldier)6589 void ReviveSoldier( SOLDIERTYPE *pSoldier )
6590 {
6591 if ( pSoldier->bLife < OKLIFE && pSoldier->bActive )
6592 {
6593 // If dead or unconscious, revive!
6594 pSoldier->uiStatusFlags &= ( ~SOLDIER_DEAD );
6595
6596 pSoldier->bLife = pSoldier->bLifeMax;
6597 pSoldier->bBleeding = 0;
6598 pSoldier->ubDesiredHeight = ANIM_STAND;
6599
6600 AddManToTeam( pSoldier->bTeam );
6601
6602 // Set to standing
6603 pSoldier->fInNonintAnim = FALSE;
6604 pSoldier->fRTInNonintAnim = FALSE;
6605
6606 // Change to standing,unless we can getup with an animation
6607 EVENT_InitNewSoldierAnim( pSoldier, STANDING, 0, TRUE );
6608 BeginSoldierGetup( pSoldier );
6609
6610 EVENT_SetSoldierPosition(pSoldier, pSoldier->sGridNo, SSP_NONE);
6611
6612 // Dirty INterface
6613 fInterfacePanelDirty = DIRTYLEVEL2;
6614 }
6615 }
6616
6617
HandleAnimationProfile(SOLDIERTYPE & s,UINT16 const usAnimState,BOOLEAN const fRemove)6618 static void HandleAnimationProfile(SOLDIERTYPE& s, UINT16 const usAnimState, BOOLEAN const fRemove)
6619 {
6620 UINT16 const anim_surface = DetermineSoldierAnimationSurface(&s, usAnimState);
6621 CHECKV(anim_surface != INVALID_ANIMATION_SURFACE);
6622
6623 INT8 const profile_id = gAnimSurfaceDatabase[anim_surface].bProfile;
6624 if (profile_id == -1) return;
6625
6626 ANIM_PROF const& profile = gpAnimProfiles[profile_id];
6627 ANIM_PROF_DIR const& profile_dir = profile.Dirs[s.bDirection];
6628
6629 // Loop tiles and set accordingly into world
6630 for (UINT32 tile_count = 0; tile_count != profile_dir.ubNumTiles; ++tile_count)
6631 {
6632 ANIM_PROF_TILE const& profile_tile = profile_dir.pTiles[tile_count];
6633 GridNo const grid_no = s.sGridNo + WORLD_COLS * profile_tile.bTileY + profile_tile.bTileX;
6634
6635 // Check if in bounds
6636 if (OutOfBounds(s.sGridNo, grid_no)) continue;
6637
6638 if (fRemove)
6639 {
6640 // Remove from world
6641 RemoveMerc(grid_no, s, true);
6642 }
6643 else
6644 {
6645 // Place into world
6646 LEVELNODE* const n = AddMercToHead(grid_no, s, FALSE);
6647 n->uiFlags |= LEVELNODE_MERCPLACEHOLDER;
6648 n->uiAnimHitLocationFlags = profile_tile.usTileFlags;
6649 }
6650 }
6651 }
6652
6653
EVENT_SoldierBeginGiveItem(SOLDIERTYPE * pSoldier)6654 void EVENT_SoldierBeginGiveItem( SOLDIERTYPE *pSoldier )
6655 {
6656 if (VerifyGiveItem(pSoldier) != NULL)
6657 {
6658 // CHANGE DIRECTION AND GOTO ANIMATION NOW
6659 pSoldier->bDesiredDirection = pSoldier->bPendingActionData3;
6660 pSoldier->bDirection = pSoldier->bPendingActionData3;
6661
6662 // begin animation
6663 EVENT_InitNewSoldierAnim( pSoldier, GIVE_ITEM, 0 , FALSE );
6664
6665 }
6666 else
6667 {
6668 UnSetEngagedInConvFromPCAction( pSoldier );
6669
6670 delete pSoldier->pTempObject;
6671 pSoldier->pTempObject = nullptr;
6672 }
6673 }
6674
6675
EVENT_SoldierBeginBladeAttack(SOLDIERTYPE * pSoldier,INT16 sGridNo,UINT8 ubDirection)6676 void EVENT_SoldierBeginBladeAttack( SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 ubDirection )
6677 {
6678 //UINT32 uiMercFlags;
6679 UINT8 ubTDirection;
6680
6681 // Increment the number of people busy doing stuff because of an attack
6682 //if (gTacticalStatus.uiFlags & INCOMBAT)
6683 //{
6684 gTacticalStatus.ubAttackBusyCount++;
6685 SLOGD("Begin blade attack: ATB %d", gTacticalStatus.ubAttackBusyCount);
6686
6687 //}
6688
6689 // CHANGE DIRECTION AND GOTO ANIMATION NOW
6690 EVENT_SetSoldierDesiredDirection( pSoldier, ubDirection );
6691 EVENT_SetSoldierDirection( pSoldier, ubDirection );
6692 // CHANGE TO ANIMATION
6693
6694 // DETERMINE ANIMATION TO PLAY
6695 // LATER BASED ON IF TAREGT KNOWS OF US, STANCE, ETC
6696 // GET POINTER TO TAREGT
6697 if (pSoldier->uiStatusFlags & SOLDIER_MONSTER)
6698 {
6699 // Is there an unconscious guy at gridno......
6700 const SOLDIERTYPE* const tgt = WhoIsThere2(sGridNo, pSoldier->bTargetLevel);
6701 if (tgt != NULL && ((tgt->bLife < OKLIFE && tgt->bLife > 0) || (tgt->bBreath < OKBREATH && tgt->bCollapsed)))
6702 {
6703 pSoldier->uiPendingActionData4 = tgt->ubID;
6704 // add regen bonus
6705 pSoldier->bRegenerationCounter++;
6706 EVENT_InitNewSoldierAnim( pSoldier, MONSTER_BEGIN_EATTING_FLESH, 0, FALSE );
6707 }
6708 else
6709 {
6710 if ( PythSpacesAway( pSoldier->sGridNo, sGridNo ) <= 1 )
6711 {
6712 EVENT_InitNewSoldierAnim( pSoldier, MONSTER_CLOSE_ATTACK, 0, FALSE );
6713 }
6714 else
6715 {
6716 EVENT_InitNewSoldierAnim( pSoldier, ADULTMONSTER_ATTACKING, 0, FALSE );
6717 }
6718 }
6719 }
6720 else if (pSoldier->ubBodyType == BLOODCAT)
6721 {
6722 // Check if it's a claws or teeth...
6723 if ( pSoldier->inv[ HANDPOS ].usItem == BLOODCAT_CLAW_ATTACK )
6724 {
6725 EVENT_InitNewSoldierAnim( pSoldier, BLOODCAT_SWIPE, 0, FALSE );
6726 }
6727 else
6728 {
6729 EVENT_InitNewSoldierAnim( pSoldier, BLOODCAT_BITE_ANIM, 0, FALSE );
6730 }
6731 }
6732 else
6733 {
6734 SOLDIERTYPE* const pTSoldier = WhoIsThere2(sGridNo, pSoldier->bTargetLevel);
6735 if (pTSoldier != NULL)
6736 {
6737 // Look at stance of target
6738 switch( gAnimControl[ pTSoldier->usAnimState ].ubEndHeight )
6739 {
6740 case ANIM_STAND:
6741 case ANIM_CROUCH:
6742
6743 // CHECK IF HE CAN SEE US, IF SO RANDOMIZE
6744 if ( pTSoldier->bOppList[ pSoldier->ubID ] == 0 && pTSoldier->bTeam != pSoldier->bTeam )
6745 {
6746 // WE ARE NOT SEEN
6747 EVENT_InitNewSoldierAnim( pSoldier, STAB, 0 , FALSE );
6748 }
6749 else
6750 {
6751 // WE ARE SEEN
6752 if ( Random( 50 ) > 25 )
6753 {
6754 EVENT_InitNewSoldierAnim( pSoldier, STAB, 0 , FALSE );
6755 }
6756 else
6757 {
6758 EVENT_InitNewSoldierAnim( pSoldier, SLICE, 0 , FALSE );
6759 }
6760
6761 // IF WE ARE SEEN, MAKE SURE GUY TURNS!
6762 // Get direction to target
6763 // IF WE ARE AN ANIMAL, CAR, MONSTER, DONT'T TURN
6764 if ( !( pTSoldier->uiStatusFlags & ( SOLDIER_MONSTER | SOLDIER_ANIMAL | SOLDIER_VEHICLE ) ) )
6765 {
6766 // OK, stop merc....
6767 EVENT_StopMerc(pTSoldier);
6768
6769 if ( pTSoldier->bTeam != OUR_TEAM )
6770 {
6771 CancelAIAction(pTSoldier);
6772 }
6773
6774 ubTDirection = (UINT8)GetDirectionFromGridNo( pSoldier->sGridNo, pTSoldier );
6775 SendSoldierSetDesiredDirectionEvent( pTSoldier, ubTDirection );
6776 }
6777 }
6778
6779 break;
6780
6781 case ANIM_PRONE:
6782
6783 // CHECK OUR STANCE
6784 if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight != ANIM_CROUCH )
6785 {
6786 // SET DESIRED STANCE AND SET PENDING ANIMATION
6787 ChangeSoldierStance(pSoldier, ANIM_CROUCH);
6788 pSoldier->usPendingAnimation = CROUCH_STAB;
6789 }
6790 else
6791 {
6792 // USE crouched one
6793 // NEED TO CHANGE STANCE IF NOT CROUCHD!
6794 EVENT_InitNewSoldierAnim( pSoldier, CROUCH_STAB, 0 , FALSE );
6795 }
6796 break;
6797 }
6798 }
6799 else
6800 {
6801 // OK, SEE IF THERE IS AN OBSTACLE HERE...
6802 if ( !NewOKDestination( pSoldier, sGridNo, FALSE, pSoldier->bLevel ) )
6803 {
6804 EVENT_InitNewSoldierAnim( pSoldier, STAB, 0 , FALSE );
6805 }
6806 else
6807 {
6808 const ROTTING_CORPSE* const c = GetCorpseAtGridNo(sGridNo, pSoldier->bLevel);
6809 const UINT16 state = (c != NULL && IsValidDecapitationCorpse(c) ? DECAPITATE : CROUCH_STAB);
6810 EVENT_InitNewSoldierAnim(pSoldier, state, 0, FALSE);
6811 }
6812 }
6813 }
6814
6815 // SET TARGET GRIDNO
6816 pSoldier->sTargetGridNo = sGridNo;
6817 pSoldier->bTargetLevel = pSoldier->bLevel;
6818 pSoldier->target = WhoIsThere2(sGridNo, pSoldier->bTargetLevel);
6819 }
6820
6821
EVENT_SoldierBeginPunchAttack(SOLDIERTYPE * pSoldier,INT16 sGridNo,UINT8 ubDirection)6822 void EVENT_SoldierBeginPunchAttack( SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 ubDirection )
6823 {
6824 //UINT32 uiMercFlags;
6825 UINT8 ubTDirection;
6826 BOOLEAN fChangeDirection = FALSE;
6827 UINT16 usItem;
6828
6829 // Get item in hand...
6830 usItem = pSoldier->inv[ HANDPOS ].usItem;
6831
6832
6833 // Increment the number of people busy doing stuff because of an attack
6834 //if (gTacticalStatus.uiFlags & INCOMBAT)
6835 //{
6836 gTacticalStatus.ubAttackBusyCount++;
6837 SLOGD("Begin HTH attack: ATB %d", gTacticalStatus.ubAttackBusyCount);
6838
6839 //}
6840
6841 // get target.....
6842 SOLDIERTYPE* const pTSoldier = WhoIsThere2(pSoldier->sTargetGridNo, pSoldier->bLevel);
6843 if (pTSoldier == NULL) return;
6844
6845 fChangeDirection = TRUE;
6846
6847
6848 if ( fChangeDirection )
6849 {
6850 // CHANGE DIRECTION AND GOTO ANIMATION NOW
6851 EVENT_SetSoldierDesiredDirection( pSoldier, ubDirection );
6852 EVENT_SetSoldierDirection( pSoldier, ubDirection );
6853 }
6854
6855
6856 if (HAS_SKILL_TRAIT(pSoldier, MARTIALARTS) && !AreInMeanwhile() && usItem != CROWBAR)
6857 {
6858 // Are we in attack mode yet?
6859 if ( pSoldier->usAnimState != NINJA_BREATH && gAnimControl[ pSoldier->usAnimState ].ubHeight == ANIM_STAND && gAnimControl[ pTSoldier->usAnimState ].ubHeight != ANIM_PRONE )
6860 {
6861 EVENT_InitNewSoldierAnim( pSoldier, NINJA_GOTOBREATH, 0 , FALSE );
6862 }
6863 else
6864 {
6865 DoNinjaAttack( pSoldier );
6866 }
6867 }
6868 else
6869 {
6870 // Look at stance of target
6871 switch( gAnimControl[ pTSoldier->usAnimState ].ubEndHeight )
6872 {
6873 case ANIM_STAND:
6874 case ANIM_CROUCH:
6875
6876 if ( usItem != CROWBAR )
6877 {
6878 EVENT_InitNewSoldierAnim( pSoldier, PUNCH, 0 , FALSE );
6879 }
6880 else
6881 {
6882 EVENT_InitNewSoldierAnim( pSoldier, CROWBAR_ATTACK, 0 , FALSE );
6883 }
6884
6885 // CHECK IF HE CAN SEE US, IF SO CHANGE DIR
6886 if ( pTSoldier->bOppList[ pSoldier->ubID ] == 0 && pTSoldier->bTeam != pSoldier->bTeam )
6887 {
6888 // Get direction to target
6889 // IF WE ARE AN ANIMAL, CAR, MONSTER, DONT'T TURN
6890 if ( !( pTSoldier->uiStatusFlags & ( SOLDIER_MONSTER | SOLDIER_ANIMAL | SOLDIER_VEHICLE ) ) )
6891 {
6892 // OK, stop merc....
6893 EVENT_StopMerc(pTSoldier);
6894
6895 if ( pTSoldier->bTeam != OUR_TEAM )
6896 {
6897 CancelAIAction(pTSoldier);
6898 }
6899
6900 ubTDirection = (UINT8)GetDirectionFromGridNo( pSoldier->sGridNo, pTSoldier );
6901 SendSoldierSetDesiredDirectionEvent( pTSoldier, ubTDirection );
6902 }
6903 }
6904 break;
6905
6906 case ANIM_PRONE:
6907
6908 // CHECK OUR STANCE
6909 // ATE: Added this for CIV body types 'cause of elliot
6910 if ( !IS_MERC_BODY_TYPE( pSoldier ) )
6911 {
6912 EVENT_InitNewSoldierAnim( pSoldier, PUNCH, 0 , FALSE );
6913 }
6914 else
6915 {
6916 if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight != ANIM_CROUCH )
6917 {
6918 // SET DESIRED STANCE AND SET PENDING ANIMATION
6919 ChangeSoldierStance(pSoldier, ANIM_CROUCH);
6920 pSoldier->usPendingAnimation = PUNCH_LOW;
6921 }
6922 else
6923 {
6924 // USE crouched one
6925 // NEED TO CHANGE STANCE IF NOT CROUCHD!
6926 EVENT_InitNewSoldierAnim( pSoldier, PUNCH_LOW, 0 , FALSE );
6927 }
6928 }
6929 break;
6930 }
6931 }
6932
6933 // SET TARGET GRIDNO
6934 pSoldier->sTargetGridNo = sGridNo;
6935 pSoldier->bTargetLevel = pSoldier->bLevel;
6936 pSoldier->sLastTarget = sGridNo;
6937 pSoldier->target = WhoIsThere2(sGridNo, pSoldier->bTargetLevel);
6938 }
6939
6940
EVENT_SoldierBeginKnifeThrowAttack(SOLDIERTYPE * pSoldier,INT16 sGridNo,UINT8 ubDirection)6941 void EVENT_SoldierBeginKnifeThrowAttack( SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 ubDirection )
6942 {
6943 // Increment the number of people busy doing stuff because of an attack
6944 //if (gTacticalStatus.uiFlags & INCOMBAT)
6945 //{
6946 gTacticalStatus.ubAttackBusyCount++;
6947 //}
6948 pSoldier->bBulletsLeft = 1;
6949 SLOGD("Starting knifethrow attack, bullets left %d", pSoldier->bBulletsLeft);
6950
6951 EVENT_InitNewSoldierAnim( pSoldier, THROW_KNIFE, 0 , FALSE );
6952
6953 // CHANGE DIRECTION AND GOTO ANIMATION NOW
6954 EVENT_SetSoldierDesiredDirection( pSoldier, ubDirection );
6955 EVENT_SetSoldierDirection( pSoldier, ubDirection );
6956
6957
6958 // SET TARGET GRIDNO
6959 pSoldier->sTargetGridNo = sGridNo;
6960 pSoldier->fTurningFromPronePosition = 0;
6961 // NB target level must be set by functions outside of here... but I think it
6962 // is already set in HandleItem or in the AI code - CJC
6963 pSoldier->target = WhoIsThere2(sGridNo, pSoldier->bTargetLevel);
6964 }
6965
6966
EVENT_SoldierBeginDropBomb(SOLDIERTYPE * pSoldier)6967 void EVENT_SoldierBeginDropBomb( SOLDIERTYPE *pSoldier )
6968 {
6969 // Increment the number of people busy doing stuff because of an attack
6970 switch( gAnimControl[ pSoldier->usAnimState ].ubHeight )
6971 {
6972 case ANIM_STAND:
6973
6974 EVENT_InitNewSoldierAnim( pSoldier, PLANT_BOMB, 0 , FALSE );
6975 break;
6976
6977 default:
6978
6979 // Call hander for planting bomb...
6980 HandleSoldierDropBomb( pSoldier, pSoldier->sPendingActionData2 );
6981 SoldierGotoStationaryStance( pSoldier );
6982 break;
6983 }
6984
6985 }
6986
6987
EVENT_SoldierBeginUseDetonator(SOLDIERTYPE * pSoldier)6988 void EVENT_SoldierBeginUseDetonator( SOLDIERTYPE *pSoldier )
6989 {
6990 // Increment the number of people busy doing stuff because of an attack
6991 switch( gAnimControl[ pSoldier->usAnimState ].ubHeight )
6992 {
6993 case ANIM_STAND:
6994
6995 EVENT_InitNewSoldierAnim( pSoldier, USE_REMOTE, 0 , FALSE );
6996 break;
6997
6998 default:
6999
7000 // Call hander for planting bomb...
7001 HandleSoldierUseRemote( pSoldier, pSoldier->sPendingActionData2 );
7002 break;
7003 }
7004 }
7005
EVENT_SoldierBeginFirstAid(SOLDIERTYPE * pSoldier,INT16 sGridNo,UINT8 ubDirection)7006 void EVENT_SoldierBeginFirstAid( SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 ubDirection )
7007 {
7008 //UINT32 uiMercFlags;
7009 BOOLEAN fRefused = FALSE;
7010
7011 SOLDIERTYPE* const pTSoldier = WhoIsThere2(sGridNo, pSoldier->bLevel);
7012 if (pTSoldier != NULL)
7013 {
7014 // OK, check if we should play quote...
7015 if ( pTSoldier->bTeam != OUR_TEAM )
7016 {
7017 if (pTSoldier->ubProfile != NO_PROFILE && MercProfile(pTSoldier->ubProfile).isNPCorRPC() && !RPC_RECRUITED(pTSoldier))
7018 {
7019 fRefused = PCDoesFirstAidOnNPC( pTSoldier->ubProfile );
7020 }
7021
7022 if ( !fRefused )
7023 {
7024 if ( CREATURE_OR_BLOODCAT( pTSoldier ) )
7025 {
7026 // nope!!
7027 fRefused = TRUE;
7028 ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, g_langRes->Message[ STR_REFUSE_FIRSTAID_FOR_CREATURE ] );
7029 }
7030 else if ( !pTSoldier->bNeutral && pTSoldier->bLife >= OKLIFE && pTSoldier->bSide != pSoldier->bSide )
7031 {
7032 fRefused = TRUE;
7033 ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, g_langRes->Message[ STR_REFUSE_FIRSTAID ] );
7034 }
7035
7036 }
7037 }
7038
7039 if ( fRefused )
7040 {
7041 UnSetUIBusy(pSoldier);
7042 return;
7043 }
7044
7045 // ATE: We can only give firsty aid to one perosn at a time... cancel
7046 // any now...
7047 InternalGivingSoldierCancelServices( pSoldier, FALSE );
7048
7049 // CHANGE DIRECTION AND GOTO ANIMATION NOW
7050 EVENT_SetSoldierDesiredDirectionForward(pSoldier, ubDirection);
7051 EVENT_SetSoldierDirection( pSoldier, ubDirection );
7052
7053 // CHECK OUR STANCE AND GOTO CROUCH IF NEEDED
7054 //if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight != ANIM_CROUCH )
7055 //{
7056 // // SET DESIRED STANCE AND SET PENDING ANIMATION
7057 // ChangeSoldierStance(pSoldier, ANIM_CROUCH);
7058 // pSoldier->usPendingAnimation = START_AID;
7059 //}
7060 //else
7061 {
7062 // CHANGE TO ANIMATION
7063 EVENT_InitNewSoldierAnim( pSoldier, START_AID, 0 , FALSE );
7064 }
7065
7066 // SET TARGET GRIDNO
7067 pSoldier->sTargetGridNo = sGridNo;
7068
7069 // SET PARTNER ID
7070 pSoldier->service_partner = pTSoldier;
7071
7072 // SET PARTNER'S COUNT REFERENCE
7073 pTSoldier->ubServiceCount++;
7074
7075 // If target and doer are no the same guy...
7076 if (pTSoldier != pSoldier && !pTSoldier->bCollapsed)
7077 {
7078 SoldierGotoStationaryStance( pTSoldier );
7079 }
7080 }
7081 }
7082
7083
EVENT_SoldierEnterVehicle(SOLDIERTYPE & s,GridNo const gridno)7084 void EVENT_SoldierEnterVehicle(SOLDIERTYPE& s, GridNo const gridno)
7085 {
7086 SOLDIERTYPE const* const tgt = FindSoldier(gridno, FIND_SOLDIER_GRIDNO);
7087 if (tgt && tgt->uiStatusFlags & SOLDIER_VEHICLE)
7088 {
7089 VEHICLETYPE& v = GetVehicle(tgt->bVehicleID);
7090 PutSoldierInVehicle(s, v);
7091 }
7092 UnSetUIBusy(&s);
7093 }
7094
7095
SoldierDressWound(SOLDIERTYPE * pSoldier,SOLDIERTYPE * pVictim,INT16 sKitPts,INT16 sStatus)7096 UINT32 SoldierDressWound( SOLDIERTYPE *pSoldier, SOLDIERTYPE *pVictim, INT16 sKitPts, INT16 sStatus )
7097 {
7098 UINT32 uiDressSkill, uiPossible, uiActual, uiMedcost, uiDeficiency, uiAvailAPs, uiUsedAPs;
7099 UINT8 ubBelowOKlife, ubPtsLeft;
7100
7101 if (pVictim->bBleeding < 1 && pVictim->bLife >= OKLIFE )
7102 {
7103 return(0); // nothing to do, shouldn't have even been called!
7104 }
7105
7106 if ( pVictim->bLife == 0 )
7107 {
7108 return(0);
7109 }
7110
7111 // in case he has multiple kits in hand, limit influence of kit status to 100%!
7112 if (sStatus >= 100)
7113 {
7114 sStatus = 100;
7115 }
7116
7117 // calculate wound-dressing skill (3x medical, 2x equip, 10x level, 1x dex)
7118 uiDressSkill = ((3 * EffectiveMedical(pSoldier)) + // medical knowledge
7119 (2 * sStatus) + // state of medical kit
7120 (10 * EffectiveExpLevel(pSoldier)) + // battle injury experience
7121 EffectiveDexterity(pSoldier)) / 7; // general "handiness"
7122
7123 // try to use every AP that the merc has left
7124 uiAvailAPs = pSoldier->bActionPoints;
7125
7126 // OK, If we are in real-time, use another value...
7127 if (!(gTacticalStatus.uiFlags & INCOMBAT))
7128 {
7129 // Set to a value which looks good based on our tactical turns duration
7130 uiAvailAPs = RT_FIRST_AID_GAIN_MODIFIER;
7131 }
7132
7133 // calculate how much bandaging CAN be done this turn
7134 uiPossible = ( uiAvailAPs * uiDressSkill ) / 50; // max rate is 2 * fullAPs
7135
7136 // if no healing is possible (insufficient APs or insufficient dressSkill)
7137 if (!uiPossible)
7138 return(0);
7139
7140
7141 if (pSoldier->inv[ HANDPOS ].usItem == MEDICKIT ) // using the GOOD medic stuff
7142 {
7143 uiPossible += ( uiPossible / 2); // add extra 50 %
7144 }
7145
7146 uiActual = uiPossible; // start by assuming maximum possible
7147
7148
7149 // figure out how far below OKLIFE the victim is
7150 if (pVictim->bLife >= OKLIFE)
7151 {
7152 ubBelowOKlife = 0;
7153 }
7154 else
7155 {
7156 ubBelowOKlife = OKLIFE - pVictim->bLife;
7157 }
7158
7159 // figure out how many healing pts we need to stop dying (2x cost)
7160 uiDeficiency = (2 * ubBelowOKlife );
7161
7162 // if, after that, the patient will still be bleeding
7163 if ( (pVictim->bBleeding - ubBelowOKlife ) > 0)
7164 {
7165 // then add how many healing pts we need to stop bleeding (1x cost)
7166 uiDeficiency += ( pVictim->bBleeding - ubBelowOKlife );
7167 }
7168
7169 // now, make sure we weren't going to give too much
7170 if ( uiActual > uiDeficiency) // if we were about to apply too much
7171 uiActual = uiDeficiency; // reduce actual not to waste anything
7172
7173
7174 // now make sure we HAVE that much
7175 if (pSoldier->inv[ HANDPOS ].usItem == MEDICKIT)
7176 {
7177 uiMedcost = (uiActual + 1) / 2; // cost is only half, rounded up
7178
7179 if ( uiMedcost > (UINT32)sKitPts ) // if we can't afford this
7180 {
7181 uiMedcost = sKitPts; // what CAN we afford?
7182 uiActual = uiMedcost * 2; // give double this as aid
7183 }
7184 }
7185 else
7186 {
7187 uiMedcost = uiActual;
7188
7189 if ( uiMedcost > (UINT32)sKitPts) // can't afford it
7190 {
7191 uiMedcost = uiActual = sKitPts; // recalc cost AND aid
7192 }
7193 }
7194
7195 ubPtsLeft = (UINT8)uiActual;
7196
7197
7198 // heal real life points first (if below OKLIFE) because we don't want the
7199 // patient still DYING if bandages run out, or medic is disabled/distracted!
7200 // NOTE: Dressing wounds for life below OKLIFE now costs 2 pts/life point!
7201 if ( ubPtsLeft && pVictim->bLife < OKLIFE)
7202 {
7203 // if we have enough points to bring him all the way to OKLIFE this turn
7204 if ( ubPtsLeft >= (2 * ubBelowOKlife ) )
7205 {
7206 // raise life to OKLIFE
7207 pVictim->bLife = OKLIFE;
7208
7209 // reduce bleeding by the same number of life points healed up
7210 pVictim->bBleeding -= ubBelowOKlife;
7211
7212 // use up appropriate # of actual healing points
7213 ubPtsLeft -= (2 * ubBelowOKlife);
7214 }
7215 else
7216 {
7217 pVictim->bLife += ( ubPtsLeft / 2);
7218 pVictim->bBleeding -= ( ubPtsLeft / 2);
7219
7220 ubPtsLeft = ubPtsLeft % 2; // if ptsLeft was odd, ptsLeft = 1
7221 }
7222
7223 // this should never happen any more, but make sure bleeding not negative
7224 if (pVictim->bBleeding < 0)
7225 {
7226 pVictim->bBleeding = 0;
7227 }
7228
7229 // if this healing brought the patient out of the worst of it, cancel dying
7230 if (pVictim->bLife >= OKLIFE )
7231 {
7232 //pVictim->dying = pVictim->dyingComment = FALSE;
7233 //pVictim->shootOn = TRUE;
7234
7235 // turn off merc QUOTE flags
7236 pVictim->fDyingComment = FALSE;
7237
7238 }
7239
7240 // update patient's entire panel (could have regained consciousness, etc.)
7241 }
7242
7243
7244 // if any healing points remain, apply that to any remaining bleeding (1/1)
7245 // DON'T spend any APs/kit pts to cure bleeding until merc is no longer dying
7246 //if ( ubPtsLeft && pVictim->bBleeding && !pVictim->dying)
7247 if ( ubPtsLeft && pVictim->bBleeding )
7248 {
7249 // if we have enough points to bandage all remaining bleeding this turn
7250 if (ubPtsLeft >= pVictim->bBleeding )
7251 {
7252 ubPtsLeft -= pVictim->bBleeding;
7253 pVictim->bBleeding = 0;
7254 }
7255 else // bandage what we can
7256 {
7257 pVictim->bBleeding -= ubPtsLeft;
7258 ubPtsLeft = 0;
7259 }
7260
7261 // update patient's life bar only
7262 }
7263
7264
7265 // if wound has been dressed enough so that bleeding won't occur, turn off
7266 // the "warned about bleeding" flag so merc tells us about the next bleeding
7267 if ( pVictim->bBleeding <= MIN_BLEEDING_THRESHOLD )
7268 {
7269 pVictim->fWarnedAboutBleeding = FALSE;
7270 }
7271
7272
7273 // if there are any ptsLeft now, then we didn't actually get to use them
7274 uiActual -= ubPtsLeft;
7275
7276 // usedAPs equals (actionPts) * (%of possible points actually used)
7277 uiUsedAPs = ( uiActual * uiAvailAPs ) / uiPossible;
7278
7279 if (pSoldier->inv[ HANDPOS ].usItem == MEDICKIT) // using the GOOD medic stuff
7280 {
7281 uiUsedAPs = ( uiUsedAPs * 2) / 3; // reverse 50% bonus by taking 2/3rds
7282 }
7283
7284 DeductPoints( pSoldier, (INT16)uiUsedAPs, (INT16)( ( uiUsedAPs * BP_PER_AP_LT_EFFORT) ) );
7285
7286 if (IsOnOurTeam(*pSoldier))
7287 {
7288 // MEDICAL GAIN (actual / 2): Helped someone by giving first aid
7289 StatChange(*pSoldier, MEDICALAMT, uiActual / 2, FROM_SUCCESS);
7290
7291 // DEXTERITY GAIN (actual / 6): Helped someone by giving first aid
7292 StatChange(*pSoldier, DEXTAMT, uiActual / 6, FROM_SUCCESS);
7293 }
7294
7295 return( uiMedcost );
7296 }
7297
7298
InternalReceivingSoldierCancelServices(SOLDIERTYPE * pSoldier,BOOLEAN fPlayEndAnim)7299 static void InternalReceivingSoldierCancelServices(SOLDIERTYPE* pSoldier, BOOLEAN fPlayEndAnim)
7300 {
7301 if (pSoldier->ubServiceCount <= 0) return;
7302
7303 // Loop through guys who have us as servicing
7304 FOR_EACH_SOLDIER(pTSoldier)
7305 {
7306 if (pTSoldier->service_partner == pSoldier)
7307 {
7308 // END SERVICE!
7309 pSoldier->ubServiceCount--;
7310
7311 pTSoldier->service_partner = NULL;
7312
7313 if (gTacticalStatus.fAutoBandageMode)
7314 {
7315 pSoldier->auto_bandaging_medic = NULL;
7316 ActionDone(pTSoldier);
7317 }
7318 else
7319 {
7320 // don't use end aid animation in autobandage
7321 if (pTSoldier->bLife >= OKLIFE && pTSoldier->bBreath > 0 && fPlayEndAnim)
7322 {
7323 EVENT_InitNewSoldierAnim(pTSoldier, END_AID, 0, FALSE);
7324 }
7325 }
7326 }
7327 }
7328 }
7329
7330
ReceivingSoldierCancelServices(SOLDIERTYPE * pSoldier)7331 void ReceivingSoldierCancelServices( SOLDIERTYPE *pSoldier )
7332 {
7333 InternalReceivingSoldierCancelServices( pSoldier, TRUE );
7334 }
7335
7336
InternalGivingSoldierCancelServices(SOLDIERTYPE * pSoldier,BOOLEAN fPlayEndAnim)7337 void InternalGivingSoldierCancelServices( SOLDIERTYPE *pSoldier, BOOLEAN fPlayEndAnim )
7338 {
7339 // GET TARGET SOLDIER
7340 SOLDIERTYPE* const pTSoldier = pSoldier->service_partner;
7341 if (pTSoldier != NULL)
7342 {
7343 // END SERVICE!
7344 pTSoldier->ubServiceCount--;
7345
7346 pSoldier->service_partner = NULL;
7347
7348 if ( gTacticalStatus.fAutoBandageMode )
7349 {
7350 pTSoldier->auto_bandaging_medic = NULL;
7351
7352 ActionDone( pSoldier );
7353 }
7354 else
7355 {
7356 if ( pSoldier->bLife >= OKLIFE && pSoldier->bBreath > 0 && fPlayEndAnim )
7357 {
7358 // don't use end aid animation in autobandage
7359 EVENT_InitNewSoldierAnim( pSoldier, END_AID, 0 , FALSE );
7360 }
7361 }
7362 }
7363
7364 }
7365
GivingSoldierCancelServices(SOLDIERTYPE * pSoldier)7366 void GivingSoldierCancelServices( SOLDIERTYPE *pSoldier )
7367 {
7368 InternalGivingSoldierCancelServices( pSoldier, TRUE );
7369 }
7370
7371
HaultSoldierFromSighting(SOLDIERTYPE * pSoldier,BOOLEAN fFromSightingEnemy)7372 void HaultSoldierFromSighting( SOLDIERTYPE *pSoldier, BOOLEAN fFromSightingEnemy )
7373 {
7374 SoldierSP soldier = GetSoldier(pSoldier);
7375
7376 // If we are a 'specialmove... ignore...
7377 if ( ( gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_SPECIALMOVE ) )
7378 {
7379 return;
7380 }
7381
7382 // Whether or not we are about to throw an object
7383 bool fIsThrowing = (pSoldier->pTempObject != NULL);
7384
7385 // Below are the animations for attacks which do not involve an object.
7386 // We use fTurningToShoot and pTempObject to check for other attacks.
7387 bool fIsAttacking = pSoldier->usPendingAnimation == THROW_KNIFE ||
7388 pSoldier->usPendingAnimation == SLICE ||
7389 pSoldier->usPendingAnimation == STAB ||
7390 pSoldier->usPendingAnimation == CROUCH_STAB ||
7391 pSoldier->usPendingAnimation == PUNCH ||
7392 pSoldier->usPendingAnimation == PUNCH_LOW ||
7393 pSoldier->usPendingAnimation == CROWBAR_ATTACK;
7394
7395 // OK, check if we were going to throw something, and give it back if so!
7396 if (fIsThrowing && fFromSightingEnemy)
7397 {
7398 // Place it back into inv....
7399 AutoPlaceObject( pSoldier, pSoldier->pTempObject, FALSE );
7400 delete pSoldier->pTempObject;
7401 pSoldier->pTempObject = NULL;
7402 pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
7403 }
7404
7405 // Here, we need to handle the situation when we're in the middle of an attack animation we see somebody
7406 if ((fIsThrowing && fFromSightingEnemy) || fIsAttacking)
7407 {
7408 // Decrement attack counter...
7409 STLOGD("Reducing attacker busy count..., ending attack ({}) because saw something", Internals::getAnimationName(pSoldier->usPendingAnimation));
7410 ReduceAttackBusyCount(pSoldier, FALSE);
7411
7412 // ATE: Goto stationary stance......
7413 SoldierGotoStationaryStance( pSoldier );
7414
7415 DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
7416 }
7417
7418 if ( !( gTacticalStatus.uiFlags & INCOMBAT ) )
7419 {
7420 EVENT_StopMerc(pSoldier);
7421 }
7422 else
7423 {
7424 // Pause this guy from no APS
7425 AdjustNoAPToFinishMove( pSoldier, TRUE );
7426
7427 pSoldier->ubReasonCantFinishMove = REASON_STOPPED_SIGHT;
7428
7429 // ATE; IF turning to shoot, stop!
7430 // ATE: We want to do this only for enemies, not items....
7431 if ( pSoldier->fTurningToShoot && fFromSightingEnemy )
7432 {
7433 pSoldier->fTurningToShoot = FALSE;
7434 // Release attacker
7435
7436 // OK - this is hightly annoying , but due to the huge combinations of
7437 // things that can happen - 1 of them is that sLastTarget will get unset
7438 // after turn is done - so set flag here to tell it not to...
7439 pSoldier->fDontUnsetLastTargetFromTurn = TRUE;
7440
7441 SLOGD("Reducing attacker busy count..., ending fire because saw something");
7442 ReduceAttackBusyCount(pSoldier, FALSE);
7443 }
7444
7445 // OK, if we are stopped at our destination, cancel pending action...
7446 if ( fFromSightingEnemy )
7447 {
7448 if ( soldier->hasPendingAction() && pSoldier->sGridNo == pSoldier->sFinalDestination )
7449 {
7450 soldier->removePendingAction();
7451 }
7452
7453 // Stop pending animation....
7454 pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
7455 }
7456
7457 if ( !pSoldier->fTurningToShoot )
7458 {
7459 pSoldier->fTurningFromPronePosition = FALSE;
7460 }
7461 }
7462
7463 // Unset UI!
7464 if ( fFromSightingEnemy || ( fIsThrowing && !pSoldier->fTurningToShoot ) )
7465 {
7466 UnSetUIBusy(pSoldier);
7467 }
7468
7469 pSoldier->bTurningFromUI = FALSE;
7470
7471 UnSetEngagedInConvFromPCAction( pSoldier );
7472 }
7473
7474
EVENT_StopMerc(SOLDIERTYPE * const s)7475 void EVENT_StopMerc(SOLDIERTYPE* const s)
7476 {
7477 EVENT_StopMerc(s, s->sGridNo, s->bDirection);
7478 }
7479
7480
7481 // Halt event is used to stop a merc - networking should check / adjust to gridno?
EVENT_StopMerc(SOLDIERTYPE * const s,GridNo const grid_no,INT8 const direction)7482 void EVENT_StopMerc(SOLDIERTYPE* const s, GridNo const grid_no, INT8 const direction)
7483 {
7484 SoldierSP soldier = GetSoldier(s);
7485
7486 if (!s->fDelayedMovement)
7487 {
7488 soldier->removePendingAnimation();
7489 }
7490
7491 s->bEndDoorOpenCode = 0;
7492 s->fTurningFromPronePosition = 0;
7493 s->ubPathIndex = 0; // Cancel path data!
7494 s->ubPathDataSize = 0;
7495 s->fDelayedMovement = FALSE; // Set ext tile waiting flag off
7496 s->bReverse = FALSE; // Turn off reverse
7497
7498 // Move guy to gridno - should be the same unless in multiplayer
7499 EVENT_SetSoldierPosition(s, grid_no, SSP_NONE);
7500 s->sDestXPos = (INT16)s->dXPos;
7501 s->sDestYPos = (INT16)s->dYPos;
7502 EVENT_SetSoldierDirection(s, direction);
7503
7504 if (gAnimControl[s->usAnimState].uiFlags & ANIM_MOVING)
7505 {
7506 SoldierGotoStationaryStance(s);
7507 }
7508
7509 // ATE: If turning to shoot, stop!
7510 if (s->fTurningToShoot)
7511 {
7512 s->fTurningToShoot = FALSE;
7513 // Release attacker
7514 SLOGD("Reducing attacker busy count..., ending fire because saw something");
7515 ReduceAttackBusyCount(s, FALSE);
7516 }
7517
7518 // Turn off multi-move speed override
7519 if (s->sGridNo == s->sFinalDestination)
7520 {
7521 s->fUseMoverrideMoveSpeed = FALSE;
7522 }
7523
7524 UnSetUIBusy(s);
7525 UnMarkMovementReserved(*s);
7526 }
7527
7528
ReLoadSoldierAnimationDueToHandItemChange(SOLDIERTYPE * const s,UINT16 const usOldItem,UINT16 const usNewItem)7529 void ReLoadSoldierAnimationDueToHandItemChange(SOLDIERTYPE* const s, UINT16 const usOldItem, UINT16 const usNewItem)
7530 {
7531 // DON'T continue aiming!
7532 // GOTO STANCE
7533 // CHECK FOR AIMING ANIMATIONS
7534
7535 // Shutoff burst....
7536 // ( we could be on, then change gun that does not have burst )
7537 if (GCM->getItem(usNewItem)->isWeapon() && GCM->getWeapon(usNewItem)->ubShotsPerBurst == 0)
7538 {
7539 s->bDoBurst = FALSE;
7540 s->bWeaponMode = WM_NORMAL;
7541 }
7542
7543 if (gAnimControl[s->usAnimState].uiFlags & ANIM_FIREREADY)
7544 {
7545 // Stop aiming!
7546 SoldierGotoStationaryStance(s);
7547 }
7548
7549 GivingSoldierCancelServices(s);
7550
7551 // Switch on stance!
7552 switch (gAnimControl[s->usAnimState].ubEndHeight)
7553 {
7554 case ANIM_STAND:
7555 {
7556 // Did we have a rifle and do we now not have one?
7557 bool const old_rifle = IsRifle(usOldItem);
7558 bool const new_rifle = IsRifle(usNewItem);
7559 if (old_rifle && !new_rifle)
7560 {
7561 // Put it away!
7562 EVENT_InitNewSoldierAnim(s, LOWER_RIFLE, 0, FALSE);
7563 break;
7564 }
7565 else if (!old_rifle && new_rifle)
7566 {
7567 // Bring it up!
7568 EVENT_InitNewSoldierAnim(s, RAISE_RIFLE, 0, FALSE);
7569 break;
7570 }
7571 }
7572 // fallthrough
7573
7574 case ANIM_CROUCH:
7575 case ANIM_PRONE:
7576 SetSoldierAnimationSurface(s, s->usAnimState);
7577 break;
7578 }
7579 }
7580
7581
CreateEnemyGlow16BPPPalette(const SGPPaletteEntry * pPalette,UINT32 rscale,UINT32 gscale)7582 static UINT16* CreateEnemyGlow16BPPPalette(const SGPPaletteEntry* pPalette, UINT32 rscale, UINT32 gscale)
7583 {
7584 Assert(pPalette != NULL);
7585
7586 UINT16* const p16BPPPalette = new UINT16[256]{};
7587
7588 for (UINT32 cnt = 0; cnt < 256; cnt++)
7589 {
7590 UINT8 r = __max(rscale, pPalette[cnt].r);
7591 UINT8 g = __max(gscale, pPalette[cnt].g);
7592 UINT8 b = pPalette[cnt].b;
7593 p16BPPPalette[cnt] = Get16BPPColor(FROMRGB(r, g, b));
7594 }
7595 return p16BPPPalette;
7596 }
7597
7598
CreateEnemyGreyGlow16BPPPalette(const SGPPaletteEntry * pPalette,UINT32 rscale,UINT32 gscale)7599 static UINT16* CreateEnemyGreyGlow16BPPPalette(const SGPPaletteEntry* pPalette, UINT32 rscale, UINT32 gscale)
7600 {
7601 Assert(pPalette != NULL);
7602
7603 UINT16* const p16BPPPalette = new UINT16[256]{};
7604
7605 for (UINT32 cnt = 0; cnt < 256; cnt++)
7606 {
7607 UINT32 lumin = (pPalette[cnt].r * 299 + pPalette[cnt].g * 587 + pPalette[cnt].b * 114) / 1000;
7608 UINT32 rmod = 100 * lumin / 256;
7609 UINT32 gmod = 100 * lumin / 256;
7610 UINT32 bmod = 100 * lumin / 256;
7611
7612 rmod = __max(rscale, rmod);
7613 gmod = __max(gscale, gmod);
7614
7615 UINT8 r = __min(rmod, 255);
7616 UINT8 g = __min(gmod, 255);
7617 UINT8 b = __min(bmod, 255);
7618 p16BPPPalette[cnt] = Get16BPPColor(FROMRGB(r, g, b));
7619 }
7620 return p16BPPPalette;
7621 }
7622
7623
ContinueMercMovement(SOLDIERTYPE * pSoldier)7624 void ContinueMercMovement( SOLDIERTYPE *pSoldier )
7625 {
7626 INT16 sAPCost;
7627 INT16 sGridNo;
7628
7629 sGridNo = pSoldier->sFinalDestination;
7630
7631 // Can we afford this?
7632 if ( pSoldier->bGoodContPath )
7633 {
7634 sGridNo = pSoldier->sContPathLocation;
7635 }
7636 else
7637 {
7638 // ATE: OK, don't cancel count, so pending actions are still valid...
7639 pSoldier->ubPendingActionAnimCount = 0;
7640 }
7641
7642 // get a path to dest...
7643 if ( FindBestPath( pSoldier, sGridNo, pSoldier->bLevel, pSoldier->usUIMovementMode, NO_COPYROUTE, 0 ) )
7644 {
7645 sAPCost = PtsToMoveDirection( pSoldier, guiPathingData[ 0 ] );
7646
7647 if (EnoughPoints(pSoldier, sAPCost, 0, pSoldier->bTeam == OUR_TEAM))
7648 {
7649 // Acknowledge
7650 if ( pSoldier->bTeam == OUR_TEAM )
7651 {
7652 DoMercBattleSound( pSoldier, BATTLE_SOUND_OK1 );
7653
7654 // If we have a face, tell text in it to go away!
7655 FACETYPE* const face = pSoldier->face;
7656 if (face != NULL) face->fDisplayTextOver = FACE_ERASE_TEXT_OVER;
7657 }
7658
7659 AdjustNoAPToFinishMove( pSoldier, FALSE );
7660
7661 SetUIBusy(pSoldier);
7662
7663 // OK, try and get a path to out dest!
7664 EVENT_InternalGetNewSoldierPath( pSoldier, sGridNo, pSoldier->usUIMovementMode, FALSE, TRUE );
7665 }
7666 }
7667 }
7668
7669
CheckForBreathCollapse(SOLDIERTYPE & s)7670 bool CheckForBreathCollapse(SOLDIERTYPE& s)
7671 {
7672 // Check if we are out of breath
7673 if (s.bBreathMax > 70 &&
7674 s.bBreath < 20 &&
7675 !(s.usQuoteSaidFlags & SOLDIER_QUOTE_SAID_LOW_BREATH) &&
7676 gAnimControl[s.usAnimState].ubEndHeight == ANIM_STAND)
7677 {
7678 // Warn
7679 TacticalCharacterDialogue(&s, QUOTE_OUT_OF_BREATH);
7680 // Set flag indicating we were warned
7681 s.usQuoteSaidFlags |= SOLDIER_QUOTE_SAID_LOW_BREATH;
7682 }
7683
7684 #if 0 // XXX was commented out
7685 // Check for drowning
7686 if (s.bBreath < 10 &&
7687 !(s.usQuoteSaidFlags & SOLDIER_QUOTE_SAID_DROWNING) &&
7688 s.bOverTerrainType == DEEP_WATER)
7689 {
7690 // Warn
7691 TacticalCharacterDialogue(&s, QUOTE_DROWNING);
7692 // Set flag indicating we were warned
7693 s.usQuoteSaidFlags |= SOLDIER_QUOTE_SAID_DROWNING;
7694 // WISDOM GAIN (25): Starting to drown
7695 StatChange(s, WISDOMAMT, 25, FALSE);
7696 }
7697 #endif
7698
7699 if (s.bBreath != 0) return false;
7700 if (s.bCollapsed) return false;
7701 if (s.uiStatusFlags & (SOLDIER_VEHICLE | SOLDIER_ANIMAL | SOLDIER_MONSTER)) return false;
7702
7703 // Collapse
7704 // Set a flag, because we may still be in the middle of an animation what is
7705 // not interruptable
7706 s.bBreathCollapsed = TRUE;
7707 return true;
7708 }
7709
7710
InternalIsValidStance(const SOLDIERTYPE * pSoldier,INT8 bDirection,INT8 bNewStance)7711 BOOLEAN InternalIsValidStance(const SOLDIERTYPE* pSoldier, INT8 bDirection, INT8 bNewStance)
7712 {
7713 UINT16 usOKToAddStructID=0;
7714 UINT16 usAnimSurface=0;
7715 UINT16 usAnimState;
7716
7717 // Check, if dest is prone, we can actually do this!
7718
7719 // If we are a vehicle, we can only 'stand'
7720 if ( ( pSoldier->uiStatusFlags & SOLDIER_VEHICLE ) && bNewStance != ANIM_STAND )
7721 {
7722 return( FALSE );
7723 }
7724
7725 // Check if we are in water?
7726 if ( MercInWater( pSoldier ) )
7727 {
7728 if ( bNewStance == ANIM_PRONE || bNewStance == ANIM_CROUCH )
7729 {
7730 return( FALSE );
7731 }
7732 }
7733
7734 if ( pSoldier->ubBodyType == ROBOTNOWEAPON && bNewStance != ANIM_STAND )
7735 {
7736 return( FALSE );
7737 }
7738
7739 // Check if we are in water?
7740 if ( AM_AN_EPC( pSoldier ) )
7741 {
7742 if ( bNewStance == ANIM_PRONE )
7743 {
7744 return( FALSE );
7745 }
7746 else
7747 {
7748 return( TRUE );
7749 }
7750 }
7751
7752
7753 if ( pSoldier->bCollapsed )
7754 {
7755 if ((bNewStance == ANIM_STAND || bNewStance == ANIM_CROUCH) && pSoldier->bBreath < OKBREATH)
7756 {
7757 return( FALSE );
7758 }
7759 }
7760
7761 // Check if we can do this....
7762 if ( pSoldier->pLevelNode && pSoldier->pLevelNode->pStructureData != NULL )
7763 {
7764 usOKToAddStructID = pSoldier->pLevelNode->pStructureData->usStructureID;
7765 }
7766 else
7767 {
7768 usOKToAddStructID = INVALID_STRUCTURE_ID;
7769 }
7770
7771 switch( bNewStance )
7772 {
7773 case ANIM_STAND:
7774
7775 usAnimState = STANDING;
7776 break;
7777
7778 case ANIM_CROUCH:
7779
7780 usAnimState = CROUCHING;
7781 break;
7782
7783
7784 case ANIM_PRONE:
7785
7786 usAnimState = PRONE;
7787 break;
7788
7789 default:
7790
7791 // Something gone funny here....
7792 usAnimState = pSoldier->usAnimState;
7793 SLOGW("Wrong desired stance given: %d, %d.",
7794 bNewStance, pSoldier->usAnimState );
7795 }
7796
7797 usAnimSurface = DetermineSoldierAnimationSurface( pSoldier, usAnimState );
7798
7799 // Get structure ref........
7800 const STRUCTURE_FILE_REF* const pStructureFileRef = GetAnimationStructureRef(pSoldier, usAnimSurface, usAnimState);
7801 if ( pStructureFileRef != NULL )
7802 {
7803 // Can we add structure data for this stance...?
7804 if (!OkayToAddStructureToWorld(pSoldier->sGridNo, pSoldier->bLevel, &pStructureFileRef->pDBStructureRef[OneCDirection(bDirection)], usOKToAddStructID))
7805 {
7806 return( FALSE );
7807 }
7808 }
7809
7810 return( TRUE );
7811 }
7812
7813
IsValidStance(const SOLDIERTYPE * pSoldier,INT8 bNewStance)7814 BOOLEAN IsValidStance(const SOLDIERTYPE* pSoldier, INT8 bNewStance)
7815 {
7816 return( InternalIsValidStance( pSoldier, pSoldier->bDirection, bNewStance ) );
7817 }
7818
7819
IsValidMovementMode(const SOLDIERTYPE * pSoldier,INT16 usMovementMode)7820 BOOLEAN IsValidMovementMode(const SOLDIERTYPE* pSoldier, INT16 usMovementMode)
7821 {
7822 // Check, if dest is prone, we can actually do this!
7823
7824 // Check if we are in water?
7825 if ( MercInWater( pSoldier ) )
7826 {
7827 if ( usMovementMode == RUNNING || usMovementMode == SWATTING || usMovementMode == CRAWLING )
7828 {
7829 return( FALSE );
7830 }
7831 }
7832
7833 return( TRUE );
7834 }
7835
7836
SelectMoveAnimationFromStance(SOLDIERTYPE * pSoldier)7837 void SelectMoveAnimationFromStance( SOLDIERTYPE *pSoldier )
7838 {
7839 // Determine which animation to do...depending on stance and gun in hand...
7840 switch ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
7841 {
7842 case ANIM_STAND:
7843 EVENT_InitNewSoldierAnim( pSoldier, WALKING, 0 , FALSE );
7844 break;
7845
7846 case ANIM_PRONE:
7847 EVENT_InitNewSoldierAnim( pSoldier, CRAWLING, 0 , FALSE );
7848 break;
7849
7850 case ANIM_CROUCH:
7851 EVENT_InitNewSoldierAnim( pSoldier, SWATTING, 0 , FALSE );
7852 break;
7853
7854 }
7855
7856 }
7857
7858
GetActualSoldierAnimDims(SOLDIERTYPE const * const s)7859 static ETRLEObject const& GetActualSoldierAnimDims(SOLDIERTYPE const* const s)
7860 {
7861 static ETRLEObject const fallback = { 0, 0, 0, 0, 5, 5 };
7862
7863 UINT16 const anim_surface = GetSoldierAnimationSurface(s);
7864 if (anim_surface == INVALID_ANIMATION_SURFACE) return fallback;
7865
7866 SGPVObject const* vo = gAnimSurfaceDatabase[anim_surface].hVideoObject;
7867 if (!vo) return fallback;
7868
7869 // XXX comment seems wrong
7870 // OK, noodle here on what we should do... If we take each frame, it will be
7871 // different slightly depending on the frame and the value returned here will
7872 // vary thusly. However, for the uses of this function, we should be able to
7873 // use just the first frame...
7874 return vo->SubregionProperties(s->usAniFrame);
7875 }
7876
7877
SetSoldierLocatorOffsets(SOLDIERTYPE * const s)7878 static void SetSoldierLocatorOffsets(SOLDIERTYPE* const s)
7879 {
7880 // OK, from our animation, get height, width
7881 ETRLEObject const& dims = GetActualSoldierAnimDims(s);
7882 s->sBoundingBoxWidth = dims.usWidth;
7883 s->sBoundingBoxHeight = dims.usHeight;
7884 s->sBoundingBoxOffsetX = dims.sOffsetX;
7885 s->sBoundingBoxOffsetY = dims.sOffsetY;
7886 }
7887
7888
SoldierCarriesTwoHandedWeapon(SOLDIERTYPE * pSoldier)7889 static BOOLEAN SoldierCarriesTwoHandedWeapon(SOLDIERTYPE* pSoldier)
7890 {
7891 UINT16 usItem;
7892
7893 usItem = pSoldier->inv[ HANDPOS ].usItem;
7894
7895 if ( usItem != NOTHING && (GCM->getItem(usItem)->isTwoHanded()) )
7896 {
7897 return( TRUE );
7898 }
7899
7900 return( FALSE );
7901 }
7902
7903
7904 static void SoldierBleed(SOLDIERTYPE* pSoldier, BOOLEAN fBandagedBleed);
7905
7906
CheckBleeding(SOLDIERTYPE * pSoldier)7907 static INT32 CheckBleeding(SOLDIERTYPE* pSoldier)
7908 {
7909 INT8 bBandaged; //,savedOurTurn;
7910 INT32 iBlood = NOBLOOD;
7911
7912 if ( pSoldier->bLife != 0 )
7913 {
7914 // if merc is hurt beyond the minimum required to bleed, or he's dying
7915 if ( ( pSoldier->bBleeding > MIN_BLEEDING_THRESHOLD) || pSoldier->bLife < OKLIFE )
7916 {
7917 // if he's NOT in the process of being bandaged or DOCTORed
7918 if ( ( pSoldier->ubServiceCount == 0 ) && ( AnyDoctorWhoCanHealThisPatient( pSoldier, HEALABLE_EVER ) == NULL ) )
7919 {
7920 // may drop blood whether or not any bleeding takes place this turn
7921 if ( pSoldier->bTilesMoved < 1 )
7922 {
7923 iBlood = ( ( pSoldier->bBleeding - MIN_BLEEDING_THRESHOLD ) / BLOODDIVISOR ); // + pSoldier->dying;
7924 if ( iBlood > MAXBLOODQUANTITY )
7925 {
7926 iBlood = MAXBLOODQUANTITY;
7927 }
7928 }
7929 else
7930 {
7931 iBlood = NOBLOOD;
7932 }
7933
7934 // Are we in a different mode?
7935 if (!(gTacticalStatus.uiFlags & INCOMBAT))
7936 {
7937 pSoldier->dNextBleed -= (FLOAT)RT_NEXT_BLEED_MODIFIER;
7938 }
7939 else
7940 {
7941 // Do a single step descrease
7942 pSoldier->dNextBleed--;
7943 }
7944
7945 // if it's time to lose some blood
7946 if ( pSoldier->dNextBleed <= 0)
7947 {
7948 // first, calculate if soldier is bandaged
7949 bBandaged = pSoldier->bLifeMax - pSoldier->bBleeding - pSoldier->bLife;
7950
7951 // as long as he's bandaged and not "dying"
7952 if ( bBandaged && pSoldier->bLife >= OKLIFE )
7953 {
7954 // just bleeding through existing bandages
7955 pSoldier->bBleeding++;
7956
7957 SoldierBleed( pSoldier, TRUE );
7958 }
7959 else // soldier is either not bandaged at all or is dying
7960 {
7961 if ( pSoldier->bLife < OKLIFE ) // if he's dying
7962 {
7963 // if he's conscious, and he hasn't already, say his "dying quote"
7964 if ( ( pSoldier->bLife >= CONSCIOUSNESS ) && !pSoldier->fDyingComment )
7965 {
7966 TacticalCharacterDialogue( pSoldier, QUOTE_SERIOUSLY_WOUNDED );
7967
7968 pSoldier->fDyingComment = TRUE;
7969 }
7970
7971 // can't permit lifemax to ever bleed beneath OKLIFE, or that
7972 // soldier might as well be dead!
7973 if (pSoldier->bLifeMax >= OKLIFE)
7974 {
7975 // bleeding while "dying" costs a PERMANENT point of life each time!
7976 pSoldier->bLifeMax--;
7977 pSoldier->bBleeding--;
7978 }
7979 }
7980 }
7981
7982 // either way, a point of life (health) is lost because of bleeding
7983 // This will also update the life bar
7984
7985 SoldierBleed( pSoldier, FALSE );
7986
7987
7988 // if he's not dying (which includes him saying the dying quote just
7989 // now), and he hasn't warned us that he's bleeding yet, he does so
7990 // Also, not if they are being bandaged....
7991 if ( ( pSoldier->bLife >= OKLIFE ) && !pSoldier->fDyingComment && !pSoldier->fWarnedAboutBleeding && !gTacticalStatus.fAutoBandageMode && pSoldier->ubServiceCount == 0 )
7992 {
7993 TacticalCharacterDialogue( pSoldier, QUOTE_STARTING_TO_BLEED );
7994
7995 // "starting to bleed" quote
7996 pSoldier->fWarnedAboutBleeding = TRUE;
7997 }
7998
7999 pSoldier->dNextBleed = CalcSoldierNextBleed( pSoldier );
8000
8001 }
8002 }
8003 }
8004 }
8005 return( iBlood );
8006 }
8007
8008
FlashSoldierPortrait(SOLDIERTYPE * const s)8009 void FlashSoldierPortrait(SOLDIERTYPE* const s)
8010 {
8011 s->fFlashPortrait = FLASH_PORTRAIT_START;
8012 s->bFlashPortraitFrame = FLASH_PORTRAIT_STARTSHADE;
8013 RESETTIMECOUNTER(s->PortraitFlashCounter, FLASH_PORTRAIT_DELAY);
8014 }
8015
8016
SoldierBleed(SOLDIERTYPE * const s,const BOOLEAN fBandagedBleed)8017 static void SoldierBleed(SOLDIERTYPE* const s, const BOOLEAN fBandagedBleed)
8018 {
8019 // OK, here make some stuff happen for bleeding
8020 // A banaged bleed does not show damage taken, just through existing bandages
8021
8022 if (guiCurrentScreen != GAME_SCREEN || s->bInSector)
8023 {
8024 FlashSoldierPortrait(s);
8025 if (guiCurrentScreen == MAP_SCREEN) SetInfoChar(s);
8026 }
8027
8028 if (!fBandagedBleed)
8029 {
8030 SoldierTakeDamage(s, 1, 100, TAKE_DAMAGE_BLOODLOSS, NULL);
8031 }
8032 }
8033
8034
SoldierCollapse(SOLDIERTYPE * pSoldier)8035 void SoldierCollapse( SOLDIERTYPE *pSoldier )
8036 {
8037 BOOLEAN fMerc = FALSE;
8038
8039 if ( pSoldier->ubBodyType <= REGFEMALE )
8040 {
8041 fMerc = TRUE;
8042 }
8043
8044 // If we are an animal, etc, don't do anything....
8045 switch( pSoldier->ubBodyType )
8046 {
8047 case ADULTFEMALEMONSTER:
8048 case AM_MONSTER:
8049 case YAF_MONSTER:
8050 case YAM_MONSTER:
8051 case LARVAE_MONSTER:
8052 case INFANT_MONSTER:
8053 case QUEENMONSTER:
8054
8055 // Give breath back....
8056 DeductPoints( pSoldier,0, (INT16)-5000 );
8057 return;
8058 }
8059
8060 pSoldier->bCollapsed = TRUE;
8061
8062 ReceivingSoldierCancelServices( pSoldier );
8063
8064 // CC has requested - handle sight here...
8065 HandleSight(*pSoldier, SIGHT_LOOK);
8066
8067 // Check height
8068 switch( gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
8069 {
8070 case ANIM_STAND:
8071
8072 if ( pSoldier->bOverTerrainType == DEEP_WATER )
8073 {
8074 EVENT_InitNewSoldierAnim( pSoldier, DEEP_WATER_DIE, 0, FALSE );
8075 }
8076 else if ( pSoldier->bOverTerrainType == LOW_WATER )
8077 {
8078 EVENT_InitNewSoldierAnim( pSoldier, WATER_DIE, 0, FALSE );
8079 }
8080 else
8081 {
8082 BeginTyingToFall( pSoldier );
8083 EVENT_InitNewSoldierAnim( pSoldier, FALLFORWARD_FROMHIT_STAND, 0, FALSE );
8084 }
8085 break;
8086
8087 case ANIM_CROUCH:
8088
8089 // Crouched or prone, only for mercs!
8090 BeginTyingToFall( pSoldier );
8091
8092 if ( fMerc )
8093 {
8094 EVENT_InitNewSoldierAnim( pSoldier, FALLFORWARD_FROMHIT_CROUCH, 0 , FALSE);
8095 }
8096 else
8097 {
8098 // For civs... use fall from stand...
8099 EVENT_InitNewSoldierAnim( pSoldier, FALLFORWARD_FROMHIT_STAND, 0 , FALSE);
8100 }
8101 break;
8102
8103 case ANIM_PRONE:
8104
8105 switch( pSoldier->usAnimState )
8106 {
8107 case FALLFORWARD_FROMHIT_STAND:
8108 case ENDFALLFORWARD_FROMHIT_CROUCH:
8109
8110 ChangeSoldierState( pSoldier, STAND_FALLFORWARD_STOP, 0, FALSE );
8111 break;
8112
8113 case FALLBACK_HIT_STAND:
8114 ChangeSoldierState( pSoldier, FALLBACKHIT_STOP, 0, FALSE );
8115 break;
8116
8117 default:
8118 EVENT_InitNewSoldierAnim( pSoldier, PRONE_LAY_FROMHIT, 0 , FALSE );
8119 break;
8120 }
8121 break;
8122 }
8123
8124 if (pSoldier->uiStatusFlags & SOLDIER_ENEMY)
8125 {
8126 INT8 bPanicTrigger = ClosestPanicTrigger(pSoldier);
8127 if (bPanicTrigger != -1 && !gTacticalStatus.bPanicTriggerIsAlarm[bPanicTrigger] && gTacticalStatus.the_chosen_one == pSoldier)
8128 {
8129 // replace this guy as the chosen one!
8130 gTacticalStatus.the_chosen_one = NULL;
8131 MakeClosestEnemyChosenOne();
8132 }
8133
8134 if ((gTacticalStatus.uiFlags & INCOMBAT) && (pSoldier->uiStatusFlags & SOLDIER_UNDERAICONTROL))
8135 {
8136 SLOGD("Ending turn for %d because of error from HandleItem", pSoldier->ubID);
8137 EndAIGuysTurn(*pSoldier);
8138 }
8139 }
8140 }
8141
8142
CalcSoldierNextBleed(SOLDIERTYPE * pSoldier)8143 static FLOAT CalcSoldierNextBleed(SOLDIERTYPE* pSoldier)
8144 {
8145 INT8 bBandaged;
8146
8147 // calculate how many turns before he bleeds again
8148 // bleeding faster the lower life gets, and if merc is running around
8149 //pSoldier->nextbleed = 2 + (pSoldier->life / (10 + pSoldier->tilesMoved)); // min = 2
8150
8151 // if bandaged, give 1/2 of the bandaged life points back into equation
8152 bBandaged = pSoldier->bLifeMax - pSoldier->bLife - pSoldier->bBleeding;
8153
8154 return( (FLOAT)1 + (FLOAT)( (pSoldier->bLife + bBandaged / 2) / (10 + pSoldier->bTilesMoved) ) ); // min = 1
8155 }
8156
8157
HandlePlacingRoofMarker(SOLDIERTYPE & s,bool const set,bool const force)8158 void HandlePlacingRoofMarker(SOLDIERTYPE& s, bool const set, bool const force)
8159 {
8160 if (set && s.bVisible == -1) return;
8161
8162 if (s.bLevel != SECOND_LEVEL) return;
8163 // We are on the roof, add roof UI piece
8164
8165 // Return if we are still climbing roof
8166 if (!force && s.usAnimState == CLIMBUPROOF) return;
8167
8168 GridNo const gridno = s.sGridNo;
8169 if (!gpWorldLevelData[gridno].pRoofHead) return;
8170
8171 if (!set)
8172 {
8173 RemoveRoof(gridno, FIRSTPOINTERS11);
8174 }
8175 else if (!IndexExistsInRoofLayer(gridno, FIRSTPOINTERS11))
8176 {
8177 // It does not exist already
8178 LEVELNODE* const l = AddRoofToTail(gridno, FIRSTPOINTERS11);
8179 l->ubShadeLevel = DEFAULT_SHADE_LEVEL;
8180 l->ubNaturalShadeLevel = DEFAULT_SHADE_LEVEL;
8181 }
8182 }
8183
8184
PositionSoldierLight(SOLDIERTYPE * pSoldier)8185 void PositionSoldierLight( SOLDIERTYPE *pSoldier )
8186 {
8187 // DO ONLY IF WE'RE AT A GOOD LEVEL
8188 if ( ubAmbientLightLevel < MIN_AMB_LEVEL_FOR_MERC_LIGHTS )
8189 {
8190 return;
8191 }
8192
8193 if ( !pSoldier->bInSector )
8194 {
8195 return;
8196 }
8197
8198 if ( pSoldier->bTeam != OUR_TEAM )
8199 {
8200 return;
8201 }
8202
8203 if ( pSoldier->bLife < OKLIFE )
8204 {
8205 return;
8206 }
8207
8208 //if the player DOESNT want the merc to cast light
8209 if( !gGameSettings.fOptions[ TOPTION_MERC_CASTS_LIGHT ] )
8210 {
8211 return;
8212 }
8213
8214 if (pSoldier->light == NULL) CreateSoldierLight(pSoldier);
8215
8216 LIGHT_SPRITE* const l = pSoldier->light;
8217 LightSpritePower(l, TRUE);
8218 LightSpriteFake(l);
8219 LightSpritePosition(l, pSoldier->sX / CELL_X_SIZE, pSoldier->sY / CELL_Y_SIZE);
8220 }
8221
8222
PickPickupAnimation(SOLDIERTYPE * pSoldier,INT32 iItemIndex,INT16 sGridNo,INT8 bZLevel)8223 void PickPickupAnimation( SOLDIERTYPE *pSoldier, INT32 iItemIndex, INT16 sGridNo, INT8 bZLevel )
8224 {
8225 INT8 bDirection;
8226 STRUCTURE *pStructure;
8227 BOOLEAN fDoNormalPickup = TRUE;
8228
8229
8230 // OK, Given the gridno, determine if it's the same one or different....
8231 if ( sGridNo != pSoldier->sGridNo )
8232 {
8233 // Get direction to face....
8234 bDirection = (INT8)GetDirectionFromGridNo( sGridNo, pSoldier );
8235 pSoldier->ubPendingDirection = bDirection;
8236
8237 // Change to pickup animation
8238 EVENT_InitNewSoldierAnim( pSoldier, ADJACENT_GET_ITEM, 0 , FALSE );
8239
8240 if (!(pSoldier->uiStatusFlags & SOLDIER_PC))
8241 {
8242 // set "pending action" value for AI so it will wait
8243 pSoldier->bAction = AI_ACTION_PENDING_ACTION;
8244 }
8245
8246 }
8247 else
8248 {
8249 // If in water....
8250 if ( MercInWater( pSoldier ) )
8251 {
8252 UnSetUIBusy(pSoldier);
8253 HandleSoldierPickupItem( pSoldier, iItemIndex, sGridNo, bZLevel );
8254 SoldierGotoStationaryStance( pSoldier );
8255 if (!(pSoldier->uiStatusFlags & SOLDIER_PC))
8256 {
8257 // reset action value for AI because we're done!
8258 ActionDone( pSoldier );
8259 }
8260
8261 }
8262 else
8263 {
8264 // Don't show animation of getting item, if we are not standing
8265 switch ( gAnimControl[ pSoldier->usAnimState ].ubHeight )
8266 {
8267 case ANIM_STAND:
8268
8269 // OK, if we are looking at z-level >0, AND
8270 // we have a strucxture with items in it
8271 // look for orientation and use angle accordingly....
8272 if ( bZLevel > 0 )
8273 {
8274 //#if 0
8275 // Get direction to face....
8276 if ( ( pStructure = FindStructure( (INT16)sGridNo, ( STRUCTURE_HASITEMONTOP | STRUCTURE_OPENABLE ) ) ) != NULL )
8277 {
8278 fDoNormalPickup = FALSE;
8279
8280 // OK, look at orientation
8281 switch( pStructure->ubWallOrientation )
8282 {
8283 case OUTSIDE_TOP_LEFT:
8284 case INSIDE_TOP_LEFT:
8285
8286 bDirection = (INT8)NORTH;
8287 break;
8288
8289 case OUTSIDE_TOP_RIGHT:
8290 case INSIDE_TOP_RIGHT:
8291
8292 bDirection = (INT8)WEST;
8293 break;
8294
8295 default:
8296
8297 bDirection = pSoldier->bDirection;
8298 break;
8299 }
8300
8301 //pSoldier->ubPendingDirection = bDirection;
8302 EVENT_SetSoldierDesiredDirection( pSoldier, bDirection );
8303 EVENT_SetSoldierDirection( pSoldier, bDirection );
8304
8305 // Change to pickup animation
8306 EVENT_InitNewSoldierAnim( pSoldier, ADJACENT_GET_ITEM, 0 , FALSE );
8307 }
8308 //#endif
8309 }
8310
8311 if ( fDoNormalPickup )
8312 {
8313 EVENT_InitNewSoldierAnim( pSoldier, PICKUP_ITEM, 0 , FALSE );
8314 }
8315
8316 if (!(pSoldier->uiStatusFlags & SOLDIER_PC))
8317 {
8318 // set "pending action" value for AI so it will wait
8319 pSoldier->bAction = AI_ACTION_PENDING_ACTION;
8320 }
8321 break;
8322
8323 case ANIM_CROUCH:
8324 case ANIM_PRONE:
8325 UnSetUIBusy(pSoldier);
8326 HandleSoldierPickupItem( pSoldier, iItemIndex, sGridNo, bZLevel );
8327 SoldierGotoStationaryStance( pSoldier );
8328 if (!(pSoldier->uiStatusFlags & SOLDIER_PC))
8329 {
8330 // reset action value for AI because we're done!
8331 ActionDone( pSoldier );
8332 }
8333 break;
8334 }
8335 }
8336 }
8337 }
8338
PickDropItemAnimation(SOLDIERTYPE * pSoldier)8339 void PickDropItemAnimation( SOLDIERTYPE *pSoldier )
8340 {
8341 // Don't show animation of getting item, if we are not standing
8342 switch ( gAnimControl[ pSoldier->usAnimState ].ubHeight )
8343 {
8344 case ANIM_STAND:
8345
8346 EVENT_InitNewSoldierAnim( pSoldier, DROP_ITEM, 0 , FALSE );
8347 break;
8348
8349 case ANIM_CROUCH:
8350 case ANIM_PRONE:
8351
8352 SoldierHandleDropItem( pSoldier );
8353 SoldierGotoStationaryStance( pSoldier );
8354 break;
8355 }
8356 }
8357
8358
EVENT_SoldierBeginCutFence(SOLDIERTYPE * pSoldier,INT16 sGridNo,UINT8 ubDirection)8359 void EVENT_SoldierBeginCutFence( SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 ubDirection )
8360 {
8361 // Make sure we have a structure here....
8362 if ( IsCuttableWireFenceAtGridNo( sGridNo ) )
8363 {
8364 // CHANGE DIRECTION AND GOTO ANIMATION NOW
8365 EVENT_SetSoldierDesiredDirectionForward(pSoldier, ubDirection);
8366 EVENT_SetSoldierDirection( pSoldier, ubDirection );
8367
8368 //BOOLEAN CutWireFence( INT16 sGridNo )
8369
8370 // SET TARGET GRIDNO
8371 pSoldier->sTargetGridNo = sGridNo;
8372
8373 // CHANGE TO ANIMATION
8374 EVENT_InitNewSoldierAnim( pSoldier, CUTTING_FENCE, 0 , FALSE );
8375 }
8376 }
8377
8378
EVENT_SoldierBeginRepair(SOLDIERTYPE & s,GridNo const gridno,UINT8 const direction)8379 void EVENT_SoldierBeginRepair(SOLDIERTYPE& s, GridNo const gridno, UINT8 const direction)
8380 {
8381 SOLDIERTYPE* tgt;
8382 INT8 const repair_item = IsRepairableStructAtGridNo(gridno, &tgt);
8383 if (repair_item == 0) return;
8384
8385 // Change direction and goto animation now
8386 EVENT_SetSoldierDesiredDirection(&s, direction);
8387 EVENT_SetSoldierDirection(&s, direction);
8388 EVENT_InitNewSoldierAnim(&s, GOTO_REPAIRMAN, 0, FALSE);
8389
8390 switch (repair_item) // Set mercs's assignment to repair
8391 {
8392 case 2: /* Vehicle */ SetSoldierAssignmentRepair(s, FALSE, tgt->ubID); break;
8393 default: throw std::runtime_error(ST::string("unknown repair assignment: {}", repair_item).to_std_string());
8394 }
8395 }
8396
8397
EVENT_SoldierBeginRefuel(SOLDIERTYPE * pSoldier,INT16 sGridNo,UINT8 ubDirection)8398 void EVENT_SoldierBeginRefuel( SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 ubDirection )
8399 {
8400 // Make sure we have a structure here....
8401 if (GetRefuelableStructAtGridNo(sGridNo) != NULL)
8402 {
8403 // CHANGE DIRECTION AND GOTO ANIMATION NOW
8404 EVENT_SetSoldierDesiredDirection( pSoldier, ubDirection );
8405 EVENT_SetSoldierDirection( pSoldier, ubDirection );
8406
8407 //BOOLEAN CutWireFence( INT16 sGridNo )
8408
8409 // SET TARGET GRIDNO
8410 //pSoldier->sTargetGridNo = sGridNo;
8411
8412 // CHANGE TO ANIMATION
8413 EVENT_InitNewSoldierAnim( pSoldier, REFUEL_VEHICLE, 0 , FALSE );
8414 // SET BUDDY'S ASSIGNMENT TO REPAIR...
8415 }
8416 }
8417
8418
EVENT_SoldierBeginTakeBlood(SOLDIERTYPE * pSoldier,INT16 sGridNo,UINT8 ubDirection)8419 void EVENT_SoldierBeginTakeBlood( SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 ubDirection )
8420 {
8421 ROTTING_CORPSE *pCorpse;
8422
8423
8424 // See if these is a corpse here....
8425 pCorpse = GetCorpseAtGridNo( sGridNo , pSoldier->bLevel );
8426
8427 if ( pCorpse != NULL )
8428 {
8429 pSoldier->uiPendingActionData4 = CORPSE2ID(pCorpse);
8430
8431 // CHANGE DIRECTION AND GOTO ANIMATION NOW
8432 EVENT_SetSoldierDesiredDirection( pSoldier, ubDirection );
8433 EVENT_SetSoldierDirection( pSoldier, ubDirection );
8434
8435 EVENT_InitNewSoldierAnim( pSoldier, TAKE_BLOOD_FROM_CORPSE, 0 , FALSE );
8436 }
8437 else
8438 {
8439 // Say NOTHING quote...
8440 DoMercBattleSound( pSoldier, BATTLE_SOUND_NOTHING );
8441 }
8442 }
8443
8444
EVENT_SoldierBeginAttachCan(SOLDIERTYPE * pSoldier,INT16 sGridNo,UINT8 ubDirection)8445 void EVENT_SoldierBeginAttachCan( SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 ubDirection )
8446 {
8447 STRUCTURE *pStructure;
8448 DOOR_STATUS *pDoorStatus;
8449
8450 // OK, find door, attach to door, do animation...., remove item....
8451
8452 // First make sure we still have item in hand....
8453 if ( pSoldier->inv[ HANDPOS ].usItem != STRING_TIED_TO_TIN_CAN )
8454 {
8455 return;
8456 }
8457
8458 pStructure = FindStructure( sGridNo, STRUCTURE_ANYDOOR );
8459
8460 if ( pStructure == NULL )
8461 {
8462 return;
8463 }
8464
8465 // Modify door status to make sure one is created for this door
8466 // Use the current door state for this
8467 if ( !(pStructure->fFlags & STRUCTURE_OPEN) )
8468 {
8469 ModifyDoorStatus( sGridNo, FALSE, FALSE );
8470 }
8471 else
8472 {
8473 ModifyDoorStatus( sGridNo, TRUE, TRUE );
8474 }
8475
8476 // Now get door status...
8477 pDoorStatus = GetDoorStatus( sGridNo );
8478 if ( pDoorStatus == NULL )
8479 {
8480 // SOmething wrong here...
8481 return;
8482 }
8483
8484 // OK set flag!
8485 pDoorStatus->ubFlags |= DOOR_HAS_TIN_CAN;
8486
8487 // Do animation
8488 EVENT_SetSoldierDesiredDirection( pSoldier, ubDirection );
8489 EVENT_SetSoldierDirection( pSoldier, ubDirection );
8490
8491 EVENT_InitNewSoldierAnim( pSoldier, ATTACH_CAN_TO_STRING, 0 , FALSE );
8492
8493 // Remove item...
8494 DeleteObj( &(pSoldier->inv[ HANDPOS ] ) );
8495 fInterfacePanelDirty = DIRTYLEVEL2;
8496
8497 }
8498
8499
EVENT_SoldierBeginReloadRobot(SOLDIERTYPE * pSoldier,INT16 sGridNo,UINT8 ubDirection,UINT8 ubMercSlot)8500 void EVENT_SoldierBeginReloadRobot( SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 ubDirection, UINT8 ubMercSlot )
8501 {
8502 // Make sure we have a robot here....
8503 const SOLDIERTYPE* const tgt = WhoIsThere2(sGridNo, pSoldier->bLevel);
8504 if (tgt != NULL && tgt->uiStatusFlags & SOLDIER_ROBOT)
8505 {
8506 // CHANGE DIRECTION AND GOTO ANIMATION NOW
8507 EVENT_SetSoldierDesiredDirection( pSoldier, ubDirection );
8508 EVENT_SetSoldierDirection( pSoldier, ubDirection );
8509
8510 // CHANGE TO ANIMATION
8511 EVENT_InitNewSoldierAnim( pSoldier, RELOAD_ROBOT, 0 , FALSE );
8512
8513 }
8514 }
8515
8516
ChangeToFlybackAnimation(SOLDIERTYPE * pSoldier,INT8 bDirection)8517 static void ChangeToFlybackAnimation(SOLDIERTYPE* pSoldier, INT8 bDirection)
8518 {
8519 UINT16 usNewGridNo;
8520 SoldierSP soldier = GetSoldier(pSoldier);
8521
8522 // Get dest gridno, convert to center coords
8523 usNewGridNo = NewGridNo(pSoldier->sGridNo, DirectionInc(OppositeDirection(bDirection)));
8524 usNewGridNo = NewGridNo(usNewGridNo, DirectionInc(OppositeDirection(bDirection)));
8525
8526 soldier->removePendingAction();
8527
8528 // Set path....
8529 pSoldier->ubPathDataSize = 0;
8530 pSoldier->ubPathIndex = 0;
8531 pSoldier->ubPathingData[pSoldier->ubPathDataSize] = OppositeDirection(pSoldier->bDirection);
8532 pSoldier->ubPathDataSize++;
8533 pSoldier->ubPathingData[pSoldier->ubPathDataSize] = OppositeDirection(pSoldier->bDirection);
8534 pSoldier->ubPathDataSize++;
8535 pSoldier->sFinalDestination = usNewGridNo;
8536 EVENT_InternalSetSoldierDestination( pSoldier, pSoldier->ubPathingData[ pSoldier->ubPathIndex ], FALSE, FLYBACK_HIT );
8537
8538 // Get a new direction based on direction
8539 EVENT_InitNewSoldierAnim( pSoldier, FLYBACK_HIT, 0 , FALSE );
8540 }
8541
ChangeToFallbackAnimation(SOLDIERTYPE * pSoldier,INT8 bDirection)8542 void ChangeToFallbackAnimation( SOLDIERTYPE *pSoldier, INT8 bDirection )
8543 {
8544 UINT16 usNewGridNo;
8545 SoldierSP soldier = GetSoldier(pSoldier);
8546
8547 // Get dest gridno, convert to center coords
8548 usNewGridNo = NewGridNo(pSoldier->sGridNo, DirectionInc(OppositeDirection(bDirection)));
8549 //usNewGridNo = NewGridNo( (UINT16)usNewGridNo, (UINT16)(-1 * DirectionInc( bDirection ) ) );
8550
8551 soldier->removePendingAction();
8552
8553 // Set path....
8554 pSoldier->ubPathDataSize = 0;
8555 pSoldier->ubPathIndex = 0;
8556 pSoldier->ubPathingData[pSoldier->ubPathDataSize] = OppositeDirection(pSoldier->bDirection);
8557 pSoldier->ubPathDataSize++;
8558 pSoldier->sFinalDestination = usNewGridNo;
8559 EVENT_InternalSetSoldierDestination( pSoldier, pSoldier->ubPathingData[ pSoldier->ubPathIndex ], FALSE, FALLBACK_HIT_STAND );
8560
8561 // Get a new direction based on direction
8562 EVENT_InitNewSoldierAnim( pSoldier, FALLBACK_HIT_STAND, 0 , FALSE );
8563 }
8564
8565
SetSoldierCowerState(SOLDIERTYPE * pSoldier,BOOLEAN fOn)8566 void SetSoldierCowerState( SOLDIERTYPE *pSoldier, BOOLEAN fOn )
8567 {
8568 // Robot's don't cower!
8569 if ( pSoldier->ubBodyType == ROBOTNOWEAPON )
8570 {
8571 SLOGW("Robot was told to cower!");
8572 return;
8573 }
8574
8575 // OK< set flag and do anim...
8576 if ( fOn )
8577 {
8578 if ( !( pSoldier->uiStatusFlags & SOLDIER_COWERING ) )
8579 {
8580 EVENT_InitNewSoldierAnim( pSoldier, START_COWER, 0, FALSE );
8581
8582 pSoldier->uiStatusFlags |= SOLDIER_COWERING;
8583
8584 pSoldier->ubDesiredHeight = ANIM_CROUCH;
8585 }
8586 }
8587 else
8588 {
8589 if ( (pSoldier->uiStatusFlags & SOLDIER_COWERING) )
8590 {
8591 EVENT_InitNewSoldierAnim( pSoldier, END_COWER, 0, FALSE );
8592
8593 pSoldier->uiStatusFlags &= (~SOLDIER_COWERING );
8594
8595 pSoldier->ubDesiredHeight = ANIM_STAND;
8596 }
8597 }
8598 }
8599
8600
MercStealFromMerc(SOLDIERTYPE * const pSoldier,const SOLDIERTYPE * const pTarget)8601 void MercStealFromMerc(SOLDIERTYPE* const pSoldier, const SOLDIERTYPE* const pTarget)
8602 {
8603 INT16 sActionGridNo, sGridNo, sAdjustedGridNo;
8604 UINT8 ubDirection;
8605
8606 SoldierSP soldier = GetSoldier(pSoldier);
8607
8608 // OK, find an adjacent gridno....
8609 sGridNo = pTarget->sGridNo;
8610
8611 // See if we can get there to punch
8612 sActionGridNo = FindAdjacentGridEx( pSoldier, sGridNo, &ubDirection, &sAdjustedGridNo, TRUE, FALSE );
8613 if ( sActionGridNo != -1 )
8614 {
8615 soldier->setPendingAction(MERC_STEAL);
8616 pSoldier->sPendingActionData2 = pTarget->sGridNo;
8617 pSoldier->bPendingActionData3 = ubDirection;
8618
8619 // CHECK IF WE ARE AT THIS GRIDNO NOW
8620 if ( pSoldier->sGridNo != sActionGridNo )
8621 {
8622 // WALK UP TO DEST FIRST
8623 SendGetNewSoldierPathEvent(pSoldier, sActionGridNo);
8624 }
8625 else
8626 {
8627 EVENT_SetSoldierDesiredDirection( pSoldier, ubDirection );
8628 EVENT_InitNewSoldierAnim( pSoldier, STEAL_ITEM, 0 , FALSE );
8629 }
8630
8631 // OK, set UI
8632 gTacticalStatus.ubAttackBusyCount++;
8633 // reset attacking item (hand)
8634 pSoldier->usAttackingWeapon = 0;
8635 SLOGD("Starting STEAL attack, attack count now %d",
8636 gTacticalStatus.ubAttackBusyCount);
8637
8638 SetUIBusy(pSoldier);
8639 }
8640 }
8641
PlayerSoldierStartTalking(SOLDIERTYPE * pSoldier,UINT8 ubTargetID,BOOLEAN fValidate)8642 BOOLEAN PlayerSoldierStartTalking( SOLDIERTYPE *pSoldier, UINT8 ubTargetID, BOOLEAN fValidate )
8643 {
8644 UINT32 uiRange;
8645
8646 if ( ubTargetID == NOBODY )
8647 {
8648 return( FALSE );
8649 }
8650
8651 SOLDIERTYPE& tgt = GetMan(ubTargetID);
8652
8653 // Check distance again, to be sure
8654 if ( fValidate )
8655 {
8656 // OK, since we locked this guy from moving
8657 // we should be close enough, so talk ( unless he is now dead )
8658 if (!IsValidTalkableNPC(&tgt, FALSE, FALSE, FALSE)) return FALSE;
8659
8660 uiRange = GetRangeFromGridNoDiff(pSoldier->sGridNo, tgt.sGridNo);
8661
8662 if ( uiRange > ( NPC_TALK_RADIUS * 2 ) )
8663 {
8664 // Todo here - should we follow dude?
8665 return( FALSE );
8666 }
8667
8668
8669 }
8670
8671 // Get APs...
8672 const INT16 sAPCost = AP_TALK;
8673
8674 // Deduct points from our guy....
8675 DeductPoints( pSoldier, sAPCost, 0 );
8676
8677 INT16 const sFacingDir = GetDirectionFromGridNo(tgt.sGridNo, pSoldier);
8678
8679 // Set our guy facing
8680 SendSoldierSetDesiredDirectionEvent( pSoldier, sFacingDir );
8681
8682 // Set NPC facing
8683 SendSoldierSetDesiredDirectionEvent(&tgt, OppositeDirection(sFacingDir));
8684
8685 // Stop our guys...
8686 EVENT_StopMerc(pSoldier);
8687
8688 // ATE; Check for normal civs...
8689 if (GetCivType(&tgt) != CIV_TYPE_NA)
8690 {
8691 StartCivQuote(&tgt);
8692 return( FALSE );
8693 }
8694
8695
8696 // Are we an EPC that is being escorted?
8697 if (tgt.ubProfile != NO_PROFILE && tgt.ubWhatKindOfMercAmI == MERC_TYPE__EPC)
8698 {
8699 return InitiateConversation(&tgt, pSoldier, APPROACH_EPC_WHO_IS_RECRUITED);
8700 }
8701 else if (tgt.bNeutral)
8702 {
8703 switch (tgt.ubProfile)
8704 {
8705 case JIM:
8706 case JACK:
8707 case OLAF:
8708 case RAY:
8709 case OLGA:
8710 case TYRONE:
8711 // Start combat etc
8712 DeleteTalkingMenu();
8713 CancelAIAction(&tgt);
8714 AddToShouldBecomeHostileOrSayQuoteList(&tgt);
8715 break;
8716 default:
8717 // Start talking!
8718 return InitiateConversation(&tgt, pSoldier, NPC_INITIAL_QUOTE);
8719 }
8720 }
8721 else
8722 {
8723 // Start talking with hostile NPC
8724 return InitiateConversation(&tgt, pSoldier, APPROACH_ENEMY_NPC_QUOTE);
8725 }
8726
8727 return( TRUE );
8728 }
8729
8730
IsValidSecondHandShot(SOLDIERTYPE const * const s)8731 bool IsValidSecondHandShot(SOLDIERTYPE const* const s)
8732 {
8733 OBJECTTYPE const& o = s->inv[SECONDHANDPOS];
8734 return GCM->getItem(o.usItem)->getItemClass() == IC_GUN &&
8735 !(GCM->getItem(o.usItem)->isTwoHanded()) &&
8736 !s->bDoBurst &&
8737 GCM->getItem(s->inv[HANDPOS].usItem)->getItemClass() == IC_GUN &&
8738 o.bGunStatus >= USABLE &&
8739 o.ubGunShotsLeft > 0;
8740 }
8741
8742
IsValidSecondHandShotForReloadingPurposes(SOLDIERTYPE const * const s)8743 bool IsValidSecondHandShotForReloadingPurposes(SOLDIERTYPE const* const s)
8744 {
8745 // Should be maintained as same as function above with line about ammo taken
8746 // out!
8747 OBJECTTYPE const& o = s->inv[SECONDHANDPOS];
8748 return GCM->getItem(o.usItem)->getItemClass() == IC_GUN &&
8749 !s->bDoBurst &&
8750 GCM->getItem(s->inv[HANDPOS].usItem)->getItemClass() == IC_GUN &&
8751 o.bGunStatus >= USABLE;
8752 }
8753
8754
CanRobotBeControlled(const SOLDIERTYPE * const robot)8755 BOOLEAN CanRobotBeControlled(const SOLDIERTYPE* const robot)
8756 {
8757 Assert(robot->uiStatusFlags & SOLDIER_ROBOT);
8758 const SOLDIERTYPE* const controller = robot->robot_remote_holder;
8759 if (controller == NULL) return FALSE;
8760 return ControllingRobot(controller);
8761 }
8762
8763
ControllingRobot(const SOLDIERTYPE * s)8764 BOOLEAN ControllingRobot(const SOLDIERTYPE* s)
8765 {
8766 if (!s->bActive) return FALSE;
8767
8768 // EPCs can't control the robot (no inventory to hold remote, for one)
8769 if (AM_AN_EPC(s)) return FALSE;
8770
8771 // Don't require s->bInSector here, it must work from mapscreen!
8772
8773 // are we in ok shape?
8774 if (s->bLife < OKLIFE || s->bTeam != OUR_TEAM)
8775 {
8776 return( FALSE );
8777 }
8778
8779 // allow control from within vehicles - allows strategic travel in a vehicle with robot!
8780 if (s->bAssignment >= ON_DUTY && s->bAssignment != VEHICLE)
8781 {
8782 return( FALSE );
8783 }
8784
8785 // is the soldier wearing a robot remote control?
8786 if (!IsWearingHeadGear(*s, ROBOT_REMOTE_CONTROL))
8787 {
8788 return( FALSE );
8789 }
8790
8791 // Find the robot
8792 const SOLDIERTYPE* const pRobot = FindSoldierByProfileIDOnPlayerTeam(ROBOT);
8793 if ( !pRobot )
8794 {
8795 return( FALSE );
8796 }
8797
8798 // Are we in the same sector....?
8799 // ARM: CHANGED TO WORK IN MAPSCREEN, DON'T USE WorldSector HERE
8800 if (pRobot->sSectorX == s->sSectorX &&
8801 pRobot->sSectorY == s->sSectorY &&
8802 pRobot->bSectorZ == s->bSectorZ)
8803 {
8804 // they have to be either both in sector, or both on the road
8805 if (pRobot->fBetweenSectors == s->fBetweenSectors)
8806 {
8807 // if they're on the road...
8808 if ( pRobot->fBetweenSectors )
8809 {
8810 // they have to be in the same squad or vehicle
8811 if (pRobot->bAssignment != s->bAssignment) return FALSE;
8812
8813 // if in a vehicle, must be the same vehicle
8814 if (pRobot->bAssignment == VEHICLE && pRobot->iVehicleId != s->iVehicleId)
8815 {
8816 return( FALSE );
8817 }
8818 }
8819
8820 // all OK!
8821 return( TRUE );
8822 }
8823 }
8824
8825 return( FALSE );
8826 }
8827
8828
GetRobotController(const SOLDIERTYPE * pSoldier)8829 const SOLDIERTYPE *GetRobotController( const SOLDIERTYPE *pSoldier )
8830 {
8831 return pSoldier->robot_remote_holder;
8832 }
8833
8834
UpdateRobotControllerGivenRobot(SOLDIERTYPE * pRobot)8835 void UpdateRobotControllerGivenRobot( SOLDIERTYPE *pRobot )
8836 {
8837 // Loop through guys and look for a controller!
8838 FOR_EACH_IN_TEAM(s, OUR_TEAM)
8839 {
8840 if (ControllingRobot(s))
8841 {
8842 pRobot->robot_remote_holder = s;
8843 return;
8844 }
8845 }
8846
8847 pRobot->robot_remote_holder = NULL;
8848 }
8849
8850
UpdateRobotControllerGivenController(SOLDIERTYPE * pSoldier)8851 void UpdateRobotControllerGivenController( SOLDIERTYPE *pSoldier )
8852 {
8853 // First see if are still controlling the robot
8854 if ( !ControllingRobot( pSoldier ) )
8855 {
8856 return;
8857 }
8858
8859 FOR_EACH_IN_TEAM(s, OUR_TEAM)
8860 {
8861 if (s->uiStatusFlags & SOLDIER_ROBOT)
8862 {
8863 s->robot_remote_holder = pSoldier;
8864 }
8865 }
8866 }
8867
8868
HandleSoldierTakeDamageFeedback(SOLDIERTYPE * const s)8869 static void HandleSoldierTakeDamageFeedback(SOLDIERTYPE* const s)
8870 {
8871 // Do sound.....
8872 // ATE: Limit how often we grunt...
8873 if (GetJA2Clock() - s->uiTimeSinceLastBleedGrunt > 1000)
8874 {
8875 s->uiTimeSinceLastBleedGrunt = GetJA2Clock();
8876 DoMercBattleSound(s, BATTLE_SOUND_HIT1);
8877 }
8878
8879 FlashSoldierPortrait(s);
8880 }
8881
8882
HandleSystemNewAISituation(SOLDIERTYPE * const pSoldier)8883 void HandleSystemNewAISituation(SOLDIERTYPE* const pSoldier)
8884 {
8885 SoldierSP soldier = GetSoldier(pSoldier);
8886
8887 // Are we an AI guy?
8888 if ( gTacticalStatus.ubCurrentTeam != OUR_TEAM && pSoldier->bTeam != OUR_TEAM )
8889 {
8890 if ( pSoldier->bNewSituation == IS_NEW_SITUATION )
8891 {
8892 // Cancel what they were doing....
8893 pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
8894 pSoldier->fTurningFromPronePosition = FALSE;
8895 pSoldier->ubPendingDirection = NO_PENDING_DIRECTION;
8896 soldier->removePendingAction();
8897 pSoldier->bEndDoorOpenCode = 0;
8898
8899 // if this guy isn't under direct AI control, WHO GIVES A FLYING FLICK?
8900 if ( pSoldier->uiStatusFlags & SOLDIER_UNDERAICONTROL )
8901 {
8902 if ( pSoldier->fTurningToShoot )
8903 {
8904 pSoldier->fTurningToShoot = FALSE;
8905 // Release attacker
8906 // OK - this is hightly annoying , but due to the huge combinations of
8907 // things that can happen - 1 of them is that sLastTarget will get unset
8908 // after turn is done - so set flag here to tell it not to...
8909 pSoldier->fDontUnsetLastTargetFromTurn = TRUE;
8910 SLOGD(
8911 "Reducing attacker busy count..., ending fire because saw something: DONE IN SYSTEM NEW SITUATION");
8912 ReduceAttackBusyCount(pSoldier, FALSE);
8913 }
8914
8915 if ( pSoldier->pTempObject != NULL )
8916 {
8917 // Place it back into inv....
8918 AutoPlaceObject( pSoldier, pSoldier->pTempObject, FALSE );
8919 delete pSoldier->pTempObject;
8920 pSoldier->pTempObject = NULL;
8921 pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
8922
8923 // Decrement attack counter...
8924 SLOGD(
8925 "Reducing attacker busy count..., ending throw because saw something: DONE IN SYSTEM NEW SITUATION");
8926 ReduceAttackBusyCount(pSoldier, FALSE);
8927 }
8928 }
8929 }
8930 }
8931 }
8932
8933
InternalPlaySoldierFootstepSound(SOLDIERTYPE * const s)8934 static void InternalPlaySoldierFootstepSound(SOLDIERTYPE* const s)
8935 {
8936 // Determine if we are on the floor
8937 if (s->uiStatusFlags & SOLDIER_VEHICLE) return;
8938
8939 INT8 volume = MIDVOLUME;
8940 if (s->usAnimState == HOPFENCE) volume = HIGHVOLUME;
8941
8942 if (s->uiStatusFlags & SOLDIER_ROBOT)
8943 {
8944 PlaySoldierJA2Sample(s, ROBOT_BEEP, volume, 1, TRUE);
8945 return;
8946 }
8947
8948 // Assume outside
8949 SoundID sound_base = WALK_LEFT_OUT;
8950 UINT8 random_max = 4;
8951 if (s->usAnimState == CRAWLING)
8952 {
8953 sound_base = CRAWL_1;
8954 }
8955 else switch (s->bOverTerrainType) // Pick base based on terrain over
8956 {
8957 case FLAT_FLOOR: sound_base = WALK_LEFT_IN; break;
8958 case DIRT_ROAD:
8959 case PAVED_ROAD: sound_base = WALK_LEFT_ROAD; break;
8960 case LOW_WATER:
8961 case MED_WATER: sound_base = WATER_WALK1_IN; random_max = 2; break;
8962 case DEEP_WATER: sound_base = SWIM_1; random_max = 2; break;
8963 }
8964
8965 // Pick a random sound
8966 UINT8 random_snd;
8967 do
8968 {
8969 random_snd = Random(random_max);
8970 }
8971 while (random_snd == s->ubLastFootPrintSound);
8972
8973 s->ubLastFootPrintSound = random_snd;
8974
8975 // If in realtime, don't play at full volume, because too many people walking
8976 // around sounds don't sound good (unless we are the selected guy, then always
8977 // play at reg volume)
8978 if (!(gTacticalStatus.uiFlags & INCOMBAT) && s != GetSelectedMan())
8979 {
8980 volume = LOWVOLUME;
8981 }
8982
8983 PlaySoldierJA2Sample(s, static_cast<SoundID>(sound_base + random_snd), volume, 1, TRUE);
8984 }
8985
8986
PlaySoldierFootstepSound(SOLDIERTYPE * pSoldier)8987 void PlaySoldierFootstepSound(SOLDIERTYPE* pSoldier)
8988 {
8989 // normally, not in stealth mode
8990 if ( !pSoldier->bStealthMode )
8991 {
8992 InternalPlaySoldierFootstepSound( pSoldier );
8993 }
8994 }
8995
8996
PlayStealthySoldierFootstepSound(SOLDIERTYPE * pSoldier)8997 void PlayStealthySoldierFootstepSound(SOLDIERTYPE* pSoldier)
8998 {
8999 // even if in stealth mode
9000 InternalPlaySoldierFootstepSound( pSoldier );
9001 }
9002
9003
CrowsFlyAway(const UINT8 ubTeam)9004 void CrowsFlyAway(const UINT8 ubTeam)
9005 {
9006 FOR_EACH_IN_TEAM(s, ubTeam)
9007 {
9008 if (s->bInSector &&
9009 s->ubBodyType == CROW &&
9010 s->usAnimState != CROW_FLY)
9011 {
9012 // fly away even if not seen!
9013 HandleCrowFlyAway(s);
9014 }
9015 }
9016 }
9017
9018
BeginTyingToFall(SOLDIERTYPE * pSoldier)9019 void BeginTyingToFall( SOLDIERTYPE *pSoldier )
9020 {
9021 pSoldier->bStartFallDir = pSoldier->bDirection;
9022 pSoldier->fTryingToFall = TRUE;
9023
9024 // Randomize direction
9025 if ( Random( 50 ) < 25 )
9026 {
9027 pSoldier->fFallClockwise = TRUE;
9028 }
9029 else
9030 {
9031 pSoldier->fFallClockwise = FALSE;
9032 }
9033 }
9034
SetSoldierAsUnderAiControl(SOLDIERTYPE * pSoldierToSet)9035 void SetSoldierAsUnderAiControl( SOLDIERTYPE *pSoldierToSet )
9036 {
9037 if ( pSoldierToSet == NULL )
9038 {
9039 return;
9040 }
9041
9042 FOR_EACH_SOLDIER(s)
9043 {
9044 s->uiStatusFlags &= ~SOLDIER_UNDERAICONTROL;
9045 }
9046
9047 pSoldierToSet->uiStatusFlags |= SOLDIER_UNDERAICONTROL;
9048 }
9049
9050
9051 static void EnableDisableSoldierLightEffects(BOOLEAN fEnableLights);
9052
9053
HandlePlayerTogglingLightEffects(BOOLEAN fToggleValue)9054 void HandlePlayerTogglingLightEffects( BOOLEAN fToggleValue )
9055 {
9056 if( fToggleValue )
9057 {
9058 //Toggle light status
9059 gGameSettings.fOptions[ TOPTION_MERC_CASTS_LIGHT ] ^= TRUE;
9060 }
9061
9062 //Update all the mercs in the sector
9063 EnableDisableSoldierLightEffects( gGameSettings.fOptions[ TOPTION_MERC_CASTS_LIGHT ] );
9064
9065 SetRenderFlags(RENDER_FLAG_FULL);
9066 }
9067
9068
EnableDisableSoldierLightEffects(BOOLEAN const enable_lights)9069 static void EnableDisableSoldierLightEffects(BOOLEAN const enable_lights)
9070 {
9071 FOR_EACH_IN_TEAM(s, OUR_TEAM)
9072 {
9073 if (!s->bInSector) continue;
9074 if (s->bLife < OKLIFE) continue;
9075 // att: NO Soldier Lights if in a VEHICLE
9076 if (s->bAssignment == VEHICLE) continue;
9077
9078 if (enable_lights)
9079 {
9080 // Add the light around the merc
9081 PositionSoldierLight(s);
9082 }
9083 else
9084 {
9085 // Delete the fake light the merc casts
9086 DeleteSoldierLight(s);
9087 // Light up the merc though
9088 SetSoldierPersonalLightLevel(s);
9089 }
9090 }
9091 }
9092
9093
SetSoldierPersonalLightLevel(SOLDIERTYPE * const s)9094 static void SetSoldierPersonalLightLevel(SOLDIERTYPE* const s)
9095 {
9096 if (!s || s->sGridNo == NOWHERE) return;
9097 // The light level for the soldier
9098 LEVELNODE& n = *gpWorldLevelData[s->sGridNo].pMercHead;
9099 n.ubShadeLevel = 3;
9100 n.ubSumLights = 5;
9101 n.ubMaxLights = 5;
9102 n.ubNaturalShadeLevel = 5;
9103 }
9104
9105
9106 #ifdef WITH_UNITTESTS
9107 #undef FAIL
9108 #include "gtest/gtest.h"
9109
TEST(SoldierControl,asserts)9110 TEST(SoldierControl, asserts)
9111 {
9112 EXPECT_EQ(lengthof(gubMaxActionPoints), static_cast<size_t>(TOTALBODYTYPES));
9113 EXPECT_EQ(sizeof(KEY_ON_RING), 2u);
9114 }
9115
9116 #endif
9117