PDA

View Full Version : #bot filter


Shin Noir
09-18-2009, 08:29 PM
So, I'm told that the bots will be rewritten from scratch again.

Assuming you utilize the same automated bot AI system, I really recommend writing something like I was working on. It's a #bot filter command.

It's a simple unsigned integer with a bunch of bitmask filters to specify turning on or off certain bot AI routines. Ideally store it in the database when the bot saves, and a simple system for allowing players to customize what they want their bot to do.

I wrote a bit of it, such as..
line 19 of bot.h

//Shin: AI Defines, used for ai filter list.
#define AI_HEAL 1
#define AI_ROOT 2
#define AI_SNARE 4
#define AI_SLOW 8
#define AI_NUKE 16
#define AI_DOT 32
#define AI_PET 64 //Use a pet? (shamans etc)
#define AI_ILLUSION 128 //Use illusion spells? (annoying enchanter!)
#define AI_LIFETAP 256
#define AI_HOT 512 //Heal Over Time Spell Casting
#define AI_BUFF 1024 //Do Buff Routine/Keep Buffs Up?
#define AI_FEAR 2048 //Do Fear Type Spells
#define AI_DISPELL 4096 //Do Dispells?

Line 133 after SetTarget

virtual void SetTarget(Mob* mob);
virtual void SetFilter(uint16 value); //Shin: Set/Get AI bitmask
uint16 GetFilter() { return _filter; }

Line 262 after the comment

// Private "base stats" Members
uint16 _filter; //Used for AI filter methods
[/code]

line 8345 after #bot camp help line

c->Message(0, "#bot filter [list|option] - Turn On/Off a bot routine filter."); //Shin: For filters


This code is incomplete, I was working on it but when I realize a total rewrite of the bot system was in place, I stopped. But here's a general example:

if (!strcasecmp(sep->arg[1], "filter"))
{ //Shin: AI Options! and yes, I put the { on next line. Sue me.
if((c->GetTarget() == NULL) || (c->GetTarget() == c) || !c->GetTarget()->IsBot()) {
c->Message(15, "You must target a bot!");
return;
}

Bot* tBot = c->GetTarget()->CastToBot();

if (!strcasecmp(sep->arg[2], "list"))
c->Message(15, "This would list current AI options enabled.");
if (!strcasecmp(sep->arg[2], "heal"))
{
tBot->SetFilter(tBot->GetFilter() ^ AI_HEAL);
(tBot->GetFilter() & AI_HEAL) ? tBot->Say("I will now heal members of my group.") : c->Message(15, "I will no longer heal members of my group.");
}
if (!strcasecmp(sep->arg[2], "root"))
{
tBot->SetFilter(tBot->GetFilter() ^ AI_ROOT);
(tBot->GetFilter() & AI_ROOT) ? tBot->Say("I will now try to root monsters.") : c->Message(15, "I will no longer try to root monsters.");
}
if (!strcasecmp(sep->arg[2], "snare"))
{
tBot->SetFilter(tBot->GetFilter() ^ AI_SNARE);
(tBot->GetFilter() & AI_SNARE) ? tBot->Say("I will now try to snare monsters.") : c->Message(15, "I will no longer try to snare monsters.");
}
if (!strcasecmp(sep->arg[2], "slow"))
{
tBot->SetFilter(tBot->GetFilter() ^ AI_SLOW);
(tBot->GetFilter() & AI_SLOW) ? tBot->Say("I will now try to slow monsters.") : c->Message(15, "I will no longer try to slow monsters.");
}
if (!strcasecmp(sep->arg[2], "nuke"))
{
tBot->SetFilter(tBot->GetFilter() ^ AI_NUKE);
(tBot->GetFilter() & AI_NUKE) ? tBot->Say("I will now try to cast direct damage spells on monsters.") : c->Message(15, "I will no longer try to cast direct damage spells on monsters.");
}
if (!strcasecmp(sep->arg[2], "dot"))
{
tBot->SetFilter(tBot->GetFilter() ^ AI_DOT);
(tBot->GetFilter() & AI_DOT) ? tBot->Say("I will now try to cast damage over time spells on monsters.") : c->Message(15, "I will no longer try to cast damage over time spells on monsters.");
}
if (!strcasecmp(sep->arg[2], "pet"))
{
tBot->SetFilter(tBot->GetFilter() ^ AI_PET);
(tBot->GetFilter() & AI_PET) ? tBot->Say("I will now try to keep a pet up.") : c->Message(15, "I will no longer try to keep a pet up.");
}
if (!strcasecmp(sep->arg[2], "illusion"))
{
tBot->SetFilter(tBot->GetFilter() ^ AI_ILLUSION);
(tBot->GetFilter() & AI_ILLUSION) ? tBot->Say("I will now cast illusion spells on the group when possible.") : c->Message(15, "I will no longer cast illusion spells on the group.");
}
if (!strcasecmp(sep->arg[2], "buff"))
{
tBot->SetFilter(tBot->GetFilter() ^ AI_BUFF);
(tBot->GetFilter() & AI_BUFF) ? tBot->Say("I will now try to keep buffs on group members.") : c->Message(15, "I will no longer try to keep buffs on group members.");
}


}

Then in each spelltype case, have an added condition such as:


