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

/***************************************************************************
 * mob_ai.c
 *
 * Of note with these new AI functions: Much has yet to be factored into the
 * combat AI. Mobiles now have mana, and do not get to cast unlimited spells
 * as in other Merc's spec_funs. There are separate AI functions for idle
 * and combat, and all of them must work seamlessly with the mob prog engine.
 * For the most part, the old spec_fun system is deprecated, and is mainly
 * used more like an extra set of ACT flags for a mobile to determine its
 * roles and functions in an area.
 *
 * Extensive commenting and documentation should be made in each AI function
 * so there is no guesswork as to why a certain check or function is there.
 *
 * One basic premise to determine AI is this: while mobiles can be multiclass,
 * one class is considered its "favored" class. The class that is hard set
 * in the mobile's "class" field is considered that class. Its base AI, thus,
 * will be determined on that premise.
 ***************************************************************************/

#include "mud.h"


/*
 * Old specials functions still in use
 */
 
struct spec_fun_type
{
	char *name;
	SPEC_FUN *function;
};

const struct spec_fun_type spec_fun[]=
{
	{"spec_executioner",			spec_executioner},
	{"spec_fido",							spec_fido},
	{"spec_guard",						spec_guard},
	{"spec_janitor",					spec_janitor},
	{"spec_thief",						spec_thief},
	{"",											NULL}
};

/*
	Given a name, Return the appropriate spec fun.
*/

SPEC_FUN *spec_lookup( const char *name )
{
	int i;

	push_call("spec_lookup(%p)",name);

	for (i = 0 ; is_string(spec_fun[i].name) ; i++)
	{
		if (!strcasecmp(name, spec_fun[i].name))
		{
			pop_call();
			return spec_fun[i].function;
		}
	}
	pop_call();
	return NULL;
}

/*
	Given a spec function, Return the appropriate name.
*/

char *spec_name_lookup( SPEC_FUN *fun )
{
	int i;

	push_call("spec_name(%p)",fun);

	for (i = 0 ; is_string(spec_fun[i].name) ; i++)
	{
		if (spec_fun[i].function == fun)
		{
			pop_call();
			return spec_fun[i].name;
		}
	}
	pop_call();
	return 0;
}

/*
 * Use for particularly nasty guard, or bounty hunters
 * who only aim to kill criminals - Kregor
 */
bool spec_executioner( CHAR_DATA *ch )
{
	char buf[MAX_STRING_LENGTH];
	CHAR_DATA *victim;
	CHAR_DATA *v_next;
	char *crime;

	if ( !IS_AWAKE(ch) || in_combat(ch) || IS_SET( ch->in_room->room_flags, ROOM_SAFE))
	{
		pop_call();
		return FALSE;
	}
	crime = "";
	for ( victim = ch->in_room->first_person; victim != NULL; victim = v_next )
	{
		v_next = victim->next_in_room;
		
		if (IS_NPC(victim))
			continue;

		if ( IS_PLR(victim, PLR_KILLER) )
		{
			crime = "killer"; break;
		}
		if ( IS_PLR(victim, PLR_THIEF) )
		{
			crime = "thief"; break;
		}
		else if (get_bounty(victim->pcdata->pvnum) != NULL)
		{
			crime = "criminal"; break;
		}
	}
	if ( victim == NULL )
	{
		pop_call();
		return FALSE;
	}
	sprintf( buf, "Your head is mine, %s", crime );
	do_shout( ch, buf );
	fight( ch, victim );

	pop_call();
	return TRUE;
}


bool spec_fido( CHAR_DATA *ch )
{
	OBJ_DATA *corpse;

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

	for (corpse = ch->in_room->first_content ; corpse ; corpse = corpse->next_content)
	{
		if (corpse->item_type != ITEM_CORPSE_NPC)
		{
			continue;
		}
		act( "$n savagely devours a corpse.", ch, NULL, NULL, TO_ROOM);

		while (corpse->first_content)
		{
			corpse->first_content->sac_timer = OBJ_SAC_TIME;

			obj_to_room(corpse->first_content, ch->in_room->vnum);
		}
		junk_obj( corpse );

		pop_call();
		return TRUE;
	}
	pop_call();
	return FALSE;
}

/*
 * Heavily modified original spec fun to include area justice system. - Kregor
 */
