Lyonesse/bin/
Lyonesse/doc/eng/
Lyonesse/doc/ita/
Lyonesse/lib/
Lyonesse/lib/buildings/
Lyonesse/lib/clans/
Lyonesse/lib/data/
Lyonesse/lib/etc/
Lyonesse/lib/house/
Lyonesse/lib/misc/
Lyonesse/lib/plralias/A-E/
Lyonesse/lib/plralias/F-J/
Lyonesse/lib/plralias/K-O/
Lyonesse/lib/plralias/P-T/
Lyonesse/lib/plralias/U-Z/
Lyonesse/lib/plralias/ZZZ/
Lyonesse/lib/plrobjs/A-E/
Lyonesse/lib/plrobjs/F-J/
Lyonesse/lib/plrobjs/K-O/
Lyonesse/lib/plrobjs/P-T/
Lyonesse/lib/plrobjs/U-Z/
Lyonesse/lib/plrobjs/ZZZ/
Lyonesse/lib/plrsave/A-E/
Lyonesse/lib/plrsave/F-J/
Lyonesse/lib/plrsave/K-O/
Lyonesse/lib/plrsave/P-T/
Lyonesse/lib/plrsave/U-Z/
Lyonesse/lib/plrsave/ZZZ/
Lyonesse/lib/ships/
Lyonesse/lib/stables/
Lyonesse/lib/text/help/
Lyonesse/lib/world/
Lyonesse/lib/world/bld/
Lyonesse/lib/world/ship/
Lyonesse/lib/world/shp/
Lyonesse/lib/world/wls/
Lyonesse/lib/world/wls/Life/
Lyonesse/lib/world/wls/Map/
Lyonesse/log/
/**************************************************************************
 * #   #   #   ##   #  #  ###   ##   ##  ###       http://www.lyonesse.it *
 * #    # #   #  #  ## #  #    #    #    #                                *
 * #     #    #  #  # ##  ##    #    #   ##   ## ##  #  #  ##             *
 * #     #    #  #  # ##  #      #    #  #    # # #  #  #  # #            *
 * ###   #     ##   #  #  ###  ##   ##   ###  #   #  ####  ##    Ver. 1.0 *
 *                                                                        *
 * -Based on CircleMud & Smaug-     Copyright (c) 2001-2002 by Mithrandir *
 *                                                                        *
 * ********************************************************************** */
/* ************************************************************************
*   File: mobact.c                                      Part of CircleMUD *
*  Usage: Functions for generating intelligent (?) behavior in mobiles    *
*                                                                         *
*  All rights reserved.  See license.doc for complete information.        *
*                                                                         *
*  Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University *
*  CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.               *
************************************************************************ */

#include "conf.h"
#include "sysdep.h"


#include "structs.h"
#include "utils.h"
#include "db.h"
#include "comm.h"
#include "interpreter.h"
#include "handler.h"
#include "spells.h"
#include "constants.h"


/* external globals */
extern int no_specials;

/* external functions */
ACMD(do_say);
ACMD(do_get);
ACMD(do_action);
int	hunt_victim(CHAR_DATA *ch);
int	compute_armor_class(CHAR_DATA *ch);

/* local functions */
void mobile_activity(void);
void clearMemory(CHAR_DATA *ch);
void check_switch_opponent(CHAR_DATA *ch);
bool aggressive_mob_on_a_leash(CHAR_DATA *slave, CHAR_DATA *master, CHAR_DATA *attack);

#define MOB_AGGR_TO_ALIGN (MOB_AGGR_EVIL | MOB_AGGR_NEUTRAL | MOB_AGGR_GOOD)


