aboutsummaryrefslogtreecommitdiff
path: root/src/server/game/AI
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/game/AI')
-rw-r--r--src/server/game/AI/AuctionHouseBot/AuctionHouseBot.cpp1860
-rw-r--r--src/server/game/AI/AuctionHouseBot/AuctionHouseBot.h1225
-rw-r--r--src/server/game/AI/CombatAI.cpp370
-rw-r--r--src/server/game/AI/CombatAI.h129
-rw-r--r--src/server/game/AI/CreatureAI.cpp162
-rw-r--r--src/server/game/AI/CreatureAI.h192
-rw-r--r--src/server/game/AI/CreatureAIFactory.h53
-rw-r--r--src/server/game/AI/CreatureAIImpl.h638
-rw-r--r--src/server/game/AI/CreatureAIRegistry.cpp59
-rw-r--r--src/server/game/AI/CreatureAIRegistry.h29
-rw-r--r--src/server/game/AI/CreatureAISelector.cpp136
-rw-r--r--src/server/game/AI/CreatureAISelector.h34
-rw-r--r--src/server/game/AI/EventAI/CreatureEventAI.cpp1384
-rw-r--r--src/server/game/AI/EventAI/CreatureEventAI.h637
-rw-r--r--src/server/game/AI/EventAI/CreatureEventAIMgr.cpp745
-rw-r--r--src/server/game/AI/EventAI/CreatureEventAIMgr.h46
-rw-r--r--src/server/game/AI/GuardAI.cpp137
-rw-r--r--src/server/game/AI/GuardAI.h55
-rw-r--r--src/server/game/AI/PassiveAI.cpp81
-rw-r--r--src/server/game/AI/PassiveAI.h86
-rw-r--r--src/server/game/AI/PetAI.cpp471
-rw-r--r--src/server/game/AI/PetAI.h64
-rw-r--r--src/server/game/AI/ReactorAI.cpp59
-rw-r--r--src/server/game/AI/ReactorAI.h40
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedCreature.cpp740
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedCreature.h290
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp506
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedEscortAI.h116
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp387
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedFollowerAI.h67
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedGossip.h187
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedGuardAI.cpp194
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedGuardAI.h45
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedInstance.h20
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedSimpleAI.cpp278
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedSimpleAI.h71
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedSmartAI.cpp8
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedSmartAI.h8
-rw-r--r--src/server/game/AI/TotemAI.cpp116
-rw-r--r--src/server/game/AI/TotemAI.h47
-rw-r--r--src/server/game/AI/UnitAI.cpp327
-rw-r--r--src/server/game/AI/UnitAI.h161
42 files changed, 12260 insertions, 0 deletions
diff --git a/src/server/game/AI/AuctionHouseBot/AuctionHouseBot.cpp b/src/server/game/AI/AuctionHouseBot/AuctionHouseBot.cpp
new file mode 100644
index 00000000000..ebbf48e6476
--- /dev/null
+++ b/src/server/game/AI/AuctionHouseBot/AuctionHouseBot.cpp
@@ -0,0 +1,1860 @@
+#include "ObjectMgr.h"
+#include "AuctionHouseMgr.h"
+#include "AuctionHouseBot.h"
+#include <vector>
+
+#include "Policies/SingletonImp.h"
+INSTANTIATE_SINGLETON_1(AuctionHouseBot);
+
+using namespace std;
+vector<uint32> npcItems;
+vector<uint32> lootItems;
+vector<uint32> greyTradeGoodsBin;
+vector<uint32> whiteTradeGoodsBin;
+vector<uint32> greenTradeGoodsBin;
+vector<uint32> blueTradeGoodsBin;
+vector<uint32> purpleTradeGoodsBin;
+vector<uint32> orangeTradeGoodsBin;
+vector<uint32> yellowTradeGoodsBin;
+vector<uint32> greyItemsBin;
+vector<uint32> whiteItemsBin;
+vector<uint32> greenItemsBin;
+vector<uint32> blueItemsBin;
+vector<uint32> purpleItemsBin;
+vector<uint32> orangeItemsBin;
+vector<uint32> yellowItemsBin;
+AuctionHouseBot::AuctionHouseBot()
+{
+ debug_Out = false;
+ debug_Out_Filters = false;
+ AHBSeller = false;
+ AHBBuyer = false;
+
+ //Begin Filters
+
+ Vendor_Items = false;
+ Loot_Items = false;
+ Other_Items = false;
+ Vendor_TGs = false;
+ Loot_TGs = false;
+ Other_TGs = false;
+
+ No_Bind = false;
+ Bind_When_Picked_Up = false;
+ Bind_When_Equipped = false;
+ Bind_When_Use = false;
+ Bind_Quest_Item = false;
+
+ DisableBeta_PTR_Unused = false;
+ DisablePermEnchant = false;
+ DisableConjured = false;
+ DisableGems = false;
+ DisableMoney = false;
+ DisableMoneyLoot = false;
+ DisableLootable = false;
+ DisableKeys = false;
+ DisableDuration = false;
+ DisableBOP_Or_Quest_NoReqLevel = false;
+
+ DisableWarriorItems = false;
+ DisablePaladinItems = false;
+ DisableHunterItems = false;
+ DisableRogueItems = false;
+ DisablePriestItems = false;
+ DisableDKItems = false;
+ DisableShamanItems = false;
+ DisableMageItems = false;
+ DisableWarlockItems = false;
+ DisableUnusedClassItems = false;
+ DisableDruidItems = false;
+
+ DisableItemsBelowLevel = 0;
+ DisableItemsAboveLevel = 0;
+ DisableTGsBelowLevel = 0;
+ DisableTGsAboveLevel = 0;
+ DisableItemsBelowGUID = 0;
+ DisableItemsAboveGUID = 0;
+ DisableTGsBelowGUID = 0;
+ DisableTGsAboveGUID = 0;
+ DisableItemsBelowReqLevel = 0;
+ DisableItemsAboveReqLevel = 0;
+ DisableTGsBelowReqLevel = 0;
+ DisableTGsAboveReqLevel = 0;
+ DisableItemsBelowReqSkillRank = 0;
+ DisableItemsAboveReqSkillRank = 0;
+ DisableTGsBelowReqSkillRank = 0;
+ DisableTGsAboveReqSkillRank = 0;
+
+ //End Filters
+
+ _lastrun_a = time(NULL);
+ _lastrun_h = time(NULL);
+ _lastrun_n = time(NULL);
+
+ AllianceConfig = AHBConfig(2);
+ HordeConfig = AHBConfig(6);
+ NeutralConfig = AHBConfig(7);
+}
+
+AuctionHouseBot::~AuctionHouseBot()
+{
+}
+
+void AuctionHouseBot::addNewAuctions(Player *AHBplayer, AHBConfig *config)
+{
+ if (!AHBSeller)
+ {
+ if (debug_Out) sLog.outError("AHSeller: Disabled");
+ return;
+ }
+
+ uint32 minItems = config->GetMinItems();
+ uint32 maxItems = config->GetMaxItems();
+
+ if (maxItems == 0)
+ {
+ //if (debug_Out) sLog.outString("AHSeller: Auctions disabled");
+ return;
+ }
+
+ AuctionHouseEntry const* ahEntry = auctionmgr.GetAuctionHouseEntry(config->GetAHFID());
+ if (!ahEntry)
+ {
+ return;
+ }
+ AuctionHouseObject* auctionHouse = auctionmgr.GetAuctionsMap(config->GetAHFID());
+ if (!auctionHouse)
+ {
+ return;
+ }
+
+ uint32 auctions = auctionHouse->Getcount();
+
+ if (auctions >= minItems)
+ {
+ //if (debug_Out) sLog.outString("AHSeller: Auctions above minimum");
+ return;
+ }
+
+ if (auctions >= maxItems)
+ {
+ //if (debug_Out) sLog.outString("AHSeller: Auctions at or above maximum");
+ return;
+ }
+
+ uint32 items = 0;
+ if ((maxItems - auctions) >= ItemsPerCycle)
+ items = ItemsPerCycle;
+ else
+ items = (maxItems - auctions);
+
+ if (debug_Out) sLog.outString("AHSeller: Adding %u Auctions", items);
+
+ uint32 AuctioneerGUID = 0;
+
+ switch (config->GetAHID())
+ {
+ case 2:
+ AuctioneerGUID = 79707; //Human in stormwind.
+ break;
+ case 6:
+ AuctioneerGUID = 4656; //orc in Orgrimmar
+ break;
+ case 7:
+ AuctioneerGUID = 23442; //goblin in GZ
+ break;
+ default:
+ if (debug_Out) sLog.outError("AHSeller: GetAHID() - Default switch reached");
+ AuctioneerGUID = 23442; //default to neutral 7
+ break;
+ }
+
+ if (debug_Out) sLog.outString("AHSeller: Current Auctineer GUID is %u", AuctioneerGUID);
+
+ uint32 greyTGcount = config->GetPercents(AHB_GREY_TG);
+ uint32 whiteTGcount = config->GetPercents(AHB_WHITE_TG);
+ uint32 greenTGcount = config->GetPercents(AHB_GREEN_TG);
+ uint32 blueTGcount = config->GetPercents(AHB_BLUE_TG);
+ uint32 purpleTGcount = config->GetPercents(AHB_PURPLE_TG);
+ uint32 orangeTGcount = config->GetPercents(AHB_ORANGE_TG);
+ uint32 yellowTGcount = config->GetPercents(AHB_YELLOW_TG);
+ uint32 greyIcount = config->GetPercents(AHB_GREY_I);
+ uint32 whiteIcount = config->GetPercents(AHB_WHITE_I);
+ uint32 greenIcount = config->GetPercents(AHB_GREEN_I);
+ uint32 blueIcount = config->GetPercents(AHB_BLUE_I);
+ uint32 purpleIcount = config->GetPercents(AHB_PURPLE_I);
+ uint32 orangeIcount = config->GetPercents(AHB_ORANGE_I);
+ uint32 yellowIcount = config->GetPercents(AHB_YELLOW_I);
+/* uint32 total = greyTGcount + whiteTGcount + greenTGcount + blueTGcount
+ + purpleTGcount + orangeTGcount + yellowTGcount
+ + whiteIcount + greenIcount + blueIcount + purpleIcount
+ + orangeIcount + yellowIcount;
+*/
+ uint32 greyTGoods = config->GetItemCounts(AHB_GREY_TG);
+ uint32 whiteTGoods = config->GetItemCounts(AHB_WHITE_TG);
+ uint32 greenTGoods = config->GetItemCounts(AHB_GREEN_TG);
+ uint32 blueTGoods = config->GetItemCounts(AHB_BLUE_TG);
+ uint32 purpleTGoods = config->GetItemCounts(AHB_PURPLE_TG);
+ uint32 orangeTGoods = config->GetItemCounts(AHB_ORANGE_TG);
+ uint32 yellowTGoods = config->GetItemCounts(AHB_YELLOW_TG);
+
+ uint32 greyItems = config->GetItemCounts(AHB_GREY_I);
+ uint32 whiteItems = config->GetItemCounts(AHB_WHITE_I);
+ uint32 greenItems = config->GetItemCounts(AHB_GREEN_I);
+ uint32 blueItems = config->GetItemCounts(AHB_BLUE_I);
+ uint32 purpleItems = config->GetItemCounts(AHB_PURPLE_I);
+ uint32 orangeItems = config->GetItemCounts(AHB_ORANGE_I);
+ uint32 yellowItems = config->GetItemCounts(AHB_YELLOW_I);
+ if (debug_Out) sLog.outString("AHSeller: %u items", items);
+
+ // only insert a few at a time, so as not to peg the processor
+ for (uint32 cnt = 1; cnt <= items; cnt++)
+ {
+ if (debug_Out) sLog.outString("AHSeller: %u count", cnt);
+ uint32 itemID = 0;
+ uint32 itemColor = 99;
+ uint32 loopbreaker = 0;
+ while (itemID == 0 && loopbreaker <= 50)
+ {
+ ++loopbreaker;
+ uint32 choice = urand(0, 13);
+ itemColor = choice;
+ switch (choice)
+ {
+ case 0:
+ {
+ if ((greyItemsBin.size() > 0) && (greyItems < greyIcount))
+ itemID = greyItemsBin[urand(0, greyItemsBin.size() - 1)];
+ else continue;
+ break;
+ }
+ case 1:
+ {
+ if ((whiteItemsBin.size() > 0) && (whiteItems < whiteIcount))
+ itemID = whiteItemsBin[urand(0, whiteItemsBin.size() - 1)];
+ else continue;
+ break;
+ }
+ case 2:
+ {
+ if ((greenItemsBin.size() > 0) && (greenItems < greenIcount))
+ itemID = greenItemsBin[urand(0, greenItemsBin.size() - 1)];
+ else continue;
+ break;
+ }
+ case 3:
+ {
+ if ((blueItemsBin.size() > 0) && (blueItems < blueIcount))
+ itemID = blueItemsBin[urand(0, blueItemsBin.size() - 1)];
+ else continue;
+ break;
+ }
+ case 4:
+ {
+ if ((purpleItemsBin.size() > 0) && (purpleItems < purpleIcount))
+ itemID = purpleItemsBin[urand(0, purpleItemsBin.size() - 1)];
+ else continue;
+ break;
+ }
+ case 5:
+ {
+ if ((orangeItemsBin.size() > 0) && (orangeItems < orangeIcount))
+ itemID = orangeItemsBin[urand(0, orangeItemsBin.size() - 1)];
+ else continue;
+ break;
+ }
+ case 6:
+ {
+ if ((yellowItemsBin.size() > 0) && (yellowItems < yellowIcount))
+ itemID = yellowItemsBin[urand(0, yellowItemsBin.size() - 1)];
+ else continue;
+ break;
+ }
+ case 7:
+ {
+ if ((greyTradeGoodsBin.size() > 0) && (greyTGoods < greyTGcount))
+ itemID = greyTradeGoodsBin[urand(0, greyTradeGoodsBin.size() - 1)];
+ else continue;
+ break;
+ }
+ case 8:
+ {
+ if ((whiteTradeGoodsBin.size() > 0) && (whiteTGoods < whiteTGcount))
+ itemID = whiteTradeGoodsBin[urand(0, whiteTradeGoodsBin.size() - 1)];
+ else continue;
+ break;
+ }
+ case 9:
+ {
+ if ((greenTradeGoodsBin.size() > 0) && (greenTGoods < greenTGcount))
+ itemID = greenTradeGoodsBin[urand(0, greenTradeGoodsBin.size() - 1)];
+ else continue;
+ break;
+ }
+ case 10:
+ {
+ if ((blueTradeGoodsBin.size() > 0) && (blueTGoods < blueTGcount))
+ itemID = blueTradeGoodsBin[urand(0, blueTradeGoodsBin.size() - 1)];
+ else continue;
+ break;
+ }
+ case 11:
+ {
+ if ((purpleTradeGoodsBin.size() > 0) && (purpleTGoods < purpleTGcount))
+ itemID = purpleTradeGoodsBin[urand(0, purpleTradeGoodsBin.size() - 1)];
+ else continue;
+ break;
+ }
+ case 12:
+ {
+ if ((orangeTradeGoodsBin.size() > 0) && (orangeTGoods < orangeTGcount))
+ itemID = orangeTradeGoodsBin[urand(0, orangeTradeGoodsBin.size() - 1)];
+ else continue;
+ break;
+ }
+ case 13:
+ {
+ if ((yellowTradeGoodsBin.size() > 0) && (yellowTGoods < yellowTGcount))
+ itemID = yellowTradeGoodsBin[urand(0, yellowTradeGoodsBin.size() - 1)];
+ else continue;
+ break;
+ }
+ default:
+ {
+ if (debug_Out) sLog.outError("AHSeller: itemID Switch - Default Reached");
+ break;
+ }
+ }
+
+ if (itemID == 0)
+ {
+ if (debug_Out) sLog.outError("AHSeller: Item::CreateItem() - ItemID is 0");
+ continue;
+ }
+
+ ItemPrototype const* prototype = objmgr.GetItemPrototype(itemID);
+ if (prototype == NULL)
+ {
+ if (debug_Out) sLog.outError("AHSeller: Huh?!?! prototype == NULL");
+ continue;
+ }
+
+ Item* item = Item::CreateItem(itemID, 1, AHBplayer);
+ if (item == NULL)
+ {
+ if (debug_Out) sLog.outError("AHSeller: Item::CreateItem() returned NULL");
+ break;
+ }
+ item->AddToUpdateQueueOf(AHBplayer);
+
+ uint32 randomPropertyId = Item::GenerateItemRandomPropertyId(itemID);
+ if (randomPropertyId != 0)
+ item->SetItemRandomProperties(randomPropertyId);
+
+ uint64 buyoutPrice = 0;
+ uint64 bidPrice = 0;
+ uint32 stackCount = 1;
+
+ switch (SellMethod)
+ {
+ case 0:
+ buyoutPrice = prototype->SellPrice;
+ break;
+ case 1:
+ buyoutPrice = prototype->BuyPrice;
+ break;
+ }
+
+ if ((prototype->Quality >= 0) && (prototype->Quality <= AHB_MAX_QUALITY))
+ {
+ if (config->GetMaxStack(prototype->Quality) > 1 && item->GetMaxStackCount() > 1)
+ stackCount = urand(1, minValue(item->GetMaxStackCount(), config->GetMaxStack(prototype->Quality)));
+ else if (config->GetMaxStack(prototype->Quality) == 0 && item->GetMaxStackCount() > 1)
+ stackCount = urand(1, item->GetMaxStackCount());
+ else
+ stackCount = 1;
+ buyoutPrice *= urand(config->GetMinPrice(prototype->Quality), config->GetMaxPrice(prototype->Quality));
+ buyoutPrice /= 100;
+ bidPrice = buyoutPrice * urand(config->GetMinBidPrice(prototype->Quality), config->GetMaxBidPrice(prototype->Quality));
+ bidPrice /= 100;
+ }
+ else
+ {
+ // quality is something it shouldn't be, let's get out of here
+ if (debug_Out) sLog.outError("AHBuyer: Quality %u not Supported", prototype->Quality);
+ item->RemoveFromUpdateQueueOf(AHBplayer);
+ continue;
+ }
+
+ uint32 etime = urand(1,3);
+ switch(etime)
+ {
+ case 1:
+ etime = 43200;
+ break;
+ case 2:
+ etime = 86400;
+ break;
+ case 3:
+ etime = 172800;
+ break;
+ default:
+ etime = 86400;
+ break;
+ }
+ item->SetCount(stackCount);
+
+ uint32 dep = auctionmgr.GetAuctionDeposit(ahEntry, etime, item);
+
+ AuctionEntry* auctionEntry = new AuctionEntry;
+ auctionEntry->Id = objmgr.GenerateAuctionID();
+ auctionEntry->auctioneer = AuctioneerGUID;
+ auctionEntry->item_guidlow = item->GetGUIDLow();
+ auctionEntry->item_template = item->GetEntry();
+ auctionEntry->owner = AHBplayer->GetGUIDLow();
+ auctionEntry->startbid = bidPrice * stackCount;
+ auctionEntry->buyout = buyoutPrice * stackCount;
+ auctionEntry->bidder = 0;
+ auctionEntry->bid = 0;
+ auctionEntry->deposit = dep;
+ auctionEntry->expire_time = (time_t) etime + time(NULL);
+ auctionEntry->auctionHouseEntry = ahEntry;
+ item->SaveToDB();
+ item->RemoveFromUpdateQueueOf(AHBplayer);
+ auctionmgr.AddAItem(item);
+ auctionHouse->AddAuction(auctionEntry);
+ auctionEntry->SaveToDB();
+
+ switch(itemColor)
+ {
+ case 0:
+ ++greyItems;
+ break;
+ case 1:
+ ++whiteItems;
+ break;
+ case 2:
+ ++greenItems;
+ break;
+ case 3:
+ ++blueItems;
+ break;
+ case 4:
+ ++purpleItems;
+ break;
+ case 5:
+ ++orangeItems;
+ break;
+ case 6:
+ ++yellowItems;
+ break;
+ case 7:
+ ++greyTGoods;
+ break;
+ case 8:
+ ++whiteTGoods;
+ break;
+ case 9:
+ ++greenTGoods;
+ break;
+ case 10:
+ ++blueTGoods;
+ break;
+ case 11:
+ ++purpleTGoods;
+ break;
+ case 12:
+ ++orangeTGoods;
+ break;
+ case 13:
+ ++yellowTGoods;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+void AuctionHouseBot::addNewAuctionBuyerBotBid(Player *AHBplayer, AHBConfig *config, WorldSession *session)
+{
+ if (!AHBBuyer)
+ {
+ if (debug_Out) sLog.outError("AHBuyer: Disabled");
+ return;
+ }
+
+ QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT id FROM auctionhouse WHERE itemowner<>%u AND buyguid<>%u", AHBplayerGUID, AHBplayerGUID);
+
+ if (!result)
+ return;
+
+ if (result->GetRowCount() == 0)
+ return;
+
+ // Fetches content of selected AH
+ AuctionHouseObject* auctionHouse = auctionmgr.GetAuctionsMap(config->GetAHFID());
+ vector<uint32> possibleBids;
+
+ do
+ {
+ uint32 tmpdata = result->Fetch()->GetUInt32();
+ possibleBids.push_back(tmpdata);
+ }while (result->NextRow());
+
+ for (uint32 count = 1; count <= config->GetBidsPerInterval(); ++count)
+ {
+ // Do we have anything to bid? If not, stop here.
+ if (possibleBids.empty())
+ {
+ //if (debug_Out) sLog.outString("AHBuyer: I have no items to bid on.");
+ count = config->GetBidsPerInterval();
+ continue;
+ }
+
+ // Choose random auction from possible auctions
+ uint32 vectorPos = urand(0, possibleBids.size() - 1);
+ vector<uint32>::iterator iter = possibleBids.begin();
+ advance(iter, vectorPos);
+
+ // from auctionhousehandler.cpp, creates auction pointer & player pointer
+ AuctionEntry* auction = auctionHouse->GetAuction(*iter);
+
+ // Erase the auction from the vector to prevent bidding on item in next iteration.
+ possibleBids.erase(iter);
+
+ if (!auction)
+ continue;
+
+ // get exact item information
+ Item *pItem = auctionmgr.GetAItem(auction->item_guidlow);
+ if (!pItem)
+ {
+ if (debug_Out) sLog.outError("AHBuyer: Item %u doesn't exist, perhaps bought already?", auction->item_guidlow);
+ continue;
+ }
+
+ // get item prototype
+ ItemPrototype const* prototype = objmgr.GetItemPrototype(auction->item_template);
+
+ // check which price we have to use, startbid or if it is bidded already
+ uint32 currentprice;
+ if (auction->bid)
+ currentprice = auction->bid;
+ else
+ currentprice = auction->startbid;
+
+ // Prepare portion from maximum bid
+ double bidrate = static_cast<double>(urand(1, 100)) / 100;
+ long double bidMax = 0;
+
+ // check that bid has acceptable value and take bid based on vendorprice, stacksize and quality
+ switch (BuyMethod)
+ {
+ case 0:
+ {
+ if ((prototype->Quality >= 0) && (prototype->Quality <= AHB_MAX_QUALITY))
+ {
+ if (currentprice < prototype->SellPrice * pItem->GetCount() * config->GetBuyerPrice(prototype->Quality))
+ bidMax = prototype->SellPrice * pItem->GetCount() * config->GetBuyerPrice(prototype->Quality);
+ }
+ else
+ {
+ // quality is something it shouldn't be, let's get out of here
+ if (debug_Out) sLog.outError("AHBuyer: Quality %u not Supported", prototype->Quality);
+ continue;
+ }
+ break;
+ }
+ case 1:
+ {
+ if ((prototype->Quality >= 0) && (prototype->Quality <= AHB_MAX_QUALITY))
+ {
+ if (currentprice < prototype->BuyPrice * pItem->GetCount() * config->GetBuyerPrice(prototype->Quality))
+ bidMax = prototype->BuyPrice * pItem->GetCount() * config->GetBuyerPrice(prototype->Quality);
+ }
+ else
+ {
+ // quality is something it shouldn't be, let's get out of here
+ if (debug_Out) sLog.outError("AHBuyer: Quality %u not Supported", prototype->Quality);
+ continue;
+ }
+ break;
+ }
+ }
+
+ // check some special items, and do recalculating to their prices
+ switch (prototype->Class)
+ {
+ // ammo
+ case 6:
+ bidMax = 0;
+ break;
+ default:
+ break;
+ }
+
+ if (bidMax == 0)
+ {
+ // quality check failed to get bidmax, let's get out of here
+ continue;
+ }
+
+ // Calculate our bid
+ long double bidvalue = currentprice + ((bidMax - currentprice) * bidrate);
+ // Convert to uint32
+ uint32 bidprice = static_cast<uint32>(bidvalue);
+
+ // Check our bid is high enough to be valid. If not, correct it to minimum.
+ if ((currentprice + auction->GetAuctionOutBid()) > bidprice)
+ bidprice = currentprice + auction->GetAuctionOutBid();
+
+ if (debug_Out)
+ {
+ sLog.outString("-------------------------------------------------");
+ sLog.outString("AHBuyer: Info for Auction #%u:", auction->Id);
+ sLog.outString("AHBuyer: AuctionHouse: %u", auction->GetHouseId());
+ sLog.outString("AHBuyer: Auctioneer: %u", auction->auctioneer);
+ sLog.outString("AHBuyer: Owner: %u", auction->owner);
+ sLog.outString("AHBuyer: Bidder: %u", auction->bidder);
+ sLog.outString("AHBuyer: Starting Bid: %u", auction->startbid);
+ sLog.outString("AHBuyer: Current Bid: %u", currentprice);
+ sLog.outString("AHBuyer: Buyout: %u", auction->buyout);
+ sLog.outString("AHBuyer: Deposit: %u", auction->deposit);
+ sLog.outString("AHBuyer: Expire Time: %u", auction->expire_time);
+ sLog.outString("AHBuyer: Bid Rate: %f", bidrate);
+ sLog.outString("AHBuyer: Bid Max: %f", bidMax);
+ sLog.outString("AHBuyer: Bid Value: %f", bidvalue);
+ sLog.outString("AHBuyer: Bid Price: %u", bidprice);
+ sLog.outString("AHBuyer: Item GUID: %u", auction->item_guidlow);
+ sLog.outString("AHBuyer: Item Template: %u", auction->item_template);
+ sLog.outString("AHBuyer: Item Info:");
+ sLog.outString("AHBuyer: Item ID: %u", prototype->ItemId);
+ sLog.outString("AHBuyer: Buy Price: %u", prototype->BuyPrice);
+ sLog.outString("AHBuyer: Sell Price: %u", prototype->SellPrice);
+ sLog.outString("AHBuyer: Bonding: %u", prototype->Bonding);
+ sLog.outString("AHBuyer: Quality: %u", prototype->Quality);
+ sLog.outString("AHBuyer: Item Level: %u", prototype->ItemLevel);
+ sLog.outString("AHBuyer: Ammo Type: %u", prototype->AmmoType);
+ sLog.outString("-------------------------------------------------");
+ }
+
+ // Check whether we do normal bid, or buyout
+ if ((bidprice < auction->buyout) || (auction->buyout == 0))
+ {
+
+ if (auction->bidder > 0)
+ {
+ if (auction->bidder == AHBplayer->GetGUIDLow())
+ {
+ //pl->ModifyMoney(-int32(price - auction->bid));
+ }
+ else
+ {
+ // mail to last bidder and return money
+ session->SendAuctionOutbiddedMail(auction , bidprice);
+ //pl->ModifyMoney(-int32(price));
+ }
+ }
+
+ auction->bidder = AHBplayer->GetGUIDLow();
+ auction->bid = bidprice;
+
+ // Saving auction into database
+ CharacterDatabase.PExecute("UPDATE auctionhouse SET buyguid = '%u',lastbid = '%u' WHERE id = '%u'", auction->bidder, auction->bid, auction->Id);
+ }
+ else
+ {
+ //buyout
+ if ((auction->bidder) && (AHBplayer->GetGUIDLow() != auction->bidder))
+ {
+ session->SendAuctionOutbiddedMail(auction, auction->buyout);
+ }
+ auction->bidder = AHBplayer->GetGUIDLow();
+ auction->bid = auction->buyout;
+
+ // Send mails to buyer & seller
+ auctionmgr.SendAuctionSalePendingMail(auction);
+ auctionmgr.SendAuctionSuccessfulMail(auction);
+ auctionmgr.SendAuctionWonMail(auction);
+ auction->DeleteFromDB();
+ uint32 item_template = auction->item_template;
+ auctionmgr.RemoveAItem(auction->item_guidlow);
+ auctionHouse->RemoveAuction(auction, item_template);
+ }
+ }
+}
+
+void AuctionHouseBot::Update()
+{
+ time_t _newrun = time(NULL);
+ if ((!AHBSeller) && (!AHBBuyer))
+ return;
+
+ WorldSession _session(AHBplayerAccount, NULL, SEC_PLAYER, true, 0, LOCALE_enUS);
+ Player _AHBplayer(&_session);
+ _AHBplayer.Initialize(AHBplayerGUID);
+ ObjectAccessor::Instance().AddObject(&_AHBplayer);
+
+ // Add New Bids
+ if (!sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION))
+ {
+ addNewAuctions(&_AHBplayer, &AllianceConfig);
+ if (((_newrun - _lastrun_a) >= (AllianceConfig.GetBiddingInterval() * MINUTE)) && (AllianceConfig.GetBidsPerInterval() > 0))
+ {
+ //if (debug_Out) sLog.outString("AHBuyer: %u seconds have passed since last bid", (_newrun - _lastrun_a));
+ //if (debug_Out) sLog.outString("AHBuyer: Bidding on Alliance Auctions");
+ addNewAuctionBuyerBotBid(&_AHBplayer, &AllianceConfig, &_session);
+ _lastrun_a = _newrun;
+ }
+
+ addNewAuctions(&_AHBplayer, &HordeConfig);
+ if (((_newrun - _lastrun_h) >= (HordeConfig.GetBiddingInterval() * MINUTE)) && (HordeConfig.GetBidsPerInterval() > 0))
+ {
+ //if (debug_Out) sLog.outString("AHBuyer: %u seconds have passed since last bid", (_newrun - _lastrun_h));
+ //if (debug_Out) sLog.outString("AHBuyer: Bidding on Horde Auctions");
+ addNewAuctionBuyerBotBid(&_AHBplayer, &HordeConfig, &_session);
+ _lastrun_h = _newrun;
+ }
+ }
+
+ addNewAuctions(&_AHBplayer, &NeutralConfig);
+ if (((_newrun - _lastrun_n) >= (NeutralConfig.GetBiddingInterval() * MINUTE)) && (NeutralConfig.GetBidsPerInterval() > 0))
+ {
+ //if (debug_Out) sLog.outString("AHBuyer: %u seconds have passed since last bid", (_newrun - _lastrun_n));
+ //if (debug_Out) sLog.outString("AHBuyer: Bidding on Neutral Auctions");
+ addNewAuctionBuyerBotBid(&_AHBplayer, &NeutralConfig, &_session);
+ _lastrun_n = _newrun;
+ }
+ ObjectAccessor::Instance().RemoveObject(&_AHBplayer);
+}
+
+void AuctionHouseBot::Initialize()
+{
+ debug_Out = sConfig.GetBoolDefault("AuctionHouseBot.DEBUG", false);
+ debug_Out_Filters = sConfig.GetBoolDefault("AuctionHouseBot.DEBUG_FILTERS", false);
+
+ AHBSeller = sConfig.GetBoolDefault("AuctionHouseBot.EnableSeller", false);
+ AHBBuyer = sConfig.GetBoolDefault("AuctionHouseBot.EnableBuyer", false);
+ SellMethod = sConfig.GetBoolDefault("AuctionHouseBot.UseBuyPriceForSeller", false);
+ BuyMethod = sConfig.GetBoolDefault("AuctionHouseBot.UseBuyPriceForBuyer", false);
+
+ AHBplayerAccount = sConfig.GetIntDefault("AuctionHouseBot.Account", 0);
+ AHBplayerGUID = sConfig.GetIntDefault("AuctionHouseBot.GUID", 0);
+ ItemsPerCycle = sConfig.GetIntDefault("AuctionHouseBot.ItemsPerCycle", 200);
+
+ //Begin Filters
+
+ Vendor_Items = sConfig.GetBoolDefault("AuctionHouseBot.VendorItems", false);
+ Loot_Items = sConfig.GetBoolDefault("AuctionHouseBot.LootItems", true);
+ Other_Items = sConfig.GetBoolDefault("AuctionHouseBot.OtherItems", false);
+ Vendor_TGs = sConfig.GetBoolDefault("AuctionHouseBot.VendorTradeGoods", false);
+ Loot_TGs = sConfig.GetBoolDefault("AuctionHouseBot.LootTradeGoods", true);
+ Other_TGs = sConfig.GetBoolDefault("AuctionHouseBot.OtherTradeGoods", false);
+
+ No_Bind = sConfig.GetBoolDefault("AuctionHouseBot.No_Bind", true);
+ Bind_When_Picked_Up = sConfig.GetBoolDefault("AuctionHouseBot.Bind_When_Picked_Up", false);
+ Bind_When_Equipped = sConfig.GetBoolDefault("AuctionHouseBot.Bind_When_Equipped", true);
+ Bind_When_Use = sConfig.GetBoolDefault("AuctionHouseBot.Bind_When_Use", true);
+ Bind_Quest_Item = sConfig.GetBoolDefault("AuctionHouseBot.Bind_Quest_Item", false);
+
+ DisableBeta_PTR_Unused = sConfig.GetBoolDefault("AuctionHouseBot.DisableBeta_PTR_Unused", false);
+ DisablePermEnchant = sConfig.GetBoolDefault("AuctionHouseBot.DisablePermEnchant", false);
+ DisableConjured = sConfig.GetBoolDefault("AuctionHouseBot.DisableConjured", false);
+ DisableGems = sConfig.GetBoolDefault("AuctionHouseBot.DisableGems", false);
+ DisableMoney = sConfig.GetBoolDefault("AuctionHouseBot.DisableMoney", false);
+ DisableMoneyLoot = sConfig.GetBoolDefault("AuctionHouseBot.DisableMoneyLoot", false);
+ DisableLootable = sConfig.GetBoolDefault("AuctionHouseBot.DisableLootable", false);
+ DisableKeys = sConfig.GetBoolDefault("AuctionHouseBot.DisableKeys", false);
+ DisableDuration = sConfig.GetBoolDefault("AuctionHouseBot.DisableDuration", false);
+ DisableBOP_Or_Quest_NoReqLevel = sConfig.GetBoolDefault("AuctionHouseBot.DisableBOP_Or_Quest_NoReqLevel", false);
+
+ DisableWarriorItems = sConfig.GetBoolDefault("AuctionHouseBot.DisableWarriorItems", false);
+ DisablePaladinItems = sConfig.GetBoolDefault("AuctionHouseBot.DisablePaladinItems", false);
+ DisableHunterItems = sConfig.GetBoolDefault("AuctionHouseBot.DisableHunterItems", false);
+ DisableRogueItems = sConfig.GetBoolDefault("AuctionHouseBot.DisableRogueItems", false);
+ DisablePriestItems = sConfig.GetBoolDefault("AuctionHouseBot.DisablePriestItems", false);
+ DisableDKItems = sConfig.GetBoolDefault("AuctionHouseBot.DisableDKItems", false);
+ DisableShamanItems = sConfig.GetBoolDefault("AuctionHouseBot.DisableShamanItems", false);
+ DisableMageItems = sConfig.GetBoolDefault("AuctionHouseBot.DisableMageItems", false);
+ DisableWarlockItems = sConfig.GetBoolDefault("AuctionHouseBot.DisableWarlockItems", false);
+ DisableUnusedClassItems = sConfig.GetBoolDefault("AuctionHouseBot.DisableUnusedClassItems", false);
+ DisableDruidItems = sConfig.GetBoolDefault("AuctionHouseBot.DisableDruidItems", false);
+
+ DisableItemsBelowLevel = sConfig.GetIntDefault("AuctionHouseBot.DisableItemsBelowLevel", 0);
+ DisableItemsAboveLevel = sConfig.GetIntDefault("AuctionHouseBot.DisableItemsAboveLevel", 0);
+ DisableTGsBelowLevel = sConfig.GetIntDefault("AuctionHouseBot.DisableTGsBelowLevel", 0);
+ DisableTGsAboveLevel = sConfig.GetIntDefault("AuctionHouseBot.DisableTGsAboveLevel", 0);
+ DisableItemsBelowGUID = sConfig.GetIntDefault("AuctionHouseBot.DisableItemsBelowGUID", 0);
+ DisableItemsAboveGUID = sConfig.GetIntDefault("AuctionHouseBot.DisableItemsAboveGUID", 0);
+ DisableTGsBelowGUID = sConfig.GetIntDefault("AuctionHouseBot.DisableTGsBelowGUID", 0);
+ DisableTGsAboveGUID = sConfig.GetIntDefault("AuctionHouseBot.DisableTGsAboveGUID", 0);
+ DisableItemsBelowReqLevel = sConfig.GetIntDefault("AuctionHouseBot.DisableItemsBelowReqLevel", 0);
+ DisableItemsAboveReqLevel = sConfig.GetIntDefault("AuctionHouseBot.DisableItemsAboveReqLevel", 0);
+ DisableTGsBelowReqLevel = sConfig.GetIntDefault("AuctionHouseBot.DisableTGsBelowReqLevel", 0);
+ DisableTGsAboveReqLevel = sConfig.GetIntDefault("AuctionHouseBot.DisableTGsAboveReqLevel", 0);
+ DisableItemsBelowReqSkillRank = sConfig.GetIntDefault("AuctionHouseBot.DisableItemsBelowReqSkillRank", 0);
+ DisableItemsAboveReqSkillRank = sConfig.GetIntDefault("AuctionHouseBot.DisableItemsAboveReqSkillRank", 0);
+ DisableTGsBelowReqSkillRank = sConfig.GetIntDefault("AuctionHouseBot.DisableTGsBelowReqSkillRank", 0);
+ DisableTGsAboveReqSkillRank = sConfig.GetIntDefault("AuctionHouseBot.DisableTGsAboveReqSkillRank", 0);
+
+ //End Filters
+ if (!sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION))
+ {
+ LoadValues(&AllianceConfig);
+ LoadValues(&HordeConfig);
+ }
+ LoadValues(&NeutralConfig);
+
+ //
+ // check if the AHBot account/GUID in the config actually exists
+ //
+
+ if ((AHBplayerAccount != 0) || (AHBplayerGUID != 0))
+ {
+ QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT 1 FROM characters WHERE account = %u AND guid = %u", AHBplayerAccount, AHBplayerGUID);
+ if (!result)
+ {
+ sLog.outError("AuctionHouseBot: The account/GUID-information set for your AHBot is incorrect (account: %u guid: %u)", AHBplayerAccount, AHBplayerGUID);
+ return;
+ }
+ }
+
+ if (AHBSeller)
+ {
+ QueryResult_AutoPtr results = QueryResult_AutoPtr(NULL);
+ char npcQuery[] = "SELECT distinct item FROM npc_vendor";
+ results = WorldDatabase.Query(npcQuery);
+ if (results != NULL)
+ {
+ do
+ {
+ Field* fields = results->Fetch();
+ npcItems.push_back(fields[0].GetUInt32());
+
+ } while (results->NextRow());
+ }
+ else
+ {
+ if (debug_Out) sLog.outString("AuctionHouseBot: \"%s\" failed", npcQuery);
+ }
+
+ char lootQuery[] = "SELECT item FROM creature_loot_template UNION "
+ "SELECT item FROM reference_loot_template UNION "
+ "SELECT item FROM disenchant_loot_template UNION "
+ "SELECT item FROM fishing_loot_template UNION "
+ "SELECT item FROM gameobject_loot_template UNION "
+ "SELECT item FROM item_loot_template UNION "
+ "SELECT item FROM milling_loot_template UNION "
+ "SELECT item FROM pickpocketing_loot_template UNION "
+ "SELECT item FROM prospecting_loot_template UNION "
+ "SELECT item FROM skinning_loot_template";
+
+ results = WorldDatabase.Query(lootQuery);
+ if (results != NULL)
+ {
+ do
+ {
+ Field* fields = results->Fetch();
+ lootItems.push_back(fields[0].GetUInt32());
+
+ } while (results->NextRow());
+ }
+ else
+ {
+ if (debug_Out) sLog.outString("AuctionHouseBot: \"%s\" failed", lootQuery);
+ }
+
+ for (uint32 itemID = 0; itemID < sItemStorage.MaxEntry; itemID++)
+ {
+ ItemPrototype const* prototype = objmgr.GetItemPrototype(itemID);
+
+ if (prototype == NULL)
+ continue;
+
+ switch (prototype->Bonding)
+ {
+ case NO_BIND:
+ if (!No_Bind)
+ continue;
+ break;
+ case BIND_WHEN_PICKED_UP:
+ if (!Bind_When_Picked_Up)
+ continue;
+ break;
+ case BIND_WHEN_EQUIPED:
+ if (!Bind_When_Equipped)
+ continue;
+ break;
+ case BIND_WHEN_USE:
+ if (!Bind_When_Use)
+ continue;
+ break;
+ case BIND_QUEST_ITEM:
+ if (!Bind_Quest_Item)
+ continue;
+ break;
+ default:
+ continue;
+ break;
+ }
+
+ switch (SellMethod)
+ {
+ case 0:
+ if (prototype->SellPrice == 0)
+ continue;
+ break;
+ case 1:
+ if (prototype->BuyPrice == 0)
+ continue;
+ break;
+ }
+
+ if ((prototype->Quality < 0) || (prototype->Quality > 6))
+ continue;
+
+ if ((Vendor_Items == 0) && !(prototype->Class == ITEM_CLASS_TRADE_GOODS))
+ {
+ bool isVendorItem = false;
+
+ for (unsigned int i = 0; (i < npcItems.size()) && (!isVendorItem); i++)
+ {
+ if (itemID == npcItems[i])
+ isVendorItem = true;
+ }
+
+ if (isVendorItem)
+ continue;
+ }
+
+ if ((Vendor_TGs == 0) && (prototype->Class == ITEM_CLASS_TRADE_GOODS))
+ {
+ bool isVendorTG = false;
+
+ for (unsigned int i = 0; (i < npcItems.size()) && (!isVendorTG); i++)
+ {
+ if (itemID == npcItems[i])
+ isVendorTG = true;
+ }
+
+ if (isVendorTG)
+ continue;
+ }
+
+ if ((Loot_Items == 0) && !(prototype->Class == ITEM_CLASS_TRADE_GOODS))
+ {
+ bool isLootItem = false;
+
+ for (unsigned int i = 0; (i < lootItems.size()) && (!isLootItem); i++)
+ {
+ if (itemID == lootItems[i])
+ isLootItem = true;
+ }
+
+ if (isLootItem)
+ continue;
+ }
+
+ if ((Loot_TGs == 0) && (prototype->Class == ITEM_CLASS_TRADE_GOODS))
+ {
+ bool isLootTG = false;
+
+ for (unsigned int i = 0; (i < lootItems.size()) && (!isLootTG); i++)
+ {
+ if (itemID == lootItems[i])
+ isLootTG = true;
+ }
+
+ if (isLootTG)
+ continue;
+ }
+
+ if ((Other_Items == 0) && !(prototype->Class == ITEM_CLASS_TRADE_GOODS))
+ {
+ bool isVendorItem = false;
+ bool isLootItem = false;
+
+ for (unsigned int i = 0; (i < npcItems.size()) && (!isVendorItem); i++)
+ {
+ if (itemID == npcItems[i])
+ isVendorItem = true;
+ }
+ for (unsigned int i = 0; (i < lootItems.size()) && (!isLootItem); i++)
+ {
+ if (itemID == lootItems[i])
+ isLootItem = true;
+ }
+ if ((!isLootItem) && (!isVendorItem))
+ continue;
+ }
+
+ if ((Other_TGs == 0) && (prototype->Class == ITEM_CLASS_TRADE_GOODS))
+ {
+ bool isVendorTG = false;
+ bool isLootTG = false;
+
+ for (unsigned int i = 0; (i < npcItems.size()) && (!isVendorTG); i++)
+ {
+ if (itemID == npcItems[i])
+ isVendorTG = true;
+ }
+ for (unsigned int i = 0; (i < lootItems.size()) && (!isLootTG); i++)
+ {
+ if (itemID == lootItems[i])
+ isLootTG = true;
+ }
+ if ((!isLootTG) && (!isVendorTG))
+ continue;
+ }
+
+ //TODO:Make list of items and create a vector
+ // Disable PTR/Beta/Unused items
+ if ((DisableBeta_PTR_Unused) && ((prototype->ItemId == 21878) || (prototype->ItemId == 27774) || (prototype->ItemId == 27811) || (prototype->ItemId == 28117) || (prototype->ItemId == 28112)))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (PTR/Beta/Unused Item)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable permanent enchants items
+ if ((DisablePermEnchant) && (prototype->Class == ITEM_CLASS_PERMANENT))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Permanent Enchant Item)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable conjured items
+ if ((DisableConjured) && (prototype->IsConjuredConsumable()))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Conjured Consumable)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable gems
+ if ((DisableGems) && (prototype->Class == ITEM_CLASS_GEM))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Gem)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable money
+ if ((DisableMoney) && (prototype->Class == ITEM_CLASS_MONEY))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Money)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable moneyloot
+ if ((DisableMoneyLoot) && (prototype->MinMoneyLoot > 0))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (MoneyLoot)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable lootable items
+ if ((DisableLootable) && (prototype->Flags & 4))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Lootable Item)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable Keys
+ if ((DisableKeys) && (prototype->Class == ITEM_CLASS_KEY))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Quest Item)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable items with duration
+ if ((DisableDuration) && (prototype->Duration > 0))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Has a Duration)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable items which are BOP or Quest Items and have a required level lower than the item level
+ if ((DisableBOP_Or_Quest_NoReqLevel) && ((prototype->Bonding == BIND_WHEN_PICKED_UP || prototype->Bonding == BIND_QUEST_ITEM) && (prototype->RequiredLevel < prototype->ItemLevel)))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (BOP or BQI and Required Level is less than Item Level)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable items specifically for Warrior
+ if ((DisableWarriorItems) && (prototype->AllowableClass == 1))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Warrior Item)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable items specifically for Paladin
+ if ((DisablePaladinItems) && (prototype->AllowableClass == 2))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Paladin Item)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable items specifically for Hunter
+ if ((DisableHunterItems) && (prototype->AllowableClass == 4))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Hunter Item)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable items specifically for Rogue
+ if ((DisableRogueItems) && (prototype->AllowableClass == 8))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Rogue Item)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable items specifically for Priest
+ if ((DisablePriestItems) && (prototype->AllowableClass == 16))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Priest Item)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable items specifically for DK
+ if ((DisableDKItems) && (prototype->AllowableClass == 32))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (DK Item)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable items specifically for Shaman
+ if ((DisableShamanItems) && (prototype->AllowableClass == 64))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Shaman Item)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable items specifically for Mage
+ if ((DisableMageItems) && (prototype->AllowableClass == 128))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Mage Item)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable items specifically for Warlock
+ if ((DisableWarlockItems) && (prototype->AllowableClass == 256))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Warlock Item)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable items specifically for Unused Class
+ if ((DisableUnusedClassItems) && (prototype->AllowableClass == 512))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Unused Item)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable items specifically for Druid
+ if ((DisableDruidItems) && (prototype->AllowableClass == 1024))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Druid Item)", prototype->ItemId);
+ continue;
+ }
+
+ // Disable Items below level X
+ if ((DisableItemsBelowLevel) && (prototype->Class != ITEM_CLASS_TRADE_GOODS) && (prototype->ItemLevel < DisableItemsBelowLevel))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Item Level = %u)", prototype->ItemId, prototype->ItemLevel);
+ continue;
+ }
+
+ // Disable Items above level X
+ if ((DisableItemsAboveLevel) && (prototype->Class != ITEM_CLASS_TRADE_GOODS) && (prototype->ItemLevel > DisableItemsAboveLevel))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Item Level = %u)", prototype->ItemId, prototype->ItemLevel);
+ continue;
+ }
+
+ // Disable Trade Goods below level X
+ if ((DisableTGsBelowLevel) && (prototype->Class == ITEM_CLASS_TRADE_GOODS) && (prototype->ItemLevel < DisableTGsBelowLevel))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Trade Good %u disabled (Trade Good Level = %u)", prototype->ItemId, prototype->ItemLevel);
+ continue;
+ }
+
+ // Disable Trade Goods above level X
+ if ((DisableTGsAboveLevel) && (prototype->Class == ITEM_CLASS_TRADE_GOODS) && (prototype->ItemLevel > DisableTGsAboveLevel))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Trade Good %u disabled (Trade Good Level = %u)", prototype->ItemId, prototype->ItemLevel);
+ continue;
+ }
+
+ // Disable Items below GUID X
+ if ((DisableItemsBelowGUID) && (prototype->Class != ITEM_CLASS_TRADE_GOODS) && (prototype->ItemId < DisableItemsBelowGUID))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Item Level = %u)", prototype->ItemId, prototype->ItemLevel);
+ continue;
+ }
+
+ // Disable Items above GUID X
+ if ((DisableItemsAboveGUID) && (prototype->Class != ITEM_CLASS_TRADE_GOODS) && (prototype->ItemId > DisableItemsAboveGUID))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Item Level = %u)", prototype->ItemId, prototype->ItemLevel);
+ continue;
+ }
+
+ // Disable Trade Goods below GUID X
+ if ((DisableTGsBelowGUID) && (prototype->Class == ITEM_CLASS_TRADE_GOODS) && (prototype->ItemId < DisableTGsBelowGUID))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Trade Good Level = %u)", prototype->ItemId, prototype->ItemLevel);
+ continue;
+ }
+
+ // Disable Trade Goods above GUID X
+ if ((DisableTGsAboveGUID) && (prototype->Class == ITEM_CLASS_TRADE_GOODS) && (prototype->ItemId > DisableTGsAboveGUID))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (Trade Good Level = %u)", prototype->ItemId, prototype->ItemLevel);
+ continue;
+ }
+
+ // Disable Items for level lower than X
+ if ((DisableItemsBelowReqLevel) && (prototype->RequiredLevel < DisableItemsBelowReqLevel))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (RequiredLevel = %u)", prototype->ItemId, prototype->RequiredLevel);
+ continue;
+ }
+
+ // Disable Items for level higher than X
+ if ((DisableItemsAboveReqLevel) && (prototype->RequiredLevel > DisableItemsAboveReqLevel))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (RequiredLevel = %u)", prototype->ItemId, prototype->RequiredLevel);
+ continue;
+ }
+
+ // Disable Trade Goods for level lower than X
+ if ((DisableTGsBelowReqLevel) && (prototype->RequiredLevel < DisableTGsBelowReqLevel))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Trade Good %u disabled (RequiredLevel = %u)", prototype->ItemId, prototype->RequiredLevel);
+ continue;
+ }
+
+ // Disable Trade Goods for level higher than X
+ if ((DisableTGsAboveReqLevel) && (prototype->RequiredLevel > DisableTGsAboveReqLevel))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Trade Good %u disabled (RequiredLevel = %u)", prototype->ItemId, prototype->RequiredLevel);
+ continue;
+ }
+
+ // Disable Items that require skill lower than X
+ if ((DisableItemsBelowReqSkillRank) && (prototype->RequiredSkillRank < DisableItemsBelowReqSkillRank))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (RequiredSkillRank = %u)", prototype->ItemId, prototype->RequiredSkillRank);
+ continue;
+ }
+
+ // Disable Items that require skill higher than X
+ if ((DisableItemsAboveReqSkillRank) && (prototype->RequiredSkillRank > DisableItemsAboveReqSkillRank))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (RequiredSkillRank = %u)", prototype->ItemId, prototype->RequiredSkillRank);
+ continue;
+ }
+
+ // Disable Trade Goods that require skill lower than X
+ if ((DisableTGsBelowReqSkillRank) && (prototype->RequiredSkillRank < DisableTGsBelowReqSkillRank))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (RequiredSkillRank = %u)", prototype->ItemId, prototype->RequiredSkillRank);
+ continue;
+ }
+
+ // Disable Trade Goods that require skill higher than X
+ if ((DisableTGsAboveReqSkillRank) && (prototype->RequiredSkillRank > DisableTGsAboveReqSkillRank))
+ {
+ if (debug_Out_Filters) sLog.outString("AuctionHouseBot: Item %u disabled (RequiredSkillRank = %u)", prototype->ItemId, prototype->RequiredSkillRank);
+ continue;
+ }
+
+ switch (prototype->Quality)
+ {
+ case AHB_GREY:
+ if (prototype->Class == ITEM_CLASS_TRADE_GOODS)
+ greyTradeGoodsBin.push_back(itemID);
+ else
+ greyItemsBin.push_back(itemID);
+ break;
+
+ case AHB_WHITE:
+ if (prototype->Class == ITEM_CLASS_TRADE_GOODS)
+ whiteTradeGoodsBin.push_back(itemID);
+ else
+ whiteItemsBin.push_back(itemID);
+ break;
+
+ case AHB_GREEN:
+ if (prototype->Class == ITEM_CLASS_TRADE_GOODS)
+ greenTradeGoodsBin.push_back(itemID);
+ else
+ greenItemsBin.push_back(itemID);
+ break;
+
+ case AHB_BLUE:
+ if (prototype->Class == ITEM_CLASS_TRADE_GOODS)
+ blueTradeGoodsBin.push_back(itemID);
+ else
+ blueItemsBin.push_back(itemID);
+ break;
+
+ case AHB_PURPLE:
+ if (prototype->Class == ITEM_CLASS_TRADE_GOODS)
+ purpleTradeGoodsBin.push_back(itemID);
+ else
+ purpleItemsBin.push_back(itemID);
+ break;
+
+ case AHB_ORANGE:
+ if (prototype->Class == ITEM_CLASS_TRADE_GOODS)
+ orangeTradeGoodsBin.push_back(itemID);
+ else
+ orangeItemsBin.push_back(itemID);
+ break;
+
+ case AHB_YELLOW:
+ if (prototype->Class == ITEM_CLASS_TRADE_GOODS)
+ yellowTradeGoodsBin.push_back(itemID);
+ else
+ yellowItemsBin.push_back(itemID);
+ break;
+ }
+ }
+
+ if ((greyTradeGoodsBin.size() == 0) &&
+ (whiteTradeGoodsBin.size() == 0) &&
+ (greenTradeGoodsBin.size() == 0) &&
+ (blueTradeGoodsBin.size() == 0) &&
+ (purpleTradeGoodsBin.size() == 0) &&
+ (orangeTradeGoodsBin.size() == 0) &&
+ (yellowTradeGoodsBin.size() == 0) &&
+ (greyItemsBin.size() == 0) &&
+ (whiteItemsBin.size() == 0) &&
+ (greenItemsBin.size() == 0) &&
+ (blueItemsBin.size() == 0) &&
+ (purpleItemsBin.size() == 0) &&
+ (orangeItemsBin.size() == 0) &&
+ (yellowItemsBin.size() == 0))
+ {
+ sLog.outError("AuctionHouseBot: No items");
+ AHBSeller = 0;
+ }
+
+ sLog.outString("AuctionHouseBot:");
+ sLog.outString("loaded %u grey trade goods", greyTradeGoodsBin.size());
+ sLog.outString("loaded %u white trade goods", whiteTradeGoodsBin.size());
+ sLog.outString("loaded %u green trade goods", greenTradeGoodsBin.size());
+ sLog.outString("loaded %u blue trade goods", blueTradeGoodsBin.size());
+ sLog.outString("loaded %u purple trade goods", purpleTradeGoodsBin.size());
+ sLog.outString("loaded %u orange trade goods", orangeTradeGoodsBin.size());
+ sLog.outString("loaded %u yellow trade goods", yellowTradeGoodsBin.size());
+ sLog.outString("loaded %u grey items", greyItemsBin.size());
+ sLog.outString("loaded %u white items", whiteItemsBin.size());
+ sLog.outString("loaded %u green items", greenItemsBin.size());
+ sLog.outString("loaded %u blue items", blueItemsBin.size());
+ sLog.outString("loaded %u purple items", purpleItemsBin.size());
+ sLog.outString("loaded %u orange items", orangeItemsBin.size());
+ sLog.outString("loaded %u yellow items", yellowItemsBin.size());
+ }
+ sLog.outString("AuctionHouseBot and AuctionHouseBuyer have been loaded.");
+}
+
+void AuctionHouseBot::IncrementItemCounts(AuctionEntry* ah)
+{
+ // from auctionhousehandler.cpp, creates auction pointer & player pointer
+
+ // get exact item information
+ Item *pItem = auctionmgr.GetAItem(ah->item_guidlow);
+ if (!pItem)
+ {
+ if (debug_Out) sLog.outError("AHBot: Item %u doesn't exist, perhaps bought already?", ah->item_guidlow);
+ return;
+ }
+
+ // get item prototype
+ ItemPrototype const* prototype = objmgr.GetItemPrototype(ah->item_template);
+
+ AHBConfig *config;
+
+ FactionTemplateEntry const* u_entry = sFactionTemplateStore.LookupEntry(ah->GetHouseFaction());
+ if (!u_entry)
+ {
+ if (debug_Out) sLog.outError("AHBot: %u returned as House Faction. Neutral", ah->GetHouseFaction());
+ config = &NeutralConfig;
+ }
+ else if (u_entry->ourMask & FACTION_MASK_ALLIANCE)
+ {
+ if (debug_Out) sLog.outError("AHBot: %u returned as House Faction. Alliance", ah->GetHouseFaction());
+ config = &AllianceConfig;
+ }
+ else if (u_entry->ourMask & FACTION_MASK_HORDE)
+ {
+ if (debug_Out) sLog.outError("AHBot: %u returned as House Faction. Horde", ah->GetHouseFaction());
+ config = &HordeConfig;
+ }
+ else
+ {
+ if (debug_Out) sLog.outError("AHBot: %u returned as House Faction. Neutral", ah->GetHouseFaction());
+ config = &NeutralConfig;
+ }
+
+ config->IncItemCounts(prototype->Class, prototype->Quality);
+}
+
+void AuctionHouseBot::DecrementItemCounts(AuctionEntry* ah, uint32 item_template)
+{
+ // get item prototype
+ ItemPrototype const* prototype = objmgr.GetItemPrototype(item_template);
+
+ AHBConfig *config;
+
+ FactionTemplateEntry const* u_entry = sFactionTemplateStore.LookupEntry(ah->GetHouseFaction());
+ if (!u_entry)
+ {
+ if (debug_Out) sLog.outError("AHBot: %u returned as House Faction. Neutral", ah->GetHouseFaction());
+ config = &NeutralConfig;
+ }
+ else if (u_entry->ourMask & FACTION_MASK_ALLIANCE)
+ {
+ if (debug_Out) sLog.outError("AHBot: %u returned as House Faction. Alliance", ah->GetHouseFaction());
+ config = &AllianceConfig;
+ }
+ else if (u_entry->ourMask & FACTION_MASK_HORDE)
+ {
+ if (debug_Out) sLog.outError("AHBot: %u returned as House Faction. Horde", ah->GetHouseFaction());
+ config = &HordeConfig;
+ }
+ else
+ {
+ if (debug_Out) sLog.outError("AHBot: %u returned as House Faction. Neutral", ah->GetHouseFaction());
+ config = &NeutralConfig;
+ }
+
+ config->DecItemCounts(prototype->Class, prototype->Quality);
+}
+
+void AuctionHouseBot::Commands(uint32 command, uint32 ahMapID, uint32 col, char* args)
+{
+ AHBConfig *config = NULL;
+ switch (ahMapID)
+ {
+ case 2:
+ config = &AllianceConfig;
+ break;
+ case 6:
+ config = &HordeConfig;
+ break;
+ case 7:
+ config = &NeutralConfig;
+ break;
+ }
+ std::string color;
+ switch (col)
+ {
+ case AHB_GREY:
+ color = "grey";
+ break;
+ case AHB_WHITE:
+ color = "white";
+ break;
+ case AHB_GREEN:
+ color = "green";
+ break;
+ case AHB_BLUE:
+ color = "blue";
+ break;
+ case AHB_PURPLE:
+ color = "purple";
+ break;
+ case AHB_ORANGE:
+ color = "orange";
+ break;
+ case AHB_YELLOW:
+ color = "yellow";
+ break;
+ default:
+ break;
+ }
+ switch (command)
+ {
+ case 0: //ahexpire
+ {
+ AuctionHouseObject* auctionHouse = auctionmgr.GetAuctionsMap(config->GetAHFID());
+
+ AuctionHouseObject::AuctionEntryMap::iterator itr;
+ itr = auctionHouse->GetAuctionsBegin();
+
+ while (itr != auctionHouse->GetAuctionsEnd())
+ {
+ if (itr->second->owner == AHBplayerGUID)
+ {
+ itr->second->expire_time = sWorld.GetGameTime();
+ uint32 id = itr->second->Id;
+ uint32 expire_time = itr->second->expire_time;
+ CharacterDatabase.PExecute("UPDATE auctionhouse SET time = '%u' WHERE id = '%u'", expire_time, id);
+ }
+ ++itr;
+ }
+ }
+ break;
+ case 1: //min items
+ {
+ char * param1 = strtok(args, " ");
+ uint32 minItems = (uint32) strtoul(param1, NULL, 0);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET minitems = '%u' WHERE auctionhouse = '%u'", minItems, ahMapID);
+ config->SetMinItems(minItems);
+ }
+ break;
+ case 2: //max items
+ {
+ char * param1 = strtok(args, " ");
+ uint32 maxItems = (uint32) strtoul(param1, NULL, 0);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET maxitems = '%u' WHERE auctionhouse = '%u'", maxItems, ahMapID);
+ config->SetMaxItems(maxItems);
+ }
+ break;
+ case 3: //min time Deprecated (Place holder for future commands)
+ break;
+ case 4: //max time Deprecated (Place holder for future commands)
+ break;
+ case 5: //percentages
+ {
+ char * param1 = strtok(args, " ");
+ char * param2 = strtok(NULL, " ");
+ char * param3 = strtok(NULL, " ");
+ char * param4 = strtok(NULL, " ");
+ char * param5 = strtok(NULL, " ");
+ char * param6 = strtok(NULL, " ");
+ char * param7 = strtok(NULL, " ");
+ char * param8 = strtok(NULL, " ");
+ char * param9 = strtok(NULL, " ");
+ char * param10 = strtok(NULL, " ");
+ char * param11 = strtok(NULL, " ");
+ char * param12 = strtok(NULL, " ");
+ char * param13 = strtok(NULL, " ");
+ char * param14 = strtok(NULL, " ");
+ uint32 greytg = (uint32) strtoul(param1, NULL, 0);
+ uint32 whitetg = (uint32) strtoul(param2, NULL, 0);
+ uint32 greentg = (uint32) strtoul(param3, NULL, 0);
+ uint32 bluetg = (uint32) strtoul(param4, NULL, 0);
+ uint32 purpletg = (uint32) strtoul(param5, NULL, 0);
+ uint32 orangetg = (uint32) strtoul(param6, NULL, 0);
+ uint32 yellowtg = (uint32) strtoul(param7, NULL, 0);
+ uint32 greyi = (uint32) strtoul(param8, NULL, 0);
+ uint32 whitei = (uint32) strtoul(param9, NULL, 0);
+ uint32 greeni = (uint32) strtoul(param10, NULL, 0);
+ uint32 bluei = (uint32) strtoul(param11, NULL, 0);
+ uint32 purplei = (uint32) strtoul(param12, NULL, 0);
+ uint32 orangei = (uint32) strtoul(param13, NULL, 0);
+ uint32 yellowi = (uint32) strtoul(param14, NULL, 0);
+
+ CharacterDatabase.BeginTransaction();
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET percentgreytradegoods = '%u' WHERE auctionhouse = '%u'", greytg, ahMapID);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET percentwhitetradegoods = '%u' WHERE auctionhouse = '%u'", whitetg, ahMapID);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET percentgreentradegoods = '%u' WHERE auctionhouse = '%u'", greentg, ahMapID);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET percentbluetradegoods = '%u' WHERE auctionhouse = '%u'", bluetg, ahMapID);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET percentpurpletradegoods = '%u' WHERE auctionhouse = '%u'", purpletg, ahMapID);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET percentorangetradegoods = '%u' WHERE auctionhouse = '%u'", orangetg, ahMapID);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET percentyellowtradegoods = '%u' WHERE auctionhouse = '%u'", yellowtg, ahMapID);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET percentgreyitems = '%u' WHERE auctionhouse = '%u'", greyi, ahMapID);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET percentwhiteitems = '%u' WHERE auctionhouse = '%u'", whitei, ahMapID);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET percentgreenitems = '%u' WHERE auctionhouse = '%u'", greeni, ahMapID);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET percentblueitems = '%u' WHERE auctionhouse = '%u'", bluei, ahMapID);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET percentpurpleitems = '%u' WHERE auctionhouse = '%u'", purplei, ahMapID);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET percentorangeitems = '%u' WHERE auctionhouse = '%u'", orangei, ahMapID);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET percentyellowitems = '%u' WHERE auctionhouse = '%u'", yellowi, ahMapID);
+ CharacterDatabase.CommitTransaction();
+ config->SetPercentages(greytg, whitetg, greentg, bluetg, purpletg, orangetg, yellowtg, greyi, whitei, greeni, bluei, purplei, orangei, yellowi);
+ }
+ break;
+ case 6: //min prices
+ {
+ char * param1 = strtok(args, " ");
+ uint32 minPrice = (uint32) strtoul(param1, NULL, 0);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET minprice%s = '%u' WHERE auctionhouse = '%u'",color.c_str(), minPrice, ahMapID);
+ config->SetMinPrice(col, minPrice);
+ }
+ break;
+ case 7: //max prices
+ {
+ char * param1 = strtok(args, " ");
+ uint32 maxPrice = (uint32) strtoul(param1, NULL, 0);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET maxprice%s = '%u' WHERE auctionhouse = '%u'",color.c_str(), maxPrice, ahMapID);
+ config->SetMaxPrice(col, maxPrice);
+ }
+ break;
+ case 8: //min bid price
+ {
+ char * param1 = strtok(args, " ");
+ uint32 minBidPrice = (uint32) strtoul(param1, NULL, 0);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET minbidprice%s = '%u' WHERE auctionhouse = '%u'",color.c_str(), minBidPrice, ahMapID);
+ config->SetMinBidPrice(col, minBidPrice);
+ }
+ break;
+ case 9: //max bid price
+ {
+ char * param1 = strtok(args, " ");
+ uint32 maxBidPrice = (uint32) strtoul(param1, NULL, 0);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET maxbidprice%s = '%u' WHERE auctionhouse = '%u'",color.c_str(), maxBidPrice, ahMapID);
+ config->SetMaxBidPrice(col, maxBidPrice);
+ }
+ break;
+ case 10: //max stacks
+ {
+ char * param1 = strtok(args, " ");
+ uint32 maxStack = (uint32) strtoul(param1, NULL, 0);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET maxstack%s = '%u' WHERE auctionhouse = '%u'",color.c_str(), maxStack, ahMapID);
+ config->SetMaxStack(col, maxStack);
+ }
+ break;
+ case 11: //buyer bid prices
+ {
+ char * param1 = strtok(args, " ");
+ uint32 buyerPrice = (uint32) strtoul(param1, NULL, 0);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET buyerprice%s = '%u' WHERE auctionhouse = '%u'",color.c_str(), buyerPrice, ahMapID);
+ config->SetBuyerPrice(col, buyerPrice);
+ }
+ break;
+ case 12: //buyer bidding interval
+ {
+ char * param1 = strtok(args, " ");
+ uint32 bidInterval = (uint32) strtoul(param1, NULL, 0);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET buyerbiddinginterval = '%u' WHERE auctionhouse = '%u'", bidInterval, ahMapID);
+ config->SetBiddingInterval(bidInterval);
+ }
+ break;
+ case 13: //buyer bids per interval
+ {
+ char * param1 = strtok(args, " ");
+ uint32 bidsPerInterval = (uint32) strtoul(param1, NULL, 0);
+ CharacterDatabase.PExecute("UPDATE auctionhousebot SET buyerbidsperinterval = '%u' WHERE auctionhouse = '%u'", bidsPerInterval, ahMapID);
+ config->SetBidsPerInterval(bidsPerInterval);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void AuctionHouseBot::LoadValues(AHBConfig *config)
+{
+ if (debug_Out) sLog.outString("Start Settings for %s Auctionhouses:", CharacterDatabase.PQuery("SELECT name FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetString());
+ if (AHBSeller)
+ {
+ //load min and max items
+ config->SetMinItems(CharacterDatabase.PQuery("SELECT minitems FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxItems(CharacterDatabase.PQuery("SELECT maxitems FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ //load percentages
+ uint32 greytg = CharacterDatabase.PQuery("SELECT percentgreytradegoods FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32();
+ uint32 whitetg = CharacterDatabase.PQuery("SELECT percentwhitetradegoods FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32();
+ uint32 greentg = CharacterDatabase.PQuery("SELECT percentgreentradegoods FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32();
+ uint32 bluetg = CharacterDatabase.PQuery("SELECT percentbluetradegoods FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32();
+ uint32 purpletg = CharacterDatabase.PQuery("SELECT percentpurpletradegoods FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32();
+ uint32 orangetg = CharacterDatabase.PQuery("SELECT percentorangetradegoods FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32();
+ uint32 yellowtg = CharacterDatabase.PQuery("SELECT percentyellowtradegoods FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32();
+ uint32 greyi = CharacterDatabase.PQuery("SELECT percentgreyitems FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32();
+ uint32 whitei = CharacterDatabase.PQuery("SELECT percentwhiteitems FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32();
+ uint32 greeni = CharacterDatabase.PQuery("SELECT percentgreenitems FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32();
+ uint32 bluei = CharacterDatabase.PQuery("SELECT percentblueitems FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32();
+ uint32 purplei = CharacterDatabase.PQuery("SELECT percentpurpleitems FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32();
+ uint32 orangei = CharacterDatabase.PQuery("SELECT percentorangeitems FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32();
+ uint32 yellowi = CharacterDatabase.PQuery("SELECT percentyellowitems FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32();
+ config->SetPercentages(greytg, whitetg, greentg, bluetg, purpletg, orangetg, yellowtg, greyi, whitei, greeni, bluei, purplei, orangei, yellowi);
+ //load min and max prices
+ config->SetMinPrice(AHB_GREY, CharacterDatabase.PQuery("SELECT minpricegrey FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxPrice(AHB_GREY, CharacterDatabase.PQuery("SELECT maxpricegrey FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMinPrice(AHB_WHITE, CharacterDatabase.PQuery("SELECT minpricewhite FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxPrice(AHB_WHITE, CharacterDatabase.PQuery("SELECT maxpricewhite FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMinPrice(AHB_GREEN, CharacterDatabase.PQuery("SELECT minpricegreen FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxPrice(AHB_GREEN, CharacterDatabase.PQuery("SELECT maxpricegreen FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMinPrice(AHB_BLUE, CharacterDatabase.PQuery("SELECT minpriceblue FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxPrice(AHB_BLUE, CharacterDatabase.PQuery("SELECT maxpriceblue FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMinPrice(AHB_PURPLE, CharacterDatabase.PQuery("SELECT minpricepurple FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxPrice(AHB_PURPLE, CharacterDatabase.PQuery("SELECT maxpricepurple FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMinPrice(AHB_ORANGE, CharacterDatabase.PQuery("SELECT minpriceorange FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxPrice(AHB_ORANGE, CharacterDatabase.PQuery("SELECT maxpriceorange FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMinPrice(AHB_YELLOW, CharacterDatabase.PQuery("SELECT minpriceyellow FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxPrice(AHB_YELLOW, CharacterDatabase.PQuery("SELECT maxpriceyellow FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ //load min and max bid prices
+ config->SetMinBidPrice(AHB_GREY, CharacterDatabase.PQuery("SELECT minbidpricegrey FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxBidPrice(AHB_GREY, CharacterDatabase.PQuery("SELECT maxbidpricegrey FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMinBidPrice(AHB_WHITE, CharacterDatabase.PQuery("SELECT minbidpricewhite FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxBidPrice(AHB_WHITE, CharacterDatabase.PQuery("SELECT maxbidpricewhite FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMinBidPrice(AHB_GREEN, CharacterDatabase.PQuery("SELECT minbidpricegreen FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxBidPrice(AHB_GREEN, CharacterDatabase.PQuery("SELECT maxbidpricegreen FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMinBidPrice(AHB_BLUE, CharacterDatabase.PQuery("SELECT minbidpriceblue FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxBidPrice(AHB_BLUE, CharacterDatabase.PQuery("SELECT maxbidpriceblue FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMinBidPrice(AHB_PURPLE, CharacterDatabase.PQuery("SELECT minbidpricepurple FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxBidPrice(AHB_PURPLE, CharacterDatabase.PQuery("SELECT maxbidpricepurple FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMinBidPrice(AHB_ORANGE, CharacterDatabase.PQuery("SELECT minbidpriceorange FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxBidPrice(AHB_ORANGE, CharacterDatabase.PQuery("SELECT maxbidpriceorange FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMinBidPrice(AHB_YELLOW, CharacterDatabase.PQuery("SELECT minbidpriceyellow FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxBidPrice(AHB_YELLOW, CharacterDatabase.PQuery("SELECT maxbidpriceyellow FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ //load max stacks
+ config->SetMaxStack(AHB_GREY, CharacterDatabase.PQuery("SELECT maxstackgrey FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxStack(AHB_WHITE, CharacterDatabase.PQuery("SELECT maxstackwhite FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxStack(AHB_GREEN, CharacterDatabase.PQuery("SELECT maxstackgreen FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxStack(AHB_BLUE, CharacterDatabase.PQuery("SELECT maxstackblue FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxStack(AHB_PURPLE, CharacterDatabase.PQuery("SELECT maxstackpurple FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxStack(AHB_ORANGE, CharacterDatabase.PQuery("SELECT maxstackorange FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetMaxStack(AHB_YELLOW, CharacterDatabase.PQuery("SELECT maxstackyellow FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ if (debug_Out)
+ {
+ sLog.outString("minItems = %u", config->GetMinItems());
+ sLog.outString("maxItems = %u", config->GetMaxItems());
+ sLog.outString("percentGreyTradeGoods = %u", config->GetPercentages(AHB_GREY_TG));
+ sLog.outString("percentWhiteTradeGoods = %u", config->GetPercentages(AHB_WHITE_TG));
+ sLog.outString("percentGreenTradeGoods = %u", config->GetPercentages(AHB_GREEN_TG));
+ sLog.outString("percentBlueTradeGoods = %u", config->GetPercentages(AHB_BLUE_TG));
+ sLog.outString("percentPurpleTradeGoods = %u", config->GetPercentages(AHB_PURPLE_TG));
+ sLog.outString("percentOrangeTradeGoods = %u", config->GetPercentages(AHB_ORANGE_TG));
+ sLog.outString("percentYellowTradeGoods = %u", config->GetPercentages(AHB_YELLOW_TG));
+ sLog.outString("percentGreyItems = %u", config->GetPercentages(AHB_GREY_I));
+ sLog.outString("percentWhiteItems = %u", config->GetPercentages(AHB_WHITE_I));
+ sLog.outString("percentGreenItems = %u", config->GetPercentages(AHB_GREEN_I));
+ sLog.outString("percentBlueItems = %u", config->GetPercentages(AHB_BLUE_I));
+ sLog.outString("percentPurpleItems = %u", config->GetPercentages(AHB_PURPLE_I));
+ sLog.outString("percentOrangeItems = %u", config->GetPercentages(AHB_ORANGE_I));
+ sLog.outString("percentYellowItems = %u", config->GetPercentages(AHB_YELLOW_I));
+ sLog.outString("minPriceGrey = %u", config->GetMinPrice(AHB_GREY));
+ sLog.outString("maxPriceGrey = %u", config->GetMaxPrice(AHB_GREY));
+ sLog.outString("minPriceWhite = %u", config->GetMinPrice(AHB_WHITE));
+ sLog.outString("maxPriceWhite = %u", config->GetMaxPrice(AHB_WHITE));
+ sLog.outString("minPriceGreen = %u", config->GetMinPrice(AHB_GREEN));
+ sLog.outString("maxPriceGreen = %u", config->GetMaxPrice(AHB_GREEN));
+ sLog.outString("minPriceBlue = %u", config->GetMinPrice(AHB_BLUE));
+ sLog.outString("maxPriceBlue = %u", config->GetMaxPrice(AHB_BLUE));
+ sLog.outString("minPricePurple = %u", config->GetMinPrice(AHB_PURPLE));
+ sLog.outString("maxPricePurple = %u", config->GetMaxPrice(AHB_PURPLE));
+ sLog.outString("minPriceOrange = %u", config->GetMinPrice(AHB_ORANGE));
+ sLog.outString("maxPriceOrange = %u", config->GetMaxPrice(AHB_ORANGE));
+ sLog.outString("minPriceYellow = %u", config->GetMinPrice(AHB_YELLOW));
+ sLog.outString("maxPriceYellow = %u", config->GetMaxPrice(AHB_YELLOW));
+ sLog.outString("minBidPriceGrey = %u", config->GetMinBidPrice(AHB_GREY));
+ sLog.outString("maxBidPriceGrey = %u", config->GetMaxBidPrice(AHB_GREY));
+ sLog.outString("minBidPriceWhite = %u", config->GetMinBidPrice(AHB_WHITE));
+ sLog.outString("maxBidPriceWhite = %u", config->GetMaxBidPrice(AHB_WHITE));
+ sLog.outString("minBidPriceGreen = %u", config->GetMinBidPrice(AHB_GREEN));
+ sLog.outString("maxBidPriceGreen = %u", config->GetMaxBidPrice(AHB_GREEN));
+ sLog.outString("minBidPriceBlue = %u", config->GetMinBidPrice(AHB_BLUE));
+ sLog.outString("maxBidPriceBlue = %u", config->GetMinBidPrice(AHB_BLUE));
+ sLog.outString("minBidPricePurple = %u", config->GetMinBidPrice(AHB_PURPLE));
+ sLog.outString("maxBidPricePurple = %u", config->GetMaxBidPrice(AHB_PURPLE));
+ sLog.outString("minBidPriceOrange = %u", config->GetMinBidPrice(AHB_ORANGE));
+ sLog.outString("maxBidPriceOrange = %u", config->GetMaxBidPrice(AHB_ORANGE));
+ sLog.outString("minBidPriceYellow = %u", config->GetMinBidPrice(AHB_YELLOW));
+ sLog.outString("maxBidPriceYellow = %u", config->GetMaxBidPrice(AHB_YELLOW));
+ sLog.outString("maxStackGrey = %u", config->GetMaxStack(AHB_GREY));
+ sLog.outString("maxStackWhite = %u", config->GetMaxStack(AHB_WHITE));
+ sLog.outString("maxStackGreen = %u", config->GetMaxStack(AHB_GREEN));
+ sLog.outString("maxStackBlue = %u", config->GetMaxStack(AHB_BLUE));
+ sLog.outString("maxStackPurple = %u", config->GetMaxStack(AHB_PURPLE));
+ sLog.outString("maxStackOrange = %u", config->GetMaxStack(AHB_ORANGE));
+ sLog.outString("maxStackYellow = %u", config->GetMaxStack(AHB_YELLOW));
+ }
+ //AuctionHouseEntry const* ahEntry = auctionmgr.GetAuctionHouseEntry(config->GetAHFID());
+ AuctionHouseObject* auctionHouse = auctionmgr.GetAuctionsMap(config->GetAHFID());
+
+ config->ResetItemCounts();
+ uint32 auctions = auctionHouse->Getcount();
+
+ if (auctions)
+ {
+ for (AuctionHouseObject::AuctionEntryMap::const_iterator itr = auctionHouse->GetAuctionsBegin(); itr != auctionHouse->GetAuctionsEnd(); ++itr)
+ {
+ AuctionEntry *Aentry = itr->second;
+ Item *item = auctionmgr.GetAItem(Aentry->item_guidlow);
+ if (item)
+ {
+ ItemPrototype const *prototype = item->GetProto();
+ if (prototype)
+ {
+ switch (prototype->Quality)
+ {
+ case 0:
+ if (prototype->Class == ITEM_CLASS_TRADE_GOODS)
+ config->IncItemCounts(AHB_GREY_TG);
+ else
+ config->IncItemCounts(AHB_GREY_I);
+ break;
+ case 1:
+ if (prototype->Class == ITEM_CLASS_TRADE_GOODS)
+ config->IncItemCounts(AHB_WHITE_TG);
+ else
+ config->IncItemCounts(AHB_WHITE_I);
+ break;
+ case 2:
+ if (prototype->Class == ITEM_CLASS_TRADE_GOODS)
+ config->IncItemCounts(AHB_GREEN_TG);
+ else
+ config->IncItemCounts(AHB_GREEN_I);
+ break;
+ case 3:
+ if (prototype->Class == ITEM_CLASS_TRADE_GOODS)
+ config->IncItemCounts(AHB_BLUE_TG);
+ else
+ config->IncItemCounts(AHB_BLUE_I);
+ break;
+ case 4:
+ if (prototype->Class == ITEM_CLASS_TRADE_GOODS)
+ config->IncItemCounts(AHB_PURPLE_TG);
+ else
+ config->IncItemCounts(AHB_PURPLE_I);
+ break;
+ case 5:
+ if (prototype->Class == ITEM_CLASS_TRADE_GOODS)
+ config->IncItemCounts(AHB_ORANGE_TG);
+ else
+ config->IncItemCounts(AHB_ORANGE_I);
+ break;
+ case 6:
+ if (prototype->Class == ITEM_CLASS_TRADE_GOODS)
+ config->IncItemCounts(AHB_YELLOW_TG);
+ else
+ config->IncItemCounts(AHB_YELLOW_I);
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (debug_Out)
+ {
+ sLog.outString("Current Items in %s Auctionhouses:", CharacterDatabase.PQuery("SELECT name FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetString());
+ sLog.outString("Grey Trade Goods\t%u\tGrey Items\t%u", config->GetItemCounts(AHB_GREY_TG), config->GetItemCounts(AHB_GREY_I));
+ sLog.outString("White Trade Goods\t%u\tWhite Items\t%u", config->GetItemCounts(AHB_WHITE_TG), config->GetItemCounts(AHB_WHITE_I));
+ sLog.outString("Green Trade Goods\t%u\tGreen Items\t%u", config->GetItemCounts(AHB_GREEN_TG), config->GetItemCounts(AHB_GREEN_I));
+ sLog.outString("Blue Trade Goods\t%u\tBlue Items\t%u", config->GetItemCounts(AHB_BLUE_TG), config->GetItemCounts(AHB_BLUE_I));
+ sLog.outString("Purple Trade Goods\t%u\tPurple Items\t%u", config->GetItemCounts(AHB_PURPLE_TG), config->GetItemCounts(AHB_PURPLE_I));
+ sLog.outString("Orange Trade Goods\t%u\tOrange Items\t%u", config->GetItemCounts(AHB_ORANGE_TG), config->GetItemCounts(AHB_ORANGE_I));
+ sLog.outString("Yellow Trade Goods\t%u\tYellow Items\t%u", config->GetItemCounts(AHB_YELLOW_TG), config->GetItemCounts(AHB_YELLOW_I));
+ }
+ }
+ if (AHBBuyer)
+ {
+ //load buyer bid prices
+ config->SetBuyerPrice(AHB_GREY, CharacterDatabase.PQuery("SELECT buyerpricegrey FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetBuyerPrice(AHB_WHITE, CharacterDatabase.PQuery("SELECT buyerpricewhite FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetBuyerPrice(AHB_GREEN, CharacterDatabase.PQuery("SELECT buyerpricegreen FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetBuyerPrice(AHB_BLUE, CharacterDatabase.PQuery("SELECT buyerpriceblue FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetBuyerPrice(AHB_PURPLE, CharacterDatabase.PQuery("SELECT buyerpricepurple FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetBuyerPrice(AHB_ORANGE, CharacterDatabase.PQuery("SELECT buyerpriceorange FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ config->SetBuyerPrice(AHB_YELLOW, CharacterDatabase.PQuery("SELECT buyerpriceyellow FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ //load bidding interval
+ config->SetBiddingInterval(CharacterDatabase.PQuery("SELECT buyerbiddinginterval FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ //load bids per interval
+ config->SetBidsPerInterval(CharacterDatabase.PQuery("SELECT buyerbidsperinterval FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetUInt32());
+ if (debug_Out)
+ {
+ sLog.outString("buyerPriceGrey = %u", config->GetBuyerPrice(AHB_GREY));
+ sLog.outString("buyerPriceWhite = %u", config->GetBuyerPrice(AHB_WHITE));
+ sLog.outString("buyerPriceGreen = %u", config->GetBuyerPrice(AHB_GREEN));
+ sLog.outString("buyerPriceBlue = %u", config->GetBuyerPrice(AHB_BLUE));
+ sLog.outString("buyerPricePurple = %u", config->GetBuyerPrice(AHB_PURPLE));
+ sLog.outString("buyerPriceOrange = %u", config->GetBuyerPrice(AHB_ORANGE));
+ sLog.outString("buyerPriceYellow = %u", config->GetBuyerPrice(AHB_YELLOW));
+ sLog.outString("buyerBiddingInterval = %u", config->GetBiddingInterval());
+ sLog.outString("buyerBidsPerInterval = %u", config->GetBidsPerInterval());
+ }
+ }
+ if (debug_Out) sLog.outString("End Settings for %s Auctionhouses:", CharacterDatabase.PQuery("SELECT name FROM auctionhousebot WHERE auctionhouse = %u",config->GetAHID())->Fetch()->GetString());
+}
diff --git a/src/server/game/AI/AuctionHouseBot/AuctionHouseBot.h b/src/server/game/AI/AuctionHouseBot/AuctionHouseBot.h
new file mode 100644
index 00000000000..208a09aa0b2
--- /dev/null
+++ b/src/server/game/AI/AuctionHouseBot/AuctionHouseBot.h
@@ -0,0 +1,1225 @@
+#ifndef AUCTION_HOUSE_BOT_H
+#define AUCTION_HOUSE_BOT_H
+
+#include "World.h"
+#include "Config/ConfigEnv.h"
+#include "ItemPrototype.h"
+
+#define AHB_GREY 0
+#define AHB_WHITE 1
+#define AHB_GREEN 2
+#define AHB_BLUE 3
+#define AHB_PURPLE 4
+#define AHB_ORANGE 5
+#define AHB_YELLOW 6
+#define AHB_MAX_QUALITY 6
+#define AHB_GREY_TG 0
+#define AHB_WHITE_TG 1
+#define AHB_GREEN_TG 2
+#define AHB_BLUE_TG 3
+#define AHB_PURPLE_TG 4
+#define AHB_ORANGE_TG 5
+#define AHB_YELLOW_TG 6
+#define AHB_GREY_I 7
+#define AHB_WHITE_I 8
+#define AHB_GREEN_I 9
+#define AHB_BLUE_I 10
+#define AHB_PURPLE_I 11
+#define AHB_ORANGE_I 12
+#define AHB_YELLOW_I 13
+
+class AHBConfig
+{
+private:
+ uint32 AHID;
+ uint32 AHFID;
+ uint32 minItems;
+ uint32 maxItems;
+ uint32 percentGreyTradeGoods;
+ uint32 percentWhiteTradeGoods;
+ uint32 percentGreenTradeGoods;
+ uint32 percentBlueTradeGoods;
+ uint32 percentPurpleTradeGoods;
+ uint32 percentOrangeTradeGoods;
+ uint32 percentYellowTradeGoods;
+ uint32 percentGreyItems;
+ uint32 percentWhiteItems;
+ uint32 percentGreenItems;
+ uint32 percentBlueItems;
+ uint32 percentPurpleItems;
+ uint32 percentOrangeItems;
+ uint32 percentYellowItems;
+ uint32 minPriceGrey;
+ uint32 maxPriceGrey;
+ uint32 minBidPriceGrey;
+ uint32 maxBidPriceGrey;
+ uint32 maxStackGrey;
+ uint32 minPriceWhite;
+ uint32 maxPriceWhite;
+ uint32 minBidPriceWhite;
+ uint32 maxBidPriceWhite;
+ uint32 maxStackWhite;
+ uint32 minPriceGreen;
+ uint32 maxPriceGreen;
+ uint32 minBidPriceGreen;
+ uint32 maxBidPriceGreen;
+ uint32 maxStackGreen;
+ uint32 minPriceBlue;
+ uint32 maxPriceBlue;
+ uint32 minBidPriceBlue;
+ uint32 maxBidPriceBlue;
+ uint32 maxStackBlue;
+ uint32 minPricePurple;
+ uint32 maxPricePurple;
+ uint32 minBidPricePurple;
+ uint32 maxBidPricePurple;
+ uint32 maxStackPurple;
+ uint32 minPriceOrange;
+ uint32 maxPriceOrange;
+ uint32 minBidPriceOrange;
+ uint32 maxBidPriceOrange;
+ uint32 maxStackOrange;
+ uint32 minPriceYellow;
+ uint32 maxPriceYellow;
+ uint32 minBidPriceYellow;
+ uint32 maxBidPriceYellow;
+ uint32 maxStackYellow;
+
+ uint32 buyerPriceGrey;
+ uint32 buyerPriceWhite;
+ uint32 buyerPriceGreen;
+ uint32 buyerPriceBlue;
+ uint32 buyerPricePurple;
+ uint32 buyerPriceOrange;
+ uint32 buyerPriceYellow;
+ uint32 buyerBiddingInterval;
+ uint32 buyerBidsPerInterval;
+
+ uint32 greytgp;
+ uint32 whitetgp;
+ uint32 greentgp;
+ uint32 bluetgp;
+ uint32 purpletgp;
+ uint32 orangetgp;
+ uint32 yellowtgp;
+ uint32 greyip;
+ uint32 whiteip;
+ uint32 greenip;
+ uint32 blueip;
+ uint32 purpleip;
+ uint32 orangeip;
+ uint32 yellowip;
+
+ uint32 greyTGoods;
+ uint32 whiteTGoods;
+ uint32 greenTGoods;
+ uint32 blueTGoods;
+ uint32 purpleTGoods;
+ uint32 orangeTGoods;
+ uint32 yellowTGoods;
+
+ uint32 greyItems;
+ uint32 whiteItems;
+ uint32 greenItems;
+ uint32 blueItems;
+ uint32 purpleItems;
+ uint32 orangeItems;
+ uint32 yellowItems;
+
+public:
+ AHBConfig(uint32 ahid)
+ {
+ AHID = ahid;
+ switch(ahid)
+ {
+ case 2:
+ AHFID = 55;
+ break;
+ case 6:
+ AHFID = 29;
+ break;
+ case 7:
+ AHFID = 120;
+ break;
+ default:
+ AHFID = 120;
+ break;
+ }
+ }
+ AHBConfig()
+ {
+ }
+ uint32 GetAHID()
+ {
+ return AHID;
+ }
+ uint32 GetAHFID()
+ {
+ return AHFID;
+ }
+ void SetMinItems(uint32 value)
+ {
+ minItems = value;
+ }
+ uint32 GetMinItems()
+ {
+ if ((minItems == 0) && (maxItems))
+ return maxItems;
+ else if ((maxItems) && (minItems > maxItems))
+ return maxItems;
+ else
+ return minItems;
+ }
+ void SetMaxItems(uint32 value)
+ {
+ maxItems = value;
+ CalculatePercents();
+ }
+ uint32 GetMaxItems()
+ {
+ return maxItems;
+ }
+ void SetPercentages(uint32 greytg, uint32 whitetg, uint32 greentg, uint32 bluetg, uint32 purpletg, uint32 orangetg, uint32 yellowtg, uint32 greyi, uint32 whitei, uint32 greeni, uint32 bluei, uint32 purplei, uint32 orangei, uint32 yellowi)
+ {
+ uint32 totalPercent = greytg + whitetg + greentg + bluetg + purpletg + orangetg + yellowtg + greyi + whitei + greeni + bluei + purplei + orangei + yellowi;
+
+ if (totalPercent == 0)
+ {
+ maxItems = 0;
+ }
+ else if (totalPercent != 100)
+ {
+ greytg = 0;
+ whitetg = 27;
+ greentg = 12;
+ bluetg = 10;
+ purpletg = 1;
+ orangetg = 0;
+ yellowtg = 0;
+ greyi = 0;
+ whitei = 10;
+ greeni = 30;
+ bluei = 8;
+ purplei = 2;
+ orangei = 0;
+ yellowi = 0;
+ }
+ percentGreyTradeGoods = greytg;
+ percentWhiteTradeGoods = whitetg;
+ percentGreenTradeGoods = greentg;
+ percentBlueTradeGoods = bluetg;
+ percentPurpleTradeGoods = purpletg;
+ percentOrangeTradeGoods = orangetg;
+ percentYellowTradeGoods = yellowtg;
+ percentGreyItems = greyi;
+ percentWhiteItems = whitei;
+ percentGreenItems = greeni;
+ percentBlueItems = bluei;
+ percentPurpleItems = purplei;
+ percentOrangeItems = orangei;
+ percentYellowItems = yellowi;
+ CalculatePercents();
+ }
+ uint32 GetPercentages(uint32 color)
+ {
+ switch(color)
+ {
+ case AHB_GREY_TG:
+ return percentGreyTradeGoods;
+ break;
+ case AHB_WHITE_TG:
+ return percentWhiteTradeGoods;
+ break;
+ case AHB_GREEN_TG:
+ return percentGreenTradeGoods;
+ break;
+ case AHB_BLUE_TG:
+ return percentBlueTradeGoods;
+ break;
+ case AHB_PURPLE_TG:
+ return percentPurpleTradeGoods;
+ break;
+ case AHB_ORANGE_TG:
+ return percentOrangeTradeGoods;
+ break;
+ case AHB_YELLOW_TG:
+ return percentYellowTradeGoods;
+ break;
+ case AHB_GREY_I:
+ return percentGreyItems;
+ break;
+ case AHB_WHITE_I:
+ return percentWhiteItems;
+ break;
+ case AHB_GREEN_I:
+ return percentGreenItems;
+ break;
+ case AHB_BLUE_I:
+ return percentBlueItems;
+ break;
+ case AHB_PURPLE_I:
+ return percentPurpleItems;
+ break;
+ case AHB_ORANGE_I:
+ return percentOrangeItems;
+ break;
+ case AHB_YELLOW_I:
+ return percentYellowItems;
+ break;
+ default:
+ return 0;
+ break;
+ }
+ }
+ void SetMinPrice(uint32 color, uint32 value)
+ {
+ switch(color)
+ {
+ case AHB_GREY:
+ minPriceGrey = value;
+ break;
+ case AHB_WHITE:
+ minPriceWhite = value;
+ break;
+ case AHB_GREEN:
+ minPriceGreen = value;
+ break;
+ case AHB_BLUE:
+ minPriceBlue = value;
+ break;
+ case AHB_PURPLE:
+ minPricePurple = value;
+ break;
+ case AHB_ORANGE:
+ minPriceOrange = value;
+ break;
+ case AHB_YELLOW:
+ minPriceYellow = value;
+ break;
+ default:
+ break;
+ }
+ }
+ uint32 GetMinPrice(uint32 color)
+ {
+ switch(color)
+ {
+ case AHB_GREY:
+ {
+ if (minPriceGrey == 0)
+ return 100;
+ else if (minPriceGrey > maxPriceGrey)
+ return maxPriceGrey;
+ else
+ return minPriceGrey;
+ break;
+ }
+ case AHB_WHITE:
+ {
+ if (minPriceWhite == 0)
+ return 150;
+ else if (minPriceWhite > maxPriceWhite)
+ return maxPriceWhite;
+ else
+ return minPriceWhite;
+ break;
+ }
+ case AHB_GREEN:
+ {
+ if (minPriceGreen == 0)
+ return 200;
+ else if (minPriceGreen > maxPriceGreen)
+ return maxPriceGreen;
+ else
+ return minPriceGreen;
+ break;
+ }
+ case AHB_BLUE:
+ {
+ if (minPriceBlue == 0)
+ return 250;
+ else if (minPriceBlue > maxPriceBlue)
+ return maxPriceBlue;
+ else
+ return minPriceBlue;
+ break;
+ }
+ case AHB_PURPLE:
+ {
+ if (minPricePurple == 0)
+ return 300;
+ else if (minPricePurple > maxPricePurple)
+ return maxPricePurple;
+ else
+ return minPricePurple;
+ break;
+ }
+ case AHB_ORANGE:
+ {
+ if (minPriceOrange == 0)
+ return 400;
+ else if (minPriceOrange > maxPriceOrange)
+ return maxPriceOrange;
+ else
+ return minPriceOrange;
+ break;
+ }
+ case AHB_YELLOW:
+ {
+ if (minPriceYellow == 0)
+ return 500;
+ else if (minPriceYellow > maxPriceYellow)
+ return maxPriceYellow;
+ else
+ return minPriceYellow;
+ break;
+ }
+ default:
+ {
+ return 0;
+ break;
+ }
+ }
+ }
+ void SetMaxPrice(uint32 color, uint32 value)
+ {
+ switch(color)
+ {
+ case AHB_GREY:
+ maxPriceGrey = value;
+ break;
+ case AHB_WHITE:
+ maxPriceWhite = value;
+ break;
+ case AHB_GREEN:
+ maxPriceGreen = value;
+ break;
+ case AHB_BLUE:
+ maxPriceBlue = value;
+ break;
+ case AHB_PURPLE:
+ maxPricePurple = value;
+ break;
+ case AHB_ORANGE:
+ maxPriceOrange = value;
+ break;
+ case AHB_YELLOW:
+ maxPriceYellow = value;
+ break;
+ default:
+ break;
+ }
+ }
+ uint32 GetMaxPrice(uint32 color)
+ {
+ switch(color)
+ {
+ case AHB_GREY:
+ {
+ if (maxPriceGrey == 0)
+ return 150;
+ else
+ return maxPriceGrey;
+ break;
+ }
+ case AHB_WHITE:
+ {
+ if (maxPriceWhite == 0)
+ return 250;
+ else
+ return maxPriceWhite;
+ break;
+ }
+ case AHB_GREEN:
+ {
+ if (maxPriceGreen == 0)
+ return 300;
+ else
+ return maxPriceGreen;
+ break;
+ }
+ case AHB_BLUE:
+ {
+ if (maxPriceBlue == 0)
+ return 350;
+ else
+ return maxPriceBlue;
+ break;
+ }
+ case AHB_PURPLE:
+ {
+ if (maxPricePurple == 0)
+ return 450;
+ else
+ return maxPricePurple;
+ break;
+ }
+ case AHB_ORANGE:
+ {
+ if (maxPriceOrange == 0)
+ return 550;
+ else
+ return maxPriceOrange;
+ break;
+ }
+ case AHB_YELLOW:
+ {
+ if (maxPriceYellow == 0)
+ return 650;
+ else
+ return maxPriceYellow;
+ break;
+ }
+ default:
+ {
+ return 0;
+ break;
+ }
+ }
+ }
+ void SetMinBidPrice(uint32 color, uint32 value)
+ {
+ switch(color)
+ {
+ case AHB_GREY:
+ minBidPriceGrey = value;
+ break;
+ case AHB_WHITE:
+ minBidPriceWhite = value;
+ break;
+ case AHB_GREEN:
+ minBidPriceGreen = value;
+ break;
+ case AHB_BLUE:
+ minBidPriceBlue = value;
+ break;
+ case AHB_PURPLE:
+ minBidPricePurple = value;
+ break;
+ case AHB_ORANGE:
+ minBidPriceOrange = value;
+ break;
+ case AHB_YELLOW:
+ minBidPriceYellow = value;
+ break;
+ default:
+ break;
+ }
+ }
+ uint32 GetMinBidPrice(uint32 color)
+ {
+ switch(color)
+ {
+ case AHB_GREY:
+ {
+ if (minBidPriceGrey > 100)
+ return 100;
+ else
+ return minBidPriceGrey;
+ break;
+ }
+ case AHB_WHITE:
+ {
+ if (minBidPriceWhite > 100)
+ return 100;
+ else
+ return minBidPriceWhite;
+ break;
+ }
+ case AHB_GREEN:
+ {
+ if (minBidPriceGreen > 100)
+ return 100;
+ else
+ return minBidPriceGreen;
+ break;
+ }
+ case AHB_BLUE:
+ {
+ if (minBidPriceBlue > 100)
+ return 100;
+ else
+ return minBidPriceBlue;
+ break;
+ }
+ case AHB_PURPLE:
+ {
+ if (minBidPricePurple > 100)
+ return 100;
+ else
+ return minBidPricePurple;
+ break;
+ }
+ case AHB_ORANGE:
+ {
+ if (minBidPriceOrange > 100)
+ return 100;
+ else
+ return minBidPriceOrange;
+ break;
+ }
+ case AHB_YELLOW:
+ {
+ if (minBidPriceYellow > 100)
+ return 100;
+ else
+ return minBidPriceYellow;
+ break;
+ }
+ default:
+ {
+ return 0;
+ break;
+ }
+ }
+ }
+ void SetMaxBidPrice(uint32 color, uint32 value)
+ {
+ switch(color)
+ {
+ case AHB_GREY:
+ maxBidPriceGrey = value;
+ break;
+ case AHB_WHITE:
+ maxBidPriceWhite = value;
+ break;
+ case AHB_GREEN:
+ maxBidPriceGreen = value;
+ break;
+ case AHB_BLUE:
+ maxBidPriceBlue = value;
+ break;
+ case AHB_PURPLE:
+ maxBidPricePurple = value;
+ break;
+ case AHB_ORANGE:
+ maxBidPriceOrange = value;
+ break;
+ case AHB_YELLOW:
+ maxBidPriceYellow = value;
+ break;
+ default:
+ break;
+ }
+ }
+ uint32 GetMaxBidPrice(uint32 color)
+ {
+ switch(color)
+ {
+ case AHB_GREY:
+ {
+ if (maxBidPriceGrey > 100)
+ return 100;
+ else
+ return maxBidPriceGrey;
+ break;
+ }
+ case AHB_WHITE:
+ {
+ if (maxBidPriceWhite > 100)
+ return 100;
+ else
+ return maxBidPriceWhite;
+ break;
+ }
+ case AHB_GREEN:
+ {
+ if (maxBidPriceGreen > 100)
+ return 100;
+ else
+ return maxBidPriceGreen;
+ break;
+ }
+ case AHB_BLUE:
+ {
+ if (maxBidPriceBlue > 100)
+ return 100;
+ else
+ return maxBidPriceBlue;
+ break;
+ }
+ case AHB_PURPLE:
+ {
+ if (maxBidPricePurple > 100)
+ return 100;
+ else
+ return maxBidPricePurple;
+ break;
+ }
+ case AHB_ORANGE:
+ {
+ if (maxBidPriceOrange > 100)
+ return 100;
+ else
+ return maxBidPriceOrange;
+ break;
+ }
+ case AHB_YELLOW:
+ {
+ if (maxBidPriceYellow > 100)
+ return 100;
+ else
+ return maxBidPriceYellow;
+ break;
+ }
+ default:
+ {
+ return 0;
+ break;
+ }
+ }
+ }
+ void SetMaxStack(uint32 color, uint32 value)
+ {
+ switch(color)
+ {
+ case AHB_GREY:
+ maxStackGrey = value;
+ break;
+ case AHB_WHITE:
+ maxStackWhite = value;
+ break;
+ case AHB_GREEN:
+ maxStackGreen = value;
+ break;
+ case AHB_BLUE:
+ maxStackBlue = value;
+ break;
+ case AHB_PURPLE:
+ maxStackPurple = value;
+ break;
+ case AHB_ORANGE:
+ maxStackOrange = value;
+ break;
+ case AHB_YELLOW:
+ maxStackYellow = value;
+ break;
+ default:
+ break;
+ }
+ }
+ uint32 GetMaxStack(uint32 color)
+ {
+ switch(color)
+ {
+ case AHB_GREY:
+ {
+ return maxStackGrey;
+ break;
+ }
+ case AHB_WHITE:
+ {
+ return maxStackWhite;
+ break;
+ }
+ case AHB_GREEN:
+ {
+ return maxStackGreen;
+ break;
+ }
+ case AHB_BLUE:
+ {
+ return maxStackBlue;
+ break;
+ }
+ case AHB_PURPLE:
+ {
+ return maxStackPurple;
+ break;
+ }
+ case AHB_ORANGE:
+ {
+ return maxStackOrange;
+ break;
+ }
+ case AHB_YELLOW:
+ {
+ return maxStackYellow;
+ break;
+ }
+ default:
+ {
+ return 0;
+ break;
+ }
+ }
+ }
+ void SetBuyerPrice(uint32 color, uint32 value)
+ {
+ switch(color)
+ {
+ case AHB_GREY:
+ buyerPriceGrey = value;
+ break;
+ case AHB_WHITE:
+ buyerPriceWhite = value;
+ break;
+ case AHB_GREEN:
+ buyerPriceGreen = value;
+ break;
+ case AHB_BLUE:
+ buyerPriceBlue = value;
+ break;
+ case AHB_PURPLE:
+ buyerPricePurple = value;
+ break;
+ case AHB_ORANGE:
+ buyerPriceOrange = value;
+ break;
+ case AHB_YELLOW:
+ buyerPriceYellow = value;
+ break;
+ default:
+ break;
+ }
+ }
+ uint32 GetBuyerPrice(uint32 color)
+ {
+ switch(color)
+ {
+ case AHB_GREY:
+ return buyerPriceGrey;
+ break;
+ case AHB_WHITE:
+ return buyerPriceWhite;
+ break;
+ case AHB_GREEN:
+ return buyerPriceGreen;
+ break;
+ case AHB_BLUE:
+ return buyerPriceBlue;
+ break;
+ case AHB_PURPLE:
+ return buyerPricePurple;
+ break;
+ case AHB_ORANGE:
+ return buyerPriceOrange;
+ break;
+ case AHB_YELLOW:
+ return buyerPriceYellow;
+ break;
+ default:
+ return 0;
+ break;
+ }
+ }
+ void SetBiddingInterval(uint32 value)
+ {
+ buyerBiddingInterval = value;
+ }
+ uint32 GetBiddingInterval()
+ {
+ return buyerBiddingInterval;
+ }
+ void CalculatePercents()
+ {
+ greytgp = (uint32) (((double)percentGreyTradeGoods / 100.0) * maxItems);
+ whitetgp = (uint32) (((double)percentWhiteTradeGoods / 100.0) * maxItems);
+ greentgp = (uint32) (((double)percentGreenTradeGoods / 100.0) * maxItems);
+ bluetgp = (uint32) (((double)percentBlueTradeGoods / 100.0) * maxItems);
+ purpletgp = (uint32) (((double)percentPurpleTradeGoods / 100.0) * maxItems);
+ orangetgp = (uint32) (((double)percentOrangeTradeGoods / 100.0) * maxItems);
+ yellowtgp = (uint32) (((double)percentYellowTradeGoods / 100.0) * maxItems);
+ greyip = (uint32) (((double)percentGreyItems / 100.0) * maxItems);
+ whiteip = (uint32) (((double)percentWhiteItems / 100.0) * maxItems);
+ greenip = (uint32) (((double)percentGreenItems / 100.0) * maxItems);
+ blueip = (uint32) (((double)percentBlueItems / 100.0) * maxItems);
+ purpleip = (uint32) (((double)percentPurpleItems / 100.0) * maxItems);
+ orangeip = (uint32) (((double)percentOrangeItems / 100.0) * maxItems);
+ yellowip = (uint32) (((double)percentYellowItems / 100.0) * maxItems);
+ uint32 total = greytgp + whitetgp + greentgp + bluetgp + purpletgp + orangetgp + yellowtgp + greyip + whiteip + greenip + blueip + purpleip + orangeip + yellowip;
+ int32 diff = (maxItems - total);
+ if (diff < 0)
+ {
+ if ((whiteip - diff) > 0)
+ whiteip -= diff;
+ else if ((greenip - diff) > 0)
+ greenip -= diff;
+ }
+ else if (diff < 0)
+ {
+ whiteip += diff;
+ }
+ }
+ uint32 GetPercents(uint32 color)
+ {
+ switch(color)
+ {
+ case AHB_GREY_TG:
+ return greytgp;
+ break;
+ case AHB_WHITE_TG:
+ return whitetgp;
+ break;
+ case AHB_GREEN_TG:
+ return greentgp;
+ break;
+ case AHB_BLUE_TG:
+ return bluetgp;
+ break;
+ case AHB_PURPLE_TG:
+ return purpletgp;
+ break;
+ case AHB_ORANGE_TG:
+ return orangetgp;
+ break;
+ case AHB_YELLOW_TG:
+ return yellowtgp;
+ break;
+ case AHB_GREY_I:
+ return greyip;
+ break;
+ case AHB_WHITE_I:
+ return whiteip;
+ break;
+ case AHB_GREEN_I:
+ return greenip;
+ break;
+ case AHB_BLUE_I:
+ return blueip;
+ break;
+ case AHB_PURPLE_I:
+ return purpleip;
+ break;
+ case AHB_ORANGE_I:
+ return orangeip;
+ break;
+ case AHB_YELLOW_I:
+ return yellowip;
+ break;
+ default:
+ return 0;
+ break;
+ }
+ }
+
+ void DecItemCounts(uint32 Class, uint32 Quality)
+ {
+ switch(Class)
+ {
+ case ITEM_CLASS_TRADE_GOODS:
+ DecItemCounts(Quality);
+ break;
+ default:
+ DecItemCounts(Quality + 7);
+ break;
+ }
+ }
+
+ void DecItemCounts(uint32 color)
+ {
+ switch(color)
+ {
+ case AHB_GREY_TG:
+ --greyTGoods;
+ break;
+ case AHB_WHITE_TG:
+ --whiteTGoods;
+ break;
+ case AHB_GREEN_TG:
+ --greenTGoods;
+ break;
+ case AHB_BLUE_TG:
+ --blueTGoods;
+ break;
+ case AHB_PURPLE_TG:
+ --purpleTGoods;
+ break;
+ case AHB_ORANGE_TG:
+ --orangeTGoods;
+ break;
+ case AHB_YELLOW_TG:
+ --yellowTGoods;
+ break;
+ case AHB_GREY_I:
+ --greyItems;
+ break;
+ case AHB_WHITE_I:
+ --whiteItems;
+ break;
+ case AHB_GREEN_I:
+ --greenItems;
+ break;
+ case AHB_BLUE_I:
+ --blueItems;
+ break;
+ case AHB_PURPLE_I:
+ --purpleItems;
+ break;
+ case AHB_ORANGE_I:
+ --orangeItems;
+ break;
+ case AHB_YELLOW_I:
+ --yellowItems;
+ break;
+ default:
+ break;
+ }
+ }
+
+ void IncItemCounts(uint32 Class, uint32 Quality)
+ {
+ switch(Class)
+ {
+ case ITEM_CLASS_TRADE_GOODS:
+ IncItemCounts(Quality);
+ break;
+ default:
+ IncItemCounts(Quality + 7);
+ break;
+ }
+ }
+
+ void IncItemCounts(uint32 color)
+ {
+ switch(color)
+ {
+ case AHB_GREY_TG:
+ ++greyTGoods;
+ break;
+ case AHB_WHITE_TG:
+ ++whiteTGoods;
+ break;
+ case AHB_GREEN_TG:
+ ++greenTGoods;
+ break;
+ case AHB_BLUE_TG:
+ ++blueTGoods;
+ break;
+ case AHB_PURPLE_TG:
+ ++purpleTGoods;
+ break;
+ case AHB_ORANGE_TG:
+ ++orangeTGoods;
+ break;
+ case AHB_YELLOW_TG:
+ ++yellowTGoods;
+ break;
+ case AHB_GREY_I:
+ ++greyItems;
+ break;
+ case AHB_WHITE_I:
+ ++whiteItems;
+ break;
+ case AHB_GREEN_I:
+ ++greenItems;
+ break;
+ case AHB_BLUE_I:
+ ++blueItems;
+ break;
+ case AHB_PURPLE_I:
+ ++purpleItems;
+ break;
+ case AHB_ORANGE_I:
+ ++orangeItems;
+ break;
+ case AHB_YELLOW_I:
+ ++yellowItems;
+ break;
+ default:
+ break;
+ }
+ }
+
+ void ResetItemCounts()
+ {
+ greyTGoods = 0;
+ whiteTGoods = 0;
+ greenTGoods = 0;
+ blueTGoods = 0;
+ purpleTGoods = 0;
+ orangeTGoods = 0;
+ yellowTGoods = 0;
+
+ greyItems = 0;
+ whiteItems = 0;
+ greenItems = 0;
+ blueItems = 0;
+ purpleItems = 0;
+ orangeItems = 0;
+ yellowItems = 0;
+ }
+
+ uint32 TotalItemCounts()
+ {
+ return(
+ greyTGoods +
+ whiteTGoods +
+ greenTGoods +
+ blueTGoods +
+ purpleTGoods +
+ orangeTGoods +
+ yellowTGoods +
+
+ greyItems +
+ whiteItems +
+ greenItems +
+ blueItems +
+ purpleItems +
+ orangeItems +
+ yellowItems);
+ }
+
+ uint32 GetItemCounts(uint32 color)
+ {
+ switch(color)
+ {
+ case AHB_GREY_TG:
+ return greyTGoods;
+ break;
+ case AHB_WHITE_TG:
+ return whiteTGoods;
+ break;
+ case AHB_GREEN_TG:
+ return greenTGoods;
+ break;
+ case AHB_BLUE_TG:
+ return blueTGoods;
+ break;
+ case AHB_PURPLE_TG:
+ return purpleTGoods;
+ break;
+ case AHB_ORANGE_TG:
+ return orangeTGoods;
+ break;
+ case AHB_YELLOW_TG:
+ return yellowTGoods;
+ break;
+ case AHB_GREY_I:
+ return greyItems;
+ break;
+ case AHB_WHITE_I:
+ return whiteItems;
+ break;
+ case AHB_GREEN_I:
+ return greenItems;
+ break;
+ case AHB_BLUE_I:
+ return blueItems;
+ break;
+ case AHB_PURPLE_I:
+ return purpleItems;
+ break;
+ case AHB_ORANGE_I:
+ return orangeItems;
+ break;
+ case AHB_YELLOW_I:
+ return yellowItems;
+ break;
+ default:
+ return 0;
+ break;
+ }
+ }
+ void SetBidsPerInterval(uint32 value)
+ {
+ buyerBidsPerInterval = value;
+ }
+ uint32 GetBidsPerInterval()
+ {
+ return buyerBidsPerInterval;
+ }
+ ~AHBConfig()
+ {
+ }
+};
+class AuctionHouseBot
+{
+private:
+
+ bool debug_Out;
+ bool debug_Out_Filters;
+
+ bool AHBSeller;
+ bool AHBBuyer;
+ bool BuyMethod;
+ bool SellMethod;
+
+ uint32 AHBplayerAccount;
+ uint32 AHBplayerGUID;
+ uint32 ItemsPerCycle;
+
+ //Begin Filters
+
+ bool Vendor_Items;
+ bool Loot_Items;
+ bool Other_Items;
+ bool Vendor_TGs;
+ bool Loot_TGs;
+ bool Other_TGs;
+
+ bool No_Bind;
+ bool Bind_When_Picked_Up;
+ bool Bind_When_Equipped;
+ bool Bind_When_Use;
+ bool Bind_Quest_Item;
+
+ bool DisableBeta_PTR_Unused;
+ bool DisablePermEnchant;
+ bool DisableConjured;
+ bool DisableGems;
+ bool DisableMoney;
+ bool DisableMoneyLoot;
+ bool DisableLootable;
+ bool DisableKeys;
+ bool DisableDuration;
+ bool DisableBOP_Or_Quest_NoReqLevel;
+
+ bool DisableWarriorItems;
+ bool DisablePaladinItems;
+ bool DisableHunterItems;
+ bool DisableRogueItems;
+ bool DisablePriestItems;
+ bool DisableDKItems;
+ bool DisableShamanItems;
+ bool DisableMageItems;
+ bool DisableWarlockItems;
+ bool DisableUnusedClassItems;
+ bool DisableDruidItems;
+
+ uint32 DisableItemsBelowLevel;
+ uint32 DisableItemsAboveLevel;
+ uint32 DisableTGsBelowLevel;
+ uint32 DisableTGsAboveLevel;
+ uint32 DisableItemsBelowGUID;
+ uint32 DisableItemsAboveGUID;
+ uint32 DisableTGsBelowGUID;
+ uint32 DisableTGsAboveGUID;
+ uint32 DisableItemsBelowReqLevel;
+ uint32 DisableItemsAboveReqLevel;
+ uint32 DisableTGsBelowReqLevel;
+ uint32 DisableTGsAboveReqLevel;
+ uint32 DisableItemsBelowReqSkillRank;
+ uint32 DisableItemsAboveReqSkillRank;
+ uint32 DisableTGsBelowReqSkillRank;
+ uint32 DisableTGsAboveReqSkillRank;
+
+ //End Filters
+
+ AHBConfig AllianceConfig;
+ AHBConfig HordeConfig;
+ AHBConfig NeutralConfig;
+
+ time_t _lastrun_a;
+ time_t _lastrun_h;
+ time_t _lastrun_n;
+
+ inline uint32 minValue(uint32 a, uint32 b) { return a <= b ? a : b; };
+ void addNewAuctions(Player *AHBplayer, AHBConfig *config);
+ void addNewAuctionBuyerBotBid(Player *AHBplayer, AHBConfig *config, WorldSession *session);
+
+public:
+ AuctionHouseBot();
+ ~AuctionHouseBot();
+ void Update();
+ void Initialize();
+ void LoadValues(AHBConfig*);
+ void DecrementItemCounts(AuctionEntry* ah, uint32 item_template);
+ void IncrementItemCounts(AuctionEntry* ah);
+ void Commands(uint32, uint32, uint32, char*);
+ uint32 GetAHBplayerGUID() { return AHBplayerGUID; };
+};
+
+#define auctionbot Trinity::Singleton<AuctionHouseBot>::Instance()
+
+#endif
diff --git a/src/server/game/AI/CombatAI.cpp b/src/server/game/AI/CombatAI.cpp
new file mode 100644
index 00000000000..0d0ff17ffd7
--- /dev/null
+++ b/src/server/game/AI/CombatAI.cpp
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "CombatAI.h"
+#include "SpellMgr.h"
+#include "Vehicle.h"
+
+int AggressorAI::Permissible(const Creature *creature)
+{
+ // have some hostile factions, it will be selected by IsHostileTo check at MoveInLineOfSight
+ if (!creature->isCivilian() && !creature->IsNeutralToAll())
+ return PERMIT_BASE_PROACTIVE;
+
+ return PERMIT_BASE_NO;
+}
+
+void AggressorAI::UpdateAI(const uint32 /*diff*/)
+{
+ if (!UpdateVictim())
+ return;
+
+ DoMeleeAttackIfReady();
+}
+
+// some day we will delete these useless things
+int CombatAI::Permissible(const Creature * /*creature*/)
+{
+ return PERMIT_BASE_NO;
+}
+
+int ArchorAI::Permissible(const Creature * /*creature*/)
+{
+ return PERMIT_BASE_NO;
+}
+
+int TurretAI::Permissible(const Creature * /*creature*/)
+{
+ return PERMIT_BASE_NO;
+}
+
+int AOEAI::Permissible(const Creature * /*creature*/)
+{
+ return PERMIT_BASE_NO;
+}
+
+int VehicleAI::Permissible(const Creature * /*creature*/)
+{
+ return PERMIT_BASE_NO;
+}
+
+void CombatAI::InitializeAI()
+{
+ for (uint32 i = 0; i < CREATURE_MAX_SPELLS; ++i)
+ if (me->m_spells[i] && GetSpellStore()->LookupEntry(me->m_spells[i]))
+ spells.push_back(me->m_spells[i]);
+
+ CreatureAI::InitializeAI();
+}
+
+void CombatAI::Reset()
+{
+ events.Reset();
+}
+
+void CombatAI::JustDied(Unit *killer)
+{
+ for (SpellVct::iterator i = spells.begin(); i != spells.end(); ++i)
+ if (AISpellInfo[*i].condition == AICOND_DIE)
+ me->CastSpell(killer, *i, true);
+}
+
+void CombatAI::EnterCombat(Unit *who)
+{
+ for (SpellVct::iterator i = spells.begin(); i != spells.end(); ++i)
+ {
+ if (AISpellInfo[*i].condition == AICOND_AGGRO)
+ me->CastSpell(who, *i, false);
+ else if (AISpellInfo[*i].condition == AICOND_COMBAT)
+ events.ScheduleEvent(*i, AISpellInfo[*i].cooldown + rand()%AISpellInfo[*i].cooldown);
+ }
+}
+
+void CombatAI::UpdateAI(const uint32 diff)
+{
+ if (!UpdateVictim())
+ return;
+
+ events.Update(diff);
+
+ if (me->hasUnitState(UNIT_STAT_CASTING))
+ return;
+
+ if (uint32 spellId = events.ExecuteEvent())
+ {
+ DoCast(spellId);
+ events.ScheduleEvent(spellId, AISpellInfo[spellId].cooldown + rand()%AISpellInfo[spellId].cooldown);
+ }
+ else
+ DoMeleeAttackIfReady();
+}
+
+/////////////////
+//CasterAI
+/////////////////
+
+void CasterAI::InitializeAI()
+{
+ CombatAI::InitializeAI();
+
+ float m_attackDist = 30.0f;
+ for (SpellVct::iterator itr = spells.begin(); itr != spells.end(); ++itr)
+ if (AISpellInfo[*itr].condition == AICOND_COMBAT && m_attackDist > GetAISpellInfo(*itr)->maxRange)
+ m_attackDist = GetAISpellInfo(*itr)->maxRange;
+ if (m_attackDist == 30.0f)
+ m_attackDist = MELEE_RANGE;
+}
+
+void CasterAI::EnterCombat(Unit *who)
+{
+ if (spells.empty())
+ return;
+
+ uint32 spell = rand()%spells.size();
+ uint32 count = 0;
+ for (SpellVct::iterator itr = spells.begin(); itr != spells.end(); ++itr, ++count)
+ {
+ if (AISpellInfo[*itr].condition == AICOND_AGGRO)
+ me->CastSpell(who, *itr, false);
+ else if (AISpellInfo[*itr].condition == AICOND_COMBAT)
+ {
+ uint32 cooldown = GetAISpellInfo(*itr)->realCooldown;
+ if (count == spell)
+ {
+ DoCast(spells[spell]);
+ cooldown += me->GetCurrentSpellCastTime(*itr);
+ }
+ events.ScheduleEvent(*itr, cooldown);
+ }
+ }
+}
+
+void CasterAI::UpdateAI(const uint32 diff)
+{
+ if (!UpdateVictim())
+ return;
+
+ events.Update(diff);
+
+ if (me->hasUnitState(UNIT_STAT_CASTING))
+ return;
+
+ if (uint32 spellId = events.ExecuteEvent())
+ {
+ DoCast(spellId);
+ uint32 casttime = me->GetCurrentSpellCastTime(spellId);
+ events.ScheduleEvent(spellId, (casttime ? casttime : 500) + GetAISpellInfo(spellId)->realCooldown);
+ }
+}
+
+//////////////
+//ArchorAI
+//////////////
+
+ArchorAI::ArchorAI(Creature *c) : CreatureAI(c)
+{
+ if (!me->m_spells[0])
+ sLog.outError("ArchorAI set for creature (entry = %u) with spell1=0. AI will do nothing", me->GetEntry());
+
+ m_minRange = GetSpellMinRange(me->m_spells[0], false);
+ if (!m_minRange)
+ m_minRange = MELEE_RANGE;
+ me->m_CombatDistance = GetSpellMaxRange(me->m_spells[0], false);
+ me->m_SightDistance = me->m_CombatDistance;
+}
+
+void ArchorAI::AttackStart(Unit *who)
+{
+ if (!who)
+ return;
+
+ if (me->IsWithinCombatRange(who, m_minRange))
+ {
+ if (me->Attack(who, true) && !who->IsFlying())
+ me->GetMotionMaster()->MoveChase(who);
+ }
+ else
+ {
+ if (me->Attack(who, false) && !who->IsFlying())
+ me->GetMotionMaster()->MoveChase(who, me->m_CombatDistance);
+ }
+
+ if (who->IsFlying())
+ me->GetMotionMaster()->MoveIdle();
+}
+
+void ArchorAI::UpdateAI(const uint32 /*diff*/)
+{
+ if (!UpdateVictim())
+ return;
+
+ if (!me->IsWithinCombatRange(me->getVictim(), m_minRange))
+ DoSpellAttackIfReady(me->m_spells[0]);
+ else
+ DoMeleeAttackIfReady();
+}
+
+//////////////
+//TurretAI
+//////////////
+
+TurretAI::TurretAI(Creature *c) : CreatureAI(c)
+{
+ if (!me->m_spells[0])
+ sLog.outError("TurretAI set for creature (entry = %u) with spell1=0. AI will do nothing", me->GetEntry());
+
+ m_minRange = GetSpellMinRange(me->m_spells[0], false);
+ me->m_CombatDistance = GetSpellMaxRange(me->m_spells[0], false);
+ me->m_SightDistance = me->m_CombatDistance;
+}
+
+bool TurretAI::CanAIAttack(const Unit * /*who*/) const
+{
+ // TODO: use one function to replace it
+ if (!me->IsWithinCombatRange(me->getVictim(), me->m_CombatDistance)
+ || m_minRange && me->IsWithinCombatRange(me->getVictim(), m_minRange))
+ return false;
+ return true;
+}
+
+void TurretAI::AttackStart(Unit *who)
+{
+ if (who)
+ me->Attack(who, false);
+}
+
+void TurretAI::UpdateAI(const uint32 /*diff*/)
+{
+ if (!UpdateVictim())
+ return;
+
+ DoSpellAttackIfReady(me->m_spells[0]);
+}
+
+//////////////
+//AOEAI
+//////////////
+
+AOEAI::AOEAI(Creature *c) : CreatureAI(c)
+{
+ if (!me->m_spells[0])
+ sLog.outError("AOEAI set for creature (entry = %u) with spell1=0. AI will do nothing", me->GetEntry());
+
+ me->SetVisibility(VISIBILITY_ON);//visible to see all spell anims
+ me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE);//can't be targeted
+ me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_ATTACKABLE_1);//can't be damaged
+ me->SetDisplayId(11686);//invisible model,around a size of a player
+}
+
+bool AOEAI::CanAIAttack(const Unit * /*who*/) const
+{
+ return false;
+}
+
+void AOEAI::AttackStart(Unit * /*who*/)
+{
+}
+
+void AOEAI::UpdateAI(const uint32 /*diff*/)
+{
+ if (!me->HasAura(me->m_spells[0]))
+ me->CastSpell(me, me->m_spells[0],false);
+}
+
+//////////////
+//VehicleAI
+//////////////
+
+VehicleAI::VehicleAI(Creature *c) : CreatureAI(c), m_vehicle(c->GetVehicleKit()), m_IsVehicleInUse(false), m_ConditionsTimer(VEHICLE_CONDITION_CHECK_TIME)
+{
+ LoadConditions();
+ m_DoDismiss = false;
+ m_DismissTimer = VEHICLE_DISMISS_TIME;
+}
+
+
+//NOTE: VehicleAI::UpdateAI runs even while the vehicle is mounted
+void VehicleAI::UpdateAI(const uint32 diff)
+{
+ CheckConditions(diff);
+
+ if (m_DoDismiss)
+ {
+ if (m_DismissTimer < diff)
+ {
+ m_DoDismiss = false;
+ me->SetVisibility(VISIBILITY_OFF);
+ me->ForcedDespawn();
+ }else m_DismissTimer -= diff;
+ }
+}
+
+void VehicleAI::Reset()
+{
+ me->SetVisibility(VISIBILITY_ON);
+
+ m_vehicle->Reset();
+}
+
+void VehicleAI::OnCharmed(bool apply)
+{
+ if (m_IsVehicleInUse && !apply && !conditions.empty())//was used and has conditions
+ {
+ m_DoDismiss = true;//needs reset
+ me->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_PLAYER_VEHICLE);
+ me->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK);
+ }
+ else if (apply)
+ m_DoDismiss = false;//in use again
+ m_DismissTimer = VEHICLE_DISMISS_TIME;//reset timer
+ m_IsVehicleInUse = apply;
+}
+
+void VehicleAI::LoadConditions()
+{
+ conditions = sConditionMgr.GetConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_CREATURE_TEMPLATE_VEHICLE, me->GetEntry());
+ if (!conditions.empty())
+ {
+ sLog.outDebug("VehicleAI::LoadConditions: loaded %u conditions", uint32(conditions.size()));
+ }
+}
+
+void VehicleAI::CheckConditions(const uint32 diff)
+{
+ if(m_ConditionsTimer < diff)
+ {
+ if (!conditions.empty())
+ {
+ for (SeatMap::iterator itr = m_vehicle->m_Seats.begin(); itr != m_vehicle->m_Seats.end(); ++itr)
+ if (Unit *passenger = itr->second.passenger)
+ {
+ if (Player* plr = passenger->ToPlayer())
+ {
+ if (!sConditionMgr.IsPlayerMeetToConditions(plr, conditions))
+ {
+ plr->ExitVehicle();
+ return;//check other pessanger in next tick
+ }
+ }
+ }
+ }
+ m_ConditionsTimer = VEHICLE_CONDITION_CHECK_TIME;
+ } else m_ConditionsTimer -= diff;
+} \ No newline at end of file
diff --git a/src/server/game/AI/CombatAI.h b/src/server/game/AI/CombatAI.h
new file mode 100644
index 00000000000..8626b38dd37
--- /dev/null
+++ b/src/server/game/AI/CombatAI.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef TRINITY_COMBATAI_H
+#define TRINITY_COMBATAI_H
+
+#include "CreatureAI.h"
+#include "CreatureAIImpl.h"
+#include "ConditionMgr.h"
+
+class Creature;
+
+class AggressorAI : public CreatureAI
+{
+ public:
+ explicit AggressorAI(Creature *c) : CreatureAI(c) {}
+
+ void UpdateAI(const uint32);
+ static int Permissible(const Creature *);
+};
+
+typedef std::vector<uint32> SpellVct;
+
+class CombatAI : public CreatureAI
+{
+ public:
+ explicit CombatAI(Creature *c) : CreatureAI(c) {}
+
+ void InitializeAI();
+ void Reset();
+ void EnterCombat(Unit* who);
+ void JustDied(Unit *killer);
+ void UpdateAI(const uint32 diff);
+ static int Permissible(const Creature *);
+ protected:
+ EventMap events;
+ SpellVct spells;
+};
+
+class CasterAI : public CombatAI
+{
+ public:
+ explicit CasterAI(Creature *c) : CombatAI(c) { m_attackDist = MELEE_RANGE; }
+ void InitializeAI();
+ void AttackStart(Unit * victim) { AttackStartCaster(victim, m_attackDist); }
+ void UpdateAI(const uint32 diff);
+ void EnterCombat(Unit * /*who*/);
+ private:
+ float m_attackDist;
+};
+
+struct ArchorAI : public CreatureAI
+{
+ public:
+ explicit ArchorAI(Creature *c);
+ void AttackStart(Unit *who);
+ void UpdateAI(const uint32 diff);
+
+ static int Permissible(const Creature *);
+ protected:
+ float m_minRange;
+};
+
+struct TurretAI : public CreatureAI
+{
+ public:
+ explicit TurretAI(Creature *c);
+ bool CanAIAttack(const Unit *who) const;
+ void AttackStart(Unit *who);
+ void UpdateAI(const uint32 diff);
+
+ static int Permissible(const Creature *);
+ protected:
+ float m_minRange;
+};
+
+struct AOEAI : public CreatureAI
+{
+ public:
+ explicit AOEAI(Creature *c);
+ bool CanAIAttack(const Unit *who) const;
+ void AttackStart(Unit *who);
+ void UpdateAI(const uint32 diff);
+
+ static int Permissible(const Creature *);
+};
+#define VEHICLE_CONDITION_CHECK_TIME 1000
+#define VEHICLE_DISMISS_TIME 5000
+struct VehicleAI : public CreatureAI
+{
+ public:
+ explicit VehicleAI(Creature *c);
+
+ void UpdateAI(const uint32 diff);
+ static int Permissible(const Creature *);
+ void Reset();
+ void MoveInLineOfSight(Unit *) {}
+ void AttackStart(Unit *) {}
+ void OnCharmed(bool apply);
+
+ private:
+ Vehicle* m_vehicle;
+ bool m_IsVehicleInUse;
+ void LoadConditions();
+ void CheckConditions(const uint32 diff);
+ ConditionList conditions;
+ uint32 m_ConditionsTimer;
+ bool m_DoDismiss;
+ uint32 m_DismissTimer;
+};
+
+#endif
diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp
new file mode 100644
index 00000000000..fb8f37ae492
--- /dev/null
+++ b/src/server/game/AI/CreatureAI.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "CreatureAI.h"
+#include "CreatureAIImpl.h"
+#include "Creature.h"
+#include "World.h"
+#include "SpellMgr.h"
+#include "Vehicle.h"
+
+//Disable CreatureAI when charmed
+void CreatureAI::OnCharmed(bool /*apply*/)
+{
+ //me->IsAIEnabled = !apply;*/
+ me->NeedChangeAI = true;
+ me->IsAIEnabled = false;
+}
+
+AISpellInfoType * UnitAI::AISpellInfo;
+ AISpellInfoType * GetAISpellInfo(uint32 i) { return &CreatureAI::AISpellInfo[i]; }
+
+void CreatureAI::DoZoneInCombat(Creature* creature)
+{
+ if (!creature)
+ creature = me;
+
+ if (!creature->CanHaveThreatList())
+ return;
+
+ Map *map = creature->GetMap();
+ if (!map->IsDungeon()) //use IsDungeon instead of Instanceable, in case battlegrounds will be instantiated
+ {
+ sLog.outError("DoZoneInCombat call for map that isn't an instance (creature entry = %d)", creature->GetTypeId() == TYPEID_UNIT ? creature->ToCreature()->GetEntry() : 0);
+ return;
+ }
+
+ if (!creature->HasReactState(REACT_PASSIVE) && !creature->getVictim())
+ {
+ if (Unit *target = creature->SelectNearestTarget(50))
+ creature->AI()->AttackStart(target);
+ else if (creature->isSummon())
+ {
+ if (Unit *summoner = creature->ToTempSummon()->GetSummoner())
+ {
+ Unit *target = summoner->getAttackerForHelper();
+ if (!target && summoner->CanHaveThreatList() && !summoner->getThreatManager().isThreatListEmpty())
+ target = summoner->getThreatManager().getHostilTarget();
+ if (target && (creature->IsFriendlyTo(summoner) || creature->IsHostileTo(target)))
+ creature->AI()->AttackStart(target);
+ }
+ }
+ }
+
+ if (!creature->HasReactState(REACT_PASSIVE) && !creature->getVictim())
+ {
+ sLog.outError("DoZoneInCombat called for creature that has empty threat list (creature entry = %u)", creature->GetEntry());
+ return;
+ }
+
+ Map::PlayerList const &PlList = map->GetPlayers();
+
+ if (PlList.isEmpty())
+ return;
+
+ for (Map::PlayerList::const_iterator i = PlList.begin(); i != PlList.end(); ++i)
+ {
+ if (Player* pPlayer = i->getSource())
+ {
+ if (pPlayer->isGameMaster())
+ continue;
+
+ if (pPlayer->isAlive())
+ {
+ creature->SetInCombatWith(pPlayer);
+ pPlayer->SetInCombatWith(creature);
+ creature->AddThreat(pPlayer, 0.0f);
+ }
+
+ /* Causes certain things to never leave the threat list (Priest Lightwell, etc):
+ for (Unit::ControlList::const_iterator itr = pPlayer->m_Controlled.begin(); itr != pPlayer->m_Controlled.end(); ++itr)
+ {
+ creature->SetInCombatWith(*itr);
+ (*itr)->SetInCombatWith(creature);
+ creature->AddThreat(*itr, 0.0f);
+ }*/
+ }
+ }
+}
+
+// scripts does not take care about MoveInLineOfSight loops
+// MoveInLineOfSight can be called inside another MoveInLineOfSight and cause stack overflow
+void CreatureAI::MoveInLineOfSight_Safe(Unit *who)
+{
+ if (m_MoveInLineOfSight_locked == true)
+ return;
+ m_MoveInLineOfSight_locked = true;
+ MoveInLineOfSight(who);
+ m_MoveInLineOfSight_locked = false;
+}
+
+void CreatureAI::MoveInLineOfSight(Unit *who)
+{
+ if (me->getVictim())
+ return;
+
+ if (me->GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET) // non-combat pets should just stand there and look good;)
+ return;
+
+ if (me->canStartAttack(who, false))
+ AttackStart(who);
+ //else if (who->getVictim() && me->IsFriendlyTo(who)
+ // && me->IsWithinDistInMap(who, sWorld.getConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_RADIUS))
+ // && me->canStartAttack(who->getVictim(), true)) // TODO: if we use true, it will not attack it when it arrives
+ // me->GetMotionMaster()->MoveChase(who->getVictim());
+}
+
+void CreatureAI::EnterEvadeMode()
+{
+ if (!_EnterEvadeMode())
+ return;
+
+ sLog.outDebug("Creature %u enters evade mode.", me->GetEntry());
+
+ if (!me->GetVehicle()) // otherwise me will be in evade mode forever
+ {
+ if (Unit *owner = me->GetCharmerOrOwner())
+ {
+ me->GetMotionMaster()->Clear(false);
+ me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle(), MOTION_SLOT_ACTIVE);
+ }
+ else
+ me->GetMotionMaster()->MoveTargetedHome();
+ }
+
+ Reset();
+
+ if (me->IsVehicle()) // use the same sequence of addtoworld, aireset may remove all summons!
+ me->GetVehicleKit()->Reset();
+}
+
+/*void CreatureAI::AttackedBy(Unit* attacker)
+{
+ if (!me->getVictim())
+ AttackStart(attacker);
+}*/
diff --git a/src/server/game/AI/CreatureAI.h b/src/server/game/AI/CreatureAI.h
new file mode 100644
index 00000000000..c03d3dd09d0
--- /dev/null
+++ b/src/server/game/AI/CreatureAI.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef TRINITY_CREATUREAI_H
+#define TRINITY_CREATUREAI_H
+
+#include "UnitAI.h"
+#include "Common.h"
+
+class WorldObject;
+class Unit;
+class Creature;
+class Player;
+struct SpellEntry;
+
+#define TIME_INTERVAL_LOOK 5000
+#define VISIBILITY_RANGE 10000
+
+//Spell targets used by SelectSpell
+enum SelectTargetType
+{
+ SELECT_TARGET_DONTCARE = 0, //All target types allowed
+
+ SELECT_TARGET_SELF, //Only Self casting
+
+ SELECT_TARGET_SINGLE_ENEMY, //Only Single Enemy
+ SELECT_TARGET_AOE_ENEMY, //Only AoE Enemy
+ SELECT_TARGET_ANY_ENEMY, //AoE or Single Enemy
+
+ SELECT_TARGET_SINGLE_FRIEND, //Only Single Friend
+ SELECT_TARGET_AOE_FRIEND, //Only AoE Friend
+ SELECT_TARGET_ANY_FRIEND, //AoE or Single Friend
+};
+
+//Spell Effects used by SelectSpell
+enum SelectEffect
+{
+ SELECT_EFFECT_DONTCARE = 0, //All spell effects allowed
+ SELECT_EFFECT_DAMAGE, //Spell does damage
+ SELECT_EFFECT_HEALING, //Spell does healing
+ SELECT_EFFECT_AURA, //Spell applies an aura
+};
+
+enum SCEquip
+{
+ EQUIP_NO_CHANGE = -1,
+ EQUIP_UNEQUIP = 0
+};
+
+class CreatureAI : public UnitAI
+{
+ protected:
+ Creature * const me;
+
+ bool UpdateVictim();
+ bool UpdateVictimWithGaze();
+ bool UpdateCombatState();
+
+ void SetGazeOn(Unit *target);
+
+ Creature *DoSummon(uint32 uiEntry, const Position &pos, uint32 uiDespawntime = 30000, TempSummonType uiType = TEMPSUMMON_CORPSE_TIMED_DESPAWN);
+ Creature *DoSummon(uint32 uiEntry, WorldObject *obj, float fRadius = 5.0f, uint32 uiDespawntime = 30000, TempSummonType uiType = TEMPSUMMON_CORPSE_TIMED_DESPAWN);
+ Creature *DoSummonFlyer(uint32 uiEntry, WorldObject *obj, float fZ, float fRadius = 5.0f, uint32 uiDespawntime = 30000, TempSummonType uiType = TEMPSUMMON_CORPSE_TIMED_DESPAWN);
+
+ public:
+ explicit CreatureAI(Creature *c) : UnitAI((Unit*)c), me(c), m_MoveInLineOfSight_locked(false) {}
+
+ virtual ~CreatureAI() {}
+
+ /// == Reactions At =================================
+
+ // Called if IsVisible(Unit *who) is true at each *who move, reaction at visibility zone enter
+ void MoveInLineOfSight_Safe(Unit *who);
+
+ // Called for reaction at stopping attack at no attackers or targets
+ virtual void EnterEvadeMode();
+
+ // Called for reaction at enter to combat if not in combat yet (enemy can be NULL)
+ virtual void EnterCombat(Unit* /*enemy*/) {}
+
+ // Called at any Damage from any attacker (before damage apply)
+ // Note: it for recalculation damage or special reaction at damage
+ // for attack reaction use AttackedBy called for not DOT damage in Unit::DealDamage also
+ virtual void DamageTaken(Unit * /*done_by*/, uint32 & /*damage*/) {}
+
+ // Called when the creature is killed
+ virtual void JustDied(Unit *) {}
+
+ // Called when the creature kills a unit
+ virtual void KilledUnit(Unit *) {}
+
+ // Called when the creature summon successfully other creature
+ virtual void JustSummoned(Creature*) {}
+ virtual void IsSummonedBy(Unit * /*summoner*/) {}
+
+ virtual void SummonedCreatureDespawn(Creature* /*unit*/) {}
+
+ // Called when hit by a spell
+ virtual void SpellHit(Unit*, const SpellEntry*) {}
+
+ // Called when spell hits a target
+ virtual void SpellHitTarget(Unit* /*target*/, const SpellEntry*) {}
+
+ // Called to get trigger target for aura effect
+ virtual Unit * GetAuraEffectTriggerTarget(uint32 /*spellId*/, uint8 /*effIndex*/) {return NULL;}
+
+ // Called when the creature is target of hostile action: swing, hostile spell landed, fear/etc)
+ //virtual void AttackedBy(Unit* attacker);
+ virtual bool IsEscorted() { return false; }
+
+ // Called when creature is spawned or respawned (for reseting variables)
+ virtual void JustRespawned() { Reset(); }
+
+ // Called at waypoint reached or point movement finished
+ virtual void MovementInform(uint32 /*MovementType*/, uint32 /*Data*/) {}
+
+ void OnCharmed(bool apply);
+
+ //virtual void SpellClick(Player *player) {}
+
+ // Called at reaching home after evade
+ virtual void JustReachedHome() {}
+
+ void DoZoneInCombat(Creature* pUnit = NULL);
+
+ // Called at text emote receive from player
+ virtual void ReceiveEmote(Player* /*pPlayer*/, uint32 /*text_emote*/) {}
+
+ /// == Triggered Actions Requested ==================
+
+ // Called when creature attack expected (if creature can and no have current victim)
+ // Note: for reaction at hostile action must be called AttackedBy function.
+ //virtual void AttackStart(Unit *) {}
+
+ // Called at World update tick
+ //virtual void UpdateAI(const uint32 /*diff*/) {}
+
+ /// == State checks =================================
+
+ // Is unit visible for MoveInLineOfSight
+ //virtual bool IsVisible(Unit *) const { return false; }
+
+ // called when the corpse of this creature gets removed
+ virtual void CorpseRemoved(uint32 & /*respawnDelay*/) {}
+
+ // Called when victim entered water and creature can not enter water
+ //virtual bool canReachByRangeAttack(Unit*) { return false; }
+
+ /// == Fields =======================================
+
+ // Pointer to controlled by AI creature
+ //Creature* const me;
+
+ virtual void PassengerBoarded(Unit * /*who*/, int8 /*seatId*/, bool /*apply*/) {}
+
+ protected:
+ virtual void MoveInLineOfSight(Unit *);
+
+ bool _EnterEvadeMode();
+
+ private:
+ bool m_MoveInLineOfSight_locked;
+};
+
+enum Permitions
+{
+ PERMIT_BASE_NO = -1,
+ PERMIT_BASE_IDLE = 1,
+ PERMIT_BASE_REACTIVE = 100,
+ PERMIT_BASE_PROACTIVE = 200,
+ PERMIT_BASE_FACTION_SPECIFIC = 400,
+ PERMIT_BASE_SPECIAL = 800
+};
+
+#endif
diff --git a/src/server/game/AI/CreatureAIFactory.h b/src/server/game/AI/CreatureAIFactory.h
new file mode 100644
index 00000000000..6aa69eaaa29
--- /dev/null
+++ b/src/server/game/AI/CreatureAIFactory.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef TRINITY_CREATUREAIFACTORY_H
+#define TRINITY_CREATUREAIFACTORY_H
+
+//#include "Policies/Singleton.h"
+#include "Dynamic/ObjectRegistry.h"
+#include "Dynamic/FactoryHolder.h"
+
+struct SelectableAI : public FactoryHolder<CreatureAI>, public Permissible<Creature>
+{
+ SelectableAI(const char *id) : FactoryHolder<CreatureAI>(id) {}
+};
+
+template<class REAL_AI>
+struct CreatureAIFactory : public SelectableAI
+{
+ CreatureAIFactory(const char *name) : SelectableAI(name) {}
+
+ CreatureAI* Create(void *) const;
+
+ int Permit(const Creature *c) const { return REAL_AI::Permissible(c); }
+};
+
+template<class REAL_AI>
+inline CreatureAI*
+CreatureAIFactory<REAL_AI>::Create(void *data) const
+{
+ Creature* creature = reinterpret_cast<Creature *>(data);
+ return (new REAL_AI(creature));
+}
+
+typedef FactoryHolder<CreatureAI> CreatureAICreator;
+typedef FactoryHolder<CreatureAI>::FactoryHolderRegistry CreatureAIRegistry;
+typedef FactoryHolder<CreatureAI>::FactoryHolderRepository CreatureAIRepository;
+#endif
diff --git a/src/server/game/AI/CreatureAIImpl.h b/src/server/game/AI/CreatureAIImpl.h
new file mode 100644
index 00000000000..d2f96ffcabc
--- /dev/null
+++ b/src/server/game/AI/CreatureAIImpl.h
@@ -0,0 +1,638 @@
+/*
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef CREATUREAIIMPL_H
+#define CREATUREAIIMPL_H
+
+#include "Common.h"
+#include "Platform/Define.h"
+#include "TemporarySummon.h"
+#include "CreatureAI.h"
+#include "SpellMgr.h"
+
+template<class T>
+inline
+const T& RAND(const T& v1, const T& v2)
+{
+ return (urand(0,1)) ? v1 : v2;
+}
+
+template<class T>
+inline
+const T& RAND(const T& v1, const T& v2, const T& v3)
+{
+ switch (urand(0,2))
+ {
+ default:
+ case 0: return v1;
+ case 1: return v2;
+ case 2: return v3;
+ }
+}
+
+template<class T>
+inline
+const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4)
+{
+ switch (urand(0,3))
+ {
+ default:
+ case 0: return v1;
+ case 1: return v2;
+ case 2: return v3;
+ case 3: return v4;
+ }
+}
+
+template<class T>
+inline
+const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5)
+{
+ switch (urand(0,4))
+ {
+ default:
+ case 0: return v1;
+ case 1: return v2;
+ case 2: return v3;
+ case 3: return v4;
+ case 4: return v5;
+ }
+}
+
+template<class T>
+inline
+const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6)
+{
+ switch (urand(0,5))
+ {
+ default:
+ case 0: return v1;
+ case 1: return v2;
+ case 2: return v3;
+ case 3: return v4;
+ case 4: return v5;
+ case 5: return v6;
+ }
+}
+
+template<class T>
+inline
+const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7)
+{
+ switch (urand(0,6))
+ {
+ default:
+ case 0: return v1;
+ case 1: return v2;
+ case 2: return v3;
+ case 3: return v4;
+ case 4: return v5;
+ case 5: return v6;
+ case 6: return v7;
+ }
+}
+
+template<class T>
+inline
+const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8)
+{
+ switch (urand(0,7))
+ {
+ default:
+ case 0: return v1;
+ case 1: return v2;
+ case 2: return v3;
+ case 3: return v4;
+ case 4: return v5;
+ case 5: return v6;
+ case 6: return v7;
+ case 7: return v8;
+ }
+}
+
+template<class T>
+inline
+const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
+ const T& v9)
+{
+ switch (urand(0,8))
+ {
+ default:
+ case 0: return v1;
+ case 1: return v2;
+ case 2: return v3;
+ case 3: return v4;
+ case 4: return v5;
+ case 5: return v6;
+ case 6: return v7;
+ case 7: return v8;
+ case 8: return v9;
+ }
+}
+
+template<class T>
+inline
+const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
+ const T& v9, const T& v10)
+{
+ switch (urand(0,9))
+ {
+ default:
+ case 0: return v1;
+ case 1: return v2;
+ case 2: return v3;
+ case 3: return v4;
+ case 4: return v5;
+ case 5: return v6;
+ case 6: return v7;
+ case 7: return v8;
+ case 8: return v9;
+ case 9: return v10;
+ }
+}
+
+template<class T>
+inline
+const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
+ const T& v9, const T& v10, const T& v11)
+{
+ switch (urand(0,10))
+ {
+ default:
+ case 0: return v1;
+ case 1: return v2;
+ case 2: return v3;
+ case 3: return v4;
+ case 4: return v5;
+ case 5: return v6;
+ case 6: return v7;
+ case 7: return v8;
+ case 8: return v9;
+ case 9: return v10;
+ case 10: return v11;
+ }
+}
+
+template<class T>
+inline
+const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
+ const T& v9, const T& v10, const T& v11, const T& v12)
+{
+ switch (urand(0,11))
+ {
+ default:
+ case 0: return v1;
+ case 1: return v2;
+ case 2: return v3;
+ case 3: return v4;
+ case 4: return v5;
+ case 5: return v6;
+ case 6: return v7;
+ case 7: return v8;
+ case 8: return v9;
+ case 9: return v10;
+ case 10: return v11;
+ case 11: return v12;
+ }
+}
+
+template<class T>
+inline
+const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
+ const T& v9, const T& v10, const T& v11, const T& v12, const T& v13)
+{
+ switch (urand(0,12))
+ {
+ default:
+ case 0: return v1;
+ case 1: return v2;
+ case 2: return v3;
+ case 3: return v4;
+ case 4: return v5;
+ case 5: return v6;
+ case 6: return v7;
+ case 7: return v8;
+ case 8: return v9;
+ case 9: return v10;
+ case 10: return v11;
+ case 11: return v12;
+ case 12: return v13;
+ }
+}
+
+template<class T>
+inline
+const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
+ const T& v9, const T& v10, const T& v11, const T& v12, const T& v13, const T& v14)
+{
+ switch (urand(0,13))
+ {
+ default:
+ case 0: return v1;
+ case 1: return v2;
+ case 2: return v3;
+ case 3: return v4;
+ case 4: return v5;
+ case 5: return v6;
+ case 6: return v7;
+ case 7: return v8;
+ case 8: return v9;
+ case 9: return v10;
+ case 10: return v11;
+ case 11: return v12;
+ case 12: return v13;
+ case 13: return v14;
+ }
+}
+
+template<class T>
+inline
+const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
+ const T& v9, const T& v10, const T& v11, const T& v12, const T& v13, const T& v14, const T& v15)
+{
+ switch (urand(0,14))
+ {
+ default:
+ case 0: return v1;
+ case 1: return v2;
+ case 2: return v3;
+ case 3: return v4;
+ case 4: return v5;
+ case 5: return v6;
+ case 6: return v7;
+ case 7: return v8;
+ case 8: return v9;
+ case 9: return v10;
+ case 10: return v11;
+ case 11: return v12;
+ case 12: return v13;
+ case 13: return v14;
+ case 14: return v15;
+ }
+}
+
+template<class T>
+inline
+const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
+ const T& v9, const T& v10, const T& v11, const T& v12, const T& v13, const T& v14, const T& v15, const T& v16)
+{
+ switch (urand(0,15))
+ {
+ default:
+ case 0: return v1;
+ case 1: return v2;
+ case 2: return v3;
+ case 3: return v4;
+ case 4: return v5;
+ case 5: return v6;
+ case 6: return v7;
+ case 7: return v8;
+ case 8: return v9;
+ case 9: return v10;
+ case 10: return v11;
+ case 11: return v12;
+ case 12: return v13;
+ case 13: return v14;
+ case 14: return v15;
+ case 15: return v16;
+ }
+}
+
+class EventMap : private std::map<uint32, uint32>
+{
+ private:
+ uint32 m_time, m_phase;
+ public:
+ explicit EventMap() : m_phase(0), m_time(0) {}
+
+ uint32 GetTimer() const { return m_time; }
+
+ void Reset() { clear(); m_time = 0; m_phase = 0; }
+
+ void Update(uint32 time) { m_time += time; }
+
+ void SetPhase(uint32 phase)
+ {
+ if (phase && phase < 9)
+ m_phase = (1 << (phase + 24));
+ }
+
+ void ScheduleEvent(uint32 eventId, uint32 time, uint32 gcd = 0, uint32 phase = 0)
+ {
+ time += m_time;
+ if (gcd && gcd < 9)
+ eventId |= (1 << (gcd + 16));
+ if (phase && phase < 9)
+ eventId |= (1 << (phase + 24));
+ iterator itr = find(time);
+ while (itr != end())
+ {
+ ++time;
+ itr = find(time);
+ }
+ insert(std::make_pair(time, eventId));
+ }
+
+ void RescheduleEvent(uint32 eventId, uint32 time, uint32 gcd = 0, uint32 phase = 0)
+ {
+ CancelEvent(eventId);
+ ScheduleEvent(eventId, time, gcd, phase);
+ }
+
+ void RepeatEvent(uint32 time)
+ {
+ if (empty())
+ return;
+ uint32 eventId = begin()->second;
+ erase(begin());
+ time += m_time;
+ iterator itr = find(time);
+ while (itr != end())
+ {
+ ++time;
+ itr = find(time);
+ }
+ insert(std::make_pair(time, eventId));
+ }
+
+ void PopEvent()
+ {
+ erase(begin());
+ }
+
+ uint32 ExecuteEvent()
+ {
+ while (!empty())
+ {
+ if (begin()->first > m_time)
+ return 0;
+ else if (m_phase && (begin()->second & 0xFF000000) && !(begin()->second & m_phase))
+ erase(begin());
+ else
+ {
+ uint32 eventId = (begin()->second & 0x0000FFFF);
+ erase(begin());
+ return eventId;
+ }
+ }
+ return 0;
+ }
+
+ uint32 GetEvent()
+ {
+ while (!empty())
+ {
+ if (begin()->first > m_time)
+ return 0;
+ else if (m_phase && (begin()->second & 0xFF000000) && !(begin()->second & m_phase))
+ erase(begin());
+ else
+ return (begin()->second & 0x0000FFFF);
+ }
+ return 0;
+ }
+
+ // Delay all events
+ void DelayEvents(uint32 delay)
+ {
+ if (delay < m_time)
+ m_time -= delay;
+ else
+ m_time = 0;
+ }
+
+ // Delay all events having the specified Global Cooldown.
+ void DelayEvents(uint32 delay, uint32 gcd)
+ {
+ uint32 nextTime = m_time + delay;
+ gcd = (1 << (gcd + 16));
+ for (iterator itr = begin(); itr != end() && itr->first < nextTime;)
+ {
+ if (itr->second & gcd)
+ {
+ ScheduleEvent(itr->second, itr->first-m_time+delay);
+ erase(itr);
+ itr = begin();
+ }
+ else
+ ++itr;
+ }
+ }
+
+ void CancelEvent(uint32 eventId)
+ {
+ for (iterator itr = begin(); itr != end();)
+ {
+ if (eventId == (itr->second & 0x0000FFFF))
+ {
+ erase(itr);
+ itr = begin();
+ }
+ else
+ ++itr;
+ }
+ }
+
+ void CancelEventsByGCD(uint32 gcd)
+ {
+ gcd = (1 << (gcd + 16));
+
+ for (iterator itr = begin(); itr != end();)
+ {
+ if (itr->second & gcd)
+ {
+ erase(itr);
+ itr = begin();
+ }
+ else
+ ++itr;
+ }
+ }
+};
+
+enum AITarget
+{
+ AITARGET_SELF,
+ AITARGET_VICTIM,
+ AITARGET_ENEMY,
+ AITARGET_ALLY,
+ AITARGET_BUFF,
+ AITARGET_DEBUFF,
+};
+
+enum AICondition
+{
+ AICOND_AGGRO,
+ AICOND_COMBAT,
+ AICOND_DIE,
+};
+
+#define AI_DEFAULT_COOLDOWN 5000
+
+struct AISpellInfoType
+{
+ AISpellInfoType() : target(AITARGET_SELF), condition(AICOND_COMBAT)
+ , cooldown(AI_DEFAULT_COOLDOWN), realCooldown(0), maxRange(0.0f){}
+ AITarget target;
+ AICondition condition;
+ uint32 cooldown;
+ uint32 realCooldown;
+ float maxRange;
+};
+
+ AISpellInfoType * GetAISpellInfo(uint32 i);
+
+inline void CreatureAI::SetGazeOn(Unit *target)
+{
+ if (me->canAttack(target))
+ {
+ AttackStart(target);
+ me->SetReactState(REACT_PASSIVE);
+ }
+}
+
+inline bool CreatureAI::UpdateVictimWithGaze()
+{
+ if (!me->isInCombat())
+ return false;
+
+ if (me->HasReactState(REACT_PASSIVE))
+ {
+ if (me->getVictim())
+ return true;
+ else
+ me->SetReactState(REACT_AGGRESSIVE);
+ }
+
+ if (Unit *victim = me->SelectVictim())
+ AttackStart(victim);
+ return me->getVictim();
+}
+
+inline bool CreatureAI::UpdateCombatState()
+{
+ if (!me->isInCombat())
+ return false;
+
+ if (!me->HasReactState(REACT_PASSIVE))
+ {
+ if (Unit *victim = me->SelectVictim())
+ AttackStart(victim);
+ return me->getVictim();
+ }
+ else if (me->getThreatManager().isThreatListEmpty())
+ {
+ EnterEvadeMode();
+ return false;
+ }
+
+ return true;
+}
+
+inline bool CreatureAI::UpdateVictim()
+{
+ if (!me->isInCombat())
+ return false;
+
+ if (!me->HasReactState(REACT_PASSIVE))
+ {
+ if (Unit *victim = me->SelectVictim())
+ AttackStart(victim);
+ return me->getVictim();
+ }
+ else if (me->getThreatManager().isThreatListEmpty())
+ {
+ EnterEvadeMode();
+ return false;
+ }
+
+ return true;
+}
+
+/*
+inline bool CreatureAI::UpdateVictim()
+{
+ if (!me->isInCombat())
+ return false;
+ if (Unit *victim = me->SelectVictim())
+ AttackStart(victim);
+ return me->getVictim();
+}
+*/
+
+inline bool CreatureAI::_EnterEvadeMode()
+{
+ if (!me->isAlive())
+ return false;
+
+ // sometimes bosses stuck in combat?
+ me->RemoveAllAuras();
+ me->DeleteThreatList();
+ me->CombatStop(true);
+ me->LoadCreaturesAddon();
+ me->SetLootRecipient(NULL);
+ me->ResetPlayerDamageReq();
+
+ if (me->IsInEvadeMode())
+ return false;
+
+ return true;
+}
+
+inline void UnitAI::DoCast(Unit* victim, uint32 spellId, bool triggered)
+{
+ if (!victim || (me->hasUnitState(UNIT_STAT_CASTING) && !triggered))
+ return;
+
+ me->CastSpell(victim, spellId, triggered);
+}
+
+inline void UnitAI::DoCastVictim(uint32 spellId, bool triggered)
+{
+ me->CastSpell(me->getVictim(), spellId, triggered);
+}
+
+inline void UnitAI::DoCastAOE(uint32 spellId, bool triggered)
+{
+ if (!triggered && me->hasUnitState(UNIT_STAT_CASTING))
+ return;
+
+ me->CastSpell((Unit*)NULL, spellId, triggered);
+}
+
+inline Creature *CreatureAI::DoSummon(uint32 uiEntry, const Position &pos, uint32 uiDespawntime, TempSummonType uiType)
+{
+ return me->SummonCreature(uiEntry, pos, uiType, uiDespawntime);
+}
+
+inline Creature *CreatureAI::DoSummon(uint32 uiEntry, WorldObject* obj, float fRadius, uint32 uiDespawntime, TempSummonType uiType)
+{
+ Position pos;
+ obj->GetRandomNearPosition(pos, fRadius);
+ return me->SummonCreature(uiEntry, pos, uiType, uiDespawntime);
+}
+
+inline Creature *CreatureAI::DoSummonFlyer(uint32 uiEntry, WorldObject *obj, float _fZ, float fRadius, uint32 uiDespawntime, TempSummonType uiType)
+{
+ Position pos;
+ obj->GetRandomNearPosition(pos, fRadius);
+ pos.m_positionZ += _fZ;
+ return me->SummonCreature(uiEntry, pos, uiType, uiDespawntime);
+}
+
+#endif
+
diff --git a/src/server/game/AI/CreatureAIRegistry.cpp b/src/server/game/AI/CreatureAIRegistry.cpp
new file mode 100644
index 00000000000..9db30a0a5c4
--- /dev/null
+++ b/src/server/game/AI/CreatureAIRegistry.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "PassiveAI.h"
+#include "ReactorAI.h"
+#include "CombatAI.h"
+#include "GuardAI.h"
+#include "PetAI.h"
+#include "TotemAI.h"
+#include "CreatureEventAI.h"
+#include "RandomMovementGenerator.h"
+#include "MovementGeneratorImpl.h"
+#include "CreatureAIRegistry.h"
+#include "WaypointMovementGenerator.h"
+#include "CreatureAIFactory.h"
+
+//#include "CreatureAIImpl.h"
+namespace AIRegistry
+{
+ void Initialize()
+ {
+ (new CreatureAIFactory<NullCreatureAI>("NullCreatureAI"))->RegisterSelf();
+ (new CreatureAIFactory<TriggerAI>("TriggerAI"))->RegisterSelf();
+ (new CreatureAIFactory<AggressorAI>("AggressorAI"))->RegisterSelf();
+ (new CreatureAIFactory<ReactorAI>("ReactorAI"))->RegisterSelf();
+ (new CreatureAIFactory<PassiveAI>("PassiveAI"))->RegisterSelf();
+ (new CreatureAIFactory<CritterAI>("CritterAI"))->RegisterSelf();
+ (new CreatureAIFactory<GuardAI>("GuardAI"))->RegisterSelf();
+ (new CreatureAIFactory<PetAI>("PetAI"))->RegisterSelf();
+ (new CreatureAIFactory<TotemAI>("TotemAI"))->RegisterSelf();
+ (new CreatureAIFactory<CombatAI>("CombatAI"))->RegisterSelf();
+ (new CreatureAIFactory<ArchorAI>("ArchorAI"))->RegisterSelf();
+ (new CreatureAIFactory<TurretAI>("TurretAI"))->RegisterSelf();
+ (new CreatureAIFactory<CreatureEventAI>("EventAI"))->RegisterSelf();
+ (new CreatureAIFactory<AOEAI>("AOEAI"))->RegisterSelf();
+ (new CreatureAIFactory<VehicleAI>("VehicleAI"))->RegisterSelf();
+
+ (new MovementGeneratorFactory<RandomMovementGenerator<Creature> >(RANDOM_MOTION_TYPE))->RegisterSelf();
+ (new MovementGeneratorFactory<WaypointMovementGenerator<Creature> >(WAYPOINT_MOTION_TYPE))->RegisterSelf();
+ }
+}
+
diff --git a/src/server/game/AI/CreatureAIRegistry.h b/src/server/game/AI/CreatureAIRegistry.h
new file mode 100644
index 00000000000..0d83d29cf45
--- /dev/null
+++ b/src/server/game/AI/CreatureAIRegistry.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef TRINITY_CREATUREAIREGISTRY_H
+#define TRINITY_CREATUREAIREGISTRY_H
+
+namespace AIRegistry
+{
+ void Initialize(void);
+}
+#endif
+
diff --git a/src/server/game/AI/CreatureAISelector.cpp b/src/server/game/AI/CreatureAISelector.cpp
new file mode 100644
index 00000000000..d3fd5a8aed9
--- /dev/null
+++ b/src/server/game/AI/CreatureAISelector.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "Creature.h"
+#include "CreatureAISelector.h"
+#include "PassiveAI.h"
+#include "Policies/SingletonImp.h"
+#include "MovementGenerator.h"
+#include "Pet.h"
+#include "TemporarySummon.h"
+#include "CreatureAIFactory.h"
+#include "ScriptMgr.h"
+
+INSTANTIATE_SINGLETON_1(CreatureAIRegistry);
+INSTANTIATE_SINGLETON_1(MovementGeneratorRegistry);
+
+namespace FactorySelector
+{
+ CreatureAI* selectAI(Creature *creature)
+ {
+ const CreatureAICreator *ai_factory = NULL;
+ CreatureAIRegistry &ai_registry(CreatureAIRepository::Instance());
+
+ if (creature->isPet())
+ ai_factory = ai_registry.GetRegistryItem("PetAI");
+
+ //scriptname in db
+ if (!ai_factory)
+ if (CreatureAI* scriptedAI = sScriptMgr.GetAI(creature))
+ return scriptedAI;
+
+ // AIname in db
+ std::string ainame=creature->GetAIName();
+ if (!ai_factory && !ainame.empty())
+ ai_factory = ai_registry.GetRegistryItem(ainame.c_str());
+
+ // select by NPC flags
+ if (!ai_factory)
+ {
+ if (creature->IsVehicle())
+ ai_factory = ai_registry.GetRegistryItem("VehicleAI");
+ else if (creature->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN) && ((Guardian*)creature)->GetOwner()->GetTypeId() == TYPEID_PLAYER)
+ ai_factory = ai_registry.GetRegistryItem("PetAI");
+ else if (creature->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK))
+ ai_factory = ai_registry.GetRegistryItem("NullCreatureAI");
+ else if (creature->isGuard())
+ ai_factory = ai_registry.GetRegistryItem("GuardAI");
+ else if (creature->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN))
+ ai_factory = ai_registry.GetRegistryItem("PetAI");
+ else if (creature->isTotem())
+ ai_factory = ai_registry.GetRegistryItem("TotemAI");
+ else if (creature->isTrigger())
+ {
+ if (creature->m_spells[0])
+ ai_factory = ai_registry.GetRegistryItem("TriggerAI");
+ else
+ ai_factory = ai_registry.GetRegistryItem("NullCreatureAI");
+ }
+ else if (creature->GetCreatureType() == CREATURE_TYPE_CRITTER && !creature->HasUnitTypeMask(UNIT_MASK_GUARDIAN))
+ ai_factory = ai_registry.GetRegistryItem("CritterAI");
+ }
+
+ // select by permit check
+ if (!ai_factory)
+ {
+ int best_val = -1;
+ typedef CreatureAIRegistry::RegistryMapType RMT;
+ RMT const &l = ai_registry.GetRegisteredItems();
+ for (RMT::const_iterator iter = l.begin(); iter != l.end(); ++iter)
+ {
+ const CreatureAICreator *factory = iter->second;
+ const SelectableAI *p = dynamic_cast<const SelectableAI *>(factory);
+ assert(p != NULL);
+ int val = p->Permit(creature);
+ if (val > best_val)
+ {
+ best_val = val;
+ ai_factory = p;
+ }
+ }
+ }
+
+ // select NullCreatureAI if not another cases
+ ainame = (ai_factory == NULL) ? "NullCreatureAI" : ai_factory->key();
+
+ DEBUG_LOG("Creature %u used AI is %s.", creature->GetGUIDLow(), ainame.c_str());
+ return (ai_factory == NULL ? new NullCreatureAI(creature) : ai_factory->Create(creature));
+ }
+
+ MovementGenerator* selectMovementGenerator(Creature *creature)
+ {
+ MovementGeneratorRegistry &mv_registry(MovementGeneratorRepository::Instance());
+ assert(creature->GetCreatureInfo() != NULL);
+ const MovementGeneratorCreator *mv_factory = mv_registry.GetRegistryItem(creature->GetDefaultMovementType());
+
+ /* if (mv_factory == NULL)
+ {
+ int best_val = -1;
+ std::vector<std::string> l;
+ mv_registry.GetRegisteredItems(l);
+ for (std::vector<std::string>::iterator iter = l.begin(); iter != l.end(); ++iter)
+ {
+ const MovementGeneratorCreator *factory = mv_registry.GetRegistryItem((*iter).c_str());
+ const SelectableMovement *p = dynamic_cast<const SelectableMovement *>(factory);
+ assert(p != NULL);
+ int val = p->Permit(creature);
+ if (val > best_val)
+ {
+ best_val = val;
+ mv_factory = p;
+ }
+ }
+ }*/
+
+ return (mv_factory == NULL ? NULL : mv_factory->Create(creature));
+
+ }
+}
+
diff --git a/src/server/game/AI/CreatureAISelector.h b/src/server/game/AI/CreatureAISelector.h
new file mode 100644
index 00000000000..98eeee809d2
--- /dev/null
+++ b/src/server/game/AI/CreatureAISelector.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef TRINITY_CREATUREAISELECTOR_H
+#define TRINITY_CREATUREAISELECTOR_H
+
+class CreatureAI;
+class Creature;
+class MovementGenerator;
+
+namespace FactorySelector
+{
+ CreatureAI* selectAI(Creature *);
+ MovementGenerator* selectMovementGenerator(Creature *);
+}
+#endif
+
diff --git a/src/server/game/AI/EventAI/CreatureEventAI.cpp b/src/server/game/AI/EventAI/CreatureEventAI.cpp
new file mode 100644
index 00000000000..47c8e9e6ad8
--- /dev/null
+++ b/src/server/game/AI/EventAI/CreatureEventAI.cpp
@@ -0,0 +1,1384 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "Common.h"
+#include "CreatureEventAI.h"
+#include "CreatureEventAIMgr.h"
+#include "ObjectMgr.h"
+#include "Spell.h"
+#include "World.h"
+#include "Cell.h"
+#include "CellImpl.h"
+#include "GameEventMgr.h"
+#include "GridNotifiers.h"
+#include "GridNotifiersImpl.h"
+#include "InstanceData.h"
+#include "SpellMgr.h"
+#include "CreatureAIImpl.h"
+#include "ConditionMgr.h"
+
+bool CreatureEventAIHolder::UpdateRepeatTimer(Creature* creature, uint32 repeatMin, uint32 repeatMax)
+{
+ if (repeatMin == repeatMax)
+ Time = repeatMin;
+ else if (repeatMax > repeatMin)
+ Time = urand(repeatMin, repeatMax);
+ else
+ {
+ sLog.outErrorDb("CreatureEventAI: Creature %u using Event %u (Type = %u) has RandomMax < RandomMin. Event repeating disabled.", creature->GetEntry(), Event.event_id, Event.event_type);
+ Enabled = false;
+ return false;
+ }
+
+ return true;
+}
+
+int CreatureEventAI::Permissible(const Creature *creature)
+{
+ if (creature->GetAIName() == "EventAI")
+ return PERMIT_BASE_SPECIAL;
+ return PERMIT_BASE_NO;
+}
+
+CreatureEventAI::CreatureEventAI(Creature *c) : CreatureAI(c)
+{
+ // Need make copy for filter unneeded steps and safe in case table reload
+ CreatureEventAI_Event_Map::const_iterator CreatureEvents = CreatureEAI_Mgr.GetCreatureEventAIMap().find(me->GetEntry());
+ if (CreatureEvents != CreatureEAI_Mgr.GetCreatureEventAIMap().end())
+ {
+ std::vector<CreatureEventAI_Event>::const_iterator i;
+ for (i = (*CreatureEvents).second.begin(); i != (*CreatureEvents).second.end(); ++i)
+ {
+
+ //Debug check
+ #ifndef TRINITY_DEBUG
+ if ((*i).event_flags & EFLAG_DEBUG_ONLY)
+ continue;
+ #endif
+ if (me->GetMap()->IsDungeon())
+ {
+ if ((1 << (me->GetMap()->GetSpawnMode()+1)) & (*i).event_flags)
+ {
+ //event flagged for instance mode
+ CreatureEventAIList.push_back(CreatureEventAIHolder(*i));
+ }
+ continue;
+ }
+ CreatureEventAIList.push_back(CreatureEventAIHolder(*i));
+ }
+ //EventMap had events but they were not added because they must be for instance
+ if (CreatureEventAIList.empty())
+ sLog.outError("CreatureEventAI: Creature %u has events but no events added to list because of instance flags.", me->GetEntry());
+ }
+ else
+ sLog.outError("CreatureEventAI: EventMap for Creature %u is empty but creature is using CreatureEventAI.", me->GetEntry());
+
+ bEmptyList = CreatureEventAIList.empty();
+ Phase = 0;
+ CombatMovementEnabled = true;
+ MeleeEnabled = true;
+ AttackDistance = 0.0f;
+ AttackAngle = 0.0f;
+
+ InvinceabilityHpLevel = 0;
+
+ //Handle Spawned Events
+ if (!bEmptyList)
+ {
+ for (std::list<CreatureEventAIHolder>::iterator i = CreatureEventAIList.begin(); i != CreatureEventAIList.end(); ++i)
+ if (SpawnedEventConditionsCheck((*i).Event))
+ ProcessEvent(*i);
+ }
+}
+
+bool CreatureEventAI::ProcessEvent(CreatureEventAIHolder& pHolder, Unit* pActionInvoker /*=NULL*/)
+{
+ if (!pHolder.Enabled || pHolder.Time)
+ return false;
+
+ //Check the inverse phase mask (event doesn't trigger if current phase bit is set in mask)
+ if (pHolder.Event.event_inverse_phase_mask & (1 << Phase))
+ return false;
+
+ CreatureEventAI_Event const& event = pHolder.Event;
+
+ //Check event conditions based on the event type, also reset events
+ switch (event.event_type)
+ {
+ case EVENT_T_TIMER:
+ if (!me->isInCombat())
+ return false;
+
+ //Repeat Timers
+ pHolder.UpdateRepeatTimer(me,event.timer.repeatMin,event.timer.repeatMax);
+ break;
+ case EVENT_T_TIMER_OOC:
+ if (me->isInCombat())
+ return false;
+
+ //Repeat Timers
+ pHolder.UpdateRepeatTimer(me,event.timer.repeatMin,event.timer.repeatMax);
+ break;
+ case EVENT_T_HP:
+ {
+ if (!me->isInCombat() || !me->GetMaxHealth())
+ return false;
+
+ uint32 perc = (me->GetHealth()*100) / me->GetMaxHealth();
+
+ if (perc > event.percent_range.percentMax || perc < event.percent_range.percentMin)
+ return false;
+
+ //Repeat Timers
+ pHolder.UpdateRepeatTimer(me,event.percent_range.repeatMin,event.percent_range.repeatMax);
+ break;
+ }
+ case EVENT_T_MANA:
+ {
+ if (!me->isInCombat() || !me->GetMaxPower(POWER_MANA))
+ return false;
+
+ uint32 perc = (me->GetPower(POWER_MANA)*100) / me->GetMaxPower(POWER_MANA);
+
+ if (perc > event.percent_range.percentMax || perc < event.percent_range.percentMin)
+ return false;
+
+ //Repeat Timers
+ pHolder.UpdateRepeatTimer(me,event.percent_range.repeatMin,event.percent_range.repeatMax);
+ break;
+ }
+ case EVENT_T_AGGRO:
+ break;
+ case EVENT_T_KILL:
+ //Repeat Timers
+ pHolder.UpdateRepeatTimer(me,event.kill.repeatMin,event.kill.repeatMax);
+ break;
+ case EVENT_T_DEATH:
+ case EVENT_T_EVADE:
+ break;
+ case EVENT_T_SPELLHIT:
+ //Spell hit is special case, param1 and param2 handled within CreatureEventAI::SpellHit
+
+ //Repeat Timers
+ pHolder.UpdateRepeatTimer(me,event.spell_hit.repeatMin,event.spell_hit.repeatMax);
+ break;
+ case EVENT_T_RANGE:
+ //Repeat Timers
+ pHolder.UpdateRepeatTimer(me,event.range.repeatMin,event.range.repeatMax);
+ break;
+ case EVENT_T_OOC_LOS:
+ //Repeat Timers
+ pHolder.UpdateRepeatTimer(me,event.ooc_los.repeatMin,event.ooc_los.repeatMax);
+ break;
+ case EVENT_T_RESET:
+ case EVENT_T_SPAWNED:
+ break;
+ case EVENT_T_TARGET_HP:
+ {
+ if (!me->isInCombat() || !me->getVictim() || !me->getVictim()->GetMaxHealth())
+ return false;
+
+ uint32 perc = (me->getVictim()->GetHealth()*100) / me->getVictim()->GetMaxHealth();
+
+ if (perc > event.percent_range.percentMax || perc < event.percent_range.percentMin)
+ return false;
+
+ //Repeat Timers
+ pHolder.UpdateRepeatTimer(me,event.percent_range.repeatMin,event.percent_range.repeatMax);
+ break;
+ }
+ case EVENT_T_TARGET_CASTING:
+ if (!me->isInCombat() || !me->getVictim() || !me->getVictim()->IsNonMeleeSpellCasted(false, false, true))
+ return false;
+
+ //Repeat Timers
+ pHolder.UpdateRepeatTimer(me,event.target_casting.repeatMin,event.target_casting.repeatMax);
+ break;
+ case EVENT_T_FRIENDLY_HP:
+ {
+ if (!me->isInCombat())
+ return false;
+
+ Unit* pUnit = DoSelectLowestHpFriendly(event.friendly_hp.radius, event.friendly_hp.hpDeficit);
+ if (!pUnit)
+ return false;
+
+ pActionInvoker = pUnit;
+
+ //Repeat Timers
+ pHolder.UpdateRepeatTimer(me,event.friendly_hp.repeatMin,event.friendly_hp.repeatMax);
+ break;
+ }
+ case EVENT_T_FRIENDLY_IS_CC:
+ {
+ if (!me->isInCombat())
+ return false;
+
+ std::list<Creature*> pList;
+ DoFindFriendlyCC(pList, event.friendly_is_cc.radius);
+
+ //List is empty
+ if (pList.empty())
+ return false;
+
+ //We don't really care about the whole list, just return first available
+ pActionInvoker = *(pList.begin());
+
+ //Repeat Timers
+ pHolder.UpdateRepeatTimer(me,event.friendly_is_cc.repeatMin,event.friendly_is_cc.repeatMax);
+ break;
+ }
+ case EVENT_T_FRIENDLY_MISSING_BUFF:
+ {
+ std::list<Creature*> pList;
+ DoFindFriendlyMissingBuff(pList, event.friendly_buff.radius, event.friendly_buff.spellId);
+
+ //List is empty
+ if (pList.empty())
+ return false;
+
+ //We don't really care about the whole list, just return first available
+ pActionInvoker = *(pList.begin());
+
+ //Repeat Timers
+ pHolder.UpdateRepeatTimer(me,event.friendly_buff.repeatMin,event.friendly_buff.repeatMax);
+ break;
+ }
+ case EVENT_T_SUMMONED_UNIT:
+ {
+ //Prevent event from occuring on no unit or non creatures
+ if (!pActionInvoker || pActionInvoker->GetTypeId() != TYPEID_UNIT)
+ return false;
+
+ //Creature id doesn't match up
+ if (pActionInvoker->ToCreature()->GetEntry() != event.summon_unit.creatureId)
+ return false;
+
+ //Repeat Timers
+ pHolder.UpdateRepeatTimer(me,event.summon_unit.repeatMin,event.summon_unit.repeatMax);
+ break;
+ }
+ case EVENT_T_TARGET_MANA:
+ {
+ if (!me->isInCombat() || !me->getVictim() || !me->getVictim()->GetMaxPower(POWER_MANA))
+ return false;
+
+ uint32 perc = (me->getVictim()->GetPower(POWER_MANA)*100) / me->getVictim()->GetMaxPower(POWER_MANA);
+
+ if (perc > event.percent_range.percentMax || perc < event.percent_range.percentMin)
+ return false;
+
+ //Repeat Timers
+ pHolder.UpdateRepeatTimer(me,event.percent_range.repeatMin,event.percent_range.repeatMax);
+ break;
+ }
+ case EVENT_T_REACHED_HOME:
+ case EVENT_T_RECEIVE_EMOTE:
+ break;
+ case EVENT_T_BUFFED:
+ {
+ //Note: checked only aura for effect 0, if need check aura for effect 1/2 then
+ // possible way: pack in event.buffed.amount 2 uint16 (ammount+effectIdx)
+ Aura const * aura = me->GetAura(event.buffed.spellId);
+ if (!aura || aura->GetStackAmount() < event.buffed.amount)
+ return false;
+
+ //Repeat Timers
+ pHolder.UpdateRepeatTimer(me,event.buffed.repeatMin,event.buffed.repeatMax);
+ break;
+ }
+ case EVENT_T_TARGET_BUFFED:
+ {
+ //Prevent event from occuring on no unit
+ if (!pActionInvoker)
+ return false;
+
+ //Note: checked only aura for effect 0, if need check aura for effect 1/2 then
+ // possible way: pack in event.buffed.amount 2 uint16 (ammount+effectIdx)
+ Aura const * aura = pActionInvoker->GetAura(event.buffed.spellId);
+ if (!aura || aura->GetStackAmount() < event.buffed.amount)
+ return false;
+
+ //Repeat Timers
+ pHolder.UpdateRepeatTimer(me,event.buffed.repeatMin,event.buffed.repeatMax);
+ break;
+ }
+ default:
+ sLog.outErrorDb("CreatureEventAI: Creature %u using Event %u has invalid Event Type(%u), missing from ProcessEvent() Switch.", me->GetEntry(), pHolder.Event.event_id, pHolder.Event.event_type);
+ break;
+ }
+
+ //Disable non-repeatable events
+ if (!(pHolder.Event.event_flags & EFLAG_REPEATABLE))
+ pHolder.Enabled = false;
+
+ //Store random here so that all random actions match up
+ uint32 rnd = rand();
+
+ //Return if chance for event is not met
+ if (pHolder.Event.event_chance <= rnd % 100)
+ return false;
+
+ //Process actions
+ for (uint8 j = 0; j < MAX_ACTIONS; ++j)
+ ProcessAction(pHolder.Event.action[j], rnd, pHolder.Event.event_id, pActionInvoker);
+
+ return true;
+}
+
+void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 rnd, uint32 EventId, Unit* pActionInvoker)
+{
+ switch (action.type)
+ {
+ case ACTION_T_TEXT:
+ {
+ if (!action.text.TextId1)
+ return;
+
+ int32 temp = 0;
+
+ if (action.text.TextId2 && action.text.TextId3)
+ temp = RAND(action.text.TextId1,action.text.TextId2,action.text.TextId3);
+ else if (action.text.TextId2 && urand(0,1))
+ temp = action.text.TextId2;
+ else
+ temp = action.text.TextId1;
+
+ if (temp)
+ {
+ Unit* target = NULL;
+
+ if (pActionInvoker)
+ {
+ if (pActionInvoker->GetTypeId() == TYPEID_PLAYER)
+ target = pActionInvoker;
+ else if (Unit* owner = pActionInvoker->GetOwner())
+ {
+ if (owner->GetTypeId() == TYPEID_PLAYER)
+ target = owner;
+ }
+ }
+ else
+ {
+ target = me->getVictim();
+ if (target && target->GetTypeId() != TYPEID_PLAYER)
+ if (Unit* owner = target->GetOwner())
+ if (owner->GetTypeId() == TYPEID_PLAYER)
+ target = owner;
+ }
+
+ DoScriptText(temp, me, target);
+ }
+ break;
+ }
+ case ACTION_T_SET_FACTION:
+ {
+ if (action.set_faction.factionId)
+ me->setFaction(action.set_faction.factionId);
+ else
+ {
+ if (CreatureInfo const* ci = GetCreatureTemplateStore(me->GetEntry()))
+ {
+ //if no id provided, assume reset and then use default
+ if (me->getFaction() != ci->faction_A)
+ me->setFaction(ci->faction_A);
+ }
+ }
+ break;
+ }
+ case ACTION_T_MORPH_TO_ENTRY_OR_MODEL:
+ {
+ if (action.morph.creatureId || action.morph.modelId)
+ {
+ //set model based on entry from creature_template
+ if (action.morph.creatureId)
+ {
+ if (CreatureInfo const* ci = GetCreatureTemplateStore(action.morph.creatureId))
+ {
+ uint32 display_id = objmgr.ChooseDisplayId(0,ci);
+ me->SetDisplayId(display_id);
+ }
+ }
+ //if no param1, then use value from param2 (modelId)
+ else
+ me->SetDisplayId(action.morph.modelId);
+ }
+ else
+ me->DeMorph();
+ break;
+ }
+ case ACTION_T_SOUND:
+ me->PlayDirectSound(action.sound.soundId);
+ break;
+ case ACTION_T_EMOTE:
+ me->HandleEmoteCommand(action.emote.emoteId);
+ break;
+ case ACTION_T_RANDOM_SOUND:
+ {
+ int32 temp = GetRandActionParam(rnd, action.random_sound.soundId1, action.random_sound.soundId2, action.random_sound.soundId3);
+ if (temp >= 0)
+ me->PlayDirectSound(temp);
+ break;
+ }
+ case ACTION_T_RANDOM_EMOTE:
+ {
+ int32 temp = GetRandActionParam(rnd, action.random_emote.emoteId1, action.random_emote.emoteId2, action.random_emote.emoteId3);
+ if (temp >= 0)
+ me->HandleEmoteCommand(temp);
+ break;
+ }
+ case ACTION_T_CAST:
+ {
+ Unit* target = GetTargetByType(action.cast.target, pActionInvoker);
+ Unit* caster = me;
+
+ if (!target)
+ return;
+
+ if (action.cast.castFlags & CAST_FORCE_TARGET_SELF)
+ caster = target;
+
+ //Allowed to cast only if not casting (unless we interrupt ourself) or if spell is triggered
+ bool canCast = !caster->IsNonMeleeSpellCasted(false) || (action.cast.castFlags & (CAST_TRIGGERED | CAST_INTURRUPT_PREVIOUS));
+
+ // If cast flag CAST_AURA_NOT_PRESENT is active, check if target already has aura on them
+ if (action.cast.castFlags & CAST_AURA_NOT_PRESENT)
+ {
+ if (target->HasAura(action.cast.spellId))
+ return;
+ }
+
+ if (canCast)
+ {
+ const SpellEntry* tSpell = GetSpellStore()->LookupEntry(action.cast.spellId);
+
+ //Verify that spell exists
+ if (tSpell)
+ {
+ //Check if cannot cast spell
+ if (!(action.cast.castFlags & (CAST_FORCE_TARGET_SELF | CAST_FORCE_CAST)) &&
+ !CanCast(target, tSpell, (action.cast.castFlags & CAST_TRIGGERED)))
+ {
+ //Melee current victim if flag not set
+ if (!(action.cast.castFlags & CAST_NO_MELEE_IF_OOM))
+ {
+ if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == TARGETED_MOTION_TYPE)
+ {
+ AttackDistance = 0.0f;
+ AttackAngle = 0.0f;
+
+ me->GetMotionMaster()->MoveChase(me->getVictim(), AttackDistance, AttackAngle);
+ }
+ }
+
+ }
+ else
+ {
+ //Interrupt any previous spell
+ if (caster->IsNonMeleeSpellCasted(false) && action.cast.castFlags & CAST_INTURRUPT_PREVIOUS)
+ caster->InterruptNonMeleeSpells(false);
+
+ caster->CastSpell(target, action.cast.spellId, (action.cast.castFlags & CAST_TRIGGERED));
+ }
+
+ }
+ else
+ sLog.outErrorDb("CreatureEventAI: event %d creature %d attempt to cast spell that doesn't exist %d", EventId, me->GetEntry(), action.cast.spellId);
+ }
+ break;
+ }
+ case ACTION_T_SUMMON:
+ {
+ Unit* target = GetTargetByType(action.summon.target, pActionInvoker);
+
+ Creature* pCreature = NULL;
+
+ if (action.summon.duration)
+ pCreature = me->SummonCreature(action.summon.creatureId, 0.0f, 0.0f, 0.0f, 0.0f, TEMPSUMMON_TIMED_OR_DEAD_DESPAWN, action.summon.duration);
+ else
+ pCreature = me->SummonCreature(action.summon.creatureId, 0.0f, 0.0f, 0.0f, 0.0f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 0);
+
+ if (!pCreature)
+ sLog.outErrorDb("CreatureEventAI: failed to spawn creature %u. Spawn event %d is on creature %d", action.summon.creatureId, EventId, me->GetEntry());
+ else if (action.summon.target != TARGET_T_SELF && target)
+ pCreature->AI()->AttackStart(target);
+ break;
+ }
+ case ACTION_T_THREAT_SINGLE_PCT:
+ if (Unit* target = GetTargetByType(action.threat_single_pct.target, pActionInvoker))
+ me->getThreatManager().modifyThreatPercent(target, action.threat_single_pct.percent);
+ break;
+ case ACTION_T_THREAT_ALL_PCT:
+ {
+ std::list<HostileReference*>& threatList = me->getThreatManager().getThreatList();
+ for (std::list<HostileReference*>::iterator i = threatList.begin(); i != threatList.end(); ++i)
+ if (Unit* Temp = Unit::GetUnit(*me,(*i)->getUnitGuid()))
+ me->getThreatManager().modifyThreatPercent(Temp, action.threat_all_pct.percent);
+ break;
+ }
+ case ACTION_T_QUEST_EVENT:
+ if (Unit* target = GetTargetByType(action.quest_event.target, pActionInvoker))
+ if (target->GetTypeId() == TYPEID_PLAYER)
+ target->ToPlayer()->AreaExploredOrEventHappens(action.quest_event.questId);
+ break;
+ case ACTION_T_CAST_EVENT:
+ if (Unit* target = GetTargetByType(action.cast_event.target, pActionInvoker))
+ if (target->GetTypeId() == TYPEID_PLAYER)
+ target->ToPlayer()->CastedCreatureOrGO(action.cast_event.creatureId, me->GetGUID(), action.cast_event.spellId);
+ break;
+ case ACTION_T_SET_UNIT_FIELD:
+ {
+ Unit* target = GetTargetByType(action.set_unit_field.target, pActionInvoker);
+
+ // not allow modify important for integrity object fields
+ if (action.set_unit_field.field < OBJECT_END || action.set_unit_field.field >= UNIT_END)
+ return;
+
+ if (target)
+ target->SetUInt32Value(action.set_unit_field.field, action.set_unit_field.value);
+
+ break;
+ }
+ case ACTION_T_SET_UNIT_FLAG:
+ if (Unit* target = GetTargetByType(action.unit_flag.target, pActionInvoker))
+ target->SetFlag(UNIT_FIELD_FLAGS, action.unit_flag.value);
+ break;
+ case ACTION_T_REMOVE_UNIT_FLAG:
+ if (Unit* target = GetTargetByType(action.unit_flag.target, pActionInvoker))
+ target->RemoveFlag(UNIT_FIELD_FLAGS, action.unit_flag.value);
+ break;
+ case ACTION_T_AUTO_ATTACK:
+ MeleeEnabled = action.auto_attack.state != 0;
+ break;
+ case ACTION_T_COMBAT_MOVEMENT:
+ // ignore no affect case
+ if (CombatMovementEnabled == (action.combat_movement.state != 0))
+ return;
+
+ CombatMovementEnabled = action.combat_movement.state != 0;
+
+ //Allow movement (create new targeted movement gen only if idle)
+ if (CombatMovementEnabled)
+ {
+ Unit* victim = me->getVictim();
+ if (me->isInCombat() && victim)
+ {
+ if (action.combat_movement.melee)
+ {
+ me->addUnitState(UNIT_STAT_MELEE_ATTACKING);
+ me->SendMeleeAttackStart(victim);
+ }
+ if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == IDLE_MOTION_TYPE)
+ me->GetMotionMaster()->MoveChase(victim, AttackDistance, AttackAngle); // Targeted movement generator will start melee automatically, no need to send it explicitly
+ }
+ }
+ else
+ {
+ if (me->isInCombat())
+ {
+ Unit* victim = me->getVictim();
+ if (action.combat_movement.melee && victim)
+ {
+ me->clearUnitState(UNIT_STAT_MELEE_ATTACKING);
+ me->SendMeleeAttackStop(victim);
+ }
+ if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == TARGETED_MOTION_TYPE)
+ me->GetMotionMaster()->MoveIdle();
+ }
+ }
+ break;
+ case ACTION_T_SET_PHASE:
+ Phase = action.set_phase.phase;
+ break;
+ case ACTION_T_INC_PHASE:
+ {
+ int32 new_phase = int32(Phase)+action.set_inc_phase.step;
+ if (new_phase < 0)
+ {
+ sLog.outErrorDb("CreatureEventAI: Event %d decrease Phase under 0. CreatureEntry = %d", EventId, me->GetEntry());
+ Phase = 0;
+ }
+ else if (new_phase >= MAX_PHASE)
+ {
+ sLog.outErrorDb("CreatureEventAI: Event %d incremented Phase above %u. Phase mask cannot be used with phases past %u. CreatureEntry = %d", EventId, MAX_PHASE-1, MAX_PHASE-1, me->GetEntry());
+ Phase = MAX_PHASE-1;
+ }
+ else
+ Phase = new_phase;
+
+ break;
+ }
+ case ACTION_T_EVADE:
+ EnterEvadeMode();
+ break;
+ case ACTION_T_FLEE_FOR_ASSIST:
+ me->DoFleeToGetAssistance();
+ break;
+ case ACTION_T_QUEST_EVENT_ALL:
+ if (pActionInvoker && pActionInvoker->GetTypeId() == TYPEID_PLAYER)
+ {
+ if (Unit* Temp = Unit::GetUnit(*me,pActionInvoker->GetGUID()))
+ if (Temp->GetTypeId() == TYPEID_PLAYER)
+ Temp->ToPlayer()->GroupEventHappens(action.quest_event_all.questId,me);
+ }
+ break;
+ case ACTION_T_CAST_EVENT_ALL:
+ {
+ std::list<HostileReference*>& threatList = me->getThreatManager().getThreatList();
+ for (std::list<HostileReference*>::iterator i = threatList.begin(); i != threatList.end(); ++i)
+ if (Unit* Temp = Unit::GetUnit(*me,(*i)->getUnitGuid()))
+ if (Temp->GetTypeId() == TYPEID_PLAYER)
+ Temp->ToPlayer()->CastedCreatureOrGO(action.cast_event_all.creatureId, me->GetGUID(), action.cast_event_all.spellId);
+ break;
+ }
+ case ACTION_T_REMOVEAURASFROMSPELL:
+ if (Unit* target = GetTargetByType(action.remove_aura.target, pActionInvoker))
+ target->RemoveAurasDueToSpell(action.remove_aura.spellId);
+ break;
+ case ACTION_T_RANGED_MOVEMENT:
+ AttackDistance = (float)action.ranged_movement.distance;
+ AttackAngle = action.ranged_movement.angle/180.0f*M_PI;
+
+ if (CombatMovementEnabled)
+ {
+ me->GetMotionMaster()->MoveChase(me->getVictim(), AttackDistance, AttackAngle);
+ }
+ break;
+ case ACTION_T_RANDOM_PHASE:
+ Phase = GetRandActionParam(rnd, action.random_phase.phase1, action.random_phase.phase2, action.random_phase.phase3);
+ break;
+ case ACTION_T_RANDOM_PHASE_RANGE:
+ if (action.random_phase_range.phaseMin <= action.random_phase_range.phaseMax)
+ Phase = urand(action.random_phase_range.phaseMin, action.random_phase_range.phaseMax);
+ else
+ sLog.outErrorDb("CreatureEventAI: ACTION_T_RANDOM_PHASE_RANGE cannot have Param2 < Param1. Event = %d. CreatureEntry = %d", EventId, me->GetEntry());
+ break;
+ case ACTION_T_SUMMON_ID:
+ {
+ Unit* target = GetTargetByType(action.summon_id.target, pActionInvoker);
+
+ CreatureEventAI_Summon_Map::const_iterator i = CreatureEAI_Mgr.GetCreatureEventAISummonMap().find(action.summon_id.spawnId);
+ if (i == CreatureEAI_Mgr.GetCreatureEventAISummonMap().end())
+ {
+ sLog.outErrorDb("CreatureEventAI: failed to spawn creature %u. Summon map index %u does not exist. EventID %d. CreatureID %d", action.summon_id.creatureId, action.summon_id.spawnId, EventId, me->GetEntry());
+ return;
+ }
+
+ Creature* pCreature = NULL;
+ if ((*i).second.SpawnTimeSecs)
+ pCreature = me->SummonCreature(action.summon_id.creatureId, (*i).second.position_x, (*i).second.position_y, (*i).second.position_z, (*i).second.orientation, TEMPSUMMON_TIMED_OR_DEAD_DESPAWN, (*i).second.SpawnTimeSecs);
+ else
+ pCreature = me->SummonCreature(action.summon_id.creatureId, (*i).second.position_x, (*i).second.position_y, (*i).second.position_z, (*i).second.orientation, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 0);
+
+ if (!pCreature)
+ sLog.outErrorDb("CreatureEventAI: failed to spawn creature %u. EventId %d.Creature %d", action.summon_id.creatureId, EventId, me->GetEntry());
+ else if (action.summon_id.target != TARGET_T_SELF && target)
+ pCreature->AI()->AttackStart(target);
+
+ break;
+ }
+ case ACTION_T_KILLED_MONSTER:
+ //first attempt player who tapped creature
+ if (Player* pPlayer = me->GetLootRecipient())
+ pPlayer->RewardPlayerAndGroupAtEvent(action.killed_monster.creatureId, me);
+ else
+ {
+ //if not available, use pActionInvoker
+ if (Unit* pTarget = GetTargetByType(action.killed_monster.target, pActionInvoker))
+ if (Player* pPlayer2 = pTarget->GetCharmerOrOwnerPlayerOrPlayerItself())
+ pPlayer2->RewardPlayerAndGroupAtEvent(action.killed_monster.creatureId, me);
+ }
+ break;
+ case ACTION_T_SET_INST_DATA:
+ {
+ InstanceData* pInst = (InstanceData*)me->GetInstanceData();
+ if (!pInst)
+ {
+ sLog.outErrorDb("CreatureEventAI: Event %d attempt to set instance data without instance script. Creature %d", EventId, me->GetEntry());
+ return;
+ }
+
+ pInst->SetData(action.set_inst_data.field, action.set_inst_data.value);
+ break;
+ }
+ case ACTION_T_SET_INST_DATA64:
+ {
+ Unit* target = GetTargetByType(action.set_inst_data64.target, pActionInvoker);
+ if (!target)
+ {
+ sLog.outErrorDb("CreatureEventAI: Event %d attempt to set instance data64 but Target == NULL. Creature %d", EventId, me->GetEntry());
+ return;
+ }
+
+ InstanceData* pInst = (InstanceData*)me->GetInstanceData();
+ if (!pInst)
+ {
+ sLog.outErrorDb("CreatureEventAI: Event %d attempt to set instance data64 without instance script. Creature %d", EventId, me->GetEntry());
+ return;
+ }
+
+ pInst->SetData64(action.set_inst_data64.field, target->GetGUID());
+ break;
+ }
+ case ACTION_T_UPDATE_TEMPLATE:
+ if (me->GetEntry() == action.update_template.creatureId)
+ {
+
+ sLog.outErrorDb("CreatureEventAI: Event %d ACTION_T_UPDATE_TEMPLATE call with param1 == current entry. Creature %d", EventId, me->GetEntry());
+ return;
+ }
+
+ me->UpdateEntry(action.update_template.creatureId, action.update_template.team ? HORDE : ALLIANCE);
+ break;
+ case ACTION_T_DIE:
+ if (me->isDead())
+ {
+
+ sLog.outErrorDb("CreatureEventAI: Event %d ACTION_T_DIE on dead creature. Creature %d", EventId, me->GetEntry());
+ return;
+ }
+ me->Kill(me);
+ break;
+ case ACTION_T_ZONE_COMBAT_PULSE:
+ {
+ me->SetInCombatWithZone();
+ break;
+ }
+ case ACTION_T_CALL_FOR_HELP:
+ {
+ me->CallForHelp(action.call_for_help.radius);
+ break;
+ }
+ break;
+
+ // TRINITY ONLY
+ case ACTION_T_MOVE_RANDOM_POINT: //dosen't work in combat
+ {
+ float x,y,z;
+ me->GetClosePoint(x, y, z, me->GetObjectSize() / 3, action.raw.param1);
+ me->GetMotionMaster()->MovePoint(0,x,y,z);
+ break;
+ }
+ case ACTION_T_SET_STAND_STATE:
+ me->SetStandState(UnitStandStateType(action.raw.param1));
+ break;
+ case ACTION_T_SET_PHASE_MASK:
+ me->SetPhaseMask(action.raw.param1, true);
+ break;
+ case ACTION_T_SET_VISIBILITY:
+ me->SetVisibility(UnitVisibility(action.raw.param1));
+ break;
+ case ACTION_T_SET_ACTIVE:
+ me->setActive(action.raw.param1 ? true : false);
+ break;
+ case ACTION_T_SET_AGGRESSIVE:
+ me->SetReactState(ReactStates(action.raw.param1));
+ break;
+ case ACTION_T_ATTACK_START_PULSE:
+ AttackStart(me->SelectNearestTarget((float)action.raw.param1));
+ break;
+ case ACTION_T_SUMMON_GO:
+ {
+ GameObject* pObject = NULL;
+
+ float x,y,z;
+ me->GetPosition(x,y,z);
+ pObject = me->SummonGameObject(action.raw.param1, x, y, z, 0, 0, 0, 0, 0, action.raw.param2);
+ if (!pObject)
+ {
+ sLog.outErrorDb("TSCR: EventAI failed to spawn object %u. Spawn event %d is on creature %d", action.raw.param1, EventId, me->GetEntry());
+ }
+ break;
+ }
+
+ case ACTION_T_SET_SHEATH:
+ {
+ me->SetSheath(SheathState(action.set_sheath.sheath));
+ break;
+ }
+ case ACTION_T_FORCE_DESPAWN:
+ {
+ me->ForcedDespawn(action.forced_despawn.msDelay);
+ break;
+ }
+ case ACTION_T_SET_INVINCIBILITY_HP_LEVEL:
+ {
+ if (action.invincibility_hp_level.is_percent)
+ InvinceabilityHpLevel = me->GetMaxHealth()*action.invincibility_hp_level.hp_level/100;
+ else
+ InvinceabilityHpLevel = action.invincibility_hp_level.hp_level;
+ break;
+ }
+ }
+}
+
+void CreatureEventAI::JustRespawned()
+{
+ Reset();
+
+ if (bEmptyList)
+ return;
+
+ //Handle Spawned Events
+ for (std::list<CreatureEventAIHolder>::iterator i = CreatureEventAIList.begin(); i != CreatureEventAIList.end(); ++i)
+ if (SpawnedEventConditionsCheck((*i).Event))
+ ProcessEvent(*i);
+}
+
+void CreatureEventAI::Reset()
+{
+ EventUpdateTime = EVENT_UPDATE_TIME;
+ EventDiff = 0;
+
+ if (bEmptyList)
+ return;
+
+
+ for (std::list<CreatureEventAIHolder>::iterator i = CreatureEventAIList.begin(); i != CreatureEventAIList.end(); ++i)
+ {
+ if ((*i).Event.event_type == EVENT_T_RESET)
+ ProcessEvent(*i);
+ }
+
+
+ //Reset all events to enabled
+ for (std::list<CreatureEventAIHolder>::iterator i = CreatureEventAIList.begin(); i != CreatureEventAIList.end(); ++i)
+ {
+ CreatureEventAI_Event const& event = (*i).Event;
+ switch (event.event_type)
+ {
+ //Reset all out of combat timers
+ case EVENT_T_TIMER_OOC:
+ {
+ if ((*i).UpdateRepeatTimer(me,event.timer.initialMin,event.timer.initialMax))
+ (*i).Enabled = true;
+ break;
+ }
+ //default:
+ //TODO: enable below code line / verify this is correct to enable events previously disabled (ex. aggro yell), instead of enable this in void EnterCombat()
+ //(*i).Enabled = true;
+ //(*i).Time = 0;
+ //break;
+ }
+ }
+}
+
+void CreatureEventAI::JustReachedHome()
+{
+ me->LoadCreaturesAddon();
+
+ if (!bEmptyList)
+ {
+ for (std::list<CreatureEventAIHolder>::iterator i = CreatureEventAIList.begin(); i != CreatureEventAIList.end(); ++i)
+ {
+ if ((*i).Event.event_type == EVENT_T_REACHED_HOME)
+ ProcessEvent(*i);
+ }
+ }
+
+ Reset();
+}
+
+void CreatureEventAI::EnterEvadeMode()
+{
+ CreatureAI::EnterEvadeMode();
+
+ if (bEmptyList)
+ return;
+
+ //Handle Evade events
+ for (std::list<CreatureEventAIHolder>::iterator i = CreatureEventAIList.begin(); i != CreatureEventAIList.end(); ++i)
+ {
+ if ((*i).Event.event_type == EVENT_T_EVADE)
+ ProcessEvent(*i);
+ }
+}
+
+void CreatureEventAI::JustDied(Unit* killer)
+{
+ Reset();
+
+ if (bEmptyList)
+ return;
+
+ //Handle Evade events
+ for (std::list<CreatureEventAIHolder>::iterator i = CreatureEventAIList.begin(); i != CreatureEventAIList.end(); ++i)
+ {
+ if ((*i).Event.event_type == EVENT_T_DEATH)
+ ProcessEvent(*i, killer);
+ }
+
+ // reset phase after any death state events
+ Phase = 0;
+}
+
+void CreatureEventAI::KilledUnit(Unit* victim)
+{
+ if (bEmptyList || victim->GetTypeId() != TYPEID_PLAYER)
+ return;
+
+ for (std::list<CreatureEventAIHolder>::iterator i = CreatureEventAIList.begin(); i != CreatureEventAIList.end(); ++i)
+ {
+ if ((*i).Event.event_type == EVENT_T_KILL)
+ ProcessEvent(*i, victim);
+ }
+}
+
+void CreatureEventAI::JustSummoned(Creature* pUnit)
+{
+ if (bEmptyList || !pUnit)
+ return;
+
+ for (std::list<CreatureEventAIHolder>::iterator i = CreatureEventAIList.begin(); i != CreatureEventAIList.end(); ++i)
+ {
+ if ((*i).Event.event_type == EVENT_T_SUMMONED_UNIT)
+ ProcessEvent(*i, pUnit);
+ }
+}
+
+void CreatureEventAI::EnterCombat(Unit *enemy)
+{
+ //Check for on combat start events
+ if (!bEmptyList)
+ {
+ for (std::list<CreatureEventAIHolder>::iterator i = CreatureEventAIList.begin(); i != CreatureEventAIList.end(); ++i)
+ {
+ CreatureEventAI_Event const& event = (*i).Event;
+ switch (event.event_type)
+ {
+ case EVENT_T_AGGRO:
+ (*i).Enabled = true;
+ ProcessEvent(*i, enemy);
+ break;
+ //Reset all in combat timers
+ case EVENT_T_TIMER:
+ if ((*i).UpdateRepeatTimer(me,event.timer.initialMin,event.timer.initialMax))
+ (*i).Enabled = true;
+ break;
+ //All normal events need to be re-enabled and their time set to 0
+ default:
+ (*i).Enabled = true;
+ (*i).Time = 0;
+ break;
+ }
+ }
+ }
+
+ EventUpdateTime = EVENT_UPDATE_TIME;
+ EventDiff = 0;
+}
+
+void CreatureEventAI::AttackStart(Unit *who)
+{
+ if (!who)
+ return;
+
+ if (me->Attack(who, MeleeEnabled))
+ {
+ if (CombatMovementEnabled)
+ {
+ me->GetMotionMaster()->MoveChase(who, AttackDistance, AttackAngle);
+ }
+ else
+ {
+ me->GetMotionMaster()->MoveIdle();
+ }
+ }
+}
+
+void CreatureEventAI::MoveInLineOfSight(Unit *who)
+{
+ if (me->getVictim())
+ return;
+
+ //Check for OOC LOS Event
+ if (!bEmptyList)
+ {
+ for (std::list<CreatureEventAIHolder>::iterator itr = CreatureEventAIList.begin(); itr != CreatureEventAIList.end(); ++itr)
+ {
+ if ((*itr).Event.event_type == EVENT_T_OOC_LOS)
+ {
+ //can trigger if closer than fMaxAllowedRange
+ float fMaxAllowedRange = (*itr).Event.ooc_los.maxRange;
+
+ //if range is ok and we are actually in LOS
+ if (me->IsWithinDistInMap(who, fMaxAllowedRange) && me->IsWithinLOSInMap(who))
+ {
+ //if friendly event&&who is not hostile OR hostile event&&who is hostile
+ if (((*itr).Event.ooc_los.noHostile && !me->IsHostileTo(who)) ||
+ ((!(*itr).Event.ooc_los.noHostile) && me->IsHostileTo(who)))
+ ProcessEvent(*itr, who);
+ }
+ }
+ }
+ }
+
+ CreatureAI::MoveInLineOfSight(who);
+}
+
+void CreatureEventAI::SpellHit(Unit* pUnit, const SpellEntry* pSpell)
+{
+
+ if (bEmptyList)
+ return;
+
+ for (std::list<CreatureEventAIHolder>::iterator i = CreatureEventAIList.begin(); i != CreatureEventAIList.end(); ++i)
+ if ((*i).Event.event_type == EVENT_T_SPELLHIT)
+ //If spell id matches (or no spell id) & if spell school matches (or no spell school)
+ if (!(*i).Event.spell_hit.spellId || pSpell->Id == (*i).Event.spell_hit.spellId)
+ if (pSpell->SchoolMask & (*i).Event.spell_hit.schoolMask)
+ ProcessEvent(*i, pUnit);
+}
+
+void CreatureEventAI::UpdateAI(const uint32 diff)
+{
+ //Check if we are in combat (also updates calls threat update code)
+ bool Combat = UpdateVictim();
+
+ if (!bEmptyList)
+ {
+ //Events are only updated once every EVENT_UPDATE_TIME ms to prevent lag with large amount of events
+ if (EventUpdateTime <= diff)
+ {
+ EventDiff += diff;
+
+ //Check for time based events
+ for (std::list<CreatureEventAIHolder>::iterator i = CreatureEventAIList.begin(); i != CreatureEventAIList.end(); ++i)
+ {
+ //Decrement Timers
+ if ((*i).Time)
+ {
+ if (EventDiff <= (*i).Time)
+ {
+ //Do not decrement timers if event cannot trigger in this phase
+ if (!((*i).Event.event_inverse_phase_mask & (1 << Phase)))
+ (*i).Time -= EventDiff;
+
+ //Skip processing of events that have time remaining
+ continue;
+ }
+ else (*i).Time = 0;
+ }
+
+ //Events that are updated every EVENT_UPDATE_TIME
+ switch ((*i).Event.event_type)
+ {
+ case EVENT_T_TIMER_OOC:
+ ProcessEvent(*i);
+ break;
+ case EVENT_T_TIMER:
+ case EVENT_T_MANA:
+ case EVENT_T_HP:
+ case EVENT_T_TARGET_HP:
+ case EVENT_T_TARGET_CASTING:
+ case EVENT_T_FRIENDLY_HP:
+ if (me->getVictim())
+ ProcessEvent(*i);
+ break;
+ case EVENT_T_RANGE:
+ if (me->getVictim())
+ if (me->IsInMap(me->getVictim()))
+ if (me->IsInRange(me->getVictim(),(float)(*i).Event.range.minDist,(float)(*i).Event.range.maxDist))
+ ProcessEvent(*i);
+ break;
+ }
+ }
+
+ EventDiff = 0;
+ EventUpdateTime = EVENT_UPDATE_TIME;
+ }
+ else
+ {
+ EventDiff += diff;
+ EventUpdateTime -= diff;
+ }
+ }
+
+ //Melee Auto-Attack
+ if (Combat && MeleeEnabled)
+ DoMeleeAttackIfReady();
+}
+
+inline uint32 CreatureEventAI::GetRandActionParam(uint32 rnd, uint32 param1, uint32 param2, uint32 param3)
+{
+ switch (rnd % 3)
+ {
+ case 0: return param1;
+ case 1: return param2;
+ case 2: return param3;
+ }
+ return 0;
+}
+
+inline int32 CreatureEventAI::GetRandActionParam(uint32 rnd, int32 param1, int32 param2, int32 param3)
+{
+ switch (rnd % 3)
+ {
+ case 0: return param1;
+ case 1: return param2;
+ case 2: return param3;
+ }
+ return 0;
+}
+
+inline Unit* CreatureEventAI::GetTargetByType(uint32 Target, Unit* pActionInvoker)
+{
+ switch (Target)
+ {
+ case TARGET_T_SELF:
+ return me;
+ case TARGET_T_HOSTILE:
+ return me->getVictim();
+ case TARGET_T_HOSTILE_SECOND_AGGRO:
+ return SelectTarget(SELECT_TARGET_TOPAGGRO,1);
+ case TARGET_T_HOSTILE_LAST_AGGRO:
+ return SelectTarget(SELECT_TARGET_BOTTOMAGGRO,0);
+ case TARGET_T_HOSTILE_RANDOM:
+ return SelectTarget(SELECT_TARGET_RANDOM,0);
+ case TARGET_T_HOSTILE_RANDOM_NOT_TOP:
+ return SelectTarget(SELECT_TARGET_RANDOM,1);
+ case TARGET_T_ACTION_INVOKER:
+ return pActionInvoker;
+ default:
+ return NULL;
+ };
+}
+
+Unit* CreatureEventAI::DoSelectLowestHpFriendly(float range, uint32 MinHPDiff)
+{
+ CellPair p(Trinity::ComputeCellPair(me->GetPositionX(), me->GetPositionY()));
+ Cell cell(p);
+ cell.data.Part.reserved = ALL_DISTRICT;
+ cell.SetNoCreate();
+
+ Unit* pUnit = NULL;
+
+ Trinity::MostHPMissingInRange u_check(me, range, MinHPDiff);
+ Trinity::UnitLastSearcher<Trinity::MostHPMissingInRange> searcher(me, pUnit, u_check);
+
+ /*
+ typedef TYPELIST_4(GameObject, Creature*except pets*, DynamicObject, Corpse*Bones*) AllGridObjectTypes;
+ This means that if we only search grid then we cannot possibly return pets or players so this is safe
+ */
+ TypeContainerVisitor<Trinity::UnitLastSearcher<Trinity::MostHPMissingInRange>, GridTypeMapContainer > grid_unit_searcher(searcher);
+
+ cell.Visit(p, grid_unit_searcher, *me->GetMap(), *me, range);
+ return pUnit;
+}
+
+void CreatureEventAI::DoFindFriendlyCC(std::list<Creature*>& _list, float range)
+{
+ CellPair p(Trinity::ComputeCellPair(me->GetPositionX(), me->GetPositionY()));
+ Cell cell(p);
+ cell.data.Part.reserved = ALL_DISTRICT;
+ cell.SetNoCreate();
+
+ Trinity::FriendlyCCedInRange u_check(me, range);
+ Trinity::CreatureListSearcher<Trinity::FriendlyCCedInRange> searcher(me, _list, u_check);
+
+ TypeContainerVisitor<Trinity::CreatureListSearcher<Trinity::FriendlyCCedInRange>, GridTypeMapContainer > grid_creature_searcher(searcher);
+
+ cell.Visit(p, grid_creature_searcher, *me->GetMap());
+}
+
+void CreatureEventAI::DoFindFriendlyMissingBuff(std::list<Creature*>& _list, float range, uint32 spellid)
+{
+ CellPair p(Trinity::ComputeCellPair(me->GetPositionX(), me->GetPositionY()));
+ Cell cell(p);
+ cell.data.Part.reserved = ALL_DISTRICT;
+ cell.SetNoCreate();
+
+ Trinity::FriendlyMissingBuffInRange u_check(me, range, spellid);
+ Trinity::CreatureListSearcher<Trinity::FriendlyMissingBuffInRange> searcher(me, _list, u_check);
+
+ TypeContainerVisitor<Trinity::CreatureListSearcher<Trinity::FriendlyMissingBuffInRange>, GridTypeMapContainer > grid_creature_searcher(searcher);
+
+ cell.Visit(p, grid_creature_searcher, *me->GetMap());
+}
+
+//*********************************
+//*** Functions used globally ***
+
+void CreatureEventAI::DoScriptText(int32 textEntry, WorldObject* pSource, Unit* target)
+{
+ if (!pSource)
+ {
+ sLog.outErrorDb("CreatureEventAI: DoScriptText entry %i, invalid Source pointer.",textEntry);
+ return;
+ }
+
+ if (textEntry >= 0)
+ {
+ sLog.outErrorDb("CreatureEventAI: DoScriptText with source entry %u (TypeId=%u, guid=%u) attempts to process text entry %i, but text entry must be negative.",pSource->GetEntry(),pSource->GetTypeId(),pSource->GetGUIDLow(),textEntry);
+ return;
+ }
+
+ CreatureEventAI_TextMap::const_iterator i = CreatureEAI_Mgr.GetCreatureEventAITextMap().find(textEntry);
+
+ if (i == CreatureEAI_Mgr.GetCreatureEventAITextMap().end())
+ {
+ sLog.outErrorDb("CreatureEventAI: DoScriptText with source entry %u (TypeId=%u, guid=%u) could not find text entry %i.",pSource->GetEntry(),pSource->GetTypeId(),pSource->GetGUIDLow(),textEntry);
+ return;
+ }
+
+ sLog.outDebug("CreatureEventAI: DoScriptText: text entry=%i, Sound=%u, Type=%u, Language=%u, Emote=%u",textEntry,(*i).second.SoundId,(*i).second.Type,(*i).second.Language,(*i).second.Emote);
+
+ if ((*i).second.SoundId)
+ {
+ if (GetSoundEntriesStore()->LookupEntry((*i).second.SoundId))
+ pSource->PlayDirectSound((*i).second.SoundId);
+ else
+ sLog.outErrorDb("CreatureEventAI: DoScriptText entry %i tried to process invalid sound id %u.",textEntry,(*i).second.SoundId);
+ }
+
+ if ((*i).second.Emote)
+ {
+ if (pSource->GetTypeId() == TYPEID_UNIT || pSource->GetTypeId() == TYPEID_PLAYER)
+ {
+ ((Unit*)pSource)->HandleEmoteCommand((*i).second.Emote);
+ }
+ else
+ sLog.outErrorDb("CreatureEventAI: DoScriptText entry %i tried to process emote for invalid TypeId (%u).",textEntry,pSource->GetTypeId());
+ }
+
+ switch((*i).second.Type)
+ {
+ case CHAT_TYPE_SAY:
+ pSource->MonsterSay(textEntry, (*i).second.Language, target ? target->GetGUID() : 0);
+ break;
+ case CHAT_TYPE_YELL:
+ pSource->MonsterYell(textEntry, (*i).second.Language, target ? target->GetGUID() : 0);
+ break;
+ case CHAT_TYPE_TEXT_EMOTE:
+ pSource->MonsterTextEmote(textEntry, target ? target->GetGUID() : 0);
+ break;
+ case CHAT_TYPE_BOSS_EMOTE:
+ pSource->MonsterTextEmote(textEntry, target ? target->GetGUID() : 0, true);
+ break;
+ case CHAT_TYPE_WHISPER:
+ {
+ if (target && target->GetTypeId() == TYPEID_PLAYER)
+ pSource->MonsterWhisper(textEntry, target->GetGUID());
+ else sLog.outErrorDb("CreatureEventAI: DoScriptText entry %i cannot whisper without target unit (TYPEID_PLAYER).", textEntry);
+ }break;
+ case CHAT_TYPE_BOSS_WHISPER:
+ {
+ if (target && target->GetTypeId() == TYPEID_PLAYER)
+ pSource->MonsterWhisper(textEntry, target->GetGUID(), true);
+ else sLog.outErrorDb("CreatureEventAI: DoScriptText entry %i cannot whisper without target unit (TYPEID_PLAYER).", textEntry);
+ }break;
+ case CHAT_TYPE_ZONE_YELL:
+ pSource->MonsterYellToZone(textEntry, (*i).second.Language, target ? target->GetGUID() : 0);
+ break;
+ }
+}
+
+bool CreatureEventAI::CanCast(Unit* Target, SpellEntry const *Spell, bool Triggered)
+{
+ //No target so we can't cast
+ if (!Target || !Spell)
+ return false;
+
+ //Silenced so we can't cast
+ if (!Triggered && me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED))
+ return false;
+
+ //Check for power
+ if (!Triggered && me->GetPower((Powers)Spell->powerType) < CalculatePowerCost(Spell, me, GetSpellSchoolMask(Spell)))
+ return false;
+
+ SpellRangeEntry const *TempRange = NULL;
+
+ TempRange = GetSpellRangeStore()->LookupEntry(Spell->rangeIndex);
+
+ //Spell has invalid range store so we can't use it
+ if (!TempRange)
+ return false;
+
+ //Unit is out of range of this spell
+ if (!me->IsInRange(Target,TempRange->minRangeHostile,TempRange->maxRangeHostile))
+ return false;
+
+ return true;
+}
+
+void CreatureEventAI::ReceiveEmote(Player* pPlayer, uint32 text_emote)
+{
+ if (bEmptyList)
+ return;
+
+ for (std::list<CreatureEventAIHolder>::iterator itr = CreatureEventAIList.begin(); itr != CreatureEventAIList.end(); ++itr)
+ {
+ if ((*itr).Event.event_type == EVENT_T_RECEIVE_EMOTE)
+ {
+ if ((*itr).Event.receive_emote.emoteId != text_emote)
+ return;
+
+ Condition* cond = new Condition();
+ cond->mConditionType = ConditionType((*itr).Event.receive_emote.condition);
+ cond->mConditionValue1 = (*itr).Event.receive_emote.conditionValue1;
+ cond->mConditionValue2 = (*itr).Event.receive_emote.conditionValue2;
+
+ if (cond->Meets(pPlayer))
+ {
+ sLog.outDebug("CreatureEventAI: ReceiveEmote CreatureEventAI: Condition ok, processing");
+ ProcessEvent(*itr, pPlayer);
+ }
+ }
+ }
+}
+
+void CreatureEventAI::DamageTaken(Unit* /*done_by*/, uint32& damage)
+{
+ if (InvinceabilityHpLevel > 0 && me->GetHealth() < InvinceabilityHpLevel+damage)
+ {
+ if (me->GetHealth() <= InvinceabilityHpLevel)
+ damage = 0;
+ else
+ damage = me->GetHealth() - InvinceabilityHpLevel;
+ }
+}
+
+bool CreatureEventAI::SpawnedEventConditionsCheck(CreatureEventAI_Event const& event)
+{
+ if (event.event_type != EVENT_T_SPAWNED)
+ return false;
+
+ switch (event.spawned.condition)
+ {
+ case SPAWNED_EVENT_ALWAY:
+ // always
+ return true;
+ case SPAWNED_EVENT_MAP:
+ // map ID check
+ return me->GetMapId() == event.spawned.conditionValue1;
+ case SPAWNED_EVENT_ZONE:
+ {
+ // zone ID check
+ uint32 zone, area;
+ me->GetZoneAndAreaId(zone,area);
+ return zone == event.spawned.conditionValue1 || area == event.spawned.conditionValue1;
+ }
+ default:
+ break;
+ }
+
+ return false;
+}
diff --git a/src/server/game/AI/EventAI/CreatureEventAI.h b/src/server/game/AI/EventAI/CreatureEventAI.h
new file mode 100644
index 00000000000..2fc5de46089
--- /dev/null
+++ b/src/server/game/AI/EventAI/CreatureEventAI.h
@@ -0,0 +1,637 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef TRINITY_CREATURE_EAI_H
+#define TRINITY_CREATURE_EAI_H
+
+#include "Common.h"
+#include "Creature.h"
+#include "CreatureAI.h"
+#include "Unit.h"
+
+class Player;
+class WorldObject;
+
+#define EVENT_UPDATE_TIME 500
+#define MAX_ACTIONS 3
+#define MAX_PHASE 32
+
+enum EventAI_Type
+{
+ EVENT_T_TIMER = 0, // InitialMin, InitialMax, RepeatMin, RepeatMax
+ EVENT_T_TIMER_OOC = 1, // InitialMin, InitialMax, RepeatMin, RepeatMax
+ EVENT_T_HP = 2, // HPMax%, HPMin%, RepeatMin, RepeatMax
+ EVENT_T_MANA = 3, // ManaMax%,ManaMin% RepeatMin, RepeatMax
+ EVENT_T_AGGRO = 4, // NONE
+ EVENT_T_KILL = 5, // RepeatMin, RepeatMax
+ EVENT_T_DEATH = 6, // NONE
+ EVENT_T_EVADE = 7, // NONE
+ EVENT_T_SPELLHIT = 8, // SpellID, School, RepeatMin, RepeatMax
+ EVENT_T_RANGE = 9, // MinDist, MaxDist, RepeatMin, RepeatMax
+ EVENT_T_OOC_LOS = 10, // NoHostile, MaxRnage, RepeatMin, RepeatMax
+ EVENT_T_SPAWNED = 11, // Condition, CondValue1
+ EVENT_T_TARGET_HP = 12, // HPMax%, HPMin%, RepeatMin, RepeatMax
+ EVENT_T_TARGET_CASTING = 13, // RepeatMin, RepeatMax
+ EVENT_T_FRIENDLY_HP = 14, // HPDeficit, Radius, RepeatMin, RepeatMax
+ EVENT_T_FRIENDLY_IS_CC = 15, // DispelType, Radius, RepeatMin, RepeatMax
+ EVENT_T_FRIENDLY_MISSING_BUFF = 16, // SpellId, Radius, RepeatMin, RepeatMax
+ EVENT_T_SUMMONED_UNIT = 17, // CreatureId, RepeatMin, RepeatMax
+ EVENT_T_TARGET_MANA = 18, // ManaMax%, ManaMin%, RepeatMin, RepeatMax
+ EVENT_T_QUEST_ACCEPT = 19, // QuestID
+ EVENT_T_QUEST_COMPLETE = 20, //
+ EVENT_T_REACHED_HOME = 21, // NONE
+ EVENT_T_RECEIVE_EMOTE = 22, // EmoteId, Condition, CondValue1, CondValue2
+ EVENT_T_BUFFED = 23, // Param1 = SpellID, Param2 = Number of Time STacked, Param3/4 Repeat Min/Max
+ EVENT_T_TARGET_BUFFED = 24, // Param1 = SpellID, Param2 = Number of Time STacked, Param3/4 Repeat Min/Max
+ EVENT_T_RESET = 35, // Is it called after combat, when the creature respawn and spawn. -- TRINITY ONLY
+
+ EVENT_T_END,
+};
+
+enum EventAI_ActionType
+{
+ ACTION_T_NONE = 0, // No action
+ ACTION_T_TEXT = 1, // TextId1, optionally -TextId2, optionally -TextId3(if -TextId2 exist). If more than just -TextId1 is defined, randomize. Negative values.
+ ACTION_T_SET_FACTION = 2, // FactionId (or 0 for default)
+ ACTION_T_MORPH_TO_ENTRY_OR_MODEL = 3, // Creature_template entry(param1) OR ModelId (param2) (or 0 for both to demorph)
+ ACTION_T_SOUND = 4, // SoundId
+ ACTION_T_EMOTE = 5, // EmoteId
+ ACTION_T_RANDOM_SAY = 6, // UNUSED
+ ACTION_T_RANDOM_YELL = 7, // UNUSED
+ ACTION_T_RANDOM_TEXTEMOTE = 8, // UNUSED
+ ACTION_T_RANDOM_SOUND = 9, // SoundId1, SoundId2, SoundId3 (-1 in any field means no output if randomed that field)
+ ACTION_T_RANDOM_EMOTE = 10, // EmoteId1, EmoteId2, EmoteId3 (-1 in any field means no output if randomed that field)
+ ACTION_T_CAST = 11, // SpellId, Target, CastFlags
+ ACTION_T_SUMMON = 12, // CreatureID, Target, Duration in ms
+ ACTION_T_THREAT_SINGLE_PCT = 13, // Threat%, Target
+ ACTION_T_THREAT_ALL_PCT = 14, // Threat%
+ ACTION_T_QUEST_EVENT = 15, // QuestID, Target
+ ACTION_T_CAST_EVENT = 16, // QuestID, SpellId, Target - must be removed as hack?
+ ACTION_T_SET_UNIT_FIELD = 17, // Field_Number, Value, Target
+ ACTION_T_SET_UNIT_FLAG = 18, // Flags (may be more than one field OR'd together), Target
+ ACTION_T_REMOVE_UNIT_FLAG = 19, // Flags (may be more than one field OR'd together), Target
+ ACTION_T_AUTO_ATTACK = 20, // AllowAttackState (0 = stop attack, anything else means continue attacking)
+ ACTION_T_COMBAT_MOVEMENT = 21, // AllowCombatMovement (0 = stop combat based movement, anything else continue attacking)
+ ACTION_T_SET_PHASE = 22, // Phase
+ ACTION_T_INC_PHASE = 23, // Value (may be negative to decrement phase, should not be 0)
+ ACTION_T_EVADE = 24, // No Params
+ ACTION_T_FLEE_FOR_ASSIST = 25, // No Params
+ ACTION_T_QUEST_EVENT_ALL = 26, // QuestID
+ ACTION_T_CAST_EVENT_ALL = 27, // CreatureId, SpellId
+ ACTION_T_REMOVEAURASFROMSPELL = 28, // Target, Spellid
+ ACTION_T_RANGED_MOVEMENT = 29, // Distance, Angle
+ ACTION_T_RANDOM_PHASE = 30, // PhaseId1, PhaseId2, PhaseId3
+ ACTION_T_RANDOM_PHASE_RANGE = 31, // PhaseMin, PhaseMax
+ ACTION_T_SUMMON_ID = 32, // CreatureId, Target, SpawnId
+ ACTION_T_KILLED_MONSTER = 33, // CreatureId, Target
+ ACTION_T_SET_INST_DATA = 34, // Field, Data
+ ACTION_T_SET_INST_DATA64 = 35, // Field, Target
+ ACTION_T_UPDATE_TEMPLATE = 36, // Entry, Team
+ ACTION_T_DIE = 37, // No Params
+ ACTION_T_ZONE_COMBAT_PULSE = 38, // No Params
+ ACTION_T_CALL_FOR_HELP = 39, // Radius
+ ACTION_T_SET_SHEATH = 40, // Sheath (0-passive,1-melee,2-ranged)
+ ACTION_T_FORCE_DESPAWN = 41, // No Params
+ ACTION_T_SET_INVINCIBILITY_HP_LEVEL = 42, // MinHpValue, format(0-flat,1-percent from max health)
+
+ ACTION_T_SET_PHASE_MASK = 97,
+ ACTION_T_SET_STAND_STATE = 98,
+ ACTION_T_MOVE_RANDOM_POINT = 99,
+ ACTION_T_SET_VISIBILITY = 100,
+ ACTION_T_SET_ACTIVE = 101, //Apply
+ ACTION_T_SET_AGGRESSIVE = 102, //Apply
+ ACTION_T_ATTACK_START_PULSE = 103, //Distance
+ ACTION_T_SUMMON_GO = 104, //GameObjectID, DespawnTime in ms
+
+ ACTION_T_END = 105,
+};
+
+enum Target
+{
+ //Self (me)
+ TARGET_T_SELF = 0, //Self cast
+
+ //Hostile targets (if pet then returns pet owner)
+ TARGET_T_HOSTILE, //Our current target (ie: highest aggro)
+ TARGET_T_HOSTILE_SECOND_AGGRO, //Second highest aggro (generaly used for cleaves and some special attacks)
+ TARGET_T_HOSTILE_LAST_AGGRO, //Dead last on aggro (no idea what this could be used for)
+ TARGET_T_HOSTILE_RANDOM, //Just any random target on our threat list
+ TARGET_T_HOSTILE_RANDOM_NOT_TOP, //Any random target except top threat
+
+ //Invoker targets (if pet then returns pet owner)
+ TARGET_T_ACTION_INVOKER, //Unit who caused this Event to occur (only works for EVENT_T_AGGRO, EVENT_T_KILL, EVENT_T_DEATH, EVENT_T_SPELLHIT, EVENT_T_OOC_LOS, EVENT_T_FRIENDLY_HP, EVENT_T_FRIENDLY_IS_CC, EVENT_T_FRIENDLY_MISSING_BUFF)
+
+ //Hostile targets (including pets)
+ TARGET_T_HOSTILE_WPET, //Current target (can be a pet)
+ TARGET_T_HOSTILE_WPET_SECOND_AGGRO, //Second highest aggro (generaly used for cleaves and some special attacks)
+ TARGET_T_HOSTILE_WPET_LAST_AGGRO, //Dead last on aggro (no idea what this could be used for)
+ TARGET_T_HOSTILE_WPET_RANDOM, //Just any random target on our threat list
+ TARGET_T_HOSTILE_WPET_RANDOM_NOT_TOP, //Any random target except top threat
+
+ TARGET_T_ACTION_INVOKER_WPET,
+
+ TARGET_T_END
+};
+
+enum CastFlags
+{
+ CAST_INTURRUPT_PREVIOUS = 0x01, //Interrupt any spell casting
+ CAST_TRIGGERED = 0x02, //Triggered (this makes spell cost zero mana and have no cast time)
+ CAST_FORCE_CAST = 0x04, //Forces cast even if creature is out of mana or out of range
+ CAST_NO_MELEE_IF_OOM = 0x08, //Prevents creature from entering melee if out of mana or out of range
+ CAST_FORCE_TARGET_SELF = 0x10, //Forces the target to cast this spell on itself
+ CAST_AURA_NOT_PRESENT = 0x20, //Only casts the spell if the target does not have an aura from the spell
+};
+
+enum EventFlags
+{
+ EFLAG_REPEATABLE = 0x01, //Event repeats
+ EFLAG_DIFFICULTY_0 = 0x02, //Event only occurs in instance difficulty 0
+ EFLAG_DIFFICULTY_1 = 0x04, //Event only occurs in instance difficulty 1
+ EFLAG_DIFFICULTY_2 = 0x08, //Event only occurs in instance difficulty 2
+ EFLAG_DIFFICULTY_3 = 0x10, //Event only occurs in instance difficulty 3
+ EFLAG_RESERVED_5 = 0x20,
+ EFLAG_RESERVED_6 = 0x40,
+ EFLAG_DEBUG_ONLY = 0x80, //Event only occurs in debug build
+
+ EFLAG_DIFFICULTY_ALL = (EFLAG_DIFFICULTY_0|EFLAG_DIFFICULTY_1|EFLAG_DIFFICULTY_2|EFLAG_DIFFICULTY_3)
+};
+
+enum SpawnedEventMode
+{
+ SPAWNED_EVENT_ALWAY = 0,
+ SPAWNED_EVENT_MAP = 1,
+ SPAWNED_EVENT_ZONE = 2
+};
+
+// String text additional data, used in (CreatureEventAI)
+struct StringTextData
+{
+ uint32 SoundId;
+ uint8 Type;
+ uint32 Language;
+ uint32 Emote;
+};
+// Text Maps
+typedef UNORDERED_MAP<int32, StringTextData> CreatureEventAI_TextMap;
+
+struct CreatureEventAI_Action
+{
+ EventAI_ActionType type: 16;
+ union
+ {
+ // ACTION_T_TEXT = 1
+ struct
+ {
+ int32 TextId1;
+ int32 TextId2;
+ int32 TextId3;
+ } text;
+ // ACTION_T_SET_FACTION = 2
+ struct
+ {
+ uint32 factionId; // faction or 0 for default)
+ } set_faction;
+ // ACTION_T_MORPH_TO_ENTRY_OR_MODEL = 3
+ struct
+ {
+ uint32 creatureId; // set one from fields (or 0 for both to demorph)
+ uint32 modelId;
+ } morph;
+ // ACTION_T_SOUND = 4
+ struct
+ {
+ uint32 soundId;
+ } sound;
+ // ACTION_T_EMOTE = 5
+ struct
+ {
+ uint32 emoteId;
+ } emote;
+ // ACTION_T_RANDOM_SOUND = 9
+ struct
+ {
+ int32 soundId1; // (-1 in any field means no output if randomed that field)
+ int32 soundId2;
+ int32 soundId3;
+ } random_sound;
+ // ACTION_T_RANDOM_EMOTE = 10
+ struct
+ {
+ int32 emoteId1; // (-1 in any field means no output if randomed that field)
+ int32 emoteId2;
+ int32 emoteId3;
+ } random_emote;
+ // ACTION_T_CAST = 11
+ struct
+ {
+ uint32 spellId;
+ uint32 target;
+ uint32 castFlags;
+ } cast;
+ // ACTION_T_SUMMON = 12
+ struct
+ {
+ uint32 creatureId;
+ uint32 target;
+ uint32 duration;
+ } summon;
+ // ACTION_T_THREAT_SINGLE_PCT = 13
+ struct
+ {
+ int32 percent;
+ uint32 target;
+ } threat_single_pct;
+ // ACTION_T_THREAT_ALL_PCT = 14
+ struct
+ {
+ int32 percent;
+ } threat_all_pct;
+ // ACTION_T_QUEST_EVENT = 15
+ struct
+ {
+ uint32 questId;
+ uint32 target;
+ } quest_event;
+ // ACTION_T_CAST_EVENT = 16
+ struct
+ {
+ uint32 creatureId;
+ uint32 spellId;
+ uint32 target;
+ } cast_event;
+ // ACTION_T_SET_UNIT_FIELD = 17
+ struct
+ {
+ uint32 field;
+ uint32 value;
+ uint32 target;
+ } set_unit_field;
+ // ACTION_T_SET_UNIT_FLAG = 18, // value provided mask bits that will be set
+ // ACTION_T_REMOVE_UNIT_FLAG = 19, // value provided mask bits that will be clear
+ struct
+ {
+ uint32 value;
+ uint32 target;
+ } unit_flag;
+ // ACTION_T_AUTO_ATTACK = 20
+ struct
+ {
+ uint32 state; // 0 = stop attack, anything else means continue attacking
+ } auto_attack;
+ // ACTION_T_COMBAT_MOVEMENT = 21
+ struct
+ {
+ uint32 state; // 0 = stop combat based movement, anything else continue attacking
+ uint32 melee; // if set: at stop send melee combat stop if in combat, use for terminate melee fighting state for switch to ranged
+ } combat_movement;
+ // ACTION_T_SET_PHASE = 22
+ struct
+ {
+ uint32 phase;
+ } set_phase;
+ // ACTION_T_INC_PHASE = 23
+ struct
+ {
+ int32 step;
+ } set_inc_phase;
+ // ACTION_T_QUEST_EVENT_ALL = 26
+ struct
+ {
+ uint32 questId;
+ } quest_event_all;
+ // ACTION_T_CAST_EVENT_ALL = 27
+ struct
+ {
+ uint32 creatureId;
+ uint32 spellId;
+ } cast_event_all;
+ // ACTION_T_REMOVEAURASFROMSPELL = 28
+ struct
+ {
+ uint32 target;
+ uint32 spellId;
+ } remove_aura;
+ // ACTION_T_RANGED_MOVEMENT = 29
+ struct
+ {
+ uint32 distance;
+ int32 angle;
+ } ranged_movement;
+ // ACTION_T_RANDOM_PHASE = 30
+ struct
+ {
+ uint32 phase1;
+ uint32 phase2;
+ uint32 phase3;
+ } random_phase;
+ // ACTION_T_RANDOM_PHASE_RANGE = 31
+ struct
+ {
+ uint32 phaseMin;
+ uint32 phaseMax;
+ } random_phase_range;
+ // ACTION_T_SUMMON_ID = 32
+ struct
+ {
+ uint32 creatureId;
+ uint32 target;
+ uint32 spawnId;
+ } summon_id;
+ // ACTION_T_KILLED_MONSTER = 33
+ struct
+ {
+ uint32 creatureId;
+ uint32 target;
+ } killed_monster;
+ // ACTION_T_SET_INST_DATA = 34
+ struct
+ {
+ uint32 field;
+ uint32 value;
+ } set_inst_data;
+ // ACTION_T_SET_INST_DATA64 = 35
+ struct
+ {
+ uint32 field;
+ uint32 target;
+ } set_inst_data64;
+ // ACTION_T_UPDATE_TEMPLATE = 36
+ struct
+ {
+ uint32 creatureId;
+ uint32 team;
+ } update_template;
+ // ACTION_T_CALL_FOR_HELP = 39
+ struct
+ {
+ uint32 radius;
+ } call_for_help;
+ // ACTION_T_SET_SHEATH = 40
+ struct
+ {
+ uint32 sheath;
+ } set_sheath;
+ // ACTION_T_FORCE_DESPAWN = 41
+ struct
+ {
+ uint32 msDelay;
+ } forced_despawn;
+ // ACTION_T_SET_INVINCIBILITY_HP_LEVEL = 42
+ struct
+ {
+ uint32 hp_level;
+ uint32 is_percent;
+ } invincibility_hp_level;
+ // RAW
+ struct
+ {
+ uint32 param1;
+ uint32 param2;
+ uint32 param3;
+ } raw;
+ };
+};
+
+struct CreatureEventAI_Event
+{
+ uint32 event_id;
+
+ uint32 creature_id;
+
+ uint32 event_inverse_phase_mask;
+
+ EventAI_Type event_type : 16;
+ uint8 event_chance : 8;
+ uint8 event_flags : 8;
+
+ union
+ {
+ // EVENT_T_TIMER = 0
+ // EVENT_T_TIMER_OOC = 1
+ struct
+ {
+ uint32 initialMin;
+ uint32 initialMax;
+ uint32 repeatMin;
+ uint32 repeatMax;
+ } timer;
+ // EVENT_T_HP = 2
+ // EVENT_T_MANA = 3
+ // EVENT_T_TARGET_HP = 12
+ // EVENT_T_TARGET_MANA = 18
+ struct
+ {
+ uint32 percentMax;
+ uint32 percentMin;
+ uint32 repeatMin;
+ uint32 repeatMax;
+ } percent_range;
+ // EVENT_T_KILL = 5
+ struct
+ {
+ uint32 repeatMin;
+ uint32 repeatMax;
+ } kill;
+ // EVENT_T_SPELLHIT = 8
+ struct
+ {
+ uint32 spellId;
+ uint32 schoolMask; // -1 ( == 0xffffffff) is ok value for full mask, or must be more limited mask like (0 < 1) = 1 for normal/physical school
+ uint32 repeatMin;
+ uint32 repeatMax;
+ } spell_hit;
+ // EVENT_T_RANGE = 9
+ struct
+ {
+ uint32 minDist;
+ uint32 maxDist;
+ uint32 repeatMin;
+ uint32 repeatMax;
+ } range;
+ // EVENT_T_OOC_LOS = 10
+ struct
+ {
+ uint32 noHostile;
+ uint32 maxRange;
+ uint32 repeatMin;
+ uint32 repeatMax;
+ } ooc_los;
+ // EVENT_T_SPAWNED = 11
+ struct
+ {
+ uint32 condition;
+ uint32 conditionValue1;
+ } spawned;
+ // EVENT_T_TARGET_CASTING = 13
+ struct
+ {
+ uint32 repeatMin;
+ uint32 repeatMax;
+ } target_casting;
+ // EVENT_T_FRIENDLY_HP = 14
+ struct
+ {
+ uint32 hpDeficit;
+ uint32 radius;
+ uint32 repeatMin;
+ uint32 repeatMax;
+ } friendly_hp;
+ // EVENT_T_FRIENDLY_IS_CC = 15
+ struct
+ {
+ uint32 dispelType; // unused ?
+ uint32 radius;
+ uint32 repeatMin;
+ uint32 repeatMax;
+ } friendly_is_cc;
+ // EVENT_T_FRIENDLY_MISSING_BUFF = 16
+ struct
+ {
+ uint32 spellId;
+ uint32 radius;
+ uint32 repeatMin;
+ uint32 repeatMax;
+ } friendly_buff;
+ // EVENT_T_SUMMONED_UNIT = 17
+ struct
+ {
+ uint32 creatureId;
+ uint32 repeatMin;
+ uint32 repeatMax;
+ } summon_unit;
+ // EVENT_T_QUEST_ACCEPT = 19
+ // EVENT_T_QUEST_COMPLETE = 20
+ struct
+ {
+ uint32 questId;
+ } quest;
+ // EVENT_T_RECEIVE_EMOTE = 22
+ struct
+ {
+ uint32 emoteId;
+ uint32 condition;
+ uint32 conditionValue1;
+ uint32 conditionValue2;
+ } receive_emote;
+ // EVENT_T_BUFFED = 23
+ // EVENT_T_TARGET_BUFFED = 24
+ struct
+ {
+ uint32 spellId;
+ uint32 amount;
+ uint32 repeatMin;
+ uint32 repeatMax;
+ } buffed;
+
+ // RAW
+ struct
+ {
+ uint32 param1;
+ uint32 param2;
+ uint32 param3;
+ uint32 param4;
+ } raw;
+ };
+
+ CreatureEventAI_Action action[MAX_ACTIONS];
+};
+//Event_Map
+typedef UNORDERED_MAP<uint32, std::vector<CreatureEventAI_Event> > CreatureEventAI_Event_Map;
+
+struct CreatureEventAI_Summon
+{
+ uint32 id;
+
+ float position_x;
+ float position_y;
+ float position_z;
+ float orientation;
+ uint32 SpawnTimeSecs;
+};
+
+//EventSummon_Map
+typedef UNORDERED_MAP<uint32, CreatureEventAI_Summon> CreatureEventAI_Summon_Map;
+
+struct CreatureEventAIHolder
+{
+ CreatureEventAIHolder(CreatureEventAI_Event p) : Event(p), Time(0), Enabled(true){}
+
+ CreatureEventAI_Event Event;
+ uint32 Time;
+ bool Enabled;
+
+ // helper
+ bool UpdateRepeatTimer(Creature* creature, uint32 repeatMin, uint32 repeatMax);
+};
+
+class CreatureEventAI : public CreatureAI
+{
+
+ public:
+ explicit CreatureEventAI(Creature *c);
+ ~CreatureEventAI()
+ {
+ CreatureEventAIList.clear();
+ }
+ void JustRespawned();
+ void Reset();
+ void JustReachedHome();
+ void EnterCombat(Unit *enemy);
+ void EnterEvadeMode();
+ void JustDied(Unit* /*killer*/);
+ void KilledUnit(Unit* victim);
+ void JustSummoned(Creature* pUnit);
+ void AttackStart(Unit *who);
+ void MoveInLineOfSight(Unit *who);
+ void SpellHit(Unit* pUnit, const SpellEntry* pSpell);
+ void DamageTaken(Unit* done_by, uint32& damage);
+ void UpdateAI(const uint32 diff);
+ void ReceiveEmote(Player* pPlayer, uint32 text_emote);
+ static int Permissible(const Creature *);
+
+ bool ProcessEvent(CreatureEventAIHolder& pHolder, Unit* pActionInvoker = NULL);
+ void ProcessAction(CreatureEventAI_Action const& action, uint32 rnd, uint32 EventId, Unit* pActionInvoker);
+ inline uint32 GetRandActionParam(uint32 rnd, uint32 param1, uint32 param2, uint32 param3);
+ inline int32 GetRandActionParam(uint32 rnd, int32 param1, int32 param2, int32 param3);
+ inline Unit* GetTargetByType(uint32 Target, Unit* pActionInvoker);
+
+ void DoScriptText(int32 textEntry, WorldObject* pSource, Unit* target);
+ bool CanCast(Unit* Target, SpellEntry const *Spell, bool Triggered);
+
+ bool SpawnedEventConditionsCheck(CreatureEventAI_Event const& event);
+
+ Unit* DoSelectLowestHpFriendly(float range, uint32 MinHPDiff);
+ void DoFindFriendlyMissingBuff(std::list<Creature*>& _list, float range, uint32 spellid);
+ void DoFindFriendlyCC(std::list<Creature*>& _list, float range);
+
+ //Holder for events (stores enabled, time, and eventid)
+ std::list<CreatureEventAIHolder> CreatureEventAIList;
+ uint32 EventUpdateTime; //Time between event updates
+ uint32 EventDiff; //Time between the last event call
+ bool bEmptyList;
+
+ //Variables used by Events themselves
+ uint8 Phase; // Current phase, max 32 phases
+ bool CombatMovementEnabled; // If we allow targeted movment gen (movement twoards top threat)
+ bool MeleeEnabled; // If we allow melee auto attack
+ float AttackDistance; // Distance to attack from
+ float AttackAngle; // Angle of attack
+ uint32 InvinceabilityHpLevel; // Minimal health level allowed at damage apply
+};
+#endif
diff --git a/src/server/game/AI/EventAI/CreatureEventAIMgr.cpp b/src/server/game/AI/EventAI/CreatureEventAIMgr.cpp
new file mode 100644
index 00000000000..83d62ca74dc
--- /dev/null
+++ b/src/server/game/AI/EventAI/CreatureEventAIMgr.cpp
@@ -0,0 +1,745 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "Common.h"
+#include "Database/DatabaseEnv.h"
+#include "Database/SQLStorage.h"
+#include "CreatureEventAI.h"
+#include "CreatureEventAIMgr.h"
+#include "ObjectMgr.h"
+#include "ProgressBar.h"
+#include "Policies/SingletonImp.h"
+#include "ObjectDefines.h"
+#include "GridDefines.h"
+#include "ConditionMgr.h"
+
+INSTANTIATE_SINGLETON_1(CreatureEventAIMgr);
+
+// -------------------
+void CreatureEventAIMgr::LoadCreatureEventAI_Texts()
+{
+ // Drop Existing Text Map, only done once and we are ready to add data from multiple sources.
+ m_CreatureEventAI_TextMap.clear();
+
+ // Load EventAI Text
+ objmgr.LoadTrinityStrings(WorldDatabase,"creature_ai_texts",MIN_CREATURE_AI_TEXT_STRING_ID,MAX_CREATURE_AI_TEXT_STRING_ID);
+
+ // Gather Additional data from EventAI Texts
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, sound, type, language, emote FROM creature_ai_texts");
+
+ sLog.outString("Loading EventAI Texts additional data...");
+ if (result)
+ {
+ barGoLink bar(result->GetRowCount());
+ uint32 count = 0;
+
+ do
+ {
+ bar.step();
+ Field* fields = result->Fetch();
+ StringTextData temp;
+
+ int32 i = fields[0].GetInt32();
+ temp.SoundId = fields[1].GetInt32();
+ temp.Type = fields[2].GetInt32();
+ temp.Language = fields[3].GetInt32();
+ temp.Emote = fields[4].GetInt32();
+
+ // range negative
+ if (i > MIN_CREATURE_AI_TEXT_STRING_ID || i <= MAX_CREATURE_AI_TEXT_STRING_ID)
+ {
+ sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` is not in valid range(%d-%d)",i,MIN_CREATURE_AI_TEXT_STRING_ID,MAX_CREATURE_AI_TEXT_STRING_ID);
+ continue;
+ }
+
+ // range negative (don't must be happen, loaded from same table)
+ if (!objmgr.GetTrinityStringLocale(i))
+ {
+ sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` not found",i);
+ continue;
+ }
+
+ if (temp.SoundId)
+ {
+ if (!sSoundEntriesStore.LookupEntry(temp.SoundId))
+ sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` has Sound %u but sound does not exist.",i,temp.SoundId);
+ }
+
+ if (!GetLanguageDescByID(temp.Language))
+ sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` using Language %u but Language does not exist.",i,temp.Language);
+
+ if (temp.Type > CHAT_TYPE_ZONE_YELL)
+ sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` has Type %u but this Chat Type does not exist.",i,temp.Type);
+
+ if (temp.Emote)
+ {
+ if (!sEmotesStore.LookupEntry(temp.Emote))
+ sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` has Emote %u but emote does not exist.",i,temp.Emote);
+ }
+
+ m_CreatureEventAI_TextMap[i] = temp;
+ ++count;
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u additional CreatureEventAI Texts data.", count);
+ }
+ else
+ {
+ barGoLink bar(1);
+ bar.step();
+ sLog.outString();
+ sLog.outString(">> Loaded 0 additional CreatureEventAI Texts data. DB table `creature_ai_texts` is empty.");
+ }
+
+}
+
+// -------------------
+void CreatureEventAIMgr::LoadCreatureEventAI_Summons()
+{
+
+ //Drop Existing EventSummon Map
+ m_CreatureEventAI_Summon_Map.clear();
+
+ // Gather additional data for EventAI
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id, position_x, position_y, position_z, orientation, spawntimesecs FROM creature_ai_summons");
+ if (result)
+ {
+ barGoLink bar(result->GetRowCount());
+ uint32 Count = 0;
+
+ do
+ {
+ bar.step();
+ Field *fields = result->Fetch();
+
+ CreatureEventAI_Summon temp;
+
+ uint32 i = fields[0].GetUInt32();
+ temp.position_x = fields[1].GetFloat();
+ temp.position_y = fields[2].GetFloat();
+ temp.position_z = fields[3].GetFloat();
+ temp.orientation = fields[4].GetFloat();
+ temp.SpawnTimeSecs = fields[5].GetUInt32();
+
+ if (!Trinity::IsValidMapCoord(temp.position_x,temp.position_y,temp.position_z,temp.orientation))
+ {
+ sLog.outErrorDb("CreatureEventAI: Summon id %u have wrong coordinates (%f,%f,%f,%f), skipping.", i,temp.position_x,temp.position_y,temp.position_z,temp.orientation);
+ continue;
+ }
+
+ //Add to map
+ m_CreatureEventAI_Summon_Map[i] = temp;
+ ++Count;
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u CreatureEventAI summon definitions", Count);
+ }
+ else
+ {
+ barGoLink bar(1);
+ bar.step();
+ sLog.outString();
+ sLog.outString(">> Loaded 0 CreatureEventAI Summon definitions. DB table `creature_ai_summons` is empty.");
+ }
+
+}
+
+// -------------------
+void CreatureEventAIMgr::LoadCreatureEventAI_Scripts()
+{
+ //Drop Existing EventAI List
+ m_CreatureEventAI_Event_Map.clear();
+
+ // Gather event data
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id, creature_id, event_type, event_inverse_phase_mask, event_chance, event_flags, "
+ "event_param1, event_param2, event_param3, event_param4, "
+ "action1_type, action1_param1, action1_param2, action1_param3, "
+ "action2_type, action2_param1, action2_param2, action2_param3, "
+ "action3_type, action3_param1, action3_param2, action3_param3 "
+ "FROM creature_ai_scripts");
+ if (result)
+ {
+ barGoLink bar(result->GetRowCount());
+ uint32 Count = 0;
+
+ do
+ {
+ bar.step();
+ Field *fields = result->Fetch();
+
+ CreatureEventAI_Event temp;
+ temp.event_id = EventAI_Type(fields[0].GetUInt32());
+ uint32 i = temp.event_id;
+
+ temp.creature_id = fields[1].GetUInt32();
+ uint32 creature_id = temp.creature_id;
+
+ uint32 e_type = fields[2].GetUInt32();
+ //Report any errors in event
+ if (e_type >= EVENT_T_END)
+ {
+ sLog.outErrorDb("CreatureEventAI: Event %u have wrong type (%u), skipping.", i,e_type);
+ continue;
+ }
+ temp.event_type = EventAI_Type(e_type);
+
+ temp.event_inverse_phase_mask = fields[3].GetUInt32();
+ temp.event_chance = fields[4].GetUInt8();
+ temp.event_flags = fields[5].GetUInt8();
+ temp.raw.param1 = fields[6].GetUInt32();
+ temp.raw.param2 = fields[7].GetUInt32();
+ temp.raw.param3 = fields[8].GetUInt32();
+ temp.raw.param4 = fields[9].GetUInt32();
+
+ //Creature does not exist in database
+ if (!sCreatureStorage.LookupEntry<CreatureInfo>(temp.creature_id))
+ {
+ sLog.outErrorDb("CreatureEventAI: Event %u has script for non-existing creature entry (%u), skipping.", i, temp.creature_id);
+ continue;
+ }
+
+ //No chance of this event occuring
+ if (temp.event_chance == 0)
+ sLog.outErrorDb("CreatureEventAI: Event %u has 0 percent chance. Event will never trigger!", i);
+ //Chance above 100, force it to be 100
+ else if (temp.event_chance > 100)
+ {
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using event %u with more than 100 percent chance. Adjusting to 100 percent.", temp.creature_id, i);
+ temp.event_chance = 100;
+ }
+
+ //Individual event checks
+ switch (temp.event_type)
+ {
+ case EVENT_T_TIMER:
+ case EVENT_T_TIMER_OOC:
+ if (temp.timer.initialMax < temp.timer.initialMin)
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using timed event(%u) with param2 < param1 (InitialMax < InitialMin). Event will never repeat.", temp.creature_id, i);
+ if (temp.timer.repeatMax < temp.timer.repeatMin)
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
+ break;
+ case EVENT_T_HP:
+ case EVENT_T_MANA:
+ case EVENT_T_TARGET_HP:
+ case EVENT_T_TARGET_MANA:
+ if (temp.percent_range.percentMax > 100)
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using percentage event(%u) with param2 (MinPercent) > 100. Event will never trigger! ", temp.creature_id, i);
+
+ if (temp.percent_range.percentMax <= temp.percent_range.percentMin)
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using percentage event(%u) with param1 <= param2 (MaxPercent <= MinPercent). Event will never trigger! ", temp.creature_id, i);
+
+ if (temp.event_flags & EFLAG_REPEATABLE && !temp.percent_range.repeatMin && !temp.percent_range.repeatMax)
+ {
+ sLog.outErrorDb("CreatureEventAI: Creature %u has param3 and param4=0 (RepeatMin/RepeatMax) but cannot be repeatable without timers. Removing EFLAG_REPEATABLE for event %u.", temp.creature_id, i);
+ temp.event_flags &= ~EFLAG_REPEATABLE;
+ }
+ break;
+ case EVENT_T_SPELLHIT:
+ if (temp.spell_hit.spellId)
+ {
+ SpellEntry const* pSpell = sSpellStore.LookupEntry(temp.spell_hit.spellId);
+ if (!pSpell)
+ {
+ sLog.outErrorDb("CreatureEventAI: Creature %u has non-existant SpellID(%u) defined in event %u.", temp.creature_id, temp.spell_hit.spellId, i);
+ continue;
+ }
+
+ if ((temp.spell_hit.schoolMask & pSpell->SchoolMask) != pSpell->SchoolMask)
+ sLog.outErrorDb("CreatureEventAI: Creature %u has param1(spellId %u) but param2 is not -1 and not equal to spell's school mask. Event %u can never trigger.", temp.creature_id, temp.spell_hit.schoolMask, i);
+ }
+
+ if (!temp.spell_hit.schoolMask)
+ sLog.outErrorDb("CreatureEventAI: Creature %u is using invalid SpellSchoolMask(%u) defined in event %u.", temp.creature_id, temp.spell_hit.schoolMask, i);
+
+ if (temp.spell_hit.repeatMax < temp.spell_hit.repeatMin)
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
+ break;
+ case EVENT_T_RANGE:
+ if (temp.range.maxDist < temp.range.minDist)
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with param2 < param1 (MaxDist < MinDist). Event will never repeat.", temp.creature_id, i);
+ if (temp.range.repeatMax < temp.range.repeatMin)
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
+ break;
+ case EVENT_T_OOC_LOS:
+ if (temp.ooc_los.repeatMax < temp.ooc_los.repeatMin)
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
+ break;
+ case EVENT_T_SPAWNED:
+ switch(temp.spawned.condition)
+ {
+ case SPAWNED_EVENT_ALWAY:
+ break;
+ case SPAWNED_EVENT_MAP:
+ if (!sMapStore.LookupEntry(temp.spawned.conditionValue1))
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using spawned event(%u) with param1 = %u 'map specific' but with not existed map (%u) in param2. Event will never repeat.", temp.creature_id, i, temp.spawned.condition, temp.spawned.conditionValue1);
+ break;
+ case SPAWNED_EVENT_ZONE:
+ if (!GetAreaEntryByAreaID(temp.spawned.conditionValue1))
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using spawned event(%u) with param1 = %u 'area specific' but with not existed area (%u) in param2. Event will never repeat.", temp.creature_id, i, temp.spawned.condition, temp.spawned.conditionValue1);
+ default:
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using invalid spawned event %u mode (%u) in param1", temp.creature_id, i, temp.spawned.condition);
+ break;
+ }
+ break;
+ case EVENT_T_FRIENDLY_HP:
+ if (temp.friendly_hp.repeatMax < temp.friendly_hp.repeatMin)
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
+ break;
+ case EVENT_T_FRIENDLY_IS_CC:
+ if (temp.friendly_is_cc.repeatMax < temp.friendly_is_cc.repeatMin)
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
+ break;
+ case EVENT_T_FRIENDLY_MISSING_BUFF:
+ {
+ SpellEntry const* pSpell = sSpellStore.LookupEntry(temp.spell_hit.spellId);
+ if (!pSpell)
+ {
+ sLog.outErrorDb("CreatureEventAI: Creature %u has non-existant SpellID(%u) defined in event %u.", temp.creature_id, temp.spell_hit.spellId, i);
+ continue;
+ }
+ if (temp.friendly_buff.repeatMax < temp.friendly_buff.repeatMin)
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
+ break;
+ }
+ case EVENT_T_KILL:
+ if (temp.kill.repeatMax < temp.kill.repeatMin)
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
+ break;
+ case EVENT_T_TARGET_CASTING:
+ if (temp.target_casting.repeatMax < temp.target_casting.repeatMin)
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
+ break;
+ case EVENT_T_SUMMONED_UNIT:
+ if (!sCreatureStorage.LookupEntry<CreatureInfo>(temp.summon_unit.creatureId))
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with not existed creature template id (%u) in param1, skipped.", temp.creature_id, i, temp.summon_unit.creatureId);
+ if (temp.summon_unit.repeatMax < temp.summon_unit.repeatMin)
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
+ break;
+ case EVENT_T_QUEST_ACCEPT:
+ case EVENT_T_QUEST_COMPLETE:
+ if (!objmgr.GetQuestTemplate(temp.quest.questId))
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with not existed qyest id (%u) in param1, skipped.", temp.creature_id, i, temp.quest.questId);
+ sLog.outErrorDb("CreatureEventAI: Creature %u using not implemented event (%u) in event %u.", temp.creature_id, temp.event_id, i);
+ continue;
+
+ case EVENT_T_AGGRO:
+ case EVENT_T_DEATH:
+ case EVENT_T_EVADE:
+ case EVENT_T_REACHED_HOME:
+ {
+ if (temp.event_flags & EFLAG_REPEATABLE)
+ {
+ sLog.outErrorDb("CreatureEventAI: Creature %u has EFLAG_REPEATABLE set. Event can never be repeatable. Removing flag for event %u.", temp.creature_id, i);
+ temp.event_flags &= ~EFLAG_REPEATABLE;
+ }
+
+ break;
+ }
+
+ case EVENT_T_RECEIVE_EMOTE:
+ {
+ if (!sEmotesTextStore.LookupEntry(temp.receive_emote.emoteId))
+ {
+ sLog.outErrorDb("CreatureEventAI: Creature %u using event %u: param1 (EmoteTextId: %u) are not valid.",temp.creature_id, i, temp.receive_emote.emoteId);
+ continue;
+ }
+ if (temp.receive_emote.condition)
+ {
+ Condition* cond = new Condition();
+ cond->mConditionType = ConditionType(temp.receive_emote.condition);
+ cond->mConditionValue1 = temp.receive_emote.conditionValue1;
+ cond->mConditionValue2 = temp.receive_emote.conditionValue2;
+ if (!sConditionMgr.isConditionTypeValid(cond))
+ {
+ sLog.outErrorDb("CreatureEventAI: Creature %u using event %u: param2 (Condition: %u) are not valid.",temp.creature_id, i, temp.receive_emote.condition);
+ continue;
+ }
+ }
+
+ if (!(temp.event_flags & EFLAG_REPEATABLE))
+ {
+ sLog.outErrorDb("CreatureEventAI: Creature %u using event %u: EFLAG_REPEATABLE not set. Event must always be repeatable. Flag applied.", temp.creature_id, i);
+ temp.event_flags |= EFLAG_REPEATABLE;
+ }
+
+ break;
+ }
+
+ case EVENT_T_BUFFED:
+ case EVENT_T_TARGET_BUFFED:
+ {
+ SpellEntry const* pSpell = sSpellStore.LookupEntry(temp.buffed.spellId);
+ if (!pSpell)
+ {
+ sLog.outErrorDb("CreatureEventAI: Creature %u has non-existant SpellID(%u) defined in event %u.", temp.creature_id, temp.spell_hit.spellId, i);
+ continue;
+ }
+ if (temp.buffed.repeatMax < temp.buffed.repeatMin)
+ sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
+ break;
+ }
+
+ default:
+ sLog.outErrorDb("CreatureEventAI: Creature %u using not checked at load event (%u) in event %u. Need check code update?", temp.creature_id, temp.event_id, i);
+ break;
+ }
+
+ for (uint32 j = 0; j < MAX_ACTIONS; j++)
+ {
+ uint16 action_type = fields[10+(j*4)].GetUInt16();
+ if (action_type >= ACTION_T_END)
+ {
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u has incorrect action type (%u), replace by ACTION_T_NONE.", i, j+1, action_type);
+ temp.action[j].type = ACTION_T_NONE;
+ continue;
+ }
+
+ CreatureEventAI_Action& action = temp.action[j];
+
+ action.type = EventAI_ActionType(action_type);
+ action.raw.param1 = fields[11+(j*4)].GetUInt32();
+ action.raw.param2 = fields[12+(j*4)].GetUInt32();
+ action.raw.param3 = fields[13+(j*4)].GetUInt32();
+
+ //Report any errors in actions
+ switch (action.type)
+ {
+ case ACTION_T_NONE:
+ break;
+ case ACTION_T_TEXT:
+ {
+ if (action.text.TextId1 < 0)
+ {
+ if (m_CreatureEventAI_TextMap.find(action.text.TextId1) == m_CreatureEventAI_TextMap.end())
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u param1 refrences non-existing entry in texts table.", i, j+1);
+ }
+ if (action.text.TextId2 < 0)
+ {
+ if (m_CreatureEventAI_TextMap.find(action.text.TextId2) == m_CreatureEventAI_TextMap.end())
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u param2 refrences non-existing entry in texts table.", i, j+1);
+
+ if (!action.text.TextId1)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u has param2, but param1 is not set. Required for randomized text.", i, j+1);
+ }
+ if (action.text.TextId3 < 0)
+ {
+ if (m_CreatureEventAI_TextMap.find(action.text.TextId3) == m_CreatureEventAI_TextMap.end())
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u param3 refrences non-existing entry in texts table.", i, j+1);
+
+ if (!action.text.TextId1 || !action.text.TextId2)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u has param3, but param1 and/or param2 is not set. Required for randomized text.", i, j+1);
+ }
+ break;
+ }
+ case ACTION_T_SET_FACTION:
+ if (action.set_faction.factionId !=0 && !sFactionStore.LookupEntry(action.set_faction.factionId))
+ {
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent FactionId %u.", i, j+1, action.set_faction.factionId);
+ action.set_faction.factionId = 0;
+ }
+ break;
+ case ACTION_T_MORPH_TO_ENTRY_OR_MODEL:
+ if (action.morph.creatureId !=0 || action.morph.modelId !=0)
+ {
+ if (action.morph.creatureId && !sCreatureStorage.LookupEntry<CreatureInfo>(action.morph.creatureId))
+ {
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant Creature entry %u.", i, j+1, action.morph.creatureId);
+ action.morph.creatureId = 0;
+ }
+
+ if (action.morph.modelId)
+ {
+ if (action.morph.creatureId)
+ {
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u have unused ModelId %u with also set creature id %u.", i, j+1, action.morph.modelId,action.morph.creatureId);
+ action.morph.modelId = 0;
+ }
+ else if (!sCreatureDisplayInfoStore.LookupEntry(action.morph.modelId))
+ {
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant ModelId %u.", i, j+1, action.morph.modelId);
+ action.morph.modelId = 0;
+ }
+ }
+ }
+ break;
+ case ACTION_T_SOUND:
+ if (!sSoundEntriesStore.LookupEntry(action.sound.soundId))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant SoundID %u.", i, j+1, action.sound.soundId);
+ break;
+ case ACTION_T_EMOTE:
+ if (!sEmotesStore.LookupEntry(action.emote.emoteId))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u param1 (EmoteId: %u) are not valid.", i, j+1, action.emote.emoteId);
+ break;
+ case ACTION_T_RANDOM_SOUND:
+ if (!sSoundEntriesStore.LookupEntry(action.random_sound.soundId1))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u param1 uses non-existant SoundID %u.", i, j+1, action.random_sound.soundId1);
+ if (action.random_sound.soundId2 >= 0 && !sSoundEntriesStore.LookupEntry(action.random_sound.soundId2))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u param2 uses non-existant SoundID %u.", i, j+1, action.random_sound.soundId2);
+ if (action.random_sound.soundId3 >= 0 && !sSoundEntriesStore.LookupEntry(action.random_sound.soundId3))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u param3 uses non-existant SoundID %u.", i, j+1, action.random_sound.soundId3);
+ break;
+ case ACTION_T_RANDOM_EMOTE:
+ if (!sEmotesStore.LookupEntry(action.random_emote.emoteId1))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u param1 (EmoteId: %u) are not valid.", i, j+1, action.random_emote.emoteId1);
+ if (action.random_emote.emoteId2 >= 0 && !sEmotesStore.LookupEntry(action.random_emote.emoteId2))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u param2 (EmoteId: %u) are not valid.", i, j+1, action.random_emote.emoteId2);
+ if (action.random_emote.emoteId3 >= 0 && !sEmotesStore.LookupEntry(action.random_emote.emoteId3))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u param3 (EmoteId: %u) are not valid.", i, j+1, action.random_emote.emoteId3);
+ break;
+ case ACTION_T_CAST:
+ {
+ const SpellEntry *spell = sSpellStore.LookupEntry(action.cast.spellId);
+ if (!spell)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent SpellID %u.", i, j+1, action.cast.spellId);
+ /* FIXME: temp.raw.param3 not have event tipes with recovery time in it....
+ else
+ {
+ if (spell->RecoveryTime > 0 && temp.event_flags & EFLAG_REPEATABLE)
+ {
+ //output as debug for now, also because there's no general rule all spells have RecoveryTime
+ if (temp.event_param3 < spell->RecoveryTime)
+ sLog.outDebug("CreatureEventAI: Event %u Action %u uses SpellID %u but cooldown is longer(%u) than minumum defined in event param3(%u).", i, j+1,action.cast.spellId, spell->RecoveryTime, temp.event_param3);
+ }
+ }
+ */
+
+ //Cast is always triggered if target is forced to cast on self
+ if (action.cast.castFlags & CAST_FORCE_TARGET_SELF)
+ action.cast.castFlags |= CAST_TRIGGERED;
+
+ if (action.cast.target >= TARGET_T_END)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
+ break;
+ }
+ case ACTION_T_SUMMON:
+ if (!sCreatureStorage.LookupEntry<CreatureInfo>(action.summon.creatureId))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent creature entry %u.", i, j+1, action.summon.creatureId);
+
+ if (action.summon.target >= TARGET_T_END)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
+ break;
+ case ACTION_T_THREAT_SINGLE_PCT:
+ if (std::abs(action.threat_single_pct.percent) > 100)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses invalid percent value %u.", i, j+1, action.threat_single_pct.percent);
+ if (action.threat_single_pct.target >= TARGET_T_END)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
+ break;
+ case ACTION_T_THREAT_ALL_PCT:
+ if (std::abs(action.threat_all_pct.percent) > 100)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses invalid percent value %u.", i, j+1, action.threat_all_pct.percent);
+ break;
+ case ACTION_T_QUEST_EVENT:
+ if (Quest const* qid = objmgr.GetQuestTemplate(action.quest_event.questId))
+ {
+ if (!qid->HasFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u. SpecialFlags for quest entry %u does not include |2, Action will not have any effect.", i, j+1, action.quest_event.questId);
+ }
+ else
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent Quest entry %u.", i, j+1, action.quest_event.questId);
+
+ if (action.quest_event.target >= TARGET_T_END)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
+
+ break;
+ case ACTION_T_CAST_EVENT:
+ if (!sCreatureStorage.LookupEntry<CreatureInfo>(action.cast_event.creatureId))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent creature entry %u.", i, j+1, action.cast_event.creatureId);
+ if (!sSpellStore.LookupEntry(action.cast_event.spellId))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent SpellID %u.", i, j+1, action.cast_event.spellId);
+ if (action.cast_event.target >= TARGET_T_END)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
+ break;
+ case ACTION_T_SET_UNIT_FIELD:
+ if (action.set_unit_field.field < OBJECT_END || action.set_unit_field.field >= UNIT_END)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u param1 (UNIT_FIELD*). Index out of range for intended use.", i, j+1);
+ if (action.set_unit_field.target >= TARGET_T_END)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
+ break;
+ case ACTION_T_SET_UNIT_FLAG:
+ case ACTION_T_REMOVE_UNIT_FLAG:
+ if (action.unit_flag.target >= TARGET_T_END)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
+ break;
+ case ACTION_T_SET_PHASE:
+ if (action.set_phase.phase >= MAX_PHASE)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phase >= %u. Phase mask cannot be used past phase %u.", i, j+1, MAX_PHASE, MAX_PHASE-1);
+ break;
+ case ACTION_T_INC_PHASE:
+ if (action.set_inc_phase.step == 0)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u is incrementing phase by 0. Was this intended?", i, j+1);
+ else if (std::abs(action.set_inc_phase.step) > MAX_PHASE-1)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u is change phase by too large for any use %i.", i, j+1, action.set_inc_phase.step);
+ break;
+ case ACTION_T_QUEST_EVENT_ALL:
+ if (Quest const* qid = objmgr.GetQuestTemplate(action.quest_event_all.questId))
+ {
+ if (!qid->HasFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u. SpecialFlags for quest entry %u does not include |2, Action will not have any effect.", i, j+1, action.quest_event_all.questId);
+ }
+ else
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent Quest entry %u.", i, j+1, action.quest_event_all.questId);
+ break;
+ case ACTION_T_CAST_EVENT_ALL:
+ if (!sCreatureStorage.LookupEntry<CreatureInfo>(action.cast_event_all.creatureId))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent creature entry %u.", i, j+1, action.cast_event_all.creatureId);
+ if (!sSpellStore.LookupEntry(action.cast_event_all.spellId))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent SpellID %u.", i, j+1, action.cast_event_all.spellId);
+ break;
+ case ACTION_T_REMOVEAURASFROMSPELL:
+ if (!sSpellStore.LookupEntry(action.remove_aura.spellId))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent SpellID %u.", i, j+1, action.remove_aura.spellId);
+ if (action.remove_aura.target >= TARGET_T_END)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
+ break;
+ case ACTION_T_RANDOM_PHASE: //PhaseId1, PhaseId2, PhaseId3
+ if (action.random_phase.phase1 >= MAX_PHASE)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phase1 >= %u. Phase mask cannot be used past phase %u.", i, j+1, MAX_PHASE, MAX_PHASE-1);
+ if (action.random_phase.phase2 >= MAX_PHASE)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phase2 >= %u. Phase mask cannot be used past phase %u.", i, j+1, MAX_PHASE, MAX_PHASE-1);
+ if (action.random_phase.phase3 >= MAX_PHASE)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phase3 >= %u. Phase mask cannot be used past phase %u.", i, j+1, MAX_PHASE, MAX_PHASE-1);
+ break;
+ case ACTION_T_RANDOM_PHASE_RANGE: //PhaseMin, PhaseMax
+ if (action.random_phase_range.phaseMin >= MAX_PHASE)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phaseMin >= %u. Phase mask cannot be used past phase %u.", i, j+1, MAX_PHASE, MAX_PHASE-1);
+ if (action.random_phase_range.phaseMin >= MAX_PHASE)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phaseMax >= %u. Phase mask cannot be used past phase %u.", i, j+1, MAX_PHASE, MAX_PHASE-1);
+ if (action.random_phase_range.phaseMin >= action.random_phase_range.phaseMax)
+ {
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phaseMax <= phaseMin.", i, j+1);
+ std::swap(action.random_phase_range.phaseMin,action.random_phase_range.phaseMax);
+ // equal case processed at call
+ }
+ break;
+ case ACTION_T_SUMMON_ID:
+ if (!sCreatureStorage.LookupEntry<CreatureInfo>(action.summon_id.creatureId))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant creature entry %u.", i, j+1, action.summon_id.creatureId);
+ if (action.summon_id.target >= TARGET_T_END)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
+ if (m_CreatureEventAI_Summon_Map.find(action.summon_id.spawnId) == m_CreatureEventAI_Summon_Map.end())
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u summons missing CreatureEventAI_Summon %u", i, j+1, action.summon_id.spawnId);
+ break;
+ case ACTION_T_KILLED_MONSTER:
+ if (!sCreatureStorage.LookupEntry<CreatureInfo>(action.killed_monster.creatureId))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant creature entry %u.", i, j+1, action.killed_monster.creatureId);
+ if (action.killed_monster.target >= TARGET_T_END)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
+ break;
+ case ACTION_T_SET_INST_DATA:
+ if (!(temp.event_flags & EFLAG_DIFFICULTY_ALL))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u. Cannot set instance data without difficulty event flags.", i, j+1);
+ if (action.set_inst_data.value > 4/*SPECIAL*/)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set instance data above encounter state 4. Custom case?", i, j+1);
+ break;
+ case ACTION_T_SET_INST_DATA64:
+ if (!(temp.event_flags & EFLAG_DIFFICULTY_ALL))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u. Cannot set instance data without difficulty event flags.", i, j+1);
+ if (action.set_inst_data64.target >= TARGET_T_END)
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
+ break;
+ case ACTION_T_UPDATE_TEMPLATE:
+ if (!sCreatureStorage.LookupEntry<CreatureInfo>(action.update_template.creatureId))
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant creature entry %u.", i, j+1, action.update_template.creatureId);
+ break;
+ case ACTION_T_SET_SHEATH:
+ if (action.set_sheath.sheath >= MAX_SHEATH_STATE)
+ {
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses wrong sheath state %u.", i, j+1, action.set_sheath.sheath);
+ action.set_sheath.sheath = SHEATH_STATE_UNARMED;
+ }
+ break;
+ case ACTION_T_SET_INVINCIBILITY_HP_LEVEL:
+ if (action.invincibility_hp_level.is_percent)
+ {
+ if (action.invincibility_hp_level.hp_level > 100)
+ {
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses wrong percent value %u.", i, j+1, action.invincibility_hp_level.hp_level);
+ action.invincibility_hp_level.hp_level = 100;
+ }
+ }
+ break;
+ case ACTION_T_EVADE: //No Params
+ case ACTION_T_FLEE_FOR_ASSIST: //No Params
+ case ACTION_T_DIE: //No Params
+ case ACTION_T_ZONE_COMBAT_PULSE: //No Params
+ case ACTION_T_FORCE_DESPAWN: //No Params
+ case ACTION_T_AUTO_ATTACK: //AllowAttackState (0 = stop attack, anything else means continue attacking)
+ case ACTION_T_COMBAT_MOVEMENT: //AllowCombatMovement (0 = stop combat based movement, anything else continue attacking)
+ case ACTION_T_RANGED_MOVEMENT: //Distance, Angle
+ case ACTION_T_CALL_FOR_HELP: //Distance
+ break;
+
+ case ACTION_T_RANDOM_SAY:
+ case ACTION_T_RANDOM_YELL:
+ case ACTION_T_RANDOM_TEXTEMOTE:
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u currently unused ACTION type. Did you forget to update database?", i, j+1);
+ break;
+
+ case ACTION_T_MOVE_RANDOM_POINT:
+ case ACTION_T_SET_STAND_STATE:
+ case ACTION_T_SET_PHASE_MASK:
+ case ACTION_T_SET_VISIBILITY:
+ case ACTION_T_SET_ACTIVE:
+ case ACTION_T_SET_AGGRESSIVE:
+ case ACTION_T_ATTACK_START_PULSE:
+ case ACTION_T_SUMMON_GO:
+ break;
+
+ default:
+ sLog.outErrorDb("CreatureEventAI: Event %u Action %u have currently not checked at load action type (%u). Need check code update?", i, j+1, temp.action[j].type);
+ break;
+ }
+ }
+
+ //Add to list
+ m_CreatureEventAI_Event_Map[creature_id].push_back(temp);
+ ++Count;
+
+ if (CreatureInfo const* cInfo = sCreatureStorage.LookupEntry<CreatureInfo>(temp.creature_id))
+ {
+ if (!cInfo->AIName || !cInfo->AIName[0])
+ {
+ //sLog.outErrorDb("CreatureEventAI: Creature Entry %u has EventAI script but its AIName is empty. Set to EventAI as default.", cInfo->Entry);
+ size_t len = strlen("EventAI")+1;
+ const_cast<CreatureInfo*>(cInfo)->AIName = new char[len];
+ strncpy(const_cast<char*>(cInfo->AIName), "EventAI", len);
+ }
+ if (strcmp(cInfo->AIName, "EventAI"))
+ {
+ //sLog.outErrorDb("CreatureEventAI: Creature Entry %u has EventAI script but it has AIName %s. EventAI script will be overriden.", cInfo->Entry, cInfo->AIName);
+ }
+ if (cInfo->ScriptID)
+ {
+ //sLog.outErrorDb("CreatureEventAI: Creature Entry %u has EventAI script but it also has C++ script. EventAI script will be overriden.", cInfo->Entry);
+ }
+ }
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u CreatureEventAI scripts", Count);
+ }
+ else
+ {
+ barGoLink bar(1);
+ bar.step();
+ sLog.outString();
+ sLog.outString(">> Loaded 0 CreatureEventAI scripts. DB table `creature_ai_scripts` is empty.");
+ }
+}
diff --git a/src/server/game/AI/EventAI/CreatureEventAIMgr.h b/src/server/game/AI/EventAI/CreatureEventAIMgr.h
new file mode 100644
index 00000000000..ef191b22463
--- /dev/null
+++ b/src/server/game/AI/EventAI/CreatureEventAIMgr.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef TRINITY_CREATURE_EAI_MGR_H
+#define TRINITY_CREATURE_EAI_MGR_H
+
+#include "Common.h"
+#include "CreatureEventAI.h"
+
+class CreatureEventAIMgr
+{
+ public:
+ CreatureEventAIMgr(){};
+ ~CreatureEventAIMgr(){};
+
+ void LoadCreatureEventAI_Texts();
+ void LoadCreatureEventAI_Summons();
+ void LoadCreatureEventAI_Scripts();
+
+ CreatureEventAI_Event_Map const& GetCreatureEventAIMap() const { return m_CreatureEventAI_Event_Map; }
+ CreatureEventAI_Summon_Map const& GetCreatureEventAISummonMap() const { return m_CreatureEventAI_Summon_Map; }
+ CreatureEventAI_TextMap const& GetCreatureEventAITextMap() const { return m_CreatureEventAI_TextMap; }
+
+ private:
+ CreatureEventAI_Event_Map m_CreatureEventAI_Event_Map;
+ CreatureEventAI_Summon_Map m_CreatureEventAI_Summon_Map;
+ CreatureEventAI_TextMap m_CreatureEventAI_TextMap;
+};
+
+#define CreatureEAI_Mgr Trinity::Singleton<CreatureEventAIMgr>::Instance()
+#endif
diff --git a/src/server/game/AI/GuardAI.cpp b/src/server/game/AI/GuardAI.cpp
new file mode 100644
index 00000000000..19d5b5d8354
--- /dev/null
+++ b/src/server/game/AI/GuardAI.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "GuardAI.h"
+#include "Errors.h"
+#include "Player.h"
+#include "ObjectAccessor.h"
+#include "World.h"
+#include "CreatureAIImpl.h"
+
+int GuardAI::Permissible(const Creature *creature)
+{
+ if (creature->isGuard())
+ return PERMIT_BASE_SPECIAL;
+
+ return PERMIT_BASE_NO;
+}
+
+GuardAI::GuardAI(Creature *c) : CreatureAI(c), i_victimGuid(0), i_state(STATE_NORMAL), i_tracker(TIME_INTERVAL_LOOK)
+{
+}
+
+void GuardAI::MoveInLineOfSight(Unit *u)
+{
+ // Ignore Z for flying creatures
+ if (!me->canFly() && me->GetDistanceZ(u) > CREATURE_Z_ATTACK_RANGE)
+ return;
+
+ if (!me->getVictim() && me->canAttack(u) &&
+ (u->IsHostileToPlayers() || me->IsHostileTo(u) /*|| u->getVictim() && me->IsFriendlyTo(u->getVictim())*/) &&
+ u->isInAccessiblePlaceFor(me))
+ {
+ float attackRadius = me->GetAttackDistance(u);
+ if (me->IsWithinDistInMap(u,attackRadius))
+ {
+ //Need add code to let guard support player
+ AttackStart(u);
+ //u->RemoveAurasByType(SPELL_AURA_MOD_STEALTH);
+ }
+ }
+}
+
+void GuardAI::EnterEvadeMode()
+{
+ if (!me->isAlive())
+ {
+ DEBUG_LOG("Creature stopped attacking because he is dead [guid=%u]", me->GetGUIDLow());
+ me->GetMotionMaster()->MoveIdle();
+
+ i_state = STATE_NORMAL;
+
+ i_victimGuid = 0;
+ me->CombatStop(true);
+ me->DeleteThreatList();
+ return;
+ }
+
+ Unit* victim = ObjectAccessor::GetUnit(*me, i_victimGuid);
+
+ if (!victim)
+ {
+ DEBUG_LOG("Creature stopped attacking because victim does not exist [guid=%u]", me->GetGUIDLow());
+ }
+ else if (!victim ->isAlive())
+ {
+ DEBUG_LOG("Creature stopped attacking because victim is dead [guid=%u]", me->GetGUIDLow());
+ }
+ else if (victim ->HasStealthAura())
+ {
+ DEBUG_LOG("Creature stopped attacking because victim is using stealth [guid=%u]", me->GetGUIDLow());
+ }
+ else if (victim ->isInFlight())
+ {
+ DEBUG_LOG("Creature stopped attacking because victim is flying away [guid=%u]", me->GetGUIDLow());
+ }
+ else
+ {
+ DEBUG_LOG("Creature stopped attacking because victim outran him [guid=%u]", me->GetGUIDLow());
+ }
+
+ me->RemoveAllAuras();
+ me->DeleteThreatList();
+ i_victimGuid = 0;
+ me->CombatStop(true);
+ i_state = STATE_NORMAL;
+
+ // Remove TargetedMovementGenerator from MotionMaster stack list, and add HomeMovementGenerator instead
+ if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == TARGETED_MOTION_TYPE)
+ me->GetMotionMaster()->MoveTargetedHome();
+}
+
+void GuardAI::UpdateAI(const uint32 /*diff*/)
+{
+ // update i_victimGuid if me->getVictim() !=0 and changed
+ if (!UpdateVictim())
+ return;
+
+ i_victimGuid = me->getVictim()->GetGUID();
+
+ if (me->isAttackReady())
+ {
+ if (me->IsWithinMeleeRange(me->getVictim()))
+ {
+ me->AttackerStateUpdate(me->getVictim());
+ me->resetAttackTimer();
+ }
+ }
+}
+
+bool GuardAI::IsVisible(Unit *pl) const
+{
+ return me->IsWithinDist(pl,sWorld.getConfig(CONFIG_SIGHT_GUARDER))
+ && pl->isVisibleForOrDetect(me,true);
+}
+
+void GuardAI::JustDied(Unit *killer)
+{
+ if (Player* pkiller = killer->GetCharmerOrOwnerPlayerOrPlayerItself())
+ me->SendZoneUnderAttackMessage(pkiller);
+}
diff --git a/src/server/game/AI/GuardAI.h b/src/server/game/AI/GuardAI.h
new file mode 100644
index 00000000000..73e3692a770
--- /dev/null
+++ b/src/server/game/AI/GuardAI.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef TRINITY_GUARDAI_H
+#define TRINITY_GUARDAI_H
+
+#include "CreatureAI.h"
+#include "Timer.h"
+
+class Creature;
+
+class GuardAI : public CreatureAI
+{
+ enum GuardState
+ {
+ STATE_NORMAL = 1,
+ STATE_LOOK_AT_VICTIM = 2
+ };
+
+ public:
+
+ explicit GuardAI(Creature *c);
+
+ void MoveInLineOfSight(Unit *);
+ void EnterEvadeMode();
+ void JustDied(Unit *);
+ bool IsVisible(Unit *) const;
+
+ void UpdateAI(const uint32);
+ static int Permissible(const Creature *);
+
+ private:
+ uint64 i_victimGuid;
+ GuardState i_state;
+ TimeTracker i_tracker;
+};
+#endif
+
diff --git a/src/server/game/AI/PassiveAI.cpp b/src/server/game/AI/PassiveAI.cpp
new file mode 100644
index 00000000000..c6c92f6df04
--- /dev/null
+++ b/src/server/game/AI/PassiveAI.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "PassiveAI.h"
+#include "Creature.h"
+#include "TemporarySummon.h"
+
+PassiveAI::PassiveAI(Creature *c) : CreatureAI(c) { me->SetReactState(REACT_PASSIVE); }
+PossessedAI::PossessedAI(Creature *c) : CreatureAI(c) { me->SetReactState(REACT_PASSIVE); }
+NullCreatureAI::NullCreatureAI(Creature *c) : CreatureAI(c) { me->SetReactState(REACT_PASSIVE); }
+
+void PassiveAI::UpdateAI(const uint32)
+{
+ if (me->isInCombat() && me->getAttackers().empty())
+ EnterEvadeMode();
+}
+
+void PossessedAI::AttackStart(Unit *target)
+{
+ me->Attack(target, true);
+}
+
+void PossessedAI::UpdateAI(const uint32 /*diff*/)
+{
+ if (me->getVictim())
+ {
+ if (!me->canAttack(me->getVictim()))
+ me->AttackStop();
+ else
+ DoMeleeAttackIfReady();
+ }
+}
+
+void PossessedAI::JustDied(Unit * /*u*/)
+{
+ // We died while possessed, disable our loot
+ me->RemoveFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE);
+}
+
+void PossessedAI::KilledUnit(Unit* victim)
+{
+ // We killed a creature, disable victim's loot
+ if (victim->GetTypeId() == TYPEID_UNIT)
+ victim->RemoveFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE);
+}
+
+void CritterAI::DamageTaken(Unit * /*done_by*/, uint32 &)
+{
+ if (!me->hasUnitState(UNIT_STAT_FLEEING))
+ me->SetControlled(true, UNIT_STAT_FLEEING);
+}
+
+void CritterAI::EnterEvadeMode()
+{
+ if (me->hasUnitState(UNIT_STAT_FLEEING))
+ me->SetControlled(false, UNIT_STAT_FLEEING);
+ CreatureAI::EnterEvadeMode();
+}
+
+void TriggerAI::IsSummonedBy(Unit *summoner)
+{
+ if (me->m_spells[0])
+ me->CastSpell(me, me->m_spells[0], false, 0, 0, summoner->GetGUID());
+}
diff --git a/src/server/game/AI/PassiveAI.h b/src/server/game/AI/PassiveAI.h
new file mode 100644
index 00000000000..c13e2d1a63c
--- /dev/null
+++ b/src/server/game/AI/PassiveAI.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef TRINITY_PASSIVEAI_H
+#define TRINITY_PASSIVEAI_H
+
+#include "CreatureAI.h"
+//#include "CreatureAIImpl.h"
+
+class PassiveAI : public CreatureAI
+{
+ public:
+ explicit PassiveAI(Creature *c);
+
+ void MoveInLineOfSight(Unit *) {}
+ void AttackStart(Unit *) {}
+ void UpdateAI(const uint32);
+
+ static int Permissible(const Creature *) { return PERMIT_BASE_IDLE; }
+};
+
+class PossessedAI : public CreatureAI
+{
+ public:
+ explicit PossessedAI(Creature *c);
+
+ void MoveInLineOfSight(Unit *) {}
+ void AttackStart(Unit *target);
+ void UpdateAI(const uint32);
+ void EnterEvadeMode() {}
+
+ void JustDied(Unit*);
+ void KilledUnit(Unit* victim);
+
+ static int Permissible(const Creature *) { return PERMIT_BASE_IDLE; }
+};
+
+class NullCreatureAI : public CreatureAI
+{
+ public:
+ explicit NullCreatureAI(Creature *c);
+
+ void MoveInLineOfSight(Unit *) {}
+ void AttackStart(Unit *) {}
+ void UpdateAI(const uint32) {}
+ void EnterEvadeMode() {}
+ void OnCharmed(bool /*apply*/) {}
+
+ static int Permissible(const Creature *) { return PERMIT_BASE_IDLE; }
+};
+
+class CritterAI : public PassiveAI
+{
+ public:
+ explicit CritterAI(Creature *c) : PassiveAI(c) {}
+
+ void DamageTaken(Unit *done_by, uint32 & /*damage*/);
+ void EnterEvadeMode();
+};
+
+class TriggerAI : public NullCreatureAI
+{
+ public:
+ explicit TriggerAI(Creature *c) : NullCreatureAI(c) {}
+ void IsSummonedBy(Unit *summoner);
+};
+
+#endif
+
diff --git a/src/server/game/AI/PetAI.cpp b/src/server/game/AI/PetAI.cpp
new file mode 100644
index 00000000000..09ec8fae53f
--- /dev/null
+++ b/src/server/game/AI/PetAI.cpp
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "PetAI.h"
+#include "Errors.h"
+#include "Pet.h"
+#include "Player.h"
+#include "DBCStores.h"
+#include "Spell.h"
+#include "ObjectAccessor.h"
+#include "SpellMgr.h"
+#include "Creature.h"
+#include "World.h"
+#include "Util.h"
+
+int PetAI::Permissible(const Creature *creature)
+{
+ if (creature->isPet())
+ return PERMIT_BASE_SPECIAL;
+
+ return PERMIT_BASE_NO;
+}
+
+PetAI::PetAI(Creature *c) : CreatureAI(c), i_tracker(TIME_INTERVAL_LOOK)
+{
+ m_AllySet.clear();
+ UpdateAllies();
+}
+
+void PetAI::EnterEvadeMode()
+{
+}
+
+bool PetAI::_needToStop() const
+{
+ // This is needed for charmed creatures, as once their target was reset other effects can trigger threat
+ if (me->isCharmed() && me->getVictim() == me->GetCharmer())
+ return true;
+
+ return !me->canAttack(me->getVictim());
+}
+
+void PetAI::_stopAttack()
+{
+ if (!me->isAlive())
+ {
+ DEBUG_LOG("Creature stoped attacking cuz his dead [guid=%u]", me->GetGUIDLow());
+ me->GetMotionMaster()->Clear();
+ me->GetMotionMaster()->MoveIdle();
+ me->CombatStop();
+ me->getHostileRefManager().deleteReferences();
+
+ return;
+ }
+
+ me->AttackStop();
+ me->GetCharmInfo()->SetIsCommandAttack(false);
+ HandleReturnMovement();
+}
+
+void PetAI::UpdateAI(const uint32 diff)
+{
+ if (!me->isAlive())
+ return;
+
+ Unit* owner = me->GetCharmerOrOwner();
+
+ if (m_updateAlliesTimer <= diff)
+ // UpdateAllies self set update timer
+ UpdateAllies();
+ else
+ m_updateAlliesTimer -= diff;
+
+ // me->getVictim() can't be used for check in case stop fighting, me->getVictim() clear at Unit death etc.
+ if (me->getVictim())
+ {
+ if (_needToStop())
+ {
+ DEBUG_LOG("Pet AI stoped attacking [guid=%u]", me->GetGUIDLow());
+ _stopAttack();
+ return;
+ }
+
+ DoMeleeAttackIfReady();
+ }
+ else if (owner && me->GetCharmInfo()) //no victim
+ {
+ Unit *nextTarget = SelectNextTarget();
+
+ if (nextTarget)
+ AttackStart(nextTarget);
+ else
+ HandleReturnMovement();
+ }
+ else if (owner && !me->hasUnitState(UNIT_STAT_FOLLOW)) // no charm info and no victim
+ me->GetMotionMaster()->MoveFollow(owner,PET_FOLLOW_DIST, me->GetFollowAngle());
+
+ if (!me->GetCharmInfo())
+ return;
+
+ // Autocast (casted only in combat or persistent spells in any state)
+ if (me->GetGlobalCooldown() == 0 && !me->hasUnitState(UNIT_STAT_CASTING))
+ {
+ typedef std::vector<std::pair<Unit*, Spell*> > TargetSpellList;
+ TargetSpellList targetSpellStore;
+
+ for (uint8 i = 0; i < me->GetPetAutoSpellSize(); ++i)
+ {
+ uint32 spellID = me->GetPetAutoSpellOnPos(i);
+ if (!spellID)
+ continue;
+
+ SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellID);
+ if (!spellInfo)
+ continue;
+
+ // ignore some combinations of combat state and combat/noncombat spells
+ if (!me->getVictim())
+ {
+ // ignore attacking spells, and allow only self/around spells
+ if (!IsPositiveSpell(spellInfo->Id))
+ continue;
+
+ // non combat spells allowed
+ // only pet spells have IsNonCombatSpell and not fit this reqs:
+ // Consume Shadows, Lesser Invisibility, so ignore checks for its
+ if (!IsNonCombatSpell(spellInfo))
+ {
+ // allow only spell without spell cost or with spell cost but not duration limit
+ int32 duration = GetSpellDuration(spellInfo);
+ if ((spellInfo->manaCost || spellInfo->ManaCostPercentage || spellInfo->manaPerSecond) && duration > 0)
+ continue;
+
+ // allow only spell without cooldown > duration
+ int32 cooldown = GetSpellRecoveryTime(spellInfo);
+ if (cooldown >= 0 && duration >= 0 && cooldown > duration)
+ continue;
+ }
+ }
+ else
+ {
+ // just ignore non-combat spells
+ if (IsNonCombatSpell(spellInfo))
+ continue;
+ }
+
+ Spell *spell = new Spell(me, spellInfo, false, 0);
+
+ // Fix to allow pets on STAY to autocast
+ if (me->getVictim() && _CanAttack(me->getVictim()) && spell->CanAutoCast(me->getVictim()))
+ {
+ targetSpellStore.push_back(std::make_pair<Unit*, Spell*>(me->getVictim(), spell));
+ continue;
+ }
+ else
+ {
+ bool spellUsed = false;
+ for (std::set<uint64>::const_iterator tar = m_AllySet.begin(); tar != m_AllySet.end(); ++tar)
+ {
+ Unit* Target = ObjectAccessor::GetUnit(*me,*tar);
+
+ //only buff targets that are in combat, unless the spell can only be cast while out of combat
+ if (!Target)
+ continue;
+
+ if (spell->CanAutoCast(Target))
+ {
+ targetSpellStore.push_back(std::make_pair<Unit*, Spell*>(Target, spell));
+ spellUsed = true;
+ break;
+ }
+ }
+ if (!spellUsed)
+ delete spell;
+ }
+ }
+
+ //found units to cast on to
+ if (!targetSpellStore.empty())
+ {
+ uint32 index = urand(0, targetSpellStore.size() - 1);
+
+ Spell* spell = targetSpellStore[index].second;
+ Unit* target = targetSpellStore[index].first;
+
+ targetSpellStore.erase(targetSpellStore.begin() + index);
+
+ SpellCastTargets targets;
+ targets.setUnitTarget(target);
+
+ if (!me->HasInArc(M_PI, target))
+ {
+ me->SetInFront(target);
+ if (target && target->GetTypeId() == TYPEID_PLAYER)
+ me->SendUpdateToPlayer(target->ToPlayer());
+
+ if (owner && owner->GetTypeId() == TYPEID_PLAYER)
+ me->SendUpdateToPlayer(owner->ToPlayer());
+ }
+
+ me->AddCreatureSpellCooldown(spell->m_spellInfo->Id);
+
+ spell->prepare(&targets);
+ }
+
+ // deleted cached Spell objects
+ for (TargetSpellList::const_iterator itr = targetSpellStore.begin(); itr != targetSpellStore.end(); ++itr)
+ delete itr->second;
+ }
+}
+
+void PetAI::UpdateAllies()
+{
+ Unit* owner = me->GetCharmerOrOwner();
+ Group *pGroup = NULL;
+
+ m_updateAlliesTimer = 10*IN_MILISECONDS; //update friendly targets every 10 seconds, lesser checks increase performance
+
+ if (!owner)
+ return;
+ else if (owner->GetTypeId() == TYPEID_PLAYER)
+ pGroup = owner->ToPlayer()->GetGroup();
+
+ //only pet and owner/not in group->ok
+ if (m_AllySet.size() == 2 && !pGroup)
+ return;
+ //owner is in group; group members filled in already (no raid -> subgroupcount = whole count)
+ if (pGroup && !pGroup->isRaidGroup() && m_AllySet.size() == (pGroup->GetMembersCount() + 2))
+ return;
+
+ m_AllySet.clear();
+ m_AllySet.insert(me->GetGUID());
+ if (pGroup) //add group
+ {
+ for (GroupReference *itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next())
+ {
+ Player* Target = itr->getSource();
+ if (!Target || !pGroup->SameSubGroup((Player*)owner, Target))
+ continue;
+
+ if (Target->GetGUID() == owner->GetGUID())
+ continue;
+
+ m_AllySet.insert(Target->GetGUID());
+ }
+ }
+ else //remove group
+ m_AllySet.insert(owner->GetGUID());
+}
+
+void PetAI::KilledUnit(Unit *victim)
+{
+ // Called from Unit::Kill() in case where pet or owner kills something
+ // if owner killed this victim, pet may still be attacking something else
+ if (me->getVictim() && me->getVictim() != victim)
+ return;
+
+ // Clear target just in case. May help problem where health / focus / mana
+ // regen gets stuck. Also resets attack command.
+ // Can't use _stopAttack() because that activates movement handlers and ignores
+ // next target selection
+ me->AttackStop();
+ me->GetCharmInfo()->SetIsCommandAttack(false);
+
+ Unit *nextTarget = SelectNextTarget();
+
+ if (nextTarget)
+ AttackStart(nextTarget);
+ else
+ HandleReturnMovement(); // Return
+}
+
+void PetAI::AttackStart(Unit *target)
+{
+ // Overrides Unit::AttackStart to correctly evaluate Pet states
+
+ // Check all pet states to decide if we can attack this target
+ if (!_CanAttack(target))
+ return;
+
+ // We can attack, should we chase or not?
+ if (me->GetCharmInfo()->HasCommandState(COMMAND_FOLLOW))
+ DoAttack(target,true); // FOLLOW, attack with chase
+ else
+ {
+ if (me->GetCharmInfo()->IsCommandAttack())
+ DoAttack(target,true); // STAY or FOLLOW, player clicked "attack" so attack with chase
+ else
+ DoAttack(target,false); // STAY, target in range, attack not clicked so attack without chase
+ }
+}
+
+Unit *PetAI::SelectNextTarget()
+{
+ // Provides next target selection after current target death
+
+ // Passive pets don't do next target selection
+ if (me->HasReactState(REACT_PASSIVE))
+ return NULL;
+
+ // Check pet's attackers first to prevent dragging mobs back
+ // to owner
+ if (me->getAttackerForHelper())
+ return me->getAttackerForHelper();
+
+ // Check owner's attackers if pet didn't have any
+ if (me->GetCharmerOrOwner()->getAttackerForHelper())
+ return me->GetCharmerOrOwner()->getAttackerForHelper();
+
+ // 3.0.2 - Pets now start attacking their owners target in defensive mode as soon as the hunter does
+ if (me->GetCharmerOrOwner()->getVictim())
+ return me->GetCharmerOrOwner()->getVictim();
+
+ // Default
+ return NULL;
+}
+
+void PetAI::HandleReturnMovement()
+{
+ // Handles moving the pet back to stay or owner
+
+ if (me->GetCharmInfo()->HasCommandState(COMMAND_STAY))
+ {
+ if (!me->GetCharmInfo()->IsAtStay() && !me->GetCharmInfo()->IsReturning())
+ {
+ // Return to previous position where stay was clicked
+ if (!me->GetCharmInfo()->IsCommandAttack())
+ {
+ float x,y,z;
+
+ me->GetCharmInfo()->GetStayPosition(x, y, z);
+ me->GetCharmInfo()->SetIsReturning(true);
+ me->GetMotionMaster()->Clear();
+ me->GetMotionMaster()->MovePoint(me->GetGUIDLow(),x,y,z);
+ }
+ }
+ }
+ else // COMMAND_FOLLOW
+ {
+ if (!me->GetCharmInfo()->IsFollowing() && !me->GetCharmInfo()->IsReturning())
+ {
+ if (!me->GetCharmInfo()->IsCommandAttack())
+ {
+ me->GetCharmInfo()->SetIsReturning(true);
+ me->GetMotionMaster()->Clear();
+ me->GetMotionMaster()->MoveFollow(me->GetCharmerOrOwner(), PET_FOLLOW_DIST, me->GetFollowAngle());
+ }
+ }
+ }
+
+}
+
+void PetAI::DoAttack(Unit *target, bool chase)
+{
+ // Handles attack with or without chase and also resets all
+ // PetAI flags for next update / creature kill
+
+ // me->GetCharmInfo()->SetIsCommandAttack(false);
+
+ // The following conditions are true if chase == true
+ // (Follow && (Aggressive || Defensive))
+ // ((Stay || Follow) && (Passive && player clicked attack))
+
+ if (chase)
+ {
+ if (me->Attack(target,true))
+ {
+ me->GetCharmInfo()->SetIsAtStay(false);
+ me->GetCharmInfo()->SetIsFollowing(false);
+ me->GetCharmInfo()->SetIsReturning(false);
+ me->GetMotionMaster()->Clear();
+ me->GetMotionMaster()->MoveChase(target);
+ }
+ }
+ else // (Stay && ((Aggressive || Defensive) && In Melee Range)))
+ {
+ me->GetCharmInfo()->SetIsAtStay(true);
+ me->GetCharmInfo()->SetIsFollowing(false);
+ me->GetCharmInfo()->SetIsReturning(false);
+ me->Attack(target,true);
+ }
+}
+
+void PetAI::MovementInform(uint32 moveType, uint32 data)
+{
+ // Receives notification when pet reaches stay or follow owner
+ switch (moveType)
+ {
+ case POINT_MOTION_TYPE:
+ {
+ // Pet is returning to where stay was clicked. data should be
+ // pet's GUIDLow since we set that as the waypoint ID
+ if (data == me->GetGUIDLow() && me->GetCharmInfo()->IsReturning())
+ {
+ me->GetCharmInfo()->SetIsAtStay(true);
+ me->GetCharmInfo()->SetIsReturning(false);
+ me->GetCharmInfo()->SetIsFollowing(false);
+ me->GetCharmInfo()->SetIsCommandAttack(false);
+ me->GetMotionMaster()->Clear();
+ me->GetMotionMaster()->MoveIdle();
+ }
+ }
+ break;
+
+ case TARGETED_MOTION_TYPE:
+ {
+ // If data is owner's GUIDLow then we've reached follow point,
+ // otherwise we're probably chasing a creature
+ if (me->GetCharmerOrOwner() && me->GetCharmInfo() && data == me->GetCharmerOrOwner()->GetGUIDLow() && me->GetCharmInfo()->IsReturning())
+ {
+ me->GetCharmInfo()->SetIsAtStay(false);
+ me->GetCharmInfo()->SetIsReturning(false);
+ me->GetCharmInfo()->SetIsFollowing(true);
+ me->GetCharmInfo()->SetIsCommandAttack(false);
+ me->addUnitState(UNIT_STAT_FOLLOW);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+bool PetAI::_CanAttack(Unit *target)
+{
+ // Evaluates wether a pet can attack a specific
+ // target based on CommandState, ReactState and other flags
+
+ // Returning - check first since pets returning ignore attacks
+ if (me->GetCharmInfo()->IsReturning())
+ return false;
+
+ // Passive - check now so we don't have to worry about passive in later checks
+ if (me->HasReactState(REACT_PASSIVE))
+ return me->GetCharmInfo()->IsCommandAttack();
+
+ // Pets commanded to attack should not stop their approach if attacked by another creature
+ if (me->getVictim() && (me->getVictim() != target))
+ return !me->GetCharmInfo()->IsCommandAttack();
+
+ // From this point on, pet will always be either aggressive or defensive
+
+ // Stay - can attack if target is within range or commanded to
+ if (me->GetCharmInfo()->HasCommandState(COMMAND_STAY))
+ return (me->IsWithinMeleeRange(target, MIN_MELEE_REACH) || me->GetCharmInfo()->IsCommandAttack());
+
+ // Follow
+ if (me->GetCharmInfo()->HasCommandState(COMMAND_FOLLOW))
+ return true;
+
+ // default, though we shouldn't ever get here
+ return false;
+}
diff --git a/src/server/game/AI/PetAI.h b/src/server/game/AI/PetAI.h
new file mode 100644
index 00000000000..f6087a129ae
--- /dev/null
+++ b/src/server/game/AI/PetAI.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef TRINITY_PETAI_H
+#define TRINITY_PETAI_H
+
+#include "CreatureAI.h"
+#include "Timer.h"
+
+class Creature;
+class Spell;
+
+class PetAI : public CreatureAI
+{
+ public:
+
+ explicit PetAI(Creature *c);
+
+ void EnterEvadeMode();
+ void JustDied(Unit * /*who*/) { _stopAttack(); }
+
+ void UpdateAI(const uint32);
+ static int Permissible(const Creature *);
+
+ void KilledUnit(Unit * /*victim*/);
+ void AttackStart(Unit *target);
+ void MovementInform(uint32 moveType, uint32 data);
+
+ private:
+ bool _isVisible(Unit *) const;
+ bool _needToStop(void) const;
+ void _stopAttack(void);
+
+ void UpdateAllies();
+
+ TimeTracker i_tracker;
+ bool inCombat;
+ std::set<uint64> m_AllySet;
+ uint32 m_updateAlliesTimer;
+
+ Unit *SelectNextTarget();
+ void HandleReturnMovement();
+ void DoAttack(Unit *target, bool chase);
+ bool _CanAttack(Unit *target);
+};
+#endif
+
diff --git a/src/server/game/AI/ReactorAI.cpp b/src/server/game/AI/ReactorAI.cpp
new file mode 100644
index 00000000000..fdca6314747
--- /dev/null
+++ b/src/server/game/AI/ReactorAI.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "ByteBuffer.h"
+#include "ReactorAI.h"
+#include "Errors.h"
+#include "Log.h"
+#include "ObjectAccessor.h"
+#include "CreatureAIImpl.h"
+
+#define REACTOR_VISIBLE_RANGE (26.46f)
+
+int
+ReactorAI::Permissible(const Creature *creature)
+{
+ if (creature->isCivilian() || creature->IsNeutralToAll())
+ return PERMIT_BASE_REACTIVE;
+
+ return PERMIT_BASE_NO;
+}
+
+void
+ReactorAI::MoveInLineOfSight(Unit *)
+{
+}
+
+void
+ReactorAI::UpdateAI(const uint32 /*time_diff*/)
+{
+ // update i_victimGuid if me->getVictim() !=0 and changed
+ if (!UpdateVictim())
+ return;
+
+ if (me->isAttackReady())
+ {
+ if (me->IsWithinMeleeRange(me->getVictim()))
+ {
+ me->AttackerStateUpdate(me->getVictim());
+ me->resetAttackTimer();
+ }
+ }
+}
diff --git a/src/server/game/AI/ReactorAI.h b/src/server/game/AI/ReactorAI.h
new file mode 100644
index 00000000000..ae4e6403962
--- /dev/null
+++ b/src/server/game/AI/ReactorAI.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef TRINITY_REACTORAI_H
+#define TRINITY_REACTORAI_H
+
+#include "CreatureAI.h"
+
+class Unit;
+
+class ReactorAI : public CreatureAI
+{
+ public:
+
+ explicit ReactorAI(Creature *c) : CreatureAI(c) {}
+
+ void MoveInLineOfSight(Unit *);
+
+ void UpdateAI(const uint32);
+ static int Permissible(const Creature *);
+};
+#endif
+
diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp
new file mode 100644
index 00000000000..8c4ddd14f07
--- /dev/null
+++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp
@@ -0,0 +1,740 @@
+/* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * Thanks to the original authors: ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
+ *
+ * This program is free software licensed under GPL version 2
+ * Please see the included DOCS/LICENSE.TXT for more information */
+
+#include "ScriptedPch.h"
+#include "Item.h"
+#include "Spell.h"
+#include "ObjectMgr.h"
+#include "TemporarySummon.h"
+
+// Spell summary for ScriptedAI::SelectSpell
+struct TSpellSummary
+{
+ uint8 Targets; // set of enum SelectTarget
+ uint8 Effects; // set of enum SelectEffect
+} *SpellSummary;
+
+void SummonList::DoZoneInCombat(uint32 entry)
+{
+ for (iterator i = begin(); i != end();)
+ {
+ Creature *summon = Unit::GetCreature(*me, *i);
+ ++i;
+ if (summon && summon->IsAIEnabled
+ && (!entry || summon->GetEntry() == entry))
+ summon->AI()->DoZoneInCombat();
+ }
+}
+
+void SummonList::DoAction(uint32 entry, uint32 info)
+{
+ for (iterator i = begin(); i != end();)
+ {
+ Creature *summon = Unit::GetCreature(*me, *i);
+ ++i;
+ if (summon && summon->IsAIEnabled
+ && (!entry || summon->GetEntry() == entry))
+ summon->AI()->DoAction(info);
+ }
+}
+
+void SummonList::DespawnEntry(uint32 entry)
+{
+ for (iterator i = begin(); i != end();)
+ {
+ Creature *summon = Unit::GetCreature(*me, *i);
+ if (!summon)
+ erase(i++);
+ else if (summon->GetEntry() == entry)
+ {
+ erase(i++);
+ summon->setDeathState(JUST_DIED);
+ summon->RemoveCorpse();
+ }
+ else
+ ++i;
+ }
+}
+
+void SummonList::DespawnAll()
+{
+ while (!empty())
+ {
+ Creature *summon = Unit::GetCreature(*me, *begin());
+ if (!summon)
+ erase(begin());
+ else
+ {
+ erase(begin());
+ if (summon->isSummon())
+ {
+ summon->DestroyForNearbyPlayers();
+ CAST_SUM(summon)->UnSummon();
+ }
+ else
+ summon->DisappearAndDie();
+ }
+ }
+}
+
+ScriptedAI::ScriptedAI(Creature* pCreature) : CreatureAI(pCreature),
+ me(pCreature),
+ IsFleeing(false),
+ m_bCombatMovement(true),
+ m_uiEvadeCheckCooldown(2500)
+{
+ m_heroicMode = me->GetMap()->IsHeroic();
+ m_difficulty = Difficulty(me->GetMap()->GetSpawnMode());
+}
+
+void ScriptedAI::AttackStartNoMove(Unit* pWho)
+{
+ if (!pWho)
+ return;
+
+ if (me->Attack(pWho, false))
+ DoStartNoMovement(pWho);
+}
+
+void ScriptedAI::UpdateAI(const uint32 /*uiDiff*/)
+{
+ //Check if we have a current target
+ if (!UpdateVictim())
+ return;
+
+ if (me->isAttackReady())
+ {
+ //If we are within range melee the target
+ if (me->IsWithinMeleeRange(me->getVictim()))
+ {
+ me->AttackerStateUpdate(me->getVictim());
+ me->resetAttackTimer();
+ }
+ }
+}
+
+void ScriptedAI::DoStartMovement(Unit* pVictim, float fDistance, float fAngle)
+{
+ if (pVictim)
+ me->GetMotionMaster()->MoveChase(pVictim, fDistance, fAngle);
+}
+
+void ScriptedAI::DoStartNoMovement(Unit* pVictim)
+{
+ if (!pVictim)
+ return;
+
+ me->GetMotionMaster()->MoveIdle();
+}
+
+void ScriptedAI::DoStopAttack()
+{
+ if (me->getVictim())
+ me->AttackStop();
+}
+
+void ScriptedAI::DoCastSpell(Unit* pTarget, SpellEntry const* pSpellInfo, bool bTriggered)
+{
+ if (!pTarget || me->IsNonMeleeSpellCasted(false))
+ return;
+
+ me->StopMoving();
+ me->CastSpell(pTarget, pSpellInfo, bTriggered);
+}
+
+void ScriptedAI::DoPlaySoundToSet(WorldObject* pSource, uint32 uiSoundId)
+{
+ if (!pSource)
+ return;
+
+ if (!GetSoundEntriesStore()->LookupEntry(uiSoundId))
+ {
+ error_log("TSCR: Invalid soundId %u used in DoPlaySoundToSet (Source: TypeId %u, GUID %u)", uiSoundId, pSource->GetTypeId(), pSource->GetGUIDLow());
+ return;
+ }
+
+ pSource->PlayDirectSound(uiSoundId);
+}
+
+Creature* ScriptedAI::DoSpawnCreature(uint32 uiId, float fX, float fY, float fZ, float fAngle, uint32 uiType, uint32 uiDespawntime)
+{
+ return me->SummonCreature(uiId, me->GetPositionX()+fX, me->GetPositionY()+fY, me->GetPositionZ()+fZ, fAngle, (TempSummonType)uiType, uiDespawntime);
+}
+
+Unit* ScriptedAI::SelectUnit(SelectAggroTarget pTarget, uint32 uiPosition)
+{
+ //ThreatList m_threatlist;
+ std::list<HostileReference*>& threatlist = me->getThreatManager().getThreatList();
+ std::list<HostileReference*>::iterator itr = threatlist.begin();
+ std::list<HostileReference*>::reverse_iterator ritr = threatlist.rbegin();
+
+ if (uiPosition >= threatlist.size() || !threatlist.size())
+ return NULL;
+
+ switch (pTarget)
+ {
+ case SELECT_TARGET_RANDOM:
+ advance (itr , uiPosition + (rand() % (threatlist.size() - uiPosition)));
+ return Unit::GetUnit((*me),(*itr)->getUnitGuid());
+ break;
+
+ case SELECT_TARGET_TOPAGGRO:
+ advance (itr , uiPosition);
+ return Unit::GetUnit((*me),(*itr)->getUnitGuid());
+ break;
+
+ case SELECT_TARGET_BOTTOMAGGRO:
+ advance (ritr , uiPosition);
+ return Unit::GetUnit((*me),(*ritr)->getUnitGuid());
+ break;
+
+ default:
+ return UnitAI::SelectTarget(pTarget, uiPosition);
+ }
+}
+
+SpellEntry const* ScriptedAI::SelectSpell(Unit* pTarget, uint32 uiSchool, uint32 uiMechanic, SelectTargetType selectTargets, uint32 uiPowerCostMin, uint32 uiPowerCostMax, float fRangeMin, float fRangeMax, SelectEffect selectEffects)
+{
+ //No target so we can't cast
+ if (!pTarget)
+ return false;
+
+ //Silenced so we can't cast
+ if (me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED))
+ return false;
+
+ //Using the extended script system we first create a list of viable spells
+ SpellEntry const* apSpell[CREATURE_MAX_SPELLS];
+ memset(apSpell, 0, sizeof(SpellEntry*)*CREATURE_MAX_SPELLS);
+
+ uint32 uiSpellCount = 0;
+
+ SpellEntry const* pTempSpell;
+ SpellRangeEntry const* pTempRange;
+
+ //Check if each spell is viable(set it to null if not)
+ for (uint32 i = 0; i < CREATURE_MAX_SPELLS; i++)
+ {
+ pTempSpell = GetSpellStore()->LookupEntry(me->m_spells[i]);
+
+ //This spell doesn't exist
+ if (!pTempSpell)
+ continue;
+
+ // Targets and Effects checked first as most used restrictions
+ //Check the spell targets if specified
+ if (selectTargets && !(SpellSummary[me->m_spells[i]].Targets & (1 << (selectTargets-1))))
+ continue;
+
+ //Check the type of spell if we are looking for a specific spell type
+ if (selectEffects && !(SpellSummary[me->m_spells[i]].Effects & (1 << (selectEffects-1))))
+ continue;
+
+ //Check for school if specified
+ if (uiSchool && (pTempSpell->SchoolMask & uiSchool) == 0)
+ continue;
+
+ //Check for spell mechanic if specified
+ if (uiMechanic && pTempSpell->Mechanic != uiMechanic)
+ continue;
+
+ //Make sure that the spell uses the requested amount of power
+ if (uiPowerCostMin && pTempSpell->manaCost < uiPowerCostMin)
+ continue;
+
+ if (uiPowerCostMax && pTempSpell->manaCost > uiPowerCostMax)
+ continue;
+
+ //Continue if we don't have the mana to actually cast this spell
+ if (pTempSpell->manaCost > me->GetPower((Powers)pTempSpell->powerType))
+ continue;
+
+ //Get the Range
+ pTempRange = GetSpellRangeStore()->LookupEntry(pTempSpell->rangeIndex);
+
+ //Spell has invalid range store so we can't use it
+ if (!pTempRange)
+ continue;
+
+ //Check if the spell meets our range requirements
+ if (fRangeMin && me->GetSpellMinRangeForTarget(pTarget, pTempRange) < fRangeMin)
+ continue;
+ if (fRangeMax && me->GetSpellMaxRangeForTarget(pTarget, pTempRange) > fRangeMax)
+ continue;
+
+ //Check if our target is in range
+ if (me->IsWithinDistInMap(pTarget, me->GetSpellMinRangeForTarget(pTarget, pTempRange)) || !me->IsWithinDistInMap(pTarget, me->GetSpellMaxRangeForTarget(pTarget, pTempRange)))
+ continue;
+
+ //All good so lets add it to the spell list
+ apSpell[uiSpellCount] = pTempSpell;
+ ++uiSpellCount;
+ }
+
+ //We got our usable spells so now lets randomly pick one
+ if (!uiSpellCount)
+ return NULL;
+
+ return apSpell[rand()%uiSpellCount];
+}
+
+bool ScriptedAI::CanCast(Unit* pTarget, SpellEntry const* pSpell, bool bTriggered)
+{
+ //No target so we can't cast
+ if (!pTarget || !pSpell)
+ return false;
+
+ //Silenced so we can't cast
+ if (!bTriggered && me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED))
+ return false;
+
+ //Check for power
+ if (!bTriggered && me->GetPower((Powers)pSpell->powerType) < pSpell->manaCost)
+ return false;
+
+ SpellRangeEntry const* pTempRange = GetSpellRangeStore()->LookupEntry(pSpell->rangeIndex);
+
+ //Spell has invalid range store so we can't use it
+ if (!pTempRange)
+ return false;
+
+ //Unit is out of range of this spell
+ if (me->IsInRange(pTarget, me->GetSpellMinRangeForTarget(pTarget, pTempRange), me->GetSpellMaxRangeForTarget(pTarget, pTempRange)))
+ return false;
+
+ return true;
+}
+
+void FillSpellSummary()
+{
+ SpellSummary = new TSpellSummary[GetSpellStore()->GetNumRows()];
+
+ SpellEntry const* pTempSpell;
+
+ for (uint32 i = 0; i < GetSpellStore()->GetNumRows(); ++i)
+ {
+ SpellSummary[i].Effects = 0;
+ SpellSummary[i].Targets = 0;
+
+ pTempSpell = GetSpellStore()->LookupEntry(i);
+ //This spell doesn't exist
+ if (!pTempSpell)
+ continue;
+
+ for (uint32 j = 0; j < 3; ++j)
+ {
+ //Spell targets self
+ if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_CASTER)
+ SpellSummary[i].Targets |= 1 << (SELECT_TARGET_SELF-1);
+
+ //Spell targets a single enemy
+ if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_ENEMY ||
+ pTempSpell->EffectImplicitTargetA[j] == TARGET_DST_TARGET_ENEMY)
+ SpellSummary[i].Targets |= 1 << (SELECT_TARGET_SINGLE_ENEMY-1);
+
+ //Spell targets AoE at enemy
+ if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_AREA_ENEMY_SRC ||
+ pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_AREA_ENEMY_DST ||
+ pTempSpell->EffectImplicitTargetA[j] == TARGET_SRC_CASTER ||
+ pTempSpell->EffectImplicitTargetA[j] == TARGET_DEST_DYNOBJ_ENEMY)
+ SpellSummary[i].Targets |= 1 << (SELECT_TARGET_AOE_ENEMY-1);
+
+ //Spell targets an enemy
+ if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_ENEMY ||
+ pTempSpell->EffectImplicitTargetA[j] == TARGET_DST_TARGET_ENEMY ||
+ pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_AREA_ENEMY_SRC ||
+ pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_AREA_ENEMY_DST ||
+ pTempSpell->EffectImplicitTargetA[j] == TARGET_SRC_CASTER ||
+ pTempSpell->EffectImplicitTargetA[j] == TARGET_DEST_DYNOBJ_ENEMY)
+ SpellSummary[i].Targets |= 1 << (SELECT_TARGET_ANY_ENEMY-1);
+
+ //Spell targets a single friend(or self)
+ if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_CASTER ||
+ pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_ALLY ||
+ pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_PARTY)
+ SpellSummary[i].Targets |= 1 << (SELECT_TARGET_SINGLE_FRIEND-1);
+
+ //Spell targets aoe friends
+ if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_PARTY_CASTER ||
+ pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_PARTY_TARGET ||
+ pTempSpell->EffectImplicitTargetA[j] == TARGET_SRC_CASTER)
+ SpellSummary[i].Targets |= 1 << (SELECT_TARGET_AOE_FRIEND-1);
+
+ //Spell targets any friend(or self)
+ if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_CASTER ||
+ pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_ALLY ||
+ pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_PARTY ||
+ pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_PARTY_CASTER ||
+ pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_PARTY_TARGET ||
+ pTempSpell->EffectImplicitTargetA[j] == TARGET_SRC_CASTER)
+ SpellSummary[i].Targets |= 1 << (SELECT_TARGET_ANY_FRIEND-1);
+
+ //Make sure that this spell includes a damage effect
+ if (pTempSpell->Effect[j] == SPELL_EFFECT_SCHOOL_DAMAGE ||
+ pTempSpell->Effect[j] == SPELL_EFFECT_INSTAKILL ||
+ pTempSpell->Effect[j] == SPELL_EFFECT_ENVIRONMENTAL_DAMAGE ||
+ pTempSpell->Effect[j] == SPELL_EFFECT_HEALTH_LEECH)
+ SpellSummary[i].Effects |= 1 << (SELECT_EFFECT_DAMAGE-1);
+
+ //Make sure that this spell includes a healing effect (or an apply aura with a periodic heal)
+ if (pTempSpell->Effect[j] == SPELL_EFFECT_HEAL ||
+ pTempSpell->Effect[j] == SPELL_EFFECT_HEAL_MAX_HEALTH ||
+ pTempSpell->Effect[j] == SPELL_EFFECT_HEAL_MECHANICAL ||
+ (pTempSpell->Effect[j] == SPELL_EFFECT_APPLY_AURA && pTempSpell->EffectApplyAuraName[j] == 8))
+ SpellSummary[i].Effects |= 1 << (SELECT_EFFECT_HEALING-1);
+
+ //Make sure that this spell applies an aura
+ if (pTempSpell->Effect[j] == SPELL_EFFECT_APPLY_AURA)
+ SpellSummary[i].Effects |= 1 << (SELECT_EFFECT_AURA-1);
+ }
+ }
+}
+
+void ScriptedAI::DoResetThreat()
+{
+ if (!me->CanHaveThreatList() || me->getThreatManager().isThreatListEmpty())
+ {
+ error_log("TSCR: DoResetThreat called for creature that either cannot have threat list or has empty threat list (me entry = %d)", me->GetEntry());
+ return;
+ }
+
+ std::list<HostileReference*>& threatlist = me->getThreatManager().getThreatList();
+
+ for (std::list<HostileReference*>::iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
+ {
+ Unit* pUnit = Unit::GetUnit((*me), (*itr)->getUnitGuid());
+
+ if (pUnit && DoGetThreat(pUnit))
+ DoModifyThreatPercent(pUnit, -100);
+ }
+}
+
+float ScriptedAI::DoGetThreat(Unit* pUnit)
+{
+ if (!pUnit) return 0.0f;
+ return me->getThreatManager().getThreat(pUnit);
+}
+
+void ScriptedAI::DoModifyThreatPercent(Unit* pUnit, int32 pct)
+{
+ if (!pUnit) return;
+ me->getThreatManager().modifyThreatPercent(pUnit, pct);
+}
+
+void ScriptedAI::DoTeleportTo(float fX, float fY, float fZ, uint32 uiTime)
+{
+ me->Relocate(fX, fY, fZ);
+ me->SendMonsterMove(fX, fY, fZ, uiTime);
+}
+
+void ScriptedAI::DoTeleportTo(const float fPos[4])
+{
+ me->NearTeleportTo(fPos[0], fPos[1], fPos[2], fPos[3]);
+}
+
+void ScriptedAI::DoTeleportPlayer(Unit* pUnit, float fX, float fY, float fZ, float fO)
+{
+ if (!pUnit || pUnit->GetTypeId() != TYPEID_PLAYER)
+ {
+ if (pUnit)
+ error_log("TSCR: Creature %u (Entry: %u) Tried to teleport non-player unit (Type: %u GUID: %u) to x: %f y:%f z: %f o: %f. Aborted.", me->GetGUID(), me->GetEntry(), pUnit->GetTypeId(), pUnit->GetGUID(), fX, fY, fZ, fO);
+ return;
+ }
+
+ CAST_PLR(pUnit)->TeleportTo(pUnit->GetMapId(), fX, fY, fZ, fO, TELE_TO_NOT_LEAVE_COMBAT);
+}
+
+void ScriptedAI::DoTeleportAll(float fX, float fY, float fZ, float fO)
+{
+ Map *map = me->GetMap();
+ if (!map->IsDungeon())
+ return;
+
+ Map::PlayerList const &PlayerList = map->GetPlayers();
+ for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
+ if (Player* i_pl = i->getSource())
+ if (i_pl->isAlive())
+ i_pl->TeleportTo(me->GetMapId(), fX, fY, fZ, fO, TELE_TO_NOT_LEAVE_COMBAT);
+}
+
+Unit* ScriptedAI::DoSelectLowestHpFriendly(float fRange, uint32 uiMinHPDiff)
+{
+ Unit* pUnit = NULL;
+ Trinity::MostHPMissingInRange u_check(me, fRange, uiMinHPDiff);
+ Trinity::UnitLastSearcher<Trinity::MostHPMissingInRange> searcher(me, pUnit, u_check);
+ me->VisitNearbyObject(fRange, searcher);
+
+ return pUnit;
+}
+
+std::list<Creature*> ScriptedAI::DoFindFriendlyCC(float fRange)
+{
+ std::list<Creature*> pList;
+ Trinity::FriendlyCCedInRange u_check(me, fRange);
+ Trinity::CreatureListSearcher<Trinity::FriendlyCCedInRange> searcher(me, pList, u_check);
+ me->VisitNearbyObject(fRange, searcher);
+ return pList;
+}
+
+std::list<Creature*> ScriptedAI::DoFindFriendlyMissingBuff(float fRange, uint32 uiSpellid)
+{
+ std::list<Creature*> pList;
+ Trinity::FriendlyMissingBuffInRange u_check(me, fRange, uiSpellid);
+ Trinity::CreatureListSearcher<Trinity::FriendlyMissingBuffInRange> searcher(me, pList, u_check);
+ me->VisitNearbyObject(fRange, searcher);
+ return pList;
+}
+
+Player* ScriptedAI::GetPlayerAtMinimumRange(float fMinimumRange)
+{
+ Player* pPlayer = NULL;
+
+ CellPair pair(Trinity::ComputeCellPair(me->GetPositionX(), me->GetPositionY()));
+ Cell cell(pair);
+ cell.data.Part.reserved = ALL_DISTRICT;
+ cell.SetNoCreate();
+
+ Trinity::PlayerAtMinimumRangeAway check(me, fMinimumRange);
+ Trinity::PlayerSearcher<Trinity::PlayerAtMinimumRangeAway> searcher(me, pPlayer, check);
+ TypeContainerVisitor<Trinity::PlayerSearcher<Trinity::PlayerAtMinimumRangeAway>, GridTypeMapContainer> visitor(searcher);
+
+ cell.Visit(pair, visitor, *(me->GetMap()));
+
+ return pPlayer;
+}
+
+void ScriptedAI::SetEquipmentSlots(bool bLoadDefault, int32 uiMainHand, int32 uiOffHand, int32 uiRanged)
+{
+ if (bLoadDefault)
+ {
+ if (CreatureInfo const* pInfo = GetCreatureTemplateStore(me->GetEntry()))
+ me->LoadEquipment(pInfo->equipmentId,true);
+
+ return;
+ }
+
+ if (uiMainHand >= 0)
+ me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 0, uint32(uiMainHand));
+
+ if (uiOffHand >= 0)
+ me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1, uint32(uiOffHand));
+
+ if (uiRanged >= 0)
+ me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 2, uint32(uiRanged));
+}
+
+void ScriptedAI::SetCombatMovement(bool bCombatMove)
+{
+ m_bCombatMovement = bCombatMove;
+}
+
+enum eNPCs
+{
+ NPC_BROODLORD = 12017,
+ NPC_VOID_REAVER = 19516,
+ NPC_JAN_ALAI = 23578,
+ NPC_SARTHARION = 28860
+};
+
+// Hacklike storage used for misc creatures that are expected to evade of outside of a certain area.
+// It is assumed the information is found elswehere and can be handled by the core. So far no luck finding such information/way to extract it.
+bool ScriptedAI::EnterEvadeIfOutOfCombatArea(const uint32 uiDiff)
+{
+ if (m_uiEvadeCheckCooldown <= uiDiff)
+ m_uiEvadeCheckCooldown = 2500;
+ else
+ {
+ m_uiEvadeCheckCooldown -= uiDiff;
+ return false;
+ }
+
+ if (me->IsInEvadeMode() || !me->getVictim())
+ return false;
+
+ float fX = me->GetPositionX();
+ float fY = me->GetPositionY();
+ float fZ = me->GetPositionZ();
+
+ switch(me->GetEntry())
+ {
+ case NPC_BROODLORD: // broodlord (not move down stairs)
+ if (fZ > 448.60f)
+ return false;
+ break;
+ case NPC_VOID_REAVER: // void reaver (calculate from center of room)
+ if (me->GetDistance2d(432.59f, 371.93f) < 105.0f)
+ return false;
+ break;
+ case NPC_JAN_ALAI: // jan'alai (calculate by Z)
+ if (fZ > 12.0f)
+ return false;
+ break;
+ case NPC_SARTHARION: // sartharion (calculate box)
+ if (fX > 3218.86f && fX < 3275.69f && fY < 572.40f && fY > 484.68f)
+ return false;
+ break;
+ default:
+ error_log("TSCR: EnterEvadeIfOutOfCombatArea used for creature entry %u, but does not have any definition.", me->GetEntry());
+ return false;
+ }
+
+ EnterEvadeMode();
+ return true;
+}
+
+void Scripted_NoMovementAI::AttackStart(Unit* pWho)
+{
+ if (!pWho)
+ return;
+
+ if (me->Attack(pWho, true))
+ {
+ DoStartNoMovement(pWho);
+ }
+}
+
+BossAI::BossAI(Creature *c, uint32 id) : ScriptedAI(c)
+, bossId(id), summons(me), instance(c->GetInstanceData())
+, boundary(instance ? instance->GetBossBoundary(id) : NULL)
+{
+}
+
+void BossAI::_Reset()
+{
+ if (!me->isAlive())
+ return;
+
+ events.Reset();
+ summons.DespawnAll();
+ if (instance)
+ instance->SetBossState(bossId, NOT_STARTED);
+}
+
+void BossAI::_JustDied()
+{
+ events.Reset();
+ summons.DespawnAll();
+ if (instance)
+ {
+ instance->SetBossState(bossId, DONE);
+ instance->SaveToDB();
+ }
+}
+
+void BossAI::_EnterCombat()
+{
+ me->setActive(true);
+ DoZoneInCombat();
+ if (instance)
+ instance->SetBossState(bossId, IN_PROGRESS);
+}
+
+void BossAI::TeleportCheaters()
+{
+ float x, y, z;
+ me->GetPosition(x, y, z);
+ std::list<HostileReference*> &m_threatlist = me->getThreatManager().getThreatList();
+ for (std::list<HostileReference*>::iterator itr = m_threatlist.begin(); itr != m_threatlist.end(); ++itr)
+ if ((*itr)->getTarget()->GetTypeId() == TYPEID_PLAYER && !CheckBoundary((*itr)->getTarget()))
+ (*itr)->getTarget()->NearTeleportTo(x, y, z, 0);
+}
+
+bool BossAI::CheckBoundary(Unit *who)
+{
+ if (!boundary || !who)
+ return true;
+
+ for (BossBoundaryMap::const_iterator itr = boundary->begin(); itr != boundary->end(); ++itr)
+ {
+ switch (itr->first)
+ {
+ case BOUNDARY_N:
+ if (me->GetPositionX() > itr->second)
+ return false;
+ break;
+ case BOUNDARY_S:
+ if (me->GetPositionX() < itr->second)
+ return false;
+ break;
+ case BOUNDARY_E:
+ if (me->GetPositionY() < itr->second)
+ return false;
+ break;
+ case BOUNDARY_W:
+ if (me->GetPositionY() > itr->second)
+ return false;
+ break;
+ case BOUNDARY_NW:
+ if (me->GetPositionX() + me->GetPositionY() > itr->second)
+ return false;
+ break;
+ case BOUNDARY_SE:
+ if (me->GetPositionX() + me->GetPositionY() < itr->second)
+ return false;
+ break;
+ case BOUNDARY_NE:
+ if (me->GetPositionX() - me->GetPositionY() > itr->second)
+ return false;
+ break;
+ case BOUNDARY_SW:
+ if (me->GetPositionX() - me->GetPositionY() < itr->second)
+ return false;
+ break;
+ }
+ }
+
+ return true;
+}
+
+void BossAI::JustSummoned(Creature *summon)
+{
+ summons.Summon(summon);
+ if (me->isInCombat())
+ DoZoneInCombat(summon);
+}
+
+void BossAI::SummonedCreatureDespawn(Creature *summon)
+{
+ summons.Despawn(summon);
+}
+
+#define GOBJECT(x) (const_cast<GameObjectInfo*>(GetGameObjectInfo(x)))
+
+void LoadOverridenSQLData()
+{
+ GameObjectInfo *goInfo;
+
+ // Sunwell Plateau : Kalecgos : Spectral Rift
+ goInfo = GOBJECT(187055);
+ if (goInfo)
+ if (goInfo->type == GAMEOBJECT_TYPE_GOOBER)
+ goInfo->goober.lockId = 57; // need LOCKTYPE_QUICK_OPEN
+
+ // Naxxramas : Sapphiron Birth
+ goInfo = GOBJECT(181356);
+ if (goInfo)
+ if (goInfo->type == GAMEOBJECT_TYPE_TRAP)
+ goInfo->trap.radius = 50;
+}
+
+// SD2 grid searchers.
+Creature *GetClosestCreatureWithEntry(WorldObject *pSource, uint32 uiEntry, float fMaxSearchRange, bool bAlive)
+{
+ return pSource->FindNearestCreature(uiEntry, fMaxSearchRange, bAlive);
+}
+GameObject *GetClosestGameObjectWithEntry(WorldObject *pSource, uint32 uiEntry, float fMaxSearchRange)
+{
+ return pSource->FindNearestGameObject(uiEntry, fMaxSearchRange);
+}
+void GetCreatureListWithEntryInGrid(std::list<Creature*>& lList, WorldObject *pSource, uint32 uiEntry, float fMaxSearchRange)
+{
+ return pSource->GetCreatureListWithEntryInGrid(lList, uiEntry, fMaxSearchRange);
+}
+void GetGameObjectListWithEntryInGrid(std::list<GameObject*>& lList, WorldObject *pSource, uint32 uiEntry, float fMaxSearchRange)
+{
+ return pSource->GetGameObjectListWithEntryInGrid(lList, uiEntry, fMaxSearchRange);
+}
diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.h b/src/server/game/AI/ScriptedAI/ScriptedCreature.h
new file mode 100644
index 00000000000..a7b8b530f66
--- /dev/null
+++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.h
@@ -0,0 +1,290 @@
+/* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * Thanks to the original authors: ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
+ *
+ * This program is free software licensed under GPL version 2
+ * Please see the included DOCS/LICENSE.TXT for more information */
+
+#ifndef SC_CREATURE_H
+#define SC_CREATURE_H
+
+#include "Creature.h"
+#include "CreatureAI.h"
+#include "CreatureAIImpl.h"
+#include "InstanceData.h"
+
+#define SCRIPT_CAST_TYPE dynamic_cast
+//#define SCRIPT_CAST_TYPE static_cast
+
+#define CAST_PLR(a) (SCRIPT_CAST_TYPE<Player*>(a))
+#define CAST_CRE(a) (SCRIPT_CAST_TYPE<Creature*>(a))
+#define CAST_SUM(a) (SCRIPT_CAST_TYPE<TempSummon*>(a))
+#define CAST_PET(a) (SCRIPT_CAST_TYPE<Pet*>(a))
+#define CAST_AI(a,b) (SCRIPT_CAST_TYPE<a*>(b))
+
+#define GET_SPELL(a) (const_cast<SpellEntry*>(GetSpellStore()->LookupEntry(a)))
+
+class ScriptedInstance;
+
+class SummonList : public std::list<uint64>
+{
+ public:
+ explicit SummonList(Creature* creature) : me(creature) {}
+ void Summon(Creature *summon) { push_back(summon->GetGUID()); }
+ void Despawn(Creature *summon) { remove(summon->GetGUID()); }
+ void DespawnEntry(uint32 entry);
+ void DespawnAll();
+ void DoAction(uint32 entry, uint32 info);
+ void DoZoneInCombat(uint32 entry = 0);
+ private:
+ Creature *me;
+};
+
+struct ScriptedAI : public CreatureAI
+{
+ explicit ScriptedAI(Creature* pCreature);
+ virtual ~ScriptedAI() {}
+
+ //*************
+ //CreatureAI Functions
+ //*************
+
+ void AttackStartNoMove(Unit *pTarget);
+
+ // Called at any Damage from any attacker (before damage apply)
+ void DamageTaken(Unit* /*pDone_by*/, uint32& /*uiDamage*/) {}
+
+ //Called at World update tick
+ void UpdateAI(const uint32);
+
+ //Called at creature death
+ void JustDied(Unit* /*who*/){}
+
+ //Called at creature killing another unit
+ void KilledUnit(Unit* /*who*/){}
+
+ // Called when the creature summon successfully other creature
+ void JustSummoned(Creature*) {}
+
+ // Called when a summoned creature is despawned
+ void SummonedCreatureDespawn(Creature*) {}
+
+ // Called when hit by a spell
+ void SpellHit(Unit* /*caster*/, const SpellEntry * /*spell*/) {}
+
+ // Called when spell hits a target
+ void SpellHitTarget(Unit * /*pTarget*/, const SpellEntry * /*spell*/) {}
+
+ //Called at waypoint reached or PointMovement end
+ void MovementInform(uint32 /*type*/, uint32 /*id*/){}
+
+ // Called when AI is temporarily replaced or put back when possess is applied or removed
+ void OnPossess(bool /*apply*/) {}
+
+ //*************
+ // Variables
+ //*************
+
+ //Pointer to creature we are manipulating
+ Creature* me;
+
+ //For fleeing
+ bool IsFleeing;
+
+ //*************
+ //Pure virtual functions
+ //*************
+
+ //Called at creature reset either by death or evade
+ void Reset() {}
+
+ //Called at creature aggro either by MoveInLOS or Attack Start
+ void EnterCombat(Unit* /*who*/) {}
+
+ //*************
+ //AI Helper Functions
+ //*************
+
+ //Start movement toward victim
+ void DoStartMovement(Unit* pVictim, float fDistance = 0, float fAngle = 0);
+
+ //Start no movement on victim
+ void DoStartNoMovement(Unit* pVictim);
+
+ //Stop attack of current victim
+ void DoStopAttack();
+
+ //Cast spell by spell info
+ void DoCastSpell(Unit* pTarget, SpellEntry const* pSpellInfo, bool bTriggered = false);
+
+ //Plays a sound to all nearby players
+ void DoPlaySoundToSet(WorldObject* pSource, uint32 sound);
+
+ //Drops all threat to 0%. Does not remove players from the threat list
+ void DoResetThreat();
+
+ float DoGetThreat(Unit* u);
+ void DoModifyThreatPercent(Unit* pUnit, int32 pct);
+
+ void DoTeleportTo(float fX, float fY, float fZ, uint32 uiTime = 0);
+ void DoTeleportTo(const float pos[4]);
+
+ void DoAction(const int32 /*param*/) {}
+
+ //Teleports a player without dropping threat (only teleports to same map)
+ void DoTeleportPlayer(Unit* pUnit, float fX, float fY, float fZ, float fO);
+ void DoTeleportAll(float fX, float fY, float fZ, float fO);
+
+ //Returns friendly unit with the most amount of hp missing from max hp
+ Unit* DoSelectLowestHpFriendly(float fRange, uint32 uiMinHPDiff = 1);
+
+ //Returns a list of friendly CC'd units within range
+ std::list<Creature*> DoFindFriendlyCC(float fRange);
+
+ //Returns a list of all friendly units missing a specific buff within range
+ std::list<Creature*> DoFindFriendlyMissingBuff(float fRange, uint32 uiSpellId);
+
+ //Return a player with at least minimumRange from me
+ Player* GetPlayerAtMinimumRange(float fMinimumRange);
+
+ //Spawns a creature relative to me
+ Creature* DoSpawnCreature(uint32 uiId, float fX, float fY, float fZ, float fAngle, uint32 uiType, uint32 uiDespawntime);
+
+ //Selects a unit from the creature's current aggro list
+ Unit* SelectUnit(SelectAggroTarget pTarget, uint32 uiPosition);
+
+ bool HealthBelowPct(uint32 pct) const { return me->GetHealth() * 100 < me->GetMaxHealth() * pct; }
+
+ //Returns spells that meet the specified criteria from the creatures spell list
+ SpellEntry const* SelectSpell(Unit* Target, uint32 School, uint32 Mechanic, SelectTargetType Targets, uint32 PowerCostMin, uint32 PowerCostMax, float RangeMin, float RangeMax, SelectEffect Effect);
+
+ //Checks if you can cast the specified spell
+ bool CanCast(Unit* pTarget, SpellEntry const* pSpell, bool bTriggered = false);
+
+ void SetEquipmentSlots(bool bLoadDefault, int32 uiMainHand = EQUIP_NO_CHANGE, int32 uiOffHand = EQUIP_NO_CHANGE, int32 uiRanged = EQUIP_NO_CHANGE);
+
+ //Generally used to control if MoveChase() is to be used or not in AttackStart(). Some creatures does not chase victims
+ void SetCombatMovement(bool CombatMove);
+ bool IsCombatMovement() { return m_bCombatMovement; }
+
+ bool EnterEvadeIfOutOfCombatArea(const uint32 uiDiff);
+
+ // return true for heroic mode. i.e.
+ // - for dungeon in mode 10-heroic,
+ // - for raid in mode 10-Heroic
+ // - for raid in mode 25-heroic
+ // DO NOT USE to check raid in mode 25-normal.
+ bool IsHeroic() { return m_heroicMode; }
+
+ // return the dungeon or raid difficulty
+ Difficulty getDifficulty() { return m_difficulty; }
+
+ template<class T> inline
+ const T& DUNGEON_MODE(const T& normal5, const T& heroic10)
+ {
+ switch(m_difficulty)
+ {
+ case DUNGEON_DIFFICULTY_NORMAL:
+ return normal5;
+ case DUNGEON_DIFFICULTY_HEROIC:
+ return heroic10;
+ }
+
+ return heroic10;
+ }
+
+ template<class T> inline
+ const T& RAID_MODE(const T& normal10, const T& normal25)
+ {
+ switch(m_difficulty)
+ {
+ case RAID_DIFFICULTY_10MAN_NORMAL:
+ return normal10;
+ case RAID_DIFFICULTY_25MAN_NORMAL:
+ return normal25;
+ }
+
+ return normal25;
+ }
+
+ template<class T> inline
+ const T& RAID_MODE(const T& normal10, const T& normal25, const T& heroic10, const T& heroic25)
+ {
+ switch(m_difficulty)
+ {
+ case RAID_DIFFICULTY_10MAN_NORMAL:
+ return normal10;
+ case RAID_DIFFICULTY_25MAN_NORMAL:
+ return normal25;
+ case RAID_DIFFICULTY_10MAN_HEROIC:
+ return heroic10;
+ case RAID_DIFFICULTY_25MAN_HEROIC:
+ return heroic25;
+ }
+
+ return heroic25;
+ }
+
+ private:
+ bool m_bCombatMovement;
+ uint32 m_uiEvadeCheckCooldown;
+
+ bool m_heroicMode;
+ Difficulty m_difficulty;
+};
+
+struct Scripted_NoMovementAI : public ScriptedAI
+{
+ Scripted_NoMovementAI(Creature* creature) : ScriptedAI(creature) {}
+ virtual ~Scripted_NoMovementAI() {}
+
+ //Called at each attack of me by any victim
+ void AttackStart(Unit* who);
+};
+
+struct BossAI : public ScriptedAI
+{
+ BossAI(Creature *c, uint32 id);
+ virtual ~BossAI() {}
+
+ const uint32 bossId;
+ EventMap events;
+ SummonList summons;
+ InstanceData * const instance;
+ const BossBoundaryMap * const boundary;
+
+ void JustSummoned(Creature *summon);
+ void SummonedCreatureDespawn(Creature *summon);
+
+ void UpdateAI(const uint32 diff) = 0;
+
+ void Reset() { _Reset(); }
+ void EnterCombat(Unit * /*who*/) { _EnterCombat(); }
+ void JustDied(Unit * /*killer*/) { _JustDied(); }
+ void JustReachedHome() { me->setActive(false); }
+
+ protected:
+ void _Reset();
+ void _EnterCombat();
+ void _JustDied();
+ void _JustReachedHome() { me->setActive(false); }
+
+ bool CheckInRoom()
+ {
+ if (CheckBoundary(me))
+ return true;
+ EnterEvadeMode();
+ return false;
+ }
+ bool CheckBoundary(Unit *who);
+ void TeleportCheaters();
+};
+
+// SD2 grid searchers.
+Creature *GetClosestCreatureWithEntry(WorldObject *pSource, uint32 uiEntry, float fMaxSearchRange, bool bAlive = true);
+GameObject *GetClosestGameObjectWithEntry(WorldObject *pSource, uint32 uiEntry, float fMaxSearchRange);
+void GetCreatureListWithEntryInGrid(std::list<Creature*>& lList, WorldObject* pSource, uint32 uiEntry, float fMaxSearchRange);
+void GetGameObjectListWithEntryInGrid(std::list<GameObject*>& lList, WorldObject* pSource, uint32 uiEntry, float fMaxSearchRange);
+
+#endif
+
diff --git a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp
new file mode 100644
index 00000000000..8b7aca888e1
--- /dev/null
+++ b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp
@@ -0,0 +1,506 @@
+/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
+ * This program is free software licensed under GPL version 2
+ * Please see the included DOCS/LICENSE.TXT for more information */
+
+/* ScriptData
+SDName: Npc_EscortAI
+SD%Complete: 100
+SDComment:
+SDCategory: Npc
+EndScriptData */
+
+#include "ScriptedPch.h"
+#include "ScriptedEscortAI.h"
+
+enum ePoints
+{
+ POINT_LAST_POINT = 0xFFFFFF,
+ POINT_HOME = 0xFFFFFE
+};
+
+npc_escortAI::npc_escortAI(Creature* pCreature) : ScriptedAI(pCreature),
+ m_uiPlayerGUID(0),
+ MaxPlayerDistance(DEFAULT_MAX_PLAYER_DISTANCE),
+ m_uiPlayerCheckTimer(1000),
+ m_uiWPWaitTimer(2500),
+ m_uiEscortState(STATE_ESCORT_NONE),
+ m_bIsActiveAttacker(true),
+ m_bIsRunning(false),
+ DespawnAtEnd(true),
+ DespawnAtFar(true),
+ m_pQuestForEscort(NULL),
+ m_bCanInstantRespawn(false),
+ m_bCanReturnToStart(false),
+ ScriptWP(false)
+{}
+
+void npc_escortAI::AttackStart(Unit* pWho)
+{
+ if (!pWho)
+ return;
+
+ if (me->Attack(pWho, true))
+ {
+ if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == POINT_MOTION_TYPE)
+ me->GetMotionMaster()->MovementExpired();
+
+ if (IsCombatMovement())
+ me->GetMotionMaster()->MoveChase(pWho);
+ }
+}
+
+//see followerAI
+bool npc_escortAI::AssistPlayerInCombat(Unit* pWho)
+{
+ if (!pWho || !pWho->getVictim())
+ return false;
+
+ //experimental (unknown) flag not present
+ if (!(me->GetCreatureInfo()->type_flags & CREATURE_TYPEFLAGS_UNK13))
+ return false;
+
+ //not a player
+ if (!pWho->getVictim()->GetCharmerOrOwnerPlayerOrPlayerItself())
+ return false;
+
+ //never attack friendly
+ if (me->IsFriendlyTo(pWho))
+ return false;
+
+ //too far away and no free sight?
+ if (me->IsWithinDistInMap(pWho, GetMaxPlayerDistance()) && me->IsWithinLOSInMap(pWho))
+ {
+ //already fighting someone?
+ if (!me->getVictim())
+ {
+ AttackStart(pWho);
+ return true;
+ }
+ else
+ {
+ pWho->SetInCombatWith(me);
+ me->AddThreat(pWho, 0.0f);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void npc_escortAI::MoveInLineOfSight(Unit* pWho)
+{
+ if (!me->hasUnitState(UNIT_STAT_STUNNED) && pWho->isTargetableForAttack() && pWho->isInAccessiblePlaceFor(me))
+ {
+ if (HasEscortState(STATE_ESCORT_ESCORTING) && AssistPlayerInCombat(pWho))
+ return;
+
+ if (!me->canFly() && me->GetDistanceZ(pWho) > CREATURE_Z_ATTACK_RANGE)
+ return;
+
+ if (me->IsHostileTo(pWho))
+ {
+ float fAttackRadius = me->GetAttackDistance(pWho);
+ if (me->IsWithinDistInMap(pWho, fAttackRadius) && me->IsWithinLOSInMap(pWho))
+ {
+ if (!me->getVictim())
+ {
+ pWho->RemoveAurasDueToSpell(SPELL_AURA_MOD_STEALTH);
+ AttackStart(pWho);
+ }
+ else if (me->GetMap()->IsDungeon())
+ {
+ pWho->SetInCombatWith(me);
+ me->AddThreat(pWho, 0.0f);
+ }
+ }
+ }
+ }
+}
+
+void npc_escortAI::JustDied(Unit* /*pKiller*/)
+{
+ if (!HasEscortState(STATE_ESCORT_ESCORTING) || !m_uiPlayerGUID || !m_pQuestForEscort)
+ return;
+
+ if (Player* pPlayer = GetPlayerForEscort())
+ {
+ if (Group* pGroup = pPlayer->GetGroup())
+ {
+ for (GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next())
+ {
+ if (Player* pMember = pRef->getSource())
+ {
+ if (pMember->GetQuestStatus(m_pQuestForEscort->GetQuestId()) == QUEST_STATUS_INCOMPLETE)
+ pMember->FailQuest(m_pQuestForEscort->GetQuestId());
+ }
+ }
+ }
+ else
+ {
+ if (pPlayer->GetQuestStatus(m_pQuestForEscort->GetQuestId()) == QUEST_STATUS_INCOMPLETE)
+ pPlayer->FailQuest(m_pQuestForEscort->GetQuestId());
+ }
+ }
+}
+
+void npc_escortAI::JustRespawned()
+{
+ m_uiEscortState = STATE_ESCORT_NONE;
+
+ if (!IsCombatMovement())
+ SetCombatMovement(true);
+
+ //add a small delay before going to first waypoint, normal in near all cases
+ m_uiWPWaitTimer = 2500;
+
+ if (me->getFaction() != me->GetCreatureInfo()->faction_A)
+ me->RestoreFaction();
+
+ Reset();
+}
+
+void npc_escortAI::ReturnToLastPoint()
+{
+ float x, y, z, o;
+ me->GetHomePosition(x, y, z, o);
+ me->GetMotionMaster()->MovePoint(POINT_LAST_POINT, x, y, z);
+}
+
+void npc_escortAI::EnterEvadeMode()
+{
+ me->RemoveAllAuras();
+ me->DeleteThreatList();
+ me->CombatStop(true);
+ me->SetLootRecipient(NULL);
+
+ if (HasEscortState(STATE_ESCORT_ESCORTING))
+ {
+ AddEscortState(STATE_ESCORT_RETURNING);
+ ReturnToLastPoint();
+ debug_log("TSCR: EscortAI has left combat and is now returning to last point");
+ }
+ else
+ {
+ me->GetMotionMaster()->MoveTargetedHome();
+ Reset();
+ }
+}
+
+bool npc_escortAI::IsPlayerOrGroupInRange()
+{
+ if (Player* pPlayer = GetPlayerForEscort())
+ {
+ if (Group* pGroup = pPlayer->GetGroup())
+ {
+ for (GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next())
+ {
+ Player* pMember = pRef->getSource();
+
+ if (pMember && me->IsWithinDistInMap(pMember, GetMaxPlayerDistance()))
+ {
+ return true;
+ break;
+ }
+ }
+ }
+ else
+ {
+ if (me->IsWithinDistInMap(pPlayer, GetMaxPlayerDistance()))
+ return true;
+ }
+ }
+ return false;
+}
+
+void npc_escortAI::UpdateAI(const uint32 uiDiff)
+{
+ //Waypoint Updating
+ if (HasEscortState(STATE_ESCORT_ESCORTING) && !me->getVictim() && m_uiWPWaitTimer && !HasEscortState(STATE_ESCORT_RETURNING))
+ {
+ if (m_uiWPWaitTimer <= uiDiff)
+ {
+ //End of the line
+ if (CurrentWP == WaypointList.end())
+ {
+ if (DespawnAtEnd)
+ {
+ debug_log("TSCR: EscortAI reached end of waypoints");
+
+ if (m_bCanReturnToStart)
+ {
+ float fRetX, fRetY, fRetZ;
+ me->GetRespawnCoord(fRetX, fRetY, fRetZ);
+
+ me->GetMotionMaster()->MovePoint(POINT_HOME, fRetX, fRetY, fRetZ);
+
+ m_uiWPWaitTimer = 0;
+
+ debug_log("TSCR: EscortAI are returning home to spawn location: %u, %f, %f, %f", POINT_HOME, fRetX, fRetY, fRetZ);
+ return;
+ }
+
+ if (m_bCanInstantRespawn)
+ {
+ me->setDeathState(JUST_DIED);
+ me->Respawn();
+ }
+ else
+ me->ForcedDespawn();
+
+ return;
+ }
+ else
+ {
+ debug_log("TSCR: EscortAI reached end of waypoints with Despawn off");
+
+ return;
+ }
+ }
+
+ if (!HasEscortState(STATE_ESCORT_PAUSED))
+ {
+ me->GetMotionMaster()->MovePoint(CurrentWP->id, CurrentWP->x, CurrentWP->y, CurrentWP->z);
+ debug_log("TSCR: EscortAI start waypoint %u (%f, %f, %f).", CurrentWP->id, CurrentWP->x, CurrentWP->y, CurrentWP->z);
+
+ WaypointStart(CurrentWP->id);
+
+ m_uiWPWaitTimer = 0;
+ }
+ }
+ else
+ m_uiWPWaitTimer -= uiDiff;
+ }
+
+ //Check if player or any member of his group is within range
+ if (HasEscortState(STATE_ESCORT_ESCORTING) && m_uiPlayerGUID && !me->getVictim() && !HasEscortState(STATE_ESCORT_RETURNING))
+ {
+ if (m_uiPlayerCheckTimer <= uiDiff)
+ {
+ if (DespawnAtFar && !IsPlayerOrGroupInRange())
+ {
+ debug_log("TSCR: EscortAI failed because player/group was to far away or not found");
+
+ if (m_bCanInstantRespawn)
+ {
+ me->setDeathState(JUST_DIED);
+ me->Respawn();
+ }
+ else
+ me->ForcedDespawn();
+
+ return;
+ }
+
+ m_uiPlayerCheckTimer = 1000;
+ }
+ else
+ m_uiPlayerCheckTimer -= uiDiff;
+ }
+
+ UpdateEscortAI(uiDiff);
+}
+
+void npc_escortAI::UpdateEscortAI(const uint32 /*uiDiff*/)
+{
+ if (!UpdateVictim())
+ return;
+
+ DoMeleeAttackIfReady();
+}
+
+void npc_escortAI::MovementInform(uint32 uiMoveType, uint32 uiPointId)
+{
+ if (uiMoveType != POINT_MOTION_TYPE || !HasEscortState(STATE_ESCORT_ESCORTING))
+ return;
+
+ //Combat start position reached, continue waypoint movement
+ if (uiPointId == POINT_LAST_POINT)
+ {
+ debug_log("TSCR: EscortAI has returned to original position before combat");
+
+ if (m_bIsRunning && me->HasUnitMovementFlag(MOVEMENTFLAG_WALK_MODE))
+ me->RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE);
+ else if (!m_bIsRunning && !me->HasUnitMovementFlag(MOVEMENTFLAG_WALK_MODE))
+ me->AddUnitMovementFlag(MOVEMENTFLAG_WALK_MODE);
+
+ RemoveEscortState(STATE_ESCORT_RETURNING);
+
+ if (!m_uiWPWaitTimer)
+ m_uiWPWaitTimer = 1;
+ }
+ else if (uiPointId == POINT_HOME)
+ {
+ debug_log("TSCR: EscortAI has returned to original home location and will continue from beginning of waypoint list.");
+
+ CurrentWP = WaypointList.begin();
+ m_uiWPWaitTimer = 1;
+ }
+ else
+ {
+ //Make sure that we are still on the right waypoint
+ if (CurrentWP->id != uiPointId)
+ {
+ error_log("TSCR ERROR: EscortAI reached waypoint out of order %u, expected %u, creature entry %u", uiPointId, CurrentWP->id, me->GetEntry());
+ return;
+ }
+
+ debug_log("TSCR: EscortAI Waypoint %u reached", CurrentWP->id);
+
+ //Call WP function
+ WaypointReached(CurrentWP->id);
+
+ m_uiWPWaitTimer = CurrentWP->WaitTimeMs + 1;
+
+ ++CurrentWP;
+ }
+}
+
+/*
+void npc_escortAI::OnPossess(bool apply)
+{
+ // We got possessed in the middle of being escorted, store the point
+ // where we left off to come back to when possess is removed
+ if (HasEscortState(STATE_ESCORT_ESCORTING))
+ {
+ if (apply)
+ me->GetPosition(LastPos.x, LastPos.y, LastPos.z);
+ else
+ {
+ Returning = true;
+ me->GetMotionMaster()->MovementExpired();
+ me->GetMotionMaster()->MovePoint(WP_LAST_POINT, LastPos.x, LastPos.y, LastPos.z);
+ }
+ }
+}
+*/
+
+void npc_escortAI::AddWaypoint(uint32 id, float x, float y, float z, uint32 WaitTimeMs)
+{
+ Escort_Waypoint t(id, x, y, z, WaitTimeMs);
+
+ WaypointList.push_back(t);
+
+ // i think SD2 no longer uses this function
+ ScriptWP = true;
+ /*PointMovement wp;
+ wp.m_uiCreatureEntry = me->GetEntry();
+ wp.m_uiPointId = id;
+ wp.m_fX = x;
+ wp.m_fY = y;
+ wp.m_fZ = z;
+ wp.m_uiWaitTime = WaitTimeMs;
+ PointMovementMap[wp.m_uiCreatureEntry].push_back(wp);*/
+}
+
+void npc_escortAI::FillPointMovementListForCreature()
+{
+ std::vector<ScriptPointMove> const &pPointsEntries = pSystemMgr.GetPointMoveList(me->GetEntry());
+
+ if (pPointsEntries.empty())
+ return;
+
+ std::vector<ScriptPointMove>::const_iterator itr;
+
+ for (itr = pPointsEntries.begin(); itr != pPointsEntries.end(); ++itr)
+ {
+ Escort_Waypoint pPoint(itr->uiPointId, itr->fX, itr->fY, itr->fZ, itr->uiWaitTime);
+ WaypointList.push_back(pPoint);
+ }
+}
+
+void npc_escortAI::SetRun(bool bRun)
+{
+ if (bRun)
+ {
+ if (!m_bIsRunning)
+ me->RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE);
+ else
+ debug_log("TSCR: EscortAI attempt to set run mode, but is already running.");
+ }
+ else
+ {
+ if (m_bIsRunning)
+ me->AddUnitMovementFlag(MOVEMENTFLAG_WALK_MODE);
+ else
+ debug_log("TSCR: EscortAI attempt to set walk mode, but is already walking.");
+ }
+ m_bIsRunning = bRun;
+}
+
+//TODO: get rid of this many variables passed in function.
+void npc_escortAI::Start(bool bIsActiveAttacker, bool bRun, uint64 uiPlayerGUID, const Quest* pQuest, bool bInstantRespawn, bool bCanLoopPath)
+{
+ if (me->getVictim())
+ {
+ error_log("TSCR ERROR: EscortAI attempt to Start while in combat. Scriptname: %s, creature entry: %u", me->GetScriptName().c_str(), me->GetEntry());
+ return;
+ }
+
+ if (HasEscortState(STATE_ESCORT_ESCORTING))
+ {
+ error_log("TSCR: EscortAI attempt to Start while already escorting.");
+ return;
+ }
+
+ if (!ScriptWP) // sd2 never adds wp in script, but tc does
+ {
+
+ if (!WaypointList.empty())
+ WaypointList.clear();
+
+ FillPointMovementListForCreature();
+
+ }
+
+ if (WaypointList.empty())
+ {
+ error_db_log("TSCR: EscortAI Start with 0 waypoints (possible missing entry in script_waypoint. Quest: %u).", pQuest ? pQuest->GetQuestId() : 0);
+ return;
+ }
+
+ //set variables
+ m_bIsActiveAttacker = bIsActiveAttacker;
+ m_bIsRunning = bRun;
+
+ m_uiPlayerGUID = uiPlayerGUID;
+ m_pQuestForEscort = pQuest;
+
+ m_bCanInstantRespawn = bInstantRespawn;
+ m_bCanReturnToStart = bCanLoopPath;
+
+ if (m_bCanReturnToStart && m_bCanInstantRespawn)
+ debug_log("TSCR: EscortAI is set to return home after waypoint end and instant respawn at waypoint end. Creature will never despawn.");
+
+ if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE)
+ {
+ me->GetMotionMaster()->MovementExpired();
+ me->GetMotionMaster()->MoveIdle();
+ debug_log("TSCR: EscortAI start with WAYPOINT_MOTION_TYPE, changed to MoveIdle.");
+ }
+
+ //disable npcflags
+ me->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);
+
+ debug_log("TSCR: EscortAI started with %u waypoints. ActiveAttacker = %d, Run = %d, PlayerGUID = %u", WaypointList.size(), m_bIsActiveAttacker, m_bIsRunning, m_uiPlayerGUID);
+
+ CurrentWP = WaypointList.begin();
+
+ //Set initial speed
+ if (m_bIsRunning)
+ me->RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE);
+ else
+ me->AddUnitMovementFlag(MOVEMENTFLAG_WALK_MODE);
+
+ AddEscortState(STATE_ESCORT_ESCORTING);
+}
+
+void npc_escortAI::SetEscortPaused(bool bPaused)
+{
+ if (!HasEscortState(STATE_ESCORT_ESCORTING))
+ return;
+
+ if (bPaused)
+ AddEscortState(STATE_ESCORT_PAUSED);
+ else
+ RemoveEscortState(STATE_ESCORT_PAUSED);
+}
diff --git a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h
new file mode 100644
index 00000000000..807a5644eb8
--- /dev/null
+++ b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h
@@ -0,0 +1,116 @@
+/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
+ * This program is free software licensed under GPL version 2
+ * Please see the included DOCS/LICENSE.TXT for more information */
+
+#ifndef SC_ESCORTAI_H
+#define SC_ESCORTAI_H
+
+#include "ScriptSystem.h"
+
+#define DEFAULT_MAX_PLAYER_DISTANCE 50
+
+struct Escort_Waypoint
+{
+ Escort_Waypoint(uint32 _id, float _x, float _y, float _z, uint32 _w)
+ {
+ id = _id;
+ x = _x;
+ y = _y;
+ z = _z;
+ WaitTimeMs = _w;
+ }
+
+ uint32 id;
+ float x;
+ float y;
+ float z;
+ uint32 WaitTimeMs;
+};
+
+enum eEscortState
+{
+ STATE_ESCORT_NONE = 0x000, //nothing in progress
+ STATE_ESCORT_ESCORTING = 0x001, //escort are in progress
+ STATE_ESCORT_RETURNING = 0x002, //escort is returning after being in combat
+ STATE_ESCORT_PAUSED = 0x004 //will not proceed with waypoints before state is removed
+};
+
+struct npc_escortAI : public ScriptedAI
+{
+ public:
+ explicit npc_escortAI(Creature* pCreature);
+ ~npc_escortAI() {}
+
+ // CreatureAI functions
+ void AttackStart(Unit* who);
+
+ void MoveInLineOfSight(Unit* who);
+
+ void JustDied(Unit*);
+
+ void JustRespawned();
+
+ void ReturnToLastPoint();
+
+ void EnterEvadeMode();
+
+ void UpdateAI(const uint32); //the "internal" update, calls UpdateEscortAI()
+ virtual void UpdateEscortAI(const uint32); //used when it's needed to add code in update (abilities, scripted events, etc)
+
+ void MovementInform(uint32, uint32);
+
+ // EscortAI functions
+ void AddWaypoint(uint32 id, float x, float y, float z, uint32 WaitTimeMs = 0);
+
+ virtual void WaypointReached(uint32 uiPointId) = 0;
+ virtual void WaypointStart(uint32 /*uiPointId*/) {}
+
+ void Start(bool bIsActiveAttacker = true, bool bRun = false, uint64 uiPlayerGUID = 0, const Quest* pQuest = NULL, bool bInstantRespawn = false, bool bCanLoopPath = false);
+
+ void SetRun(bool bRun = true);
+ void SetEscortPaused(bool uPaused);
+
+ bool HasEscortState(uint32 uiEscortState) { return (m_uiEscortState & uiEscortState); }
+ virtual bool IsEscorted() { return (m_uiEscortState & STATE_ESCORT_ESCORTING); }
+
+ void SetMaxPlayerDistance(float newMax) { MaxPlayerDistance = newMax; }
+ float GetMaxPlayerDistance() { return MaxPlayerDistance; }
+
+ void SetDespawnAtEnd(bool despawn) { DespawnAtEnd = despawn; }
+ void SetDespawnAtFar(bool despawn) { DespawnAtFar = despawn; }
+ bool GetAttack() { return m_bIsActiveAttacker; }//used in EnterEvadeMode override
+ void SetCanAttack(bool attack) { m_bIsActiveAttacker = attack; }
+ uint64 GetEventStarterGUID() { return m_uiPlayerGUID; }
+
+ protected:
+ Player* GetPlayerForEscort() { return (Player*)Unit::GetUnit(*me, m_uiPlayerGUID); }
+
+ private:
+ bool AssistPlayerInCombat(Unit* pWho);
+ bool IsPlayerOrGroupInRange();
+ void FillPointMovementListForCreature();
+
+ void AddEscortState(uint32 uiEscortState) { m_uiEscortState |= uiEscortState; }
+ void RemoveEscortState(uint32 uiEscortState) { m_uiEscortState &= ~uiEscortState; }
+
+ uint64 m_uiPlayerGUID;
+ uint32 m_uiWPWaitTimer;
+ uint32 m_uiPlayerCheckTimer;
+ uint32 m_uiEscortState;
+ float MaxPlayerDistance;
+
+ const Quest* m_pQuestForEscort; //generally passed in Start() when regular escort script.
+
+ std::list<Escort_Waypoint> WaypointList;
+ std::list<Escort_Waypoint>::iterator CurrentWP;
+
+ bool m_bIsActiveAttacker; //obsolete, determined by faction.
+ bool m_bIsRunning; //all creatures are walking by default (has flag MOVEMENTFLAG_WALK)
+ bool m_bCanInstantRespawn; //if creature should respawn instantly after escort over (if not, database respawntime are used)
+ bool m_bCanReturnToStart; //if creature can walk same path (loop) without despawn. Not for regular escort quests.
+ bool DespawnAtEnd;
+ bool DespawnAtFar;
+ bool ScriptWP;
+};
+#endif
+
diff --git a/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp
new file mode 100644
index 00000000000..4f1543dc778
--- /dev/null
+++ b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp
@@ -0,0 +1,387 @@
+/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
+ * This program is free software licensed under GPL version 2
+ * Please see the included DOCS/LICENSE.TXT for more information */
+
+/* ScriptData
+SDName: FollowerAI
+SD%Complete: 50
+SDComment: This AI is under development
+SDCategory: Npc
+EndScriptData */
+
+#include "ScriptedPch.h"
+#include "ScriptedFollowerAI.h"
+
+const float MAX_PLAYER_DISTANCE = 100.0f;
+
+enum ePoints
+{
+ POINT_COMBAT_START = 0xFFFFFF
+};
+
+FollowerAI::FollowerAI(Creature* pCreature) : ScriptedAI(pCreature),
+ m_uiLeaderGUID(0),
+ m_pQuestForFollow(NULL),
+ m_uiUpdateFollowTimer(2500),
+ m_uiFollowState(STATE_FOLLOW_NONE)
+{}
+
+void FollowerAI::AttackStart(Unit* pWho)
+{
+ if (!pWho)
+ return;
+
+ if (me->Attack(pWho, true))
+ {
+ me->AddThreat(pWho, 0.0f);
+ me->SetInCombatWith(pWho);
+ pWho->SetInCombatWith(me);
+
+ if (me->hasUnitState(UNIT_STAT_FOLLOW))
+ me->clearUnitState(UNIT_STAT_FOLLOW);
+
+ if (IsCombatMovement())
+ me->GetMotionMaster()->MoveChase(pWho);
+ }
+}
+
+//This part provides assistance to a player that are attacked by pWho, even if out of normal aggro range
+//It will cause me to attack pWho that are attacking _any_ player (which has been confirmed may happen also on offi)
+//The flag (type_flag) is unconfirmed, but used here for further research and is a good candidate.
+bool FollowerAI::AssistPlayerInCombat(Unit* pWho)
+{
+ if (!pWho || !pWho->getVictim())
+ return false;
+
+ //experimental (unknown) flag not present
+ if (!(me->GetCreatureInfo()->type_flags & CREATURE_TYPEFLAGS_UNK13))
+ return false;
+
+ //not a player
+ if (!pWho->getVictim()->GetCharmerOrOwnerPlayerOrPlayerItself())
+ return false;
+
+ //never attack friendly
+ if (me->IsFriendlyTo(pWho))
+ return false;
+
+ //too far away and no free sight?
+ if (me->IsWithinDistInMap(pWho, MAX_PLAYER_DISTANCE) && me->IsWithinLOSInMap(pWho))
+ {
+ //already fighting someone?
+ if (!me->getVictim())
+ {
+ AttackStart(pWho);
+ return true;
+ }
+ else
+ {
+ pWho->SetInCombatWith(me);
+ me->AddThreat(pWho, 0.0f);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void FollowerAI::MoveInLineOfSight(Unit* pWho)
+{
+ if (!me->hasUnitState(UNIT_STAT_STUNNED) && pWho->isTargetableForAttack() && pWho->isInAccessiblePlaceFor(me))
+ {
+ if (HasFollowState(STATE_FOLLOW_INPROGRESS) && AssistPlayerInCombat(pWho))
+ return;
+
+ if (!me->canFly() && me->GetDistanceZ(pWho) > CREATURE_Z_ATTACK_RANGE)
+ return;
+
+ if (me->IsHostileTo(pWho))
+ {
+ float fAttackRadius = me->GetAttackDistance(pWho);
+ if (me->IsWithinDistInMap(pWho, fAttackRadius) && me->IsWithinLOSInMap(pWho))
+ {
+ if (!me->getVictim())
+ {
+ pWho->RemoveAurasDueToSpell(SPELL_AURA_MOD_STEALTH);
+ AttackStart(pWho);
+ }
+ else if (me->GetMap()->IsDungeon())
+ {
+ pWho->SetInCombatWith(me);
+ me->AddThreat(pWho, 0.0f);
+ }
+ }
+ }
+ }
+}
+
+void FollowerAI::JustDied(Unit* /*pKiller*/)
+{
+ if (!HasFollowState(STATE_FOLLOW_INPROGRESS) || !m_uiLeaderGUID || !m_pQuestForFollow)
+ return;
+
+ //TODO: need a better check for quests with time limit.
+ if (Player* pPlayer = GetLeaderForFollower())
+ {
+ if (Group* pGroup = pPlayer->GetGroup())
+ {
+ for (GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next())
+ {
+ if (Player* pMember = pRef->getSource())
+ {
+ if (pMember->GetQuestStatus(m_pQuestForFollow->GetQuestId()) == QUEST_STATUS_INCOMPLETE)
+ pMember->FailQuest(m_pQuestForFollow->GetQuestId());
+ }
+ }
+ }
+ else
+ {
+ if (pPlayer->GetQuestStatus(m_pQuestForFollow->GetQuestId()) == QUEST_STATUS_INCOMPLETE)
+ pPlayer->FailQuest(m_pQuestForFollow->GetQuestId());
+ }
+ }
+}
+
+void FollowerAI::JustRespawned()
+{
+ m_uiFollowState = STATE_FOLLOW_NONE;
+
+ if (!IsCombatMovement())
+ SetCombatMovement(true);
+
+ if (me->getFaction() != me->GetCreatureInfo()->faction_A)
+ me->setFaction(me->GetCreatureInfo()->faction_A);
+
+ Reset();
+}
+
+void FollowerAI::EnterEvadeMode()
+{
+ me->RemoveAllAuras();
+ me->DeleteThreatList();
+ me->CombatStop(true);
+ me->SetLootRecipient(NULL);
+
+ if (HasFollowState(STATE_FOLLOW_INPROGRESS))
+ {
+ debug_log("TSCR: FollowerAI left combat, returning to CombatStartPosition.");
+
+ if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == TARGETED_MOTION_TYPE)
+ {
+ float fPosX, fPosY, fPosZ;
+ me->GetPosition(fPosX, fPosY, fPosZ);
+ me->GetMotionMaster()->MovePoint(POINT_COMBAT_START, fPosX, fPosY, fPosZ);
+ }
+ }
+ else
+ {
+ if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == TARGETED_MOTION_TYPE)
+ me->GetMotionMaster()->MoveTargetedHome();
+ }
+
+ Reset();
+}
+
+void FollowerAI::UpdateAI(const uint32 uiDiff)
+{
+ if (HasFollowState(STATE_FOLLOW_INPROGRESS) && !me->getVictim())
+ {
+ if (m_uiUpdateFollowTimer <= uiDiff)
+ {
+ if (HasFollowState(STATE_FOLLOW_COMPLETE) && !HasFollowState(STATE_FOLLOW_POSTEVENT))
+ {
+ debug_log("TSCR: FollowerAI is set completed, despawns.");
+ me->ForcedDespawn();
+ return;
+ }
+
+ bool bIsMaxRangeExceeded = true;
+
+ if (Player* pPlayer = GetLeaderForFollower())
+ {
+ if (HasFollowState(STATE_FOLLOW_RETURNING))
+ {
+ debug_log("TSCR: FollowerAI is returning to leader.");
+
+ RemoveFollowState(STATE_FOLLOW_RETURNING);
+ me->GetMotionMaster()->MoveFollow(pPlayer, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
+ return;
+ }
+
+ if (Group* pGroup = pPlayer->GetGroup())
+ {
+ for (GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next())
+ {
+ Player* pMember = pRef->getSource();
+
+ if (pMember && me->IsWithinDistInMap(pMember, MAX_PLAYER_DISTANCE))
+ {
+ bIsMaxRangeExceeded = false;
+ break;
+ }
+ }
+ }
+ else
+ {
+ if (me->IsWithinDistInMap(pPlayer, MAX_PLAYER_DISTANCE))
+ bIsMaxRangeExceeded = false;
+ }
+ }
+
+ if (bIsMaxRangeExceeded)
+ {
+ debug_log("TSCR: FollowerAI failed because player/group was to far away or not found");
+ me->ForcedDespawn();
+ return;
+ }
+
+ m_uiUpdateFollowTimer = 1000;
+ }
+ else
+ m_uiUpdateFollowTimer -= uiDiff;
+ }
+
+ UpdateFollowerAI(uiDiff);
+}
+
+void FollowerAI::UpdateFollowerAI(const uint32 /*uiDiff*/)
+{
+ if (!UpdateVictim())
+ return;
+
+ DoMeleeAttackIfReady();
+}
+
+void FollowerAI::MovementInform(uint32 uiMotionType, uint32 uiPointId)
+{
+ if (uiMotionType != POINT_MOTION_TYPE || !HasFollowState(STATE_FOLLOW_INPROGRESS))
+ return;
+
+ if (uiPointId == POINT_COMBAT_START)
+ {
+ if (GetLeaderForFollower())
+ {
+ if (!HasFollowState(STATE_FOLLOW_PAUSED))
+ AddFollowState(STATE_FOLLOW_RETURNING);
+ }
+ else
+ me->ForcedDespawn();
+ }
+}
+
+void FollowerAI::StartFollow(Player* pLeader, uint32 uiFactionForFollower, const Quest* pQuest)
+{
+ if (me->getVictim())
+ {
+ debug_log("TSCR: FollowerAI attempt to StartFollow while in combat.");
+ return;
+ }
+
+ if (HasFollowState(STATE_FOLLOW_INPROGRESS))
+ {
+ error_log("TSCR: FollowerAI attempt to StartFollow while already following.");
+ return;
+ }
+
+ //set variables
+ m_uiLeaderGUID = pLeader->GetGUID();
+
+ if (uiFactionForFollower)
+ me->setFaction(uiFactionForFollower);
+
+ m_pQuestForFollow = pQuest;
+
+ if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE)
+ {
+ me->GetMotionMaster()->Clear();
+ me->GetMotionMaster()->MoveIdle();
+ debug_log("TSCR: FollowerAI start with WAYPOINT_MOTION_TYPE, set to MoveIdle.");
+ }
+
+ me->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);
+
+ AddFollowState(STATE_FOLLOW_INPROGRESS);
+
+ me->GetMotionMaster()->MoveFollow(pLeader, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
+
+ debug_log("TSCR: FollowerAI start follow %s (GUID %u)", pLeader->GetName(), m_uiLeaderGUID);
+}
+
+Player* FollowerAI::GetLeaderForFollower()
+{
+ if (Player* pLeader = Unit::GetPlayer(m_uiLeaderGUID))
+ {
+ if (pLeader->isAlive())
+ return pLeader;
+ else
+ {
+ if (Group* pGroup = pLeader->GetGroup())
+ {
+ for (GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next())
+ {
+ Player* pMember = pRef->getSource();
+
+ if (pMember && pMember->isAlive() && me->IsWithinDistInMap(pMember, MAX_PLAYER_DISTANCE))
+ {
+ debug_log("TSCR: FollowerAI GetLeader changed and returned new leader.");
+ m_uiLeaderGUID = pMember->GetGUID();
+ return pMember;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ debug_log("TSCR: FollowerAI GetLeader can not find suitable leader.");
+ return NULL;
+}
+
+void FollowerAI::SetFollowComplete(bool bWithEndEvent)
+{
+ if (me->hasUnitState(UNIT_STAT_FOLLOW))
+ {
+ me->clearUnitState(UNIT_STAT_FOLLOW);
+
+ me->StopMoving();
+ me->GetMotionMaster()->Clear();
+ me->GetMotionMaster()->MoveIdle();
+ }
+
+ if (bWithEndEvent)
+ AddFollowState(STATE_FOLLOW_POSTEVENT);
+ else
+ {
+ if (HasFollowState(STATE_FOLLOW_POSTEVENT))
+ RemoveFollowState(STATE_FOLLOW_POSTEVENT);
+ }
+
+ AddFollowState(STATE_FOLLOW_COMPLETE);
+}
+
+void FollowerAI::SetFollowPaused(bool bPaused)
+{
+ if (!HasFollowState(STATE_FOLLOW_INPROGRESS) || HasFollowState(STATE_FOLLOW_COMPLETE))
+ return;
+
+ if (bPaused)
+ {
+ AddFollowState(STATE_FOLLOW_PAUSED);
+
+ if (me->hasUnitState(UNIT_STAT_FOLLOW))
+ {
+ me->clearUnitState(UNIT_STAT_FOLLOW);
+
+ me->StopMoving();
+ me->GetMotionMaster()->Clear();
+ me->GetMotionMaster()->MoveIdle();
+ }
+ }
+ else
+ {
+ RemoveFollowState(STATE_FOLLOW_PAUSED);
+
+ if (Player* pLeader = GetLeaderForFollower())
+ me->GetMotionMaster()->MoveFollow(pLeader, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
+ }
+}
diff --git a/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.h b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.h
new file mode 100644
index 00000000000..d352141e3e9
--- /dev/null
+++ b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.h
@@ -0,0 +1,67 @@
+/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
+ * This program is free software licensed under GPL version 2
+ * Please see the included DOCS/LICENSE.TXT for more information */
+
+#ifndef SC_FOLLOWERAI_H
+#define SC_FOLLOWERAI_H
+
+#include "ScriptSystem.h"
+
+enum eFollowState
+{
+ STATE_FOLLOW_NONE = 0x000,
+ STATE_FOLLOW_INPROGRESS = 0x001, //must always have this state for any follow
+ STATE_FOLLOW_RETURNING = 0x002, //when returning to combat start after being in combat
+ STATE_FOLLOW_PAUSED = 0x004, //disables following
+ STATE_FOLLOW_COMPLETE = 0x008, //follow is completed and may end
+ STATE_FOLLOW_PREEVENT = 0x010, //not implemented (allow pre event to run, before follow is initiated)
+ STATE_FOLLOW_POSTEVENT = 0x020 //can be set at complete and allow post event to run
+};
+
+class FollowerAI : public ScriptedAI
+{
+ public:
+ explicit FollowerAI(Creature* pCreature);
+ ~FollowerAI() {}
+
+ //virtual void WaypointReached(uint32 uiPointId) = 0;
+
+ void MovementInform(uint32 uiMotionType, uint32 uiPointId);
+
+ void AttackStart(Unit*);
+
+ void MoveInLineOfSight(Unit*);
+
+ void EnterEvadeMode();
+
+ void JustDied(Unit*);
+
+ void JustRespawned();
+
+ void UpdateAI(const uint32); //the "internal" update, calls UpdateFollowerAI()
+ virtual void UpdateFollowerAI(const uint32); //used when it's needed to add code in update (abilities, scripted events, etc)
+
+ void StartFollow(Player* pPlayer, uint32 uiFactionForFollower = 0, const Quest* pQuest = NULL);
+
+ void SetFollowPaused(bool bPaused); //if special event require follow mode to hold/resume during the follow
+ void SetFollowComplete(bool bWithEndEvent = false);
+
+ bool HasFollowState(uint32 uiFollowState) { return (m_uiFollowState & uiFollowState); }
+
+ protected:
+ Player* GetLeaderForFollower();
+
+ private:
+ void AddFollowState(uint32 uiFollowState) { m_uiFollowState |= uiFollowState; }
+ void RemoveFollowState(uint32 uiFollowState) { m_uiFollowState &= ~uiFollowState; }
+
+ bool AssistPlayerInCombat(Unit* pWho);
+
+ uint64 m_uiLeaderGUID;
+ uint32 m_uiUpdateFollowTimer;
+ uint32 m_uiFollowState;
+
+ const Quest* m_pQuestForFollow; //normally we have a quest
+};
+
+#endif
diff --git a/src/server/game/AI/ScriptedAI/ScriptedGossip.h b/src/server/game/AI/ScriptedAI/ScriptedGossip.h
new file mode 100644
index 00000000000..6f1da291c45
--- /dev/null
+++ b/src/server/game/AI/ScriptedAI/ScriptedGossip.h
@@ -0,0 +1,187 @@
+/* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * Thanks to the original authors: ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
+ *
+ * This program is free software licensed under GPL version 2
+ * Please see the included DOCS/LICENSE.TXT for more information */
+
+#ifndef SC_GOSSIP_H
+#define SC_GOSSIP_H
+
+#include "Player.h"
+#include "GossipDef.h"
+#include "QuestDef.h"
+
+// Gossip Item Text
+#define GOSSIP_TEXT_BROWSE_GOODS "I'd like to browse your goods."
+#define GOSSIP_TEXT_TRAIN "Train me!"
+
+#define GOSSIP_TEXT_BANK "The bank"
+#define GOSSIP_TEXT_IRONFORGE_BANK "The bank of Ironforge"
+#define GOSSIP_TEXT_STORMWIND_BANK "The bank of Stormwind"
+#define GOSSIP_TEXT_WINDRIDER "The wind rider master"
+#define GOSSIP_TEXT_GRYPHON "The gryphon master"
+#define GOSSIP_TEXT_BATHANDLER "The bat handler"
+#define GOSSIP_TEXT_HIPPOGRYPH "The hippogryph master"
+#define GOSSIP_TEXT_ZEPPLINMASTER "The zeppelin master"
+#define GOSSIP_TEXT_DEEPRUNTRAM "The Deeprun Tram"
+#define GOSSIP_TEXT_FERRY "The Rut'theran Ferry"
+#define GOSSIP_TEXT_FLIGHTMASTER "The flight master"
+#define GOSSIP_TEXT_AUCTIONHOUSE "The auction house"
+#define GOSSIP_TEXT_GUILDMASTER "The guild master"
+#define GOSSIP_TEXT_INN "The inn"
+#define GOSSIP_TEXT_MAILBOX "The mailbox"
+#define GOSSIP_TEXT_STABLEMASTER "The stable master"
+#define GOSSIP_TEXT_WEAPONMASTER "The weapon master"
+#define GOSSIP_TEXT_OFFICERS "The officers' lounge"
+#define GOSSIP_TEXT_BATTLEMASTER "The battlemaster"
+#define GOSSIP_TEXT_BARBER "Barber"
+#define GOSSIP_TEXT_CLASSTRAINER "A class trainer"
+#define GOSSIP_TEXT_PROFTRAINER "A profession trainer"
+#define GOSSIP_TEXT_LEXICON "Lexicon of Power"
+
+#define GOSSIP_TEXT_ALTERACVALLEY "Alterac Valley"
+#define GOSSIP_TEXT_ARATHIBASIN "Arathi Basin"
+#define GOSSIP_TEXT_WARSONGULCH "Warsong Gulch"
+#define GOSSIP_TEXT_ARENA "Arena"
+#define GOSSIP_TEXT_EYEOFTHESTORM "Eye of The Storm"
+#define GOSSIP_TEXT_STRANDOFANCIENT "Strand of the Ancients"
+
+#define GOSSIP_TEXT_DEATH_KNIGHT "Death Knight"
+#define GOSSIP_TEXT_DRUID "Druid"
+#define GOSSIP_TEXT_HUNTER "Hunter"
+#define GOSSIP_TEXT_PRIEST "Priest"
+#define GOSSIP_TEXT_ROGUE "Rogue"
+#define GOSSIP_TEXT_WARRIOR "Warrior"
+#define GOSSIP_TEXT_PALADIN "Paladin"
+#define GOSSIP_TEXT_SHAMAN "Shaman"
+#define GOSSIP_TEXT_MAGE "Mage"
+#define GOSSIP_TEXT_WARLOCK "Warlock"
+
+#define GOSSIP_TEXT_ALCHEMY "Alchemy"
+#define GOSSIP_TEXT_BLACKSMITHING "Blacksmithing"
+#define GOSSIP_TEXT_COOKING "Cooking"
+#define GOSSIP_TEXT_ENCHANTING "Enchanting"
+#define GOSSIP_TEXT_ENGINEERING "Engineering"
+#define GOSSIP_TEXT_FIRSTAID "First Aid"
+#define GOSSIP_TEXT_HERBALISM "Herbalism"
+#define GOSSIP_TEXT_INSCRIPTION "Inscription"
+#define GOSSIP_TEXT_JEWELCRAFTING "Jewelcrafting"
+#define GOSSIP_TEXT_LEATHERWORKING "Leatherworking"
+#define GOSSIP_TEXT_TAILORING "Tailoring"
+#define GOSSIP_TEXT_MINING "Mining"
+#define GOSSIP_TEXT_FISHING "Fishing"
+#define GOSSIP_TEXT_SKINNING "Skinning"
+
+enum eTradeskill
+{
+// Skill defines
+ TRADESKILL_ALCHEMY = 1,
+ TRADESKILL_BLACKSMITHING = 2,
+ TRADESKILL_COOKING = 3,
+ TRADESKILL_ENCHANTING = 4,
+ TRADESKILL_ENGINEERING = 5,
+ TRADESKILL_FIRSTAID = 6,
+ TRADESKILL_HERBALISM = 7,
+ TRADESKILL_LEATHERWORKING = 8,
+ TRADESKILL_POISONS = 9,
+ TRADESKILL_TAILORING = 10,
+ TRADESKILL_MINING = 11,
+ TRADESKILL_FISHING = 12,
+ TRADESKILL_SKINNING = 13,
+ TRADESKILL_JEWLCRAFTING = 14,
+ TRADESKILL_INSCRIPTION = 15,
+
+ TRADESKILL_LEVEL_NONE = 0,
+ TRADESKILL_LEVEL_APPRENTICE = 1,
+ TRADESKILL_LEVEL_JOURNEYMAN = 2,
+ TRADESKILL_LEVEL_EXPERT = 3,
+ TRADESKILL_LEVEL_ARTISAN = 4,
+ TRADESKILL_LEVEL_MASTER = 5,
+ TRADESKILL_LEVEL_GRAND_MASTER = 6,
+
+// Gossip defines
+ GOSSIP_ACTION_TRADE = 1,
+ GOSSIP_ACTION_TRAIN = 2,
+ GOSSIP_ACTION_TAXI = 3,
+ GOSSIP_ACTION_GUILD = 4,
+ GOSSIP_ACTION_BATTLE = 5,
+ GOSSIP_ACTION_BANK = 6,
+ GOSSIP_ACTION_INN = 7,
+ GOSSIP_ACTION_HEAL = 8,
+ GOSSIP_ACTION_TABARD = 9,
+ GOSSIP_ACTION_AUCTION = 10,
+ GOSSIP_ACTION_INN_INFO = 11,
+ GOSSIP_ACTION_UNLEARN = 12,
+ GOSSIP_ACTION_INFO_DEF = 1000,
+
+ GOSSIP_SENDER_MAIN = 1,
+ GOSSIP_SENDER_INN_INFO = 2,
+ GOSSIP_SENDER_INFO = 3,
+ GOSSIP_SENDER_SEC_PROFTRAIN = 4,
+ GOSSIP_SENDER_SEC_CLASSTRAIN = 5,
+ GOSSIP_SENDER_SEC_BATTLEINFO = 6,
+ GOSSIP_SENDER_SEC_BANK = 7,
+ GOSSIP_SENDER_SEC_INN = 8,
+ GOSSIP_SENDER_SEC_MAILBOX = 9,
+ GOSSIP_SENDER_SEC_STABLEMASTER = 10
+};
+
+extern uint32 GetSkillLevel(Player *player,uint32 skill);
+
+// Defined fuctions to use with player.
+
+// This fuction add's a menu item,
+// a - Icon Id
+// b - Text
+// c - Sender(this is to identify the current Menu with this item)
+// d - Action (identifys this Menu Item)
+// e - Text to be displayed in pop up box
+// f - Money value in pop up box
+#define ADD_GOSSIP_ITEM(a,b,c,d) PlayerTalkClass->GetGossipMenu().AddMenuItem(a,b,c,d,"",0)
+#define ADD_GOSSIP_ITEM_EXTENDED(a,b,c,d,e,f,g) PlayerTalkClass->GetGossipMenu().AddMenuItem(a,b,c,d,e,f,g)
+
+// This fuction Sends the current menu to show to client, a - NPCTEXTID(uint32) , b - npc guid(uint64)
+#define SEND_GOSSIP_MENU(a,b) PlayerTalkClass->SendGossipMenu(a,b)
+
+// This fuction shows POI(point of interest) to client.
+// a - position X
+// b - position Y
+// c - Icon Id
+// d - Flags
+// e - Data
+// f - Location Name
+#define SEND_POI(a,b,c,d,e,f) PlayerTalkClass->SendPointOfInterest(a,b,c,d,e,f)
+
+// Closes the Menu
+#define CLOSE_GOSSIP_MENU() PlayerTalkClass->CloseGossip()
+
+// Fuction to tell to client the details
+// a - quest object
+// b - npc guid(uint64)
+// c - Activate accept(bool)
+#define SEND_QUEST_DETAILS(a,b,c) PlayerTalkClass->SendQuestDetails(a,b,c)
+
+// Fuction to tell to client the requested items to complete quest
+// a - quest object
+// b - npc guid(uint64)
+// c - Iscompletable(bool)
+// d - close at cancel(bool) - in case single incomplite ques
+#define SEND_REQUESTEDITEMS(a,b,c,d) PlayerTalkClass->SendRequestedItems(a,b,c,d)
+
+// Fuctions to send NPC lists, a - is always the npc guid(uint64)
+#define SEND_VENDORLIST(a) GetSession()->SendListInventory(a)
+#define SEND_TRAINERLIST(a) GetSession()->SendTrainerList(a)
+
+// Ressurect's the player if is dead.
+#define SEND_SPRESURRECT() GetSession()->SendSpiritResurrect()
+
+// Get the player's honor rank.
+#define GET_HONORRANK() GetHonorRank()
+// -----------------------------------
+
+// defined fuctions to use with Creature
+
+#define QUEST_DIALOG_STATUS(a,b,c) GetSession()->getDialogStatus(a,b,c)
+#endif
+
diff --git a/src/server/game/AI/ScriptedAI/ScriptedGuardAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedGuardAI.cpp
new file mode 100644
index 00000000000..68dc8690470
--- /dev/null
+++ b/src/server/game/AI/ScriptedAI/ScriptedGuardAI.cpp
@@ -0,0 +1,194 @@
+/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>.sourceforge.net/>
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* ScriptData
+SDName: Guard_AI
+SD%Complete: 90
+SDComment:
+SDCategory: Guards
+EndScriptData */
+
+#include "ScriptedPch.h"
+#include "ScriptedGuardAI.h"
+
+// **** This script is for use within every single guard to save coding time ****
+
+#define GENERIC_CREATURE_COOLDOWN 5000
+
+#define SAY_GUARD_SIL_AGGRO1 -1070001
+#define SAY_GUARD_SIL_AGGRO2 -1070002
+#define SAY_GUARD_SIL_AGGRO3 -1070003
+
+guardAI::guardAI(Creature* pCreature) : ScriptedAI(pCreature),
+ GlobalCooldown(0),
+ BuffTimer(0)
+{}
+
+void guardAI::Reset()
+{
+ GlobalCooldown = 0;
+ BuffTimer = 0; //Rebuff as soon as we can
+}
+
+void guardAI::EnterCombat(Unit *who)
+{
+ if (me->GetEntry() == 15184)
+ DoScriptText(RAND(SAY_GUARD_SIL_AGGRO1,SAY_GUARD_SIL_AGGRO2,SAY_GUARD_SIL_AGGRO3), me, who);
+
+ if (SpellEntry const *spell = me->reachWithSpellAttack(who))
+ DoCastSpell(who, spell);
+}
+
+void guardAI::JustDied(Unit *Killer)
+{
+ //Send Zone Under Attack message to the LocalDefense and WorldDefense Channels
+ if (Player* pKiller = Killer->GetCharmerOrOwnerPlayerOrPlayerItself())
+ me->SendZoneUnderAttackMessage(pKiller);
+}
+
+void guardAI::UpdateAI(const uint32 diff)
+{
+ //Always decrease our global cooldown first
+ if (GlobalCooldown > diff)
+ GlobalCooldown -= diff;
+ else GlobalCooldown = 0;
+
+ //Buff timer (only buff when we are alive and not in combat
+ if (me->isAlive() && !me->isInCombat())
+ if (BuffTimer <= diff)
+ {
+ //Find a spell that targets friendly and applies an aura (these are generally buffs)
+ SpellEntry const *info = SelectSpell(me, 0, 0, SELECT_TARGET_ANY_FRIEND, 0, 0, 0, 0, SELECT_EFFECT_AURA);
+
+ if (info && !GlobalCooldown)
+ {
+ //Cast the buff spell
+ DoCastSpell(me, info);
+
+ //Set our global cooldown
+ GlobalCooldown = GENERIC_CREATURE_COOLDOWN;
+
+ //Set our timer to 10 minutes before rebuff
+ BuffTimer = 600000;
+ } //Try again in 30 seconds
+ else BuffTimer = 30000;
+ } else BuffTimer -= diff;
+
+ //Return since we have no target
+ if (!UpdateVictim())
+ return;
+
+ // Make sure our attack is ready and we arn't currently casting
+ if (me->isAttackReady() && !me->IsNonMeleeSpellCasted(false))
+ {
+ //If we are within range melee the target
+ if (me->IsWithinMeleeRange(me->getVictim()))
+ {
+ bool Healing = false;
+ SpellEntry const *info = NULL;
+
+ //Select a healing spell if less than 30% hp
+ if (me->GetHealth()*100 / me->GetMaxHealth() < 30)
+ info = SelectSpell(me, 0, 0, SELECT_TARGET_ANY_FRIEND, 0, 0, 0, 0, SELECT_EFFECT_HEALING);
+
+ //No healing spell available, select a hostile spell
+ if (info) Healing = true;
+ else info = SelectSpell(me->getVictim(), 0, 0, SELECT_TARGET_ANY_ENEMY, 0, 0, 0, 0, SELECT_EFFECT_DONTCARE);
+
+ //20% chance to replace our white hit with a spell
+ if (info && rand() % 5 == 0 && !GlobalCooldown)
+ {
+ //Cast the spell
+ if (Healing)DoCastSpell(me, info);
+ else DoCastSpell(me->getVictim(), info);
+
+ //Set our global cooldown
+ GlobalCooldown = GENERIC_CREATURE_COOLDOWN;
+ }
+ else me->AttackerStateUpdate(me->getVictim());
+
+ me->resetAttackTimer();
+ }
+ }
+ else
+ {
+ //Only run this code if we arn't already casting
+ if (!me->IsNonMeleeSpellCasted(false))
+ {
+ bool Healing = false;
+ SpellEntry const *info = NULL;
+
+ //Select a healing spell if less than 30% hp ONLY 33% of the time
+ if (me->GetHealth()*100 / me->GetMaxHealth() < 30 && rand() % 3 == 0)
+ info = SelectSpell(me, 0, 0, SELECT_TARGET_ANY_FRIEND, 0, 0, 0, 0, SELECT_EFFECT_HEALING);
+
+ //No healing spell available, See if we can cast a ranged spell (Range must be greater than ATTACK_DISTANCE)
+ if (info) Healing = true;
+ else info = SelectSpell(me->getVictim(), 0, 0, SELECT_TARGET_ANY_ENEMY, 0, 0, NOMINAL_MELEE_RANGE, 0, SELECT_EFFECT_DONTCARE);
+
+ //Found a spell, check if we arn't on cooldown
+ if (info && !GlobalCooldown)
+ {
+ //If we are currently moving stop us and set the movement generator
+ if ((*me).GetMotionMaster()->GetCurrentMovementGeneratorType() != IDLE_MOTION_TYPE)
+ {
+ (*me).GetMotionMaster()->Clear(false);
+ (*me).GetMotionMaster()->MoveIdle();
+ }
+
+ //Cast spell
+ if (Healing) DoCastSpell(me,info);
+ else DoCastSpell(me->getVictim(),info);
+
+ //Set our global cooldown
+ GlobalCooldown = GENERIC_CREATURE_COOLDOWN;
+
+ } //If no spells available and we arn't moving run to target
+ else if ((*me).GetMotionMaster()->GetCurrentMovementGeneratorType() != TARGETED_MOTION_TYPE)
+ {
+ //Cancel our current spell and then mutate new movement generator
+ me->InterruptNonMeleeSpells(false);
+ (*me).GetMotionMaster()->Clear(false);
+ (*me).GetMotionMaster()->MoveChase(me->getVictim());
+ }
+ }
+ }
+}
+
+void guardAI::DoReplyToTextEmote(uint32 em)
+{
+ switch(em)
+ {
+ case TEXTEMOTE_KISS: me->HandleEmoteCommand(EMOTE_ONESHOT_BOW); break;
+ case TEXTEMOTE_WAVE: me->HandleEmoteCommand(EMOTE_ONESHOT_WAVE); break;
+ case TEXTEMOTE_SALUTE: me->HandleEmoteCommand(EMOTE_ONESHOT_SALUTE); break;
+ case TEXTEMOTE_SHY: me->HandleEmoteCommand(EMOTE_ONESHOT_FLEX); break;
+ case TEXTEMOTE_RUDE:
+ case TEXTEMOTE_CHICKEN: me->HandleEmoteCommand(EMOTE_ONESHOT_POINT); break;
+ }
+}
+
+void guardAI_orgrimmar::ReceiveEmote(Player* pPlayer, uint32 text_emote)
+{
+ if (pPlayer->GetTeam() == HORDE)
+ DoReplyToTextEmote(text_emote);
+}
+
+void guardAI_stormwind::ReceiveEmote(Player* pPlayer, uint32 text_emote)
+{
+ if (pPlayer->GetTeam() == ALLIANCE)
+ DoReplyToTextEmote(text_emote);
+}
diff --git a/src/server/game/AI/ScriptedAI/ScriptedGuardAI.h b/src/server/game/AI/ScriptedAI/ScriptedGuardAI.h
new file mode 100644
index 00000000000..d28f612625e
--- /dev/null
+++ b/src/server/game/AI/ScriptedAI/ScriptedGuardAI.h
@@ -0,0 +1,45 @@
+/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
+ * This program is free software licensed under GPL version 2
+ * Please see the included DOCS/LICENSE.TXT for more information */
+
+#ifndef SC_GUARDAI_H
+#define SC_GUARDAI_H
+
+#define GENERIC_CREATURE_COOLDOWN 5000
+
+struct guardAI : public ScriptedAI
+{
+ public:
+ explicit guardAI(Creature* pCreature);
+ ~guardAI() {}
+
+ uint32 GlobalCooldown; //This variable acts like the global cooldown that players have (1.5 seconds)
+ uint32 BuffTimer; //This variable keeps track of buffs
+
+ void Reset();
+
+ void EnterCombat(Unit * /*who*/);
+
+ void JustDied(Unit *Killer);
+
+ void UpdateAI(const uint32 diff);
+
+ //common used for guards in main cities
+ void DoReplyToTextEmote(uint32 em);
+};
+
+struct guardAI_orgrimmar : public guardAI
+{
+ guardAI_orgrimmar(Creature *c) : guardAI(c) {}
+
+ void ReceiveEmote(Player *player, uint32 text_emote);
+};
+
+struct guardAI_stormwind : public guardAI
+{
+ guardAI_stormwind(Creature *c) : guardAI(c) {}
+
+ void ReceiveEmote(Player *player, uint32 text_emote);
+};
+#endif
+
diff --git a/src/server/game/AI/ScriptedAI/ScriptedInstance.h b/src/server/game/AI/ScriptedAI/ScriptedInstance.h
new file mode 100644
index 00000000000..25593e05300
--- /dev/null
+++ b/src/server/game/AI/ScriptedAI/ScriptedInstance.h
@@ -0,0 +1,20 @@
+/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
+* This program is free software licensed under GPL version 2
+* Please see the included DOCS/LICENSE.TXT for more information */
+
+#ifndef SC_INSTANCE_H
+#define SC_INSTANCE_H
+
+#include "InstanceData.h"
+#include "Map.h"
+
+#define OUT_SAVE_INST_DATA debug_log("TSCR: Saving Instance Data for Instance %s (Map %d, Instance Id %d)", instance->GetMapName(), instance->GetId(), instance->GetInstanceId())
+#define OUT_SAVE_INST_DATA_COMPLETE debug_log("TSCR: Saving Instance Data for Instance %s (Map %d, Instance Id %d) completed.", instance->GetMapName(), instance->GetId(), instance->GetInstanceId())
+#define OUT_LOAD_INST_DATA(a) debug_log("TSCR: Loading Instance Data for Instance %s (Map %d, Instance Id %d). Input is '%s'", instance->GetMapName(), instance->GetId(), instance->GetInstanceId(), a)
+#define OUT_LOAD_INST_DATA_COMPLETE debug_log("TSCR: Instance Data Load for Instance %s (Map %d, Instance Id: %d) is complete.",instance->GetMapName(), instance->GetId(), instance->GetInstanceId())
+#define OUT_LOAD_INST_DATA_FAIL error_log("TSCR: Unable to load Instance Data for Instance %s (Map %d, Instance Id: %d).",instance->GetMapName(), instance->GetId(), instance->GetInstanceId())
+
+#define ScriptedInstance InstanceData
+
+#endif
+
diff --git a/src/server/game/AI/ScriptedAI/ScriptedSimpleAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedSimpleAI.cpp
new file mode 100644
index 00000000000..08797515837
--- /dev/null
+++ b/src/server/game/AI/ScriptedAI/ScriptedSimpleAI.cpp
@@ -0,0 +1,278 @@
+/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+/* ScriptData
+SDName: SimpleAI
+SD%Complete: 100
+SDComment: Base Class for SimpleAI creatures
+SDCategory: Creatures
+EndScriptData */
+
+#include "ScriptedPch.h"
+#include "ScriptedSimpleAI.h"
+
+SimpleAI::SimpleAI(Creature *c) : ScriptedAI(c)
+{
+ //Clear all data
+ Aggro_TextId[0] = 0;
+ Aggro_TextId[1] = 0;
+ Aggro_TextId[2] = 0;
+ Aggro_Sound[0] = 0;
+ Aggro_Sound[1] = 0;
+ Aggro_Sound[2] = 0;
+
+ Death_TextId[0] = 0;
+ Death_TextId[1] = 0;
+ Death_TextId[2] = 0;
+ Death_Sound[0] = 0;
+ Death_Sound[1] = 0;
+ Death_Sound[2] = 0;
+ Death_Spell = 0;
+ Death_Target_Type = 0;
+
+ Kill_TextId[0] = 0;
+ Kill_TextId[1] = 0;
+ Kill_TextId[2] = 0;
+ Kill_Sound[0] = 0;
+ Kill_Sound[1] = 0;
+ Kill_Sound[2] = 0;
+ Kill_Spell = 0;
+ Kill_Target_Type = 0;
+
+ memset(Spell,0,sizeof(Spell));
+
+ EnterEvadeMode();
+}
+
+void SimpleAI::Reset()
+{
+}
+
+void SimpleAI::EnterCombat(Unit *who)
+{
+ //Reset cast timers
+ if (Spell[0].First_Cast >= 0)
+ Spell_Timer[0] = Spell[0].First_Cast;
+ else Spell_Timer[0] = 1000;
+ if (Spell[1].First_Cast >= 0)
+ Spell_Timer[1] = Spell[1].First_Cast;
+ else Spell_Timer[1] = 1000;
+ if (Spell[2].First_Cast >= 0)
+ Spell_Timer[2] = Spell[2].First_Cast;
+ else Spell_Timer[2] = 1000;
+ if (Spell[3].First_Cast >= 0)
+ Spell_Timer[3] = Spell[3].First_Cast;
+ else Spell_Timer[3] = 1000;
+ if (Spell[4].First_Cast >= 0)
+ Spell_Timer[4] = Spell[4].First_Cast;
+ else Spell_Timer[4] = 1000;
+ if (Spell[5].First_Cast >= 0)
+ Spell_Timer[5] = Spell[5].First_Cast;
+ else Spell_Timer[5] = 1000;
+ if (Spell[6].First_Cast >= 0)
+ Spell_Timer[6] = Spell[6].First_Cast;
+ else Spell_Timer[6] = 1000;
+ if (Spell[7].First_Cast >= 0)
+ Spell_Timer[7] = Spell[7].First_Cast;
+ else Spell_Timer[7] = 1000;
+ if (Spell[8].First_Cast >= 0)
+ Spell_Timer[8] = Spell[8].First_Cast;
+ else Spell_Timer[8] = 1000;
+ if (Spell[9].First_Cast >= 0)
+ Spell_Timer[9] = Spell[9].First_Cast;
+ else Spell_Timer[9] = 1000;
+
+ uint8 random_text = urand(0,2);
+
+ //Random text
+ if (Aggro_TextId[random_text])
+ DoScriptText(Aggro_TextId[random_text], me, who);
+
+ //Random sound
+ if (Aggro_Sound[random_text])
+ DoPlaySoundToSet(me, Aggro_Sound[random_text]);
+}
+
+void SimpleAI::KilledUnit(Unit *victim)
+{
+ uint8 random_text = urand(0,2);
+
+ //Random yell
+ if (Kill_TextId[random_text])
+ DoScriptText(Kill_TextId[random_text], me, victim);
+
+ //Random sound
+ if (Kill_Sound[random_text])
+ DoPlaySoundToSet(me, Kill_Sound[random_text]);
+
+ if (!Kill_Spell)
+ return;
+
+ Unit *pTarget = NULL;
+
+ switch (Kill_Target_Type)
+ {
+ case CAST_SELF:
+ pTarget = me;
+ break;
+ case CAST_HOSTILE_TARGET:
+ pTarget = me->getVictim();
+ break;
+ case CAST_HOSTILE_SECOND_AGGRO:
+ pTarget = SelectUnit(SELECT_TARGET_TOPAGGRO,1);
+ break;
+ case CAST_HOSTILE_LAST_AGGRO:
+ pTarget = SelectUnit(SELECT_TARGET_BOTTOMAGGRO,0);
+ break;
+ case CAST_HOSTILE_RANDOM:
+ pTarget = SelectUnit(SELECT_TARGET_RANDOM,0);
+ break;
+ case CAST_KILLEDUNIT_VICTIM:
+ pTarget = victim;
+ break;
+ }
+
+ //Target is ok, cast a spell on it
+ if (pTarget)
+ DoCast(pTarget, Kill_Spell);
+}
+
+void SimpleAI::DamageTaken(Unit *killer, uint32 &damage)
+{
+ //Return if damage taken won't kill us
+ if (me->GetHealth() > damage)
+ return;
+
+ uint8 random_text = urand(0,2);
+
+ //Random yell
+ if (Death_TextId[random_text])
+ DoScriptText(Death_TextId[random_text], me, killer);
+
+ //Random sound
+ if (Death_Sound[random_text])
+ DoPlaySoundToSet(me, Death_Sound[random_text]);
+
+ if (!Death_Spell)
+ return;
+
+ Unit *pTarget = NULL;
+
+ switch (Death_Target_Type)
+ {
+ case CAST_SELF:
+ pTarget = me;
+ break;
+ case CAST_HOSTILE_TARGET:
+ pTarget = me->getVictim();
+ break;
+ case CAST_HOSTILE_SECOND_AGGRO:
+ pTarget = SelectUnit(SELECT_TARGET_TOPAGGRO,1);
+ break;
+ case CAST_HOSTILE_LAST_AGGRO:
+ pTarget = SelectUnit(SELECT_TARGET_BOTTOMAGGRO,0);
+ break;
+ case CAST_HOSTILE_RANDOM:
+ pTarget = SelectUnit(SELECT_TARGET_RANDOM,0);
+ break;
+ case CAST_JUSTDIED_KILLER:
+ pTarget = killer;
+ break;
+ }
+
+ //Target is ok, cast a spell on it
+ if (pTarget)
+ DoCast(pTarget, Death_Spell);
+}
+
+void SimpleAI::UpdateAI(const uint32 diff)
+{
+ //Return since we have no target
+ if (!UpdateVictim())
+ return;
+
+ //Spells
+ for (uint32 i = 0; i < 10; ++i)
+ {
+ //Spell not valid
+ if (!Spell[i].Enabled || !Spell[i].Spell_Id)
+ continue;
+
+ if (Spell_Timer[i] <= diff)
+ {
+ //Check if this is a percentage based
+ if (Spell[i].First_Cast < 0 && Spell[i].First_Cast > -100 && me->GetHealth()*100 / me->GetMaxHealth() > -Spell[i].First_Cast)
+ continue;
+
+ //Check Current spell
+ if (!(Spell[i].InterruptPreviousCast && me->IsNonMeleeSpellCasted(false)))
+ {
+ Unit *pTarget = NULL;
+
+ switch (Spell[i].Cast_Target_Type)
+ {
+ case CAST_SELF:
+ pTarget = me;
+ break;
+ case CAST_HOSTILE_TARGET:
+ pTarget = me->getVictim();
+ break;
+ case CAST_HOSTILE_SECOND_AGGRO:
+ pTarget = SelectUnit(SELECT_TARGET_TOPAGGRO,1);
+ break;
+ case CAST_HOSTILE_LAST_AGGRO:
+ pTarget = SelectUnit(SELECT_TARGET_BOTTOMAGGRO,0);
+ break;
+ case CAST_HOSTILE_RANDOM:
+ pTarget = SelectUnit(SELECT_TARGET_RANDOM,0);
+ break;
+ }
+
+ //Target is ok, cast a spell on it and then do our random yell
+ if (pTarget)
+ {
+ if (me->IsNonMeleeSpellCasted(false))
+ me->InterruptNonMeleeSpells(false);
+
+ DoCast(pTarget, Spell[i].Spell_Id);
+
+ //Yell and sound use the same number so that you can make
+ //the Creature yell with the correct sound effect attached
+ uint8 random_text = urand(0,2);
+
+ //Random yell
+ if (Spell[i].TextId[random_text])
+ DoScriptText(Spell[i].TextId[random_text], me, pTarget);
+
+ //Random sound
+ if (Spell[i].Text_Sound[random_text])
+ DoPlaySoundToSet(me, Spell[i].Text_Sound[random_text]);
+ }
+
+ }
+
+ //Spell will cast agian when the cooldown is up
+ if (Spell[i].CooldownRandomAddition)
+ Spell_Timer[i] = Spell[i].Cooldown + (rand() % Spell[i].CooldownRandomAddition);
+ else Spell_Timer[i] = Spell[i].Cooldown;
+
+ } else Spell_Timer[i] -= diff;
+
+ }
+
+ DoMeleeAttackIfReady();
+}
+
diff --git a/src/server/game/AI/ScriptedAI/ScriptedSimpleAI.h b/src/server/game/AI/ScriptedAI/ScriptedSimpleAI.h
new file mode 100644
index 00000000000..c4689ff3fab
--- /dev/null
+++ b/src/server/game/AI/ScriptedAI/ScriptedSimpleAI.h
@@ -0,0 +1,71 @@
+/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
+* This program is free software licensed under GPL version 2
+* Please see the included DOCS/LICENSE.TXT for more information */
+
+#ifndef SC_SIMPLEAI_H
+#define SC_SIMPLEAI_H
+
+enum CastTarget
+{
+ CAST_SELF = 0, //Self cast
+ CAST_HOSTILE_TARGET, //Our current target (ie: highest aggro)
+ CAST_HOSTILE_SECOND_AGGRO, //Second highest aggro (generaly used for cleaves and some special attacks)
+ CAST_HOSTILE_LAST_AGGRO, //Dead last on aggro (no idea what this could be used for)
+ CAST_HOSTILE_RANDOM, //Just any random target on our threat list
+ CAST_FRIENDLY_RANDOM, //NOT YET IMPLEMENTED
+
+ //Special cases
+ CAST_KILLEDUNIT_VICTIM, //Only works within KilledUnit function
+ CAST_JUSTDIED_KILLER, //Only works within JustDied function
+};
+
+struct SimpleAI : public ScriptedAI
+{
+ SimpleAI(Creature *c);// : ScriptedAI(c);
+
+ void Reset();
+
+ void EnterCombat(Unit * /*who*/);
+
+ void KilledUnit(Unit * /*victim*/);
+
+ void DamageTaken(Unit *killer, uint32 &damage);
+
+ void UpdateAI(const uint32 diff);
+
+public:
+
+ int32 Aggro_TextId[3];
+ uint32 Aggro_Sound[3];
+
+ int32 Death_TextId[3];
+ uint32 Death_Sound[3];
+ uint32 Death_Spell;
+ uint32 Death_Target_Type;
+
+ int32 Kill_TextId[3];
+ uint32 Kill_Sound[3];
+ uint32 Kill_Spell;
+ uint32 Kill_Target_Type;
+
+ struct SimpleAI_Spell
+ {
+ uint32 Spell_Id; //Spell ID to cast
+ int32 First_Cast; //Delay for first cast
+ uint32 Cooldown; //Cooldown between casts
+ uint32 CooldownRandomAddition; //Random addition to cooldown (in range from 0 - CooldownRandomAddition)
+ uint32 Cast_Target_Type; //Target type (note that certain spells may ignore this)
+ bool InterruptPreviousCast; //Interrupt a previous cast if this spell needs to be cast
+ bool Enabled; //Spell enabled or disabled (default: false)
+
+ //3 texts to many?
+ int32 TextId[3];
+ uint32 Text_Sound[3];
+ }Spell[10];
+
+protected:
+ uint32 Spell_Timer[10];
+};
+
+#endif
+
diff --git a/src/server/game/AI/ScriptedAI/ScriptedSmartAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedSmartAI.cpp
new file mode 100644
index 00000000000..c5c11d5bef9
--- /dev/null
+++ b/src/server/game/AI/ScriptedAI/ScriptedSmartAI.cpp
@@ -0,0 +1,8 @@
+/* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * Thanks to the original authors: ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
+ *
+ * This program is free software licensed under GPL version 2
+ * Please see the included DOCS/LICENSE.TXT for more information */
+
+#include "ScriptedSmartAI.h" \ No newline at end of file
diff --git a/src/server/game/AI/ScriptedAI/ScriptedSmartAI.h b/src/server/game/AI/ScriptedAI/ScriptedSmartAI.h
new file mode 100644
index 00000000000..a1e10a06bb2
--- /dev/null
+++ b/src/server/game/AI/ScriptedAI/ScriptedSmartAI.h
@@ -0,0 +1,8 @@
+/* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * Thanks to the original authors: ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
+ *
+ * This program is free software licensed under GPL version 2
+ * Please see the included DOCS/LICENSE.TXT for more information */
+
+#include "Creature.h" \ No newline at end of file
diff --git a/src/server/game/AI/TotemAI.cpp b/src/server/game/AI/TotemAI.cpp
new file mode 100644
index 00000000000..a6464f189e8
--- /dev/null
+++ b/src/server/game/AI/TotemAI.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "TotemAI.h"
+#include "Totem.h"
+#include "Creature.h"
+#include "DBCStores.h"
+#include "ObjectAccessor.h"
+#include "SpellMgr.h"
+
+#include "GridNotifiers.h"
+#include "GridNotifiersImpl.h"
+#include "CellImpl.h"
+
+int
+TotemAI::Permissible(const Creature *creature)
+{
+ if (creature->isTotem())
+ return PERMIT_BASE_PROACTIVE;
+
+ return PERMIT_BASE_NO;
+}
+
+TotemAI::TotemAI(Creature *c) : CreatureAI(c), i_victimGuid(0)
+{
+ assert(c->isTotem());
+}
+
+void
+TotemAI::MoveInLineOfSight(Unit *)
+{
+}
+
+void TotemAI::EnterEvadeMode()
+{
+ me->CombatStop(true);
+}
+
+void
+TotemAI::UpdateAI(const uint32 /*diff*/)
+{
+ if (me->ToTotem()->GetTotemType() != TOTEM_ACTIVE)
+ return;
+
+ if (!me->isAlive() || me->IsNonMeleeSpellCasted(false))
+ return;
+
+ // Search spell
+ SpellEntry const *spellInfo = sSpellStore.LookupEntry(me->ToTotem()->GetSpell());
+ if (!spellInfo)
+ return;
+
+ // Get spell range
+ SpellRangeEntry const* srange = sSpellRangeStore.LookupEntry(spellInfo->rangeIndex);
+ float max_range = GetSpellMaxRangeForHostile(srange);
+
+ // SPELLMOD_RANGE not applied in this place just because not existence range mods for attacking totems
+
+ // pointer to appropriate target if found any
+ Unit* victim = i_victimGuid ? ObjectAccessor::GetUnit(*me, i_victimGuid) : NULL;
+
+ // Search victim if no, not attackable, or out of range, or friendly (possible in case duel end)
+ if (!victim ||
+ !victim->isTargetableForAttack() || !me->IsWithinDistInMap(victim, max_range) ||
+ me->IsFriendlyTo(victim) || !victim->isVisibleForOrDetect(me,false))
+ {
+ victim = NULL;
+ Trinity::NearestAttackableUnitInObjectRangeCheck u_check(me, me, max_range);
+ Trinity::UnitLastSearcher<Trinity::NearestAttackableUnitInObjectRangeCheck> checker(me, victim, u_check);
+ me->VisitNearbyObject(max_range, checker);
+ }
+
+ // If have target
+ if (victim)
+ {
+ // remember
+ i_victimGuid = victim->GetGUID();
+
+ // attack
+ me->SetInFront(victim); // client change orientation by self
+ me->CastSpell(victim, me->ToTotem()->GetSpell(), false);
+ }
+ else
+ i_victimGuid = 0;
+}
+
+void
+TotemAI::AttackStart(Unit *)
+{
+ // Sentry totem sends ping on attack
+ if (me->GetEntry() == SENTRY_TOTEM_ENTRY && me->GetOwner()->GetTypeId() == TYPEID_PLAYER)
+ {
+ WorldPacket data(MSG_MINIMAP_PING, (8+4+4));
+ data << me->GetGUID();
+ data << me->GetPositionX();
+ data << me->GetPositionY();
+ ((Player*)me->GetOwner())->GetSession()->SendPacket(&data);
+ }
+}
diff --git a/src/server/game/AI/TotemAI.h b/src/server/game/AI/TotemAI.h
new file mode 100644
index 00000000000..34f4dfa9945
--- /dev/null
+++ b/src/server/game/AI/TotemAI.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef TRINITY_TOTEMAI_H
+#define TRINITY_TOTEMAI_H
+
+#include "CreatureAI.h"
+#include "Timer.h"
+
+class Creature;
+class Totem;
+
+class TotemAI : public CreatureAI
+{
+ public:
+
+ explicit TotemAI(Creature *c);
+
+ void MoveInLineOfSight(Unit *);
+ void AttackStart(Unit *);
+ void EnterEvadeMode();
+
+ void UpdateAI(const uint32);
+ static int Permissible(const Creature *);
+
+ private:
+ uint64 i_victimGuid;
+};
+#endif
+
diff --git a/src/server/game/AI/UnitAI.cpp b/src/server/game/AI/UnitAI.cpp
new file mode 100644
index 00000000000..a156ec573d9
--- /dev/null
+++ b/src/server/game/AI/UnitAI.cpp
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "UnitAI.h"
+#include "Player.h"
+#include "Creature.h"
+#include "SpellAuras.h"
+#include "SpellAuraEffects.h"
+#include "SpellMgr.h"
+#include "CreatureAIImpl.h"
+
+void UnitAI::AttackStart(Unit *victim)
+{
+ if (victim && me->Attack(victim, true))
+ me->GetMotionMaster()->MoveChase(victim);
+}
+
+void UnitAI::AttackStartCaster(Unit *victim, float dist)
+{
+ if (victim && me->Attack(victim, false))
+ me->GetMotionMaster()->MoveChase(victim, dist);
+}
+
+void UnitAI::DoMeleeAttackIfReady()
+{
+ if (me->hasUnitState(UNIT_STAT_CASTING))
+ return;
+
+ //Make sure our attack is ready and we aren't currently casting before checking distance
+ if (me->isAttackReady())
+ {
+ //If we are within range melee the target
+ if (me->IsWithinMeleeRange(me->getVictim()))
+ {
+ me->AttackerStateUpdate(me->getVictim());
+ me->resetAttackTimer();
+ }
+ }
+ if (me->haveOffhandWeapon() && me->isAttackReady(OFF_ATTACK))
+ {
+ //If we are within range melee the target
+ if (me->IsWithinMeleeRange(me->getVictim()))
+ {
+ me->AttackerStateUpdate(me->getVictim(), OFF_ATTACK);
+ me->resetAttackTimer(OFF_ATTACK);
+ }
+ }
+}
+
+bool UnitAI::DoSpellAttackIfReady(uint32 spell)
+{
+ if (me->hasUnitState(UNIT_STAT_CASTING))
+ return true;
+
+ if (me->isAttackReady())
+ {
+ if (me->IsWithinCombatRange(me->getVictim(), GetSpellMaxRange(spell, false)))
+ {
+ me->CastSpell(me->getVictim(), spell, false);
+ me->resetAttackTimer();
+ }
+ else
+ return false;
+ }
+ return true;
+}
+
+// default predicate function to select target based on distance, player and/or aura criteria
+struct DefaultTargetSelector : public std::unary_function<Unit *, bool>
+{
+ const Unit *me;
+ float m_dist;
+ bool m_playerOnly;
+ int32 m_aura;
+
+ // pUnit: the reference unit
+ // dist: if 0: ignored, if > 0: maximum distance to the reference unit, if < 0: minimum distance to the reference unit
+ // playerOnly: self explaining
+ // aura: if 0: ignored, if > 0: the target shall have the aura, if < 0, the target shall NOT have the aura
+ DefaultTargetSelector(const Unit *pUnit, float dist, bool playerOnly, int32 aura) : me(pUnit), m_dist(dist), m_playerOnly(playerOnly), m_aura(aura) {}
+
+ bool operator() (const Unit *pTarget)
+ {
+ if (!me)
+ return false;
+
+ if (!pTarget)
+ return false;
+
+ if (m_playerOnly && (pTarget->GetTypeId() != TYPEID_PLAYER))
+ return false;
+
+ if (m_dist > 0.0f && !me->IsWithinCombatRange(pTarget, m_dist))
+ return false;
+
+ if (m_dist < 0.0f && me->IsWithinCombatRange(pTarget, -m_dist))
+ return false;
+
+ if (m_aura)
+ {
+ if (m_aura > 0)
+ {
+ if (!pTarget->HasAura(m_aura))
+ return false;
+ }
+ else
+ {
+ if (pTarget->HasAura(m_aura))
+ return false;
+ }
+ }
+
+ return true;
+ }
+};
+
+Unit* UnitAI::SelectTarget(SelectAggroTarget targetType, uint32 position, float dist, bool playerOnly, int32 aura)
+{
+ return SelectTarget(targetType, position, DefaultTargetSelector(me, dist, playerOnly, aura));
+}
+
+void UnitAI::SelectTargetList(std::list<Unit*> &targetList, uint32 num, SelectAggroTarget targetType, float dist, bool playerOnly, int32 aura)
+{
+ const std::list<HostileReference*> &threatlist = me->getThreatManager().getThreatList();
+
+ if (threatlist.empty())
+ return;
+
+ DefaultTargetSelector targetSelector(me, dist,playerOnly, aura);
+ for (std::list<HostileReference*>::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
+ if (targetSelector((*itr)->getTarget()))
+ targetList.push_back((*itr)->getTarget());
+
+ if (targetType == SELECT_TARGET_NEAREST || targetType == SELECT_TARGET_FARTHEST)
+ targetList.sort(Trinity::ObjectDistanceOrderPred(me));
+
+ if (targetType == SELECT_TARGET_FARTHEST || targetType == SELECT_TARGET_BOTTOMAGGRO)
+ targetList.reverse();
+
+ if (targetList.size() < num)
+ return;
+
+ if (targetType == SELECT_TARGET_RANDOM)
+ {
+ while (num < targetList.size()) {
+ std::list<Unit*>::iterator itr = targetList.begin();
+ advance(itr, urand(0, targetList.size()-1));
+ targetList.erase(itr);
+ }
+ }
+ else
+ targetList.resize(num);
+}
+
+float UnitAI::DoGetSpellMaxRange(uint32 spellId, bool positive)
+{
+ return GetSpellMaxRange(spellId, positive);
+}
+
+void UnitAI::DoAddAuraToAllHostilePlayers(uint32 spellid)
+{
+ if (me->isInCombat())
+ {
+ std::list<HostileReference*>& threatlist = me->getThreatManager().getThreatList();
+ for (std::list<HostileReference*>::iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
+ {
+ if (Unit *pTemp = Unit::GetUnit(*me,(*itr)->getUnitGuid()))
+ if (pTemp->GetTypeId() == TYPEID_PLAYER)
+ me->AddAura(spellid, pTemp);
+ }
+ }else
+ return;
+}
+
+void UnitAI::DoCastToAllHostilePlayers(uint32 spellid, bool triggered)
+{
+ if (me->isInCombat())
+ {
+ std::list<HostileReference*>& threatlist = me->getThreatManager().getThreatList();
+ for (std::list<HostileReference*>::iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
+ {
+ if (Unit *pTemp = Unit::GetUnit(*me,(*itr)->getUnitGuid()))
+ if (pTemp->GetTypeId() == TYPEID_PLAYER)
+ me->CastSpell(pTemp, spellid, triggered);
+ }
+ }else
+ return;
+}
+
+void UnitAI::DoCast(uint32 spellId)
+{
+ Unit *target = NULL;
+ //sLog.outError("aggre %u %u", spellId, (uint32)AISpellInfo[spellId].target);
+ switch(AISpellInfo[spellId].target)
+ {
+ default:
+ case AITARGET_SELF: target = me; break;
+ case AITARGET_VICTIM: target = me->getVictim(); break;
+ case AITARGET_ENEMY:
+ {
+ const SpellEntry * spellInfo = GetSpellStore()->LookupEntry(spellId);
+ bool playerOnly = spellInfo->AttributesEx3 & SPELL_ATTR_EX3_PLAYERS_ONLY;
+ //float range = GetSpellMaxRange(spellInfo, false);
+ target = SelectTarget(SELECT_TARGET_RANDOM, 0, GetSpellMaxRange(spellInfo, false), playerOnly);
+ break;
+ }
+ case AITARGET_ALLY: target = me; break;
+ case AITARGET_BUFF: target = me; break;
+ case AITARGET_DEBUFF:
+ {
+ const SpellEntry * spellInfo = GetSpellStore()->LookupEntry(spellId);
+ bool playerOnly = spellInfo->AttributesEx3 & SPELL_ATTR_EX3_PLAYERS_ONLY;
+ float range = GetSpellMaxRange(spellInfo, false);
+
+ DefaultTargetSelector targetSelector(me, range, playerOnly, -(int32)spellId);
+ if (!(spellInfo->Attributes & SPELL_ATTR_BREAKABLE_BY_DAMAGE)
+ && !(spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_VICTIM)
+ && targetSelector(me->getVictim()))
+ target = me->getVictim();
+ else
+ target = SelectTarget(SELECT_TARGET_RANDOM, 0, targetSelector);
+ break;
+ }
+ }
+
+ if (target)
+ me->CastSpell(target, spellId, false);
+}
+
+#define UPDATE_TARGET(a) {if (AIInfo->target<a) AIInfo->target=a;}
+
+void UnitAI::FillAISpellInfo()
+{
+ AISpellInfo = new AISpellInfoType[GetSpellStore()->GetNumRows()];
+
+ AISpellInfoType *AIInfo = AISpellInfo;
+ const SpellEntry * spellInfo;
+
+ for (uint32 i = 0; i < GetSpellStore()->GetNumRows(); ++i, ++AIInfo)
+ {
+ spellInfo = GetSpellStore()->LookupEntry(i);
+ if (!spellInfo)
+ continue;
+
+ if (spellInfo->Attributes & SPELL_ATTR_CASTABLE_WHILE_DEAD)
+ AIInfo->condition = AICOND_DIE;
+ else if (IsPassiveSpell(i) || GetSpellDuration(spellInfo) == -1)
+ AIInfo->condition = AICOND_AGGRO;
+ else
+ AIInfo->condition = AICOND_COMBAT;
+
+ if (AIInfo->cooldown < spellInfo->RecoveryTime)
+ AIInfo->cooldown = spellInfo->RecoveryTime;
+
+ if (!GetSpellMaxRange(spellInfo, false))
+ UPDATE_TARGET(AITARGET_SELF)
+ else
+ {
+ for (uint32 j = 0; j < 3; ++j)
+ {
+ uint32 targetType = spellInfo->EffectImplicitTargetA[j];
+
+ if (targetType == TARGET_UNIT_TARGET_ENEMY
+ || targetType == TARGET_DST_TARGET_ENEMY)
+ UPDATE_TARGET(AITARGET_VICTIM)
+ else if (targetType == TARGET_UNIT_AREA_ENEMY_DST)
+ UPDATE_TARGET(AITARGET_ENEMY)
+
+ if (spellInfo->Effect[j] == SPELL_EFFECT_APPLY_AURA)
+ {
+ if (targetType == TARGET_UNIT_TARGET_ENEMY)
+ UPDATE_TARGET(AITARGET_DEBUFF)
+ else if (IsPositiveSpell(i))
+ UPDATE_TARGET(AITARGET_BUFF)
+ }
+ }
+ }
+ AIInfo->realCooldown = spellInfo->RecoveryTime + spellInfo->StartRecoveryTime;
+ SpellRangeEntry const* srange = sSpellRangeStore.LookupEntry(spellInfo->rangeIndex);
+ if (srange)
+ AIInfo->maxRange = srange->maxRangeHostile * 3 / 4;
+ }
+}
+
+//Enable PlayerAI when charmed
+void PlayerAI::OnCharmed(bool apply) { me->IsAIEnabled = apply; }
+
+void SimpleCharmedAI::UpdateAI(const uint32 /*diff*/)
+{
+ Creature *charmer = me->GetCharmer()->ToCreature();
+
+ //kill self if charm aura has infinite duration
+ if (charmer->IsInEvadeMode())
+ {
+ Unit::AuraEffectList const& auras = me->GetAuraEffectsByType(SPELL_AURA_MOD_CHARM);
+ for (Unit::AuraEffectList::const_iterator iter = auras.begin(); iter != auras.end(); ++iter)
+ if ((*iter)->GetCasterGUID() == charmer->GetGUID() && (*iter)->GetBase()->IsPermanent())
+ {
+ charmer->Kill(me);
+ return;
+ }
+ }
+
+ if (!charmer->isInCombat())
+ me->GetMotionMaster()->MoveFollow(charmer, PET_FOLLOW_DIST, me->GetFollowAngle());
+
+ Unit *target = me->getVictim();
+ if (!target || !charmer->canAttack(target))
+ AttackStart(charmer->SelectNearestTargetInAttackDistance());
+}
diff --git a/src/server/game/AI/UnitAI.h b/src/server/game/AI/UnitAI.h
new file mode 100644
index 00000000000..62b7090a2d0
--- /dev/null
+++ b/src/server/game/AI/UnitAI.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef TRINITY_UNITAI_H
+#define TRINITY_UNITAI_H
+
+#include "Platform/Define.h"
+#include <list>
+#include "Unit.h"
+
+class Unit;
+class Player;
+struct AISpellInfoType;
+
+//Selection method used by SelectTarget
+enum SelectAggroTarget
+{
+ SELECT_TARGET_RANDOM = 0, //Just selects a random target
+ SELECT_TARGET_TOPAGGRO, //Selects targes from top aggro to bottom
+ SELECT_TARGET_BOTTOMAGGRO, //Selects targets from bottom aggro to top
+ SELECT_TARGET_NEAREST,
+ SELECT_TARGET_FARTHEST,
+};
+
+class UnitAI
+{
+ protected:
+ Unit * const me;
+ public:
+ explicit UnitAI(Unit *u) : me(u) {}
+ virtual ~UnitAI() {}
+
+ virtual bool CanAIAttack(const Unit * /*who*/) const { return true; }
+ virtual void AttackStart(Unit *);
+ virtual void UpdateAI(const uint32 diff) = 0;
+
+ virtual void InitializeAI() { if (!me->isDead()) Reset(); }
+
+ virtual void Reset() {};
+
+ // Called when unit is charmed
+ virtual void OnCharmed(bool apply) = 0;
+
+ // Pass parameters between AI
+ virtual void DoAction(const int32 /*param*/ = 0) {}
+ virtual uint32 GetData(uint32 /*id = 0*/) { return 0; }
+ virtual void SetData(uint32 /*id*/, uint32 /*value*/) {}
+ virtual void SetGUID(const uint64 &/*guid*/, int32 /*id*/ = 0) {}
+ virtual uint64 GetGUID(int32 /*id*/ = 0) { return 0; }
+
+ Unit* SelectTarget(SelectAggroTarget targetType, uint32 position = 0, float dist = 0.0f, bool playerOnly = false, int32 aura = 0);
+ void SelectTargetList(std::list<Unit*> &targetList, uint32 num, SelectAggroTarget targetType, float dist = 0.0f, bool playerOnly = false, int32 aura = 0);
+
+ // Select the targets satifying the predicate.
+ // predicate shall extend std::unary_function<Unit *, bool>
+ template<class PREDICATE> Unit* SelectTarget(SelectAggroTarget targetType, uint32 position, PREDICATE predicate)
+ {
+ const std::list<HostileReference *> &threatlist = me->getThreatManager().getThreatList();
+ std::list<Unit*> targetList;
+
+ if (position >= threatlist.size())
+ return NULL;
+
+ for (std::list<HostileReference*>::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
+ {
+ HostileReference* ref = (*itr);
+ if (predicate(ref->getTarget()))
+ targetList.push_back(ref->getTarget());
+ }
+
+ if (position >= targetList.size())
+ return NULL;
+
+ if (targetType == SELECT_TARGET_NEAREST || targetType == SELECT_TARGET_FARTHEST)
+ targetList.sort(Trinity::ObjectDistanceOrderPred(me));
+
+ switch(targetType)
+ {
+ case SELECT_TARGET_NEAREST:
+ case SELECT_TARGET_TOPAGGRO:
+ {
+ std::list<Unit*>::iterator itr = targetList.begin();
+ advance(itr, position);
+ return *itr;
+ }
+ break;
+
+ case SELECT_TARGET_FARTHEST:
+ case SELECT_TARGET_BOTTOMAGGRO:
+ {
+ std::list<Unit*>::reverse_iterator ritr = targetList.rbegin();
+ advance(ritr, position);
+ return *ritr;
+ }
+ break;
+
+ case SELECT_TARGET_RANDOM:
+ {
+ std::list<Unit*>::iterator itr = targetList.begin();
+ advance(itr, urand(position, targetList.size()-1));
+ return *itr;
+ }
+ break;
+ }
+
+ return NULL;
+ }
+
+ void AttackStartCaster(Unit *victim, float dist);
+
+ void DoAddAuraToAllHostilePlayers(uint32 spellid);
+ void DoCast(uint32 spellId);
+ void DoCast(Unit* victim, uint32 spellId, bool triggered = false);
+ void DoCastToAllHostilePlayers(uint32 spellid, bool triggered = false);
+ void DoCastVictim(uint32 spellId, bool triggered = false);
+ void DoCastAOE(uint32 spellId, bool triggered = false);
+
+ float DoGetSpellMaxRange(uint32 spellId, bool positive = false);
+
+ void DoMeleeAttackIfReady();
+ bool DoSpellAttackIfReady(uint32 spell);
+
+ static AISpellInfoType *AISpellInfo;
+ static void FillAISpellInfo();
+};
+
+class PlayerAI : public UnitAI
+{
+ protected:
+ Player* const me;
+ public:
+ explicit PlayerAI(Player *p) : UnitAI((Unit*)p), me(p) {}
+
+ void OnCharmed(bool apply);
+};
+
+class SimpleCharmedAI : public PlayerAI
+{
+ public:
+ void UpdateAI(const uint32 diff);
+ SimpleCharmedAI(Player *p): PlayerAI(p) {}
+};
+
+#endif