Mud20/accounts/
Mud20/accounts/c/
Mud20/accounts/f/
Mud20/accounts/k/
Mud20/accounts/s/
Mud20/accounts/t/
Mud20/area_current/
Mud20/area_current/newareas/
Mud20/bin/
Mud20/clans/
Mud20/gods/
Mud20/old-sources/
Mud20/player/
Mud20/player/a/del/
Mud20/player/b/
Mud20/player/b/bak/
Mud20/player/b/del/
Mud20/player/f/
Mud20/player/f/bak/
Mud20/player/f/del/
Mud20/player/k/
Mud20/player/k/bak/
Mud20/player/k/del/
Mud20/player/k/dmp/
Mud20/player/m/
Mud20/player/m/bak/
Mud20/player/o/
Mud20/player/o/bak/
Mud20/player/p/
Mud20/player/s/
Mud20/player/s/bak/
Mud20/player/s/del/
Mud20/player/t/
Mud20/player/t/del/
Mud20/player/v/
Mud20/public_html/
Mud20/races/
Mud20/skilltables/
__MACOSX/Mud20/accounts/
__MACOSX/Mud20/accounts/c/
__MACOSX/Mud20/accounts/f/
__MACOSX/Mud20/accounts/k/
__MACOSX/Mud20/accounts/s/
__MACOSX/Mud20/area_current/
__MACOSX/Mud20/area_current/core_areas/
__MACOSX/Mud20/area_current/helps/
__MACOSX/Mud20/area_current/newareas/
__MACOSX/Mud20/backups/
__MACOSX/Mud20/bin/
__MACOSX/Mud20/clans/
__MACOSX/Mud20/gods/
__MACOSX/Mud20/log/
__MACOSX/Mud20/old-sources/
__MACOSX/Mud20/player/
__MACOSX/Mud20/player/a/del/
__MACOSX/Mud20/player/b/
__MACOSX/Mud20/player/b/bak/
__MACOSX/Mud20/player/f/
__MACOSX/Mud20/player/f/bak/
__MACOSX/Mud20/player/f/del/
__MACOSX/Mud20/player/k/
__MACOSX/Mud20/player/k/bak/
__MACOSX/Mud20/player/k/del/
__MACOSX/Mud20/player/k/dmp/
__MACOSX/Mud20/player/m/
__MACOSX/Mud20/player/m/bak/
__MACOSX/Mud20/player/o/
__MACOSX/Mud20/player/o/bak/
__MACOSX/Mud20/player/p/
__MACOSX/Mud20/player/s/
__MACOSX/Mud20/player/s/bak/
__MACOSX/Mud20/player/t/del/
__MACOSX/Mud20/player/v/
__MACOSX/Mud20/public_html/
__MACOSX/Mud20/races/
__MACOSX/Mud20/skilltables/
/***************************************************************************
 * Mud20 1.0 by Todd H. Johnson (Kregor) a derivative of the Open Gaming   *
 * License by Wizards of the Coast. All comments referring to D20, OGL,    *
 * and SRD refer to the System Reference Document for the Open Gaming      *
 * system. Any inclusion of these derivatives must include credit to the   *
 * Mud20 system, the full and complete Open Gaming License, and credit to  *
 * the respective authors. See ../doc/srd.txt for more information.        *
 *                                                                         *
 * Emud  2.2 by Igor van den Hoven, Michiel Lange, and Martin Bethlehem.   *
 *                                                                         *
 * MrMud 1.4 by David Bills, Dug Michael and Martin Gallwey                *
 *                                                                         *
 * Merc  2.1 Diku Mud improvments copyright (C) 1992, 1993 by Michael      *
 * Chastain, Michael Quan, and Mitchell Tse.                               *
 *                                                                         *
 * Original Diku Mud copyright (C) 1990 1991 by Sebastian Hammer,          *
 * Michael Seifert, Hans Henrik St{rfeld, Tom Madsen, and Katje Nyboe.     *
 ***************************************************************************/

/**************************************************************************
 * File:    combat_handler.c                                              *
 * Author:  Midboss                                                       *
 * Purpose: Core functionality for turn-based combat on ROM MUDs.         *
 * Modified by Kregor for the Mud20 System, Jan 2008                      *
 **************************************************************************/

#include "mud.h"

	/*
	 * Guards come to the rescue of NPC citizens in distress.
	 * Note that putting this in combat_update does in fact mean that the more ACT_CITIZENs
	 * there are involved in the battle (this should include guards)
	 * The more often another guard will be summoned. This is the desired
	 * effect and should aid in area razing defense - Kregor
	 */
void check_guard_assist( CHAR_DATA *ch, CHAR_DATA *attacker )
{
	MOB_INDEX_DATA *mobIndex;
	CHAR_DATA *guard;

	push_call("check_guard_assist(%p)", ch);
	
	if (IS_ACT(ch, ACT_CITIZEN) && ch->in_room->area == ch->pIndexData->area)
	{
		if (number_bits(3) == 0)
		{
			if ((mobIndex = get_mob_index(ch->pIndexData->area->guard)) == NULL)
				log_build_printf(ch->pIndexData->vnum, "mobile_turn (%d): guard data for area invalid.");
			else
			{
				guard = create_mobile(mobIndex);
				char_to_room( guard, ch->in_room->vnum, TRUE );
				guard->npcdata->sac_timer = 60;
				RESTRING(guard->npcdata->sac_string, "$n departs the vicinity as the turmoil calms.");
				act("$n comes to the aid of $N.", guard, NULL, ch, TO_ROOM);
				if (attacker != NULL)
				{
					fight(guard, attacker);
				}
			}
		}
	}
	pop_call();
	return;
}


/*
 * Updater for what few autonomous functions used in this combat system.
 * Mostly just triggers mobile's turns and updates battlefield conditions.
 * Also controls the automatic turn skip when someone tries to stall.
 */
void combat_update (void)
{
	COMBAT_DATA * battle, * battle_next;
	CHAR_DATA * ch;
	
	push_call("combat_update()");

	for (battle = battle_list; battle != NULL; battle = battle_next)
	{
		battle_next = battle->next;

		if (battle->turn_list->roundtime >= 2000000000)
			nuke_roundtime (battle);

		ch = battle->turn_list->unit;

		if (ch == NULL)
		{
			bug ("combat_update (): Turn with NULL character!", 0);
			clean_combat (battle);
			pop_call();
			return;
		}

		if (!IS_NPC(ch) && ch->desc == NULL)
			linkdead_turn (ch);
		else if (!IS_AWAKE(ch)) // unconscious/mortal stay in combat, but only to lose affects and bleed
			turn_end (battle->turn_list, 200 - ch->initiative);
		else if (!mounted_combat_check(ch)) // failure to control mount in combat forfeits turn
			turn_end (battle->turn_list, 200 - ch->initiative);
		else if (IS_AFFECTED(ch, AFF2_DAZED))
		{
			act("You are too dazed to fight.", ch, NULL, NULL, TO_CHAR);
			act("$n simply stands around in a daze.", ch, NULL, NULL, TO_ROOM);
			turn_end (battle->turn_list, 200 - ch->initiative);
		}			
		else if (is_affected(ch, gsn_irresistible_dance))
			irresistible_dance(ch);
		else if (IS_AFFECTED(ch, AFF2_CONFUSION)) // confusion can override attacks
			confused_turn(ch);
		else if (IS_NPC (ch) && !ch->desc)
			mobile_turn (ch);
		else
		{
			battle->turn_list->timer++;
		}
		
		if (battle->turn_list->timer >= 20) //two minutes to end a turn seems reasonable? Kregor
			turn_end (battle->turn_list, 200 - ch->initiative);
			
		if (IS_PLR(ch, PLR_WIZTIME))
		{
			if (ch->last_attacked != NULL)
				ch_printf_color(ch, "{058}DEBUG: you last attacked %s.\n\r", get_name(ch->last_attacked));
		}
		check_victory (battle);
	}
	pop_call();
	return;
}

/*
 * Checks to see if a char has taken all the
 * actions they can take in a turn, if so, then
 * advance to the next turn - Kregor
 */
bool turn_complete( CHAR_DATA *ch )
{
	TURN_DATA *turn;

	push_call("turn_complete(%p)",ch);
	
	if (!in_combat(ch))
	{
		pop_call();
		return FALSE;
	}	

	if (!is_active(ch))
	{
		pop_call();
		return FALSE;
	}
	
	if ((turn = get_turn_char(ch)) == NULL)
	{
		pop_call();
		return FALSE;
	}
	
	if (ch->casting || ch->concentrating)
	{
		pop_call();
		return FALSE;
	}

	if (IS_SET(ch->action, ACTION_STANDARD) && IS_SET(ch->action, ACTION_MOVE))
	{
		turn_end (turn, 200 - ch->initiative);
		pop_call();
		return TRUE;
	}
	pop_call();
	return FALSE;
}

	
/*
 * Lets a mobile take its turn.
 * All the AI functions happen here. All functions should control their
 * own action space, and should all pass both ch and victim. The loop
 * for deciding a target may not yet be the best. - Kregor
 */
void mobile_turn (CHAR_DATA * ch)
{
	CHAR_DATA *victim, *vch = NULL, *list;
	TURN_DATA *turn;

	push_call("mobile_turn(%p)",ch);

	if (!IS_NPC(ch))
	{
		pop_call();
		return;
	}
	
	if ((turn = get_turn_char(ch)) == NULL)
	{
		pop_call();
		return;
	}
	
	if (!IS_AWAKE(ch))
	{
		turn_end (turn, 200 - ch->initiative);
		pop_call();
		return;
	}
	
	if (!in_combat(ch))
	{
		pop_call();
		return;
	}
	
	// hit percent prog triggers before aggression, overrides AI - Kregor
	if ((victim = who_fighting(ch)) != NULL && mprog_hitprcnt_trigger(ch, victim))
	{
		TAKE_ACTION(ch, ACTION_FULL);
		pop_call();
		return;
	}
	
	// Select the target of aggression if not already focused on someone
	if (!victim)
	{
		for (vch = ch, list = ch->in_battle->unit_list; list != NULL; list = list->next_in_battle)
		{
			if (!is_same_group(ch, list))
			{
				if (vch == ch)
					vch = list;
				else if (who_fighting(list) && who_fighting(list) == ch)
				{
					vch = list;
					break;
				}
				else if (vch != ch && number_percent () < 45)
					vch = list;
				else
					continue;
			}
			else if ((vch = who_fighting(list)) != NULL)
			{
				vch = list;
				break;
			}
		}
	}
	if (vch == ch)
	{
		clean_combat (ch->in_battle);
		pop_call();
		return;
	}
	else if (victim)
	{
		vch = check_tanking(ch, victim);
	}
	
	if (IS_AWAKE(ch) && ch->position < POS_FIGHTING)
	{
		do_stand(ch, "");
	}
	
	if (!is_safe(ch, vch)) // if safe, end turn
	{
		// fight_prog takes over turn, overrides regular AI attack - Kregor
		if (mprog_fight_trigger(ch, vch))
		{
			TAKE_ACTION(ch, ACTION_FULL);
		}
		else if (!IS_SET(ch->act, ACT_NOFIGHT)) // the AI functions for combat need to go here!
		{
			if (breath_weapon(ch, vch))
			{
				pop_call();
				return;
			}
			if (gaze_attack(ch, vch))
			{
				pop_call();
				return;
			}
			if (caster_AI_combat(ch, vch))
			{
				pop_call();
				return;
			}
			if (warrior_AI_combat(ch, vch))
			{
				pop_call();
				return;
			}
			else
			{
				do_say(ch, "Awwwwww! I'm outta options!");
			}
		}
	}
	TAKE_ACTION(ch, ACTION_FULL);
	pop_call();
	return;
}

/*
 * Hacking mobile_turn to handle confused characters - Kregor
 */
