Added safety checks

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8626 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Flugente
2018-09-30 19:40:55 +00:00
parent 8c6f69fbb8
commit 50d7b679c1
5 changed files with 498 additions and 104 deletions
+36 -30
View File
@@ -146,13 +146,15 @@ Incident_Stats::AddStat( SOLDIERTYPE* pSoldier, UINT8 aType )
{
UINT8 group = CAMPAIGNHISTORY_SD_MERC;
switch ( pSoldier->bTeam )
if ( pSoldier )
{
case OUR_TEAM:
group = CAMPAIGNHISTORY_SD_MERC;
break;
switch ( pSoldier->bTeam )
{
case OUR_TEAM:
group = CAMPAIGNHISTORY_SD_MERC;
break;
case ENEMY_TEAM:
case ENEMY_TEAM:
{
if ( ARMED_VEHICLE( pSoldier ) )
group = CAMPAIGNHISTORY_SD_ENEMY_TANK;
@@ -165,16 +167,16 @@ Incident_Stats::AddStat( SOLDIERTYPE* pSoldier, UINT8 aType )
}
break;
case CREATURE_TEAM:
if ( pSoldier->IsZombie() )
group = CAMPAIGNHISTORY_SD_ZOMBIE;
else if ( pSoldier->ubSoldierClass == SOLDIER_CLASS_BANDIT )
group = CAMPAIGNHISTORY_SD_ENEMY_ADMIN;
else
group = CAMPAIGNHISTORY_SD_CREATURE;
break;
case CREATURE_TEAM:
if ( pSoldier->IsZombie() )
group = CAMPAIGNHISTORY_SD_ZOMBIE;
else if ( pSoldier->ubSoldierClass == SOLDIER_CLASS_BANDIT )
group = CAMPAIGNHISTORY_SD_ENEMY_ADMIN;
else
group = CAMPAIGNHISTORY_SD_CREATURE;
break;
case MILITIA_TEAM:
case MILITIA_TEAM:
{
if ( pSoldier->ubSoldierClass == SOLDIER_CLASS_GREEN_MILITIA )
group = CAMPAIGNHISTORY_SD_MILITIA_GREEN;
@@ -185,10 +187,19 @@ Incident_Stats::AddStat( SOLDIERTYPE* pSoldier, UINT8 aType )
}
break;
case CIV_TEAM:
default:
group = CAMPAIGNHISTORY_SD_CIV;
break;
case CIV_TEAM:
default:
group = CAMPAIGNHISTORY_SD_CIV;
break;
}
if ( ARMED_VEHICLE( pSoldier ) )
{
if ( pSoldier->bSide == 0 )
usIncidentFlags |= INCIDENT_TANKS_PLAYERSIDE;
else
usIncidentFlags |= INCIDENT_TANKS_ENEMY;
}
}
switch ( aType )
@@ -197,10 +208,13 @@ Incident_Stats::AddStat( SOLDIERTYPE* pSoldier, UINT8 aType )
{
usKills[group]++;
if ( pSoldier->ubProfile == KINGPIN )
usOneTimeEventFlags |= INCIDENT_ONETIMEEVENT_DEATH_KINGPIN;
else if ( pSoldier->ubProfile == DARREL )
usOneTimeEventFlags |= INCIDENT_ONETIMEEVENT_MASSACRE_HICKS;
if ( pSoldier )
{
if ( pSoldier->ubProfile == KINGPIN )
usOneTimeEventFlags |= INCIDENT_ONETIMEEVENT_DEATH_KINGPIN;
else if ( pSoldier->ubProfile == DARREL )
usOneTimeEventFlags |= INCIDENT_ONETIMEEVENT_MASSACRE_HICKS;
}
}
break;
@@ -225,14 +239,6 @@ Incident_Stats::AddStat( SOLDIERTYPE* pSoldier, UINT8 aType )
usPromotions[group]++;
break;
}
if ( ARMED_VEHICLE( pSoldier ) )
{
if ( pSoldier->bSide == 0 )
usIncidentFlags |= INCIDENT_TANKS_PLAYERSIDE;
else
usIncidentFlags |= INCIDENT_TANKS_ENEMY;
}
}
UINT8 tmpnr = 0;
+21
View File
@@ -8957,6 +8957,27 @@ void DoInteractiveActionDefaultResult( INT32 sGridNo, UINT8 ubID, BOOLEAN aSucce
aSuccess = TRUE;
}
}
else
{
aSuccess = FALSE;
// not enough money for a soda, hu? Too bad for you Flugente played the old Crusader Games, which teach you how to handle that particular issue!
ScreenMsg( FONT_MCOLOR_RED, MSG_INTERFACE, L"The World Economic Consortium does not condone moochers!" );
INT16 direction = GetDirectionToGridNoFromGridNo( sGridNo, pSoldier->sGridNo );
INT32 startgridno = NewGridNo( sGridNo, DirectionInc(direction) );
INT32 targetgridno = NewGridNo( startgridno, DirectionInc( direction ) );
FLOAT x, y, z;
GetTargetWorldPositions( NULL, targetgridno, &x, &y, &z );
// fire at torso height
z += 100.0f;
// fire a AP rocket with damage 40. The ammotype currently gets rerolled to Ball, but it's not like anyone will complain
FireBulletGivenTarget_NoObjectNoSoldier( 55, 2, 40, startgridno, z, x, y, z, 0 );
}
}
}
}
+421 -34
View File
@@ -5219,7 +5219,7 @@ INT8 FireBulletGivenTarget( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOA
}
// Flugente: anti-materiel ammo
if ( AmmoTypes[ pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType ].ammoflag & AMMO_ANTIMATERIEL )
if ( AmmoTypes[(*pObjAttHand)[0]->data.gun.ubGunAmmoType ].ammoflag & AMMO_ANTIMATERIEL )
usBulletFlags |= BULLET_FLAG_ANTIMATERIEL;
// Flugente: if object is infected, missile is too (intended for throwing knifes)
@@ -5280,7 +5280,7 @@ INT8 FireBulletGivenTarget( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOA
//zilpin: End of new code block.
// GET BULLET
for (ubLoop = 0; ubLoop < ubShots; ubLoop++)
for (ubLoop = 0; ubLoop < ubShots; ++ubLoop)
{
iBullet = CreateBullet( pFirer->ubID, fFake, usBulletFlags,usHandItem );
if (iBullet == -1)
@@ -5782,9 +5782,6 @@ INT8 FireBulletGivenTargetTrapOnly( SOLDIERTYPE* pThrower, OBJECTTYPE* pObj, INT
if ( !pObj )
return 0;
if ( !pThrower )
return 0;
FLOAT dStartX = 0;
FLOAT dStartY = 0;
@@ -5815,8 +5812,11 @@ INT8 FireBulletGivenTargetTrapOnly( SOLDIERTYPE* pThrower, OBJECTTYPE* pObj, INT
UINT16 usBulletFlags = 0;
int n=0;
UINT16 usItem = pObj->usItem;
UINT8 ammotype = ( *pObj )[0]->data.gun.ubGunAmmoType;
BOOLEAN fBuckshot = FALSE;
if ( AmmoTypes[(*pObj)[0]->data.gun.ubGunAmmoType].numberOfBullets > 1 )
if ( AmmoTypes[ammotype].numberOfBullets > 1 )
fBuckshot = TRUE;
dStartX = (FLOAT) CenterX( gridno );
@@ -5851,34 +5851,34 @@ INT8 FireBulletGivenTargetTrapOnly( SOLDIERTYPE* pThrower, OBJECTTYPE* pObj, INT
fTracer = FALSE;
// Check if we have spit as a weapon!
if ( Weapon[ pObj->usItem ].ubWeaponClass == MONSTERCLASS )
if ( Weapon[usItem].ubWeaponClass == MONSTERCLASS )
{
usBulletFlags |= BULLET_FLAG_CREATURE_SPIT;
}
else if ( Item[ pObj->usItem ].usItemClass == IC_THROWING_KNIFE )
else if ( Item[usItem].usItemClass == IC_THROWING_KNIFE )
{
usBulletFlags |= BULLET_FLAG_KNIFE;
}
else if ( Item[pObj->usItem].rocketlauncher )
else if ( Item[usItem].rocketlauncher )
{
usBulletFlags |= BULLET_FLAG_MISSILE;
}
else if ( Item[pObj->usItem].cannon )
else if ( Item[usItem].cannon )
{
usBulletFlags |= BULLET_FLAG_TANK_CANNON;
}
else if ( Item[pObj->usItem].rocketrifle )
else if ( Item[usItem].rocketrifle )
{
usBulletFlags |= BULLET_FLAG_SMALL_MISSILE;
}
else if ( pObj->usItem == FLAMETHROWER )
else if ( usItem == FLAMETHROWER )
{
usBulletFlags |= BULLET_FLAG_FLAME;
ubSpreadIndex = 2;
}
// Flugente: anti-materiel ammo
if ( AmmoTypes[(*pObj)[0]->data.gun.ubGunAmmoType].ammoflag & AMMO_ANTIMATERIEL )
if ( AmmoTypes[ammotype].ammoflag & AMMO_ANTIMATERIEL )
usBulletFlags |= BULLET_FLAG_ANTIMATERIEL;
// no option to use fire bursts or autofire yet
@@ -5900,36 +5900,34 @@ INT8 FireBulletGivenTargetTrapOnly( SOLDIERTYPE* pThrower, OBJECTTYPE* pObj, INT
fTracer = FALSE;
}
}*/
else if ( AmmoTypes[ (*pObj)[0]->data.gun.ubGunAmmoType ].tracerEffect && gGameSettings.fOptions[ TOPTION_TRACERS_FOR_SINGLE_FIRE ] )
else if ( AmmoTypes[ammotype].tracerEffect && gGameSettings.fOptions[ TOPTION_TRACERS_FOR_SINGLE_FIRE ] )
{
//usBulletFlags |= BULLET_FLAG_TRACER;
fTracer = TRUE;
}
ubImpact =(UINT8) GetDamage( pObj );
ubImpact = (UINT8) GetDamage( pObj );
// we have to adjust the damage...
//ubImpact = min(255, 4 * ubImpact );
//zilpin: Begin new code block for spread patterns, number of projectiles, impact adjustment, etc.
{
ObjectData *weapon = &((*pObj)[0]->data);
ubShots = AmmoTypes[ weapon->gun.ubGunAmmoType].numberOfBullets;
ubShots = AmmoTypes[ammotype].numberOfBullets;
ubSpreadIndex = GetSpreadPattern( pObj );
if( ubShots>1 )
{
fBuckshot = true;
usBulletFlags |= BULLET_FLAG_BUCKSHOT;
ubImpact = (UINT8) (ubImpact * AmmoTypes[weapon->gun.ubGunAmmoType].multipleBulletDamageMultiplier / max(1,AmmoTypes[weapon->gun.ubGunAmmoType].multipleBulletDamageDivisor) );
ubImpact = (UINT8) (ubImpact * AmmoTypes[ammotype].multipleBulletDamageMultiplier / max(1,AmmoTypes[ammotype].multipleBulletDamageDivisor) );
}
weapon=NULL;
}
//zilpin: End of new code block.
// GET BULLET
for (ubLoop = 0; ubLoop < ubShots; ubLoop++)
for (ubLoop = 0; ubLoop < ubShots; ++ubLoop)
{
iBullet = CreateBullet( NOBODY, FALSE, usBulletFlags, pObj->usItem );
iBullet = CreateBullet( NOBODY, FALSE, usBulletFlags, usItem );
if (iBullet == -1)
{
//DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Failed to create bullet") );
@@ -6226,30 +6224,32 @@ INT8 FireBulletGivenTargetTrapOnly( SOLDIERTYPE* pThrower, OBJECTTYPE* pObj, INT
}
}*/
if(is_client)send_bullet( pBullet, pObj->usItem );
FireBullet( pThrower->ubID, pBullet, FALSE );
if(is_client)
send_bullet( pBullet, usItem );
FireBullet( pThrower ? pThrower->ubID : NOBODY, pBullet, FALSE );
}
///////////////////////// SOUND ////////////////////////////
//PLAY SOUND
// ( For throwing knife.. it's earlier in the animation
if ( Weapon[ pObj->usItem ].sSound != 0 && Item[ pObj->usItem ].usItemClass != IC_THROWING_KNIFE )
if ( Weapon[usItem].sSound != 0 && Item[usItem].usItemClass != IC_THROWING_KNIFE )
{
// Switch on silencer...
UINT16 noisefactor = GetPercentNoiseVolume( pObj );
if( noisefactor < gGameExternalOptions.gubMaxPercentNoiseSilencedSound || Weapon[ pObj->usItem ].ubAttackVolume <= 10 )
if( noisefactor < gGameExternalOptions.gubMaxPercentNoiseSilencedSound || Weapon[usItem].ubAttackVolume <= 10 )
{
INT32 uiSound;
uiSound = Weapon [ pObj->usItem ].silencedSound;
INT32 uiSound = Weapon [usItem].silencedSound;
PlayJA2Sample( uiSound, RATE_11025, SoundVolume( HIGHVOLUME, gridno ), 1, SoundDir( gridno ) );
}
else
{
INT8 volume = HIGHVOLUME;
if ( noisefactor < 100 ) volume = (volume * noisefactor) / 100;
PlayJA2Sample( Weapon[ pObj->usItem ].sSound, RATE_11025, SoundVolume( volume, gridno ), 1, SoundDir( gridno ) );
if ( noisefactor < 100 )
volume = (volume * noisefactor) / 100;
PlayJA2Sample( Weapon[usItem].sSound, RATE_11025, SoundVolume( volume, gridno ), 1, SoundDir( gridno ) );
}
}
///////////////////////// SOUND ////////////////////////////
@@ -6260,7 +6260,7 @@ INT8 FireBulletGivenTargetTrapOnly( SOLDIERTYPE* pThrower, OBJECTTYPE* pObj, INT
//gTacticalStatus.ubAttackBusyCount++;
///////////////////////// SOUND ////////////////////////////
UINT8 ubVolume = Weapon[ pObj->usItem ].ubAttackVolume;
UINT8 ubVolume = Weapon[usItem].ubAttackVolume;
// Snap: get cumulative noise reduction from the weapon and its attachments
UINT16 noisefactor = GetPercentNoiseVolume( pObj );
@@ -6273,7 +6273,7 @@ INT8 FireBulletGivenTargetTrapOnly( SOLDIERTYPE* pThrower, OBJECTTYPE* pObj, INT
ubVolume = __max( 1, ( ubVolume * GetPercentNoiseVolume( pObj ) ) / 100 );
}
MakeNoise( NOBODY, gridno, 0, pThrower->bOverTerrainType, ubVolume, NOISE_GUNFIRE );
MakeNoise( NOBODY, gridno, 0, pThrower ? pThrower->bOverTerrainType : FLAT_GROUND, ubVolume, NOISE_GUNFIRE );
///////////////////////// SOUND ////////////////////////////
///////////////////////// OVERHEATING AND STATUS REDUCTION ////////////////////////////
@@ -6310,7 +6310,7 @@ INT8 FireBulletGivenTargetTrapOnly( SOLDIERTYPE* pThrower, OBJECTTYPE* pObj, INT
{
(*pObj)[0]->data.objectStatus--;
if ( Random(100) < Item[pObj->usItem].usDamageChance )
if ( Random(100) < Item[usItem].usDamageChance )
{
(*pObj)[0]->data.sRepairThreshold--;
}
@@ -6360,13 +6360,400 @@ INT8 FireBulletGivenTargetTrapOnly( SOLDIERTYPE* pThrower, OBJECTTYPE* pObj, INT
///////////////////////// JAMMING ////////////////////////////
//<SB> manual recharge
if (Weapon[Item[pObj->usItem].ubClassIndex].APsToReloadManually > 0)
if (Weapon[Item[usItem].ubClassIndex].APsToReloadManually > 0)
(*pObj)[0]->data.gun.ubGunState &= ~GS_CARTRIDGE_IN_CHAMBER;
//<SB>
return( TRUE );
}
// Flugente: fire a shot without a soldier or an object
INT8 FireBulletGivenTarget_NoObjectNoSoldier( UINT16 usItem, UINT8 ammotype, UINT8 ubImpact, INT32 gridno, FLOAT dStartZ, FLOAT dEndX, FLOAT dEndY, FLOAT dEndZ, INT16 sHitBy )
{
FLOAT dStartX = 0;
FLOAT dStartY = 0;
FLOAT d2DDistance = 0;
FLOAT dDeltaX = 0;
FLOAT dDeltaY = 0;
FLOAT dDeltaZ = 0;
DOUBLE ddOrigHorizAngle = 0;
DOUBLE ddOrigVerticAngle = 0;
DOUBLE ddHorizAngle = 0;
DOUBLE ddVerticAngle = 0;
DOUBLE ddAdjustedHorizAngle = 0;
DOUBLE ddAdjustedVerticAngle = 0;
DOUBLE ddDummyHorizAngle = 0;
DOUBLE ddDummyVerticAngle = 0;
BULLET* pBullet = NULL;
INT32 iBullet = 0;
INT32 iDistance = 0;
UINT8 ubLoop = 0;
UINT8 ubShots = 0;
INT8 bCTGT = 0;
UINT8 ubSpreadIndex = 0;
UINT16 usBulletFlags = 0;
int n = 0;
BOOLEAN fBuckshot = FALSE;
if ( AmmoTypes[ammotype].numberOfBullets > 1 )
fBuckshot = TRUE;
dStartX = (FLOAT)CenterX( gridno );
dStartY = (FLOAT)CenterY( gridno );
dDeltaX = dEndX - dStartX;
dDeltaY = dEndY - dStartY;
dDeltaZ = dEndZ - dStartZ;
d2DDistance = Distance2D( dDeltaX, dDeltaY );
iDistance = (INT32)d2DDistance;
if ( d2DDistance != iDistance )
{
iDistance += 1;
d2DDistance = (FLOAT)( iDistance );
}
ddOrigHorizAngle = atan2( dDeltaY, dDeltaX );
ddOrigVerticAngle = atan2( dDeltaZ, ( d2DDistance * HEIGHTUNITS_PER_CELL ) );//dnl ch60 010913
ddAdjustedHorizAngle = ddOrigHorizAngle;
ddAdjustedVerticAngle = ddOrigVerticAngle;
ubShots = 1;
fTracer = FALSE;
// Check if we have spit as a weapon!
if ( Weapon[usItem].ubWeaponClass == MONSTERCLASS )
{
usBulletFlags |= BULLET_FLAG_CREATURE_SPIT;
}
else if ( Item[usItem].usItemClass == IC_THROWING_KNIFE )
{
usBulletFlags |= BULLET_FLAG_KNIFE;
}
else if ( Item[usItem].rocketlauncher )
{
usBulletFlags |= BULLET_FLAG_MISSILE;
}
else if ( Item[usItem].cannon )
{
usBulletFlags |= BULLET_FLAG_TANK_CANNON;
}
else if ( Item[usItem].rocketrifle )
{
usBulletFlags |= BULLET_FLAG_SMALL_MISSILE;
}
else if ( usItem == FLAMETHROWER )
{
usBulletFlags |= BULLET_FLAG_FLAME;
ubSpreadIndex = 2;
}
// Flugente: anti-materiel ammo
if ( AmmoTypes[ammotype].ammoflag & AMMO_ANTIMATERIEL )
usBulletFlags |= BULLET_FLAG_ANTIMATERIEL;
if ( AmmoTypes[ammotype].tracerEffect && gGameSettings.fOptions[TOPTION_TRACERS_FOR_SINGLE_FIRE] )
fTracer = TRUE;
//zilpin: Begin new code block for spread patterns, number of projectiles, impact adjustment, etc.
{
ubShots = AmmoTypes[ammotype].numberOfBullets;
ubSpreadIndex = GetSpreadPattern( NULL );
if ( ubShots>1 )
{
fBuckshot = true;
usBulletFlags |= BULLET_FLAG_BUCKSHOT;
ubImpact = (UINT8)( ubImpact * AmmoTypes[ammotype].multipleBulletDamageMultiplier / max( 1, AmmoTypes[ammotype].multipleBulletDamageDivisor ) );
}
}
//zilpin: End of new code block.
// GET BULLET
for ( ubLoop = 0; ubLoop < ubShots; ++ubLoop )
{
iBullet = CreateBullet( NOBODY, FALSE, usBulletFlags, usItem );
if ( iBullet == -1 )
{
//DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Failed to create bullet") );
return( FALSE );
}
pBullet = GetBulletPtr( iBullet );
// HEADROCK HAM 4: The HitBy value now holds the ratio between the Distance Aperture and the Final Aperture.
// Basically, this represents by how much our shooter has managed to make the shot more accurate than it would
// be without any extra aiming or optical equipment. HitBy can now only be positive, and equals 100 when the
// shot is perfectly on-target, or 0 when it is as inaccurate as the shooting system can possibly allow.
// In this sense, sApertureRatio is the closest "relative" that the NCTH system has to the old system. it tells
// us, on a scale of 0-100, how accurate we are compared to "Best" and "Worst" accuracy.
// Note also that references to this value have been changed accordingly. Most imporantly, functions that alter
// the path of the bullet, or determine whether collisions are more likely thanks to low HitBy, have been
// altered to _not use this value at all_.
// At the moment, HitBy is used only for determining extra damage (0-50% extra), and for determining
// how much extra experience is gained by the shooter if the target is actually hit by the bullet.
pBullet->sHitBy = sHitBy;
// HEADROCK HAM 4: TODO: Determine whether this value is required anymore. Why ever let bullets
// pass through the roof at all??
if ( dStartZ < WALL_HEIGHT_UNITS )
{
if ( dEndZ > WALL_HEIGHT_UNITS )
{
pBullet->fCheckForRoof = TRUE;
}
else
{
pBullet->fCheckForRoof = FALSE;
}
}
else // dStartZ >= WALL_HEIGHT_UNITS; presumably >
{
if ( dEndZ < WALL_HEIGHT_UNITS )
{
pBullet->fCheckForRoof = TRUE;
}
else
{
pBullet->fCheckForRoof = FALSE;
}
}
// Flugente: we have to set this, otherwise the gun that fired this shot wont be found in WeaponHit()
pBullet->fFragment = true;
//zilpin: pellet spread patterns externalized in XML
//Now the first shot will use the pattern, as well.
//Single shot weapons will still be stuck with straight-ahead only, though.
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 );
pBullet->qIncrY = FloatToFixed( dDeltaY / (FLOAT)iDistance );
pBullet->qIncrZ = FloatToFixed( dDeltaZ / (FLOAT)iDistance );
}
else if ( ubShots > 1 )
{
//Also prevent out-of-bounds overflows.
//Rotates through the array if more pellets than expected, and uses pattern index 0 if requested pattern does not exist.
if ( gpSpreadPattern == NULL || giSpreadPatternCount<1 ) {
//XML file missing, or empty, or error while loading. Use hard-coded defaults.
//This is from the original code.
if ( ubSpreadIndex > 2 || ubSpreadIndex<0 ) ubSpreadIndex = 0;
n = ubLoop % BUCKSHOT_SHOTS;
ddHorizAngle = ddAdjustedHorizAngle + ddShotgunSpread[ubSpreadIndex][n][0];
ddVerticAngle = ddAdjustedVerticAngle + ddShotgunSpread[ubSpreadIndex][n][1];
}
else
{
double d = 0, r = 0, xspread = 0, yspread = 0;
int n = 0;
//Use spread patterns loaded from XML.
if ( ubSpreadIndex >= giSpreadPatternCount || ubSpreadIndex < 0 )
ubSpreadIndex = 0;
xspread = gpSpreadPattern[ubSpreadIndex].xspread;
yspread = gpSpreadPattern[ubSpreadIndex].yspread;
//Only use randomized spread pattern if the random spread is defined AND each static angle already fired once.
if ( ubLoop >= gpSpreadPattern[ubSpreadIndex].iCount && ( xspread + yspread ) ) {
//Create random angle within range, positive and negative.
switch ( gpSpreadPattern[ubSpreadIndex].method )
{
case SPREADPATTERNMETHOD_RECT: //Rectangle Method. (Simple)
//Applying a new random number to each angle results in a rectangular spread pattern, rather than an oval one.
ddHorizAngle = (double)rand() * 2 * xspread / RAND_MAX - xspread;
ddVerticAngle = (double)rand() * 2 * yspread / RAND_MAX - yspread;
break;
case SPREADPATTERNMETHOD_DIAMOND: //Diamond Method. (Kinda Simple)
//Angles are generated within a diamond shaped region.
//This is more natural than the rectangular pattern, but still not optimal.
//The first random number is in a range of 0 to (x+y).
d = ( xspread + yspread ) * (double)rand() / (double)RAND_MAX;
//The second random number determines the percentage of that value to use on x. The rest is spent on y.
r = (double)rand() / (double)RAND_MAX;
ddHorizAngle = r*d;
ddVerticAngle = ( 1 - r )*d;
//Positive and negative are then randomly determined. Otherwise, everthing would always shoot to the high right (+,+).
n = rand() % 4;
if ( n & 1 ) ddHorizAngle *= -1;
if ( n & 2 ) ddVerticAngle *= -1;
break;
case SPREADPATTERNMETHOD_ELLIPSE: //Ellipse Method.
//Angles are generated within an ellipse.
//This is getting close to true spread behaviour, and may be the preferred general purpose method.
//However, due to the distribution of random numbers for r, the pattern generated tends to have
//more pellets land along the axis, making the pattern look a bit like the Swiss cross sometimes.
//(It only becomes noticable in simulations I do in an external program. In-game it looks fine.)
//
//First, get our random range of -pi to pi. This could be recalculated for every use, but doesn't need to be.
r = (double)rand() * 2 * PI / RAND_MAX - PI;
//Which axis is our major axis?
if ( xspread > yspread )
{
//The Ellipse.
// Any point on an Ellipse border line = (x,y)
// where x = m * cos(r) and y = n * sin(r)
// where m = major axis radius and n = minor axis radius and r = all values -pi <= r <= pi
//We use any random value between -pi and pi, but vary the radius to get points inside the ellipse as well.
ddHorizAngle = ( (double)rand()*xspread / RAND_MAX ) * cos( r );
ddVerticAngle = ( (double)rand()*yspread / RAND_MAX ) * sin( r );
}
else {
//Reverse sine and cosine if our major axis is y.
ddHorizAngle = ( (double)rand()*xspread / RAND_MAX ) * sin( r );
ddVerticAngle = ( (double)rand()*yspread / RAND_MAX ) * cos( r );
}
break;
case -1: //Optimal Method. (Most realistic)
//Not yet implemented.
//Using the random distribution above, normal distrubution causes more pellets to end up toward the middle.
//This is generally not noticable, and in fact feels more natural to some people (not zilpin),
//but is not _even_ distribution.
//In addition, since it is random, it is possible for all of the pellets to vear off center,
//resulting in rare unnatural freak shots, which would never occur in a real shotgun.
//This may add to gameplay, but it may bother some people (like zilpin).
//Should anyone devise a way to adjust the ellipse distribution to account for this, here is where to put it.
//This would probably be done by adjusting the random value of 'r' above.
//Contact zilpin for real life shotgun spread pattern data, if you happen to be one of those mathematicians
//who can extrapolate a succinct algorithm from raw data. But keep in mind that there's not much data to go on.
//
//Also note that in the real world, shot spread tends to take the shape of a sagging funnel, rather than a cone.
//That is to say, the path of each pellet follows a mild curve, not a straight line.
//I'm really not going to worry about that here. Ever.
break;
default:
//If an invalid method is set in the structure, no randomized pellets are shot.
//Note that there still may have been static angle pellets fired.
//This should never happen, since the XML loading function will set them to giSpreadPatternMethod_Default.
break;
}
}
else if ( gpSpreadPattern[ubSpreadIndex].iCount>0 )
{
//Use static angle, if they exist.
n = ubLoop % gpSpreadPattern[ubSpreadIndex].iCount;
ddHorizAngle = gpSpreadPattern[ubSpreadIndex].x[n];
ddVerticAngle = gpSpreadPattern[ubSpreadIndex].y[n];
}
else
{
//No angles defined, so just fire straight.
ddHorizAngle = ddVerticAngle = 0.0;
}
DOUBLE ddRawHorizAngle = ddHorizAngle;
DOUBLE ddRawVerticAngle = ddVerticAngle;
//Adjust based on the to-hit deviation calculated when the first pellet was fired.
ddHorizAngle += ddAdjustedHorizAngle;
ddVerticAngle += ddAdjustedVerticAngle;
if ( TRUE )
{
FILE *OutFile;
if ( ( OutFile = fopen( "SpreadPatternLog.txt", "a+t" ) ) != NULL )
{
//To easily cut-and-paste these values from the log into a C/C++ source file for later analysis
//Lots of reference debug info in a comment.
fprintf( OutFile, "{ % 9.8f , % 9.8f , % 9.8f , % 9.8f }, //DEBUG: merc %4d fired pellet %4d of %4d using method %4d %12s with SpreadPattern %4d %s\n",
ddRawHorizAngle, ddRawVerticAngle,
ddHorizAngle, ddVerticAngle,
NOBODY, ubLoop, ubShots,
gpSpreadPattern[ubSpreadIndex].method, gSpreadPatternMethodNames[gpSpreadPattern[ubSpreadIndex].method],
ubSpreadIndex, gpSpreadPattern[ubSpreadIndex].Name,
NULL
);
fclose( OutFile );
}
}
}
//Just calculate the increments the bullet will use, not any of the to-hit adjustments, because we already did.
CalculateFiringIncrementsSimple( ddHorizAngle, ddVerticAngle, pBullet );
}
pBullet->ddHorizAngle = ddHorizAngle;
// buckshot pellets after the first can hit friendlies even at close range
pBullet->fAimed = FALSE;
if ( pBullet->usFlags & BULLET_FLAG_KNIFE )
{
pBullet->ubItemStatus = 100;
}
// apply increments for first move
//zilpin: pellet spread patterns externalized in XML
//This is a bugfix for strange behavior when firing, such as the projectile hitting walls behind a merc.
//The bullet should start it's journey at the beginning, not down range.
//Commented out the qIncr adjustments.
pBullet->qCurrX = FloatToFixed( dStartX ); //+ pBullet->qIncrX;
pBullet->qCurrY = FloatToFixed( dStartY ); //+ pBullet->qIncrY;
pBullet->qCurrZ = FloatToFixed( dStartZ ); //+ pBullet->qIncrZ;
pBullet->iImpact = ubImpact;
pBullet->iRange = GetModifiedGunRange( usItem );
// HEADROCK HAM 5.1: Define original point.
pBullet->sOrigGridNo = ( (INT32)dStartX ) / CELL_X_SIZE + ( (INT32)dStartY ) / CELL_Y_SIZE * WORLD_COLS;
pBullet->sTargetGridNo = ( (INT32)dEndX ) / CELL_X_SIZE + ( (INT32)dEndY ) / CELL_Y_SIZE * WORLD_COLS;
pBullet->bStartCubesAboveLevelZ = (INT8)CONVERT_HEIGHTUNITS_TO_INDEX( (INT32)dStartZ );
pBullet->bEndCubesAboveLevelZ = (INT8)CONVERT_HEIGHTUNITS_TO_INDEX( (INT32)dEndZ );
if ( is_client )
send_bullet( pBullet, usItem );
FireBullet( NOBODY, pBullet, FALSE );
}
///////////////////////// SOUND ////////////////////////////
//PLAY SOUND
// ( For throwing knife.. it's earlier in the animation
if ( Weapon[usItem].sSound != 0 && Item[usItem].usItemClass != IC_THROWING_KNIFE )
{
// Switch on silencer...
UINT16 noisefactor = 100;// GetPercentNoiseVolume( pObj );
if ( noisefactor < gGameExternalOptions.gubMaxPercentNoiseSilencedSound || Weapon[usItem].ubAttackVolume <= 10 )
{
INT32 uiSound = Weapon[usItem].silencedSound;
PlayJA2Sample( uiSound, RATE_11025, SoundVolume( HIGHVOLUME, gridno ), 1, SoundDir( gridno ) );
}
else
{
INT8 volume = HIGHVOLUME;
if ( noisefactor < 100 )
volume = ( volume * noisefactor ) / 100;
PlayJA2Sample( Weapon[usItem].sSound, RATE_11025, SoundVolume( volume, gridno ), 1, SoundDir( gridno ) );
}
}
UINT8 ubVolume = Weapon[usItem].ubAttackVolume;
MakeNoise( NOBODY, gridno, 0, FLAT_GROUND, ubVolume, NOISE_GUNFIRE );
///////////////////////// SOUND ////////////////////////////
return( TRUE );
}
INT8 ChanceToGetThrough( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOAT dEndZ )
{
if ( Item[pFirer->usAttackingWeapon].usItemClass == IC_GUN || Item[ pFirer->usAttackingWeapon ].usItemClass == IC_THROWING_KNIFE || Item[pFirer->usAttackingWeapon].rocketlauncher )
+3
View File
@@ -73,6 +73,9 @@ INT8 FireFragmentGivenTarget( UINT8 ubOwner, FLOAT dStartX, FLOAT dStartY, FLOAT
// Flugente: fire a shot from a gun that has no user (used for traps with attached guns)
INT8 FireBulletGivenTargetTrapOnly( SOLDIERTYPE* pThrower, OBJECTTYPE* pObj, INT32 gridno, FLOAT dStartZ, FLOAT dEndX, FLOAT dEndY, FLOAT dEndZ, INT16 sHitBy);
// Flugente: fire a shot without a soldier or an object
INT8 FireBulletGivenTarget_NoObjectNoSoldier( UINT16 usItem, UINT8 ammotype, UINT8 ubImpact, INT32 gridno, FLOAT dStartZ, FLOAT dEndX, FLOAT dEndY, FLOAT dEndZ, INT16 sHitBy );
#define CALC_FROM_ALL_DIRS -1
#define CALC_FROM_WANTED_DIR -2
#define NO_DISTANCE_LIMIT -3
+17 -40
View File
@@ -917,42 +917,10 @@ BOOLEAN WriteWeaponStats()
UINT16 GunRange( OBJECTTYPE * pObj, SOLDIERTYPE * pSoldier ) // SANDRO - added argument
{
//INT8 bAttachPos;
UINT16 rng;
if ( Item[ pObj->usItem ].usItemClass & IC_WEAPON )
{
//bAttachPos = FindAttachment( pObj, GUN_BARREL_EXTENDER );
//if ( bAttachPos == ITEM_NOT_FOUND )
//{
// rng = ( Weapon[ pObj->usItem ].usRange );
// DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("GunRange: base rng=%d",rng));
//}
//else
//{
// rng = ( Weapon[ pObj->usItem ].usRange + (GUN_BARREL_RANGE_BONUS * WEAPON_STATUS_MOD(pObj->bAttachStatus[ bAttachPos ]) / 100 ) );
// DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("GunRange: Found extender rng=%d",rng));
//}
//if ( Weapon [ pObj->usItem ].ubWeaponType == GUN_SHOTGUN && (*pObj)[0]->data.gun.ubGunAmmoType != AMMO_BUCKSHOT )
//{
// rng += SOLID_SLUG_RANGE_BONUS;
// DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("GunRange: Found solid slug rng=%d",rng));
//}
//else
//{
// bAttachPos = FindAttachment( pObj, DUCKBILL );
// if ( bAttachPos != ITEM_NOT_FOUND )
// {
// rng += DUCKBILL_RANGE_BONUS * (WEAPON_STATUS_MOD(pObj->bAttachStatus[ bAttachPos ]) / 100 );
// DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("GunRange: Found duckbill rng=%d",rng));
// }
//}
UINT16 usRange = GetModifiedGunRange(pObj->usItem);
// Snap: attachment status is factored into the range bonus calculation
@@ -1634,12 +1602,21 @@ void GetTargetWorldPositions( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo, FLOAT
FLOAT dTargetX;
FLOAT dTargetY;
FLOAT dTargetZ;
SOLDIERTYPE *pTargetSoldier;
SOLDIERTYPE* pTargetSoldier = NULL;
INT8 bStructHeight;
INT16 sXMapPos, sYMapPos;
UINT32 uiRoll;
INT8 bTargetCubeLevel = 0;
INT8 bTargetLevel = 0;
if ( pSoldier )
{
pTargetSoldier = SimpleFindSoldier( sTargetGridNo, pSoldier->bTargetLevel );
bTargetCubeLevel = pSoldier->bTargetCubeLevel;
bTargetLevel = pSoldier->bTargetLevel;
}
pTargetSoldier = SimpleFindSoldier( sTargetGridNo, pSoldier->bTargetLevel );
if ( pTargetSoldier )
{
// SAVE OPP ID
@@ -1716,21 +1693,20 @@ void GetTargetWorldPositions( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo, FLOAT
}
else
{
// GET TARGET XY VALUES
ConvertGridNoToCenterCellXY( sTargetGridNo, &sXMapPos, &sYMapPos );
// fire at centre of tile
dTargetX = (FLOAT) sXMapPos;
dTargetY = (FLOAT) sYMapPos;
if (pSoldier->bTargetCubeLevel)
if ( bTargetCubeLevel )
{
// fire at the centre of the cube specified
dTargetZ = ( (FLOAT) (pSoldier->bTargetCubeLevel + pSoldier->bTargetLevel * PROFILE_Z_SIZE) - 0.5f) * HEIGHT_UNITS_PER_INDEX;
dTargetZ = ( (FLOAT) (bTargetCubeLevel + bTargetLevel * PROFILE_Z_SIZE) - 0.5f) * HEIGHT_UNITS_PER_INDEX;
}
else
{
bStructHeight = GetStructureTargetHeight( sTargetGridNo, (BOOLEAN) (pSoldier->bTargetLevel == 1) );
bStructHeight = GetStructureTargetHeight( sTargetGridNo, (BOOLEAN) (bTargetLevel == 1) );
if (bStructHeight > 0)
{
// fire at the centre of the cube *one below* the tallest of the tallest structure
@@ -1739,14 +1715,15 @@ void GetTargetWorldPositions( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo, FLOAT
// reduce target level by 1
bStructHeight--;
}
dTargetZ = ((FLOAT) (bStructHeight + pSoldier->bTargetLevel * PROFILE_Z_SIZE) - 0.5f) * HEIGHT_UNITS_PER_INDEX;
dTargetZ = ((FLOAT) (bStructHeight + bTargetLevel * PROFILE_Z_SIZE) - 0.5f) * HEIGHT_UNITS_PER_INDEX;
}
else
{
// fire at 1 unit above the level of the ground
dTargetZ = (FLOAT) (pSoldier->bTargetLevel * PROFILE_Z_SIZE) * HEIGHT_UNITS_PER_INDEX + 1;
dTargetZ = (FLOAT) (bTargetLevel * PROFILE_Z_SIZE) * HEIGHT_UNITS_PER_INDEX + 1;
}
}
// adjust for terrain height
dTargetZ += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[sTargetGridNo].sHeight );
}