// // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // DESCRIPTION: // Weapon sprite animation, weapon objects. // Action functions for weapons. // #include "doomdef.h" #include "d_event.h" #include "deh_misc.h" #include "m_random.h" #include "p_local.h" #include "s_sound.h" // State. #include "doomstat.h" // Data. #include "sounds.h" #include "p_pspr.h" #define LOWERSPEED FRACUNIT*6 #define RAISESPEED FRACUNIT*6 #define WEAPONBOTTOM 128*FRACUNIT #define WEAPONTOP 32*FRACUNIT // // P_SetPsprite // // [STRIFE] // villsa: Removed psprite sx, sy modification via misc1/2 // void P_SetPsprite ( player_t* player, int position, statenum_t stnum ) { pspdef_t* psp; state_t* state; psp = &player->psprites[position]; do { if (!stnum) { // object removed itself psp->state = NULL; break; } state = &states[stnum]; psp->state = state; psp->tics = state->tics; // could be 0 // villsa [STRIFE] unused /*if (state->misc1) { // coordinate set psp->sx = state->misc1 << FRACBITS; psp->sy = state->misc2 << FRACBITS; }*/ // Call action routine. // Modified handling. if (state->action.acp2) { state->action.acp2(player, psp); if (!psp->state) break; } stnum = psp->state->nextstate; } while (!psp->tics); // an initial state of 0 could cycle through } // haleyjd 09/06/10: [STRIFE] Removed P_CalcSwing // // P_BringUpWeapon // Starts bringing the pending weapon up // from the bottom of the screen. // Uses player // // villsa [STRIFE] Modifications for Strife weapons // void P_BringUpWeapon (player_t* player) { statenum_t newstate; if (player->pendingweapon == wp_nochange) player->pendingweapon = player->readyweapon; if (player->pendingweapon == wp_flame) S_StartSound (player->mo, sfx_flidl); // villsa [STRIFE] flame sounds newstate = weaponinfo[player->pendingweapon].upstate; player->psprites[ps_weapon].sy = WEAPONBOTTOM; P_SetPsprite (player, ps_weapon, newstate); // villsa [STRIFE] set various flash states if(player->pendingweapon == wp_elecbow) P_SetPsprite(player, ps_flash, S_XBOW_10); // 31 else if(player->pendingweapon == wp_sigil && player->sigiltype) P_SetPsprite(player, ps_flash, S_SIGH_00 + player->sigiltype); // 117 else P_SetPsprite(player, ps_flash, S_NULL); player->pendingweapon = wp_nochange; } // // P_CheckAmmo // Returns true if there is enough ammo to shoot. // If not, selects the next weapon to use. // // villsa [STRIFE] Changes to handle Strife weapons // boolean P_CheckAmmo (player_t* player) { ammotype_t ammo; int count; ammo = weaponinfo[player->readyweapon].ammo; // Minimal amount for one shot varies. if (player->readyweapon == wp_torpedo) count = 30; else if (player->readyweapon == wp_mauler) count = 20; else count = 1; // Regular. // Some do not need ammunition anyway. // Return if current ammunition sufficient. if (ammo == am_noammo || player->ammo[ammo] >= count) return true; // Out of ammo, pick a weapon to change to. // Preferences are set here. // villsa [STRIFE] new weapon preferences if (player->weaponowned[wp_mauler] && player->ammo[am_cell] >= 20) player->pendingweapon = wp_mauler; else if(player->weaponowned[wp_rifle] && player->ammo[am_bullets]) player->pendingweapon = wp_rifle; else if (player->weaponowned[wp_elecbow] && player->ammo[am_elecbolts]) player->pendingweapon = wp_elecbow; else if (player->weaponowned[wp_missile] && player->ammo[am_missiles]) player->pendingweapon = wp_missile; else if (player->weaponowned[wp_flame] && player->ammo[am_cell]) player->pendingweapon = wp_flame; else if (player->weaponowned[wp_hegrenade] && player->ammo[am_hegrenades]) player->pendingweapon = wp_hegrenade; else if (player->weaponowned[wp_poisonbow] && player->ammo[am_poisonbolts]) player->pendingweapon = wp_poisonbow; else if (player->weaponowned[wp_wpgrenade] && player->ammo[am_wpgrenades]) player->pendingweapon = wp_wpgrenade; // BUG: This will *never* be selected for an automatic switch because the // normal Mauler is higher priority and uses less ammo. else if (player->weaponowned[wp_torpedo] && player->ammo[am_cell] >= 30) player->pendingweapon = wp_torpedo; else player->pendingweapon = wp_fist; // Now set appropriate weapon overlay. P_SetPsprite(player, ps_weapon, weaponinfo[player->readyweapon].downstate); return false; } // // P_FireWeapon. // // villsa [STRIFE] Changes for player state and weapons // void P_FireWeapon (player_t* player) { statenum_t newstate; if (!P_CheckAmmo (player)) return; P_SetMobjState (player->mo, S_PLAY_05); // 292 newstate = weaponinfo[player->readyweapon].atkstate; P_SetPsprite (player, ps_weapon, newstate); // villsa [STRIFE] exclude these weapons from causing noise if(player->readyweapon > wp_elecbow && player->readyweapon != wp_poisonbow) P_NoiseAlert (player->mo, player->mo); } // // P_DropWeapon // Player died, so put the weapon away. // void P_DropWeapon (player_t* player) { P_SetPsprite (player, ps_weapon, weaponinfo[player->readyweapon].downstate); } // // A_WeaponReady // The player can fire the weapon // or change to another weapon at this time. // Follows after getting weapon up, // or after previous attack/fire sequence. // void A_WeaponReady( player_t* player, pspdef_t* psp) { statenum_t newstate; int angle; // get out of attack state if (player->mo->state == &states[S_PLAY_05] || // 292 player->mo->state == &states[S_PLAY_06]) // 293 { P_SetMobjState (player->mo, S_PLAY_00); // 287 } // villsa [STRIFE] check for wp_flame instead of chainsaw // haleyjd 09/06/10: fixed state (00 rather than 01) if (player->readyweapon == wp_flame && psp->state == &states[S_FLMT_00]) // 62 { S_StartSound (player->mo, sfx_flidl); } // check for change // if player is dead, put the weapon away if (player->pendingweapon != wp_nochange || !player->health) { // change weapon // (pending weapon should allready be validated) newstate = weaponinfo[player->readyweapon].downstate; P_SetPsprite (player, ps_weapon, newstate); return; } // check for fire // the missile launcher and torpedo do not auto fire if (player->cmd.buttons & BT_ATTACK) { if ( !player->attackdown || (player->readyweapon != wp_missile && player->readyweapon != wp_torpedo)) // villsa [STRIFE] replace bfg with torpedo { player->attackdown = true; P_FireWeapon (player); return; } } else player->attackdown = false; // bob the weapon based on movement speed angle = (128*leveltime)&FINEMASK; psp->sx = FRACUNIT + FixedMul (player->bob, finecosine[angle]); angle &= FINEANGLES/2-1; psp->sy = WEAPONTOP + FixedMul (player->bob, finesine[angle]); } // // A_ReFire // The player can re-fire the weapon // without lowering it entirely. // void A_ReFire ( player_t* player, pspdef_t* psp ) { // check for fire // (if a weaponchange is pending, let it go through instead) if ( (player->cmd.buttons & BT_ATTACK) && player->pendingweapon == wp_nochange && player->health) { player->refire++; P_FireWeapon (player); } else { player->refire = 0; P_CheckAmmo (player); } } // // A_CheckReload // void A_CheckReload(player_t* player, pspdef_t* psp) { P_CheckAmmo(player); // villsa [STRIFE] set animating sprite for crossbow if(player->readyweapon == wp_elecbow) P_SetPsprite(player, player->readyweapon, S_XBOW_10); } // // A_Lower // Lowers current weapon, // and changes weapon at bottom. // void A_Lower ( player_t* player, pspdef_t* psp ) { psp->sy += LOWERSPEED; // Is already down. if (psp->sy < WEAPONBOTTOM ) return; // Player is dead. if (player->playerstate == PST_DEAD) { psp->sy = WEAPONBOTTOM; // don't bring weapon back up return; } // The old weapon has been lowered off the screen, // so change the weapon and start raising it if (!player->health) { // Player is dead, so keep the weapon off screen. P_SetPsprite (player, ps_weapon, S_NULL); return; } player->readyweapon = player->pendingweapon; P_BringUpWeapon (player); } // // A_Raise // void A_Raise ( player_t* player, pspdef_t* psp ) { statenum_t newstate; psp->sy -= RAISESPEED; if (psp->sy > WEAPONTOP ) return; psp->sy = WEAPONTOP; // The weapon has been raised all the way, // so change to the ready state. newstate = weaponinfo[player->readyweapon].readystate; P_SetPsprite (player, ps_weapon, newstate); } // // A_GunFlash // void A_GunFlash ( player_t* player, pspdef_t* psp ) { P_SetMobjState (player->mo, S_PLAY_06); P_SetPsprite (player,ps_flash,weaponinfo[player->readyweapon].flashstate); } // // WEAPON ATTACKS // // // A_Punch // void A_Punch(player_t* player, pspdef_t* psp) { angle_t angle; int damage; int slope; int sound; int stamina; int t; // villsa [STRIFE] new damage formula // haleyjd 09/19/10: seriously corrected... stamina = player->stamina; damage = (P_Random() & ((stamina/10) + 7)) * ((stamina/10) + 2); if(player->powers[pw_strength]) damage *= 10; angle = player->mo->angle; t = P_Random(); angle += (t - P_Random()) << 18; slope = P_AimLineAttack (player->mo, angle, PLAYERMELEERANGE); P_LineAttack (player->mo, angle, PLAYERMELEERANGE, slope, damage); // turn to face target if(linetarget) { // villsa [STRIFE] check for non-flesh types if(linetarget->flags & MF_NOBLOOD) sound = sfx_mtalht; else sound = sfx_meatht; S_StartSound (player->mo, sound); player->mo->angle = R_PointToAngle2 (player->mo->x, player->mo->y, linetarget->x, linetarget->y); // villsa [STRIFE] apply flag player->mo->flags |= MF_JUSTATTACKED; // villsa [STRIFE] do punch alert routine P_DoPunchAlert(player->mo, linetarget); } else S_StartSound (player->mo, sfx_swish); } // // A_FireFlameThrower // // villsa [STRIFE] new codepointer // void A_FireFlameThrower(player_t* player, pspdef_t* psp) { mobj_t* mo; int t; P_SetMobjState(player->mo, S_PLAY_06); player->ammo[weaponinfo[player->readyweapon].ammo]--; t = P_Random(); player->mo->angle += (t - P_Random()) << 18; mo = P_SpawnPlayerMissile(player->mo, MT_SFIREBALL); mo->momz += (5*FRACUNIT); } // // A_FireMissile // // villsa [STRIFE] completly new compared to the original // void A_FireMissile(player_t* player, pspdef_t* psp) { angle_t an; int t; // haleyjd 09/19/10: I previously missed an add op that meant it should be // accuracy * 5, not 4. Checks out with other sources. an = player->mo->angle; t = P_Random(); player->mo->angle += (t - P_Random()) << (19 - (player->accuracy * 5 / 100)); P_SetMobjState(player->mo, S_PLAY_06); player->ammo[weaponinfo[player->readyweapon].ammo]--; P_SpawnPlayerMissile(player->mo, MT_MINIMISSLE); player->mo->angle = an; } // // A_FireMauler2 // // villsa [STRIFE] - new codepointer // void A_FireMauler2(player_t* player, pspdef_t* pspr) { P_SetMobjState(player->mo, S_PLAY_06); P_DamageMobj(player->mo, player->mo, NULL, 20); player->ammo[weaponinfo[player->readyweapon].ammo] -= 30; P_SpawnPlayerMissile(player->mo, MT_TORPEDO); P_Thrust(player, player->mo->angle + ANG180, 512000); } // // A_FireGrenade // // villsa [STRIFE] - new codepointer // void A_FireGrenade(player_t* player, pspdef_t* pspr) { mobjtype_t type; mobj_t* mo; state_t* st1; state_t* st2; angle_t an; fixed_t radius; // decide on what type of grenade to spawn if(player->readyweapon == wp_hegrenade) { type = MT_HEGRENADE; } else if(player->readyweapon == wp_wpgrenade) { type = MT_PGRENADE; } else { type = MT_HEGRENADE; fprintf(stderr, "Warning: A_FireGrenade used on wrong weapon!\n"); } player->ammo[weaponinfo[player->readyweapon].ammo]--; // set flash frame st1 = &states[(pspr->state - states) + weaponinfo[player->readyweapon].flashstate]; st2 = &states[weaponinfo[player->readyweapon].atkstate]; P_SetPsprite(player, ps_flash, st1 - st2); player->mo->z += 32*FRACUNIT; // ugh mo = P_SpawnMortar(player->mo, type); player->mo->z -= 32*FRACUNIT; // ugh // change momz based on player's pitch mo->momz = FixedMul((player->pitch<info->speed) + (8*FRACUNIT); S_StartSound(mo, mo->info->seesound); radius = mobjinfo[type].radius + player->mo->info->radius; an = (player->mo->angle >> ANGLETOFINESHIFT); mo->x += FixedMul(finecosine[an], radius + (4*FRACUNIT)); mo->y += FixedMul(finesine[an], radius + (4*FRACUNIT)); // shoot grenade from left or right side? if(&states[weaponinfo[player->readyweapon].atkstate] == pspr->state) an = (player->mo->angle - ANG90) >> ANGLETOFINESHIFT; else an = (player->mo->angle + ANG90) >> ANGLETOFINESHIFT; mo->x += FixedMul((15*FRACUNIT), finecosine[an]); mo->y += FixedMul((15*FRACUNIT), finesine[an]); // set bounce flag mo->flags |= MF_BOUNCE; } // // A_FireElectricBolt // villsa [STRIFE] - new codepointer // void A_FireElectricBolt(player_t* player, pspdef_t* pspr) { angle_t an = player->mo->angle; int t; // haleyjd 09/19/10: Use 5 mul on accuracy here as well t = P_Random(); player->mo->angle += (t - P_Random()) << (18 - (player->accuracy * 5 / 100)); player->ammo[weaponinfo[player->readyweapon].ammo]--; P_SpawnPlayerMissile(player->mo, MT_ELECARROW); player->mo->angle = an; S_StartSound(player->mo, sfx_xbow); } // // A_FirePoisonBolt // villsa [STRIFE] - new codepointer // void A_FirePoisonBolt(player_t* player, pspdef_t* pspr) { angle_t an = player->mo->angle; int t; // haleyjd 09/19/10: Use 5 mul on accuracy here as well t = P_Random(); player->mo->angle += (t - P_Random()) << (18 - (player->accuracy * 5 / 100)); player->ammo[weaponinfo[player->readyweapon].ammo]--; P_SpawnPlayerMissile(player->mo, MT_POISARROW); player->mo->angle = an; S_StartSound(player->mo, sfx_xbow); } // // P_BulletSlope // Sets a slope so a near miss is at aproximately // the height of the intended target // // haleyjd 09/06/10 [STRIFE] Modified with a little target hack... // fixed_t bulletslope; void P_BulletSlope (mobj_t *mo) { angle_t an; // see which target is to be aimed at an = mo->angle; bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT); if (!linetarget) { an += 1<<26; bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT); if (!linetarget) { an -= 2<<26; bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT); } } // haleyjd 09/06/10: [STRIFE] Somebody added this here, and without it, you // will get spurious crashing in routines such as P_LookForPlayers! if(linetarget) mo->target = linetarget; } // // P_GunShot // // [STRIFE] Modifications to support accuracy. // void P_GunShot ( mobj_t* mo, boolean accurate ) { angle_t angle; int damage; angle = mo->angle; // villsa [STRIFE] apply player accuracy // haleyjd 09/18/10: made some corrections: use 5x accuracy; // eliminated order-of-evaluation dependency if (!accurate) { int t = P_Random(); angle += (t - P_Random()) << (20 - ((mo->player->accuracy * 5) / 100)); } // haleyjd 09/18/10 [STRIFE] corrected damage formula and moved down to // preserve proper P_Random call order. damage = 4 * (P_Random() % 3 + 1); P_LineAttack (mo, angle, MISSILERANGE, bulletslope, damage); } // // A_FireRifle // // villsa [STRIFE] - new codepointer // void A_FireRifle(player_t* player, pspdef_t* pspr) { S_StartSound(player->mo, sfx_rifle); if(player->ammo[weaponinfo[player->readyweapon].ammo]) { P_SetMobjState(player->mo, S_PLAY_06); // 293 player->ammo[weaponinfo[player->readyweapon].ammo]--; P_BulletSlope(player->mo); P_GunShot(player->mo, !player->refire); } } // // A_FireMauler1 // // villsa [STRIFE] - new codepointer // void A_FireMauler1(player_t* player, pspdef_t* pspr) { int i; angle_t angle; int damage; // haleyjd 09/18/10: Corrected ammo check to use >= if(player->ammo[weaponinfo[player->readyweapon].ammo] >= 20) { player->ammo[weaponinfo[player->readyweapon].ammo] -= 20; P_BulletSlope(player->mo); S_StartSound(player->mo, sfx_pgrdat); for(i = 0; i < 20; i++) { int t; damage = 5*(P_Random ()%3+1); angle = player->mo->angle; t = P_Random(); angle += (t - P_Random()) << 19; t = P_Random(); P_LineAttack(player->mo, angle, 2112*FRACUNIT, bulletslope + ((t - P_Random())<<5), damage); } } } // // A_SigilSound // // villsa [STRIFE] - new codepointer // void A_SigilSound(player_t* player, pspdef_t* pspr) { S_StartSound(player->mo, sfx_siglup); player->extralight = 2; } // // A_FireSigil // // villsa [STRIFE] - new codepointer // void A_FireSigil(player_t* player, pspdef_t* pspr) { mobj_t* mo; angle_t an; int i; // keep info on armor because sigil does piercing damage i = player->armortype; player->armortype = 0; // BUG: setting inflictor causes firing the Sigil to always push the player // toward the east, no matter what direction he is facing. P_DamageMobj(player->mo, player->mo, NULL, 4 * (player->sigiltype + 1)); // restore armor player->armortype = i; S_StartSound(player->mo, sfx_siglup); switch(player->sigiltype) { // falling lightning bolts from the sky case 0: P_BulletSlope(player->mo); if(linetarget) { // haleyjd 09/18/10: corrected z coordinate mo = P_SpawnMobj(linetarget->x, linetarget->y, ONFLOORZ, MT_SIGIL_A_GROUND); mo->tracer = linetarget; } else { an = player->mo->angle>>ANGLETOFINESHIFT; mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SIGIL_A_GROUND); mo->momx += FixedMul((28*FRACUNIT), finecosine[an]); mo->momy += FixedMul((28*FRACUNIT), finesine[an]); } mo->health = -1; mo->target = player->mo; break; // simple projectile case 1: P_SpawnPlayerMissile(player->mo, MT_SIGIL_B_SHOT)->health = -1; break; // spread shot case 2: player->mo->angle -= ANG90; // starting at 270... for(i = 0; i < 20; i++) // increment by 1/10 of 90, 20 times. { player->mo->angle += (ANG90 / 10); mo = P_SpawnMortar(player->mo, MT_SIGIL_C_SHOT); mo->health = -1; mo->z = player->mo->z + (32*FRACUNIT); } player->mo->angle -= ANG90; // subtract off the extra 90 break; // tracer attack case 3: P_BulletSlope(player->mo); if(linetarget) { mo = P_SpawnPlayerMissile(player->mo, MT_SIGIL_D_SHOT); mo->tracer = linetarget; } else { an = player->mo->angle >> ANGLETOFINESHIFT; mo = P_SpawnPlayerMissile(player->mo, MT_SIGIL_D_SHOT); mo->momx += FixedMul(mo->info->speed, finecosine[an]); mo->momy += FixedMul(mo->info->speed, finesine[an]); } mo->health = -1; break; // mega blast case 4: mo = P_SpawnPlayerMissile(player->mo, MT_SIGIL_E_SHOT); mo->health = -1; if(!linetarget) { an = player->pitch >> ANGLETOFINESHIFT; mo->momz += FixedMul(finesine[an], mo->info->speed); } break; default: break; } } // // A_GunFlashThinker // // villsa [STRIFE] - new codepointer // void A_GunFlashThinker(player_t* player, pspdef_t* pspr) { if(player->readyweapon == wp_sigil && player->sigiltype) P_SetPsprite(player, ps_flash, S_SIGH_00 + player->sigiltype); else P_SetPsprite(player, ps_flash, S_NULL); } // // ? // void A_Light0 (player_t *player, pspdef_t *psp) { player->extralight = 0; } void A_Light1 (player_t *player, pspdef_t *psp) { player->extralight = 1; } void A_Light2 (player_t *player, pspdef_t *psp) { player->extralight = 2; } // // A_SigilShock // // villsa [STRIFE] - new codepointer // void A_SigilShock (player_t *player, pspdef_t *psp) { player->extralight = -3; } // // A_TorpedoExplode // // villsa [STRIFE] - new codepointer // void A_TorpedoExplode(mobj_t* actor) { int i; actor->angle -= ANG180; for(i = 0; i < 80; i++) { actor->angle += (ANG90 / 20); P_SpawnMortar(actor, MT_TORPEDOSPREAD)->target = actor->target; } } // // A_MaulerSound // // villsa [STRIFE] - new codepointer // void A_MaulerSound(player_t *player, pspdef_t *psp) { int t; S_StartSound(player->mo, sfx_proton); t = P_Random(); psp->sx += (t - P_Random()) << 10; t = P_Random(); psp->sy += (t - P_Random()) << 10; } // // P_SetupPsprites // Called at start of level for each player. // void P_SetupPsprites(player_t* player) { int i; // remove all psprites for(i = 0; i < NUMPSPRITES; i++) player->psprites[i].state = NULL; // spawn the gun player->pendingweapon = player->readyweapon; P_BringUpWeapon(player); } // // P_MovePsprites // Called every tic by player thinking routine. // void P_MovePsprites (player_t* player) { int i; pspdef_t* psp; state_t* state; psp = &player->psprites[0]; for(i = 0; i < NUMPSPRITES; i++, psp++) { // a null state means not active if((state = psp->state)) { // drop tic count and possibly change state // a -1 tic count never changes if(psp->tics != -1) { psp->tics--; if(!psp->tics) P_SetPsprite (player, i, psp->state->nextstate); } } } player->psprites[ps_flash].sx = player->psprites[ps_weapon].sx; player->psprites[ps_flash].sy = player->psprites[ps_weapon].sy; // villsa [STRIFE] extra stuff for targeter player->psprites[ps_targleft].sx = (160*FRACUNIT) - ((100 - player->accuracy) << FRACBITS); player->psprites[ps_targright].sx = ((100 - player->accuracy) << FRACBITS) + (160*FRACUNIT); }