void confused_turn(CHAR_DATA * ch)
{
	CHAR_DATA *victim, *vch = NULL;
	TURN_DATA *turn;

	push_call("confused_turn(%p)",ch);

	if ((turn = get_turn_char(ch)) == NULL)
	{
		pop_call();
		return;
	}
	
	if (!IS_AWAKE(ch))
	{
		turn_end (turn, 200 - ch->initiative);
		pop_call();
		return;
	}
	
	if (!in_combat(ch))
	{
		pop_call();
		return;
	}
	
	// Select the target of aggression if not already focused on someone
	if ((victim = who_fighting(ch)) == NULL)
	{
		for (vch = ch->in_battle->unit_list ; vch ; vch = ch->next_in_battle)
		{
			if (is_master(vch, ch)) // because I'm nice, skip over pets - Kregor
				continue;

			if (vch->last_attacked && vch->last_attacked == ch)
			{
				break;
			}

			if (number_bits(1) == 0)
			{
				break;
			}
		}
	}
	// select collateral damage
	if (vch == NULL)
	{
		for (vch = ch->in_room->first_person ; vch ; vch = vch->next)
		{
			if (is_master(vch, ch))
				continue;

			if (vch->last_attacked && vch->last_attacked == ch)
			{
				break;
			}

			if (number_bits(1) == 0)
			{
				break;
			}
		}
	}
	
	if (!victim)
		victim = vch;
	
	switch (number_range(1,4))
	{
		case 1:
			attack(ch, vch);
			attack(ch, vch);
			break;
		case 2:
			act("$n stands around, babbling nonsense.", ch, NULL, NULL, TO_ROOM);
			act("You stand around, babbling nonsense.", ch, NULL, NULL, TO_CHAR);
			break;
		case 3:
			act("You swing wildly, hitting yourself in the process!", ch, NULL, NULL, TO_CHAR);
			act("$n swings wildly, only hitting himself as a result!", ch, NULL, NULL, TO_ROOM);
			damage(ch, ch, dice(1,8) + stat_bonus(TRUE, ch, APPLY_STR), TYPE_NOFIGHT, NULL);
			break;
		case 4:
			attack(ch, vch);
			attack(ch, vch);
			break;
	}
	TAKE_ACTION(ch, ACTION_FULL);

	pop_call();
	return;
}

/*
 * If link dead, remove from combat - Kregor
 */
void linkdead_turn (CHAR_DATA * ch)
{
	push_call("linkdead_turn(%p)",ch);

	char_from_combat (ch);
	pop_call();
	return;
}


/*
 * This will end a combat, and notify the victorious party.
 */
void check_victory (COMBAT_DATA * battle)
{
	CHAR_DATA *leader;
	CHAR_DATA *unit;
	bool victory = TRUE;
	bool found = FALSE;

	push_call("check_victory(%p)",battle);

	//First look for PCs.  Since mobs don't gain, if only mobs are left, we abort.
	for (unit = battle->unit_list; unit != NULL; unit = unit->next_in_battle)
		if (!IS_NPC (unit))
			found = TRUE;

	if (!found)
	{
		clean_combat (battle);
		return;
	}

	for (leader = battle->unit_list; leader != NULL; leader = leader->next_in_battle)
	{
		if (!IS_AWAKE(leader))
			continue;

		//Once we find the first guy that isn't out, we look for another guy that
		//isn't out, and isn't on his team. If one isn't found, battle's over.
		for (unit = battle->unit_list; unit != NULL; unit = unit->next_in_battle)
		{
			if ((!IS_AWAKE(unit) && !is_same_group (leader, unit))
				|| is_same_group (leader, unit) || leader == unit)
				continue;
			else
			{
				victory = FALSE;
				break;
			}
		}
		if (!victory)
			continue;
		else
		{
			for (unit = battle->unit_list; unit != NULL; unit = unit->next_in_battle)
			{
				if (is_same_group (leader, unit))
					act ("{138}You have won the battle!", unit, NULL, NULL, TO_CHAR);
				else
					act ("{138}You have lost the battle!", unit, NULL, NULL, TO_CHAR);
			}
			clean_combat (battle);
			pop_call();
			return;
		}
	}
	pop_call();
	return;
}

/*
 * True if ch is part of any combat.
 */
bool in_combat (CHAR_DATA * ch)
{
	push_call("in_combat(%p)",ch);

	if (ch->in_battle)
	{
		pop_call();
		return TRUE;
	}

	pop_call();
	return FALSE;
}

/*
 * True if ch and victim are both in same combat.
 */
bool in_same_combat (CHAR_DATA * ch, CHAR_DATA *victim)
{
	push_call("in_combat(%p)",ch);

	if (ch == NULL || victim == NULL)
	{
		pop_call();
		return FALSE;
	}
	
	if (!ch->in_battle || victim->in_battle)
	{
		pop_call();
		return FALSE;
	}
	
	if (ch->in_battle != victim->in_battle)
	{
		pop_call();
		return FALSE;
	}

	pop_call();
	return TRUE;
}

/*
 * True if ch may take a turn right now.
 */
bool is_active (CHAR_DATA * ch)
{
	push_call("is_active(%p)",ch);

	if (!in_combat (ch))
	{
		pop_call();
		return FALSE;
	}

	if (ch->turn == ch->in_battle->turn_list)
	{
		pop_call();
		return TRUE;
	}
	pop_call();
	return FALSE;
}

/*
 * initiative per D20 SRD - Kregor
 */
void roll_initiative( CHAR_DATA *ch )
{	
	int init;
	
	push_call("roll_initiative(%p)", ch);
	
	if (arcane_mastery(ch, SCHOOL_DIVINATION))
		init = 20 + stat_bonus(TRUE, ch, APPLY_DEX);
	else
		init = dex_roll(ch);

	if (learned(ch, gsn_imp_initiative))
		init += 4;
	if (learned(ch, gsn_alertness))
		init += 2;
	if (learned(ch, gsn_first_strike) && armor_type_worn(ch) == ARMOR_NONE
	&& get_eq_char(ch, WEAR_SHIELD) == NULL)
	{
		init += multi_class_level(ch, gsn_first_strike) / 2;
	}
	if (get_monk_style(ch) == STYLE_INVISIBLE_EYE && class_level(ch, CLASS_MONK) >= 12)
		init += 2;
	if (IS_AFFECTED(ch, AFF2_HALLUCINATE))
		init -= 4;
		
	ch->initiative = UMAX(1, init);

	if (IS_PLR(ch, PLR_WIZTIME))
		ch_printf_color(ch, "DEBUG: You score %d for initiative.\n\r", ch->initiative);
	
	pop_call();
	return;
}

/*
 * Inserts a character into combat and adds their turn to the list.
 */
void char_to_combat (CHAR_DATA * ch, COMBAT_DATA * combat)
{
	TURN_DATA * turn;
	AFFECT_DATA af;

	push_call("char_to_combat(%p,%p)",ch,combat);

	if (combat->unit_list == NULL)
		combat->unit_list = ch;
	else
	{
		ch->next_in_battle = combat->unit_list;
		combat->unit_list  = ch;
	}
	ch->in_battle = combat;

	roll_initiative(ch);
	turn = new_turn ();
	turn->unit = ch;
	turn->in_battle = combat;

	//So people don't end up getting a dozen turns by joining in.
	if (combat->turn_count > 0) 
		turn->roundtime = (combat->turn_list->roundtime + 200) - ch->initiative;
	else
		turn->roundtime = 500 - ch->initiative;

	ch->turn = turn;
	ch->action = 0;
	if (IS_STAGGERED(ch))
		ch->action = ACTION_MOVE;
	if (IS_FLYING(ch))
		TAKE_ACTION(ch, ACTION_MOVE);
	ch->attack = 0;
	ch->attack_part = -1;
	insert_turn (combat, turn);
	
	// check racial abilities that only happen once in a fight
	frightful_presence(ch);		
	check_aura(ch, gsn_aura_of_menace);
	check_aura(ch, gsn_babble);
	
	if (learned(ch, gsn_daring_charge))
	{
		af.type				= gsn_daring_charge;
		af.level		 	= multi_class_level(ch, gsn_daring_charge);
		af.duration		= 1;
		af.bittype  	= AFFECT_TO_NONE;
		af.bitvector	= AFF_NONE;
		af.location 	= APPLY_MOR_TOHIT;
		af.modifier		= af.level;
		affect_join( ch, ch, &af);

		af.bitvector	= AFF_NONE;
		af.location 	= APPLY_MOR_DAMG;
		af.modifier		= af.level;
		affect_join( ch, ch, &af);
		
		ch->uses[gsn_daring_charge]++;
	}
		
	pop_call();
	return;
}

/*
 * Removes a ch and their turn entry from combat.
 */
void char_from_combat (CHAR_DATA * ch)
{
	CHAR_DATA * prev;

	push_call("char_from_combat(%p)",ch);

	if (ch == NULL)
	{
		bug ("char_from_combat() -- null ch!?", 0);
		pop_call();
		return;
	}

	stop_fighting(ch, TRUE);

	if (!in_combat(ch))
	{
		pop_call();
		return;
	}

	if (ch->in_battle->unit_list == ch)
	{
		ch->in_battle->unit_list = ch->next_in_battle;
		ch->next_in_battle = NULL;
	}
	else
	{
		for (prev = ch->in_battle->unit_list; prev != NULL; prev = prev->next_in_battle)
		{
			if (prev->next_in_battle == ch)
			{
				prev->next_in_battle = ch->next_in_battle;
				ch->next_in_battle = NULL;
				break;
			}
		}

		if (prev == NULL)
		{
			bug ("char_from_combat() -- ch not found.", 0);
			pop_call();
			return;
		}
	}
	remove_turn (ch->turn);
	ch->turn = NULL;
	ch->in_battle = NULL;

	// strip all effects that only last until end of fight
	if (is_affected(ch, gsn_barbarian_rage))
	{
		affect_strip(ch, gsn_barbarian_rage);
		if (!learned(ch, gsn_tireless_rage))
		{
			int cost = get_max_move(ch) / 2;
			move_loss(ch, NULL, cost);
		}
	}
	if (is_affected(ch, gsn_blood_rage))
		affect_strip(ch, gsn_blood_rage);
	if (is_affected(ch, gsn_smite)) 
		affect_strip(ch, gsn_smite); 
	if (is_affected(ch, gsn_retributive_strike)) 
		affect_strip(ch, gsn_retributive_strike); 

	stop_fighting(ch, TRUE);

	pop_call();
	return;
}

/*
 * Returns the data for ch's next/current turn.
 */
TURN_DATA * get_turn_char (CHAR_DATA * ch)
{
	TURN_DATA * turn;

	push_call("get_turn_char(%p)",ch);

	if (!in_combat (ch))
	{
		pop_call();
		return NULL;
	}

	for (turn = ch->in_battle->turn_list; turn != NULL; turn = turn->next)
	{
		if (turn->unit == ch)
		{
			pop_call();
			return turn;
		}
	}
	pop_call();
	return NULL;
}

/*
 * Inserts a turn into its proper position in the specified list.
 */
void insert_turn (COMBAT_DATA * combat, TURN_DATA * nTurn)
{
	push_call("insert_turn(%p,%p)",combat,nTurn);

	if (combat->turn_list == NULL)
	{
		combat->turn_list = nTurn;
		nTurn->in_battle = combat;
	}
	else
	{
		TURN_DATA * prev;

		if (combat->turn_list->roundtime > nTurn->roundtime)
		{
			nTurn->next = combat->turn_list;
			combat->turn_list = nTurn;
			nTurn->in_battle = combat;
		}
		else
		{
			for (prev = combat->turn_list; prev != NULL; prev = prev->next)
			{
				if (prev->next != NULL && prev->next->roundtime > nTurn->roundtime)
				{
					nTurn->next = prev->next;
					prev->next  = nTurn;
					nTurn->in_battle = combat;
					break;
				}
				else if (prev->next == NULL)
				{
					prev->next  = nTurn;
					nTurn->in_battle = combat;
					break;
				}
			}
		}
	}
	pop_call();
	return;
}

/*
 * Removes a turn from the list without freeing it.
 * It isn't freed here because you have to use this every time you move a turn
 * to a new position in the list.
 */
void remove_turn (TURN_DATA * turn)
{
	TURN_DATA * prev;

	push_call("remove_turn(%p)",turn);

	if (turn == NULL)
	{
		bug ("remove_turn() -- null turn!?", 0);
		pop_call();
		return;
	}

	if (turn == turn->in_battle->turn_list)
	{
		turn->in_battle->turn_list = turn->next;
		turn->next = NULL;
	}
	else
	{
		for (prev = turn->in_battle->turn_list; prev != NULL; prev = prev->next)
		{
			if (prev->next == turn)
			{
				prev->next = turn->next;
				turn->next = NULL;
				break;
			}
		}
	}
	pop_call();
	return;
}


/*
 * Similar to roundtime (), but used specifically at the end of each action.
 */
