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 (<nasAttachmentClass>, <AttachmentPoint>, <AttachToPointAPCost>, <Attachment>)



git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@5331 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Wanne
2012-06-06 08:27:12 +00:00
parent a4cfec6ec7
commit f200ac8d3d
7 changed files with 809 additions and 46 deletions
+21 -2
View File
@@ -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
{
+574
View File
@@ -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 ////////////////////////////
//<SB> manual recharge
if (Weapon[Item[pObj->usItem].ubClassIndex].APsToReloadManually > 0)
(*pObj)[0]->data.gun.ubGunState &= ~GS_CARTRIDGE_IN_CHAMBER;
//<SB>
}
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
@@ -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
-3
View File
@@ -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
+3
View File
@@ -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
{
+205 -41
View File
@@ -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
+3
View File
@@ -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;