/* Emacs style mode select   -*- C++ -*- */
/*-----------------------------------------------------------------------------*/

/* $Id:$*/

/* Copyright (C) 1993-1996 by id Software, Inc.*/

/* This source is available for distribution and/or modification*/
/* only under the terms of the DOOM Source Code License as*/
/* published by id Software. All rights reserved.*/

/* The source is distributed in the hope that it will be useful,*/
/* but WITHOUT ANY WARRANTY; without even the implied warranty of*/
/* FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License*/
/* for more details.*/

/* $Log:$*/

/* DESCRIPTION:*/
/*	Weapon sprite animation, weapon objects.*/
/*	Action functions for weapons.*/

/*-----------------------------------------------------------------------------*/

static const char
rcsid[] = "$Id: p_pspr.c,v 1.5 1997/02/03 22:45:12 b1 Exp $";

#include "doomdef.h"
#include "d_event.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*/


/* plasma cells for a bfg attack*/
int BFGcells = 40;


/* New to fix the position of the weapon in lowres mode */
static int     weapontop;



/* P_SetPsprite*/

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*/

	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*/
}




/* P_CalcSwing*/

static fixed_t	swingx;
static fixed_t	swingy;

void P_CalcSwing (player_t*	player)
{
    fixed_t	swing;
    int		angle;

    /* OPTIMIZE: tablify this.*/
    /* A LUT would allow for different modes,*/
    /*  and add flexibility.*/

    swing = player->bob;

    angle = (FINEANGLES/70*leveltime)&FINEMASK;
    swingx = FixedMul ( swing, finesine[angle]);

    angle = (FINEANGLES/70*leveltime+FINEANGLES/2)&FINEMASK;
    swingy = -FixedMul ( swingx, finesine[angle]);
}




/* P_BringUpWeapon*/
/* Starts bringing the pending weapon up*/
/* from the bottom of the screen.*/
/* Uses player*/

void P_BringUpWeapon (player_t* player)
{
    statenum_t	newstate;

    if (player->pendingweapon == wp_nochange)
	player->pendingweapon = player->readyweapon;

    if (player->pendingweapon == wp_chainsaw)
	S_StartSound (player->mo, sfx_sawup);

    newstate = weaponinfo[player->pendingweapon].upstate;

    player->pendingweapon = wp_nochange;
    player->psprites[ps_weapon].sy = WEAPONBOTTOM;

    P_SetPsprite (player, ps_weapon, newstate);
}


/* P_CheckAmmo*/
/* Returns true if there is enough ammo to shoot.*/
/* If not, selects the next weapon to use.*/

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_bfg)
	count = BFGcells;
    else if (player->readyweapon == wp_supershotgun)
	count = 2;	/* Double barrel.*/
    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.*/
    do
    {
	if (player->weaponowned[wp_plasma]
	    && player->ammo[am_cell]
	    && (gamemode != shareware) )
	{
	    player->pendingweapon = wp_plasma;
	}
	else if (player->weaponowned[wp_supershotgun]
		 && player->ammo[am_shell]>2
		 && (gamemode == commercial) )
	{
	    player->pendingweapon = wp_supershotgun;
	}
	else if (player->weaponowned[wp_chaingun]
		 && player->ammo[am_clip])
	{
	    player->pendingweapon = wp_chaingun;
	}
	else if (player->weaponowned[wp_shotgun]
		 && player->ammo[am_shell])
	{
	    player->pendingweapon = wp_shotgun;
	}
	else if (player->ammo[am_clip])
	{
	    player->pendingweapon = wp_pistol;
	}
	else if (player->weaponowned[wp_chainsaw])
	{
	    player->pendingweapon = wp_chainsaw;
	}
	else if (player->weaponowned[wp_missile]
		 && player->ammo[am_misl])
	{
	    player->pendingweapon = wp_missile;
	}
	else if (player->weaponowned[wp_bfg]
		 && player->ammo[am_cell]>40
		 && (gamemode != shareware) )
	{
	    player->pendingweapon = wp_bfg;
	}
	else
	{
	    /* If everything fails.*/
	    player->pendingweapon = wp_fist;
	}

    } while (player->pendingweapon == wp_nochange);

    /* Now set appropriate weapon overlay.*/
    P_SetPsprite (player,
		  ps_weapon,
		  weaponinfo[player->readyweapon].downstate);

    return false;
}



/* P_FireWeapon.*/

