Files
source/Tactical/Points.cpp
T
Sevenfm 904fae7a96 CancelDrag, StartDrag: update interface panel to change drag icon status.
DrawTraitRadius uses usColour.
Allow to start dragging from any stance, soldier will crouch automatically.
Show draggable objects when drag skill menu is active.
Press [\] key to start dragging object/solder/corpse in front of merc.

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@9369 3b4a5df2-a311-0410-b5c6-a8a6f20db521
2022-04-24 18:21:44 +00:00

4686 lines
168 KiB
C++

#ifdef PRECOMPILEDHEADERS
#include "Tactical All.h"
#else
#include "sgp.h"
#include "worlddef.h"
#include "points.h"
#include "overhead.h"
#include "Font control.h"
#include "interface.h"
#include "Isometric utils.h"
#include "pathai.h"
#include "interface.h"
#include "message.h"
#include "Animation Control.h"
#include "Weapons.h"
#include "structure wrap.h"
#include "dialogue control.h"
#include "items.h"
#include "rt time defines.h"
#include "ai.h"
#include "handle ui.h"
#include "text.h"
#include "SkillCheck.h"
#include "wcheck.h"
#include "Soldier Profile.h"
#include "Soldier macros.h"
#include "Random.h"
#include "Campaign.h"
#include "drugs and alcohol.h"
#include "GameSettings.h"
#include "worldman.h"
#include "math.h"
#include "Map Information.h"
#include "Interface Items.h"
#include "Soldier Control.h" // added by SANDRO
#include "opplist.h" // added by SANDRO
#include "lighting.h" // added by SANDRO
#include "Food.h" // added by Flugente
#include "AIInternals.h" //dnl ch69 150913
#include "CampaignStats.h" // added by Flugente
#endif
#include "connect.h"
#include "GameInitOptionsScreen.h"
//rain
//#define BREATH_GAIN_REDUCTION_PER_RAIN_INTENSITY 25
//end rain
extern BOOLEAN IsValidSecondHandShot( SOLDIERTYPE *pSoldier );
extern UINT16 PickSoldierReadyAnimation( SOLDIERTYPE *pSoldier, BOOLEAN fEndReady, BOOLEAN fHipStance );
INT16 GetBreathPerAP( SOLDIERTYPE *pSoldier, UINT16 usAnimState );
INT16 TerrainActionPoints( SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bDir, INT8 bLevel )
{
// SANDRO - Note: this procedure was changed a bit
INT16 sAPCost = 0;
INT16 sSwitchValue;
BOOLEAN fHiddenStructVisible; // Used for hidden struct visiblity
// SANDRO - removed AP cost of moving stealthy or reversed outside this procedure
//if (GridCost[gridno] == NPCMINECOST)
// switchValue = BackupGridCost[gridno];
//else
sSwitchValue = gubWorldMovementCosts[sGridNo][bDir][ bLevel ];
// Check reality vs what the player knows....
if ( pSoldier->bTeam == gbPlayerNum )
{
// Is this obstcale a hidden tile that has not been revealed yet?
if( DoesGridNoContainHiddenStruct( sGridNo, &fHiddenStructVisible ) )
{
// Are we not visible, if so use terrain costs!
if ( !fHiddenStructVisible )
{
// Set cost of terrain!
sSwitchValue = gTileTypeMovementCost[ gpWorldLevelData[ sGridNo ].ubTerrainID ];
}
}
}
//ddd window
if ( sSwitchValue == TRAVELCOST_JUMPABLEWINDOW
|| sSwitchValue == TRAVELCOST_JUMPABLEWINDOW_N
|| sSwitchValue == TRAVELCOST_JUMPABLEWINDOW_W)
{
if (bDir & 1)
return -1;
}
//ddd if we don't check this condition, then when we approach to the tile with a window, when the distance to that tile will be 1
//we'll get nice message, that the way is blocked
if ( sSwitchValue == TRAVELCOST_NOT_STANDING)// || sSwitchValue == TRAVELCOST_JUMPABLEWINDOW)
{
// use the cost of the terrain!
sSwitchValue = gTileTypeMovementCost[ gpWorldLevelData[ sGridNo ].ubTerrainID ];
}
else if (IS_TRAVELCOST_DOOR( sSwitchValue ))
{
// Can't travel diagonally through a door
if (bDir & 1)
{
return -1;
}
sSwitchValue = DoorTravelCost( pSoldier, sGridNo, (UINT8) sSwitchValue, (BOOLEAN) (pSoldier->bTeam == gbPlayerNum), NULL );
}
else if (gfPlotPathToExitGrid && sSwitchValue == TRAVELCOST_EXITGRID)
{
sSwitchValue = gTileTypeMovementCost[ gpWorldLevelData[ sGridNo ].ubTerrainID ];
}
if (sSwitchValue >= TRAVELCOST_BLOCKED && sSwitchValue != TRAVELCOST_DOOR )
{
return(-1); // Cost too much to be considered!
}
//CHRISL: We can't jump a fence while wearing a backpack, to consider fences as impassible
// SANDRO - Headrocks change to backpack check implemented
if(sSwitchValue == TRAVELCOST_FENCE && UsingNewInventorySystem() == true && pSoldier->inv[BPACKPOCKPOS].exists() == true
&& ((gGameExternalOptions.sBackpackWeightToClimb == -1) || (INT16)pSoldier->inv[BPACKPOCKPOS].GetWeightOfObjectInStack() + Item[pSoldier->inv[BPACKPOCKPOS].usItem].sBackpackWeightModifier > gGameExternalOptions.sBackpackWeightToClimb)
&& ((gGameExternalOptions.fUseGlobalBackpackSettings == TRUE) || (Item[pSoldier->inv[BPACKPOCKPOS].usItem].fAllowClimbing == FALSE)))
{
return(-1);
}
switch( sSwitchValue )
{
case TRAVELCOST_DIRTROAD :
case TRAVELCOST_FLAT : sAPCost += APBPConstants[AP_MOVEMENT_FLAT];
break;
//case TRAVELCOST_BUMPY :
case TRAVELCOST_GRASS : sAPCost += APBPConstants[AP_MOVEMENT_GRASS];
break;
case TRAVELCOST_THICK : sAPCost += APBPConstants[AP_MOVEMENT_BUSH];
break;
case TRAVELCOST_DEBRIS : sAPCost += APBPConstants[AP_MOVEMENT_RUBBLE];
break;
case TRAVELCOST_SHORE : sAPCost += APBPConstants[AP_MOVEMENT_SHORE]; // wading shallow water
if ( !IS_MERC_BODY_TYPE( pSoldier ) )
{
return -1;
}
break;
case TRAVELCOST_KNEEDEEP : sAPCost += APBPConstants[AP_MOVEMENT_LAKE]; // wading waist/chest deep - very slow
if ( !IS_MERC_BODY_TYPE( pSoldier ) )
{
return -1;
}
break;
case TRAVELCOST_DEEPWATER: sAPCost += APBPConstants[AP_MOVEMENT_OCEAN]; // can swim, so it's faster than wading
if ( !IS_MERC_BODY_TYPE( pSoldier ) )
{
return -1;
}
break;
/*
case TRAVELCOST_VEINEND :
case TRAVELCOST_VEINMID : sAPCost += APBPConstants[AP_MOVEMENT_FLAT];
break;
*/
// SANDRO - opening door cost will be added after Athletics check
case TRAVELCOST_DOOR: sAPCost += APBPConstants[AP_MOVEMENT_FLAT];
if (pSoldier->bTeam == gbPlayerNum)
{
return -1;
}
//sAPCost += APBPConstants[AP_MOVEMENT_FLAT] + APBPConstants[AP_OPEN_DOOR] + APBPConstants[AP_OPEN_DOOR]; // Include open and close costs!
break;
// cost for jumping a fence REPLACES all other AP costs!
case TRAVELCOST_FENCE :
//ddd window{
case TRAVELCOST_JUMPABLEWINDOW:
case TRAVELCOST_JUMPABLEWINDOW_N:
case TRAVELCOST_JUMPABLEWINDOW_W:
//ddd window}
if (!IS_MERC_BODY_TYPE( pSoldier ))
{
return -1;
}
// CHRISL:
// SANDRO - some little changes here
if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSoldier ) != ITEM_NOT_FOUND)
return( GetAPsToJumpFence( pSoldier, TRUE ) );
else
return( GetAPsToJumpFence( pSoldier, FALSE ) );
case TRAVELCOST_NONE : return( 0 );
default:
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Calc AP: Unrecongnized MP type %d in %d, direction %d", sSwitchValue, sGridNo, bDir ) );
break;
}
// silversurfer: don't multiply here or the following bonus/penalty calculations will all be wrong and cause our mercs to spend too many APs
// direction is now taken into account in function "ActionPointCost"
/* // SANDRO - note: as it is, the modifiers for walking/running/etc are not multiplied for diagonal movement, but is it intentional?
if (bDir & 1)
{
sAPCost = (sAPCost * 14) / 10;
}*/
return(sAPCost);
}
INT16 BreathPointAdjustmentForCarriedWeight( SOLDIERTYPE * pSoldier )
{
UINT32 uiCarriedPercent;
UINT32 uiPercentCost;
uiCarriedPercent = CalculateCarriedWeight( pSoldier, TRUE );
if (uiCarriedPercent < 101)
{
// normal BP costs
uiPercentCost = 100;
}
else
{
if (uiCarriedPercent < 151)
{
// between 101 and 150% of max carried weight, extra BP cost
// of 1% per % above 100... so at 150%, we pay 150%
uiPercentCost = 100 + (uiCarriedPercent - 100) * 3;
}
else if (uiCarriedPercent < 201)
{
// between 151 and 200% of max carried weight, extra BP cost
// of 2% per % above 150... so at 200%, we pay 250%
uiPercentCost = 100 + (uiCarriedPercent - 100) * 3 + (uiCarriedPercent - 150);
}
else
{
// over 200%, extra BP cost of 3% per % above 200
uiPercentCost = 100 + (uiCarriedPercent - 100) * 3 + (uiCarriedPercent - 150) + (uiCarriedPercent - 200);
// so at 250% weight, we pay 400% breath!
}
}
return( (INT16) uiPercentCost );
}
INT16 TerrainBreathPoints(SOLDIERTYPE * pSoldier, INT32 sGridNo, INT8 bDir, UINT16 usMovementMode)
{
FLOAT iPoints = 0;
UINT8 ubMovementCost;
ubMovementCost = gubWorldMovementCosts[sGridNo][bDir][0];
// WANNE.WATER: If our soldier is not on the ground level and the tile is a "water" tile, then simply set the tile to "FLAT_GROUND"
// This should fix "problems" for special modified maps
UINT8 ubTerrainID = gpWorldLevelData[ sGridNo ].ubTerrainID;
if ( TERRAIN_IS_WATER( ubTerrainID) && pSoldier->pathing.bLevel > 0 )
{
ubTerrainID = FLAT_GROUND;
// WANNE.WATER.BUGFIX: If we "walk on water", take the travelcost_flat BPs instead of the high swimming BPs ...
ubMovementCost = TRAVELCOST_FLAT;
}
switch( ubMovementCost )
{
case TRAVELCOST_DIRTROAD :
case TRAVELCOST_FLAT : iPoints = APBPConstants[BP_MOVEMENT_FLAT]; break;
//case TRAVELCOST_BUMPY :
case TRAVELCOST_GRASS : iPoints = APBPConstants[BP_MOVEMENT_GRASS]; break;
case TRAVELCOST_THICK : iPoints = APBPConstants[BP_MOVEMENT_BUSH]; break;
case TRAVELCOST_DEBRIS : iPoints = APBPConstants[BP_MOVEMENT_RUBBLE]; break;
case TRAVELCOST_SHORE : iPoints = APBPConstants[BP_MOVEMENT_SHORE]; break; // wading shallow water
case TRAVELCOST_KNEEDEEP : iPoints = APBPConstants[BP_MOVEMENT_LAKE]; break; // wading waist/chest deep - very slow
case TRAVELCOST_DEEPWATER : iPoints = APBPConstants[BP_MOVEMENT_OCEAN]; break; // can swim, so it's faster than wading
// case TRAVELCOST_VEINEND :
// case TRAVELCOST_VEINMID : iPoints = APBPConstants[BP_MOVEMENT_FLAT]; break;
case TRAVELCOST_FENCE :
if( FindBackpackOnSoldier( pSoldier ) != ITEM_NOT_FOUND )
iPoints = APBPConstants[BP_JUMPFENCEBPACK];
else
iPoints = APBPConstants[BP_JUMPFENCE];
break;
default:
if ( IS_TRAVELCOST_DOOR( ubMovementCost ) )
{
iPoints = APBPConstants[BP_MOVEMENT_FLAT];
break;
}
/*
#ifdef TESTVERSION
NumMessage("ERROR: TerrainBreathPoints: Unrecognized grid cost = ",
GridCost[gridno]);
#endif
*/
return(0);
}
if ( usMovementMode == WALKING_WEAPON_RDY ||
usMovementMode == WALKING_DUAL_RDY ||
usMovementMode == CROUCHEDMOVE_RIFLE_READY ||
usMovementMode == CROUCHEDMOVE_DUAL_READY ||
usMovementMode == CROUCHEDMOVE_PISTOL_READY )
{
iPoints += APBPConstants[BP_MOVEMENT_READY];
}
iPoints = iPoints * BreathPointAdjustmentForCarriedWeight( pSoldier ) / 100.0f;
// ATE - MAKE MOVEMENT ALWAYS WALK IF IN WATER
if ( TERRAIN_IS_WATER( ubTerrainID) )
{
usMovementMode = WALKING;
}
// so, then we must modify it for other movement styles and accumulate
switch(usMovementMode)
{
case RUNNING:
case ADULTMONSTER_WALKING:
case BLOODCAT_RUN:
iPoints *= APBPConstants[BP_RUN_ENERGYCOSTFACTOR]; break;
case SIDE_STEP:
case SIDE_STEP_WEAPON_RDY:
case SIDE_STEP_DUAL_RDY:
case SIDE_STEP_ALTERNATIVE_RDY:
case WALK_BACKWARDS:
case BLOODCAT_WALK_BACKWARDS:
case MONSTER_WALK_BACKWARDS:
case WALKING_WEAPON_RDY:
case WALKING_DUAL_RDY:
case WALKING_ALTERNATIVE_RDY:
case WALKING :
iPoints *= APBPConstants[BP_WALK_ENERGYCOSTFACTOR]; break;
case START_SWAT:
case SWATTING:
case SWAT_BACKWARDS:
case SIDE_STEP_CROUCH_RIFLE:
case SIDE_STEP_CROUCH_PISTOL:
case SIDE_STEP_CROUCH_DUAL:
case CROUCHEDMOVE_RIFLE_READY:
case CROUCHEDMOVE_PISTOL_READY:
case CROUCHEDMOVE_DUAL_READY:
iPoints *= APBPConstants[BP_SWAT_ENERGYCOSTFACTOR]; break;
case CRAWLING: iPoints *= APBPConstants[BP_CRAWL_ENERGYCOSTFACTOR]; break;
}
// SANDRO - STOMP traits - Athletics reduce breath points spent for moving
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, ATHLETICS_NT ))
{
iPoints = max(1, (iPoints * (100 - gSkillTraitValues.ubATBPsMovementReduction) / 100.0f) );
}
// Flugente: scuba fins reduce movement cost in water, but increase cost on land
if ( pSoldier->inv[LEGPOS].exists() && HasItemFlag( pSoldier->inv[LEGPOS].usItem, SCUBA_FINS ) )
{
if ( TERRAIN_IS_HIGH_WATER( ubTerrainID) )
{
iPoints /= 2;
}
else
{
iPoints *= 2;
}
}
// Flugente: backgrounds
if ( TERRAIN_IS_HIGH_WATER( ubTerrainID) )
iPoints = iPoints * ( 100 + pSoldier->GetBackgroundValue(BG_SWIMMING) ) / 100.0f;
// ATE: Adjust these by realtime movement
if (!(gTacticalStatus.uiFlags & TURNBASED) || !(gTacticalStatus.uiFlags & INCOMBAT ) )
{
// ATE: ADJUST FOR RT - MAKE BREATH GO A LITTLE FASTER!
// silversurfer: now externalized to APBPConstants.ini
//iPoints = (INT32)( iPoints * TB_BREATH_DEDUCT_MODIFIER );
iPoints = iPoints * APBPConstants[BP_RT_BREATH_DEDUCT_MODIFIER] / 100.0f;
}
// moving diagonally
if (bDir & 1)
iPoints *= 1.4f;
return( (INT16)iPoints);
}
INT16 ActionPointCost( SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bDir, UINT16 usMovementMode )
{
INT16 sTileCost, sSwitchValue;
FLOAT sPoints = 0;
// get the tile cost for that tile based on WALKING
sTileCost = TerrainActionPoints( pSoldier, sGridNo, bDir, pSoldier->pathing.bLevel );
if (sTileCost == -1)
{
return 100;
}
// Get switch value...
sSwitchValue = gubWorldMovementCosts[ sGridNo ][ bDir ][ pSoldier->pathing.bLevel ];
// silversurfer: modified handling below because we need to take stance change into account
/* // Tile cost should not be reduced based on movement mode...
if ( sSwitchValue == TRAVELCOST_FENCE )
{
return( sTileCost );
}*/
// WANNE.WATER: If our soldier is not on the ground level and the tile is a "water" tile, then simply set the tile to "FLAT_GROUND"
// This should fix "problems" for special modified maps
UINT8 ubTerrainID = gpWorldLevelData[ sGridNo ].ubTerrainID;
if ( TERRAIN_IS_WATER( ubTerrainID) && pSoldier->pathing.bLevel > 0 )
ubTerrainID = FLAT_GROUND;
// silversurfer: after we jump over a fence we end in crouched stance so we have to spend AP to get back to the original stance.
// Also we have to spend AP to get up before jumping.
if ( sSwitchValue == TRAVELCOST_FENCE )
{
switch( usMovementMode )
{
case RUNNING:
case WALKING:
case WALKING_WEAPON_RDY:
case WALKING_DUAL_RDY:
case WALKING_ALTERNATIVE_RDY:
// Add cost to stand up once after jump
sTileCost += GetAPsCrouch(pSoldier, TRUE);
break;
case SWATTING:
case START_SWAT:
case SWAT_BACKWARDS:
case SIDE_STEP_CROUCH_RIFLE:
case SIDE_STEP_CROUCH_PISTOL:
case SIDE_STEP_CROUCH_DUAL:
case CROUCHEDMOVE_RIFLE_READY:
case CROUCHEDMOVE_PISTOL_READY:
case CROUCHEDMOVE_DUAL_READY:
// Add cost to stand up once before jump
sTileCost += GetAPsCrouch(pSoldier, TRUE);
break;
case CRAWLING:
// Add cost twice to change stance
sTileCost += GetAPsCrouch(pSoldier, TRUE) + ( 2 * GetAPsProne(pSoldier, TRUE) );
break;
default:
break;
}
return( sTileCost );
}
// ATE - MAKE MOVEMENT ALWAYS WALK IF IN WATER
if ( TERRAIN_IS_WATER( ubTerrainID) )
{
usMovementMode = WALKING;
}
// so, then we must modify it for other movement styles and accumulate
// CHRISL: Adjusted system to use different move costs while wearing a backpack
if (sTileCost > 0)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// SANDRO - This part have been modified "a bit"
// Check movement modifiers
switch( usMovementMode )
{
case RUNNING:
case ADULTMONSTER_WALKING:
case BLOODCAT_RUN:
sPoints = sTileCost + APBPConstants[AP_MODIFIER_RUN];
break;
case CROW_FLY:
case SIDE_STEP:
case WALK_BACKWARDS:
case ROBOT_WALK:
case BLOODCAT_WALK_BACKWARDS:
case MONSTER_WALK_BACKWARDS:
case LARVAE_WALK:
case WALKING :
case WALKING_ALTERNATIVE_RDY:
case SIDE_STEP_ALTERNATIVE_RDY:
sPoints = sTileCost + APBPConstants[AP_MODIFIER_WALK];
if ( usMovementMode == WALKING && !(pSoldier->MercInWater()) && ( (gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_FIREREADY ) || (gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_FIRE ) ))
{
sPoints += APBPConstants[AP_MODIFIER_READY];
}
break;
case SIDE_STEP_WEAPON_RDY:
case SIDE_STEP_DUAL_RDY:
case WALKING_WEAPON_RDY:
case WALKING_DUAL_RDY:
sPoints = sTileCost + APBPConstants[AP_MODIFIER_WALK] + APBPConstants[AP_MODIFIER_READY];
break;
case START_SWAT:
case SWAT_BACKWARDS:
case SWATTING:
case SIDE_STEP_CROUCH_RIFLE:
case SIDE_STEP_CROUCH_PISTOL:
case SIDE_STEP_CROUCH_DUAL:
case CROUCHEDMOVE_RIFLE_READY:
case CROUCHEDMOVE_PISTOL_READY:
case CROUCHEDMOVE_DUAL_READY:
sPoints = sTileCost + APBPConstants[AP_MODIFIER_SWAT];
break;
case CRAWLING:
sPoints = sTileCost + APBPConstants[AP_MODIFIER_CRAWL];
break;
default:
// Invalid movement mode
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Invalid movement mode %d used in ActionPointCost", usMovementMode ) );
sPoints = sTileCost;
break;
}
// Check for reverse mode
if ( pSoldier->bReverse || gUIUseReverse )
sPoints += APBPConstants[AP_REVERSE_MODIFIER];
// STOMP traits - Athletics trait decreases movement cost
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, ATHLETICS_NT ))
{
sPoints = max(1.0f, ( sPoints * (100 - (FLOAT)gSkillTraitValues.ubATAPsMovementReduction) / 100.0f ) );
}
if (usMovementMode == RUNNING && pSoldier->usAnimState != RUNNING)
{
// CHRISL
if ((UsingNewInventorySystem() == true) && FindBackpackOnSoldier(pSoldier) != ITEM_NOT_FOUND)
{
sPoints += GetAPsStartRun(pSoldier) + 2; // changed by SANDRO
}
else
{
sPoints += GetAPsStartRun(pSoldier); // changed by SANDRO
}
}
// Flugente: riot shields lower movement speed
if ( pSoldier->IsRiotShieldEquipped( ) )
sPoints *= gItemSettings.fShieldMovementAPCostModifier;
// Flugente: dragging someone
if ( pSoldier->IsDragging( ) )
sPoints *= gItemSettings.fDragAPCostModifier;
// Flugente: scuba fins reduce movement cost in water, but increase cost on land
if ( pSoldier->inv[LEGPOS].exists() && HasItemFlag( pSoldier->inv[LEGPOS].usItem, SCUBA_FINS ) )
{
if ( TERRAIN_IS_HIGH_WATER( ubTerrainID) )
{
sPoints /= 2;
}
else
{
sPoints *= 2;
}
}
// Flugente: swimming background
if ( TERRAIN_IS_HIGH_WATER( ubTerrainID) )
sPoints = sPoints * ( 100 + pSoldier->GetBackgroundValue(BG_SWIMMING) ) / 100.0f;
// Check if doors if not player's merc (they have to open them manually)
if ( sSwitchValue == TRAVELCOST_DOOR && pSoldier->bTeam != gbPlayerNum )
{
sPoints += GetAPsToOpenDoor( pSoldier ) + GetAPsToOpenDoor( pSoldier ); // Include open and close costs!
}
// Check for stealth mode
if ( pSoldier->bStealthMode )
{
// STOMP traits - Stealthy trait decreases stealth AP modifier
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, STEALTHY_NT ))
{
sPoints += max(0, (FLOAT)( APBPConstants[AP_STEALTH_MODIFIER] * (100 - gSkillTraitValues.ubSTStealthModeSpeedBonus) / 100.0f) );
}
else
{
sPoints += APBPConstants[AP_STEALTH_MODIFIER];
}
}
// Check for backpack
// Moa: apply penalty for heavily packed backpack (wobble penalty)
if ( UsingNewInventorySystem() == true && gGameExternalOptions.fBackPackWeightLowersAP )
{
INT8 bSlot= FindBackpackOnSoldier( pSoldier );
if ( bSlot != ITEM_NOT_FOUND )
{
UINT16 usBPPenalty = APBPConstants[AP_MODIFIER_PACK];
if ( bSlot == BPACKPOCKPOS ) //Backpack caried on back
{
OBJECTTYPE * pObj = &( pSoldier->inv[ BPACKPOCKPOS ] );
UINT16 usBackPackWeight = CalculateObjectWeight( pObj );
// CalculateObjectWeight checks for active LBE gear. Unfortunatly our backpack is not active since we are carying it.
// Sounds not intuitive at all, active means the LBE caries items (marked with blue *), but when put on the LBE adittional slots of our soldier
// are activated where something can be carried. So we have to add the weights of those slots as well.
std::vector<INT8> vbLBESlots;
GetLBESlots( BPACKPOCKPOS, vbLBESlots );
for ( UINT8 i = 0; i < vbLBESlots.size() ; i++ )
{
pObj = &( pSoldier->inv[ vbLBESlots[ i ] ] );
usBackPackWeight += CalculateObjectWeight( pObj );
}
usBPPenalty = min( ( usBackPackWeight / 50 ), usBPPenalty ); //1 AP penalty for each 5kg of weight up to the penalty defined by AP_MODIFIER_PACK (default = 4)
}
else //Backpack caried not on back (maybe somewhere inside another LBE or in Hand?)
{
//apply full penalty
}
sPoints += usBPPenalty;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
}
if (sSwitchValue == TRAVELCOST_NOT_STANDING)
{
switch(usMovementMode)
{
case RUNNING:
case WALKING :
case WALKING_WEAPON_RDY:
case WALKING_DUAL_RDY:
case LARVAE_WALK:
case SIDE_STEP:
case SIDE_STEP_WEAPON_RDY:
case SIDE_STEP_DUAL_RDY:
case WALK_BACKWARDS:
case WALKING_ALTERNATIVE_RDY:
case SIDE_STEP_ALTERNATIVE_RDY:
// charge crouch APs for ducking head!
sPoints += GetAPsCrouch(pSoldier, TRUE); // SANDRO changed
break;
default:
break;
}
}
// moving diagonally
if (bDir & 1)
sPoints *= 1.4f;
return( (INT16)(sPoints + 0.5f) );
}
INT16 EstimateActionPointCost( SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bDir, UINT16 usMovementMode, INT8 bPathIndex, INT8 bPathLength )
{
// This action point cost code includes the penalty for having to change
// stance after jumping a fence IF our path continues...
INT16 sTileCost, sPoints, sSwitchValue;
sPoints = 0;
// get the tile cost for that tile based on WALKING
sTileCost = TerrainActionPoints( pSoldier, sGridNo, bDir, pSoldier->pathing.bLevel );
if (sTileCost == -1)
{
return 100;
}
// Get switch value...
sSwitchValue = gubWorldMovementCosts[ sGridNo ][ bDir ][ pSoldier->pathing.bLevel ];
#if 1 //Moa: set to 0 to use original copy and paste code from ActionPointCost()
if ( sSwitchValue == TRAVELCOST_FENCE )
{
// If we are changeing stance ( either before or after getting there....
// We need to reflect that...
switch(usMovementMode)
{
case SIDE_STEP:
case SIDE_STEP_WEAPON_RDY:
case SIDE_STEP_DUAL_RDY:
case WALK_BACKWARDS:
case RUNNING:
case WALKING :
case WALKING_WEAPON_RDY:
case WALKING_DUAL_RDY:
case WALKING_ALTERNATIVE_RDY :
case SIDE_STEP_ALTERNATIVE_RDY:
// silversurfer: this is now done in function "ActionPointCost"
/* // Add here cost to go from crouch to stand AFTER fence hop....
// Since it's AFTER.. make sure we will be moving after jump...
if ( ( bPathIndex + 2 ) < bPathLength )
{
sPoints += GetAPsCrouch(pSoldier, TRUE); // SANDRO changed..
}*/
// if we were running we need to charge some APs to start running again
if ( ( bPathIndex + 2 ) < bPathLength && usMovementMode == RUNNING )
{
sPoints += GetAPsStartRun(pSoldier);
}
break;
case SWATTING:
case START_SWAT:
case SWAT_BACKWARDS:
case SIDE_STEP_CROUCH_RIFLE:
case SIDE_STEP_CROUCH_PISTOL:
case SIDE_STEP_CROUCH_DUAL:
case CROUCHEDMOVE_RIFLE_READY:
case CROUCHEDMOVE_PISTOL_READY:
case CROUCHEDMOVE_DUAL_READY:
// silversurfer: this is now done in function "ActionPointCost"
// Add cost to stand once there BEFORE....
// sPoints += GetAPsCrouch(pSoldier, TRUE); // SANDRO changed..
break;
case CRAWLING:
// Can't do it here.....
break;
}
}
sPoints += ActionPointCost( pSoldier, sGridNo, bDir, usMovementMode );
return (sPoints);
#else
// Tile cost should not be reduced based on movement mode...
if ( sSwitchValue == TRAVELCOST_FENCE )
{
return( sTileCost );
}
// WANNE.WATER: If our soldier is not on the ground level and the tile is a "water" tile, then simply set the tile to "FLAT_GROUND"
// This should fix "problems" for special modified maps
UINT8 ubTerrainID = gpWorldLevelData[ sGridNo ].ubTerrainID;
if ( TERRAIN_IS_WATER( ubTerrainID) && pSoldier->pathing.bLevel > 0 )
ubTerrainID = FLAT_GROUND;
// ATE - MAKE MOVEMENT ALWAYS WALK IF IN WATER
if ( TERRAIN_IS_WATER( ubTerrainID) )
{
usMovementMode = WALKING;
}
// so, then we must modify it for other movement styles and accumulate
// CHRISL: Adjusted system to use different move costs while wearing a backpack
if (sTileCost > 0)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// SANDRO - This part have been modified "a bit"
// Check movement modifiers
switch( usMovementMode )
{
case RUNNING:
case ADULTMONSTER_WALKING:
case BLOODCAT_RUN:
sPoints = sTileCost + APBPConstants[AP_MODIFIER_RUN];
break;
case CROW_FLY:
case SIDE_STEP:
case WALK_BACKWARDS:
case ROBOT_WALK:
case BLOODCAT_WALK_BACKWARDS:
case MONSTER_WALK_BACKWARDS:
case LARVAE_WALK:
case WALKING :
case WALKING_ALTERNATIVE_RDY :
case SIDE_STEP_ALTERNATIVE_RDY :
sPoints = sTileCost + APBPConstants[AP_MODIFIER_WALK];
if (!(pSoldier->MercInWater()) && ( (gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_FIREREADY ) || (gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_FIRE ) ) && !(gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_ALT_WEAPON_HOLDING ) )
{
sPoints += APBPConstants[AP_MODIFIER_READY];
}
break;
case SIDE_STEP_WEAPON_RDY:
case SIDE_STEP_DUAL_RDY:
case WALKING_WEAPON_RDY:
case WALKING_DUAL_RDY:
sPoints = sTileCost + APBPConstants[AP_MODIFIER_WALK] + APBPConstants[AP_MODIFIER_READY];
break;
case START_SWAT:
case SWAT_BACKWARDS:
case SWATTING:
sPoints = sTileCost + APBPConstants[AP_MODIFIER_SWAT];
break;
case CRAWLING:
sPoints = sTileCost + APBPConstants[AP_MODIFIER_CRAWL];
break;
default:
// Invalid movement mode
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Invalid movement mode %d used in ActionPointCost", usMovementMode ) );
sPoints = sTileCost;
break;
}
// Check for reverse mode
if ( pSoldier->bReverse || gUIUseReverse )
sPoints += APBPConstants[AP_REVERSE_MODIFIER];
// STOMP traits - Athletics trait decreases movement cost
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, ATHLETICS_NT ))
{
sPoints = max(1, (INT16)((sPoints * (100 - gSkillTraitValues.ubATAPsMovementReduction) / 100) + 0.5));
}
// Flugente: riot shields lower movement speed
if ( pSoldier->IsRiotShieldEquipped( ) )
sPoints *= gItemSettings.fShieldMovementAPCostModifier;
// Flugente: dragging someone
if ( pSoldier->IsDraggingSomeone( ) )
sPoints *= gItemSettings.fDragAPCostModifier;
// Check if doors if not player's merc (they have to open them manually)
if ( sSwitchValue == TRAVELCOST_DOOR && pSoldier->bTeam != gbPlayerNum )
{
sPoints += GetAPsToOpenDoor( pSoldier ) + GetAPsToOpenDoor( pSoldier ); // Include open and close costs!
}
// Check for stealth mode
if ( pSoldier->bStealthMode )
{
// STOMP traits - Stealthy trait decreases stealth AP modifier
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, STEALTHY_NT ))
{
sPoints += (max(0, (INT16)((APBPConstants[AP_STEALTH_MODIFIER] * (100 - gSkillTraitValues.ubSTStealthModeSpeedBonus) / 100) + 0.5)));
}
else
{
sPoints += APBPConstants[AP_STEALTH_MODIFIER];
}
}
// Check for backpack
if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSoldier ) != ITEM_NOT_FOUND )
sPoints += APBPConstants[AP_MODIFIER_PACK];
///////////////////////////////////////////////////////////////////////////////////////////////////////////
}
// Get switch value...
sSwitchValue = gubWorldMovementCosts[ sGridNo ][ bDir ][ pSoldier->pathing.bLevel ];
// ATE: If we have a 'special cost, like jump fence...
if ( sSwitchValue == TRAVELCOST_FENCE )
{
// If we are changeing stance ( either before or after getting there....
// We need to reflect that...
switch(usMovementMode)
{
case SIDE_STEP:
case SIDE_STEP_WEAPON_RDY:
case SIDE_STEP_DUAL_RDY:
case WALK_BACKWARDS:
case RUNNING:
case WALKING :
case WALKING_WEAPON_RDY:
case WALKING_DUAL_RDY:
case WALKING_ALTERNATIVE_RDY :
case SIDE_STEP_ALTERNATIVE_RDY:
// Add here cost to go from crouch to stand AFTER fence hop....
// Since it's AFTER.. make sure we will be moving after jump...
if ( ( bPathIndex + 2 ) < bPathLength )
{
sPoints += GetAPsCrouch(pSoldier, TRUE); // SANDRO changed..
}
break;
case SWATTING:
case START_SWAT:
case SWAT_BACKWARDS:
// Add cost to stand once there BEFORE....
sPoints += GetAPsCrouch(pSoldier, TRUE); // SANDRO changed..
break;
case CRAWLING:
// Can't do it here.....
break;
}
}
else if (sSwitchValue == TRAVELCOST_NOT_STANDING)
{
switch(usMovementMode)
{
case RUNNING:
case WALKING :
case WALKING_WEAPON_RDY:
case WALKING_DUAL_RDY:
case SIDE_STEP:
case SIDE_STEP_WEAPON_RDY:
case SIDE_STEP_DUAL_RDY:
case WALK_BACKWARDS:
case WALKING_ALTERNATIVE_RDY :
case SIDE_STEP_ALTERNATIVE_RDY:
// charge crouch APs for ducking head!
sPoints += GetAPsCrouch(pSoldier, TRUE); // SANDRO changed..
break;
default:
break;
}
}
return( sPoints );
#endif
}
BOOLEAN EnoughPoints( SOLDIERTYPE *pSoldier, INT16 sAPCost, INT32 iBPCost, BOOLEAN fDisplayMsg )
{
INT16 sNewAP = 0;
//CHRISL: if we don't have soldier information, assume we have enough points.
if(pSoldier == NULL)
return( TRUE );
// If this guy is on a special move... don't care about APS, OR BPSs!
if ( pSoldier->ubWaitActionToDo )
{
return( TRUE );
}
if ( pSoldier->ubQuoteActionID >=QUOTE_ACTION_ID_TRAVERSE_EAST && pSoldier->ubQuoteActionID <= QUOTE_ACTION_ID_TRAVERSE_NORTH )
{
// AI guy on special move off map
return( TRUE );
}
// IN realtime.. only care about BPs
if ( ( gTacticalStatus.uiFlags & REALTIME ) || !(gTacticalStatus.uiFlags & INCOMBAT ) || !pSoldier->bInSector)
{
sAPCost = 0;
}
#ifdef NETWORKED
if( !IsTheSolderUnderMyControl( pSoldier->ubID) )
{
return( TRUE );
}
#endif
if (is_networked)
{
if(pSoldier->ubID > 119 || (!is_server && pSoldier->ubID > 20))
{
return(TRUE);
}//hayden , if soldier replication, allow.
}
// Get New points
sNewAP = pSoldier->bActionPoints - sAPCost;
// If we cannot deduct points, return FALSE
if ( sNewAP < 0 )
{
// Display message if it's our own guy
if ( pSoldier->bTeam == gbPlayerNum && fDisplayMsg )
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, TacticalStr[ NOT_ENOUGH_APS_STR ] );
}
return( FALSE );
}
return( TRUE );
}
void DeductPoints( SOLDIERTYPE *pSoldier, INT16 sAPCost, INT32 iBPCost, UINT8 ubInterruptType )
{
INT16 sNewAP = 0;
INT8 bNewBreath;
// Flugente: if we spend AP...
if ( sAPCost > 0 )
{
// ... then spotter status ends
pSoldier->usSkillCounter[SOLDIER_COUNTER_SPOTTER] = 0;
// if we are temporarily overt
if ( pSoldier->usSoldierFlagMask & SOLDIER_COVERT_TEMPORARY_OVERT && pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_COVERTOPS_TEMPORARYOVERT_APS] > 0 )
pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_COVERTOPS_TEMPORARYOVERT_APS] = max( 0, (INT16)(pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_COVERTOPS_TEMPORARYOVERT_APS] - sAPCost) );
// sevenfm: indicate in realtime that soldier spent some action point this turn
pSoldier->usSoldierFlagMask2 |= SOLDIER_SPENT_AP;
pSoldier->StopChatting();
// sevenfm: stop muzzle flash
if (pSoldier->flags.fMuzzleFlash)
{
EndMuzzleFlash(pSoldier);
}
}
// in real time, there IS no AP cost, (only breath cost)
if (!(gTacticalStatus.uiFlags & TURNBASED) || !(gTacticalStatus.uiFlags & INCOMBAT ) || !pSoldier->bInSector)
{
sAPCost = 0;
}
// anv: there were weird things going on here, deducting fuel for strategic travel happens elsewhere anyway
if ( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE )
{
iBPCost = 0;
}
// Get New points
sNewAP = pSoldier->bActionPoints - sAPCost;
// If this is the first time with no action points, set UI flag
if ( sNewAP <= 0 && pSoldier->bActionPoints > 0 )
{
pSoldier->flags.fUIFirstTimeNOAP = TRUE;
fInterfacePanelDirty = TRUE;
}
// WANNE: negative APs didn't actually have any effect on the next turn because they were being reset to 0 before they could have any effect (by Headrock)
// If we cannot deduct points, return FALSE
/*
if ( sNewAP < 0 )
{
sNewAP = 0;
}
*/
pSoldier->bActionPoints = sNewAP;
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Deduct Points (%d at %d) %d %d", pSoldier->ubID, pSoldier->sGridNo, sAPCost, iBPCost ) );
if ( AM_A_ROBOT( pSoldier ) )
{
// zap all breath costs for robot
iBPCost = 0;
}
// is there a BREATH deduction/transaction to be made? (REMEMBER: could be a GAIN!)
// NB: iBPCost > 0 - breath loss, iBPCost < 0 - breath gain
if (iBPCost)
{
// Flugente: if we GAIN breath points, adjust them - if we are hungry, we get fewer points back
// Flugente: ubMaxMorale can now be influenced by our food situation
if ( iBPCost < 0 && UsingFoodSystem() )
ReduceBPRegenForHunger(pSoldier, &iBPCost);
// Flugente: backgrounds
if ( iBPCost < 0 )
iBPCost = (INT32) (iBPCost * (100 + pSoldier->GetBackgroundValue(BG_PERC_REGEN_ENERGY)) / 100);
if (is_networked)
{
// Adjust breath changes due to spending or regaining of energy
if(!pSoldier->bActive)
{
}
else
iBPCost = AdjustBreathPts(pSoldier, iBPCost);
}
else
{
iBPCost = AdjustBreathPts(pSoldier, iBPCost);
}
// Snap: award some health and strength for exertion
// Do 4 StatChange rolls for health (2 for strength) per 10 breath points spent?
// NB: The scale of iBPCost is 100 per breath point (APBPConstants[BP_RATIO_RED_PTS_TO_NORMAL])
if ( PTR_OURTEAM && iBPCost >= APBPConstants[BP_MOVEMENT_GRASS] && ubInterruptType != DISABLED_INTERRUPT
&& (INT32) PreRandom( 10 * APBPConstants[BP_RATIO_RED_PTS_TO_NORMAL] ) < iBPCost )
{
StatChange(pSoldier, HEALTHAMT, 4, FALSE);
StatChange(pSoldier, STRAMT, 2, FALSE);
}
// CJC: moved check for high breathred to below so that negative breath can be detected
// Snap: kill the overflow bug already!
// NB: normally, sBreathRed == (100 - bBreath)*APBPConstants[BP_RATIO_RED_PTS_TO_NORMAL]
const INT16 BREATH_RED_MAX = 100 * APBPConstants[BP_RATIO_RED_PTS_TO_NORMAL];
if ( pSoldier->sBreathRed + iBPCost < 0 )
{
pSoldier->sBreathRed = 0;
}
else if ( pSoldier->sBreathRed + iBPCost > BREATH_RED_MAX )
{
// Snap: moved this up, because it had no effect below
// Take off 1 AP per 5 (negative) breath...
//CHRISL: Need to adjust this so that AP lose is based on AP_MAXIMUM
pSoldier->bActionPoints -= (INT16)( (float)( pSoldier->sBreathRed + iBPCost - BREATH_RED_MAX )
/ (float)( 5 * APBPConstants[BP_RATIO_RED_PTS_TO_NORMAL])
* (float)(4 * (float)APBPConstants[AP_MAXIMUM] / 100));
// HEADROCK HAM 3.1: This may be the problem with suppression - it limits the lower APs to 0, which breaks
// suppression's negative values. Changed instances of "0" to the APBP constant.
if ( pSoldier->bActionPoints < APBPConstants[AP_MIN_LIMIT] )
{
pSoldier->bActionPoints = APBPConstants[AP_MIN_LIMIT];
}
pSoldier->sBreathRed = BREATH_RED_MAX;
}
else
{
pSoldier->sBreathRed += iBPCost;
}
// Get new breath
bNewBreath = ( pSoldier->bBreathMax * APBPConstants[BP_RATIO_RED_PTS_TO_NORMAL] - pSoldier->sBreathRed )
/ APBPConstants[BP_RATIO_RED_PTS_TO_NORMAL];
// Snap: This should never happen...
if ( bNewBreath > pSoldier->bBreathMax )
{
bNewBreath = pSoldier->bBreathMax;
}
else if ( bNewBreath < 0 )
{
bNewBreath = 0;
}
pSoldier->bBreath = bNewBreath;
}
// UPDATE BAR
DirtyMercPanelInterface( pSoldier, DIRTYLEVEL1 );
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SANDRO - Interrupt counter
if( UsingImprovedInterruptSystem() && sAPCost > 0 && ubInterruptType != DISABLED_INTERRUPT )
{
UINT8 ubPointsRegistered = 0;
UINT16 uCnt = 0;
SOLDIERTYPE *pOpponent;
BOOLEAN fFoundInterrupter = FALSE;
for ( uCnt = 0; uCnt < MAX_NUM_SOLDIERS; uCnt++ )
{
// first find all guys who watch us
pOpponent = MercPtrs[ uCnt ];
if ( pOpponent == NULL)
continue; // not here or not even breathing -> next!
if ( pOpponent->stats.bLife < OKLIFE || pOpponent->bCollapsed || !pOpponent->bActive )
continue; // not here or not even breathing -> next!
if ( pSoldier->bTeam == pOpponent->bTeam )
continue; // same team? -> next!
if ( pSoldier->bSide == pOpponent->bSide )
continue; // not enemy
if ( CONSIDERED_NEUTRAL( pSoldier, pOpponent ) )
continue; // neutral
// if we see or hear him
// dunno if this is the best solution yet, probably yes
if ( pOpponent->aiData.bOppList[pSoldier->ubID] == SEEN_CURRENTLY ||
pOpponent->aiData.bOppList[pSoldier->ubID] == HEARD_THIS_TURN)
//if (SoldierToSoldierLineOfSightTest( pOpponent, pSoldier, TRUE, CALC_FROM_WANTED_DIR ) != 0)
{
// calculate how much points do we "register" (let's try to avoid chance-based calc to not inspire save-load mania)
// Experience says how well is the observer able to notice and percieve the environment around him, i.e. gives us the actual chance per AP
if ( pOpponent->aiData.bOppList[pSoldier->ubID] == HEARD_THIS_TURN )
{
// if we only heard him, keep it lower
ubPointsRegistered = (gGameExternalOptions.ubBasicPercentRegisterValueIIS - 20)
+ (gGameExternalOptions.ubPercentRegisterValuePerLevelIIS * EffectiveExpLevel( pOpponent )); // base 40% + 4% per level
}
else
{
ubPointsRegistered = gGameExternalOptions.ubBasicPercentRegisterValueIIS
+ (gGameExternalOptions.ubPercentRegisterValuePerLevelIIS * EffectiveExpLevel( pOpponent )); // base 60% + 4% per level
}
// adjust by range to target
//INT32 iRange = GetRangeInCellCoordsFromGridNoDiff( pOpponent->sGridNo, pSoldier->sGridNo ); // calculate actual range
//INT16 iDistVisible = (pOpponent->GetMaxDistanceVisible(pOpponent->sGridNo, pOpponent->bTargetLevel, CALC_FROM_ALL_DIRS ) * CELL_X_SIZE); // how far do we see
INT16 iDistVisible = pOpponent->GetMaxDistanceVisible(pOpponent->sGridNo, pOpponent->bTargetLevel, CALC_FROM_ALL_DIRS ) * CELL_X_SIZE; // -1% registered by 4% of the difference of how far we can see and how far is the target
iDistVisible = max(iDistVisible, CELL_X_SIZE);
ubPointsRegistered -= min( 25, ( 25 * GetRangeInCellCoordsFromGridNoDiff( pOpponent->sGridNo, pSoldier->sGridNo ) / iDistVisible ));
if ( gGameOptions.fNewTraitSystem )
{
// without Night Ops, we get small penalty for interrupting a target in dark
if ( !(HAS_SKILL_TRAIT( pOpponent, NIGHT_OPS_NT )) && pOpponent->aiData.bOppList[pSoldier->ubID] == SEEN_CURRENTLY)
{
INT8 bLightLevel = LightTrueLevel(pSoldier->sGridNo, pSoldier->pathing.bLevel);
if ( bLightLevel > 6) // 7+ lightlevel is darkness
{
ubPointsRegistered -= bLightLevel; // -7 to -12%
}
}
// Martial Arts trait reduces the points registered when moving
if ( HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ) && (ubInterruptType == SP_MOVEMENT_INTERRUPT || ubInterruptType == MOVEMENT_INTERRUPT ) &&
Item[pSoldier->inv[HANDPOS].usItem].usItemClass & (IC_PUNCH|IC_BLADE|IC_NONE)) // only get this if we have no weapon or blade in hands
{
// check how far we are, we only get this bonus at certain range
INT16 sTileDistance = PythSpacesAway(pSoldier->sGridNo, pOpponent->sGridNo);
if ( (sTileDistance <= 9) && (pOpponent->bTargetLevel == pSoldier->bTargetLevel)) // we must be on the same ground level as well
{
UINT8 ubBonus = gSkillTraitValues.ubMAReducedAPsRegisteredWhenMoving * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT );
if (sTileDistance > 6) // make it gradually effective based on distance
ubBonus = ubBonus * (10 - sTileDistance) / 4; // at 9th tile it is only 25% of the trait bonus, from 7th tile it is 100%
// check direction, we get full bonus if running towards the victim, but others around watching the scene have somehow better chance to interfere
if ( pSoldier->ubDirection != GetDirectionToGridNoFromGridNo( pSoldier->sGridNo, pOpponent->sGridNo ) )
{
ubBonus = ubBonus *2/3; // two thirds only sound reasonable
}
ubPointsRegistered -= ubBonus;
}
}
// Stealhty trait reduced the points registered on all actions
if ( HAS_SKILL_TRAIT( pSoldier, STEALTHY_NT ) )
{
ubPointsRegistered -= gSkillTraitValues.ubSTReducedAPsRegistered;
}
}
// ALRIGHT! Get final value
if (pOpponent->aiData.bOppList[pSoldier->ubID] == SEEN_CURRENTLY )
ubPointsRegistered = max( 20, min( 100, ubPointsRegistered ) ); // 20% is minimum on seeing
else
ubPointsRegistered = max( 10, min( 100, ubPointsRegistered ) ); // 10% is minimum on hearing
ubPointsRegistered = min( 100, ubPointsRegistered ); // 100% is maximum ofc
ubPointsRegistered = (UINT8)(sAPCost * ubPointsRegistered / 100.0f + 0.5f); // now calc how many APs we will award
// increase the counter
if ( ubPointsRegistered )
{
pOpponent->aiData.ubInterruptCounter[pSoldier->ubID] += ubPointsRegistered;
fFoundInterrupter = TRUE;
}
}
}
// if interrupted, freeze our guy and trigger interrupt situation
if (fFoundInterrupter)
{
// if we've got the special movement flag type here, pass it to after-action type
// it's only meant for martial arts bonuses
if ( ubInterruptType == SP_MOVEMENT_INTERRUPT )
{
ubInterruptType = AFTERACTION_INTERRUPT;
}
// OK, something happened, set the interrupt pending flag
gTacticalStatus.ubInterruptPending = ubInterruptType;
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
INT32 AdjustBreathPts( SOLDIERTYPE * pSoldier , INT32 iBPCost )
{
INT16 sBreathFactor = 100;
UINT8 ubBandaged;
//NumMessage("BEFORE adjustments, BREATH PTS = ",breathPts);
// in real time, there IS no AP cost, (only breath cost)
/*
if (!(gTacticalStatus.uiFlags & TURNBASED) || !(gTacticalStatus.uiFlags & INCOMBAT ) )
{
// ATE: ADJUST FOR RT - MAKE BREATH GO A LITTLE FASTER!
iBPCost *= TB_BREATH_DEDUCT_MODIFIER;
}
*/
// adjust breath factor for current breath deficiency
sBreathFactor += (100 - pSoldier->bBreath);
// adjust breath factor for current life deficiency (but add 1/2 bandaging)
ubBandaged = pSoldier->stats.bLifeMax - pSoldier->stats.bLife - pSoldier->bBleeding;
//sBreathFactor += (pSoldier->stats.bLifeMax - (pSoldier->stats.bLife + (ubBandaged / 2)));
sBreathFactor += 100 * (pSoldier->stats.bLifeMax - (pSoldier->stats.bLife + (ubBandaged / 2))) / pSoldier->stats.bLifeMax;
if ( pSoldier->stats.bStrength > 80 )
{
// give % reduction to breath costs for high strength mercs
sBreathFactor -= (pSoldier->stats.bStrength - 80) / 2;
}
/* THIS IS OLD JAGGED ALLIANCE STUFF (left for possible future reference)
// apply penalty due to high temperature, heat, and hot Metaviran sun
// if INDOORS, in DEEP WATER, or possessing HEAT TOLERANCE trait
if ((ptr->terrtype == FLOORTYPE) || (ptr->terr >= OCEAN21) ||
(ptr->trait == HEAT_TOLERANT))
breathFactor += (Status.heatFactor / 5); // 20% of normal heat penalty
else
breathFactor += Status.heatFactor; // not used to this!
*/
// SANDRO - added heat intolerant penalty
if ( MercIsHot( pSoldier ) )
{
sBreathFactor += 100;
}
// SANDRO - Less breath consumption for Primitive chars
if ( DoesMercHavePersonality( pSoldier, CHAR_TRAIT_PRIMITIVE ) )
{
sBreathFactor -= 15; // -15% breath consumption
}
// if a non-swimmer type is thrashing around in deep water
if ( DoesMercHaveDisability( pSoldier, NONSWIMMER ) )
{
if ( pSoldier->usAnimState == DEEP_WATER_TRED || pSoldier->usAnimState == DEEP_WATER_SWIM)
{
sBreathFactor *= 7; // lose breath 5 times faster in deep water!
}
}
if ( sBreathFactor == 0 )
{
iBPCost = 0;
}
else if (iBPCost > 0) // breath DECREASE
// increase breath COST by breathFactor
iBPCost = ((iBPCost * sBreathFactor) / 100);
else // breath INCREASE
// decrease breath GAIN by breathFactor
iBPCost = ((iBPCost * 100) / sBreathFactor);
return(iBPCost);
}
void UnusedAPsToBreath( SOLDIERTYPE * pSoldier )
{
INT16 sUnusedAPs, sBreathPerAP = 0, sBreathChange, sRTBreathMod;
// Note to Andrew (or whomever else it may concern):
// This function deals with BETWEEN TURN breath/energy gains. The basic concept is:
//
// - look at LAST (current) animation of merc to see what he's now doing
// - look at how many AP remain unspent (indicating duration of time doing that anim)
//
// figure out how much breath/energy (if any) he should recover. Obviously if a merc
// is STANDING BREATHING and hasn't spent any AP then it means he *stood around* for
// the entire duration of one turn (which, instead of spending energy, REGAINS energy)
// COMMENTED OUT FOR NOW SINCE MOST OF THE ANIMATION DEFINES DO NOT MATCH
// If we are not in turn-based combat...
if ( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE )
{
return;
}
if ( !( gTacticalStatus.uiFlags & TURNBASED ) || !(gTacticalStatus.uiFlags & INCOMBAT ) )
{
// ALRIGHT, GIVE A FULL AMOUNT BACK, UNLES MODIFIED BY WHAT ACTIONS WE WERE DOING
sBreathPerAP = GetBreathPerAP( pSoldier, pSoldier->usAnimState );
// adjust for carried weight
sBreathPerAP = sBreathPerAP * 100 / BreathPointAdjustmentForCarriedWeight( pSoldier );
// If this value is -ve, we have a gain, else we have a loos which we should not really do
// We just want to limit this to no gain if we were doing stuff...
sBreathChange = 3 * sBreathPerAP;
// Adjust for on drugs
HandleBPEffectDueToDrugs( pSoldier, &sBreathChange );
// Flugente: why would we forbid losing breath in realtime?
/*if ( sBreathChange > 0 )
{
sBreathChange = 0;
}*/
if ( sBreathChange < 0 )
{
// We have a gain, now limit this depending on what we were doing...
// OK for RT, look at how many tiles we have moved, our last move anim
if ( pSoldier->ubTilesMovedPerRTBreathUpdate > 0 )
{
// How long have we done this for?
// And what anim were we doing?
sBreathPerAP = GetBreathPerAP( pSoldier, pSoldier->usLastMovementAnimPerRTBreathUpdate );
sRTBreathMod = sBreathPerAP * pSoldier->ubTilesMovedPerRTBreathUpdate;
// Deduct some if we were exerting ourselves
// We add here because to gain breath, sBreathChange needs to be -ve
if ( sRTBreathMod > 0 )
{
sBreathChange += sRTBreathMod;
}
if ( sBreathChange < 0 )
{
sBreathChange = 0;
}
}
}
// Divide by a number to adjust that in realtimer we do not want to recover as
// as fast as the TB values do
// silversurfer: now externalized to APBPConstants.ini
//sBreathChange *= TB_BREATH_RECOVER_MODIFIER;
sBreathChange = INT16 ( sBreathChange * APBPConstants[BP_RT_BREATH_RECOVER_MODIFIER] / 100 );
// SANDRO: get BP *cost* for weapon holding (reduce breath gain, or even make it breath loss)
if ( gGameExternalOptions.ubEnergyCostForWeaponWeight )
{
sBreathChange += 3 * GetBPCostPer10APsForGunHolding( pSoldier ) / 10;
// if we are actually losing breath by holding a very heavy gun up, lower it if breath already at critical line
if ( sBreathChange > 0 && pSoldier->bBreath < OKBREATH )
{
// ok, if this gun is rather heavy, and cost us at least 3 energy points per turn
if ( (GetBPCostPer10APsForGunHolding( pSoldier ) * 10) >= (300 * gGameExternalOptions.ubEnergyCostForWeaponWeight / 100) )
{
// throw quote
if ( !(pSoldier->usQuoteSaidFlags & SOLDIER_QUOTE_SAID_LOW_BREATH ) )
{
TacticalCharacterDialogue( pSoldier, QUOTE_OUT_OF_BREATH );
pSoldier->usQuoteSaidFlags |= SOLDIER_QUOTE_SAID_LOW_BREATH;
}
// Put gun down....
pSoldier->InternalSoldierReadyWeapon(pSoldier->ubDirection, TRUE, FALSE );
}
}
}
// adjust breath only, don't touch action points!
DeductPoints(pSoldier,0,(INT16)sBreathChange );
// Reset value for RT breath update
pSoldier->ubTilesMovedPerRTBreathUpdate = 0;
}
else
{
// if merc has any APs left unused this turn (that aren't carrying over)
if (pSoldier->bActionPoints > APBPConstants[MAX_AP_CARRIED])
{
// SANDRO - don't reduce the APs if collapsed
if ( pSoldier->bCollapsed || pSoldier->bBreathCollapsed )
sUnusedAPs = pSoldier->bActionPoints;
else
sUnusedAPs = pSoldier->bActionPoints - APBPConstants[MAX_AP_CARRIED];
sBreathPerAP = GetBreathPerAP( pSoldier, pSoldier->usAnimState );
if (sBreathPerAP < 0)
{
// can't gain any breath when we've just been gassed, OR
// if standing in tear gas without a gas mask on
if ( pSoldier->flags.uiStatusFlags & SOLDIER_GASSED )
{
return; // can't breathe here, so get no breath back!
}
}
// adjust for carried weight
sBreathPerAP = sBreathPerAP * 100 / BreathPointAdjustmentForCarriedWeight( pSoldier );
// SANDRO: what the hell? This calculation is not correct, it means the less APs you have the more Breath you gain,
// when the exact opposite should happen according to the function of this entire procedure!
//if ( pSoldier->bTeam != CIV_TEAM && pSoldier->bTeam != gbPlayerNum)
//{
// switch( gGameOptions.ubDifficultyLevel )
// {
// case DIF_LEVEL_EASY:
// sBreathChange = ((APBPConstants[AP_MAXIMUM] + gGameExternalOptions.iEasyAPBonus)- sUnusedAPs) * sBreathPerAP;
// break;
// case DIF_LEVEL_MEDIUM:
// sBreathChange = ((APBPConstants[AP_MAXIMUM] + gGameExternalOptions.iExperiencedAPBonus)-sUnusedAPs) * sBreathPerAP;
// break;
// case DIF_LEVEL_HARD:
// sBreathChange = ((APBPConstants[AP_MAXIMUM] + gGameExternalOptions.iExpertAPBonus)- sUnusedAPs) * sBreathPerAP;
// break;
// case DIF_LEVEL_INSANE:
// sBreathChange = ((APBPConstants[AP_MAXIMUM] + gGameExternalOptions.iInsaneAPBonus)- sUnusedAPs) * sBreathPerAP;
// break;
// default:
// sBreathChange = (APBPConstants[AP_MAXIMUM] - sUnusedAPs) * sBreathPerAP;
// }
//}
//else
//{
// sBreathChange = ((APBPConstants[AP_MAXIMUM] + gGameExternalOptions.iPlayerAPBonus)- sUnusedAPs) * sBreathPerAP;
//}
// SANDRO: if we want to make difference between difficulty levels, then just make it so with higher difficulty the enemy regenerate faster:
if ( pSoldier->bTeam == ENEMY_TEAM )
{
switch( gGameOptions.ubDifficultyLevel )
{
case DIF_LEVEL_EASY:
sBreathPerAP = sBreathPerAP * 9/10; // -10%
break;
case DIF_LEVEL_MEDIUM:
sBreathPerAP = sBreathPerAP; // normal
break;
case DIF_LEVEL_HARD:
sBreathPerAP = sBreathPerAP * 11/10; // +10%
break;
case DIF_LEVEL_INSANE:
sBreathPerAP = sBreathPerAP * 6/5; // +20%
break;
default:
sBreathPerAP = sBreathPerAP * 9/10; // -10%
break;
}
}
// SANDRO: this simple thing is the correct calculation
sBreathChange = (sUnusedAPs * sBreathPerAP);
// SANDRO: get BP *cost* for weapon holding (reduce breath gain, or even make it breath loss)
if ( gGameExternalOptions.ubEnergyCostForWeaponWeight )
{
sBreathChange += sUnusedAPs * GetBPCostPer10APsForGunHolding( pSoldier ) / 10;
// if we are actually losing breath by holding a very heavy gun up, lower it if breath already at critical line
if ( sBreathChange > 0 && pSoldier->bBreath < OKBREATH )
{
// ok, if this gun is rather heavy, and cost us at least 3 energy points per turn
if ( (GetBPCostPer10APsForGunHolding( pSoldier ) * 10) >= (300 * gGameExternalOptions.ubEnergyCostForWeaponWeight / 100) )
{
// throw quote
if ( !(pSoldier->usQuoteSaidFlags & SOLDIER_QUOTE_SAID_LOW_BREATH ) )
{
TacticalCharacterDialogue( pSoldier, QUOTE_OUT_OF_BREATH );
pSoldier->usQuoteSaidFlags |= SOLDIER_QUOTE_SAID_LOW_BREATH;
}
// Put gun down....
pSoldier->InternalSoldierReadyWeapon(pSoldier->ubDirection, TRUE, FALSE );
}
}
}
}
else
{
sBreathChange = 0;
}
// Adjust for on drugs
HandleBPEffectDueToDrugs( pSoldier, &sBreathChange );
// SANDRO - Penalty for breath regain for being hit by Martial artist
if ((pSoldier->lUnregainableBreath > 0) && (sBreathChange < 0) && gGameOptions.fNewTraitSystem)
{
if (pSoldier->lUnregainableBreath >= (0 - sBreathChange))
{
pSoldier->lUnregainableBreath += sBreathChange;
sBreathChange = 0;
}
else
{
sBreathChange += (INT16)(pSoldier->lUnregainableBreath);
pSoldier->lUnregainableBreath = 0;
}
}
// adjust breath only, don't touch action points!
DeductPoints(pSoldier,0,(INT16)sBreathChange );
}
}
INT16 GetBreathPerAP( SOLDIERTYPE *pSoldier, UINT16 usAnimState )
{
INT16 sBreathPerAP = 0;
BOOLEAN fAnimTypeFound = FALSE;
if ( gAnimControl[ usAnimState ].uiFlags & ANIM_VARIABLE_EFFORT )
{
// Default effort
sBreathPerAP = APBPConstants[BP_PER_AP_MIN_EFFORT];
// OK, check if we are in water and are waling/standing
if ( 0 && pSoldier->MercInWater( ) )
{
switch( usAnimState )
{
case STANDING:
sBreathPerAP = APBPConstants[BP_PER_AP_LT_EFFORT];
break;
case WALKING:
sBreathPerAP = APBPConstants[BP_PER_AP_MOD_EFFORT];
break;
}
}
else
{
switch( usAnimState )
{
case STANDING:
sBreathPerAP = APBPConstants[BP_PER_AP_NO_EFFORT];
break;
case WALKING:
case WALKING_WEAPON_RDY:
case WALKING_DUAL_RDY:
case WALKING_ALTERNATIVE_RDY:
sBreathPerAP = APBPConstants[BP_PER_AP_LT_EFFORT];
break;
}
}
fAnimTypeFound = TRUE;
}
switch ( EFFORT( gAnimControl[ usAnimState ].uiFlags) ) {
case 0: // No effort
sBreathPerAP = APBPConstants[BP_PER_AP_NO_EFFORT];
fAnimTypeFound = TRUE;
break;
case 1: // Minimal effort
sBreathPerAP = APBPConstants[BP_PER_AP_MIN_EFFORT];
fAnimTypeFound = TRUE;
break;
case 2: // Light effort
sBreathPerAP = APBPConstants[BP_PER_AP_LT_EFFORT];
fAnimTypeFound = TRUE;
break;
case 3: // Moderate effort
sBreathPerAP = APBPConstants[BP_PER_AP_MOD_EFFORT];
fAnimTypeFound = TRUE;
break;
}
if ( !fAnimTypeFound )
{
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Unknown end-of-turn breath anim: %s", gAnimControl[ usAnimState ].zAnimStr ) );
}
//rain
// Reduce breath gain on 25%/rain intensity
// Lalien: only for soldiers that are in loaded sector,
if ( gfWorldLoaded && pSoldier->bInSector && !pSoldier->bSectorZ )
{
if( sBreathPerAP < 0 && ( pSoldier->pathing.bLevel || !FindStructure( pSoldier->sGridNo, STRUCTURE_ROOF ) ) && pSoldier->bBreath > 1)
{
FLOAT weatherpenalty = gGameExternalOptions.dBreathGainReduction[SectorInfo[SECTOR( pSoldier->sSectorX, pSoldier->sSectorY )].usWeather];
// Added a feature to reduce rain effect on regaining breath with Ranger trait - SANDRO
FLOAT appliedpenalty = 1.0f;
if ( HAS_SKILL_TRAIT( pSoldier, SURVIVAL_NT ) && gGameOptions.fNewTraitSystem )
appliedpenalty = min( 1.0f, max( 0.0f, appliedpenalty - gSkillTraitValues.dSVWeatherPenaltiesReduction * NUM_SKILL_TRAITS( pSoldier, SURVIVAL_NT ) ) );
sBreathPerAP -= (INT16)(sBreathPerAP * weatherpenalty * appliedpenalty);
}
}
//end rain
return( sBreathPerAP );
}
//UINT8 CalcAPsToBurst( INT8 bBaseActionPoints, UINT16 usItem )
INT16 CalcAPsToBurst( INT16 bBaseActionPoints, OBJECTTYPE * pObj, SOLDIERTYPE* pSoldier )
{
INT32 aps, iModifiedAPs;
iModifiedAPs = Weapon[ pObj->usItem ].bBurstAP;
// modify by ini values
if ( Item[ pObj->usItem ].usItemClass == IC_GUN )
iModifiedAPs *= gItemSettings.fBurstAPModifierGun[ Weapon[ pObj->usItem ].ubWeaponType ];
// base APs is what you'd get from CalcActionPoints();
// NB round UP, so 21-25 APs pay full
aps = ( iModifiedAPs * bBaseActionPoints + (APBPConstants[AP_MAXIMUM] - 1) ) / APBPConstants[AP_MAXIMUM];
/*if ( GetPercentBurstFireAPReduction(pObj)>0 )
{
aps = (aps * 100) / (100 + GetBurstFireAPReductionStatus(pObj) / (100/GetPercentBurstFireAPReduction(pObj)));
}
if ( GetPercentAPReduction(pSoldier, pObj)>0 )
{
aps = (aps * 100) / (100 + GetAPReductionStatus(pObj) / (100/GetPercentAPReduction(pSoldier, pObj)));
}*/
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcAPsToBurst: before bonus aps = %d, std bonus = %d, burst bonus = %d", aps,GetPercentAPReduction(pSoldier, pObj),GetPercentBurstFireAPReduction(pObj)));
// Snap: do this a little differently: % reduction means
// aps <- aps * ( 100 - red ) / 100
aps = ( aps * ( 100 - GetPercentAPReduction(pSoldier, pObj) ) ) / 100;
// Snap: moved this up to allow burst AP reduction up to 100%
aps = __max( aps, ( iModifiedAPs + 1 ) / 2 );
aps = ( aps * ( 100 - GetPercentBurstFireAPReduction(pObj) ) ) / 100;
if ( aps < 0 ) aps = 0;
else if ( aps > APBPConstants[AP_MAXIMUM] ) aps = APBPConstants[AP_MAXIMUM];
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcAPsToBurst: return aps = %d", aps));
return (UINT8) aps;
}
// HEADROCK HAM 4: Same as above, without percent modifiers.
INT16 CalcAPsToBurstNoModifier( INT16 bBaseActionPoints, UINT16 usItem )
{
INT32 aps, iModifiedAPs;
iModifiedAPs = Weapon[ usItem ].bBurstAP;
// modify by ini values
// although the function says "no modifier" the ini values represent our new base value
if ( Item[ usItem ].usItemClass == IC_GUN )
iModifiedAPs *= gItemSettings.fBurstAPModifierGun[ Weapon[ usItem ].ubWeaponType ];
aps = ( iModifiedAPs * bBaseActionPoints + (APBPConstants[AP_MAXIMUM] - 1) ) / APBPConstants[AP_MAXIMUM];
aps = __max( aps, ( iModifiedAPs + 1 ) / 2 );
if ( aps < 0 ) aps = 0;
else if ( aps > APBPConstants[AP_MAXIMUM] ) aps = APBPConstants[AP_MAXIMUM];
return (UINT8) aps;
}
INT16 CalcAPsToBurstNoModifier( INT16 bBaseActionPoints, OBJECTTYPE * pObj )
{
return CalcAPsToBurstNoModifier( bBaseActionPoints, pObj->usItem );
}
INT16 CalcAPsToAutofire( INT16 bBaseActionPoints, OBJECTTYPE * pObj, UINT8 bDoAutofire, SOLDIERTYPE* pSoldier )
{
//CHRISL: We send the actual number of rounds being fired in the bDoAutofire paramter. But that implies that it would take longer to fire the first round
// in an autofire sequence then it would take to fire a single round during a single fire sequence. That doesn't make any sense. It should only cost
// additional APs to fire rounds beyond the first in an autofire sequence. But rather then mess with all the locations that call this function, I'm going
// to simply adjust the parameter once we get to the function.
if(bDoAutofire > 1)
bDoAutofire -= 1;
else
return 0;
// INT8 bAttachPos;
INT32 aps=APBPConstants[AP_MAXIMUM] + 1;
if ( GetAutofireShotsPerFiveAPs (pObj) )
{
// Snap: Do this just like in CalcAPsToBurst
const INT32 autofireaps =
aps = ( ( APBPConstants[AUTOFIRE_SHOTS_AP_VALUE] * bDoAutofire * bBaseActionPoints ) / GetAutofireShotsPerFiveAPs(pObj) + (APBPConstants[AP_MAXIMUM] - 1) ) / APBPConstants[AP_MAXIMUM];
//DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcAPsToAutofire: base aps = %d, # shots = %d",aps,pSoldier->bDoAutofire ));
//check for spring and bolt
//bAttachPos = FindAttachment( &(pSoldier->inv[HANDPOS]), SPRING_AND_BOLT_UPGRADE );
//if ( bAttachPos != -1 )
//{
// aps = (aps * 100) / (100 + pSoldier->inv[HANDPOS].bAttachStatus[ bAttachPos ] / 5);
// DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcAPsToAutofire: found rod and spring, aps = %d, # shots = %d",aps,pSoldier->bDoAutofire ));
//}
//bAttachPos = FindAttachment( &(pSoldier->inv[HANDPOS]), REFLEX_SCOPED );
//if ( bAttachPos != -1 )
//{
// aps = (aps * 100) / (100 + pSoldier->inv[HANDPOS].bAttachStatus[ bAttachPos ] / 5);
// DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcAPsToAutofire: found reflex scope, aps = %d, # shots = %d",aps,pSoldier->bDoAutofire ));
//}
//bAttachPos = FindAttachment( &(pSoldier->inv[HANDPOS]), REFLEX_UNSCOPED );
//if ( bAttachPos != -1 )
//{
// aps = ( aps * 100 ) / (100 + pSoldier->inv[HANDPOS].bAttachStatus[ bAttachPos ] / 5);
// DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcAPsToAutofire: found reflex sight, aps = %d, # shots = %d",aps,pSoldier->bDoAutofire ));
//}
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcAPsToAutoFire: before bonus aps = %d, std bonus = %d, auto bonus = %d", aps,GetPercentAPReduction(pSoldier, pObj),GetPercentAutofireAPReduction(pObj)));
aps = ( aps * ( 100 - GetPercentAPReduction(pSoldier, pObj) ) ) / 100;
aps = __max( aps, ( autofireaps + 1 ) / 2 );
// Snap: extend the burst bonus to autofire as well?
aps = ( aps * ( 100 - GetPercentAutofireAPReduction(pObj) ) ) / 100;
if ( aps < 0 ) aps = 0;
//CHRISL: This can cause problem if a player uses any of the xxx_AP_BONUS values in the INI file.
//else if ( aps > APBPConstants[AP_MAXIMUM] ) aps = APBPConstants[AP_MAXIMUM];
// HEADROCK HAM 4: But we NEED an upper cap, so here it is:
else
aps = __min(255,aps);
}
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcAPsToAutoFire: return aps = %d", aps));
return (UINT8) aps;
}
// HEADROCK HAM 4: Same as above, without modifiers
INT16 CalcAPsToAutofireNoModifier( INT16 bBaseActionPoints, UINT16 usItem, UINT8 bDoAutofire )
{
INT32 aps = APBPConstants[AP_MAXIMUM] + 1;
if ( GetAutofireShotsPerFiveAPs ( usItem ) )
{
const INT32 autofireaps =
aps = ( ( APBPConstants[AUTOFIRE_SHOTS_AP_VALUE] * bDoAutofire * bBaseActionPoints ) / GetAutofireShotsPerFiveAPs( usItem ) + (APBPConstants[AP_MAXIMUM] - 1) ) / APBPConstants[AP_MAXIMUM];
aps = __max( aps, ( autofireaps + 1 ) / 2 );
if ( aps < 0 ) aps = 0;
}
return (UINT8) aps;
}
INT16 CalcAPsToAutofireNoModifier( INT16 bBaseActionPoints, OBJECTTYPE * pObj, UINT8 bDoAutofire )
{
return CalcAPsToAutofireNoModifier( bBaseActionPoints, pObj->usItem, bDoAutofire );
}
INT16 CalcTotalAPsToAttack( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubAddTurningCost, INT16 bAimTime )
{
UINT16 sAPCost = 0;
UINT16 usItemNum;
INT32 sActionGridNo;
UINT8 ubDirection;
INT32 sAdjustedGridNo;
UINT32 uiItemClass;
BOOLEAN fAddingTurningCost = FALSE;
BOOLEAN fAddingRaiseGunCost = FALSE;
// LOOK IN BUDDY'S HAND TO DETERMINE WHAT TO DO HERE
// Flugente: check for underbarrel weapons and use that object if necessary
OBJECTTYPE* AttackingWeapon = pSoldier->GetUsedWeapon( &(pSoldier->inv[HANDPOS]) );
UINT16 usUBItemNum = pSoldier->GetUsedWeaponNumber( &(pSoldier->inv[HANDPOS]) );
usItemNum = pSoldier->inv[HANDPOS].usItem;
uiItemClass = Item[ usUBItemNum ].usItemClass;
if ( uiItemClass == IC_GUN || uiItemClass == IC_LAUNCHER || uiItemClass == IC_THROWING_KNIFE )
{
sAPCost = MinAPsToAttack( pSoldier, sGridNo, ubAddTurningCost, bAimTime );
if ( pSoldier->bDoBurst )
{
if(pSoldier->bDoAutofire && GetAutofireShotsPerFiveAPs(AttackingWeapon) > 0 )
sAPCost += CalcAPsToAutofire( pSoldier->CalcActionPoints( ), AttackingWeapon, pSoldier->bDoAutofire, pSoldier );
else
sAPCost += CalcAPsToBurst( pSoldier->CalcActionPoints( ), AttackingWeapon, pSoldier );
}
//else //ddd comment for aimed burst
{
if (gGameExternalOptions.fIncreasedAimingCost )
{
// HEADROCK HAM B2.6: Changed the number of APs to attack when aiming.
if (bAimTime > 0)
{
GetAPChargeForShootOrStabWRTGunRaises( pSoldier, sGridNo, ubAddTurningCost, &fAddingTurningCost, &fAddingRaiseGunCost, bAimTime );
if(fAddingRaiseGunCost == TRUE)
{
// HEADROCK HAM 3: No idea what should come here... For now I've put my extra gun-raise costs
// outside this IF (see below).
}
// HEADROCK HAM 3: One-time penalty: Add part of the weapon's Ready AP cost. Reinstated because
// it's now externalized.
if (gGameExternalOptions.ubFirstAimReadyCostDivisor > 0)
{
UINT16 usWeaponReadyTime;
UINT8 ubReadyTimeDivisor;
usWeaponReadyTime = Weapon[ pSoldier->inv[HANDPOS].usItem ].ubReadyTime * (100 - GetPercentReadyTimeAPReduction(&pSoldier->inv[HANDPOS])) / 100;
ubReadyTimeDivisor = gGameExternalOptions.ubFirstAimReadyCostDivisor;
sAPCost += usWeaponReadyTime / ubReadyTimeDivisor;
}
// If the weapon has a scope, and the target is within eligible range for scope use
if ( (UsingNewCTHSystem() == false && IsScoped(&pSoldier->inv[HANDPOS]) && GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sGridNo ) >= GetMinRangeForAimBonus( pSoldier, &pSoldier->inv[HANDPOS]) && !pSoldier->IsValidAlternativeFireMode(bAimTime,sGridNo))
|| (UsingNewCTHSystem() == true && GetBestScopeMagnificationFactor(pSoldier, &pSoldier->inv[HANDPOS], (FLOAT)GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sGridNo ) > 1.0 ) && !pSoldier->IsValidAlternativeFireMode(bAimTime,sGridNo)) )
{
// Add an individual cost for EACH click, as necessary.
sAPCost += APBPConstants[AP_FIRST_CLICK_AIM_SCOPE];
if (bAimTime > 1)
sAPCost += APBPConstants[AP_SECOND_CLICK_AIM_SCOPE];
if (bAimTime > 2)
sAPCost += APBPConstants[AP_THIRD_CLICK_AIM_SCOPE];
if (bAimTime > 3)
sAPCost += APBPConstants[AP_FOURTH_CLICK_AIM_SCOPE];
if (bAimTime > 4)
sAPCost += APBPConstants[AP_FIFTH_CLICK_AIM_SCOPE];
if (bAimTime > 5)
sAPCost += APBPConstants[AP_SIXTH_CLICK_AIM_SCOPE];
if (bAimTime > 6)
sAPCost += APBPConstants[AP_SEVENTH_CLICK_AIM_SCOPE];
if (bAimTime > 7)
sAPCost += APBPConstants[AP_EIGHTH_CLICK_AIM_SCOPE];
}
// Weapon has no scope or not within requried range. Apply regular AP costs.
else
{
sAPCost += bAimTime * APBPConstants[AP_CLICK_AIM];
}
}
}
// Regular cost system
else
{
sAPCost += bAimTime * APBPConstants[AP_CLICK_AIM];
}
}
}
//ATE: HERE, need to calculate APs!
if ( uiItemClass & IC_EXPLOSV )
{
sAPCost = MinAPsToAttack( pSoldier, sGridNo, ubAddTurningCost, bAimTime, 0 );
//sAPCost = 5;
}
if ( uiItemClass == IC_PUNCH || uiItemClass == IC_BLADE || uiItemClass == IC_TENTACLES )//dnl ch73 031013, silversurfer: tentacles are melee too
{
// IF we are at this gridno, calc min APs but if not, calc cost to goto this location
if ( pSoldier->sGridNo != sGridNo )
{
sAdjustedGridNo = NOWHERE;
// See if we can get there to stab
if (pSoldier->ubBodyType == BLOODCAT)
{
sActionGridNo = FindNextToAdjacentGridEx(pSoldier, sGridNo, &ubDirection, &sAdjustedGridNo, TRUE, FALSE);
}
else if (CREATURE_OR_BLOODCAT(pSoldier) && PythSpacesAway(pSoldier->sGridNo, sGridNo) > 1)
{
sActionGridNo = FindNextToAdjacentGridEx(pSoldier, sGridNo, &ubDirection, &sAdjustedGridNo, TRUE, FALSE);
if (sActionGridNo == -1)
{
sActionGridNo = FindAdjacentGridEx(pSoldier, sGridNo, &ubDirection, &sAdjustedGridNo, TRUE, FALSE);
}
}
else
{
// See if we can get there to stab
sActionGridNo = FindAdjacentGridEx(pSoldier, sGridNo, &ubDirection, &sAdjustedGridNo, TRUE, FALSE, true);
}
if (!TileIsOutOfBounds(sActionGridNo))
{
if (pSoldier->sGridNo == sActionGridNo)
{
pSoldier->sWalkToAttackWalkToCost = 0;
}
else
{
// OK, in order to avoid path calculations here all the time... save and check if it's changed!
if (pSoldier->sWalkToAttackGridNo == sActionGridNo && pSoldier->sWalkToAttackMovementMode == (UINT8)pSoldier->usUIMovementMode)
{
sAPCost += (UINT8)(pSoldier->sWalkToAttackWalkToCost);
}
else
{
// Save for next time...
pSoldier->sWalkToAttackMovementMode = (UINT8)pSoldier->usUIMovementMode;
pSoldier->sWalkToAttackWalkToCost = PlotPath(pSoldier, sActionGridNo, NO_COPYROUTE, NO_PLOT, TEMPORARY, (UINT16)pSoldier->usUIMovementMode, NOT_STEALTH, FORWARD, pSoldier->bActionPoints);
pSoldier->sWalkToAttackEndDirection = gfPlotPathEndDirection;
if (pSoldier->sWalkToAttackWalkToCost == 0)
{
return(99);
}
sAPCost += pSoldier->sWalkToAttackWalkToCost;
}
}
}
else
{
return( 0 );
}
// Save old location!
pSoldier->sWalkToAttackGridNo = sActionGridNo;
}
// Add points to attack
sAPCost += MinAPsToPunch(pSoldier, sAdjustedGridNo);
// Add points to turn around if needed
sAPCost += CalculateActionTurningCost(pSoldier, sActionGridNo, sAdjustedGridNo, pSoldier->sWalkToAttackEndDirection);
// Add aim time...
sAPCost += (bAimTime*APBPConstants[AP_CLICK_AIM]);
}
return( sAPCost );
}
INT16 MinAPsToAttack(SOLDIERTYPE *pSoldier, INT32 sGridno, UINT8 ubAddTurningCost, INT16 bAimTime, UINT8 ubForceRaiseGunCost )
{
INT16 sAPCost = 0;
UINT32 uiItemClass;
if ( pSoldier->bWeaponMode == WM_ATTACHED_GL || pSoldier->bWeaponMode == WM_ATTACHED_GL_BURST || pSoldier->bWeaponMode == WM_ATTACHED_GL_AUTO )
{
INT16 glItem;
// look for an attached grenade launcher
glItem = GetAttachedGrenadeLauncher( &(pSoldier->inv[ HANDPOS ]) );
if ( glItem == NONE )
{
// default to hand
// LOOK IN BUDDY'S HAND TO DETERMINE WHAT TO DO HERE
uiItemClass = Item[ pSoldier->inv[HANDPOS].usItem ].usItemClass;
}
else
{
uiItemClass = Item[ glItem ].usItemClass;
}
}
else
{
UINT16 undbarItem = pSoldier->GetUsedWeaponNumber( &(pSoldier->inv[ HANDPOS ]) );
// LOOK IN BUDDY'S HAND TO DETERMINE WHAT TO DO HERE
uiItemClass = Item[ undbarItem ].usItemClass;
}
if(uiItemClass & (IC_GUN | IC_LAUNCHER | IC_THROWING_KNIFE))
sAPCost = MinAPsToShootOrStab(pSoldier, sGridno, bAimTime, ubAddTurningCost, ubForceRaiseGunCost);
else if(uiItemClass & (IC_GRENADE | IC_THROWN))
sAPCost = MinAPsToThrow(pSoldier, sGridno, ubAddTurningCost);
return sAPCost;
}
INT16 CalcAimSkill( SOLDIERTYPE * pSoldier, UINT16 usWeapon )
{
INT16 bAimSkill;
if ( Item[ usWeapon ].usItemClass == IC_GUN || Item[ usWeapon ].usItemClass == IC_LAUNCHER )
{
// GUNS: modify aiming cost by shooter's MARKSMANSHIP
bAimSkill = EffectiveMarksmanship( pSoldier );
}
else
// for now use this for all other weapons
//if ( Item[ usInHand ].usItemClass == IC_BLADE )
{
// KNIVES: modify aiming cost by avg of attacker's DEXTERITY & AGILITY
bAimSkill = ( EffectiveDexterity( pSoldier, FALSE ) + EffectiveAgility( pSoldier, FALSE ) ) / 2;
//return( 4 );
}
return( bAimSkill );
}
INT16 BaseAPsToShootOrStab( INT16 bAPs, INT16 bAimSkill, OBJECTTYPE * pObj, SOLDIERTYPE* pSoldier )
{
INT32 Top, Bottom;
FLOAT rof;
// INT8 bAttachPos;
//CHRISL: By changing rof to a FLOAT value, we resolve some of the rounding that causes APs to be skipped. For example:
// assuming bAPs = 80 (default in a 100Ap system) and bAimSkill = 80 (also a default setting)
// If rof = 15, we return an AP cost of 24. If rof = 16, we return an AP cost of 22. With an integer value, we can't
// get an AP cost of 23. But if rof = 15.5, we return an AP cost of 23 (22.94 which gets rounded up). With an
// rof = 15.46, we return an AP cost of exactly 23. So this will allow for fine control over "default" AP costs.
// Calculate default top & bottom of the magic "aiming" formula!
// Formula: AP to Shoot% = (maxAP_soldier) / ((MAXAIM*0.5 + aimSkill_soldier*0.5) * ROF_PER4TURNS / 4)
// AP to Shoot = (AP to Shoot%) * MAXAP
// get this man's maximum possible action points (ignoring carryovers)
// the 2 times is here only to allow rounding off using integer math later
//Top = 2 * bAPs;//pSoldier->CalcActionPoints( );
// Shots per turn rating is for max. aimSkill(100), drops down to 1/2 at = 0
// DIVIDE BY 4 AT THE END HERE BECAUSE THE SHOTS PER TURN IS NOW QUADRUPLED!
// NB need to define shots per turn for ALL Weapons then.
// 0verhaul: Do not calculate rate of fire bonus for stackable items (knives)
// Their info is an array of item status, not weapon info, and they don't repeat
// fire anyway.
rof = Weapon[ pObj->usItem ].ubShotsPer4Turns;
// modify by ini values
if ( Item[ pObj->usItem ].usItemClass == IC_GUN )
rof *= gItemSettings.fShotsPer4TurnsModifierGun[ Weapon[ pObj->usItem ].ubWeaponType ];
else if ( Item[ pObj->usItem ].usItemClass == IC_LAUNCHER )
rof *= gItemSettings.fShotsPer4TurnsModifierLauncher;
else if ( Item[ pObj->usItem ].usItemClass == IC_BLADE )
rof *= gItemSettings.fShotsPer4TurnsModifierBlade;
else if ( Item[ pObj->usItem ].usItemClass == IC_PUNCH )
rof *= gItemSettings.fShotsPer4TurnsModifierPunch;
else if ( Item[ pObj->usItem ].usItemClass == IC_TENTACLES )
rof *= gItemSettings.fShotsPer4TurnsModifierTentacle;
else if ( Item[ pObj->usItem ].usItemClass == IC_THROWING_KNIFE )
rof *= gItemSettings.fShotsPer4TurnsModifierThrowKnife;
//CHRISL: Resolves a problem with stackable weapons not using attachments like Reflex sight. Found by Mugsy
//if (ItemSlotLimit(pObj, STACK_SIZE_LIMIT) == 1)//NOT STACKABLE!
// be sure to only check weapons
if ( Item[pObj->usItem].usItemClass & ( IC_WEAPON | IC_PUNCH ) )
{
rof += GetRateOfFireBonus(pObj);
}
//Bottom = ( ( 50 + (bAimSkill / 2) ) * rof ) / 4;
// add minimum aiming time to the overall minimum AP_cost
// This here ROUNDS UP fractions of 0.5 or higher using integer math
// This works because 'top' is 2x what it really should be throughout
//return( ( ( ( 100 * Top ) / Bottom ) + 1) / 2);
// Snap: Refactored the formula to reduce the number of integer divisions
Top = 8 * bAPs;
//CHRISL: Resolves a problem with stackable weapons not using attachments like Reflex sight. Found by Mugsy
//if (ItemSlotLimit(pObj, STACK_SIZE_LIMIT) == 1)
{
Top *= ( 100 - GetPercentAPReduction(pSoldier, pObj) );
}
//else
//{
// Top *= 100;
//}
// WANNE : Fixed CTD that occurs when trowing item (grenade, throwing knife, ...)
// with open description box in tactical
INT16 baseAPsToShootOrStab = -1;
if (rof > 0)
{
Bottom = (INT32)(( 100 + bAimSkill ) * rof);
baseAPsToShootOrStab = ( Top + Bottom / 2 ) / Bottom;
}
// (this will round to the nearest integer)
return baseAPsToShootOrStab;
}
// HEADROCK HAM 4: Same function as above, except no modifier.
INT16 BaseAPsToShootOrStabNoModifier( INT16 bAPs, INT16 bAimSkill, UINT16 usItem )
{
INT32 Top, Bottom;
FLOAT rof;
rof = Weapon[usItem].ubShotsPer4Turns;
// modify by ini values
// although this function says "no modifiers" our ini values are supposed to provide new base values
if ( Item[usItem].usItemClass == IC_GUN )
rof *= gItemSettings.fShotsPer4TurnsModifierGun[Weapon[usItem].ubWeaponType];
else if ( Item[usItem].usItemClass == IC_LAUNCHER )
rof *= gItemSettings.fShotsPer4TurnsModifierLauncher;
else if ( Item[usItem].usItemClass == IC_BLADE )
rof *= gItemSettings.fShotsPer4TurnsModifierBlade;
else if ( Item[usItem].usItemClass == IC_PUNCH )
rof *= gItemSettings.fShotsPer4TurnsModifierPunch;
else if ( Item[usItem].usItemClass == IC_TENTACLES )
rof *= gItemSettings.fShotsPer4TurnsModifierTentacle;
else if ( Item[usItem].usItemClass == IC_THROWING_KNIFE )
rof *= gItemSettings.fShotsPer4TurnsModifierThrowKnife;
Top = 8 * bAPs * 100;
INT16 baseAPsToShootOrStab = -1;
if (rof > 0)
{
Bottom = (INT32)(( 100 + bAimSkill ) * rof);
baseAPsToShootOrStab = ( Top + Bottom / 2 ) / Bottom;
}
return baseAPsToShootOrStab;
}
INT16 BaseAPsToShootOrStabNoModifier( INT16 bAPs, INT16 bAimSkill, OBJECTTYPE * pObj )
{
return BaseAPsToShootOrStabNoModifier( bAPs, bAimSkill, pObj->usItem );
}
void GetAPChargeForShootOrStabWRTGunRaises( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubAddTurningCost, BOOLEAN *pfChargeTurning, BOOLEAN *pfChargeRaise, INT16 bAimTime )
{
UINT8 ubDirection;
UINT32 uiMercFlags;
UINT16 usTargID;
BOOLEAN fAddingTurningCost = FALSE;
BOOLEAN fAddingRaiseGunCost = FALSE;
if (!TileIsOutOfBounds(sGridNo))
{
// OK, get a direction and see if we need to turn...
if (ubAddTurningCost)
{
// Given a gridno here, check if we are on a guy - if so - get his gridno
if ( FindSoldier( sGridNo, &usTargID, &uiMercFlags, FIND_SOLDIER_GRIDNO ) )
{
sGridNo = MercPtrs[ usTargID ]->sGridNo;
}
ubDirection = (UINT8)GetDirectionFromGridNo( sGridNo, pSoldier );
// Is it the same as he's facing?
if ( ubDirection != pSoldier->ubDirection && !(ubDirection == pSoldier->pathing.bDesiredDirection && pSoldier->aiData.bLastAction == AI_ACTION_CHANGE_FACING) )//dnl ch64 310813 sometimes turning is in progress and APs already deducted
{
fAddingTurningCost = TRUE;
}
}
}
else
{
if (ubAddTurningCost)
{
// Assume we need to add cost!
fAddingTurningCost = TRUE;
}
}
if ( Item[ pSoldier->inv[ HANDPOS ].usItem ].usItemClass == IC_THROWING_KNIFE )
{
}
else
{
// SANDRO - alternative weapon holding feature
if ( gGameExternalOptions.ubAllowAlternativeWeaponHolding == 3 )
{
if ( pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD && !( gAnimControl[ pSoldier->usAnimState ].uiFlags &( ANIM_ALT_WEAPON_HOLDING ) ))
{
fAddingRaiseGunCost = TRUE;
}
else if ( pSoldier->bScopeMode != USE_ALT_WEAPON_HOLD && ( gAnimControl[ pSoldier->usAnimState ].uiFlags &( ANIM_ALT_WEAPON_HOLDING ) || !( gAnimControl[ pSoldier->usAnimState ].uiFlags &( ANIM_FIREREADY | ANIM_FIRE ) ) ))
{
fAddingRaiseGunCost = TRUE;
}
}
else if ( gGameExternalOptions.ubAllowAlternativeWeaponHolding == 2 )
{
if ( !( gAnimControl[ pSoldier->usAnimState ].uiFlags &( ANIM_FIREREADY | ANIM_FIRE ) ) ||
((gAnimControl[ pSoldier->usAnimState ].uiFlags &( ANIM_ALT_WEAPON_HOLDING )) &&
(bAimTime > GetNumberAltFireAimLevels( pSoldier, sGridNo )) ))
{
fAddingRaiseGunCost = TRUE;
}
}
else if ( gGameExternalOptions.ubAllowAlternativeWeaponHolding == 1 )
{
if ( !( gAnimControl[ pSoldier->usAnimState ].uiFlags &( ANIM_FIREREADY | ANIM_FIRE ) ) ||
((gAnimControl[ pSoldier->usAnimState ].uiFlags &( ANIM_ALT_WEAPON_HOLDING )) &&
(bAimTime > 0) ))
{
fAddingRaiseGunCost = TRUE;
}
}
else
{
if ( !( gAnimControl[ pSoldier->usAnimState ].uiFlags &( ANIM_FIREREADY | ANIM_FIRE ) ) )
{
fAddingRaiseGunCost = TRUE;
}
}
//CHRISL: Handle ready weapon with facing changes based on stance
if(fAddingTurningCost && !fAddingRaiseGunCost)
{
switch (gAnimControl[ pSoldier->usAnimState ].ubHeight)
{
case ANIM_STAND:
fAddingRaiseGunCost = GetDirectionChangeAmount(sGridNo, pSoldier, 3);
break;
case ANIM_CROUCH:
fAddingRaiseGunCost = GetDirectionChangeAmount(sGridNo, pSoldier, 1);
break;
case ANIM_PRONE:
fAddingRaiseGunCost = TRUE;
break;
}
}
}
(*pfChargeTurning) = fAddingTurningCost;
(*pfChargeRaise ) = fAddingRaiseGunCost;
}
UINT16 CalculateActionTurningCost(SOLDIERTYPE *pSoldier, INT32 sActionGridNo, INT32 sAdjustedGridNo, UINT8 ubDirection)
{
UINT16 sAPCost = 0;
// if soldier is already at sActionGridNo, use current direction instead of calculated one
if (pSoldier->sGridNo == sActionGridNo) ubDirection = pSoldier->ubDirection;
// Is it the same as direction we need?
if (ubDirection != GetDirectionToGridNoFromGridNo(sActionGridNo, sAdjustedGridNo))
{
OBJECTTYPE *pObjUsed = pSoldier->GetUsedWeapon(&pSoldier->inv[HANDPOS]);
UINT16 usItem = pObjUsed->usItem;
if (gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE)
sAPCost += CalculateTurningCost(pSoldier, usItem, TRUE, ANIM_CROUCH);
else
sAPCost += CalculateTurningCost(pSoldier, usItem, TRUE);
}
return sAPCost;
}
UINT16 CalculateTurningCost(SOLDIERTYPE *pSoldier, UINT16 usItem, BOOLEAN fAddingTurningCost, INT8 bDesiredHeight)//dnl ch72 190913
{
UINT16 usTrueAnimState, usTurningCost = 0;
if(fAddingTurningCost)
{
usTrueAnimState = pSoldier->usAnimState;
switch(bDesiredHeight)
{
case ANIM_STAND:
pSoldier->usAnimState = STANDING;
break;
case ANIM_CROUCH:
pSoldier->usAnimState = CROUCHING;
break;
case ANIM_PRONE:
pSoldier->usAnimState = PRONE;
break;
default:
break;
}
if(Item[usItem].usItemClass == IC_THROWING_KNIFE)
pSoldier->usAnimState = STANDING;
usTurningCost = GetAPsToLook(pSoldier);
pSoldier->usAnimState = usTrueAnimState;
}
return(usTurningCost);
}
UINT16 CalculateRaiseGunCost(SOLDIERTYPE *pSoldier, BOOLEAN fAddingRaiseGunCost, INT32 iTargetGridNum, INT16 bAimTime )
{
UINT16 usRaiseGunCost = 0, usAnimState;
if (fAddingRaiseGunCost )
{
BOOLEAN fAltFireMode = FALSE;
if ( gGameExternalOptions.ubAllowAlternativeWeaponHolding )
{
if ( gGameExternalOptions.ubAllowAlternativeWeaponHolding == 3 )//dnl ch69 130913 condition (&& pSoldier->bTeam == gbPlayerNum) must be out because prevent AI from doing alternate weapon holding
{
if ( pSoldier->bScopeMode == -1 )
fAltFireMode = TRUE;
}
else if ( gGameExternalOptions.ubAllowAlternativeWeaponHolding == 1 )
{
if ( bAimTime == 0 )
fAltFireMode = TRUE;
}
else
{
if ( bAimTime <= GetNumberAltFireAimLevels( pSoldier, iTargetGridNum ) )
fAltFireMode = TRUE;
}
}
usAnimState = PickSoldierReadyAnimation( pSoldier, FALSE, fAltFireMode );
usRaiseGunCost = GetAPsToReadyWeapon( pSoldier, usAnimState );
pSoldier->flags.fDontChargeReadyAPs = FALSE;
}
return usRaiseGunCost;
}
INT16 MinAPsToShootOrStab(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 bAimTime, UINT8 ubAddTurningCost, UINT8 ubForceRaiseGunCost )
{
UINT32 uiMercFlags;
UINT16 usTargID;
INT16 bFullAPs;
INT16 bAimSkill;
INT16 bAPCost = APBPConstants[AP_MIN_AIM_ATTACK];
BOOLEAN fAddingTurningCost = FALSE;
BOOLEAN fAddingRaiseGunCost = FALSE;
UINT16 usItem;
UINT16 usRaiseGunCost = 0;
UINT16 usTurningCost = 0;
//UINT16 usRange;
UINT16 usUBItem = 0;
if ( pSoldier->bWeaponMode == WM_ATTACHED_GL || pSoldier->bWeaponMode == WM_ATTACHED_GL_BURST || pSoldier->bWeaponMode == WM_ATTACHED_GL_AUTO )
{
usItem = GetAttachedGrenadeLauncher(&pSoldier->inv[HANDPOS] );//UNDER_GLAUNCHER;
usUBItem = usItem;
}
else
{
usItem = pSoldier->inv[ HANDPOS ].usItem;
// Flugente: we need a second item in case we are using an underbarrel weapon. Not all checks should apply for that one, as aiming is still done with the main weapon
usUBItem = pSoldier->GetUsedWeaponNumber(&pSoldier->inv[HANDPOS]);
}
OBJECTTYPE* pObjUsed = pSoldier->GetUsedWeapon( &(pSoldier->inv[HANDPOS]) );
GetAPChargeForShootOrStabWRTGunRaises( pSoldier, sGridNo, ubAddTurningCost, &fAddingTurningCost, &fAddingRaiseGunCost, bAimTime );
// Snap: reversed DIGICRAB's change.
// bFullAPs are BASE APs, which do not include APs caried over from the previous turn.
bFullAPs = pSoldier->CalcActionPoints( );
// aim skill is the same whether we are using 1 or 2 guns
bAimSkill = CalcAimSkill( pSoldier, usItem );
if (!TileIsOutOfBounds(sGridNo))
{
// Given a gridno here, check if we are on a guy - if so - get his gridno
if ( FindSoldier( sGridNo, &usTargID, &uiMercFlags, FIND_SOLDIER_GRIDNO ) )
{
sGridNo = MercPtrs[ usTargID ]->sGridNo;
}
//usRange = GetRangeFromGridNoDiff( pSoldier->sGridNo, sGridNo );
}
if ( pSoldier->bWeaponMode == WM_ATTACHED_GL || pSoldier->bWeaponMode == WM_ATTACHED_GL_BURST || pSoldier->bWeaponMode == WM_ATTACHED_GL_AUTO )
{
INT16 glItem;
OBJECTTYPE GrenadeLauncher;
// look for an attached grenade launcher
glItem = GetAttachedGrenadeLauncher( &(pSoldier->inv[ HANDPOS ]));
// create temporary grenade launcher and use that
if ( glItem != NONE )
{
CreateItem( glItem, GetGrenadeLauncherStatus(&pSoldier->inv[ HANDPOS ]), &GrenadeLauncher );
}
else
{
// fake it, use a 100 status...
CreateItem( UNDER_GLAUNCHER, 100, &GrenadeLauncher );
}
//CHRISL: When prone and using a bipod, bipod should help compensate for recoil. To reflect this, our shot AP cost should be minimially reduced
if(gGameExternalOptions.ubFlatAFTHBtoPrecentMultiplier > 0 && gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE && GetBipodBonus(&GrenadeLauncher) > 0){
bAPCost += (BaseAPsToShootOrStab( bFullAPs, bAimSkill, &GrenadeLauncher, pSoldier ) * (100 - GetBipodBonus(&GrenadeLauncher)) / 100);
} else {
bAPCost += BaseAPsToShootOrStab( bFullAPs, bAimSkill, &GrenadeLauncher, pSoldier );
}
// SANDRO - STOMP traits - reduced APs needed to fire underbarrel grenade launchers for Heavy Weapons trait
if ( HAS_SKILL_TRAIT( pSoldier, HEAVY_WEAPONS_NT ) && gGameOptions.fNewTraitSystem )
{
// bAPCost = (INT16)((bAPCost * (100 - gSkillTraitValues.ubHWGrenadeLaunchersAPsReduction * NUM_SKILL_TRAITS( pSoldier, HEAVY_WEAPONS_NT ) ) / 100)+ 0.5);
bAPCost = (INT16)(bAPCost * GetAttackAPTraitMultiplier( pSoldier, pObjUsed, pSoldier->bWeaponMode ) + 0.5f);
}
}
else if ( pSoldier->IsValidSecondHandShot( ) )
{
OBJECTTYPE* pSecondObjUsed = pSoldier->GetUsedWeapon( &(pSoldier->inv[SECONDHANDPOS]) );
// SANDRO - gunslinger check for firing speed
// if ( HAS_SKILL_TRAIT( pSoldier, GUNSLINGER_NT ) && gGameOptions.fNewTraitSystem )
// {
INT16 bcst1 = (INT16)(BaseAPsToShootOrStab( bFullAPs, bAimSkill, &(pSoldier->inv[HANDPOS]), pSoldier ) * GetAttackAPTraitMultiplier( pSoldier, pObjUsed, pSoldier->bWeaponMode ) + 0.5f);
INT16 bcst2 = (INT16)(BaseAPsToShootOrStab( bFullAPs, bAimSkill, &(pSoldier->inv[SECONDHANDPOS]), pSoldier ) * GetAttackAPTraitMultiplier( pSoldier, pSecondObjUsed, pSoldier->bWeaponMode ) + 0.5f);
// if ( Weapon[ usItem ].ubWeaponType == GUN_PISTOL )
// bcst1 = (INT16)((bcst1 * ( 100 - gSkillTraitValues.ubGSFiringSpeedBonusPistols) / 100)+ 0.5);
// if ( Weapon[ pSoldier->inv[SECONDHANDPOS].usItem ].ubWeaponType == GUN_PISTOL )
// bcst2 = (INT16)((bcst2 * ( 100 - gSkillTraitValues.ubGSFiringSpeedBonusPistols) / 100)+ 0.5);
bAPCost += __max( bcst1, bcst2 );
/* }
else
{
// charge the maximum of the two
bAPCost += __max(
BaseAPsToShootOrStab( bFullAPs, bAimSkill, &(pSoldier->inv[HANDPOS]), pSoldier ),
BaseAPsToShootOrStab( bFullAPs, bAimSkill, &(pSoldier->inv[SECONDHANDPOS]), pSoldier ) );
}*/
}
else
{
//CHRISL: When prone and using a bipod, bipod should help compensate for recoil. To reflect this, our shot AP cost should be minimially reduced
if(gGameExternalOptions.ubFlatAFTHBtoPrecentMultiplier > 0 && gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE && GetBipodBonus(&(pSoldier->inv[HANDPOS])) > 0)
{
bAPCost += (BaseAPsToShootOrStab( bFullAPs, bAimSkill, pObjUsed, pSoldier ) * (100 - GetBipodBonus(&(pSoldier->inv[HANDPOS]))) / 100);
}
else
{
bAPCost += BaseAPsToShootOrStab( bFullAPs, bAimSkill, pObjUsed, pSoldier );
}
// If we are using alternative weapon holding (from hip rifle/one-handed pistol), the shot can be faster if set in the ini
if ( gGameExternalOptions.ubAltWeaponHoldingFireSpeedBonus > 0 && pSoldier->IsValidAlternativeFireMode( bAimTime, sGridNo ))
{
bAPCost = (INT16)(bAPCost * (100 - gGameExternalOptions.ubAltWeaponHoldingFireSpeedBonus ) / 100.0f + 0.5f);
}
/////////////////////////////////////////////////////////////////////////////////////
// SANDRO - STOMP traits
////////////////////////////////////////////////////
if ( gGameOptions.fNewTraitSystem )
{
// silversurfer: new function to handle all modifiers
bAPCost = (INT16)(bAPCost * GetAttackAPTraitMultiplier( pSoldier, pObjUsed, pSoldier->bWeaponMode ) + 0.5f);
/* // Decreased APs needed for LMG - Auto Weapons
if (Weapon[usUBItem].ubWeaponType == GUN_LMG && (pSoldier->bDoBurst || pSoldier->bDoAutofire) && ( HAS_SKILL_TRAIT( pSoldier, AUTO_WEAPONS_NT ) ) )
{
bAPCost = (INT16)((bAPCost * (100 - gSkillTraitValues.ubAWFiringSpeedBonusLMGs ) / 100)+ 0.5);
}
#if 0//dnl ch73 021013 relocate this to MinAPsToPunch
// Decreased APs needed for melee attacks - Melee (Blade only)
else if ( Item[ usUBItem ].usItemClass == IC_BLADE && ( HAS_SKILL_TRAIT( pSoldier, MELEE_NT ) ) )
{
bAPCost = (INT16)((bAPCost * (100 - gSkillTraitValues.ubMEBladesAPsReduction ) / 100)+ 0.5);
}
#endif
// Decreased APs needed for throwing knives - Throwing
else if ( Item[ usUBItem ].usItemClass == IC_THROWING_KNIFE && ( HAS_SKILL_TRAIT( pSoldier, THROWING_NT ) ) )
{
bAPCost = (INT16)((bAPCost * (100 - gSkillTraitValues.ubTHBladesAPsReduction ) / 100)+ 0.5);
}
// Decreased APs needed for launchers - Heavy Weapons
else if ( (Item[ usUBItem ].usItemClass == IC_LAUNCHER || Item[usUBItem].grenadelauncher ) && !(Item[usUBItem].rocketlauncher) && !(Item[usUBItem].mortar) && ( HAS_SKILL_TRAIT( pSoldier, HEAVY_WEAPONS_NT ) ))
{
bAPCost = (INT16)((bAPCost * (100 - gSkillTraitValues.ubHWGrenadeLaunchersAPsReduction * NUM_SKILL_TRAITS( pSoldier, HEAVY_WEAPONS_NT ) ) / 100)+ 0.5);
}
// Decreased APs needed for launchers - Heavy Weapons
else if (( Item[usUBItem].rocketlauncher || Item[usUBItem].singleshotrocketlauncher ) && !(Item[usUBItem].mortar) && ( HAS_SKILL_TRAIT( pSoldier, HEAVY_WEAPONS_NT ) ))
{
bAPCost = (INT16)((bAPCost * (100 - gSkillTraitValues.ubHWRocketLaunchersAPsReduction * NUM_SKILL_TRAITS( pSoldier, HEAVY_WEAPONS_NT ) ) / 100)+ 0.5);
}
// Decreased APs needed for mortar - Heavy Weapons
else if ( Item[usUBItem].mortar )
{
bAPCost = (INT16)((bAPCost * (100 - gSkillTraitValues.ubHWMortarAPsReduction * NUM_SKILL_TRAITS( pSoldier, HEAVY_WEAPONS_NT ) + pSoldier->GetBackgroundValue(BG_ARTILLERY) ) / 100)+ 0.5);
}
// Decreased APs needed for pistols and machine pistols - Gunslinger
else if (Weapon[ usUBItem ].ubWeaponType == GUN_PISTOL && HAS_SKILL_TRAIT( pSoldier, GUNSLINGER_NT ) )
{
bAPCost = (INT16)((bAPCost * (100 - gSkillTraitValues.ubGSFiringSpeedBonusPistols * NUM_SKILL_TRAITS( pSoldier, GUNSLINGER_NT ) ) / 100)+ 0.5);
}
// Decrease APs needed for Shotguns - Ranger
else if (Weapon[ usUBItem ].ubWeaponType == GUN_SHOTGUN && HAS_SKILL_TRAIT( pSoldier, RANGER_NT ) )
{
bAPCost = (INT16)((bAPCost * (100 - gSkillTraitValues.ubRAFiringSpeedBonusShotguns * NUM_SKILL_TRAITS( pSoldier, RANGER_NT ) ) / 100)+ 0.5);
}*/
}
/////////////////////////////////////////////////////////////////////////////////////
}
if (ubForceRaiseGunCost == TRUE )//dnl ch69 140913
{
fAddingRaiseGunCost = TRUE;
}
if (!TileIsOutOfBounds(sGridNo))
{
// throwing knifes
if ( Item[ usUBItem ].usItemClass == IC_THROWING_KNIFE )
{
// Do we need to stand up?
bAPCost += GetAPsToChangeStance( pSoldier, ANIM_STAND );
}
#if 0//dnl ch73 021013 relocate this to MinAPsToPunch
// blunt weapons & blades
else if ( Item[ usUBItem ].usItemClass == IC_PUNCH || Item[ usUBItem ].usItemClass == IC_BLADE )
{
if ( usTargID != NOBODY )
{
// Check if target is prone, if so, calc cost...
if ( gAnimControl[ MercPtrs[ usTargID ]->usAnimState ].ubEndHeight == ANIM_PRONE )
bAPCost += GetAPsToChangeStance( pSoldier, ANIM_CROUCH );
else
bAPCost += GetAPsToChangeStance( pSoldier, ANIM_STAND );
}
}
#endif
else if(Item[usItem].rocketlauncher || Item[usItem].grenadelauncher || Item[usItem].mortar)//dnl ch72 260913 move this here from bottom, need to change as rocketlaucher could be fired from crouch too
{
if(gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE || Item[usItem].mortar && gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_STAND)
bAPCost += GetAPsToChangeStance(pSoldier, ANIM_CROUCH);
else
bAPCost += GetAPsToChangeStance(pSoldier, gAnimControl[pSoldier->usAnimState].ubEndHeight);
}
}
if ( AM_A_ROBOT( pSoldier ) || ARMED_VEHICLE( pSoldier ) || ENEMYROBOT( pSoldier ) || ubForceRaiseGunCost == 2 )//dnl ch64 300813 robots and tanks cannot do this //dnl ch69 150913 need option to override raise gun cost
{
fAddingRaiseGunCost = FALSE;
}
//Calculate usTurningCost
if (!TileIsOutOfBounds(sGridNo))
{
#if 0//dnl ch73 021013 relocate this to MinAPsToPunch
// Buggler: actual melee ap deduction for turning applies only when target is 1 tile away
if ( !( ( Item[ usUBItem ].usItemClass == IC_PUNCH || Item[ usUBItem ].usItemClass == IC_BLADE ) && usRange > 1 ) )
#endif
{
if(Item[usItem].rocketlauncher || Item[usItem].grenadelauncher || Item[usItem].mortar)//dnl ch72 260913
{
if(gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE || Item[usItem].mortar && gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_STAND)
usTurningCost = CalculateTurningCost(pSoldier, usItem, fAddingTurningCost, ANIM_CROUCH);
else
usTurningCost = CalculateTurningCost(pSoldier, usItem, fAddingTurningCost);
}
else
usTurningCost = CalculateTurningCost(pSoldier, usItem, fAddingTurningCost);
}
}
//Calculate usRaiseGunCost
usRaiseGunCost = CalculateRaiseGunCost(pSoldier, fAddingRaiseGunCost, sGridNo, bAimTime );
//charge for turn and/or raise based on the results of fAddingTurningCost & fAddingRaiseGunCost
// silversurfer: this was different from HandleItem() and therefore it displayed different values
// on the cursor than those deducted for real
// bAPCost += (usTurningCost + usRaiseGunCost);
if ( gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE )
{
if ( fAddingTurningCost && fAddingRaiseGunCost )
{
if(usRaiseGunCost > usTurningCost)
bAPCost += usRaiseGunCost;
else
bAPCost += usTurningCost;
}
else
bAPCost += (usTurningCost + usRaiseGunCost);
}
else
bAPCost += (usTurningCost + usRaiseGunCost);
// if attacking a new target (or if the specific target is uncertain)
// Added check if the weapon is throwing knife/melee weapons - otherwise it would add APs for change target on cursor but not actually deduct them afterwards - SANDRO
if ( ubForceRaiseGunCost == TRUE || (( sGridNo != pSoldier->sLastTarget ) && !Item[usUBItem].rocketlauncher && ( Item[ usUBItem ].usItemClass != IC_THROWING_KNIFE )/* && ( Item[ usUBItem ].usItemClass != IC_PUNCH ) && ( Item[ usUBItem ].usItemClass != IC_BLADE )*/ ) )//dnl ch69 140913 //dnl ch73 290913
{
if ( pSoldier->IsValidAlternativeFireMode( bAimTime, sGridNo ) )
bAPCost += (APBPConstants[AP_CHANGE_TARGET] / 2);
else
bAPCost += APBPConstants[AP_CHANGE_TARGET];
}
// sevenfm: add unjam AP cost
if( Item[usUBItem].usItemClass == IC_GUN &&
!EXPLOSIVE_GUN( usUBItem ) &&
!(pSoldier->bWeaponMode == WM_ATTACHED_GL || pSoldier->bWeaponMode == WM_ATTACHED_GL_BURST || pSoldier->bWeaponMode == WM_ATTACHED_GL_AUTO) &&
(*pObjUsed)[0]->data.gun.bGunAmmoStatus < 0)
{
bAPCost += APBPConstants[AP_UNJAM];
}
#if 0//dnl ch63 240813 this seems very wrong, in most case (pSoldier->bActionPoints > bFullAps) and this will return less points then is actually required and could cancel some AI actions, like throwing grenades
// the minimum AP cost of ANY shot can NEVER be more than merc's maximum APs!
if ( bAPCost > bFullAPs )
bAPCost = bFullAPs;
#endif
// this SHOULD be impossible, but nevertheless...
if ( bAPCost < 1 )
bAPCost = 1;
return bAPCost;
}
INT16 MinAPsToPunch(SOLDIERTYPE *pSoldier, INT32 sGridNo)
{
INT16 bAPCost = APBPConstants[AP_MIN_AIM_ATTACK];
OBJECTTYPE *pObjUsed = pSoldier->GetUsedWeapon(&pSoldier->inv[HANDPOS]);
UINT16 usItem = pObjUsed->usItem;
INT16 bFullAPs = pSoldier->CalcActionPoints();
INT16 bAimSkill = CalcAimSkill(pSoldier, pSoldier->inv[HANDPOS].usItem);
if(usItem == NONE/* || Item[usItem].brassknuckles*/)
bAPCost += ApsToPunch(pSoldier);// SANDRO - changed this to direct us to specific calc function
else
{
bAPCost += BaseAPsToShootOrStab(bFullAPs, bAimSkill, pObjUsed, pSoldier);
// Decreased APs needed for melee attacks - Melee (Blade only)
if(Item[usItem].usItemClass == IC_BLADE && (HAS_SKILL_TRAIT(pSoldier, MELEE_NT)))
bAPCost = (INT16)(bAPCost * (100 - gSkillTraitValues.ubMEBladesAPsReduction) / 100.0f + 0.5f);
}
// sevenfm: check enemy only if we have correct gridNo
if( !TileIsOutOfBounds(sGridNo) )
{
UINT16 usTargID = WhoIsThere2(sGridNo, pSoldier->bTargetLevel);
// Given a gridno here, check if we are on a guy - if so - get his gridno
if(usTargID != NOBODY)
{
// Check if target is prone, if so, calc cost...
if(gAnimControl[MercPtrs[usTargID]->usAnimState].ubEndHeight == ANIM_PRONE)
bAPCost += GetAPsToChangeStance(pSoldier, ANIM_CROUCH);
else
bAPCost += GetAPsToChangeStance(pSoldier, ANIM_STAND);
}
}
return(bAPCost);
}
// SANDRO - added function
INT16 ApsToPunch( SOLDIERTYPE *pSoldier )
{
if (gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ))
{
return ( max( 1, (INT16)(APBPConstants[AP_PUNCH] * (100 - gSkillTraitValues.ubMAPunchAPsReduction * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT )) / 100.0f + 0.5f)) );
}
else
{
return ( APBPConstants[AP_PUNCH] );
}
}
INT16 MinPtsToMove(SOLDIERTYPE *pSoldier)
{
// look around all 8 directions and return lowest terrain cost
UINT8 cnt;
INT16 sLowest=32767;//dnl ch64 290813 100AP made return of INT8 or 127 obsolete as other variables are INT16
INT16 sCost;
INT32 sGridNo;
if ( ARMED_VEHICLE( pSoldier ) && !gGameExternalOptions.fEnemyTanksCanMoveInTactical )
{
return(sLowest);//dnl ch64 290813 100AP made INT8 return obsolete
}
// WANNE - BMP: FIX: Valid directions are only from 0-7!!
for ( cnt = 0; cnt < NUM_WORLD_DIRECTIONS; ++cnt )
{
sGridNo = NewGridNo(pSoldier->sGridNo,DirectionInc(cnt));
if (sGridNo != pSoldier->sGridNo)
{
if ( (sCost=ActionPointCost( pSoldier, sGridNo, cnt , pSoldier->usUIMovementMode ) ) < sLowest )
{
sLowest = sCost;
}
}
}
return(sLowest);//dnl ch64 290813 100AP made INT8 return obsolete
}
INT8 PtsToMoveDirection(SOLDIERTYPE *pSoldier, INT8 bDirection )
{
INT16 sCost;
INT32 sGridNo;
INT8 bOverTerrainType;
UINT16 usMoveModeToUse;
sGridNo = NewGridNo( pSoldier->sGridNo, DirectionInc( bDirection ) );
usMoveModeToUse = pSoldier->usUIMovementMode;
// ATE: Check if the new place is watter and we were tying to run....
bOverTerrainType = GetTerrainType( sGridNo );
// WANNE.WATER: If our soldier is not on the ground level and the tile is a "water" tile, then simply set the tile to "FLAT_GROUND"
// This should fix "problems" for special modified maps
if ( TERRAIN_IS_WATER( bOverTerrainType) && pSoldier->pathing.bLevel > 0 )
bOverTerrainType = FLAT_GROUND;
if ( TERRAIN_IS_WATER( bOverTerrainType) )
{
usMoveModeToUse = WALKING;
}
sCost = ActionPointCost( pSoldier, sGridNo, bDirection , usMoveModeToUse );
return( (INT8)sCost );
}
INT8 MinAPsToStartMovement( SOLDIERTYPE * pSoldier, UINT16 usMovementMode )
{
INT8 bAPs = 0;
switch( usMovementMode )
{
case RUNNING:
case WALKING:
case WALKING_WEAPON_RDY:
case WALKING_DUAL_RDY:
if (gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE)
{
bAPs += GetAPsCrouch(pSoldier, TRUE) + GetAPsProne(pSoldier, TRUE);
}
else if (gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_CROUCH)
{
bAPs += GetAPsCrouch(pSoldier, TRUE);
}
break;
case SWATTING:
case CROUCHEDMOVE_RIFLE_READY:
case CROUCHEDMOVE_PISTOL_READY:
case CROUCHEDMOVE_DUAL_READY:
if (gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE)
{
bAPs += GetAPsProne(pSoldier, TRUE);
}
else if (gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND)
{
bAPs += GetAPsCrouch(pSoldier, TRUE);
}
break;
case CRAWLING:
if (gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND)
{
bAPs += GetAPsCrouch(pSoldier, TRUE) + GetAPsProne(pSoldier, TRUE);
}
else if (gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_CROUCH)
{
//bAPs += GetAPsCrouch(pSoldier, TRUE);
bAPs += GetAPsProne(pSoldier, TRUE);
}
break;
default:
break;
}
/*
if (usMovementMode == RUNNING && pSoldier->usAnimState != RUNNING )
{
bAPs += GetAPsStartRun( pSoldier ); // changed by SANDRO
}
*/
return( bAPs );
}
BOOLEAN EnoughAmmo( SOLDIERTYPE *pSoldier, BOOLEAN fDisplay, INT8 bInvPos )
{
if ( pSoldier->inv[ bInvPos ].exists() == true )
{
if ( pSoldier->bWeaponMode == WM_ATTACHED_GL || pSoldier->bWeaponMode == WM_ATTACHED_GL_BURST || pSoldier->bWeaponMode == WM_ATTACHED_GL_AUTO )
{
UINT16 glItem = GetAttachedGrenadeLauncher( &(pSoldier->inv[bInvPos]));
OBJECTTYPE* pAttachment = FindLaunchableAttachment( &(pSoldier->inv[bInvPos]), glItem);
if ( pAttachment->exists() )
return TRUE;
else
return FALSE;
}
else
{
OBJECTTYPE* pObjUsed = pSoldier->GetUsedWeapon( &(pSoldier->inv[bInvPos]) );
UINT16 usItemUsed = pSoldier->GetUsedWeaponNumber( &(pSoldier->inv[bInvPos]) );
if ( Item[usItemUsed].singleshotrocketlauncher )
{
// hack... they turn empty afterwards anyways
return( TRUE );
}
if (Item[ usItemUsed ].usItemClass == IC_LAUNCHER || Item[usItemUsed].cannon )
{
if ( FindAttachmentByClass( pObjUsed, IC_GRENADE ) != 0 )
{
return( TRUE );
}
if (Item[usItemUsed].usItemClass == IC_BOMB)
{
return (TRUE);
}
// ATE: Did an else if here...
if ( FindAttachmentByClass( pObjUsed, IC_BOMB ) != 0 )
{
return( TRUE );
}
// WANNE: If there is a tank, it always have ammo to shoot, no check!
if (Item[usItemUsed].cannon)
{
return ( TRUE );
}
if ( fDisplay && ( Chance( gGameExternalOptions.iChanceSayAnnoyingPhrase ) || GetMagSize( pObjUsed, 0 ) > 4 ) )
{
TacticalCharacterDialogue( pSoldier, QUOTE_OUT_OF_AMMO );
}
return( FALSE );
}
else if (Item[ usItemUsed ].usItemClass == IC_GUN)
{
if ( (*pObjUsed)[0]->data.gun.ubGunShotsLeft == 0 )
{
if ( fDisplay && ( Chance( gGameExternalOptions.iChanceSayAnnoyingPhrase ) || GetMagSize( pObjUsed, 0 ) > 4 ) )
{
TacticalCharacterDialogue( pSoldier, QUOTE_OUT_OF_AMMO );
}
return( FALSE );
}
//<SB> manual recharge
if( pSoldier->bTeam == OUR_TEAM )
{
if ( !( (*pObjUsed)[0]->data.gun.ubGunState & GS_CARTRIDGE_IN_CHAMBER ) )
{
return( FALSE );
}
}
//</SB>
}
}
return( TRUE );
}
return( FALSE );
}
void DeductAmmo( SOLDIERTYPE *pSoldier, INT8 bInvPos )
{
return DeductAmmo( pSoldier, &(pSoldier->inv[bInvPos]) );
}
void DeductAmmo( SOLDIERTYPE *pSoldier, OBJECTTYPE* pObj )
{
if ( pSoldier && pObj->exists( ) )
{
// tanks never run out of MG ammo!
// unlimited cannon ammo is handled in AI
if ( (ARMED_VEHICLE( pSoldier ) || ENEMYROBOT( pSoldier )) && !Item[pObj->usItem].cannon )
return;
if ( Item[pObj->usItem].cannon )
{
}
else if ( Item[ pObj->usItem ].usItemClass == IC_GUN && !Item[pObj->usItem].cannon && pSoldier->bWeaponMode != WM_ATTACHED_GL && pSoldier->bWeaponMode != WM_ATTACHED_GL_BURST && pSoldier->bWeaponMode != WM_ATTACHED_GL_AUTO )
{
// Flugente: check for underbarrel weapons and use that object if necessary
OBJECTTYPE* pObjUsed = pSoldier->GetUsedWeapon( pObj );
// Flugente: external feeding allows us to take ammo from somewhere other than our magazine, like a belt in our inventory our even another mercs
if ( gGameExternalOptions.ubExternalFeeding > 0 )
{
if ( !DeductBulletViaExternalFeeding(pSoldier, pObj) )
{
// bullet couldn't be fed externally -> remove bullet from the weapon's magazine
// OK, let's see, don't overrun...
if ( (*pObjUsed)[0]->data.gun.ubGunShotsLeft != 0 )
{
(*pObjUsed)[0]->data.gun.ubGunShotsLeft--;
}
}
}
else
{
// OK, let's see, don't overrun...
if ( (*pObjUsed)[0]->data.gun.ubGunShotsLeft != 0 )
{
(*pObjUsed)[0]->data.gun.ubGunShotsLeft--;
}
}
// Flugente: campaign stats - ammo. explosive consumption is handled elsewhere
if ( pSoldier->bTeam == OUR_TEAM )
{
gCampaignStats.AddConsumption(CAMPAIGN_CONSUMED_AMMO, (FLOAT)(Item[(*pObjUsed)[0]->data.gun.usGunAmmoItem].ubWeight) / (FLOAT)(Magazine[ Item[ (*pObjUsed)[0]->data.gun.usGunAmmoItem ].ubClassIndex ].ubMagSize ) );
}
}
else if ( Item[ pObj->usItem ].usItemClass == IC_LAUNCHER || Item[pObj->usItem].cannon || pSoldier->bWeaponMode == WM_ATTACHED_GL || pSoldier->bWeaponMode == WM_ATTACHED_GL_BURST || pSoldier->bWeaponMode == WM_ATTACHED_GL_AUTO )
{
OBJECTTYPE* pAttachment = FindAttachmentByClass( pObj, IC_GRENADE );
if ( !pAttachment->exists() )
{
pAttachment = FindAttachmentByClass( pObj, IC_BOMB );
}
//CHRISL: Added a magsize value to Explosives.xml which is checked by GetExpMagSize. This will allow OAS to load individual grenades into
// multi-launch grenades and fire correctly
if (pAttachment->exists())
{
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DeductAmmo: deducting for GL: found attachment, about to decide whether to remove it"));
if ( Item[ pObj->usItem ].usItemClass == IC_LAUNCHER && GetExpMagSize(pAttachment) > 1 )
{
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DeductAmmo: deducting for milkor"));
(*pAttachment)[0]->data.objectStatus -= (INT8) ceil((double)( 100 / GetExpMagSize(pAttachment) )) ;
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DeductAmmo: deducting for milkor: resulting status: %d, remove? = %d",(*pAttachment)[0]->data.objectStatus,((*pAttachment)[0]->data.objectStatus <= (INT8) ( 100 / GetExpMagSize(pAttachment) ))));
if ( (*pAttachment)[0]->data.objectStatus < (INT8) ceil((double)( 100 / GetExpMagSize(pAttachment) ) ))
{
pObj->RemoveAttachment( pAttachment);
}
}
else if ( (pSoldier->bWeaponMode == WM_ATTACHED_GL || pSoldier->bWeaponMode == WM_ATTACHED_GL_BURST || pSoldier->bWeaponMode == WM_ATTACHED_GL_AUTO ) && GetExpMagSize(pAttachment) > 1 )
{
(*pAttachment)[0]->data.objectStatus -= (INT8) ceil((double)( 100 / GetExpMagSize(pAttachment) )) ;
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DeductAmmo: deducting for OICW GL: resulting status: %d, remove? = %d",(*pAttachment)[0]->data.objectStatus,((*pAttachment)[0]->data.objectStatus <= (INT8) ( 100 / GetExpMagSize(pAttachment) ))));
if ( (*pAttachment)[0]->data.objectStatus < (INT8) ceil((double)( 100 / GetExpMagSize(pAttachment) ) ))
{
pObj->RemoveAttachment( pAttachment);
}
}
else
{
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DeductAmmo: deducting for GL: removing attachment"));
pObj->RemoveAttachment( pAttachment);
}
}
}
// Dirty Bars
DirtyMercPanelInterface( pSoldier, DIRTYLEVEL1 );
}
}
UINT16 GetAPsToPickupItem( SOLDIERTYPE *pSoldier, INT32 usMapPos )
{
ITEM_POOL *pItemPool;
UINT16 sAPCost = 0;
INT32 sActionGridNo;
// Check if we are over an item pool
if ( GetItemPool( usMapPos, &pItemPool, pSoldier->pathing.bLevel ) )
{
// If we are in the same tile, just return pickup cost
sActionGridNo = AdjustGridNoForItemPlacement( pSoldier, usMapPos );
if ( pSoldier->sGridNo != sActionGridNo )
{
sAPCost = PlotPath( pSoldier, sActionGridNo, NO_COPYROUTE, NO_PLOT, TEMPORARY, (INT16)pSoldier->usUIMovementMode, NOT_STEALTH, FORWARD, pSoldier->bActionPoints );
// If point cost is zero, return 0
if ( sAPCost != 0 )
{
// ADD APS TO PICKUP
sAPCost += GetBasicAPsToPickupItem( pSoldier ); // changed by SANDRO
}
}
else
{
sAPCost += GetBasicAPsToPickupItem( pSoldier ); // changed by SANDRO
}
}
return( sAPCost );
}
UINT16 GetAPsToGiveItem( SOLDIERTYPE *pSoldier, INT32 usMapPos )
{
UINT16 sAPCost = 0;
sAPCost = PlotPath( pSoldier, usMapPos, NO_COPYROUTE, NO_PLOT, TEMPORARY, (UINT16)pSoldier->usUIMovementMode, NOT_STEALTH, FORWARD, pSoldier->bActionPoints );
// If point cost is zero, return 0
if ( sAPCost != 0 || pSoldier->sGridNo == usMapPos )
{
// ADD APS TO PICKUP
sAPCost += APBPConstants[AP_GIVE_ITEM];
}
return( sAPCost );
}
INT16 GetAPsToReloadGunWithAmmo( SOLDIERTYPE *pSoldier, OBJECTTYPE * pGun, OBJECTTYPE * pAmmo, BOOLEAN usAllAPs )
{
if (Item[ pGun->usItem ].usItemClass == IC_LAUNCHER)
{
// always standard AP cost
//return( APBPConstants[AP_RELOAD_GUN] );
return GetAPsToReload(pGun);
}
if(Weapon[pGun->usItem].swapClips == 1 && GetMagSize(pGun) == Magazine[Item[pAmmo->usItem].ubClassIndex].ubMagSize)
{
// normal situation
// SANDRO - added a feature here to reduce APs needed to reload if having GUNSLINGER skill
if ( gGameOptions.fNewTraitSystem )
{
// if pistol or revolver
if ( HAS_SKILL_TRAIT( pSoldier, GUNSLINGER_NT ) && ( Weapon[ pGun->usItem ].ubWeaponType == GUN_PISTOL || Weapon[ pGun->usItem ].ubWeaponType == GUN_M_PISTOL ) )
return(INT16)( GetAPsToReload(pGun) * max(0,(100 - gSkillTraitValues.ubGSRealoadSpeedHandgunsBonus * NUM_SKILL_TRAITS( pSoldier, GUNSLINGER_NT ) - (HAS_SKILL_TRAIT( pSoldier, AMBIDEXTROUS_NT ) ? gSkillTraitValues.ubAMReloadSpeedMagazines : 0 ))) / 100.0f + 0.5f);
// other weapons for ambidextrous
else if (HAS_SKILL_TRAIT( pSoldier, AMBIDEXTROUS_NT ))
return(INT16)( GetAPsToReload(pGun) * max(0,(100 - gSkillTraitValues.ubAMReloadSpeedMagazines )) / 100.0f + 0.5f );
// normal case
else
return GetAPsToReload(pGun);
}
else
return GetAPsToReload(pGun);
}
else if(Weapon[pGun->usItem].swapClips == 1)
{
// wrong size ammo item
if( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, AMBIDEXTROUS_NT ) ) // SANDRO - added Ambidextrous check
return (INT16)((GetAPsToReload(pGun) * APBPConstants[AP_WRONG_MAG] / 10.0f) * (100 - gSkillTraitValues.ubAMReloadSpeedLoose) / 100.0f + 0.5f);
else
return (INT16)((GetAPsToReload(pGun) * APBPConstants[AP_WRONG_MAG] / 10.0f) + 0.5f);
}
else
{
// individually loaded guns
int reload = 0;
int rounds = __min((GetMagSize(pGun)-(*pGun)[0]->data.gun.ubGunShotsLeft), Magazine[Item[pAmmo->usItem].ubClassIndex].ubMagSize);
FLOAT fSpeedBonus = 1.0;
// Ambidextrous check
if( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, AMBIDEXTROUS_NT ) )
fSpeedBonus *= (FLOAT)(100 - gSkillTraitValues.ubAMReloadSpeedLoose) / 100.0;
// Ranger bonus to reload shotgun shells manually
if( Weapon[ pGun->usItem ].ubWeaponType == GUN_SHOTGUN && HAS_SKILL_TRAIT( pSoldier, RANGER_NT ) )
fSpeedBonus *= (FLOAT)(100 - gSkillTraitValues.ubRAReloadSpeedShotgunsManual * NUM_SKILL_TRAITS( pSoldier, RANGER_NT ) ) / 100.0;
// limit bonus to proper values
fSpeedBonus = __min( 1.0, __max( 0.1, fSpeedBonus ) );
if(!((*pGun)[0]->data.gun.ubGunState & GS_WEAPON_BEING_RELOADED))
{
reload = GetAPsToReload(pGun) * fSpeedBonus;
/* // SANDRO - added Ambidextrous check
if( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, AMBIDEXTROUS_NT ) )
{
reload = (INT16)(reload * (100 - gSkillTraitValues.ubAMReloadSpeedMagazines)/100);
}*/
}
if(usAllAPs == 1)
{
for(int i = 1; i <= rounds; i++)
{
/* // SANDRO - added Ambidextrous check
if( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, AMBIDEXTROUS_NT ) )
{
if(EnoughPoints(pSoldier,(reload + (i * (INT16)((APBPConstants[AP_RELOAD_LOOSE]*(100 - gSkillTraitValues.ubAMReloadSpeedLoose)/100) + 0.5))),0,FALSE) == FALSE)
{
rounds = i-1;
break;
}
}
else
{*/
if(EnoughPoints(pSoldier,(reload + (i * (INT16)(APBPConstants[AP_RELOAD_LOOSE] * fSpeedBonus + 0.5))),0,FALSE) == FALSE)
{
rounds = i-1;
break;
}
// }
}
}
else if (usAllAPs == 2)
rounds = 1;
/* // SANDRO - added Ambidextrous check
if( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, AMBIDEXTROUS_NT ) )
return (reload + ((INT16) (rounds * (APBPConstants[AP_RELOAD_LOOSE]*(100 - gSkillTraitValues.ubAMReloadSpeedLoose)/100) + 0.5)));
else*/
return (reload + (rounds * (INT16)(APBPConstants[AP_RELOAD_LOOSE] * fSpeedBonus + 0.5)));
}
return GetAPsToReload(pGun); // added by SANDRO - safety check
}
INT16 GetAPsToAutoReload( SOLDIERTYPE * pSoldier, bool aReloadEvenIfNotEmpty )
{
OBJECTTYPE * pObj;
INT8 bSlot, bSlot2, bExcludeSlot;
INT16 bAPCost = 0, bAPCost2 = 0;;
CHECKF( pSoldier );
// Flugente: check for underbarrel weapons and use that object if necessary
pObj = pSoldier->GetUsedWeapon( &(pSoldier->inv[HANDPOS]) );
//<SB> manual recharge
if ((*pObj)[0]->data.gun.ubGunShotsLeft && !((*pObj)[0]->data.gun.ubGunState & GS_CARTRIDGE_IN_CHAMBER) )
{
INT16 sModifiedReloadAP = Weapon[Item[(pObj)->usItem].ubClassIndex].APsToReloadManually;
// modify by ini values
if ( Item[ pObj->usItem ].usItemClass == IC_GUN )
sModifiedReloadAP *= gItemSettings.fAPtoReloadManuallyModifierGun[ Weapon[ pObj->usItem ].ubWeaponType ];
else if ( Item[ pObj->usItem ].usItemClass == IC_LAUNCHER )
sModifiedReloadAP *= gItemSettings.fAPtoReloadManuallyModifierLauncher;
////////////////////////////////////////////////////////////////////////////////////////////////////////
// STOMP traits - SANDRO
if ( gGameOptions.fNewTraitSystem )
{
// Sniper trait makes chambering a round faster
if (( Weapon[Item[(pObj)->usItem].ubClassIndex].ubWeaponType == GUN_SN_RIFLE || Weapon[Item[(pObj)->usItem].ubClassIndex].ubWeaponType == GUN_RIFLE ) && HAS_SKILL_TRAIT( pSoldier, SNIPER_NT ))
return ( (INT16)(sModifiedReloadAP * (100 - gSkillTraitValues.ubSNChamberRoundAPsReduction * NUM_SKILL_TRAITS( pSoldier, SNIPER_NT )) / 100.0f + 0.5f));
// Ranger trait makes pumping shotguns faster
else if (( Weapon[Item[(pObj)->usItem].ubClassIndex].ubWeaponType == GUN_SHOTGUN ) && HAS_SKILL_TRAIT( pSoldier, RANGER_NT ))
return ( (INT16)(sModifiedReloadAP * (100 - gSkillTraitValues.ubRAPumpShotgunsAPsReduction * NUM_SKILL_TRAITS( pSoldier, RANGER_NT )) / 100.0f + 0.5f));
else
return (sModifiedReloadAP);
}
else
{
return (sModifiedReloadAP);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////
}
//</SB>
if (Item[pObj->usItem].usItemClass == IC_GUN || Item[pObj->usItem].usItemClass == IC_LAUNCHER)
{
bSlot = FindAmmoToReload( pSoldier, HANDPOS, NO_SLOT );
if (bSlot != NO_SLOT)
{
// Flugente: only reload if it's empty, or we really want to
if ( aReloadEvenIfNotEmpty || !EnoughAmmo( pSoldier, FALSE, HANDPOS ) )
{
// we would reload using this ammo!
bAPCost += GetAPsToReloadGunWithAmmo( pSoldier, pObj, &( pSoldier->inv[bSlot] ) );
}
}
// Flugente: only reload if it's empty, or we really want to
if ( pSoldier->IsValidSecondHandShotForReloadingPurposes()
&& ( aReloadEvenIfNotEmpty || !EnoughAmmo( pSoldier, FALSE, SECONDHANDPOS ) ) )
{
// Flugente: check for underbarrel weapons and use that object if necessary
pObj = pSoldier->GetUsedWeapon( &(pSoldier->inv[SECONDHANDPOS]) );
bExcludeSlot = NO_SLOT;
bSlot2 = NO_SLOT;
// if the ammo for the first gun is the same we have to do special checks
if ( ValidAmmoType( pObj->usItem, pSoldier->inv[ bSlot ].usItem ) )
{
if ( pSoldier->inv[ bSlot ].ubNumberOfObjects == 1 )
{
// we must not consider this slot for reloading!
bExcludeSlot = bSlot;
}
else
{
// we can reload the 2nd gun from the same pocket!
bSlot2 = bSlot;
}
}
if (bSlot2 == NO_SLOT)
{
bSlot2 = FindAmmoToReload( pSoldier, SECONDHANDPOS, bExcludeSlot );
}
if (bSlot2 != NO_SLOT)
{
// we would reload using this ammo!
bAPCost2 = GetAPsToReloadGunWithAmmo( pSoldier, pObj, &(pSoldier->inv[bSlot2] ) );
if ( EnoughPoints( pSoldier, (INT16) (bAPCost + bAPCost2), 0, FALSE ) )
{
// we can afford to reload both guns; otherwise display just for 1 gun
bAPCost += bAPCost2;
}
}
}
}
return( bAPCost );
}
UINT16 GetAPsToReloadRobot( SOLDIERTYPE *pSoldier, SOLDIERTYPE *pRobot )
{
UINT16 sAPCost = 0;
INT32 sActionGridNo;
UINT8 ubDirection;
INT32 sAdjustedGridNo;
sActionGridNo = FindAdjacentGridEx( pSoldier, pRobot->sGridNo, &ubDirection, &sAdjustedGridNo, TRUE, FALSE );
sAPCost = PlotPath( pSoldier, sActionGridNo, NO_COPYROUTE, NO_PLOT, TEMPORARY, (UINT16)pSoldier->usUIMovementMode, NOT_STEALTH, FORWARD, pSoldier->bActionPoints );
// If point cost is zero, return 0
if ( sAPCost != 0 || sActionGridNo == pSoldier->sGridNo )
{
// ADD APS TO RELOAD
sAPCost += 4;
}
return( sAPCost );
}
INT16 GetAPsToChangeStance( SOLDIERTYPE *pSoldier, INT8 bDesiredHeight )
{
UINT16 sAPCost = 0;
INT8 bCurrentHeight;
bCurrentHeight = gAnimControl[ pSoldier->usAnimState ].ubEndHeight;
if ( bCurrentHeight == bDesiredHeight )
{
return(0); // no need to got through the code below here - SANDRO
}
// SANDRO - changed this here to call specific functions
if ( bCurrentHeight == ANIM_STAND && bDesiredHeight == ANIM_PRONE )
{
sAPCost = GetAPsCrouch(pSoldier, TRUE) + GetAPsProne(pSoldier, TRUE);
}
if ( bCurrentHeight == ANIM_STAND && bDesiredHeight == ANIM_CROUCH )
{
sAPCost = GetAPsCrouch(pSoldier, TRUE);
}
if ( bCurrentHeight == ANIM_CROUCH && bDesiredHeight == ANIM_PRONE )
{
sAPCost = GetAPsProne(pSoldier, TRUE);
}
if ( bCurrentHeight == ANIM_CROUCH && bDesiredHeight == ANIM_STAND )
{
sAPCost = GetAPsCrouch(pSoldier, TRUE*2);//dnl ch70 160913
}
if ( bCurrentHeight == ANIM_PRONE && bDesiredHeight == ANIM_STAND )
{
sAPCost = GetAPsProne(pSoldier, TRUE*2) + GetAPsCrouch(pSoldier, TRUE*2);//dnl ch70 160913
}
if ( bCurrentHeight == ANIM_PRONE && bDesiredHeight == ANIM_CROUCH )
{
sAPCost = GetAPsProne(pSoldier, TRUE*2);//dnl ch70 160913
}
return( sAPCost );
}
// SANDRO - actually this procedure is nowhere used in the entire JA2 code - commented out
//INT16 GetBPsToChangeStance( SOLDIERTYPE *pSoldier, INT8 bDesiredHeight )
//{
// UINT16 sBPCost = 0;
// INT8 bCurrentHeight;
//
// bCurrentHeight = gAnimControl[ pSoldier->usAnimState ].ubEndHeight;
//
// if ( bCurrentHeight == bDesiredHeight )
// {
// sBPCost = 0;
// }
//
// if ( bCurrentHeight == ANIM_STAND && bDesiredHeight == ANIM_PRONE )
// {
// sBPCost = APBPConstants[BP_CROUCH] + APBPConstants[BP_PRONE];
// }
// if ( bCurrentHeight == ANIM_STAND && bDesiredHeight == ANIM_CROUCH )
// {
// sBPCost = APBPConstants[BP_CROUCH];
// }
// if ( bCurrentHeight == ANIM_CROUCH && bDesiredHeight == ANIM_PRONE )
// {
// sBPCost = APBPConstants[BP_PRONE];
// }
// if ( bCurrentHeight == ANIM_CROUCH && bDesiredHeight == ANIM_STAND )
// {
// sBPCost = APBPConstants[BP_CROUCH];
// }
// if ( bCurrentHeight == ANIM_PRONE && bDesiredHeight == ANIM_STAND )
// {
// sBPCost = APBPConstants[BP_PRONE] + APBPConstants[BP_CROUCH];
// }
// if ( bCurrentHeight == ANIM_PRONE && bDesiredHeight == ANIM_CROUCH )
// {
// sBPCost = APBPConstants[BP_PRONE];
// }
//
// return( sBPCost );
//}
INT16 GetAPsToLook( SOLDIERTYPE *pSoldier )
{
// Set # of APs
switch( gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
{
// Now change to appropriate animation
case ANIM_STAND:
if (HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ) && gGameOptions.fNewTraitSystem )
return( max( 1, (INT16)(APBPConstants[AP_LOOK_STANDING] * (100 - gSkillTraitValues.ubMAApsTurnAroundReduction * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT )) / 100.0f + 0.5f ) ) );
else
return( APBPConstants[AP_LOOK_STANDING] );
break;
case ANIM_CROUCH:
if (HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ) && gGameOptions.fNewTraitSystem )
return( max( 1, (INT16)(APBPConstants[AP_LOOK_CROUCHED] * (100 - gSkillTraitValues.ubMAApsTurnAroundReduction * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT )) / 100.0f + 0.5f ) ) );
else
return( APBPConstants[AP_LOOK_CROUCHED] );
break;
case ANIM_PRONE:
// APBPConstants[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
// crouched, turn, and then go back down. Hence you go up (APBPConstants[AP_PRONE]), turn (APBPConstants[AP_LOOK_PRONE]) and down (APBPConstants[AP_PRONE]).
//dnl ch70 160913 because of backpack cost for going up is 2
if (HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ) && gGameOptions.fNewTraitSystem )
return( max( 1, (INT16)(APBPConstants[AP_LOOK_PRONE] * (100 - gSkillTraitValues.ubMAApsTurnAroundReduction * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT )) / 100.0f + 0.5f)) + GetAPsProne(pSoldier, TRUE) + GetAPsProne(pSoldier, TRUE*2) );
else
return( APBPConstants[AP_LOOK_PRONE] + GetAPsProne(pSoldier, TRUE) + GetAPsProne(pSoldier, TRUE*2) );
break;
// no other values should be possible
default:
Assert( FALSE );
return(0);
break;
}
}
BOOLEAN CheckForMercContMove( SOLDIERTYPE *pSoldier )
{
INT16 sAPCost;
INT32 sGridNo;
if ( !( gTacticalStatus.uiFlags & INCOMBAT ) )
{
return( FALSE );
}
if ( gpItemPointer != NULL )
{
return( FALSE );
}
if( pSoldier->stats.bLife >= OKLIFE )
{
if( pSoldier->sGridNo != pSoldier->pathing.sFinalDestination || pSoldier->bGoodContPath )
{
// OK< check if we are the selected guy!
if ( pSoldier->ubID == gusSelectedSoldier )
{
if (SoldierOnScreen( pSoldier->ubID ) )
{
sGridNo = pSoldier->pathing.sFinalDestination;
if ( pSoldier->bGoodContPath )
{
sGridNo = pSoldier->sContPathLocation;
}
// Do a check if we can afford move here!
// get a path to dest...
if ( FindBestPath( pSoldier, sGridNo, pSoldier->pathing.bLevel, pSoldier->usUIMovementMode, NO_COPYROUTE, 0 ) )
{
sAPCost = PtsToMoveDirection( pSoldier, (UINT8)guiPathingData[ 0 ] );
if ( EnoughPoints( pSoldier, sAPCost, 0 , FALSE ) )
{
return( TRUE );
}
}
else
{
return( FALSE );
}
}
}
}
}
return( FALSE );
}
INT16 GetAPsToReadyWeapon( SOLDIERTYPE *pSoldier, UINT16 usAnimState )
{
UINT16 usItem;
UINT8 ubReadyAPs = 0;
// Don't check the ready APs of an attached underbarrel launcher; the cost of raising a weapon
// should not change with firing mode as it creates an unfun opportunity for micro-optimization
/*if(pSoldier->bWeaponMode == WM_ATTACHED_GL || pSoldier->bWeaponMode == WM_ATTACHED_GL_BURST || pSoldier->bWeaponMode == WM_ATTACHED_GL_AUTO)//dnl ch72 250913
usItem = GetAttachedGrenadeLauncher(&pSoldier->inv[HANDPOS]);
else*/
usItem = pSoldier->inv[HANDPOS].usItem;
// If this is a dwel pistol anim
// ATE: What was I thinking, hooking into animations like this....
//if ( usAnimState == READY_DUAL_STAND || usAnimState == READY_DUAL_CROUCH )
//{
//return( APBPConstants[AP_READY_DUAL] );
//}
if ( AM_A_ROBOT( pSoldier ) && gGameExternalOptions.fRobotNoReadytime){
return 0;
}
if ( pSoldier->IsValidSecondHandShot( ) )
{
//Madd: return the greater of the two weapons + 1:
INT16 rt1 = 0, rt2 = 0;
// SANDRO - STOMP traits - decrease APs to ready pistols and revolvers if having Gunslinger trait
if ( HAS_SKILL_TRAIT(pSoldier, GUNSLINGER_NT) && ( gGameOptions.fNewTraitSystem ))
{
if (Weapon[usItem].ubWeaponType == GUN_PISTOL)
rt1 = (INT16)(Weapon[usItem].ubReadyTime * max(0, (100 - GetPercentReadyTimeAPReduction(&pSoldier->inv[ HANDPOS ]) - gSkillTraitValues.ubGSPercentReadyPistolsReduction * NUM_SKILL_TRAITS(pSoldier, GUNSLINGER_NT) ) ) / 100);
if (Weapon[pSoldier->inv[SECONDHANDPOS].usItem].ubWeaponType == GUN_PISTOL)
rt2 = (INT16)(Weapon[pSoldier->inv[SECONDHANDPOS].usItem].ubReadyTime * max(0, (100 - GetPercentReadyTimeAPReduction(&pSoldier->inv[ HANDPOS ]) - gSkillTraitValues.ubGSPercentReadyPistolsReduction * NUM_SKILL_TRAITS(pSoldier, GUNSLINGER_NT) ) ) / 100);
}
else
{
rt1 = Weapon[usItem].ubReadyTime * ( 100 - GetPercentReadyTimeAPReduction(&pSoldier->inv[ HANDPOS ]) ) / 100;
rt2 = Weapon[pSoldier->inv[SECONDHANDPOS].usItem].ubReadyTime * ( 100 - GetPercentReadyTimeAPReduction(&pSoldier->inv[ SECONDHANDPOS ]) ) / 100;
}
ubReadyAPs = ( max(rt1,rt2) + APBPConstants[AP_READY_DUAL] );
}
// OK, now check type of weapon
if ( usItem == NOTHING )
{
return( 0 );
}
else
{
// CHECK FOR RIFLE (and grenade launchers)
if ( Item[ usItem ].usItemClass & (IC_GUN | IC_LAUNCHER) )
{
ubReadyAPs = Weapon[ usItem ].ubReadyTime;
//ADB if 0, which it normally is, avoid the function call and division
if (ubReadyAPs == 0)
{
return 0;
}
else
{
// Get bonus to draw speed from attachments
ubReadyAPs = (( ubReadyAPs * ( 100 - GetPercentReadyTimeAPReduction(&pSoldier->inv[ HANDPOS ]) ) ) / 100);
// alternative weapon holding?
if ( gGameExternalOptions.ubAllowAlternativeWeaponHolding )
{
// If we are told to go to the alt weapon holding mode
if ( gAnimControl[ usAnimState ].uiFlags & ( ANIM_ALT_WEAPON_HOLDING ) || (pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD && usAnimState == INVALID_ANIMATION) )//dnl ch71 180913
{
if ( Item[ usItem ].twohanded )
{
// Raising only to hip, either charge no APs or a portion of them
ubReadyAPs = ((ubReadyAPs * gGameExternalOptions.ubToAltWeaponHoldReadyAPsPerc) + 99) / 100 ; // round up for rifles
}
else
{
// raising pistol one-handed, make it faster
ubReadyAPs = (ubReadyAPs * gGameExternalOptions.ubToAltWeaponHoldReadyAPsPerc) / 100 ; // round down for pistols
}
}
// If we are told to go from alternative to standard weapon holding
else if ( gAnimControl[ pSoldier->usAnimState ].uiFlags & ( ANIM_ALT_WEAPON_HOLDING ) )
{
if ( Item[ usItem ].twohanded )
{
ubReadyAPs = ubReadyAPs * gGameExternalOptions.ubFromAltWeaponHoldReadyAPsPerc / 100; // round down for rifles
}
else
{
ubReadyAPs = ((ubReadyAPs * gGameExternalOptions.ubFromAltWeaponHoldReadyAPsPerc)+ 99) / 100; // round up for pistols
}
}
}
// STOMP traits
// Decrease APs to ready LMG if having Auto Weapon trait
if ( (Weapon[usItem].ubWeaponType == GUN_LMG) && HAS_SKILL_TRAIT(pSoldier, AUTO_WEAPONS_NT) && ( gGameOptions.fNewTraitSystem ))
{
ubReadyAPs = (( ubReadyAPs * max(0, (100 - gSkillTraitValues.ubAWPercentReadyLMGReduction * NUM_SKILL_TRAITS(pSoldier, AUTO_WEAPONS_NT) ) ) / 100)); // -10% per trait
}
// Decrease APs to ready pistols and revolvers if having Gunslinger trait
else if ( (Weapon[usItem].ubWeaponType == GUN_PISTOL) && HAS_SKILL_TRAIT(pSoldier, GUNSLINGER_NT) && ( gGameOptions.fNewTraitSystem ))
{
ubReadyAPs = (( ubReadyAPs * max(0, (100 - gSkillTraitValues.ubGSPercentReadyPistolsReduction * NUM_SKILL_TRAITS(pSoldier, GUNSLINGER_NT) ) ) / 100)); // -15% per trait
}
}
}
}
return( (INT16)max( 0, ubReadyAPs ) );
}
INT16 GetAPsToClimbRoof( SOLDIERTYPE *pSoldier, BOOLEAN fClimbDown )
{
// SANDRO - STOMP traits - added a feature to reduce APs needed to climb on or off roof for Martial Arts trait
UINT16 iAPsToClimb = 0;
if ( !fClimbDown )
{
if ( HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ) && gGameOptions.fNewTraitSystem )
iAPsToClimb = max( 1, (INT16)(APBPConstants[AP_CLIMBROOF] * ( 100 - gSkillTraitValues.ubMAAPsClimbOrJumpReduction * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT )) / 100.0f + 0.5f)); // -25% per trait
else
iAPsToClimb = APBPConstants[AP_CLIMBROOF];
// OK, add aps to goto stand stance...
iAPsToClimb += GetAPsToChangeStance( pSoldier, ANIM_STAND );
}
else
{
if ( HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ) && gGameOptions.fNewTraitSystem )
iAPsToClimb = max( 1, (INT16)(APBPConstants[AP_CLIMBOFFROOF] * ( 100 - gSkillTraitValues.ubMAAPsClimbOrJumpReduction * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT )) / 100.0f + 0.5f)); // -25% per trait
else
iAPsToClimb = APBPConstants[AP_CLIMBOFFROOF];
// Add aps to goto crouch
iAPsToClimb += GetAPsToChangeStance( pSoldier, ANIM_CROUCH );
}
// A check if we don't go to zero somehow - SANDRO
if( iAPsToClimb < 1 )
iAPsToClimb = 1;
// return our value
return(iAPsToClimb);
}
INT16 GetAPsToJumpWall( SOLDIERTYPE *pSoldier, BOOLEAN fClimbDown )
{
// SANDRO - STOMP traits - added a feature to reduce APs needed to climb on or off roof for Martial Arts trait
UINT16 iAPsToClimb = 0;
if ( !fClimbDown )
{
if ( HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ) && gGameOptions.fNewTraitSystem )
iAPsToClimb = max( 1, (INT16)(APBPConstants[AP_JUMPWALL] * ( 100 - gSkillTraitValues.ubMAAPsClimbOrJumpReduction * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT )) / 100.0f + 0.5f)); // -25% per trait
else
iAPsToClimb = APBPConstants[AP_JUMPWALL];
// OK, add aps to goto stand stance...
iAPsToClimb += GetAPsToChangeStance( pSoldier, ANIM_STAND );
}
else
{
if ( HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ) && gGameOptions.fNewTraitSystem )
iAPsToClimb = max( 1, (INT16)(APBPConstants[AP_JUMPOFFWALL] * ( 100 - gSkillTraitValues.ubMAAPsClimbOrJumpReduction * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT )) / 100.0f + 0.5f)); // -25% per trait
else
iAPsToClimb = APBPConstants[AP_JUMPOFFWALL];
// Add aps to goto crouch
iAPsToClimb += GetAPsToChangeStance( pSoldier, ANIM_CROUCH );
}
// A check if we don't go to zero somehow - SANDRO
if( iAPsToClimb < 1 )
iAPsToClimb = 1;
// return our value
return(iAPsToClimb);
}
INT16 GetAPsToJumpThroughWindows( SOLDIERTYPE *pSoldier, BOOLEAN fWithBackpack )
{
// STOMP traits - Martial Arts reduce APs spent for jumping obstacles
if ( !fWithBackpack )
{
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ))
return( max( 1, (INT16)(APBPConstants[AP_JUMPWINDOW] * ( 100 - gSkillTraitValues.ubMAAPsClimbOrJumpReduction * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT )) / 100.0f + 0.5f ) ) ); // -25% per trait
else
return( APBPConstants[AP_JUMPWINDOW] );
}
else
{
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ))
return( max( 1, (INT16)(APBPConstants[AP_JUMPWINDOW] * ( 100 - gSkillTraitValues.ubMAAPsClimbOrJumpReduction * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT )) / 100.0f + 0.5f ) ) ); // -25% per trait
else
return( APBPConstants[AP_JUMPWINDOW] );
}
}
INT16 GetBPsToClimbRoof( SOLDIERTYPE *pSoldier, BOOLEAN fClimbDown )
{
// SANDRO - STOMP traits - Athletics reduce breath points spent for moving
if ( !fClimbDown )
{
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, ATHLETICS_NT ))
return( APBPConstants[BP_CLIMBROOF] * (100 - gSkillTraitValues.ubATBPsMovementReduction) / 100 );
else
return( APBPConstants[BP_CLIMBROOF] );
}
else
{
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, ATHLETICS_NT ))
return( APBPConstants[BP_CLIMBOFFROOF] * (100 - gSkillTraitValues.ubATBPsMovementReduction) / 100 );
else
return( APBPConstants[BP_CLIMBOFFROOF] );
}
}
// SANDRO - added function to calculate APs for jumping over fence
INT16 GetAPsToJumpFence( SOLDIERTYPE *pSoldier, BOOLEAN fWithBackpack )
{
// STOMP traits - Martial Arts reduce APs spent for jumping obstacles
if ( !fWithBackpack )
{
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ))
return( max( 1, (INT16)(APBPConstants[AP_JUMPFENCE] * ( 100 - gSkillTraitValues.ubMAAPsClimbOrJumpReduction * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT )) / 100.0f + 0.5f ) ) ); // -25% per trait
else
return( APBPConstants[AP_JUMPFENCE] );
}
else
{
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ))
return( max( 1, (INT16)(APBPConstants[AP_JUMPFENCEBPACK] * ( 100 - gSkillTraitValues.ubMAAPsClimbOrJumpReduction * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT )) / 100.0f + 0.5f ) ) ); // -25% per trait
else
return( APBPConstants[AP_JUMPFENCEBPACK] );
}
}
INT16 GetBPsToJumpWall( SOLDIERTYPE *pSoldier, BOOLEAN fClimbDown )
{
// SANDRO - STOMP traits - Athletics reduce breath points spent for moving
if ( !fClimbDown )
{
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, ATHLETICS_NT ))
return( APBPConstants[BP_JUMPWALL] * (100 - gSkillTraitValues.ubATBPsMovementReduction) / 100 );
else
return( APBPConstants[BP_JUMPWALL] );
}
else
{
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, ATHLETICS_NT ))
return( APBPConstants[BP_JUMPOFFWALL] * (100 - gSkillTraitValues.ubATBPsMovementReduction) / 100 );
else
return( APBPConstants[BP_JUMPOFFWALL] );
}
}
// SANDRO - added function to calculate BPs for jumping over fence
INT16 GetBPsToJumpFence( SOLDIERTYPE *pSoldier, BOOLEAN fWithBackpack )
{
// STOMP traits - Athletics reduce breath points spent for moving
if ( !fWithBackpack )
{
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, ATHLETICS_NT ))
return( APBPConstants[BP_JUMPFENCE] * (100 - gSkillTraitValues.ubATBPsMovementReduction) / 100 );
else
return( APBPConstants[BP_JUMPFENCE] );
}
else
{
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, ATHLETICS_NT ))
return( APBPConstants[BP_JUMPFENCEBPACK] * (100 - gSkillTraitValues.ubATBPsMovementReduction) / 100 );
else
return( APBPConstants[BP_JUMPFENCEBPACK] );
}
}
// SANDRO - added function to calculate BPs for jumping over fence
INT16 GetBPsToJumpThroughWindows( SOLDIERTYPE *pSoldier, BOOLEAN fWithBackpack )
{
// STOMP traits - Athletics reduce breath points spent for moving
if ( !fWithBackpack )
{
// WANNE: Yes, we use the jumpfence BPs, it is simpler for now :)
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, ATHLETICS_NT ))
return( APBPConstants[BP_JUMPFENCE] * (100 - gSkillTraitValues.ubATBPsMovementReduction) / 100 );
else
return( APBPConstants[BP_JUMPFENCE] );
}
else
{
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, ATHLETICS_NT ))
return( APBPConstants[BP_JUMPFENCEBPACK] * (100 - gSkillTraitValues.ubATBPsMovementReduction) / 100 );
else
return( APBPConstants[BP_JUMPFENCEBPACK] );
}
}
INT16 GetAPsToCutFence( SOLDIERTYPE *pSoldier )
{
// OK, it's normally just cost, but add some if different stance...
return( GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) + APBPConstants[AP_USEWIRECUTTERS] );
}
INT16 GetAPsToBeginFirstAid( SOLDIERTYPE *pSoldier )
{
// OK, it's normally just cost, but add some if different stance...
return( GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) + APBPConstants[AP_START_FIRST_AID] );
}
INT16 GetAPsToBeginRepair( SOLDIERTYPE *pSoldier )
{
// OK, it's normally just cost, but add some if different stance...
return( GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) + APBPConstants[AP_START_REPAIR] );
}
INT16 GetAPsToRefuelVehicle( SOLDIERTYPE *pSoldier )
{
// OK, it's normally just cost, but add some if different stance...
return( GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) + APBPConstants[AP_REFUEL_VEHICLE] );
}
#define TOSSES_PER_10TURNS 18 // max # of grenades tossable in 10 turns
//COMMENTED OUT, AS THEY WERE
//1. ALREADY DEFINED IN POINTS.H TO BEGIN WITH
//2. EXTERNALIZED BY GOTTHARD 7/2/08
//#define APBPConstants[AP_MIN_AIM_ATTACK] 0 // minimum permitted extra aiming
//#define APBPConstants[AP_MAX_AIM_ATTACK] 4 // maximum permitted extra aiming
INT16 MinAPsToThrow( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubAddTurningCost )
{
INT32 iTop, iBottom;
INT32 iFullAPs;
INT32 iAPCost = APBPConstants[AP_MIN_AIM_ATTACK];
UINT16 usInHand;
#if 0//dnl ch72 180913
UINT16 usTargID;
UINT32 uiMercFlags;
UINT8 ubDirection;
if(pSoldier->bWeaponMode == WM_ATTACHED_GL || pSoldier->bWeaponMode == WM_ATTACHED_GL_BURST || pSoldier->bWeaponMode == WM_ATTACHED_GL_AUTO)//dnl ch63 240813
usInHand = GetAttachedGrenadeLauncher(&pSoldier->inv[HANDPOS]);
else
#endif
// make sure the guy's actually got a throwable item in his hand!
usInHand = pSoldier->inv[HANDPOS].usItem;
#if 0//dnl ch72 180913 this goes down because of new trait system
if ( ( !(Item[ usInHand ].usItemClass & IC_GRENADE) &&
!(Item[ usInHand ].usItemClass & IC_LAUNCHER)) )
{
//AXP 25.03.2007: See if we are about to throw grenade (grenade was not in hand, but in temp object)
if ( pSoldier->pTempObject != NULL && pSoldier->pThrowParams != NULL &&
pSoldier->pThrowParams->ubActionCode == THROW_ARM_ITEM && (Item[ pSoldier->pTempObject->usItem ].usItemClass & IC_GRENADE) )
{
//nothing here
}
else
{
#ifdef JA2TESTVERSION
ScreenMsg( MSG_FONT_YELLOW, MSG_DEBUG, L"MinAPsToThrow - Called when in-hand item is %d", usInHand );
#endif
return(0);
}
}
if (!TileIsOutOfBounds(sGridNo))
{
// Given a gridno here, check if we are on a guy - if so - get his gridno
if ( FindSoldier( sGridNo, &usTargID, &uiMercFlags, FIND_SOLDIER_GRIDNO ) )
{
sGridNo = MercPtrs[ usTargID ]->sGridNo;
}
/*// OK, get a direction and see if we need to turn...
if (ubAddTurningCost)
{
ubDirection = (UINT8)GetDirectionFromGridNo( sGridNo, pSoldier );
// Is it the same as he's facing?
if ( ubDirection != pSoldier->ubDirection )
{
//Lalien: disabled it again
//AXP 25.03.2007: Reenabled look cost
//iAPCost += GetAPsToLook( pSoldier );
}
}*/
}
/*else
{
// Assume we need to add cost!
//iAPCost += GetAPsToLook( pSoldier );
}*/
// if attacking a new target (or if the specific target is uncertain)
//AXP 25.03.2007: Aim-at-same-tile AP cost/bonus doesn't make any sense for thrown objects
//if ( ( sGridNo != pSoldier->sLastTarget ) )
//{
// iAPCost += APBPConstants[AP_CHANGE_TARGET];
//}
//iAPCost += GetAPsToChangeStance( pSoldier, ANIM_STAND ); // moved lower - SANDRO
#endif
// Calculate default top & bottom of the magic "aiming" formula)
bool grenadAPreductionpossible = false;
FLOAT tossesper10turns = TOSSES_PER_10TURNS;
// if grenade, or if we've thrown the object for real, it disapears from our hands and is converted to TempObject
if ( Item[usInHand].usItemClass & IC_GRENADE
|| ( pSoldier->pTempObject != NULL && pSoldier->pThrowParams != NULL &&
pSoldier->pThrowParams->ubActionCode == THROW_ARM_ITEM && ( Item[pSoldier->pTempObject->usItem].usItemClass & IC_GRENADE ) ) )
{
grenadAPreductionpossible = true;
// modify by ini values
tossesper10turns *= gItemSettings.fShotsPer10TurnsModifierThrowGrenade;
}
// get this man's maximum possible action points (ignoring carryovers)
iFullAPs = pSoldier->CalcActionPoints( );
// the 2 times is here only to around rounding off using integer math later
iTop = 2 * iFullAPs;
// if it's anything but a mortar
// if ( usInHand != MORTAR)
// tosses per turn is for max dexterity, drops down to 1/2 at dexterity = 0
// bottom = (TOSSES_PER_10TURNS * (50 + (ptr->dexterity / 2)) / 10);
//else
iBottom = ( tossesper10turns * (50 + ( pSoldier->stats.bDexterity / 2 ) ) / 10 );
// add minimum aiming time to the overall minimum AP_cost
// This here ROUNDS UP fractions of 0.5 or higher using integer math
// This works because 'top' is 2x what it really should be throughout
iAPCost += ( ( ( 100 * iTop ) / iBottom) + 1) / 2;
// SANDRO - STOMP traits - reduce APs needed to throw grenades if having Demolitions skill
if( HAS_SKILL_TRAIT( pSoldier, DEMOLITIONS_NT ) && gGameOptions.fNewTraitSystem )
{
if ( grenadAPreductionpossible )
{
iAPCost = max( 1, (INT32)(iAPCost * (100 - gSkillTraitValues.ubDEAPsNeededToThrowGrenadesReduction) / 100));
}
}
// moved from above - SANDRO
//dnl ch72 180913 someone create throwing grenades from crouch so ANIM_STAND is not only stance for throwing, and as throwing object disappears from our hand when is thrown we need to determine desired height for turning cost
if(ubAddTurningCost && !TileIsOutOfBounds(sGridNo) && GetDirectionFromGridNo(sGridNo, pSoldier) != pSoldier->ubDirection)
ubAddTurningCost = TRUE;
else
ubAddTurningCost = FALSE;
if(gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE)
{
iAPCost += GetAPsToChangeStance(pSoldier, ANIM_CROUCH);
iAPCost += CalculateTurningCost(pSoldier, usInHand, ubAddTurningCost, ANIM_CROUCH);
}
else
{
iAPCost += GetAPsToChangeStance(pSoldier, gAnimControl[pSoldier->usAnimState].ubEndHeight);
iAPCost += CalculateTurningCost(pSoldier, usInHand, ubAddTurningCost);
}
// the minimum AP cost of ANY throw can NEVER be more than merc has APs!
if ( iAPCost > iFullAPs )
iAPCost = iFullAPs;
// this SHOULD be impossible, but nevertheless...
if ( iAPCost < 1 )
iAPCost = 1;
return ( (INT16)iAPCost );
}
INT16 GetAPsToDropBomb( SOLDIERTYPE *pSoldier )
{
// SANDRO - STOMP traits - Ambidextrous bonus
if( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, AMBIDEXTROUS_NT ) )
return( max( 1, (INT16)(APBPConstants[AP_DROP_BOMB] * (100 - gSkillTraitValues.ubAMHandleBombsAPsReduction) / 100.0f + 0.5f)) );
else
return( APBPConstants[AP_DROP_BOMB] );
}
INT16 GetAPsToPlantMine( SOLDIERTYPE *pSoldier )
{
// SANDRO - STOMP traits - Ambidextrous bonus
if( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, AMBIDEXTROUS_NT ) )
return( max( 1, (INT16)(APBPConstants[AP_BURY_MINE] * (100 - gSkillTraitValues.ubAMHandleBombsAPsReduction) / 100.0f + 0.5f)) );
else
return( APBPConstants[AP_BURY_MINE] );
}
UINT16 GetTotalAPsToDropBomb( SOLDIERTYPE *pSoldier, INT32 sGridNo )
{
INT16 sAPs = 0;
sAPs = PlotPath( pSoldier, sGridNo, NO_COPYROUTE, NO_PLOT, TEMPORARY, (UINT16)pSoldier->usUIMovementMode, NOT_STEALTH, FORWARD, pSoldier->bActionPoints );
if ( sAPs > 0 )
{
if(Item[pSoldier->inv[HANDPOS].usItem].mine == 1)
sAPs += GetAPsToPlantMine( pSoldier ); // changed by SANDRO
else
sAPs += GetAPsToDropBomb( pSoldier ); // changed by SANDRO
}
return( sAPs );
}
INT16 GetAPsToUseRemote( SOLDIERTYPE *pSoldier )
{
return( APBPConstants[AP_USE_REMOTE] );
}
INT16 GetAPsToStealItem( SOLDIERTYPE *pSoldier, SOLDIERTYPE *pTargetSoldier, INT16 sMapPos ) // SANDRO - added target
{
INT16 sAPCost = 0;
if (sMapPos != -1)
{
sAPCost = PlotPath( pSoldier, sMapPos, NO_COPYROUTE, NO_PLOT, TEMPORARY, (UINT16)pSoldier->usUIMovementMode, NOT_STEALTH, FORWARD, pSoldier->bActionPoints );
}
// ADD APS TO PICKUP
/////////////////////////////////////////////////////////////////////////////////////////
// CHANGED BY SANDRO - REDUCE AP COST TO STEAL FOR MARTIAL ARTS AND HAND TO HAND
if (pTargetSoldier != NULL && pTargetSoldier->bCollapsed && gGameExternalOptions.fEnhancedCloseCombatSystem)
{
sAPCost += (GetBasicAPsToPickupItem( pSoldier )); // stealing from collapsed soldiers is treated differently
}
else if (HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ) && ( gGameOptions.fNewTraitSystem ))
{
sAPCost += max( 1, (INT16)(APBPConstants[AP_STEAL_ITEM] * (100 - gSkillTraitValues.ubMAReducedAPsToSteal * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT ))/ 100.0f + 0.5f));
}
else
{
sAPCost += APBPConstants[AP_STEAL_ITEM];
}
/////////////////////////////////////////////////////////////////////////////////////////
// CJC August 13 2002: added cost to stand into equation
if (!(PTR_STANDING))
{
sAPCost += GetAPsToChangeStance( pSoldier, ANIM_STAND );
}
return sAPCost;
}
INT16 GetBPsToStealItem( SOLDIERTYPE *pSoldier )
{
return( APBPConstants[BP_STEAL_ITEM] );
}
INT16 GetAPsToUseJar( SOLDIERTYPE *pSoldier, INT32 usMapPos )
{
return GetAPsToChangeStance(pSoldier, ANIM_CROUCH) + APBPConstants[AP_TAKE_BLOOD];
}
INT16 GetAPsToUseCan( SOLDIERTYPE *pSoldier, INT32 usMapPos )
{
return GetAPsToChangeStance(pSoldier, ANIM_CROUCH) + APBPConstants[AP_ATTACH_CAN];
}
INT16 GetAPsToHandcuff( SOLDIERTYPE *pSoldier, INT32 usMapPos )
{
return GetAPsToChangeStance(pSoldier, ANIM_CROUCH) + APBPConstants[AP_HANDCUFF];
}
INT16 GetAPsToApplyItem( SOLDIERTYPE *pSoldier, INT32 usMapPos )
{
INT16 sAPCost = 0;
sAPCost = PlotPath( pSoldier, usMapPos, NO_COPYROUTE, NO_PLOT, TEMPORARY, (UINT16)pSoldier->usUIMovementMode, NOT_STEALTH, FORWARD, pSoldier->bActionPoints );
sAPCost += APBPConstants[AP_APPLYITEM];
return sAPCost;
}
INT16 GetAPsToFillBloodbag( SOLDIERTYPE *pSoldier, INT32 usMapPos )
{
INT16 sAPCost = 0;
sAPCost = PlotPath( pSoldier, usMapPos, NO_COPYROUTE, NO_PLOT, TEMPORARY, (UINT16)pSoldier->usUIMovementMode, NOT_STEALTH, FORWARD, pSoldier->bActionPoints );
sAPCost += APBPConstants[AP_FILLBLOODBAG];
return sAPCost;
}
// added by Flugente
INT16 GetAPsForMultiTurnAction( SOLDIERTYPE *pSoldier, UINT8 usActionType )
{
INT16 sAPCost = 0;
switch ( usActionType )
{
case MTA_FORTIFY:
sAPCost += APBPConstants[AP_FORTIFICATION];
break;
case MTA_REMOVE_FORTIFY:
sAPCost += APBPConstants[AP_REMOVE_FORTIFICATION];
break;
case MTA_HACK:
sAPCost += APBPConstants[AP_HACK];
break;
}
if ( usActionType == MTA_FORTIFY || usActionType == MTA_REMOVE_FORTIFY )
{
sAPCost = (sAPCost * (100 + pSoldier->GetBackgroundValue(BG_FORTIFY))) / 100;
}
return sAPCost;
}
INT16 GetAPsForInteractiveAction( SOLDIERTYPE *pSoldier, UINT16 usActionType )
{
INT16 sAPCost = 0;
switch ( usActionType )
{
case INTERACTIVE_STRUCTURE_HACKABLE:
sAPCost = GetAPsForMultiTurnAction( pSoldier, MTA_HACK );
break;
case INTERACTIVE_STRUCTURE_READFILE:
sAPCost += APBPConstants[AP_READFILE];
break;
case INTERACTIVE_STRUCTURE_WATERTAP:
sAPCost += APBPConstants[AP_WATERTAP];
break;
case INTERACTIVE_STRUCTURE_SODAMACHINE:
sAPCost += APBPConstants[AP_SODAMACHINE];
break;
case INTERACTIVE_STRUCTURE_MINIGAME:
sAPCost += APBPConstants[AP_READFILE];
break;
case INTERACTIVE_STRUCTURE_VARIOUS:
sAPCost += APBPConstants[AP_READFILE];
break;
default:
break;
}
return sAPCost;
}
INT16 GetAPsToJumpOver( SOLDIERTYPE *pSoldier )
{
// -25% APs needed to for MA traits
if ( HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ) && gGameOptions.fNewTraitSystem )
return( GetAPsToChangeStance( pSoldier, ANIM_STAND ) + max( 1, (INT16)(APBPConstants[AP_JUMP_OVER] * (100 - gSkillTraitValues.ubMAAPsClimbOrJumpReduction * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT )) / 100.0f + 0.5f) ) );
else
return( GetAPsToChangeStance( pSoldier, ANIM_STAND ) + APBPConstants[AP_JUMP_OVER] );
}
// HEADROCK HAM 3.6: Calculate the actual AP cost to add this many Extra Aiming levels, taking into account
// APBP Constants and extra game features.
INT32 CalcAPCostForAiming( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo, INT8 bAimTime )
{
Assert(pSoldier != NULL);
Assert(&pSoldier->inv[HANDPOS] != NULL);
INT16 sAPCost = 0;
UINT16 usItemNum = pSoldier->inv[HANDPOS].usItem;
#ifndef dnlCALCBESTSHOT//dnl ch69 150913 if this is turn on we get incorrect APs as MinAPsToShootOrStab always include raise gun cost
if (!ARMED_VEHICLE(pSoldier) && !( gAnimControl[ pSoldier->usAnimState ].uiFlags & ( ANIM_FIREREADY | ANIM_FIRE )))//dnl ch64 310813
{
// Weapon not ready, check aiming from hip, else add raise gun cost
//if (!pSoldier->IsValidShotFromHip(bAimTime,sTargetGridNo))
{
sAPCost += CalculateRaiseGunCost( pSoldier, TRUE, sTargetGridNo, bAimTime );
}
}
#endif
if (gGameExternalOptions.fIncreasedAimingCost )
{
// HEADROCK HAM B2.6: Changed the number of APs to attack when aiming.
// HEADROCK HAM 3.1: Externalized the entire function to allow customization of each detail.
if (bAimTime > 0)
{
// HEADROCK HAM 3: One-time penalty: Add part of the weapon's Ready AP cost. Reinstated because
// it's now externalized.
if (gGameExternalOptions.ubFirstAimReadyCostDivisor > 0)
{
UINT16 usWeaponReadyTime;
UINT8 ubReadyTimeDivisor;
usWeaponReadyTime = Weapon[ usItemNum ].ubReadyTime * (100 - GetPercentReadyTimeAPReduction(&pSoldier->inv[HANDPOS])) / 100;
ubReadyTimeDivisor = gGameExternalOptions.ubFirstAimReadyCostDivisor;
sAPCost += usWeaponReadyTime / ubReadyTimeDivisor;
}
// If the weapon has a scope, and the target is within eligible range for scope use
if ( (UsingNewCTHSystem() == false && IsScoped(&pSoldier->inv[HANDPOS]) && GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sTargetGridNo ) >= GetMinRangeForAimBonus(pSoldier, &pSoldier->inv[HANDPOS]) && !pSoldier->IsValidAlternativeFireMode(bAimTime,sTargetGridNo))
|| (UsingNewCTHSystem() == true && GetBestScopeMagnificationFactor(pSoldier, &pSoldier->inv[HANDPOS], (FLOAT)GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sTargetGridNo ) > 1.0 ) && !pSoldier->IsValidAlternativeFireMode(bAimTime,sTargetGridNo)))
{
// Add an individual cost for EACH click, as necessary.
sAPCost += APBPConstants[AP_FIRST_CLICK_AIM_SCOPE];
if (bAimTime > 1)
sAPCost += APBPConstants[AP_SECOND_CLICK_AIM_SCOPE];
if (bAimTime > 2)
sAPCost += APBPConstants[AP_THIRD_CLICK_AIM_SCOPE];
if (bAimTime > 3)
sAPCost += APBPConstants[AP_FOURTH_CLICK_AIM_SCOPE];
if (bAimTime > 4)
sAPCost += APBPConstants[AP_FIFTH_CLICK_AIM_SCOPE];
if (bAimTime > 5)
sAPCost += APBPConstants[AP_SIXTH_CLICK_AIM_SCOPE];
if (bAimTime > 6)
sAPCost += APBPConstants[AP_SEVENTH_CLICK_AIM_SCOPE];
if (bAimTime > 7)
sAPCost += APBPConstants[AP_EIGHTH_CLICK_AIM_SCOPE];
}
// Weapon has no scope or not within requried range. Apply regular AP costs.
else
{
sAPCost += bAimTime * APBPConstants[AP_CLICK_AIM];
}
}
else
{
return(0);
}
}
// Regular cost system
else
{
sAPCost += bAimTime * APBPConstants[AP_CLICK_AIM];
}
return sAPCost;
}
// HEADROCK HAM 3.6: Calculate how many Aiming Levels we can get with a given amount of APs.
INT8 CalcAimingLevelsAvailableWithAP( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo, INT8 bAPsLeft )
{
INT8 bAllowedLevels = 0;
for (INT16 x = APBPConstants[AP_MIN_AIM_ATTACK]; x <= AllowedAimingLevels(pSoldier, sTargetGridNo); x++)
{
if (CalcAPCostForAiming( pSoldier, sTargetGridNo, (INT8)x ) <= (INT32)bAPsLeft)
{
bAllowedLevels = (INT8)x;
}
else
{
break;
}
}
return (bAllowedLevels);
}
// SANDRO - Added feature to reduce APs for change states, if having MA trait
INT16 GetAPsCrouch( SOLDIERTYPE *pSoldier, BOOLEAN fBackpackCheck )
{
UINT16 iFinalAPsToCrouch;
iFinalAPsToCrouch = APBPConstants[AP_CROUCH];
// if backpack and new inventory
if ( fBackpackCheck && (UsingNewInventorySystem() == true) && pSoldier->inv[BPACKPOCKPOS].exists() == true && !pSoldier->flags.ZipperFlag)
iFinalAPsToCrouch += fBackpackCheck;//dnl ch70 160913 was 1
// -x% APs needed to change stance for MA trait
if ( HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ) && ( gGameOptions.fNewTraitSystem ))
iFinalAPsToCrouch = (INT16)(iFinalAPsToCrouch * ( 100 - gSkillTraitValues.ubMAAPsChangeStanceReduction * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT )) / 100.0f + 0.5f );
if (iFinalAPsToCrouch < 1 )
iFinalAPsToCrouch = 1;
return( iFinalAPsToCrouch );
}
// SANDRO - Added feature to reduce APs for change states, if having MA trait
INT16 GetAPsProne( SOLDIERTYPE *pSoldier, BOOLEAN fBackpackCheck )
{
UINT16 iFinalAPsToLieDown;
iFinalAPsToLieDown = APBPConstants[AP_PRONE];
// if backpack and new inventory
if ( fBackpackCheck && (UsingNewInventorySystem() == true) && pSoldier->inv[BPACKPOCKPOS].exists() == true && !pSoldier->flags.ZipperFlag)
iFinalAPsToLieDown += fBackpackCheck;//dnl ch70 160913 was 1
// -x% APs needed to change stance for MA trait
if ( HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ) && ( gGameOptions.fNewTraitSystem ))
iFinalAPsToLieDown = (INT16)(iFinalAPsToLieDown * ( 100 - gSkillTraitValues.ubMAAPsChangeStanceReduction * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT )) / 100.0f + 0.5f );
if (iFinalAPsToLieDown < 1 )
iFinalAPsToLieDown = 1;
return( iFinalAPsToLieDown );
}
// SANDRO - Added feature to calculate start run cost (for Athletics trait)
INT16 GetAPsStartRun( SOLDIERTYPE *pSoldier )
{
INT16 val = APBPConstants[AP_START_RUN_COST];
// Flugente: riot shields lower movement speed
if ( pSoldier->IsRiotShieldEquipped( ) )
val *= gItemSettings.fShieldMovementAPCostModifier;
if ( pSoldier->IsDragging( ) )
val *= gItemSettings.fDragAPCostModifier;
// Athletics trait
if( HAS_SKILL_TRAIT( pSoldier, ATHLETICS_NT ) && gGameOptions.fNewTraitSystem )
val = max( 1, (INT16)(val * (100 - gSkillTraitValues.ubATAPsMovementReduction) / 100.0f + 0.5f) );
return val;
}
// SANDRO - Added feature to calculate APs for handling doors (for Ambidextrous trait)
INT16 GetAPsToOpenDoor( SOLDIERTYPE *pSoldier )
{
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, AMBIDEXTROUS_NT ) )
return( max( 1, (INT16)(APBPConstants[AP_OPEN_DOOR] * (100 - gSkillTraitValues.ubAMHandleDoorsAPsReduction) / 100.0f + 0.5f )) );
else
return( APBPConstants[AP_OPEN_DOOR] );
}
// SANDRO - Added feature to calculate APs for handling doors (for Ambidextrous trait)
INT16 GetAPsToPicklock( SOLDIERTYPE *pSoldier )
{
if( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, AMBIDEXTROUS_NT ) )
return( max( 1, (INT16)(APBPConstants[AP_PICKLOCK] * (100 - gSkillTraitValues.ubAMHandleDoorsAPsReduction) / 100.0f + 0.5f )) );
else
return( APBPConstants[AP_PICKLOCK] );
}
// SANDRO - Added feature to calculate APs for handling doors (for Ambidextrous trait)
INT16 GetAPsToBombDoor( SOLDIERTYPE *pSoldier )
{
if( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, AMBIDEXTROUS_NT ) )
return( max( 1, (INT16)(APBPConstants[AP_EXPLODE_DOOR] * (100 - gSkillTraitValues.ubAMHandleDoorsAPsReduction) / 100.0f + 0.5f )) );
else
return( APBPConstants[AP_EXPLODE_DOOR] );
}
// SANDRO - Added feature to calculate APs for handling doors (for Ambidextrous trait)
INT16 GetAPsToUntrapDoor( SOLDIERTYPE *pSoldier )
{
if( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, AMBIDEXTROUS_NT ) )
return( max( 1, (INT16)(APBPConstants[AP_UNTRAP_DOOR] * (100 - gSkillTraitValues.ubAMHandleDoorsAPsReduction) / 100.0f + 0.5f )) );
else
return( APBPConstants[AP_UNTRAP_DOOR] );
}
// SANDRO - Added feature to calculate APs for pickup items (for Ambidextrous trait)
INT16 GetBasicAPsToPickupItem( SOLDIERTYPE *pSoldier )
{
if( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, AMBIDEXTROUS_NT ) )
return( max( 1, (INT16)(APBPConstants[AP_PICKUP_ITEM] * (100 - gSkillTraitValues.ubAMPickItemsAPsReduction) / 100.0f + 0.5f )) );
else
return( APBPConstants[AP_PICKUP_ITEM] );
}
// SANDRO - Added feature to calculate APs for disarming mine (for Ambidextrous trait)
INT16 GetAPsToDisarmMine( SOLDIERTYPE *pSoldier )
{
if( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, AMBIDEXTROUS_NT ) )
return( max( 1, (INT16)(APBPConstants[AP_DISARM_MINE] * (100 - gSkillTraitValues.ubAMHandleBombsAPsReduction) / 100.0f + 0.5f )) );
else
return( APBPConstants[AP_DISARM_MINE] );
}
INT32 GetBPCostPer10APsForGunHolding( SOLDIERTYPE * pSoldier, BOOLEAN fEstimate )
{
if ( !gGameExternalOptions.ubEnergyCostForWeaponWeight )
return 0;
if ( pSoldier == NULL )
return 0;
if ( Item[pSoldier->inv[HANDPOS].usItem].usItemClass != IC_GUN && Item[pSoldier->inv[HANDPOS].usItem].usItemClass != IC_LAUNCHER )
return 0;
if ( !(gAnimControl[ pSoldier->usAnimState ].uiFlags & (ANIM_FIRE | ANIM_FIREREADY) ) && !fEstimate ) // don't if weapon not raised, but do if we are gonna estimate the cost
return 0;
if ( ARMED_VEHICLE( pSoldier ) || AM_A_ROBOT( pSoldier ) || ENEMYROBOT( pSoldier ) )
return 0;
//////////////////////////////////////////////////////////////////////////////
// THE BASIC COST FOR HOLDING THE GUN RAISED (per AP)
//////////////////////////////////////////////////////////////////////////////
// This somehow represents the effort we must make to keep the gun shouldered and pointing on a target.
// The cost is not directly generated by firing, but is applied on the fire, as we spend APs.. it is also applied
// on shouldering the gun and keeping the gun shouldered over the turns, as well as walking with it up, or turning around.
INT32 iBPcost = 0;
// Get weapon total weight
INT16 sWeaponWeight = CalculateObjectWeight(&pSoldier->inv[pSoldier->ubAttackingHand]);
// Make the weight impact exponential, so lightweight guns cost very little or nearly no energy,
// but as the gun weight goes up, the impact raises dramatically
// this divisor controls the final value and is adjusted by our strength
INT8 bDivisor = 30;
double dModifier = 0;
// +0.25 per strength between 70 and 80, +0.5 per strength between 80 and 90, +0.75 per strength over 90
// --> max modifier +15 at 100 strength, which equals cca -33% energy cost compared to a merc with 60-70 strength
INT16 sOurStrength = EffectiveStrength( pSoldier, FALSE );
if ( sOurStrength > 70 )
{
dModifier += (sOurStrength - 70)/4;
if ( sOurStrength > 80 )
{
dModifier += (sOurStrength - 80)/4;
if ( sOurStrength > 90 )
dModifier += (sOurStrength - 90)/4;
}
}
// -0.25 per strength below 60, further -0.25 per strength below 50, and further -0.25 per strength below 40
// --> max modifier -15 at 40- strength, which equals doubled energy cost compared to a merc with 60-70 strength
else if ( sOurStrength < 60 )
{
dModifier -= (60 - sOurStrength)/4;
if ( sOurStrength < 50 )
{
dModifier -= (50 - sOurStrength)/4;
if ( sOurStrength < 40 )
dModifier -= (40 - sOurStrength)/4;
}
}
// Round the number properly
if (dModifier)
bDivisor += (INT8)(dModifier + 0.5);
else
bDivisor += (INT8)(dModifier - 0.5);
// Calculate the basic cost
// Note: we are multiplying it by 10 here because the final cost is per 10 APs, and because we need some precision in the calculation
iBPcost = 10 * sWeaponWeight * sWeaponWeight / bDivisor;
// ------------------------------------
// APPLY MODIFIERS FOR STANCE AND BIPOD
dModifier = 0;
// Alternative weapon holding?
if (( gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_ALT_WEAPON_HOLDING ) || (fEstimate && pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD) )
{
if ( Item[pSoldier->inv[pSoldier->ubAttackingHand].usItem].twohanded ) // firing from hip is not nearly ?n effort
dModifier += 80; // only 20% cost if on hip
else // holding pistol in one hand is worse in this case
dModifier -= 25; // increased cost by 25%
}
// If weapon is rested on something or we are prone, reduce the power
if ( gGameExternalOptions.fWeaponResting && pSoldier->IsWeaponMounted() || ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE ) )
{
dModifier += 90; // only 10% of the regular cost if prone or rested
if ( GetBipodBonus(&pSoldier->inv[pSoldier->ubAttackingHand]) > 0)
iBPcost = 0; // with bipod on top, there is no bp cost
}
else if ( gAnimControl[ pSoldier->usAnimState ].ubHeight == ANIM_CROUCH )
dModifier += 50; // only 50% of the cost if crouching
if ( dModifier != 0 )
{
dModifier = min( 99, dModifier );
iBPcost = (INT32)(iBPcost * (100 - dModifier) / 100);
}
iBPcost = max(0, iBPcost);
// What the hell, can't shoulder these, return some crazy breath cost
if ( Weapon[pSoldier->inv[pSoldier->ubAttackingHand].usItem].HeavyGun && gGameExternalOptions.ubAllowAlternativeWeaponHolding && (!(gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_ALT_WEAPON_HOLDING) || (fEstimate && pSoldier->bScopeMode != USE_ALT_WEAPON_HOLD )) )
{
iBPcost = (iBPcost + 25) * 5;
}
// For enemies, modify by difficulty
if ( pSoldier->bTeam == ENEMY_TEAM )
{
switch( gGameOptions.ubDifficultyLevel )
{
case DIF_LEVEL_EASY:
break;
case DIF_LEVEL_MEDIUM:
iBPcost = (iBPcost * 9)/10; // -10%
break;
case DIF_LEVEL_HARD:
iBPcost = (iBPcost * 3)/4; // -25%
break;
case DIF_LEVEL_INSANE:
iBPcost /= 2; // -50%
break;
default:
iBPcost = (iBPcost * 9)/10; // -10%
break;
}
}
// Adjust by predefined ini setting
iBPcost = (iBPcost * gGameExternalOptions.ubEnergyCostForWeaponWeight / 100);
// Now we have a number, but we must convert this number to represent BP cost PER 1 AP
// This is roughly "a guess"
iBPcost /= 25;
// this can make the cost zero, but that's what we actully want - with very light guns, there is no effort at all
// ::: overview :::
// a merc with 85 strength (+5 modifier to divisor)...
// 1) a gun weighting 3 kilos (lightweight ARs), generates cca 25 points = 1 BP per AP = 100 BPs per full turn = pretty much nothing
// 2) a gun weighting 5 kilos (most ARs with some attachments), generates cca 71 points = 3 BPs per AP = 300 BPs per full turn = still ok, similar to climb down a roof once
// 3) a gun weighting 8 kilos ("lighter" LMGs like PKM, or heavier ARs with lots of attachments), generates cca 183 points = 7 BPs per AP = 700 BPs per full turn = you can handle it, its just like climb roof up and then down once
// 4) a gun weighting 11 kilos (Rheinmetall MG3, FN MAG), generates cca 346 points = 13 BPs per AP = 1300 BPs per full turn = starts to feel rather heavy soon
// 5) s gun weighting 15 kilos (you trying to shoulder Barrett? or Browning M1919?), generates cca 643 points = 26 BPs per AP = 2600 BPs per turn = uhh,
// after testing, a gun weighting 12 kilos, shouldered in standing position, with 85 strength, together with recoil kick (small one on this one though)...
// took 5-6 whole turns to totaly exhaust the merc, if one turn is equal 20-30 seconds, then it is 2-3 minutes... I think that's reasonable
return ( iBPcost );
}
INT32 GetBPCostForRecoilkick( SOLDIERTYPE * pSoldier )
{
if ( !gGameExternalOptions.ubEnergyCostForWeaponRecoilKick )
return 0;
if ( pSoldier == NULL )
return 0;
if ( Item[pSoldier->inv[pSoldier->ubAttackingHand].usItem].usItemClass != IC_GUN && Item[pSoldier->inv[pSoldier->ubAttackingHand].usItem].usItemClass != IC_LAUNCHER )
return 0;
if ( ARMED_VEHICLE( pSoldier ) || AM_A_ROBOT( pSoldier ) || ENEMYROBOT( pSoldier ) )
return 0;
//////////////////////////////////////////////////////////////////////////////
// THE GUN RECOIL KICK ENERGY COST
//////////////////////////////////////////////////////////////////////////////
// This is not about how heavy is the gun, but how much it kicks our shoulder.
// Single shots may not prove much effect, but on autofire this may incure some nasty energy loss with certain guns.
// Impact indirectly speaks of the size of the bullet, and thus strength of the backforce, theoretically.. so take it to the account.
// (Other than that, we have nothing to base our calculation on by now, the solution would be to make a new tag in weapons.xml, but that's modder-unfriendly.)
// silversurfer: Let's use the function that is designed for that.
//INT32 iKickPower = (INT32)(Weapon[pSoldier->inv[pSoldier->ubAttackingHand].usItem].ubImpact);
INT32 iKickPower = (INT32)GetBasicDamage(&pSoldier->inv[pSoldier->ubAttackingHand]);
// get weapon total weight
// Weapon weight here actually helps us, since it absorbs the power of the backforce.
INT16 sWeaponWeight = CalculateObjectWeight(&pSoldier->inv[pSoldier->ubAttackingHand]);
// --------------------------
// CALCULATE BASIC KICK POWER
// "Standard weapon" impact is around 30, "standard weapon" weight plus some attachments is 3-5 kilos,
// so for 30 and less impact make the kick power equal to it, then exponentially rise, so 40 impact makes double the amount.
// Some crazy weapons with 50 (or more) impact then have more than 5x more kick power than "reasonable" guns.
if ( iKickPower > 30 )
iKickPower = 30 + ((iKickPower - 30) * (iKickPower - 30) / 3);
// reduce it by 1% per 0,2 kilo of weapon weight
sWeaponWeight = min(99,(sWeaponWeight / 2));
iKickPower = iKickPower * (100 - sWeaponWeight) / 100;
// If one-handed gun, reduce it a bit, since the whole thing is somewhat different.
if ( !Item[pSoldier->inv[pSoldier->ubAttackingHand].usItem].twohanded )
iKickPower = iKickPower * 3 / 4; // -25%
// ::: overview :::
// Glock 17 generates cca 18 points
// Colt M4 (with some attachemnts) generates cca 25 points
// .50 Beowulf (with some attachments) generates cca 40 points
// OSV-96 generates cca 115 points
double dModifier = 0;
// ----------------------------
// APPLY REDUCTION FOR STRENGTH
// Furthermore reduce the kicking power by our strength (with "exceptionally" low strength, we get a penalty though).
// -1,5% per strength over 90, -1% per strength between 80 and 90, and -0.5% per strength between 70 and 80
// --> max reduction -30% at 100 strength
INT16 sOurStrength = EffectiveStrength( pSoldier, FALSE );
if ( sOurStrength < 55 )
{
dModifier -= (55 - sOurStrength) / 2; //-0.5% per strength below 55
if ( sOurStrength < 35 )
dModifier -= (35 - sOurStrength) / 2; //-1% total per strength below 35
}
else if ( sOurStrength > 70 )
{
dModifier += (sOurStrength - 70) / 2; //-0.5% per strength over 70
if ( sOurStrength > 80 )
{
dModifier += (sOurStrength - 80) / 2; //-1% total per strength over 80
if ( sOurStrength > 90 )
dModifier += (sOurStrength - 90) / 2; //-1,5% total per strength over 90
}
}
if ( dModifier != 0 )
{
dModifier = min( 99, dModifier );
iKickPower = (INT32)(iKickPower * (100 - dModifier) / 100);
}
// ------------------------------------
// APPLY MODIFIERS FOR STANCE AND BIPOD
dModifier = 0;
// Alternative weapon holding?
if ( gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_ALT_WEAPON_HOLDING )
{
if ( Item[pSoldier->inv[pSoldier->ubAttackingHand].usItem].twohanded ) // firing from hip makes the kicking rather diminishing
dModifier += 80; // only 20% of the regular kick power
else // holding pistol in one hand is worse in this case
dModifier -= 33; // plus 33% power
}
// If weapon is rested on something or we are prone, reduce the power
if ( gGameExternalOptions.fWeaponResting && pSoldier->IsWeaponMounted() || ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE ) )
{
dModifier += 40; // only 60% of the regular kick power if prone
dModifier += (2 * GetBipodBonus(&pSoldier->inv[pSoldier->ubAttackingHand])); // minus up to 20% for bipod
}
else if ( gAnimControl[ pSoldier->usAnimState ].ubHeight == ANIM_CROUCH )
dModifier += 20; // only 80% of the regular kick power if crouching
if ( dModifier != 0 )
{
dModifier = min( 99, dModifier );
iKickPower = (INT32)(iKickPower * (100 - dModifier) / 100);
}
// ---------------------------
// APPLY MODIFIER FOR AUTOFIRE
// Now, on autofire this becomes real thing.. the kick power evolves by the length of the autofire
// Depending somehow on the weapon autofire penalty, the 4th-6th bullet can already deal double the amount of energy loss.
if ( pSoldier->bDoBurst > 1 )
{
// per every shot after the first one, the power increases by an amount based on the weapon steadiness (autopenalty - reduction of it)
// the additional kick factor is 5% base + half the auto penalty, but is itself also reduced by the above adjustments
INT16 sSteadiness = Weapon[pSoldier->inv[pSoldier->ubAttackingHand].usItem].AutoPenalty - GetAutoToHitBonus(&pSoldier->inv[pSoldier->ubAttackingHand], FALSE );
dModifier = (INT32)((3 + (sSteadiness / 2)) * (100 - dModifier) / 100);
if ( dModifier > 0 )
{
for ( INT16 i = 1; i < pSoldier->bDoBurst; i++ )
{
iKickPower = (INT32)(iKickPower * ( 100 + dModifier ) / 100);
}
}
}
// For enemies, modify by difficulty
if ( pSoldier->bTeam == ENEMY_TEAM )
{
switch( gGameOptions.ubDifficultyLevel )
{
case DIF_LEVEL_EASY:
break;
case DIF_LEVEL_MEDIUM:
iKickPower = (iKickPower * 9)/10; // -10%
break;
case DIF_LEVEL_HARD:
iKickPower = (iKickPower * 3)/4; // -25%
break;
case DIF_LEVEL_INSANE:
iKickPower /= 2; // -50%
break;
default:
iKickPower = (iKickPower * 9)/10; // -10%
break;
}
}
// adjust by predefined ini setting
iKickPower = (iKickPower * gGameExternalOptions.ubEnergyCostForWeaponRecoilKick / 100);
iKickPower = max(0, iKickPower);
return ( iKickPower );
}
INT16 GetAPsToBreakWindow(SOLDIERTYPE *pSoldier, BOOLEAN fStance)
{
if (fStance)
{
return MinAPsToPunch(pSoldier, pSoldier->sGridNo);
}
return MinAPsToPunch(pSoldier, NOWHERE);
}
INT16 GetAPsToStartDrag(SOLDIERTYPE *pSoldier, BOOLEAN fStance)
{
INT16 sAPCost = 0;
if (fStance && gAnimControl[pSoldier->usAnimState].ubEndHeight != ANIM_CROUCH)
{
if (gAnimControl[pSoldier->usAnimState].ubEndHeight > ANIM_CROUCH)
sAPCost += GetAPsCrouch(pSoldier, TRUE);
else
sAPCost += GetAPsProne(pSoldier, TRUE);
}
return sAPCost;
}