shithub: choc

ref: 0e526452efc77a6fb8889545b456dfa58a92628f
dir: /src/hexen/p_inter.c/

View raw version
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 1993-2008 Raven Software
// 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.
//


#include "h2def.h"
#include "m_misc.h"
#include "m_random.h"
#include "i_system.h"
#include "p_local.h"
#include "s_sound.h"

#define BONUSADD 6

int ArmorIncrement[NUMCLASSES][NUMARMOR] = {
    {25 * FRACUNIT, 20 * FRACUNIT, 15 * FRACUNIT, 5 * FRACUNIT},
    {10 * FRACUNIT, 25 * FRACUNIT, 5 * FRACUNIT, 20 * FRACUNIT},
    {5 * FRACUNIT, 15 * FRACUNIT, 10 * FRACUNIT, 25 * FRACUNIT},
    {0, 0, 0, 0}
};

int AutoArmorSave[NUMCLASSES] =
    { 15 * FRACUNIT, 10 * FRACUNIT, 5 * FRACUNIT, 0 };

const char *TextKeyMessages[] = {
    TXT_KEY_STEEL,
    TXT_KEY_CAVE,
    TXT_KEY_AXE,
    TXT_KEY_FIRE,
    TXT_KEY_EMERALD,
    TXT_KEY_DUNGEON,
    TXT_KEY_SILVER,
    TXT_KEY_RUSTED,
    TXT_KEY_HORN,
    TXT_KEY_SWAMP,
    TXT_KEY_CASTLE
};

static void SetDormantArtifact(mobj_t * arti);
static void TryPickupArtifact(player_t * player, artitype_t artifactType,
                              mobj_t * artifact);
static void TryPickupWeapon(player_t * player, pclass_t weaponClass,
                            weapontype_t weaponType, mobj_t * weapon,
                            const char *message);
static void TryPickupWeaponPiece(player_t * player, pclass_t matchClass,
                                 int pieceValue, mobj_t * pieceMobj);

//--------------------------------------------------------------------------
//
// PROC P_SetMessage
//
//--------------------------------------------------------------------------

void P_SetMessage(player_t * player, const char *message, boolean ultmsg)
{
    if ((player->ultimateMessage || !messageson) && !ultmsg)
    {
        return;
    }

    M_StringCopy(player->message, message, sizeof(player->message));
//    strupr(player->message);
    player->messageTics = MESSAGETICS;
    player->yellowMessage = false;
    if (ultmsg)
    {
        player->ultimateMessage = true;
    }
    if (player == &players[consoleplayer])
    {
        BorderTopRefresh = true;
    }
}

//==========================================================================
//
// P_SetYellowMessage
//
//==========================================================================

void P_SetYellowMessage(player_t * player, const char *message, boolean ultmsg)
{
    if ((player->ultimateMessage || !messageson) && !ultmsg)
    {
        return;
    }
    M_StringCopy(player->message, message, sizeof(player->message));
    player->messageTics = 5 * MESSAGETICS;      // Bold messages last longer
    player->yellowMessage = true;
    if (ultmsg)
    {
        player->ultimateMessage = true;
    }
    if (player == &players[consoleplayer])
    {
        BorderTopRefresh = true;
    }
}

//==========================================================================
//
// P_ClearMessage
//
//==========================================================================

void P_ClearMessage(player_t * player)
{
    player->messageTics = 0;
    if (player == &players[consoleplayer])
    {
        BorderTopRefresh = true;
    }
}

//----------------------------------------------------------------------------
//
// PROC P_HideSpecialThing
//
//----------------------------------------------------------------------------

void P_HideSpecialThing(mobj_t * thing)
{
    thing->flags &= ~MF_SPECIAL;
    thing->flags2 |= MF2_DONTDRAW;
    P_SetMobjState(thing, S_HIDESPECIAL1);
}

//--------------------------------------------------------------------------
//
// FUNC P_GiveMana
//
// Returns true if the player accepted the mana, false if it was
// refused (player has MAX_MANA).
//
//--------------------------------------------------------------------------

boolean P_GiveMana(player_t * player, manatype_t mana, int count)
{
    int prevMana;
    //weapontype_t changeWeapon;

    if (mana == MANA_NONE || mana == MANA_BOTH)
    {
        return (false);
    }
    if ((unsigned int) mana > NUMMANA)
    {
        I_Error("P_GiveMana: bad type %i", mana);
    }
    if (player->mana[mana] == MAX_MANA)
    {
        return (false);
    }
    if (gameskill == sk_baby || gameskill == sk_nightmare)
    {                           // extra mana in baby mode and nightmare mode
        count += count >> 1;
    }
    prevMana = player->mana[mana];

    player->mana[mana] += count;
    if (player->mana[mana] > MAX_MANA)
    {
        player->mana[mana] = MAX_MANA;
    }
    if (player->class == PCLASS_FIGHTER && player->readyweapon == WP_SECOND
        && mana == MANA_1 && prevMana <= 0)
    {
        P_SetPsprite(player, ps_weapon, S_FAXEREADY_G);
    }
    return (true);
}

//==========================================================================
//
// TryPickupWeapon
//
//==========================================================================

static void TryPickupWeapon(player_t * player, pclass_t weaponClass,
                            weapontype_t weaponType, mobj_t * weapon,
                            const char *message)
{
    boolean remove;
    boolean gaveMana;
    boolean gaveWeapon;

    remove = true;
    if (player->class != weaponClass)
    {                           // Wrong class, but try to pick up for mana
        if (netgame && !deathmatch)
        {                       // Can't pick up weapons for other classes in coop netplay
            return;
        }
        if (weaponType == WP_SECOND)
        {
            if (!P_GiveMana(player, MANA_1, 25))
            {
                return;
            }
        }
        else
        {
            if (!P_GiveMana(player, MANA_2, 25))
            {
                return;
            }
        }
    }
    else if (netgame && !deathmatch)
    {                           // Cooperative net-game
        if (player->weaponowned[weaponType])
        {
            return;
        }
        player->weaponowned[weaponType] = true;
        if (weaponType == WP_SECOND)
        {
            P_GiveMana(player, MANA_1, 25);
        }
        else
        {
            P_GiveMana(player, MANA_2, 25);
        }
        player->pendingweapon = weaponType;
        remove = false;
    }
    else
    {                           // Deathmatch or single player game
        if (weaponType == WP_SECOND)
        {
            gaveMana = P_GiveMana(player, MANA_1, 25);
        }
        else
        {
            gaveMana = P_GiveMana(player, MANA_2, 25);
        }
        if (player->weaponowned[weaponType])
        {
            gaveWeapon = false;
        }
        else
        {
            gaveWeapon = true;
            player->weaponowned[weaponType] = true;
            if (weaponType > player->readyweapon)
            {                   // Only switch to more powerful weapons
                player->pendingweapon = weaponType;
            }
        }
        if (!(gaveWeapon || gaveMana))
        {                       // Player didn't need the weapon or any mana
            return;
        }
    }

    P_SetMessage(player, message, false);
    if (weapon->special)
    {
        P_ExecuteLineSpecial(weapon->special, weapon->args,
                             NULL, 0, player->mo);
        weapon->special = 0;
    }

    if (remove)
    {
        if (deathmatch && !(weapon->flags2 & MF2_DROPPED))
        {
            P_HideSpecialThing(weapon);
        }
        else
        {
            P_RemoveMobj(weapon);
        }
    }

    player->bonuscount += BONUSADD;
    if (player == &players[consoleplayer])
    {
        S_StartSound(NULL, SFX_PICKUP_WEAPON);
        SB_PaletteFlash(false);
    }
}

//--------------------------------------------------------------------------
//
// FUNC P_GiveWeapon
//
// Returns true if the weapon or its mana was accepted.
//
//--------------------------------------------------------------------------

