// 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).
//
//-----------------------------------------------------------------------------




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

#include "doomstat.h"

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

#include "am_map.h"

#include "c_console.h"
#include "c_dispatch.h"

#include "p_local.h"

#include "s_sound.h"

#include "p_inter.h"
#include "p_lnspec.h"

#include "sv_main.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};

CVAR (sv_nobfg, "0", CVAR_ARCHIVE)

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

BOOL P_GiveAmmo (player_t *player, ammotype_t ammo, int num)
{
	int oldammo;
	int playernum;
		
	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.value == sk_baby
		|| gameskill.value == 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];

	for (playernum=0; playernum<MAXPLAYERS; playernum++)
		if (player == &players[playernum])
			break;
	
	client_t *cl = &clients[playernum];

	MSG_WriteByte (&cl->reliablebuf, svc_giveammo);
	MSG_WriteByte (&cl->reliablebuf, (byte)ammo);
	MSG_WriteShort (&cl->reliablebuf, num);

	// 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;
	}
		
	MSG_WriteByte (&clients[playernum].reliablebuf, svc_changeweapon);
	MSG_WriteByte (&clients[playernum].reliablebuf, (byte)player->pendingweapon);
		
	return true;
}


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

	// [RH] Don't get the weapon if no graphics for it
	state_t *state = states + weaponinfo[weapon].readystate;
	if ((state->frame & FF_FRAMEMASK) >= sprites[state->sprite].numframes)
		return false;

	
	for (playernum=0; playernum<MAXPLAYERS; playernum++)
		if (player == &players[playernum])
			break;

	if (multiplayer &&
		((!deathmatch.value && !alwaysapplydmflags.value) || dmflags & DF_WEAPONS_STAY) &&
		!dropped)
	{
		// leave placed weapons forever on net games
		if (player->weaponowned[weapon])
			return false;

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

		if (deathmatch.value)
			P_GiveAmmo (player, weaponinfo[weapon].ammo, 5);
		else
			P_GiveAmmo (player, weaponinfo[weapon].ammo, 2);
		
		if (!player->userinfo.neverswitch)
		{
			player->pendingweapon = weapon;
		
			MSG_WriteByte (&clients[playernum].reliablebuf, svc_changeweapon);
			MSG_WriteByte (&clients[playernum].reliablebuf, (byte)player->pendingweapon);
		}

//		S_Sound (player->mo, CHAN_ITEM, "misc/w_pkup", 1, ATTN_NORM);
		SV_Sound (player->mo, CHAN_ITEM, "misc/w_pkup", ATTN_NORM);

		for (int i=0; i<MAXPLAYERS; i++)
		{
			client_t *cl = &clients[i];

			if (cl->cls < cs_spawned)
				continue;

			MSG_WriteByte (&cl->reliablebuf, svc_touchspecial);
			MSG_WriteByte (&cl->reliablebuf, playernum);
			switch (weapon)
			{
			case wp_bfg:
				MSG_WriteByte (&cl->reliablebuf, SPR_BFUG);
				break;
			case wp_chaingun:
				MSG_WriteByte (&cl->reliablebuf, SPR_MGUN);
				break;
			case wp_chainsaw:
				MSG_WriteByte (&cl->reliablebuf, SPR_CSAW);
				break;
			case wp_missile:
				MSG_WriteByte (&cl->reliablebuf, SPR_LAUN);
				break;
			case wp_plasma:
				MSG_WriteByte (&cl->reliablebuf, SPR_PLAS);
				break;
			case wp_shotgun:
				MSG_WriteByte (&cl->reliablebuf, SPR_SHOT);
				break;
			case wp_supershotgun:
				MSG_WriteByte (&cl->reliablebuf, SPR_SGN2);
				break;				
			}
		}

		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;
		if (!player->userinfo.neverswitch)
		{
			player->pendingweapon = weapon;
			MSG_WriteByte (&clients[playernum].reliablebuf, svc_changeweapon);
			MSG_WriteByte (&clients[playernum].reliablebuf, (byte)player->pendingweapon);
		}
	}
		
	return (gaveweapon || gaveammo);
}

 

