/*
* FILE: ai.c
* Purpose: To create a basic AI sub-system to make
* AI battles more realistic..
* Credits: Zachery Crosswhite (Cyhawk/Thri)
* Contact: cyhawkx@gmail.com
* Contact2: AIM: CalibanL
*
* Install Very basic, I used rom's previous ACT_MAGE
* and info: bit for this. Near the top of mobile_update in
* update.c, after the check for specs, and after
* my forced do_wear(ch, "all") (cough) I stuck..
*
* if (IS_SET(ch->act, ACT_MAGE))
* ai_wizard(ch);
*
* I also changed MOBILE_PULSE to 20, due to
* the fact its normaly 8, and 99% of stock spells
* are 12 or higher.. so.. This is to prevent
* un-human-like casting ability. I would
* say make it 12 perhaps..
*
* Idealy, when this is expanded, there will be
* a wrapper to determine class/AI, as well as
* choices for universal stuff (returning home,
* sleeping, eating, etc) before the specifics...
* As well as checks for how much 'wait' the mob
* has from spells (I have timed casting, but the
* idea is the same for Insta-cast/wait stock) This
* way, the mob casts as fast, or faster than a
* good player.
*
* TODO: Well, obviously this is just a basic piece of code...
* There needs to be ones for each 'class' of mobile
* you want to have, the 4 basics included in stock,
* as well as other special ones, (Group Healers,
* mobile leaders/'boss' mobs, maybe special mobs in
* here too, or 'super' mages..
*
* Intelligence/Wisdom Checks, dumb wizards wont always
* cast, this also gives credence to giving mobs stat points
* too.
*
* Checks for Mana, right now, they will always attempt to cast,
* I forsee some major spam-fests whe theyre out of mana.. ugh
* Maybe if theyre really hurt, they flee/find a healer..
* Ooo, I can see some intersting City AI here.
*
* Debuf/Curse spells seprated. Lets face it, in PK, it goes in
* a specific order. Debuf->curse->fight (curse being any stat
* reduction or offensive enchantments). Mobiles should do this
* too.
*
* Add function wrappers for ease of expandability and improvement
* of universal AI functions like going home, eating, etc.
*
* Licence: This code falls under the Rom Licence, with the following
* additions:
* 8) A helpfile accessable via the keyword '_thri' Must be
* viewable to all players, regardless of level, with the following
* Information:
*
* AI Sub-system created by Thri of Vandagard MUD.
* Email: cyhawkx@gmail.com
* AIM: CalibanL
*
* Any previous snippets of mine used must also be included
* in this helpfile.
*
* 9) If you have a 'credits' command or credits helpfile,
* a small mention of my Name and the code snip should also
* be there.
*
* 10) You are also required to make contact with Thri via
* any means listed. (AIM/Email) Telling him you are going
* to use his code. (Email is best).
*
* Afterward: Anywho, Enjoy the code, and make sure you expand it!
* Also, if you do expand it, send me what ya did, id love
* to incorporate this into the main sources, and maybe
* release a new stock codebase based around this, ROM 3.0?
* well, you know what i mean =)
*
* -Cyhawk/Thri
*/
#if defined(macintosh)
#include <types.h>
#else
#include <sys/types.h>
#endif
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "merc.h"
#include "interp.h"
#include "magic.h"
#include "recycle.h"
#include "tables.h"
/**
* AI_DEBUG: Turn on/off Chatting of what the AI is doing
*/
#define AI_DEBUG 1
/** ai()
*
* Primary wrapper to take care of basics (sleep, eat)
* and to determine individual ai functions via class/race
*
* Sleeping? -> Hungry? -> Thirsty? -> Tired?
* -> Enemies? -> Attack? -> race_ai
* -> class_ai
*
*/
void ai(CHAR_DATA * mob)
{
int _mob_hp = 100 * (mob->hit) / (mob->max_hit + 1);
bool can_heal_self = TRUE; /* Expand me */
CHAR_DATA * vch;
CHAR_DATA * vch_next;
/*
* ACTION_ Sleeping?
*/
switch (mob->position)
{ default:
/* STANDING: Do nothing */
case POS_STANDING:
break;
/* SLEEPING: Check HP (75%) Day/Night, AFF_SLEEP */
case POS_SLEEPING:
case POS_RESTING:
case POS_SITTING:
/* AFF_SLEEP: Continue */
if (IS_AFFECTED(mob, AFF_SLEEP))
break;
/* Hurt? Continue.. */
if (_mob_hp <= 75 && !can_heal_self)
break;
break;
}
/*
* ACTION_ Wear equipment
*/
do_wear(mob, "all");
/*
* ACTION_ Class AI
*/
if (ai_wizard(mob))
return;
if (ai_priest(mob))
return;
/*
* ACTION_ Enemy here?
*/
/* We don't want this called if the area is not empty
* This does effectivly remove the possibility
* of AI/AI combat in the background.
*
* I Placed most of the checks BEFORE we cycle through
* the room list to hopefuly reduce CPU load.
*/
/* Empty area check */
if (mob->in_room->area->empty)
return;
/* Calm Check */
if (IS_AFFECTED (mob, AFF_CALM))
return;
/* Safe room Check */
if (IS_SET (mob->in_room->room_flags, ROOM_SAFE))
return;
/* Aggressive Check */
if (!IS_SET (mob->act, ACT_AGGRESSIVE))
return;
/* Awake Check */
if (!IS_AWAKE(mob))
return;
/* Fighting Check */
if (mob->fighting != NULL)
return;
/* Not empty, ok, search for a victim */
for (vch = mob->in_room->people; vch != NULL; vch = vch_next)
{
vch_next = vch->next_in_room;
/* Make sure its a PC, and can see the victim */
if (IS_NPC (vch))
continue;
/* Wizinvis immortals immune to aggri mobs */
if (vch->invis_level >= 106)
{
#if AI_DEBUG
do_function(mob, &do_say, "Can't attack imms!");
#endif
return;
}
/* Ok, lets attack em, and have some fun too */
if (IS_AFFECTED(vch, AFF_INVISIBLE))
{
do_function(mob, &do_emote, "looks at you.");
do_function(mob, &do_say, "I found you!");
multi_hit (mob, vch, TYPE_UNDEFINED);
return;
}
if (IS_AFFECTED(vch, AFF_HIDE))
{
do_function(mob, &do_emote, "sniffs around...");
do_function(mob, &do_say, "Trying to hide are we?");
multi_hit (mob, vch, TYPE_UNDEFINED);
return;
}
if (IS_AFFECTED(vch, AFF_SNEAK))
{
do_function(mob, &do_emote, "laughs.");
do_function(mob, &do_say, "Sneaky one aren't ya...");
multi_hit (mob, vch, TYPE_UNDEFINED);
return;
}
/* Ok, not affected by anything, just kill em! */
multi_hit (mob, vch, TYPE_UNDEFINED);
return;
}
return;
}
/* Basic Action outline:
-> Cast
-> Fight -> Melee
Action -> Debuf
-> Flee?
-> Heal Self
-> Idle -> Enchant Self
-> Idle Chit Chat
*/
/** ai_wizard()
*
* Primary AI Control for Wizard mobs, follows
* a basic procedure, hopefuly this will work.
*
* Returns TRUE if it did something.
*/
bool ai_wizard(CHAR_DATA * mob) {
int _mob_hp = 100 * (mob->hit) / (mob->max_hit + 1);
int _level = mob->level;
int chance = 0;
/* If not a mage, dont go */
if (!IS_SET(mob->act, ACT_MAGE))
return FALSE;
mob->spell_cyan = 50;
mob->spell_red = 50;
mob->spell_yellow = 50;
/* Ok, Lets start with idle actions.. */
if (mob->fighting == NULL)
{
/* ACTION_ Heal Start (75% or lower) */
if (_mob_hp < 75)
{
#if AI_DEBUG
do_function (mob, &do_say, "I need to heal myself");
#endif
/* ACTION_ Heal: Lets see if he can hide first */
if (!is_affected (mob, skill_lookup("invisibility"))
&& _level >= 8
&& !IS_AFFECTED(mob, AFF_INVISIBLE))
{
#if AI_DEBUG
do_function (mob, &do_say, "Woha that was close, gotta hide!");
#endif
do_function (mob, &do_cast, "'invisibility'");
return TRUE;
}
if (_level >= 25)
{
#if AI_DEBUG
do_function (mob, &do_say, "I'm over level 25, I'll use Heal");
#endif
do_function (mob, &do_cast, "heal self");
return TRUE;
}
else
{
if (mob->position == POS_RESTING)
return TRUE;
#if AI_DEBUG
do_function (mob, &do_say, "I'm not over level 25, I need to rest");
#endif
do_function (mob, &do_rest, "rest");
return TRUE;
}
}
else
{
if (mob->position != POS_STANDING)
{
#if AI_DEBUG
do_function (mob, &do_say, "Much Better!");
#endif
do_function (mob, &do_stand, "");
}
}
/* ACTION_ Heal End */
/* ACTION_ Enchant */
/* Enchant - Orcish Strength */
if (!is_affected (mob, skill_lookup("orcish strength"))
&& _level >= 3)
{
#if AI_DEBUG
do_function (mob, &do_say, "I don't have orcish strength on.");
#endif
do_function (mob, &do_cast, "'orcish strength'");
return TRUE;
}
/* Enchant - Invisibility */
if (!is_affected (mob, skill_lookup("invisibility"))
&& _level >= 8
&& !IS_AFFECTED(mob, AFF_INVISIBLE)
&& mob->fighting == NULL)
{
#if AI_DEBUG
do_function (mob, &do_say, "I need to be invisible!");
#endif
do_function (mob, &do_cast, "'invisibility'");
return TRUE;
}
/* Enchant - Invisibility */
if (!is_affected (mob, skill_lookup("stone skin"))
&& _level >= 12)
{
#if AI_DEBUG
do_function (mob, &do_say, "I need to protect myself with Stone skin!");
#endif
do_function (mob, &do_cast, "'stone skin'");
return TRUE;
}
/* Enchant - Adept */
if (!is_affected (mob, skill_lookup("adept"))
&& _level >= 20)
{
#if AI_DEBUG
do_function (mob, &do_say, "What kind of Wizard isnt adept?");
#endif
do_function (mob, &do_cast, "'adept'");
return TRUE;
}
/* Enchant - Flight */
if (!is_affected (mob, skill_lookup("flight"))
&& _level >= 23
&& !IS_AFFECTED(mob, AFF_FLYING))
{
#if AI_DEBUG
do_function (mob, &do_say, "My legs are tired...");
#endif
do_function (mob, &do_cast, "'flight'");
return TRUE;
}
/* Enchant - Giant Strength */
if (!is_affected (mob, skill_lookup("giant strength"))
&& _level >= 26)
{
#if AI_DEBUG
do_function (mob, &do_emote, "looks in a mirror and tries to flex.");
do_function (mob, &do_say, "Hmm, the ladies sure dont like wimps!");
#endif
do_function (mob, &do_cast, "'giant strength'");
return TRUE;
}
/* Enchant - Haste */
if (!is_affected (mob, skill_lookup("haste"))
&& _level >= 30)
{
#if AI_DEBUG
do_function (mob, &do_say, "I feel sluggish...");
#endif
do_function (mob, &do_cast, "'haste'");
return TRUE;
}
/* Enchant - Immolate */
if (!is_affected (mob, skill_lookup("immolate"))
&& _level >= 31)
{
#if AI_DEBUG
do_function (mob, &do_say, "I need to feel some POWAAAHH!!");
#endif
do_function (mob, &do_cast, "'immolate'");
return TRUE;
}
/* Enchant - Anti-magic Shell */
if (!is_affected (mob, skill_lookup("anti-magic shell"))
&& _level >= 35)
{
#if AI_DEBUG
do_function (mob, &do_say, "I feel too vulnerable..");
#endif
do_function (mob, &do_cast, "'anti-magic shell'");
return TRUE;
}
/* Enchant - Manashield */
if (!is_affected (mob, skill_lookup("manashield"))
&& _level >= 45)
{
#if AI_DEBUG
do_function (mob, &do_say, "Dude? Wheres my Manashield?");
#endif
do_function (mob, &do_cast, "'manashield'");
return TRUE;
}
}
/* Ok, Idle actions done, lets start the fighting stuff! */
if (mob->fighting != NULL)
{
/* ACTION_ Fighting: Flee under 35% */
if (_mob_hp <= 35)
{
/* 50% chance to flee every update */
if (number_range(1, 100) > 50)
{
#if AI_DEBUG
do_function (mob, &do_say, "Holy bejezzus batman, Im out of here!");
#endif
do_function (mob, &do_flee, "");
return TRUE;
}
}
/* Idealy, spells will be Highest Level to Lowest
* So the AI will hit harder as theyre stronger..
*/
/* Cut levels every 20 for ease of use */
/*
* Level 21-41
*/
if ((_level >= 21) && (_level <= 999))
{
chance = number_range(1, 7);
if (chance == 1 && _level >= 21)
{
#if AI_DEBUG
do_function (mob, &do_say, "Acid anyone?");
#endif
do_function (mob, &do_cast, "'acid blast'");
return TRUE;
}
if (chance == 2 && _level >= 28)
{
#if AI_DEBUG
do_function (mob, &do_say, "Mmm fire..");
#endif
do_function (mob, &do_cast, "'fire storm'");
return TRUE;
}
if (chance == 3 && _level >= 28)
{
#if AI_DEBUG
do_function (mob, &do_say, "Just die..");
#endif
do_function (mob, &do_cast, "'death touch'");
return TRUE;
}
if (chance == 4 && _level >= 32)
{
#if AI_DEBUG
do_function (mob, &do_say, "Here kitty kitty..");
#endif
do_function (mob, &do_cast, "'energy drain'");
return TRUE;
}
if (chance == 5 && _level >= 36)
{
#if AI_DEBUG
do_function (mob, &do_say, "Is it cold in here, or is it just you?");
#endif
do_function (mob, &do_cast, "'ice bolt'");
return TRUE;
}
if (chance == 6 && _level >= 38)
{
#if AI_DEBUG
do_function (mob, &do_say, "crush crush CRUSH CRUSH! FIRE!");
#endif
do_function (mob, &do_cast, "'fire crush'");
return TRUE;
}
if (chance == 7 && _level >= 40)
{
#if AI_DEBUG
do_function (mob, &do_say, "Wanna smell my breath?");
#endif
do_function (mob, &do_cast, "'acid breath'");
return TRUE;
}
return TRUE;
}
/*
* Level Under 20
*/
if (_level <= 20)
{
chance = number_range(1, 10);
if (chance == 1 && _level >= 1)
{
#if AI_DEBUG
do_function (mob, &do_say, "Eat Wizard Fire!");
#endif
do_function (mob, &do_cast, "'wizard fire'");
return TRUE;
}
if (chance == 2 && _level >= 1)
{
#if AI_DEBUG
do_function (mob, &do_say, "Suck on some Wizard Frost!");
#endif
do_function (mob, &do_cast, "'wizard frost'");
return TRUE;
}
if (chance == 3 && _level >= 1)
{
#if AI_DEBUG
do_function (mob, &do_say, "Feel the Powah of Wizard Spark!");
#endif
do_function (mob, &do_cast, "'wizard spark'");
return TRUE;
}
if (chance == 4 && _level >= 4)
{
#if AI_DEBUG
do_function (mob, &do_say, "Are my hands hot to you?");
#endif
do_function (mob, &do_cast, "'burning hands'");
return TRUE;
}
if (chance == 5 && _level >= 5)
{
#if AI_DEBUG
do_function (mob, &do_say, "Do you feel sick?");
#endif
do_function (mob, &do_cast, "'weaken'");
return TRUE;
}
if (chance == 6 && _level >= 6)
{
#if AI_DEBUG
do_function (mob, &do_say, "Ooo, purtty colours...");
#endif
do_function (mob, &do_cast, "'colour spray'");
return TRUE;
}
if (chance == 7 && _level >= 9)
{
#if AI_DEBUG
do_function (mob, &do_say, "Ooo, its chilly in here!");
#endif
do_function (mob, &do_cast, "'chill touch'");
return TRUE;
}
if (chance == 8 && _level >= 10)
{
#if AI_DEBUG
do_function (mob, &do_say, "Wow, your strong, can I have some?");
#endif
do_function (mob, &do_cast, "'leech strength'");
return TRUE;
}
if (chance == 9 && _level >= 16)
{
#if AI_DEBUG
do_function (mob, &do_say, "You can't hit what you can't see!");
#endif
do_function (mob, &do_cast, "'blindness'");
return TRUE;
}
if (chance == 10 && _level >= 19)
{
#if AI_DEBUG
do_function (mob, &do_say, "Hope this hurts!");
#endif
do_function (mob, &do_cast, "'force spike'");
return TRUE;
}
return TRUE;
}
} /* End fighting */
return FALSE;
}
/** ai_priest()
*
* Primary AI Control for Priest Mobs.
*
* Returns TRUE if it did something.
*/
bool ai_priest(CHAR_DATA * mob) {
int _mob_hp = 100 * (mob->hit) / (mob->max_hit + 1);
int _level = mob->level;
int chance = 0;
/* If not a mage, dont go */
if (!IS_SET(mob->act, ACT_CLERIC))
return FALSE;
mob->spell_cyan = 50;
mob->spell_yellow = 50;
mob->spell_red = 50;
mob->spell_violet = 50;
/*
* ACTION_ Not fighting
*/
if (mob->fighting == NULL)
{
/*
* ACTION_ Remove Bad crap
*/
{
/* Remove: Poison */
if (IS_AFFECTED(mob, AFF_POISON)
&& _level >= 14)
{
#if AI_DEBUG
do_function (mob, &do_say, "I feel so.. sick...");
#endif
do_function (mob, &do_cast, "'cure poison'");
return TRUE;
}
/* Remove: Blindness */
if (IS_AFFECTED(mob, AFF_BLIND)
&& _level >= 11)
{
#if AI_DEBUG
do_function (mob, &do_say, "I can't see!!!");
#endif
do_function (mob, &do_cast, "'cure blindness'");
return TRUE;
}
/* Remove: Curse */
if (IS_AFFECTED(mob, AFF_CURSE)
&& _level >= 21)
{
#if AI_DEBUG
do_function (mob, &do_say, "I feel disconnected..");
#endif
do_function (mob, &do_cast, "'remove curse' self");
return TRUE;
}
}
/* ACTION_ Heal Start (75% or lower) */
if (_mob_hp < 75)
{
/* Work backwards in terms of power for better heal spells */
/* HEAL_ Restoration */
if (_level >= 95)
{
#if AI_DEBUG
do_function (mob, &do_say, "Restoration here I come!");
#endif
do_function (mob, &do_cast, "restoration");
return TRUE;
}
/* HEAL_ Heal Spell */
if (_level >= 60)
{
#if AI_DEBUG
do_function (mob, &do_say, "I'll just use HEAL then...");
#endif
do_function (mob, &do_cast, "heal");
return TRUE;
}
/* HEAL_ Cure Critical */
if (_level >= 25)
{
#if AI_DEBUG
do_function (mob, &do_say, "Being beat on is the suck");
#endif
do_function (mob, &do_cast, "cure serious");
return TRUE;
}
/* HEAL_ Heal Spell */
if (_level >= 60)
{
#if AI_DEBUG
do_function (mob, &do_say, "I'll just use HEAL then...");
#endif
do_function (mob, &do_cast, "heal");
return TRUE;
}
if (_level >= 1)
{
#if AI_DEBUG
do_function (mob, &do_say, "I'm at least level 1, cure light wounds");
#endif
do_function (mob, &do_cast, "cure light wounds");
return TRUE;
}
}
/*
* ACTION_ Healing: End
*/
/*
* ACTION_ Enchant
*/
{
/* Enchant - Armor */
if (!is_affected (mob, skill_lookup("armor"))
&& _level >= 1)
{
#if AI_DEBUG
do_function (mob, &do_say, "I don't have armor on...");
#endif
do_function (mob, &do_cast, "'armor'");
return TRUE;
}
/* Enchant - Bless */
if (!is_affected (mob, skill_lookup("bless"))
&& _level >= 3)
{
#if AI_DEBUG
do_function (mob, &do_say, "I'm not rightous!");
#endif
do_function (mob, &do_cast, "'bless'");
return TRUE;
}
/* Enchant - Bark Skin */
if (!is_affected (mob, skill_lookup("bark skin"))
&& _level >= 15)
{
#if AI_DEBUG
do_function (mob, &do_say, "My Skin is too soft");
#endif
do_function (mob, &do_cast, "'bark skin'");
return TRUE;
}
/* Enchant - Holy Sight */
if (!is_affected (mob, skill_lookup("holy sight"))
&& _level >= 37)
{
#if AI_DEBUG
do_function (mob, &do_say, "My My its dark in here");
#endif
do_function (mob, &do_cast, "'holy sight'");
return TRUE;
}
/* Enchant - Frenzy */
if (!is_affected (mob, skill_lookup("frenzy"))
&& _level >= 41)
{
#if AI_DEBUG
do_function (mob, &do_say, "I'm not mad enough!");
#endif
do_function (mob, &do_cast, "'frenzy'");
return TRUE;
}
/* Enchant - Sanctuary */
if (!is_affected (mob, skill_lookup("sanctuary"))
&& !IS_AFFECTED(mob, AFF_SANCTUARY)
&& _level >= 46)
{
#if AI_DEBUG
do_function (mob, &do_say, "Wow, its not pretty here");
#endif
do_function (mob, &do_cast, "'sanctuary'");
return TRUE;
}
}
}
/*
* END Not Fighting
*/
return FALSE;
}