void turn_end (TURN_DATA * turn, int roundtime)
{
	CHAR_DATA *grappled;
	CHAR_DATA *caster;
	CHAR_DATA *ch, *ch_next;
	int lvl;

	push_call("turn_end(%p,%p)",turn,roundtime);

	turn->timer = 0;
	turn->roundtime += roundtime;
	
	ch = turn->unit;
	ch_next = turn->next->unit;

	if (!IS_NPC(ch))
		ch->pcdata->last_combat = mud->current_time;

	if (IS_SET(ch->in_room->progtypes, FIGHT_PROG))
		rprog_fight_trigger(ch);

	// If grappling at end of affected char's turn, do damage - Kregor
	if ((grappled = who_grappling(ch)) != NULL)
	{
		if ((lvl = get_affect_level(ch, gsn_fire_shield)) > 0 && !save_resist(ch, grappled, gsn_fire_shield, lvl))
		{
			damage(ch, grappled, dice(1,6) + UMIN(lvl,15), gsn_fire_shield, NULL);
		}
		else if ((lvl = get_affect_level(ch, gsn_thorn_body)) > 0 && !save_resist(ch, grappled, gsn_thorn_body, lvl))
		{
			damage(ch, grappled, dice(1,6) + UMIN(lvl,15), gsn_thorn_body, NULL);
		}
		else if ((lvl = race_skill(ch, gsn_barbed_touch)) > 0)
		{
			damage(ch, grappled, dice(1,lvl), gsn_barbed_touch, NULL);
		}
		else if ((lvl = race_skill(ch, gsn_fire_touch)) > 0)
		{
			damage(ch, grappled, dice(1,lvl), gsn_fire_touch, NULL);
		}
		else if ((lvl = race_skill(ch, gsn_frost_touch)) > 0)
		{
			damage(ch, grappled, dice(1,lvl), gsn_frost_touch, NULL);
		}
		else if ((lvl = race_skill(ch, gsn_shock_touch)) > 0)
		{
			damage(ch, grappled, dice(1,lvl), gsn_shock_touch, NULL);
		}
		else if ((lvl = race_skill(ch, gsn_acid_touch)) > 0)
		{
			damage(ch, grappled, dice(1,lvl), gsn_acid_touch, NULL);
		}
	}

	act( "You have ended your turn.", ch, NULL, NULL, TO_CHAR);
	act( "$n has ended $s turn.", ch, NULL, NULL, TO_ROOM);

	damage_update(ch);
	affect_update(ch);
	obj_char_update(ch);

	act( "It is now your turn.", ch_next, NULL, NULL, TO_CHAR);
	
	// update cleric with status of his comrades.
	if (is_affected(ch_next, gsn_status))
	{
		show_party_line(ch_next);
	}

	check_guard_assist(ch, NULL);

	ch_next->action = 0;
	if (IS_STAGGERED(ch_next))
	{
		act("You are staggered and can only take a partial action.", ch_next, NULL, NULL, TO_CHAR);
		ch_next->action = ACTION_MOVE;
	}
	if (is_mounting(ch_next) && !learned(ch_next->mounting, gsn_combat_rearing))
	{
		act("You take a move action to control your mount.", ch_next, NULL, NULL, TO_CHAR);
		TAKE_ACTION(ch_next, ACTION_MOVE);
	}
	if (IS_FLYING(ch_next))
	{
		act("You take a move action to remain airborne.", ch_next, NULL, NULL, TO_CHAR);
		TAKE_ACTION(ch_next, ACTION_MOVE);
	}
	ch_next->attack = 0;
	ch_next->attack_part = -1;
	
	if (is_affected(ch_next, gsn_crushing_hand))
	{
		if (IS_AFFECTED(ch_next, AFF_FREEDOM) || (caster = get_caster(ch_next, gsn_crushing_hand)) == NULL || combat_maneuver_check(caster, ch_next, gsn_crushing_hand) < 0)
		{
			act("{038}You break free from the hand's grasp!", ch, NULL, NULL, TO_CHAR);
			act("{038}$n breaks free from the hand's grasp!", ch, NULL, NULL, TO_ROOM);
			affect_strip(ch_next, gsn_crushing_hand);
		}
		else
		{
			damage(caster, ch_next, dice(2,6)+12, gsn_crushing_hand, NULL);
		}
		TAKE_ACTION(ch_next, ACTION_MOVE);		
	}
	else if (is_affected(ch_next, gsn_grasping_hand))
	{
		if (IS_AFFECTED(ch_next, AFF_FREEDOM) || (caster = get_caster(ch_next, gsn_grasping_hand)) == NULL || combat_maneuver_check(caster, ch_next, gsn_grasping_hand) < 0)
		{
			act("{038}You break free from the hand's grasp!", ch, NULL, NULL, TO_CHAR);
			act("{038}$n breaks free from the hand's grasp!", ch, NULL, NULL, TO_ROOM);
			affect_strip(ch_next, gsn_grasping_hand);
		}
		else
		{
			act("{118}The giant hand of force holds you fast!", ch, NULL, NULL, TO_CHAR);
			act("{118}The giant hand of force holds $n fast!", ch, NULL, NULL, TO_ROOM);
		}
		TAKE_ACTION(ch_next, ACTION_MOVE);		
	}
	else if (IS_ENTANGLED(ch_next))
	{
		int diceroll;
		
		if (learned(ch_next, gsn_escape_artist))
			diceroll = escape_artist_roll(ch_next);
		else
			diceroll = str_roll(ch_next);
		if (diceroll < 15 && domain_apotheosis(ch_next, DOMAIN_STRENGTH))
			diceroll = UMAX(diceroll, str_roll(ch_next));
		if (diceroll >= 15)
		{
			act("$tYou break free from your entanglement!", ch_next, get_color_string(ch, COLOR_YOU_HIT, VT102_BOLD), NULL, TO_CHAR);
			act("$t$n breaks free from $s entanglement!", ch_next, get_color_string(ch, COLOR_YOU_HIT, VT102_BOLD), NULL, TO_ROOM);
			AFFECT_STRIP(ch_next, AFF2_ENTANGLED);
		}
		else
		{
			act("$tYou fail to escape your entanglement!", ch_next, get_color_string(ch, COLOR_YOU_ARE_HIT, VT102_BOLD), NULL, TO_CHAR);
			act("$t$n fails to escape $s entanglement!", ch_next, get_color_string(ch, COLOR_YOU_ARE_HIT, VT102_BOLD), NULL, TO_ROOM);
		}
		TAKE_ACTION(ch_next, ACTION_MOVE);
	}
	else if ((grappled = ch_next->grappled_by) != NULL)
	{
		if (combat_maneuver_check(ch_next, grappled, gsn_grapple) >= 0)
		{
			stop_grapple(grappled);
		}
		else
		{
			act("$tYou try to break $N's hold, but fail.", ch_next, get_color_string(ch_next, COLOR_YOU_ARE_HIT, VT102_BOLD), grappled, TO_CHAR);
			act("$t$n tries to break $N's hold, but fails.", ch_next, "{178}", grappled, TO_NOTVICT);
			act("$t$n tries to break your hold, but fails.", ch_next, get_color_string(grappled, COLOR_YOU_HIT, VT102_BOLD), grappled, TO_VICT);
		}
		TAKE_ACTION(ch_next, ACTION_MOVE);
	}
	else if ((grappled = ch_next->grappling) != NULL)
	{
		if (combat_maneuver_check(ch_next, grappled, gsn_grapple) < 0)
		{
			stop_grapple(grappled);
		}
		else
		{
			act("$tYou maintain your hold on $N.", ch_next, get_color_string(ch_next, COLOR_YOU_HIT, VT102_BOLD), grappled, TO_CHAR);
			act("$t$n maintains $s grapple on $N.", ch_next,  "{178}", grappled, TO_NOTVICT);
			act("$t$n maintains $s grapple on you.", ch_next, get_color_string(grappled, COLOR_YOU_ARE_HIT, VT102_BOLD), grappled, TO_VICT);
		}
		TAKE_ACTION(ch_next, ACTION_MOVE);
	}
	
	check_aura(ch_next, gsn_aura_of_menace);
	check_aura(ch_next, gsn_babble);
		
	remove_turn(turn);
	insert_turn(turn->in_battle, turn);
	turn->in_battle->turn_count++;
	
	pop_call();
	return;
}

/*
 * Moves the turn down the list a bit.
 */
void skip_turn (TURN_DATA * turn, bool fWilling)
{
	push_call("skip_turn(%p,%p)",turn,fWilling);

	turn->roundtime = turn->next->roundtime;
	remove_turn (turn);
	insert_turn (turn->in_battle, turn);
	if (fWilling)
		turn->in_battle->turn_count++;

	pop_call();
	return;
}

/*
 * Adds to the roundtime of a turn, then replaces it in the order.
 */
void roundtime (TURN_DATA * turn, int roundtime)
{
	push_call("roundtime(%p,%p)",turn,roundtime);

	turn->roundtime += roundtime;
	remove_turn (turn);
	insert_turn (turn->in_battle, turn);
	
	pop_call();
	return;
}

/*
 * Swaps the placement of two turns, by switching roundtimes and reordering.
 */
void swap_turn (TURN_DATA * aTurn, TURN_DATA * bTurn)
{
	COMBAT_DATA * battle = aTurn->in_battle;
	int rt = aTurn->roundtime;

	push_call("swap_turn(%p,%p)",aTurn,bTurn);

	aTurn->roundtime = bTurn->roundtime;
	bTurn->roundtime = rt;

	remove_turn (aTurn);
	remove_turn (bTurn);
	insert_turn (battle, aTurn);
	insert_turn (battle, bTurn);
	
	pop_call();
	return;
}

/*
 * Just in case some newb with a trigger leaves a battle against a training
 * dummy running for weeks on end with trigs, or something.  You can never
 * have too much protection, y'know.                             -- Midboss
 */
void nuke_roundtime (COMBAT_DATA * battle)
{
	TURN_DATA * list;

	push_call("nuke_rountime(%p)",battle);

	for (list = battle->turn_list; list != NULL; list = list->next)
		list->roundtime -= 2000000000;
	
	pop_call();
	return;
}

/*
 * Creates a new battle between two parties.
 * Recycles the data if there somehow aren't enough people.
 */
void initiate_combat (CHAR_DATA * ch, CHAR_DATA * victim)
{
	CHAR_DATA * och;
	TURN_DATA * turn;
	COMBAT_DATA * battle;
	int acount = 0, bcount = 0;
	
	push_call("initiate_combat(%p,%p)",ch,victim);

	ALLOCMEM(battle, COMBAT_DATA, 1);

	if (battle_list == NULL)
		battle_list = battle;
	else
	{
		battle->next = battle_list;
		battle_list = battle;
	}

	set_fighting(ch, victim);
	set_fighting(victim, ch);
	
	for (och = ch->in_room->first_person; och != NULL; och = och->next_in_room)
	{
		if (is_same_group (ch, och) && !IS_ACT(och, ACT_NOASSIST))
		{
			char_to_combat (och, battle);
			set_fighting(och, victim);
		}
		if (is_same_group (victim, och) && !IS_ACT(och, ACT_NOASSIST))
		{
			char_to_combat (och, battle);
			set_fighting(och, ch);
		}
	}

	for (turn = battle->turn_list; turn != NULL; turn = turn->next)
	{
		if (is_same_group (ch, turn->unit))
			acount++;
		else 
			bcount++;
	}
	if (acount < 1 || bcount < 1)
		clean_combat (battle);
	
	pop_call();
	return;
}

/*
 * is ch dead or nonexistent?
 */
bool valid_victim( CHAR_DATA *ch )
{
	push_call("valid_victim(%p)",ch);

	if (ch == NULL)
	{
		pop_call();
		return FALSE;
	}
	if (ch->position == POS_DEAD || IS_PLR(ch, PLR_DEAD))
	{
		pop_call();
		return FALSE;
	}
	if (ch->in_room && ch->in_room->vnum == ROOM_VNUM_DEATH)
	{
		pop_call();
		return FALSE;
	}
	pop_call();
	return TRUE;
}

/*
 * is ch REALLY fighting victim?
 */
