// 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:
//      Handling interactions (i.e., collisions).
//
//-----------------------------------------------------------------------------


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


// Data.
#include "doomdef.h"
#include "dstrings.h"
#include "sounds.h"

#include "doomstat.h"

#include "m_random.h"
#include "i_system.h"

#include "am_map.h"

#include "p_local.h"

#include "s_sound.h"

#ifdef __GNUG__
#pragma implementation "p_inter.h"
#endif
#include "p_inter.h"


#define BONUSADD        6




// a weapon is found with two clip loads,
// a big item has five clip loads
int     maxammo[NUMAMMO] = {200, 50, 300, 50};
int     clipammo[NUMAMMO] = {10, 4, 20, 1};

// Dehacked
int     MAXHEALTH = 100;

//
// GET STUFF
//

//
// P_GiveAmmo
// Num is the number of clip loads,
// not the individual count (0= 1/2 clip).
// Returns false if the ammo can't be picked up at all
//

boolean
P_GiveAmmo
( player_t*     player,
  ammotype_t    ammo,
  int           num )
{
    int         oldammo;

    if (ammo == am_noammo)
        return false;

    if (ammo < 0 || ammo > NUMAMMO)
        I_Error ("P_GiveAmmo: bad type %i", ammo);

    if ( player->ammo[ammo] == player->maxammo[ammo]  )
        return false;

    if (num)
        num *= clipammo[ammo];
    else
        num = clipammo[ammo]/2;

    if (gameskill == sk_baby
        || gameskill == sk_nightmare)
    {
        // give double ammo in trainer mode,
        // you'll need in nightmare
        num <<= 1;
    }


    oldammo = player->ammo[ammo];
    player->ammo[ammo] += num;

    if (player->ammo[ammo] > player->maxammo[ammo])
        player->ammo[ammo] = player->maxammo[ammo];

    // If non zero ammo,
    // don't change up weapons,
    // player was lower on purpose.
    if (oldammo)
        return true;

    // We were down to zero,
    // so select a new weapon.
    // Preferences are not user selectable.
    switch (ammo)
    {
      case am_clip:
        if (player->readyweapon == wp_fist)
        {
            if (player->weaponowned[wp_chaingun])
                player->pendingweapon = wp_chaingun;
            else
                player->pendingweapon = wp_pistol;
        }
        break;

      case am_shell:
        if (player->readyweapon == wp_fist
            || player->readyweapon == wp_pistol)
        {
            if (player->weaponowned[wp_shotgun])
                player->pendingweapon = wp_shotgun;
        }
        break;

      case am_cell:
        if (player->readyweapon == wp_fist
            || player->readyweapon == wp_pistol)
        {
            if (player->weaponowned[wp_plasma])
                player->pendingweapon = wp_plasma;
        }
        break;

      case am_misl:
        if (player->readyweapon == wp_fist)
        {
            if (player->weaponowned[wp_missile])
                player->pendingweapon = wp_missile;
        }
      default:
        break;
    }

    return true;
}


//
// P_GiveWeapon
// The weapon name may have a MF_DROPPED flag ored in.
//
boolean
P_GiveWeapon
( player_t*     player,
  weapontype_t  weapon,
  boolean       dropped )
{
    boolean     gaveammo;
    boolean     gaveweapon;

    if (netgame
        && (deathmatch!=2)
         && !dropped )
    {
        // leave placed weapons forever on net games
        if (player->weaponowned[weapon])
            return false;

        player->bonuscount += BONUSADD;
        player->weaponowned[weapon] = true;

        if (deathmatch)
            P_GiveAmmo (player, weaponinfo[weapon].ammo, 5);
        else
            P_GiveAmmo (player, weaponinfo[weapon].ammo, 2);
        player->pendingweapon = weapon;

        if (player == &players[consoleplayer])
            S_StartSound (NULL, sfx_wpnup);
        return false;
    }

    if (weaponinfo[weapon].ammo != am_noammo)
    {
        // give one clip with a dropped weapon,
        // two clips with a found weapon
        if (dropped)
            gaveammo = P_GiveAmmo (player, weaponinfo[weapon].ammo, 1);
        else
            gaveammo = P_GiveAmmo (player, weaponinfo[weapon].ammo, 2);
    }
    else
        gaveammo = false;

    if (player->weaponowned[weapon])
        gaveweapon = false;
    else
    {
        gaveweapon = true;
        player->weaponowned[weapon] = true;
        player->pendingweapon = weapon;
    }

    return (gaveweapon || gaveammo);
}



