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.1

0.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--; }