bool valid_fight( CHAR_DATA *ch, CHAR_DATA *victim )
{
	push_call("valid_fight(%p,%p)",ch,victim);

	/* Keep from fighting thin air... */
	if (ch == NULL)
	{
		pop_call();
		return FALSE;
	}
	if (victim == NULL)
	{
		pop_call();
		return FALSE;
	}
	/* keep SuperMob from fighting */
	if (victim == supermob || ch == supermob)
	{
		pop_call();
		return FALSE;
	}
	
	// if either party died, it's not valid fight
	if (victim->position == POS_DEAD)
	{
		pop_call();
		return FALSE;
	}
	if (victim->in_room)
	{
		switch (victim->in_room->vnum)
		{
			case ROOM_VNUM_TEMPLE:
			case ROOM_VNUM_SCHOOL:
			case ROOM_VNUM_DEATH:
				pop_call();
				return FALSE;
			default:
				break;
		}
	}
	if (ch->in_room)
	{
		switch (ch->in_room->vnum)
		{
			case ROOM_VNUM_TEMPLE:
			case ROOM_VNUM_SCHOOL:
			case ROOM_VNUM_DEATH:
				pop_call();
				return FALSE;
			default:
				break;
		}
	}
	
	// ch can't attack if he's out cold.
	if (ch->position <= POS_SLEEPING)
	{
		pop_call();
		return FALSE;
	}
	
// 	/* prevent melee in separate rooms */
// 	if (ch->in_room != victim->in_room)
// 	{
// 		pop_call();
// 		return FALSE;
// 	}
	
	pop_call();
	return TRUE;
}

/*
 * What person is ch actually fighting
 */
CHAR_DATA *who_fighting( CHAR_DATA *ch )
{
	push_call("who_fighting(%p)",ch);
// 
// 	pop_call();
// 	return NULL;
// 	
	if (ch == NULL || ch->last_attacked == NULL)
	{
		pop_call();
		return NULL;
	}
	
	if (!is_string(ch->last_attacked->name))
	{
		log_printf("who_fighting: who->name == NULL for %s", ch->name);
		ch->last_attacked = NULL;
		dump_stack();
		pop_call();
		return NULL;
	}
	pop_call();
	return ch->last_attacked;
}


/*
 * Is CH grappling, or grappled by someone
 */
CHAR_DATA *who_grappling( CHAR_DATA *ch )
{
	CHAR_DATA *grappled;

	push_call("who_grappling(%p)",ch);

	if ((grappled = ch->grappling) == NULL)
	{
		if ((grappled = ch->grappled_by) == NULL)
		{
			pop_call();
			return NULL;
		}
	}
	if (!is_string(grappled->name))
	{
		log_printf("who_grappling: who->name == NULL for %s", ch->name);
		if (ch->grappling)
			stop_grapple(ch);
		if (ch->grappled_by)
			stop_grapple(ch->grappled_by);
		dump_stack();
		pop_call();
		return NULL;
	}
	pop_call();
	return grappled;
}


/*
 * Choose a new target for aggression
 * choose new victim fighting CH first,
 * else pick a member of victim's group - Kregor 1/5/08
 */
CHAR_DATA *get_next_fighting(CHAR_DATA *ch, CHAR_DATA *victim)
{
	CHAR_DATA *rch, *rch_next;
	
	push_call("get_next_fighting(%p)",ch);
	
	for (rch = ch->in_room->first_person ; rch ; rch = rch_next)
	{
		rch_next = rch->next_in_room;
		
		if (rch == victim)
			continue;

		if (!IS_AWAKE(rch))
			continue;

		if (who_fighting(rch) == NULL)
			continue;

		if (who_fighting(rch) == ch)
		{
			pop_call();
			return rch;
		}

		if (is_same_group(who_fighting(rch),ch))
		{
			pop_call();
			return rch;
		}
	}
	pop_call();
	return NULL;
}


/*
	Start ch fighting against victim.
*/
void set_fighting( CHAR_DATA *ch, CHAR_DATA *victim )
{
	push_call("set_fighting(%p,%p)",ch,victim);

	if (ch == victim)
	{
		pop_call();
		return;
	}

	if (ch->in_room != victim->in_room)
	{
		pop_call();
		return;
	}

	if (IS_AFFECTED(ch, AFF_SLEEP))
	{
		affect_strip(ch, gsn_sleep);
	}

	if (ch->position == POS_STANDING)
	{
		ch->position = POS_FIGHTING;
	}
	
	ch->last_attacked = victim;
	
	if (!IS_NPC(ch) && is_string(ch->pcdata->pose))
		STRFREE(ch->pcdata->pose);
		
	if (!IS_NPC(victim) && is_string(victim->pcdata->pose))
		STRFREE(victim->pcdata->pose);
		
	if (!IS_NPC(ch))
	{
		ch->pcdata->last_combat = mud->current_time;
	}

	if (!IS_NPC(victim))
	{
		victim->pcdata->last_combat = mud->current_time;
	}

	update_pos(ch,-1);

	pop_call();
	return;
}

/*
	And stop ch from fighting, and alternately those fighting ch.
*/
void stop_fighting( CHAR_DATA *ch, bool fBoth )
{
	CHAR_DATA *victim, *och;

	push_call("stop_fighting(%p,%p)",ch,fBoth);

	free_unit( ch );

	if (!fBoth)
	{
		pop_call();
		return;
	}

	for (och = mud->f_char ; och ; och = mud->update_wch)
	{
		mud->update_wch = och->next;

		if (och->last_attacked && och->last_attacked == ch)
		{
			if ((victim = get_next_fighting(och, ch)) == NULL)
			{
				free_unit (och);
				continue;
			}
			set_fighting(och, victim);
		}
	}
	pop_call();
	return;
}

void free_unit( CHAR_DATA *ch )
{
	push_call("free_unit(%p)",ch);

	ch->last_attacked = NULL;
	ch->last_attacker = NULL;
	ch->position = POS_STANDING;
	ch->attack_part = -1;
	ch->initiative = 0;
	ch->attack = 0;
	ch->action = 0;
	
	if (ch->grappled_by)
	{
		stop_grapple(ch->grappled_by);
	}
	if (ch->grappling)
	{
		stop_grapple(ch);
	}

	update_pos(ch,-1);

	pop_call();
	return;
}

/*
 * Frees combat data at the end of battle.
 */
void clean_combat (COMBAT_DATA * battle)
{
	COMBAT_DATA *list;
	CHAR_DATA *unit, *unit_next;

	push_call("clean_combat(%p)",battle);

	for (unit = battle->unit_list; unit != NULL; unit = unit_next)
	{
		unit_next = unit->next_in_battle;
		char_from_combat (unit);
		//Perhaps call raw_kill() here on KOd mobiles?
		if (unit->position == POS_DEAD)
			raw_kill(unit, -1);
	}

	if (battle == battle_list)
		battle_list = battle->next;
	else
	for (list = battle_list; list != NULL; list = list->next)
	{
		if (list->next == battle)
			list->next = battle->next;
	}

	battle->next = NULL;

	free_combat (battle);
	pop_call();
	return;
}

TURN_DATA *new_turn (void)
{
	TURN_DATA *turn;

	push_call("new_turn()");

	ALLOCMEM(turn, TURN_DATA, 1);
	turn->next = NULL;

	pop_call();
	return turn;
}

void free_turn (TURN_DATA * turn)
{
	push_call("free_turn(%p)",turn);

	turn = turn->next;
	FREEMEM(turn);
	
	pop_call();
	return;
}

void free_combat (COMBAT_DATA * battle)
{
	push_call("free_combat(%p)",battle);

	battle = battle->next;
	FREEMEM(battle);
	
	pop_call();
	return;
}


/*
 * Move action, only here for testing purposes.
 */
void do_move (CHAR_DATA *ch, char *argument)
{
	push_call("do_move(%p)",ch);

	if (!in_combat (ch))
	{
		send_to_char ("You're not fighting!\n\r", ch);
		pop_call();
		return;
	}

	act( "You execute a move action.", ch, NULL, NULL, TO_CHAR);
	act( "$n executes a move action.", ch, NULL, NULL, TO_ROOM);
	TAKE_ACTION(ch, ACTION_MOVE);

	pop_call();
	return;
}

/*
 * Withdraws a character from combat with check for victory.
 */
void withdraw_combat(CHAR_DATA *ch)
{
	COMBAT_DATA * battle;

	push_call("withdraw_combat(%p)",ch);

	if (!in_combat (ch))
	{
		pop_call();
		return;
	}
	
	battle = ch->in_battle;

	char_from_combat (ch);
	check_victory (battle);

	pop_call();
	return;
}


/*
 * This is no longer supported in code, but left
 * the old function deprecated here - Kregor
 */
void add_to_victory_list( CHAR_DATA *ch, CHAR_DATA *victim)
{
	int i;
	char buf[MAX_STRING_LENGTH];

	push_call("add_to_victory_list(%p,%p)",ch,victim);

	for (i = 1 ; i < VICTORY_LIST_SIZE ; i++)
	{
		STRFREE(victory_list[i-1]);
		victory_list[i-1] = STRALLOC(victory_list[i]);
	}
	sprintf(buf, "Z%12s (Level %2d) %12s (Level %2d) %s", ch->name, ch->level, victim->name, victim->level, get_time_string(mud->current_time));

	RESTRING(victory_list[i-1], buf);

	save_victors();
	pop_call();
	return;
}


/*
 * Qualifiers in the check_murder function - Kregor
 */
bool can_attack( CHAR_DATA *ch, CHAR_DATA *victim)
{
	push_call("can_attack(%p,%p)",ch,victim);

	if (victim == NULL)
	{
		pop_call();
		return TRUE;
	}
	
	// I am god, and I can kill you!
	if (IS_GOD(ch))
	{
		pop_call();
		return TRUE;
	}

	// This means it's not murder.
	if (IS_NPC(ch) || IS_NPC(victim))
	{
		pop_call();
		return TRUE;
	}

	// Anyone in the arena is fair game.
	if (in_area(ch, ROOM_VNUM_ARENA) && in_area(victim, ROOM_VNUM_ARENA))
	{
		pop_call();
		return TRUE;
	}

	// PKill is restricted by character level
	if ((!IS_NPC(ch) && ch->level < 3) || (!IS_NPC(victim) && victim->level < 3))
	{
		pop_call();
		return FALSE;
	}
	pop_call();
	return TRUE;
}

/*
 * New function to check for pkill allowed,
 * control murder restrictions. - Kregor 1/5/08
 */
bool check_murder(CHAR_DATA *ch, CHAR_DATA *victim)
{
	bool fMurder = FALSE;
	
	push_call("check_murder(%p,%p)",ch,victim);
	
	if (victim == NULL)
	{
		pop_call();
		return TRUE;
	}
	
	if (IS_NPC(victim) && IS_SET(victim->act, ACT_PET) && victim->master)
	{
		if (victim->master == ch)
		{
			act( "Why not just DISCHARGE $M instead?", ch, NULL, victim, TO_CHAR);
			pop_call();
			return FALSE;
		}
		if (!in_area(ch, ROOM_VNUM_ARENA))
			fMurder = TRUE;
	}

	if (!IS_NPC(ch) || IS_SET(ch->act, ACT_PET) || ch->desc != NULL)
	{
		if (!IS_NPC(victim) && !in_area(ch, ROOM_VNUM_ARENA))
			fMurder = TRUE;
	}
	
	if (!can_attack(ch, victim))
	{
		pop_call();
		return FALSE;
	}

	if (fMurder)
	{
		if (ch == victim)
		{
			pop_call();
			return TRUE;
		}
		if (victim->pcdata->just_died_ctr > 0)
		{
			send_to_char("That person is currently protected by their god.\n\r", ch);
			pop_call();
			return FALSE;
		}	
		if (!IS_SET(victim->act, PLR_KILLER) && !IS_SET(victim->act, PLR_THIEF))
		{
			if (ch->pcdata->last_connect + 120 > mud->current_time)
			{
				ch_printf_color(ch, "You must wait %d seconds before you can murder anyone.\n\r", ch->pcdata->last_connect + 120 - mud->current_time);
				pop_call();
				return FALSE;
			}
		}	
		if (!IS_NPC(victim) && victim->pcdata->corpse != NULL)
		{
			act("OOC: $N has been killed quite recently.", ch, NULL, victim, TO_CHAR);
			pop_call();
			return FALSE;
		}	
		if (!check_add_attack(ch, victim))
		{
			act("OOC: $N has been attacked too many times recently.", ch, NULL, victim, TO_CHAR);
			pop_call();
			return FALSE;
		}

		check_killer( ch, victim );
		ch->pcdata->just_died_ctr = 0;
		clear_attack_list(ch);
		log_printf("ROOM [%u] MURDER %s attacking %s", ch->in_room->vnum, ch->name, victim->name);
	}
	pop_call();
	return TRUE;
}