/*
boolean P_GiveWeapon(player_t *player, pclass_t class, weapontype_t weapon)
{
	boolean gaveMana;
	boolean gaveWeapon;

	if(player->class != class)
	{ // player cannot use this weapon, take it anyway, and get mana
		if(netgame && !deathmatch)
		{ // Can't pick up weapons for other classes in coop netplay
			return false;
		}
		if(weapon == WP_SECOND)
		{
			return P_GiveMana(player, MANA_1, 25);
		}
		else
		{
			return P_GiveMana(player, MANA_2, 25);
		}		
	}
	if(netgame && !deathmatch)
	{ // Cooperative net-game
		if(player->weaponowned[weapon])
		{
			return(false);
		}
		player->bonuscount += BONUSADD;
		player->weaponowned[weapon] = true;
		if(weapon == WP_SECOND)
		{
			P_GiveMana(player, MANA_1, 25);
		}
		else 
		{
			P_GiveMana(player, MANA_2, 25);
		}
		player->pendingweapon = weapon;
		if(player == &players[consoleplayer])
		{
			S_StartSound(NULL, SFX_PICKUP_WEAPON);
		}
		return(false);
	}
	if(weapon == WP_SECOND)
	{
		gaveMana = P_GiveMana(player, MANA_1, 25);
	}
	else 
	{
		gaveMana = P_GiveMana(player, MANA_2, 25);
	}
	if(player->weaponowned[weapon])
	{
		gaveWeapon = false;
	}
	else
	{
		gaveWeapon = true;
		player->weaponowned[weapon] = true;
		if(weapon > player->readyweapon)
		{ // Only switch to more powerful weapons
			player->pendingweapon = weapon;
		}
	}
	return(gaveWeapon || gaveMana);
}
*/

//===========================================================================
//
// P_GiveWeaponPiece
//
//===========================================================================

/*
boolean P_GiveWeaponPiece(player_t *player, pclass_t class, int piece)
{
	P_GiveMana(player, MANA_1, 20);
	P_GiveMana(player, MANA_2, 20);
	if(player->class != class)
	{
		return true;
	}
	else if(player->pieces&piece)
	{ // player already has that weapon piece
		return true;
	}
	player->pieces |= piece;
	if(player->pieces == 7)
	{ // player has built the fourth weapon!
		P_GiveWeapon(player, class, WP_FOURTH);
		S_StartSound(player->mo, SFX_WEAPON_BUILD);
	}
	return true;
}
*/

//==========================================================================
//
// TryPickupWeaponPiece
//
//==========================================================================

static void TryPickupWeaponPiece(player_t * player, pclass_t matchClass,
                                 int pieceValue, mobj_t * pieceMobj)
{
    boolean remove;
    boolean checkAssembled;
    boolean gaveWeapon;
    int gaveMana;
    static const char *fourthWeaponText[] = {
        TXT_WEAPON_F4,
        TXT_WEAPON_C4,
        TXT_WEAPON_M4
    };
    static const char *weaponPieceText[] = {
        TXT_QUIETUS_PIECE,
        TXT_WRAITHVERGE_PIECE,
        TXT_BLOODSCOURGE_PIECE
    };
    static int pieceValueTrans[] = {
        0,                      // 0: never
        WPIECE1 | WPIECE2 | WPIECE3,    // WPIECE1 (1)
        WPIECE2 | WPIECE3,      // WPIECE2 (2)
        0,                      // 3: never
        WPIECE3                 // WPIECE3 (4)
    };

    remove = true;
    checkAssembled = true;
    gaveWeapon = false;
    if (player->class != matchClass)
    {                           // Wrong class, but try to pick up for mana
        if (netgame && !deathmatch)
        {                       // Can't pick up wrong-class weapons in coop netplay
            return;
        }
        checkAssembled = false;
        gaveMana = P_GiveMana(player, MANA_1, 20) +
            P_GiveMana(player, MANA_2, 20);
        if (!gaveMana)
        {                       // Didn't need the mana, so don't pick it up
            return;
        }
    }
    else if (netgame && !deathmatch)
    {                           // Cooperative net-game
        if (player->pieces & pieceValue)
        {                       // Already has the piece
            return;
        }
        pieceValue = pieceValueTrans[pieceValue];
        P_GiveMana(player, MANA_1, 20);
        P_GiveMana(player, MANA_2, 20);
        remove = false;
    }
    else
    {                           // Deathmatch or single player game
        gaveMana = P_GiveMana(player, MANA_1, 20) +
            P_GiveMana(player, MANA_2, 20);
        if (player->pieces & pieceValue)
        {                       // Already has the piece, check if mana needed
            if (!gaveMana)
            {                   // Didn't need the mana, so don't pick it up
                return;
            }
            checkAssembled = false;
        }
    }

    // Pick up the weapon piece
    if (pieceMobj->special)
    {
        P_ExecuteLineSpecial(pieceMobj->special, pieceMobj->args,
                             NULL, 0, player->mo);
        pieceMobj->special = 0;
    }
    if (remove)
    {
        if (deathmatch && !(pieceMobj->flags2 & MF2_DROPPED))
        {
            P_HideSpecialThing(pieceMobj);
        }
        else
        {
            P_RemoveMobj(pieceMobj);
        }
    }
    player->bonuscount += BONUSADD;
    if (player == &players[consoleplayer])
    {
        SB_PaletteFlash(false);
    }

    // Check if fourth weapon assembled
    if (checkAssembled)
    {
        player->pieces |= pieceValue;
        if (player->pieces == (WPIECE1 | WPIECE2 | WPIECE3))
        {
            gaveWeapon = true;
            player->weaponowned[WP_FOURTH] = true;
            player->pendingweapon = WP_FOURTH;
        }
    }

    if (gaveWeapon)
    {
        P_SetMessage(player, fourthWeaponText[matchClass], false);
        // Play the build-sound full volume for all players
        S_StartSound(NULL, SFX_WEAPON_BUILD);
    }
    else
    {
        P_SetMessage(player, weaponPieceText[matchClass], false);
        if (player == &players[consoleplayer])
        {
            S_StartSound(NULL, SFX_PICKUP_WEAPON);
        }
    }
}

//---------------------------------------------------------------------------
//
// FUNC P_GiveBody
//
// Returns false if the body isn't needed at all.
//
//---------------------------------------------------------------------------

boolean P_GiveBody(player_t * player, int num)
{
    int max;

    max = MAXHEALTH;
    if (player->morphTics)
    {
        max = MAXMORPHHEALTH;
    }
    if (player->health >= max)
    {
        return (false);
    }
    player->health += num;
    if (player->health > max)
    {
        player->health = max;
    }
    player->mo->health = player->health;
    return (true);
}

//---------------------------------------------------------------------------
//
// FUNC P_GiveArmor
//
// Returns false if the armor is worse than the current armor.
//
//---------------------------------------------------------------------------

boolean P_GiveArmor(player_t * player, armortype_t armortype, int amount)
{
    int hits;
    int totalArmor;

    if (amount == -1)
    {
        hits = ArmorIncrement[player->class][armortype];
        if (player->armorpoints[armortype] >= hits)
        {
            return false;
        }
        else
        {
            player->armorpoints[armortype] = hits;
        }
    }
    else
    {
        hits = amount * 5 * FRACUNIT;
        totalArmor = player->armorpoints[ARMOR_ARMOR]
            + player->armorpoints[ARMOR_SHIELD]
            + player->armorpoints[ARMOR_HELMET]
            + player->armorpoints[ARMOR_AMULET]
            + AutoArmorSave[player->class];
        if (totalArmor < ArmorMax[player->class] * 5 * FRACUNIT)
        {
            player->armorpoints[armortype] += hits;
        }
        else
        {
            return false;
        }
    }
    return true;
}

