#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 #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 && !pSoldier->CanClimbWithCurrentBackpack()) { 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 { usBPPenalty = GetBackbackAPPenaltyFromBackpack(pSoldier); } 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 ( 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); } 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 ); } else if(ItemIsRocketLauncher(usItem) || ItemIsGrenadeLauncher(usItem) || ItemIsMortar(usItem))//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 || ItemIsMortar(usItem) && 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(ItemIsRocketLauncher(usItem) || ItemIsGrenadeLauncher(usItem) || ItemIsMortar(usItem))//dnl ch72 260913 { if(gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE || ItemIsMortar(usItem) && 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 ) && !ItemIsRocketLauncher(usUBItem) && ( 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]; } // 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 (ItemIsSingleShotRocketLauncher(usItemUsed)) { // hack... they turn empty afterwards anyways return( TRUE ); } if (Item[ usItemUsed ].usItemClass == IC_LAUNCHER || ItemIsCannon(usItemUsed)) { 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 (ItemIsCannon(usItemUsed)) { 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 ); } // manual recharge if( pSoldier->bTeam == OUR_TEAM ) { if ( !( (*pObjUsed)[0]->data.gun.ubGunState & GS_CARTRIDGE_IN_CHAMBER ) ) { return( FALSE ); } } // } } 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 )) && !ItemIsCannon(pObj->usItem) ) return; if (ItemIsCannon(pObj->usItem)) { } else if ( Item[ pObj->usItem ].usItemClass == IC_GUN && !ItemIsCannon(pObj->usItem) && 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 || ItemIsCannon(pObj->usItem) || 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]) ); // 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); } //////////////////////////////////////////////////////////////////////////////////////////////////////// } // 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 && !(pSoldier->bCollapsed && pSoldier->bBreath < OKBREATH) ) { 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 (ItemIsTwoHanded(usItem)) { // 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 (ItemIsTwoHanded(usItem)) { 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; // make sure the guy's actually got a throwable item in his hand! usInHand = pSoldier->inv[HANDPOS].usItem; // 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, THROWING_NT ) && gGameOptions.fNewTraitSystem ) { if ( grenadAPreductionpossible ) { iAPCost = max( 1, (INT32)(iAPCost * (100 - gSkillTraitValues.ubTHAPsNeededToThrowGrenadesReduction) / 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(ItemIsMine(pSoldier->inv[HANDPOS].usItem)) 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) // min was added to stick with the behaviour above (+1) assuming the backpack is heavier than BACKPACK_WEIGHT_FACTOR iFinalAPsToCrouch += min(1, GetBackbackAPPenaltyFromBackpack(pSoldier)); // -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) // min was added to stick with the behaviour above (+1) assuming the backpack is heavier than BACKPACK_WEIGHT_FACTOR iFinalAPsToLieDown += min(1, GetBackbackAPPenaltyFromBackpack(pSoldier)); // -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 (ItemIsTwoHanded(pSoldier->inv[pSoldier->ubAttackingHand].usItem)) // 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 ( !ItemIsTwoHanded(pSoldier->inv[pSoldier->ubAttackingHand].usItem) ) 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 (ItemIsTwoHanded(pSoldier->inv[pSoldier->ubAttackingHand].usItem)) // 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; } INT16 GetBackbackAPPenaltyFromBackpack(SOLDIERTYPE *pSoldier) { UINT16 usBPPenalty = 0; OBJECTTYPE * pObj = &( pSoldier->inv[ BPACKPOCKPOS ] ); if ((UsingNewInventorySystem() == true) && pSoldier->inv[BPACKPOCKPOS].exists() && pObj != NULL) { 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 vbLBESlots; GetLBESlots( BPACKPOCKPOS, vbLBESlots ); for ( UINT8 i = 0; i < vbLBESlots.size() ; i++ ) { pObj = &( pSoldier->inv[ vbLBESlots[ i ] ] ); usBackPackWeight += CalculateObjectWeight( pObj ); } //1 AP penalty for each 5kg of weight up to the penalty defined by AP_MODIFIER_PACK (default = 4) // BUSCHER: Externalized the weight factor (previously 50 = 5.0 kg) // usBPPenalty = min( ( usBackPackWeight / 50 ), usBPPenalty ); usBPPenalty = min( ( usBackPackWeight / gGameExternalOptions.ubBackPackWeightFactorForAPPenalty), APBPConstants[AP_MODIFIER_PACK] ); } return usBPPenalty; }