/*
	true if victim is a legal target for a mass attack - Scandum 27/05/2002
*/
bool can_mass_attack( CHAR_DATA *ch, CHAR_DATA *victim )
{
	push_call("can_mass_attack(%p,%p)",ch,victim);

	if (victim == ch)
	{
		pop_call();
		return FALSE;
	}

	if (victim == supermob)
	{
		pop_call();
		return FALSE;
	}

	// anyone fighting CH is fair game!
	if (who_fighting(victim) && who_fighting(victim) == ch)
	{
		pop_call();
		return TRUE;
	}

	// anyone attacking someone in your group is also fair game!
	if (who_fighting(victim) && is_same_group(ch, who_fighting(victim)))
	{
		pop_call();
		return TRUE;
	}

	if (IS_ACT(victim, ACT_MOBINVIS))
	{
		pop_call();
		return FALSE;
	}

	if (is_same_group(ch, victim))
	{
		pop_call();
		return FALSE;
	}

	if (is_master(victim, ch))
	{
		pop_call();
		return FALSE;
	}

	if (in_area(ch, ROOM_VNUM_ARENA))
	{
		pop_call();
		return TRUE;
	}

	// Should prevent some nasty AOE kills from mobiles against friendlies
	if (IS_NPC(ch) && who_fighting(ch) && !is_same_group(ch, who_fighting(ch)))
	{
		pop_call();
		return FALSE;
	}

	pop_call();
	return TRUE;
}

/*
	Rewrote this function to put all the no fighting crap in one
	neat function, charmed and possessed mobs attack like players.

	Scandum 18/02/02
	
	Revised - Kregor
	Rewrote santuary affect per d20 rules. This is separate 
	from can_melee_attack	in that this checks for ability to 
	do ANY hostile action, not just a physical attack.
*/
bool is_safe( CHAR_DATA *ch, CHAR_DATA *victim)
{
	push_call("is_safe(%p,%p)",ch,victim);

	if (IS_SET(ch->in_room->room_flags, ROOM_SAFE))
	{
		send_to_char( "Wards of peace prevent you from doing that.\n\r", ch);
		pop_call();
		return TRUE;
	}
	
	// should assure no auto actions happen from people who are down
	if (ch->position < POS_FIGHTING)
	{
		act("You are in no position to do anything aggressive.", ch, NULL, NULL, TO_CHAR);
		pop_call();
		return TRUE;
	}

// These are only needed for auto actions, commands are snagged in interp.c
	if (IS_AFFECTED(ch, AFF2_PETRIFICATION) || (IS_AFFECTED(ch, AFF2_PARALYSIS) && !IS_AFFECTED(ch, AFF_FREEDOM)))
	{
		pop_call();
		return TRUE;
	}

	if (IS_AFFECTED(ch, AFF2_DAZED))
	{
		act( "You are too dazed to act.", ch, NULL, NULL, TO_CHAR);
		act( "$n simply stands in a daze.", ch, NULL, NULL, TO_ROOM);
		pop_call();
		return TRUE;
	}
	if (IS_AFFECTED(ch, AFF2_NAUSEATED))
	{
		act( "You are too nauseous to act.", ch, NULL, NULL, TO_CHAR);
		act( "$n struggles against wretching.", ch, NULL, NULL, TO_ROOM);
		pop_call();
		return TRUE;
	}
	if (IS_AFFECTED(ch, AFF2_STUNNED))
	{
		act( "You are too stunned to act.", ch, NULL, NULL, TO_CHAR);
		pop_call();
		return TRUE;
	}
	
	if (victim == NULL)
	{
		pop_call();
		return FALSE;
	}
	
	if (!IS_NPC(ch) && !IS_NPC(victim) && IS_SET(ch->in_room->area->flags, AFLAG_NO_PVP))
	{
		send_to_char( "You are currently in a no-pvp area.\n\r", ch);
		pop_call();
		return TRUE;
	}
		
	if (victim == supermob)
	{
		send_to_char( "You cannot attack the SuperMob!\n\r", ch);
		pop_call();
		return TRUE;
	}

	if (is_affected(victim, gsn_time_stop))
	{
		act("You cannot affect $N while $E is frozen in time.", ch, NULL, victim, TO_CHAR);
		pop_call();
		return TRUE;
	}

	if (!can_attack( ch, victim ))
	{
		act( "You may not do that against $N.", ch, NULL, victim, TO_CHAR);
		pop_call();
		return TRUE;
	}

	//hostile action removes fascination.
	if (IS_AFFECTED(victim, AFF2_FASCINATED))
	{
		AFFECT_STRIP(victim, AFF2_FASCINATED);
		act("Your fascination is broken due to $N's actions.", victim, NULL, ch, TO_CHAR);
	}

	if (IS_AFFECTED(ch, AFF2_CHARMED))
	{
		if ((is_affected(ch, gsn_charm_monster) && get_caster(ch, gsn_charm_monster) == victim)
		|| (is_affected(ch, gsn_charm_person) && get_caster(ch, gsn_charm_person) == victim)
		|| (is_affected(ch, gsn_charm_plant) && get_caster(ch, gsn_charm_plant) == victim)
		|| (is_affected(ch, gsn_charm_animal) && get_caster(ch, gsn_charm_animal) == victim))
		{
			act( "You like $N too much to attack $M.", ch, NULL, victim, TO_CHAR);
			pop_call();
			return TRUE;
		}
	}

	// already fighting, sanctuary doesn't matter - Kregor
	if (who_fighting(ch) == victim)
	{
		pop_call();
		return FALSE;
	}

	if (who_fighting(victim) == ch)
	{
		pop_call();
		return FALSE;
	}

	if (IS_AFFECTED(victim, AFF_SANCTUARY))
	{
		if (!save_resist(ch, victim, gsn_sanctuary, get_affect_level(victim, gsn_sanctuary)))
		{
			send_to_char( "A magical force stays your hand.\n\r", ch);
			pop_call();
			return TRUE;
		}
	}
	
	if (IS_AFFECTED(victim, gsn_undeath_ward) && IS_UNDEAD(ch))
	{
		if (!save_resist(ch, victim, gsn_undeath_ward, get_affect_level(victim, gsn_undeath_ward)))
		{
			send_to_char( "A powerful ward stays your hand.\n\r", ch);
			pop_call();
			return TRUE;
		}
	}
	
	if (IS_AFFECTED(ch, AFF_DOMINATE) && (is_master(ch, victim) || is_same_group(ch, victim->master)))
	{
		act( "You may not attack your master.", ch, NULL, victim, TO_CHAR);
		pop_call();
		return TRUE;
	}

	if (in_area(ch, ROOM_VNUM_ARENA))
	{
		pop_call();
		return FALSE;
	}

	if (IS_NPC(ch) && !in_combat(ch) && ch->hit < get_max_hit(ch) / 2 && IS_SET(ch->act, ACT_WIMPY))
	{
		if (!IS_AFFECTED(ch, AFF_DOMINATE) && !IS_SET(ch->act, ACT_PET) && ch->desc == NULL)
		{
			pop_call();
			return TRUE;
		}
	}

	if (IS_AFFECTED(ch, AFF_DOMINATE) && ch->master == victim )
	{
		act( "$N is your beloved master.", ch, NULL, victim, TO_CHAR);
		pop_call();
		return TRUE;
	}
	pop_call();
	return FALSE;
}

/*
 * New function to determine whether an aggressive action
 * can be performed outside of combat or out of turn - Kregor 01/2008
 * Make sure this is the LAST check before the action, so
 * that AoO isn't wasted by other conditions!
 */
bool can_surprise(CHAR_DATA *ch, CHAR_DATA *victim)
{
	push_call("can_surprize(%p,%p)",ch,victim);

	if (ch == NULL || victim == NULL)
	{
		pop_call();
		return FALSE;
	}

	if (ch == victim)
	{
		act("Attack yourself?", ch, NULL, NULL, TO_CHAR);
		pop_call();
		return FALSE;
	}

	if (in_combat(ch))
	{
		if (!is_active(ch))
		{
			if (!has_opportunity(ch, victim))
			{
				send_to_char ("Just wait your turn.\n\r", ch);
				pop_call();
				return FALSE;
			}
			else if (check_murder(ch, victim))
			{
				take_opportunity(ch, victim);
				pop_call();
				return TRUE;
			}
		}
		else
		{
			pop_call();
			return TRUE;
		}
	}
	else if (has_opportunity(ch, victim))
	{
		if (check_murder(ch, victim))
		{
			take_opportunity(ch, victim);
			pop_call();
			return TRUE;
		}
	}
	else if (!can_see(victim, ch) || !IS_AWAKE(victim) || multi(ch, gsn_backstab) != -1)
	{
		if (check_murder(ch, victim))
		{
			if (IS_AFFECTED(ch, AFF_HIDE))
			{
				AFFECT_STRIP(ch, AFF_HIDE);
				act( "$n steps out from $s hiding spot.",   ch, NULL, NULL, TO_ROOM );
				act( "You step out from your hiding spot.", ch, NULL, NULL, TO_CHAR );
			}
			if (IS_AFFECTED(ch, AFF_INVISIBLE))
			{
				affect_strip(ch, gsn_invis);
				REMOVE_BIT(ch->affected_by, AFF_INVISIBLE);
			}
			act( "$n takes you by surprise!", ch, NULL, victim, TO_VICT);
			act( "You takes $N by surprise!", ch, NULL, victim, TO_CHAR);
			act( "$n takes $N by surprise!", ch, NULL, victim, TO_NOTVICT);
			pop_call();
			return TRUE;
		}
	}
	act ("$N is too wary to be taken by surprise. Try starting a FIGHT first.", ch, NULL, victim, TO_CHAR);
	pop_call();
	return FALSE;
}


bool check_recently_fought( CHAR_DATA *ch, CHAR_DATA *victim )
{
	CHAR_DATA *ch_ld;
	int pvnum_ld;

	push_call("check_recently_fought(%p,%p)",ch,victim);

	if (!IS_NPC(victim))
	{
		pop_call();
		return FALSE;
	}

	if (victim->npcdata->pvnum_last_hit == 0)
	{
		pop_call();
		return FALSE;
	}

	if (MP_VALID_MOB(ch))
	{
		pop_call();
		return FALSE;
	}

	ch_ld = ch;

	if (IS_NPC(ch) && ch->master)
	{
		ch_ld = ch_ld->master;
	}

	if (ch_ld->leader != NULL)
	{
		ch_ld = ch_ld->leader;
	}

	if (!IS_NPC(ch_ld))
	{
		pvnum_ld = ch_ld->pcdata->pvnum;
	}
	else
	{
		pvnum_ld = 0;
	}

	if (ch_ld == NULL || IS_NPC(ch_ld))
	{
		pop_call();
		return TRUE;
	}

	if (pvnum_ld != victim->npcdata->pvnum_last_hit)
	{
		pop_call();
		return TRUE;
	}
	pop_call();
	return FALSE;
}

/*
	See if an attack justifies a KILLER flag.
*/

void check_killer( CHAR_DATA *ch, CHAR_DATA *victim )
{
	push_call("check_killer(%p,%p)",ch,victim);

	if (IS_PLR(victim, PLR_KILLER))
	{
		pop_call();
		return;
	}

	if (!IS_PLR(ch, PLR_KILLER))
	{
		send_to_char("*** PLAYER KILLER status recorded. ***\n\r", ch);
	}
	SET_BIT(ch->act, PLR_KILLER);
	ch->pcdata->killer_played = ch->pcdata->played;

	pop_call();
	return;
}

/*
	Code for total and max pvnum attacks  -  Chaos 4/20/99
*/