//---------------------------------------------------------------------------
//
// PROC P_GiveKey
//
//---------------------------------------------------------------------------

int P_GiveKey(player_t * player, keytype_t key)
{
    if (player->keys & (1 << key))
    {
        return false;
    }
    player->bonuscount += BONUSADD;
    player->keys |= 1 << key;
    return true;
}

//---------------------------------------------------------------------------
//
// FUNC P_GivePower
//
// Returns true if power accepted.
//
//---------------------------------------------------------------------------

boolean P_GivePower(player_t * player, powertype_t power)
{
    if (power == pw_invulnerability)
    {
        if (player->powers[power] > BLINKTHRESHOLD)
        {                       // Already have it
            return (false);
        }
        player->powers[power] = INVULNTICS;
        player->mo->flags2 |= MF2_INVULNERABLE;
        if (player->class == PCLASS_MAGE)
        {
            player->mo->flags2 |= MF2_REFLECTIVE;
        }
        return (true);
    }
    if (power == pw_flight)
    {
        if (player->powers[power] > BLINKTHRESHOLD)
        {                       // Already have it
            return (false);
        }
        player->powers[power] = FLIGHTTICS;
        player->mo->flags2 |= MF2_FLY;
        player->mo->flags |= MF_NOGRAVITY;
        if (player->mo->z <= player->mo->floorz)
        {
            player->flyheight = 10;     // thrust the player in the air a bit
        }
        return (true);
    }
    if (power == pw_infrared)
    {
        if (player->powers[power] > BLINKTHRESHOLD)
        {                       // Already have it
            return (false);
        }
        player->powers[power] = INFRATICS;
        return (true);
    }
    if (power == pw_speed)
    {
        if (player->powers[power] > BLINKTHRESHOLD)
        {                       // Already have it
            return (false);
        }
        player->powers[power] = SPEEDTICS;
        return (true);
    }
    if (power == pw_minotaur)
    {
        // Doesn't matter if already have power, renew ticker
        player->powers[power] = MAULATORTICS;
        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);
}

//==========================================================================
//
// TryPickupArtifact
//
//==========================================================================

static void TryPickupArtifact(player_t * player, artitype_t artifactType,
                              mobj_t * artifact)
{
    static const char *artifactMessages[NUMARTIFACTS] = {
        NULL,
        TXT_ARTIINVULNERABILITY,
        TXT_ARTIHEALTH,
        TXT_ARTISUPERHEALTH,
        TXT_ARTIHEALINGRADIUS,
        TXT_ARTISUMMON,
        TXT_ARTITORCH,
        TXT_ARTIEGG,
        TXT_ARTIFLY,
        TXT_ARTIBLASTRADIUS,
        TXT_ARTIPOISONBAG,
        TXT_ARTITELEPORTOTHER,
        TXT_ARTISPEED,
        TXT_ARTIBOOSTMANA,
        TXT_ARTIBOOSTARMOR,
        TXT_ARTITELEPORT,
        TXT_ARTIPUZZSKULL,
        TXT_ARTIPUZZGEMBIG,
        TXT_ARTIPUZZGEMRED,
        TXT_ARTIPUZZGEMGREEN1,
        TXT_ARTIPUZZGEMGREEN2,
        TXT_ARTIPUZZGEMBLUE1,
        TXT_ARTIPUZZGEMBLUE2,
        TXT_ARTIPUZZBOOK1,
        TXT_ARTIPUZZBOOK2,
        TXT_ARTIPUZZSKULL2,
        TXT_ARTIPUZZFWEAPON,
        TXT_ARTIPUZZCWEAPON,
        TXT_ARTIPUZZMWEAPON,
        TXT_ARTIPUZZGEAR,       // All gear pickups use the same text
        TXT_ARTIPUZZGEAR,
        TXT_ARTIPUZZGEAR,
        TXT_ARTIPUZZGEAR
    };

    if (gamemode == shareware)
    {
        artifactMessages[arti_blastradius] = TXT_ARTITELEPORT;
        artifactMessages[arti_teleport] = TXT_ARTIBLASTRADIUS;
    }

    if (P_GiveArtifact(player, artifactType, artifact))
    {
        if (artifact->special)
        {
            P_ExecuteLineSpecial(artifact->special, artifact->args,
                                 NULL, 0, NULL);
            artifact->special = 0;
        }
        player->bonuscount += BONUSADD;
        if (artifactType < arti_firstpuzzitem)
        {
            SetDormantArtifact(artifact);
            S_StartSound(artifact, SFX_PICKUP_ARTIFACT);
            P_SetMessage(player, artifactMessages[artifactType], false);
        }
        else
        {                       // Puzzle item
            S_StartSound(NULL, SFX_PICKUP_ITEM);
            P_SetMessage(player, artifactMessages[artifactType], true);
            if (!netgame || deathmatch)
            {                   // Remove puzzle items if not cooperative netplay
                P_RemoveMobj(artifact);
            }
        }
    }
}

//---------------------------------------------------------------------------
//
// FUNC P_GiveArtifact
//
// Returns true if artifact accepted.
//
//---------------------------------------------------------------------------

boolean P_GiveArtifact(player_t * player, artitype_t arti, mobj_t * mo)
{
    int i;
    int j;
    boolean slidePointer;

    slidePointer = false;
    i = 0;
    while (player->inventory[i].type != arti && i < player->inventorySlotNum)
    {
        i++;
    }
    if (i == player->inventorySlotNum)
    {
        if (arti < arti_firstpuzzitem)
        {
            i = 0;
            while (player->inventory[i].type < arti_firstpuzzitem
                   && i < player->inventorySlotNum)
            {
                i++;
            }
            if (i != player->inventorySlotNum)
            {
                for (j = player->inventorySlotNum; j > i; j--)
                {
                    player->inventory[j].count =
                        player->inventory[j - 1].count;
                    player->inventory[j].type = player->inventory[j - 1].type;
                    slidePointer = true;
                }
            }
        }
        player->inventory[i].count = 1;
        player->inventory[i].type = arti;
        player->inventorySlotNum++;
    }
    else
    {
        if (arti >= arti_firstpuzzitem && netgame && !deathmatch)
        {                       // Can't carry more than 1 puzzle item in coop netplay
            return false;
        }
        if (player->inventory[i].count >= 25)
        {                       // Player already has 25 of this item
            return false;
        }
        player->inventory[i].count++;
    }
    if (!player->artifactCount)
    {
        player->readyArtifact = arti;
    }
    else if (player == &players[consoleplayer] && slidePointer
             && i <= inv_ptr)
    {
        inv_ptr++;
        curpos++;
        if (curpos > 6)
        {
            curpos = 6;
        }
    }
    player->artifactCount++;
    return (true);
}

//==========================================================================
//
// SetDormantArtifact
//
// Removes the MF_SPECIAL flag and initiates the artifact pickup
// animation.
//
//==========================================================================

static void SetDormantArtifact(mobj_t * arti)
{
    arti->flags &= ~MF_SPECIAL;
    if (deathmatch && !(arti->flags2 & MF2_DROPPED))
    {
        if (arti->type == MT_ARTIINVULNERABILITY)
        {
            P_SetMobjState(arti, S_DORMANTARTI3_1);
        }
        else if (arti->type == MT_SUMMONMAULATOR || arti->type == MT_ARTIFLY)
        {
            P_SetMobjState(arti, S_DORMANTARTI2_1);
        }
        else
        {
            P_SetMobjState(arti, S_DORMANTARTI1_1);
        }
    }
    else
    {                           // Don't respawn
        P_SetMobjState(arti, S_DEADARTI1);
    }
}