bool spec_guard( CHAR_DATA *ch )
{
	CHAR_DATA *victim;
	CHAR_DATA *v_next;
	CHAR_DATA *ech;
	BOUNTY_DATA *bounty;
	bool align_diff;

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

	if (!IS_AWAKE(ch) || in_combat(ch) || IS_SET(ch->in_room->room_flags, ROOM_SAFE))
	{
		pop_call();
		return FALSE;
	}
	if (IS_AFFECTED(ch, AFF_DOMINATE) || IS_AFFECTED(ch, AFF2_CHARMED))
	{
		pop_call();
		return FALSE;
	}
	align_diff = FALSE;
	ech      = NULL;

	for (victim = ch->in_room->first_person ; victim ; victim = v_next)
	{
		v_next = victim->next_in_room;

		if (!can_see(ch, victim))
			continue;

// 		if (IS_PLR(victim, PLR_KILLER|PLR_THIEF))
// 		{
// 			if (number_percent() < 20)
// 			{
// 				do_shout(ch, format("There is a %s at %s! Alarm!", IS_PLR(victim, PLR_KILLER) ? "killer" : "thief", ch->in_room->name));
// 				break;
// 			}
// 		}
		if ((bounty = get_record(victim, ch->pIndexData->area, -1)) != NULL)
		{
			if(number_percent() < 50) // guards aren't always perceptive - Kregor
			{
				if (!in_combat(victim)) // if not in comabat, execute punishments - Kregor
				{
					if (bounty->punishment == PUNISHMENT_DEATH)
					{
						do_shout(ch, format("There is a criminal at %s! Alarm!", ch->in_room->name));
						break;
					}
					if (bounty->punishment == PUNISHMENT_SEVER)
					{
						do_sayto(ch, format("%s I am carrying out your punishment for %s!", victim->name, crime_titles[bounty->crime]));
						sever_limb(ch, victim, CAN_WEAR_HANDS);
						remove_bounty(bounty);
						save_bounties();					
						pop_call();
						return TRUE;
					}
					if (bounty->punishment == PUNISHMENT_CONFISCATE)
					{
						if (confiscate(ch, victim))
						{
							remove_bounty(bounty);
							save_bounties();					
							pop_call();
							return TRUE;
						}
					}
					if (bounty->punishment == PUNISHMENT_JAIL)
					{
						int jailroom;
						
						do_shout(ch, format("There is a criminal at %s! Alarm!", ch->in_room->name));
	
						if ((jailroom = ch->pIndexData->area->dungeon) == 0)
						{
							if ((jailroom = ch->pIndexData->area->courtroom) == 0)
							{
								log_build_printf(ch->pIndexData->vnum, "collect_bounty: dungeon and courtroom not set for area.");
								pop_call();
								return TRUE;
							}
						}
						act("$n and $s contingent swarm and subdue you, dragging you off!", ch, NULL, victim, TO_VICT);
						act("$n and $s contingent swarm and subdue $N, dragging $M off!", ch, NULL, victim, TO_NOTVICT);
	
						char_from_room(victim);
						char_to_room(victim, jailroom, TRUE);
						
						remove_bounty(bounty);
						save_bounties();					
						pop_call();
						return TRUE;
					}
				}
				else // otherwise, join against criminal - Kregor
				{
					do_shout(ch, format("There is a criminal at %s! Alarm!", ch->in_room->name));
					break;
				}
			}
		}
		if ( in_combat(victim) )
		{
			if (IS_CITIZEN(victim))
			{
				continue;
			}
			if (IS_NPC(victim) && victim->pIndexData == ch->pIndexData)
			{
				continue;
			}
			if ((ech = who_fighting(victim)) != NULL)
			{
				if (IS_CITIZEN(ech))
				{
					do_shout(ch, format("There is an attacker at %s! Alarm!", ch->in_room->name));
					if (get_record(victim, ch->pIndexData->area, CRIME_ASSAULT))
						set_bounty(ech, victim, CRIME_ASSAULT);
					break;
				}
				if (!IS_NPC(victim) && !IS_NPC(ech))
				{
					victim = NULL;
					break;
				}
			}
			if ( number_percent() < OPPOSITE_ALIGN( ch, victim ) )
			{
				align_diff = TRUE;
				ech      = victim;
			}
		}
	}
	if ( victim != NULL )
	{
		fight( ch, victim );
		pop_call();
		return TRUE;
	}

	if ( ech != NULL )
	{
		if (in_combat(ech) && !align_diff && number_percent() < 75)
		{
			do_say(ch, "Alright you miscreants, break it up NOW!");
			act("$n muscles $s way in to stop the fighting.", ch, NULL, NULL, TO_ROOM);
			for ( victim = ch->in_room->first_person ; victim != NULL ; victim = v_next )
			{
				v_next = victim->next_in_room;

				if (in_combat(victim))
					clean_combat(victim->in_battle);
			}
			pop_call();
			return TRUE;
		}
		do_shout( ch, format("Alarm! There is an attacker at %s", ch->in_room->name)	);
		fight( ch, ech );		
		pop_call();
		return TRUE;
	}
	pop_call();
	return FALSE;
}

