NCTH CalcNewChanceToHitGun refactoring / cleanup (by silversurfer)

o Externalized ja2_options.ini value if you want to use / or not the cleaned up version of CalcNewChanceToHitGun: USE_NEW_CTH_CALCULATION
o The cleanup only effects the game if you play with NCTH system
o The refactoring makes the code much easier to read and also shrinks the locs
o Fixed some old bugs in calculation
o In the future the switch USE_NEW_CTH_CALCULATION can be removed completely
o more infos: http://www.ja-galaxy-forum.com/board/ubbthreads.php/topics/322986/Re_Potential_NCTH_Code_Change_.html#Post322986


git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@6235 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Wanne
2013-07-21 17:08:42 +00:00
parent 502f5f6b77
commit 1b7c69dd0e
4 changed files with 953 additions and 8 deletions
+4
View File
@@ -693,6 +693,10 @@ void LoadGameExternalOptions()
gGameExternalOptions.guiMaxWeaponSize = iniReader.ReadInteger("System Limit Settings","MAX_WEAPON_SIZE", 9, 0, 32000);
gGameExternalOptions.guiOIVSizeNumber = iniReader.ReadInteger("System Limit Settings","OLD_INVENTORY_ITEM_NUMBER", 99, 0, 65535);
// silversurfer: this is only for the cleaned up NCTH calculation. Please remove if the new functions are accepted
// see Tactical\Weapons.cpp funtion CalcNewChanceToHitGun()
gGameExternalOptions.fUseNewCTHCalculation = iniReader.ReadBoolean("System Limit Settings", "USE_NEW_CTH_CALCULATION", TRUE);
//################# Data File Settings #################
// WANNE: Always use prof.dat??
+4
View File
@@ -512,6 +512,10 @@ typedef struct
UINT16 guiMaxItemSize;
UINT16 guiOIVSizeNumber;
// silversurfer: this is only for the cleaned up NCTH calculation. Please remove if the new functions are accepted
// see Tactical\Weapons.cpp funtion CalcNewChanceToHitGun()
BOOLEAN fUseNewCTHCalculation;
//Sight range
UINT32 ubStraightSightRange;
UINT32 ubVisDistDecreasePerRainIntensity;
+5
View File
@@ -12801,6 +12801,11 @@ FLOAT GetScopeRangeMultiplier( SOLDIERTYPE *pSoldier, OBJECTTYPE *pObj, FLOAT d2
FLOAT rangeModifier = gGameCTHConstants.SCOPE_RANGE_MULTIPLIER;
iScopeFactor = GetScopeMagnificationFactor( pSoldier, pObj, d2DDistance );
// if we are not using a scope we will not apply "SCOPE_RANGE_MULTIPLIER"
if ( iScopeFactor <= 1.0 )
rangeModifier = 1.0;
if( gGameOptions.fNewTraitSystem )
{
if(iScopeFactor > 5.0f)
+940 -8
View File
@@ -103,6 +103,19 @@ BOOLEAN UseBlade( SOLDIERTYPE *pSoldier , INT32 sTargetGridNo );
BOOLEAN UseThrown( SOLDIERTYPE *pSoldier , INT32 sTargetGridNo );
BOOLEAN UseLauncher( SOLDIERTYPE *pSoldier , INT32 sTargetGridNo );
// new subroutines for NCTH calculation
FLOAT CalcNewChanceToHitBaseAttributeBonus(SOLDIERTYPE *pSoldier);
FLOAT CalcNewChanceToHitBaseEffectBonus(SOLDIERTYPE *pSoldier);
FLOAT CalcNewChanceToHitBaseWeaponBonus(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTime, FLOAT fGunBaseDifficulty, UINT8 stance);
FLOAT CalcNewChanceToHitBaseSpecialBonus(SOLDIERTYPE *pSoldier);
FLOAT CalcNewChanceToHitBaseTargetBonus(SOLDIERTYPE *pSoldier, SOLDIERTYPE *pTarget, INT32 sGridNo, INT32 iRange, UINT8 ubAimPos, BOOLEAN fCantSeeTarget);
FLOAT CalcNewChanceToHitAimAttributeBonus(SOLDIERTYPE *pSoldier);
FLOAT CalcNewChanceToHitAimEffectBonus(SOLDIERTYPE *pSoldier);
FLOAT CalcNewChanceToHitAimWeaponBonus(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTime, FLOAT fGunAimDifficulty, UINT8 stance);
FLOAT CalcNewChanceToHitAimSpecialBonus(SOLDIERTYPE *pSoldier);
FLOAT CalcNewChanceToHitAimTargetBonus(SOLDIERTYPE *pSoldier, SOLDIERTYPE *pTarget, INT32 sGridNo, INT32 iRange, UINT8 ubAimPos, BOOLEAN fCantSeeTarget);
FLOAT CalcNewChanceToHitAimTraitBonus(SOLDIERTYPE *pSoldier, FLOAT fDifference, INT32 sGridNo, INT16 ubAimTime, FLOAT fScopeMagFactor, UINT32 uiBestScopeRange);
INT32 HTHImpact( SOLDIERTYPE * pSoldier, SOLDIERTYPE * pTarget, INT32 iHitBy, BOOLEAN fBladeAttack );
BOOLEAN gfNextShotKills = FALSE;
@@ -4814,6 +4827,12 @@ BOOLEAN InRange( SOLDIERTYPE *pSoldier, INT32 sGridNo )
UINT32 CalcNewChanceToHitGun(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTime, UINT8 ubAimPos )
{
//////////////////////////////////////////////////////////////////////////////////
// START COMMON PART OF CLEANED UP AND OLD VERSION OF NCTH CALCULATION
// When the old code is removed some of the old variables can be removed too
// because then they are not used anymore.
// iChance, bBandaged, iTraitModifier
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
// HEADROCK HAM 4:
@@ -4834,8 +4853,8 @@ UINT32 CalcNewChanceToHitGun(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTi
INT8 bBandaged, iTraitModifier;
INT16 sDistVis, sDistVisNoScope;
UINT8 ubTargetID;
bool highPowerScope = false;
INT16 sTotalAutofirePenalty = 0;
// bool highPowerScope = false;
// INT16 sTotalAutofirePenalty = 0;
bool fCantSeeTarget = false;
FLOAT scopeRangeMod;
@@ -4859,12 +4878,6 @@ UINT32 CalcNewChanceToHitGun(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTi
ubTargetID = WhoIsThere2( sGridNo, pSoldier->bTargetLevel ); // Target ubID
pTarget = SimpleFindSoldier( sGridNo, pSoldier->bTargetLevel ); // Target Pointer
// Calculate how easy it is to handle this gun.
FLOAT iGunDifficulty = (FLOAT)(( Weapon[ usInHand ].ubHandling * (100 - GetPercentReadyTimeAPReduction(pInHand) )) / 100);
iGunDifficulty *= (FLOAT)(100 / APBPConstants[AP_MAXIMUM]); // Adjust for 100AP/25AP
FLOAT iGunBaseDifficulty = iGunDifficulty;
FLOAT iGunAimDifficulty = iGunDifficulty;
UINT8 stance = gAnimControl[ pSoldier->usAnimState ].ubEndHeight;
// Flugente: new feature: if the next tile in our sight direction has a height so that we could rest our weapon on it, we do that, thereby gaining the prone boni instead. This includes bipods
@@ -4927,6 +4940,249 @@ UINT32 CalcNewChanceToHitGun(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTi
iSightRange = __max((INT16)(sDistVis*1.5), iRange*2);
if(iSightRange > sDistVis)
fCantSeeTarget = true;
//////////////////////////////////////////////////////////////////////////////////
// END COMMON PART OF CLEANED UP AND OLD VERSION OF NCTH CALCULATION
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
// START CLEANED UP VERSION OF NCTH CALCULATION
//////////////////////////////////////////////////////////////////////////////////
// silversurfer: This is a cleaned up version of the NCTH calculation and hopefully easier to read.
// The if clause and the rest of the old code below its else can be removed if the new version is accepted.
// The "if (gGameExternalOptions.fUseNewCTHCalculation)" is only there for easy switching in Ja2_Options.ini.
//////////////////////////////////////////////////////////////////////////////////
if (gGameExternalOptions.fUseNewCTHCalculation)
{
FLOAT fBaseChance = 0, fBaseModifier = 0, fAimChance = 0, fAimModifier = 0;
//////////////////////////////////////////////////////////////////////////////////
// First step: Calculate a base CTH value
//
// This "Base" CTH is a percentage value describing how well our gun's muzzle is aimed
// when not spending any extra effort on aiming. In other words, it's the most basic state,
// where the shooter only brings up the gun into the general direction of the target, and
// pulls the trigger.
//
// This is calculated first by factoring in skills, and later by adding up various conditional
// factors into a percentage which is then applied to those skills.
//
// First calculate the basic value for BASE CTH by factoring in all the character's skills.
// The most important skill here is EXPERIENCE.
fBaseChance = CalcNewChanceToHitBaseAttributeBonus(pSoldier);
// this shooter will never hit anything
if ( fBaseChance <= gGameExternalOptions.ubMinimumCTH )
return gGameExternalOptions.ubMinimumCTH;
// Add a flat Base bonus from the item and its attachments.
INT32 imoda = GetFlatBaseModifier( pInHand, ANIM_PRONE );
INT32 imodb = GetFlatBaseModifier( pInHand, stance );
fBaseChance += (FLOAT)((gGameExternalOptions.ubProneModifierPercentage * imoda + (100 - gGameExternalOptions.ubProneModifierPercentage) * imodb)/100);
// get bonus from effects lasting on the shooter (morale, injury, shock etc.)
fBaseModifier += CalcNewChanceToHitBaseEffectBonus(pSoldier);
// Calculate how easy it is to handle the weapon
FLOAT fGunDifficulty = (FLOAT)(( Weapon[ usInHand ].ubHandling * (100 - GetPercentReadyTimeAPReduction(pInHand) )) / 100);
fGunDifficulty *= (FLOAT)(100 / APBPConstants[AP_MAXIMUM]); // Adjust for 100AP/25AP
FLOAT fGunBaseDifficulty = fGunDifficulty;
FLOAT fGunAimDifficulty = fGunDifficulty;
// get bonus from weapon handling
fBaseModifier += CalcNewChanceToHitBaseWeaponBonus(pSoldier, sGridNo, ubAimTime, fGunBaseDifficulty, stance);
// get special bonus (enemy/militia bonus, special characters, game difficulty bonus etc.)
fBaseModifier += CalcNewChanceToHitBaseSpecialBonus(pSoldier);
// get target specific bonus
fBaseModifier += CalcNewChanceToHitBaseTargetBonus(pSoldier, pTarget, sGridNo, iRange, ubAimPos, fCantSeeTarget);
//CHRISL: Applying the Gear AimBonus (penalty) here, and directly to fBaseModifier as a flat penalty, instead of altering iSightRange above. For now
// I'm just applying this to the BaseModifier which means that aiming can overcome the Gear AimBonus (penalty).
fBaseModifier += GetGearAimBonus ( pSoldier, iSightRange, ubAimTime );
////////////////////////////////////
// Finish BASE CTH calculation:
fBaseChance = (fBaseChance * (100+fBaseModifier) / 100);
fBaseChance = __max( fBaseChance, 0 );
fBaseChance = __min( fBaseChance, 100 );
//////////////////////////////////////////////////////////////////////////////////
// Second step: Calculate bonuses from aiming
//
// In this step, we figure out how much CTH we get from aiming. Naturally, we
// need extra aiming (bAimTime) to be applied.
//
// CTH is capped by a value determined by the shooter's skills. The better
// your skills, the higher CTH can go. A steady hand (DEX) and training
// (MRK) are paramount here.
// After that, the cap is raised or lowered by other factors, like fatigue
// or the size of the gun.
//
// Finally, we determine how much each Aim Click will give us. Returns are
// diminishing, so the more clicks we put in, the less CTH for each additional
// click. However, the last few points can be absolutely crucial for long-range
// shooting.
//
// Scope magnification can be important here, but only if the target is closer
// than the scope's minimum aiming range. If it is closer, the shooter begins
// to suffer aiming penalties which can render aiming almost useless.
if (ubAimTime > 0)
{
// Are we using a scope? If so, what's the range factor?
FLOAT fScopeMagFactor, fRangeModifier;
//CHRISL: This does make sense but it effectively makes high powered scopes worthless if a target is actually visible. As an example, a Battle Scope
// is going to have a iScopeMagFactor of 7. With a "NORMAL_SHOOTING_DISTANCE" also of 7, we're going to end up with uiBestScopeRange of 49. That's
// effectilvey saying that any target within 490m is "too close" for the scope to be effective. That by itself isn't realistic. But in JA2 it's also
// a big drawback. Consider that normally (excluding terrain, lighting and environment) you can see 26 tiles. The Battle Scope increases that by 50%
// in bright light, meaning you can see up to 39 tiles away. A target standing 49 tiles is considered "invisible" to you because the target is beyond
// your 39 tile vision range. Sniper scopes (10 iScopeMagFactor) are even worse (uiBestScopeRange = 70 but visible range limited to 49 tiles). So the
// only way to get the full benefit of a higher powered scope is to have a spotter AND to suffer the various "TARGET_INVISIBLE" penalties. This makes
// no sense. Admittedly, higher powered scopes should be less efficienct at "close range" but is 490m (700m for sniper scope) really "close range"?
// Also, we can't simply change NORMAL_SHOOTING_DISTANCE because this value also effects shooting without a scope. We don't want ot make it harder for
// "scopeless" shooting. So to try and counter some of this, while still showing that scopes are less effecient at "close range", I'm going to cut the
// uiBestScopeRange value in half. This should allow a Battle Scope to reach full effeciency at 24 tiles and a Sniper scope will be fully effecient at
// 35 tiles. ACOG becomes fully effecient at 14 tiles and 2x is fully effeciency at 7 tiles (compared to 28 and 14 respectively). This does mean that a
// 2x scope reaches full effeciency at the same point as "scopeless" shooting, but I don't think this will be a serious problem.
if ( !pSoldier->IsValidAlternativeFireMode( ubAimTime, sGridNo ) ) // ignore scopes when firing from hip/fast shot from pistol
{
fScopeMagFactor = GetBestScopeMagnificationFactor( pSoldier, &(pSoldier->inv[pSoldier->ubAttackingHand]), d2DDistance );
fRangeModifier = GetScopeRangeMultiplier(pSoldier, &(pSoldier->inv[pSoldier->ubAttackingHand]), (FLOAT)iRange);
}
else
{
fScopeMagFactor = 1.0f;
// we are shooting from hip so SCOPE_RANGE_MULTIPLIER cannot be applied
fRangeModifier = 1.0f;
}
UINT32 uiBestScopeRange = (UINT32)(fScopeMagFactor * gGameCTHConstants.NORMAL_SHOOTING_DISTANCE * fRangeModifier);
// get attribute based aiming value.
// This is also the CTH cap, the absolute maximum CTH a shooter can get.
fAimChance = CalcNewChanceToHitAimAttributeBonus(pSoldier);
// Add percent-based modifier from the gun and its attachments
FLOAT moda = (FLOAT)(fAimChance * GetPercentCapModifier( pInHand, ANIM_PRONE )) / 100;
FLOAT modb = (FLOAT)(fAimChance * GetPercentCapModifier( pInHand, stance )) / 100;
fAimChance += (FLOAT)((gGameExternalOptions.ubProneModifierPercentage * moda + (100 - gGameExternalOptions.ubProneModifierPercentage) * modb)/100);
// get direct AimChance bonus for traits (throwing, sniper etc.)
FLOAT fDifference = 99 - fAimChance;
fAimChance += CalcNewChanceToHitAimTraitBonus(pSoldier, fDifference, sGridNo, ubAimTime, fScopeMagFactor, uiBestScopeRange);
// get aimbonus from effects lasting on the shooter
fAimModifier += CalcNewChanceToHitAimEffectBonus(pSoldier);
// get aimbonus from weapon handling
fAimModifier += CalcNewChanceToHitAimWeaponBonus(pSoldier, sGridNo, ubAimTime, fGunAimDifficulty, stance);
// get special aimbonus (game difficulty etc.)
fAimModifier += CalcNewChanceToHitAimSpecialBonus(pSoldier);
// apply bonus from traits
// Flugente: moved trait modifiers into a member function
if(gGameOptions.fNewTraitSystem)
{
UINT8 targetprofile = NOBODY;
if ( pTarget && pTarget->ubProfile != NOBODY )
targetprofile = pTarget->ubProfile;
fAimModifier += pSoldier->GetTraitCTHModifier( usInHand, ubAimTime, targetprofile );
}
// get aimbonus from target
fAimModifier += CalcNewChanceToHitAimTargetBonus(pSoldier, pTarget, sGridNo, iRange, ubAimPos, fCantSeeTarget);
// silversurfer: this doesn't make sense. We always apply a penalty when we can see the target?
// invisible targets are already taken into account one step above in aimbonus from target
// VISIBILITY
/* if (iRange > 0 && iSightRange > iRange)
{
FLOAT fTempPenalty = (FLOAT)((FLOAT)iSightRange / (FLOAT)iRange);
fTempPenalty = (FLOAT)(100 / fTempPenalty);
fAimModifier += ((100-fTempPenalty) * gGameCTHConstants.AIM_VISIBILITY)/100;
fAimModifier = __max( gGameCTHConstants.AIM_TARGET_INVISIBLE, fAimModifier );
}*/
// factor in scopes under their range
if ( !pSoldier->IsValidAlternativeFireMode( ubAimTime, sGridNo ) )
{
if (fScopeMagFactor > 1.0 && iRange < (INT32)uiBestScopeRange)
{
// Calculate how much penalty this scope should give at this range
FLOAT dScopePenaltyRatio = ((FLOAT)uiBestScopeRange / (FLOAT)iRange);
INT32 iScopePenalty = (INT32)(dScopePenaltyRatio * gGameCTHConstants.AIM_TOO_CLOSE_SCOPE * (fScopeMagFactor /2));
fAimModifier += iScopePenalty;
}
else if (fScopeMagFactor == 1.0f && GetHighestScopeMagnificationFactor( &(pSoldier->inv[pSoldier->ubAttackingHand]) ) > 1.0f )
{
// Not using a scope, but it's still there. Give half the penalty based on the size of the scope.
INT32 iScopePenalty = (INT32)(((GetHighestScopeMagnificationFactor( &(pSoldier->inv[pSoldier->ubAttackingHand]) )/2) * gGameCTHConstants.AIM_TOO_CLOSE_SCOPE)/2);
fAimModifier += iScopePenalty;
}
}
// Make sure cap is within limits
fAimChance = __max(fAimChance, __max(0,(UINT32)fBaseChance));
fAimChance = __min(fAimChance, gGameExternalOptions.ubMaximumCTH);
// Now figure out the distance between the Base CTH and the CTH Cap. This is the distance we'll potentially
// cover when applying the maximum number of aiming clicks for this gun.
INT32 iMaxAimBonus = (INT32)(fAimChance - fBaseChance);
iMaxAimBonus = (INT32)((iMaxAimBonus * (100+fAimModifier)) / 100);
iMaxAimBonus = __max(0, iMaxAimBonus); // can't get less than 0 points for aiming...
// Now let's find out how many CTH points we get per aiming level.
UINT8 ubAllowedAimingLevels = AllowedAimingLevels( pSoldier, sGridNo );
if(ubAimTime == ubAllowedAimingLevels)
gCTHDisplay.fMaxAimReached = TRUE;
FLOAT dAimFractionsDivisor = 0.0;
for (UINT8 cnt = 0; cnt < ubAllowedAimingLevels; cnt++)
{
dAimFractionsDivisor += (cnt+1);
}
FLOAT iAimPoints = 0;
FLOAT iAimPointFraction = (FLOAT)((FLOAT)iMaxAimBonus / dAimFractionsDivisor);
for (UINT8 x = 0; x < ubAimTime; x++)
{
iAimPoints += iAimPointFraction * (ubAllowedAimingLevels-x);
// Add Flat Modifier from the weapon and its attachments
INT32 moda = GetFlatAimModifier( pInHand, ANIM_PRONE );
INT32 modb = GetFlatAimModifier( pInHand, stance );
iAimPoints += (gGameExternalOptions.ubProneModifierPercentage * moda + (100 - gGameExternalOptions.ubProneModifierPercentage) * modb)/100;
}
// Finally, add the appropriate number of CTH points to our chance-to-hit, and limit it into good values.
fBaseChance = __max(fBaseChance + (INT32)iAimPoints, fBaseChance);
fBaseChance = __min(fBaseChance, (INT32)fAimChance);
}
// Impose global limits.
fBaseChance = __min(fBaseChance, gGameExternalOptions.ubMaximumCTH);
fBaseChance = __max(fBaseChance, gGameExternalOptions.ubMinimumCTH);
return ((INT32)fBaseChance);
}
else
{
//////////////////////////////////////////////////////////////////////////////////
// END CLEANED UP VERSION OF NCTH CALCULATION
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
// START OLD VERSION OF NCTH CALCULATION
//////////////////////////////////////////////////////////////////////////////////
// Calculate how easy it is to handle this gun.
FLOAT iGunDifficulty = (FLOAT)(( Weapon[ usInHand ].ubHandling * (100 - GetPercentReadyTimeAPReduction(pInHand) )) / 100);
iGunDifficulty *= (FLOAT)(100 / APBPConstants[AP_MAXIMUM]); // Adjust for 100AP/25AP
FLOAT iGunBaseDifficulty = iGunDifficulty;
FLOAT iGunAimDifficulty = iGunDifficulty;
//////////////////////////////////////////////////////////////////////////////////
// First step: Calculate a base CTH value
@@ -5739,6 +5995,10 @@ UINT32 CalcNewChanceToHitGun(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTi
return (iChance);
}
//////////////////////////////////////////////////////////////////////////////////
// END OLD VERSION OF NCTH CALCULATION
//////////////////////////////////////////////////////////////////////////////////
}
/*
@@ -11566,3 +11826,675 @@ FLOAT GetOverheatDamageThreshold( OBJECTTYPE *pObj )
return damagethreshold;
}
FLOAT CalcNewChanceToHitBaseAttributeBonus(SOLDIERTYPE *pSoldier)
{
INT8 bExperience = EffectiveExpLevel( pSoldier );
INT8 bMarksmanship = EffectiveMarksmanship( pSoldier );
INT16 bDexterity = EffectiveDexterity( pSoldier, FALSE );
INT16 bWisdom = EffectiveWisdom( pSoldier );
// this shooter will never hit anything
if ( bMarksmanship == 0 || bDexterity == 0 )
{
return( gGameExternalOptions.ubMinimumCTH );
}
FLOAT fCombinedSkill = (FLOAT)(gGameCTHConstants.BASE_EXP*(bExperience*10));
fCombinedSkill += (FLOAT)(gGameCTHConstants.BASE_MARKS * bMarksmanship);
fCombinedSkill += (FLOAT)(gGameCTHConstants.BASE_DEX * bDexterity);
fCombinedSkill += (FLOAT)(gGameCTHConstants.BASE_WIS * bWisdom);
// Divide by the total coefficient value.
fCombinedSkill /= (FLOAT)(gGameCTHConstants.BASE_EXP + gGameCTHConstants.BASE_MARKS + gGameCTHConstants.BASE_DEX + gGameCTHConstants.BASE_WIS);
// At this point we have a value of 1 to 100. Divide by 3.
fCombinedSkill /= 3;
return fCombinedSkill;
}
FLOAT CalcNewChanceToHitBaseEffectBonus(SOLDIERTYPE *pSoldier)
{
FLOAT fBaseModifier = 0;
// MORALE
INT8 iCurrentMorale = GetMoraleModifier(pSoldier);
if (iCurrentMorale > 0)
{
fBaseModifier += (FLOAT)(iCurrentMorale * gGameCTHConstants.BASE_HIGH_MORALE);
}
else if (iCurrentMorale < 0)
{
fBaseModifier += (FLOAT)(iCurrentMorale * gGameCTHConstants.BASE_LOW_MORALE);
}
// INJURY
if (pSoldier->stats.bLife < pSoldier->stats.bLifeMax)
{
FLOAT fTempPenalty = gGameCTHConstants.BASE_INJURY;
// Bleeding damage is used as a percentage off the Max Life, giving a penalty of up to BASE_INJURY.
fBaseModifier += (FLOAT)(pSoldier->bBleeding * fTempPenalty) / pSoldier->stats.bLifeMax;
// Bandage damage is used similarly, but is only 1/3 as bad.
INT8 bBandaged = pSoldier->stats.bLifeMax - pSoldier->stats.bLife - pSoldier->bBleeding;
fBaseModifier += ((bBandaged * fTempPenalty) / pSoldier->stats.bLifeMax) / 3;
}
// FATIGUE
fBaseModifier += (gGameCTHConstants.BASE_FATIGUE * (100 - pSoldier->bBreath)) / 100;
// DRUNKNESS
INT8 iDrunkness = GetDrunkLevel(pSoldier);
if (iDrunkness > 0)
{
iDrunkness -= 1;
fBaseModifier += gGameCTHConstants.BASE_DRUNK[iDrunkness];
}
// GASSED
if ( pSoldier->flags.uiStatusFlags & SOLDIER_GASSED )
{
fBaseModifier += gGameCTHConstants.BASE_GASSED;
}
// BEING BANDAGED
if (pSoldier->ubServiceCount > 0)
{
fBaseModifier += gGameCTHConstants.BASE_BEING_BANDAGED;
}
// SHOCK (SHOOTER)
if (pSoldier->aiData.bShock)
{
FLOAT fTempPenalty = gGameCTHConstants.BASE_SHOCK;
FLOAT fShockPercentage = (FLOAT)(pSoldier->aiData.bShock * 100) / gGameExternalOptions.ubMaxSuppressionShock;
fBaseModifier += (fTempPenalty * fShockPercentage) / 100;
}
return fBaseModifier;
}
FLOAT CalcNewChanceToHitBaseWeaponBonus(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTime, FLOAT fGunBaseDifficulty, UINT8 stance)
{
FLOAT fBaseModifier = 0;
UINT16 usInHand = pSoldier->usAttackingWeapon;
OBJECTTYPE * pInHand = &(pSoldier->inv[pSoldier->ubAttackingHand]);
// FIRING FROM ALTERNATIVE WEAPON HOLDING (faster shot, less accuracy)
if ( pSoldier->IsValidAlternativeFireMode( ubAimTime, sGridNo ) )
{
fBaseModifier -= gGameExternalOptions.ubAltWeaponHoldingCtHPenaly;
}
// FIRING 1-HANDED WEAPONS
if ( !(Item[ usInHand ].twohanded ) ) //JMich todo: underbarrel
{
if (pSoldier->inv[SECONDHANDPOS].exists() != false)
{
if ( pSoldier->IsValidSecondHandShot( ) )
{
// Penalty for shooting two pistols. Ambidextrous trait halves this.
FLOAT fTempPenalty = (gGameCTHConstants.BASE_TWO_GUNS * fGunBaseDifficulty) - fGunBaseDifficulty;
if (gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, AMBIDEXTROUS_NT ))
{
fTempPenalty = fTempPenalty * (100 - gSkillTraitValues.ubAMPenaltyDoubleReduction) / 100;
}
else if (!gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, AMBIDEXT_OT ))
{
fTempPenalty = 0;
}
fGunBaseDifficulty += fTempPenalty;
}
else
{
// Penalty for shooting a pistol with just one hand
fGunBaseDifficulty *= gGameCTHConstants.BASE_ONE_HANDED;
}
}
}
// HEAVY WEAPON in OLD trait system
if (Item[usInHand].rocketlauncher && !(gGameOptions.fNewTraitSystem) )
{
// Penalty for shooting heavy weapons (launchers). Heavy Weapons skill halves this, once per skill level.
FLOAT fTempPenalty = (gGameCTHConstants.BASE_HEAVY_WEAPON * fGunBaseDifficulty) - fGunBaseDifficulty;
for (INT32 x=0; x < NUM_SKILL_TRAITS( pSoldier, HEAVY_WEAPS_OT ); x++)
{
fTempPenalty /= 2;
}
fGunBaseDifficulty += fTempPenalty;
}
// STANCE
switch ( stance )
{
case ANIM_CROUCH:
fGunBaseDifficulty *= gGameCTHConstants.BASE_CROUCHING_STANCE;
break;
case ANIM_PRONE:
fGunBaseDifficulty *= gGameCTHConstants.BASE_PRONE_STANCE;
break;
}
// Percentage based handling modifier from the gun and its attachments
FLOAT moda = (fGunBaseDifficulty * GetPercentHandlingModifier( pInHand, ANIM_PRONE )) / 100;
FLOAT modb = (fGunBaseDifficulty * GetPercentHandlingModifier( pInHand, stance )) / 100;
fGunBaseDifficulty += ((gGameExternalOptions.ubProneModifierPercentage * moda + (100 - gGameExternalOptions.ubProneModifierPercentage) * modb)/100);
// Now apply Gun Difficulty to the Base Modifier.
fBaseModifier -= fGunBaseDifficulty * gGameCTHConstants.BASE_DRAW_COST;
// Percentage based modifier from the weapon and its attachments
moda = (fGunBaseDifficulty * GetPercentBaseModifier( pInHand, ANIM_PRONE )) / 100;
modb = (fGunBaseDifficulty * GetPercentBaseModifier( pInHand, stance )) / 100;
fBaseModifier += ((gGameExternalOptions.ubProneModifierPercentage * moda + (100 - gGameExternalOptions.ubProneModifierPercentage) * modb)/100);
return fBaseModifier;
}
FLOAT CalcNewChanceToHitBaseSpecialBonus(SOLDIERTYPE *pSoldier)
{
FLOAT fBaseModifier = 0;
/////////////////////////////////////////////////////////////////////////////////////
// SANDRO - Bonus CtH for Militia
if (pSoldier->ubSoldierClass == SOLDIER_CLASS_GREEN_MILITIA && gGameExternalOptions.sGreenMilitiaCtHBonusPercent != 0)
fBaseModifier += gGameExternalOptions.sGreenMilitiaCtHBonusPercent;
else if (pSoldier->ubSoldierClass == SOLDIER_CLASS_REG_MILITIA && gGameExternalOptions.sRegularMilitiaCtHBonusPercent != 0)
fBaseModifier += gGameExternalOptions.sRegularMilitiaCtHBonusPercent;
else if (pSoldier->ubSoldierClass == SOLDIER_CLASS_ELITE_MILITIA && gGameExternalOptions.sVeteranMilitiaCtHBonusPercent != 0)
fBaseModifier += gGameExternalOptions.sVeteranMilitiaCtHBonusPercent;
// bonus for enemy
else if (pSoldier->ubSoldierClass == SOLDIER_CLASS_ADMINISTRATOR && gGameExternalOptions.sEnemyAdminCtHBonusPercent != 0)
fBaseModifier += gGameExternalOptions.sEnemyAdminCtHBonusPercent;
else if (pSoldier->ubSoldierClass == SOLDIER_CLASS_ARMY && gGameExternalOptions.sEnemyRegularCtHBonusPercent != 0)
fBaseModifier += gGameExternalOptions.sEnemyRegularCtHBonusPercent;
else if (pSoldier->ubSoldierClass == SOLDIER_CLASS_ELITE && gGameExternalOptions.sEnemyEliteCtHBonusPercent != 0)
fBaseModifier += gGameExternalOptions.sEnemyEliteCtHBonusPercent;
// SANDRO - option to make special NPCs stronger - chance to hit
if (gGameExternalOptions.usSpecialNPCStronger > 0)
{
switch( pSoldier->ubProfile )
{
case CARMEN:
case QUEEN:
case JOE:
case ANNIE:
case CHRIS:
case KINGPIN:
case TIFFANY:
case T_REX:
case DRUGGIST:
case GENERAL:
case JIM:
case JACK:
case OLAF:
case RAY:
case OLGA:
case TYRONE:
case MIKE:
fBaseModifier += gGameExternalOptions.usSpecialNPCStronger;
break;
}
}
// GAME DIFFICULTY
if ( !(pSoldier->flags.uiStatusFlags & SOLDIER_PC ) && (pSoldier->bSide != gbPlayerNum) )
{
fBaseModifier += gGameCTHConstants.BASE_DIFFICULTY[gGameOptions.ubDifficultyLevel];
}
return fBaseModifier;
}
FLOAT CalcNewChanceToHitBaseTargetBonus(SOLDIERTYPE *pSoldier, SOLDIERTYPE *pTarget, INT32 sGridNo, INT32 iRange, UINT8 ubAimPos, BOOLEAN fCantSeeTarget)
{
FLOAT fBaseModifier = 0;
INT32 iHeightDifference = 0;
// SHOOTING AT SAME TARGET AGAIN
if (sGridNo == pSoldier->sLastTarget )
fBaseModifier += (FLOAT)gGameCTHConstants.BASE_SAME_TARGET;
// SHOOTING AT A TARGET AT DIFFERENT HEIGHT?
if ( pTarget == NULL )
{
// Shooting to roof.
if ( pSoldier->bTargetLevel > pSoldier->pathing.bLevel )
{
iHeightDifference = 3 * pSoldier->bTargetLevel;
}
}
else
{
// HEIGHT DIFFERENCE
UINT32 uiShooterHeight = 0;
UINT32 uiTargetHeight = 0;
if ( pSoldier->pathing.bLevel > 0 )
{
uiShooterHeight += 3 * pSoldier->pathing.bLevel;
}
switch ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
{
case ANIM_STAND:
uiShooterHeight += 2;
break;
case ANIM_CROUCH:
uiShooterHeight += 1;
break;
}
if (pTarget->pathing.bLevel > 0)
{
uiTargetHeight += 3 * pTarget->pathing.bLevel;
}
switch ( gAnimControl[ pTarget->usAnimState ].ubEndHeight )
{
case ANIM_STAND:
switch (ubAimPos)
{
case AIM_SHOT_HEAD:
uiTargetHeight += 2;
break;
case AIM_SHOT_TORSO:
case AIM_SHOT_RANDOM:
case AIM_SHOT_GLAND:
uiTargetHeight += 1;
break;
}
break;
case ANIM_CROUCH:
switch (ubAimPos)
{
case AIM_SHOT_HEAD:
uiTargetHeight += 1;
break;
}
break;
}
iHeightDifference = uiShooterHeight - uiTargetHeight;
if (iHeightDifference < 0)
{
iHeightDifference *= -1;
}
else
{
iHeightDifference = 0;
}
}
// Height difference is mitigated by range. A LONGER range reduces this penalty!
if (iRange > 0 && iHeightDifference > 0)
{
FLOAT fTempPenalty = gGameCTHConstants.BASE_SHOOTING_UPWARDS * iHeightDifference;
fTempPenalty /= iRange;
fBaseModifier += fTempPenalty;
}
// Target has high Agility or Experience and is therefore harder to hit
FLOAT fTempPenalty = (FLOAT)__max((pTarget->stats.bExpLevel*10), pTarget->stats.bAgility);
fBaseModifier += (fTempPenalty * gGameCTHConstants.BASE_AGILE_TARGET) / 100;
// INVIS TARGET
if (fCantSeeTarget)
{
fBaseModifier += gGameCTHConstants.BASE_TARGET_INVISIBLE;
}
return fBaseModifier;
}
FLOAT CalcNewChanceToHitAimAttributeBonus(SOLDIERTYPE *pSoldier)
{
INT8 bExperience = EffectiveExpLevel( pSoldier );
INT8 bMarksmanship = EffectiveMarksmanship( pSoldier );
INT16 bDexterity = EffectiveDexterity( pSoldier, FALSE );
INT16 bWisdom = EffectiveWisdom( pSoldier );
// Start by calculating the CTH cap, the absolute maximum CTH a shooter can get.
FLOAT fCombinedSkill = (FLOAT)( gGameCTHConstants.AIM_EXP * bExperience * 10 );
fCombinedSkill += (FLOAT)( gGameCTHConstants.AIM_MARKS * bMarksmanship );
fCombinedSkill += (FLOAT)( gGameCTHConstants.AIM_DEX * bDexterity );
fCombinedSkill += (FLOAT)( gGameCTHConstants.AIM_WIS * bWisdom );
// Divide by the total coefficient value.
fCombinedSkill /= (FLOAT)(gGameCTHConstants.AIM_EXP + gGameCTHConstants.AIM_MARKS + gGameCTHConstants.AIM_DEX + gGameCTHConstants.AIM_WIS);
return fCombinedSkill;
}
FLOAT CalcNewChanceToHitAimEffectBonus(SOLDIERTYPE *pSoldier)
{
FLOAT fAimModifier = 0;
// MORALE
INT32 iTempModifier = GetMoraleModifier( pSoldier );
if ( iTempModifier > 0 )
{
fAimModifier += gGameCTHConstants.AIM_HIGH_MORALE * iTempModifier;
}
else if ( iTempModifier < 0 )
{
fAimModifier += gGameCTHConstants.AIM_LOW_MORALE * iTempModifier;
}
// Flugente: drugs can temporarily cause a merc to go psycho
if ( pSoldier->ubProfile != NO_PROFILE && (gMercProfiles[ pSoldier->ubProfile ].bDisability == PSYCHO || MercUnderTheInfluence(pSoldier, DRUG_TYPE_PSYCHO) ) )
{
fAimModifier += gGameCTHConstants.AIM_PSYCHO;
}
// INJURY
if (pSoldier->stats.bLife < pSoldier->stats.bLifeMax)
{
FLOAT fTempPenalty = gGameCTHConstants.AIM_INJURY;
// Bleeding damage is used as a percentage off the Max Life, giving a penalty of up to BASE_INJURY.
fAimModifier += (pSoldier->bBleeding * fTempPenalty) / pSoldier->stats.bLifeMax;
// Bandage damage is used similarly, but is only 1/3 as bad.
INT8 bBandaged = pSoldier->stats.bLifeMax - pSoldier->stats.bLife - pSoldier->bBleeding;
fAimModifier += ((bBandaged * fTempPenalty) / pSoldier->stats.bLifeMax) / 3;
}
// FATIGUE
fAimModifier += (gGameCTHConstants.AIM_FATIGUE * (100 - pSoldier->bBreath)) / 100;
// DRUNKNESS
INT8 iDrunkness = GetDrunkLevel(pSoldier);
if (iDrunkness > 0)
{
iDrunkness -= 1;
fAimModifier += gGameCTHConstants.AIM_DRUNK[iDrunkness];
}
// GASSED
if ( pSoldier->flags.uiStatusFlags & SOLDIER_GASSED )
{
fAimModifier += gGameCTHConstants.AIM_GASSED;
}
// BEING BANDAGED
if (pSoldier->ubServiceCount > 0)
{
fAimModifier += gGameCTHConstants.AIM_BEING_BANDAGED;
}
// SHOCK (SHOOTER)
if (pSoldier->aiData.bShock)
{
FLOAT fTempPenalty = gGameCTHConstants.AIM_SHOCK;
FLOAT fShockPercentage = (FLOAT)(pSoldier->aiData.bShock * 100) / gGameExternalOptions.ubMaxSuppressionShock;
fAimModifier += (fTempPenalty * fShockPercentage) / 100;
}
return fAimModifier;
}
FLOAT CalcNewChanceToHitAimWeaponBonus(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTime, FLOAT fGunAimDifficulty, UINT8 stance)
{
FLOAT fAimModifier = 0;
UINT16 usInHand = pSoldier->usAttackingWeapon;
OBJECTTYPE * pInHand = &(pSoldier->inv[pSoldier->ubAttackingHand]);
// "AIMING" FROM ALTERNATIVE WEAPON HOLDING (faster shots, no scopes, less accuracy)
if ( pSoldier->IsValidAlternativeFireMode( ubAimTime, sGridNo ))
{
//due to the way aiming levels are handled in NCTH, this penalty is increased here by 1/3 (it is harmonized by reduced aiming clicks)
fAimModifier -= gGameExternalOptions.ubAltWeaponHoldingAimingPenaly * 4 / 3;
}
// WEAPON CONDITION
if ( (*pInHand)[0]->data.objectStatus < 50 )
{
fAimModifier += gGameCTHConstants.AIM_GUN_CONDITION * (50 - (*pInHand)[0]->data.objectStatus);
}
// FIRING 1-HANDED WEAPONS
if ( !(Item[ usInHand ].twohanded ) ) //JMich Todo: fix for UnderBarrel firing
{
if (pSoldier->inv[SECONDHANDPOS].exists() != false)
{
if ( pSoldier->IsValidSecondHandShot( ) )
{
// Penalty for shooting two pistols. Ambidextrous trait halves this.
FLOAT fTempPenalty = (gGameCTHConstants.AIM_TWO_GUNS * fGunAimDifficulty) - fGunAimDifficulty;
if (gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, AMBIDEXTROUS_NT ))
{
fTempPenalty = fTempPenalty * (100 - gSkillTraitValues.ubAMPenaltyDoubleReduction) / 100;
}
else if (!gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, AMBIDEXT_OT ))
{
fTempPenalty = 0;
}
fGunAimDifficulty += fTempPenalty;
}
else
{
// Penalty for shooting a pistol with just one hand
fGunAimDifficulty *= gGameCTHConstants.AIM_ONE_HANDED;
}
}
}
// HEAVY WEAPON in OLD trait system
if (Item[usInHand].rocketlauncher && !(gGameOptions.fNewTraitSystem) )
{
// Penalty for shooting heavy weapons (launchers). Heavy Weapons skill halves this, once per skill level.
FLOAT fTempPenalty = (gGameCTHConstants.AIM_HEAVY_WEAPON * fGunAimDifficulty) - fGunAimDifficulty;
for (INT32 x=0; x < NUM_SKILL_TRAITS( pSoldier, HEAVY_WEAPS_OT ); x++)
{
fTempPenalty /= 2;
}
fGunAimDifficulty += fTempPenalty;
}
// STANCE
switch (stance)
{
case ANIM_STAND:
fGunAimDifficulty *= gGameCTHConstants.AIM_STANDING_STANCE;
break;
case ANIM_CROUCH:
fGunAimDifficulty *= gGameCTHConstants.AIM_CROUCHING_STANCE;
break;
case ANIM_PRONE:
fGunAimDifficulty *= gGameCTHConstants.AIM_PRONE_STANCE;
break;
}
// Percentage based handling modifier from the gun and its attachments
FLOAT moda = (fGunAimDifficulty * GetPercentHandlingModifier( pInHand, ANIM_PRONE )) / 100;
FLOAT modb = (fGunAimDifficulty * GetPercentHandlingModifier( pInHand, stance )) / 100;
fGunAimDifficulty += (gGameExternalOptions.ubProneModifierPercentage * moda + (100 - gGameExternalOptions.ubProneModifierPercentage) * modb)/100;
// Now apply Gun Difficulty to the Aim Modifier
fAimModifier -= gGameCTHConstants.AIM_DRAW_COST * fGunAimDifficulty;
// Percent modifier from the weapon and its attachments
INT32 imoda = GetPercentAimModifier( pInHand, ANIM_PRONE );
INT32 imodb = GetPercentAimModifier( pInHand, stance );
fAimModifier += (FLOAT)(gGameExternalOptions.ubProneModifierPercentage * imoda + (100 - gGameExternalOptions.ubProneModifierPercentage) * imodb)/100;
return fAimModifier;
}
FLOAT CalcNewChanceToHitAimSpecialBonus(SOLDIERTYPE *pSoldier)
{
FLOAT fAimModifier = 0;
// GAME DIFFICULTY
if ( !(pSoldier->flags.uiStatusFlags & SOLDIER_PC ) && (pSoldier->bSide != gbPlayerNum) )
{
fAimModifier += gGameCTHConstants.AIM_DIFFICULTY[gGameOptions.ubDifficultyLevel];
}
return fAimModifier;
}
FLOAT CalcNewChanceToHitAimTargetBonus(SOLDIERTYPE *pSoldier, SOLDIERTYPE *pTarget, INT32 sGridNo, INT32 iRange, UINT8 ubAimPos, BOOLEAN fCantSeeTarget)
{
FLOAT fAimModifier = 0;
INT32 iHeightDifference = 0;
// SHOOTING AT A TARGET AT DIFFERENT HEIGHT?
if ( pTarget == NULL )
{
// Shooting to roof.
if ( pSoldier->bTargetLevel > pSoldier->pathing.bLevel )
{
iHeightDifference = 3 * pSoldier->bTargetLevel;
}
}
else
{
// HEIGHT DIFFERENCE
UINT32 uiShooterHeight = 0;
UINT32 uiTargetHeight = 0;
if ( pSoldier->pathing.bLevel > 0 )
{
uiShooterHeight += 3 * pSoldier->pathing.bLevel;
}
switch ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
{
case ANIM_STAND:
uiShooterHeight += 2;
break;
case ANIM_CROUCH:
uiShooterHeight += 1;
break;
}
if (pTarget->pathing.bLevel > 0)
{
uiTargetHeight += 3 * pTarget->pathing.bLevel;
}
switch ( gAnimControl[ pTarget->usAnimState ].ubEndHeight )
{
case ANIM_STAND:
switch (ubAimPos)
{
case AIM_SHOT_HEAD:
uiTargetHeight += 2;
break;
case AIM_SHOT_TORSO:
case AIM_SHOT_RANDOM:
case AIM_SHOT_GLAND:
uiTargetHeight += 1;
break;
}
break;
case ANIM_CROUCH:
switch (ubAimPos)
{
case AIM_SHOT_HEAD:
uiTargetHeight += 1;
break;
}
break;
}
iHeightDifference = uiShooterHeight - uiTargetHeight;
if (iHeightDifference < 0)
{
iHeightDifference *= -1;
}
else
{
iHeightDifference = 0;
}
}
// Height difference is mitigated by range. A LONGER range reduces this penalty!
if (iRange > 0 && iHeightDifference > 0)
{
FLOAT fTempPenalty = gGameCTHConstants.AIM_SHOOTING_UPWARDS * iHeightDifference;
fTempPenalty /= iRange;
fAimModifier += fTempPenalty;
}
// target is invisible to the shooter
if(fCantSeeTarget)
{
fAimModifier += gGameCTHConstants.AIM_TARGET_INVISIBLE;
}
return fAimModifier;
}
FLOAT CalcNewChanceToHitAimTraitBonus(SOLDIERTYPE *pSoldier, FLOAT fDifference, INT32 sGridNo, INT16 ubAimTime, FLOAT fScopeMagFactor, UINT32 uiBestScopeRange)
{
FLOAT fAimChance = 0;
FLOAT fSniperSkillBonus = 0;
UINT16 usInHand = pSoldier->usAttackingWeapon;
INT32 iRange = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sGridNo );
// SANDRO - added support to Throwing trait
if ( fAimChance < gGameExternalOptions.ubMaximumCTH && Item[ usInHand ].usItemClass == IC_THROWING_KNIFE )
{
if( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, THROWING_NT ) )
{
fAimChance += (FLOAT)((fDifference * gSkillTraitValues.ubTHBladesCtHBonusPerClick*3) / gGameExternalOptions.ubMaximumCTH);
}
}
// Add bonuses from Sniper Skill. Applies only when using a scope at or above its "best" range.
else if ( !pSoldier->IsValidAlternativeFireMode( ubAimTime, sGridNo ) )
{
if (fAimChance < gGameExternalOptions.ubMaximumCTH && fScopeMagFactor > 1.0 && iRange >= (INT32)uiBestScopeRange )
{
INT8 loop;
if(gGameOptions.fNewTraitSystem)
{
if ( Weapon[usInHand].ubWeaponType == GUN_PISTOL || Weapon[usInHand].ubWeaponType == GUN_M_PISTOL )
loop = NUM_SKILL_TRAITS( pSoldier, GUNSLINGER_NT );
else
loop = NUM_SKILL_TRAITS( pSoldier, SNIPER_NT );
}
else
loop = NUM_SKILL_TRAITS( pSoldier, PROF_SNIPER_OT );
for (INT8 cnt = 0; cnt < loop; cnt++)
{
// For each level of the sniper skill, cover a defined distance between the current CTH Cap and 99 (the
// maximum...
// SANDRO - this results in giving us total bonus on fAimChance of lame 3-8%... compared to 10-20% bonus *PER AIM CLICK* in OCTH
// ..as an artificial solution multiply the bonus by at least 2 to achieve some meaningful results
if(gGameOptions.fNewTraitSystem)
{
if ( Weapon[usInHand].ubWeaponType == GUN_PISTOL || Weapon[usInHand].ubWeaponType == GUN_M_PISTOL )
fSniperSkillBonus += (FLOAT)((fDifference * gSkillTraitValues.ubGSAimingBonusPerClick * 2) / gGameExternalOptions.ubMaximumCTH);
else
fSniperSkillBonus += (FLOAT)((fDifference * gSkillTraitValues.ubSNAimingBonusPerClick * 2) / gGameExternalOptions.ubMaximumCTH);
}
else
fSniperSkillBonus += (FLOAT)((fDifference * gGameCTHConstants.AIM_SNIPER_SKILL * 2) / gGameExternalOptions.ubMaximumCTH);
fDifference -= fSniperSkillBonus;
}
fAimChance += fSniperSkillBonus;
}
}
return fAimChance;
}