//---------------------------------------------------------------------------
//
// PROC A_RestoreArtifact
//
//---------------------------------------------------------------------------

void A_RestoreArtifact(mobj_t * arti)
{
    arti->flags |= MF_SPECIAL;
    P_SetMobjState(arti, arti->info->spawnstate);
    S_StartSound(arti, SFX_RESPAWN);
}

//---------------------------------------------------------------------------
//
// PROC A_RestoreSpecialThing1
//
// Make a special thing visible again.
//
//---------------------------------------------------------------------------

void A_RestoreSpecialThing1(mobj_t * thing)
{
    thing->flags2 &= ~MF2_DONTDRAW;
    S_StartSound(thing, SFX_RESPAWN);
}

//---------------------------------------------------------------------------
//
// PROC A_RestoreSpecialThing2
//
//---------------------------------------------------------------------------

void A_RestoreSpecialThing2(mobj_t * thing)
{
    thing->flags |= MF_SPECIAL;
    P_SetMobjState(thing, thing->info->spawnstate);
}

//---------------------------------------------------------------------------
//
// PROC P_TouchSpecialThing
//
//---------------------------------------------------------------------------

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

    delta = special->z - toucher->z;
    if (delta > toucher->height || delta < -32 * FRACUNIT)
    {                           // Out of reach
        return;
    }
    if (toucher->health <= 0)
    {                           // Toucher is dead
        return;
    }
    sound = SFX_PICKUP_ITEM;
    player = toucher->player;
    respawn = true;
    switch (special->sprite)
    {
            // Items
        case SPR_PTN1:         // Item_HealingPotion
            if (!P_GiveBody(player, 10))
            {
                return;
            }
            P_SetMessage(player, TXT_ITEMHEALTH, false);
            break;
        case SPR_ARM1:
            if (!P_GiveArmor(player, ARMOR_ARMOR, -1))
            {
                return;
            }
            P_SetMessage(player, TXT_ARMOR1, false);
            break;
        case SPR_ARM2:
            if (!P_GiveArmor(player, ARMOR_SHIELD, -1))
            {
                return;
            }
            P_SetMessage(player, TXT_ARMOR2, false);
            break;
        case SPR_ARM3:
            if (!P_GiveArmor(player, ARMOR_HELMET, -1))
            {
                return;
            }
            P_SetMessage(player, TXT_ARMOR3, false);
            break;
        case SPR_ARM4:
            if (!P_GiveArmor(player, ARMOR_AMULET, -1))
            {
                return;
            }
            P_SetMessage(player, TXT_ARMOR4, false);
            break;

            // Keys
        case SPR_KEY1:
        case SPR_KEY2:
        case SPR_KEY3:
        case SPR_KEY4:
        case SPR_KEY5:
        case SPR_KEY6:
        case SPR_KEY7:
        case SPR_KEY8:
        case SPR_KEY9:
        case SPR_KEYA:
        case SPR_KEYB:
            if (!P_GiveKey(player, special->sprite - SPR_KEY1))
            {
                return;
            }
            P_SetMessage(player, TextKeyMessages[special->sprite - SPR_KEY1],
                         true);
            sound = SFX_PICKUP_KEY;

            // Check and process the special now in case the key doesn't
            // get removed for coop netplay
            if (special->special)
            {
                P_ExecuteLineSpecial(special->special, special->args,
                                     NULL, 0, toucher);
                special->special = 0;
            }

            if (!netgame)
            {                   // Only remove keys in single player game
                break;
            }
            player->bonuscount += BONUSADD;
            if (player == &players[consoleplayer])
            {
                S_StartSound(NULL, sound);
                SB_PaletteFlash(false);
            }
            return;

            // Artifacts
        case SPR_PTN2:
            TryPickupArtifact(player, arti_health, special);
            return;
        case SPR_SOAR:
            TryPickupArtifact(player, arti_fly, special);
            return;
        case SPR_INVU:
            TryPickupArtifact(player, arti_invulnerability, special);
            return;
        case SPR_SUMN:
            TryPickupArtifact(player, arti_summon, special);
            return;
        case SPR_PORK:
            TryPickupArtifact(player, arti_egg, special);
            return;
        case SPR_SPHL:
            TryPickupArtifact(player, arti_superhealth, special);
            return;
        case SPR_HRAD:
            TryPickupArtifact(player, arti_healingradius, special);
            return;
        case SPR_TRCH:
            TryPickupArtifact(player, arti_torch, special);
            return;
        case SPR_ATLP:
            TryPickupArtifact(player, arti_teleport, special);
            return;
        case SPR_TELO:
            TryPickupArtifact(player, arti_teleportother, special);
            return;
        case SPR_PSBG:
            TryPickupArtifact(player, arti_poisonbag, special);
            return;
        case SPR_SPED:
            TryPickupArtifact(player, arti_speed, special);
            return;
        case SPR_BMAN:
            TryPickupArtifact(player, arti_boostmana, special);
            return;
        case SPR_BRAC:
            TryPickupArtifact(player, arti_boostarmor, special);
            return;
        case SPR_BLST:
            TryPickupArtifact(player, arti_blastradius, special);
            return;

            // Puzzle artifacts
        case SPR_ASKU:
            TryPickupArtifact(player, arti_puzzskull, special);
            return;
        case SPR_ABGM:
            TryPickupArtifact(player, arti_puzzgembig, special);
            return;
        case SPR_AGMR:
            TryPickupArtifact(player, arti_puzzgemred, special);
            return;
        case SPR_AGMG:
            TryPickupArtifact(player, arti_puzzgemgreen1, special);
            return;
        case SPR_AGG2:
            TryPickupArtifact(player, arti_puzzgemgreen2, special);
            return;
        case SPR_AGMB:
            TryPickupArtifact(player, arti_puzzgemblue1, special);
            return;
        case SPR_AGB2:
            TryPickupArtifact(player, arti_puzzgemblue2, special);
            return;
        case SPR_ABK1:
            TryPickupArtifact(player, arti_puzzbook1, special);
            return;
        case SPR_ABK2:
            TryPickupArtifact(player, arti_puzzbook2, special);
            return;
        case SPR_ASK2:
            TryPickupArtifact(player, arti_puzzskull2, special);
            return;
        case SPR_AFWP:
            TryPickupArtifact(player, arti_puzzfweapon, special);
            return;
        case SPR_ACWP:
            TryPickupArtifact(player, arti_puzzcweapon, special);
            return;
        case SPR_AMWP:
            TryPickupArtifact(player, arti_puzzmweapon, special);
            return;
        case SPR_AGER:
            TryPickupArtifact(player, arti_puzzgear1, special);
            return;
        case SPR_AGR2:
            TryPickupArtifact(player, arti_puzzgear2, special);
            return;
        case SPR_AGR3:
            TryPickupArtifact(player, arti_puzzgear3, special);
            return;
        case SPR_AGR4:
            TryPickupArtifact(player, arti_puzzgear4, special);
            return;

            // Mana
        case SPR_MAN1:
            if (!P_GiveMana(player, MANA_1, 15))
            {
                return;
            }
            P_SetMessage(player, TXT_MANA_1, false);
            break;
        case SPR_MAN2:
            if (!P_GiveMana(player, MANA_2, 15))
            {
                return;
            }
            P_SetMessage(player, TXT_MANA_2, false);
            break;
        case SPR_MAN3:         // Double Mana Dodecahedron
            if (!P_GiveMana(player, MANA_1, 20))
            {
                if (!P_GiveMana(player, MANA_2, 20))
                {
                    return;
                }
            }
            else
            {
                P_GiveMana(player, MANA_2, 20);
            }
            P_SetMessage(player, TXT_MANA_BOTH, false);
            break;

            // 2nd and 3rd Mage Weapons
        case SPR_WMCS:         // Frost Shards
            TryPickupWeapon(player, PCLASS_MAGE, WP_SECOND,
                            special, TXT_WEAPON_M2);
            return;
        case SPR_WMLG:         // Arc of Death
            TryPickupWeapon(player, PCLASS_MAGE, WP_THIRD,
                            special, TXT_WEAPON_M3);
            return;

            // 2nd and 3rd Fighter Weapons
        case SPR_WFAX:         // Timon's Axe
            TryPickupWeapon(player, PCLASS_FIGHTER, WP_SECOND,
                            special, TXT_WEAPON_F2);
            return;
        case SPR_WFHM:         // Hammer of Retribution
            TryPickupWeapon(player, PCLASS_FIGHTER, WP_THIRD,
                            special, TXT_WEAPON_F3);
            return;

            // 2nd and 3rd Cleric Weapons
        case SPR_WCSS:         // Serpent Staff
            TryPickupWeapon(player, PCLASS_CLERIC, WP_SECOND,
                            special, TXT_WEAPON_C2);
            return;
        case SPR_WCFM:         // Firestorm
            TryPickupWeapon(player, PCLASS_CLERIC, WP_THIRD,
                            special, TXT_WEAPON_C3);
            return;

            // Fourth Weapon Pieces
        case SPR_WFR1:
            TryPickupWeaponPiece(player, PCLASS_FIGHTER, WPIECE1, special);
            return;
        case SPR_WFR2:
            TryPickupWeaponPiece(player, PCLASS_FIGHTER, WPIECE2, special);
            return;
        case SPR_WFR3:
            TryPickupWeaponPiece(player, PCLASS_FIGHTER, WPIECE3, special);
            return;
        case SPR_WCH1:
            TryPickupWeaponPiece(player, PCLASS_CLERIC, WPIECE1, special);
            return;
        case SPR_WCH2:
            TryPickupWeaponPiece(player, PCLASS_CLERIC, WPIECE2, special);
            return;
        case SPR_WCH3:
            TryPickupWeaponPiece(player, PCLASS_CLERIC, WPIECE3, special);
            return;
        case SPR_WMS1:
            TryPickupWeaponPiece(player, PCLASS_MAGE, WPIECE1, special);
            return;
        case SPR_WMS2:
            TryPickupWeaponPiece(player, PCLASS_MAGE, WPIECE2, special);
            return;
        case SPR_WMS3:
            TryPickupWeaponPiece(player, PCLASS_MAGE, WPIECE3, special);
            return;

        default:
            I_Error("P_SpecialThing: Unknown gettable thing");
    }
    if (special->special)
    {
        P_ExecuteLineSpecial(special->special, special->args, NULL,
                             0, toucher);
        special->special = 0;
    }
    if (deathmatch && respawn && !(special->flags2 & MF2_DROPPED))
    {
        P_HideSpecialThing(special);
    }
    else
    {
        P_RemoveMobj(special);
    }
    player->bonuscount += BONUSADD;
    if (player == &players[consoleplayer])
    {
        S_StartSound(NULL, sound);
        SB_PaletteFlash(false);
    }
}

