From 50d7b679c19d24d0b2ed314edace029328425b00 Mon Sep 17 00:00:00 2001 From: Flugente Date: Sun, 30 Sep 2018 19:40:55 +0000 Subject: [PATCH] Added safety checks git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8626 3b4a5df2-a311-0410-b5c6-a8a6f20db521 --- Laptop/CampaignStats.cpp | 66 +++--- Tactical/Handle Items.cpp | 21 ++ Tactical/LOS.cpp | 455 +++++++++++++++++++++++++++++++++++--- Tactical/LOS.h | 3 + Tactical/Weapons.cpp | 57 ++--- 5 files changed, 498 insertions(+), 104 deletions(-) diff --git a/Laptop/CampaignStats.cpp b/Laptop/CampaignStats.cpp index c545777d..a278c66f 100644 --- a/Laptop/CampaignStats.cpp +++ b/Laptop/CampaignStats.cpp @@ -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; diff --git a/Tactical/Handle Items.cpp b/Tactical/Handle Items.cpp index 9cb1ce4f..d6388576 100644 --- a/Tactical/Handle Items.cpp +++ b/Tactical/Handle Items.cpp @@ -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 ); + } } } } diff --git a/Tactical/LOS.cpp b/Tactical/LOS.cpp index 31947584..6e68514b 100644 --- a/Tactical/LOS.cpp +++ b/Tactical/LOS.cpp @@ -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 //////////////////////////// // 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; // 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 ) diff --git a/Tactical/LOS.h b/Tactical/LOS.h index bdb7578f..3af5798e 100644 --- a/Tactical/LOS.h +++ b/Tactical/LOS.h @@ -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 diff --git a/Tactical/Weapons.cpp b/Tactical/Weapons.cpp index 3fef9f9e..199ef02f 100644 --- a/Tactical/Weapons.cpp +++ b/Tactical/Weapons.cpp @@ -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 ); }