****************************************************************************

** Merged Source Code from Development trunk, for the Spring 2011 Release **
****************************************************************************
o Development Trunk (Revision: 4444):  https://81.169.133.124/source/ja2/branches/Wanne/JA2%201.13%20MP
o This trunk (the official 1.13 source trunk) will only be used for fixing bugs. No new features will be added here.
  New Features are only included in the Development trunk
o !!! After we have fixed a bug here in this trunk, we should MANUALLY merge the bugfix in the development trunk !!!

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@4446 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Wanne
2011-05-26 11:04:47 +00:00
parent c999420bb7
commit 6e2bdd557c
191 changed files with 17816 additions and 6106 deletions
+457 -255
View File
@@ -3562,7 +3562,7 @@ UINT8 CalcChanceToGetThrough( BULLET * pBullet )
// HEADROCK HAM 4: Using new gravity constant from INI. See also a second change below.
if ((UsingNewCTHSystem() == false && pBullet->iLoop > pBullet->iRange * 2) ||
(UsingNewCTHSystem() == true && pBullet->iLoop > pBullet->iRange * gGameCTHConstants.GRAVITY_COEFFICIENT))
(UsingNewCTHSystem() == true && pBullet->iLoop > (INT32)(pBullet->iRange * gGameCTHConstants.RANGE_COEFFICIENT)))
{
// beyond max effective range, bullet starts to drop!
// since we're doing an increment based on distance, not time, the
@@ -4229,6 +4229,11 @@ INT8 FireBulletGivenTargetNCTH( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY,
//if ( ubLoop == 0 )
if( ubShots == 1 )
{
// CHRISL: If we don't set the ddHorizAngle, at the very least, shooting by corners is impossible. Unfortunately, I don't know what other
// impacts these two lines will have. Headrock didn't include them when he originally wrote NCTH but they are in a similar location in the
// OCTH code. Hopefully no issues will result from this change.
ddHorizAngle = ddOrigHorizAngle;
ddVerticAngle = ddOrigVerticAngle;
// HEADROCK HAM 4: Firing increments no longer required here (NCTH)
// calculate by hand (well, without angles) to match LOS
pBullet->qIncrX = FloatToFixed( dDeltaX / (FLOAT)iDistance );
@@ -5871,8 +5876,7 @@ void MoveBullet( INT32 iBullet )
FLOAT dDistanceMoved = PythSpacesAway( pBullet->pFirer->sGridNo, pBullet->sGridNo ) * 10.0f;
// HEADROCK HAM 4: Now using an INI=set Gravity Constant.
// HEADROCK HAM 4: TODO: Should this also be changed to use the Gravity Coefficient?
if ( (dDistanceMoved > pBullet->iRange) )
if ( (dDistanceMoved > (FLOAT)pBullet->iRange*gGameCTHConstants.RANGE_COEFFICIENT) )
{
// beyond max effective range, bullet starts to drop!
// since we're doing an increment based on distance, not time, the
@@ -6502,6 +6506,7 @@ BOOLEAN CalculateLOSNormal( STRUCTURE *pStructure, INT8 bLOSX, INT8 bLOSY, INT8
void AdjustTargetCenterPoint( SOLDIERTYPE *pShooter, INT32 iTargetGridNo, FLOAT *dEndX, FLOAT *dEndY, FLOAT *dEndZ, OBJECTTYPE *pWeapon, UINT32 uiMuzzleSway, INT16 *sApertureRatio )
{
SOLDIERTYPE *pTarget = SimpleFindSoldier( iTargetGridNo, pShooter->bTargetLevel );
///////////////////////////////////////////
// Calculate shooter's coordinates
@@ -6651,12 +6656,14 @@ void AdjustTargetCenterPoint( SOLDIERTYPE *pShooter, INT32 iTargetGridNo, FLOAT
// Note that the Target Tracking formula only adjusts the muzzle along the X-Axis, I.E. moving the
// shot behind the target, never up or down.
SOLDIERTYPE *pTarget = SimpleFindSoldier( iTargetGridNo, pShooter->bTargetLevel );
if (pTarget)
{
CalcTargetMovementOffset( pShooter, pTarget, pWeapon, &dMuzzleOffsetX, ddOrigHorizAngle, (INT32)iAperture );
}
pShooter->dInitialMuzzleOffsetX = dMuzzleOffsetX;
pShooter->dInitialMuzzleOffsetY = dMuzzleOffsetY;
/////////////////////////////////////////////
// First shot in a burst/auto volley
@@ -6733,7 +6740,7 @@ void AdjustTargetCenterPoint( SOLDIERTYPE *pShooter, INT32 iTargetGridNo, FLOAT
UINT16 uiRange = GunRange(pWeapon, pShooter);
// Is the target beyond the gun's range?
if (d2DDistance > (FLOAT)uiRange)
if (d2DDistance > ((FLOAT)uiRange*gGameCTHConstants.RANGE_COEFFICIENT))
{
//////////////////////////////////////////////////////////////////////////////////////////
// STEP 5: Bullet Drop Compensation
@@ -6765,7 +6772,7 @@ void AdjustTargetCenterPoint( SOLDIERTYPE *pShooter, INT32 iTargetGridNo, FLOAT
// within that circle, and sends the bullet that way. Over greater distances, this can amount to
// several penalties, thus making accurate weapons essential for any long-range shot.
CalcBulletDeviation( pShooter, &dShotOffsetX, &dShotOffsetY, pWeapon, (UINT32)d2DDistance );
FLOAT iBulletDev = CalcBulletDeviation( pShooter, &dShotOffsetX, &dShotOffsetY, pWeapon, (UINT32)d2DDistance );
/////////////////////////////////////////////////////////////////////////////////////////////
// STEP 7: Limit Shot Angle
@@ -6776,6 +6783,7 @@ void AdjustTargetCenterPoint( SOLDIERTYPE *pShooter, INT32 iTargetGridNo, FLOAT
// result of too many modifiers by the previous functions. In addition, the INI value also
// affects accuracy of ALL shots in the game, and is enforced here a second time.
LimitImpactPointByFacing( pShooter, pTarget, &dShotOffsetX, &dShotOffsetY, dEndX, dEndY );
LimitImpactPointToMaxAperture( &dShotOffsetX, &dShotOffsetY, iDistanceAperture );
@@ -6802,9 +6810,9 @@ void AdjustTargetCenterPoint( SOLDIERTYPE *pShooter, INT32 iTargetGridNo, FLOAT
swprintf(szUpDown, L"down");
}
if (pShooter->bDoBurst == 1)
if (pShooter->bDoBurst == 0 || pShooter->bDoBurst == 1)
{
ScreenMsg( FONT_MCOLOR_LTRED, MSG_INTERFACE, L"%d. Shot aperture %2.1f, goes %2.1f %s and %2.1f %s", pShooter->bDoBurst, iAperture, dShotOffsetX, szLeftRight, dShotOffsetY, szUpDown );
ScreenMsg( FONT_MCOLOR_LTRED, MSG_INTERFACE, L"%d. Shot aperture %2.1f, Accuracy %2.1f, goes %2.1f %s and %2.1f %s", pShooter->bDoBurst, iAperture, iBulletDev, dShotOffsetX, szLeftRight, dShotOffsetY, szUpDown );
}
else
{
@@ -6846,43 +6854,42 @@ void AdjustTargetCenterPoint( SOLDIERTYPE *pShooter, INT32 iTargetGridNo, FLOAT
*dEndY += dSecondDeltaY;
*dEndZ = __max(-127, *dEndZ + (dShotOffsetY * 11.3f)); // Y, which represented up/down offset, is actually the Z axis in 3d game terms...
// These values are returned to the calling function, in this case UseGun(). That function then fires the bullet
// These values are returned to the calling function, in this case UseGunNCTH(). That function then fires the bullet
// directly at the intended coordinates.
}
FLOAT CalcProjectionFactor( SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon, FLOAT d2DDistance, UINT8 ubAimTime )
{
FLOAT iProjectionFactor = 1.0f;
FLOAT iTargetMagFactor = d2DDistance / gGameCTHConstants.NORMAL_SHOOTING_DISTANCE;
if (ubAimTime > 0)
{
iProjectionFactor = GetProjectionFactor( pWeapon );
if (floor(iTargetMagFactor*10) > floor(iProjectionFactor*10.001))
{
iProjectionFactor -= (iTargetMagFactor - iProjectionFactor);
iProjectionFactor = __max(iProjectionFactor, 1.0f);
}
}
return iProjectionFactor;
}
FLOAT CalcMagFactor( SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon, FLOAT d2DDistance, UINT8 ubAimTime )
{
FLOAT iFinalMagFactor = 0;
FLOAT iScopeFactor = 0;
FLOAT iProjectionFactor = 0;
FLOAT iTargetMagFactor = d2DDistance / gGameCTHConstants.NORMAL_SHOOTING_DISTANCE;
FLOAT rangeModifier = GetScopeRangeMultiplier(pShooter, pWeapon, d2DDistance);
if (ubAimTime > 0)
{
iScopeFactor = GetBestScopeMagnificationFactor( pWeapon, (UINT32)d2DDistance );
iProjectionFactor = GetProjectionFactor( pWeapon );
// Now calculate the optimal magnification that would be required for the target's current distance.
FLOAT iTargetMagFactor = d2DDistance / gGameCTHConstants.NORMAL_SHOOTING_DISTANCE;
if (iTargetMagFactor > iProjectionFactor)
{
// Projection items, like lasers or red dot sights, work up to their "best" range without error.
// If the target is closer than that best range, the projection factor remains the same, helping
// all shots at the target be more accurate.
// However, if the target is further away, the projection begins to lose its effectiveness, becoming
// less useful until it loses all value.
iProjectionFactor -= (iTargetMagFactor - iProjectionFactor);
iProjectionFactor = __max(iProjectionFactor, 1.0f);
}
if (iTargetMagFactor < iScopeFactor)
{
// Scopes give their best Mag Factor at a certain distance and beyond that distance. If the target is
// closer however, it's the target's distance that determines the scope's mag factor. This makes scopes
// quite less useful at ranges closer than they are intended for.
iScopeFactor = iTargetMagFactor;
}
// The final factor is the largest of the two.
iScopeFactor = GetBestScopeMagnificationFactor( pShooter, pWeapon, d2DDistance );
iScopeFactor = __min(iScopeFactor, __max(1.0f, iTargetMagFactor/rangeModifier));
iProjectionFactor = CalcProjectionFactor(pShooter, pWeapon, d2DDistance, ubAimTime);
iFinalMagFactor = __max(iScopeFactor, iProjectionFactor);
}
else
@@ -6985,6 +6992,15 @@ void CalcTargetMovementOffset( SOLDIERTYPE *pShooter, SOLDIERTYPE *pTarget, OBJE
INT16 sDistanceMoved = (INT16)sqrt((double) (sDeltaX * sDeltaX) + (sDeltaY * sDeltaY));
sDistanceMoved /= CELL_X_SIZE; // convert to Tiles.
//CHRISL: The above all assumes that the target is actually still possibly moving at the same rate
// he was moving at before the end of his turn. But what happens if a target moves, stops and
// changes stance? We use usAnimState to figure out what stance we're currently in by looking at
// out current animation state. And I think usUIMovementMode tells us what animation state we
// last moved in. So if these two values are different, we should be able to assume that the target
// has stopped moving.
if (pTarget->usAnimState != pTarget->usUIMovementMode)
sDistanceMoved = 0;
if (sDistanceMoved == 0)
{
//Target hasn't moved. Simply abort the formula, as there will be no movement penalty anyway.
@@ -7129,7 +7145,7 @@ void CalcRangeCompensationOffset( SOLDIERTYPE *pShooter, FLOAT *dMuzzleOffsetY,
// and how fast it is spinning along its axis will all determine how far it can go before this happens.
//
// The JA2 Bullet Mechanism already simulates this effect, by decreasing a bullet's vertical angle repeatedly
// once it crosses its "maximum range" (as defined by the weapon's Range property). The bullet falls to the
// once it crosses its "maximum range" (as defined by twice the weapon's Range property). The bullet falls to the
// ground with increasing rapidity, until it collides with the ground.
//
// NCTH uses this principle in reverse - allowing shooters to compensate for this effect and thereby increase
@@ -7149,7 +7165,7 @@ void CalcRangeCompensationOffset( SOLDIERTYPE *pShooter, FLOAT *dMuzzleOffsetY,
// Avoid division by 0.
return;
}
if (usGunRange >= iRangeToTarget)
if ((usGunRange*gGameCTHConstants.RANGE_COEFFICIENT) >= iRangeToTarget)
{
return;
}
@@ -7164,7 +7180,7 @@ void CalcRangeCompensationOffset( SOLDIERTYPE *pShooter, FLOAT *dMuzzleOffsetY,
// We now take the Range to Target and the Maximum Range for the bullet, and find the distance between them.
// This is how much distance the bullet has to cover while dropping out of the sky. In other words, we need to
// make the bullet fly this much longer than it would normally have.
INT32 iRangeDiff = (iRangeToTarget-usGunRange) / 10;
INT32 iRangeDiff = (iRangeToTarget-(UINT32)(usGunRange*gGameCTHConstants.RANGE_COEFFICIENT)) / 10;
// To know how much we need to raise our muzzle, what we do is calculate how far below the target the bullet would
// hit, assuming no muzzle adjustment, and pretending there's no ground to collide with. The result is equal to
@@ -7271,7 +7287,7 @@ void CalcMuzzleSway( SOLDIERTYPE *pShooter, FLOAT *dMuzzleOffsetX, FLOAT *dMuzzl
*dMuzzleOffsetY += (dDeltaY * bUpDown) * dVerticalBias;
}
void CalcBulletDeviation( SOLDIERTYPE *pShooter, FLOAT *dShotOffsetX, FLOAT *dShotOffsetY, OBJECTTYPE *pWeapon, UINT32 uiRange )
FLOAT CalcBulletDeviation( SOLDIERTYPE *pShooter, FLOAT *dShotOffsetX, FLOAT *dShotOffsetY, OBJECTTYPE *pWeapon, UINT32 uiRange )
{
//////////////////////////////////////////////////////////////////////////////////////////////////
// HEADROCK HAM 4: New Shooting Mechanism, Bullet Deviation
@@ -7301,6 +7317,15 @@ void CalcBulletDeviation( SOLDIERTYPE *pShooter, FLOAT *dShotOffsetX, FLOAT *dSh
// We start by reading the gun's Accuracy value. We'll use that as the basis for everything else.
INT16 sAccuracy = GetGunAccuracy( pWeapon );
UINT16 sEffRange = Weapon[Item[pShooter->inv[pShooter->ubAttackingHand].usItem].ubClassIndex].usRange + GetRangeBonus(&(pShooter->inv[ pShooter->ubAttackingHand ]));
// WANNE: I got a CTD in a multiplayer test game, because sEffRange was 0 (division to zero).
// I don't know why this happend?
FLOAT iRangeRatio = 1.0f;
if (sEffRange > 0)
{
iRangeRatio = __max(1.0f, (FLOAT)(uiRange / sEffRange));
}
// This value can be anywhere between 0 and 100. Let's make sure.
sAccuracy = __max(0, sAccuracy);
@@ -7313,12 +7338,15 @@ void CalcBulletDeviation( SOLDIERTYPE *pShooter, FLOAT *dShotOffsetX, FLOAT *dSh
// deviation circle (100% radius), while a gun with accuracy 100 produces a minimal sized
// circle (actually, a dot with 0% radius).
FLOAT iBulletDev = (gGameCTHConstants.MAX_BULLET_DEV * (100-sAccuracy)) / 100;
if (gGameCTHConstants.RANGE_EFFECTS_DEV == TRUE)
iBulletDev *= iRangeRatio;
iBulletDev /= 2; // This compensates for the difference between CellXY and ScreenXY
// If the radius is 0, this gun's bullets are flawless, and will never deviate from where
// they were fired.
if (iBulletDev == 0)
{
return;
return (iBulletDev);
}
// So now, iBulletDev is a maximum deviation for any bullet coming out of this weapon.
@@ -7348,6 +7376,96 @@ void CalcBulletDeviation( SOLDIERTYPE *pShooter, FLOAT *dShotOffsetX, FLOAT *dSh
*dShotOffsetX += dDeltaX * bLeftRight;
*dShotOffsetY += dDeltaY * bUpDown;
return (iBulletDev);
}
void LimitImpactPointByFacing( SOLDIERTYPE *pShooter, SOLDIERTYPE *pTarget, FLOAT *dShotOffsetX, FLOAT *dShotOffsetY, FLOAT *dEndX, FLOAT *dEndY )
{
////////////////////////////////////////////////////////////////////////////////////////////
//
// CHRISL HAM 4: New Shooting Mechanism, Angle Limits by Facing
//
// One drawback of the new shooting mechanism is that it depends on the the JSD files being accurate. These
// files determine how an obstruction (like an enemy soldier) takes up space in a 3D tile. This is done by
// splitting a tile into 125 "cubes", stacked 5 x 5 x 5. Each "cube" is 2 units wide, 2 units deep and about
// 6 units tall. A standing merc in the JSD files is represented by a "plus sign" shaped construct that is
// 3 "cubes" tall, 3 "cubes" wide and 1 "cube" deep. A crouching soldier is 2 x 3 x 2 and a prone soldier is
// 1 x 3 x 5. This means if we're shooting at a standind target from the side, we effectively have 1/3 the
// chance to hit as we would a target that was facing us. Unfortunately, the graphical cursor can't display
// this information so it will appear as though you have the same chance of hitting regardless of the targets
// facing.
//
// As a result of the above, this function determines the targets facing compared to the shooters, and will
// reduce the Shot Offsets if the target isn't facing directly towards or away from the shooter. This isn't
// the most accurate solution but it's the best solution I can come up with at this time. The alternative is
// to let shots that appear to have no chance of missing (shooting aperture within the targets displayed body)
// actually miss an average of 2 in every 3 shots.
//
// I'm going to assume for the moment that the 1/3rd chance only occurs when a target is facing directly perpendicular
// to your facing. I don't know for sure if this is true or not, but it's the assumption I'm going to make.
//
////////////////////////////////////////////////////////////////////////////////////////////
UINT8 iShooterFacing = pShooter->ubDirection;
UINT8 iTargetFacing = pShooter->ubDirection;
FLOAT iDivisor = gGameCTHConstants.SIDE_FACING_DIVISOR;
if(pTarget)
iTargetFacing = pTarget->ubDirection;
switch (iTargetFacing)
{
case 0:
if(iShooterFacing == 2 || iShooterFacing == 6){
*dShotOffsetX /= iDivisor;
*dShotOffsetY /= iDivisor;
}
break;
case 1:
if(iShooterFacing == 3 || iShooterFacing == 7){
*dShotOffsetX /= iDivisor;
*dShotOffsetY /= iDivisor;
}
break;
case 2:
if(iShooterFacing == 0 || iShooterFacing == 4){
*dShotOffsetX /= iDivisor;
*dShotOffsetY /= iDivisor;
}
break;
case 3:
if(iShooterFacing == 1 || iShooterFacing == 5){
*dShotOffsetX /= iDivisor;
*dShotOffsetY /= iDivisor;
}
break;
case 4:
if(iShooterFacing == 2 || iShooterFacing == 6){
*dShotOffsetX /= iDivisor;
*dShotOffsetY /= iDivisor;
}
break;
case 5:
if(iShooterFacing == 3 || iShooterFacing == 7){
*dShotOffsetX /= iDivisor;
*dShotOffsetY /= iDivisor;
}
break;
case 6:
if(iShooterFacing == 0 || iShooterFacing == 4){
*dShotOffsetX /= iDivisor;
*dShotOffsetY /= iDivisor;
}
break;
case 7:
if(iShooterFacing == 1 || iShooterFacing == 5){
*dShotOffsetX /= iDivisor;
*dShotOffsetY /= iDivisor;
}
break;
default:
break;
}
}
void LimitImpactPointToMaxAperture( FLOAT *dShotOffsetX, FLOAT *dShotOffsetY, FLOAT dDistanceAperture )
@@ -7391,6 +7509,169 @@ void LimitImpactPointToMaxAperture( FLOAT *dShotOffsetX, FLOAT *dShotOffsetY, FL
}
}
UINT32 CalcCounterForceFrequency(SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon)
{
INT8 traitLoop;
FLOAT iCounterForceFrequency = gGameCTHConstants.RECOIL_COUNTER_FREQUENCY_AGI * EffectiveAgility(pShooter);
iCounterForceFrequency += gGameCTHConstants.RECOIL_COUNTER_FREQUENCY_EXP_LEVEL * EffectiveExpLevel(pShooter) * 10;
// Average
FLOAT iDivisor = gGameCTHConstants.RECOIL_COUNTER_FREQUENCY_AGI +
gGameCTHConstants.RECOIL_COUNTER_FREQUENCY_EXP_LEVEL;
iCounterForceFrequency /= iDivisor;
// Bridge the gap to 100 with the help of the AUTO-WEAPONS skill
if(gGameOptions.fNewTraitSystem)
traitLoop = NUM_SKILL_TRAITS( pShooter, AUTO_WEAPONS_NT );
else
traitLoop = NUM_SKILL_TRAITS( pShooter, AUTO_WEAPS_OT );
for (INT32 x = 0; x < traitLoop; x++)
{
INT8 bDifference = (INT8)(100-iCounterForceFrequency);
bDifference = (INT8)(bDifference / gGameCTHConstants.RECOIL_COUNTER_FREQUENCY_AUTO_WEAPONS_DIVISOR);
iCounterForceFrequency += bDifference;
}
// Percent Modifier from weapon and its attachments
iCounterForceFrequency += (iCounterForceFrequency * GetCounterForceFrequencyModifier( pWeapon, gAnimControl[ pShooter->usAnimState ].ubEndHeight )) / 100;
// Limit to 1-100.
iCounterForceFrequency = __min(100, iCounterForceFrequency);
iCounterForceFrequency = __max(1, iCounterForceFrequency);
// Invert value. Now 100 is bad, 1 is good.
iCounterForceFrequency = 100 - iCounterForceFrequency;
// Calculate frequency
UINT32 uiBulletsPer5AP = max(1,GetAutofireShotsPerFiveAPs( pWeapon ));
UINT32 uiCounterForceFrequency = __max(1, (UINT32)ceil((uiBulletsPer5AP * iCounterForceFrequency) / 100));
//CHRISL: For the time being, this is going to override the CFF function
uiBulletsPer5AP = GetAutofireShotsPerFiveAPs( pWeapon );
if(uiBulletsPer5AP < 1)
uiBulletsPer5AP = 3;
uiCounterForceFrequency = (UINT32)ceil((FLOAT)uiBulletsPer5AP / 2.0f);
return uiCounterForceFrequency;
}
FLOAT CalcCounterForceMax(SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon, UINT8 uiStance)
{
if(uiStance == 0)
uiStance = gAnimControl[ pShooter->usAnimState ].ubHeight;
FLOAT iCounterForceMax = gGameCTHConstants.RECOIL_MAX_COUNTER_STR * EffectiveStrength(pShooter);
iCounterForceMax += gGameCTHConstants.RECOIL_MAX_COUNTER_AGI * EffectiveAgility(pShooter);
iCounterForceMax += gGameCTHConstants.RECOIL_MAX_COUNTER_EXP_LEVEL * EffectiveExpLevel(pShooter) * 10;
FLOAT iDivisor = gGameCTHConstants.RECOIL_MAX_COUNTER_STR +
gGameCTHConstants.RECOIL_MAX_COUNTER_AGI +
gGameCTHConstants.RECOIL_MAX_COUNTER_EXP_LEVEL;
iCounterForceMax /= iDivisor;
// Add the effects from the weapon and its attachments (foregrip, for instance). This is applied as a percentage.
INT32 iModifier = GetCounterForceMaxModifier( pWeapon, uiStance );
iCounterForceMax += (iCounterForceMax * iModifier) / 100;
// Limit to 0-100.
//iCounterForceMax = __max(__min(iCounterForceMax, 100), 0);
// Use the result as a percentage of the maximum possible applicable force, as defined in the INI.
iCounterForceMax = (FLOAT)((iCounterForceMax * gGameCTHConstants.RECOIL_MAX_COUNTER_FORCE) / 100);
return iCounterForceMax;
}
UINT32 CalcCounterForceAccuracy(SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon, UINT32 uiRange, BOOLEAN fTracer, bool fAnticipate)
{
INT8 traitLoop;
FLOAT iCounterForceAccuracy = gGameCTHConstants.RECOIL_COUNTER_ACCURACY_DEX * EffectiveDexterity(pShooter);
iCounterForceAccuracy += gGameCTHConstants.RECOIL_COUNTER_ACCURACY_WIS * EffectiveWisdom(pShooter);
iCounterForceAccuracy += gGameCTHConstants.RECOIL_COUNTER_ACCURACY_AGI * EffectiveAgility(pShooter);
iCounterForceAccuracy += gGameCTHConstants.RECOIL_COUNTER_ACCURACY_EXP_LEVEL * EffectiveExpLevel(pShooter) * 10;
FLOAT iDivisor = gGameCTHConstants.RECOIL_COUNTER_ACCURACY_DEX +
gGameCTHConstants.RECOIL_COUNTER_ACCURACY_WIS +
gGameCTHConstants.RECOIL_COUNTER_ACCURACY_AGI +
gGameCTHConstants.RECOIL_COUNTER_ACCURACY_EXP_LEVEL;
iCounterForceAccuracy /= iDivisor;
// Add the effects from the weapon and its attachments. A foregrip or bipod are very useful for this.
// Attachment bonuses are applied as a percentage to the accuracy of the shooter.
INT32 iModifier = GetCounterForceAccuracyModifier( pWeapon, gAnimControl[ pShooter->usAnimState ].ubHeight );
UINT32 uiCounterForceAccuracy = (UINT32)(iCounterForceAccuracy + ((iCounterForceAccuracy * iModifier) / 100));
// Now add the effect of the AutoWeapons skill. It "bridges" a portion of the gap between shooter's actual accuracy
// and 100% accuracy. For instance, if the divisor is set to 2 in the INI, the first skill level will close 50% of
// the gap, the second skill level closes another 25%.
if(gGameOptions.fNewTraitSystem)
traitLoop = NUM_SKILL_TRAITS( pShooter, AUTO_WEAPONS_NT );
else
traitLoop = NUM_SKILL_TRAITS( pShooter, AUTO_WEAPS_OT );
for (INT32 x = 0; x < traitLoop; x++)
{
INT8 bDifference = 100-uiCounterForceAccuracy;
bDifference = (INT8)(bDifference / gGameCTHConstants.RECOIL_COUNTER_ACCURACY_AUTO_WEAPONS_DIVISOR);
uiCounterForceAccuracy += bDifference;
}
////////////////////////////////////////////////////////////////////
// Sight-range effects
// If we can't see the target, but buddies can see it, CF-Accuracy drops by 50%
// If we can't see the target and neither can buddies, CF-Accuracy drops by 75%
UINT8 ubTargetID = WhoIsThere2( pShooter->sTargetGridNo, pShooter->bTargetLevel ); // Target ubID
INT16 sDistVis = pShooter->GetMaxDistanceVisible(pShooter->sTargetGridNo, pShooter->bTargetLevel, CALC_FROM_ALL_DIRS ) * CELL_X_SIZE;
gbForceWeaponNotReady = true;
INT16 sDistVisNoScope = pShooter->GetMaxDistanceVisible(pShooter->sTargetGridNo, pShooter->bTargetLevel, CALC_FROM_ALL_DIRS ) * CELL_X_SIZE;
gbForceWeaponNotReady = false;
FLOAT scopeRangeMod = (float)sDistVis / (float)sDistVisNoScope;
INT32 iSightRange = 0;
if (ubTargetID != NOBODY)
iSightRange = SoldierToSoldierLineOfSightTest( pShooter, MercPtrs[ubTargetID], TRUE, NO_DISTANCE_LIMIT, pShooter->bAimShotLocation, false );
if (iSightRange == 0) { // didn't do a bodypart-based test or can't see specific body part aimed at
iSightRange = SoldierTo3DLocationLineOfSightTest( pShooter, pShooter->sTargetGridNo, pShooter->bTargetLevel, pShooter->bTargetCubeLevel, TRUE, NO_DISTANCE_LIMIT, false );
}
if (iSightRange == 0) { // Can't see the target but we still need to know what the sight range would be if we could so we can deal with cover penalties
iSightRange = SoldierToSoldierLineOfSightTest( pShooter, MercPtrs[ubTargetID], TRUE, NO_DISTANCE_LIMIT, pShooter->bAimShotLocation, false, true );
}
// Modify iSightRange for scope use
iSightRange = (INT32)(iSightRange / scopeRangeMod);
if (pShooter->aiData.bOppList[ubTargetID] != SEEN_CURRENTLY && gbPublicOpplist[pShooter->bTeam][ubTargetID] == SEEN_CURRENTLY)
iSightRange *= 2;
else if(pShooter->aiData.bOppList[ubTargetID] != SEEN_CURRENTLY && gbPublicOpplist[pShooter->bTeam][ubTargetID] != SEEN_CURRENTLY)
iSightRange *= 4;
// CHRISL: Divide physical range by sight range. Not the other way around. We wan't CFA to go down if someone appears to be further
// then they are because of terrain or the fact that we can't see them.
uiCounterForceAccuracy = (UINT32)(uiCounterForceAccuracy * (FLOAT)((FLOAT)uiRange / iSightRange));
// Was last bullet a tracer?
if (fTracer)
{
// Augment by a certain amount, mitigated by range
uiCounterForceAccuracy += (UINT32)gGameCTHConstants.RECOIL_COUNTER_ACCURACY_TRACER_BONUS;
}
if(fAnticipate)
{
uiCounterForceAccuracy -= (UINT32)(uiCounterForceAccuracy * gGameCTHConstants.RECOIL_COUNTER_ACCURACY_ANTICIPATION / 100);
}
// Limit to 0 to 100.
uiCounterForceAccuracy = __max(__min(uiCounterForceAccuracy, 100), 0);
// Invert. 0=good, 100-bad.
uiCounterForceAccuracy = 100-uiCounterForceAccuracy;
return uiCounterForceAccuracy;
}
void CalcPreRecoilOffset( SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon, FLOAT *dMuzzleOffsetX, FLOAT *dMuzzleOffsetY, UINT32 uiRange )
{
//////////////////////////////////////////////////////////////////////////////////////
@@ -7422,6 +7703,39 @@ void CalcPreRecoilOffset( SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon, FLOAT *dMu
// this soldier to actually consider using pre-recoil offset at all. This depends entirely
// on wisdom and experience.
// We can also anticipate the recoil of the weapon, which we should set here.
INT8 bGunRecoilX;
INT8 bGunRecoilY;
FLOAT dDistanceRatio = (FLOAT)(uiRange / gGameCTHConstants.NORMAL_RECOIL_DISTANCE);
FLOAT iCounterForceMax = CalcCounterForceMax(pShooter, pWeapon);
UINT32 uiCounterForceAccuracy = CalcCounterForceAccuracy(pShooter, pWeapon, uiRange, FALSE, true);
UINT32 uiCounterForceFrequency = CalcCounterForceFrequency(pShooter, pWeapon);
iCounterForceMax = iCounterForceMax * (100-uiCounterForceAccuracy) / 100;
GetRecoil( pShooter, pWeapon, &bGunRecoilX, &bGunRecoilY, 1 );
FLOAT iGunTotalRecoil = (FLOAT)bGunRecoilX + (FLOAT)bGunRecoilY;
FLOAT iIdealCounterForceX = 0.0;
FLOAT iIdealCounterForceY = 0.0;
if(iGunTotalRecoil != 0)
{
iIdealCounterForceX = __min( iCounterForceMax, (FLOAT)bGunRecoilX );
iIdealCounterForceY = __min( iCounterForceMax, (FLOAT)bGunRecoilY );
FLOAT iTotalCounterForce = sqrt((abs(iIdealCounterForceX)*abs(iIdealCounterForceX)) + (abs(iIdealCounterForceY)*abs(iIdealCounterForceY)));
if (iTotalCounterForce > iCounterForceMax)
{
FLOAT iRatio = iTotalCounterForce / iCounterForceMax;
if (iRatio != 0)
{
iIdealCounterForceX /= iRatio;
iIdealCounterForceY /= iRatio;
}
}
pShooter->dPrevCounterForceX = -iIdealCounterForceX;
pShooter->dPrevCounterForceY = -iIdealCounterForceY;
}
INT8 traitLoop;
FLOAT iCombinedSkill = gGameCTHConstants.PRE_RECOIL_WIS * EffectiveWisdom(pShooter);
iCombinedSkill += gGameCTHConstants.PRE_RECOIL_EXP_LEVEL * EffectiveExpLevel(pShooter) * 10;
@@ -7459,41 +7773,6 @@ void CalcPreRecoilOffset( SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon, FLOAT *dMu
// shooter gets his chance to begin applying counter-force. He'll need to pre-compensate for this
// many bullets.
FLOAT iCounterForceFrequency = gGameCTHConstants.RECOIL_COUNTER_FREQUENCY_AGI * EffectiveAgility(pShooter);
iCounterForceFrequency += gGameCTHConstants.RECOIL_COUNTER_FREQUENCY_EXP_LEVEL * EffectiveExpLevel(pShooter) * 10;
// Average
iDivisor = gGameCTHConstants.RECOIL_COUNTER_FREQUENCY_AGI +
gGameCTHConstants.RECOIL_COUNTER_FREQUENCY_EXP_LEVEL;
iCounterForceFrequency /= iDivisor;
//CHRISL: TODO: STOMP need to include AUTO_WEAPS_OT here as well.
// Bridge the gap to 100 with the help of the AUTO-WEAPONS skill
if(gGameOptions.fNewTraitSystem)
traitLoop = NUM_SKILL_TRAITS( pShooter, AUTO_WEAPONS_NT );
else
traitLoop = NUM_SKILL_TRAITS( pShooter, AUTO_WEAPS_OT );
for (INT32 x = 0; x < traitLoop; x++)
{
INT8 bDifference = (INT8)(100-iCounterForceFrequency);
bDifference = (INT8)(bDifference / gGameCTHConstants.RECOIL_COUNTER_FREQUENCY_AUTO_WEAPONS_DIVISOR);
iCounterForceFrequency += bDifference;
}
// Percent Modifier from weapon and its attachments
iCounterForceFrequency += (iCounterForceFrequency * GetCounterForceFrequencyModifier( pWeapon, gAnimControl[ pShooter->usAnimState ].ubEndHeight )) / 100;
// Limit to 0-100.
iCounterForceFrequency = __min(100, iCounterForceFrequency);
iCounterForceFrequency = __max(0, iCounterForceFrequency);
// Invert value. Now 100 is bad, 0 is good.
iCounterForceFrequency = 100 - iCounterForceFrequency;
// Calculate frequency
UINT32 uiBulletsPer5AP = GetAutofireShotsPerFiveAPs( pWeapon );
UINT32 uiCounterForceFrequency = __max(1, (UINT32)(iCounterForceFrequency * uiBulletsPer5AP) / 100);
// Also, it might be possible that we don't intend to fire that many bullets at all. So we want to figure out
// how many bullets are about to be fired, and adjust accordingly.
UINT32 uiIntendedBullets;
@@ -7512,17 +7791,15 @@ void CalcPreRecoilOffset( SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon, FLOAT *dMu
uiIntendedBullets = __min( uiIntendedBullets, uiCounterForceFrequency );
// Now, let's figure out how much recoil this gun will put out during those first X bullets.
INT8 bGunRecoilX;
INT8 bGunRecoilY;
INT32 iTotalGunRecoilX = 0;
INT32 iTotalGunRecoilY = 0;
FLOAT iTotalGunRecoilX = 0;
FLOAT iTotalGunRecoilY = 0;
// For each bullet, add up its recoil.
for (UINT32 count=1; count <= uiIntendedBullets; count++)
{
GetRecoil( pWeapon, &bGunRecoilX, &bGunRecoilY, count );
iTotalGunRecoilX += bGunRecoilX;
iTotalGunRecoilY += bGunRecoilY;
GetRecoil( pShooter, pWeapon, &bGunRecoilX, &bGunRecoilY, count );
iTotalGunRecoilX += (bGunRecoilX + pShooter->dPrevCounterForceX);
iTotalGunRecoilY += (bGunRecoilY + pShooter->dPrevCounterForceY);
}
if (iTotalGunRecoilX == 0 && iTotalGunRecoilY == 0)
@@ -7545,17 +7822,16 @@ void CalcPreRecoilOffset( SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon, FLOAT *dMu
INT8 bLeftRight = PreRandom(2)?(1):(-1);
// Apply random factor
dPreRecoilX = (iTotalGunRecoilX * -1) + (dPreRecoilX * bLeftRight);
dPreRecoilY = (iTotalGunRecoilY * -1) + (dPreRecoilY * bUpDown);
dPreRecoilX = iTotalGunRecoilX + (dPreRecoilX * bLeftRight);
dPreRecoilY = iTotalGunRecoilY + (dPreRecoilY * bUpDown);
// Apply distance ratio. The further away we are, the more compensation we need.
FLOAT dDistanceRatio = (FLOAT)(uiRange / gGameCTHConstants.NORMAL_SHOOTING_DISTANCE);
dPreRecoilX *= dDistanceRatio;
dPreRecoilY *= dDistanceRatio;
// Finally, adjust our muzzle direction.
*dMuzzleOffsetX += dPreRecoilX;
*dMuzzleOffsetY += dPreRecoilY;
*dMuzzleOffsetX -= dPreRecoilX;
*dMuzzleOffsetY -= dPreRecoilY;
}
@@ -7578,15 +7854,18 @@ void CalcRecoilOffset( SOLDIERTYPE *pShooter, FLOAT *dMuzzleOffsetX, FLOAT *dMuz
// shooters might be overwhelmed and start firing towards the sky.
// Get X/Y recoil parameters from the gun.
INT8 traitLoop;
INT8 bGunRecoilX;
INT8 bGunRecoilY;
GetRecoil( pWeapon, &bGunRecoilX, &bGunRecoilY, pShooter->bDoBurst );
GetRecoil( pShooter, pWeapon, &bGunRecoilX, &bGunRecoilY, pShooter->bDoBurst-1 );
// If no recoil, then we shouldn't be here anyway.
if(bGunRecoilX == 0 && bGunRecoilY == 0)
return;
FLOAT iGunTotalRecoil = sqrt((FLOAT)(bGunRecoilX*bGunRecoilX) + (FLOAT)(bGunRecoilY*bGunRecoilY));
FLOAT iDistanceRatio = (FLOAT)((FLOAT)uiRange / gGameCTHConstants.NORMAL_SHOOTING_DISTANCE);
FLOAT iDistanceRatio = (FLOAT)uiRange / (FLOAT)gGameCTHConstants.NORMAL_RECOIL_DISTANCE;
// These variables will hold the amount of X/Y force our shooter exerts to try to fight recoil.
FLOAT iAppliedCounterForceX;
@@ -7601,30 +7880,7 @@ void CalcRecoilOffset( SOLDIERTYPE *pShooter, FLOAT *dMuzzleOffsetX, FLOAT *dMuz
// Skilled shooters get this chance more often.
// Tracer fire also helps. Once a tracer bullet is fired, we get one free chance to alter our counter-force.
FLOAT iCounterForceFrequency = gGameCTHConstants.RECOIL_COUNTER_FREQUENCY_AGI * EffectiveAgility(pShooter);
iCounterForceFrequency += gGameCTHConstants.RECOIL_COUNTER_FREQUENCY_EXP_LEVEL * EffectiveExpLevel(pShooter) * 10;
FLOAT iDivisor = gGameCTHConstants.RECOIL_COUNTER_FREQUENCY_AGI +
gGameCTHConstants.RECOIL_COUNTER_FREQUENCY_EXP_LEVEL;
iCounterForceFrequency /= iDivisor;
//CHRISL: TODO: STOMP need to include AUTO_WEAPS_OT here as well.
if(gGameOptions.fNewTraitSystem)
traitLoop = NUM_SKILL_TRAITS( pShooter, AUTO_WEAPONS_NT );
else
traitLoop = NUM_SKILL_TRAITS( pShooter, AUTO_WEAPS_OT );
for (INT32 x = 0; x < traitLoop; x++)
{
INT8 bDifference = (INT8)(100-iCounterForceFrequency);
bDifference = (INT8)(bDifference / gGameCTHConstants.RECOIL_COUNTER_FREQUENCY_AUTO_WEAPONS_DIVISOR);
iCounterForceFrequency += bDifference;
}
UINT32 uiBulletsPer5AP = GetAutofireShotsPerFiveAPs( pWeapon );
UINT32 uiCounterForceFrequency = (UINT32)((iCounterForceFrequency * uiBulletsPer5AP) / 100);
// Flat Modifier from weapon and its attachments
uiCounterForceFrequency = __max(1, uiCounterForceFrequency + ((uiCounterForceFrequency * GetCounterForceFrequencyModifier( pWeapon, gAnimControl[ pShooter->usAnimState ].ubEndHeight )) / 100));
UINT32 uiCounterForceFrequency = CalcCounterForceFrequency(pShooter, pWeapon);
BOOLEAN fAdjustmentAllowed = FALSE;
if ((pShooter->bDoBurst-1)%uiCounterForceFrequency == 0)
@@ -7653,9 +7909,30 @@ void CalcRecoilOffset( SOLDIERTYPE *pShooter, FLOAT *dMuzzleOffsetX, FLOAT *dMuz
// Our shooter wants to apply just as much counter-force as necessary to keep the next offset as close to 0 on both
// axes.
// Instead of assuming we're on a uiCounterForceFrequency round, we need to verify exactly where we are in the uiCounterForceFrequency
// sequence.
INT32 uiIntendedBullets;
if ( pShooter->bDoAutofire > 0 )
{
// Autofire. The number of bullets to be fired equals to the value of bDoAutofire
uiIntendedBullets = pShooter->bDoAutofire;
}
else
{
// Burst. Get the number of shots-per-burst from the weapon.
uiIntendedBullets = Weapon[ pWeapon->usItem ].ubShotsPerBurst + GetBurstSizeBonus(pWeapon);
}
INT8 iRound = (pShooter->bDoBurst-1) % uiCounterForceFrequency;
if(iRound == 0)
iRound = uiCounterForceFrequency;
if((pShooter->bDoBurst - 1 + iRound) > uiIntendedBullets)
iRound = uiIntendedBullets - (pShooter->bDoBurst - 1);
// Add the two together to figure out where the next bullet is going to fly in relation to the target.
FLOAT dNextOffsetX = (FLOAT)(*dMuzzleOffsetX + ((bGunRecoilX * iDistanceRatio) * uiCounterForceFrequency) );
FLOAT dNextOffsetY = (FLOAT)(*dMuzzleOffsetY + ((bGunRecoilY * iDistanceRatio) * uiCounterForceFrequency) );
FLOAT dNextOffsetX = (FLOAT)(*dMuzzleOffsetX + (bGunRecoilX * iDistanceRatio) );
FLOAT dNextOffsetY = (FLOAT)(*dMuzzleOffsetY + (bGunRecoilY * iDistanceRatio) );
//FLOAT dNextOffsetX = (FLOAT)(*dMuzzleOffsetX + ((bGunRecoilX * iDistanceRatio) * uiCounterForceFrequency) );
//FLOAT dNextOffsetY = (FLOAT)(*dMuzzleOffsetY + ((bGunRecoilY * iDistanceRatio) * uiCounterForceFrequency) );
////////////////////////////////////////////////////////////////////////////
// Calculating recoil counter-force.
@@ -7678,113 +7955,43 @@ void CalcRecoilOffset( SOLDIERTYPE *pShooter, FLOAT *dMuzzleOffsetX, FLOAT *dMuz
// maximum counter-force that can be applied. By default, it is based primarily on the strength of the shooter,
// although agility is also helpful.
FLOAT iCounterForceMax = gGameCTHConstants.RECOIL_MAX_COUNTER_STR * EffectiveStrength(pShooter);
iCounterForceMax += gGameCTHConstants.RECOIL_MAX_COUNTER_AGI * EffectiveAgility(pShooter);
iCounterForceMax += gGameCTHConstants.RECOIL_MAX_COUNTER_EXP_LEVEL * EffectiveExpLevel(pShooter) * 10;
iDivisor = gGameCTHConstants.RECOIL_MAX_COUNTER_STR +
gGameCTHConstants.RECOIL_MAX_COUNTER_AGI +
gGameCTHConstants.RECOIL_MAX_COUNTER_EXP_LEVEL;
iCounterForceMax /= iDivisor;
// Add the effects from the weapon and its attachments (foregrip, for instance). This is applied as a percentage.
INT32 iModifier = GetCounterForceMaxModifier( pWeapon, gAnimControl[ pShooter->usAnimState ].ubHeight );
iCounterForceMax += (iCounterForceMax * iModifier) / 100;
// Limit to 0-100.
iCounterForceMax = __max(__min(iCounterForceMax, 100), 0);
// Use the result as a percentage of the maximum possible applicable force, as defined in the INI.
iCounterForceMax = (FLOAT)((iCounterForceMax * gGameCTHConstants.RECOIL_MAX_COUNTER_FORCE) / 100);
FLOAT iCounterForceMax = CalcCounterForceMax(pShooter, pWeapon);
// iCounterForceMax is now the absolute limit.
// STEP 2: Now let's determine how much counter-force would be ideal. If the gun isn't kicking too powerfully,
// and/or not too much recoil has built up from previous bullets, the shooter should potentially be able to compensate
// for ALL of it. However, in some cases, the shooter simply can't exert enough force to counter ALL recoil.
// Calculate Ideal X and limit to max force
FLOAT iIdealCounterForceX = 0.0;
iIdealCounterForceX = __min( iCounterForceMax, abs((dNextOffsetX / iDistanceRatio) / uiCounterForceFrequency ));
// Calculate Ideal Y and limit to max force
FLOAT iIdealCounterForceY = 0.0;
iIdealCounterForceY = __min( iCounterForceMax, abs((dNextOffsetY / iDistanceRatio) / uiCounterForceFrequency ));
// STEP 3: Now we need to determine how accurate the shooter is when applying counter-force. He won't always apply
// STEP 2: Now we need to determine how accurate the shooter is when applying counter-force. He won't always apply
// as much as necessary, and may sometimes apply too much. The ability to apply exactly (or close to exactly) the
// amount of force required is based on various skills, especially agility and dexterity. Wisdom and experience also
// help, and the AUTO_WEAPONS skill is invaluable. In addition, tracers (when actually fired) will boost the
// ability to compensate correctly by a certain amount.
FLOAT iCounterForceAccuracy = gGameCTHConstants.RECOIL_COUNTER_ACCURACY_DEX * EffectiveDexterity(pShooter);
iCounterForceAccuracy += gGameCTHConstants.RECOIL_COUNTER_ACCURACY_WIS * EffectiveWisdom(pShooter);
iCounterForceAccuracy += gGameCTHConstants.RECOIL_COUNTER_ACCURACY_AGI * EffectiveAgility(pShooter);
iCounterForceAccuracy += gGameCTHConstants.RECOIL_COUNTER_ACCURACY_EXP_LEVEL * EffectiveExpLevel(pShooter) * 10;
UINT32 uiCounterForceAccuracy = CalcCounterForceAccuracy(pShooter, pWeapon, uiRange, fTracer);
iDivisor = gGameCTHConstants.RECOIL_COUNTER_ACCURACY_DEX +
gGameCTHConstants.RECOIL_COUNTER_ACCURACY_WIS +
gGameCTHConstants.RECOIL_COUNTER_ACCURACY_AGI +
gGameCTHConstants.RECOIL_COUNTER_ACCURACY_EXP_LEVEL;
iCounterForceAccuracy /= iDivisor;
// STEP 3: Now let's determine how much counter-force would be ideal. If the gun isn't kicking too powerfully,
// and/or not too much recoil has built up from previous bullets, the shooter should potentially be able to compensate
// for ALL of it. However, in some cases, the shooter simply can't exert enough force to counter ALL recoil.
// ChrisL: Here we need a little change. As the above comment states, we do want to try and compensate for ALL recoil which makes sense.
// And it also makes sense that we try and compensate for any recoil we previously under-compensated for. But what we're actually doing
// is trying to compensate for all recoil AND bring our offsets back to 0x0 which makes autofire incredibly accurate regardless of what
// our shots initial accuracy was. So instead of trying to compensate down to 0x0, we'll instead try to compensate back to whatever our
// initial offsets were. However, since autofire should be a bit more accurate then single shot (since we're able to "walk" rounds to the
// target) we need to adjust the initial offsets a little, the more rounds we fire.
INT8 iImprovement = __min(100,(INT8)((pShooter->bDoBurst-1) / uiCounterForceFrequency) * (fTracer?gGameCTHConstants.RECOIL_COUNTER_INCREMENT_TRACER:gGameCTHConstants.RECOIL_COUNTER_INCREMENT));
// Add the effects from the weapon and its attachments. A foregrip or bipod are very useful for this.
// Attachment bonuses are applied as a percentage to the accuracy of the shooter.
iModifier = GetCounterForceAccuracyModifier( pWeapon, gAnimControl[ pShooter->usAnimState ].ubHeight );
UINT32 uiCounterForceAccuracy = (UINT32)(iCounterForceAccuracy + ((iCounterForceAccuracy * iModifier) / 100));
// Calculate Ideal X and limit to max force
//FLOAT iIdealCounterForceX = -(bGunRecoilX + (*dMuzzleOffsetX / iDistanceRatio / iRound) );
FLOAT iInitialOffsetX = pShooter->dInitialMuzzleOffsetX;
FLOAT iBestImprovementX = iInitialOffsetX * (((FLOAT)(100-uiCounterForceAccuracy)/gGameCTHConstants.RECOIL_COUNTER_ACCURACY_COMPENSATION)/100.0f);
FLOAT iTargetOffsetX = iInitialOffsetX - (iBestImprovementX * iImprovement / 100);
FLOAT iIdealCounterForceX = -(bGunRecoilX + ((*dMuzzleOffsetX - iTargetOffsetX) / iDistanceRatio / iRound));
// Now add the effect of the AutoWeapons skill. It "bridges" a portion of the gap between shooter's actual accuracy
// and 100% accuracy. For instance, if the divisor is set to 2 in the INI, the first skill level will close 50% of
// the gap, the second skill level closes another 25%.
if(gGameOptions.fNewTraitSystem)
traitLoop = NUM_SKILL_TRAITS( pShooter, AUTO_WEAPONS_NT );
else
traitLoop = NUM_SKILL_TRAITS( pShooter, AUTO_WEAPS_OT );
for (INT32 x = 0; x < traitLoop; x++)
{
INT8 bDifference = 100-uiCounterForceAccuracy;
bDifference = (INT8)(bDifference / gGameCTHConstants.RECOIL_COUNTER_ACCURACY_AUTO_WEAPONS_DIVISOR);
uiCounterForceAccuracy += bDifference;
}
////////////////////////////////////////////////////////////////////
// Sight-range effects
// If we can't see the target, but buddies can see it, CF-Accuracy drops by 50%
// If we can't see the target and neither can buddies, CF-Accuracy drops by 75%
UINT8 ubTargetID = WhoIsThere2( pShooter->sTargetGridNo, pShooter->bTargetLevel ); // Target ubID
INT32 iSightRange = -1;
if (ubTargetID != NOBODY)
{
iSightRange = SoldierToSoldierLineOfSightTest( pShooter, MercPtrs[ubTargetID], TRUE, NO_DISTANCE_LIMIT, pShooter->bAimShotLocation, false );
if (pShooter->aiData.bOppList[ubTargetID] != SEEN_CURRENTLY && gbPublicOpplist[pShooter->bTeam][ubTargetID] == SEEN_CURRENTLY)
{
iSightRange /= 2;
}
else if(pShooter->aiData.bOppList[ubTargetID] != SEEN_CURRENTLY && gbPublicOpplist[pShooter->bTeam][ubTargetID] != SEEN_CURRENTLY)
{
iSightRange /= 4;
}
}
if (iSightRange == -1) // didn't do a bodypart-based test
{
iSightRange = SoldierTo3DLocationLineOfSightTest( pShooter, pShooter->sTargetGridNo, pShooter->bTargetLevel, pShooter->bTargetCubeLevel, TRUE, NO_DISTANCE_LIMIT, false );
iSightRange /= 4;
}
uiCounterForceAccuracy = (UINT32)(uiCounterForceAccuracy * (FLOAT)((FLOAT)iSightRange / uiRange));
// Was last bullet a tracer?
if (fTracer)
{
// Augment by a certain amount, mitigated by range
uiCounterForceAccuracy += (UINT32)gGameCTHConstants.RECOIL_COUNTER_ACCURACY_TRACER_BONUS;
}
// Limit to 0 to 100.
uiCounterForceAccuracy = __max(__min(uiCounterForceAccuracy, 100), 0);
// Invert. 0=good, 100-bad.
uiCounterForceAccuracy = 100-uiCounterForceAccuracy;
// Calculate Ideal Y and limit to max force
//FLOAT iIdealCounterForceY = -(bGunRecoilY + (*dMuzzleOffsetY / iDistanceRatio / iRound) );
FLOAT iInitialOffsetY = pShooter->dInitialMuzzleOffsetY;
FLOAT iBestImprovementY = iInitialOffsetY * (((FLOAT)(100-uiCounterForceAccuracy)/gGameCTHConstants.RECOIL_COUNTER_ACCURACY_COMPENSATION)/100.0f);
FLOAT iTargetOffsetY = iInitialOffsetY - (iBestImprovementY * iImprovement / 100);
FLOAT iIdealCounterForceY = -(bGunRecoilY + ((*dMuzzleOffsetY - iTargetOffsetY) / iDistanceRatio / iRound));
// STEP 4: Find out whether the shooter is over-compensating or under-compensating, and by how much. Higher
// force-accuracy will decrease this deviation, generating a resulting counter-force that's closer to the ideal.
@@ -7795,67 +8002,71 @@ void CalcRecoilOffset( SOLDIERTYPE *pShooter, FLOAT *dMuzzleOffsetX, FLOAT *dMuz
// Randomize a deviation value.
INT32 uiCounterForceDeviationX = PreRandom(uiCounterForceAccuracy);
// Use as a percentage to the ideal counter force.
FLOAT iCounterForceDeviationX = (FLOAT)((uiCounterForceDeviationX * iIdealCounterForceX) / 100.0);
FLOAT iCounterForceDeviationX = (FLOAT)abs((uiCounterForceDeviationX * iIdealCounterForceX) / 100.0);
// Make sure there's always a little randomness - no one is THAT accurate unless the gun is producing really minimal
// amounts of recoil (or none).
iCounterForceDeviationX = __max(iCounterForceDeviationX, iGunTotalRecoil * gGameCTHConstants.RECOIL_COUNTER_ACCURACY_MIN_ERROR);
iCounterForceDeviationX = __max(iCounterForceDeviationX, abs(iIdealCounterForceX * gGameCTHConstants.RECOIL_COUNTER_ACCURACY_MIN_ERROR));
// Determine whether we're under- or over-compensating.
INT8 bUpDownX = PreRandom(2)?(1):(-1);
// A merc's current offset and skill (in the form of CFA) should give the merc some level of control when determining whether we
// over or under compensate. If a merc can "see" that he's been undercompensating too much, he's much more likely to overcompensate
// and vice versa
INT8 bUpDownX;
if(*dMuzzleOffsetX != 0 && PreRandom(100) <= (100-uiCounterForceAccuracy))
{ //merc realizes he's over/under compensated too much
// Current offset is high so merc is more likely to overcompensate
if(*dMuzzleOffsetX > 0)
bUpDownX = PreRandom(3)?(-1):(1);
// Current offset is low so merc is more likely to undercompensate
else if(*dMuzzleOffsetX < 0)
bUpDownX = PreRandom(3)?(1):(-1);
}
else
{ //merc doesn't realize he's over/under compensating
bUpDownX = PreRandom(2)?(1):(-1);
}
iCounterForceDeviationX *= bUpDownX;
// So now we have the ideal amount, and the randomal deviation from that amount. Let's add them up.
iAppliedCounterForceX = iIdealCounterForceX + iCounterForceDeviationX;
// Limit to the maximum applied force. Our shooter cannot exert more force than his personal maximum.
iAppliedCounterForceX = __min(iAppliedCounterForceX, iCounterForceMax);
//////////////////////////
// CALCULATE FOR Y
// Randomize a deviation value.
INT32 uiCounterForceDeviationY = PreRandom(uiCounterForceAccuracy);
// Use as a percentage to the ideal counter force.
FLOAT iCounterForceDeviationY = (FLOAT)((uiCounterForceDeviationY * iIdealCounterForceY) / 100.0);
FLOAT iCounterForceDeviationY = (FLOAT)abs((uiCounterForceDeviationY * iIdealCounterForceY) / 100.0);
// Make sure there's always a little randomness - no one is THAT accurate unless the gun is producing really minimal
// amounts of recoil (or none).
iCounterForceDeviationY = __max(iCounterForceDeviationY, __min(1, iGunTotalRecoil));
iCounterForceDeviationY = __max(iCounterForceDeviationY, abs(iIdealCounterForceY * gGameCTHConstants.RECOIL_COUNTER_ACCURACY_MIN_ERROR));
// Determine whether we're under- or over-compensating.
INT8 bUpDownY = PreRandom(2)?(1):(-1);
// A merc's current offset and skill (in the form of CFA) should give the merc some level of control when determining whether we
// over or under compensate. If a merc can "see" that he's been undercompensating too much, he's much more likely to overcompensate
// and vice versa
INT8 bUpDownY;
if(*dMuzzleOffsetY != 0 && PreRandom(100) <= (100-uiCounterForceAccuracy))
{ //merc realizes he's over/under compensated too much
// Current offset is high so merc is more likely to overcompensate
if(*dMuzzleOffsetY > 0)
bUpDownY = PreRandom(3)?(-1):(1);
// Current offset is low so merc is more likely to undercompensate
else if(*dMuzzleOffsetY < 0)
bUpDownY = PreRandom(3)?(1):(-1);
}
else
{ //merc doesn't realize he's over/under compensating
bUpDownY = PreRandom(2)?(1):(-1);
}
iCounterForceDeviationY *= bUpDownY;
// So now we have the ideal amount, and the randomal deviation from that amount. Let's add them up.
iAppliedCounterForceY = iIdealCounterForceY + iCounterForceDeviationY;
// Limit to the maximum applied force. Our shooter cannot exert more force than his personal maximum.
iAppliedCounterForceY = __min(iAppliedCounterForceY, iCounterForceMax);
/////////////////////////////
// Transfer unused force from one axis to the other.
if (iAppliedCounterForceX < iCounterForceMax &&
iAppliedCounterForceX > iIdealCounterForceX &&
iAppliedCounterForceY < iIdealCounterForceY )
{
// X is using more counter force than it actually needs. Y is not using as much force as it needs.
// Find out how much remainder force we can transfer to Y, and do so.
FLOAT iRemainder = iAppliedCounterForceX - iIdealCounterForceX;
iAppliedCounterForceX -= iRemainder;
iAppliedCounterForceY = __min(iCounterForceMax, __min(iIdealCounterForceY, iAppliedCounterForceY + iRemainder));
}
else if (iAppliedCounterForceY < iCounterForceMax &&
iAppliedCounterForceY > iIdealCounterForceY &&
iAppliedCounterForceX < iIdealCounterForceX )
{
// Y is using more counter force than it actually needs. X is not using as much force as it needs.
// Find out how much remainder force we can transfer to X, and do so.
FLOAT iRemainder = iAppliedCounterForceY - iIdealCounterForceY;
iIdealCounterForceY -= iRemainder;
iAppliedCounterForceX = __min(iCounterForceMax, __min(iIdealCounterForceX, iAppliedCounterForceX + iRemainder));
}
/////////////////////////////
// Use pythagorean to scale this to the max force allowed.
// We want to use pythagorean here so that we calculate the total "vector length" of recoil and compare that to CFM
FLOAT iTotalAppliedCounterForce = sqrt((iAppliedCounterForceX*iAppliedCounterForceX) + (iAppliedCounterForceY*iAppliedCounterForceY));
if (iTotalAppliedCounterForce > iCounterForceMax)
{
@@ -7866,16 +8077,7 @@ void CalcRecoilOffset( SOLDIERTYPE *pShooter, FLOAT *dMuzzleOffsetX, FLOAT *dMuz
iAppliedCounterForceY /= iRatio;
}
}
// Make sure it's the correct direction compared to where the gun is pulling.
if (dNextOffsetX > 0)
{
iAppliedCounterForceX *= (-1);
}
if (dNextOffsetY > 0)
{
iAppliedCounterForceY *= (-1);
}
// This works because sqrt((iACFX^2)+(iACFY^2)) should result in being less then or equal to CFM
// Record how much counter force was applied this time. It will be used for the next few shots until the
// shooter can recalculate.