bool spec_janitor( CHAR_DATA *ch )
{
	OBJ_DATA *trash;
	OBJ_DATA *trash_next;

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

	if ( !IS_AWAKE(ch) )
	{
		pop_call();
		return FALSE;
	}
	for ( trash = ch->in_room->first_content; trash != NULL; trash = trash_next )
	{
		trash_next = trash->next_content;
		if ( !IS_SET( trash->wear_flags, CAN_WEAR_TAKE ) )
		{
			continue;
		}
		if ( trash->item_type == ITEM_DRINK_CON
			|| trash->item_type == ITEM_TRASH
			|| trash->cost < 11 )
		{
			act( "$n picks up some trash.", ch, NULL, NULL, TO_ROOM );
			obj_from_room( trash );
			obj_to_char( trash, ch );
			if (trash->cost < 1)
				junk_obj(trash);

			pop_call();
			return TRUE;
		}
	}
	pop_call();
	return FALSE;
}

bool spec_thief( CHAR_DATA *ch )
{
	CHAR_DATA *victim;
	CHAR_DATA *v_next;
	int gold, roll, chance;

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

	if ( !in_combat(ch) )
	{
		if (ch->hit < get_max_hit(ch) / 4)
		{
			act( "$n skulks off to nurse $s wounds.", ch, NULL, NULL, TO_ROOM);
			char_from_room(ch);
			char_to_room(ch, 3, TRUE);
			pop_call();
			return FALSE;
		}
		else
		{
			if ( ch->position != POS_STANDING && ch->pIndexData->position == POS_STANDING )
			{	
				do_stand(ch, "");
			}
		}
	}

	if ( ch->position != POS_STANDING )
	{
		pop_call();
		return FALSE;
	}
	
	if (IS_AWAKE(ch) && !IS_AFFECTED(ch, AFF_HIDE))
	{
		act( "$n {108}slips quietly into the shadows.", ch, NULL, NULL, TO_ROOM);
		SET_BIT(ch->affected_by, AFF_HIDE);
	}
	if (!IS_AFFECTED(ch, AFF_SNEAK))
	{
		SET_BIT(ch->affected_by, AFF_SNEAK);
	}

	chance = number_bits(6);	
	
	for ( victim = ch->in_room->first_person; victim != NULL; victim = v_next )
	{
		v_next = victim->next_in_room;

		if ( chance != 0 || IS_NPC(victim) || /* victim->level >= LEVEL_IMMORTAL || */!can_see( ch, victim ) )
		{
			if ( chance == 1 && can_see( victim, ch ) )
			{
				act( "$n glances around, flexing $s hands...", ch, NULL, victim, TO_VICT);
			}		
			continue;
		}
				
		if ( victim->gold <= 0 || victim->level <= 3 )
		{
			continue;
		}
		
		roll = sleight_of_hand_roll(ch);
		
		if ( sleight_of_hand_check(ch, victim, roll, 20) )
		{
			gold = victim->gold * number_range( 1, 5 ) / 100;
			gold = UMIN( gold, 5000 );
			ch->gold += gold;
			gold_transaction(victim, 0 - gold);
			if ( IS_AWAKE(victim) && perception_check(victim, ch, perception_roll(victim, SENSE_TOUCH), roll ) )
			{
				act( "You discover $n's hands in your money purse!", ch, NULL, victim, TO_VICT );
				act( "$N discovers $n's hands in $S money purse!", ch, NULL, victim, TO_NOTVICT );
			}
			pop_call();
			return TRUE;
		}
		else
		{
			if ( IS_AWAKE(victim) && perception_check(victim, ch, perception_roll(ch, SENSE_SIGHT), roll ) )
			{
				act( "You discover $n reaching for your money purse!", ch, NULL, victim, TO_VICT );
				act( "$n is caught reaching for $N's money purse!", ch, NULL, victim, TO_NOTVICT );
			}
			pop_call();
			return TRUE;
		}
	}
	pop_call();
	return FALSE;
}


/*
 * Anti-tank by Cronel (supfly@geocities.com), greatly modified,
 * giving credit where credit is due :)
 *
 * This is called from mobile_turn, for mobs fighting PCs only;
 * ch is the mob, victim is the player. Returns TRUE if "victim"
 * has changed, FALSE otherwise.
 * The code simply checks if there's someone else fighting this
 * mob in the room, and if this person is under 5 levels from the
 * one the mob is currently fighting, then powerlevel tanking has
 * been detected. The mobile will focus on the lowbie instead.
 * While not disallowing unbalanced tanking, this will make it
 * more of a challenge worth the XP and require the tank to be
 * very vigilant!
 */