// Search thinker list for minotaur
mobj_t *ActiveMinotaur(player_t * master)
{
    mobj_t *mo;
    player_t *plr;
    thinker_t *think;
    unsigned int *starttime;

    for (think = thinkercap.next; think != &thinkercap; think = think->next)
    {
        if (think->function != P_MobjThinker)
            continue;
        mo = (mobj_t *) think;
        if (mo->type != MT_MINOTAUR)
            continue;
        if (mo->health <= 0)
            continue;
        if (!(mo->flags & MF_COUNTKILL))
            continue;           // for morphed minotaurs
        if (mo->flags & MF_CORPSE)
            continue;
        starttime = (unsigned int *) mo->args;
        if ((leveltime - *starttime) >= MAULATORTICS)
            continue;
        plr = mo->special1.m->player;
        if (plr == master)
            return (mo);
    }
    return (NULL);
}


//---------------------------------------------------------------------------
//
// PROC P_KillMobj
//
//---------------------------------------------------------------------------

void P_KillMobj(mobj_t * source, mobj_t * target)
{
    byte dummyArgs[3] = {0, 0, 0};
    mobj_t *master;

    target->flags &= ~(MF_SHOOTABLE | MF_FLOAT | MF_SKULLFLY | MF_NOGRAVITY);
    target->flags |= MF_CORPSE | MF_DROPOFF;
    target->flags2 &= ~MF2_PASSMOBJ;
    target->height >>= 2;
    if ((target->flags & MF_COUNTKILL || target->type == MT_ZBELL)
        && target->special)
    {                           // Initiate monster death actions
        if (target->type == MT_SORCBOSS)
        {
            P_StartACS(target->special, 0, dummyArgs, target, NULL, 0);
        }
        else
        {
            P_ExecuteLineSpecial(target->special, target->args,
                                 NULL, 0, target);
        }
    }
    if (source && source->player)
    {                           // Check for frag changes
        if (target->player)
        {
            if (target == source)
            {                   // Self-frag
                target->player->frags[target->player - players]--;
                if (cmdfrag && netgame
                    && source->player == &players[consoleplayer])
                {               // Send out a frag count packet
                    NET_SendFrags(source->player);
                }
            }
            else
            {
                source->player->frags[target->player - players]++;
                if (cmdfrag && netgame
                    && source->player == &players[consoleplayer])
                {               // Send out a frag count packet
                    NET_SendFrags(source->player);
                }
            }
        }
    }
    if (target->player)
    {                           // Player death
        if (!source)
        {                       // Self-frag
            target->player->frags[target->player - players]--;
            if (cmdfrag && netgame
                && target->player == &players[consoleplayer])
            {                   // Send out a frag count packet
                NET_SendFrags(target->player);
            }
        }
        target->flags &= ~MF_SOLID;
        target->flags2 &= ~MF2_FLY;
        target->player->powers[pw_flight] = 0;
        target->player->playerstate = PST_DEAD;
        P_DropWeapon(target->player);
        if (target->flags2 & MF2_FIREDAMAGE)
        {                       // Player flame death
            switch (target->player->class)
            {
                case PCLASS_FIGHTER:
                    S_StartSound(target, SFX_PLAYER_FIGHTER_BURN_DEATH);
                    P_SetMobjState(target, S_PLAY_F_FDTH1);
                    return;
                case PCLASS_CLERIC:
                    S_StartSound(target, SFX_PLAYER_CLERIC_BURN_DEATH);
                    P_SetMobjState(target, S_PLAY_C_FDTH1);
                    return;
                case PCLASS_MAGE:
                    S_StartSound(target, SFX_PLAYER_MAGE_BURN_DEATH);
                    P_SetMobjState(target, S_PLAY_M_FDTH1);
                    return;
                default:
                    break;
            }
        }
        if (target->flags2 & MF2_ICEDAMAGE)
        {                       // Player ice death
            target->flags &= ~(7 << MF_TRANSSHIFT);     //no translation
            target->flags |= MF_ICECORPSE;
            switch (target->player->class)
            {
                case PCLASS_FIGHTER:
                    P_SetMobjState(target, S_FPLAY_ICE);
                    return;
                case PCLASS_CLERIC:
                    P_SetMobjState(target, S_CPLAY_ICE);
                    return;
                case PCLASS_MAGE:
                    P_SetMobjState(target, S_MPLAY_ICE);
                    return;
                case PCLASS_PIG:
                    P_SetMobjState(target, S_PIG_ICE);
                    return;
                default:
                    break;
            }
        }
    }
    if (target->flags2 & MF2_FIREDAMAGE)
    {
        if (target->type == MT_FIGHTER_BOSS
            || target->type == MT_CLERIC_BOSS || target->type == MT_MAGE_BOSS)
        {
            switch (target->type)
            {
                case MT_FIGHTER_BOSS:
                    S_StartSound(target, SFX_PLAYER_FIGHTER_BURN_DEATH);
                    P_SetMobjState(target, S_PLAY_F_FDTH1);
                    return;
                case MT_CLERIC_BOSS:
                    S_StartSound(target, SFX_PLAYER_CLERIC_BURN_DEATH);
                    P_SetMobjState(target, S_PLAY_C_FDTH1);
                    return;
                case MT_MAGE_BOSS:
                    S_StartSound(target, SFX_PLAYER_MAGE_BURN_DEATH);
                    P_SetMobjState(target, S_PLAY_M_FDTH1);
                    return;
                default:
                    break;
            }
        }
        else if (target->type == MT_TREEDESTRUCTIBLE)
        {
            P_SetMobjState(target, S_ZTREEDES_X1);
            target->height = 24 * FRACUNIT;
            S_StartSound(target, SFX_TREE_EXPLODE);
            return;
        }
    }
    if (target->flags2 & MF2_ICEDAMAGE)
    {
        target->flags |= MF_ICECORPSE;
        switch (target->type)
        {
            case MT_BISHOP:
                P_SetMobjState(target, S_BISHOP_ICE);
                return;
            case MT_CENTAUR:
            case MT_CENTAURLEADER:
                P_SetMobjState(target, S_CENTAUR_ICE);
                return;
            case MT_DEMON:
            case MT_DEMON2:
                P_SetMobjState(target, S_DEMON_ICE);
                return;
            case MT_SERPENT:
            case MT_SERPENTLEADER:
                P_SetMobjState(target, S_SERPENT_ICE);
                return;
            case MT_WRAITH:
            case MT_WRAITHB:
                P_SetMobjState(target, S_WRAITH_ICE);
                return;
            case MT_ETTIN:
                P_SetMobjState(target, S_ETTIN_ICE1);
                return;
            case MT_FIREDEMON:
                P_SetMobjState(target, S_FIRED_ICE1);
                return;
            case MT_FIGHTER_BOSS:
                P_SetMobjState(target, S_FIGHTER_ICE);
                return;
            case MT_CLERIC_BOSS:
                P_SetMobjState(target, S_CLERIC_ICE);
                return;
            case MT_MAGE_BOSS:
                P_SetMobjState(target, S_MAGE_ICE);
                return;
            case MT_PIG:
                P_SetMobjState(target, S_PIG_ICE);
                return;
            default:
                target->flags &= ~MF_ICECORPSE;
                break;
        }
    }

    if (target->type == MT_MINOTAUR)
    {
        master = target->special1.m;
        if (master->health > 0)
        {
            if (!ActiveMinotaur(master->player))
            {
                master->player->powers[pw_minotaur] = 0;
            }
        }
    }
    else if (target->type == MT_TREEDESTRUCTIBLE)
    {
        target->height = 24 * FRACUNIT;
    }
    if (target->health < -(target->info->spawnhealth >> 1)
        && target->info->xdeathstate)
    {                           // Extreme death
        P_SetMobjState(target, target->info->xdeathstate);
    }
    else
    {                           // Normal death
        if ((target->type == MT_FIREDEMON) &&
            (target->z <= target->floorz + 2 * FRACUNIT) &&
            (target->info->xdeathstate))
        {
            // This is to fix the imps' staying in fall state
            P_SetMobjState(target, target->info->xdeathstate);
        }
        else
        {
            P_SetMobjState(target, target->info->deathstate);
        }
    }
    target->tics -= P_Random() & 3;
//      I_StartSound(&actor->r, actor->info->deathsound);
}

