#include #include #include "actor.h" #include "debug4g.h" #include "engine.h" #include "trig.h" #include "gameutil.h" #include "misc.h" #include "db.h" #include "multi.h" #include "names.h" #include "screen.h" #include "sectorfx.h" #include "triggers.h" #include "error.h" #include "globals.h" #include "seq.h" #include "eventq.h" #include "dude.h" #include "ai.h" #include "view.h" #include "warp.h" #include "tile.h" #include "player.h" #include "options.h" #include "sfx.h" #include "sound.h" // can be removed once sfx complete #define kMaxSpareSprites 50 SPRITEHIT gSpriteHit[kMaxXSprites]; static void MakeSplash( SPRITE *pSprite, XSPRITE *pXSprite ); static void FireballCallback( int /* type */, int nXIndex ); //static void FlareCallback( int /* type */, int nXIndex ); struct VECTORDATA { DAMAGE_TYPE damageType; int damageValue; int maxDist; struct { int nEffect; int nSoundId; } impact[kSurfMax]; }; static VECTORDATA gVectorData[kVectorMax] = { // kVectorTine { kDamageStab, 4, M2X(2.25), { { -1, -1 }, // kSurfNone { ET_Ricochet1, kSfxForkStone }, // kSurfStone { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal { ET_Ricochet2, kSfxForkWood }, // kSurfWood { ET_Squib1, kSfxForkFlesh }, // kSurfFlesh { ET_Splash2, -1 }, // kSurfWater { ET_Ricochet2, kSfxForkWood }, // kSurfDirt { ET_Ricochet2, kSfxForkWood }, // kSurfClay { -1, -1 }, // kSurfSnow { -1, -1 }, // kSurfIce { -1, -1 }, // kSurfLeaves { -1, -1 }, // kSurfCloth { -1, kSfxForkWood }, // kSurfPlant { -1, -1 }, // kSurfGoo { -1, -1 }, // kSurfLava } }, // kVectorShell { kDamageBullet, 4, 0, { { -1, -1 }, // kSurfNone { ET_Ricochet1, -1 }, // kSurfStone { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal { ET_Ricochet2, -1 }, // kSurfWood { ET_Squib1, -1 }, // kSurfFlesh { ET_Splash2, -1 }, // kSurfWater { ET_Ricochet2, -1 }, // kSurfDirt { ET_Ricochet2, -1 }, // kSurfClay { -1, -1 }, // kSurfSnow { -1, -1 }, // kSurfIce { -1, -1 }, // kSurfLeaves { -1, -1 }, // kSurfCloth { -1, -1 }, // kSurfPlant { -1, -1 }, // kSurfGoo { -1, -1 }, // kSurfLava } }, // kVectorBullet { kDamageBullet, 4, 0, { { -1, -1 }, // kSurfNone { ET_Ricochet1, kSfxForkStone }, // kSurfStone { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal { ET_Ricochet2, kSfxForkWood }, // kSurfWood { ET_Squib1, kSfxForkFlesh }, // kSurfFlesh { ET_Splash2, -1 }, // kSurfWater { ET_Ricochet2, kSfxForkWood }, // kSurfDirt { ET_Ricochet2, kSfxForkWood }, // kSurfClay { -1, -1 }, // kSurfSnow { -1, -1 }, // kSurfIce { -1, -1 }, // kSurfLeaves { -1, -1 }, // kSurfCloth { -1, kSfxForkWood }, // kSurfPlant { -1, -1 }, // kSurfGoo { -1, -1 }, // kSurfLava } }, // kVectorBulletAP { kDamageBullet, 4, 0, { { -1, -1 }, // kSurfNone { ET_Ricochet1, kSfxForkStone }, // kSurfStone { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal { ET_Ricochet2, kSfxForkWood }, // kSurfWood { ET_Squib1, kSfxForkFlesh }, // kSurfFlesh { ET_Splash2, -1 }, // kSurfWater { ET_Ricochet2, kSfxForkWood }, // kSurfDirt { ET_Ricochet2, kSfxForkWood }, // kSurfClay { -1, -1 }, // kSurfSnow { -1, -1 }, // kSurfIce { -1, -1 }, // kSurfLeaves { -1, -1 }, // kSurfCloth { -1, kSfxForkWood }, // kSurfPlant { -1, -1 }, // kSurfGoo { -1, -1 }, // kSurfLava } }, // kVectorAxe { kDamageStab, 20, M2X(2.0), { { -1, -1 }, // kSurfNone { ET_Ricochet1, kSfxForkStone }, // kSurfStone { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal { ET_Ricochet2, kSfxForkWood }, // kSurfWood { ET_Squib1, kSfxForkFlesh }, // kSurfFlesh { ET_Splash2, -1 }, // kSurfWater { ET_Ricochet2, kSfxForkWood }, // kSurfDirt { ET_Ricochet2, kSfxForkWood }, // kSurfClay { -1, -1 }, // kSurfSnow { -1, -1 }, // kSurfIce { -1, -1 }, // kSurfLeaves { -1, -1 }, // kSurfCloth { -1, kSfxForkWood }, // kSurfPlant { -1, -1 }, // kSurfGoo { -1, -1 }, // kSurfLava } }, // kVectorCleaver { kDamageStab, 10, M2X(2.0), { { -1, -1 }, // kSurfNone { ET_Ricochet1, kSfxForkStone }, // kSurfStone { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal { ET_Ricochet2, kSfxForkWood }, // kSurfWood { ET_Squib1, kSfxForkFlesh }, // kSurfFlesh { ET_Splash2, -1 }, // kSurfWater { ET_Ricochet2, kSfxForkWood }, // kSurfDirt { ET_Ricochet2, kSfxForkWood }, // kSurfClay { -1, -1 }, // kSurfSnow { -1, -1 }, // kSurfIce { -1, -1 }, // kSurfLeaves { -1, -1 }, // kSurfCloth { -1, kSfxForkWood }, // kSurfPlant { -1, -1 }, // kSurfGoo { -1, -1 }, // kSurfLava } }, // kVectorClaw { kDamageStab, 20, M2X(2.0), { { -1, -1 }, // kSurfNone { ET_Ricochet1, kSfxForkStone }, // kSurfStone { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal { ET_Ricochet2, kSfxForkWood }, // kSurfWood { ET_Squib1, kSfxForkFlesh }, // kSurfFlesh { ET_Splash2, -1 }, // kSurfWater { ET_Ricochet2, kSfxForkWood }, // kSurfDirt { ET_Ricochet2, kSfxForkWood }, // kSurfClay { -1, -1 }, // kSurfSnow { -1, -1 }, // kSurfIce { -1, -1 }, // kSurfLeaves { -1, -1 }, // kSurfCloth { -1, kSfxForkWood }, // kSurfPlant { -1, -1 }, // kSurfGoo { -1, -1 }, // kSurfLava } }, // kVectorHoundBite { kDamageStab, 10, M2X(1.2), { { -1, -1 }, // kSurfNone { ET_Ricochet1, kSfxForkStone }, // kSurfStone { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal { ET_Ricochet2, kSfxForkWood }, // kSurfWood { ET_Squib1, kSfxForkFlesh }, // kSurfFlesh { ET_Splash2, -1 }, // kSurfWater { ET_Ricochet2, kSfxForkWood }, // kSurfDirt { ET_Ricochet2, kSfxForkWood }, // kSurfClay { -1, -1 }, // kSurfSnow { -1, -1 }, // kSurfIce { -1, -1 }, // kSurfLeaves { -1, -1 }, // kSurfCloth { -1, kSfxForkWood }, // kSurfPlant { -1, -1 }, // kSurfGoo { -1, -1 }, // kSurfLava } }, // kVectorRatBite { kDamageStab, 4, M2X(1.8), { { -1, -1 }, // kSurfNone { ET_Ricochet1, kSfxForkStone }, // kSurfStone { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal { ET_Ricochet2, kSfxForkWood }, // kSurfWood { ET_Squib1, kSfxForkFlesh }, // kSurfFlesh { ET_Splash2, -1 }, // kSurfWater { ET_Ricochet2, kSfxForkWood }, // kSurfDirt { ET_Ricochet2, kSfxForkWood }, // kSurfClay { -1, -1 }, // kSurfSnow { -1, -1 }, // kSurfIce { -1, -1 }, // kSurfLeaves { -1, -1 }, // kSurfCloth { -1, kSfxForkWood }, // kSurfPlant { -1, -1 }, // kSurfGoo { -1, -1 }, // kSurfLava } }, // kVectorSpiderBite { kDamageStab, 8, M2X(1.2), { { -1, -1 }, // kSurfNone { ET_Ricochet1, kSfxForkStone }, // kSurfStone { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal { ET_Ricochet2, kSfxForkWood }, // kSurfWood { ET_Squib1, kSfxForkFlesh }, // kSurfFlesh { ET_Splash2, -1 }, // kSurfWater { ET_Ricochet2, kSfxForkWood }, // kSurfDirt { ET_Ricochet2, kSfxForkWood }, // kSurfClay { -1, -1 }, // kSurfSnow { -1, -1 }, // kSurfIce { -1, -1 }, // kSurfLeaves { -1, -1 }, // kSurfCloth { -1, kSfxForkWood }, // kSurfPlant { -1, -1 }, // kSurfGoo { -1, -1 }, // kSurfLava } }, }; ITEMDATA gItemData[ kMaxItemTypes ] = { /* {cstat, picnum, sh, pal,xr, yr, stat, type, flags}, */ {0, kPicKey1, -8, 0, 32, 32, kStatItem, kItemKey1, 0}, {0, kPicKey2, -8, 0, 32, 32, kStatItem, kItemKey2, 0}, {0, kPicKey3, -8, 0, 32, 32, kStatItem, kItemKey3, 0}, {0, kPicKey4, -8, 0, 32, 32, kStatItem, kItemKey4, 0}, {0, kPicKey5, -8, 0, 32, 32, kStatItem, kItemKey5, 0}, {0, kPicKey6, -8, 0, 32, 32, kStatItem, kItemKey6, 0}, {0, -1, -8, 0, -1, -1, kStatItem, kItemKey7, 0}, {0, kPicDocBag, -8, 0, 48, 48, kStatItem, kItemDoctorBag, 0}, {0, kPicMedPouch, -8, 0, 40, 40, kStatItem, kItemMedPouch, 0}, {0, kPicEssence, -8, 0, 40, 40, kStatItem, kItemLifeEssence, 0}, {0, kAnmLifeSeed, -8, 0, 40, 40, kStatItem, kItemLifeSeed, 0}, {0, kPicPotion, -8, 0, 40, 40, kStatItem, kItemPotion1, 0}, {0, kAnmFeather, -8, 0, 40, 40, kStatItem, kItemFeatherFall, 0}, {0, kAnmInviso, -8, 0, 40, 40, kStatItem, kItemLtdInvisibility, 0}, {0, kPicInvulnerable, -8, 0, 40, 40, kStatItem, kItemInvulnerability, 0}, {0, kPicJumpBoots, -8, 0, 40, 40, kStatItem, kItemJumpBoots, 0}, {0, kPicRavenFlight, -8, 0, 40, 40, kStatItem, kItemRavenFlight, 0}, {0, kPicGunsAkimbo, -8, 0, 40, 40, kStatItem, kItemGunsAkimbo, 0}, {0, kPicDivingSuit, -8, 0, 80, 64, kStatItem, kItemDivingSuit, 0}, {0, kPicGasMask, -8, 0, 40, 40, kStatItem, kItemGasMask, 0}, {0, kAnmClone, -8, 0, 40, 40, kStatItem, kItemClone, 0}, {0, kPicCrystalBall, -8, 0, 40, 40, kStatItem, kItemCrystalBall, 0}, {0, kPicDecoy, -8, 0, 40, 40, kStatItem, kItemDecoy, 0}, {0, kAnmDoppleganger, -8, 0, 40, 40, kStatItem, kItemDoppleganger, 0}, {0, kAnmReflectShots, -8, 0, 40, 40, kStatItem, kItemReflectiveShots, 0}, {0, kPicRoseGlasses, -8, 0, 40, 40, kStatItem, kItemRoseGlasses, 0}, {0, kAnmCloakNDagger, -8, 0, 64, 64, kStatItem, kItemShadowCloak, 0}, {0, kPicShroom1, -8, 0, 48, 48, kStatItem, kItemShroomRage, 0}, {0, kPicShroom2, -8, 0, 48, 48, kStatItem, kItemShroomDelirium, 0}, {0, kPicShroom3, -8, 0, 48, 48, kStatItem, kItemShroomGrow, 0}, {0, kPicShroom4, -8, 0, 48, 48, kStatItem, kItemShroomShrink, 0}, {0, kPicDeathMask, -8, 0, 40, 40, kStatItem, kItemDeathMask, 0}, {0, kPicGoblet, -8, 0, 40, 40, kStatItem, kItemWineGoblet, 0}, {0, kPicBottle1, -8, 0, 40, 40, kStatItem, kItemWineBottle, 0}, {0, kPicSkullGrail, -8, 0, 40, 40, kStatItem, kItemSkullGrail, 0}, {0, kPicSilverGrail, -8, 0, 40, 40, kStatItem, kItemSilverGrail, 0}, {0, kPicTome1, -8, 0, 40, 40, kStatItem, kItemTome, 0}, {0, kPicBlackChest, -8, 0, 40, 40, kStatItem, kItemBlackChest, 0}, {0, kPicWoodChest, -8, 0, 40, 40, kStatItem, kItemWoodenChest, 0}, {0, kPicAsbestosSuit, -8, 0, 80, 64, kStatItem, kItemAsbestosArmor, 0}, }; AMMOITEMDATA gAmmoItemData[kAmmoItemMax - kAmmoItemBase] = { /* {cstat, picnum, sh, pal,xr, yr, flags, count, ammoType, weaponType}, */ {0, kPicSprayCan, -8, 0, 40, 40, 0, kTimerRate*8, kAmmoSprayCan, kWeaponSprayCan}, {0, kPicTNTStick, -8, 0, 48, 48, 0, 1, kAmmoTNTStick, kWeaponTNT}, {0, kPicTNTPak, -8, 0, 48, 48, 0, 7, kAmmoTNTStick, kWeaponTNT}, {0, kPicTNTBox, -8, 0, 48, 48, 0, 14, kAmmoTNTStick, kWeaponTNT}, {0, kPicTNTProx, -8, 0, 48, 48, 0, 1, kAmmoTNTProximity, kWeaponTNT}, {0, kPicTNTRemote, -8, 0, 48, 48, 0, 1, kAmmoTNTRemote, kWeaponTNT}, {0, kPicTNTTimer, -8, 0, 48, 48, 0, 1, kAmmoTNTStick, kWeaponTNT}, {0, kPicShotShells, -8, 0, 48, 48, 0, 4, kAmmoShell}, {0, kPicShellBox, -8, 0, 48, 48, 0, 16, kAmmoShell}, {0, kPicBullets, -8, 0, 48, 48, 0, 8, kAmmoBullet}, {0, kPicBulletBox, -8, 0, 48, 48, 0, 50, kAmmoBullet}, {0, kPicBulletBoxAP, -8, 0, 48, 48, 0, 50, kAmmoBulletAP}, {0, kPicTommyDrum, -8, 0, 48, 48, 0, 100, kAmmoBullet}, {0, kPicSpear, -8, 0, 48, 48, 0, 1, kAmmoSpear}, {0, kPicSpears, -8, 0, 48, 48, 0, 6, kAmmoSpear}, {0, kPicSpearExplode, -8, 0, 48, 48, 0, 6, kAmmoSpearXP}, {0, kPicFlares, -8, 0, 48, 48, 0, 8, kAmmoFlare}, {0, kPicFlareHE, -8, 0, 48, 48, 0, 8, kAmmoFlare}, {0, kPicFlareBurst, -8, 0, 48, 48, 0, 8, kAmmoFlareSB}, }; WEAPONITEMDATA gWeaponItemData[ kWeaponItemMax - kWeaponItemBase ] = { /* {cstat, picnum, sh, pal,xr, yr, flags, weaponType, ammoType, count }, */ { 0, -1, 0, 0, 0, 0, 0, kWeaponNone, kAmmoNone, 0 }, { 0, kPicShotgun, -8, 0, 48, 48, 0, kWeaponShotgun, kAmmoShell, 8 }, { 0, kPicTommyGun, -8, 0, 48, 48, 0, kWeaponTommy, kAmmoBullet, 50 }, { 0, kPicFlareGun, -8, 0, 48, 48, 0, kWeaponFlare, kAmmoFlare, 8 }, { 0, kPicVoodooDoll, -8, 0, 48, 48, 0, kWeaponVoodoo, kAmmoVoodoo, 1 }, { 0, kPicSpearGun, -8, 0, 48, 48, 0, kWeaponSpear, kAmmoSpear, 6 }, { 0, kPicShadowGun, -8, 0, 48, 48, 0, kWeaponShadow, kAmmoNone, 0 }, { 0, -1, 0, 0, 0, 0, 0, kWeaponPitchfork, kAmmoNone, 0 }, // don't actually pick this up { 0, kPicSprayCan, -8, 0, 48, 48, 0, kWeaponSprayCan, kAmmoSprayCan, kTimerRate*8}, { 0, kPicTNTStick, -8, 0, 48, 48, 0, kWeaponTNT, kAmmoTNTStick, 1}, }; struct MissileType { short picnum; int velocity; int angleOfs; uchar xrepeat; uchar yrepeat; char shade; } missileInfo[] = { { kAnmButcherKnife, (M2X(14.0) << 4) / kTimerRate, kAngle90, 40, 40, -16 }, // kMissileButcherKnife { kAnmFlare, (M2X(20.0) << 4) / kTimerRate, 0, 32, 32, -128 }, // kMissileFlare { kAnmFlare, (M2X(20.0) << 4) / kTimerRate, 0, 32, 32, -128 }, // kMissileExplodingFlare { kAnmFlare, (M2X(20.0) << 4) / kTimerRate, 0, 32, 32, -128 }, // kMissileStarburstFlare { 0, (M2X(4.0) << 4) / kTimerRate, 0, 24, 24, -128 }, // kMissileSprayFlame { 0, (M2X(16.0) << 4) / kTimerRate, 0, 32, 32, -128 }, // kMissileFireball { kAnmSpear, (M2X(16.0) << 4) / kTimerRate, kAngle270, 64, 64, -8 }, // kMissileSpear // 18.0 { kAnmEctoSkull, (M2X(16.0) << 4) / kTimerRate, 0, 32, 32, -24 }, // kMissileEctoSkull { 0, (M2X(6.0) << 4) / kTimerRate, 0, 24, 24, -128 }, // kMissileHoundFire { 0, (M2X(12.0) << 4) / kTimerRate, 0, 8, 8, 0 }, // kMissileGreenPuke { 0, (M2X(12.0) << 4) / kTimerRate, 0, 8, 8, 0 }, // kMissileRedPuke }; static int dragTable[] = { 0, 0x0C00, // kDepthTread 0x1A00, // kDepthWade 0x1A00, // kDepthSwim }; // miscellaneous effects enum { kSeqSprayFlame1 = 0, kSeqSprayFlame2, kSeqSkull, kSeqExplodeC1L, // large concussion on ground kSeqExplodeC1M, // medium concussion on ground kSeqExplodeC1S, // small concussion on ground kSeqExplodeC2L, // large concussion in air kSeqExplodeC2M, // medium concussion in air kSeqExplodeC2S, // small concussion in air kSeqExplodeC2T, // tiny concussion in air kSeqSplash1, kSeqSplash2, kSeqSplash3, // blood spash kSeqRicochet1, kSeqGoreWing, kSeqGoreHead, kSeqBarrel, kSeqBloodPool, kSeqRespawn, kSeqFlareSmoke, kSeqSquib1, kSeqFluorescentLight, kSeqClearGlass, kSeqStainedGlass, kSeqWeb, kSeqBeam, kSeqVase1, kSeqVase2, kSeqZombieBones, kSeqSkullExplode, kSeqMetalGrate1, kSeqFireball, kSeqBoneBreak, kSeqBurningTree1, kSeqBurningTree2, kSeqHoundFire, kSeqMGunDead, kSeqRicochet2, kSeqEffectMax, }; struct THINGINFO { short startHealth; short mass; // in KG char clipdist; ushort flags; int damageShift[kDamageMax]; // use to indicate resistance to damage types }; static THINGINFO thingInfo[kThingMax - kThingBase] = { { // kThingTNTBarrel 40, 150, 32, kAttrMove | kAttrGravity, { kNoDamage, // kDamagePummel 0, // kDamageFall 0, // kDamageBurn 0, // kDamageBullet 1, // kDamageStab 0, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingTNTProxArmed 5, 5, 16, kAttrMove | kAttrGravity, { kNoDamage, // kDamagePummel kNoDamage, // kDamageFall 0, // kDamageBurn 0, // kDamageBullet kNoDamage, // kDamageStab 0, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingTNTRemArmed 5, 5, 16, kAttrMove | kAttrGravity, { kNoDamage, // kDamagePummel kNoDamage, // kDamageFall 0, // kDamageBurn 0, // kDamageBullet kNoDamage, // kDamageStab 0, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingBlueVase 1, 20, 32, kAttrMove | kAttrGravity, { kNoDamage, // kDamagePummel 0, // kDamageFall kNoDamage, // kDamageBurn 0, // kDamageBullet 0, // kDamageStab 0, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingBrownVase 1, 150, 32, kAttrMove | kAttrGravity, { kNoDamage, // kDamagePummel 0, // kDamageFall kNoDamage, // kDamageBurn 0, // kDamageBullet 0, // kDamageStab 0, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingCrateFace 10, 0, 0, 0, { kNoDamage, // kDamagePummel kNoDamage, // kDamageFall kNoDamage, // kDamageBurn kNoDamage, // kDamageBullet kNoDamage, // kDamageStab 0, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingClearGlass 1, 0, 0, 0, { 0, // kDamagePummel 0, // kDamageFall kNoDamage, // kDamageBurn 0, // kDamageBullet 0, // kDamageStab 0, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingFluorescent 1, 0, 0, 0, { 0, // kDamagePummel kNoDamage, // kDamageFall kNoDamage, // kDamageBurn 0, // kDamageBullet 0, // kDamageStab 0, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingWallCrack 8, 0, 0, 0, { kNoDamage, // kDamagePummel kNoDamage, // kDamageFall kNoDamage, // kDamageBurn kNoDamage, // kDamageBullet kNoDamage, // kDamageStab 0, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingWoodBeam 8, 0, 0, 0, { 0, // kDamagePummel kNoDamage, // kDamageFall kNoDamage, // kDamageBurn 0, // kDamageBullet 0, // kDamageStab 0, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingWeb 4, 0, 0, 0, { 0, // kDamagePummel kNoDamage, // kDamageFall 0, // kDamageBurn 1, // kDamageBullet 0, // kDamageStab 0, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingMetalGrate1 20, 0, 0, 0, { 3, // kDamagePummel kNoDamage, // kDamageFall kNoDamage, // kDamageBurn 2, // kDamageBullet 4, // kDamageStab 1, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingBurnableTree 1, 0, 0, 0, { kNoDamage, // kDamagePummel kNoDamage, // kDamageFall 0, // kDamageBurn kNoDamage, // kDamageBullet kNoDamage, // kDamageStab kNoDamage, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown 0, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingMachineGun 50, 0, 0, 0, { kNoDamage, // kDamagePummel kNoDamage, // kDamageFall kNoDamage, // kDamageBurn 2, // kDamageBullet 2, // kDamageStab 1, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingTNTStick 5, 2, 16, kAttrMove | kAttrGravity, { 0, // kDamagePummel kNoDamage, // kDamageFall 0, // kDamageBurn 0, // kDamageBullet 0, // kDamageStab 0, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingTNTBundle 5, 14, 16, kAttrMove | kAttrGravity, { 0, // kDamagePummel kNoDamage, // kDamageFall 0, // kDamageBurn 0, // kDamageBullet 0, // kDamageStab 0, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingBoneClub 5, 6, 16, kAttrMove | kAttrGravity, { kNoDamage, // kDamagePummel kNoDamage, // kDamageFall kNoDamage, // kDamageBurn kNoDamage, // kDamageBullet kNoDamage, // kDamageStab kNoDamage, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingZombieBones 8, 3, 16, kAttrMove | kAttrGravity, { 0, // kDamagePummel 0, // kDamageFall kNoDamage, // kDamageBurn 0, // kDamageBullet 0, // kDamageStab 0, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingWaterDrip 0, // startHealth 1, // mass 1, // clipDist kAttrGravity, { kNoDamage, // kDamagePummel kNoDamage, // kDamageFall kNoDamage, // kDamageBurn kNoDamage, // kDamageBullet kNoDamage, // kDamageStab kNoDamage, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingBloodDrip 0, // startHealth 1, // mass 1, // clipDist kAttrGravity, { kNoDamage, // kDamagePummel kNoDamage, // kDamageFall kNoDamage, // kDamageBurn kNoDamage, // kDamageBullet kNoDamage, // kDamageStab kNoDamage, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingBubble 0, // startHealth -1, // mass 1, // clipDist kAttrMove, { kNoDamage, // kDamagePummel kNoDamage, // kDamageFall kNoDamage, // kDamageBurn kNoDamage, // kDamageBullet kNoDamage, // kDamageStab kNoDamage, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingBubbles 0, // startHealth -1, // mass 1, // clipDist kAttrMove, { kNoDamage, // kDamagePummel kNoDamage, // kDamageFall kNoDamage, // kDamageBurn kNoDamage, // kDamageBullet kNoDamage, // kDamageStab kNoDamage, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, { // kThingGibSmall 0, // startHealth 2, // mass 4, // clipDist kAttrMove | kAttrGravity, { kNoDamage, // kDamagePummel kNoDamage, // kDamageFall kNoDamage, // kDamageBurn kNoDamage, // kDamageBullet kNoDamage, // kDamageStab kNoDamage, // kDamageExplode kNoDamage, // kDamageGas kNoDamage, // kDamageDrown kNoDamage, // kDamageSpirit kNoDamage, // kDamageVoodoo }, }, }; void actAllocateSpares( void ) { /* dprintf("Creating spare sprites\n"); for (int i = 0; i < kMaxSpareSprites; i++) { int nSprite = insertsprite( 0, kStatSpares ); dassert(nSprite != -1); dbInsertXSprite(nSprite); sprite[nSprite].cstat |= kSpriteInvisible; } */ } /******************************************************************************* FUNCTION: actInit() DESCRIPTION: Initialize the actor subsystem. Locks all sequences, and preloads all tiles for sequences used in the map. NOTES: *******************************************************************************/ void actInit( void ) { int nSprite; BOOL used[kDudeMax - kDudeBase]; memset(used, FALSE, sizeof(used)); // allocate sprites to use for effects actAllocateSpares(); // see which dudes are present for (nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; if ( pSprite->type < kDudeBase || pSprite->type >= kDudeMax ) { dprintf("ERROR IN SPRITE %i\n", nSprite); dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax); } used[pSprite->type - kDudeBase] = TRUE; } // preload tiles for dude sequences dprintf("Preload dude sequence tiles\n"); for (int i = 0; i < kDudeMax - kDudeBase; i++) { if ( used[i] ) { // only preload art for idle sequence // if ( dudeInfo[i].pSeq[0] != NULL ) // dudeInfo[i].pSeq[0]->Preload(); } } // initialize all dudes for (nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; int nXSprite = pSprite->extra; dassert(nXSprite > 0 && nXSprite < kMaxXSprites); XSPRITE *pXSprite = &xsprite[nXSprite]; int dudeIndex = pSprite->type - kDudeBase; pSprite->cstat |= kSpriteBlocking | kSpriteHitscan; pSprite->clipdist = dudeInfo[dudeIndex].clipdist; pSprite->flags = kAttrMove | kAttrGravity | kAttrFalling; pSprite->xvel = pSprite->yvel = pSprite->zvel = 0; pXSprite->health = dudeInfo[dudeIndex].startHealth << 4; if ( gSysRes.Lookup( dudeInfo[dudeIndex].seqStartID + kSeqDudeIdle, ".SEQ") != NULL ) seqSpawn(dudeInfo[dudeIndex].seqStartID + kSeqDudeIdle, SS_SPRITE, nXSprite); } // initialize all things for (nSprite = headspritestat[kStatThing]; nSprite >= 0; nSprite = nextspritestat[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; int nXSprite = pSprite->extra; dassert(nXSprite > 0 && nXSprite < kMaxXSprites); XSPRITE *pXSprite = &xsprite[nXSprite]; int thingIndex = pSprite->type - kThingBase; pSprite->cstat |= kSpriteBlocking; // | kSpriteHitscan should be handled in BSTUB by hitbit pSprite->clipdist = thingInfo[thingIndex].clipdist; pSprite->flags = thingInfo[thingIndex].flags; pSprite->xvel = pSprite->yvel = pSprite->zvel = 0; pXSprite->health = thingInfo[thingIndex].startHealth << 4; switch(pSprite->type) { case kThingMachineGun: pXSprite->state = 0; // these must start off break; default: pXSprite->state = 1; break; } } aiInit(); } #define kGlobalForceShift 26 // arbitrary scale for all concussion static void ConcussSprite( int nSource, int nSprite, int x, int y, int z, int ticks ) { SPRITE *pSprite = &sprite[nSprite]; int dx = pSprite->x - x; int dy = pSprite->y - y; int dz = (pSprite->z - z) >> 4; int dist2 = ClipLow(dx * dx + dy * dy + dz * dz, 32 << 4); int nTile = pSprite->picnum; int area = tilesizx[nTile] * pSprite->xrepeat * tilesizy[nTile] * pSprite->yrepeat >> 12; int force = divscale(ticks, dist2, kGlobalForceShift); if ( pSprite->flags & kAttrMove ) { int mass = 0; if ( pSprite->type >= kDudeBase && pSprite->type < kDudeMax ) mass = dudeInfo[pSprite->type - kDudeBase].mass; else if ( pSprite->type >= kThingBase && pSprite->type < kThingMax ) mass = thingInfo[pSprite->type - kThingBase].mass; else ThrowError("Unexpected type encountered in ConcussSprite()", ES_ERROR); dassert(mass != 0); int impulse = muldiv(force, area, qabs(mass)); dx = mulscale16(impulse, dx); dy = mulscale16(impulse, dy); dz = mulscale16(impulse, dz); pSprite->xvel += dx; pSprite->yvel += dy; pSprite->zvel += dz; } actDamageSprite(nSource, nSprite, kDamageExplode, force); } /******************************************************************************* FUNCTION: ReflectVector() DESCRIPTION: Reflects a vector off a wall PARAMETERS: nFraction is elasticity (0x10000 == perfectly elastic) *******************************************************************************/ static void ReflectVector( short *dx, short *dy, int nWall, int nFraction ) { // calculate normal for wall int nx = -(wall[wall[nWall].point2].y - wall[nWall].y) >> 4; int ny = (wall[wall[nWall].point2].x - wall[nWall].x) >> 4; int dotProduct = *dx * nx + *dy * ny; int length2 = nx * nx + ny * ny; dassert(length2 > 0); int dot2 = dotProduct + mulscale16r(dotProduct, nFraction); int sd = divscale16(dot2, length2); *dx -= mulscale16r(nx, sd); *dy -= mulscale16r(ny, sd); } static void DropPickupObject( int nActor, int nObject ) { dassert( nActor >= 0 && nActor < kMaxSprites && sprite[nActor].statnum < kMaxStatus ); dassert( (nObject >= kItemBase && nObject < kItemMax) || (nObject >= kAmmoItemBase && nObject < kAmmoItemMax) || (nObject >= kWeaponItemBase && nObject < kWeaponItemMax) ); // create a sprite for the dropped ammo SPRITE *pActor = &sprite[nActor]; int nSprite = actSpawnSprite( pActor->sectnum, pActor->x, pActor->y, sector[pActor->sectnum].floorz, kStatItem, FALSE ); SPRITE *pSprite = &sprite[ nSprite ]; if ( nObject >= kItemBase && nObject < kItemMax ) { int nItemIndex = nObject - kItemBase; pSprite->type = (short)nObject; pSprite->picnum = gItemData[nItemIndex].picnum; pSprite->shade = gItemData[nItemIndex].shade ; pSprite->xrepeat = gItemData[nItemIndex].xrepeat; pSprite->yrepeat = gItemData[nItemIndex].yrepeat; if (nObject >= kItemKey1 && nObject <= kItemKey7) { // PF/NN: should this be in bloodbath too? if ( gNetMode == kNetModeCoop ) // force permanent keys in Coop mode { dbInsertXSprite( nSprite ); XSPRITE *pXSprite = &xsprite[ pSprite->extra ]; pXSprite->respawn = kRespawnPermanent; pXSprite->respawnTime = 0; } } } else if ( nObject >= kAmmoItemBase && nObject < kAmmoItemMax ) { int nAmmoIndex = nObject - kAmmoItemBase; pSprite->type = (short)nObject; pSprite->picnum = gAmmoItemData[nAmmoIndex].picnum; pSprite->shade = gAmmoItemData[nAmmoIndex].shade ; pSprite->xrepeat = gAmmoItemData[nAmmoIndex].xrepeat; pSprite->yrepeat = gAmmoItemData[nAmmoIndex].yrepeat; } else if ( nObject >= kWeaponItemBase && nObject < kWeaponItemMax ) { int nWeaponIndex = nObject - kWeaponItemBase; pSprite->type = (short)nObject; pSprite->picnum = gWeaponItemData[nWeaponIndex].picnum; pSprite->shade = gWeaponItemData[nWeaponIndex].shade ; pSprite->xrepeat = gWeaponItemData[nWeaponIndex].xrepeat; pSprite->yrepeat = gWeaponItemData[nWeaponIndex].yrepeat; } else ThrowError("Unhandled nObject passed to DropPickupObject()", ES_ERROR); } BOOL actHealDude( XSPRITE *pXDude, int healValue, int maxHealthClip) { dassert(pXDude != NULL); healValue <<= 4; // fix this later in the calling code maxHealthClip <<= 4; if ( pXDude->health < maxHealthClip ) { pXDude->health = ClipHigh(pXDude->health + healValue, maxHealthClip); dprintf("Health=%d\n", pXDude->health >> 4); return TRUE; } return FALSE; } void actKillSprite( int nSource, int nSprite, DAMAGE_TYPE damageType ) { dassert(nSprite >= 0 && nSprite < kMaxSprites); SPRITE *pSprite = &sprite[nSprite]; SPRITE *pSource = &sprite[nSource]; dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax); int dudeIndex = pSprite->type - kDudeBase; DUDEINFO *pDudeInfo = &dudeInfo[dudeIndex]; int nXSprite = pSprite->extra; dassert(nXSprite > 0); XSPRITE *pXSprite = &xsprite[pSprite->extra]; pXSprite->moveState = kMoveStill; // handle first cerberus head death if (pSprite->type == kDudeCerberus) { seqSpawn(dudeInfo[dudeIndex].seqStartID + kSeqDudeDeath1, SS_SPRITE, nXSprite); return; } actPostSprite( nSprite, kStatThing ); trTriggerSprite( nSprite, pXSprite, kCommandOff ); // trigger death message pSprite->flags |= kAttrMove | kAttrGravity | kAttrFalling; if ( IsPlayerSprite(nSprite) ) { PLAYER *pPlayer = &gPlayer[pSprite->type - kDudePlayer1]; powerupClear( pPlayer ); dprintf("health = %i\n",pXSprite->health); if (pXSprite->health == 0) pPlayer->deathTime = 0; if ( IsPlayerSprite(nSource) ) { int nKilledIndex = pSprite->type - kDudePlayer1; int nKillerIndex = pSource->type - kDudePlayer1; PLAYER *pFragger = &gPlayer[nKillerIndex]; if (nSource == nSprite) // fragged yourself, eh? { pPlayer->fragCount--; pPlayer->fragInfo[nKillerIndex]--; // frags against self is negative } else { pFragger->fragCount++; pFragger->fragInfo[nKilledIndex]++; // frags against others are positive } } // drop pickup items for (int i = 1; i < 8; i++) if ( pPlayer->hasKey[i] ) DropPickupObject(nSprite, kItemKey1 + i - 1); if ( pPlayer->hasWeapon[kWeaponShotgun] ) DropPickupObject(nSprite, kWeaponItemShotgun); if ( pPlayer->hasWeapon[kWeaponTommy] ) DropPickupObject(nSprite, kWeaponItemTommyGun); if ( pPlayer->hasWeapon[kWeaponFlare] ) DropPickupObject(nSprite, kWeaponItemFlareGun); if ( pPlayer->hasWeapon[kWeaponSpear] ) DropPickupObject(nSprite, kWeaponItemSpearGun); if ( pPlayer->hasWeapon[kWeaponShadow] ) DropPickupObject(nSprite, kWeaponItemShadowGun); } if ( pXSprite->key > 0 ) DropPickupObject( nSprite, kItemKey1 + pXSprite->key - 1 ); int deathType; switch (damageType) { case kDamageFall: case kDamageExplode: deathType = kSeqDudeDeath2; // sfxStart3DSound(nXSprite, kSfxThingSplat); break; case kDamageBurn: deathType = kSeqDudeDeath3; // sfxStart3DSound(nXSprite, kSfxThingBurn); break; default: deathType = kSeqDudeDeath1; break; } // are we missing this sequence? if so, just delete it if ( gSysRes.Lookup( dudeInfo[dudeIndex].seqStartID + deathType, ".SEQ") == NULL ) { dprintf("sprite missing death sequence: deleted\n"); seqKill(SS_SPRITE, nXSprite); // make sure we remove any active sequence actPostSprite( nSprite, kStatFree ); return; } switch (pSprite->type) { case kDudeAxeZombie: if (pSprite->owner >= 0) { PLAYER *pPlayer = &gPlayer[sprite[pSprite->owner].type - kDudePlayer1]; playerDeleteLackey( pPlayer, nSprite ); } seqSpawn(dudeInfo[dudeIndex].seqStartID + deathType, SS_SPRITE, nXSprite); break; case kDudeFatZombie: // if ( damageType == kDamageBurn ) // { // int thingIndex = kThingZombieBones - kThingBase; // // pSprite->type = kThingZombieBones; // pSprite->clipdist = thingInfo[thingIndex].clipdist; // pSprite->flags = thingInfo[thingIndex].flags; // pSprite->xvel = pSprite->yvel = pSprite->zvel = 0; // pXSprite->health = thingInfo[thingIndex].startHealth << 4; // } seqSpawn(dudeInfo[dudeIndex].seqStartID + deathType, SS_SPRITE, nXSprite); break; default: dprintf("spawning sprite %i death sequence %x\n", nSprite, dudeInfo[dudeIndex].seqStartID + deathType); seqSpawn(dudeInfo[dudeIndex].seqStartID + deathType, SS_SPRITE, nXSprite); break; } // drop any items or weapons if (pSprite->type == kDudeTommyCultist) { int nDropCheck = Random(100); // constants? table? if (nDropCheck < 80) DropPickupObject( nSprite, kAmmoItemBullets ); else if (nDropCheck < 95) DropPickupObject( nSprite, kAmmoItemBulletBox ); else DropPickupObject( nSprite, kWeaponItemTommyGun ); } else if (pSprite->type == kDudeShotgunCultist) { int nDropCheck = Random(100); if (nDropCheck < 40) DropPickupObject( nSprite, kAmmoItemShells ); else if (nDropCheck < 75) DropPickupObject( nSprite, kAmmoItemShellBox ); else DropPickupObject( nSprite, kWeaponItemShotgun ); } // SpawnGibs( nSprite, pXSprite ); // gib generator if ( damageType == kDamageExplode ) { int angle, velocity = 120; // thing gibs for (int i = 0; i < kGibMax && pDudeInfo->gib[i].chance > 0; i++) { if ( Random(256) < pDudeInfo->gib[i].chance ) { int nGib = actCloneSprite(pSprite); changespritestat( (short)nGib, kStatThing ); SPRITE *pGib = &sprite[nGib]; angle = Random(kAngle360); pGib->type = kThingGibSmall; pGib->picnum = pDudeInfo->gib[i].tile; pGib->xvel += mulscale30(velocity, Cos(angle)); pGib->yvel += mulscale30(velocity, Sin(angle)); pGib->zvel -= 128; // toss it in the air a bit pGib->cstat &= ~kSpriteBlocking & ~kSpriteHitscan; pGib->flags = kAttrMove | kAttrGravity | kAttrFalling; pGib->pal = kPLUNormal; } } // debris gibs for (i = 0; i < 50; i++) { int nGib = actCloneSprite(pSprite); changespritestat( (short)nGib, kStatDebris); SPRITE *pGib = &sprite[nGib]; pGib->picnum = 2053; // "worm" giblet pGib->xvel += BiRandom(500); pGib->yvel += BiRandom(500); pGib->zvel += BiRandom(500); pGib->cstat &= ~kSpriteBlocking & ~kSpriteHitscan; pGib->flags = kAttrMove | kAttrGravity | kAttrFalling; pGib->pal = kPLUNormal; } } } void actDamageSprite( int nSource, int nSprite, DAMAGE_TYPE nDamageType, int nDamage ) { dassert(nSprite >= 0 && nSprite < kMaxSprites); SPRITE *pSprite = &sprite[nSprite]; int nXSprite = pSprite->extra; if (nXSprite < 0) return; dassert(nXSprite > 0 && nXSprite < kMaxXSprites); XSPRITE *pXSprite = &xsprite[nXSprite]; if (pXSprite->health == 0) // it's already toast return; switch ( pSprite->statnum ) { case kStatDude: { dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax); // calculate and apply damage to the dude or player sprite int nShift = dudeInfo[pSprite->type - kDudeBase].damageShift[nDamageType]; nDamage = (nShift >= kNoDamage) ? 0 : nDamage >> nShift; pXSprite->health = ClipLow(pXSprite->health - nDamage, 0); // process results and effects of damage if ( IsPlayerSprite(pSprite) ) playerDamageSprite( &gPlayer[pSprite->type - kDudePlayer1], nSource, nDamage ); else aiDamageSprite( pSprite, pXSprite, nSource, nDamageType, nDamage ); // kill dudes lacking health to sustain life if (pXSprite->health == 0) { // prevent dudes from exploding from weak explosion damage if ( nDamageType == kDamageExplode && nDamage < (5 << 4) ) nDamageType = kDamagePummel; if ( IsPlayerSprite(pSprite) ) sfxStart3DSound(nXSprite, kSfxPlayDie); actKillSprite(nSource, nSprite, nDamageType); } break; } case kStatThing: case kStatProximity: { dassert(pSprite->type >= kThingBase && pSprite->type < kThingMax); int thingIndex = pSprite->type - kThingBase; nDamage >>= thingInfo[thingIndex].damageShift[nDamageType]; pXSprite->health = ClipLow(pXSprite->health - nDamage, 0); if (pXSprite->health == 0) { pSprite->owner = (short)nSource; trTriggerSprite(nSprite, pXSprite, kCommandOff); switch ( pSprite->type ) { case kThingMachineGun: seqSpawn(kSeqMGunDead, SS_SPRITE, pSprite->extra); sfxStart3DSound(nXSprite, kSfxMGDie); break; case kThingBlueVase: seqSpawn(kSeqVase1, SS_SPRITE, pSprite->extra); sfxStart3DSound(nXSprite, kSfxPotteryHit); if (pXSprite->data1 > 0) DropPickupObject( nSprite, pXSprite->data1 ); if (pXSprite->data2 > 0) DropPickupObject( nSprite, pXSprite->data2 ); break; case kThingBrownVase: seqSpawn(kSeqVase2, SS_SPRITE, pSprite->extra); sfxStart3DSound(nXSprite, kSfxPotteryHit); if (pXSprite->data1 > 0) DropPickupObject( nSprite, pXSprite->data1 ); if (pXSprite->data2 > 0) DropPickupObject( nSprite, pXSprite->data2 ); break; case kThingClearGlass: pSprite->yrepeat >>= 1; seqSpawn(kSeqClearGlass, SS_SPRITE, pSprite->extra); sfxStart3DSound(nXSprite, kSfxGlassHit); break; case kThingFluorescent: seqSpawn(kSeqFluorescentLight, SS_SPRITE, pSprite->extra); break; case kThingWoodBeam: seqSpawn(kSeqBeam, SS_SPRITE, pSprite->extra); break; case kThingWeb: seqSpawn(kSeqWeb, SS_SPRITE, pSprite->extra); break; case kThingMetalGrate1: seqSpawn(kSeqMetalGrate1, SS_SPRITE, pSprite->extra); break; case kThingFlammableTree: if (pXSprite->data1 == 0) seqSpawn(kSeqBurningTree1, SS_SPRITE, pSprite->extra); else if (pXSprite->data1 == 1) seqSpawn(kSeqBurningTree2, SS_SPRITE, pSprite->extra); sfxStart3DSound(nXSprite, kSfxBurn); break; case kThingZombieBones: dprintf("damaging zombie bones\n"); if ( seqGetStatus(SS_SPRITE, nXSprite) < 0 ) // body finished burning seqSpawn(kSeqZombieBones, SS_SPRITE, pSprite->extra); break; } } break; } } } void actImpactMissile( int nSprite, int hitInfo ) { SPRITE *pMissile = &sprite[nSprite]; int hitType = hitInfo & kHitTypeMask; int hitObject = hitInfo & kHitIndexMask; int nXMissile = pMissile->extra; switch (pMissile->type) { case kMissileFireball: actExplodeSprite( nSprite ); break; case kMissileFlare: case kMissileExplodingFlare: case kMissileStarburstFlare: { XSPRITE *pXMissile = &xsprite[pMissile->extra]; if (pMissile->type == kMissileExplodingFlare || pMissile->type == kMissileStarburstFlare) { actExplodeSprite( nSprite ); break; } if ( hitType == kHitSprite && sprite[hitObject].extra > 0 ) { SPRITE *pObject = &sprite[hitObject]; XSPRITE *pXObject = &xsprite[pObject->extra]; pMissile->picnum = kAnmFlareBurn; pXMissile->target = hitObject; pXMissile->targetZ = pMissile->z - pObject->z; pXMissile->goalAng = getangle( pMissile->x - pObject->x, pMissile->y - pObject->y ) - pObject->ang; pXMissile->state = 1; actAddBurnTime(pMissile->owner, pXObject, 8 * kTimerRate); actDamageSprite(pMissile->owner, hitObject, kDamageStab, 10 << 4); actPostSprite( nSprite, kStatFlare ); evPost(nSprite, SS_SPRITE, 8 * kTimerRate); // callback to flare } else { // ADD: spawn an explosion later actPostSprite( nSprite, kStatFree ); } break; } case kMissileSprayFlame: case kMissileHoundFire: // seqKill(SS_SPRITE, nXMissile); // deletesprite((short)nSprite); if ( hitType == kHitSprite && sprite[hitObject].extra > 0 ) { XSPRITE *pXObject = &xsprite[sprite[hitObject].extra]; actAddBurnTime( pMissile->owner, pXObject, kFrameTicks ); } break; case kMissileEctoSkull: actPostSprite( nSprite, kStatEffect ); seqSpawn(kSeqSkullExplode, SS_SPRITE, pMissile->extra); if ( hitType == kHitSprite && sprite[hitObject].statnum == kStatDude ) { actDamageSprite(pMissile->owner, hitObject, kDamageSpirit, 50 << 4); SPRITE *pDude = &sprite[pMissile->owner]; XSPRITE *pXDude = &xsprite[pDude->extra]; if ( pXDude->health > 0 ) actHealDude(pXDude, 25); } break; case kMissileButcherKnife: actPostSprite( nSprite, kStatEffect ); pMissile->cstat &= ~kSpriteWall; pMissile->type = kNothing; seqSpawn(kSeqSkullExplode, SS_SPRITE, pMissile->extra); if ( hitType == kHitSprite && sprite[hitObject].statnum == kStatDude ) { actDamageSprite(pMissile->owner, hitObject, kDamageSpirit, 15 << 4); SPRITE *pDude = &sprite[pMissile->owner]; XSPRITE *pXDude = &xsprite[pDude->extra]; int dudeIndex = pDude->type - kDudeBase; if ( pXDude->health > 0 ) actHealDude(pXDude, 10, dudeInfo[dudeIndex].startHealth); } break; default: seqKill(SS_SPRITE, nXMissile); actPostSprite( nSprite, kStatFree ); if (hitType == kHitSprite) actDamageSprite(pMissile->owner, hitObject, kDamagePummel, 5 << 4); break; } } void ProcessTouchObjects( int nSprite, int nXSprite ) { SPRITE *pSprite = &sprite[nSprite]; XSPRITE *pXSprite = &xsprite[nXSprite]; SPRITEHIT *pSpriteHit = &gSpriteHit[nXSprite]; int nHitObject = pSpriteHit->ceilHit & kHitIndexMask; switch( pSpriteHit->ceilHit & kHitTypeMask ) { case kHitSprite: if ( sprite[nHitObject].extra > 0 ) { SPRITE *pHit = &sprite[nHitObject]; XSPRITE *pXHit = &xsprite[pHit->extra]; switch ( pHit->type ) { case kTrapSawBlade: if ( pXHit->state ) { pXHit->data1 = 1; pXHit->data2 = ClipHigh(pXHit->data2 + 2 * kFrameTicks, kTimerRate * 5); actDamageSprite(nSprite, nSprite, kDamageStab, 4); } else actDamageSprite(nSprite, nSprite, kDamageStab, 1); break; } } break; case kHitWall: case kHitSector: break; } nHitObject = pSpriteHit->moveHit & kHitIndexMask; switch( pSpriteHit->moveHit & kHitTypeMask ) { case kHitSprite: break; case kHitWall: case kHitSector: break; } nHitObject = pSpriteHit->floorHit & kHitIndexMask; switch( pSpriteHit->floorHit & kHitTypeMask ) { case kHitSprite: if ( sprite[nHitObject].extra > 0 ) { SPRITE *pHit = &sprite[nHitObject]; XSPRITE *pXHit = &xsprite[pHit->extra]; switch ( pHit->type ) { case kTrapSawBlade: if ( pXHit->state ) { pXHit->data1 = 1; pXHit->data2 = ClipHigh(pXHit->data2 + 2 * kFrameTicks, kTimerRate * 5); actDamageSprite(nSprite, nSprite, kDamageStab, 4); } else actDamageSprite(nSprite, nSprite, kDamageStab, 1); break; } } break; case kHitWall: case kHitSector: break; } } #define kMinZVel (6 << 4) #define kAirDrag 0x0100 static int MoveThing( int nSprite, char cliptype ) { dassert(nSprite >= 0 && nSprite < kMaxSprites); SPRITE *pSprite = &sprite[nSprite]; int nXSprite = pSprite->extra; XSPRITE *pXSprite = &xsprite[nXSprite]; if ( !(pSprite->flags & kAttrFalling) && !pSprite->xvel && !pSprite->yvel && !pSprite->zvel ) return 0; short nSector = pSprite->sectnum; dassert(nSector >= 0 && nSector < kMaxSectors); gSpriteHit[nXSprite].ceilHit = 0; gSpriteHit[nXSprite].floorHit = 0; gSpriteHit[nXSprite].moveHit = 0; int zTop, zBot; GetSpriteExtents(pSprite, &zTop, &zBot); if ( pSprite->xvel || pSprite->yvel ) { short oldcstat = pSprite->cstat; pSprite->cstat &= ~kSpriteBlocking & ~kSpriteHitscan; gSpriteHit[nXSprite].moveHit = ClipMove(&pSprite->x, &pSprite->y, &pSprite->z, &nSector, pSprite->xvel * kFrameTicks >> 4, pSprite->yvel * kFrameTicks >> 4, pSprite->clipdist << 2, (pSprite->z - zTop) / 4, (zBot - pSprite->z) / 4, cliptype); pSprite->cstat = oldcstat; if ((nSector != pSprite->sectnum) && (nSector >= 0)) changespritesect((short)nSprite, nSector); switch (gSpriteHit[nXSprite].moveHit & kHitTypeMask) { case kHitWall: { int nWall = gSpriteHit[nXSprite].moveHit & kHitIndexMask; ReflectVector(&pSprite->xvel, &pSprite->yvel, nWall, 0x4000); pSprite->zvel = (short)mulscale16(pSprite->zvel, 0xC000); break; } // need to handle sprite collisions here.... } } long ceilz, ceilhit, floorz, floorhit; GetZRange(pSprite, &ceilz, &ceilhit, &floorz, &floorhit, pSprite->clipdist << 2, cliptype); if ( pSprite->zvel ) { pSprite->z += pSprite->zvel * kFrameTicks; pSprite->zvel -= mulscale16r(pSprite->zvel, kAirDrag); } if ( (pSprite->flags & kAttrGravity) && zBot < floorz ) { pSprite->z += kGravity * kFrameTicks * kFrameTicks / 2; pSprite->zvel += kGravity * kFrameTicks; pSprite->flags |= kAttrFalling; } // check for warping in linked sectors int nUpper = gUpperLink[nSector], nLower = gLowerLink[nSector]; if ( nUpper >= 0 && pSprite->z < sprite[nUpper].z ) { nLower = sprite[nUpper].owner; changespritesect((short)nSprite, sprite[nLower].sectnum); pSprite->x += sprite[nLower].x - sprite[nUpper].x; pSprite->y += sprite[nLower].y - sprite[nUpper].y; pSprite->z += sprite[nLower].z - sprite[nUpper].z; viewBackupSpriteLoc(nSprite, pSprite); // prevent interpolation GetZRange(pSprite, &ceilz, &ceilhit, &floorz, &floorhit, pSprite->clipdist << 2, cliptype); } else if ( nLower >= 0 && pSprite->z > sprite[nLower].z ) { nUpper = sprite[nLower].owner; changespritesect((short)nSprite, sprite[nUpper].sectnum); pSprite->x += sprite[nUpper].x - sprite[nLower].x; pSprite->y += sprite[nUpper].y - sprite[nLower].y; pSprite->z += sprite[nUpper].z - sprite[nLower].z; viewBackupSpriteLoc(nSprite, pSprite); // prevent interpolation GetZRange(pSprite, &ceilz, &ceilhit, &floorz, &floorhit, pSprite->clipdist << 2, cliptype); } GetSpriteExtents(pSprite, &zTop, &zBot); // hit floor? if ( zBot >= floorz ) { gSpriteHit[nXSprite].floorHit = floorhit; pSprite->z += floorz - zBot; if ( pSprite->flags & kAttrFalling ) { pSprite->xvel = (short)mulscale16(pSprite->xvel, 0xC000); pSprite->yvel = (short)mulscale16(pSprite->yvel, 0xC000); pSprite->zvel = (short)mulscale16(-pSprite->zvel, 0x4000); if ( qabs(pSprite->zvel) < kMinZVel) { pSprite->zvel = 0; pSprite->flags &= ~kAttrFalling; } return kHitFloor | nSector; } pSprite->zvel = 0; } // hit ceiling if ( zTop < ceilz && ((ceilhit & kHitTypeMask) != kHitSector || !(sector[nSector].ceilingstat & kSectorParallax)) ) { gSpriteHit[nXSprite].ceilHit = ceilhit; pSprite->z += ClipLow(ceilz - zTop, 0); pSprite->xvel = (short)mulscale16(pSprite->xvel, 0xC000); pSprite->yvel = (short)mulscale16(pSprite->yvel, 0xC000); pSprite->zvel = (short)mulscale16(-pSprite->zvel, 0x4000); // return kHitCeiling | nSector; } // drag and friction if ( pSprite->xvel || pSprite->yvel ) { // air drag pSprite->xvel -= mulscale16r(pSprite->xvel, kAirDrag); pSprite->yvel -= mulscale16r(pSprite->yvel, kAirDrag); if ( !(pSprite->flags & kAttrFalling) ) { // sliding int vel = qdist(pSprite->xvel, pSprite->yvel); int nFrict = ClipHigh(kFrameTicks * kGroundFriction, vel); if ( (floorhit & kHitTypeMask) == kHitSprite ) { int nUnderSprite = floorhit & kHitIndexMask; if ( (sprite[nUnderSprite].cstat & kSpriteRMask) == kSpriteFace ) { // push it off the face sprite pSprite->xvel += mulscale(kFrameTicks, pSprite->x - sprite[nUnderSprite].x, 6); pSprite->yvel += mulscale(kFrameTicks, pSprite->y - sprite[nUnderSprite].y, 6); return gSpriteHit[nXSprite].moveHit; } } if (vel > 0) { nFrict = divscale16(nFrict, vel); pSprite->xvel -= mulscale16(nFrict, pSprite->xvel); pSprite->yvel -= mulscale16(nFrict, pSprite->yvel); } } } return gSpriteHit[nXSprite].moveHit; } static void MoveDebris( int nSprite ) { dassert(nSprite >= 0 && nSprite < kMaxSprites); SPRITE *pSprite = &sprite[nSprite]; int nXSprite = pSprite->extra; short nSector = pSprite->sectnum; dassert(nSector >= 0 && nSector < kMaxSectors); if ( pSprite->xvel || pSprite->yvel ) { pSprite->x += pSprite->xvel * kFrameTicks >> 4; pSprite->y += pSprite->yvel * kFrameTicks >> 4; // air drag pSprite->xvel -= mulscale16r(pSprite->xvel, kAirDrag); pSprite->yvel -= mulscale16r(pSprite->yvel, kAirDrag); } if ( pSprite->zvel ) { pSprite->z += pSprite->zvel * kFrameTicks; pSprite->zvel -= mulscale16r(pSprite->zvel, kAirDrag); } if ( pSprite->flags & kAttrGravity ) { pSprite->z += kGravity * kFrameTicks * kFrameTicks / 2; pSprite->zvel += kGravity * kFrameTicks; } if ( !FindSector(pSprite->x, pSprite->y, pSprite->z, &nSector) ) { actPostSprite(nSprite, kStatFree); return; } if ( nSector != pSprite->sectnum ) changespritesect((short)nSprite, nSector); } #define kScreamVel 1200 // zvel at which player screams #define kGruntVel 700 #define kDudeDrag 0x2800 #define kMinDudeVel (2 << 4) static void MoveDude( int nSprite ) { dassert(nSprite >= 0 && nSprite < kMaxSprites); SPRITE *pSprite = &sprite[nSprite]; int nXSprite = pSprite->extra; XSPRITE *pXSprite = &xsprite[nXSprite]; if ( !(pSprite->flags & kAttrFalling) && !pSprite->xvel && !pSprite->yvel && !pSprite->zvel ) return; PLAYER *pPlayer = NULL; if ( IsPlayerSprite(pSprite) ) pPlayer = &gPlayer[pSprite->type - kDudePlayer1]; dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax); DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase]; int zTop, zBot; GetSpriteExtents(pSprite, &zTop, &zBot); int floorDist = (zBot - pSprite->z) / 4; int ceilDist = (pSprite->z - zTop) / 4; int clipDist = pSprite->clipdist << 2; short nSector = pSprite->sectnum; dassert(nSector >= 0 && nSector < kMaxSectors); // if ( !(pSprite->xvel || pSprite->yvel || pSprite->zvel) && fWasOnFloor ) // return; gSpriteHit[nXSprite].ceilHit = 0; gSpriteHit[nXSprite].floorHit = 0; gSpriteHit[nXSprite].moveHit = 0; if ( pSprite->xvel || pSprite->yvel ) { int oldX = pSprite->x; int oldY = pSprite->y; if ( pPlayer && gNoClip ) { pSprite->x += pSprite->xvel * kFrameTicks >> 4; pSprite->y += pSprite->yvel * kFrameTicks >> 4; updatesector( pSprite->x, pSprite->y, &nSector); if (nSector == -1) { pSprite->x = oldX; pSprite->y = oldY; nSector = pSprite->sectnum; } } else { short oldcstat = pSprite->cstat; pSprite->cstat &= ~kSpriteBlocking & ~kSpriteHitscan; gSpriteHit[nXSprite].moveHit = ClipMove( &pSprite->x, &pSprite->y, &pSprite->z, &nSector, pSprite->xvel * kFrameTicks >> 4, pSprite->yvel * kFrameTicks >> 4, clipDist, ceilDist, floorDist, 0); pSprite->cstat = oldcstat; } if ( pPlayer ) { if ( nSector != pSprite->sectnum && nSector >= 0 ) { // process sector exit/enter triggers int nXSector; nXSector = sector[pSprite->sectnum].extra; if ( nXSector > 0 && xsector[nXSector].triggerExit ) trTriggerSector(pSprite->sectnum, &xsector[nXSector], kCommandSectorExit); nXSector = sector[nSector].extra; if ( nXSector > 0 && xsector[nXSector].triggerEnter ) trTriggerSector(nSector, &xsector[nXSector], kCommandSectorEnter); } } if ( nSector != pSprite->sectnum ) changespritesect((short)nSprite, nSector); switch (gSpriteHit[nXSprite].moveHit & kHitTypeMask) { case kHitWall: { int nWall = gSpriteHit[nXSprite].moveHit & kHitIndexMask; WALL *pWall = &wall[nWall]; // don't bounce off wall if not fully blocking if ( pWall->nextsector != -1 ) { SECTOR *pSector = §or[pWall->nextsector]; if ( pSector->floorz > zTop || pSector->ceilingz < zBot ) { ReflectVector(&pSprite->xvel, &pSprite->yvel, nWall, 0); break; } } ReflectVector(&pSprite->xvel, &pSprite->yvel, nWall, 0x4000); pSprite->zvel = (short)mulscale16(pSprite->zvel, 0xC000); break; } // need to handle sprite collisions here.... } } long ceilz, ceilhit, floorz, floorhit; if ( pPlayer ) clipDist += 16; // increase clipdist to allow jumping onto ledges GetZRange(pSprite, &ceilz, &ceilhit, &floorz, &floorhit, clipDist, 0); if ( pSprite->zvel ) { pSprite->z += pSprite->zvel * kFrameTicks; pSprite->zvel -= mulscale16r(pSprite->zvel, kAirDrag); } if ( pPlayer && pSprite->zvel > kScreamVel && !pPlayer->fScreamed ) { pPlayer->fScreamed = TRUE; sfxStart3DSound(nXSprite, kSfxPlayFall); } if ( (pSprite->flags & kAttrGravity) && zBot < floorz ) { pSprite->z += kGravity * kFrameTicks * kFrameTicks / 2; pSprite->zvel += kGravity * kFrameTicks; pSprite->flags |= kAttrFalling; } // check for warping in linked sectors int nUpper = gUpperLink[nSector], nLower = gLowerLink[nSector]; if ( nUpper >= 0 && pSprite->z < sprite[nUpper].z ) { nLower = sprite[nUpper].owner; changespritesect((short)nSprite, sprite[nLower].sectnum); pSprite->x += sprite[nLower].x - sprite[nUpper].x; pSprite->y += sprite[nLower].y - sprite[nUpper].y; pSprite->z += sprite[nLower].z - sprite[nUpper].z; viewBackupSpriteLoc(nSprite, pSprite); // prevent interpolation GetZRange(pSprite, &ceilz, &ceilhit, &floorz, &floorhit, clipDist, 0); } else if ( nLower >= 0 && pSprite->z > sprite[nLower].z ) { nUpper = sprite[nLower].owner; changespritesect((short)nSprite, sprite[nUpper].sectnum); pSprite->x += sprite[nUpper].x - sprite[nLower].x; pSprite->y += sprite[nUpper].y - sprite[nLower].y; pSprite->z += sprite[nUpper].z - sprite[nLower].z; viewBackupSpriteLoc(nSprite, pSprite); // prevent interpolation GetZRange(pSprite, &ceilz, &ceilhit, &floorz, &floorhit, clipDist, 0); } GetSpriteExtents(pSprite, &zTop, &zBot); if ( pPlayer && zBot >= floorz) { long floorz2 = floorz, floorhit2 = floorhit; GetZRange(pSprite, &ceilz, &ceilhit, &floorz, &floorhit, pSprite->clipdist << 2, 0); if ( zBot <= floorz && pSprite->z - floorz2 < floorDist ) { floorz = floorz2; floorhit2 = floorhit; } } // hit floor? if ( zBot >= floorz ) { gSpriteHit[nXSprite].floorHit = floorhit; pSprite->z += floorz - zBot; if ( pSprite->flags & kAttrFalling ) { // need some way of converting fall damge to a non-linear curve int fallDamage = mulscale16(pSprite->zvel, pSprite->zvel); fallDamage = mulscale(fallDamage, fallDamage, 1); if ( fallDamage > (20 << 4) ) actDamageSprite(nSprite, nSprite, kDamageFall, fallDamage); if ( pPlayer && pXSprite->health > 0 ) { pPlayer->fScreamed = FALSE; if ( pSprite->zvel > kGruntVel ) sfxStart3DSound(nXSprite, kSfxPlayLand); } pSprite->xvel = (short)mulscale16(pSprite->xvel, 0xC000); pSprite->yvel = (short)mulscale16(pSprite->yvel, 0xC000); pSprite->zvel = (short)mulscale16(-pSprite->zvel, 0x2000); if ( pSprite->zvel > -kMinZVel ) { pSprite->zvel = 0; pSprite->flags &= ~kAttrFalling; } int nSurfType = tileGetSurfType(floorhit); if ( nSurfType == kSurfWater ) { actSpawnEffect(pSprite->sectnum, pSprite->x, pSprite->y, floorz, ET_Splash1); } return; } pSprite->zvel = 0; } // hit ceiling if ( zTop < ceilz && ((ceilhit & kHitTypeMask) != kHitSector || !(sector[nSector].ceilingstat & kSectorParallax)) ) { pSprite->z += ClipLow(ceilz - zTop, 0); pSprite->xvel = (short)mulscale16(pSprite->xvel, 0xC000); pSprite->yvel = (short)mulscale16(pSprite->yvel, 0xC000); pSprite->zvel = (short)mulscale16(-pSprite->zvel, 0x2000); } if ( zTop <= ceilz ) gSpriteHit[nXSprite].ceilHit = ceilhit; // drag and friction if ( pSprite->xvel || pSprite->yvel ) { // air drag pSprite->xvel -= mulscale16(pSprite->xvel, kAirDrag); pSprite->yvel -= mulscale16(pSprite->yvel, kAirDrag); if ( !(pSprite->flags & kAttrFalling) ) { if ( (floorhit & kHitTypeMask) == kHitSprite ) { int nUnderSprite = floorhit & kHitIndexMask; if ( (sprite[nUnderSprite].cstat & kSpriteRMask) == kSpriteFace ) { // push it off the face sprite pSprite->xvel += mulscale(kFrameTicks, pSprite->x - sprite[nUnderSprite].x, 6); pSprite->yvel += mulscale(kFrameTicks, pSprite->y - sprite[nUnderSprite].y, 6); return; } } // movement drag pSprite->xvel -= (sshort)mulscale16r(pSprite->xvel, kDudeDrag); pSprite->yvel -= (sshort)mulscale16r(pSprite->yvel, kDudeDrag); if ( qdist(pSprite->xvel, pSprite->yvel) < kMinDudeVel ) pSprite->xvel = pSprite->yvel = 0; } } ProcessTouchObjects( nSprite, nXSprite ); } // missiles are self-propelled and are unaffected by gravity static int MoveMissile( int nSprite ) { dassert(nSprite >= 0 && nSprite < kMaxSprites); SPRITE *pSprite = &sprite[nSprite]; return movesprite((short)nSprite, pSprite->xvel, pSprite->yvel, pSprite->zvel, 4 << 8, 4 << 8, 1, kFrameTicks); } void actExplodeSprite( int nSprite ) { SPRITE *pSprite = &sprite[nSprite]; int nXSprite = pSprite->extra; dassert(nXSprite > 0 && nXSprite < kMaxXSprites); // already exploding? if (pSprite->statnum == kStatExplosion) return; switch ( pSprite->type ) { case kMissileExplodingFlare: case kMissileStarburstFlare: seqSpawn(kSeqExplodeC2T, SS_SPRITE, nXSprite); break; case kThingTNTStick: if ( gSpriteHit[nXSprite].floorHit == 0 ) seqSpawn(kSeqExplodeC2S, SS_SPRITE, nXSprite); else seqSpawn(kSeqExplodeC1S, SS_SPRITE, nXSprite); break; case kThingTNTProxArmed: case kThingTNTRemArmed: case kThingTNTBundle: // if ( gSpriteHit[nXSprite].floorHit == 0 ) seqSpawn(kSeqExplodeC2M, SS_SPRITE, nXSprite); // else // seqSpawn(kSeqExplodeC1M, SS_SPRITE, nXSprite); break; case kThingTNTBarrel: { // spawn an explosion effect int nEffect = actSpawnSprite( pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, kStatExplosion, TRUE ); sprite[nEffect].owner = pSprite->owner; // set owner for frag/targeting // place barrel on the respawn list or just delete it if ( actCheckRespawn( nSprite ) ) { XSPRITE *pXSprite = &xsprite[nXSprite]; pXSprite->state = 0; pXSprite->health = thingInfo[kThingTNTBarrel - kThingBase].startHealth << 4; } else actPostSprite( nSprite, kStatFree ); // reset locals to point at the effect, not the barrel nSprite = nEffect; pSprite = &sprite[nEffect]; nXSprite = pSprite->extra; seqSpawn(kSeqExplodeC2L, SS_SPRITE, nXSprite); break; } default: seqSpawn(kSeqExplodeC2M, SS_SPRITE, nXSprite); break; } pSprite->xvel = pSprite->yvel = pSprite->zvel = 0; actPostSprite( nSprite, kStatExplosion ); pSprite->flags &= ~(kAttrMove | kAttrGravity); sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxExplodeCS); } void actProcessSprites(void) { int nSprite; int nDude, nNextDude; // process proximity triggered sprites for (nSprite = headspritestat[kStatProximity]; nSprite >= 0; nSprite = nextspritestat[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; int nXSprite = pSprite->extra; dassert(nXSprite > 0 && nXSprite < kMaxXSprites); XSPRITE *pXSprite = &xsprite[nXSprite]; for (nDude = headspritestat[kStatDude]; nDude >= 0; nDude = nNextDude) { nNextDude = nextspritestat[nDude]; if ( CheckProximity(&sprite[nDude], pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, 64) ) trTriggerSprite(nSprite, pXSprite, kCommandSpriteProximity); } } // process things for effects for (nSprite = headspritestat[kStatThing]; nSprite >= 0; nSprite = nextspritestat[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; if ( pSprite->extra > 0 ) { XSPRITE *pXSprite = &xsprite[pSprite->extra]; if ( actGetBurnTime(pXSprite) > 0 ) { pXSprite->burnTime = ClipLow(pXSprite->burnTime - kFrameTicks, 0); actDamageSprite( pXSprite->burnSource, nSprite, kDamageBurn, 4 * kFrameTicks ); } } } // process things for movement for (nSprite = headspritestat[kStatThing]; nSprite >= 0; nSprite = nextspritestat[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; if ( pSprite->flags & (kAttrMove | kAttrGravity) ) { viewBackupSpriteLoc(nSprite, pSprite); int hitInfo = MoveThing(nSprite, 1); if (hitInfo != 0) { int nXSprite = pSprite->extra; if (nXSprite > 0) { XSPRITE *pXSprite = &xsprite[nXSprite]; if ( pXSprite->triggerProximity ) trTriggerSprite(nSprite, pXSprite, kCommandOff); switch( pSprite->type ) { case kThingWaterDrip: case kThingBloodDrip: MakeSplash(pSprite, pXSprite); break; case kThingBoneClub: seqSpawn(kSeqBoneBreak, SS_SPRITE, nXSprite); if ( (hitInfo & kHitTypeMask) == kHitSprite ) actDamageSprite( pSprite->owner, hitInfo & kHitIndexMask, kDamagePummel, 12 ); break; } } } } } // process debris sprites for (nSprite = headspritestat[kStatDebris]; nSprite >= 0; nSprite = nextspritestat[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; viewBackupSpriteLoc(nSprite, pSprite); MoveDebris(nSprite); } // process missile sprites for (nSprite = headspritestat[kStatMissile]; nSprite >= 0; nSprite = nextspritestat[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; viewBackupSpriteLoc(nSprite, pSprite); int hitInfo = MoveMissile(nSprite); // process impacts if (hitInfo != 0) actImpactMissile( nSprite, hitInfo ); } // process explosions for (nSprite = headspritestat[kStatExplosion]; nSprite >= 0; nSprite = nextspritestat[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; int x = pSprite->x, y = pSprite->y, z = pSprite->z, nSector = pSprite->sectnum; int nAffected; int radius = tilesizx[pSprite->picnum] * pSprite->xrepeat >> 6; for (nAffected = headspritestat[kStatDude]; nAffected >= 0; nAffected = nextspritestat[nAffected]) { if ( CheckProximity(&sprite[nAffected], x, y, z, nSector, radius) ) ConcussSprite(pSprite->owner, nAffected, x, y, z, kFrameTicks); } for (nAffected = headspritestat[kStatThing]; nAffected >= 0; nAffected = nextspritestat[nAffected]) { if ( CheckProximity(&sprite[nAffected], x, y, z, nSector, radius) ) ConcussSprite(pSprite->owner, nAffected, x, y, z, kFrameTicks); } for (nAffected = headspritestat[kStatProximity]; nAffected >= 0; nAffected = nextspritestat[nAffected]) { if ( CheckProximity(&sprite[nAffected], x, y, z, nSector, radius) ) ConcussSprite(pSprite->owner, nAffected, x, y, z, kFrameTicks); } } // process traps for effects for (nSprite = headspritestat[kStatTraps]; nSprite >= 0; nSprite = nextspritestat[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; if ( pSprite->extra > 0 ) { XSPRITE *pXSprite = &xsprite[pSprite->extra]; switch( pSprite->type ) { case kTrapSawBlade: pXSprite->data2 = ClipLow(pXSprite->data2 - kFrameTicks, 0); break; } } } // process dudes for effects for (nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; if ( pSprite->extra > 0 ) { XSPRITE *pXSprite = &xsprite[pSprite->extra]; if ( actGetBurnTime(pXSprite) > 0 ) { pXSprite->burnTime = ClipLow(pXSprite->burnTime - kFrameTicks, 0); actDamageSprite( pXSprite->burnSource, nSprite, kDamageBurn, 2 * kFrameTicks ); } } } // process dudes for movement for (nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; int nSector = pSprite->sectnum; viewBackupSpriteLoc(nSprite, pSprite); // special sector processing if ( sector[nSector].extra > 0 ) { int nXSector = sector[ nSector ]. extra; dassert(nXSector > 0 && nXSector < kMaxXSectors); dassert(xsector[nXSector].reference == nSector); XSECTOR *pXSector = &xsector[nXSector]; if ( pXSector->panVel && (pXSector->panAlways || pXSector->busy)) { int windDrag = 0x0200; // 16:16 fixed point fraction int panVel = pXSector->panVel; int panAngle = pXSector->panAngle; if ( !pXSector->panAlways && pXSector->busy ) panVel = mulscale16(panVel, pXSector->busy); if (sector[nSector].floorstat & kSectorRelAlign) { panAngle += GetWallAngle(sector[nSector].wallptr) + kAngle90; panAngle &= kAngleMask; } if (pXSector->wind) { int windX = mulscale30(panVel, Cos(panAngle)) - pSprite->xvel; int windY = mulscale30(panVel, Sin(panAngle)) - pSprite->yvel; pSprite->xvel += mulscale16(kFrameTicks * windX, windDrag); pSprite->yvel += mulscale16(kFrameTicks * windY, windDrag); } } // handle water dragging if ( pXSector->depth > 0 ) { if ( pSprite->z >= sector[nSector].floorz ) { int pushDrag = dragTable[pXSector->depth]; int panVel = 0; int panAngle = pXSector->panAngle; if (pXSector->panAlways || pXSector->state || pXSector->busy) { panVel = pXSector->panVel; if ( !pXSector->panAlways && pXSector->busy ) panVel = mulscale16(panVel, pXSector->busy); } if (sector[nSector].floorstat & kSectorRelAlign) { panAngle += GetWallAngle(sector[nSector].wallptr) + kAngle90; panAngle &= kAngleMask; } int pushX = mulscale30(panVel, Cos(panAngle)) - pSprite->xvel; int pushY = mulscale30(panVel, Sin(panAngle)) - pSprite->yvel; int nDrag = ClipHigh(kFrameTicks * pushDrag, 0x10000); pSprite->xvel += mulscale16(pushX, nDrag); pSprite->yvel += mulscale16(pushY, nDrag); } } else if ( pXSector->underwater ) // handle underwater dragging { int pushDrag = dragTable[pXSector->depth]; int panVel = 0; int panAngle = pXSector->panAngle; if (pXSector->panAlways || pXSector->state || pXSector->busy) { panVel = pXSector->panVel; if ( !pXSector->panAlways && pXSector->busy ) panVel = mulscale16(panVel, pXSector->busy); } if (sector[nSector].floorstat & kSectorRelAlign) { panAngle += GetWallAngle(sector[nSector].wallptr) + kAngle90; panAngle &= kAngleMask; } int pushX = mulscale30(panVel, Cos(panAngle)) - pSprite->xvel; int pushY = mulscale30(panVel, Sin(panAngle)) - pSprite->yvel; pSprite->xvel += mulscale16(kFrameTicks * pushX, pushDrag); pSprite->yvel += mulscale16(kFrameTicks * pushY, pushDrag); } } MoveDude(nSprite); } // process flares to keep them burning on dudes for (nSprite = headspritestat[kStatFlare]; nSprite >= 0; nSprite = nextspritestat[nSprite]) { SPRITE *pSprite = &sprite[nSprite]; XSPRITE *pXSprite = &xsprite[pSprite->extra]; SPRITE *pTarget = &sprite[pXSprite->target]; viewBackupSpriteLoc(nSprite, pSprite); int nAngle = pTarget->ang + pXSprite->goalAng; int x = pTarget->x + mulscale30r( Cos(nAngle), pTarget->clipdist << 1 ); // halfway into clipdist int y = pTarget->y + mulscale30r( Sin(nAngle), pTarget->clipdist << 1 ); int z = pTarget->z + pXSprite->targetZ; setsprite((short)pXSprite->reference, x, y, z); } aiProcessDudes(); } /*********************************************************************** * actSpawnSprite() * * Spawns a new sprite at the specified world coordinates. **********************************************************************/ int actSpawnSprite( short nSector, int x, int y, int z, short nStatus, BOOL bAddXSprite ) { int nSprite = insertsprite( nSector, nStatus ); if (nSprite >= 0) sprite[nSprite].extra = -1; else { dprintf("Out of sprites -- reclaiming sprite from purge list\n"); nSprite = headspritestat[kStatPurge]; dassert(nSprite >= 0); changespritesect((short)nSprite, nSector); changespritestat((short)nSprite, nStatus); } setsprite( (short)nSprite, x, y, z ); SPRITE *pSprite = &sprite[nSprite]; pSprite->type = 0; viewBackupSpriteLoc(nSprite, pSprite); // optionally create an xsprite if ( bAddXSprite && pSprite->extra == -1 ) { int nXSprite = dbInsertXSprite(nSprite); gSpriteHit[nXSprite].floorHit = 0; gSpriteHit[nXSprite].ceilHit = 0; } return nSprite; } int actCloneSprite( SPRITE *pSourceSprite ) { int nSprite = insertsprite( pSourceSprite->sectnum, pSourceSprite->statnum); if ( nSprite < 0 ) { dprintf("Out of sprites -- reclaiming sprite from purge list\n"); nSprite = headspritestat[kStatPurge]; dassert(nSprite >= 0); changespritesect((short)nSprite, pSourceSprite->sectnum); changespritestat((short)nSprite, pSourceSprite->statnum); } SPRITE *pSprite = &sprite[nSprite]; *pSprite = *pSourceSprite; viewBackupSpriteLoc(nSprite, pSprite); // don't copy xsprite pSprite->extra = -1; return nSprite; } int actSpawnThing( short nSector, int x, int y, int z, int thingType ) { dassert( thingType >= kThingBase && thingType < kThingMax ); int nThing = actSpawnSprite(nSector, x, y, z, kStatThing, TRUE ); SPRITE *pThing = &sprite[nThing]; pThing->type = (short)thingType; int thingIndex = thingType - kThingBase; int nXThing = pThing->extra; dassert(nXThing > 0 && nXThing < kMaxXSprites); XSPRITE *pXThing = &xsprite[nXThing]; pThing->clipdist = thingInfo[thingIndex].clipdist; pThing->flags = thingInfo[thingIndex].flags; if ( pThing->flags & kAttrGravity ) pThing->flags |= kAttrFalling; pThing->pal = 0; pXThing->health = thingInfo[thingIndex].startHealth << 4; SetBitString(show2dsprite, nThing); switch (thingType) { case kThingTNTStick: pThing->shade = -32; pThing->picnum = 2169; pThing->cstat |= kSpriteHitscan; pThing->xrepeat = pThing->yrepeat = 32; break; case kThingTNTBundle: pThing->shade = -32; pThing->picnum = 2172; pThing->cstat |= kSpriteHitscan; pThing->xrepeat = pThing->yrepeat = 32; break; case kThingTNTProxArmed: pThing->shade = -16; pThing->picnum = kAnmTNTProxArmed; pThing->cstat |= kSpriteHitscan; pThing->xrepeat = pThing->yrepeat = 32; break; case kThingTNTRemArmed: pThing->shade = -16; pThing->picnum = kAnmTNTRemArmed; pThing->cstat |= kSpriteHitscan; pThing->xrepeat = pThing->yrepeat = 32; break; case kThingBoneClub: pThing->shade = 0; pThing->picnum = kAnmBoneClub; pThing->cstat |= kSpriteHitscan; pThing->xrepeat = pThing->yrepeat = 32; break; case kThingWaterDrip: pThing->picnum = kAnmDrip; pThing->pal = kPLUCold; //pThing->cstat |= kSpriteTranslucent | kSpriteTranslucentR; pThing->shade = 0; // -12; break; case kThingBloodDrip: pThing->picnum = kAnmDrip; pThing->pal = kPLURed; //pThing->cstat |= kSpriteTranslucent | kSpriteTranslucentR; pThing->shade = 0; // -12; break; } return nThing; } int actFireThing( int nActor, int z, int nSlope, int thingType, int velocity ) { dassert( thingType >= kThingBase && thingType < kThingMax ); SPRITE *pActor = &sprite[nActor]; int nThing = actSpawnThing(pActor->sectnum, pActor->x + mulscale30(pActor->clipdist << 2, Cos(pActor->ang)), pActor->y + mulscale30(pActor->clipdist << 2, Sin(pActor->ang)), z, thingType); SPRITE *pThing = &sprite[nThing]; pThing->owner = (short)nActor; pThing->ang = pActor->ang; pThing->xvel = (short)mulscale30(velocity, Cos(pActor->ang)); pThing->yvel = (short)mulscale30(velocity, Sin(pActor->ang)); pThing->zvel = (short)mulscale(velocity, nSlope, 14); pThing->xvel += pActor->xvel / 2; pThing->yvel += pActor->yvel / 2; pThing->zvel += pActor->zvel / 2; return nThing; } void actFireMissile( int nActor, int z, int dx, int dy, int dz, int missileType ) { dassert( missileType >= kMissileBase && missileType < kMissileMax ); SPRITE *pActor = &sprite[nActor]; int nSprite = actSpawnSprite(pActor->sectnum, pActor->x + mulscale30(pActor->clipdist << 1, Cos(pActor->ang)), pActor->y + mulscale30(pActor->clipdist << 1, Sin(pActor->ang)), z, kStatMissile, TRUE ); SPRITE *pSprite = &sprite[nSprite]; pSprite->type = (short)missileType; MissileType *pMissType = &missileInfo[ missileType - kMissileBase ]; int velocity = pMissType->velocity; pSprite->shade = pMissType->shade; pSprite->pal = 0; pSprite->clipdist = 16; pSprite->flags = kAttrMove; pSprite->xrepeat = pMissType->xrepeat; pSprite->yrepeat = pMissType->yrepeat; pSprite->picnum = pMissType->picnum; pSprite->ang = (short)((pActor->ang + missileInfo[missileType - kMissileBase].angleOfs) & kAngleMask); pSprite->xvel = (short)mulscale(velocity, dx, 14); pSprite->yvel = (short)mulscale(velocity, dy, 14); pSprite->zvel = (short)mulscale(velocity, dz, 14); pSprite->owner = (short)nActor; SetBitString(show2dsprite, nSprite); int nXSprite = pSprite->extra; dassert(nXSprite > 0 && nXSprite < kMaxXSprites); XSPRITE *pXSprite = &xsprite[nXSprite]; switch( missileType ) { case kMissileButcherKnife: pSprite->cstat |= kSpriteWall; break; case kMissileSpear: pSprite->cstat |= kSpriteWall; break; case kMissileEctoSkull: seqSpawn(kSeqSkull, SS_SPRITE, nXSprite); break; case kMissileFireball: seqSpawn(kSeqFireball, SS_SPRITE, nXSprite, &FireballCallback); break; case kMissileHoundFire: seqSpawn(kSeqHoundFire, SS_SPRITE, nXSprite); pSprite->xvel += (pActor->xvel / 2) + BiRandom(50); pSprite->yvel += (pActor->yvel / 2) + BiRandom(50); pSprite->zvel += (pActor->zvel / 2) + BiRandom(50); break; case kMissileSprayFlame: if ( Chance(0x8000) ) seqSpawn(kSeqSprayFlame1, SS_SPRITE, nXSprite); else seqSpawn(kSeqSprayFlame2, SS_SPRITE, nXSprite); pSprite->xvel += (pActor->xvel / 2) + BiRandom(50); pSprite->yvel += (pActor->yvel / 2) + BiRandom(50); pSprite->zvel += (pActor->zvel / 2) + BiRandom(50); break; case kMissileStarburstFlare: evPost(nSprite, SS_SPRITE, kTimerRate/6 ); // callback to flare break; case kMissileFlare: case kMissileExplodingFlare: //seqSpawn(kSeqFlare, SS_SPRITE, nXSprite, &FlareCallback); //pSprite->pal = kPLURed; break; default: break; } } /*********************************************************************** * actSpawnEffect() * * Spawns an effect sprite at the specified world coordinates. **********************************************************************/ SPRITE *actSpawnEffect( short nSector, int x, int y, int z, int nEffect ) { int nSprite = actSpawnSprite(nSector, x, y, z, kStatEffect, TRUE); SPRITE *pSprite = &sprite[nSprite]; int nXSprite = pSprite->extra; dassert(nXSprite > 0 && nXSprite < kMaxXSprites); switch( nEffect ) { case ET_Splash1: // splash from dude landing in water seqSpawn(kSeqSplash1, SS_SPRITE, nXSprite); break; case ET_Splash2: // splash from bullet hitting water seqSpawn(kSeqSplash2, SS_SPRITE, nXSprite); break; case ET_Ricochet1: // bullet ricochet off stone/metal if ( Chance(0x8000) ) pSprite->cstat |= kSpriteFlipX; seqSpawn(kSeqRicochet1, SS_SPRITE, nXSprite); break; case ET_Ricochet2: // bullet ricochet off wood/dirt/clay if ( Chance(0x8000) ) pSprite->cstat |= kSpriteFlipX; seqSpawn(kSeqRicochet2, SS_SPRITE, nXSprite); break; case ET_Squib1: if ( Chance(0x8000) ) pSprite->cstat |= kSpriteFlipX; seqSpawn(kSeqSquib1, SS_SPRITE, nXSprite); break; case ET_SmokeTrail: if ( Chance(0x8000) ) pSprite->cstat |= kSpriteFlipX; if ( Chance(0x8000) ) pSprite->cstat |= kSpriteFlipY; seqSpawn(kSeqFlareSmoke, SS_SPRITE, nXSprite); break; default: // don't have a sequence yet, so just delete it for now dprintf("actSpawnEffect: missing sequence\n"); actPostSprite( nSprite, kStatFree ); return NULL; } return pSprite; } BOOL actCheckRespawn( int nSprite ) { SPRITE *pSprite = &sprite[nSprite]; if ( pSprite->extra > 0 ) { XSPRITE *pXSprite = &xsprite[pSprite->extra]; if (pXSprite->respawn == kRespawnPermanent) return TRUE; // if (sprite respawn is forced OR (sprite has optional respawning set // AND (sprite is a dude AND global dude respawning is set) // OR (sprite is a thing AND global thing respawning is set))) if (pXSprite->respawn == kRespawnAlways || (pXSprite->respawn == kRespawnOptional && (pSprite->type >= kDudeBase && pSprite->type < kDudeMax && gRespawnEnemies) || ((pSprite->type < kDudeBase || pSprite->type > kDudeMax) && gRespawnItems))) { if (pXSprite->respawnTime > 0) { dprintf("Respawn statnum: %d\n",pSprite->statnum); pSprite->owner = pSprite->statnum; // store the sprite's status list for respawning actPostSprite( nSprite, kStatRespawn ); pSprite->cstat &= ~kSpriteBlocking & ~kSpriteHitscan; pSprite->cstat |= kSpriteInvisible; evPost(nSprite, SS_SPRITE, pXSprite->respawnTime * kTimerRate / 10, kCommandRespawn); return TRUE; } } } return FALSE; // indicate sprite will not respawn, and should be deleted, exploded, etc. } void actRespawnDude( int nSprite ) { SPRITE *pSprite = &sprite[nSprite]; XSPRITE *pXSprite = &xsprite[pSprite->extra]; // probably need more dude-specific initialization pXSprite->moveState = 0; pXSprite->aiState = 0; pXSprite->health = 0; pXSprite->target = 0; pXSprite->targetX = 0; pXSprite->targetY = 0; pXSprite->targetZ = 0; pXSprite->key = 0; // respawned dudes don't drop additional permanent keys } void actRespawnSprite( int nSprite ) { SPRITE *pSprite = &sprite[nSprite]; actPostSprite( nSprite, pSprite->owner ); pSprite->owner = -1; pSprite->cstat &= ~kSpriteInvisible; if ( pSprite->extra > 0) { XSPRITE *pXSprite = &xsprite[pSprite->extra]; pXSprite->isTriggered = 0; if (pSprite->type >= kDudeBase && pSprite->type < kDudeMax) actRespawnDude( nSprite ); } // spawn the respawn special effect int nRespawn = actSpawnSprite( pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, kStatDefault, TRUE ); seqSpawn(kSeqRespawn, SS_SPRITE, sprite[nRespawn].extra); } void actFireVector( int nActor, int z, int dx, int dy, int dz, VECTOR_TYPE vectorType ) { SPRITE *pActor = &sprite[nActor]; HITINFO hitInfo; dassert(vectorType >= 0 && vectorType < kVectorMax); VECTORDATA *pVectorData = &gVectorData[vectorType]; int maxDist = pVectorData->maxDist; int hitCode = VectorScan( pActor, z, dx, dy, dz, &hitInfo ); // determine a point just a bit back from the intersection to place the ricochet int rx = hitInfo.hitx - mulscale(dx, 1 << 4, 14); int ry = hitInfo.hity - mulscale(dy, 1 << 4, 14); int rz = hitInfo.hitz - mulscale(dz, 1 << 4, 14); BYTE nSurf = kSurfNone; if ( maxDist == 0 || qdist(hitInfo.hitx - pActor->x, hitInfo.hity - pActor->y) < maxDist ) { switch ( hitCode ) { case SS_CEILING: if ( sector[hitInfo.hitsect].ceilingstat & kSectorParallax ) nSurf = kSurfNone; else nSurf = surfType[sector[hitInfo.hitsect].ceilingpicnum]; break; case SS_FLOOR: if ( sector[hitInfo.hitsect].floorstat & kSectorParallax ) nSurf = kSurfNone; else nSurf = surfType[sector[hitInfo.hitsect].floorpicnum]; break; case SS_WALL: { int nWall = hitInfo.hitwall; dassert( nWall >= 0 && nWall < kMaxWalls ); nSurf = surfType[wall[nWall].picnum]; WALL *pWall = &wall[nWall]; int nXWall = wall[nWall].extra; if ( nXWall > 0 ) { XWALL *pXWall = &xwall[nXWall]; if ( pXWall->triggerImpact ) trTriggerWall(nWall, pXWall, kCommandWallImpact); } break; } case SS_MASKED: { int nWall = hitInfo.hitwall; dassert( nWall >= 0 && nWall < kMaxWalls ); nSurf = surfType[wall[nWall].overpicnum]; WALL *pWall = &wall[nWall]; int nXWall = wall[nWall].extra; if ( nXWall > 0 ) { XWALL *pXWall = &xwall[nXWall]; if ( pXWall->triggerImpact ) trTriggerWall(nWall, pXWall, kCommandWallImpact); } break; } case SS_SPRITE: { int nSprite = hitInfo.hitsprite; nSurf = surfType[sprite[nSprite].picnum]; dassert( nSprite >= 0 && nSprite < kMaxSprites); SPRITE *pSprite = &sprite[nSprite]; // back off ricochet (squibs) even more rx -= mulscale(dx, 7 << 4, 14); ry -= mulscale(dy, 7 << 4, 14); rz -= mulscale(dz, 7 << 4, 14); actDamageSprite(nActor, nSprite, pVectorData->damageType, pVectorData->damageValue << 4); int nXSprite = pSprite->extra; if ( nXSprite > 0 ) { XSPRITE *pXSprite = &xsprite[nXSprite]; if ( pXSprite->triggerImpact ) trTriggerSprite(nSprite, pXSprite, kCommandSpriteImpact); } break; } } } if ( pVectorData->impact[nSurf].nEffect >= 0 ) actSpawnEffect(hitInfo.hitsect, rx, ry, rz, pVectorData->impact[nSurf].nEffect); if ( pVectorData->impact[nSurf].nSoundId >= 0 ) sfxCreate3DSound(rx, ry, rz, pVectorData->impact[nSurf].nSoundId); } static void FireballCallback( int /* type */, int nXIndex ) { XSPRITE *pXSprite = &xsprite[nXIndex]; int nSprite = pXSprite->reference; SPRITE *pSprite = &sprite[nSprite]; SPRITE *pSmoke = actSpawnEffect( pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, ET_SmokeTrail ); if (gDetail < kDetailLevelMax) pSmoke->cstat |= kSpriteInvisible; } //static void FlareCallback( int /* type */, int nXIndex ) //{ // XSPRITE *pXSprite = &xsprite[nXIndex]; // int nSprite = pXSprite->reference; // SPRITE *pSprite = &sprite[nSprite]; // // actSpawnEffect( pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, ET_SmokeTrail ); //} struct POSTPONE { short nSprite; short nStatus; }; int gPostCount = 0; POSTPONE gPost[ kMaxSprites ]; /*********************************************************************** * actPostSprite() * * Postpones deletion or status list change for a sprite. * An nStatus value of kStatFree passed to this function will * postpone deletion of the sprite until the gPostpone list is * next processed. **********************************************************************/ void actPostSprite( int nSprite, int nStatus ) { dassert( gPostCount < kMaxSprites ); dassert( nSprite < kMaxSprites && sprite[nSprite].statnum < kMaxStatus ); dassert( nStatus >= 0 && nStatus <= kStatFree ); // see if it is already in the list (we may want to semaphore with an attr bit for speed) for (int n = 0; n < gPostCount; n++) if ( gPost[n].nSprite == nSprite ) break; gPost[n].nSprite = (short)nSprite; gPost[n].nStatus = (short)nStatus; if ( n == gPostCount ) gPostCount++; } /*********************************************************************** * actPostProcess() * * Processes postponed sprite events to ensure that sprite list * processing functions normally when sprites are deleted or change * status. * **********************************************************************/ void actPostProcess( void ) { if (gPostCount) { for ( int i = 0; i < gPostCount; i++ ) { POSTPONE *pPost = &gPost[i]; if ( pPost->nStatus == kStatFree ) deletesprite( pPost->nSprite ); else changespritestat( pPost->nSprite, pPost->nStatus ); } gPostCount = 0; } } static void MakeSplash( SPRITE *pSprite, XSPRITE *pXSprite ) { int nXSprite = pSprite->extra; int nSprite = pXSprite->reference; pSprite->flags &= ~kAttrGravity; // no bouncing... pSprite->z -= 4 << 8; // up one pixel viewBackupSpriteLoc(nSprite, pSprite); // prevent interpolation int nSurfType = tileGetSurfType(gSpriteHit[nXSprite].floorHit); switch ( pSprite->type ) { case kThingWaterDrip: switch (nSurfType) { case kSurfWater: seqSpawn(kSeqSplash1, SS_SPRITE, nXSprite); sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxDrip3, 0x2000); // add a check for depth that uses either kSfxDrip2 (deep) or kSfxDrip3 (not deep) break; default: seqSpawn(kSeqSplash2, SS_SPRITE, nXSprite); sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxDrip1, 0x2000); break; } break; case kThingBloodDrip: seqSpawn(kSeqSplash3, SS_SPRITE, nXSprite); sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxDrip1, 0x2000); break; } }