CHAR_DATA *check_tanking( CHAR_DATA *ch, CHAR_DATA *victim )
{
	CHAR_DATA *lowbie;

	push_call("check_tanking(%p,%p)",ch,victim);
	
	if (!who_fighting(ch) || who_fighting(ch) != victim)
	{
		pop_call();
		return victim;
	}
	/* if the tank is a sneak attacker, don't turn away, that would be stupid */
	if (learned(victim, gsn_backstab))
	{
		pop_call();
		return victim;
	}
	
	for (lowbie = ch->in_room->first_person ; lowbie ; lowbie = lowbie->next_in_room)
	{
		if (lowbie == ch || lowbie == victim || IS_NPC(lowbie))
			continue;
			
		// if lowbie is fighting at range, can't attack him
		if (!IS_SET(lowbie->attack, ATTACK_MELEE))
			continue;

		if (who_fighting(lowbie) == ch && lowbie->level < victim->level - 5)
		{
			act( "$n turns to fight $N.", ch, NULL, lowbie, TO_NOTVICT );
			act( "$n turns to fight YOU!", ch, NULL, lowbie, TO_VICT );
			pop_call();
			return lowbie;
		}

	}
	pop_call();
	return victim;
}


 /*
 * Queries whether mobile has mana to cast
 * a spell in the class he casts it in - Kregor
 */
bool has_mana(CHAR_DATA *ch, int sn)
{
	int class, circle;

	push_call("has_mana(%p,%p)",ch,sn);
	
	if (!is_spell(sn))
	{
		bug("has_mana: not a spell!", 0);
		pop_call();
		return FALSE;
	}
	
	if ((class = multi(ch, sn)) == -1)
	{
		if (IS_NPC(ch))
		{
			bug("has_mana: mobile %d cannot cast spell.", ch->pIndexData->vnum);
			pop_call();
			return FALSE;
		}
		else
		{
			pop_call();
			return FALSE;
		}
	}
	
	if ((circle = get_spell_circle(ch, sn)) == -1)
	{
		if (IS_NPC(ch))
		{
			bug("has_mana: mobile %d cannot cast spell.", ch->pIndexData->vnum);
			pop_call();
			return FALSE;
		}
		else
		{
			pop_call();
			return FALSE;
		}
	}
	
	if (get_mana(ch, sn, circle, class) > ch->mana[class])
	{
		if (!IS_NPC(ch))
		{
			send_to_char("You do not have enough mana.\n\r", ch);
			pop_call();
			return FALSE;
		}
		else
		{
			pop_call();
			return FALSE;
		}
	}
	pop_call();
	return TRUE;
}


/* 
 * does the char have a certain class of effect active?
 * The core purpose of this function is to keep a 
 * caster from spam casting multiple buffs or defenses
 * when he should be casting offensive spells in combat.
 */
bool has_affect(CHAR_DATA *ch, int type)
{
	AFFECT_DATA *paf, *paf_next;

	push_call("has_affect(%p,%p)",ch,type);
	
	if (!ch->first_affect)
	{
		pop_call();
		return FALSE;
	}

	for (paf = ch->first_affect ; paf != NULL ; paf = paf_next)
	{
		paf_next = paf->next;

		if (skill_table[paf->type].spell_class == type)
		{
			pop_call();
			return TRUE;
		}
	}
	pop_call();
	return FALSE;
}			
	

/*
 * get a random spell, of the right type, of the
 * 3 highest spell circles available - Kregor
 */
int get_spell(CHAR_DATA *ch, int class, int type)
{
	int circle, sn, pick;

	push_call("get_spell(%p,%p,%p)",ch,class,type);

	//first get the highest circle abailable for class
	
	for (circle = max_spell_circle(ch, class) ; circle >= 0 ; circle--)
	{
		if (get_mana(ch, -1, circle, class) <= ch->mana[class])
			break;
	}
	if (!circle)
	{
		pop_call();
		return -1;
	}
	
	// limit the number of attempts to pick a good spell
	// range should be the gsn of the lowest number spell,
	// to the gsn of the highest.
	for (pick = 0 ; pick < 100 ; pick++)
	{
		sn = number_range(gsn_acid_arrow, gsn_wraith_form);
		
		if (multi(ch, sn) != class)
			continue;
		if (skill_table[sn].spell_class != type)
			continue;
		if (skill_table[sn].skill_level[class] > circle)
			continue;
		if (skill_table[sn].skill_level[class] < UMAX(0, circle - 3))
			continue;
		break;
	}
	if (pick == 100)
	{
		pop_call();
		return -1;
	}
	pop_call();
	return sn;
}
			


/*
 * AI for spell casters during combat
 * controlled in mobile_turn in combat_handler - Kregor
 */