//
// P_GiveBody
// Returns false if the body isn't needed at all
//
BOOL 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.
//
BOOL 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
//
BOOL P_GiveCard (player_t *player, card_t card)
{
	if (player->cards[card])
		return false;
	
	player->bonuscount = BONUSADD;
	player->cards[card] = 1;
	return true;
}


//
// P_GivePower
//
BOOL 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 (AActor *special, AActor *toucher)
{
	player_t*	player;
	int         playernum;
	int 		i;
	fixed_t 	delta;
	int 		sound;
				
	delta = special->z - toucher->z;

	if (delta > toucher->height || delta < -8*FRACUNIT)
	{
		// out of reach
		return;
	}
		
	sound = 0; 
	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.GreenAC))
			return;
		break;
				
	  case SPR_ARM2:
		if (!P_GiveArmor (player, deh.BlueAC))
			return;
		break;
		
		// bonus items
	  case SPR_BON1:
		player->health++;				// can go over 100%
		if (player->health > deh.MaxSoulsphere)
			player->health = deh.MaxSoulsphere;
		player->mo->health = player->health;
		break;
		
	  case SPR_BON2:
		player->armorpoints++;			// can go over 100%
		if (player->armorpoints > deh.MaxArmor)
			player->armorpoints = deh.MaxArmor;
		if (!player->armortype)
			player->armortype = deh.GreenAC;
		break;
		
	  case SPR_SOUL:
		player->health += deh.SoulsphereHealth;
		if (player->health > deh.MaxSoulsphere)
			player->health = deh.MaxSoulsphere;
		player->mo->health = player->health;
		sound = 1;
		break;
		
	  case SPR_MEGA:
		player->health = deh.MegasphereHealth;
		player->mo->health = player->health;
		P_GiveArmor (player,deh.BlueAC);
		sound = 1;
		break;
		
		// cards
		// leave cards for everyone
	  case SPR_BKEY:
		if (!P_GiveCard (player, it_bluecard) )
			return;
		sound = 3;
		break;
		
	  case SPR_YKEY:
		if (!P_GiveCard (player, it_yellowcard) )
			return;
		sound = 3;
		break;
		
	  case SPR_RKEY:
		if (!P_GiveCard (player, it_redcard) )
			return;
		sound = 3;
		break;
		
	  case SPR_BSKU:
		if (!P_GiveCard (player, it_blueskull) )
			return;
		sound = 3;
		break;
		
	  case SPR_YSKU:
		if (!P_GiveCard (player, it_yellowskull) )
			return;
		sound = 3;
		break;
		
	  case SPR_RSKU:
		if (!P_GiveCard (player, it_redskull) )
			return;
		sound = 3;
		break;
		
		// medikits, heals
	  case SPR_STIM:
		if (!P_GiveBody (player, 10))
			return;
		break;
		
	  case SPR_MEDI:
		if (!P_GiveBody (player, 25))
			return;
		break;

		
		// power ups
	  case SPR_PINV:
		if (!P_GivePower (player, pw_invulnerability))
			return;
		sound = 1;
		break;
		
	  case SPR_PSTR:
		if (!P_GivePower (player, pw_strength))
			return;
		if (player->readyweapon != wp_fist)
		{
			player->pendingweapon = wp_fist;
			int playernum;
			for (playernum=0; playernum<MAXPLAYERS; playernum++)
				if (player == &players[playernum])
					break;
			
			MSG_WriteByte (&clients[playernum].reliablebuf, svc_changeweapon);
			MSG_WriteByte (&clients[playernum].reliablebuf, (byte)player->pendingweapon);
		}
		sound = 1;
		break;
		
	  case SPR_PINS:
		if (!P_GivePower (player, pw_invisibility))
			return;
		sound = 1;
		break;
		
	  case SPR_SUIT:
		if (!P_GivePower (player, pw_ironfeet))
			return;
		sound = 1;
		break;
		
	  case SPR_PMAP:
		if (!P_GivePower (player, pw_allmap))
			return;
		sound = 1;
		break;
		
	  case SPR_PVIS:
		if (!P_GivePower (player, pw_infrared))
			return;
		sound = 1;
		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;
		}
		break;
		
	  case SPR_AMMO:
		if (!P_GiveAmmo (player, am_clip,5))
			return;
		break;
		
	  case SPR_ROCK:
		if (!P_GiveAmmo (player, am_misl,1))
			return;
		break;
		
	  case SPR_BROK:
		if (!P_GiveAmmo (player, am_misl,5))
			return;
		break;
		
	  case SPR_CELL:
		if (!P_GiveAmmo (player, am_cell,1))
			return;
		break;
		
	  case SPR_CELP:
		if (!P_GiveAmmo (player, am_cell,5))
			return;
		break;
		
	  case SPR_SHEL:
		if (!P_GiveAmmo (player, am_shell,1))
			return;
		break;
		
	  case SPR_SBOX:
		if (!P_GiveAmmo (player, am_shell,5))
			return;
		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, (ammotype_t)i, 1);
		break;
		
		// weapons
	  case SPR_BFUG:
		if (!sv_nobfg.value)
		{
			if (!P_GiveWeapon (player, wp_bfg, special->flags & MF_DROPPED))
				return;
			sound = 2;		
		}
		break;
		
	  case SPR_MGUN:
		if (!P_GiveWeapon (player, wp_chaingun, special->flags & MF_DROPPED))
			return;
		sound = 2;		
		break;
		
	  case SPR_CSAW:
		if (!P_GiveWeapon (player, wp_chainsaw, special->flags & MF_DROPPED))
			return;
		sound = 2;		
		break;
		
	  case SPR_LAUN:
		if (!P_GiveWeapon (player, wp_missile, special->flags & MF_DROPPED))
			return;
		sound = 2;		
		break;
		
	  case SPR_PLAS:
		if (!P_GiveWeapon (player, wp_plasma, special->flags & MF_DROPPED))
			return;
		sound = 2;		
		break;
		
	  case SPR_SHOT:
		if (!P_GiveWeapon (player, wp_shotgun, special->flags & MF_DROPPED))
			return;
		sound = 2;		
		break;
				
	  case SPR_SGN2:
		if (!P_GiveWeapon (player, wp_supershotgun, special->flags & MF_DROPPED))
			return;
		sound = 2;		
		break;
				
	  default:
		I_Error ("P_SpecialThing: Unknown gettable thing");
	}

	for (playernum=0; playernum<MAXPLAYERS; playernum++)
		if (player == &players[playernum])
			break;

    for (i=0; i<MAXPLAYERS; i++)
	{
		client_t *cl = &clients[i];

		if (cl->cls < cs_spawned)
			continue;

		MSG_WriteByte (&cl->reliablebuf, svc_touchspecial);
		MSG_WriteByte (&cl->reliablebuf, playernum);
		MSG_WriteByte (&cl->reliablebuf, special->sprite);
	}

	if ( multiplayer && (special->sprite == SPR_BKEY 
		|| special->sprite == SPR_RKEY || special->sprite == SPR_YKEY
		|| special->sprite == SPR_BSKU || special->sprite == SPR_RSKU
		|| special->sprite == SPR_YSKU) )
		return;


	// [RH] Execute an attached special (if any)
	if (special->special)
	{
		LineSpecials[special->special] (NULL, toucher, special->args[0],
			special->args[1], special->args[2], special->args[3], special->args[4]);
		special->special = 0;
	}

	if (special->flags & MF_COUNTITEM)
	{
		player->itemcount++;
		level.found_items++;
	}
	special->Destroy ();
	player->bonuscount = BONUSADD;

	{
		AActor   *ent;

		ent = player->mo;

		switch (sound) 
		{
			case 0:
			case 3:
//				S_Sound (ent, CHAN_ITEM, "misc/i_pkup", 1, ATTN_NORM);
				SV_Sound (ent, CHAN_ITEM, "misc/i_pkup", ATTN_NORM);
				break;
			case 1:
//				S_Sound (ent, CHAN_ITEM, "misc/p_pkup", 1,
//					!ent ? ATTN_SURROUND : ATTN_NORM);
				SV_Sound (ent, CHAN_ITEM, "misc/p_pkup", !ent ? ATTN_SURROUND : ATTN_NORM);
				break;
			case 2:
//				S_Sound (ent, CHAN_ITEM, "misc/w_pkup", 1, ATTN_NORM);
				SV_Sound (ent, CHAN_ITEM, "misc/w_pkup", ATTN_NORM);

				break;
		}
	}
}