bool check_add_attack( CHAR_DATA *ch, CHAR_DATA *victim )
{
	push_call("check_add_attack(%p,%p)",ch,victim);

	/*
		Killers and Thieves can always be attacked - Scandum 07/02/02
	*/

	if (!IS_NPC(ch) && !IS_NPC(victim))
	{
		int oldest_attack, total_pvnum_attacks, attacks_today, cnt;

		attacks_today = 0;
		oldest_attack = 0;
		total_pvnum_attacks = 0;

		for (cnt = 0 ; cnt < MAX_PK_ATTACKS ; cnt++)
		{
			if (victim->pcdata->last_pk_attack_time[cnt] == 0)
			{
				oldest_attack = cnt;
			}
			else if (victim->pcdata->last_pk_attack_time[oldest_attack] > victim->pcdata->last_pk_attack_time[cnt])
			{
				oldest_attack = cnt;
			}

			if (victim->pcdata->last_pk_attack_time[cnt] > mud->current_time)
			{
				attacks_today++;
			}

			if (ch->pcdata->pvnum == victim->pcdata->last_pk_attack_pvnum[cnt]
			&& victim->pcdata->last_pk_attack_time[cnt] > mud->current_time)
			{
				total_pvnum_attacks++;
			}
		}
		if (!IS_SET(victim->act, PLR_KILLER) && !IS_SET(victim->act, PLR_THIEF))
		{
			if (attacks_today >= MAX_PK_ATTACKS)
			{
				pop_call();
				return( FALSE );
			}

			if (total_pvnum_attacks >= 5)
			{
				pop_call();
				return( FALSE );
			}
			victim->pcdata->last_pk_attack_time[oldest_attack] = mud->current_time + 60 * 60 * 6;
			victim->pcdata->last_pk_attack_pvnum[oldest_attack] = ch->pcdata->pvnum;

			STRFREE(victim->pcdata->last_pk_attack_name[oldest_attack]);
			victim->pcdata->last_pk_attack_name[oldest_attack] = STRALLOC(ch->name);
		}
	}
	pop_call();
	return( TRUE );
}

/*
	Scandum 14-01-2003
*/

void clear_attack_list( CHAR_DATA *ch )
{
	int cnt;

	push_call("clear_attack_list(%p)",ch);

	for (cnt = 0 ; cnt < MAX_PK_ATTACKS ; cnt++)
	{
		ch->pcdata->last_pk_attack_time[cnt] = 0;

		STRFREE(ch->pcdata->last_pk_attack_name[cnt]);
		ch->pcdata->last_pk_attack_name[cnt] = STRDUPE(str_empty);
	}
	pop_call();
	return;
}

void spam_attack_list( CHAR_DATA *ch )
{
	int cnt;

	push_call("spam_attack_list(%p)",ch);

	for (cnt = 0 ; cnt < MAX_PK_ATTACKS ; cnt++)
	{
		ch->pcdata->last_pk_attack_time[cnt]  = mud->current_time + 60 * 60 * 24;
		ch->pcdata->last_pk_attack_pvnum[cnt] = 0;

		STRFREE(ch->pcdata->last_pk_attack_name[cnt]);
		ch->pcdata->last_pk_attack_name[cnt] = STRDUPE(str_empty);
	}
	pop_call();
	return;
}


/*
 * Set position of a victim.
 * Updated to include D20 guidelines
 * for stunning and unconsciousness - Kregor 10/07
 */
void update_pos( CHAR_DATA *victim, int pos )
{
	CHAR_DATA *gch;

	push_call("update_pos(%p)",victim);

	// really, can't get any deader.
	if (victim->position == POS_DEAD)
	{
		free_cast(victim);
		free_skill(victim);
		pop_call();
		return;
	}
	
	if (in_combat(victim) && in_camp(victim))
	{
		for (gch = victim->in_room->first_person ; gch ; gch = gch->next_in_room)
		{
			if (is_same_group(victim, gch) && IS_AFFECTED(gch, AFF2_CAMPING))
			{
				send_to_char( "Your camp is destroyed!\n\r", victim );
				REMOVE_AFFECT(gch, AFF2_CAMPING);
				break;
			}
		}
	}

	if (in_combat(victim) && victim->rest > 0)
	{
		send_to_char( "Your rest is spoiled!\n\r", victim );
		victim->rest = 0;
	}

	if ((!CAN_CRITICAL(victim) && victim->hit <= 0)
	|| victim->hit <= -10)
	{
		victim->position = POS_DEAD;
		free_cast(victim);
		free_skill(victim);
		pop_call();
		return;
	}
	
	if (victim->level + get_apply(victim, APPLY_LEVEL) <= 0)
	{
		act( "$tYou succumb to energy drain!", victim, get_color_code(victim, COLOR_YOU_ARE_HIT, VT102_BOLD), NULL, TO_CHAR);
		act( "$t$n succumbs to energy drain!", victim, get_color_code(victim, COLOR_YOU_ARE_HIT, VT102_BOLD), NULL, TO_ROOM);
		victim->position = POS_DEAD;
		free_cast(victim);
		free_skill(victim);
		pop_call();
		return;
	}

	if (get_curr_con(victim) == 0)
	{
		act( "$tYou succumb to consitution loss!", victim, get_color_code(victim, COLOR_YOU_ARE_HIT, VT102_BOLD), NULL, TO_CHAR);
		victim->position = POS_DEAD;
		free_cast(victim);
		free_skill(victim);
		pop_call();
		return;
	}
	
	if (pos != -1)
	{
		victim->position = URANGE(0, pos, POS_STANDING);
	}

	if (victim->position < POS_FIGHTING)
	{
		if (is_affected(victim, gsn_defensive_stance))
		{
			affect_strip(victim, gsn_defensive_stance);
		}
	}

	if (victim->hit > 0 && victim->nonlethal <= victim->hit)
	{
		if (get_curr_wis(victim) == 0 || get_curr_int(victim) == 0 || get_curr_cha(victim) == 0)
		{
			if (victim->position > POS_INCAP)
			{
				act( "$tYou collapse into a catatonic state!", victim, get_color_code(victim, COLOR_YOU_ARE_HIT, VT102_BOLD), NULL, TO_CHAR);
			}
			victim->position = POS_INCAP;
		}
		else if (get_curr_str(victim) == 0 || get_curr_dex(victim) == 0)
		{
			if (victim->position > POS_INCAP)
			{
				act( "$tYou fall paralyzed to the ground!", victim, get_color_code(victim, COLOR_YOU_ARE_HIT, VT102_BOLD), NULL, TO_CHAR);
			}
			victim->position = POS_INCAP;
		}
		else if (IS_AFFECTED(victim, AFF2_UNCONSCIOUS))
		{
			victim->position = POS_STUNNED;
		}
		else if (drunk_level(victim) >= DRUNK_TOTALLED)
		{
			victim->position = POS_STUNNED;
		}
		else if (victim->position <= POS_STUNNED)
		{
			act( "You recover consciousness.", victim, NULL, NULL, TO_CHAR);
			act( "$n recovers consciousness.", victim, NULL, NULL, TO_ROOM);
			victim->position = POS_RESTING;
		}
		// stunned chars drop their wielded weapons - Kregor
		if (IS_AFFECTED(victim, AFF2_STUNNED) || victim->position <= POS_STUNNED)
		{
			OBJ_DATA *obj;

			if ((obj = get_wield(victim, FALSE)) != NULL)
			{
				act( "$p slips out of your grasp.", victim, obj, NULL, TO_CHAR );
				act( "$p slips out of $n's grasp.", victim, obj, NULL, TO_ROOM );
				unequip_char(victim, obj, FALSE);
			}
			if ((obj = get_wield(victim, TRUE)) != NULL)
			{
				act( "$p slips out of your grasp.", victim, obj, NULL, TO_CHAR );
				act( "$p slips out of $n's grasp.", victim, obj, NULL, TO_ROOM );
				unequip_char(victim, obj, FALSE);
			}
		}
		if (victim->position <= POS_SLEEPING)
		{
			free_cast(victim);
			free_skill(victim);
		}
		pop_call();
		return;
	}

	if (victim->position != POS_INCAP)
	{
		if (victim->hit <= -1)
		{
			if (!race_skill(victim, gsn_ferocity) || victim->uses[gsn_ferocity])
			{
				if (!learned(victim, gsn_diehard) || !will_save(victim, NULL, 10 - (victim->hit * 2), -1))
				{
					victim->position = POS_MORTAL;
				}
			}
		}
		else if (victim->hit == 0 || !domain_apotheosis(victim, DOMAIN_SUFFERING))
		{
			victim->position = POS_STUNNED;
		}
	}

	if (victim->position <= POS_SLEEPING)
	{
		free_cast(victim);
		free_skill(victim);
	}
	pop_call();
	return;
}

void group_gain( CHAR_DATA *ch, CHAR_DATA *victim )
{
	CHAR_DATA *gch;
	int xp, group_bonus, group_count, bonus;

	push_call("group_gain(%p,%p)",ch,victim);

	if (IS_AFFECTED(victim, AFF_DOMINATE) || IS_ACT(victim, ACT_PET))
	{
		pop_call();
		return;
	}

	if (IS_NPC(victim) && victim->pIndexData->vnum == 9901) /* No Demons */
	{
		pop_call();
		return;
	}

	group_bonus = group_count = 0;

	for (gch = ch->in_room->first_person ; gch ; gch = gch->next_in_room)
	{
		if (is_same_group(gch, ch))
		{
			group_count += 1;
			
			if (IS_NPC(gch))
				continue;
				
			group_bonus += 1;

			gch->alignment -= victim->alignment / 50;
			gch->ethos -= victim->ethos / 50;
			
			gch->alignment = URANGE(-1000, gch->alignment, 1000);
			gch->ethos = URANGE(-1000, gch->ethos, 1000);
		}
	}

	bonus = group_bonus * 10;
	bonus += 100;

	for (gch = ch->in_room->first_person ; gch ; gch = gch->next_in_room)
	{
		if (!is_same_group(gch, ch) || IS_NPC(gch))
		{
			continue;
		}

		xp = xp_compute(gch, victim);
		if (group_count > 1)
			xp = xp * bonus / 100;
		wiz_printf("group_gain(%s) - bonus xp: %d", gch->name, xp);
		xp /= group_count;
		wiz_printf("group_gain(%s) - share: %d", gch->name, xp);

		ch_printf_color(gch, "You receive %d experience points.", xp);
		ch_printf_color(gch, "\n\r");

		gain_exp(gch, xp);
		
		if (!gain_favor(gch, DOMAIN_WAR, 1))
			if (!gain_favor(gch, DOMAIN_DESTRUCTION, 1))
				if (!gain_favor(gch, DOMAIN_RETRIBUTION, 1))
					if (!gain_favor(gch, DOMAIN_SUFFERING, 1))
						gain_favor(gch, DOMAIN_WRATH, 1);

		if (faith_enemies(gch, victim))
			gain_favor(gch, -1, 1);
			
		if (which_god(gch) == which_god(victim))
			gain_favor(gch, -1, -5);

		check_zap(gch, TRUE);
	}
	pop_call();
	return;
}


/*
 * Calculate how much experience a character is worth.
 * Formula derived from the OSRIC ruleset, and thus does not
 * infringe upon non-Open Gaming content from WotC - Kregor
 */
int get_exp_worth( CHAR_DATA *ch )
{
	int exp;
	int level = UMIN(ch->level, 21);
	int sn;

	push_call("get_exp_worth(%p)",ch);

	//base XP is based on hit dice
	exp = (level + level - 1) * 10;
	wiz_printf("get_exp_worth(%s) - base xp: %d", ch->name, exp);

	//add 1 xp per hit point, per level of victim
	exp += get_max_hit(ch) * level;
	
	// each exceptional race attack grants +50% of base xp
	for (sn = 0 ; *skill_table[sn].name != '\0' ; sn++)
	{
		switch (skill_table[sn].skilltype)
		{
			default:
				break;
			case FSKILL_RACEATTACK:
				if (race_skill(ch, sn))
					exp += (level + level - 1) * 5;
				break;
			case FSKILL_SPELL:
				if (race_skill(ch, sn))
				{
					if (skill_table[sn].native_level <= 3)
						exp += (level + level - 1) * 5 / 2; // spell-like abilities of 3rd or below only +25%
					else
						exp += (level + level - 1) * 5;
				}
				break;
		}
	}
	wiz_printf("get_exp_worth(%s) - final xp: %d", ch->name, exp);
	
	pop_call();
	return exp;
}


/*
 * Adjust XP awarded to a char based on
 * level difference with victim - Kregor
 */
int xp_compute( CHAR_DATA *gch, CHAR_DATA *victim )
{
	int xp;
	int spread;

	push_call("xp_compute(%p,%p)",gch,victim);

	if (gch->in_room == NULL)
	{
		pop_call();
		return(0);
	}

	if (IS_SET(gch->in_room->room_flags, ROOM_RIP))
	{
		pop_call();
		return(0);
	}

	xp = get_exp_worth(victim);

	if (!IS_NPC(victim))
	{
		xp = 0;
	}

	spread = 10 + gch->level / 10;

	if (gch->level > victim->level)
	{
		xp = xp * spread / (gch->level - victim->level + spread);
	}
	pop_call();
	return UMAX(1, xp);
}


/*
 * Calc the experience worth based on a stated
 * Challenge Rating per OSRIC calculations - Kregor
 */