bool caster_AI_combat(CHAR_DATA *ch, CHAR_DATA *victim)
{
	int cls, sn;
	char buf[MAX_INPUT_LENGTH];
	CHAR_DATA *vch, *vch_next;

	push_call("caster_AI_combat(%p,%p)",ch,victim);
	
	/* if not a caster, this function is a waste of time */
	if (!is_caster(ch))
	{
		pop_call();
		return FALSE;
	}
	
	/* can't cast without a standard action, so skip of you can't take one */
	if (IS_SET(ch->action, ACTION_STANDARD))
	{
		pop_call();
		return FALSE;
	}
	
	//bard will sing first if needed and can
	if (learned(ch, gsn_bardic_song))
	{
		if (!is_affected(ch, gsn_bardic_song) && CHECK_USES(ch, gsn_bardic_song))
		{
			do_sing(ch, "");
			pop_call();
			return TRUE;
		}
	}
	
	/* default to chosen class (hard coded class) */
	cls = ch->class;
	
	/* if primary caster class is out of mana, try another */
	if (!ch->mana[ch->class])
	{
		for (cls = 0 ; cls < MAX_CORE_CLASS ; cls++)
		{
			if (class_level(ch, cls) && ch->mana[cls] > 0)
				break;
		}
	}
	
	/* unless wiz/sorcerer, fall back to melee if no mana */
	if (!ch->mana[cls] && ch->class != CLASS_WIZARD && ch->class != CLASS_SORCERER)
	{
		do_say(ch, "Awwwww, I'm outta mana!");
		pop_call();
		return FALSE;
	}
	
	/* if sorc/wiz, cast damage cantrips */
	else if (!ch->mana[cls])
	{
		switch (number_range(1,4))
		{
			case 1:
				do_cast(ch, "'electric jolt'");
				pop_call();
				return TRUE;
			case 2:
				do_cast(ch, "'acid splash'");
				pop_call();
				return TRUE;
			case 3:
				do_cast(ch, "'flame tongue'");
				pop_call();
				return TRUE;
			case 4:
				do_cast(ch, "'ray of frost'");
				pop_call();
				return TRUE;
		}
	}
	
	/* will protect himself if unprotected at battle start */
	if (!has_affect(ch, STYPE_DEFENSE))
	{
		if ((sn = get_spell(ch, cls, STYPE_DEFENSE)) != -1)
		{
			sprintf(buf, "'%s'", skill_table[sn].name);
			if (cast_spell(ch, buf, FALSE))
			{
				pop_call();
				return TRUE;
			}
		}
	}
		
	/* healers' triage */
	if (ch->class == CLASS_CLERIC || ch->class == CLASS_DRUID
	|| ch->class == CLASS_PALADIN || ch->class == CLASS_RANGER)
	{
		/* If hurt, healer heal thyself */
		if (ch->hit < get_max_hit(ch) / 2)
		{
			if ((sn = get_spell(ch, ch->class, STYPE_HEAL)) != -1)
			{
				sprintf(buf, "'%s'", skill_table[sn].name);
				if (cast_spell(ch, buf, FALSE))
				{
					pop_call();
					return TRUE;
				}
			}
		}
		/* divine casters have 50/50 chance of just going into combat */
		if (number_bits(1) == 0)
		{
			pop_call();
			return FALSE;
		}
		if (ch->class == CLASS_CLERIC || ch->class == CLASS_DRUID)
		{
			/* else, check for injured comrades */
			for (vch = ch->in_room->first_person ; vch ; vch = vch_next)
			{
				vch_next = vch->next;
				
				if (is_same_group(vch, ch) && vch->hit < get_max_hit(vch) / 3)
				{
					if ((sn = get_spell(ch, ch->class, STYPE_HEAL)) != -1)
					{
						sprintf(buf, "'%s' %s", skill_table[sn].name, short_to_name(get_name(vch), 1));
						if (cast_spell(ch, buf, FALSE))
						{
							pop_call();
							return TRUE;
						}
					}
				}
				
				/* or maybe buff one if they need it */
				if (is_same_group(vch, ch) && number_bits(2) == 0 && !has_affect(vch, STYPE_BUFF))
				{
					if ((sn = get_spell(ch, cls, STYPE_BUFF)) != -1)
					{
						sprintf(buf, "'%s' %s", skill_table[sn].name, short_to_name(get_name(vch), 1));
						if (cast_spell(ch, buf, FALSE))
						{
							pop_call();
							return TRUE;
						}
					}
				}
			}
		}
 	}

	/* functions only trigger if there is an opponent chosen in mobile_turn */
	if (victim)
	{
		if (number_bits(2) == 0) /* debuff his opponent 1 in 4, otherwise go on offensive! */
		{
			if (has_affect(victim, STYPE_BUFF))
			{
				if ((sn = get_spell(ch, cls, STYPE_DEBUFF)) != -1)
				{
					sprintf(buf, "'%s' %s", skill_table[sn].name, short_to_name(get_name(victim), 1));
					if (cast_spell(ch, buf, FALSE))
					{
						pop_call();
						return TRUE;
					}
				}
			}
		}
		
		if (number_bits(2) == 0) /* curse opponent 1 in 4 */
		{
			if (!has_affect(victim, STYPE_CURSE))
			{
				if ((sn = get_spell(ch, cls, STYPE_CURSE)) != -1)
				{
					sprintf(buf, "'%s' %s", skill_table[sn].name, short_to_name(get_name(victim), 1));
					if (cast_spell(ch, buf, FALSE))
					{
						pop_call();
						return TRUE;
					}
				}
			}
		}

		if ((sn = get_spell(ch, cls, STYPE_ATTACK)) != -1)
		{
			sprintf(buf, "'%s' %s", skill_table[sn].name, short_to_name(get_name(victim), 1));
			if (cast_spell(ch, buf, FALSE))
			{
				pop_call();
				return TRUE;
			}
		}
	}
	pop_call();
	return FALSE;			
}