//
// P_GiveBody
// Returns false if the body isn't needed at all
//
boolean
P_GiveBody
( player_t*     player,
  int           num )
{
    if (player->health >= MAXHEALTH)
        return false;

    player->health += num;
    if (player->health > MAXHEALTH)
        player->health = MAXHEALTH;
    player->mo->health = player->health;

    return true;
}



//
// P_GiveArmor
// Returns false if the armor is worse
// than the current armor.
//
boolean
P_GiveArmor
( player_t*     player,
  int           armortype )
{
    int         hits;

    hits = armortype*100;
    if (player->armorpoints >= hits)
        return false;   // don't pick up

    player->armortype = armortype;
    player->armorpoints = hits;

    return true;
}



//
// P_GiveCard
//
void
P_GiveCard
( player_t*     player,
  card_t        card )
{
    if (player->cards[card])
        return;

    player->bonuscount = BONUSADD;
    player->cards[card] = 1;
}


//
// P_GivePower
//
boolean
P_GivePower
( player_t*     player,
  int /*powertype_t*/   power )
{
    if (power == pw_invulnerability)
    {
        player->powers[power] = INVULNTICS;
        return true;
    }

    if (power == pw_invisibility)
    {
        player->powers[power] = INVISTICS;
        player->mo->flags |= MF_SHADOW;
        return true;
    }

    if (power == pw_infrared)
    {
        player->powers[power] = INFRATICS;
        return true;
    }

    if (power == pw_ironfeet)
    {
        player->powers[power] = IRONTICS;
        return true;
    }

    if (power == pw_strength)
    {
        P_GiveBody (player, 100);
        player->powers[power] = 1;
        return true;
    }

    if (player->powers[power])
        return false;   // already got it

    player->powers[power] = 1;
    return true;
}


// Dehacked
int max_armor=200;
int green_armor_class=1;
int blue_armor_class=2;
int maxsoul=200;
int soul_health=100;
int mega_health=200;

