From f200ac8d3d3de8a86d002b8c1e47c82e0188b23c Mon Sep 17 00:00:00 2001 From: Wanne Date: Wed, 6 Jun 2012 08:27:12 +0000 Subject: [PATCH] New Feature: Traps made out of guns and a piece of tripwire (by Flugente) - guns can now be attached to tripwire and then be used as tripwire-activatd traps o Attach any firearm (any gun, no RPGs, Mortars or GLs) to a piece of tripwire. You will need the newest xmls for that. o You can plant the tripwire as you usually do. o Once activated, the gun that is attached to the wire will fire once. It will fire in the direction that you planted the bomb, just like claymore mines do (the direction that the merc planting the wire was looking). o The bullet fired has the range and damage it would have if fired from the gun. o The gun correctly heats, jams, degrades and whatever else guns do when fired. If the gun is jammed, not pumped (pumpguns) or does not have ammo, it will not fire. o After firing, the gun lies on the floor, just where the trp was planted. o This can be applied to any tripwire. You can ave multiple of these traps. Just put them into your normal trap setup however you like. o At the moment, only single shots are possible, even for guns that do not have a single shot mode (MG3 etc.) o Although I did not experience any problems with attachments, it might be a good idea to take any attachments off a gun if you attach it to tripwire. o When the gun is attached, it is possible to attach gun attachments (lasers, scopes etc.) to the explosive. Do not attach any attachments to the explosive. They will be lost if the wire is activated. o see: http://www.bears-pit.com/board/ubbthreads.php/topics/305634.html#Post305634 -> To support this feature, some (already existing) xml-tags have been added to the weapons in Data-1.13\TableData\Items.xml (, , , ) git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@5331 3b4a5df2-a311-0410-b5c6-a8a6f20db521 --- Tactical/DisplayCover.cpp | 23 +- Tactical/LOS.cpp | 574 +++++++++++++++++++++++++++++++ Tactical/LOS.h | 3 + Tactical/Weapons.cpp | 3 - Tactical/Weapons.h | 3 + TileEngine/Explosion Control.cpp | 246 ++++++++++--- TileEngine/Explosion Control.h | 3 + 7 files changed, 809 insertions(+), 46 deletions(-) diff --git a/Tactical/DisplayCover.cpp b/Tactical/DisplayCover.cpp index 653433fa..995f55b6 100644 --- a/Tactical/DisplayCover.cpp +++ b/Tactical/DisplayCover.cpp @@ -813,7 +813,7 @@ TileDefines GetTileMinesIndex( const INT8& bMines ) case MINES_NET_1: case MINES_LVL_4: return SPECIALTILE_COVER_1; // red - case MINE_BOMB_AND_WIRE: + case MINE_BOMB_AND_WIRE: case MINES_NET_2: case MINES_LVL_3: return SPECIALTILE_COVER_2; // orange @@ -1077,8 +1077,27 @@ void DetermineMineDisplayInTile( INT32 sGridNo, INT8 bLevel, INT8& bMines, BOOLE // if we're already marked as MINE_BOMB, switch to MINE_BOMB_AND_WIRE if ( bMines == MINE_BOMB ) bMines = MINE_BOMB_AND_WIRE; + else if ( bMines == MINE_BOMB_AND_WIRE ) + ; else - bMines = MINE_WIRE; + { + // check if the tripwire has a gun attached + BOOLEAN fgunfound = FALSE; + attachmentList::iterator iterend = (*pObj)[0]->attachments.end(); + for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != iterend; ++iter) + { + if ( iter->exists() && Item[iter->usItem].usItemClass == IC_GUN ) + { + fgunfound = TRUE; + break; + } + } + + if ( fgunfound ) + bMines = MINE_BOMB_AND_WIRE; + else + bMines = MINE_WIRE; + } } else { diff --git a/Tactical/LOS.cpp b/Tactical/LOS.cpp index fdf110f8..e72eca71 100644 --- a/Tactical/LOS.cpp +++ b/Tactical/LOS.cpp @@ -49,6 +49,7 @@ // HEADROCK HAM 3.6: This must be included, for testing whether Bloodcats and Enemies can see one another. #include "Campaign Types.h" #include "soldier tile.h" // added by Flugente +#include "Sound Control.h" // added by Flugente //forward declarations of common classes to eliminate includes class OBJECTTYPE; @@ -5249,6 +5250,579 @@ INT8 FireFragmentGivenTarget( SOLDIERTYPE * pThrower, FLOAT dStartX, FLOAT dStar return( TRUE ); } +// 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 ) +{ + if ( !pObj ) + return 0; + + if ( !pThrower ) + return 0; + + 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; + UINT8 ubImpact=0; + INT8 bCTGT; + UINT8 ubSpreadIndex = 0; + UINT16 usBulletFlags = 0; + int n=0; + + BOOLEAN fBuckshot = FALSE; + if ( AmmoTypes[(*pObj)[0]->data.gun.ubGunAmmoType].numberOfBullets > 1 ) + fBuckshot = TRUE; + + dStartX = (FLOAT) CenterX( gridno ); + dStartY = (FLOAT) CenterY( gridno ); + + dDeltaX = dEndX - dStartX; + dDeltaY = dEndY - dStartY; + dDeltaZ = dEndZ - dStartZ; + + //lal bugfix + if( dDeltaZ > 0 ) + d2DDistance = Distance3D( dDeltaX, dDeltaY, dDeltaZ ); + else + d2DDistance = Distance2D( dDeltaX, dDeltaY ); + + iDistance = (INT32) d2DDistance; + + if ( d2DDistance != iDistance ) + { + iDistance += 1; + d2DDistance = (FLOAT) ( iDistance); + } + + ddOrigHorizAngle = atan2( dDeltaY, dDeltaX ); + ddOrigVerticAngle = atan2( dDeltaZ, (d2DDistance * 2.56f) ); + ddAdjustedHorizAngle = ddOrigHorizAngle; + ddAdjustedVerticAngle = ddOrigVerticAngle; + + ubShots = 1; + fTracer = FALSE; + + // Check if we have spit as a weapon! + if ( Weapon[ pObj->usItem ].ubWeaponClass == MONSTERCLASS ) + { + usBulletFlags |= BULLET_FLAG_CREATURE_SPIT; + } + else if ( Item[ pObj->usItem ].usItemClass == IC_THROWING_KNIFE ) + { + usBulletFlags |= BULLET_FLAG_KNIFE; + } + else if ( Item[pObj->usItem].rocketlauncher ) + { + usBulletFlags |= BULLET_FLAG_MISSILE; + } + else if ( Item[pObj->usItem].cannon ) + { + usBulletFlags |= BULLET_FLAG_TANK_CANNON; + } + else if ( Item[pObj->usItem].rocketrifle ) + { + usBulletFlags |= BULLET_FLAG_SMALL_MISSILE; + } + else if ( pObj->usItem == FLAMETHROWER ) + { + usBulletFlags |= BULLET_FLAG_FLAME; + ubSpreadIndex = 2; + } + + // no option to use fire bursts or autofire yet + /*// HEADROCK HAM B2.5: Set tracer effect on/off for individual bullets in a Tracer Magazine, as part of the + // New Tracer System. + else if (gGameExternalOptions.ubRealisticTracers > 0 && gGameExternalOptions.ubNumBulletsPerTracer > 0 && (pFirer->bDoAutofire > 0 || pFirer->bDoBurst > 0) + && AmmoTypes[ (*pObjAttHand)[0]->data.gun.ubGunAmmoType ].tracerEffect ) + { + UINT16 iBulletsLeft, iBulletsPerTracer; + iBulletsPerTracer = gGameExternalOptions.ubNumBulletsPerTracer; + iBulletsLeft = (*pObjAttHand)[0]->data.gun.ubGunShotsLeft + pFirer->bDoBurst; + + if ((((iBulletsLeft - (pFirer->bDoBurst - 1)) / iBulletsPerTracer) - ((iBulletsLeft - pFirer->bDoBurst) / iBulletsPerTracer)) == 1) + { + fTracer = TRUE; + } + else + { + fTracer = FALSE; + } + }*/ + else if ( AmmoTypes[ (*pObj)[0]->data.gun.ubGunAmmoType ].tracerEffect && gGameSettings.fOptions[ TOPTION_TRACERS_FOR_SINGLE_FIRE ] ) + { + //usBulletFlags |= BULLET_FLAG_TRACER; + fTracer = TRUE; + } + + 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; + 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) ); + } + weapon=NULL; + } + //zilpin: End of new code block. + + // GET BULLET + for (ubLoop = 0; ubLoop < ubShots; ubLoop++) + { + iBullet = CreateBullet( NOBODY, FALSE, usBulletFlags, pObj->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 ( 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 ); + pBullet->qIncrY = FloatToFixed( dDeltaY / (FLOAT)iDistance ); + pBullet->qIncrZ = FloatToFixed( dDeltaZ / (FLOAT)iDistance ); + } + else if( ubShots > 1 ) + { + //Calculate the deviation due to to-hit, but only for the first shot. + //The bullet is only used temporarily. It gets reset later. + //Should this use d3DDistance? + //if( ubLoop<1 ){ + // CalculateFiringIncrements( ddOrigHorizAngle, ddOrigVerticAngle, d2DDistance, pBullet, &ddAdjustedHorizAngle, &ddAdjustedVerticAngle ); + //} + + // temporarily set bullet's sHitBy value to 0 to get unadjusted angles + //Note: this no longer needs to be done since the CalculateFiringIncrementsSimple is being used below. + //pBullet->sHitBy = 0; + + //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; + } + + + //#ifdef JA2TESTVERSION + DOUBLE ddRawHorizAngle = ddHorizAngle; + DOUBLE ddRawVerticAngle = ddVerticAngle; + //#endif + + //Adjust based on the to-hit deviation calculated when the first pellet was fired. + ddHorizAngle += ddAdjustedHorizAngle; + ddVerticAngle += ddAdjustedVerticAngle; + + //Logging for debugging + //#ifdef JA2TESTVERSION + 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); + } + } + //#endif + } + + //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; + + /*if (ubLoop == 0 && pFirer->bDoBurst < 2) + { + pBullet->fAimed = TRUE; + } + else + { + // buckshot pellets after the first can hit friendlies even at close range + pBullet->fAimed = FALSE; + }*/ + + // buckshot pellets after the first can hit friendlies even at close range + pBullet->fAimed = FALSE; + + if ( pBullet->usFlags & BULLET_FLAG_KNIFE ) + { + pBullet->ubItemStatus = (*pObj)[0]->data.objectStatus; + } + + // 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; + + //zilpin: pellet spread patterns externalized in XML + //Also bugfix. I understand why this was added, but it causes problems. + /* + // NB we can only apply correction for leftovers if the bullet is going to hit + // because otherwise the increments are not right for the calculations! + if ( pBullet->sHitBy >= 0 ) + { + pBullet->qCurrX += ( FloatToFixed( dDeltaX ) - pBullet->qIncrX * iDistance ) / 2; + pBullet->qCurrY += ( FloatToFixed( dDeltaY ) - pBullet->qIncrY * iDistance ) / 2; + pBullet->qCurrZ += ( FloatToFixed( dDeltaZ ) - pBullet->qIncrZ * iDistance ) / 2; + } + */ + + pBullet->iImpact = ubImpact; + + pBullet->iRange = GunRange( pObj, NULL ); // SANDRO - added argument + // 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 - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ gridno ].sHeight ) ); + //pBullet->bEndCubesAboveLevelZ = (INT8) CONVERT_HEIGHTUNITS_TO_INDEX( (INT32)dEndZ - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ pBullet->sTargetGridNo ].sHeight ) ); + + pBullet->bStartCubesAboveLevelZ = (INT8) CONVERT_HEIGHTUNITS_TO_INDEX( (INT32)dStartZ ); + pBullet->bEndCubesAboveLevelZ = (INT8) CONVERT_HEIGHTUNITS_TO_INDEX( (INT32)dEndZ ); + + // HEADROCK HAM BETA2.5: New method for signifying whether a bullet is a tracer or not, using an individual + // bullet structure flag. Hehehehe, I think this is kind of reverting to old code, isn't it? + /*if (gGameExternalOptions.ubRealisticTracers > 0 && gGameExternalOptions.ubNumBulletsPerTracer > 0 && (pFirer->bDoAutofire > 0 || pFirer->bDoBurst > 0) + && AmmoTypes[ (*pObj)[0]->data.gun.ubGunAmmoType ].tracerEffect ) + { + UINT16 iBulletsLeft, iBulletsPerTracer; + iBulletsPerTracer = gGameExternalOptions.ubNumBulletsPerTracer; + iBulletsLeft = (*pObj)[0]->data.gun.ubGunShotsLeft + pFirer->bDoBurst; + + // Is this specific bullet a tracer? - based on how many tracers there are per regular bullets in + // a tracer magazine (INI-settable). + if ((((iBulletsLeft - (pFirer->bDoBurst - 1)) / iBulletsPerTracer) - ((iBulletsLeft - pFirer->bDoBurst) / iBulletsPerTracer)) == 1) + { + pBullet->fTracer = TRUE; + } + else + { + pBullet->fTracer = 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 ) + { + // Switch on silencer... + UINT16 noisefactor = GetPercentNoiseVolume( pObj ); + if( noisefactor < MAX_PERCENT_NOISE_VOLUME_FOR_SILENCED_SOUND || Weapon[ pObj->usItem ].ubAttackVolume <= 10 ) + { + INT32 uiSound; + + uiSound = Weapon [ pObj->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 ) ); + } + } + ///////////////////////// SOUND //////////////////////////// + + // deduct ammo + (*pObj)[0]->data.gun.ubGunShotsLeft = max(0, (*pObj)[0]->data.gun.ubGunShotsLeft - 1); + + //gTacticalStatus.ubAttackBusyCount++; + + if(is_client)send_bullet( pBullet, pObj->usItem ); + FireBullet( pThrower, pBullet, FALSE ); + + ///////////////////////// SOUND //////////////////////////// + UINT8 ubVolume = Weapon[ pObj->usItem ].ubAttackVolume; + + // Snap: get cumulative noise reduction from the weapon and its attachments + UINT16 noisefactor = GetPercentNoiseVolume( pObj ); + if ( ubVolume * noisefactor > 25000 ) + { // Snap: hack this to prevent overflow (damn miserly programmers!) + ubVolume = 250; + } + else + { + ubVolume = __max( 1, ( ubVolume * GetPercentNoiseVolume( pObj ) ) / 100 ); + } + + MakeNoise( NOBODY, gridno, 0, pThrower->bOverTerrainType, ubVolume, NOISE_GUNFIRE ); + ///////////////////////// SOUND //////////////////////////// + + ///////////////////////// OVERHEATING AND STATUS REDUCTION //////////////////////////// + INT16 iOverheatReliabilityMalus = 0; + // Flugente: Increase Weapon Temperature + if ( gGameOptions.fWeaponOverheating ) + { + FLOAT overheatjampercentage = GetGunOverheatDamagePercentage( pObj ); // ... how much above the gun's usOverheatingDamageThreshold are we? ... + + if ( overheatjampercentage > 1.0 ) + iOverheatReliabilityMalus = (INT16)floor(overheatjampercentage*overheatjampercentage); + + GunIncreaseHeat( pObj ); + } + + // Flugente : Added a malus to reliability for overheated guns + // HEADROCK HAM 5: Variable NCTH base change + UINT32 uiDepreciateTest = 0; + if ( UsingNewCTHSystem() == true) + { + UINT16 usBaseChance = gGameCTHConstants.BASIC_RELIABILITY_ODDS; + FLOAT dReliabilityRatio = 3.0f * ((FLOAT)usBaseChance / (FLOAT)BASIC_DEPRECIATE_CHANCE); // Compare original odds to new odds. + uiDepreciateTest = usBaseChance + (INT16)( dReliabilityRatio * GetReliability( pObj ) - iOverheatReliabilityMalus); + uiDepreciateTest = max(0, uiDepreciateTest); + } + else + { + uiDepreciateTest = max( BASIC_DEPRECIATE_CHANCE + 3 * GetReliability( pObj ) - iOverheatReliabilityMalus, 0); + } + if ( !PreRandom( uiDepreciateTest ) && ( (*pObj)[0]->data.objectStatus > 1) ) + { + (*pObj)[0]->data.objectStatus--; + } + ///////////////////////// OVERHEATING AND STATUS REDUCTION //////////////////////////// + + ///////////////////////// JAMMING //////////////////////////// + // Algorithm for jamming + int maxJamChance = 50; // Externalize this? + int reliability = GetReliability( pObj ); + int condition = (*pObj)[0]->data.gun.bGunStatus; + int invertedBaseJamChance = condition + (reliability * 2) - gGameExternalOptions.ubWeaponReliabilityReductionPerRainIntensity * 1; + + // Flugente FTW 1: If overheating is allowed, a gun will be prone to more overheating if its temperature is high + if ( gGameOptions.fWeaponOverheating ) + { + FLOAT overheatjampercentage = GetGunOverheatJamPercentage( pObj ); // how much above the gun's usOverheatingJamThreshold are we? ... + + int overheatjamfactor = (int)(100* overheatjampercentage); // We need an integer value and rough percentages + + overheatjamfactor = max(0, overheatjamfactor - 100); // If we haven't reached the OverheatJamThreshold, no increased chance of jamming because of overheating + + invertedBaseJamChance -= overheatjamfactor; // lower invertedBaseJamChance (thereby increasing jamChance later on) + } + + if (invertedBaseJamChance < 0) + invertedBaseJamChance = 0; + else if (invertedBaseJamChance > 100) + invertedBaseJamChance = 100; + int jamChance = 100 - (int)sqrt((double)invertedBaseJamChance * (75.0 + (double)invertedBaseJamChance / 2.0)); + if (jamChance < 0) + jamChance = 0; + else if (jamChance > maxJamChance - reliability) + jamChance = maxJamChance - reliability; + + + if ((INT32) PreRandom( 100 ) < jamChance ) + { + // jam! negate the gun ammo status. + (*pObj)[0]->data.gun.bGunAmmoStatus *= -1; + } + ///////////////////////// JAMMING //////////////////////////// + + // manual recharge + if (Weapon[Item[pObj->usItem].ubClassIndex].APsToReloadManually > 0) + (*pObj)[0]->data.gun.ubGunState &= ~GS_CARTRIDGE_IN_CHAMBER; + // + } + + + 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 f6588706..834a0610 100644 --- a/Tactical/LOS.h +++ b/Tactical/LOS.h @@ -70,6 +70,9 @@ INT8 FireBulletGivenTargetNCTH( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, // HEADROCK HAM 5: Function for fragments ejected from an explosion. INT8 FireFragmentGivenTarget( SOLDIERTYPE * pThrower, FLOAT dStartX, FLOAT dStartY, FLOAT dStartZ, FLOAT dEndX, FLOAT dEndY, FLOAT dEndZ, UINT16 usExplosiveItem ); +// 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); + #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 9322aa69..7dc85034 100644 --- a/Tactical/Weapons.cpp +++ b/Tactical/Weapons.cpp @@ -66,9 +66,6 @@ extern INT8 gbCurrentRainIntensity; //#define MINCHANCETOHIT 1 //#define MAXCHANCETOHIT 99 -// NB this is arbitrary, chances in DG ranged from 1 in 6 to 1 in 20 -#define BASIC_DEPRECIATE_CHANCE 15 - #define NORMAL_RANGE 90 // # world units considered an 'avg' shot #define MIN_SCOPE_RANGE 60 // # world units after which scope's useful diff --git a/Tactical/Weapons.h b/Tactical/Weapons.h index ba0ea583..834b3d01 100644 --- a/Tactical/Weapons.h +++ b/Tactical/Weapons.h @@ -77,6 +77,9 @@ void HandleTacticalEffectsOfEquipmentChange( SOLDIERTYPE *pSoldier, UINT32 uiInv #define MIN_MORTAR_RANGE 150 // minimum range of a mortar +// NB this is arbitrary, chances in DG ranged from 1 in 6 to 1 in 20 +#define BASIC_DEPRECIATE_CHANCE 15 + // WEAPON CLASSES enum { diff --git a/TileEngine/Explosion Control.cpp b/TileEngine/Explosion Control.cpp index 20d45eca..c80a631d 100644 --- a/TileEngine/Explosion Control.cpp +++ b/TileEngine/Explosion Control.cpp @@ -106,6 +106,9 @@ UINT8 DetermineFlashbangEffect( SOLDIERTYPE *pSoldier, INT8 ubExplosionDir, BOOL // HEADROCK HAM 5.1: Explosion Fragments launcher void FireFragments( SOLDIERTYPE * pThrower, INT16 sX, INT16 sY, INT16 sZ, UINT16 usItem, UINT8 ubDirection = DIRECTION_IRRELEVANT ); +// Flugente: shoot a gun without anyone operating it (used for makeshift traps wih guns) +void FireFragmentsTrapGun( SOLDIERTYPE* pThrower, INT32 gridno, INT16 sZ, OBJECTTYPE* pObj, UINT8 ubDirection = NORTH ); + extern INT8 gbSAMGraphicList[ MAX_NUMBER_OF_SAMS ]; extern void AddToShouldBecomeHostileOrSayQuoteList( UINT8 ubID ); extern void RecompileLocalMovementCostsForWall( INT32 sGridNo, UINT8 ubOrientation ); @@ -3604,7 +3607,9 @@ BOOLEAN ActivateSurroundingTripwire( UINT8 ubID, INT32 sGridNo, INT8 bLevel, UIN if ( Item[pObj->usItem].tripwireactivation == 1 ) { // tripwire just gets activated - if ( Item[pObj->usItem].tripwire == 1 ) + // this is important - we have to check for the tripwire's temperature. + // The temperature serves as a temporary flag replacement, marking that a tripwire has already been activated, adn shouldn't be activated again + if ( Item[pObj->usItem].tripwire == 1 && (*pObj)[0]->data.bTemperature < 1 ) { // determine this tripwire's flag UINT32 ubWireNetworkFlag = (*pObj)[0]->data.ubWireNetworkFlag; @@ -3635,32 +3640,28 @@ BOOLEAN ActivateSurroundingTripwire( UINT8 ubID, INT32 sGridNo, INT8 bLevel, UIN if ( samenetwork && sameorlowerhierarchy ) { - // if we have passed the check, our tripwire belongs to the same tripwirenetwork as the one that caused this call - // it is also of the sme or a lower hierarchy - // so, this wire calls other wires in the surrounding area. But the new flag is ubWireNetworkFlag instead of ubFlag, so the hierarchy level might be lower + gubPersonToSetOffExplosions = ubID; - OBJECTTYPE newtripwireObject; - CreateItem( pObj->usItem, (*pObj)[0]->data.objectStatus, &newtripwireObject ); - - // this is important: delete the tripwire, otherwise we get into an infinite loop if there are two piecs of tripwire.... - RemoveItemFromPool( adjgrid, gWorldBombs[ uiWorldBombIndex ].iItemIndex, bLevel ); - - // if no other bomb exists here - if ( FindWorldItemForBombInGridNo(adjgrid, bLevel) == -1 ) + // put this bomb on the queue + AddBombToQueue( uiWorldBombIndex, uiTimeStamp ); + if (pObj->usItem != ACTION_ITEM || (*pObj)[0]->data.misc.bActionValue == ACTION_ITEM_BLOW_UP) { - // make sure no one thinks there is a bomb here any more! - if ( gpWorldLevelData[adjgrid].uiFlags & MAPELEMENT_PLAYER_MINE_PRESENT ) - { - RemoveBlueFlag( adjgrid, bLevel ); - } - gpWorldLevelData[adjgrid].uiFlags &= ~(MAPELEMENT_ENEMY_MINE_PRESENT); + uiTimeStamp += BOMB_QUEUE_DELAY; } - // no add a tripwire item to the floor, simulating that activating tripwire deactivates it - AddItemToPool( adjgrid, &newtripwireObject, 1, bLevel, 0, -1 ); + if ( (*pObj)[0]->data.misc.usBombItem != NOTHING && Item[ (*pObj)[0]->data.misc.usBombItem ].usItemClass & IC_EXPLOSV ) + { + fFoundMine = TRUE; + } + + // Flugente hack: the tripwire will get removed in HandleExplosionQueue (it is still needed in there until the bomb gets called). + // Because of this, ActivateSurroundingTripwire wouldm normally find this wire again, a loop would occur. + // As i do not want to create an itm flag for this purpose right now (but its on my TODO-list), we set the temperature of the wire to a higher value + // (temperature for tripwire isn't needed anywhere else, so its ok) + (*pObj)[0]->data.bTemperature = 1000.0; // activate surrounding tripwires, unless tripwire is too much damaged and we are unlucky.. - if ( newtripwireObject[0]->data.objectStatus > (INT16)Random(50) ) + if ( (*pObj)[0]->data.objectStatus > (INT16)Random(50) ) fFoundMine = ActivateSurroundingTripwire(ubID, adjgrid, bLevel, ubWireNetworkFlag); } } @@ -3699,6 +3700,53 @@ BOOLEAN ActivateSurroundingTripwire( UINT8 ubID, INT32 sGridNo, INT8 bLevel, UIN return( fFoundMine ); } +// Flugente: A special function for tripwire gun traps. Search if pObj has a gun attached. If so, fire a shot from that gun in a specific direction. Afterwards place the gun on the ground +void CheckAndFireTripwireGun( OBJECTTYPE* pObj, INT32 sGridNo, INT8 bLevel, UINT8 ubId, UINT8 ubDirection ) +{ + if ( !pObj ) + return; + + // search for attached guns + BOOLEAN fgunfound = FALSE; + OBJECTTYPE* pAttGun = NULL; + // check all attachments + attachmentList::iterator iterend = (*pObj)[0]->attachments.end(); + for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != iterend; ++iter) + { + if ( iter->exists() && Item[iter->usItem].usItemClass == IC_GUN ) + { + fgunfound = TRUE; + pAttGun = &(*iter); + break; + } + } + + if ( fgunfound && pAttGun ) + { + // we actually found a gun. + // if the gun has ammo and is not jammed, fire it + if ( (*pAttGun)[0]->data.gun.ubGunShotsLeft > 0 && (*pAttGun)[0]->data.gun.bGunAmmoStatus > 0 ) + { + // Increment attack counter... + if (gubElementsOnExplosionQueue == 0) + { + // single explosion, disable sight until the end, and set flag + // to check sight at end of attack + + gTacticalStatus.uiFlags |= (DISALLOW_SIGHT | CHECK_SIGHT_AT_END_OF_ATTACK); + } + + FireFragmentsTrapGun( MercPtrs[ubId], sGridNo, 0, pAttGun, ubDirection ); + + // this is important... if not set, the game will remain in a loop + gTacticalStatus.ubAttackBusyCount = 0; + } + + // add this gun to the floor + AddItemToPool( sGridNo, pAttGun, 1, bLevel, 0, -1 ); + } +} + void HandleExplosionQueue( void ) { UINT32 uiIndex; @@ -3743,6 +3791,39 @@ void HandleExplosionQueue( void ) NewLightEffect( sGridNo, (UINT8)Explosive[pObj->usItem].ubDuration, (UINT8)Explosive[pObj->usItem].ubStartRadius ); RemoveItemFromPool( sGridNo, gWorldBombs[ uiWorldBombIndex ].iItemIndex, ubLevel ); } + // Flugente: handle tripwire gun traps here... + // tripwire gets called and activated in ActivateSurroundingTripwire + else if ( Item[pObj->usItem].tripwire == 1 ) + { + // check if there is a gun attached to this piece of wire, and eventually fire it + CheckAndFireTripwireGun( pObj, gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].sGridNo, ubLevel, (*pObj)[0]->data.misc.ubBombOwner, (*pObj)[0]->data.ubDirection ); + + OBJECTTYPE newtripwireObject; + CreateItem( pObj->usItem, (*pObj)[0]->data.objectStatus, &newtripwireObject ); + + // determine this tripwire's flag + UINT32 ubWireNetworkFlag = (*pObj)[0]->data.ubWireNetworkFlag; + + // this is important: delete the tripwire, otherwise we get into an infinite loop if there are two piecs of tripwire.... + RemoveItemFromPool( sGridNo, gWorldBombs[ uiWorldBombIndex ].iItemIndex, ubLevel ); + + // if no other bomb exists here + if ( FindWorldItemForBombInGridNo(sGridNo, ubLevel) == -1 ) + { + // make sure no one thinks there is a bomb here any more! + if ( gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_PLAYER_MINE_PRESENT ) + { + RemoveBlueFlag( sGridNo, ubLevel ); + } + gpWorldLevelData[sGridNo].uiFlags &= ~(MAPELEMENT_ENEMY_MINE_PRESENT); + } + + // we have to set the wire's temperature back to 0, otherwise tripwire will only work once + (newtripwireObject)[0]->data.bTemperature = 0; + + // now add a tripwire item to the floor, simulating that activating tripwire deactivates it + AddItemToPool( sGridNo, &newtripwireObject, 1, ubLevel, 0, -1 ); + } else { gfExplosionQueueMayHaveChangedSight = TRUE; @@ -4204,32 +4285,30 @@ BOOLEAN SetOffBombsInGridNo( UINT8 ubID, INT32 sGridNo, BOOLEAN fAllBombs, INT8 // Flugente: a tripwire activates all other tripwires in connection, and detonates all bombs in connection that are tripwire-activated else if ( Item[pObj->usItem].tripwire == 1 ) { - OBJECTTYPE newtripwireObject; - CreateItem( pObj->usItem, (*pObj)[0]->data.objectStatus, &newtripwireObject ); + gubPersonToSetOffExplosions = ubID; - // determine this tripwire's flag - UINT32 ubWireNetworkFlag = (*pObj)[0]->data.ubWireNetworkFlag; - - // this is important: delete the tripwire, otherwise we get into an infinite loop if there are two piecs of tripwire.... - RemoveItemFromPool( sGridNo, gWorldBombs[ uiWorldBombIndex ].iItemIndex, bLevel ); - - // if no other bomb exists here - if ( FindWorldItemForBombInGridNo(sGridNo, bLevel) == -1 ) + // put this bomb on the queue + AddBombToQueue( uiWorldBombIndex, uiTimeStamp ); + if (pObj->usItem != ACTION_ITEM || (*pObj)[0]->data.misc.bActionValue == ACTION_ITEM_BLOW_UP) { - // make sure no one thinks there is a bomb here any more! - if ( gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_PLAYER_MINE_PRESENT ) - { - RemoveBlueFlag( sGridNo, bLevel ); - } - gpWorldLevelData[sGridNo].uiFlags &= ~(MAPELEMENT_ENEMY_MINE_PRESENT); + uiTimeStamp += BOMB_QUEUE_DELAY; + //uiTimeStamp += BOMB_QUEUE_DELAY; } - // no add a tripwire item to the floor, simulating that activating tripwire deactivates it - AddItemToPool( sGridNo, &newtripwireObject, 1, bLevel, 0, -1 ); + if ( (*pObj)[0]->data.misc.usBombItem != NOTHING && Item[ (*pObj)[0]->data.misc.usBombItem ].usItemClass & IC_EXPLOSV ) + { + fFoundMine = TRUE; + } + + // Flugente hack: the tripwire will get removed in HandleExplosionQueue (it is still needed in there until the bomb gets called). + // Because of this, ActivateSurroundingTripwire wouldm normally find this wire again, a loop would occur. + // As i do not want to create an itm flag for this purpose right now (but its on my TODO-list), we set the temperature of the wire to a higher value + // (temperature for tripwire isn't needed anywhere else, so its ok) + (*pObj)[0]->data.bTemperature = 1000.0; // activate surrounding tripwires and tripwire-activated mines, unless tripwire is too much damaged and we are unlucky.. - if ( newtripwireObject[0]->data.objectStatus > (INT16)Random(50) ) - fFoundMine = ActivateSurroundingTripwire(ubID, sGridNo, bLevel, ubWireNetworkFlag); + if ( (*pObj)[0]->data.objectStatus > (INT16)Random(50) ) + fFoundMine = ActivateSurroundingTripwire(ubID, sGridNo, bLevel, (*pObj)[0]->data.ubWireNetworkFlag); } else { @@ -4759,6 +4838,91 @@ void FireFragments( SOLDIERTYPE * pThrower, INT16 sX, INT16 sY, INT16 sZ, UINT16 } } +// Flugente: shoot a gun without anyone operating it (used for makeshift traps wih guns) +void FireFragmentsTrapGun( SOLDIERTYPE* pThrower, INT32 gridno, INT16 sZ, OBJECTTYPE* pObj, UINT8 ubDirection ) +{ + if ( !pObj ) + return; + + if ( !pThrower ) + return; + + // at the moment always only 1 bullet + UINT16 usNumFragments = 1; + UINT16 ubFragRange = GunRange( pObj, NULL ); + + // deviation arcs. A gun fired by tripping a wire isn't exactly precise + INT16 horizontalarc = 2; + INT16 verticalarc = 2; + + INT16 sX = CenterX(gridno); + INT16 sY = CenterY(gridno); + + AssertMsg( ubFragRange > 0 , "Fragmentation data lacks range property!" ); + + for (UINT16 x = 0; x < usNumFragments; ++x) + { + FLOAT dRandomX = 0; + FLOAT dRandomY = 0; + FLOAT dRandomZ = 0; + + // Flugente: if item is a directional explosive, determine in what direction the frags should fly + INT16 degree = (45 + ubDirection * 45) % 360; // modulo 360 to prevent nonsense from nonsensical input + //INT16 horizontalarc = horizontalarc % 360; // modulo 360 to prevent nonsense from nonsensical input + INT16 halfhorizontalarc = (INT16)(horizontalarc / 2); + INT16 sLowHorizontalD = (360 + degree - halfhorizontalarc) % 360; + INT16 dRandomDegreeH = (sLowHorizontalD + Random(horizontalarc) ) % 360; + + // transform the degree into our coordinates + if ( dRandomDegreeH < 90 ) + { + dRandomX = (FLOAT)dRandomDegreeH / 45.0f; + dRandomY = 0.0; + } + else if ( dRandomDegreeH < 180 ) + { + dRandomX = (FLOAT)2.0; + dRandomY = (FLOAT)(dRandomDegreeH - 90 ) / 45.0f; + } + else if ( dRandomDegreeH < 270 ) + { + dRandomX = (FLOAT)(270 - dRandomDegreeH) / 45.0f; + dRandomY = (FLOAT)2.0; + } + else + { + dRandomX = 0.0; + dRandomY = (FLOAT)(360 - dRandomDegreeH) / 45.0f; + } + + // X and Y now need to be distributed, at the moment they are on a circle + // project into [-1.0, 1.0] + dRandomX -= 1.0f; + dRandomY -= 1.0f; + + // vertical stuff + //INT16 verticalarc = verticalarc % 180; // modulo 180 to prevent nonsense from nonsensical input + INT16 halfverticalarc = (INT16)(verticalarc / 2); + INT16 sLowVerticalD = (90 - halfverticalarc) % 180; + + INT16 dRandomDegreeV = sLowVerticalD + Random(verticalarc); + + dRandomZ = ((FLOAT)(dRandomDegreeV) / 90.0f) - 1.0f; + + FLOAT dDeltaX = (dRandomX * ubFragRange); + FLOAT dDeltaY = (dRandomY * ubFragRange); + FLOAT dDeltaZ = ((dRandomZ * 25.6f) * 50 ); + + FLOAT dRangeMultiplier = 10; // Arbitrary, but gives good results. + + FLOAT dEndX = (FLOAT)(sX + (dDeltaX * dRangeMultiplier)); + FLOAT dEndY = (FLOAT)(sY + (dDeltaY * dRangeMultiplier)); + FLOAT dEndZ = (FLOAT)(sZ + (dDeltaZ * dRangeMultiplier)); + + FireBulletGivenTargetTrapOnly( pThrower, pObj, gridno, 150, dEndX, dEndY, dEndZ, 100 ); + } +} + #ifdef JA2UB //-- UB diff --git a/TileEngine/Explosion Control.h b/TileEngine/Explosion Control.h index ee5a56f1..7e1eda0b 100644 --- a/TileEngine/Explosion Control.h +++ b/TileEngine/Explosion Control.h @@ -136,6 +136,9 @@ void AddBombToQueue( UINT32 uiWorldBombIndex, UINT32 uiTimeStamp, BOOL fFromRemo // Flugente: activate everything connected to a tripwire in the surrounding if sGridNo on level bLevel with regard to the tripwire netwrok and hierarchy determined by ubFlag BOOLEAN ActivateSurroundingTripwire( UINT8 ubID, INT32 sGridNo, INT8 bLevel, UINT32 ubFlag ); +// Flugente: A special function for tripwire gun traps. Search if pObj has a gun attached. If so, fire a shot from that gun in a specific direction. Afterwards place the gun on the ground +void CheckAndFireTripwireGun( OBJECTTYPE* pObj, INT32 sGridNo, INT8 bLevel, UINT8 ubId, UINT8 ubDirection ); + extern void ToggleActionItemsByFrequency( INT8 bFrequency ); extern void PerformItemAction( INT32 sGridNo, OBJECTTYPE * pObj ); extern BOOLEAN gfExplosionQueueMayHaveChangedSight;