char *  const   cm_flags [] =
{
	"flurry",
	"manyshot",
	"rapidshot",
	"*",
	"*",
	"nonlethal",
	"power",
	"defensive",
	"called",
	"*",	
	"arterial",	
	"cripple",
	"*"
};

/*
 * AI for warrior classes during combat
 * controlled in mobile_turn in combat_handler - Kregor
 */
bool warrior_AI_combat(CHAR_DATA *ch, CHAR_DATA *victim)
{
	int chance;
	OBJ_DATA *wield;

	push_call("warrior_AI_combat(%p,%p)",ch,victim);
	
	if (victim == NULL)
	{
		pop_call();
		return FALSE;
	}
	
	/* get weapons for checking later */
	wield = get_wield(ch, FALSE);
	
	/* no weapon?? look for one! */
	if (!wield)
	{
		// switch hands with offhand = free action
		if ((wield = get_wield(ch, TRUE)) != NULL)
		{
			wield->wear_loc = WEAR_WIELD;
		}
		// draw another weapon in inventory = move action
		else if ((wield = get_obj_carry_type(ch, ITEM_WEAPON)) != NULL)
		{
			act("$n draws $p.", ch, wield, NULL, TO_ROOM);
			equip_char(ch, wield, WEAR_WIELD);
			if (!learned(ch, gsn_quick_draw))
				TAKE_ACTION(ch, ACTION_MOVE);
			pop_call();
			return TRUE;
		}
		// scan the ground for disarmed weapon. Grab it!
		else if (ch->pIndexData->load_eq[WEAR_WIELD] || ch->pIndexData->load_eq[WEAR_BOTH_HANDS])
		{
			for (wield = ch->in_room->first_content ; wield ; wield = wield->next_content)
			{
				if (IS_OBJ_TYPE(wield, ITEM_WEAPON))
				{
					if ((ch->pIndexData->load_eq[WEAR_WIELD] && wield->pIndexData->vnum == ch->pIndexData->load_eq[WEAR_WIELD])
					|| (ch->pIndexData->load_eq[WEAR_BOTH_HANDS] && wield->pIndexData->vnum == ch->pIndexData->load_eq[WEAR_BOTH_HANDS]))
					{
						if (get_obj(ch, wield, NULL, TRUE, FALSE))
						{
							act("$n recovers $p from the ground.", ch, wield, NULL, TO_ROOM);
							act("$n wields $p.", ch, wield, NULL, TO_ROOM);
							equip_char(ch, wield, WEAR_WIELD);
							TAKE_ACTION(ch, ACTION_MOVE);
							pop_call();
							return TRUE;
						}
					}
				}
			}
		}
	}

	/* set combat modes for NPCs that have them. */
	if (learned(ch, gsn_power_attack))
	{
		if (GET_HITROLL(ch, victim, TYPE_UNDEFINED, wield) / 2 <= calc_ac(victim, ch, FALSE))
		{
			SET_BIT(ch->combatmode, COMBAT_POWER);
		}
	}
	if (learned(ch, gsn_combat_expertise))
	{
		if (GET_HITROLL(victim, ch, TYPE_UNDEFINED, wield) / 2 <= calc_ac(ch, victim, FALSE))
		{
			SET_BIT(ch->combatmode, COMBAT_DEFENSIVE);
		}
	}
	if (learned(ch, gsn_flurry) && !wield)
	{
		SET_BIT(ch->combatmode, COMBAT_FLURRY);
	}
	if (learned(ch, gsn_arterial_strike) && number_percent() < 50)
	{
		SET_BIT(ch->combatmode, COMBAT_ARTERIAL);
	}
	else if (learned(ch, gsn_arterial_strike) && number_percent() < 50)
	{
		SET_BIT(ch->combatmode, COMBAT_CRIPPLE);
	}
	if (IS_ACT(ch, ACT_SUBDUAL))
	{
		SET_BIT(ch->combatmode, COMBAT_NONLETHAL);
	}
	if (ch->combatmode)
	{
		wiz_printf_room(ch, "warrior_AI_combat: combatmodes: %s\n\r", flag_string(ch->combatmode, cm_flags));
	}
	
	/* some special abilities here to start the battle with */
	if (learned(ch, gsn_barbarian_rage) && !IS_AFFECTED(ch, AFF2_BERSERK) && CHECK_USES(ch, gsn_barbarian_rage))
	{
		ability_rage(gsn_barbarian_rage, multi_skill_level(ch, gsn_barbarian_rage), ch, NULL, TAR_IGNORE);
		ch->uses[gsn_barbarian_rage]++;
		pop_call();
		return TRUE;
	}
	if (race_skill(ch, gsn_blood_rage) && !IS_AFFECTED(ch, AFF2_BERSERK) && ch->hit < get_max_hit(ch))
	{
		ability_rage(gsn_blood_rage, multi_skill_level(ch, gsn_blood_rage), ch, NULL, TAR_IGNORE);
		pop_call();
		return TRUE;
	}
	if (learned(ch, gsn_smite) && CHECK_USES(ch, gsn_smite))
	{
		if ((IS_GOOD(ch) && !IS_EVIL(victim))
		|| (IS_EVIL(ch) && !IS_GOOD(victim)))
		{
			if (!is_affected(victim, gsn_smite))
			{
				ability_smite(gsn_smite, multi_skill_level(ch, gsn_smite), ch, victim, TAR_CHAR_OFFENSIVE);
		
				ch->uses[gsn_smite]++;
				
				pop_call();
				return TRUE;
			}
		}
	}
	
	/* nasty rend attacks will be used half the time */
	if (race_skill(ch, gsn_rend) && number_bits(1) == 0)
	{
		if (!IS_SET(ch->action, ACTION_FULL) && rend(ch, victim))
		{
			pop_call();
			return TRUE;
		}
	}
	/* if not rend, then how about a pounce? */
	if (race_skill(ch, gsn_pounce) && number_bits(1) == 0)
	{
		if (!IS_SET(ch->action, ACTION_FULL) && pounce(ch, victim))
		{
			pop_call();
			return TRUE;
		}
	}
	
	/* 
	 * NPC will only attempt a combat maneuver
	 * if they have the feat hard set - Kregor
	 */
	if (!IS_SET(ch->attack, ATTACK_MELEE))
	{
		chance = number_range(1,10); // 50% of the time will just be a normal attack
		
		switch(chance)
		{
			case 1:
				if (disarm(ch, victim, 0))
				{
					pop_call();
					return TRUE;
				}
				break;
			case 2:
				if (trip(ch, victim, 0))
				{
					pop_call();
					return TRUE;
				}
				break;
			case 3:
				if (bash(ch, victim, 0))
				{
					pop_call();
					return TRUE;
				}
				break;
			case 4:
				if (stun(ch, victim, 0))
				{
					pop_call();
					return TRUE;
				}
				break;
			case 5:
				if (feint(ch, victim, 0))
				{
					pop_call();
					return TRUE;
				}
				break;
		}
	}
	
	if (attack(ch, victim))
	{
		pop_call();
		return TRUE;
	}
	
	pop_call();
	return FALSE;			
}

