/**************************************************************************
* # # # ## # # ### ## ## ### 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);
}