diff --git a/Init.cpp b/Init.cpp
index 916f4605..f5f22fae 100644
--- a/Init.cpp
+++ b/Init.cpp
@@ -154,6 +154,14 @@ BOOLEAN LoadExternalGameplayData(STR directoryName)
{
char fileName[MAX_PATH];
+ //zilpin: pellet spread patterns externalized in XML
+ //If file not found, or error, then the old hard-coded defaults are used by LOS.cpp
+ //This needs to be loaded before AmmoTypes and Items because SpreadPatterns can be referenced by name or index.
+ strcpy(fileName, directoryName);
+ strcat(fileName, SPREADPATTERNSFILENAME);
+ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("LoadExternalGameplayData, fileName = %s", fileName));
+ ReadInSpreadPatterns(fileName);
+
// WANNE: Enemy drops - begin
strcpy(fileName, directoryName);
strcat(fileName, ENEMYMISCDROPSFILENAME);
diff --git a/Tactical/Item Types.h b/Tactical/Item Types.h
index f324fd71..35977da9 100644
--- a/Tactical/Item Types.h
+++ b/Tactical/Item Types.h
@@ -720,6 +720,10 @@ typedef struct
BOOLEAN newinv; // item only available in new inventory mode
UINT16 defaultattachment;
+
+ //zilpin: pellet spread patterns externalized in XML
+ INT32 spreadPattern;
+
} INVTYPE;
// CHRISL: Added new structures to handle LBE gear and the two new XML files that will be needed to deal
diff --git a/Tactical/LOS.cpp b/Tactical/LOS.cpp
index 2ca938c3..52b35a7f 100644
--- a/Tactical/LOS.cpp
+++ b/Tactical/LOS.cpp
@@ -106,6 +106,12 @@ UINT32 FPMult32(UINT32 uiA, UINT32 uiB)
}
+//zilpin: pellet spread patterns externalized in XML
+t_SpreadPattern *gpSpreadPattern=NULL;
+INT32 giSpreadPatternCount=0;
+char * gSpreadPatternMethodNames[] = {"RECTANGLE","DIAMOND","ELLIPSE",NULL};
+int giSpreadPatternMethod_Default = SPREADPATTERNMETHOD_ELLIPSE;
+//zilpin: ddShotgunSpread is only used as a backup if the XML file does not exist.
static DOUBLE ddShotgunSpread[3][BUCKSHOT_SHOTS][2] =
{
{
@@ -150,6 +156,72 @@ static DOUBLE ddShotgunSpread[3][BUCKSHOT_SHOTS][2] =
};
+
+//zilpin: pellet spread patterns externalized in XML
+//Decide what spread pattern to use for an object in inventory.
+//Attachment trumps Weapon trumps Ammo trumps AmmoType
+//If multiple attachments, then the first one encountered is used.
+//This will work for non-weapons, but ammo and ammo type are only checked for guns.
+INT32 GetSpreadPattern( OBJECTTYPE * pObj )
+{
+ int i=0,n=0;
+ if( !pObj || !pObj->usItem || !pObj->exists() ) return 0;
+
+ //If there are attachments, check them. Stop on the first one with something defined.
+ //Dear God, I hate C++ iterators. What a fugly mess.
+ for (attachmentList::iterator iter = pObj[0][0]->attachments.begin(); iter != pObj[0][0]->attachments.end(); ++iter){
+ if( n=Item[ iter->usItem ].spreadPattern )
+ //An attachment has it, and it trumps everything, so return it's value.
+ return n;
+ }
+
+ //The object's own spread pattern, if it has one.
+ if( n=Item[pObj->usItem].spreadPattern )
+ return n;
+
+ //If a gun, and don't have a pattern yet, check it's ammo.
+ if( IC_GUN & Item[pObj->usItem].usItemClass ){
+ //Check for the ammo first.
+ if( n=Item[ pObj[0][0]->data.gun.usGunAmmoItem ].spreadPattern )
+ return n;
+ //If no pattern for the ammo, try the ammo type.
+ if( n=AmmoTypes[pObj[0][0]->data.gun.ubGunAmmoType].spreadPattern )
+ return n;
+ }
+
+
+ /*
+ This is the old code to iterate through all the attachments.
+ No longer works, since the NewInv project object-orientified everything.
+ *
+ //If there are attachments, check them. Stop on the first one with something defined.
+ for (i=0; iusAttachItem[i])
+ if( n=Item[ pObj->usAttachItem[i] ].spreadPattern )
+ //The attachment has it, and it trumps everything, so return it's value.
+ return n;
+
+ //The object's own spread pattern, if it has one.
+ if( n=Item[pObj->usItem].spreadPattern )
+ return n;
+
+ //If a gun, and don't have a pattern yet, check it's ammo.
+ if( IC_GUN & Item[pObj->usItem].usItemClass ){
+ //Check for the ammo first.
+ if( n=Item[ pObj->ItemData.Gun.usGunAmmoItem ].spreadPattern )
+ return n;
+ //If no pattern for the ammo, try the ammo type.
+ if( n=AmmoTypes[pObj->ItemData.Gun.ubGunAmmoType].spreadPattern )
+ return n;
+ }
+ */
+
+ //No pattern for anything found, so use the default.
+ return 0;
+}
+
+
+
static UINT8 gubTreeSightReduction[ANIM_STAND + 1] =
{
0,
@@ -3483,6 +3555,15 @@ void CalculateFiringIncrements( DOUBLE ddHorizAngle, DOUBLE ddVerticAngle, DOUBL
pBullet->qIncrZ = FloatToFixed( (FLOAT) ( sin( ddVerticAngle ) / sin( (PI/2) - ddVerticAngle ) * 2.56 ) );
}
+//zilpin: pellet spread patterns externalized in XML
+//New firing increments calculation, without any adjustment.
+void CalculateFiringIncrementsSimple( DOUBLE ddHorizAngle, DOUBLE ddVerticAngle, BULLET * pBullet)
+{
+ pBullet->qIncrX = FloatToFixed( (FLOAT) cos( ddHorizAngle ) );
+ pBullet->qIncrY = FloatToFixed( (FLOAT) sin( ddHorizAngle ) );
+ pBullet->qIncrZ = FloatToFixed( (FLOAT) ( sin( ddVerticAngle ) / sin( (PI/2) - ddVerticAngle ) * 2.56 ) );
+}
+
INT8 FireBullet( SOLDIERTYPE * pFirer, BULLET * pBullet, BOOLEAN fFake )
{
//DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireBullet"));
@@ -3577,36 +3658,37 @@ INT8 FireBulletGivenTarget( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOA
{
//DebugMsg(TOPIC_JA2,DBG_LEVEL_3,"FireBulletGivenTarget");
// fFake indicates that we should set things up for a call to ChanceToGetThrough
- FLOAT dStartZ;
+ FLOAT dStartZ=0;
- FLOAT d2DDistance;
- FLOAT dDeltaX;
- FLOAT dDeltaY;
- FLOAT dDeltaZ;
+ FLOAT d2DDistance=0;
+ FLOAT dDeltaX=0;
+ FLOAT dDeltaY=0;
+ FLOAT dDeltaZ=0;
- FLOAT dStartX;
- FLOAT dStartY;
+ FLOAT dStartX=0;
+ FLOAT dStartY=0;
- DOUBLE ddOrigHorizAngle;
- DOUBLE ddOrigVerticAngle;
- DOUBLE ddHorizAngle;
- DOUBLE ddVerticAngle;
- DOUBLE ddAdjustedHorizAngle;
- DOUBLE ddAdjustedVerticAngle;
- DOUBLE ddDummyHorizAngle;
- DOUBLE ddDummyVerticAngle;
+ 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;
- INT32 iBullet;
+ BULLET * pBullet=NULL;
+ INT32 iBullet=0;
- INT32 iDistance;
+ INT32 iDistance=0;
- UINT8 ubLoop;
- UINT8 ubShots;
- UINT8 ubImpact;
+ UINT8 ubLoop=0;
+ UINT8 ubShots=0;
+ UINT8 ubImpact=0;
INT8 bCTGT;
UINT8 ubSpreadIndex = 0;
UINT16 usBulletFlags = 0;
+ int n=0;
//DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireBulletGivenTarget"));
@@ -3694,6 +3776,12 @@ INT8 FireBulletGivenTarget( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOA
}
ubImpact =(UINT8) GetDamage(&pFirer->inv[pFirer->ubAttackingHand]);
+
+
+ //zilpin: pellet spread patterns externalized in XML
+ /* zilpin: The section below, including line comments, is the original adjustment made to multiple projectile stats.
+ Left in comments for reference, but the new handling is after it.
+ The fBuckshot flag is now ignored, so it can probably be taken out of the function definition.
// if (!fFake)
{
if (fBuckshot)
@@ -3722,6 +3810,25 @@ INT8 FireBulletGivenTarget( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOA
ubImpact = (UINT8) (ubImpact * AmmoTypes[pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType].multipleBulletDamageMultiplier / max(1,AmmoTypes[pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType].multipleBulletDamageDivisor) );
}
}
+ */
+ //zilpin: Begin new code block for spread patterns, number of projectiles, impact adjustment, etc.
+ {
+ ObjectData *weapon = &(pFirer->inv[pFirer->ubAttackingHand][0]->data);
+ ubShots = AmmoTypes[ weapon->gun.ubGunAmmoType].numberOfBullets;
+ ubSpreadIndex = GetSpreadPattern( &pFirer->inv[pFirer->ubAttackingHand] );
+ if( ubShots>1 && !fFake )
+ {
+ fBuckshot = true;
+ usBulletFlags |= BULLET_FLAG_BUCKSHOT;
+ ubImpact = (UINT8) (ubImpact * AmmoTypes[weapon->gun.ubGunAmmoType].multipleBulletDamageMultiplier / max(1,AmmoTypes[weapon->gun.ubGunAmmoType].multipleBulletDamageDivisor) );
+ if (pFirer->ubTargetID != NOBODY)
+ {
+ MercPtrs[ pFirer->ubTargetID ]->bNumPelletsHitBy = 0;
+ }
+ }
+ weapon=NULL;
+ }
+ //zilpin: End of new code block.
// GET BULLET
for (ubLoop = 0; ubLoop < ubShots; ubLoop++)
@@ -3759,7 +3866,11 @@ INT8 FireBulletGivenTarget( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOA
}
}
- if ( ubLoop == 0 )
+ //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 )
{
ddHorizAngle = ddOrigHorizAngle;
ddVerticAngle = ddOrigVerticAngle;
@@ -3779,16 +3890,156 @@ INT8 FireBulletGivenTarget( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOA
CalculateFiringIncrements( ddHorizAngle, ddVerticAngle, d2DDistance, pBullet, &ddAdjustedHorizAngle, &ddAdjustedVerticAngle );
}
}
- else
+ 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
- pBullet->sHitBy = 0;
+ //Note: this no longer needs to be done since the CalculateFiringIncrementsSimple is being used below.
+ //pBullet->sHitBy = 0;
- ddHorizAngle = ddAdjustedHorizAngle + ddShotgunSpread[ubSpreadIndex][ubLoop][0];
- ddVerticAngle = ddAdjustedVerticAngle + ddShotgunSpread[ubSpreadIndex][ubLoop][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;
- CalculateFiringIncrements( ddHorizAngle, ddVerticAngle, d2DDistance, pBullet, &ddDummyHorizAngle, &ddDummyVerticAngle );
- pBullet->sHitBy = sHitBy;
+ //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(!fFake)
+ {
+ 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,
+ pFirer->ubID, 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 );
}
@@ -3811,10 +4062,17 @@ INT8 FireBulletGivenTarget( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOA
// apply increments for first move
- 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
+ //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 )
@@ -3823,6 +4081,7 @@ INT8 FireBulletGivenTarget( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOA
pBullet->qCurrY += ( FloatToFixed( dDeltaY ) - pBullet->qIncrY * iDistance ) / 2;
pBullet->qCurrZ += ( FloatToFixed( dDeltaZ ) - pBullet->qIncrZ * iDistance ) / 2;
}
+ */
pBullet->iImpact = ubImpact;
diff --git a/Tactical/LOS.h b/Tactical/LOS.h
index 49104731..870b71d2 100644
--- a/Tactical/LOS.h
+++ b/Tactical/LOS.h
@@ -165,4 +165,33 @@ extern LOSResults gLOSTestResults;
void MoveBullet( INT32 iBullet );
//BOOLEAN FireBullet2( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOAT dEndZ, INT16 sHitBy );
+//zilpin: pellet spread patterns externalized in XML
+#define SPREADPATTERN_NAME_SIZE 32
+enum SpreadPatternMethod_enum
+{
+ SPREADPATTERNMETHOD_RECT = 0,
+ SPREADPATTERNMETHOD_DIAMOND,
+ SPREADPATTERNMETHOD_ELLIPSE,
+ SPREADPATTERNMETHOD_COUNT,
+};
+extern char *gSpreadPatternMethodNames[];
+extern int giSpreadPatternMethod_Default;
+typedef struct SpreadPattern_struct
+{
+ //Unique name.
+ char Name[SPREADPATTERN_NAME_SIZE];
+ //Max spread for randomized cone of fire.
+ DOUBLE xspread,yspread;
+ //Method (i.e. algorithm) used to distribute randomized cone.
+ int method;
+ //List of statically defined spread angles.
+ //These get used first.
+ INT32 iCount;
+ DOUBLE *x;
+ DOUBLE *y;
+} t_SpreadPattern;
+extern t_SpreadPattern *gpSpreadPattern;
+extern INT32 giSpreadPatternCount;
+extern INT32 GetSpreadPattern( OBJECTTYPE * pObj );
+
#endif
\ No newline at end of file
diff --git a/Tactical/Tactical.vcproj b/Tactical/Tactical.vcproj
index 23225500..c0e4e798 100644
--- a/Tactical/Tactical.vcproj
+++ b/Tactical/Tactical.vcproj
@@ -5636,6 +5636,10 @@
+
+
diff --git a/Tactical/Tactical_2005Express.vcproj b/Tactical/Tactical_2005Express.vcproj
index acf299cb..76f95819 100644
--- a/Tactical/Tactical_2005Express.vcproj
+++ b/Tactical/Tactical_2005Express.vcproj
@@ -1002,6 +1002,10 @@
RelativePath=".\XML_Sounds.cpp"
>
+
+
diff --git a/Tactical/Weapons.h b/Tactical/Weapons.h
index 4f57af87..f96ae784 100644
--- a/Tactical/Weapons.h
+++ b/Tactical/Weapons.h
@@ -185,6 +185,9 @@ typedef struct
INT16 lockBustingPower;
BOOLEAN tracerEffect;
+ //zilpin: pellet spread patterns externalized in XML
+ INT32 spreadPattern;
+
} AMMOTYPE;
enum
diff --git a/Tactical/XML.h b/Tactical/XML.h
index 90d1ba8b..a232a955 100644
--- a/Tactical/XML.h
+++ b/Tactical/XML.h
@@ -108,9 +108,14 @@ typedef PARSE_STAGE;
// WANNE: Sector loadscreens [2007-05-18]
#define SECTORLOADSCREENSFILENAME "Map\\SectorLoadscreens.xml"
+//zilpin: pellet spread patterns externalized in XML
+#define SPREADPATTERNSFILENAME "SpreadPatterns.xml"
+#define DELIVERYMETHODSFILENAME "Map\\DeliveryMethods.xml"
+
// Dealtar: Shipping destinations and delivery methods
#define SHIPPINGDESTINATIONSFILENAME "Map\\ShippingDestinations.xml"
#define DELIVERYMETHODSFILENAME "Map\\DeliveryMethods.xml"
+
// Gotthard: Laptop Text files [2007-10-16]
#define LAPTOPFLORISTTEXTFILENAME "Laptop\\Florist.xml"
#define LAPTOPFUNERALTEXTFILENAME "Laptop\\Funeral.xml"
@@ -233,6 +238,11 @@ extern BOOLEAN ReadInRoamingInfo(STR filename);
// Dealtar: New shipping system XMLs
extern BOOLEAN ReadInShippingDestinations(STR fileName, BOOLEAN localizedVersion);
extern BOOLEAN ReadInDeliveryMethods(STR fileName);
+
+//zilpin: pellet spread patterns externalized in XML
+extern BOOLEAN ReadInSpreadPatterns(STR fileName);
+extern BOOLEAN WriteSpreadPatterns();
+extern int FindSpreadPatternIndex( const STR strName );
//Gotthard: Laptop Florist Text
extern BOOLEAN ReadInFloristText(STR fileName);
diff --git a/Tactical/XML_AmmoTypes.cpp b/Tactical/XML_AmmoTypes.cpp
index 0dc9ab18..4539e975 100644
--- a/Tactical/XML_AmmoTypes.cpp
+++ b/Tactical/XML_AmmoTypes.cpp
@@ -67,6 +67,8 @@ ammotypeStartElementHandle(void *userData, const XML_Char *name, const XML_Char
strcmp(name, "canGoThrough") == 0 ||
strcmp(name, "standardIssue") == 0 ||
strcmp(name, "numberOfBullets") == 0 ||
+ //zilpin: pellet spread patterns externalized in XML
+ stricmp(name, "spreadPattern") == 0 ||
strcmp(name, "highExplosive") == 0 ||
strcmp(name, "explosionSize") == 0 ||
strcmp(name, "antiTank") == 0 ||
@@ -223,6 +225,12 @@ ammotypeEndElementHandle(void *userData, const XML_Char *name)
pData->curElement = ELEMENT;
pData->curAmmoType.numberOfBullets = (UINT8) atol(pData->szCharData);
}
+ //zilpin: pellet spread patterns externalized in XML
+ else if(stricmp(name, "spreadPattern") == 0)
+ {
+ pData->curElement = ELEMENT;
+ pData->curAmmoType.spreadPattern = FindSpreadPatternIndex( pData->szCharData );
+ }
else if(strcmp(name, "highExplosive") == 0)
{
pData->curElement = ELEMENT;
diff --git a/Tactical/XML_SpreadPatterns.cpp b/Tactical/XML_SpreadPatterns.cpp
new file mode 100644
index 00000000..8e9e161e
--- /dev/null
+++ b/Tactical/XML_SpreadPatterns.cpp
@@ -0,0 +1,400 @@
+//zilpin: pellet spread patterns externalized in XML
+#ifdef PRECOMPILEDHEADERS
+ #include "Tactical All.h"
+#else
+ #include "sgp.h"
+ #include "overhead types.h"
+ #include "Sound Control.h"
+ #include "Soldier Control.h"
+ #include "overhead.h"
+ #include "Event Pump.h"
+ #include "weapons.h"
+ #include "Animation Control.h"
+ #include "sys globals.h"
+ #include "Handle UI.h"
+ #include "Isometric Utils.h"
+ #include "worldman.h"
+ #include "math.h"
+ #include "points.h"
+ #include "ai.h"
+ #include "los.h"
+ #include "renderworld.h"
+ #include "opplist.h"
+ #include "interface.h"
+ #include "message.h"
+ #include "campaign.h"
+ #include "items.h"
+ #include "weapons.h"
+ #include "text.h"
+ #include "Soldier Profile.h"
+ #include "tile animation.h"
+ #include "Dialogue Control.h"
+ #include "SkillCheck.h"
+ #include "explosion control.h"
+ #include "Quests.h"
+ #include "Physics.h"
+ #include "Random.h"
+ #include "Vehicles.h"
+ #include "bullets.h"
+ #include "morale.h"
+ #include "meanwhile.h"
+ #include "SkillCheck.h"
+ #include "gamesettings.h"
+ #include "SaveLoadMap.h"
+ #include "Debug Control.h"
+ #include "expat.h"
+ #include "XML.h"
+#endif
+
+#define LIST_BUFFER_LEN (32)
+#define LIST_BUFFER_SIZE (LIST_BUFFER_LEN * sizeof(t_SpreadPattern))
+#define CALC_NEW_LIST_SIZE(c) ( LIST_BUFFER_SIZE * (1+ ((c) / LIST_BUFFER_LEN)) )
+
+#define ARRAY_BUFFER_LEN (32)
+#define ARRAY_BUFFER_SIZE (ARRAY_BUFFER_LEN * sizeof(DOUBLE))
+#define CALC_NEW_ARRAY_SIZE(c) ( ARRAY_BUFFER_SIZE * (1+ ((c) / ARRAY_BUFFER_LEN)) )
+
+struct
+{
+ PARSE_STAGE curElement;
+
+ CHAR8 szCharData[MAX_CHAR_DATA_LENGTH+1];
+
+ UINT32 currentDepth;
+ UINT32 maxReadDepth;
+}
+typedef spreadpatternParseData;
+
+
+static void XMLCALL
+spreadpatternStartElementHandle(void *userData, const XML_Char *name, const XML_Char **atts)
+{
+ int i;
+ spreadpatternParseData * pData = (spreadpatternParseData *)userData;
+
+ if(pData->currentDepth <= pData->maxReadDepth) //are we reading this element?
+ {
+ if(stricmp(name, "SPREADPATTERNLIST") == 0 && pData->curElement == ELEMENT_NONE)
+ {
+ pData->curElement = ELEMENT_LIST;
+
+ giSpreadPatternCount = 0;
+ gpSpreadPattern = NULL;
+ gpSpreadPattern = (t_SpreadPattern *)MemAlloc( LIST_BUFFER_SIZE );
+ Assert(gpSpreadPattern);
+ memset( gpSpreadPattern, 0, LIST_BUFFER_SIZE );
+
+ pData->maxReadDepth++; //we are not skipping this element
+ }
+ else if(stricmp(name, "SPREADPATTERN") == 0 && pData->curElement == ELEMENT_LIST)
+ {
+ pData->curElement = ELEMENT_LIST;
+
+ giSpreadPatternCount++;
+ if( !(giSpreadPatternCount % LIST_BUFFER_LEN) ){
+ //End of buffer. Expand buffer.
+ gpSpreadPattern = (t_SpreadPattern *)MemRealloc(gpSpreadPattern, CALC_NEW_LIST_SIZE(giSpreadPatternCount) );
+ Assert(gpSpreadPattern);
+ }
+ memset( &(gpSpreadPattern[giSpreadPatternCount-1]) , 0 , sizeof(t_SpreadPattern) );
+ gpSpreadPattern[giSpreadPatternCount-1].iCount = 0;
+ gpSpreadPattern[giSpreadPatternCount-1].xspread = 0.0;
+ gpSpreadPattern[giSpreadPatternCount-1].yspread = 0.0;
+ gpSpreadPattern[giSpreadPatternCount-1].method = giSpreadPatternMethod_Default;
+
+ gpSpreadPattern[giSpreadPatternCount-1].x = (DOUBLE *)MemAlloc( ARRAY_BUFFER_SIZE );
+ Assert( gpSpreadPattern[giSpreadPatternCount-1].x );
+ memset( gpSpreadPattern[giSpreadPatternCount-1].x , 0 , ARRAY_BUFFER_SIZE );
+ gpSpreadPattern[giSpreadPatternCount-1].y = (DOUBLE *)MemAlloc( ARRAY_BUFFER_SIZE );
+ Assert( gpSpreadPattern[giSpreadPatternCount-1].y );
+ memset( gpSpreadPattern[giSpreadPatternCount-1].y , 0 , ARRAY_BUFFER_SIZE );
+
+ pData->maxReadDepth++; //we are not skipping this element
+ }
+ else if(stricmp(name, "p") == 0 )//&& pData->curElement == ELEMENT_LIST)
+ {
+ pData->curElement = ELEMENT;
+ gpSpreadPattern[giSpreadPatternCount-1].iCount++;
+ if( !(gpSpreadPattern[giSpreadPatternCount-1].iCount % ARRAY_BUFFER_LEN ) ){
+ //End of buffer. Expand.
+ gpSpreadPattern[giSpreadPatternCount-1].x = (DOUBLE *)MemRealloc(gpSpreadPattern[giSpreadPatternCount-1].x, CALC_NEW_ARRAY_SIZE(gpSpreadPattern[giSpreadPatternCount-1].iCount) );
+ Assert( gpSpreadPattern[giSpreadPatternCount-1].x );
+ gpSpreadPattern[giSpreadPatternCount-1].y = (DOUBLE *)MemRealloc(gpSpreadPattern[giSpreadPatternCount-1].y, CALC_NEW_ARRAY_SIZE(gpSpreadPattern[giSpreadPatternCount-1].iCount) );
+ Assert( gpSpreadPattern[giSpreadPatternCount-1].y );
+ }
+ gpSpreadPattern[giSpreadPatternCount-1].x[ gpSpreadPattern[giSpreadPatternCount-1].iCount - 1 ] = 0.0;
+ gpSpreadPattern[giSpreadPatternCount-1].y[ gpSpreadPattern[giSpreadPatternCount-1].iCount - 1 ] = 0.0;
+
+ pData->maxReadDepth++; //we are not skipping this element
+ }
+ else if( //pData->curElement == ELEMENT &&
+ ( stricmp(name, "x") == 0
+ || stricmp(name, "y") == 0
+ || stricmp(name, "xspread") == 0
+ || stricmp(name, "yspread") == 0
+ || stricmp(name, "Name") == 0
+ || stricmp(name, "method") == 0
+ ))
+
+ {
+ pData->curElement = ELEMENT_PROPERTY;
+
+ pData->maxReadDepth++; //we are not skipping this element
+ }
+
+ pData->szCharData[0] = '\0';
+ }
+
+ pData->currentDepth++;
+
+}
+
+static void XMLCALL
+spreadpatternCharacterDataHandle(void *userData, const XML_Char *str, int len)
+{
+ spreadpatternParseData * pData = (spreadpatternParseData *)userData;
+
+ if( (pData->currentDepth <= pData->maxReadDepth) &&
+ (strlen(pData->szCharData) < MAX_CHAR_DATA_LENGTH)
+ && pData->curElement == ELEMENT_PROPERTY
+ ){
+ strncat(pData->szCharData,str,__min((unsigned int)len,MAX_CHAR_DATA_LENGTH-strlen(pData->szCharData)));
+ }
+}
+
+
+static void XMLCALL
+spreadpatternEndElementHandle(void *userData, const XML_Char *name)
+{
+ DOUBLE d;
+ int i;
+
+ spreadpatternParseData * pData = (spreadpatternParseData *)userData;
+
+ if(pData->currentDepth <= pData->maxReadDepth) //we're at the end of an element that we've been reading
+ {
+ if(stricmp(name, "SPREADPATTERNLIST") == 0)
+ {
+ pData->curElement = ELEMENT_NONE;
+ }
+ else if(stricmp(name, "SPREADPATTERN") == 0)
+ {
+ pData->curElement = ELEMENT_LIST;
+ }
+ else if(stricmp(name, "p") == 0)
+ {
+ //I would prefer the format to be:
+ //
+ //but it's:
+ // 0.10.10.20.2
+ //because the XML editor will probably have problems with using attributes instead of tags.
+ pData->curElement = ELEMENT_LIST;
+ }
+ else if(stricmp(name, "x") == 0)
+ {
+ pData->curElement = ELEMENT;
+ d = atof(pData->szCharData);
+ gpSpreadPattern[giSpreadPatternCount-1].x[ gpSpreadPattern[giSpreadPatternCount-1].iCount-1 ] = d;
+ }
+ else if(stricmp(name, "y") == 0)
+ {
+ pData->curElement = ELEMENT;
+ d = atof(pData->szCharData);
+ gpSpreadPattern[giSpreadPatternCount-1].y[ gpSpreadPattern[giSpreadPatternCount-1].iCount-1 ] = d;
+ }
+ else if(stricmp(name, "xspread") == 0)
+ {
+ pData->curElement = ELEMENT;
+ d = fabs( atof(pData->szCharData) );
+ gpSpreadPattern[giSpreadPatternCount-1].xspread = d;
+ }
+ else if(stricmp(name, "yspread") == 0)
+ {
+ pData->curElement = ELEMENT;
+ d = fabs( atof(pData->szCharData) );
+ gpSpreadPattern[giSpreadPatternCount-1].yspread = d;
+ }
+ else if(stricmp(name, "Name") == 0)
+ {
+ pData->curElement = ELEMENT;
+ //Trim whitespace.
+ while( pData->szCharData[0] == ' ' ) memmove( pData->szCharData , &pData->szCharData[1] , strlen(&pData->szCharData[1]) );
+ while( strlen(pData->szCharData)>0 && pData->szCharData[ strlen(pData->szCharData)-1 ] == ' ' ) pData->szCharData[ strlen(pData->szCharData)-1 ]=0;
+ //Clean and copy.
+ memset(gpSpreadPattern[giSpreadPatternCount-1].Name,0,SPREADPATTERN_NAME_SIZE);
+ strncpy(gpSpreadPattern[giSpreadPatternCount-1].Name,pData->szCharData,SPREADPATTERN_NAME_SIZE-1);
+ }
+ else if(stricmp(name, "method") == 0)
+ {
+ pData->curElement = ELEMENT;
+ //Trim whitespace.
+ while( pData->szCharData[0] == ' ' ) memmove( pData->szCharData , &pData->szCharData[1] , strlen(&pData->szCharData[1]) );
+ while( strlen(pData->szCharData)>0 && pData->szCharData[ strlen(pData->szCharData)-1 ] == ' ' ) pData->szCharData[ strlen(pData->szCharData)-1 ]=0;
+ //Find it.
+ for(i=0;iszCharData, gSpreadPatternMethodNames[i] ) == 0){
+ gpSpreadPattern[giSpreadPatternCount-1].method = i;
+ break;
+ }
+ }
+ }
+
+ pData->maxReadDepth--;
+ }
+
+ pData->currentDepth--;
+
+}
+
+
+
+
+BOOLEAN ReadInSpreadPatterns(STR fileName)
+{
+ HWFILE hFile;
+ UINT32 uiBytesRead;
+ UINT32 uiFSize;
+ CHAR8 * lpcBuffer;
+ XML_Parser parser = XML_ParserCreate(NULL);
+
+ spreadpatternParseData pData;
+
+ DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "Loading SpreadPatterns.xml" );
+
+ // Open spread pattern file
+ hFile = FileOpen( fileName, FILE_ACCESS_READ, FALSE );
+ if ( !hFile )
+ return( FALSE );
+
+ uiFSize = FileGetSize(hFile);
+ lpcBuffer = (CHAR8 *) MemAlloc(uiFSize+1);
+
+ //Read in block
+ if ( !FileRead( hFile, lpcBuffer, uiFSize, &uiBytesRead ) )
+ {
+ MemFree(lpcBuffer);
+ return( FALSE );
+ }
+
+ lpcBuffer[uiFSize] = 0; //add a null terminator
+
+ FileClose( hFile );
+
+
+ XML_SetElementHandler(parser, spreadpatternStartElementHandle, spreadpatternEndElementHandle);
+ XML_SetCharacterDataHandler(parser, spreadpatternCharacterDataHandle);
+
+
+ memset(&pData,0,sizeof(pData));
+ //pData.maxArraySize = MAXITEMS;
+
+ XML_SetUserData(parser, &pData);
+
+
+ if(!XML_Parse(parser, lpcBuffer, uiFSize, TRUE))
+ {
+ CHAR8 errorBuf[511];
+
+ sprintf(errorBuf, "XML Parser Error in SpreadPatterns.xml: %s at line %d", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser));
+ LiveMessage(errorBuf);
+
+ MemFree(lpcBuffer);
+ return FALSE;
+ }
+
+ MemFree(lpcBuffer);
+
+
+ XML_ParserFree(parser);
+
+ //Output for debugging
+ //WriteSpreadPatterns();
+
+ return( TRUE );
+}
+
+
+BOOLEAN WriteSpreadPatterns()
+{
+ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"WriteSpreadPatterns");
+ HWFILE hFile;
+
+ //Debug code; make sure that what we got from the file is the same as what's there
+ // Open a new file
+ hFile = FileOpen( "TableData\\SpreadPatterns_out.xml", FILE_ACCESS_WRITE | FILE_CREATE_ALWAYS, FALSE );
+ if ( !hFile ) return( FALSE );
+
+
+ {
+ UINT32 i,j;
+
+ FilePrintf(hFile,"\r\n");
+ FilePrintf(hFile,"\r\n", giSpreadPatternCount );
+ for(i = 0 ; i < giSpreadPatternCount; i++)
+ {
+ FilePrintf(hFile,"\t\r\n");
+ FilePrintf(hFile,"\t\r\n",i);
+ FilePrintf(hFile,"\t%s\r\n",gpSpreadPattern[i].Name);
+ FilePrintf(hFile,"\t%s \r\n",gSpreadPatternMethodNames[gpSpreadPattern[i].method], gpSpreadPattern[i].method);
+ FilePrintf(hFile,"\t%2.4f",gpSpreadPattern[i].xspread);
+ FilePrintf(hFile," %2.4f\r\n",gpSpreadPattern[i].yspread);
+ FilePrintf(hFile,"\t\r\n",gpSpreadPattern[i].iCount);
+ for(j=0;j");
+ FilePrintf(hFile,"%2.4f",gpSpreadPattern[i].x[j]);
+ FilePrintf(hFile,"%2.4f",gpSpreadPattern[i].y[j]);
+ FilePrintf(hFile,"
\r\n");
+ }
+ FilePrintf(hFile,"\t\r\n");
+ }
+ FilePrintf(hFile,"\r\n");
+ FilePrintf(hFile,"\r\n");
+ }
+
+ FileClose( hFile );
+
+ return( TRUE );
+}
+
+//zilpin: pellet spread patterns externalized in XML
+//This function is used primarily by XML loaders for Items and AmmoTypes.
+//Given a string will determine if it is a SpreadPattern name or literal index,
+//and return an integer index of the spreadpattern in the gpSpreadPattern array.
+//If the name is not found, or the index was out of range, then return 0.
+int FindSpreadPatternIndex( const STR strName )
+{
+ int i=0,n=0;
+ char str[SPREADPATTERN_NAME_SIZE]={};
+
+ //Copy search name sans leading and trailing space.
+ strncpy(str,strName,SPREADPATTERN_NAME_SIZE-1);
+ str[SPREADPATTERN_NAME_SIZE-1]=0;
+ while( str[0] == ' ' ) memmove( str , &str[1] , strlen(&str[1]) );
+ while( strlen(str)>0 && str[ strlen(str)-1 ] == ' ' ) str[ strlen(str)-1 ]=0;
+
+ //Named pattern or raw index?
+ for(i=0;i=giSpreadPatternCount )
+ n=0;
+ }
+
+ return n;
+}
diff --git a/Utils/XML_Items.cpp b/Utils/XML_Items.cpp
index 0ea6e39c..b7b4ab50 100644
--- a/Utils/XML_Items.cpp
+++ b/Utils/XML_Items.cpp
@@ -213,7 +213,8 @@ itemStartElementHandle(void *userData, const XML_Char *name, const XML_Char **at
strcmp(name, "StealthBonus") == 0 ||
strcmp(name, "SciFi") == 0 ||
strcmp(name, "NewInv") == 0 ||
-
+ //zilpin: pellet spread patterns externalized in XML
+ stricmp(name, "spreadPattern") == 0 ||
strcmp(name, "fFlags") == 0 ))
{
pData->curElement = ELEMENT_PROPERTY;
@@ -1026,6 +1027,12 @@ itemEndElementHandle(void *userData, const XML_Char *name)
pData->curElement = ELEMENT;
pData->curItem.bestlaserrange = (INT16) atol(pData->szCharData);
}
+ //zilpin: pellet spread patterns externalized in XML
+ else if(stricmp(name, "spreadPattern") == 0)
+ {
+ pData->curElement = ELEMENT;
+ pData->curItem.spreadPattern = FindSpreadPatternIndex( pData->szCharData );
+ }
pData->maxReadDepth--;
}