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