//---------------------------------------------------------------------------
//
// FUNC P_MinotaurSlam
//
//---------------------------------------------------------------------------

void P_MinotaurSlam(mobj_t * source, mobj_t * target)
{
    angle_t angle;
    fixed_t thrust;

    angle = R_PointToAngle2(source->x, source->y, target->x, target->y);
    angle >>= ANGLETOFINESHIFT;
    thrust = 16 * FRACUNIT + (P_Random() << 10);
    target->momx += FixedMul(thrust, finecosine[angle]);
    target->momy += FixedMul(thrust, finesine[angle]);
    P_DamageMobj(target, NULL, source, HITDICE(4));
    if (target->player)
    {
        target->reactiontime = 14 + (P_Random() & 7);
    }
    source->args[0] = 0;        // Stop charging
}


//---------------------------------------------------------------------------
//
// FUNC P_MorphPlayer
//
// Returns true if the player gets turned into a pig
//
//---------------------------------------------------------------------------

boolean P_MorphPlayer(player_t * player)
{
    mobj_t *pmo;
    mobj_t *fog;
    mobj_t *beastMo;
    fixed_t x;
    fixed_t y;
    fixed_t z;
    angle_t angle;
    int oldFlags2;

    if (player->powers[pw_invulnerability])
    {                           // Immune when invulnerable
        return (false);
    }
    if (player->morphTics)
    {                           // Player is already a beast
        return false;
    }
    pmo = player->mo;
    x = pmo->x;
    y = pmo->y;
    z = pmo->z;
    angle = pmo->angle;
    oldFlags2 = pmo->flags2;
    P_SetMobjState(pmo, S_FREETARGMOBJ);
    fog = P_SpawnMobj(x, y, z + TELEFOGHEIGHT, MT_TFOG);
    S_StartSound(fog, SFX_TELEPORT);
    beastMo = P_SpawnMobj(x, y, z, MT_PIGPLAYER);
    beastMo->special1.i = player->readyweapon;
    beastMo->angle = angle;
    beastMo->player = player;
    player->health = beastMo->health = MAXMORPHHEALTH;
    player->mo = beastMo;
    memset(&player->armorpoints[0], 0, NUMARMOR * sizeof(int));
    player->class = PCLASS_PIG;
    if (oldFlags2 & MF2_FLY)
    {
        beastMo->flags2 |= MF2_FLY;
    }
    player->morphTics = MORPHTICS;
    P_ActivateMorphWeapon(player);
    return (true);
}

//---------------------------------------------------------------------------
//
// FUNC P_MorphMonster
//
//---------------------------------------------------------------------------

boolean P_MorphMonster(mobj_t * actor)
{
    mobj_t *master, *monster, *fog;
    mobjtype_t moType;
    fixed_t x;
    fixed_t y;
    fixed_t z;
    mobj_t oldMonster;

    if (actor->player)
        return (false);
    if (!(actor->flags & MF_COUNTKILL))
        return false;
    if (actor->flags2 & MF2_BOSS)
        return false;
    moType = actor->type;
    switch (moType)
    {
        case MT_PIG:
            return (false);
        case MT_FIGHTER_BOSS:
        case MT_CLERIC_BOSS:
        case MT_MAGE_BOSS:
            return (false);
        default:
            break;
    }

    oldMonster = *actor;
    x = oldMonster.x;
    y = oldMonster.y;
    z = oldMonster.z;
    P_RemoveMobjFromTIDList(actor);
    P_SetMobjState(actor, S_FREETARGMOBJ);
    fog = P_SpawnMobj(x, y, z + TELEFOGHEIGHT, MT_TFOG);
    S_StartSound(fog, SFX_TELEPORT);
    monster = P_SpawnMobj(x, y, z, MT_PIG);
    monster->special2.i = moType;
    monster->special1.i = MORPHTICS + P_Random();
    monster->flags |= (oldMonster.flags & MF_SHADOW);
    monster->target = oldMonster.target;
    monster->angle = oldMonster.angle;
    monster->tid = oldMonster.tid;
    monster->special = oldMonster.special;
    P_InsertMobjIntoTIDList(monster, oldMonster.tid);
    memcpy(monster->args, oldMonster.args, 5);

    // check for turning off minotaur power for active icon
    if (moType == MT_MINOTAUR)
    {
        master = oldMonster.special1.m;
        if (master->health > 0)
        {
            if (!ActiveMinotaur(master->player))
            {
                master->player->powers[pw_minotaur] = 0;
            }
        }
    }
    return (true);
}

