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