Spread Patterns MOD (by Zilpin)

* You also have to update GameDir folder *

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@3009 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Wanne
2009-06-15 18:00:07 +00:00
parent f0424c1ef4
commit 5cd81280bc
11 changed files with 768 additions and 32 deletions
+8
View File
@@ -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);
+4
View File
@@ -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
+290 -31
View File
@@ -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; i<MAX_ATTACHMENTS; i++)
if (pObj->usAttachItem[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;
+29
View File
@@ -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
+4
View File
@@ -5636,6 +5636,10 @@
<File
RelativePath=".\XML_Sounds.cpp">
</File>
<File
RelativePath=".\XML_SpreadPatterns.cpp"
>
</File>
<File
RelativePath=".\XML_TonyInventory.cpp">
</File>
+4
View File
@@ -1002,6 +1002,10 @@
RelativePath=".\XML_Sounds.cpp"
>
</File>
<File
RelativePath=".\XML_SpreadPatterns.cpp"
>
</File>
<File
RelativePath=".\XML_TonyInventory.cpp"
>
+3
View File
@@ -185,6 +185,9 @@ typedef struct
INT16 lockBustingPower;
BOOLEAN tracerEffect;
//zilpin: pellet spread patterns externalized in XML
INT32 spreadPattern;
} AMMOTYPE;
enum
+10
View File
@@ -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);
+8
View File
@@ -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;
+400
View File
@@ -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:
// <SPREADPATTERN><p x="0.1" y="0.1"/><p x="0.2" y="0.2"/></SPREADPATTERN>
//but it's:
// <SPREADPATTERN><p/><x>0.1</x><y>0.1</y><p/><x>0.2</x><y>0.2</y></SPREADPATTERN>
//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;i<SPREADPATTERNMETHOD_COUNT;++i){
if(stricmp(pData->szCharData, 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,"<SPREADPATTERNLIST>\r\n");
FilePrintf(hFile,"<!-- giSpreadPatternCount = %d -->\r\n", giSpreadPatternCount );
for(i = 0 ; i < giSpreadPatternCount; i++)
{
FilePrintf(hFile,"\t<SPREADPATTERN>\r\n");
FilePrintf(hFile,"\t<!-- index = %d -->\r\n",i);
FilePrintf(hFile,"\t<Name>%s</Name>\r\n",gpSpreadPattern[i].Name);
FilePrintf(hFile,"\t<method>%s</method> <!-- %d -->\r\n",gSpreadPatternMethodNames[gpSpreadPattern[i].method], gpSpreadPattern[i].method);
FilePrintf(hFile,"\t<xspread>%2.4f</xspread>",gpSpreadPattern[i].xspread);
FilePrintf(hFile," <yspread>%2.4f</yspread>\r\n",gpSpreadPattern[i].yspread);
FilePrintf(hFile,"\t<!-- p iCount = %d -->\r\n",gpSpreadPattern[i].iCount);
for(j=0;j<gpSpreadPattern[i].iCount;j++)
{
FilePrintf(hFile,"\t\t<p>");
FilePrintf(hFile,"<x>%2.4f</x>",gpSpreadPattern[i].x[j]);
FilePrintf(hFile,"<y>%2.4f</y>",gpSpreadPattern[i].y[j]);
FilePrintf(hFile,"</p>\r\n");
}
FilePrintf(hFile,"\t</SPREADPATTERN>\r\n");
}
FilePrintf(hFile,"</SPREADPATTERNLIST>\r\n");
FilePrintf(hFile,"<!--End Of File-->\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<strlen(str);i++) if( !isdigit(str[i]) ) break;
if(i<strlen(str))
{
//Holds named SpreadPattern. Search.
for(i=0;i<giSpreadPatternCount;i++)
{
if( !stricmp(str, gpSpreadPattern[i].Name) )
{
//Match! Assign and return.
n=i;
break;
}
}
}else{
//Holds numeric SpreadPattern.
//Validate.
n = atol(str);
if( n<0 || n>=giSpreadPatternCount )
n=0;
}
return n;
}
+8 -1
View File
@@ -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--;
}