#include #include #include #include "actor.h" #include "ai.h" #include "db.h" #include "debug4g.h" #include "dude.h" #include "engine.h" #include "error.h" #include "eventq.h" #include "gameutil.h" #include "globals.h" #include "misc.h" #include "multi.h" #include "names.h" #include "options.h" #include "player.h" #include "screen.h" #include "sectorfx.h" #include "seq.h" #include "tile.h" #include "trig.h" #include "triggers.h" #include "view.h" #include "warp.h" #include "weapon.h" #include "aizomba.h" // currently required for lackeys #include "sfx.h" #include "sound.h" // can be removed once sfx complete #define kLookMax 60 #define kHorizUpMax 120 #define kHorizDownMax 180 // this should probably go into the posture table too #define kClimbSpeed 160 #define kPowerUpTime (kTimerRate * 30) #define kMaxPowerUpTime ((kTimerRate * 60) * 60) PLAYER gPlayer[kMaxPlayers]; PLAYER *gMe = NULL; PLAYER *gView = NULL; INPUT gPlayerInput[kMaxPlayers]; /******************************************************************************* Notes on the posture table: Acceleration units are units per clock tick. If the timer rate is changed, these values will need to change as well. *******************************************************************************/ POSTURE gPosture[] = { // player posture { 10, // frontAccel 25, 5, // sideAccel 30, 5, // backAccel 20, 350, // frontSpeed[0] 350, 800, // frontSpeed[1] 700, 300, // sideSpeed[0] 300, 500, // sideSpeed[1] 500, 250, // backSpeed[0] 250, 500, // backSpeed[1] 500, 100, // pace[0] 140, // pace[1] 3, // bobV 2, // bobH 4, // swayV 10, // swayH }, // beast posture ******** THESE ALL NEED TO LOOK LIKE ABOVE (TO DO!) ********** { 20, // frontAccel 20, 40, // sideAccel 40, 24, // backAccel 24, 500, // frontSpeed[0] 500, 1000, // frontSpeed[1] 1000, 400, // sideSpeed[0] 400, 800, // sideSpeed[1] 800, 300, // backSpeed[0] 300, 600, // backSpeed[1] 600, 100, // pace[0] 80, // pace[1] 12, // bobV 4, // bobH 8, // swayV 16, // swayH }, }; AMMOINFO gAmmoInfo[kAmmoMax] = { // max, vectorType { kTimerRate*40, kVectorNone }, // kAmmoSprayCan { 24, kVectorNone }, // kAmmoTNTStick { 8, kVectorNone }, // kAmmoTNTProximity { 8, kVectorNone }, // kAmmoTNTRemote { 50, kVectorShell }, // kAmmoShell { 500, kVectorBullet }, // kAmmoBullet { 500, kVectorBulletAP }, // kAmmoBulletAP { 24, kVectorNone }, // kAmmoFlare { 24, kVectorNone }, // kAmmoFlareSB { 10, kVectorNone }, // kAmmoVoodoo { 24, kVectorNone }, // kAmmoSpear { 24, kVectorNone }, // kAmmoSpearXP }; #if 0 enum POWERUP { kPupFeatherFall = 0, kPupJumpBoots, kPupInvisible, kPupInvulnerable, kPupJumpBoots, kPupRavenFlight, kPupGunsAkimbo, kPupDivingSuit, kPupGasMask, kPupClone, kPupCrystalBall, kPupDecoy, kPupDoppleganger, kPupReflectiveShots, kPupRoseGlasses, kPupShadowCloak, kPupShroomRage, kPupShroomDelirium, kPupShroomGrow, kPupShroomShrink, kPupDeathMask, kPupAsbestosArmor, kMaxPowerUps }; enum POWERUPFLAGS { kPowerPermanent = 0x0001, kPowerUnique = 0x0002, }; #endif struct POWERUPINFO { short picnum; long zoom; BOOL isUnique; int addPower; int maxPower; }; POWERUPINFO gPowerUpInfo[ kMaxPowerUps ] = { {-1, 0, TRUE, 1, 1}, // kItemKey1 {-1, 0, TRUE, 1, 1}, // kItemKey2 {-1, 0, TRUE, 1, 1}, // kItemKey3 {-1, 0, TRUE, 1, 1}, // kItemKey4 {-1, 0, TRUE, 1, 1}, // kItemKey5 {-1, 0, TRUE, 1, 1}, // kItemKey6 {-1, 0, TRUE, 1, 1}, // kItemKey7 {-1, 0, FALSE, 10, 100}, // kItemDoctorBag INVENTORY {-1, 0, FALSE, 5, 100}, // kItemMedPouch {-1, 0, FALSE, 20, 100}, // kItemLifeEssence {-1, 0, FALSE, 100, 200}, // kItemLifeSeed {-1, 0, FALSE, 2, 200}, // kItemPotion1 {kAnmFeather, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemFeatherFall INVENTORY {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemLtdInvisibility {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemInvulnerability {kPicJumpBoots, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemJumpBoots INVENTORY {kPicRavenFlight, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemRavenFlight INVENTORY {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemGunsAkimbo {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemDivingSuit INVENTORY {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemGasMask INVENTORY or CUT {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemClone {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemCrystalBall INVENTORY {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemDecoy INVENTORY {kAnmDoppleganger, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemDoppleganger {kAnmReflectShots, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemReflectiveShots {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemRoseGlasses INVENTORY {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemShadowCloak {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemShroomRage {-1, 0, FALSE, kPowerUpTime/4, kMaxPowerUpTime}, // kItemShroomDelirium {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemShroomGrow {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemShroomShrink {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemDeathMask INVENTORY {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemWineGoblet {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemWineBottle {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemSkullGrail {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemSilverGrail {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemTome {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemBlackChest {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemWoodenChest {-1, 0, FALSE, kPowerUpTime, kMaxPowerUpTime}, // kItemAsbestosArmor INVENTORY }; int powerupCheck( PLAYER *pPlayer, int nPowerUp ) { dassert( pPlayer != NULL ); dassert( nPowerUp >= 0 && nPowerUp < kMaxPowerUps ); return pPlayer->powerUpTimer[nPowerUp]; } void powerupDraw( PLAYER *pView ) { for (int nPowerUp = kItemMax - kItemBase - 1; nPowerUp >= 0; nPowerUp--) { int nPowerRemaining = powerupCheck( pView, nPowerUp ); if ( nPowerRemaining ) { POWERUPINFO *pInfo = &gPowerUpInfo[ nPowerUp ]; switch( nPowerUp + kItemBase ) // switch off of actual type { case kItemFeatherFall: case kItemLtdInvisibility: case kItemInvulnerability: case kItemJumpBoots: case kItemRavenFlight: case kItemGunsAkimbo: case kItemDivingSuit: case kItemGasMask: case kItemClone: case kItemCrystalBall: case kItemDecoy: case kItemDoppleganger: case kItemReflectiveShots: case kItemRoseGlasses: case kItemShadowCloak: case kItemShroomRage: case kItemShroomDelirium: case kItemShroomGrow: case kItemShroomShrink: case kItemDeathMask: case kItemWineGoblet: case kItemWineBottle: case kItemSkullGrail: case kItemSilverGrail: case kItemTome: case kItemBlackChest: case kItemWoodenChest: case kItemAsbestosArmor: { int nFlags = kRotateNormal; if ( nPowerRemaining < (kTimerRate * 8) && ( gGameClock & 32 ) ) nFlags |= kRotateTranslucent; short nTile = pInfo->picnum; if (nTile >= 0) { nTile += animateoffs(nTile, 0); rotatesprite(xdim<<15, ydim<<15, pInfo->zoom, 0, nTile, -128, kPLUNormal, (char)nFlags, gViewX0, gViewY0, gViewX1, gViewY1); } } break; default: break; } } } } BOOL powerupActivate( PLAYER *pPlayer, int nPowerUp ) { // char buffer[80]; // skip the power-up if it is unique and already activated if ( powerupCheck( pPlayer, nPowerUp ) > 0 && gPowerUpInfo[nPowerUp].isUnique ) return FALSE; pPlayer->powerUpTimer[nPowerUp] = ClipHigh( pPlayer->powerUpTimer[nPowerUp] + gPowerUpInfo[nPowerUp].addPower, gPowerUpInfo[nPowerUp].maxPower ); DUDEINFO *pDudeInfo = &dudeInfo[pPlayer->sprite->type - kDudeBase]; switch( nPowerUp + kItemBase ) // switch off of actual type { case kItemFeatherFall: case kItemJumpBoots: pDudeInfo->damageShift[ kDamageFall ] += kNoDamage; break; case kItemInvulnerability: { for ( int i = 0; i < kDamageMax; i++ ) pDudeInfo->damageShift[ i ] += kNoDamage; break; } case kItemDivingSuit: pDudeInfo->damageShift[ kDamageDrown ] += kNoDamage; break; case kItemGasMask: pDudeInfo->damageShift[ kDamageGas ] += kNoDamage; break; case kItemAsbestosArmor: pDudeInfo->damageShift[ kDamageBurn ] += kNoDamage; break; default: break; } SPRITE *pSprite = pPlayer->sprite; sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxPlayPowerUp); // sprintf( buffer, "Activating %s for %i", // gItemText[ nPowerUp ], pPlayer->powerUpTimer[nPowerUp] ); // viewSetMessage(buffer); return TRUE; } void powerupDeactivate( PLAYER *pPlayer, int nPowerUp ) { DUDEINFO *pDudeInfo = &dudeInfo[pPlayer->sprite->type - kDudeBase]; switch( nPowerUp + kItemBase ) // switch off of actual type { case kItemFeatherFall: case kItemJumpBoots: pDudeInfo->damageShift[ kDamageFall ] -= kNoDamage; break; case kItemInvulnerability: { for ( int i = 0; i < kDamageMax; i++ ) pDudeInfo->damageShift[ i ] -= kNoDamage; break; } case kItemDivingSuit: pDudeInfo->damageShift[ kDamageDrown ] -= kNoDamage; break; case kItemGasMask: pDudeInfo->damageShift[ kDamageGas ] -= kNoDamage; break; case kItemAsbestosArmor: pDudeInfo->damageShift[ kDamageBurn ] -= kNoDamage; break; default: break; } pPlayer->powerUpTimer[ nPowerUp ] = 0; } void powerupProcess( PLAYER *pPlayer ) { for (int nPowerUp = kItemMax - kItemBase - 1; nPowerUp >= 0; nPowerUp--) { if ( pPlayer->powerUpTimer[nPowerUp] ) { pPlayer->powerUpTimer[nPowerUp] = ClipLow(pPlayer->powerUpTimer[nPowerUp] - kFrameTicks, 0); if ( !powerupCheck( pPlayer, nPowerUp ) ) powerupDeactivate( pPlayer, nPowerUp ); } } } void powerupClear( PLAYER *pPlayer ) { for (int nPowerUp = kItemMax - kItemBase - 1; nPowerUp >= 0; nPowerUp--) { pPlayer->powerUpTimer[nPowerUp] = 0; } } void powerupInit( void ) { for (int nPowerUp = kItemMax - kItemBase - 1; nPowerUp >= 0; nPowerUp--) { POWERUPINFO *pInfo = &gPowerUpInfo[ nPowerUp ]; dassert( pInfo->picnum < kMaxTiles ); if ( pInfo->picnum >= 0 ) { int maxSize = 0; PICANM *pAnm = &picanm[ pInfo->picnum ]; int nFrame = (pAnm->type != 0) ? pAnm->frames : 0; for (; nFrame >= 0; nFrame-- ) { if (pAnm->type == 3) // special case for backward anims { maxSize = tilesizx[ pInfo->picnum - nFrame ]; if ( maxSize < tilesizy[ pInfo->picnum - nFrame ] ) maxSize = tilesizy[ pInfo->picnum - nFrame ]; } else { maxSize = tilesizx[ pInfo->picnum + nFrame ]; if ( maxSize < tilesizy[ pInfo->picnum + nFrame ] ) maxSize = tilesizy[ pInfo->picnum + nFrame ]; } } dassert( maxSize > 0 ); pInfo->zoom = ( 24 /*kPowerUpFrameSize*/ << 16 ) / maxSize; } } } void playerSetRace( PLAYER *pPlayer, int nLifeMode, int nType ) { dassert( nLifeMode >= kModeHuman && nLifeMode <= kModeBeast ); int dudeIndex = nType - kDudeBase; pPlayer->lifemode = nLifeMode; dudeInfo[dudeIndex] = gPlayerTemplate[ nLifeMode ]; } void playerSetGodMode( PLAYER *pPlayer, BOOL nMode ) { DUDEINFO *pDudeInfo = &dudeInfo[pPlayer->sprite->type - kDudeBase]; if ( nMode ) { for ( int i = 0; i < kDamageMax; i++ ) pDudeInfo->damageShift[i] += kNoDamage; } else { for ( int i = 0; i < kDamageMax; i++ ) pDudeInfo->damageShift[i] -= kNoDamage; } pPlayer->godMode = nMode; pPlayer->xsprite->health = pDudeInfo->startHealth << 4; } void playerReset( PLAYER *pPlayer, INPUT *pInput, int type ) { dprintf("Resetting player %d\n", type - kDudePlayer1); // get the normal player starting position, else if in bloodbath mode, // randomly pick one of kMaxPlayers starting positions ZONE *pZone; if (gNetMode == kNetModeOff || gNetMode == kNetModeCoop) pZone = &gStartZone[ type - kDudePlayer1 ]; else pZone = &gStartZone[ Random(numplayers)]; if ( pPlayer->sprite != NULL ) { // ADD: make the dead body a non-player type } int nSprite = actSpawnSprite( pZone->sector, pZone->x, pZone->y, pZone->z, kStatDude, TRUE ); SPRITE *pSprite = &sprite[nSprite]; dassert(pSprite->extra > 0 && pSprite->extra < kMaxXSprites); XSPRITE *pXSprite = &xsprite[pSprite->extra]; int dudeIndex = type - kDudeBase; playerSetRace( pPlayer, kModeHuman, type ); seqSpawn(dudeInfo[dudeIndex].seqStartID + kSeqDudeIdle, SS_SPRITE, pSprite->extra); // make the player's own sprite visible if ( pPlayer == gMe ) SetBitString(show2dsprite, nSprite); // raise the player up off the floor int zTop, zBot; GetSpriteExtents(pSprite, &zTop, &zBot); pSprite->z -= zBot - pSprite->z; pPlayer->sprite = pSprite; pPlayer->nSprite = nSprite; pPlayer->xsprite = pXSprite; pSprite->ang = pZone->angle; pSprite->type = (short)type; pSprite->clipdist = dudeInfo[dudeIndex].clipdist; pSprite->flags = kAttrMove | kAttrGravity | kAttrFalling; pXSprite->health = dudeInfo[dudeIndex].startHealth << 4; pXSprite->moveState = kMoveWalk; pXSprite->burnTime = 0; pXSprite->burnSource = -1; pPlayer->eyeAboveZ = dudeInfo[dudeIndex].eyeHeight * pSprite->yrepeat << 2; pPlayer->weaponAboveZ = pPlayer->eyeAboveZ / 2; pPlayer->bloodlust = 0; pPlayer->impactPE = 0; pPlayer->impactPhase = 0; pPlayer->standSpeed = 0; pPlayer->compression = 0; pPlayer->viewOffZ = 0; pPlayer->weapOffZ = 0; pPlayer->viewOffdZ = 0; pPlayer->weapOffdZ = 0; pPlayer->horiz = 0; pPlayer->look = 0; pPlayer->slope = 0; pPlayer->fraggerID = -1; pPlayer->airTime = 0; pPlayer->bloodTime = 0; pPlayer->gooTime = 0; pPlayer->wetTime = 0; pPlayer->godMode = FALSE; pPlayer->relAim.dx = 1 << 14; pPlayer->relAim.dy = 0; pPlayer->relAim.dz = 0; pPlayer->aimSprite = -1; for (int i = 0; i < 8; i++) pPlayer->hasKey[i] = 0; pPlayer->weapon = kWeaponNone; pPlayer->deathTime = 0; pSprite->xvel = 0; pSprite->yvel = 0; pSprite->zvel = 0; pInput->forward = 0; pInput->strafe = 0; pInput->turn = 0; pInput->syncFlags.byte = 0; pInput->buttonFlags.byte = 0; pInput->keyFlags.byte = 0; pInput->newWeapon = kWeaponPitchfork; scrDacAbsEffect(0, 0, 0); for (i = 0; i < kWeaponMax; i++) { pPlayer->hasWeapon[i] = gInfiniteAmmo; pPlayer->weaponMode[i] = 0; } pPlayer->hasWeapon[kWeaponPitchfork] = TRUE; for (i = 0; i < kAmmoMax; i++) { pPlayer->ammoCount[i] = gInfiniteAmmo ? gAmmoInfo[i].max : 0; } pPlayer->weaponTimer = 0; // idle pPlayer->weaponState = 0; pPlayer->pWeaponQAV = NULL; for (i = 0; i < kMaxPowerUps; i++) pPlayer->powerUpTimer[i] = 0; for (i = 0; i < kMaxLackeys; i++) pPlayer->nLackeySprites[i] = -1; } void playerInit( int nPlayer ) { PLAYER *pPlayer = &gPlayer[nPlayer]; dprintf("Initializing player %d\n", nPlayer); pPlayer->sprite = NULL; pPlayer->teamID = nPlayer; pPlayer->fragCount = 0; memset(pPlayer->fragInfo, 0, sizeof(pPlayer->fragInfo)); playerReset(pPlayer, &gPlayerInput[nPlayer], kDudePlayer1 + nPlayer); } static int CheckTouchSprite( SPRITE *pSprite ) { int i, next; int dx, dy, dz; for (i = headspritestat[kStatItem]; i >= 0; i = next) { next = nextspritestat[i]; dx = qabs(pSprite->x - sprite[i].x) >> 4; if (dx < kTouchXYDist) { dy = qabs(pSprite->y - sprite[i].y) >> 4; if (dy < kTouchXYDist) { int zTop, zBot; GetSpriteExtents(pSprite, &zTop, &zBot); dz = 0; if ( sprite[i].z < zTop ) dz = (zTop - sprite[i].z) >> 8; else if ( sprite[i].z > zBot ) dz = (sprite[i].z - zBot) >> 8; if (dz < kTouchZDist) { if (qdist(dx, dy) < kTouchXYDist) { int zTop, zBot; GetSpriteExtents(&sprite[i], &zTop, &zBot); if ( // center cansee(pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, sprite[i].x, sprite[i].y, sprite[i].z, sprite[i].sectnum) || // top cansee(pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, sprite[i].x, sprite[i].y, zTop, sprite[i].sectnum) || // bottom cansee(pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, sprite[i].x, sprite[i].y, zBot, sprite[i].sectnum) ) return i; } } } } } return -1; } static BOOL PickupItem( PLAYER *pPlayer, int nSprite, int nItemType ) { SPRITE *pSprite = pPlayer->sprite; XSPRITE *pXSprite = pPlayer->xsprite; int nPowerUp = nItemType - kItemBase; switch( nItemType ) { case kItemKey1: case kItemKey2: case kItemKey3: case kItemKey4: case kItemKey5: case kItemKey6: case kItemKey7: if ( pPlayer->hasKey[nItemType - kItemKey1 + 1] ) return FALSE; pPlayer->hasKey[nItemType - kItemKey1 + 1] = sprite[nSprite].picnum; break; case kItemDoctorBag: case kItemPotion1: case kItemMedPouch: case kItemLifeEssence: case kItemLifeSeed: if ( !actHealDude(pXSprite, gPowerUpInfo[nPowerUp].addPower, gPowerUpInfo[nPowerUp].maxPower) ) return FALSE; break; default: if ( !powerupActivate( pPlayer, nPowerUp ) ) return FALSE; break; } sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxPlayItemUp); return TRUE; } static BOOL PickupAmmo( PLAYER *pPlayer, int /*nSprite*/, int nAmmoItemType ) { AMMOITEMDATA *pAmmoData = &gAmmoItemData[nAmmoItemType - kAmmoItemBase]; int nAmmoType = pAmmoData->ammoType; if ( pPlayer->ammoCount[nAmmoType] >= gAmmoInfo[nAmmoType].max ) return FALSE; pPlayer->ammoCount[nAmmoType] = ClipHigh( pPlayer->ammoCount[nAmmoType] + pAmmoData->count, gAmmoInfo[nAmmoType].max ); // set the hasWeapon flags for weapons which are ammo if ( pAmmoData->weaponType != kWeaponNone ) pPlayer->hasWeapon[pAmmoData->weaponType] = TRUE; SPRITE *pSprite = pPlayer->sprite; sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxPlayItemUp); return TRUE; } static BOOL PickupWeapon( PLAYER *pPlayer, int nSprite, int nWeaponItem ) { WEAPONITEMDATA *pWeaponData = &gWeaponItemData[nWeaponItem - kWeaponItemBase]; int nWeapon = pWeaponData->weaponType; int nAmmo = pWeaponData->ammoType; // add weapon to player inventory if ( !pPlayer->hasWeapon[nWeapon] ) { pPlayer->hasWeapon[nWeapon] = TRUE; if ( nAmmo != kAmmoNone ) { // add preloaded ammo pPlayer->ammoCount[nAmmo] = ClipHigh( pPlayer->ammoCount[nAmmo] + pWeaponData->count, gAmmoInfo[nAmmo].max ); } SPRITE *pSprite = pPlayer->sprite; // sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxWeaponUp); return TRUE; } if ( nAmmo == kAmmoNone ) return FALSE; if ( pPlayer->ammoCount[nAmmo] >= gAmmoInfo[nAmmo].max ) return FALSE; BOOL isPermanent = FALSE; if ( sprite[nSprite].extra > 0) { XSPRITE *pXItem = &xsprite[sprite[nSprite].extra]; if ( pXItem->respawn == kRespawnPermanent ) isPermanent = TRUE; } if ( !isPermanent ) { // add preloaded ammo pPlayer->ammoCount[nAmmo] = ClipHigh( pPlayer->ammoCount[nAmmo] + pWeaponData->count, gAmmoInfo[nAmmo].max ); SPRITE *pSprite = pPlayer->sprite; // sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxWeaponUp); return TRUE; } return FALSE; } static void PickUp( PLAYER *pPlayer ) { int x, y, z; SPRITE *pSprite = pPlayer->sprite; XSPRITE *pXSprite = pPlayer->xsprite; char buffer[80]; x = pSprite->x; y = pSprite->y; z = pSprite->z; int nSprite = CheckTouchSprite(pSprite); if (nSprite >= 0) { BOOL bPickedUp = FALSE; int nType = sprite[nSprite].type; if (nType >= kItemBase && nType <= kItemMax) { bPickedUp = PickupItem( pPlayer, nSprite, nType ); sprintf(buffer, "Picked up %s", gItemText[ nType - kItemBase ] ); } else if (nType >= kAmmoItemBase && nType < kAmmoItemMax) { bPickedUp = PickupAmmo( pPlayer, nSprite, nType ); sprintf(buffer, "Picked up %s", gAmmoText[ nType - kAmmoItemBase ] ); } else if (nType >= kWeaponItemBase && nType < kWeaponItemMax) { bPickedUp = PickupWeapon( pPlayer, nSprite, nType ); sprintf(buffer, "Picked up %s", gWeaponText[ nType - kWeaponItemBase ] ); } if (bPickedUp) { if ( sprite[nSprite].extra > 0) { XSPRITE *pXItem = &xsprite[sprite[nSprite].extra]; if ( pXItem->triggerPickup ) trTriggerSprite(nSprite, pXItem, kCommandSpritePickup); } if ( !actCheckRespawn( nSprite ) ) actPostSprite( nSprite, kStatFree ); if (pPlayer == gMe) { viewSetMessage(buffer); scrDacRelEffect(16, 16, -16); } } } } static int ActionScan( PLAYER *pPlayer, int *nIndex, int *nXIndex) { SPRITE *pSprite = pPlayer->sprite; HITINFO hitInfo; *nIndex = 0; *nXIndex = 0; int dx = Cos(pSprite->ang) >> 16; int dy = Sin(pSprite->ang) >> 16; int dz = pPlayer->slope; int hitType = HitScan(pSprite, pSprite->z - pPlayer->eyeAboveZ, dx, dy, dz, &hitInfo); int hitDist = qdist(pSprite->x - hitInfo.hitx, pSprite->y - hitInfo.hity) >> 4; if ( hitDist < kPushXYDist ) { switch ( hitType ) { case SS_SPRITE: *nIndex = hitInfo.hitsprite; *nXIndex = sprite[*nIndex].extra; if (*nXIndex > 0 && xsprite[*nXIndex].triggerPush ) return SS_SPRITE; if ( sprite[*nIndex].statnum == kStatDude ) { SPRITE *pDude = &sprite[*nIndex]; XSPRITE *pXDude = &xsprite[*nXIndex]; dprintf("DUDE %d: ang=%d, goalAng=%d\n", *nIndex, pDude->ang, pXDude->goalAng); } break; case SS_MASKED: case SS_WALL: *nIndex = hitInfo.hitwall; *nXIndex = wall[*nIndex].extra; if (*nXIndex > 0 && xwall[*nXIndex].triggerPush ) return SS_WALL; if ( wall[*nIndex].nextsector >= 0) { *nIndex = wall[*nIndex].nextsector; *nXIndex = sector[*nIndex].extra; if (*nXIndex > 0 && xsector[*nXIndex].triggerWPush ) return SS_SECTOR; } break; case SS_FLOOR: case SS_CEILING: *nIndex = hitInfo.hitsect; *nXIndex = sector[*nIndex].extra; if (*nXIndex > 0 && xsector[*nXIndex].triggerPush ) return SS_SECTOR; break; } if ( pPlayer == gMe ) sfxStart3DSound(pPlayer->sprite->extra, kSfxPlayHunt, 0x1000); // push grunt } *nIndex = pSprite->sectnum; *nXIndex = sector[*nIndex].extra; if (*nXIndex > 0 && xsector[*nXIndex].triggerPush ) return SS_SECTOR; return -1; } static void ProcessInput( PLAYER *pPlayer, INPUT *pInput ) { long vel, svel, sin, cos; POSTURE *cp = &gPosture[pPlayer->lifemode]; SPRITE *pSprite = pPlayer->sprite; XSPRITE *pXSprite = pPlayer->xsprite; pPlayer->run = FALSE; BOOL run; WeaponProcess(pPlayer, pInput); // quick hack for death if (pXSprite->health == 0) { if ( pPlayer->deathTime == 0 ) { // pXSprite->avel = (pXSprite->avel + BiRandom(40)) & kAngleMask; } pPlayer->deathTime += kFrameTicks; pPlayer->horiz = mulscale16((1 << 15) - (Cos(ClipHigh(pPlayer->deathTime << 3, kAngle180)) >> 15), kHorizUpMax); pXSprite->moveState = kMoveDead; pPlayer->compression = mulscale16((1 << 15) - (Cos(ClipHigh(pPlayer->deathTime << 3, kAngle90)) >> 15), pPlayer->eyeAboveZ); if (pPlayer->weapon) pInput->newWeapon = pPlayer->weapon; // force weapon down if (pInput->keyFlags.action) { playerReset(pPlayer, pInput, pPlayer->sprite->type); pInput->keyFlags.action = 0; } return; // Don't allow the player to do anything else if dead } sin = Sin(pSprite->ang); cos = Cos(pSprite->ang); // find vel and svel relative to current angle vel = dmulscale30(pSprite->xvel, cos, pSprite->yvel, sin); svel = dmulscale30(pSprite->xvel, sin, -pSprite->yvel, cos); /* // ground deceleration switch ( pXSprite->moveState ) { case kMoveFall: case kMoveSwim: break; default: if (vel > 0) vel = ClipLow(vel - pInput->ticks * cp->decel, 0); else if (vel < 0) vel = ClipHigh(vel + pInput->ticks * cp->decel, 0); if (svel > 0) svel = ClipLow(svel - pInput->ticks * cp->sideDecel, 0); else if (svel < 0) svel = ClipHigh(svel + pInput->ticks * cp->sideDecel, 0); break; } */ // acceleration switch ( pXSprite->moveState ) { case kMoveWalk: case kMoveStand: case kMoveLand: case kMoveSwim: if ( pInput->forward > 0 ) { run = pInput->forward > pInput->ticks; pPlayer->run |= run; if ( vel < cp->frontSpeed[run] ); vel = ClipHigh(vel + cp->frontAccel * pInput->forward + kGroundFriction * kFrameTicks, cp->frontSpeed[run]); } else if ( pInput->forward < 0 ) { run = pInput->forward < -pInput->ticks; pPlayer->run |= run; if ( vel > -cp->backSpeed[run] ); vel = ClipLow(vel + cp->backAccel * pInput->forward - kGroundFriction * kFrameTicks, -cp->backSpeed[run]); } if ( pInput->strafe > 0 ) { run = pInput->strafe > pInput->ticks; pPlayer->run |= run; if ( svel < cp->sideSpeed[run] ); svel = ClipHigh(svel + cp->sideAccel * pInput->strafe + kGroundFriction * kFrameTicks, cp->sideSpeed[run]); } else if ( pInput->strafe < 0 ) { run = pInput->strafe < -pInput->ticks; pPlayer->run |= run; if ( svel > -cp->sideSpeed[run] ); svel = ClipLow(svel + cp->sideAccel * pInput->strafe - kGroundFriction * kFrameTicks, -cp->sideSpeed[run]); } break; } // turn player if (pInput->turn != 0) pSprite->ang = (short)((pSprite->ang + (pInput->turn * kFrameTicks >> 4)) & kAngleMask); // reconstruct x and y velocities pSprite->xvel = (short)dmulscale30(vel, cos, svel, sin); pSprite->yvel = (short)dmulscale30(vel, sin, -svel, cos); // action key if (pInput->keyFlags.action) { int nIndex, nXIndex; int keyId; switch ( ActionScan(pPlayer, &nIndex, &nXIndex) ) { case SS_SECTOR: { XSECTOR *pXSector = &xsector[nXIndex]; keyId = pXSector->key; dprintf("Key %d required\n", keyId); if ( pXSector->locked && pPlayer == gMe ) viewSetMessage("It's locked"); if ( keyId == 0 || pPlayer->hasKey[keyId] ) trTriggerSector(nIndex, pXSector, kCommandSpritePush); else if (pPlayer == gMe) { viewSetMessage("That requires a key."); } break; } case SS_WALL: { XWALL *pXWall = &xwall[nXIndex]; keyId = pXWall->key; dprintf("Key %d required\n", keyId); if ( pXWall->locked && pPlayer == gMe ) viewSetMessage("It's locked"); if ( keyId == 0 || pPlayer->hasKey[keyId] ) trTriggerWall(nIndex, pXWall, kCommandWallPush); else if (pPlayer == gMe) { viewSetMessage("That requires a key."); } break; } case SS_SPRITE: { XSPRITE *pXSprite = &xsprite[nXIndex]; keyId = pXSprite->key; dprintf("Key %d required\n", keyId); if ( pXSprite->locked && pPlayer == gMe ) viewSetMessage("It's locked"); if ( keyId == 0 || pPlayer->hasKey[keyId] ) trTriggerSprite(nIndex, pXSprite, kCommandSpritePush); else if (pPlayer == gMe) { viewSetMessage("That requires a key."); } break; } } // non-repeating, so clear flag pInput->keyFlags.action = 0; } if ( pInput->keyFlags.lookcenter && !pInput->buttonFlags.lookup && !pInput->buttonFlags.lookdown ) { if ( pPlayer->look < 0 ) pPlayer->look = ClipHigh(pPlayer->look + kFrameTicks, 0); if ( pPlayer->look > 0 ) pPlayer->look = ClipLow(pPlayer->look - kFrameTicks, 0); if ( pPlayer->look == 0 ) pInput->keyFlags.lookcenter = 0; } else { if ( pInput->buttonFlags.lookup ) pPlayer->look = ClipHigh(pPlayer->look + kFrameTicks, kLookMax); if ( pInput->buttonFlags.lookdown ) pPlayer->look = ClipLow(pPlayer->look - kFrameTicks, -kLookMax); } if ( pPlayer->look > 0 ) pPlayer->horiz = mulscale30(kHorizUpMax, Sin(pPlayer->look * (kAngle90 / kLookMax))); else if ( pPlayer->look < 0 ) pPlayer->horiz = mulscale30(kHorizDownMax, Sin(pPlayer->look * (kAngle90 / kLookMax))); else pPlayer->horiz = 0; pPlayer->slope = -pPlayer->horiz << 7; PickUp(pPlayer); } void playerMove( PLAYER *pPlayer, INPUT *pInput ) { int nSprite = pPlayer->nSprite; SPRITE *pSprite = pPlayer->sprite; int nXSprite = pSprite->extra; XSPRITE *pXSprite = pPlayer->xsprite; short nSector; int nXSector; XSECTOR *pXSector = NULL; POSTURE *cp = &gPosture[pPlayer->lifemode]; nSector = pSprite->sectnum; if ( (nXSector = sector[nSector].extra) > 0 ) pXSector = &xsector[nXSector]; int moveDist = qdist(pSprite->xvel, pSprite->yvel); ProcessInput(pPlayer, pInput); if ( pXSprite->health == 0 ) return; int dudeIndex = kDudePlayer1 - kDudeBase; if ( moveDist == 0 ) seqSpawn(dudeInfo[dudeIndex].seqStartID + kSeqDudeIdle, SS_SPRITE, nXSprite); else seqSpawn(dudeInfo[dudeIndex].seqStartID + kSeqCultistWalk, SS_SPRITE, nXSprite); // hack to make player walk if ( gSpriteHit[nXSprite].floorHit == 0 ) pXSprite->moveState = kMoveFall; else pXSprite->moveState = kMoveWalk; /* switch (pXSprite->moveState) { case kMoveFall: pPlayer->impactPE = pSprite->zvel * gSpring; pXSprite->moveState = kMoveLand; pSprite->zvel = 0; pPlayer->impactPhase = 0; pPlayer->standSpeed = kAngle90 / ClipLow(fallDamage << 2, 8); pPlayer->standSpeed = ClipLow(pPlayer->standSpeed, 2); // fall through to Move Land case kMoveLand: pPlayer->impactPhase = ClipHigh(pPlayer->impactPhase + dt * gSpringPhaseInc, kAngle90); pPlayer->compression = mulscale30(pPlayer->impactPE, Sin(pPlayer->impactPhase)); // pSprite->zvel = mulscale30(pPlayer->impactPE, Cos(pPlayer->impactPhase)); if (pPlayer->impactPhase == kAngle90) { pXSprite->moveState = kMoveStand; pPlayer->impactPhase = 0; pSprite->zvel = 0; } break; case kMoveStand: pPlayer->impactPhase = ClipHigh(pPlayer->impactPhase + dt * pPlayer->standSpeed, kAngle90); pPlayer->compression = mulscale30(pPlayer->impactPE, (1 << 30) - Sin(pPlayer->impactPhase)); if (pPlayer->impactPhase == kAngle90) { pXSprite->moveState = kMoveWalk; pSprite->zvel = 0; pPlayer->impactPE = 0; } // fall through case kMoveWalk: if ( zBot > loz ) { pPlayer->compression += zBot - loz; pSprite->z += loz - zBot; pPlayer->impactPhase = 0; pXSprite->moveState = kMoveStand; pPlayer->standSpeed = 8; pPlayer->impactPE = pPlayer->compression; } } */ if ( pInput->buttonFlags.jump && gSpriteHit[nXSprite].floorHit != 0 ) { sfxStart3DSound(nXSprite, kSfxPlayJump, 0x1000); pSprite->zvel = -400; } if (pSprite->sectnum < 0 || pSprite->sectnum >= numsectors) { dprintf("How did you get in the wrong sector (%d)?\n", pSprite->sectnum); } if (pXSector && (pXSector->underwater || (pXSector->depth == kDepthSwim && pSprite->z >= sector[nSector].floorz))) pXSprite->moveState = kMoveSwim; // bobbing and swaying code // this controls the decay on bobbing int xamp, yamp; pPlayer->bobAmp = ClipLow(pPlayer->bobAmp - 8 * kFrameTicks, 0); moveDist >>= 2; if ( gViewBobbing ) { // this bobbing code should be moved somewhere else, VIEW maybe? switch ( pPlayer->xsprite->moveState ) { case kMoveWalk: pPlayer->bobPhase = (pPlayer->bobPhase + kFrameTicks * cp->pace[pPlayer->run] * kAngle360 / 120 / kTimerRate) & kAngleMask; pPlayer->swayPhase = (pPlayer->swayPhase + kFrameTicks * cp->pace[pPlayer->run] / 2 * kAngle360 / 120 / kTimerRate) & kAngleMask; if ( pPlayer->run ) { if (pPlayer->bobAmp < 512) pPlayer->bobAmp = ClipHigh(pPlayer->bobAmp + moveDist, 512); } else { if (pPlayer->bobAmp < 384) pPlayer->bobAmp = ClipHigh(pPlayer->bobAmp + moveDist, 384); } xamp = yamp = pPlayer->bobAmp; break; case kMoveSwim: pPlayer->bobPhase = (pPlayer->bobPhase + kFrameTicks * kAngle360 / kTimerRate / 2) & kAngleMask; pPlayer->swayPhase = (pPlayer->swayPhase + kFrameTicks * kAngle360 / kTimerRate / 2) & kAngleMask; yamp = 64; xamp = 0; break; default: xamp = yamp = pPlayer->bobAmp; } pPlayer->bobHeight = mulscale30(cp->bobV * yamp, Sin(pPlayer->bobPhase * 2)); pPlayer->bobWidth = mulscale30(cp->bobH * xamp, Sin(pPlayer->bobPhase - kAngle45)); pPlayer->swayHeight = mulscale30(cp->swayV * yamp, Sin(pPlayer->swayPhase * 2)); pPlayer->swayWidth = mulscale30(cp->swayH * xamp, Sin(pPlayer->swayPhase - kAngle60)); } } void playerFireMissile( PLAYER *pPlayer, long dx, long dy, long dz, int missileType ) { actFireMissile(pPlayer->nSprite, pPlayer->sprite->z - pPlayer->weaponAboveZ, dx, dy, dz, missileType); } int playerFireThing( PLAYER *pPlayer, int relSlope, int thingType, int velocity ) { dassert( thingType >= kThingBase && thingType < kThingMax ); SPRITE *pSprite = pPlayer->sprite; return actFireThing( pPlayer->nSprite, pSprite->z - pPlayer->weaponAboveZ, pPlayer->slope + relSlope, thingType, velocity ); } void playerDamageSprite( PLAYER *pPlayer, int nSource, int nDamage ) { // if (pPlayer == gMe) if ( pPlayer == gView ) scrDacRelEffect((nDamage>>4)*2, (-nDamage>>4)*3, (-nDamage>>4)*3); if ( nDamage > 0 ) sfxStart3DSound(pPlayer->sprite->extra, kSfxPlayPain, 0x1000); // source can be targeted by lackeys? if (IsDudeSprite( nSource )) { for (int i=0; inLackeySprites[i]; if (nLackey >= 0 && nLackey != nSource) aiAlertLackey( pPlayer, nLackey, nSource, nDamage ); } } } BOOL playerAddLackey( PLAYER *pPlayer, int nLackey ) { for (int i=0; inLackeySprites[i] == nLackey) { //dprintf("lackey already in list\n"); return FALSE; } for (i=0; inLackeySprites[i] == -1) { //dprintf("adding lackey %i: %i\n",i,nLackey); pPlayer->nLackeySprites[i] = nLackey; sprite[nLackey].owner = (short)pPlayer->nSprite; break; } return (i < kMaxLackeys) ? TRUE : FALSE; } void playerDeleteLackey( PLAYER *pPlayer, int nLackey ) { for (int i=0; inLackeySprites[i] == nLackey) { pPlayer->nLackeySprites[i] = -1; sprite[nLackey].owner = -1; break; } }