/*
 * Could use some better messages, figure out how to
 * make better, more race-appropriate messages - Kregor
 */
void found_hating( CHAR_DATA *ch, CHAR_DATA *victim )
{
	push_call("found_hating(%p,%p)",ch,victim);

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

	if (!can_see(ch, victim))
	{
		if (number_bits(2) != 0)
		{
			pop_call();
			return;
		}
		if (CAN_TALK(ch))
		{
			switch (number_bits(2))
			{
				case 0:
					command(ch, do_say, "I can smell your blood %s!", victim->sex == SEX_FEMALE ? "bitch" : "bastard");
					break;
				case 1:
					command(ch, do_say, "Where are you? Come out here and die!");
					break;
				case 2:
					command(ch, do_say, "Just wait until I find you! Then you'll pay!");
					break;
				case 3:
					command(ch, do_say, "You're dead! Just as soon as I get my hands on you!");
					break;
			}
		}

		switch (number_bits(2))
		{
			case 0:
				act( "$n staggers around blindly, attacking the air!", ch, NULL, NULL, TO_ROOM);
				break;
			case 1:
				act( "$n lurches around the area trying to find something to attack!", ch, NULL, NULL, TO_ROOM);
				break;
			case 2:
				act( "$n growls in frustration as $e tries to attack unseen enemies!", ch, NULL, NULL, TO_ROOM);
				break;
			case 3:
				act( "$n snarls quietly as $e attacks thin air!", ch, NULL, NULL, TO_ROOM);
				break;
		}
		pop_call();
		return;
	}

	if (is_safe(ch, NULL))
	{
		if (number_bits(2) != 0)
		{
			pop_call();
			return;
		}
		if (CAN_TALK(ch))
		{
			switch (number_bits(2))
			{
				case 0:
					command(ch, do_say, "You're a coward! Fight me!");
					break;
				case 1:
					command(ch, do_say, "That's right, try to hide from me you coward!");
					break;
				case 2:
					command(ch, do_say, "C'mon, fight you craven dog!");
					break;
				case 3:
					command(ch, do_say, "Only a coward would hide here!");
					break;
			}
		}
		pop_call();
		return;
	}

	if (CAN_TALK(ch))
	{
		switch (number_bits(2))
		{
			case 0:
				command(ch, do_say, "I'll get you now! Die!");
				break;
			case 1:
				command(ch, do_say, "We were not done yet!");
				break;
			case 2:
				command(ch, do_say, "Defend yourself!");
				break;
			case 3:
				command(ch, do_say, "Prepare for your death!");
				break;
		}
	}
	else
	{
		switch (number_bits(2))
		{
			case 0:
				act( "$n contorts $s face and surges to the attack!", ch, NULL, NULL, TO_ROOM);
				break;
			case 1:
				act( "$n snarls and leaps to the attack!", ch, NULL, NULL, TO_ROOM);
				break;
			case 2:
				act( "$n growls and lunges forwards!", ch, NULL, NULL, TO_ROOM);
				break;
			case 3:
				act( "$n grinds $s teeth and leaps forwards!", ch, NULL, NULL, TO_ROOM);
				break;
		}
	}
	fight(ch, victim);

	pop_call();
	return;
}


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

	if (IS_NPC(ch) && ch->npcdata->hate_fear)
	{
		ch->npcdata->hate_fear = 0;
	}
	pop_call();
	return;
}