//---------------------------------------------------------------------------
//
// PROC P_AutoUseHealth
//
//---------------------------------------------------------------------------

void P_AutoUseHealth(player_t * player, int saveHealth)
{
    int i;
    int count;
    int normalCount;
    int normalSlot = 0;
    int superCount;
    int superSlot = 0;

    normalCount = superCount = 0;
    for (i = 0; i < player->inventorySlotNum; i++)
    {
        if (player->inventory[i].type == arti_health)
        {
            normalSlot = i;
            normalCount = player->inventory[i].count;
        }
        else if (player->inventory[i].type == arti_superhealth)
        {
            superSlot = i;
            superCount = player->inventory[i].count;
        }
    }
    if ((gameskill == sk_baby) && (normalCount * 25 >= saveHealth))
    {                           // Use quartz flasks
        count = (saveHealth + 24) / 25;
        for (i = 0; i < count; i++)
        {
            player->health += 25;
            P_PlayerRemoveArtifact(player, normalSlot);
        }
    }
    else if (superCount * 100 >= saveHealth)
    {                           // Use mystic urns
        count = (saveHealth + 99) / 100;
        for (i = 0; i < count; i++)
        {
            player->health += 100;
            P_PlayerRemoveArtifact(player, superSlot);
        }
    }
    else if ((gameskill == sk_baby)
             && (superCount * 100 + normalCount * 25 >= saveHealth))
    {                           // Use mystic urns and quartz flasks
        count = (saveHealth + 24) / 25;
        saveHealth -= count * 25;
        for (i = 0; i < count; i++)
        {
            player->health += 25;
            P_PlayerRemoveArtifact(player, normalSlot);
        }
        count = (saveHealth + 99) / 100;
        for (i = 0; i < count; i++)
        {
            player->health += 100;
            P_PlayerRemoveArtifact(player, normalSlot);
        }
    }
    player->mo->health = player->health;
}

/*
=================
=
= 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 barrel explosions and other environmental stuff
==================
*/

void P_DamageMobj
    (mobj_t * target, mobj_t * inflictor, mobj_t * source, int damage)
{
    unsigned ang;
    int saved;
    fixed_t savedPercent;
    player_t *player;
    mobj_t *master;
    fixed_t thrust;
    int temp;
    int i;

    if (!(target->flags & MF_SHOOTABLE))
    {
        // Shouldn't happen
        return;
    }
    if (target->health <= 0)
    {
        if (inflictor && inflictor->flags2 & MF2_ICEDAMAGE)
        {
            return;
        }
        else if (target->flags & MF_ICECORPSE)  // frozen
        {
            target->tics = 1;
            target->momx = target->momy = 0;
        }
        return;
    }
    if ((target->flags2 & MF2_INVULNERABLE) && damage < 10000)
    {                           // mobj is invulnerable
        if (target->player)
            return;             // for player, no exceptions
        if (inflictor)
        {
            switch (inflictor->type)
            {
                    // These inflictors aren't foiled by invulnerability
                case MT_HOLY_FX:
                case MT_POISONCLOUD:
                case MT_FIREBOMB:
                    break;
                default:
                    return;
            }
        }
        else
        {
            return;
        }
    }
    if (target->player)
    {
        if (damage < 1000 && ((target->player->cheats & CF_GODMODE)
                              || target->player->powers[pw_invulnerability]))
        {
            return;
        }
    }
    if (target->flags & MF_SKULLFLY)
    {
        target->momx = target->momy = target->momz = 0;
    }
    if (target->flags2 & MF2_DORMANT)
    {
        // Invulnerable, and won't wake up
        return;
    }
    player = target->player;
    if (player && gameskill == sk_baby)
    {
        // Take half damage in trainer mode
        damage >>= 1;
    }
    // Special damage types
    if (inflictor)
    {
        switch (inflictor->type)
        {
            case MT_EGGFX:
                if (player)
                {
                    P_MorphPlayer(player);
                }
                else
                {
                    P_MorphMonster(target);
                }
                return;         // Always return
            case MT_TELOTHER_FX1:
            case MT_TELOTHER_FX2:
            case MT_TELOTHER_FX3:
            case MT_TELOTHER_FX4:
            case MT_TELOTHER_FX5:
                if ((target->flags & MF_COUNTKILL) &&
                    (target->type != MT_SERPENT) &&
                    (target->type != MT_SERPENTLEADER) &&
                    (!(target->flags2 & MF2_BOSS)))
                {
                    P_TeleportOther(target);
                }
                return;
            case MT_MINOTAUR:
                if (inflictor->flags & MF_SKULLFLY)
                {               // Slam only when in charge mode
                    P_MinotaurSlam(inflictor, target);
                    return;
                }
                break;
            case MT_BISH_FX:
                // Bishops are just too nasty
                damage >>= 1;
                break;
            case MT_SHARDFX1:
                switch (inflictor->special2.i)
                {
                    case 3:
                        damage <<= 3;
                        break;
                    case 2:
                        damage <<= 2;
                        break;
                    case 1:
                        damage <<= 1;
                        break;
                    default:
                        break;
                }
                break;
            case MT_CSTAFF_MISSILE:
                // Cleric Serpent Staff does poison damage
                if (target->player)
                {
                    P_PoisonPlayer(target->player, source, 20);
                    damage >>= 1;
                }
                break;
            case MT_ICEGUY_FX2:
                damage >>= 1;
                break;
            case MT_POISONDART:
                if (target->player)
                {
                    P_PoisonPlayer(target->player, source, 20);
                    damage >>= 1;
                }
                break;
            case MT_POISONCLOUD:
                if (target->player)
                {
                    if (target->player->poisoncount < 4)
                    {
                        P_PoisonDamage(target->player, source, 15 + (P_Random() & 15), false);  // Don't play painsound
                        P_PoisonPlayer(target->player, source, 50);
                        S_StartSound(target, SFX_PLAYER_POISONCOUGH);
                    }
                    return;
                }
                else if (!(target->flags & MF_COUNTKILL))
                {               // only damage monsters/players with the poison cloud
                    return;
                }
                break;
            case MT_FSWORD_MISSILE:
                if (target->player)
                {
                    damage -= damage >> 2;
                }
                break;
            default:
                break;
        }
    }
    // Push the target unless source is using the gauntlets
    if (inflictor && (!source || !source->player)
        && !(inflictor->flags2 & MF2_NODMGTHRUST))
    {
        ang = R_PointToAngle2(inflictor->x, inflictor->y,
                              target->x, target->y);
        //thrust = damage*(FRACUNIT>>3)*100/target->info->mass;
        thrust = damage * (FRACUNIT >> 3) * 150 / 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)
    {
        savedPercent = AutoArmorSave[player->class]
            + player->armorpoints[ARMOR_ARMOR] +
            player->armorpoints[ARMOR_SHIELD] +
            player->armorpoints[ARMOR_HELMET] +
            player->armorpoints[ARMOR_AMULET];
        if (savedPercent)
        {                       // armor absorbed some damage
            if (savedPercent > 100 * FRACUNIT)
            {
                savedPercent = 100 * FRACUNIT;
            }
            for (i = 0; i < NUMARMOR; i++)
            {
                if (player->armorpoints[i])
                {
                    player->armorpoints[i] -=
                        FixedDiv(FixedMul(damage << FRACBITS,
                                          ArmorIncrement[player->class][i]),
                                 300 * FRACUNIT);
                    if (player->armorpoints[i] < 2 * FRACUNIT)
                    {
                        player->armorpoints[i] = 0;
                    }
                }
            }
            saved = FixedDiv(FixedMul(damage << FRACBITS, savedPercent),
                             100 * FRACUNIT);
            if (saved > savedPercent * 2)
            {
                saved = savedPercent * 2;
            }
            damage -= saved >> FRACBITS;
        }
        if (damage >= player->health
            && ((gameskill == sk_baby) || deathmatch) && !player->morphTics)
        {                       // Try to use some inventory health
            P_AutoUseHealth(player, damage - player->health + 1);
        }
        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);
            SB_PaletteFlash(false);
        }
    }

    //
    // do the damage
    //
    target->health -= damage;
    if (target->health <= 0)
    {                           // Death
        if (inflictor)
        {                       // check for special fire damage or ice damage deaths
            if (inflictor->flags2 & MF2_FIREDAMAGE)
            {
                if (player && !player->morphTics)
                {               // Check for flame death
                    if (target->health > -50 && damage > 25)
                    {
                        target->flags2 |= MF2_FIREDAMAGE;
                    }
                }
                else
                {
                    target->flags2 |= MF2_FIREDAMAGE;
                }
            }
            else if (inflictor->flags2 & MF2_ICEDAMAGE)
            {
                target->flags2 |= MF2_ICEDAMAGE;
            }
        }
        if (source && (source->type == MT_MINOTAUR))
        {                       // Minotaur's kills go to his master
            master = source->special1.m;
            // Make sure still alive and not a pointer to fighter head
            if (master->player && (master->player->mo == master))
            {
                source = master;
            }
        }
        if (source && (source->player) &&
            (source->player->readyweapon == WP_FOURTH))
        {
            // Always extreme death from fourth weapon
            target->health = -5000;
        }
        P_KillMobj(source, target);
        return;
    }
    if ((P_Random() < target->info->painchance)
        && !(target->flags & MF_SKULLFLY))
    {
        if (inflictor && (inflictor->type >= MT_LIGHTNING_FLOOR
                          && inflictor->type <= MT_LIGHTNING_ZAP))
        {
            if (P_Random() < 96)
            {
                target->flags |= MF_JUSTHIT;    // fight back!
                P_SetMobjState(target, target->info->painstate);
            }
            else
            {                   // "electrocute" the target
                target->frame |= FF_FULLBRIGHT;
                if (target->flags & MF_COUNTKILL && P_Random() < 128
                    && !S_GetSoundPlayingInfo(target, SFX_PUPPYBEAT))
                {
                    if ((target->type == MT_CENTAUR) ||
                        (target->type == MT_CENTAURLEADER) ||
                        (target->type == MT_ETTIN))
                    {
                        S_StartSound(target, SFX_PUPPYBEAT);
                    }
                }
            }
        }
        else
        {
            target->flags |= MF_JUSTHIT;        // fight back!
            P_SetMobjState(target, target->info->painstate);
            if (inflictor && inflictor->type == MT_POISONCLOUD)
            {
                if (target->flags & MF_COUNTKILL && P_Random() < 128
                    && !S_GetSoundPlayingInfo(target, SFX_PUPPYBEAT))
                {
                    if ((target->type == MT_CENTAUR) ||
                        (target->type == MT_CENTAURLEADER) ||
                        (target->type == MT_ETTIN))
                    {
                        S_StartSound(target, SFX_PUPPYBEAT);
                    }
                }
            }
        }
    }
    target->reactiontime = 0;   // we're awake now...
    if (!target->threshold && source && !(source->flags2 & MF2_BOSS)
        && !(target->type == MT_BISHOP) && !(target->type == MT_MINOTAUR))
    {
        // Target actor is not intent on another actor,
        // so make him chase after source
        if ((target->type == MT_CENTAUR && source->type == MT_CENTAURLEADER)
            || (target->type == MT_CENTAURLEADER
                && source->type == MT_CENTAUR))
        {
            return;
        }
        target->target = source;
        target->threshold = BASETHRESHOLD;
        if (target->state == &states[target->info->spawnstate]
            && target->info->seestate != S_NULL)
        {
            P_SetMobjState(target, target->info->seestate);
        }
    }
}

