1 #include "Handle_Items.h"
2 #include "Soldier_Find.h"
3 #include "TileDef.h"
4 #include "WorldDef.h"
5 #include "Points.h"
6 #include "Overhead.h"
7 #include "Font_Control.h"
8 #include "Interface.h"
9 #include "Isometric_Utils.h"
10 #include "PathAI.h"
11 #include "Message.h"
12 #include "Animation_Control.h"
13 #include "Weapons.h"
14 #include "Structure_Wrap.h"
15 #include "Dialogue_Control.h"
16 #include "Items.h"
17 #include "RT_Time_Defines.h"
18 #include "AI.h"
19 #include "Handle_UI.h"
20 #include "Text.h"
21 #include "SkillCheck.h"
22 #include "WCheck.h"
23 #include "Soldier_Profile.h"
24 #include "Soldier_Macros.h"
25 #include "Drugs_And_Alcohol.h"
26 #include "WorldMan.h"
27 #include "Interface_Items.h"
28 #include "Debug.h"
29
30 #include "ContentManager.h"
31 #include "GameInstance.h"
32 #include "MagazineModel.h"
33 #include "WeaponModels.h"
34
35 #include "Logger.h"
36
TerrainActionPoints(const SOLDIERTYPE * const pSoldier,const INT16 sGridno,const INT8 bDir,const INT8 bLevel)37 INT16 TerrainActionPoints(const SOLDIERTYPE* const pSoldier, const INT16 sGridno, const INT8 bDir, const INT8 bLevel)
38 {
39 INT16 sAPCost = 0;
40 INT16 sSwitchValue;
41
42 if ( pSoldier->bStealthMode )
43 sAPCost += AP_STEALTH_MODIFIER;
44
45 if ( pSoldier->bReverse || gUIUseReverse )
46 sAPCost += AP_REVERSE_MODIFIER;
47
48 sSwitchValue = gubWorldMovementCosts[sGridno][bDir][ bLevel ];
49
50 // Check reality vs what the player knows....
51 if ( sSwitchValue == TRAVELCOST_NOT_STANDING )
52 {
53 // use the cost of the terrain!
54 sSwitchValue = gTileTypeMovementCost[ gpWorldLevelData[ sGridno ].ubTerrainID ];
55 }
56 else if (IS_TRAVELCOST_DOOR( sSwitchValue ))
57 {
58 sSwitchValue = DoorTravelCost(pSoldier, sGridno, (UINT8)sSwitchValue, pSoldier->bTeam == OUR_TEAM, NULL);
59 }
60
61 if (sSwitchValue >= TRAVELCOST_BLOCKED && sSwitchValue != TRAVELCOST_DOOR )
62 {
63 return(100); // Cost too much to be considered!
64 }
65
66 switch( sSwitchValue )
67 {
68 case TRAVELCOST_DIRTROAD:
69 case TRAVELCOST_FLAT:
70 sAPCost += AP_MOVEMENT_FLAT;
71 break;
72 //case TRAVELCOST_BUMPY:
73 case TRAVELCOST_GRASS:
74 sAPCost += AP_MOVEMENT_GRASS;
75 break;
76 case TRAVELCOST_THICK:
77 sAPCost += AP_MOVEMENT_BUSH;
78 break;
79 case TRAVELCOST_DEBRIS:
80 sAPCost += AP_MOVEMENT_RUBBLE;
81 break;
82 case TRAVELCOST_SHORE:
83 sAPCost += AP_MOVEMENT_SHORE; // wading shallow water
84 break;
85 case TRAVELCOST_KNEEDEEP:
86 sAPCost += AP_MOVEMENT_LAKE; // wading waist/chest deep - very slow
87 break;
88
89 case TRAVELCOST_DEEPWATER:
90 sAPCost += AP_MOVEMENT_OCEAN; // can swim, so it's faster than wading
91 break;
92 case TRAVELCOST_DOOR:
93 sAPCost += AP_MOVEMENT_FLAT;
94 break;
95
96 // cost for jumping a fence REPLACES all other AP costs!
97 case TRAVELCOST_FENCE:
98 return( AP_JUMPFENCE );
99
100 case TRAVELCOST_NONE:
101 return( 0 );
102
103 default:
104 SLOGD(
105 "Calc AP: Unrecongnized MP type %d in %d, direction %d",
106 sSwitchValue, sGridno, bDir);
107 break;
108 }
109
110 if (bDir & 1)
111 {
112 sAPCost = (sAPCost * 14) / 10;
113 }
114
115
116 return(sAPCost);
117
118 }
119
120
BreathPointAdjustmentForCarriedWeight(SOLDIERTYPE * pSoldier)121 static INT16 BreathPointAdjustmentForCarriedWeight(SOLDIERTYPE* pSoldier)
122 {
123 UINT32 uiCarriedPercent;
124 UINT32 uiPercentCost;
125
126 uiCarriedPercent = CalculateCarriedWeight( pSoldier );
127 if (uiCarriedPercent < 101)
128 {
129 // normal BP costs
130 uiPercentCost = 100;
131 }
132 else
133 {
134 if (uiCarriedPercent < 151)
135 {
136 // between 101 and 150% of max carried weight, extra BP cost
137 // of 1% per % above 100... so at 150%, we pay 150%
138 uiPercentCost = 100 + (uiCarriedPercent - 100) * 3;
139 }
140 else if (uiCarriedPercent < 201)
141 {
142 // between 151 and 200% of max carried weight, extra BP cost
143 // of 2% per % above 150... so at 200%, we pay 250%
144 uiPercentCost = 100 + (uiCarriedPercent - 100) * 3 + (uiCarriedPercent - 150);
145 }
146 else
147 {
148 // over 200%, extra BP cost of 3% per % above 200
149 uiPercentCost = 100 + (uiCarriedPercent - 100) * 3 +
150 (uiCarriedPercent - 150) + (uiCarriedPercent - 200);
151 // so at 250% weight, we pay 400% breath!
152 }
153 }
154 return( (INT16) uiPercentCost );
155 }
156
157
TerrainBreathPoints(SOLDIERTYPE * pSoldier,INT16 sGridno,INT8 bDir,UINT16 usMovementMode)158 INT16 TerrainBreathPoints(SOLDIERTYPE * pSoldier, INT16 sGridno,INT8 bDir, UINT16 usMovementMode)
159 {
160 INT32 iPoints=0;
161 UINT8 ubMovementCost;
162
163 ubMovementCost = gubWorldMovementCosts[sGridno][bDir][0];
164
165 switch( ubMovementCost )
166 {
167 case TRAVELCOST_DIRTROAD:
168 case TRAVELCOST_FLAT:
169 iPoints = BP_MOVEMENT_FLAT;
170 break;
171 //case TRAVELCOST_BUMPY:
172 case TRAVELCOST_GRASS:
173 iPoints = BP_MOVEMENT_GRASS;
174 break;
175 case TRAVELCOST_THICK:
176 iPoints = BP_MOVEMENT_BUSH;
177 break;
178 case TRAVELCOST_DEBRIS:
179 iPoints = BP_MOVEMENT_RUBBLE;
180 break;
181 case TRAVELCOST_SHORE:
182 iPoints = BP_MOVEMENT_SHORE;
183 break; // wading shallow water
184 case TRAVELCOST_KNEEDEEP:
185 iPoints = BP_MOVEMENT_LAKE;
186 break; // wading waist/chest deep - very slow
187 case TRAVELCOST_DEEPWATER:
188 iPoints = BP_MOVEMENT_OCEAN;
189 break; // can swim, so it's faster than wading
190 default:
191 if ( IS_TRAVELCOST_DOOR( ubMovementCost ) )
192 {
193 iPoints = BP_MOVEMENT_FLAT;
194 break;
195 }
196 return(0);
197 }
198
199 iPoints = iPoints * BreathPointAdjustmentForCarriedWeight( pSoldier ) / 100;
200
201 // ATE - MAKE MOVEMENT ALWAYS WALK IF IN WATER
202 if (gpWorldLevelData[sGridno].ubTerrainID == DEEP_WATER ||
203 gpWorldLevelData[sGridno].ubTerrainID == MED_WATER ||
204 gpWorldLevelData[sGridno].ubTerrainID == LOW_WATER)
205 {
206 usMovementMode = WALKING;
207 }
208
209 // so, then we must modify it for other movement styles and accumulate
210 switch(usMovementMode)
211 {
212 case RUNNING:
213 case ADULTMONSTER_WALKING:
214 case BLOODCAT_RUN:
215 iPoints *= BP_RUN_ENERGYCOSTFACTOR;
216 break;
217
218 case SIDE_STEP:
219 case WALK_BACKWARDS:
220 case BLOODCAT_WALK_BACKWARDS:
221 case MONSTER_WALK_BACKWARDS:
222 case WALKING:
223 iPoints *= BP_WALK_ENERGYCOSTFACTOR;
224 break;
225
226 case START_SWAT:
227 case SWATTING:
228 case SWAT_BACKWARDS:
229 iPoints *= BP_SWAT_ENERGYCOSTFACTOR;
230 break;
231 case CRAWLING:
232 iPoints *= BP_CRAWL_ENERGYCOSTFACTOR;
233 break;
234
235
236 }
237
238 // ATE: Adjust these by realtime movement
239 if (!(gTacticalStatus.uiFlags & INCOMBAT))
240 {
241 // ATE: ADJUST FOR RT - MAKE BREATH GO A LITTLE FASTER!
242 iPoints = (INT32)( iPoints * TB_BREATH_DEDUCT_MODIFIER );
243 }
244
245
246 return( (INT16) iPoints);
247 }
248
249
ActionPointCost(const SOLDIERTYPE * const pSoldier,const INT16 sGridNo,const INT8 bDir,UINT16 usMovementMode)250 INT16 ActionPointCost(const SOLDIERTYPE* const pSoldier, const INT16 sGridNo, const INT8 bDir, UINT16 usMovementMode)
251 {
252 INT16 sTileCost, sPoints, sSwitchValue;
253
254 sPoints = 0;
255
256 // get the tile cost for that tile based on WALKING
257 sTileCost = TerrainActionPoints( pSoldier, sGridNo, bDir, pSoldier->bLevel );
258
259 // Get switch value...
260 sSwitchValue = gubWorldMovementCosts[ sGridNo ][ bDir ][ pSoldier->bLevel ];
261
262 // Tile cost should not be reduced based on movement mode...
263 if ( sSwitchValue == TRAVELCOST_FENCE )
264 {
265 return( sTileCost );
266 }
267
268 // ATE - MAKE MOVEMENT ALWAYS WALK IF IN WATER
269 if ( gpWorldLevelData[ sGridNo ].ubTerrainID == DEEP_WATER || gpWorldLevelData[ sGridNo ].ubTerrainID == MED_WATER || gpWorldLevelData[ sGridNo ].ubTerrainID == LOW_WATER )
270 {
271 usMovementMode = WALKING;
272 }
273
274 // so, then we must modify it for other movement styles and accumulate
275 if (sTileCost > 0)
276 {
277 switch(usMovementMode)
278 {
279 case RUNNING:
280 case ADULTMONSTER_WALKING:
281 case BLOODCAT_RUN:
282 sPoints = (INT16)( ((DOUBLE)sTileCost) / RUNDIVISOR);
283 break;
284
285 case CROW_FLY:
286 case SIDE_STEP:
287 case WALK_BACKWARDS:
288 case ROBOT_WALK:
289 case BLOODCAT_WALK_BACKWARDS:
290 case MONSTER_WALK_BACKWARDS:
291 case LARVAE_WALK:
292 case KID_SKIPPING:
293 case WALKING:
294 sPoints = (sTileCost + WALKCOST);
295 break;
296
297 case CROW_WALK:
298 case START_SWAT:
299 case SWAT_BACKWARDS:
300 case SWATTING:
301 sPoints = (sTileCost + SWATCOST);
302 break;
303 case CRAWLING:
304 sPoints = (sTileCost + CRAWLCOST);
305 break;
306
307 default:
308
309 // Invalid movement mode
310 SLOGW(
311 "Invalid movement mode %d used in ActionPointCost",
312 usMovementMode);
313 sPoints = 1;
314 }
315 }
316
317 if (sSwitchValue == TRAVELCOST_NOT_STANDING)
318 {
319 switch(usMovementMode)
320 {
321 case RUNNING:
322 case WALKING :
323 case LARVAE_WALK:
324 case SIDE_STEP:
325 case WALK_BACKWARDS:
326 // charge crouch APs for ducking head!
327 sPoints += AP_CROUCH;
328 break;
329
330 default:
331 break;
332 }
333 }
334
335 return( sPoints );
336 }
337
EstimateActionPointCost(SOLDIERTYPE * pSoldier,INT16 sGridNo,INT8 bDir,UINT16 usMovementMode,INT8 bPathIndex,INT8 bPathLength)338 INT16 EstimateActionPointCost( SOLDIERTYPE *pSoldier, INT16 sGridNo, INT8 bDir, UINT16 usMovementMode, INT8 bPathIndex, INT8 bPathLength )
339 {
340 // This action point cost code includes the penalty for having to change
341 // stance after jumping a fence IF our path continues...
342 INT16 sTileCost, sPoints, sSwitchValue;
343 sPoints = 0;
344
345 // get the tile cost for that tile based on WALKING
346 sTileCost = TerrainActionPoints( pSoldier, sGridNo, bDir, pSoldier->bLevel );
347
348 // so, then we must modify it for other movement styles and accumulate
349 if (sTileCost > 0)
350 {
351 switch(usMovementMode)
352 {
353 case RUNNING:
354 case ADULTMONSTER_WALKING:
355 case BLOODCAT_RUN:
356 sPoints = (INT16)(((DOUBLE)sTileCost) / RUNDIVISOR);
357 break;
358
359 case CROW_FLY:
360 case SIDE_STEP:
361 case ROBOT_WALK:
362 case WALK_BACKWARDS:
363 case BLOODCAT_WALK_BACKWARDS:
364 case MONSTER_WALK_BACKWARDS:
365 case LARVAE_WALK:
366 case KID_SKIPPING:
367 case WALKING :
368 sPoints = (sTileCost + WALKCOST);
369 break;
370
371 case CROW_WALK:
372 case START_SWAT:
373 case SWAT_BACKWARDS:
374 case SWATTING:
375 sPoints = (sTileCost + SWATCOST);
376 break;
377 case CRAWLING:
378 sPoints = (sTileCost + CRAWLCOST);
379 break;
380
381 default:
382
383 // Invalid movement mode
384 SLOGW(
385 "Invalid movement mode %d used in EstimateActionPointCost",
386 usMovementMode);
387 sPoints = 1;
388 }
389 }
390
391 // Get switch value...
392 sSwitchValue = gubWorldMovementCosts[ sGridNo ][ bDir ][ pSoldier->bLevel ];
393
394 // ATE: If we have a 'special cost, like jump fence...
395 if ( sSwitchValue == TRAVELCOST_FENCE )
396 {
397 // If we are changeing stance ( either before or after getting there....
398 // We need to reflect that...
399 switch(usMovementMode)
400 {
401 case SIDE_STEP:
402 case WALK_BACKWARDS:
403 case RUNNING:
404 case WALKING :
405
406 // Add here cost to go from crouch to stand AFTER fence hop....
407 // Since it's AFTER.. make sure we will be moving after jump...
408 if ( ( bPathIndex + 2 ) < bPathLength )
409 {
410 sPoints += AP_CROUCH;
411 }
412 break;
413
414 case SWATTING:
415 case START_SWAT:
416 case SWAT_BACKWARDS:
417
418 // Add cost to stand once there BEFORE....
419 sPoints += AP_CROUCH;
420 break;
421
422 case CRAWLING:
423
424 // Can't do it here.....
425 break;
426 }
427 }
428 else if (sSwitchValue == TRAVELCOST_NOT_STANDING)
429 {
430 switch(usMovementMode)
431 {
432 case RUNNING:
433 case WALKING :
434 case SIDE_STEP:
435 case WALK_BACKWARDS:
436 // charge crouch APs for ducking head!
437 sPoints += AP_CROUCH;
438 break;
439
440 default:
441 break;
442 }
443 }
444
445 return( sPoints );
446 }
447
448
EnoughPoints(const SOLDIERTYPE * pSoldier,INT16 sAPCost,INT16 sBPCost,BOOLEAN fDisplayMsg)449 BOOLEAN EnoughPoints(const SOLDIERTYPE* pSoldier, INT16 sAPCost, INT16 sBPCost, BOOLEAN fDisplayMsg)
450 {
451 INT16 sNewAP = 0;
452
453 // If this guy is on a special move... don't care about APS, OR BPSs!
454 if ( pSoldier->ubWaitActionToDo )
455 {
456 return( TRUE );
457 }
458
459 // can't do anything while collapsed
460 if (pSoldier->bCollapsed)
461 {
462 return FALSE;
463 }
464
465 if ( pSoldier->ubQuoteActionID >=QUOTE_ACTION_ID_TRAVERSE_EAST && pSoldier->ubQuoteActionID <= QUOTE_ACTION_ID_TRAVERSE_NORTH )
466 {
467 // AI guy on special move off map
468 return( TRUE );
469 }
470
471 // IN realtime.. only care about BPs
472 if (!(gTacticalStatus.uiFlags & INCOMBAT))
473 {
474 sAPCost = 0;
475 }
476
477 // Get New points
478 sNewAP = pSoldier->bActionPoints - sAPCost;
479
480 // If we cannot deduct points, return FALSE
481 if ( sNewAP < 0 )
482 {
483 // Display message if it's our own guy
484 if ( pSoldier->bTeam == OUR_TEAM && fDisplayMsg )
485 {
486 ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, TacticalStr[ NOT_ENOUGH_APS_STR ] );
487 }
488 return( FALSE );
489 }
490
491 return( TRUE );
492 }
493
494
495 static INT16 AdjustBreathPts(SOLDIERTYPE* pSold, INT16 sBPCost);
496
497
DeductPoints(SOLDIERTYPE * pSoldier,INT16 sAPCost,INT16 sBPCost)498 void DeductPoints( SOLDIERTYPE *pSoldier, INT16 sAPCost, INT16 sBPCost )
499 {
500 INT16 sNewAP = 0;
501 INT8 bNewBreath;
502
503
504 // in real time, there IS no AP cost, (only breath cost)
505 if (!(gTacticalStatus.uiFlags & INCOMBAT))
506 {
507 sAPCost = 0;
508 }
509
510 // Get New points
511 sNewAP = pSoldier->bActionPoints - sAPCost;
512
513 // If this is the first time with no action points, set UI flag
514 if ( sNewAP <= 0 && pSoldier->bActionPoints > 0 )
515 {
516 fInterfacePanelDirty = DIRTYLEVEL1;
517 }
518
519 // If we cannot deduct points, return FALSE
520 if ( sNewAP < 0 )
521 {
522 sNewAP = 0;
523 }
524
525 pSoldier->bActionPoints = (INT8)sNewAP;
526
527 SLOGD("Deduct Points (%d at %d) %d %d",
528 pSoldier->ubID, pSoldier->sGridNo, sAPCost, sBPCost);
529
530 if ( AM_A_ROBOT( pSoldier ) )
531 {
532 // zap all breath costs for robot
533 sBPCost = 0;
534 }
535
536 // is there a BREATH deduction/transaction to be made? (REMEMBER: could be a GAIN!)
537 if (sBPCost)
538 {
539 // Adjust breath changes due to spending or regaining of energy
540 sBPCost = AdjustBreathPts(pSoldier,sBPCost);
541 sBPCost *= -1;
542
543 pSoldier->sBreathRed -= sBPCost;
544
545 // CJC: moved check for high breathred to below so that negative breath can be detected
546
547 // cap breathred
548 if ( pSoldier->sBreathRed < 0 )
549 {
550 pSoldier->sBreathRed = 0;
551 }
552 if ( pSoldier->sBreathRed > 10000 )
553 {
554 pSoldier->sBreathRed = 10000;
555 }
556
557 // Get new breath
558 bNewBreath = (UINT8)( pSoldier->bBreathMax - ( (FLOAT)pSoldier->sBreathRed / (FLOAT)100 ) );
559
560 if ( bNewBreath > 100 )
561 {
562 bNewBreath = 100;
563 }
564 if ( bNewBreath < 00 )
565 {
566 // Take off 1 AP per 5 breath... rem adding a negative subtracts
567 pSoldier->bActionPoints += (bNewBreath / 5);
568 if ( pSoldier->bActionPoints < 0 )
569 {
570 pSoldier->bActionPoints = 0;
571 }
572
573 bNewBreath = 0;
574 }
575
576 if( bNewBreath > pSoldier->bBreathMax )
577 {
578 bNewBreath = pSoldier->bBreathMax;
579 }
580 pSoldier->bBreath = bNewBreath;
581 }
582
583 // UPDATE BAR
584 DirtyMercPanelInterface( pSoldier, DIRTYLEVEL1 );
585
586 }
587
588
AdjustBreathPts(SOLDIERTYPE * pSold,INT16 sBPCost)589 static INT16 AdjustBreathPts(SOLDIERTYPE* pSold, INT16 sBPCost)
590 {
591 INT16 sBreathFactor = 100;
592 UINT8 ubBandaged;
593
594 // in real time, there IS no AP cost, (only breath cost)
595 /*
596 if (!(gTacticalStatus.uiFlags & INCOMBAT))
597 {
598 // ATE: ADJUST FOR RT - MAKE BREATH GO A LITTLE FASTER!
599 sBPCost *= TB_BREATH_DEDUCT_MODIFIER;
600 }*/
601
602
603 // adjust breath factor for current breath deficiency
604 sBreathFactor += (100 - pSold->bBreath);
605
606 // adjust breath factor for current life deficiency (but add 1/2 bandaging)
607 ubBandaged = pSold->bLifeMax - pSold->bLife - pSold->bBleeding;
608 //sBreathFactor += (pSold->bLifeMax - (pSold->bLife + (ubBandaged / 2)));
609 sBreathFactor += 100 * (pSold->bLifeMax - (pSold->bLife + (ubBandaged / 2))) / pSold->bLifeMax;
610
611 if ( pSold->bStrength > 80 )
612 {
613 // give % reduction to breath costs for high strength mercs
614 sBreathFactor -= (pSold->bStrength - 80) / 2;
615 }
616
617 // if a non-swimmer type is thrashing around in deep water
618 if ( (pSold->ubProfile != NO_PROFILE ) && (gMercProfiles[ pSold->ubProfile ].bPersonalityTrait == NONSWIMMER) )
619 {
620 if ( pSold->usAnimState == DEEP_WATER_TRED || pSold->usAnimState == DEEP_WATER_SWIM)
621 {
622 sBreathFactor *= 5; // lose breath 5 times faster in deep water!
623 }
624 }
625
626 if ( sBreathFactor == 0 )
627 {
628 sBPCost = 0;
629 }
630 else if (sBPCost > 0) // breath DECREASE
631 {
632 // increase breath COST by breathFactor
633 sBPCost = ((sBPCost * sBreathFactor) / 100);
634 }
635 else // breath INCREASE
636 {
637 // decrease breath GAIN by breathFactor
638 sBPCost = ((sBPCost * 100) / sBreathFactor);
639 }
640
641 return(sBPCost);
642 }
643
644
645 static INT16 GetBreathPerAP(SOLDIERTYPE* pSoldier, UINT16 usAnimState);
646
647
UnusedAPsToBreath(SOLDIERTYPE * pSold)648 void UnusedAPsToBreath(SOLDIERTYPE *pSold)
649 {
650 INT16 sUnusedAPs, sBreathPerAP = 0, sBreathChange, sRTBreathMod;
651
652 // Note to Andrew (or whomever else it may concern):
653
654
655 // This function deals with BETWEEN TURN breath/energy gains. The basic concept is:
656 //
657 // - look at LAST (current) animation of merc to see what he's now doing
658 // - look at how many AP remain unspent (indicating duration of time doing that anim)
659 //
660 // figure out how much breath/energy (if any) he should recover. Obviously if a merc
661 // is STANDING BREATHING and hasn't spent any AP then it means he *stood around* for
662 // the entire duration of one turn (which, instead of spending energy, REGAINS energy)
663
664
665 // COMMENTED OUT FOR NOW SINCE MOST OF THE ANIMATION DEFINES DO NOT MATCH
666
667 // If we are not in turn-based combat...
668
669
670 if ( pSold->uiStatusFlags & SOLDIER_VEHICLE )
671 {
672 return;
673 }
674
675 if (!(gTacticalStatus.uiFlags & INCOMBAT))
676 {
677 // ALRIGHT, GIVE A FULL AMOUNT BACK, UNLES MODIFIED BY WHAT ACTIONS WE WERE DOING
678 sBreathPerAP = GetBreathPerAP( pSold, pSold->usAnimState );
679
680 // adjust for carried weight
681 sBreathPerAP = sBreathPerAP * 100 / BreathPointAdjustmentForCarriedWeight( pSold );
682
683 // If this value is -ve, we have a gain, else we have a loos which we should not really do
684 // We just want to limit this to no gain if we were doing stuff...
685 sBreathChange = 3 * sBreathPerAP;
686
687 // Adjust for on drugs
688 HandleBPEffectDueToDrugs( pSold, &sBreathChange );
689
690 if ( sBreathChange > 0 )
691 {
692 sBreathChange = 0;
693 }
694 else
695 {
696 // We have a gain, now limit this depending on what we were doing...
697 // OK for RT, look at how many tiles we have moved, our last move anim
698 if ( pSold->ubTilesMovedPerRTBreathUpdate > 0 )
699 {
700 // How long have we done this for?
701 // And what anim were we doing?
702 sBreathPerAP = GetBreathPerAP( pSold, pSold->usLastMovementAnimPerRTBreathUpdate );
703
704 sRTBreathMod = sBreathPerAP * pSold->ubTilesMovedPerRTBreathUpdate;
705
706 // Deduct some if we were exerting ourselves
707 // We add here because to gain breath, sBreathChange needs to be -ve
708 if ( sRTBreathMod > 0 )
709 {
710 sBreathChange += sRTBreathMod;
711 }
712
713 if ( sBreathChange < 0 )
714 {
715 sBreathChange = 0;
716 }
717 }
718 }
719
720 // Divide by a number to adjust that in realtimer we do not want to recover as
721 // as fast as the TB values do
722 sBreathChange *= TB_BREATH_RECOVER_MODIFIER;
723
724
725 // adjust breath only, don't touch action points!
726 DeductPoints(pSold,0,(INT16)sBreathChange );
727
728 // Reset value for RT breath update
729 pSold->ubTilesMovedPerRTBreathUpdate = 0;
730
731 }
732 else
733 {
734 // if merc has any APs left unused this turn (that aren't carrying over)
735 if (pSold->bActionPoints > MAX_AP_CARRIED)
736 {
737
738 sUnusedAPs = pSold->bActionPoints - MAX_AP_CARRIED;
739
740 sBreathPerAP = GetBreathPerAP( pSold, pSold->usAnimState );
741
742 if (sBreathPerAP < 0)
743 {
744 // can't gain any breath when we've just been gassed, OR
745 // if standing in tear gas without a gas mask on
746 if ( pSold->uiStatusFlags & SOLDIER_GASSED )
747 {
748 return; // can't breathe here, so get no breath back!
749 }
750 }
751
752 // adjust for carried weight
753 sBreathPerAP = sBreathPerAP * 100 / BreathPointAdjustmentForCarriedWeight( pSold );
754
755 sBreathChange = sUnusedAPs * sBreathPerAP;
756 }
757 else
758 {
759 sBreathChange = 0;
760 }
761 // Adjust for on drugs
762 HandleBPEffectDueToDrugs( pSold, &sBreathChange );
763
764 // adjust breath only, don't touch action points!
765 DeductPoints(pSold,0,(INT16)sBreathChange );
766
767 }
768 }
769
770
GetBreathPerAP(SOLDIERTYPE * pSoldier,UINT16 usAnimState)771 static INT16 GetBreathPerAP(SOLDIERTYPE* pSoldier, UINT16 usAnimState)
772 {
773 INT16 sBreathPerAP = 0;
774 BOOLEAN fAnimTypeFound = FALSE;
775
776 if ( gAnimControl[ usAnimState ].uiFlags & ANIM_VARIABLE_EFFORT )
777 {
778 // Default effort
779 sBreathPerAP = BP_PER_AP_MIN_EFFORT;
780
781 // OK, check if we are in water and are waling/standing
782 if ( MercInWater( pSoldier ) )
783 {
784 switch( usAnimState )
785 {
786 case STANDING:
787
788 sBreathPerAP = BP_PER_AP_LT_EFFORT;
789 break;
790
791 case WALKING:
792
793 sBreathPerAP = BP_PER_AP_MOD_EFFORT;
794 break;
795 }
796 }
797 else
798 {
799
800 switch( usAnimState )
801 {
802 case STANDING:
803
804 sBreathPerAP = BP_PER_AP_NO_EFFORT;
805 break;
806
807 case WALKING:
808
809 sBreathPerAP = BP_PER_AP_LT_EFFORT;
810 break;
811 }
812 }
813 fAnimTypeFound = TRUE;
814 }
815
816 if ( gAnimControl[ usAnimState ].uiFlags & ANIM_NO_EFFORT )
817 {
818 sBreathPerAP = BP_PER_AP_NO_EFFORT;
819 fAnimTypeFound = TRUE;
820 }
821
822 if ( gAnimControl[ usAnimState ].uiFlags & ANIM_MIN_EFFORT )
823 {
824 sBreathPerAP = BP_PER_AP_MIN_EFFORT;
825 fAnimTypeFound = TRUE;
826 }
827
828 if ( gAnimControl[ usAnimState ].uiFlags & ANIM_LIGHT_EFFORT )
829 {
830 sBreathPerAP = BP_PER_AP_LT_EFFORT;
831 fAnimTypeFound = TRUE;
832 }
833
834 if ( gAnimControl[ usAnimState ].uiFlags & ANIM_MODERATE_EFFORT )
835 {
836 sBreathPerAP = BP_PER_AP_MOD_EFFORT;
837 fAnimTypeFound = TRUE;
838 }
839
840 if ( !fAnimTypeFound )
841 {
842 SLOGD("Unknown end-of-turn breath anim: %s",
843 gAnimControl[usAnimState].zAnimStr);
844 }
845
846 return( sBreathPerAP );
847 }
848
849
CalcAPsToBurst(INT8 const bBaseActionPoints,OBJECTTYPE const & o)850 UINT8 CalcAPsToBurst(INT8 const bBaseActionPoints, OBJECTTYPE const& o)
851 {
852 // base APs is what you'd get from CalcActionPoints();
853 if (o.usItem == G11)
854 {
855 return( 1 );
856 }
857 else
858 {
859 // NB round UP, so 21-25 APs pay full
860 INT8 const bAttachPos = FindAttachment(&o, SPRING_AND_BOLT_UPGRADE );
861 if ( bAttachPos != -1 )
862 {
863 return (__max(3, (AP_BURST * bBaseActionPoints + (AP_MAXIMUM - 1)) / AP_MAXIMUM) * 100) / (100 + o.bAttachStatus[bAttachPos] / 5);
864 }
865 else
866 {
867 return __max(3, (AP_BURST * bBaseActionPoints + (AP_MAXIMUM - 1)) / AP_MAXIMUM);
868 }
869 }
870 }
871
872
CalcTotalAPsToAttack(SOLDIERTYPE * const s,INT16 const grid_no,UINT8 const ubAddTurningCost,INT8 const aim_time)873 UINT8 CalcTotalAPsToAttack(SOLDIERTYPE* const s, INT16 const grid_no, UINT8 const ubAddTurningCost, INT8 const aim_time)
874 {
875 UINT16 ap_cost = 0;
876 OBJECTTYPE const& in_hand = s->inv[HANDPOS];
877 switch (GCM->getItem(in_hand.usItem)->getItemClass())
878 {
879 case IC_GUN:
880 case IC_LAUNCHER:
881 case IC_TENTACLES:
882 case IC_THROWING_KNIFE:
883 ap_cost = MinAPsToAttack(s, grid_no, ubAddTurningCost);
884 ap_cost +=
885 s->bDoBurst ? CalcAPsToBurst(CalcActionPoints(s), in_hand) :
886 aim_time;
887 break;
888
889 case IC_GRENADE:
890 case IC_BOMB:
891 #if 0 // XXX always was this way
892 ap_cost = MinAPsToAttack(s, grid_no, ubAddTurningCost);
893 #else
894 ap_cost = 5;
895 #endif
896 break;
897
898 case IC_PUNCH:
899 case IC_BLADE:
900 // If we are at this gridno, calc min APs but if not, calc cost to goto this
901 // location
902 INT16 adjusted_grid_no;
903 if (s->sGridNo == grid_no)
904 {
905 adjusted_grid_no = grid_no;
906 }
907 else
908 {
909 if (s->sWalkToAttackGridNo == grid_no)
910 {
911 // In order to avoid path calculations here all the time, save and check
912 // if it's changed!
913 adjusted_grid_no = grid_no;
914 }
915 else
916 {
917 GridNo got_location = NOWHERE;
918
919 if (SOLDIERTYPE const* const tgt = WhoIsThere2(grid_no, s->bLevel))
920 {
921 if (s->ubBodyType == BLOODCAT)
922 {
923 got_location = FindNextToAdjacentGridEx(s, grid_no, 0, &adjusted_grid_no, TRUE, FALSE);
924 if (got_location == -1) got_location = NOWHERE;
925 }
926 else
927 {
928 got_location = FindAdjacentPunchTarget(s, tgt, &adjusted_grid_no);
929 }
930 }
931
932 bool got_adjacent = false;
933 if (got_location == NOWHERE && s->ubBodyType != BLOODCAT)
934 {
935 got_location = FindAdjacentGridEx(s, grid_no, 0, &adjusted_grid_no, TRUE, FALSE);
936 if (got_location == -1) got_location = NOWHERE;
937 got_adjacent = true;
938 }
939
940 if (got_location == NOWHERE) return 0;
941
942 if (s->sGridNo == got_location || !got_adjacent)
943 {
944 s->sWalkToAttackWalkToCost = 0;
945 }
946 else
947 {
948 // Save for next time
949 s->sWalkToAttackWalkToCost = PlotPath(s, got_location, NO_COPYROUTE, NO_PLOT, s->usUIMovementMode, s->bActionPoints);
950 if (s->sWalkToAttackWalkToCost == 0) return 99;
951 }
952
953 // Save old location!
954 s->sWalkToAttackGridNo = grid_no;
955 }
956 ap_cost += s->sWalkToAttackWalkToCost;
957 }
958 ap_cost += MinAPsToAttack(s, adjusted_grid_no, ubAddTurningCost);
959 ap_cost += aim_time;
960 break;
961 }
962
963 return ap_cost;
964 }
965
966
967 static UINT8 MinAPsToPunch(SOLDIERTYPE const&, GridNo, bool add_turning_cost);
968
969
MinAPsToAttack(SOLDIERTYPE * const s,GridNo const grid_no,UINT8 const add_turning_cost)970 UINT8 MinAPsToAttack(SOLDIERTYPE* const s, GridNo const grid_no, UINT8 const add_turning_cost)
971 {
972 OBJECTTYPE const& in_hand = s->inv[HANDPOS];
973 UINT16 item = in_hand.usItem;
974 if (s->bWeaponMode == WM_ATTACHED)
975 { // Look for an attached grenade launcher
976 INT8 const attach_slot = FindAttachment(&in_hand, UNDER_GLAUNCHER);
977 if (attach_slot != NO_SLOT) item = UNDER_GLAUNCHER;
978 }
979
980 switch (GCM->getItem(item)->getItemClass())
981 {
982 case IC_BLADE:
983 case IC_GUN:
984 case IC_LAUNCHER:
985 case IC_TENTACLES:
986 case IC_THROWING_KNIFE: return MinAPsToShootOrStab(*s, grid_no, add_turning_cost);
987 case IC_GRENADE:
988 case IC_THROWN: return MinAPsToThrow(*s, grid_no, add_turning_cost);
989 case IC_PUNCH: return MinAPsToPunch(*s, grid_no, add_turning_cost);
990 default: return 0;
991 }
992 }
993
994
CalcAimSkill(SOLDIERTYPE const & s,UINT16 const weapon)995 static INT8 CalcAimSkill(SOLDIERTYPE const& s, UINT16 const weapon)
996 {
997 switch (GCM->getItem(weapon)->getItemClass())
998 {
999 case IC_GUN:
1000 case IC_LAUNCHER:
1001 // Guns: Modify aiming cost by shooter's marksmanship
1002 return EffectiveMarksmanship(&s);
1003
1004 default:
1005 // Knives: Modify aiming cost by avg of attacker's dexterity & agility
1006 return (EffectiveDexterity(&s) + EffectiveAgility(&s)) / 2;
1007 }
1008 }
1009
1010
BaseAPsToShootOrStab(INT8 const bAPs,INT8 const bAimSkill,OBJECTTYPE const & o)1011 UINT8 BaseAPsToShootOrStab(INT8 const bAPs, INT8 const bAimSkill, OBJECTTYPE const& o)
1012 {
1013 INT16 sTop, sBottom;
1014
1015 // Calculate default top & bottom of the magic "aiming" formula!
1016
1017 // get this man's maximum possible action points (ignoring carryovers)
1018 // the 2 times is here only to allow rounding off using integer math later
1019 sTop = 2 * bAPs;//CalcActionPoints( pSoldier );
1020
1021 // Shots per turn rating is for max. aimSkill(100), drops down to 1/2 at = 0
1022 // DIVIDE BY 4 AT THE END HERE BECAUSE THE SHOTS PER TURN IS NOW QUADRUPLED!
1023 // NB need to define shots per turn for ALL Weapons then.
1024 sBottom = ( ( 50 + (bAimSkill / 2) ) * GCM->getWeapon(o.usItem )->ubShotsPer4Turns ) / 4;
1025
1026 INT8 const bAttachPos = FindAttachment(&o, SPRING_AND_BOLT_UPGRADE);
1027 if ( bAttachPos != -1 )
1028 {
1029 sBottom = sBottom * (100 + o.bAttachStatus[bAttachPos] / 5) / 100;
1030 }
1031
1032 // add minimum aiming time to the overall minimum AP_cost
1033 // This here ROUNDS UP fractions of 0.5 or higher using integer math
1034 // This works because 'top' is 2x what it really should be throughout
1035 return( ( ( ( 100 * sTop ) / sBottom ) + 1) / 2);
1036 }
1037
1038
GetAPChargeForShootOrStabWRTGunRaises(SOLDIERTYPE const * const s,GridNo grid_no,UINT8 const ubAddTurningCost,BOOLEAN * const charge_turning,BOOLEAN * const charge_raise)1039 void GetAPChargeForShootOrStabWRTGunRaises(SOLDIERTYPE const* const s, GridNo grid_no, UINT8 const ubAddTurningCost, BOOLEAN* const charge_turning, BOOLEAN* const charge_raise)
1040 {
1041 bool adding_turning_cost = FALSE;
1042 if (ubAddTurningCost)
1043 {
1044 if (grid_no != NOWHERE)
1045 {
1046 // Get direction and see if we need to turn
1047 // Given a gridno here, check if we are on a guy - if so - get his gridno
1048 SOLDIERTYPE const* const tgt = FindSoldier(grid_no, FIND_SOLDIER_GRIDNO);
1049 if (tgt) grid_no = tgt->sGridNo;
1050
1051 // Is it the same as he's facing?
1052 UINT8 const direction = GetDirectionFromGridNo(grid_no, s);
1053 adding_turning_cost = direction != s->bDirection;
1054 }
1055 else
1056 {
1057 // Assume we need to add cost!
1058 adding_turning_cost = true;
1059 }
1060 }
1061 *charge_turning = adding_turning_cost;
1062
1063 // Do we need to ready weapon?
1064 *charge_raise = GCM->getItem(s->inv[HANDPOS].usItem)->getItemClass() != IC_THROWING_KNIFE &&
1065 !(gAnimControl[s->usAnimState].uiFlags & (ANIM_FIREREADY | ANIM_FIRE));
1066 }
1067
1068
MinAPsToShootOrStab(SOLDIERTYPE & s,GridNo gridno,bool const add_turning_cost)1069 UINT8 MinAPsToShootOrStab(SOLDIERTYPE& s, GridNo gridno, bool const add_turning_cost)
1070 {
1071 OBJECTTYPE const& in_hand = s.inv[HANDPOS];
1072 UINT16 const item =
1073 s.bWeaponMode == WM_ATTACHED ? UNDER_GLAUNCHER : in_hand.usItem;
1074
1075 BOOLEAN adding_turning_cost;
1076 BOOLEAN adding_raise_gun_cost;
1077 GetAPChargeForShootOrStabWRTGunRaises(&s, gridno, add_turning_cost, &adding_turning_cost, &adding_raise_gun_cost);
1078
1079 UINT8 ap_cost = AP_MIN_AIM_ATTACK;
1080
1081 if (GCM->getItem(item)->getItemClass() == IC_THROWING_KNIFE ||
1082 item == ROCKET_LAUNCHER)
1083 {
1084 // Do we need to stand up?
1085 ap_cost += GetAPsToChangeStance(&s, ANIM_STAND);
1086 }
1087
1088 // ATE: Look at stance
1089 if (gAnimControl[s.usAnimState].ubHeight == ANIM_STAND)
1090 {
1091 // Don't charge turning if gun-ready
1092 if (adding_raise_gun_cost) adding_turning_cost = FALSE;
1093 }
1094 else
1095 {
1096 // Just charge turning costs
1097 if (adding_turning_cost) adding_raise_gun_cost = FALSE;
1098 }
1099
1100 if (AM_A_ROBOT(&s)) adding_raise_gun_cost = FALSE;
1101
1102 if (adding_turning_cost)
1103 {
1104 if (GCM->getItem(item)->getItemClass() == IC_THROWING_KNIFE)
1105 {
1106 ap_cost += AP_LOOK_STANDING;
1107 }
1108 else
1109 {
1110 ap_cost += GetAPsToLook(&s);
1111 }
1112 }
1113
1114 if (adding_raise_gun_cost)
1115 {
1116 ap_cost += GetAPsToReadyWeapon(&s, s.usAnimState);
1117 s.fDontChargeReadyAPs = FALSE;
1118 }
1119
1120 if (gridno != NOWHERE)
1121 {
1122 // Given a gridno here, check if we are on a guy - if so - get his gridno
1123 SOLDIERTYPE const* const tgt = FindSoldier(gridno, FIND_SOLDIER_GRIDNO);
1124 if (tgt) gridno = tgt->sGridNo;
1125 }
1126
1127 // if attacking a new target (or if the specific target is uncertain)
1128 if (gridno != s.sLastTarget && item != ROCKET_LAUNCHER)
1129 {
1130 ap_cost += AP_CHANGE_TARGET;
1131 }
1132
1133 INT8 const full_aps = CalcActionPoints(&s);
1134 // Aim skill is the same whether we are using 1 or 2 guns
1135 INT8 const aim_skill = CalcAimSkill(s, item);
1136
1137 if (s.bWeaponMode == WM_ATTACHED)
1138 {
1139 // Create temporary grenade launcher and use that
1140 INT8 const attach_slot = FindAttachment(&in_hand, UNDER_GLAUNCHER);
1141 INT8 const status = attach_slot != NO_SLOT ? in_hand.bAttachStatus[attach_slot] :
1142 100; // Fake it, use a 100 status
1143 OBJECTTYPE grenade_launcher;
1144 CreateItem(UNDER_GLAUNCHER, status, &grenade_launcher);
1145
1146 ap_cost += BaseAPsToShootOrStab(full_aps, aim_skill, grenade_launcher);
1147 }
1148 else if (IsValidSecondHandShot(&s))
1149 {
1150 // Charge the maximum of the two
1151 UINT8 const ap_1st = BaseAPsToShootOrStab(full_aps, aim_skill, in_hand);
1152 UINT8 const ap_2nd = BaseAPsToShootOrStab(full_aps, aim_skill, s.inv[SECONDHANDPOS]);
1153 ap_cost += __max(ap_1st, ap_2nd);
1154 }
1155 else
1156 {
1157 ap_cost += BaseAPsToShootOrStab(full_aps, aim_skill, in_hand);
1158 }
1159
1160 // The minimum AP cost of ANY shot can NEVER be more than merc's maximum APs
1161 if (ap_cost > full_aps) ap_cost = full_aps;
1162
1163 // this SHOULD be impossible, but nevertheless
1164 if (ap_cost < 1) ap_cost = 1;
1165
1166 return ap_cost;
1167 }
1168
1169
MinAPsToPunch(SOLDIERTYPE const & s,GridNo gridno,bool const add_turning_cost)1170 static UINT8 MinAPsToPunch(SOLDIERTYPE const& s, GridNo gridno, bool const add_turning_cost)
1171 {
1172 UINT8 ap = 4;
1173
1174 // We return 0 for invalid attacks; will be handled as impossible
1175 if (gridno == NOWHERE)
1176 {
1177 return 0;
1178 }
1179
1180 if (SOLDIERTYPE const* const tgt = WhoIsThere2(gridno, s.bTargetLevel))
1181 { // On a guy, get his gridno
1182 gridno = tgt->sGridNo;
1183
1184 // Check if target is prone, if so, calc cost
1185 if (gAnimControl[tgt->usAnimState].ubEndHeight == ANIM_PRONE)
1186 {
1187 ap += GetAPsToChangeStance(&s, ANIM_CROUCH);
1188 }
1189 else if (s.sGridNo == gridno)
1190 {
1191 ap += GetAPsToChangeStance(&s, ANIM_STAND);
1192 }
1193 }
1194
1195 if (add_turning_cost && s.sGridNo == gridno)
1196 {
1197 // Is it the same as he's facing?
1198 UINT8 const direction = GetDirectionFromGridNo(gridno, &s);
1199 if (direction != s.bDirection) ap += AP_LOOK_STANDING; // ATE: Use standing turn cost
1200 }
1201
1202 return ap;
1203 }
1204
1205
MinPtsToMove(const SOLDIERTYPE * const pSoldier)1206 INT8 MinPtsToMove(const SOLDIERTYPE* const pSoldier)
1207 {
1208 // look around all 8 directions and return lowest terrain cost
1209 UINT8 cnt;
1210 INT16 sLowest=127;
1211 INT16 sGridno,sCost;
1212
1213 if ( TANK( pSoldier ) )
1214 {
1215 return( (INT8)sLowest);
1216 }
1217
1218 for (cnt=0; cnt <= 7; cnt++)
1219 {
1220 sGridno = NewGridNo(pSoldier->sGridNo,DirectionInc(cnt));
1221 if (sGridno != pSoldier->sGridNo)
1222 {
1223 if ( (sCost=ActionPointCost( pSoldier, sGridno, cnt , pSoldier->usUIMovementMode ) ) < sLowest )
1224 {
1225 sLowest = sCost;
1226 }
1227 }
1228 }
1229 return( (INT8)sLowest);
1230 }
1231
1232
PtsToMoveDirection(const SOLDIERTYPE * const pSoldier,const UINT8 bDirection)1233 INT8 PtsToMoveDirection(const SOLDIERTYPE* const pSoldier, const UINT8 bDirection)
1234 {
1235 INT16 sGridno,sCost;
1236 INT8 bOverTerrainType;
1237 UINT16 usMoveModeToUse;
1238
1239 sGridno = NewGridNo( pSoldier->sGridNo, DirectionInc( bDirection ) );
1240
1241 usMoveModeToUse = pSoldier->usUIMovementMode;
1242
1243 // ATE: Check if the new place is watter and we were tying to run....
1244 bOverTerrainType = GetTerrainType( sGridno );
1245
1246 if ( bOverTerrainType == MED_WATER || bOverTerrainType == DEEP_WATER || bOverTerrainType == LOW_WATER )
1247 {
1248 usMoveModeToUse = WALKING;
1249 }
1250
1251 sCost = ActionPointCost( pSoldier, sGridno, bDirection , usMoveModeToUse );
1252
1253 if ( gubWorldMovementCosts[ sGridno ][ bDirection ][ pSoldier->bLevel ] != TRAVELCOST_FENCE )
1254 {
1255 if ( usMoveModeToUse == RUNNING && pSoldier->usAnimState != RUNNING )
1256 {
1257 sCost += AP_START_RUN_COST;
1258 }
1259 }
1260
1261 return( (INT8)sCost );
1262 }
1263
1264
MinAPsToStartMovement(const SOLDIERTYPE * pSoldier,UINT16 usMovementMode)1265 INT8 MinAPsToStartMovement(const SOLDIERTYPE* pSoldier, UINT16 usMovementMode)
1266 {
1267 INT8 bAPs = 0;
1268
1269 switch( usMovementMode )
1270 {
1271 case RUNNING:
1272 case WALKING:
1273 if (gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE)
1274 {
1275 bAPs += AP_CROUCH + AP_PRONE;
1276 }
1277 else if (gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_CROUCH)
1278 {
1279 bAPs += AP_CROUCH;
1280 }
1281 break;
1282 case SWATTING:
1283 if (gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE)
1284 {
1285 bAPs += AP_PRONE;
1286 }
1287 else if (gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND)
1288 {
1289 bAPs += AP_CROUCH;
1290 }
1291 break;
1292 case CRAWLING:
1293 if (gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND)
1294 {
1295 bAPs += AP_CROUCH + AP_PRONE;
1296 }
1297 else if (gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_CROUCH)
1298 {
1299 bAPs += AP_CROUCH;
1300 }
1301 break;
1302 default:
1303 break;
1304 }
1305
1306 if (usMovementMode == RUNNING && pSoldier->usAnimState != RUNNING )
1307 {
1308 bAPs += AP_START_RUN_COST;
1309 }
1310 return( bAPs );
1311 }
1312
1313
EnoughAmmo(SOLDIERTYPE * const s,BOOLEAN const fDisplay,INT8 const inv_pos)1314 BOOLEAN EnoughAmmo(SOLDIERTYPE* const s, BOOLEAN const fDisplay, INT8 const inv_pos)
1315 {
1316 OBJECTTYPE const& o = s->inv[inv_pos];
1317 UINT16 const item_idx = o.usItem;
1318 if (item_idx == NOTHING) return FALSE;
1319
1320 if (s->bWeaponMode == WM_ATTACHED) return TRUE;
1321
1322 // hack... they turn empty afterwards anyways
1323 if (item_idx == ROCKET_LAUNCHER) return TRUE;
1324
1325 const ItemModel * item = GCM->getItem(item_idx);
1326 if (item->getItemClass() == IC_LAUNCHER || item_idx == TANK_CANNON)
1327 {
1328 if (FindAttachmentByClass(&o, IC_GRENADE) != ITEM_NOT_FOUND) return TRUE;
1329 if (FindAttachmentByClass(&o, IC_BOMB) != ITEM_NOT_FOUND) return TRUE;
1330 }
1331 else if (item->getItemClass() == IC_GUN)
1332 {
1333 if (o.ubGunShotsLeft != 0) return TRUE;
1334 }
1335 else
1336 {
1337 return TRUE;
1338 }
1339
1340 if (fDisplay) TacticalCharacterDialogue(s, QUOTE_OUT_OF_AMMO);
1341 return FALSE;
1342 }
1343
1344
DeductAmmo(SOLDIERTYPE * pSoldier,INT8 bInvPos)1345 void DeductAmmo( SOLDIERTYPE *pSoldier, INT8 bInvPos )
1346 {
1347 OBJECTTYPE *pObj;
1348
1349 // tanks never run out of MG ammo!
1350 // unlimited cannon ammo is handled in AI
1351 if ( TANK( pSoldier ) && pSoldier->inv[bInvPos].usItem != TANK_CANNON )
1352 {
1353 return;
1354 }
1355
1356 pObj = &(pSoldier->inv[ bInvPos ]);
1357 if ( pObj->usItem != NOTHING )
1358 {
1359 if ( pObj->usItem == TANK_CANNON )
1360 {
1361 }
1362 else if ( GCM->getItem(pObj->usItem)->getItemClass() == IC_GUN && pObj->usItem != TANK_CANNON )
1363 {
1364 if ( pSoldier->usAttackingWeapon == pObj->usItem)
1365 {
1366 // OK, let's see, don't overrun...
1367 if ( pObj->ubGunShotsLeft != 0 )
1368 {
1369 pObj->ubGunShotsLeft--;
1370 }
1371 }
1372 else
1373 {
1374 // firing an attachment?
1375 }
1376 }
1377 else if ( GCM->getItem(pObj->usItem)->getItemClass() == IC_LAUNCHER || pObj->usItem == TANK_CANNON )
1378 {
1379 INT8 bAttachPos;
1380
1381 bAttachPos = FindAttachmentByClass( pObj, IC_GRENADE );
1382 if (bAttachPos == ITEM_NOT_FOUND )
1383 {
1384 bAttachPos = FindAttachmentByClass( pObj, IC_BOMB );
1385 }
1386
1387 if (bAttachPos != ITEM_NOT_FOUND)
1388 {
1389 RemoveAttachment( pObj, bAttachPos, NULL );
1390 }
1391 }
1392
1393 // Dirty Bars
1394 DirtyMercPanelInterface( pSoldier, DIRTYLEVEL1 );
1395
1396 }
1397 }
1398
1399
GetMovePlusActionAPCosts(SOLDIERTYPE * const s,GridNo const pos,INT16 const action_ap)1400 static INT16 GetMovePlusActionAPCosts(SOLDIERTYPE* const s, GridNo const pos, INT16 const action_ap)
1401 {
1402 if (s->sGridNo == pos) return action_ap;
1403 INT16 const move_ap = PlotPath(s, pos, NO_COPYROUTE, NO_PLOT, s->usUIMovementMode, s->bActionPoints);
1404 if (move_ap == 0) return 0; // Destination unreachable
1405 return move_ap + action_ap;
1406 }
1407
1408
GetAPsToPickupItem(SOLDIERTYPE * const s,UINT16 const usMapPos)1409 UINT16 GetAPsToPickupItem(SOLDIERTYPE* const s, UINT16 const usMapPos)
1410 {
1411 // Check if we are over an item pool
1412 if (!GetItemPool(usMapPos, s->bLevel)) return 0;
1413 INT16 const sActionGridNo = AdjustGridNoForItemPlacement(s, usMapPos);
1414 return GetMovePlusActionAPCosts(s, sActionGridNo, AP_PICKUP_ITEM);
1415 }
1416
1417
GetAPsToGiveItem(SOLDIERTYPE * const s,UINT16 const usMapPos)1418 UINT16 GetAPsToGiveItem(SOLDIERTYPE* const s, UINT16 const usMapPos)
1419 {
1420 return GetMovePlusActionAPCosts(s, usMapPos, AP_GIVE_ITEM);
1421 }
1422
1423
GetAPsToReloadGunWithAmmo(OBJECTTYPE * pGun,OBJECTTYPE * pAmmo)1424 INT8 GetAPsToReloadGunWithAmmo( OBJECTTYPE * pGun, OBJECTTYPE * pAmmo )
1425 {
1426 if (GCM->getItem(pGun->usItem)->getItemClass() == IC_LAUNCHER)
1427 {
1428 // always standard AP cost
1429 return( AP_RELOAD_GUN );
1430 }
1431 if ( GCM->getWeapon(pGun->usItem)->isSameMagCapacity(GCM->getItem(pAmmo->usItem)->asAmmo()))
1432 {
1433 // normal situation
1434 return( AP_RELOAD_GUN );
1435 }
1436 else
1437 {
1438 // trying to reload with wrong size of magazine
1439 return( AP_RELOAD_GUN + AP_RELOAD_GUN );
1440 }
1441 }
1442
GetAPsToAutoReload(SOLDIERTYPE * pSoldier)1443 INT8 GetAPsToAutoReload( SOLDIERTYPE * pSoldier )
1444 {
1445 OBJECTTYPE *pObj;
1446 INT8 bSlot, bSlot2, bExcludeSlot;
1447 INT8 bAPCost = 0;
1448 INT8 bAPCost2 = 0;
1449
1450 CHECKF( pSoldier );
1451 pObj = &(pSoldier->inv[HANDPOS]);
1452
1453 if (GCM->getItem(pObj->usItem)->getItemClass() == IC_GUN || GCM->getItem(pObj->usItem)->getItemClass() == IC_LAUNCHER)
1454 {
1455 bSlot = FindAmmoToReload( pSoldier, HANDPOS, NO_SLOT );
1456 if (bSlot == NO_SLOT)
1457 {
1458 // we would not reload
1459 return( 0 );
1460 }
1461
1462 // we would reload using this ammo!
1463 bAPCost += GetAPsToReloadGunWithAmmo( pObj, &(pSoldier->inv[bSlot] ) );
1464 // if we are valid for two-pistol shooting (reloading) and we have enough APs still
1465 // then we would do a reload of both guns!
1466 if ( IsValidSecondHandShotForReloadingPurposes( pSoldier ) )
1467 {
1468 pObj = &(pSoldier->inv[SECONDHANDPOS]);
1469 bExcludeSlot = NO_SLOT;
1470 bSlot2 = NO_SLOT;
1471
1472 // if the ammo for the first gun is the same we have to do special checks
1473 if ( ValidAmmoType( pObj->usItem, pSoldier->inv[ bSlot ].usItem ) )
1474 {
1475 if ( pSoldier->inv[ bSlot ].ubNumberOfObjects == 1 )
1476 {
1477 // we must not consider this slot for reloading!
1478 bExcludeSlot = bSlot;
1479 }
1480 else
1481 {
1482 // we can reload the 2nd gun from the same pocket!
1483 bSlot2 = bSlot;
1484 }
1485 }
1486
1487 if (bSlot2 == NO_SLOT)
1488 {
1489 bSlot2 = FindAmmoToReload( pSoldier, SECONDHANDPOS, bExcludeSlot );
1490 }
1491
1492 if (bSlot2 != NO_SLOT)
1493 {
1494 // we would reload using this ammo!
1495 bAPCost2 = GetAPsToReloadGunWithAmmo( pObj, &(pSoldier->inv[bSlot2] ) );
1496 if ( EnoughPoints( pSoldier, (INT16) (bAPCost + bAPCost2), 0, FALSE ) )
1497 {
1498 // we can afford to reload both guns; otherwise display just for 1 gun
1499 bAPCost += bAPCost2;
1500 }
1501 }
1502
1503 }
1504
1505 }
1506
1507 return( bAPCost );
1508 }
1509
1510
GetAPsToReloadRobot(SOLDIERTYPE * const s,SOLDIERTYPE const * const robot)1511 UINT16 GetAPsToReloadRobot(SOLDIERTYPE* const s, SOLDIERTYPE const* const robot)
1512 {
1513 GridNo const sActionGridNo = FindAdjacentGridEx(s, robot->sGridNo, NULL, NULL, TRUE, FALSE);
1514 return GetMovePlusActionAPCosts(s, sActionGridNo, 4);
1515 }
1516
1517
GetAPsToChangeStance(const SOLDIERTYPE * pSoldier,INT8 bDesiredHeight)1518 UINT16 GetAPsToChangeStance(const SOLDIERTYPE* pSoldier, INT8 bDesiredHeight)
1519 {
1520 UINT16 sAPCost = 0;
1521 INT8 bCurrentHeight;
1522
1523 bCurrentHeight = gAnimControl[ pSoldier->usAnimState ].ubEndHeight;
1524
1525 if ( bCurrentHeight == bDesiredHeight )
1526 {
1527 sAPCost = 0;
1528 }
1529
1530 if ( bCurrentHeight == ANIM_STAND && bDesiredHeight == ANIM_PRONE )
1531 {
1532 sAPCost = AP_CROUCH + AP_PRONE;
1533 }
1534 if ( bCurrentHeight == ANIM_STAND && bDesiredHeight == ANIM_CROUCH )
1535 {
1536 sAPCost = AP_CROUCH;
1537 }
1538 if ( bCurrentHeight == ANIM_CROUCH && bDesiredHeight == ANIM_PRONE )
1539 {
1540 sAPCost = AP_PRONE;
1541 }
1542 if ( bCurrentHeight == ANIM_CROUCH && bDesiredHeight == ANIM_STAND )
1543 {
1544 sAPCost = AP_CROUCH;
1545 }
1546 if ( bCurrentHeight == ANIM_PRONE && bDesiredHeight == ANIM_STAND )
1547 {
1548 sAPCost = AP_PRONE + AP_CROUCH;
1549 }
1550 if ( bCurrentHeight == ANIM_PRONE && bDesiredHeight == ANIM_CROUCH )
1551 {
1552 sAPCost = AP_PRONE;
1553 }
1554
1555 return( sAPCost );
1556 }
1557
GetAPsToLook(const SOLDIERTYPE * pSoldier)1558 UINT16 GetAPsToLook(const SOLDIERTYPE* pSoldier)
1559 {
1560 // Set # of APs
1561 switch( gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
1562 {
1563 // Now change to appropriate animation
1564 case ANIM_STAND: return AP_LOOK_STANDING;
1565 case ANIM_CROUCH: return AP_LOOK_CROUCHED;
1566
1567 case ANIM_PRONE:
1568 // AP_PRONE is the AP cost to go to or from the prone stance. To turn while prone, your merc has to get up to
1569 // crouched, turn, and then go back down. Hence you go up (AP_PRONE), turn (AP_LOOK_PRONE) and down (AP_PRONE).
1570 return( AP_LOOK_PRONE + AP_PRONE + AP_PRONE );
1571
1572 // no other values should be possible
1573 default:
1574 Assert( FALSE );
1575 return(0);
1576 }
1577 }
1578
1579
CheckForMercContMove(SOLDIERTYPE * const s)1580 BOOLEAN CheckForMercContMove(SOLDIERTYPE* const s)
1581 {
1582 if (!(gTacticalStatus.uiFlags & INCOMBAT)) return FALSE;
1583
1584 if (gpItemPointer != NULL) return FALSE;
1585
1586 if (s->bLife < OKLIFE) return FALSE;
1587
1588 if (s->sGridNo == s->sFinalDestination && !s->bGoodContPath) return FALSE;
1589
1590 if (s != GetSelectedMan()) return FALSE;
1591
1592 if (!SoldierOnScreen(s)) return FALSE;
1593
1594 const INT16 sGridNo = (s->bGoodContPath ? s->sContPathLocation : s->sFinalDestination);
1595 if (!FindBestPath(s, sGridNo, s->bLevel, s->usUIMovementMode, NO_COPYROUTE, 0)) return FALSE;
1596
1597 const INT16 sAPCost = PtsToMoveDirection(s, guiPathingData[0]);
1598 if (!EnoughPoints(s, sAPCost, 0, FALSE)) return FALSE;
1599
1600 return TRUE;
1601 }
1602
1603
GetAPsToReadyWeapon(const SOLDIERTYPE * const pSoldier,const UINT16 usAnimState)1604 INT16 GetAPsToReadyWeapon(const SOLDIERTYPE* const pSoldier, const UINT16 usAnimState)
1605 {
1606 UINT16 usItem;
1607
1608 // If this is a dwel pistol anim
1609 // ATE: What was I thinking, hooking into animations like this....
1610 //if ( usAnimState == READY_DUAL_STAND || usAnimState == READY_DUAL_CROUCH )
1611 //{
1612 //return( AP_READY_DUAL );
1613 //}
1614 if ( IsValidSecondHandShot( pSoldier ) )
1615 {
1616 return( AP_READY_DUAL );
1617 }
1618
1619
1620 // OK, now check type of weapon
1621 usItem = pSoldier->inv[ HANDPOS ].usItem;
1622
1623 if ( usItem == NOTHING )
1624 {
1625 return( 0 );
1626 }
1627 else
1628 {
1629 // CHECK FOR RIFLE
1630 if ( GCM->getItem(usItem)->getItemClass() == IC_GUN )
1631 {
1632 return( GCM->getWeapon( usItem )->ubReadyTime );
1633 }
1634 }
1635
1636 return( 0 );
1637 }
1638
1639
GetAPsToClimbRoof(SOLDIERTYPE * pSoldier,BOOLEAN fClimbDown)1640 INT8 GetAPsToClimbRoof( SOLDIERTYPE *pSoldier, BOOLEAN fClimbDown )
1641 {
1642 if ( !fClimbDown )
1643 {
1644 // OK, add aps to goto stand stance...
1645 return( (INT8)( AP_CLIMBROOF + GetAPsToChangeStance( pSoldier, ANIM_STAND ) ) );
1646 }
1647 else
1648 {
1649 // Add aps to goto crouch
1650 return( (INT8)( AP_CLIMBOFFROOF + GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) ) );
1651 }
1652 }
1653
GetAPsToCutFence(SOLDIERTYPE * pSoldier)1654 INT8 GetAPsToCutFence( SOLDIERTYPE *pSoldier )
1655 {
1656 // OK, it's normally just cost, but add some if different stance...
1657 return( GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) + AP_USEWIRECUTTERS );
1658 }
1659
GetAPsToBeginFirstAid(SOLDIERTYPE * pSoldier)1660 INT8 GetAPsToBeginFirstAid( SOLDIERTYPE *pSoldier )
1661 {
1662 // OK, it's normally just cost, but add some if different stance...
1663 return( GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) + AP_START_FIRST_AID );
1664 }
1665
GetAPsToBeginRepair(SOLDIERTYPE * pSoldier)1666 INT8 GetAPsToBeginRepair( SOLDIERTYPE *pSoldier )
1667 {
1668 // OK, it's normally just cost, but add some if different stance...
1669 return( GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) + AP_START_REPAIR );
1670 }
1671
GetAPsToRefuelVehicle(SOLDIERTYPE * pSoldier)1672 INT8 GetAPsToRefuelVehicle( SOLDIERTYPE *pSoldier )
1673 {
1674 // OK, it's normally just cost, but add some if different stance...
1675 return( GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) + AP_REFUEL_VEHICLE );
1676 }
1677
1678
1679 #define TOSSES_PER_10TURNS 18 // max # of grenades tossable in 10 turns
1680 #define AP_MIN_AIM_ATTACK 0 // minimum permitted extra aiming
1681 #define AP_MAX_AIM_ATTACK 4 // maximum permitted extra aiming
1682
1683
MinAPsToThrow(SOLDIERTYPE const & s,GridNo gridno,bool const add_turning_cost)1684 INT16 MinAPsToThrow(SOLDIERTYPE const& s, GridNo gridno, bool const add_turning_cost)
1685 {
1686 INT32 ap = AP_MIN_AIM_ATTACK;
1687
1688 // Make sure the guy's actually got a throwable item in his hand
1689 UINT16 const in_hand = s.inv[HANDPOS].usItem;
1690 const ItemModel *item = GCM->getItem(in_hand);
1691 if (!item)
1692 {
1693 SLOGW("MinAPsToThrow - in-hand item is missing");
1694 }
1695 else if (!(item->getItemClass() & (IC_GRENADE | IC_THROWN))) // match MinAPsToAttack
1696 {
1697 SLOGW("MinAPsToThrow - in-hand item '%s' has unexpected item class 0x%x", item->getInternalName().c_str(), item->getItemClass());
1698 }
1699
1700 if (gridno != NOWHERE)
1701 {
1702 SOLDIERTYPE const* const tgt = FindSoldier(gridno, FIND_SOLDIER_GRIDNO);
1703 if (tgt) gridno = tgt->sGridNo; // On a guy, get his gridno
1704 }
1705
1706 // if attacking a new target (or if the specific target is uncertain)
1707 if (gridno != s.sLastTarget) ap += AP_CHANGE_TARGET;
1708
1709 ap += GetAPsToChangeStance(&s, ANIM_STAND);
1710
1711 // Calculate default top & bottom of the magic "aiming" formula)
1712
1713 // Get this man's maximum possible action points (ignoring carryovers)
1714 INT32 const full_ap = CalcActionPoints(&s);
1715
1716 // The 2 times is here only to around rounding off using integer math later
1717 INT32 const top = 2 * full_ap;
1718
1719 // Tosses per turn is for max dexterity, drops down to 1/2 at dexterity = 0
1720 INT32 const bottom = TOSSES_PER_10TURNS * (50 + s.bDexterity / 2) / 10;
1721
1722 // Add minimum aiming time to the overall minimum AP_cost
1723 // This here ROUNDS UP fractions of 0.5 or higher using integer math
1724 // This works because 'top' is 2x what it really should be throughout
1725 ap += (100 * top / bottom + 1) / 2;
1726
1727 // The minimum AP cost of ANY throw can NEVER be more than merc has APs!
1728 if (ap > full_ap) ap = full_ap;
1729
1730 // This SHOULD be impossible, but nevertheless
1731 if (ap < 1) ap = 1;
1732
1733 return ap;
1734 }
1735
1736
GetAPsToDropBomb(SOLDIERTYPE * pSoldier)1737 UINT16 GetAPsToDropBomb( SOLDIERTYPE *pSoldier )
1738 {
1739 return( AP_DROP_BOMB );
1740 }
1741
1742
GetTotalAPsToDropBomb(SOLDIERTYPE * const s,INT16 const sGridNo)1743 UINT16 GetTotalAPsToDropBomb(SOLDIERTYPE* const s, INT16 const sGridNo)
1744 {
1745 return GetMovePlusActionAPCosts(s, sGridNo, AP_DROP_BOMB);
1746 }
1747
1748
1749
GetAPsToUseRemote(SOLDIERTYPE * pSoldier)1750 UINT16 GetAPsToUseRemote( SOLDIERTYPE *pSoldier )
1751 {
1752 return( AP_USE_REMOTE );
1753 }
1754
1755
GetAPsToStealItem(SOLDIERTYPE * pSoldier,INT16 usMapPos)1756 INT8 GetAPsToStealItem( SOLDIERTYPE *pSoldier, INT16 usMapPos )
1757 {
1758 UINT16 sAPCost = PlotPath(pSoldier, usMapPos, NO_COPYROUTE, NO_PLOT, pSoldier->usUIMovementMode, pSoldier->bActionPoints);
1759
1760 // ADD APS TO PICKUP
1761 sAPCost += AP_STEAL_ITEM;
1762
1763 // CJC August 13 2002: added cost to stand into equation
1764 if (!(PTR_STANDING))
1765 {
1766 sAPCost += GetAPsToChangeStance( pSoldier, ANIM_STAND );
1767 }
1768
1769 return( (INT8)sAPCost );
1770
1771 }
1772
GetAPsToUseJar(SOLDIERTYPE * pSoldier,INT16 usMapPos)1773 INT8 GetAPsToUseJar( SOLDIERTYPE *pSoldier, INT16 usMapPos )
1774 {
1775 UINT16 sAPCost = PlotPath(pSoldier, usMapPos, NO_COPYROUTE, NO_PLOT, pSoldier->usUIMovementMode, pSoldier->bActionPoints);
1776
1777 // If point cost is zero, return 0
1778 if ( sAPCost != 0 )
1779 {
1780 // ADD APS TO PICKUP
1781 sAPCost += AP_TAKE_BLOOD;
1782 }
1783
1784 return( (INT8)sAPCost );
1785
1786 }
1787
GetAPsToJumpOver(const SOLDIERTYPE * pSoldier)1788 INT8 GetAPsToJumpOver(const SOLDIERTYPE* pSoldier)
1789 {
1790 return( GetAPsToChangeStance( pSoldier, ANIM_STAND ) + AP_JUMP_OVER );
1791 }
1792
GetAPsToJumpFence(const SOLDIERTYPE * pSoldier)1793 INT8 GetAPsToJumpFence(const SOLDIERTYPE* pSoldier)
1794 {
1795 return( GetAPsToChangeStance( pSoldier, ANIM_STAND ) + AP_JUMPFENCE );
1796 }
1797