/*
 * Mobile hunting code, by Mikko Kilpikoski E-Mail: turtle@modeemi.cs.tut.fi
 * Edited to use player vnum instead of crash happy char_data pointer - Kregor
 */
bool hunt_victim( CHAR_DATA *ch )
{
	int dir, range;
	bool found = FALSE;
	CHAR_DATA	*tmp;
	CHAR_DATA *hunted;

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

	if (!IS_NPC(ch) || ch->in_room == NULL)
	{
		pop_call();
		return FALSE;
	}
	if ((hunted = get_char_pvnum(ch->npcdata->hate_fear)) == NULL)
	{
		pop_call();
		return FALSE;
	}
	if (in_combat(ch))
	{
		pop_call();
		return FALSE;
	}
	if (!IS_AWAKE(ch))
	{
		pop_call();
		return FALSE;
	}
	if (IS_ACT(ch, ACT_AGGRESSIVE) || ch->hit >= get_max_hit(ch) / 2)
	{
		pop_call();
		return FALSE;
	}
	if (IS_AFFECTED(ch, AFF2_FEAR) || ch->fear_level)
	{
		pop_call();
		return FALSE;
	}
	if (ch->position < POS_STANDING)
	{
		do_stand(ch, "");
		pop_call();
		return TRUE;
	}

	/*
	 * Make sure the victim still exists, else clear out hate_fear
	 */
	for (found = FALSE, tmp = mud->f_char; tmp && !found; tmp = tmp->next)
	{
		if (hunted == tmp)
		{
			found = TRUE;
		}
	}

	if (!found)
	{
		if (CAN_TALK(ch))
		{
			act("$n curses losing $s prey.", ch, NULL, NULL, TO_ROOM);
		}
		stop_hate_fear(ch);
		pop_call();
		return TRUE;
	}

	if (ch->in_room == hunted->in_room)
	{
		found_hating(ch, hunted);
		pop_call();
		return TRUE;
	}
	
	// longer hunting distances based on tracking and scent ability - Kregor
	if (learned(ch, gsn_track))
		range = 50;
	else
		range = 20;
	if (race_skill(ch, gsn_scent))
		range *= 2;

	if ((dir = findpath_search_victim(ch, hunted, range)) == -1)
	{
		if (CAN_TALK(ch))
		{
			act("$n curses losing $s prey.", ch, NULL, NULL, TO_ROOM);
		}
		stop_hate_fear(ch);
		pop_call();
		return TRUE;
	}

	if (can_see_dir(ch, hunted, dir))
	{
		/*
		 * AI missile/spell range attack goes here.
		 * return to stop here if makes successful action
		 * else let them persue - Kregor
		 */
	}

	// NPC will open doors to follow when it can
	if (IS_SET(ch->in_room->exit[dir]->exit_info, EX_CLOSED))
	{
		if (is_handy(ch))
		{
			do_open( ch, (char *) dir_name[dir] );
			if (IS_SET(ch->in_room->exit[dir]->exit_info, EX_CLOSED))
			{
				if (CAN_TALK(ch))
				{
					act("$n curses losing $s prey.", ch, NULL, NULL, TO_ROOM);
				}
				stop_hate_fear(ch);
				pop_call();
				return TRUE;
			}
			pop_call();
			return TRUE;
		}
	}
	move_char( ch, dir, TRUE, NULL );
	pop_call();
	return TRUE;
}