void P_FireWeapon (player_t* player)
{
    statenum_t	newstate;

    if (!P_CheckAmmo (player))
	return;

    P_SetMobjState (player->mo, S_PLAY_ATK1);
    newstate = weaponinfo[player->readyweapon].atkstate;
    P_SetPsprite (player, ps_weapon, newstate);
    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_ATK1]
	|| player->mo->state == &states[S_PLAY_ATK2] )
    {
	P_SetMobjState (player->mo, S_PLAY);
    }

    if (player->readyweapon == wp_chainsaw
	&& psp->state == &states[S_SAW])
    {
	S_StartSound (player->mo, sfx_sawidl);
    }

    /* 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 bfg do not auto fire*/
    if (player->cmd.buttons & BT_ATTACK)
    {
	if ( !player->attackdown
	     || (player->readyweapon != wp_missile
		 && player->readyweapon != wp_bfg) )
	{
	    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);
    }
}


void
A_CheckReload
( player_t*	player,
  pspdef_t*	psp )
{
    P_CheckAmmo (player);
#if 0
    if (player->ammo[am_shell]<2)
	P_SetPsprite (player, ps_weapon, S_DSNR1);
#endif
}




/* 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_ATK2);
    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		t;

    damage = (P_Random ()%10+1)<<1;

    if (player->powers[pw_strength])
	damage *= 10;

    angle = player->mo->angle;
    t = P_Random();	/* remove dependence on order of evaluation */
    angle += (t-P_Random())<<18;
    slope = P_AimLineAttack (player->mo, angle, MELEERANGE);
    P_LineAttack (player->mo, angle, MELEERANGE, slope, damage);

    /* turn to face target*/
    if (linetarget)
    {
	S_StartSound (player->mo, sfx_punch);
	player->mo->angle = R_PointToAngle2 (player->mo->x,
					     player->mo->y,
					     linetarget->x,
					     linetarget->y);
    }
}



/* A_Saw*/

void
A_Saw
( player_t*	player,
  pspdef_t*	psp )
{
    angle_t	angle;
    int		damage;
    int		slope;
    int		t;

    damage = 2*(P_Random ()%10+1);
    angle = player->mo->angle;
    t = P_Random();	/* remove dependence on order of evaluation */
    angle += (t-P_Random())<<18;

    /* use meleerange + 1 se the puff doesn't skip the flash*/
    slope = P_AimLineAttack (player->mo, angle, MELEERANGE+1);
    P_LineAttack (player->mo, angle, MELEERANGE+1, slope, damage);

    if (!linetarget)
    {
	S_StartSound (player->mo, sfx_sawful);
	return;
    }
    S_StartSound (player->mo, sfx_sawhit);

    /* turn to face target*/
    angle = R_PointToAngle2 (player->mo->x, player->mo->y,
			     linetarget->x, linetarget->y);
    if (angle - player->mo->angle > ANG180)
    {
	if (angle - player->mo->angle < -ANG90/20)
	    player->mo->angle = angle + ANG90/21;
	else
	    player->mo->angle -= ANG90/20;
    }
    else
    {
	if (angle - player->mo->angle > ANG90/20)
	    player->mo->angle = angle - ANG90/21;
	else
	    player->mo->angle += ANG90/20;
    }
    player->mo->flags |= MF_JUSTATTACKED;
}




/* A_FireMissile*/

void
A_FireMissile
( player_t*	player,
  pspdef_t*	psp )
{
    player->ammo[weaponinfo[player->readyweapon].ammo]--;
    P_SpawnPlayerMissile (player->mo, MT_ROCKET);
}



/* A_FireBFG*/

void
A_FireBFG
( player_t*	player,
  pspdef_t*	psp )
{
    player->ammo[weaponinfo[player->readyweapon].ammo] -= BFGcells;
    P_SpawnPlayerMissile (player->mo, MT_BFG);
}




/* A_FirePlasma*/

void
A_FirePlasma
( player_t*	player,
  pspdef_t*	psp )
{
    player->ammo[weaponinfo[player->readyweapon].ammo]--;

    P_SetPsprite (player,
		  ps_flash,
		  weaponinfo[player->readyweapon].flashstate+(P_Random ()&1) );

    P_SpawnPlayerMissile (player->mo, MT_PLASMA);
}




/* P_BulletSlope*/
/* Sets a slope so a near miss is at aproximately*/
/* the height of the intended target*/

static 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);
	}
    }
}



/* P_GunShot*/