void mobile_activity(void)
{
	CHAR_DATA *ch, *next_ch, *vict;
	OBJ_DATA *obj, *best_obj;
	MEMORY_REC *names;
	int found, max;
	
	for (ch = character_list; ch; ch = next_ch)
	{
		next_ch = ch->next;
		
		if (!IS_MOB(ch))
			continue;
		
		/* Examine call for special procedure */
		if (MOB_FLAGGED(ch, MOB_SPEC) && !no_specials)
		{
			if (mob_index[GET_MOB_RNUM(ch)].func == NULL)
			{
				log("SYSERR: %s (#%d): Attempting to call non-existing mob function.",
					GET_NAME(ch), GET_MOB_VNUM(ch));
				REMOVE_BIT(MOB_FLAGS(ch), MOB_SPEC);
			}
			else
			{
				char actbuf[MAX_INPUT_LENGTH] = "";
				if ((mob_index[GET_MOB_RNUM(ch)].func) (ch, ch, 0, actbuf))
					continue;		/* go to next char */
			}
		}
		
		/* If the mob is asleep exit here.. */
		if (!AWAKE(ch))
			continue;
		
		/* let fighting mob be smarter */
		if (FIGHTING(ch))
		{
			check_switch_opponent(ch);
			continue;
		}

		/* take care of encountered mobs */
		if (MOB_FLAGGED(ch, MOB_ENCOUNTER) && GET_POS(ch) == POS_STANDING)
		{
			if (--ch->mob_specials.timer < 1)
			{
				act("$n sneaks away.", FALSE, ch, NULL, NULL, TO_ROOM);
				extract_char(ch);
				continue;
			}
		}

		/* Scavenger (picking up objects) */
		if (MOB_FLAGGED(ch, MOB_SCAVENGER) && !FIGHTING(ch) && AWAKE(ch))
		{
			if (ch->in_room->first_content && !number(0, 10))
			{
				max = 1;
				best_obj = NULL;
				for (obj = ch->in_room->first_content; obj; obj = obj->next_content)
				{
					if (CAN_GET_OBJ(ch, obj) && GET_OBJ_COST(obj) > max)
					{
						best_obj = obj;
						max = GET_OBJ_COST(obj);
					}
				}
				if (best_obj != NULL)
				{
					obj_from_room(best_obj);
					best_obj = obj_to_char(best_obj, ch);
					act("$n gets $p.", FALSE, ch, best_obj, 0, TO_ROOM);
				}
			}
		}
		
		/* Aggressive Mobs */
		if (MOB_FLAGGED(ch, MOB_AGGRESSIVE | MOB_AGGR_TO_ALIGN))
		{
			found = FALSE;
			for (vict = ch->in_room->people; vict && !found; vict = vict->next_in_room)
			{
				if (IS_NPC(vict) || !CAN_SEE(ch, vict) || PRF_FLAGGED(vict, PRF_NOHASSLE))
					continue;
				if (MOB_FLAGGED(ch, MOB_WIMPY) && AWAKE(vict))
					continue;
				if (MOB_FLAGGED(ch, MOB_AGGRESSIVE) ||
					(MOB_FLAGGED(ch, MOB_AGGR_EVIL) && IS_EVIL(vict)) ||
					(MOB_FLAGGED(ch, MOB_AGGR_NEUTRAL) && IS_NEUTRAL(vict)) ||
					(MOB_FLAGGED(ch, MOB_AGGR_GOOD) && IS_GOOD(vict)))
				{
					/* Can a master successfully control the charmed monster? */
					if (aggressive_mob_on_a_leash(ch, ch->master, vict))
						continue;

					hit(ch, vict, TYPE_UNDEFINED);
					found = TRUE;
				}
			}
		}
		
		/* Mob Memory */
		if (MOB_FLAGGED(ch, MOB_MEMORY) && MEMORY(ch))
		{
			found = FALSE;
			for (vict = ch->in_room->people; vict && !found; vict = vict->next_in_room)
			{
				if (IS_NPC(vict) || !CAN_SEE(ch, vict) || PRF_FLAGGED(vict, PRF_NOHASSLE))
					continue;
				
				for (names = MEMORY(ch); names && !found; names = names->next)
				{
					if (names->id != GET_IDNUM(vict))
						continue;
					
					/* Can a master successfully control the charmed monster? */
					if (aggressive_mob_on_a_leash(ch, ch->master, vict))
						continue;
					
					found = TRUE;
					act("'Hey!  You're the fiend that attacked me!!!', exclaims $n.", FALSE, ch, 0, 0, TO_ROOM);
					hit(ch, vict, TYPE_UNDEFINED);
				}
			}
		}
		
		/*
		 * Charmed Mob Rebellion
		 *
		 * In order to rebel, there need to be more charmed monsters
		 * than the person can feasibly control at a time.  Then the
		 * mobiles have a chance based on the charisma of their leader.
		 *
		 * 1-4 = 0, 5-7 = 1, 8-10 = 2, 11-13 = 3, 14-16 = 4, 17-19 = 5, etc.
 		 */
		if (AFF_FLAGGED(ch, AFF_CHARM) && ch->master && num_followers_charmed(ch->master) > (GET_CHA(ch->master) - 2) / 3)
		{
			if (!aggressive_mob_on_a_leash(ch, ch->master, ch->master))
			{
				if (CAN_SEE(ch, ch->master) && !PRF_FLAGGED(ch->master, PRF_NOHASSLE))
					hit(ch, ch->master, TYPE_UNDEFINED);
				stop_follower(ch);
			}
		}

		/* Helper Mobs */
		if (MOB_FLAGGED(ch, MOB_HELPER) && !AFF_FLAGGED(ch, AFF_BLIND | AFF_CHARM))
		{
			found = FALSE;
			for (vict = ch->in_room->people; vict && !found; vict = vict->next_in_room)
			{
				if (ch == vict || !IS_NPC(vict) || !FIGHTING(vict))
					continue;
				if (IS_NPC(FIGHTING(vict)) || ch == FIGHTING(vict))
					continue;
				
				act("$n jumps to the aid of $N!", FALSE, ch, 0, vict, TO_ROOM);
				hit(ch, FIGHTING(vict), TYPE_UNDEFINED);
				found = TRUE;
			}
		}

		/* Hunting Mobs */
		if (AFF_FLAGGED(ch, AFF_HUNTING) && HUNTING(ch) && !FIGHTING(ch))
		{
			if (hunt_victim(ch))
				continue;
		}

		/* Mob Movement */
		if (!MOB_FLAGGED(ch, MOB_SENTINEL) && !MOB_FLAGGED(ch, MOB_ENCOUNTER) && GET_POS(ch) == POS_STANDING)
		{
			int door, max_doors;
			
			if (IN_WILD(ch))
			{
				door = number(0, 30);
				max_doors = NUM_OF_DIRS;
			}
			else
			{
				door = number(0, 18);
				max_doors = NUM_OF_EX_DIRS;
			}
			
			if (door < max_doors)
			{
				EXIT_DATA *pexit = get_exit(ch->in_room, door);
				if (pexit && pexit->to_room && !ROOM_FLAGGED(pexit->to_room, ROOM_NOMOB | ROOM_DEATH))
				{
					if (!MOB_FLAGGED(ch, MOB_STAY_ZONE) || 
						(pexit->to_room->zone == ch->in_room->zone))
					{
						perform_move(ch, door, 1);
					}
				}
			}
		}

		/* Add new mobile actions here */
	}
}