//
// P_TouchSpecialThing
//
void
P_TouchSpecialThing
( mobj_t*       special,
  mobj_t*       toucher )
{
    player_t*   player;
    int         i;
    fixed_t     delta;
    int         sound;

    delta = special->z - toucher->z;

    if (delta > toucher->height
        || delta < -8*FRACUNIT)
    {
        // out of reach
        return;
    }


    sound = sfx_itemup;
    player = toucher->player;

    // Dead thing touching.
    // Can happen with a sliding player corpse.
    if (toucher->health <= 0)
        return;

    // Identify by sprite.
    switch (special->sprite)
    {
        // armor
      case SPR_ARM1:
        if (!P_GiveArmor (player, green_armor_class))
            return;
        player->message = GOTARMOR;
        break;

      case SPR_ARM2:
        if (!P_GiveArmor (player, blue_armor_class))
            return;
        player->message = GOTMEGA;
        break;

        // bonus items
      case SPR_BON1:
        player->health++;               // can go over 100%
        if (player->health > 2*MAXHEALTH)
            player->health = 2*MAXHEALTH;
        player->mo->health = player->health;
        player->message = GOTHTHBONUS;
        break;

      case SPR_BON2:
        player->armorpoints++;          // can go over 100%
        if (player->armorpoints > max_armor)
            player->armorpoints = max_armor;
        if (!player->armortype)
            player->armortype = 1;
        player->message = GOTARMBONUS;
        break;

      case SPR_SOUL:
        player->health += soul_health;
        if (player->health > maxsoul)
            player->health = maxsoul;
        player->mo->health = player->health;
        player->message = GOTSUPER;
        sound = sfx_getpow;
        break;

      case SPR_MEGA:
        if (gamemode != commercial)
            return;
        player->health = mega_health;
        player->mo->health = player->health;
        P_GiveArmor (player,2);
        player->message = GOTMSPHERE;
        sound = sfx_getpow;
        break;

        // cards
        // leave cards for everyone
      case SPR_BKEY:
        if (!player->cards[it_bluecard])
            player->message = GOTBLUECARD;
        P_GiveCard (player, it_bluecard);
        if (!netgame)
            break;
        return;

      case SPR_YKEY:
        if (!player->cards[it_yellowcard])
            player->message = GOTYELWCARD;
        P_GiveCard (player, it_yellowcard);
        if (!netgame)
            break;
        return;

      case SPR_RKEY:
        if (!player->cards[it_redcard])
            player->message = GOTREDCARD;
        P_GiveCard (player, it_redcard);
        if (!netgame)
            break;
        return;

      case SPR_BSKU:
        if (!player->cards[it_blueskull])
            player->message = GOTBLUESKUL;
        P_GiveCard (player, it_blueskull);
        if (!netgame)
            break;
        return;

      case SPR_YSKU:
        if (!player->cards[it_yellowskull])
            player->message = GOTYELWSKUL;
        P_GiveCard (player, it_yellowskull);
        if (!netgame)
            break;
        return;

      case SPR_RSKU:
        if (!player->cards[it_redskull])
            player->message = GOTREDSKULL;
        P_GiveCard (player, it_redskull);
        if (!netgame)
            break;
        return;

        // medikits, heals
      case SPR_STIM:
        if (!P_GiveBody (player, 10))
            return;
        player->message = GOTSTIM;
        break;

      case SPR_MEDI:
        if (!P_GiveBody (player, 25))
            return;

        if (player->health < 25)
            player->message = GOTMEDINEED;
        else
            player->message = GOTMEDIKIT;
        break;


        // power ups
      case SPR_PINV:
        if (!P_GivePower (player, pw_invulnerability))
            return;
        player->message = GOTINVUL;
        sound = sfx_getpow;
        break;

      case SPR_PSTR:
        if (!P_GivePower (player, pw_strength))
            return;
        player->message = GOTBERSERK;
        if (player->readyweapon != wp_fist)
            player->pendingweapon = wp_fist;
        sound = sfx_getpow;
        break;

      case SPR_PINS:
        if (!P_GivePower (player, pw_invisibility))
            return;
        player->message = GOTINVIS;
        sound = sfx_getpow;
        break;

      case SPR_SUIT:
        if (!P_GivePower (player, pw_ironfeet))
            return;
        player->message = GOTSUIT;
        sound = sfx_getpow;
        break;

      case SPR_PMAP:
        if (!P_GivePower (player, pw_allmap))
            return;
        player->message = GOTMAP;
        sound = sfx_getpow;
        break;

      case SPR_PVIS:
        if (!P_GivePower (player, pw_infrared))
            return;
        player->message = GOTVISOR;
        sound = sfx_getpow;
        break;

        // ammo
      case SPR_CLIP:
        if (special->flags & MF_DROPPED)
        {
            if (!P_GiveAmmo (player,am_clip,0))
                return;
        }
        else
        {
            if (!P_GiveAmmo (player,am_clip,1))
                return;
        }
        player->message = GOTCLIP;
        break;

      case SPR_AMMO:
        if (!P_GiveAmmo (player, am_clip,5))
            return;
        player->message = GOTCLIPBOX;
        break;

      case SPR_ROCK:
        if (!P_GiveAmmo (player, am_misl,1))
            return;
        player->message = GOTROCKET;
        break;

      case SPR_BROK:
        if (!P_GiveAmmo (player, am_misl,5))
            return;
        player->message = GOTROCKBOX;
        break;

      case SPR_CELL:
        if (!P_GiveAmmo (player, am_cell,1))
            return;
        player->message = GOTCELL;
        break;

      case SPR_CELP:
        if (!P_GiveAmmo (player, am_cell,5))
            return;
        player->message = GOTCELLBOX;
        break;

      case SPR_SHEL:
        if (!P_GiveAmmo (player, am_shell,1))
            return;
        player->message = GOTSHELLS;
        break;

      case SPR_SBOX:
        if (!P_GiveAmmo (player, am_shell,5))
            return;
        player->message = GOTSHELLBOX;
        break;

      case SPR_BPAK:
        if (!player->backpack)
        {
            for (i=0 ; i<NUMAMMO ; i++)
                player->maxammo[i] *= 2;
            player->backpack = true;
        }
        for (i=0 ; i<NUMAMMO ; i++)
            P_GiveAmmo (player, i, 1);
        player->message = GOTBACKPACK;
        break;

        // weapons
      case SPR_BFUG:
        if (!P_GiveWeapon (player, wp_bfg, false) )
            return;
        player->message = GOTBFG9000;
        sound = sfx_wpnup;
        break;

      case SPR_MGUN:
        if (!P_GiveWeapon (player, wp_chaingun, special->flags&MF_DROPPED) )
            return;
        player->message = GOTCHAINGUN;
        sound = sfx_wpnup;
        break;

      case SPR_CSAW:
        if (!P_GiveWeapon (player, wp_chainsaw, false) )
            return;
        player->message = GOTCHAINSAW;
        sound = sfx_wpnup;
        break;

      case SPR_LAUN:
        if (!P_GiveWeapon (player, wp_missile, false) )
            return;
        player->message = GOTLAUNCHER;
        sound = sfx_wpnup;
        break;

      case SPR_PLAS:
        if (!P_GiveWeapon (player, wp_plasma, false) )
            return;
        player->message = GOTPLASMA;
        sound = sfx_wpnup;
        break;

      case SPR_SHOT:
        if (!P_GiveWeapon (player, wp_shotgun, special->flags&MF_DROPPED ) )
            return;
        player->message = GOTSHOTGUN;
        sound = sfx_wpnup;
        break;

      case SPR_SGN2:
        if (!P_GiveWeapon (player, wp_supershotgun, special->flags&MF_DROPPED ) )
            return;
        player->message = GOTSHOTGUN2;
        sound = sfx_wpnup;
        break;

      default:
        I_Error ("P_SpecialThing: Unknown gettable thing");
    }

    if (special->flags & MF_COUNTITEM)
        player->itemcount++;
    P_RemoveMobj (special);
    player->bonuscount += BONUSADD;
    if (player == &players[consoleplayer])
        S_StartSound (NULL, sound);
}