void
P_GunShot
( mobj_t*	mo,
  boolean	accurate )
{
    angle_t	angle;
    int		damage;

    damage = 5*(P_Random ()%3+1);
    angle = mo->angle;

    if (!accurate)
    {
        int t = P_Random();	/* remove dependence on order of evaluation */
	angle += (t-P_Random())<<18;
    }

    P_LineAttack (mo, angle, MISSILERANGE, bulletslope, damage);
}



/* A_FirePistol*/

void
A_FirePistol
( player_t*	player,
  pspdef_t*	psp )
{
    S_StartSound (player->mo, sfx_pistol);

    P_SetMobjState (player->mo, S_PLAY_ATK2);
    player->ammo[weaponinfo[player->readyweapon].ammo]--;

    P_SetPsprite (player,
		  ps_flash,
		  weaponinfo[player->readyweapon].flashstate);

    P_BulletSlope (player->mo);
    P_GunShot (player->mo, !player->refire);
}



/* A_FireShotgun*/

void
A_FireShotgun
( player_t*	player,
  pspdef_t*	psp )
{
    int		i;

    S_StartSound (player->mo, sfx_shotgn);
    P_SetMobjState (player->mo, S_PLAY_ATK2);

    player->ammo[weaponinfo[player->readyweapon].ammo]--;

    P_SetPsprite (player,
		  ps_flash,
		  weaponinfo[player->readyweapon].flashstate);

    P_BulletSlope (player->mo);

    for (i=0 ; i<7 ; i++)
	P_GunShot (player->mo, false);
}




/* A_FireShotgun2*/

void
A_FireShotgun2
( player_t*	player,
  pspdef_t*	psp )
{
    int		i;
    angle_t	angle;
    int		damage;


    S_StartSound (player->mo, sfx_dshtgn);
    P_SetMobjState (player->mo, S_PLAY_ATK2);

    player->ammo[weaponinfo[player->readyweapon].ammo]-=2;

    P_SetPsprite (player,
		  ps_flash,
		  weaponinfo[player->readyweapon].flashstate);

    P_BulletSlope (player->mo);

    for (i=0 ; i<20 ; i++)
    {
        int t;
	damage = 5*(P_Random ()%3+1);
	angle = player->mo->angle;
	t = P_Random();	/* remove dependence on order of evaluation */
	angle += (t-P_Random())<<19;
	t = P_Random();	/* remove dependence on order of evaluation */
	P_LineAttack (player->mo,
		      angle,
		      MISSILERANGE,
		      bulletslope + ((t-P_Random())<<5), damage);
    }
}



/* A_FireCGun*/

void
A_FireCGun
( player_t*	player,
  pspdef_t*	psp )
{
    S_StartSound (player->mo, sfx_pistol);

    if (!player->ammo[weaponinfo[player->readyweapon].ammo])
	return;

    P_SetMobjState (player->mo, S_PLAY_ATK2);
    player->ammo[weaponinfo[player->readyweapon].ammo]--;

    P_SetPsprite (player,
		  ps_flash,
		  weaponinfo[player->readyweapon].flashstate
		  + psp->state
		  - &states[S_CHAIN1] );

    P_BulletSlope (player->mo);

    P_GunShot (player->mo, !player->refire);
}




/* ?*/

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_BFGSpray*/
/* Spawn a BFG explosion on every monster in view*/

void A_BFGSpray (mobj_t* mo)
{
    int			i;
    int			j;
    int			damage;
    angle_t		an;

    /* offset angles from its attack angle*/
    for (i=0 ; i<40 ; i++)
    {
	an = mo->angle - ANG90/2 + ANG90/40*i;

	/* mo->target is the originator (player)*/
	/*  of the missile*/
	P_AimLineAttack (mo->target, an, 16*64*FRACUNIT);

	if (!linetarget)
	    continue;

	P_SpawnMobj (linetarget->x,
		     linetarget->y,
		     linetarget->z + (linetarget->height>>2),
		     MT_EXTRABFG);

	damage = 0;
	for (j=0;j<15;j++)
	    damage += (P_Random()&7) + 1;

	P_DamageMobj (linetarget, mo->target,mo->target, damage);
    }
}



/* A_BFGsound*/

void
A_BFGsound
( player_t*	player,
  pspdef_t*	psp )
{
    S_StartSound (player->mo, sfx_bfg);
}




/* 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;
}



/* New: called whenever the detail level changes to fix the weapon position */
void P_SetPosPsprites(int detail)
{
    if (detail == 0)
        weapontop = 32*FRACUNIT;
    else
        weapontop = 16*FRACUNIT;
}