/* Mob Memory Routines */

/* make ch remember victim */
void remember(CHAR_DATA *ch, CHAR_DATA *victim)
{
	MEMORY_REC *tmp;
	bool present = FALSE;
	
	if (!IS_NPC(ch) || IS_NPC(victim) || PRF_FLAGGED(victim, PRF_NOHASSLE))
		return;
	
	for (tmp = MEMORY(ch); tmp && !present; tmp = tmp->next)
	{
		if (tmp->id == GET_IDNUM(victim))
			present = TRUE;
	}
	
	if (!present)
	{
		CREATE(tmp, MEMORY_REC, 1);
		tmp->next = MEMORY(ch);
		tmp->id = GET_IDNUM(victim);
		MEMORY(ch) = tmp;
	}
}


/* make ch forget victim */
void forget(CHAR_DATA *ch, CHAR_DATA *victim)
{
	MEMORY_REC *curr, *prev = NULL;
	
	if (!(curr = MEMORY(ch)))
		return;
	
	while (curr && curr->id != GET_IDNUM(victim))
	{
		prev = curr;
		curr = curr->next;
	}
	
	if (!curr)
		return;			/* person wasn't there at all. */
	
	if (curr == MEMORY(ch))
		MEMORY(ch) = curr->next;
	else
		prev->next = curr->next;
	
	free(curr);
}