//
// KillMobj
//
void
P_KillMobj
( mobj_t*       source,
  mobj_t*       target )
{
    mobjtype_t  item;
    mobj_t*     mo;

    target->flags &= ~(MF_SHOOTABLE|MF_FLOAT|MF_SKULLFLY);

    if (target->type != MT_SKULL)
        target->flags &= ~MF_NOGRAVITY;

    target->flags |= MF_CORPSE|MF_DROPOFF;
    target->height >>= 2;

    if (source && source->player)
    {
        // count for intermission
        if (target->flags & MF_COUNTKILL)
            source->player->killcount++;

        if (target->player)
            source->player->frags[target->player-players]++;
    }
    else if (!netgame && (target->flags & MF_COUNTKILL) )
    {
        // count all monster deaths,
        // even those caused by other monsters
        players[0].killcount++;
    }

    if (target->player)
    {
        // count environment kills against you
        if (!source)
            target->player->frags[target->player-players]++;

        target->flags &= ~MF_SOLID;
        target->player->playerstate = PST_DEAD;
        P_DropWeapon (target->player);

        if (target->player == &players[consoleplayer]
            && automapactive)
        {
            // don't die in auto map,
            // switch view prior to dying
            AM_Stop ();
        }

    }

    if (target->health < -target->info->spawnhealth
        && target->info->xdeathstate)
    {
        P_SetMobjState (target, target->info->xdeathstate);
    }
    else
        P_SetMobjState (target, target->info->deathstate);
    target->tics -= P_Random()&3;

    if (target->tics < 1)
        target->tics = 1;

    //  I_StartSound (&actor->r, actor->info->deathsound);


    // Drop stuff.
    // This determines the kind of object spawned
    // during the death frame of a thing.
    switch (target->type)
    {
      case MT_WOLFSS:
      case MT_POSSESSED:
        item = MT_CLIP;
        break;

      case MT_SHOTGUY:
        item = MT_SHOTGUN;
        break;

      case MT_CHAINGUY:
        item = MT_CHAINGUN;
        break;

      default:
        return;
    }

    mo = P_SpawnMobj (target->x,target->y,ONFLOORZ, item);
    mo->flags |= MF_DROPPED;    // special versions of items
}