int xp_compute_cr( CHAR_DATA *gch, int challenge )
{
	int xp, spread;  

	push_call("xp_compute(%p,%p)",gch,challenge);

	xp = (challenge + challenge - 1) * 10;
	xp += 5 * challenge;

	spread = 10 + gch->level / 10;

	if (gch->level > challenge)
	{
		xp = xp * spread / (gch->level - challenge + spread);
	}
	pop_call();
	return UMAX(1, xp);
}


/*
 * The SPAM stat line
 * could use for the Status spell
 */
void show_party_line( CHAR_DATA *ch )
{
	CHAR_DATA *fch;
	char buf[MAX_STRING_LENGTH], buf2[200], buf3[200];

	push_call("show_party_line(%p)",ch);
	buf[0]='\0';

	for (fch = ch->in_room->first_person; fch != NULL; fch = fch->next_in_room)
	{
		if (fch != ch && is_same_group(ch, fch))
		{
			strcpy(buf3, !IS_NPC(fch) ? capitalize(fch->name) : capitalize(fch->short_descr));
			buf3[6]='\0';
			sprintf(buf2, "%s:%d/%d  ", buf3, fch->hit, get_max_hit(fch));
			strcat(buf, buf2);
		}
	}
	if (buf[0] != '\0')
	{
		ch_printf_color(ch, "%s%s\n\r", get_color_string(ch,COLOR_PROMPT,VT102_DIM), buf);
	}
	pop_call();
	return;
}

bool is_light_weapon(CHAR_DATA *ch, OBJ_DATA *obj)
{
	push_call("is_light_weapon(%p)",ch,obj);
	
	if (obj == NULL || !IS_OBJ_TYPE(obj, ITEM_WEAPON))
	{
		pop_call();
		return FALSE;
	}
	
	if (get_size(ch) > obj->size)
	{
		pop_call();
		return TRUE;
	}
	else if (get_size(ch) == obj->size && WSPEC(obj, WSPEC_LIGHT))
	{
		pop_call();
		return TRUE;
	}
	else
	{
		pop_call();
		return FALSE;
	}
}


/*
 * Find the range between ch and victim for ranged attack
 */
int get_range(CHAR_DATA *ch, CHAR_DATA *victim)
{
	int range;
	
	push_call("get_range(%p,%p)",ch,victim);
	
	if (ch == NULL || victim == NULL)
	{
		bug("get_range: NULL char!",0);
		pop_call();
		return 0;
	}
	
	for (range = 0 ; range < MAX_VNUM ; range++)
	{
		if (findpath_search_victim(ch, victim, range) == -1)
			continue;
			
		wiz_printf_room(ch, "get_range(%s,%s): range: %d\n\r", get_name(ch), get_name(victim), range);
		wiz_printf_room(victim, "get_range(%s,%s): range: %d\n\r", get_name(ch), get_name(victim), range);
		
		pop_call();
		return range;
	}
	pop_call();
	return -1;
}

/* returns weapon proficiency for a wielded weapon - Kregor 2/18/07 */
int weapon_skill( CHAR_DATA *ch, OBJ_DATA *wield )
{
	int prof;

	push_call("weapon_skill(%p,%p)",ch,wield);

	/* needs a safety in here */
	if (wield != NULL && !IS_WEAPON(wield) && !IS_AMMO(wield))
	{
		pop_call();
		return 0;
	}
	// treat NULL as unarmed strike - Kregor
	if (wield == NULL)
	{
		prof = learned(ch, gsn_unarmed_strike);
	}
	else
	{
		switch (wield->value[0])
		{
			default:
					bug("weapon_skill: bad weapon type", 0);
					prof = 0;
					break;
			case WEAPON_TYPE_AXE_DOUBLE:
					prof = learned(ch,gsn_weapon_double_axe);
					if ((ch->race == RACE_ORC || ch->race == RACE_HALFORC) && learned(ch, gsn_weapon_prof_martial))
					{
						SET_BIT(prof, WSKILL_PROFICIENT);
					}
					break;
			case WEAPON_TYPE_HANDAXE:
					prof = (learned(ch,gsn_weapon_handaxe));
					break;
			case WEAPON_TYPE_AXE_THROWING:
					prof = (learned(ch,gsn_weapon_throwing_axe));
					break;
			case WEAPON_TYPE_BATTLEAXE:
					prof = (learned(ch,gsn_weapon_battleaxe));
					break;
			case WEAPON_TYPE_BOLA:
					prof = (learned(ch,gsn_weapon_bola));
					break;
			case WEAPON_TYPE_CHAIN_SPIKED:
					prof = (learned(ch,gsn_weapon_spiked_chain));
					break;
			case WEAPON_TYPE_CLUB:
					prof = (learned(ch,gsn_weapon_club));
					break;
			case WEAPON_TYPE_CROSSBOW_HAND:
					prof = (learned(ch,gsn_weapon_crossbow_hand));
					break;
			case WEAPON_TYPE_CROSSBOW_HEAVY:
					prof = (learned(ch,gsn_weapon_crossbow_heavy));
					break;
			case WEAPON_TYPE_CROSSBOW_LIGHT:
					prof = (learned(ch,gsn_weapon_crossbow_light));
					break;
			case WEAPON_TYPE_DAGGER:
			case WEAPON_TYPE_KNIFE:
					prof = (learned(ch,gsn_weapon_short_blade));
					break;
			case WEAPON_TYPE_DART:
					prof = (learned(ch,gsn_weapon_dart));
					break;
			case WEAPON_TYPE_FALCHION:
					prof = (learned(ch,gsn_weapon_falchion));
					break;
			case WEAPON_TYPE_FLAIL:
			case WEAPON_TYPE_FLAIL_HEAVY:
					prof = (learned(ch,gsn_weapon_flail));
					break;
			case WEAPON_TYPE_FLAIL_DIRE:
					prof = (learned(ch,gsn_weapon_flail_dire));
					break;
			case WEAPON_TYPE_GLAIVE:
					prof = (learned(ch,gsn_weapon_glaive));
					break;
			case WEAPON_TYPE_GREATAXE:
					prof = (learned(ch,gsn_weapon_greataxe));
					break;
			case WEAPON_TYPE_GREATCLUB:
					prof = (learned(ch,gsn_weapon_greatclub));
					break;
			case WEAPON_TYPE_GREATSWORD:
					prof = (learned(ch,gsn_weapon_greatsword));
					break;
			case WEAPON_TYPE_GUISARME:
					prof = (learned(ch,gsn_weapon_guisarme));
					break;
			case WEAPON_TYPE_HALBERD:
					prof = (learned(ch,gsn_weapon_halberd));
					break;
			case WEAPON_TYPE_HAMMER_LIGHT:
			case WEAPON_TYPE_WARHAMMER:
					prof = (learned(ch,gsn_weapon_hammer));
					break;
			case WEAPON_TYPE_JAVELIN:
					prof = (learned(ch,gsn_weapon_javelin));
					break;
			case WEAPON_TYPE_KAMA:
					prof = (learned(ch,gsn_weapon_kama));
					break;
			case WEAPON_TYPE_KUKRI:
					prof = (learned(ch,gsn_weapon_kukri));
					break;
			case WEAPON_TYPE_LANCE:
					prof = (learned(ch,gsn_weapon_lance));
					break;
			case WEAPON_TYPE_LONGBOW:
					prof = (learned(ch,gsn_weapon_longbow));
					break;
			case WEAPON_TYPE_LONGBOW_COMPOSITE:
					prof = (learned(ch,gsn_weapon_longbow_composite));
					break;
			case WEAPON_TYPE_LONGSPEAR:
			case WEAPON_TYPE_SHORTSPEAR:
			case WEAPON_TYPE_SPEAR:
					prof = (learned(ch,gsn_weapon_spear));
					break;
			case WEAPON_TYPE_MACE_HEAVY:
			case WEAPON_TYPE_MACE_LIGHT:
					prof = (learned(ch,gsn_weapon_mace));
					break;
			case WEAPON_TYPE_MORNINGSTAR:
					prof = (learned(ch,gsn_weapon_morningstar));
					break;
			case WEAPON_TYPE_NUNCHAKU:
					prof = (learned(ch,gsn_weapon_nunchaku));
					break;
			case WEAPON_TYPE_PICK_HEAVY:
			case WEAPON_TYPE_PICK_LIGHT:
					prof = (learned(ch,gsn_weapon_pick));
					break;
			case WEAPON_TYPE_QUARTERSTAFF:
					prof = (learned(ch,gsn_weapon_quarterstaff));
					break;
			case WEAPON_TYPE_RANSEUR:
					prof = (learned(ch,gsn_weapon_ranseur));
					break;
			case WEAPON_TYPE_RAPIER:
					prof = (learned(ch,gsn_weapon_rapier));
					break;
			case WEAPON_TYPE_SAI:
					prof = (learned(ch,gsn_weapon_sai));
					break;
			case WEAPON_TYPE_SAP:
					prof = (learned(ch,gsn_weapon_sap));
					break;
			case WEAPON_TYPE_SCIMITAR:
					prof = (learned(ch,gsn_weapon_scimitar));
					break;
			case WEAPON_TYPE_SCYTHE:
					prof = (learned(ch,gsn_weapon_scythe));
					break;
			case WEAPON_TYPE_SHORTBOW:
					prof = (learned(ch,gsn_weapon_shortbow));
					break;
			case WEAPON_TYPE_SHORTBOW_COMPOSITE:
					prof = (learned(ch,gsn_weapon_shortbow_composite));
					break;
			case WEAPON_TYPE_SHURIKEN:
					prof = (learned(ch,gsn_weapon_shuriken));
					break;
			case WEAPON_TYPE_CHAKRAM:
					prof = (learned(ch,gsn_weapon_chakram));
					break;
			case WEAPON_TYPE_SIANGHAM:
					prof = (learned(ch,gsn_weapon_siangham));
					break;
			case WEAPON_TYPE_SICKLE:
					prof = (learned(ch,gsn_weapon_sickle));
					break;
			case WEAPON_TYPE_SLING:
					prof = (learned(ch,gsn_weapon_sling));
					break;
			case WEAPON_TYPE_SWORD_BASTARD:
					prof = (learned(ch,gsn_weapon_sword_bastard));
					break;
			case WEAPON_TYPE_SWORD_LONG:
					prof = (learned(ch,gsn_weapon_sword_long));
					break;
			case WEAPON_TYPE_SWORD_SHORT:
					prof = (learned(ch,gsn_weapon_sword_short));
					break;
			case WEAPON_TYPE_SWORD_TWO_BLADED:
					prof = (learned(ch,gsn_weapon_sword_double));
					break;
			case WEAPON_TYPE_TRIDENT:
					prof = (learned(ch,gsn_weapon_trident));
					break;
			case WEAPON_TYPE_URGROSH_DWARVEN:
					prof = (learned(ch,gsn_weapon_urgrosh));
					if (IS_RACE(ch, RACE_DWARF) && learned(ch, gsn_weapon_prof_martial))
					{
						SET_BIT(prof, WSKILL_PROFICIENT);
					}
					break;
			case WEAPON_TYPE_ELVEN_THINBLADE:
					prof = (learned(ch,gsn_weapon_thinblade));
					if (IS_RACE(ch, RACE_ELF) && learned(ch, gsn_weapon_prof_martial))
					{
						SET_BIT(prof, WSKILL_PROFICIENT);
					}
					break;
			case WEAPON_TYPE_WARAXE_DWARVEN:
					prof = (learned(ch,gsn_weapon_waraxe_dwarven));
					if (IS_RACE(ch, RACE_DWARF) && learned(ch, gsn_weapon_prof_martial))
					{
						SET_BIT(prof, WSKILL_PROFICIENT);
					}
					break;
			case WEAPON_TYPE_WHIP:
					prof = (learned(ch,gsn_weapon_whip));
					break;
		}
		/*
		 * cleric gains weapon proficiency in deity's weapon of choice
		 * cleric of War domain gets specialization in weapon - Kregor
		 */
		if (class_level(ch, CLASS_CLERIC) && ch->god && god_table[ch->god].favored_weapon == wield->value[0])
		{
			SET_BIT(prof, WSKILL_PROFICIENT);
			if (has_domain(ch, DOMAIN_WAR))
			{
				SET_BIT(prof, WSKILL_SPECIALIZED);
			}
		}
	}
	
	pop_call();
	return prof;
}