// [RH]
// SexMessage: Replace parts of strings with gender-specific pronouns
//
// The following expansions are performed:
//		%g -> he/she/it
//		%h -> him/her/it
//		%p -> his/her/its
//
void SexMessage (const char *from, char *to, int gender)
{
	static const char *genderstuff[3][3] = {
		{ "he",  "him", "his" },
		{ "she", "her", "her" },
		{ "it",  "it",  "its" }
	};
	static const int gendershift[3][3] = {
		{ 2, 3, 3 },
		{ 3, 3, 3 },
		{ 2, 2, 3 }
	};
	int gendermsg;

	do {
		if (*from != '%') {
			*to++ = *from;
		} else {
			switch (from[1]) {
				case 'g':	gendermsg = 0;	break;
				case 'h':	gendermsg = 1;	break;
				case 'p':	gendermsg = 2;	break;
				default:	gendermsg = -1;	break;
			}
			if (gendermsg < 0) {
				*to++ = '%';
			} else {
				strcpy (to, genderstuff[gender][gendermsg]);
				to += gendershift[gender][gendermsg];
				from++;
			}
		}
	} while (*from++);
}

// [RH]
// ClientObituary: Show a message when a player dies
//
void ClientObituary (AActor *self, AActor *inflictor, AActor *attacker)
{
	int	 mod;
	char *message;
	char gendermessage[1024];
	BOOL friendly;
	int  gender;

	if (!self->player)
		return;

	gender = self->player->userinfo.gender;

	// Treat voodoo dolls as unknown deaths
	if (inflictor && inflictor->player == self->player)
		MeansOfDeath = MOD_UNKNOWN;

	if (multiplayer && !deathmatch.value)
		MeansOfDeath |= MOD_FRIENDLY_FIRE;

	friendly = MeansOfDeath & MOD_FRIENDLY_FIRE;
	mod = MeansOfDeath & ~MOD_FRIENDLY_FIRE;
	message = NULL;

	switch (mod) {
		case MOD_SUICIDE:
			message = OB_SUICIDE;
			break;
		case MOD_FALLING:
			message = OB_FALLING;
			break;
		case MOD_CRUSH:
			message = OB_CRUSH;
			break;
		case MOD_EXIT:
			message = OB_EXIT;
			break;
		case MOD_WATER:
			message = OB_WATER;
			break;
		case MOD_SLIME:
			message = OB_SLIME;
			break;
		case MOD_LAVA:
			message = OB_LAVA;
			break;
		case MOD_BARREL:
			message = OB_BARREL;
			break;
		case MOD_SPLASH:
			message = OB_SPLASH;
			break;
	}

	if (attacker && !message) {
		if (attacker == self) {
			switch (mod) {
				case MOD_R_SPLASH:
					message = OB_R_SPLASH;
					break;
				case MOD_ROCKET:
					message = OB_ROCKET;
					break;
				default:
					message = OB_KILLEDSELF;
					break;
			}
		} else if (!attacker->player) {
			switch (attacker->type) {
				case MT_STEALTHBABY:
					message = OB_STEALTHBABY;
					break;
				case MT_STEALTHVILE:
					message = OB_STEALTHVILE;
					break;
				case MT_STEALTHBRUISER:
					message = OB_STEALTHBARON;
					break;
				case MT_STEALTHHEAD:
					message = OB_STEALTHCACO;
					break;
				case MT_STEALTHCHAINGUY:
					message = OB_STEALTHCHAINGUY;
					break;
				case MT_STEALTHSERGEANT:
					message = OB_STEALTHDEMON;
					break;
				case MT_STEALTHKNIGHT:
					message = OB_STEALTHKNIGHT;
					break;
				case MT_STEALTHIMP:
					message = OB_STEALTHIMP;
					break;
				case MT_STEALTHFATSO:
					message = OB_STEALTHFATSO;
					break;
				case MT_STEALTHUNDEAD:
					message = OB_STEALTHUNDEAD;
					break;
				case MT_STEALTHSHOTGUY:
					message = OB_STEALTHSHOTGUY;
					break;
				case MT_STEALTHZOMBIE:
					message = OB_STEALTHZOMBIE;
					break;
				default:
					if (mod == MOD_HIT) {
						switch (attacker->type) {
							case MT_UNDEAD:
								message = OB_UNDEADHIT;
								break;
							case MT_TROOP:
								message = OB_IMPHIT;
								break;
							case MT_HEAD:
								message = OB_CACOHIT;
								break;
							case MT_SERGEANT:
								message = OB_DEMONHIT;
								break;
							case MT_SHADOWS:
								message = OB_SPECTREHIT;
								break;
							case MT_BRUISER:
								message = OB_BARONHIT;
								break;
							case MT_KNIGHT:
								message = OB_KNIGHTHIT;
								break;
							default:
								break;
						}
					} else {
						switch (attacker->type) {
							case MT_POSSESSED:
								message = OB_ZOMBIE;
								break;
							case MT_SHOTGUY:
								message = OB_SHOTGUY;
								break;
							case MT_VILE:
								message = OB_VILE;
								break;
							case MT_UNDEAD:
								message = OB_UNDEAD;
								break;
							case MT_FATSO:
								message = OB_FATSO;
								break;
							case MT_CHAINGUY:
								message = OB_CHAINGUY;
								break;
							case MT_SKULL:
								message = OB_SKULL;
								break;
							case MT_TROOP:
								message = OB_IMP;
								break;
							case MT_HEAD:
								message = OB_CACO;
								break;
							case MT_BRUISER:
								message = OB_BARON;
								break;
							case MT_KNIGHT:
								message = OB_KNIGHT;
								break;
							case MT_SPIDER:
								message = OB_SPIDER;
								break;
							case MT_BABY:
								message = OB_BABY;
								break;
							case MT_CYBORG:
								message = OB_CYBORG;
								break;
							case MT_WOLFSS:
								message = OB_WOLFSS;
								break;
							default:
								break;
						}
					}
					break;
			}
		}
	}

	if (message) {
		SexMessage (message, gendermessage, gender);
		Printf (PRINT_MEDIUM, "%s %s.\n", self->player->userinfo.netname, gendermessage);
		return;
	}

	if (attacker && attacker->player) {
		if (friendly) {
			int rnum = P_Random (pr_obituary);

			attacker->player->fragcount -= 2;
			attacker->player->frags[attacker->player - players]++;
			self = attacker;
			gender = self->player->userinfo.gender;

			SV_UpdateFrags (attacker->player);

			if (rnum < 64)
				message = OB_FRIENDLY1;
			else if (rnum < 128)
				message = OB_FRIENDLY2;
			else if (rnum < 192)
				message = OB_FRIENDLY3;
			else
				message = OB_FRIENDLY4;
		} else {
			switch (mod) {
				case MOD_FIST:
					message = OB_MPFIST;
					break;
				case MOD_CHAINSAW:
					message = OB_MPCHAINSAW;
					break;
				case MOD_PISTOL:
					message = OB_MPPISTOL;
					break;
				case MOD_SHOTGUN:
					message = OB_MPSHOTGUN;
					break;
				case MOD_SSHOTGUN:
					message = OB_MPSSHOTGUN;
					break;
				case MOD_CHAINGUN:
					message = OB_MPCHAINGUN;
					break;
				case MOD_ROCKET:
					message = OB_MPROCKET;
					break;
				case MOD_R_SPLASH:
					message = OB_MPR_SPLASH;
					break;
				case MOD_PLASMARIFLE:
					message = OB_MPPLASMARIFLE;
					break;
				case MOD_BFG_BOOM:
					message = OB_MPBFG_BOOM;
					break;
				case MOD_BFG_SPLASH:
					message = OB_MPBFG_SPLASH;
					break;
				case MOD_TELEFRAG:
					message = OB_MPTELEFRAG;
					break;
				case MOD_RAILGUN:
					message = OB_RAILGUN;
					break;
			}
		}
	}

	if (message) {
		char work[256];

		SexMessage (message, gendermessage, gender);
		sprintf (work, "%%s %s\n", gendermessage);
		Printf (PRINT_MEDIUM, work, self->player->userinfo.netname,
				attacker->player->userinfo.netname);
		return;
	}

	SexMessage (OB_DEFAULT, gendermessage, gender);
	Printf (PRINT_MEDIUM, "%s %s.\n", self->player->userinfo.netname, gendermessage);
}