//
// P_DamageMobj
// Damages both enemies and players
// "inflictor" is the thing that caused the damage
//  creature or missile, can be NULL (slime, etc)
// "source" is the thing to target after taking damage
//  creature or NULL
// Source and inflictor are the same for melee attacks.
// Source can be NULL for slime, barrel explosions
// and other environmental stuff.
//
void
P_DamageMobj
( mobj_t*       target,
  mobj_t*       inflictor,
  mobj_t*       source,
  int           damage )
{
    unsigned    ang;
    int         saved;
    player_t*   player;
    fixed_t     thrust;
    int         temp;

    if ( !(target->flags & MF_SHOOTABLE) )
        return; // shouldn't happen...

    if (target->health <= 0)
        return;

    if ( target->flags & MF_SKULLFLY )
    {
        target->momx = target->momy = target->momz = 0;
    }

    player = target->player;
    if (player && gameskill == sk_baby)
        damage >>= 1;   // take half damage in trainer mode


    // Some close combat weapons should not
    // inflict thrust and push the victim out of reach,
    // thus kick away unless using the chainsaw.
    if (inflictor
        && !(target->flags & MF_NOCLIP)
        && (!source
            || !source->player
            || source->player->readyweapon != wp_chainsaw))
    {
        ang = R_PointToAngle2 ( inflictor->x,
                                inflictor->y,
                                target->x,
                                target->y);

        thrust = damage*(FRACUNIT>>3)*100/target->info->mass;

        // make fall forwards sometimes
        if ( damage < 40
             && damage > target->health
             && target->z - inflictor->z > 64*FRACUNIT
             && (P_Random ()&1) )
        {
            ang += ANG180;
            thrust *= 4;
        }

        ang >>= ANGLETOFINESHIFT;
        target->momx += FixedMul (thrust, finecosine[ang]);
        target->momy += FixedMul (thrust, finesine[ang]);
    }

    // player specific
    if (player)
    {
        // end of game hell hack
        if (target->subsector->sector->special == 11
            && damage >= target->health)
        {
            damage = target->health - 1;
        }


        // Below certain threshold,
        // ignore damage in GOD mode, or with INVUL power.
        if ( damage < 1000
             && ( (player->cheats&CF_GODMODE)
                  || player->powers[pw_invulnerability] ) )
        {
            return;
        }

        if (player->armortype)
        {
            if (player->armortype == 1)
                saved = damage/3;
            else
                saved = damage/2;

            if (player->armorpoints <= saved)
            {
                // armor is used up
                saved = player->armorpoints;
                player->armortype = 0;
            }
            player->armorpoints -= saved;
            damage -= saved;
        }
        player->health -= damage;       // mirror mobj health here for Dave
        if (player->health < 0)
            player->health = 0;

        player->attacker = source;
        player->damagecount += damage;  // add damage after armor / invuln

        if (player->damagecount > 100)
            player->damagecount = 100;  // teleport stomp does 10k points...

        temp = damage < 100 ? damage : 100;

        if (player == &players[consoleplayer])
            I_Tactile (20, 1+(temp>>4), 0);
    }

    // do the damage
    target->health -= damage;
    if (target->health <= 0)
    {
        P_KillMobj (source, target);
        return;
    }

    if ( (P_Random () < target->info->painchance)
         && !(target->flags&MF_SKULLFLY) )
    {
        target->flags |= MF_JUSTHIT;    // fight back!

        P_SetMobjState (target, target->info->painstate);
    }

    target->reactiontime = 0;           // we're awake now...

    if ( (!target->threshold || target->type == MT_VILE)
         && source && source != target
         && source->type != MT_VILE)
    {
        // if not intent on another player,
        // chase after this one
        target->target = source;
        target->threshold = BASETHRESHOLD;
        if (target->state == &states[target->info->spawnstate]
            && target->info->seestate != S_NULL)
            P_SetMobjState (target, target->info->seestate);
    }

}