//==========================================================================
//
// P_FallingDamage
//
//==========================================================================

void P_FallingDamage(player_t * player)
{
    int damage;
    int mom;
    int dist;

    mom = abs(player->mo->momz);
    dist = FixedMul(mom, 16 * FRACUNIT / 23);

    if (mom >= 63 * FRACUNIT)
    {                           // automatic death
        P_DamageMobj(player->mo, NULL, NULL, 10000);
        return;
    }
    damage = ((FixedMul(dist, dist) / 10) >> FRACBITS) - 24;
    if (player->mo->momz > -39 * FRACUNIT && damage > player->mo->health
        && player->mo->health != 1)
    {                           // No-death threshold
        damage = player->mo->health - 1;
    }
    S_StartSound(player->mo, SFX_PLAYER_LAND);
    P_DamageMobj(player->mo, NULL, NULL, damage);
}

//==========================================================================
//
// P_PoisonPlayer - Sets up all data concerning poisoning
//
//==========================================================================

void P_PoisonPlayer(player_t * player, mobj_t * poisoner, int poison)
{
    if ((player->cheats & CF_GODMODE) || player->powers[pw_invulnerability])
    {
        return;
    }
    player->poisoncount += poison;
    player->poisoner = poisoner;
    if (player->poisoncount > 100)
    {
        player->poisoncount = 100;
    }
}

//==========================================================================
//
// P_PoisonDamage - Similar to P_DamageMobj
//
//==========================================================================

void P_PoisonDamage(player_t * player, mobj_t * source, int damage,
                    boolean playPainSound)
{
    mobj_t *target;
    mobj_t *inflictor;

    target = player->mo;
    inflictor = source;
    if (target->health <= 0)
    {
        return;
    }
    if (target->flags2 & MF2_INVULNERABLE && damage < 10000)
    {                           // mobj is invulnerable
        return;
    }
    if (gameskill == sk_baby)
    {
        // Take half damage in trainer mode
        damage >>= 1;
    }
    if (damage < 1000 && ((player->cheats & CF_GODMODE)
                          || player->powers[pw_invulnerability]))
    {
        return;
    }
    if (damage >= player->health
        && ((gameskill == sk_baby) || deathmatch) && !player->morphTics)
    {                           // Try to use some inventory health
        P_AutoUseHealth(player, damage - player->health + 1);
    }
    player->health -= damage;   // mirror mobj health here for Dave
    if (player->health < 0)
    {
        player->health = 0;
    }
    player->attacker = source;

    //
    // do the damage
    //
    target->health -= damage;
    if (target->health <= 0)
    {                           // Death
        target->special1.i = damage;
        if (inflictor && !player->morphTics)
        {                       // Check for flame death
            if ((inflictor->flags2 & MF2_FIREDAMAGE)
                && (target->health > -50) && (damage > 25))
            {
                target->flags2 |= MF2_FIREDAMAGE;
            }
            if (inflictor->flags2 & MF2_ICEDAMAGE)
            {
                target->flags2 |= MF2_ICEDAMAGE;
            }
        }
        P_KillMobj(source, target);
        return;
    }
    if (!(leveltime & 63) && playPainSound)
    {
        P_SetMobjState(target, target->info->painstate);
    }
/*
	if((P_Random() < target->info->painchance)
		&& !(target->flags&MF_SKULLFLY))
	{
		target->flags |= MF_JUSTHIT; // fight back!
		P_SetMobjState(target, target->info->painstate);
	}
*/
}