/*
	Find the ac value of armor worn. Magic plusses are added by affects - Kregor
*/
int apply_ac( CHAR_DATA *ch )
{
	OBJ_DATA *obj;
	int ac;

	push_call("apply_ac(%p,%p)",ch);
	
	// added automatic armor for NPCs
	if (IS_NPC(ch) && ch->npcdata->armor_type)
	{
		pop_call();
		return armor_table[ch->npcdata->armor_type].ac_bonus;
	}
	
	for (ac = 0, obj = ch->first_carrying ; obj ; obj = obj->next_content)
	{
		if (!IS_WORN(obj))
			continue;

		if ( obj->item_type != ITEM_ARMOR )
			continue;

		if ( obj->value[0] == ARMOR_TYPE_CLOTH )
			continue;

// 		if (get_layer(obj) != LAYER_ARMOR)
// 			continue;
// 	
		switch ( obj->wear_loc )
		{
			case WEAR_BODY:
				ac += 30 * armor_table[obj->value[0]].ac_bonus;
					break;
			case WEAR_HEAD:
				ac += 20 * armor_table[obj->value[0]].ac_bonus;
					break;
			case WEAR_LEGS:
				ac += 20 * armor_table[obj->value[0]].ac_bonus;
					break;
			case WEAR_FEET:
				ac += 5 * armor_table[obj->value[0]].ac_bonus;
					break;
			case WEAR_ARMS:
				ac += 20 * armor_table[obj->value[0]].ac_bonus;
					break;
			case WEAR_HANDS:
				ac += 5 * armor_table[obj->value[0]].ac_bonus;
					break;
			default:
				ac += 0;
				break;
		}
	}
	ac /= 100;
	
	pop_call();
	return ac;
}

/*
 * Calculate base AC of ch
 * Added argument for touch attacks
 */
int GET_AC(CHAR_DATA *ch, bool touch)
{
	int AC, armor, deflect, natural;

	push_call("GET_AC(%p)",ch);
	
	AC = armor = deflect = natural = 0;

	if (!touch)
	{
		armor = UMAX(get_apply(ch, APPLY_ARMOR), ch->armor);
		if (!IS_NPC(ch) || (IS_NPC(ch) && !ch->race))
			natural = race_table[get_race(ch)].nat_armor;
		else if (IS_NPC(ch))
			natural = ch->pIndexData->nat_armor;
		natural += ch->nat_armor;
		natural = UMAX(get_apply(ch, APPLY_NATURAL_AC), natural);
	}

	deflect = UMAX(get_apply(ch, APPLY_DEFLECT), race_table[get_race(ch)].deflection);
	if (domain_apotheosis(ch, DOMAIN_PROTECTION))
		deflect = stat_bonus(TRUE, ch, APPLY_WIS);

	// incorporeals gain deflection AC == their CHA bonus - Kregor
	if (IS_INCORPOREAL(ch))
		deflect = UMAX(deflect, stat_bonus(TRUE, ch, APPLY_CHA));

	AC = armor;
	AC += deflect;
	AC += natural;
	AC += get_apply(ch, APPLY_ENHANCE_AC); // takes greater of magic armor vs. Magic Vestment affect - Kregor
	
	if (class_level(ch, CLASS_MONK) && armor_type_worn(ch) == ARMOR_NONE
	&& get_eq_char(ch, WEAR_SHIELD) == NULL && !IS_ENCUMBERED(ch) && !IS_HELPLESS(ch))
	{
		AC += class_level(ch, CLASS_MONK) / 5;
		AC += stat_bonus(TRUE, ch, APPLY_WIS);
	}
	
	if (is_affected(ch, gsn_defensive_stance) && IS_AWAKE(ch))
	{
		AC += stat_bonus(TRUE, ch, APPLY_CON);
	}
	
	switch (get_size(ch))
	{
		case SIZE_FINE:
			AC += 8;
			break;		
		case SIZE_DIMINUTIVE:
			AC += 4;
			break;		
		case SIZE_TINY:
			AC += 2;
			break;				
		case SIZE_SMALL:
			AC += 1;
			break;		
		case SIZE_LARGE:
			AC -= 1;
			break;			
		case SIZE_HUGE:
			AC -= 2;
			break;				
		case SIZE_GARGANTUAN:
			AC -= 4;
			break;		
		case SIZE_COLOSSAL:
			AC -= 8;
			break;		
	}
	
	if (IS_AFFECTED(ch, AFF2_STUNNED))
		AC -= 2;

	// AC penalty for a charge
	if (IS_SET(ch->attack, ATTACK_CHARGE))
		AC -= 2;
		
	pop_call();
	return(AC+10);
}


/* 
 * adds shield bonus against immediate opponent
 */
int shield_bonus( CHAR_DATA *ch )
{
	int shield;
	OBJ_DATA *obj;
	
	push_call("shield_bonus(%p)",ch);

	if (IS_HELPLESS(ch))
	{
		pop_call();
		return get_apply(ch, APPLY_SHIELD);
	}
	if (is_affected(ch, gsn_irresistible_dance))
	{
		pop_call();
		return get_apply(ch, APPLY_SHIELD);
	}
	shield = 0;

	if ((obj = get_eq_char(ch, WEAR_SHIELD)) != NULL && IS_OBJ_TYPE(obj, ITEM_ARMOR))
	{
		shield = armor_table[obj->value[0]].ac_bonus;			
		shield += obj->apply[APPLY_ENHANCE_AC];
		
		if (learned(ch, gsn_shield_specialization))
		{
			if (learned(ch, gsn_active_shield_defense)
			&& COMBATMODE(ch, COMBAT_DEFENSIVE) && IS_SET(ch->attack, ATTACK_MELEE))
				shield += 4;
			else
				shield += 1;
		}
		if (IS_SET(ch->attack, ATTACK_SHIELD_BASH) && !learned(ch, gsn_imp_shield_bash))
			shield = 0;	
	}
	else if (get_wield(ch, TRUE) != NULL && learned(ch, gsn_two_weapon_defense))
	{
		if (IS_SET(ch->combatmode, COMBAT_DEFENSIVE) && IS_SET(ch->attack, ATTACK_MELEE))
		{
			shield = 2;
		}
		else
		{
			shield = 1;
		}
	}
	
	shield = UMAX(get_apply(ch, APPLY_SHIELD), shield);

	pop_call();
	return(shield);
}
	
/*
 * Dodge bonuses applied if can_dodge
 */
int dodge_bonus( CHAR_DATA *ch )
{
	int dodge = 0;
	int dex_max = dex_bonus_max(ch);
	
	push_call("dodge_bonus(%p)",ch);
		
	dodge = stat_bonus(TRUE, ch, APPLY_DEX);
	if (dex_max != -1)
		dodge = UMIN(dodge, dex_max);
	dodge += get_apply(ch, APPLY_DODGE);
	dodge += get_apply(ch, APPLY_INS_AC);
	
	if (learned(ch, gsn_dodge))
	{
		dodge += 1;	
		if (get_monk_style(ch) == STYLE_COBRA_STRIKE && class_level(ch, CLASS_MONK) >= 12)
			dodge += 1;
	}
	
	if (learned(ch, gsn_canny_defense) && armor_type_worn(ch) == ARMOR_NONE
	&& get_eq_char(ch, WEAR_SHIELD) == NULL)
	{
		dodge += UMIN(stat_bonus(TRUE, ch, APPLY_INT), multi_class_level(ch, gsn_canny_defense));
	}

	if (is_mounted(ch) && learned(ch->master, gsn_mounted_defense))
		dodge += URANGE(0, learned(ch->master, gsn_mount), stat_bonus(TRUE, ch->master, APPLY_DEX));

	if (IS_SET(ch->combatmode, COMBAT_DEFENSIVE) && !MISSILE_WIELD(ch) && IS_SET(ch->attack, ATTACK_MELEE))
	{
		dodge += (learned(ch, gsn_combat_expertise) ? UMIN(stat_bonus(TRUE, ch, APPLY_DEX), base_attack(ch)) : 2);
		if (learned(ch, gsn_tumble) >= 5)
		{
			dodge += 1;
			if (learned(ch, gsn_combat_expertise))
				dodge += 1;
		}
		if (learned(ch, gsn_elaborate_defense))
			dodge += multi_class_level(ch, gsn_elaborate_defense) / 2;
	}
			
	pop_call();
	return dodge;
}

/*
 * Dodge penalties still apply if !can_dodge
 */
int dodge_penalty( CHAR_DATA *ch )
{
	int dodge = 0;
	
	push_call("dodge_penalty(%p)",ch);
		
	dodge += stat_bonus(TRUE, ch, APPLY_DEX);
	dodge += get_apply(ch, APPLY_DODGE);
	
	pop_call();
	return UMIN(dodge, 0);
}


/*
 * Does the character have reach weapon? - Kregor
 */
bool has_reach( CHAR_DATA *ch, OBJ_DATA *wield )
{
	push_call("has_reach(%p,%p)",ch,wield);
	
	if (!wield)
	{
		pop_call();
		return FALSE;
	}
	if (WSPEC(wield, WSPEC_REACH))
	{
		pop_call();
		return TRUE;
	}
	pop_call();
	return FALSE;
}


/*
 * Qualifiers for evasion or imp. evasion feat
 */
bool can_evade( CHAR_DATA *ch )
{
	push_call("can_evade(%p)",ch);
	
	if (!IS_HELPLESS(ch))
	{
		pop_call();
		return FALSE;
	}
	if (!learned(ch, gsn_evasion))
	{
		if (!learned(ch, gsn_unstoppable_rage) || !IS_AFFECTED(ch, AFF2_BERSERK))
		{
			pop_call();
			return FALSE;
		}
	}
	if (armor_type_worn(ch) >= ARMOR_MEDIUM)
	{
		pop_call();
		return FALSE;
	}
	if (IS_AFFECTED(ch, AFF2_STUNNED))
	{
		pop_call();
		return FALSE;
	}
	if (IS_FLATFOOTED(ch) && !learned(ch,gsn_uncanny_dodge))
	{
		pop_call();
		return FALSE;
	}
	if (!IS_AWAKE(ch))
	{
		pop_call();
		return FALSE;
	}
	if (IS_OVERLOADED(ch))
	{
		pop_call();
		return FALSE;
	}
	pop_call();
	return TRUE;
}


/*
 * Returns the total Base Attack bonus for all classes - Kregor 11/26/06
 */
int base_attack( CHAR_DATA *ch )
{
	int bab, lvl, cnt, type;

	push_call("base_attack(%p)",ch);

	lvl = ch->level;
	bab = 0;

	if (is_affected(ch, gsn_transformation) || is_affected(ch, gsn_divine_power))
	{
		pop_call();
		return lvl;
	}
	if (!IS_CLASSED(ch))
	{
		type = race_type(ch);
		if (race_type_table[type].base_attack == BAB_BEST)
			bab += lvl;
		else if (race_type_table[type].base_attack == BAB_POOR)
			bab += lvl / 2;
		else
			bab += lvl * 3 / 4;
	}
	else
	{
		float bab_hi, bab_md, bab_lo;

		for (cnt = bab_lo = bab_md = bab_hi = 0 ; cnt < MAX_CLASS ; cnt++)
		{
			if (lvl <= 0)
				break;
				
			if (class_level(ch, cnt) > 0)
			{
				if (cnt == CLASS_MONSTER)
				{
					type = race_type(ch);
					if (race_type_table[type].base_attack == BAB_BEST)
						bab_hi += UMIN(class_level(ch, cnt), lvl);
					else if (race_type_table[type].base_attack == BAB_POOR)
						bab_lo += UMIN(class_level(ch, cnt), lvl);
					else
						bab_md += UMIN(class_level(ch, cnt), lvl);
				}
				else if (cnt == CLASS_CLERIC && domain_apotheosis(ch, DOMAIN_WAR))
					bab_hi += UMIN(class_level(ch, cnt), lvl);
				else if (class_table[cnt].base_attack == BAB_POOR)
					bab_lo += UMIN(class_level(ch, cnt), lvl);
				else if (class_table[cnt].base_attack == BAB_GOOD)
					bab_md += UMIN(class_level(ch, cnt), lvl);
				else if (class_table[cnt].base_attack == BAB_BEST)
					bab_hi += UMIN(class_level(ch, cnt), lvl);

				lvl -= UMIN(class_level(ch, cnt), lvl);
			}
		}
		bab = 0;
		if (bab_hi > 0)
			bab += bab_hi;
		if (bab_md > 0)
			bab += bab_md * 3 / 4;
		if (bab_lo > 0)
			bab += bab_lo / 2;
	}
	pop_call();
	return(bab);
}