case SpellType_Heal: {
if (
( (spells[AIspells[i].spellid].targettype==ST_GroupTeleport || spells[AIspells[i].spellid].targettype == ST_Target || tar == this)
&& tar->DontHealMeBefore() < Timer::GetCurrentTime()
&& tar->CanBuffStack(AIspells[i].spellid, botLevel, true) >= 0)
&& (this->GetFilter() & AI_HEAL)) //Shin: Heal Filter Check
{

Also around line 1740 I did a illusion check, Enchanters are annoying with scarecrow, but so do druids, having a universal filter would be nice.
//Shin: Check for illusion spells. Don't cast if filter prevents.
if( (IsEffectInSpell(AIspells[i].spellid, SE_Illusion) && !(this->GetFilter() & AI_ILLUSION))
break;

I did a lot more modifications, but I'm not really sure it is even worth my time if you are totally revamping the system.... :/

Let me know!

Shin Noir
09-18-2009, 08:36 PM
I didn't note this, and can't see an edit button so:
The above code is NOT complete. I stopped when I caught wind you are making a new system from scratch.

However, if you like my idea and would enjoy a SVN diff to compare with latest revision, I can try to finish the code and submit a diff file for you to see every change I did.

Shin Noir
10-09-2009, 03:59 PM
I need a lot of testing with my new code.
If anyone is interested in helping me establish this command, feel free to PM (or reply) with your IM details and we can talk while I keep crashing my developer box.

You can connect to my dev box and help me out while I try various combinations and discover issues with the new code.

Shin Noir
10-10-2009, 12:17 AM
While working on this bot filter code I keep rennovating some of the older code. With the option to control aspects of your bot the AI routines it normally runs doesn't have to be so specific per class.

http://img10.imageshack.us/img10/9726/filterlist.png

A long ways away, but the above screenshot gives you a slight idea of the options... Still more to come. Heh.

Shin Noir
10-15-2009, 04:31 AM
I did a ton of work on this, but i need to debug a lot of bullcrap and figure out why I keep breaking the bots. :P


Index: Server.sln
================================================== =================
--- Server.sln (revision 1005)
+++ Server.sln (working copy)
@@ -26,16 +26,16 @@
{21168136-6906-4039-B486-6086E164E743}.Release|Win32.Build.0 = Release|Win32
{21168136-6906-4039-B486-6086E164E743}.ReleaseBots|Win32.ActiveCfg = Release|Win32
{21168136-6906-4039-B486-6086E164E743}.ReleaseBots|Win32.Build.0 = Release|Win32
- {0FD472B1-CCB8-4ED2-8F4E-A1FC34E668EC}.Debug|Win32.ActiveCfg = Debug|Win32
- {0FD472B1-CCB8-4ED2-8F4E-A1FC34E668EC}.Debug|Win32.Build.0 = Debug|Win32
+ {0FD472B1-CCB8-4ED2-8F4E-A1FC34E668EC}.Debug|Win32.ActiveCfg = DebugBots|Win32
+ {0FD472B1-CCB8-4ED2-8F4E-A1FC34E668EC}.Debug|Win32.Build.0 = DebugBots|Win32
{0FD472B1-CCB8-4ED2-8F4E-A1FC34E668EC}.DebugBots|Win32.ActiveCfg = DebugBots|Win32
{0FD472B1-CCB8-4ED2-8F4E-A1FC34E668EC}.DebugBots|Win32.Build.0 = DebugBots|Win32
{0FD472B1-CCB8-4ED2-8F4E-A1FC34E668EC}.Release|Win32.ActiveCfg = Release|Win32
{0FD472B1-CCB8-4ED2-8F4E-A1FC34E668EC}.Release|Win32.Build.0 = Release|Win32
{0FD472B1-CCB8-4ED2-8F4E-A1FC34E668EC}.ReleaseBots|Win32.ActiveCfg = ReleaseBots|Win32
{0FD472B1-CCB8-4ED2-8F4E-A1FC34E668EC}.ReleaseBots|Win32.Build.0 = ReleaseBots|Win32
- {197A661B-390A-46D6-94FC-E99F93280695}.Debug|Win32.ActiveCfg = Debug|Win32
- {197A661B-390A-46D6-94FC-E99F93280695}.Debug|Win32.Build.0 = Debug|Win32
+ {197A661B-390A-46D6-94FC-E99F93280695}.Debug|Win32.ActiveCfg = DebugBots|Win32
+ {197A661B-390A-46D6-94FC-E99F93280695}.Debug|Win32.Build.0 = DebugBots|Win32
{197A661B-390A-46D6-94FC-E99F93280695}.DebugBots|Win32.ActiveCfg = DebugBots|Win32
{197A661B-390A-46D6-94FC-E99F93280695}.DebugBots|Win32.Build.0 = DebugBots|Win32
{197A661B-390A-46D6-94FC-E99F93280695}.Release|Win32.ActiveCfg = Release|Win32
Index: zone/bot.cpp
================================================== =================
--- zone/bot.cpp (revision 1005)
+++ zone/bot.cpp (working copy)
@@ -82,7 +82,6 @@
// This constructor is used when the bot is loaded out of the database
Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double totalPlayTime, NPCType npcTypeData) : NPC(&npcTypeData, 0, 0, 0, 0, 0, 0, false) {
this->_botOwnerCharacterID = botOwnerCharacterID;
-
if(this->_botOwnerCharacterID > 0) {
this->SetBotOwner(entity_list.GetClientByCharID(this->_botOwnerCharacterID));
}
@@ -113,7 +112,6 @@
SetBotArcher(false);
SetBotCharmer(false);
SetPetChooser(false);
-
this->SetBotRaidID(0);

strcpy(this->name, this->GetCleanName());
@@ -127,13 +125,10 @@
if(GetBotOwner())
GetBotOwner()->Message(13, TempErrorMessage.c_str());
}
-
GenerateBaseStats();
GenerateArmorClass();
-
// Calculate HitPoints Last As It Uses Base Stats
GenerateBaseHitPoints();
-
CalcBotStats(false);
}

@@ -953,7 +948,7 @@
if(this->GetBotID() == 0) {
// New bot record
uint32 TempNewBotID = 0;
- if(!database.RunQuery(Query, MakeAnyLenString(&Query, "INSERT INTO bots (BotOwnerCharacterID, BotSpellsID, Name, LastName, BotLevel, Race, Class, Gender, Size, Face, LuclinHairStyle, LuclinHairColor, LuclinEyeColor, LuclinEyeColor2, LuclinBeardColor, LuclinBeard, DrakkinHeritage, DrakkinTattoo, DrakkinDetails, MR, CR, DR, FR, PR, AC, STR, STA, DEX, AGI, _INT, WIS, CHA, ATK, LastSpawnDate, TotalPlayTime) VALUES('%u', '%u', '%s', '%s', '%u', '%i', '%i', '%i', '%f', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', NOW(), 0)", this->_botOwnerCharacterID, this->GetBotSpellID(), this->GetCleanName(), this->lastname, this->GetLevel(), GetRace(), GetClass(), GetGender(), GetSize(), this->GetLuclinFace(), this->GetHairStyle(), GetHairColor(), this->GetEyeColor1(), GetEyeColor2(), this->GetBeardColor(), this->GetBeard(), this->GetDrakkinHeritage(), this->GetDrakkinTattoo(), GetDrakkinDetails(), GetMR(), GetCR(), GetDR(), GetFR(), GetPR(), GetAC(), GetSTR(), GetSTA(), GetDEX(), GetAGI(), GetINT(), GetWIS(), GetCHA(), GetATK()), TempErrorMessageBuffer, 0, &affectedRows, &TempNewBotID)) {
+ if(!database.RunQuery(Query, MakeAnyLenString(&Query, "INSERT INTO bots (BotOwnerCharacterID, BotSpellsID, Name, LastName, BotLevel, Race, Class, Gender, Size, Face, LuclinHairStyle, LuclinHairColor, LuclinEyeColor, LuclinEyeColor2, LuclinBeardColor, LuclinBeard, DrakkinHeritage, DrakkinTattoo, DrakkinDetails, MR, CR, DR, FR, PR, AC, STR, STA, DEX, AGI, _INT, WIS, CHA, ATK, LastSpawnDate, TotalPlayTime, Filter) VALUES('%u', '%u', '%s', '%s', '%u', '%i', '%i', '%i', '%f', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', NOW(), 0, 0)", this->_botOwnerCharacterID, this->GetBotSpellID(), this->GetCleanName(), this->lastname, this->GetLevel(), GetRace(), GetClass(), GetGender(), GetSize(), this->GetLuclinFace(), this->GetHairStyle(), GetHairColor(), this->GetEyeColor1(), GetEyeColor2(), this->GetBeardColor(), this->GetBeard(), this->GetDrakkinHeritage(), this->GetDrakkinTattoo(), GetDrakkinDetails(), GetMR(), GetCR(), GetDR(), GetFR(), GetPR(), GetAC(), GetSTR(), GetSTA(), GetDEX(), GetAGI(), GetINT(), GetWIS(), GetCHA(), GetATK()), TempErrorMessageBuffer, 0, &affectedRows, &TempNewBotID)) {
errorMessage = std::string(TempErrorMessageBuffer);
}
else {
@@ -963,7 +958,7 @@
}
else {
// Update existing bot record
- if(!database.RunQuery(Query, MakeAnyLenString(&Query, "UPDATE bots SET BotOwnerCharacterID = '%u', BotSpellsID = '%u', Name = '%s', LastName = '%s', BotLevel = '%u', Race = '%i', Class = '%i', Gender = '%i', Size = '%f', Face = '%i', LuclinHairStyle = '%i', LuclinHairColor = '%i', LuclinEyeColor = '%i', LuclinEyeColor2 = '%i', LuclinBeardColor = '%i', LuclinBeard = '%i', DrakkinHeritage = '%i', DrakkinTattoo = '%i', DrakkinDetails = '%i', MR = '%i', CR = '%i', DR = '%i', FR = '%i', PR = '%i', AC = '%i', STR = '%i', STA = '%i', DEX = '%i', AGI = '%i', _INT = '%i', WIS = '%i', CHA = '%i', ATK = '%i', LastSpawnDate = NOW(), TotalPlayTime = '%u' WHERE BotID = '%u'", _botOwnerCharacterID, this->GetBotSpellID(), this->GetCleanName(), this->lastname, this->GetLevel(), _baseRace, this->GetClass(), _baseGender, GetSize(), this->GetLuclinFace(), this->GetHairStyle(), GetHairColor(), this->GetEyeColor1(), GetEyeColor2(), this->GetBeardColor(), this->GetBeard(), this->GetDrakkinHeritage(), GetDrakkinTattoo(), GetDrakkinDetails(), _baseMR, _baseCR, _baseDR, _baseFR, _basePR, _baseAC, _baseSTR, _baseSTA, _baseDEX, _baseAGI, _baseINT, _baseWIS, _baseCHA, _baseATK, GetTotalPlayTime(), GetBotID()), TempErrorMessageBuffer, 0, &affectedRows)) {
+ if(!database.RunQuery(Query, MakeAnyLenString(&Query, "UPDATE bots SET BotOwnerCharacterID = '%u', BotSpellsID = '%u', Name = '%s', LastName = '%s', BotLevel = '%u', Race = '%i', Class = '%i', Gender = '%i', Size = '%f', Face = '%i', LuclinHairStyle = '%i', LuclinHairColor = '%i', LuclinEyeColor = '%i', LuclinEyeColor2 = '%i', LuclinBeardColor = '%i', LuclinBeard = '%i', DrakkinHeritage = '%i', DrakkinTattoo = '%i', DrakkinDetails = '%i', MR = '%i', CR = '%i', DR = '%i', FR = '%i', PR = '%i', AC = '%i', STR = '%i', STA = '%i', DEX = '%i', AGI = '%i', _INT = '%i', WIS = '%i', CHA = '%i', ATK = '%i', LastSpawnDate = NOW(), TotalPlayTime = '%u', Filter = '%i' WHERE BotID = '%u'", _botOwnerCharacterID, this->GetBotSpellID(), this->GetCleanName(), this->lastname, this->GetLevel(), _baseRace, this->GetClass(), _baseGender, GetSize(), this->GetLuclinFace(), this->GetHairStyle(), GetHairColor(), this->GetEyeColor1(), GetEyeColor2(), this->GetBeardColor(), this->GetBeard(), this->GetDrakkinHeritage(), GetDrakkinTattoo(), GetDrakkinDetails(), _baseMR, _baseCR, _baseDR, _baseFR, _basePR, _baseAC, _baseSTR, _baseSTA, _baseDEX, _baseAGI, _baseINT, _baseWIS, _baseCHA, _baseATK, GetTotalPlayTime(), this->GetFilter(), GetBotID()), TempErrorMessageBuffer, 0, &affectedRows)) {
errorMessage = std::string(TempErrorMessageBuffer);
}
else {
@@ -972,17 +967,16 @@
}
}

- safe_delete(Query);
-
if(!errorMessage.empty() || (Result && affectedRows != 1)) {
if(GetBotOwner() && !errorMessage.empty())
GetBotOwner()->Message(13, errorMessage.c_str());
else if(GetBotOwner())
- GetBotOwner()->Message(13, std::string("Unable to save bot to the database.").c_str());
-
+ GetBotOwner()->Message(13, Query);
Result = false;
}

+ safe_delete(Query);
+
return Result;
}

@@ -1116,7 +1110,7 @@
// Bot AI
AI_Process();

- if(HasPet())
+ if(HasPet() && IsFamiliar()) //Shin: Patch for familiars meleeing.
PetAIProcess();

return true;
@@ -1139,102 +1133,58 @@
mlog(AI__SPELLS, "Engaged autocast check triggered. Trying to cast healing spells then maybe offensive spells.");

BotRaids *br = entity_list.GetBotRaidByMob(this);
-
+ bool retval = false; //Shin: used to see if we need to proceed bot routines.
if(botClass == CLERIC)
- {
- if(br && GetBotRaidID()) {
- // try to heal the raid main tank
- if(br->GetBotMainTank() && (br->GetBotMainTank()->GetHPRatio() < 80)) {
- if(!Bot_AICastSpell(br->GetBotMainTank(), 80, SpellType_Heal)) {
- if(!entity_list.Bot_AICheckCloseBeneficialSpells(t his, 80, MobAISpellRange, SpellType_Heal)) {
- if(!Bot_AICastSpell(this, 100, SpellType_Heal)) {
- AIautocastspell_timer->Start(RandomTimer(500, 2000), false);
- return true;
- }
- }
- }
+ { //Shin: Cleric Healing Priority System.
+ if(br && GetBotRaidID() && !(GetFilter() & AI_HEAL))
+ {
+ if(br->GetBotMainTank() && (br->GetBotMainTank()->GetHPRatio() < 80))
+ { // try to heal the raid main tank
+ if (!retval) retval = Bot_AICastSpell(br->GetBotMainTank(), 80, SpellType_Heal);
+ //if (!retval) retval = entity_list.Bot_AICheckCloseBeneficialSpells(this, 80, MobAISpellRange, SpellType_Heal);
+ if (!retval) retval = Bot_AICastSpell(this, 100, SpellType_Heal);
+ }
+ else if(br->GetBotSecondTank() && (br->GetBotSecondTank()->GetHPRatio() < 80))
+ { // try to heal the raid secondar tank
+ if (!retval) retval = Bot_AICastSpell(br->GetBotSecondTank(), 80, SpellType_Heal);
+ //if (!retval) retval = entity_list.Bot_AICheckCloseBeneficialSpells(this, 80, MobAISpellRange, SpellType_Heal);
+ if (!retval) retval = Bot_AICastSpell(this, 100, SpellType_Heal);
}
- // try to heal the raid secondar tank
- else if(br->GetBotSecondTank() && (br->GetBotSecondTank()->GetHPRatio() < 80)) {
- if(!Bot_AICastSpell(br->GetBotSecondTank(), 80, SpellType_Heal)) {
- if(!entity_list.Bot_AICheckCloseBeneficialSpells(t his, 80, MobAISpellRange, SpellType_Heal)) {
- if(!Bot_AICastSpell(this, 100, SpellType_Heal)) {
- AIautocastspell_timer->Start(RandomTimer(500, 2000), false);
- return true;
- }
- }
- }
- }
}
- if(!entity_list.Bot_AICheckCloseBeneficialSpells(t his, 100, MobAISpellRange, SpellType_Heal)) {
- if(!Bot_AICastSpell(this, 100, SpellType_Escape)) {
- if(!Bot_AICastSpell(this, 100, SpellType_Heal)) {
- if(!Bot_AICastSpell(GetTarget(), 5, SpellType_DOT | SpellType_Nuke | SpellType_Lifetap | SpellType_Dispel)) {
- AIautocastspell_timer->Start(RandomTimer(500, 2000), false);
- return true;
- }
- }
- }
- }
}
- else if((botClass == DRUID) || (botClass == SHAMAN) || (botClass == PALADIN) || (botClass == SHADOWKNIGHT) || (botClass == BEASTLORD) || (botClass == RANGER))
- {
- if (!Bot_AICastSpell(this, 100, SpellType_Escape | SpellType_Pet)) {
- if (!Bot_AICastSpell(this, 100, SpellType_Heal)) {
- if (!entity_list.Bot_AICheckCloseBeneficialSpells(thi s, 80, MobAISpellRange, SpellType_Heal)) {
- if(!Bot_AICastSpell(GetTarget(), 80, SpellType_Root | SpellType_Snare | SpellType_DOT | SpellType_Nuke | SpellType_Lifetap | SpellType_Dispel)) {
- AIautocastspell_timer->Start(RandomTimer(1000, 5000), false);
- return true;
- }
- }
+ if (botClass == NECROMANCER && botLevel > 42 && IsGrouped())
+ { //Shin: Necros should twitch!
+ Group *g = GetGroup();
+ for(int i=0; i<MAX_GROUP_MEMBERS; ++i)
+ { //Cycle Members.
+ if(g->members[i] && !g->members[i]->qglobal && g->members[i]->GetManaRatio() < 5)
+ { //Shin: if this member has less than 5% mana, try to twitch.
+ break;
+
}
}
}
- else if((botClass == WIZARD) || (botClass == MAGICIAN) || (botClass == NECROMANCER)) {
- if (!Bot_AICastSpell(this, 100, SpellType_Escape | SpellType_Pet)) {
- if(!Bot_AICastSpell(GetTarget(), 80, SpellType_Root | SpellType_Snare | SpellType_DOT | SpellType_Nuke | SpellType_Lifetap | SpellType_Dispel)) {
- //no spell to cast, try again soon.
- AIautocastspell_timer->Start(RandomTimer(500, 2000), false);
- return true;
- }
- }
- }
- else if(botClass == ENCHANTER) {
- // TODO: Make enchanter to be able to mez
- if (!Bot_AICastSpell(this, 100, SpellType_Escape | SpellType_Pet)) {
- if(!Bot_AICastSpell(GetTarget(), 80, SpellType_DOT | SpellType_Nuke | SpellType_Dispel)) {
- AIautocastspell_timer->Start(RandomTimer(500, 2000), false);
- return true;
- }
- }
+ if (botClass == BARD)
+ { //Shin: Bards should really just chain cast songs every 3 seconds.
+ Bot_AICastSpell(this, 100, SpellType_Heal);
+ AIautocastspell_timer->Start(1000, false);
+ return true;
}
- else if(botClass == BARD) {
- if(!Bot_AICastSpell(this, 100, SpellType_Buff)) {
- if(!Bot_AICastSpell(GetTarget(), 100, SpellType_Nuke | SpellType_Dispel | SpellType_Escape)) {// Bards will use their debuff songs
- AIautocastspell_timer->Start(RandomTimer(10, 50), false);
- return true;
- }
- }
- }
- else {
- // And for all the others classes..
- if(!Bot_AICastSpell(this, 100, SpellType_Heal | SpellType_Escape)) { // heal itself
- if (!entity_list.Bot_AICheckCloseBeneficialSpells(thi s, 80, MobAISpellRange, SpellType_Heal)) { // heal others
- if(!Bot_AICastSpell(GetTarget(), 80, SpellTypes_Detrimental)) { // nuke..
- AIautocastspell_timer->Start(RandomTimer(500, 2000), false); // timer 5 t 20 seconds
- return true;
- }
- }
- }
- }
-
- if(botClass != BARD) {
- AIautocastspell_timer->Start(RandomTimer(500, 2000), false);
- }
-
+ //Priority is like so: Cast Self Heal, See if we need to do Escape, Cast Group Member Heals, do other stuff.
+ if (!retval && (GetFilter() & AI_HEAL)) retval = Bot_AICastSpell(this, 100, SpellType_Heal);
+ if (!retval) retval = Bot_AICastSpell(this, 100, SpellType_Escape);
+ if (!retval && (GetFilter() & AI_HEAL)) retval = entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, MobAISpellRange, SpellType_Heal);
+ if (!retval) retval = Bot_AICastSpell(GetTarget(), 5,
+ ((GetFilter() & AI_ROOT) ? 0 : SpellType_Root) |
+ ((GetFilter() & AI_SNARE) ? 0 : SpellType_Snare) |
+ ((GetFilter() & AI_DOT) ? 0 : SpellType_DOT) |
+ ((GetFilter() & AI_NUKE) ? 0 : SpellType_Nuke) |
+ ((GetFilter() & AI_LIFETAP) ? 0 : SpellType_Lifetap) |
+ ((GetFilter() & AI_DISPEL) ? 0 : SpellType_Dispel) |
+ ((GetFilter() & AI_BUFF) ? 0 : SpellType_Buff));
+ if (retval) AIautocastspell_timer->Start(RandomTimer(500, 2000), false);
return true;
}
-
return false;
}

@@ -1244,9 +1194,10 @@
if(GetManaRatio() < 99.0f) {
if(mana_timer.Check(true)) {
SetAppearance(eaSitting, false);
- if(!((int)GetManaRatio() % 24)) {
- Say("Medding for Mana. I have %3.1f%% of %d mana. It is: %d", GetManaRatio(), GetMaxMana(), GetMana());
- }
+ //Shin: commented this out. Players can use #bot mana or see if bot is sitting/standing to know status without spam.
+ //if(!((int)GetManaRatio() % 24)) {
+ // Say("Medding for Mana. I have %3.1f%% of %d mana. It is: %d", GetManaRatio(), GetMaxMana(), GetMana());
+ //}
int32 level = GetLevel();
int32 regen = (((GetSkill(MEDITATE)/10)+(level-(level/4)))/4)+4;
spellbonuses.ManaRegen = 0;
@@ -1308,9 +1259,9 @@
else {
// Let's check our mana in fights..
if(mana_timer.Check(true)) {
- if((!((int)GetManaRatio() % 12)) && ((int)GetManaRatio() < 10)) {
- Say("Medding for Mana. I have %3.1f%% of %d mana. It is: %d", GetManaRatio(), GetMaxMana(), GetMana());
- }
+ //if((!((int)GetManaRatio() % 12)) && ((int)GetManaRatio() < 10)) {
+ // Say("Medding for Mana. I have %3.1f%% of %d mana. It is: %d", GetManaRatio(), GetMaxMana(), GetMana());
+ //}
int32 level = GetLevel();
spellbonuses.ManaRegen = 0;
for(int j=0; j<BUFF_COUNT; j++) {
@@ -1617,7 +1568,7 @@
mana_cost = 0;
sint32 extraMana = 0;
sint32 hasMana = GetMana();
- if(RuleB(Bots, BotFinishBuffing)) {
+ if(RuleB(Bots, BotFinishBuffing) && !(GetFilter() & AI_BUFF)) {
if(mana_cost > hasMana) {
// Let's have the bots complete the buff time process
if(iSpellTypes & SpellType_Buff) {
@@ -1632,18 +1583,19 @@
|| spells[AIspells[i].spellid].targettype==ST_AEBard)
&& dist2 <= spells[AIspells[i].spellid].aoerange*spells[AIspells[i].spellid].aoerange)
|| dist2 <= spells[AIspells[i].spellid].range*spells[AIspells[i].spellid].range) && (mana_cost <= GetMana() || GetMana() == GetMaxMana())) {
-
switch (AIspells[i].type) {
case SpellType_Heal: {
if (
( (spells[AIspells[i].spellid].targettype==ST_GroupTeleport || spells[AIspells[i].spellid].targettype == ST_Target || tar == this)
&& tar->DontHealMeBefore() < Timer::GetCurrentTime()
- && tar->CanBuffStack(AIspells[i].spellid, botLevel, true) >= 0))
+ && tar->CanBuffStack(AIspells[i].spellid, botLevel, true) >= 0)
+ )
{
if(botClass == BARD) {
if(IsEffectInSpell(AIspells[i].spellid, SE_MovementSpeed) && !zone->CanCastOutdoor()) {
break;
}
+
}
int8 hpr = (int8)tar->GetHPRatio();
if(hpr<= 80 || ((tar->IsClient()||tar->IsPet()) && (hpr <= 98)) || (botClass == BARD))
@@ -1656,9 +1608,12 @@
break;
}
}
-
+ if (GetFilter() & AI_HEAL) break; //Shin: Don't do heal type spells if AI_HEAL is on
+ //Shin: Don't use a HoT if HoTs are filtered.
+ if (IsEffectInSpell(AIspells[i].spellid, SE_HealOverTime) && (GetFilter() & AI_HOT)) break;
int32 TempDontHealMeBeforeTime = tar->DontHealMeBefore();
-
+ printf("Heal: %s is casting %s that has a priority of %i", this->name, spells[AIspells[i].spellid].name, AIspells[i].priority);
+
AIDoSpellCast(i, tar, mana_cost, &TempDontHealMeBeforeTime);

if(TempDontHealMeBeforeTime != tar->DontHealMeBefore())
@@ -1702,6 +1657,7 @@
return(false); //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call
checked_los = true;
}
+ if (GetFilter() & AI_ROOT) break;
int32 TempDontRootMeBefore = tar->DontRootMeBefore();
AIDoSpellCast(i, tar, mana_cost, &TempDontRootMeBefore);
if(TempDontRootMeBefore != tar->DontRootMeBefore())
@@ -1717,13 +1673,14 @@
&& !tar->IsImmuneToSpell(AIspells[i].spellid, this)
&& (tar->CanBuffStack(AIspells[i].spellid, botLevel, true) >= 0)
&& !(tar->IsPet() && tar->CastToBot()->GetBotOwner()->IsClient() && this != tar) //no buffing PC's pets, but they can buff themself
-
) {
// Put the zone levitate and movement check here since bots are able to bypass the client casting check
+ if (GetFilter() & AI_BUFF) break; //Shin: Don't buff if filter is on
if( (IsEffectInSpell(AIspells[i].spellid, SE_Levitate) && !zone->CanLevitate()) ||
(IsEffectInSpell(AIspells[i].spellid, SE_MovementSpeed) && !zone->CanCastOutdoor())) {
break;
}
+ if (IsEffectInSpell(AIspells[i].spellid, SE_Illusion) && (GetFilter() & AI_ILLUSION)) break; //Shin: Don't do illusion spells if filter is on.
// when a pet class buffs its pet, it only needs to do it once
if(spells[AIspells[i].spellid].targettype == ST_Pet) {
Mob* newtar = GetPet();
@@ -1737,6 +1694,7 @@
}
}
int32 TempDontBuffMeBefore = tar->DontBuffMeBefore();
+ printf("Buff: %s is casting %s that has a priority of %i\n", this->name, spells[AIspells[i].spellid].name, AIspells[i].priority);
AIDoSpellCast(i, tar, mana_cost, &TempDontBuffMeBefore);
if(TempDontBuffMeBefore != tar->DontBuffMeBefore())
tar->SetDontBuffMeBefore(TempDontBuffMeBefore);
@@ -1768,31 +1726,37 @@
if(((MakeRandomInt(1, 100) < 50) || ((botClass == BARD) || (botClass == SHAMAN) || (botClass == ENCHANTER)))
&& ((tar->GetHPRatio() <= 95.0f) || ((botClass == BARD) || (botClass == SHAMAN) || (botClass == ENCHANTER)))
&& !tar->IsImmuneToSpell(AIspells[i].spellid, this)
- && (tar->CanBuffStack(AIspells[i].spellid, botLevel, true) >= 0))
+ && (tar->CanBuffStack(AIspells[i].spellid, botLevel, true) >= 0)
+ ) //Shin: Don't nuke if filter on
{
+
if(!checked_los) {
if(!CheckLosFN(tar))
return(false); //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call
checked_los = true;
}
+
+ if(IsFearSpell(AIspells[i].spellid) && ((GetFilter() & AI_FEAR) || tar->GetSnaredAmount() == -1 || !tar->IsRooted()))
+ break; //Don't let fear cast if the npc isn't snared or rooted or if fear disabled
+
+ if(IsSlowSpell(AIspells[i].spellid) && (GetFilter() & AI_SLOW))
+ break; //Don't let slow cast if filter is on

- if(IsFearSpell(AIspells[i].spellid))
- { // don't let fear cast if the npc isn't snared or rooted
- if(tar->GetSnaredAmount() == -1)
- {
- if(!tar->IsRooted())
- return false;
- }
- }
+ if(IsPureNukeSpell(AIspells[i].spellid) && (GetFilter() & AI_NUKE))
+ break; //Don't let nuke cast if filter is on

+ if((IsEffectInSpell(AIspells[i].spellid, SE_DEX) || IsEffectInSpell(AIspells[i].spellid,SE_ResistMagic)) && (GetFilter() & AI_DEBUFF))
+ break; //Don't let debuffs cast if filter is on.. This one is weird, I think it'll stop malo/cripple only.
+
+ printf("Nuke: %s is casting %s that has a priority of %i\n", this->name, spells[AIspells[i].spellid].name, AIspells[i].priority);
AIDoSpellCast(i, tar, mana_cost);
return true;
}
break;
}
case SpellType_Dispel: {
- if(tar->GetHPRatio() > 95.0f)
- {
+ if(tar->GetHPRatio() > 95.0f && !(GetFilter() & AI_DISPEL))
+ { //Shin: if mob's HP is greater than 95% and filter allows it
if(!checked_los) {
if(!CheckLosFN(tar))
return(false); //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call
@@ -1898,8 +1862,10 @@
case SpellType_Lifetap: {
if ((tar->GetHPRatio() <= 90.0f)
&& !tar->IsImmuneToSpell(AIspells[i].spellid, this)
- && (tar->CanBuffStack(AIspells[i].spellid, botLevel, true) >= 0))
+ && (tar->CanBuffStack(AIspells[i].spellid, botLevel, true) >= 0)
+ )
{
+ if (GetFilter() & AI_LIFETAP) return false; //Shin: Don't lifetap if filter on
if(!checked_los) {
if(!CheckLosFN(tar))
return(false); //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call
@@ -1917,6 +1883,8 @@
&& tar->DontSnareMeBefore() < Timer::GetCurrentTime()
&& tar->CanBuffStack(AIspells[i].spellid, botLevel, true) >= 0
) {
+
+ if (GetFilter() & AI_SNARE) return false; //Shin: Don't snare if filter on
if(!checked_los) {
if(!CheckLosFN(tar))
return(false); //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call
@@ -1938,12 +1906,15 @@
&& tar->DontDotMeBefore() < Timer::GetCurrentTime()
&& tar->CanBuffStack(AIspells[i].spellid, botLevel, true) >= 0
) {
+
+ if (GetFilter() & AI_DOT) return false; //Shin: Don't dot if filter on
if(!checked_los) {
if(!CheckLosFN(tar))
return(false); //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call
checked_los = true;
}
int32 TempDontDotMeBefore = tar->DontDotMeBefore();
+ printf("Dot: %s is casting %s that has a priority of %i\n", this->name, spells[AIspells[i].spellid].name, AIspells[i].priority);
AIDoSpellCast(i, tar, mana_cost, &TempDontDotMeBefore);
if(TempDontDotMeBefore != tar->DontDotMeBefore())
tar->SetDontDotMeBefore(TempDontDotMeBefore);
@@ -1955,6 +1926,7 @@
default: {
// TODO: log this error
//cout<<"Error: Unknown spell type in AICastSpell. caster:"<<this->GetCleanName()<<" type:"<<AIspells[i].type<<" slot:"<<i<<endl;
+ printf("%s casted an unknown spell %s (%i) of type: %i", this->name, spells[AIspells[i].spellid].name, AIspells[i].spellid, AIspells[i].type);
break;
}
}
@@ -1977,7 +1949,8 @@
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting.

mlog(AI__SPELLS, "Bot Engaged (pursuing) autocast check triggered. Trying to cast offensive spells.");
- if(!Bot_AICastSpell(GetTarget(), 90, SpellType_Root | SpellType_Nuke | SpellType_Lifetap | SpellType_Snare | SpellType_DOT | SpellType_Dispel)) {
+ //Shin: This is a bit of a mess to look at but it's a lot easier than the alternatives.
+ if(!Bot_AICastSpell(GetTarget(), 90, ((!(GetFilter() & AI_ROOT)) ? SpellType_Root : 0) | SpellType_Nuke | SpellType_Lifetap | SpellType_Snare | SpellType_DOT | SpellType_Dispel)) {
//no spell cast, try again soon.
AIautocastspell_timer->Start(RandomTimer(500, 2000), false);
} //else, spell casting finishing will reset the timer.
@@ -1996,101 +1969,79 @@
#endif
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting.

- //Ok, IdleCastCheck depends of class.
- // Healers WITHOUT pets will check if a heal is needed before buffing.
int8 botClass = GetClass();
- if(botClass == CLERIC || botClass == PALADIN || botClass == RANGER)
- {
- if (!Bot_AICastSpell(this, 50, SpellType_Heal | SpellType_Buff))
- {
- if(!entity_list.Bot_AICheckCloseBeneficialSpells(t his, 50, MobAISpellRange, SpellType_Heal))
- {
- if(!entity_list.Bot_AICheckCloseBeneficialSpells(t his, 50, MobAISpellRange, SpellType_Buff))
- {
- if(IsGrouped())
- {
- Group *g = GetGroup();
- if(g) {
- for(int i=0; i<MAX_GROUP_MEMBERS; ++i)
- {
- if(g->members[i] && !g->members[i]->qglobal && (g->members[i]->GetHPRatio() < 99))
- {
- if(Bot_AICastSpell(g->members[i], 100, SpellType_Heal))
- {
- AIautocastspell_timer->Start(RandomTimer(1000, 5000), false);
- return true;
- }
- }
- if(g->members[i] && !g->members[i]->qglobal && g->members[i]->GetPetID())
- {
- if(Bot_AICastSpell(g->members[i]->GetPet(), 100, SpellType_Heal))
- {
- AIautocastspell_timer->Start(RandomTimer(1000, 5000), false);
- return true;
- }
- }
- }
- }
+ if(botClass != BERSERKER || botClass != ROGUE || botClass != WARRIOR || botClass != BARD)
+ { //Shin: If class can cast spells
+ bool retval = false; //Shin: This tracks if previous Bot_AICastSpell returned false.
+ //Shin: Below checks filter conditions and if not set try to do the routine
+ //Those routines success/fail and get set to retval, which tells the next routine if it may run or not.
+ if (!(GetFilter() & AI_PET)) retval = Bot_AICastSpell(this, 100, SpellType_Pet);
+ if (!retval && (GetFilter() & AI_HEAL) && !(GetFilter() & AI_BUFF)) retval = Bot_AICastSpell(this, 100, SpellType_Buff);
+ else if (!retval && !(GetFilter() & AI_HEAL) && (GetFilter() & AI_BUFF)) retval = Bot_AICastSpell(this, 100, SpellType_Heal);
+ else if (!retval && !(GetFilter() & AI_HEAL) && !(GetFilter() & AI_BUFF)) retval = Bot_AICastSpell(this, 50, SpellType_Heal | SpellType_Buff);
+ if (!retval && !(GetFilter() & AI_HEAL)) retval = entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, MobAISpellRange, SpellType_Heal);
+ else if (!retval && !(GetFilter() & AI_BUFF)) retval = entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, MobAISpellRange, SpellType_Buff);
+
+ if(!retval && IsGrouped() && (!(GetFilter() & AI_HEAL))
+ && (botClass == PALADIN || botClass == DRUID || botClass == RANGER || botClass == SHAMAN || botClass == CLERIC
+ || botClass == BEASTLORD) )
+ { //Shin: Now for group checks. If I'm able to heal, do so.
+ Group *g = GetGroup();
+ int8 lowhp = 100; //Shin: This is to track who has the lowest HP in party.
+ int8 lowm = -1; //Shin: This tracks what member has lowest HP
+ int8 i = 0;
+ if(g) {
+ for(i=0; i<MAX_GROUP_MEMBERS; ++i)
+ { //Shin: Go through group members
+ if(g->members[i] && !g->members[i]->qglobal && lowhp < g->members[i]->GetHPRatio())
+ { //Shin: if this member has less HP than our var, set this member as heal target.
+ lowhp = g->members[i]->GetHPRatio();
+ lowm = i;
}
- AIautocastspell_timer->Start(RandomTimer(1000, 5000), false);
- return(true);
}
- }
- }
- }
- // Pets class will first cast their pet, then buffs
- else if(botClass == DRUID || botClass == MAGICIAN || botClass == SHADOWKNIGHT || botClass == SHAMAN || botClass == NECROMANCER || botClass == ENCHANTER || botClass == BEASTLORD || botClass == WIZARD)
- {
- if (!Bot_AICastSpell(this, 100, SpellType_Pet))
- {
- if (!Bot_AICastSpell(this, 50, SpellType_Heal | SpellType_Buff))
- {
- if(!entity_list.Bot_AICheckCloseBeneficialSpells(t his, 50, MobAISpellRange, SpellType_Heal))
- {
- if(!entity_list.Bot_AICheckCloseBeneficialSpells(t his, 50, MobAISpellRange, SpellType_Buff)) // then buff the group
- {
- if(IsGrouped())
- {
- Group *g = GetGroup();
- if(g) {
- for(int i=0; i<MAX_GROUP_MEMBERS; ++i)
- {
- if(g->members[i] && !g->members[i]->qglobal && (g->members[i]->GetHPRatio() < 99))
- {
- if(Bot_AICastSpell(g->members[i], 100, SpellType_Heal))
- {
- AIautocastspell_timer->Start(RandomTimer(1000, 5000), false);
- return true;
- }
- }
- if(g->members[i] && !g->members[i]->qglobal && g->members[i]->GetPetID())
- {
- if(Bot_AICastSpell(g->members[i]->GetPet(), 100, SpellType_Heal))
- {
- AIautocastspell_timer->Start(RandomTimer(1000, 5000), false);
- return true;
- }
- }
- }
- }
+ if (lowm != -1 && lowm < MAX_GROUP_MEMBERS)
+ { //Shin: If a member was found with low HP
+ if(Bot_AICastSpell(g->members[lowm], 100, SpellType_Heal))
+ { //Shin: Heal them.
+ AIautocastspell_timer->Start(RandomTimer(1000, 3000), false);
+ return true;
+ }
+ }
+ else
+ { //Shin: No member was found with low life, check if a pet needs healing.
+ lowhp = 100;
+ lowm = -1;
+ for(i=0; i<MAX_GROUP_MEMBERS; ++i)
+ { //Go through group members
+ if(g->members[i] && !g->members[i]->qglobal && g->members[i]->GetPetID() && g->members[i]->GetPet()->GetHPRatio() < lowhp)
+ { //If they have a pet and it's HP is lower than lowhp
+ lowhp = g->members[i]->GetPet()->GetHPRatio();
+ lowm = i;
}
- AIautocastspell_timer->Start(RandomTimer(1000, 5000), false);
- return(true);
}
+ if (lowm != -1 && lowm < MAX_GROUP_MEMBERS)
+ { //Shin: If a member's pet was found with low HP
+ if(Bot_AICastSpell(g->members[lowm]->GetPet(), 100, SpellType_Heal))
+ { //Shin: Heal them.
+ AIautocastspell_timer->Start(RandomTimer(1000, 3000), false);
+ return true;
+ }
+ }
}
}
}
- }
- // bard bots
+ //AIautocastspell_timer->Start(RandomTimer(1000, 3000), false); //Temporarily commented out.
+ return(true);
+
+ }
else if(botClass == BARD)
{
Bot_AICastSpell(this, 100, SpellType_Heal);
AIautocastspell_timer->Start(1000, false);
return true;
}
-
- // and standard buffing for others..
else {
+ //Shin: TODO Add filters for each type
if (!Bot_AICastSpell(this, 100, SpellType_Heal | SpellType_Buff | SpellType_Pet))
{
if(!entity_list.Bot_AICheckCloseBeneficialSpells(t his, 50, MobAISpellRange, SpellType_Heal | SpellType_Buff)) {
@@ -2099,7 +2050,7 @@
}
}
}
- AIautocastspell_timer->Start(RandomTimer(1000, 5000), false);
+ AIautocastspell_timer->Start(RandomTimer(1000, 3000), false);
return true;
}
return false;
@@ -2114,6 +2065,11 @@
}
}

+//Shin: Set Filter to value argument.
+void Bot::SetFilter(uint16 value) {
+ _filter = value;
+}
+
// AI Processing for the Bot object
void Bot::AI_Process() {
_ZP(Mob_BOT_Process);
@@ -2141,8 +2097,8 @@
if(!BotOwner || BotOwner->qglobal || (GetAppearance() == eaDead) || BotOwner->IsBot())
return;

- if(!IsEngaged()) {
- if(GetFollowID()) {
+ if(!IsEngaged()) { //Shin: Not engaged, see if owner is and if so, add to my hate list.
+ if(GetFollowID()) {
if(BotOwner && BotOwner->CastToClient()->AutoAttackEnabled() && BotOwner->GetTarget() &&
BotOwner->GetTarget()->IsNPC() && BotOwner->GetTarget()->GetHateAmount(BotOwner)) {
AddToHateList(BotOwner->GetTarget(), 1);
@@ -2242,7 +2198,7 @@
{
cast_last_time = true;
}
- if(is_combat_range && cast_last_time)
+ if(is_combat_range && cast_last_time && !(GetFilter() & AI_MELEE))
{
cast_last_time = false;

@@ -2258,9 +2214,9 @@
}
tar_ndx = 0;
}
-
- if(!IsMoving() && GetClass() == ROGUE && !BehindMob(GetTarget(), GetX(), GetY())) {
- // Move the rogue to behind the mob
+ //Shin: If I am told to melee, and I'm not taunting, stay behind.
+ if(!IsMoving() && (GetFilter() & AI_TAUNT) && !(GetFilter() & AI_MELEE) && !BehindMob(GetTarget(), GetX(), GetY())) {
+ // Move the melee to behind the mob
float newX = 0;
float newY = 0;
float newZ = 0;
@@ -2270,7 +2226,8 @@
return;
}
}
- else if(!IsMoving() && GetClass() != ROGUE && (DistNoRootNoZ(*GetTarget()) < GetTarget()->GetSize())) {
+ //Shin: Not used anymore due to behind target being default positioning.
+ /*else if(!IsMoving() && GetClass() != ROGUE && (DistNoRootNoZ(*GetTarget()) < GetTarget()->GetSize())) {
// If we are not a rogue trying to backstab, let's try to adjust our melee range so we don't appear to be bunched up
float newX = 0;
float newY = 0;
@@ -2280,8 +2237,7 @@
CalculateNewPosition2(newX, newY, newZ, GetRunspeed());
return;
}
- }
-
+ }*/
if(IsBotArcher() && ranged_timer.Check(false)) {
if(MakeRandomInt(1, 100) > 95) {
this->AI_EngagedCastCheck();
@@ -2294,7 +2250,7 @@
}

// we can't fight if we don't have a target, are stun/mezzed or dead..
- if(!IsBotArcher() && GetTarget() && !IsStunned() && !IsMezzed() && (GetAppearance() != eaDead))
+ if(!IsBotArcher() && GetTarget() && !(GetFilter() & AI_MELEE) && !IsStunned() && !IsMezzed() && (GetAppearance() != eaDead))
{
// First, special attack per class (kick, backstab etc..)
DoClassAttacks(GetTarget());
@@ -2525,7 +2481,19 @@
int followdist = 184; /*GetFollowDistance();*/
SetRunAnimSpeed(0);
if(dist2 > followdist) {
- CalculateNewPosition2(follow->GetX(), follow->GetY(), follow->GetZ(), GetRunspeed(), false);
+ //Shin: This is to make mob's positions more realistic. SHIN POSITION
+ int mypos = 0;
+ if (IsGrouped())
+ {
+ Group *g = GetGroup();
+ for(int i=0; i<MAX_GROUP_MEMBERS; ++i)
+ {
+ if(g->members[i] && g->GetID() == GetID())
+ mypos = i*2;
+ }
+ }
+ CalculateNewPosition2(follow->GetX()-mypos, follow->GetY()+mypos, follow->GetZ(), follow->GetRunspeed(), false);
+ //CalculateNewPosition2(follow->GetX(), follow->GetY(), follow->GetZ(), GetRunspeed(), false);
}
else {
SetHeading(follow->GetHeading());
@@ -2564,7 +2532,8 @@
if (botPet->IsCasting())
return;

- // if our owner isn't a pet or if he is not a client...
+ // if our owner isn't a pet or if he is not a client..
+
if (!botPet->GetOwner()->IsBot() || ( !botPet->GetOwner()->IsBot() && !botPet->GetOwner()->IsClient() ) )
return;

@@ -2608,7 +2577,7 @@
// I should probably make the casters staying in place so they can cast..

// Ok, we 're a melee or any other class lvl<12. Yes, because after it becomes hard to go in melee for casters.. even for bots..
- if( is_combat_range ) {
+ if( is_combat_range) {
botPet->GetAIMovementTimer()->Check();
if(botPet->IsMoving()) {
botPet->SetRunAnimSpeed(0);
@@ -2701,7 +2670,7 @@
botPet->SetRunAnimSpeed(0);
if(!botPet->IsRooted()) {
mlog(AI__WAYPOINTS, "Pursuing %s while engaged.", botPet->GetTarget()->GetCleanName());
- botPet->CalculateNewPosition2(botPet->GetTarget()->GetX(), botPet->GetTarget()->GetY(), botPet->GetTarget()->GetZ(), botPet->GetOwner()->GetRunspeed(), false);
+ botPet->CalculateNewPosition2(botPet->GetTarget()->GetX(), botPet->GetTarget()->GetY(), botPet->GetTarget()->GetZ(), botPet->GetOwner()->GetRunspeed()+100, false);
}
else {
botPet->SetHeading(botPet->GetTarget()->GetHeading());
@@ -2733,7 +2702,22 @@
float dist = botPet->DistNoRoot(*botPet->GetTarget());
botPet->SetRunAnimSpeed(0);
if(dist > 184) {
- botPet->CalculateNewPosition2(botPet->GetTarget()->GetX(), botPet->GetTarget()->GetY(), botPet->GetTarget()->GetZ(), botPet->GetTarget()->GetRunspeed(), false);
+ //Shin: This is to make mob's positions more realistic. SHIN POSITION
+ int mypos = 0;
+ if (IsGrouped())
+ {
+ Group *g = GetGroup();
+ for(int i=0; i<MAX_GROUP_MEMBERS; ++i)
+ {
+ if(g->members[i] && g->GetID() == botPet->GetID())
+ {
+ mypos = i*2;
+ break;
+ }
+ }
+ }
+ botPet->CalculateNewPosition2(botPet->GetTarget()->GetX()-mypos, botPet->GetTarget()->GetY()+mypos, botPet->GetTarget()->GetZ(), botPet->GetTarget()->GetRunspeed(), false);
+ //botPet->CalculateNewPosition2(botPet->GetTarget()->GetX(), botPet->GetTarget()->GetY(), botPet->GetTarget()->GetZ(), botPet->GetTarget()->GetRunspeed(), false);
}
else {
botPet->SetHeading(botPet->GetTarget()->GetHeading());
@@ -3081,13 +3065,14 @@
MYSQL_RES* DatasetResult;
MYSQL_ROW DataRow;

- if(!database.RunQuery(Query, MakeAnyLenString(&Query, "SELECT BotOwnerCharacterID, BotSpellsID, Name, LastName, BotLevel, Race, Class, Gender, Size, Face, LuclinHairStyle, LuclinHairColor, LuclinEyeColor, LuclinEyeColor2, LuclinBeardColor, LuclinBeard, DrakkinHeritage, DrakkinTattoo, DrakkinDetails, MR, CR, DR, FR, PR, AC, STR, STA, DEX, AGI, _INT, WIS, CHA, ATK, BotCreateDate, LastSpawnDate, TotalPlayTime FROM bots WHERE BotID = '%u'", botID), TempErrorMessageBuffer, &DatasetResult)) {
+ if(!database.RunQuery(Query, MakeAnyLenString(&Query, "SELECT BotOwnerCharacterID, BotSpellsID, Name, LastName, BotLevel, Race, Class, Gender, Size, Face, LuclinHairStyle, LuclinHairColor, LuclinEyeColor, LuclinEyeColor2, LuclinBeardColor, LuclinBeard, DrakkinHeritage, DrakkinTattoo, DrakkinDetails, MR, CR, DR, FR, PR, AC, STR, STA, DEX, AGI, _INT, WIS, CHA, ATK, BotCreateDate, LastSpawnDate, TotalPlayTime, Filter FROM bots WHERE BotID = '%u'", botID), TempErrorMessageBuffer, &DatasetResult)) {
*errorMessage = std::string(TempErrorMessageBuffer);
}
else {
while(DataRow = mysql_fetch_row(DatasetResult)) {
NPCType TempNPCStruct = FillNPCTypeStruct(atoi(DataRow[1]), std::string(DataRow[2]), std::string(DataRow[3]), atoi(DataRow[4]), atoi(DataRow[5]), atoi(DataRow[6]), atoi(DataRow[7]), atof(DataRow[8]), atoi(DataRow[9]), atoi(DataRow[10]), atoi(DataRow[11]), atoi(DataRow[12]), atoi(DataRow[13]), atoi(DataRow[14]), atoi(DataRow[15]), atoi(DataRow[16]), atoi(DataRow[17]), atoi(DataRow[18]), atoi(DataRow[19]), atoi(DataRow[20]), atoi(DataRow[21]), atoi(DataRow[22]), atoi(DataRow[23]), atoi(DataRow[24]), atoi(DataRow[25]), atoi(DataRow[26]), atoi(DataRow[27]), atoi(DataRow[28]), atoi(DataRow[29]), atoi(DataRow[30]), atoi(DataRow[31]), atoi(DataRow[32]));
Result = new Bot(botID, atoi(DataRow[0]), atoi(DataRow[1]), atof(DataRow[35]), TempNPCStruct);
+ Result->SetFilter(atoi(DataRow[36])); //Shin: Get filter from database.
break;
}

@@ -4687,11 +4672,9 @@
sint16 Bot::GetBotFocusEffect(botfocusType bottype, int16 spell_id) {
if (IsBardSong(spell_id))
return 0;
-
const Item_Struct* TempItem = 0;
sint16 Total = 0;
sint16 realTotal = 0;
-
//item focus
for(int x=0; x<=21; x++) {
TempItem = database.GetItem(GetBotItem(x));
@@ -6164,7 +6147,7 @@

//general stuff, for all classes....
//only gets used when their primary ability get used too
- if (taunting && HasOwner() && target->IsNPC() && target->GetBodyType() != BT_Undead && taunt_time) {
+ if (!(GetFilter() & AI_TAUNT) && taunting && HasOwner() && target->IsNPC() && target->GetBodyType() != BT_Undead && taunt_time) {
Taunt(target->CastToNPC(), false);
}

@@ -8116,7 +8099,6 @@
void Bot::CalcBotStats(bool showtext) {
if(!GetBotOwner())
return;
-
if(showtext) {
GetBotOwner()->Message(15, "Bot updating...");
}
@@ -8126,16 +8108,12 @@
GetBotOwner()->Message(15, "Previous Bots Code releases did not check Race/Class combinations during create.");
GetBotOwner()->Message(15, "Unless you are experiencing heavy lag, you should delete and remake this bot.");
}
-
if(GetBotOwner()->GetLevel() != GetLevel())
SetLevel(GetBotOwner()->GetLevel());

GenerateBaseStats();
-
GenerateAABonuses();
-
GenerateArmorClass();
-
//// Calc Base Hit Points
//int16 lm = GetClassLevelFactor();
//int16 Post255;
@@ -8145,21 +8123,16 @@
// Post255 = 0;
//sint32 bot_hp = (5)+(blevel*lm/10) + (((bsta-Post255)*blevel*lm/3000)) + ((Post255*blevel)*lm/6000);
GenerateBaseHitPoints();
-
GenerateBaseManaPoints();
-
GenerateSpecialAttacks();
-
if(showtext) {
GetBotOwner()->Message(15, "Base stats:");
GetBotOwner()->Message(15, "Level: %i HP: %i AC: %i Mana: %i STR: %i STA: %i DEX: %i AGI: %i INT: %i WIS: %i CHA: %i", GetLevel(), base_hp, AC, max_mana, STR, STA, DEX, AGI, INT, WIS, CHA);
GetBotOwner()->Message(15, "Resists-- Magic: %i, Poison: %i, Fire: %i, Cold: %i, Disease: %i.",MR,PR,FR,CR,DR);
}
-
// Let's find the items in the bot inventory
sint32 items_hp = 0;
sint32 items_mana = 0;
-
if(this->Save())
this->GetBotOwner()->CastToClient()->Message(0, "%s saved.", this->GetCleanName());
else
@@ -8329,6 +8302,8 @@
c->Message(0, "#bot magepet [earth|water|air|fire|monster] - Select the pet type you want your Mage bot to use.");
c->Message(0, "#bot giveitem - Gives your targetted bot the item you have on your cursor.");
c->Message(0, "#bot camp - Tells your bot to camp out of the game.");
+ c->Message(0, "#bot mana - Report targetted bot's current mana.");
+ c->Message(0, "#bot filter [list|option] [only|on|off] - Filter various bot routines. Use '#bot filter list' to see options."); //Shin: For #bot filter
return;
}

@@ -8364,7 +8339,246 @@

return;
}
+ if (!strcasecmp(sep->arg[1], "mana"))
+ { //Shin: Mana Report
+ if((c->GetTarget() == NULL) || (c->GetTarget() == c) || !c->GetTarget()->IsBot()) {
+ c->Message(15, "You must target a bot!");
+ return;
+ }
+ Bot* tBot = c->GetTarget()->CastToBot(); //I also changed the report structure, cleaner and easier on eyes.
+ tBot->Say("I have %3.1f%% mana. (%d/%d)", tBot->GetManaRatio(), tBot->GetMana(), tBot->GetMaxMana());
+ }

+ if (!strcasecmp(sep->arg[1], "status"))
+ { //Shin: Mana Report
+ if((c->GetTarget() == NULL) || (c->GetTarget() == c) || !c->GetTarget()->IsBot()) {
+ c->Message(15, "You must target a bot!");
+ return;
+ }
+ Bot* tBot = c->GetTarget()->CastToBot(); //I also changed the report structure, cleaner and easier on eyes.
+ switch(c->GetTarget()->CastToBot()->myStatus)
+ {
+ case Idle:
+ tBot->Say("I am currently idle.");
+ break;
+ case Casting:
+ tBot->Say("I am casting.");
+ break;
+ case Nuking:
+ tBot->Say("I am nuking.");
+ break;
+ case Buffing:
+ tBot->Say("I am buffing.");
+ break;
+ case Meleeing:
+ tBot->Say("I am meleeing.");
+ break;
+ case Healing:
+ tBot->Say("I am healing.");
+ break;
+ }
+ }
+
+ if (!strcasecmp(sep->arg[1], "filter"))
+ { //Shin: #bot filter comamnd list. A single private bitmask int16 named _filter is used to store the filter options.
+ if((c->GetTarget() == NULL) || (c->GetTarget() == c) || !c->GetTarget()->IsBot()) {
+ c->Message(15, "You must target a bot you own!");
+ return;
+ }
+ if(c->GetTarget() && c->GetTarget()->IsBot() && (c->GetTarget()->CastToBot()->GetBotOwner() != c)) {
+ c->Message(15, "This command only works on a bot you own.");
+ return;
+ }
+ Bot* tBot = c->GetTarget()->CastToBot(); //Bot is simply going to be tBot (target bot) from now on in this command.
+
+ if (!strcasecmp(sep->arg[2], "list"))
+ { //Shin: List filters. TODO: Don't list filters that aren't used by bot class.
+ string buffer = "I will: ";
+ string buffer2 = ", I will not: ";
+ ((tBot->GetFilter() & AI_HEAL)) ? buffer2 += "[heal], " : buffer += "[heal], ";
+ ((tBot->GetFilter() & AI_ROOT)) ? buffer2 += "[root], " : buffer += "[root], ";
+ ((tBot->GetFilter() & AI_SNARE)) ? buffer2 += "[snare], " : buffer += "[snare], ";
+ ((tBot->GetFilter() & AI_SLOW)) ? buffer2 += "[slow], " : buffer += "[slow], ";
+ ((tBot->GetFilter() & AI_NUKE)) ? buffer2 += "[nuke], " : buffer += "[nuke], ";
+ ((tBot->GetFilter() & AI_DOT)) ? buffer2 += "[dot], " : buffer += "[dot], ";
+ ((tBot->GetFilter() & AI_PET)) ? buffer2 += "use a [pet], " : buffer += "use a [pet], ";
+ ((tBot->GetFilter() & AI_ILLUSION)) ? buffer2 += "use [illusion] spells, " : buffer += "use [illusion] spells, ";
+ ((tBot->GetFilter() & AI_LIFETAP)) ? buffer2 += "[lifetap], " : buffer += "[lifetap], ";
+ ((tBot->GetFilter() & AI_HOT)) ? buffer2 += "use [hot] (heal over time) spells, " : buffer += "use [hot] (heal over time) spells, ";
+ ((tBot->GetFilter() & AI_BUFF)) ? buffer2 += "[buff], " : buffer += "[buff], ";
+ ((tBot->GetFilter() & AI_FEAR)) ? buffer2 += "[fear], " : buffer += "[fear], ";
+ ((tBot->GetFilter() & AI_DISPEL)) ? buffer2 += "[dispel], " : buffer += "[dispel], ";
+ ((tBot->GetFilter() & AI_MELEE)) ? buffer2 += "[melee], " : buffer += "[melee], ";
+ ((tBot->GetFilter() & AI_TAUNT)) ? buffer2 += "[taunt], " : buffer += "[taunt], ";
+ ((tBot->GetFilter() & AI_DEBUFF)) ? buffer2 += "[debuff], " : buffer += "[debuff], ";
+ if (buffer.length() == 8)
+ { //Shin: No options set, say so.
+ tBot->Say("I currently will do no AI routines. Use [#bot filter reset] to turn off all my filters.");
+ return;
+ }
+ buffer = buffer.substr(0, buffer.length()-2); //Shin: Take off the tail comma and space
+ if (buffer2.length() == 14) buffer2 = ""; //Shin: All filters are off, so no need for buffer2.
+ else buffer2 = buffer2.substr(0, buffer2.length()-2); //Shin: Take off the tail comma and space
+ buffer = buffer+buffer2+". Use [#bot filter <option>] to change how I work with a filter.";
+ tBot->Say(buffer.c_str());
+ return;
+ }
+ else if (!strcasecmp(sep->arg[2], "reset"))
+ { //Shin: Reset my filters.
+ tBot->SetFilter(0);
+ tBot->Say("I have reset my filters, turning all filters off at this time. Use [#bot filter list] to get a list of filters.");
+ }
+ else if (!strcasecmp(sep->arg[2], "code"))
+ { //This is my "save game" system, allowing you to save in a hotkey various templates for bots. Useful for different situations.
+ if (!strcasecmp(sep->arg[3], "")) tBot->Say("My bot filter code is: %i. Save this number to reload later.",tBot->GetFilter());
+ else
+ {
+ int32 i = atoi(sep->arg[3]); //Try to convert the code specified to a long integer.
+ if (i <= AI_MAX && i >= 0)
+ { //Value seems to be in range of acceptable values.
+ tBot->SetFilter(i);
+ tBot->Say("I have accepted your code. Use [#bot filter list] to see what I have planned.");
+ }
+ else tBot->Say("Your code is invalid. Try another value.");
+ }
+ }
+ else if (!strcasecmp(sep->arg[2], "heal"))
+ { //AI_HEAL
+ if (!strcasecmp(sep->arg[3], "on") || !strcasecmp(sep->arg[2], "1")) tBot->SetFilter(tBot->GetFilter() & ~AI_HEAL);
+ else if (!strcasecmp(sep->arg[3], "off") || !strcasecmp(sep->arg[2], "0")) tBot->SetFilter(tBot->GetFilter() | AI_HEAL);
+ else if (!strcasecmp(sep->arg[3], "only")) tBot->SetFilter(AI_MAX-AI_HEAL); //Filter all but this AI filter.
+ else tBot->SetFilter(tBot->GetFilter() ^ AI_HEAL); //Invert switch on/off
+ !(tBot->GetFilter() & AI_HEAL) ? tBot->Say("I will now heal group members.") : tBot->Say("I will no longer heal group members.");
+ }
+ else if (!strcasecmp(sep->arg[2], "root"))
+ { //AI_ROOT
+ if (!strcasecmp(sep->arg[3], "on") || !strcasecmp(sep->arg[2], "1")) tBot->SetFilter(tBot->GetFilter() & ~AI_ROOT);
+ else if (!strcasecmp(sep->arg[3], "off") || !strcasecmp(sep->arg[2], "0")) tBot->SetFilter(tBot->GetFilter() | AI_ROOT);
+ else if (!strcasecmp(sep->arg[3], "only")) tBot->SetFilter(AI_MAX-AI_ROOT); //Filter all but this AI filter.
+ else tBot->SetFilter(tBot->GetFilter() ^ AI_ROOT); //Invert switch on/off
+ !(tBot->GetFilter() & AI_ROOT) ? tBot->Say("I will now try to root enemies.") : tBot->Say("I will no longer try to root enemies.");
+ }
+ else if (!strcasecmp(sep->arg[2], "snare"))
+ { //AI_SNARE
+ if (!strcasecmp(sep->arg[3], "on") || !strcasecmp(sep->arg[2], "1")) tBot->SetFilter(tBot->GetFilter() & ~AI_SNARE);
+ else if (!strcasecmp(sep->arg[3], "off") || !strcasecmp(sep->arg[2], "0")) tBot->SetFilter(tBot->GetFilter() | AI_SNARE);
+ else if (!strcasecmp(sep->arg[3], "only")) tBot->SetFilter(AI_MAX-AI_SNARE); //Filter all but this AI filter.
+ else tBot->SetFilter(tBot->GetFilter() ^ AI_SNARE); //Invert switch on/off
+ !(tBot->GetFilter() & AI_SNARE) ? tBot->Say("I will now try to snare enemies.") : tBot->Say("I will no longer try to snare enemies.");
+ }
+ else if (!strcasecmp(sep->arg[2], "slow"))
+ { //AI_SLOW
+ if (!strcasecmp(sep->arg[3], "on") || !strcasecmp(sep->arg[2], "1")) tBot->SetFilter(tBot->GetFilter() & ~AI_SLOW);
+ else if (!strcasecmp(sep->arg[3], "off") || !strcasecmp(sep->arg[2], "0")) tBot->SetFilter(tBot->GetFilter() | AI_SLOW);
+ else if (!strcasecmp(sep->arg[3], "only")) tBot->SetFilter(AI_MAX-AI_SLOW); //Filter all but this AI filter.
+ else tBot->SetFilter(tBot->GetFilter() ^ AI_SLOW); //Invert switch on/off
+ !(tBot->GetFilter() & AI_SLOW) ? tBot->Say("I will now try to slow enemies.") : tBot->Say("I will no longer try to slow enemies.");
+ }
+ else if (!strcasecmp(sep->arg[2], "nuke"))
+ { //AI_NUKE
+ if (!strcasecmp(sep->arg[3], "on") || !strcasecmp(sep->arg[2], "1")) tBot->SetFilter(tBot->GetFilter() & ~AI_NUKE);
+ else if (!strcasecmp(sep->arg[3], "off") || !strcasecmp(sep->arg[2], "0")) tBot->SetFilter(tBot->GetFilter() | AI_NUKE);
+ else if (!strcasecmp(sep->arg[3], "only")) tBot->SetFilter(AI_MAX-AI_NUKE); //Filter all but this AI filter.
+ else tBot->SetFilter(tBot->GetFilter() ^ AI_NUKE); //Invert switch on/off
+ !(tBot->GetFilter() & AI_NUKE) ? tBot->Say("I will now try to nuke enemies.") : tBot->Say("I will no longer try to nuke enemies.");
+ }
+ else if (!strcasecmp(sep->arg[2], "dot"))
+ { //AI_DOT
+ if (!strcasecmp(sep->arg[3], "on") || !strcasecmp(sep->arg[2], "1")) tBot->SetFilter(tBot->GetFilter() & ~AI_DOT);
+ else if (!strcasecmp(sep->arg[3], "off") || !strcasecmp(sep->arg[2], "0")) tBot->SetFilter(tBot->GetFilter() | AI_DOT);
+ else if (!strcasecmp(sep->arg[3], "only")) tBot->SetFilter(AI_MAX-AI_DOT); //Filter all but this AI filter.
+ else tBot->SetFilter(tBot->GetFilter() ^ AI_DOT); //Invert switch on/off
+ !(tBot->GetFilter() & AI_DOT) ? tBot->Say("I will now try to dot enemies.") : tBot->Say("I will no longer try to dot enemies.");
+ }
+ else if (!strcasecmp(sep->arg[2], "pet"))
+ { //AI_PET
+ if (!strcasecmp(sep->arg[3], "on") || !strcasecmp(sep->arg[2], "1")) tBot->SetFilter(tBot->GetFilter() & ~AI_PET);
+ else if (!strcasecmp(sep->arg[3], "off") || !strcasecmp(sep->arg[2], "0")) tBot->SetFilter(tBot->GetFilter() | AI_PET);
+ else if (!strcasecmp(sep->arg[3], "only")) tBot->SetFilter(AI_MAX-AI_PET); //Filter all but this AI filter.
+ else tBot->SetFilter(tBot->GetFilter() ^ AI_PET); //Invert switch on/off
+ !(tBot->GetFilter() & AI_PET) ? tBot->Say("I will now try to keep a pet up.") : tBot->Say("I will no longer try to keep a pet up.");
+ }
+ else if (!strcasecmp(sep->arg[2], "illusion"))
+ { //AI_ILLUSION
+ if (!strcasecmp(sep->arg[3], "on") || !strcasecmp(sep->arg[2], "1")) tBot->SetFilter(tBot->GetFilter() & ~AI_ILLUSION);
+ else if (!strcasecmp(sep->arg[3], "off") || !strcasecmp(sep->arg[2], "0")) tBot->SetFilter(tBot->GetFilter() | AI_ILLUSION);
+ else if (!strcasecmp(sep->arg[3], "only")) tBot->SetFilter(AI_MAX-AI_ILLUSION); //Filter all but this AI filter.
+ else tBot->SetFilter(tBot->GetFilter() ^ AI_ILLUSION); //Invert switch on/off
+ !(tBot->GetFilter() & AI_ILLUSION) ? tBot->Say("I will now try to cast illusion spells.") : tBot->Say("I will no longer try to cast illusion spells.");
+ }
+ else if (!strcasecmp(sep->arg[2], "lifetap"))
+ { //AI_LIFETAP
+ if (!strcasecmp(sep->arg[3], "on") || !strcasecmp(sep->arg[2], "1")) tBot->SetFilter(tBot->GetFilter() & ~AI_LIFETAP);
+ else if (!strcasecmp(sep->arg[3], "off") || !strcasecmp(sep->arg[2], "0")) tBot->SetFilter(tBot->GetFilter() | AI_LIFETAP);
+ else if (!strcasecmp(sep->arg[3], "only")) tBot->SetFilter(AI_MAX-AI_LIFETAP); //Filter all but this AI filter.
+ else tBot->SetFilter(tBot->GetFilter() ^ AI_LIFETAP); //Invert switch on/off
+ !(tBot->GetFilter() & AI_LIFETAP) ? tBot->Say("I will now try to cast lifetap spells.") : tBot->Say("I will no longer try to cast lifetap spells.");
+ }
+ else if (!strcasecmp(sep->arg[2], "hot"))
+ { //AI_HOT
+ if (!strcasecmp(sep->arg[3], "on") || !strcasecmp(sep->arg[2], "1")) tBot->SetFilter(tBot->GetFilter() & ~AI_HOT);
+ else if (!strcasecmp(sep->arg[3], "off") || !strcasecmp(sep->arg[2], "0")) tBot->SetFilter(tBot->GetFilter() | AI_HOT);
+ else if (!strcasecmp(sep->arg[3], "only")) tBot->SetFilter(AI_MAX-AI_HOT); //Filter all but this AI filter.
+ else tBot->SetFilter(tBot->GetFilter() ^ AI_HOT); //Invert switch on/off
+ !(tBot->GetFilter() & AI_HOT) ? tBot->Say("I will now try to cast HoT spells.") : tBot->Say("I will no longer try to cast HoT spells.");
+ }
+ else if (!strcasecmp(sep->arg[2], "buff"))
+ { //AI_BUFF
+ if (!strcasecmp(sep->arg[3], "on") || !strcasecmp(sep->arg[2], "1")) tBot->SetFilter(tBot->GetFilter() & ~AI_BUFF);
+ else if (!strcasecmp(sep->arg[3], "off") || !strcasecmp(sep->arg[2], "0")) tBot->SetFilter(tBot->GetFilter() | AI_BUFF);
+ else if (!strcasecmp(sep->arg[3], "only")) tBot->SetFilter(AI_MAX-AI_BUFF); //Filter all but this AI filter.
+ else tBot->SetFilter(tBot->GetFilter() ^ AI_BUFF); //Invert switch on/off
+ !(tBot->GetFilter() & AI_BUFF) ? tBot->Say("I will now try to buff group members.") : tBot->Say("I will no longer try to buff group members.");
+ }
+ else if (!strcasecmp(sep->arg[2], "fear"))
+ { //AI_FEAR
+ if (!strcasecmp(sep->arg[3], "on") || !strcasecmp(sep->arg[2], "1")) tBot->SetFilter(tBot->GetFilter() & ~AI_FEAR);
+ else if (!strcasecmp(sep->arg[3], "off") || !strcasecmp(sep->arg[2], "0")) tBot->SetFilter(tBot->GetFilter() | AI_FEAR);
+ else if (!strcasecmp(sep->arg[3], "only")) tBot->SetFilter(AI_MAX-AI_FEAR); //Filter all but this AI filter.
+ else tBot->SetFilter(tBot->GetFilter() ^ AI_FEAR); //Invert switch on/off
+ !(tBot->GetFilter() & AI_FEAR) ? tBot->Say("I will now try to fear enemies.") : tBot->Say("I will no longer try to fear enemies.");
+ }
+ else if (!strcasecmp(sep->arg[2], "dispel"))
+ { //AI_DISPEL
+ if (!strcasecmp(sep->arg[3], "on") || !strcasecmp(sep->arg[2], "1")) tBot->SetFilter(tBot->GetFilter() & ~AI_DISPEL);
+ else if (!strcasecmp(sep->arg[3], "off") || !strcasecmp(sep->arg[2], "0")) tBot->SetFilter(tBot->GetFilter() | AI_DISPEL);
+ else if (!strcasecmp(sep->arg[3], "only")) tBot->SetFilter(AI_MAX-AI_DISPEL); //Filter all but this AI filter.
+ else tBot->SetFilter(tBot->GetFilter() ^ AI_DISPEL); //Invert switch on/off
+ !(tBot->GetFilter() & AI_DISPEL) ? tBot->Say("I will now try to dispel enemies.") : tBot->Say("I will no longer try to dispel enemies.");
+ }
+ else if (!strcasecmp(sep->arg[2], "melee"))
+ { //AI_MELEE
+ if (!strcasecmp(sep->arg[3], "on") || !strcasecmp(sep->arg[2], "1")) tBot->SetFilter(tBot->GetFilter() & ~AI_MELEE);
+ else if (!strcasecmp(sep->arg[3], "off") || !strcasecmp(sep->arg[2], "0")) tBot->SetFilter(tBot->GetFilter() | AI_MELEE);
+ else if (!strcasecmp(sep->arg[3], "only")) tBot->SetFilter(AI_MAX-AI_MELEE); //Filter all but this AI filter.
+ else tBot->SetFilter(tBot->GetFilter() ^ AI_MELEE); //Invert switch on/off
+ !(tBot->GetFilter() & AI_MELEE) ? tBot->Say("I will now try to melee enemies.") : tBot->Say("I will no longer try to melee enemies.");
+ }
+ else if (!strcasecmp(sep->arg[2], "taunt"))
+ { //AI_TAUNT
+ if (!strcasecmp(sep->arg[3], "on") || !strcasecmp(sep->arg[2], "1")) tBot->SetFilter(tBot->GetFilter() & ~AI_TAUNT);
+ else if (!strcasecmp(sep->arg[3], "off") || !strcasecmp(sep->arg[2], "0")) tBot->SetFilter(tBot->GetFilter() | AI_TAUNT);
+ else if (!strcasecmp(sep->arg[3], "only")) tBot->SetFilter(AI_MAX-AI_TAUNT); //Filter all but this AI filter.
+ else tBot->SetFilter(tBot->GetFilter() ^ AI_TAUNT); //Invert switch on/off
+ !(tBot->GetFilter() & AI_TAUNT) ? tBot->Say("I will now try to taunt enemies.") : tBot->Say("I will no longer try to taunt enemies.");
+ }
+ else if (!strcasecmp(sep->arg[2], "debuff"))
+ { //AI_DEBUFF
+ if (!strcasecmp(sep->arg[3], "on") || !strcasecmp(sep->arg[2], "1")) tBot->SetFilter(tBot->GetFilter() & ~AI_DEBUFF);
+ else if (!strcasecmp(sep->arg[3], "off") || !strcasecmp(sep->arg[2], "0")) tBot->SetFilter(tBot->GetFilter() | AI_DEBUFF);
+ else if (!strcasecmp(sep->arg[3], "only")) tBot->SetFilter(AI_MAX-AI_DEBUFF); //Filter all but this AI filter.
+ else tBot->SetFilter(tBot->GetFilter() ^ AI_DEBUFF); //Invert switch on/off
+ !(tBot->GetFilter() & AI_DEBUFF) ? tBot->Say("I will now try to debuff enemies.") : tBot->Say("I will no longer try to debuff enemies.");
+ }
+ else
+ { //If you don't put a valid argument, let's just list generic messages.
+ c->Message(0, "#bot filter [list|option] [only|on|off] - Filter various bot routines. Use '#bot filter list' to see options.");
+ return;
+ }
+ tBot->Save(); //Shin: No returns means we likely modified something, save the new filter.
+ return;
+ }
+
if(!strcasecmp(sep->arg[1], "create")) {
if(sep->arg[2][0] == '\0' || sep->arg[3][0] == '\0' || sep->arg[4][0] == '\0' || sep->arg[5][0] == '\0' || sep->arg[6][0] != '\0') {
c->Message(0, "Usage: #bot create [name] [class(id)] [race(id)] [gender (male/female)]");
@@ -8421,7 +8635,6 @@
c->Message(13, "Database Error: %s", TempErrorMessage.c_str());
return;
}
-
// Now that all validation is complete, we can save our newly created bot
if(!NewBot->Save())
c->Message(0, "Unable to save %s as a bot.", NewBot->GetCleanName());
@@ -11167,7 +11380,7 @@
Mob *target = c->GetTarget();
if(target && target->IsBot())
{
- for(int i=0; i<16; i++)
+ for(int i=0; i<8; i++) //Was 16, changed to 8
{
if(target->CastToBot()->BotGetSpells(i) != 0)
{
@@ -11577,7 +11790,7 @@

// franck: EQoffline
// This function has been reworked for the caster bots, when engaged.
-// Healers bots must heal thoses who loose HP.
+// Healers bots must heal those who lose HP.
bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, int8 iChance, float iRange, int16 iSpellTypes) {
_ZP(EntityList_Bot_AICheckCloseBeneficialSpells);

@@ -11606,45 +11819,80 @@
// Franck: EQoffline.
// Ok, Beneficial spells depend of the class of the caster also..
int8 botCasterClass = caster->GetClass();;
-
+ bool retval = false;
// Heal and buffs spells might have a different chance, that's why I separe them .
if( botCasterClass == CLERIC || botCasterClass == DRUID || botCasterClass == SHAMAN || botCasterClass == PALADIN || botCasterClass == BEASTLORD || botCasterClass == RANGER) {
//If AI_EngagedCastCheck() said to the healer that he had to heal
- if( iSpellTypes == SpellType_Heal ) //
+ if( iSpellTypes == SpellType_Heal)
{
- // check raids
- if( caster->CastToMob()->IsGrouped() && caster->GetBotRaidID() && (entity_list.GetBotRaidByMob(caster) != NULL)) {
+ //SHIN TODO: check raids
+/* if( caster->CastToMob()->IsGrouped() && caster->GetBotRaidID() && (entity_list.GetBotRaidByMob(caster) != NULL)) {
BotRaids *br = entity_list.GetBotRaidByMob(caster);
- // boolean trying to ai the heal rotation, prolly not working well.
- if(br) {
- if(br->GetBotMainTank() && (br->GetBotMainTank()->GetHPRatio() < 80))
- {
- if(caster->Bot_AICastSpell(br->GetBotMainTank(), 80, SpellType_Heal)) {
- return true;
+ if(br && !(GetFilter() & AI_HEAL))
+ {
+ if(br->GetBotMainTank() && (br->GetBotMainTank()->GetHPRatio() < 80))
+ { // try to heal the raid main tank
+ if (!retval) retval = Bot_AICastSpell(br->GetBotMainTank(), 80, SpellType_Heal);
+ //if (!retval) retval = entity_list.Bot_AICheckCloseBeneficialSpells(this, 80, MobAISpellRange, SpellType_Heal);
+ if (!retval) retval = Bot_AICastSpell(this, 100, SpellType_Heal);
+ }
+ else if(br->GetBotSecondTank() && (br->GetBotSecondTank()->GetHPRatio() < 80))
+ { // try to heal the raid secondar tank
+ if (!retval) retval = Bot_AICastSpell(br->GetBotSecondTank(), 80, SpellType_Heal);
+ //if (!retval) retval = entity_list.Bot_AICheckCloseBeneficialSpells(this, 80, MobAISpellRange, SpellType_Heal);
+ if (!retval) retval = Bot_AICastSpell(this, 100, SpellType_Heal);
+ }
+ }
+ }*/
+
+ // check in group
+ if(!retval && caster->IsGrouped()) {
+ Group *g = caster->GetGroup();
+ int8 lowhp = 100; //Shin: This is to track who has the lowest HP in party.
+ int8 lowm = -1; //Shin: This tracks what member has lowest HP
+ int8 i = 0;
+ if(g) {
+ for(i=0; i<MAX_GROUP_MEMBERS; ++i)
+ { //Shin: Go through group members
+ if(g->members[i] && !g->members[i]->qglobal)
+ { //Shin: if this member has less HP than our var, set this member as heal target.
+ if (lowhp < g->members[i]->GetHPRatio())
+ {
+ lowhp = g->members[i]->GetHPRatio();
+ lowm = i;
+ }
}
}
- else if(br->GetBotSecondTank() && (br->GetBotSecondTank()->GetHPRatio() < 80))
- {
- if(caster->Bot_AICastSpell(br->GetBotSecondTank(), 80, SpellType_Heal)) {
+ if (lowm != -1 && lowm < MAX_GROUP_MEMBERS)
+ { //Shin: If a member was found with low HP
+ if(caster->Bot_AICastSpell(g->members[lowm], 100, SpellType_Heal))
+ { //Shin: Heal them.
+ caster->AIautocastspell_timer->Start(caster->RandomTimer(1000, 3000), false);
return true;
}
}
- }
- }
-
- // check in group
- if(caster->IsGrouped()) {
- Group *g = entity_list.GetGroupByMob(caster);
-
- if(g) {
- for( int i = 0; i<MAX_GROUP_MEMBERS; i++) {
- if(g->members[i] && !g->members[i]->qglobal && (g->members[i]->GetHPRatio() < 80)) {
- if(caster->Bot_AICastSpell(g->members[i], 100, SpellType_Heal))
- return true;
+ else
+ { //Shin: No member was found with low life, check if a pet needs healing.
+ lowhp = 100;
+ lowm = -1;
+ for(i=0; i<MAX_GROUP_MEMBERS; ++i)
+ { //Go through group members
+ if(g->members[i] && !g->members[i]->qglobal && g->members[i]->GetPetID())
+ {
+ if (g->members[i]->GetPet()->GetHPRatio() < lowhp)
+ { //If they have a pet and it's HP is lower than lowhp
+ lowhp = g->members[i]->GetPet()->GetHPRatio();
+ lowm = i;
+ }
+ }
}
- if(g->members[i] && !g->members[i]->qglobal && g->members[i]->GetPetID()) {
- if(caster->Bot_AICastSpell(g->members[i]->GetPet(), 60, SpellType_Heal))
+ if (lowm != -1 && lowm < MAX_GROUP_MEMBERS)
+ { //Shin: If a member's pet was found with low HP
+ if(caster->Bot_AICastSpell(g->members[lowm]->GetPet(), 100, SpellType_Heal))
+ { //Shin: Heal them.
+ caster->AIautocastspell_timer->Start(caster->RandomTimer(1000, 3000), false);
return true;
+ }
}
}
}
@@ -11668,7 +11916,7 @@

if(g) {
for( int i = 0; i<MAX_GROUP_MEMBERS; i++) {
- if(g->members[i]) {
+ if(g->members[i] && !(caster->GetFilter() & AI_BUFF)) {
if(caster->Bot_AICastSpell(g->members[i], 100, SpellType_Buff)) {
return true;
}
Index: zone/bot.h
================================================== =================
--- zone/bot.h (revision 1005)
+++ zone/bot.h (working copy)
@@ -16,6 +16,25 @@

#include <sstream>

+//Shin: AI Defines, used for #bot filter options
+#define AI_MAX 65535 //Maximum AI value (All values are set)
+#define AI_HEAL 1 //Heal myself/other players
+#define AI_ROOT 2 //Root mobs
+#define AI_SNARE 4 //Snare mobs
+#define AI_SLOW 8 //Slow mobs
+#define AI_NUKE 16 //Nuke mobs
+#define AI_DOT 32 //use damage over time spells on mobs
+#define AI_PET 64 //Use a pet? (shamans etc)
+#define AI_ILLUSION 128 //Use illusion spells? (annoying enchanter!)
+#define AI_LIFETAP 256 //Use lifetaps on mobs
+#define AI_HOT 512 //Heal Over Time Spell Casting
+#define AI_BUFF 1024 //Do Buff Routine/Keep Buffs Up?
+#define AI_FEAR 2048 //Do Fear Type Spells
+#define AI_DISPEL 4096 //Do Dispel?
+#define AI_MELEE 8192 //Should I Melee?
+#define AI_TAUNT 16384 //Should I Taunt?
+#define AI_DEBUFF 32768 //Should I Taunt?
+
using namespace std;

extern bool spells_loaded;
@@ -37,10 +56,22 @@
botfocusPetPower,
botfocusResistRate,
botfocusHateReduction,
+ };
+ typedef enum botfocusType botfocusType;
+
+ enum botStatus { //Shin: Status
+ Idle = 1,
+ Casting,
+ Nuking,
+ Buffing,
+ Meleeing,
+ Healing,
+ Medding,
+ Escaping, //i dont know about this...
};
+ typedef enum botStatus botStatus;
+ int16 myStatus;

- typedef enum botfocusType botfocusType;
-
typedef std::map<uint32, uint32> BotInventory;
typedef std::pair<uint32, uint32> BotInventoryItem;

@@ -115,6 +146,8 @@
void Camp(bool databaseSave = true);
virtual void AddToHateList(Mob* other, sint32 hate = 0, sint32 damage = 0, bool iYellForHelp = true, bool bFrenzy = false, bool iBuffTic = false);
virtual void SetTarget(Mob* mob);
+ virtual void SetFilter(uint16 value); //Shin: Set #bot filter bitmask.
+ uint16 GetFilter() { return _filter; } //Shin: Get current #bot filter value

// Mob AI Virtual Override Methods
virtual void AI_Process();
@@ -241,6 +274,7 @@
Mob* _previousTarget;

// Private "base stats" Members
+ uint16 _filter; //Shin: used for #bot filter data.
sint16 _baseMR;
sint16 _baseCR;
sint16 _baseDR;
Index: zone/command.cpp
================================================== =================
--- zone/command.cpp (revision 1005)
+++ zone/command.cpp (working copy)
@@ -1437,7 +1437,7 @@

if (sep->IsNumber(2) || sep->IsNumber(3) || sep->IsNumber(4)){
//zone to specific coords
- c->MovePC(zoneid, (float)atof(sep->arg[2]), atof(sep->arg[3]), atof(sep->arg[4]), 0.0f, 0);
+ c->MovePC(zoneid, (float)atof(sep->arg[2]), atof(sep->arg[3]), atof(sep->arg[4]), atof(sep->arg[5]), 0);
}
else
//zone to safe coords
@@ -1827,15 +1827,14 @@
else if (!c->GetTarget()->IsNPC())
c->Message(0, "ERROR: Target is not a NPC!");
else {
- c->Message(0, "NPC Stats:");
- c->Message(0, " Name: %s NpcID: %u",c->GetTarget()->GetName(), c->GetTarget()->GetNPCTypeID());
- c->Message(0, " Race: %i Level: %i Class: %i",c->GetTarget()->GetRace(), c->GetTarget()->GetLevel(), c->GetTarget()->GetClass());
- c->Message(0, " Material: %i",c->GetTarget()->GetTexture());
- c->Message(0, " Current HP: %i Max HP: %i", c->GetTarget()->GetHP(), c->GetTarget()->GetMaxHP());
- //c->Message(0, "Weapon Item Number: %s",c->GetTarget()->GetWeapNo());
- c->Message(0, " Gender: %i Size: %f Bodytype: %d",c->GetTarget()->GetGender(),c->GetTarget()->GetSize(), c->GetTarget()->GetBodyType());
- c->Message(0, " Runspeed: %f Walkspeed: %f", c->GetTarget()->GetRunspeed(), c->GetTarget()->GetWalkspeed());
- c->Message(0,"Spawn Group: %i",c->GetTarget()->CastToNPC()->GetSp2());
+ c->Message(0, "NPC Stats for %s (%u):" , c->GetTarget()->GetName(), c->GetTarget()->GetNPCTypeID());
+ c->Message(0, "*Race: %i, Level: %i, Class: %i, MerchID: %i",c->GetTarget()->GetRace(), c->GetTarget()->GetLevel(), c->GetTarget()->GetClass(), c->GetTarget()->CastToNPC()->MerchantType);
+ c->Message(0, "*Gender: %i, Size: %f ",c->GetTarget()->GetGender(),c->GetTarget()->GetSize(), c->GetTarget()->GetBodyType());
+ c->Message(0, "*Texture: %i, HelmTexture: %i, BodyType: %d",c->GetTarget()->GetTexture(), c->GetTarget()->GetHelmTexture(), c->GetTarget()->GetBodyType());
+ c->Message(0, "*HP: %i", c->GetTarget()->GetMaxHP());
+ c->Message(0, "*Runspeed: %f Walkspeed: %f", c->GetTarget()->GetRunspeed(), c->GetTarget()->GetWalkspeed());
+ c->Message(0, "*FactionID: %i, AggroRad: %i", c->GetTarget()->GetPrimaryFaction(), c->GetTarget()->CastToNPC()->GetAggroRange());
+ c->Message(0, "*Spawn Group: %i",c->GetTarget()->CastToNPC()->GetSp2());
c->GetTarget()->CastToNPC()->QueryLoot(c);
}
}
Index: zone/mob.cpp
================================================== =================
--- zone/mob.cpp (revision 1005)
+++ zone/mob.cpp (working copy)
@@ -916,7 +916,8 @@
{
EQApplicationPacket* app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
PlayerPositionUpdateServer_Struct* spu = (PlayerPositionUpdateServer_Struct*)app->pBuffer;
- MakeSpawnUpdateNoDelta(spu);
+ //MakeSpawnUpdateNoDelta(spu);
+ MakeSpawnUpdate(spu);
move_tic_count = 0;
entity_list.QueueClients(this, app, true);
safe_delete(app);
@@ -964,7 +965,7 @@
spu->delta_z = NewFloatToEQ13(0);
spu->heading = FloatToEQ19(heading);
spu->animation = 0;
- spu->delta_heading = NewFloatToEQ13(0);
+ spu->delta_heading = NewFloatToEQ13(0);
spu->padding0002 =0;
spu->padding0006 =7;
spu->padding0014 =0x7f;
Index: zone/spells.cpp
================================================== =================
--- zone/spells.cpp (revision 1005)
+++ zone/spells.cpp (working copy)
@@ -3376,10 +3376,12 @@
resistchance = (resistchance * (100-focusResist) / 100);
}
}
+

#ifdef EQBOTS

- if(caster && caster->IsBot())
+
+ if(caster && caster->CastToNPC()->IsBot())
{
if(IsValidSpell(spell_id))
{
@@ -3392,6 +3394,7 @@

//Resist chance makes up the upper limit of our partial range
//Fullchance makes up the lower limit of our partial range
+
if(!IsFearSpell(spell_id))
fullchance = (resistchance * (1 - RuleR(Spells, PartialHitChance))); //default 0.7
else
Index: zone/zoning.cpp
================================================== =================
--- zone/zoning.cpp (revision 1005)
+++ zone/zoning.cpp (working copy)
@@ -239,7 +239,7 @@
dest_h = GetHeading();
else
dest_h = zone_point->target_heading;
-
+ printf("Decided destination is %i, our heading is %i and zp is %i", dest_h, GetHeading(), zone_point->target_heading);
break;
}