//
// KillMobj
//
EXTERN_CVAR (fraglimit)

void P_KillMobj (AActor *source, AActor *target, AActor *inflictor)
{
	AActor *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->flags2 &= ~MF2_PASSMOBJ;
	target->height >>= 2;

	// [RH] If the thing has a special, execute and remove it
	//		Note that the thing that killed it is considered
	//		the activator of the script.
	if ((target->flags & MF_COUNTKILL) && target->special)
	{
		LineSpecials[target->special] (NULL, source, target->args[0],
									   target->args[1], target->args[2],
									   target->args[3], target->args[4]);
		target->special = 0;
	}
	// [RH] Also set the thing's tid to 0. [why?]
	target->tid = 0;

	if (source && source->player && target->player 
		&& !target->IsTeammate (source))
	{
		SV_Logfrag(source->player, target->player);
	}

	if (target->player && !source)
		SV_Logfrag(target->player, target->player);

	if (source && source->player)
	{
		// Don't count any frags at level start, because they're just telefrags
		// resulting from insufficient deathmatch starts, and it wouldn't be
		// fair to count them toward a player's score.
		if (target->player && level.time)
		{
			source->player->frags[target->player - players]++;
			if (target->player == source->player)	// [RH] Cumulative frag count
				source->player->fragcount--;
			else
				source->player->fragcount++;
	
			SV_UpdateFrags (source->player);

			// [RH] Implement fraglimit
			if (deathmatch.value && fraglimit.value &&
				(int)fraglimit.value == source->player->fragcount)
			{
				SV_BroadcastPrintf (PRINT_HIGH, "Fraglimit hit.\n");
				G_ExitLevel (0);
			}
		}
	}
	
	if (target->player)
	{
		// [RH] Force a delay between death and respawn
		target->player->respawn_time = level.time + TICRATE;

		// count environment kills against you
		if (!source)
		{
			target->player->frags[target->player - players]++;
			target->player->fragcount--;	// [RH] Cumulative frag count
			SV_UpdateFrags (target->player);
		}
						
		target->flags &= ~MF_SOLID;
		target->player->playerstate = PST_DEAD;
		P_DropWeapon (target->player);

		if (target == players[consoleplayer].camera && 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 (pr_killmobj) & 3;

	if (target->tics < 1)
		target->tics = 1;
				
	// [RH] Death messages
	if (target->player && level.time)
		ClientObituary (target, inflictor, source);

	// Drop stuff.
	// This determines the kind of object spawned
	// during the death frame of a thing.
	mobjtype_t item = (mobjtype_t)0;

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

	if (!item)
		return; //Happens if bot or player killed when using fists.
	mo = new AActor (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.
//
int MeansOfDeath;

void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, int mod, int flags)
{
	unsigned	ang;
	int 		saved;
	player_t*	player;
	fixed_t 	thrust;
		
	if ( !(target->flags & MF_SHOOTABLE) )
		return; // shouldn't happen...
				
	if (target->health <= 0)
		return;

	MeansOfDeath = mod;

	// [RH] Andy Baker's Stealth monsters
	if (target->flags & MF_STEALTH)
	{
		target->translucency = FRACUNIT;
		target->visdir = -1;
	}

	if ( target->flags & MF_SKULLFLY )
	{
		target->momx = target->momy = target->momz = 0;
	}
		
	player = target->player;
	if (player && gameskill.value == 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 (pr_damagemobj)&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 & 255) == dDamage_End
			&& 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;
		}

		// [NightFang] - Still allow self-inflicted damage. Randy you
		// tard.
		if (target->IsTeammate (source))
		{
			// [NightFang] - Still telefrag other teammates!
			if ((dmflags & DF_NO_FRIENDLY_FIRE) && (mod != MOD_TELEFRAG))
			{
				// [NightFang] - You can still blow yourself up on teamplay modes!
				if (target != source)
					return;
			}
			else
				MeansOfDeath |= MOD_FRIENDLY_FIRE;
		}


		if (player->armortype && !(flags & DMG_NO_ARMOR))
		{
			if (player->armortype == deh.GreenAC)
				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...

		int playernum;

		for (playernum=0; playernum<MAXPLAYERS; playernum++)
			if (player == &players[playernum])
				break;

        for (int i=0; i<MAXPLAYERS; i++)
		{
			client_t *cl = &clients[i];

			if (cl->cls < cs_spawned)
				continue;         

			MSG_WriteByte (&cl->reliablebuf, svc_damageplayer);
			MSG_WriteByte (&cl->reliablebuf, playernum);
            MSG_WriteByte (&cl->reliablebuf, player->armorpoints);
            MSG_WriteShort (&cl->reliablebuf, target->health - damage);
        }
	}
	
	// do the damage
	// [RH] Only if not immune
	if (!(target->flags2 & (MF2_INVULNERABLE | MF2_DORMANT)))
	{
		target->health -= damage;
		if (target->health <= 0)
		{
			P_KillMobj (source, target, inflictor);           
			for (int i=0; i<MAXPLAYERS; i++)
            {
				client_t *cl = &clients[i];

				if (cl->cls < cs_spawned)
					continue;

                MSG_WriteByte (&cl->reliablebuf, svc_killmobj);

                if (source)
                    MSG_WriteShort (&cl->reliablebuf, source->netid);
                else
                    MSG_WriteShort (&cl->reliablebuf, 0);

                MSG_WriteShort (&cl->reliablebuf, target->netid);

                if (inflictor)
                    MSG_WriteShort (&cl->reliablebuf, inflictor->netid);
                else
                    MSG_WriteShort (&cl->reliablebuf, 0);

				MSG_WriteLong (&cl->reliablebuf, MeansOfDeath);
			}
			return;
		}
	}

	if (!(target->flags2 & MF2_DORMANT))
	{
		// [RH] Only react if not dormant
		if ( (P_Random (pr_damagemobj) < 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

			// killough 2/15/98: remember last enemy, to prevent
			// sleeping early; 2/21/98: Place priority on players

			if (!target->lastenemy || !target->lastenemy->player ||
				 target->lastenemy->health <= 0)
				target->lastenemy = target->target; // remember last enemy - killough

			target->target = source;
			target->threshold = BASETHRESHOLD;
			if (target->state == &states[target->info->spawnstate]
				&& target->info->seestate != S_NULL)
				P_SetMobjState (target, target->info->seestate);
		}
	}
}

BOOL CheckCheatmode (void);