/* erase ch's memory */
void clearMemory(CHAR_DATA *ch)
{
	MEMORY_REC *curr, *next;
	
	curr = MEMORY(ch);
	
	while (curr)
	{
		next = curr->next;
		free(curr);
		curr = next;
	}
	
	MEMORY(ch) = NULL;
}

/*
 * Npc taunts slayed player characters.  Text goes out through gossip
 * channel.  Muerte - Telnet://betterbox.net:4000
 */
void brag(CHAR_DATA *ch, CHAR_DATA *vict)
{
	DESCRIPTOR_DATA *i;
	char buf[MAX_STRING_LENGTH], brag[MAX_STRING_LENGTH];
	
	switch (number(0, 11))
	{
	case 0:
		sprintf(brag, "$n brags, '%s was just too easy a kill!'", GET_NAME(vict));
		break;
	case 1:
		sprintf(brag, "$n brags, '%s was a tasty dinner, now who's for desert? Muhaha!'", GET_NAME(vict));
		break;
	case 2:
		sprintf(brag, "$n brags, 'Bahaha! %s should stick to Odif's !'",GET_NAME(vict));
		break;
	case 3:
		sprintf(brag, "$n brags, '%s is now in need of some exp...Muhaha!'", GET_NAME(vict));
		break;
	case 4:
		sprintf(brag, "$n brags, '%s needs a hospital now. Muhaha!'",GET_NAME(vict));
		break;
	case 5:
		sprintf(brag, "$n brags, '%s's mother is a slut!  Muhaha!'", GET_NAME(vict));
		break;
	case 6:
		sprintf(brag, "$n brags, '%s is a punk, hits like a swampfly. Bah.'", GET_NAME(vict));
		break;
	case 7:
		sprintf(brag, "$n brags, '%s, your life force has just run out...Muahaha!'", GET_NAME(vict));
		break;
	case 8:
		sprintf(brag, "$n brags, 'Bah, %s should stick to the newbie zone!'",GET_NAME(vict));
		break;
	case 9:
		sprintf(brag, "$n brags, '%s, give me your daughter's number and I might return your corpse.  Muhaha!'", GET_NAME(vict));
		break;
	case 10:
		sprintf(brag, "$n brags, 'Hey %s!  Come back, you dropped your corpse! Muahaha'", GET_NAME(vict));
		break;
	case 11:
		sprintf(brag, "$n brags, 'I think %s wears pink chainmail.  Fights like a girl!  Muhaha!'", GET_NAME(vict));
		break;
	}

	sprintf(buf, "&b&1%s&0", brag);

	for (i = descriptor_list; i; i = i->next)
	{
		if (STATE(i) == CON_PLAYING && i != ch->desc && i->character &&
			!PRF_FLAGGED(i->character, PRF_NOGOSS) &&
			!PLR_FLAGGED(i->character, PLR_WRITING) &&
			!ROOM_FLAGGED(i->character->in_room, ROOM_SOUNDPROOF))
		{
			act(brag, FALSE, ch, 0, i->character, TO_VICT | TO_SLEEP);
		}
	}

}

