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

// Data.
#include "p_inter.h"

#include "am_map.h"
#include "deh_main.h"
#include "deh_misc.h"
#include "doomdef.h"
#include "doomstat.h"
#include "dstrings.h"
#include "i_system.h"
#include "m_random.h"
#include "p_local.h"
#include "s_sound.h"
#include "sounds.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};

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

//
// 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, deh_green_armor_class))
            return;
        player->message = DEH_String(GOTARMOR);
        break;

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

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

    case SPR_BON2:
        player->armorpoints++; // can go over 100%
        if (player->armorpoints > deh_max_armor)
            player->armorpoints = deh_max_armor;
        // deh_green_armor_class only applies to the green armor shirt;
        // for the armor helmets, armortype 1 is always used.
        if (!player->armortype)
            player->armortype = 1;
        player->message = DEH_String(GOTARMBONUS);
        break;

    case SPR_SOUL:
        player->health += deh_soulsphere_health;
        if (player->health > deh_max_soulsphere)
            player->health = deh_max_soulsphere;
        player->mo->health = player->health;
        player->message = DEH_String(GOTSUPER);
        sound = sfx_getpow;
        break;

    case SPR_MEGA:
        if (gamemode != commercial)
            return;
        player->health = deh_megasphere_health;
        player->mo->health = player->health;
        // We always give armor type 2 for the megasphere; dehacked only
        // affects the MegaArmor.
        P_GiveArmor(player, 2);
        player->message = DEH_String(GOTMSPHERE);
        sound = sfx_getpow;
        break;

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

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

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

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

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

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

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

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

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

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

    case SPR_PSTR:
        if (!P_GivePower(player, pw_strength))
            return;
        player->message = DEH_String(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 = DEH_String(GOTINVIS);
        sound = sfx_getpow;
        break;

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

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

    case SPR_PVIS:
        if (!P_GivePower(player, pw_infrared))
            return;
        player->message = DEH_String(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 = DEH_String(GOTCLIP);
        break;

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

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

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

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

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

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

    case SPR_SBOX:
        if (!P_GiveAmmo(player, am_shell, 5))
            return;
        player->message = DEH_String(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 = DEH_String(GOTBACKPACK);
        break;

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

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

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

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

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

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

    case SPR_SGN2:
        if (!P_GiveWeapon(player, wp_supershotgun, (special->flags & MF_DROPPED) != 0))
            return;
        player->message = DEH_String(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);

    // In Chex Quest, monsters don't drop items.

    if (gameversion == exe_chex) {
        return;
    }

    // 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(40, 10, 40 + temp * 2);
    }

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