void check_switch_opponent(CHAR_DATA *ch)
{
	CHAR_DATA *tmp, *newone = NULL;
	OBJ_DATA *wielded;
	int best = 0, weight = 0;
	int dam1, dam2;

	for (tmp = ch->in_room->people; tmp; tmp = tmp->next_in_room)
	{
		if (tmp == ch || tmp == FIGHTING(ch) || IS_NPC(tmp) || FIGHTING(tmp) != ch)
			continue;

		switch (GET_CLASS(tmp))
		{
		case CLASS_WARRIOR:		weight = -2;	break;
		case CLASS_THIEF:		weight = 0;		break;
		case CLASS_CLERIC:		weight = 2;		break;
		case CLASS_SORCERER:	weight = 4;		break;
		case CLASS_MAGIC_USER:	weight = 6;		break;
		}

		/* compares current opponent and a potential new one */
		weight += GET_LEVEL(FIGHTING(ch)) - GET_LEVEL(tmp);
		weight += GET_REAL_HITROLL(FIGHTING(ch)) - GET_REAL_HITROLL(tmp);
		weight += GET_REAL_DAMROLL(FIGHTING(ch)) - GET_REAL_DAMROLL(tmp);

		if ( compute_armor_class(FIGHTING(ch)) > compute_armor_class(tmp) )
			weight += 10;
		else
			weight -= 10;

		/* calculate max amount of damage of weapons */
		if ( (wielded = GET_EQ(FIGHTING(ch), WEAR_WIELD)) && GET_OBJ_TYPE(wielded) == ITEM_WEAPON )
			dam1 = GET_OBJ_VAL(wielded, 2) * GET_OBJ_VAL(wielded, 1);
		if ( (wielded = GET_EQ(tmp, WEAR_WIELD)) && GET_OBJ_TYPE(wielded) == ITEM_WEAPON )
			dam2 = GET_OBJ_VAL(wielded, 2) * GET_OBJ_VAL(wielded, 1);
		weight += dam1 - dam2;

		if (AFF_FLAGGED(FIGHTING(ch), AFF_SANCTUARY))
			weight += 10;
		if (AFF_FLAGGED(FIGHTING(ch), AFF_FIRESHIELD))
			weight += 50;

		if (AFF_FLAGGED(tmp, AFF_SANCTUARY))
			weight -= 10;
		if (AFF_FLAGGED(tmp, AFF_FIRESHIELD))
			weight -= 50;

		if ( weight > best )
		{
			best = weight;
			newone = tmp;
		}
	}
	
	/* have we found an easier opponent ? */
	if (newone)
	{
		act("$n sees that $N has joined the fight, and decides $E is a much better prey!",
			FALSE, ch, NULL, newone, TO_ROOM );
		do_say(ch, "Easier meat, ha ha!", 0, 0 );
		stop_fighting(ch);
		set_fighting(ch, newone);
	}
}


/*
 * An aggressive mobile wants to attack something.  If
 * they're under the influence of mind altering PC, then
 * see if their master can talk them out of it, eye them
 * down, or otherwise intimidate the slave.
 */
bool aggressive_mob_on_a_leash(CHAR_DATA *slave, CHAR_DATA *master, CHAR_DATA *attack)
{
	static int snarl_cmd;
	int dieroll;
	
	if (!master || !AFF_FLAGGED(slave, AFF_CHARM))
		return (FALSE);
	
	if (!snarl_cmd)
		snarl_cmd = find_command("snarl");
	
	/* Sit. Down boy! HEEEEeeeel! */
	dieroll = number(1, 20);
	if (dieroll != 1 && (dieroll == 20 || dieroll > 10 - GET_CHA(master) + GET_INT(slave)))
	{
		if (snarl_cmd && attack && !number(0, 3))
		{
			char victbuf[MAX_NAME_LENGTH + 1];
			
			strncpy(victbuf, GET_NAME(attack), sizeof(victbuf));
			victbuf[sizeof(victbuf) - 1] = '\0';
			
			do_action(slave, victbuf, snarl_cmd, 0);
		}
		
		/* Success! But for how long? Hehe. */
		return (TRUE);
	}
	
	/* So sorry, now you're a player killer... Tsk tsk. */
	return (FALSE);
}