aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/server/collision/Maps/TileAssembler.cpp12
-rw-r--r--src/server/collision/Models/GameObjectModel.cpp6
-rw-r--r--src/server/game/AuctionHouseBot/AuctionHouseBot.cpp18
-rw-r--r--src/server/game/AuctionHouseBot/AuctionHouseBot.h18
-rw-r--r--src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.cpp569
-rw-r--r--src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.h31
-rw-r--r--src/server/game/AuctionHouseBot/AuctionHouseBotSeller.cpp4
-rw-r--r--src/server/game/Entities/Player/Player.cpp46
-rw-r--r--src/server/game/Globals/ObjectMgr.cpp34
-rw-r--r--src/server/game/Groups/Group.cpp2
-rw-r--r--src/server/game/Handlers/QuestHandler.cpp6
-rw-r--r--src/server/game/Miscellaneous/SharedDefines.h2
-rw-r--r--src/server/game/Reputation/ReputationMgr.cpp2
-rw-r--r--src/server/game/Spells/Spell.cpp4
-rw-r--r--src/server/game/Spells/SpellMgr.cpp4
-rw-r--r--src/server/scripts/Commands/cs_debug.cpp2
-rw-r--r--src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp2
-rw-r--r--src/server/scripts/EasternKingdoms/SunwellPlateau/boss_muru.cpp2
-rw-r--r--src/server/scripts/EasternKingdoms/zone_redridge_mountains.cpp2
-rw-r--r--src/server/scripts/Kalimdor/zone_bloodmyst_isle.cpp9
-rw-r--r--src/server/scripts/Kalimdor/zone_mulgore.cpp306
-rw-r--r--src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/boss_bronjahm.cpp8
-rw-r--r--src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp414
-rw-r--r--src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.h23
-rw-r--r--src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/instance_halls_of_reflection.cpp52
-rw-r--r--src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp2
-rw-r--r--src/server/scripts/Northrend/zone_zuldrak.cpp2
-rw-r--r--src/server/shared/DataStores/DBCStore.h7
-rw-r--r--src/server/shared/Database/DatabaseWorkerPool.h9
-rw-r--r--src/server/shared/Database/MySQLConnection.cpp12
-rw-r--r--src/server/shared/Database/MySQLConnection.h2
-rw-r--r--src/server/shared/Database/Transaction.cpp12
-rw-r--r--src/server/shared/Database/Transaction.h1
-rw-r--r--src/server/shared/Debugging/Errors.h6
-rw-r--r--src/server/worldserver/worldserver.conf.dist59
35 files changed, 991 insertions, 699 deletions
diff --git a/src/server/collision/Maps/TileAssembler.cpp b/src/server/collision/Maps/TileAssembler.cpp
index 06540ecdc61..ee978211577 100644
--- a/src/server/collision/Maps/TileAssembler.cpp
+++ b/src/server/collision/Maps/TileAssembler.cpp
@@ -391,6 +391,18 @@ namespace VMAP
}
}
+ if (bounds.isEmpty())
+ {
+ std::cout << "\nModel " << std::string(buff, name_length) << " has empty bounding box" << std::endl;
+ continue;
+ }
+
+ if (!bounds.isFinite())
+ {
+ std::cout << "\nModel " << std::string(buff, name_length) << " has invalid bounding box" << std::endl;
+ continue;
+ }
+
fwrite(&displayId, sizeof(uint32), 1, model_list_copy);
fwrite(&name_length, sizeof(uint32), 1, model_list_copy);
fwrite(&buff, sizeof(char), name_length, model_list_copy);
diff --git a/src/server/collision/Models/GameObjectModel.cpp b/src/server/collision/Models/GameObjectModel.cpp
index 993c298941c..05bd5d360c6 100644
--- a/src/server/collision/Models/GameObjectModel.cpp
+++ b/src/server/collision/Models/GameObjectModel.cpp
@@ -78,6 +78,12 @@ void LoadGameObjectModelList()
break;
}
+ if (v1.isNaN() || v2.isNaN())
+ {
+ VMAP_ERROR_LOG("misc", "File '%s' Model '%s' has invalid v1%s v2%s values!", VMAP::GAMEOBJECT_MODELS, std::string(buff, name_length).c_str(), v1.toString().c_str(), v2.toString().c_str());
+ continue;
+ }
+
model_list.insert
(
ModelList::value_type( displayId, GameobjectModelData(std::string(buff, name_length), AABox(v1, v2)) )
diff --git a/src/server/game/AuctionHouseBot/AuctionHouseBot.cpp b/src/server/game/AuctionHouseBot/AuctionHouseBot.cpp
index 7e8ccb4bb23..a04e4091778 100644
--- a/src/server/game/AuctionHouseBot/AuctionHouseBot.cpp
+++ b/src/server/game/AuctionHouseBot/AuctionHouseBot.cpp
@@ -204,14 +204,24 @@ void AuctionBotConfig::GetConfigFromFile()
SetConfig(CONFIG_AHBOT_MINTIME, "AuctionHouseBot.MinTime", 1);
SetConfig(CONFIG_AHBOT_MAXTIME, "AuctionHouseBot.MaxTime", 72);
- SetConfigMinMax(CONFIG_AHBOT_BUYER_CHANCE_RATIO_ALLIANCE, "AuctionHouseBot.Buyer.Alliance.Chance.Ratio", 3, 1, 100);
- SetConfigMinMax(CONFIG_AHBOT_BUYER_CHANCE_RATIO_HORDE, "AuctionHouseBot.Buyer.Horde.Chance.Ratio", 3, 1, 100);
- SetConfigMinMax(CONFIG_AHBOT_BUYER_CHANCE_RATIO_NEUTRAL, "AuctionHouseBot.Buyer.Neutral.Chance.Ratio", 3, 1, 100);
SetConfigMinMax(CONFIG_AHBOT_BUYER_RECHECK_INTERVAL, "AuctionHouseBot.Buyer.Recheck.Interval", 20, 1, DAY / MINUTE);
+ SetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_GRAY, "AuctionHouseBot.Buyer.Baseprice.Gray", 3504);
+ SetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_WHITE, "AuctionHouseBot.Buyer.Baseprice.White", 5429);
+ SetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_GREEN, "AuctionHouseBot.Buyer.Baseprice.Green", 21752);
+ SetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_BLUE, "AuctionHouseBot.Buyer.Baseprice.Blue", 36463);
+ SetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_PURPLE, "AuctionHouseBot.Buyer.Baseprice.Purple", 87124);
+ SetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_ORANGE, "AuctionHouseBot.Buyer.Baseprice.Orange", 214347);
+ SetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_YELLOW, "AuctionHouseBot.Buyer.Baseprice.Yellow", 407406);
+ SetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_GRAY, "AuctionHouseBot.Buyer.ChanceMultiplier.Gray", 100);
+ SetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_WHITE, "AuctionHouseBot.Buyer.ChanceMultiplier.White", 100);
+ SetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_GREEN, "AuctionHouseBot.Buyer.ChanceMultiplier.Green", 100);
+ SetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_BLUE, "AuctionHouseBot.Buyer.ChanceMultiplier.Blue", 100);
+ SetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_PURPLE, "AuctionHouseBot.Buyer.ChanceMultiplier.Purple", 100);
+ SetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_ORANGE, "AuctionHouseBot.Buyer.ChanceMultiplier.Orange", 100);
+ SetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_YELLOW, "AuctionHouseBot.Buyer.ChanceMultiplier.Yellow", 100);
SetConfig(CONFIG_AHBOT_SELLER_ENABLED, "AuctionHouseBot.Seller.Enabled", false);
SetConfig(CONFIG_AHBOT_BUYER_ENABLED, "AuctionHouseBot.Buyer.Enabled", false);
- SetConfig(CONFIG_AHBOT_BUYPRICE_BUYER, "AuctionHouseBot.Buyer.Buyprice", true);
SetConfig(CONFIG_AHBOT_CLASS_MISC_MOUNT_MIN_REQ_LEVEL, "AuctionHouseBot.Class.Misc.Mount.ReqLevel.Min", 0);
SetConfig(CONFIG_AHBOT_CLASS_MISC_MOUNT_MAX_REQ_LEVEL, "AuctionHouseBot.Class.Misc.Mount.ReqLevel.Max", 0);
diff --git a/src/server/game/AuctionHouseBot/AuctionHouseBot.h b/src/server/game/AuctionHouseBot/AuctionHouseBot.h
index 964262579e6..29460ba13d7 100644
--- a/src/server/game/AuctionHouseBot/AuctionHouseBot.h
+++ b/src/server/game/AuctionHouseBot/AuctionHouseBot.h
@@ -110,10 +110,21 @@ enum AuctionBotConfigUInt32Values
CONFIG_AHBOT_CLASS_PERMANENT_PRICE_RATIO,
CONFIG_AHBOT_CLASS_MISC_PRICE_RATIO,
CONFIG_AHBOT_CLASS_GLYPH_PRICE_RATIO,
- CONFIG_AHBOT_BUYER_CHANCE_RATIO_ALLIANCE,
- CONFIG_AHBOT_BUYER_CHANCE_RATIO_HORDE,
- CONFIG_AHBOT_BUYER_CHANCE_RATIO_NEUTRAL,
CONFIG_AHBOT_BUYER_RECHECK_INTERVAL,
+ CONFIG_AHBOT_BUYER_BASEPRICE_GRAY,
+ CONFIG_AHBOT_BUYER_BASEPRICE_WHITE,
+ CONFIG_AHBOT_BUYER_BASEPRICE_GREEN,
+ CONFIG_AHBOT_BUYER_BASEPRICE_BLUE,
+ CONFIG_AHBOT_BUYER_BASEPRICE_PURPLE,
+ CONFIG_AHBOT_BUYER_BASEPRICE_ORANGE,
+ CONFIG_AHBOT_BUYER_BASEPRICE_YELLOW,
+ CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_GRAY,
+ CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_WHITE,
+ CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_GREEN,
+ CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_BLUE,
+ CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_PURPLE,
+ CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_ORANGE,
+ CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_YELLOW,
CONFIG_AHBOT_CLASS_MISC_MOUNT_MIN_REQ_LEVEL,
CONFIG_AHBOT_CLASS_MISC_MOUNT_MAX_REQ_LEVEL,
CONFIG_AHBOT_CLASS_MISC_MOUNT_MIN_SKILL_RANK,
@@ -143,7 +154,6 @@ enum AuctionBotConfigBoolValues
CONFIG_AHBOT_BIND_USE,
CONFIG_AHBOT_BIND_QUEST,
CONFIG_AHBOT_BUYPRICE_SELLER,
- CONFIG_AHBOT_BUYPRICE_BUYER,
CONFIG_AHBOT_SELLER_ENABLED,
CONFIG_AHBOT_BUYER_ENABLED,
CONFIG_AHBOT_LOCKBOX_ENABLED,
diff --git a/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.cpp b/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.cpp
index af60316cbec..bd5defe2bc3 100644
--- a/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.cpp
+++ b/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.cpp
@@ -20,7 +20,7 @@
#include "ItemPrototype.h"
#include "AuctionHouseBotBuyer.h"
-AuctionBotBuyer::AuctionBotBuyer(): _checkInterval(20)
+AuctionBotBuyer::AuctionBotBuyer() : _checkInterval(20 * MINUTE)
{
// Define faction for our main data class.
for (int i = 0; i < MAX_AUCTION_HOUSE_TYPE; ++i)
@@ -48,391 +48,394 @@ bool AuctionBotBuyer::Initialize()
if (!activeHouse)
return false;
- //load Check interval
+ // load Check interval
_checkInterval = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_RECHECK_INTERVAL) * MINUTE;
- TC_LOG_DEBUG("ahbot", "AHBot buyer interval between 2 check = %u", _checkInterval);
+ TC_LOG_DEBUG("ahbot", "AHBot buyer interval is %u minutes", _checkInterval / MINUTE);
return true;
}
-void AuctionBotBuyer::LoadBuyerValues(BuyerConfiguration& config)
+void AuctionBotBuyer::LoadConfig()
{
- uint32 factionChance;
-
- switch (config.GetHouseType())
+ for (int i = 0; i < MAX_AUCTION_HOUSE_TYPE; ++i)
{
- case AUCTION_HOUSE_ALLIANCE:
- config.BuyerPriceRatio = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ALLIANCE_PRICE_RATIO) + 50;
- factionChance = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCE_RATIO_ALLIANCE);
- break;
- case AUCTION_HOUSE_HORDE:
- config.BuyerPriceRatio = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_HORDE_PRICE_RATIO) + 50;
- factionChance = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCE_RATIO_HORDE);
- break;
- default:
- config.BuyerPriceRatio = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_NEUTRAL_PRICE_RATIO) + 50;
- factionChance = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCE_RATIO_NEUTRAL);
- break;
+ _houseConfig[i].BuyerEnabled = sAuctionBotConfig->GetConfigBuyerEnabled(AuctionHouseType(i));
+ if (_houseConfig[i].BuyerEnabled)
+ LoadBuyerValues(_houseConfig[i]);
}
+}
+
+void AuctionBotBuyer::LoadBuyerValues(BuyerConfiguration& config)
+{
- config.FactionChance = 5000 * factionChance;
}
-void AuctionBotBuyer::LoadConfig()
+// Makes an AHbot buyer cycle for AH type if necessary
+bool AuctionBotBuyer::Update(AuctionHouseType houseType)
{
- for (int i = 0; i < MAX_AUCTION_HOUSE_TYPE; ++i)
+ if (!sAuctionBotConfig->GetConfigBuyerEnabled(houseType))
+ return false;
+
+ TC_LOG_DEBUG("ahbot", "AHBot: %s buying ...", AuctionBotConfig::GetHouseTypeName(houseType));
+
+ BuyerConfiguration& config = _houseConfig[houseType];
+ uint32 eligibleItems = GetItemInformation(config);
+ if (eligibleItems)
{
- _houseConfig[i].BuyerEnabled = sAuctionBotConfig->GetConfigBuyerEnabled(AuctionHouseType(i));
- if (_houseConfig[i].BuyerEnabled)
- LoadBuyerValues(_houseConfig[i]);
+ // Prepare list of items to bid or buy - remove old items
+ PrepareListOfEntry(config);
+ // Process buying and bidding items
+ BuyAndBidItems(config);
}
+
+ return true;
}
-uint32 AuctionBotBuyer::GetBuyableEntry(BuyerConfiguration& config)
+// Collects information about item counts and minimum prices to SameItemInfo and updates EligibleItems - a list with new items eligible for bot to buy and bid
+// Returns count of items in AH that were eligible for being bought or bidded on by ahbot buyer (EligibleItems size)
+uint32 AuctionBotBuyer::GetItemInformation(BuyerConfiguration& config)
{
config.SameItemInfo.clear();
- uint32 count = 0;
time_t now = time(nullptr);
+ uint32 count = 0;
AuctionHouseObject* house = sAuctionMgr->GetAuctionsMap(config.GetHouseType());
for (AuctionHouseObject::AuctionEntryMap::const_iterator itr = house->GetAuctionsBegin(); itr != house->GetAuctionsEnd(); ++itr)
{
AuctionEntry* entry = itr->second;
Item* item = sAuctionMgr->GetAItem(entry->itemGUIDLow);
- if (item)
+ if (!item)
+ continue;
+
+ BuyerItemInfo& itemInfo = config.SameItemInfo[item->GetEntry()];
+
+ // Update item entry's count and total bid prices
+ // This can be used later to determine the prices and chances to bid
+ uint32 itemBidPrice = entry->startbid / item->GetCount();
+ itemInfo.TotalBidPrice = itemInfo.TotalBidPrice + itemBidPrice;
+ itemInfo.BidItemCount++;
+
+ // Set minimum bid price
+ if (!itemInfo.MinBidPrice)
+ itemInfo.MinBidPrice = itemBidPrice;
+ else
+ itemBidPrice = std::min(itemInfo.MinBidPrice, itemBidPrice);
+
+ // Set minimum buyout price if item has buyout
+ if (entry->buyout)
{
- ItemTemplate const * prototype = item->GetTemplate();
- if (prototype)
- {
- ++config.SameItemInfo[item->GetEntry()].ItemCount; // Structure constructor will make sure Element are correctly initialized if entry is created here.
- config.SameItemInfo[item->GetEntry()].BuyPrice = config.SameItemInfo[item->GetEntry()].BuyPrice + (itr->second->buyout / item->GetCount());
- config.SameItemInfo[item->GetEntry()].BidPrice = config.SameItemInfo[item->GetEntry()].BidPrice + (itr->second->startbid / item->GetCount());
- if (itr->second->buyout != 0)
- {
- if (itr->second->buyout / item->GetCount() < config.SameItemInfo[item->GetEntry()].MinBuyPrice)
- config.SameItemInfo[item->GetEntry()].MinBuyPrice = itr->second->buyout / item->GetCount();
- else if (config.SameItemInfo[item->GetEntry()].MinBuyPrice == 0)
- config.SameItemInfo[item->GetEntry()].MinBuyPrice = itr->second->buyout / item->GetCount();
- }
- if (itr->second->startbid / item->GetCount() < config.SameItemInfo[item->GetEntry()].MinBidPrice)
- config.SameItemInfo[item->GetEntry()].MinBidPrice = itr->second->startbid / item->GetCount();
- else if (config.SameItemInfo[item->GetEntry()].MinBidPrice == 0)
- config.SameItemInfo[item->GetEntry()].MinBidPrice = itr->second->startbid / item->GetCount();
-
- if (!entry->owner)
- {
-
- if (entry->bid != 0 && entry->bidder) // Add bid by player
- {
- config.CheckedEntry[entry->Id].LastExist = now;
- config.CheckedEntry[entry->Id].AuctionId = entry->Id;
- ++count;
- }
- }
- else
- {
- if (entry->bid != 0)
- {
- if (entry->bidder)
- {
- config.CheckedEntry[entry->Id].LastExist = now;
- config.CheckedEntry[entry->Id].AuctionId = entry->Id;
- ++count;
- }
- }
- else
- {
- config.CheckedEntry[entry->Id].LastExist = now;
- config.CheckedEntry[entry->Id].AuctionId = entry->Id;
- ++count;
- }
- }
- }
+ // Update item entry's count and total buyout prices
+ // This can be used later to determine the prices and chances to buyout
+ uint32 itemBuyPrice = entry->buyout / item->GetCount();
+ itemInfo.TotalBuyPrice = itemInfo.TotalBuyPrice + itemBuyPrice;
+ itemInfo.BuyItemCount++;
+
+ if (!itemInfo.MinBuyPrice)
+ itemInfo.MinBuyPrice = itemBuyPrice;
+ else
+ itemInfo.MinBuyPrice = std::min(itemInfo.MinBuyPrice, itemBuyPrice);
+ }
+
+ // Add/update to EligibleItems if:
+ // has a bid by player or
+ // has no bids and not owned by bot
+ if ((entry->bid && entry->bidder) || (entry->owner && !entry->bid))
+ {
+ config.EligibleItems[entry->Id].LastExist = now;
+ config.EligibleItems[entry->Id].AuctionId = entry->Id;
+ ++count;
}
}
- TC_LOG_DEBUG("ahbot", "AHBot: %u items added to buyable vector for ah type: %u", count, config.GetHouseType());
+ TC_LOG_DEBUG("ahbot", "AHBot: %u items added to buyable/biddable vector for ah type: %u", count, config.GetHouseType());
TC_LOG_DEBUG("ahbot", "AHBot: SameItemInfo size = %u", (uint32)config.SameItemInfo.size());
return count;
}
-void AuctionBotBuyer::PrepareListOfEntry(BuyerConfiguration& config)
+// ahInfo can be NULL
+bool AuctionBotBuyer::RollBuyChance(const BuyerItemInfo* ahInfo, const Item* item, const AuctionEntry* auction, uint32 /*bidPrice*/)
{
- time_t now = time(nullptr) - 5;
+ if (!auction->buyout)
+ return false;
- for (CheckEntryMap::iterator itr = config.CheckedEntry.begin(); itr != config.CheckedEntry.end();)
- {
- if (itr->second.LastExist < (now - 5))
- config.CheckedEntry.erase(itr++);
- else
- ++itr;
- }
+ uint32 itemBuyPrice = auction->buyout / item->GetCount();
+ uint32 itemPrice = item->GetTemplate()->SellPrice ? item->GetTemplate()->SellPrice : GetVendorPrice(item->GetTemplate()->Quality);
+ // The AH cut needs to be added to the price, but we dont want a 100% chance to buy if the price is exactly AH default
+ itemPrice *= 1.4f;
- TC_LOG_DEBUG("ahbot", "AHBot: CheckedEntry size = %u", (uint32)config.CheckedEntry.size());
-}
+ // This value is between 0 and 100 and is used directly as the chance to buy or bid
+ // Value equal or above 100 means 100% chance and value below 0 means 0% chance
+ float chance = 100 / sqrt(itemBuyPrice / float(itemPrice));
-bool AuctionBotBuyer::IsBuyableEntry(uint32 buyoutPrice, double inGameBuyPrice, uint32 maxBuyablePrice, uint32 minBuyPrice, uint32 maxChance, uint32 chanceRatio)
-{
- double ratio = 0;
- uint32 chance = 0;
+ // If a player has bidded on item, have fifth of normal chance
+ if (auction->bidder)
+ chance = chance / 5;
- if (buyoutPrice <= minBuyPrice)
+ if (ahInfo)
{
- if (buyoutPrice <= maxBuyablePrice)
- chance = maxChance;
- else
- {
+ float avgBuyPrice = ahInfo->TotalBuyPrice / float(ahInfo->BuyItemCount);
- if (buyoutPrice > 0 && maxBuyablePrice > 0)
- {
- ratio = double(buyoutPrice) / double(maxBuyablePrice);
- if (ratio < 10)
- chance = maxChance - (ratio * maxChance / 10);
- else
- chance = 1;
- }
- }
- }
- else if (buyoutPrice <= inGameBuyPrice)
- {
- if (buyoutPrice <= maxBuyablePrice)
- chance = maxChance / 5;
- else
- {
+ TC_LOG_DEBUG("ahbot", "AHBot: buyout average: %.1f items with buyout: %u", avgBuyPrice, ahInfo->BuyItemCount);
- if (buyoutPrice > 0 && maxBuyablePrice > 0)
- {
- ratio = double(buyoutPrice) / double(maxBuyablePrice);
- if (ratio < 10)
- chance = (maxChance / 5) - (ratio * maxChance / 50);
- else
- chance = 1;
- }
- }
- }
- else if (buyoutPrice <= maxBuyablePrice)
- chance = maxChance / 10;
- else
- {
- if (buyoutPrice > 0 && maxBuyablePrice > 0)
+ // If there are more than 5 items on AH of this entry, try weigh in the average buyout price
+ if (ahInfo->BuyItemCount > 5)
{
- ratio = double(buyoutPrice) / double(maxBuyablePrice);
- if (ratio < 10)
- chance = (maxChance / 5) - (ratio* maxChance / 50);
- else
- chance = 0;
+ chance *= 1 / sqrt(itemBuyPrice / avgBuyPrice);
}
- else
- chance = 0;
}
- if (urand(1, chanceRatio) <= chance)
- {
- TC_LOG_DEBUG("ahbot", "AHBot: WIN BUY! Chance = %u, num = %u.", chance, chanceRatio);
- return true;
- }
- else
- {
- TC_LOG_DEBUG("ahbot", "AHBot: LOOSE BUY! Chance = %u, num = %u.", chance, chanceRatio);
- return false;
- }
+ // Add config weigh in for quality
+ chance *= GetChanceMultiplier(item->GetTemplate()->Quality) / 100.0f;
+
+ float rand = frand(0, 100);
+ bool win = rand <= chance;
+ TC_LOG_DEBUG("ahbot", "AHBot: %s BUY! chance = %.2f, price = %u, buyprice = %u.", win ? "WIN" : "LOSE", chance, itemPrice, itemBuyPrice);
+ return win;
}
-bool AuctionBotBuyer::IsBidableEntry(uint32 bidPrice, double inGameBuyPrice, double maxBidablePrice, uint32 minBidPrice, uint32 maxChance, uint32 chanceRatio)
+// ahInfo can be NULL
+bool AuctionBotBuyer::RollBidChance(const BuyerItemInfo* ahInfo, const Item* item, const AuctionEntry* auction, uint32 bidPrice)
{
- double ratio = 0;
- uint32 chance = 0;
+ uint32 itemBidPrice = bidPrice / item->GetCount();
+ uint32 itemPrice = item->GetTemplate()->SellPrice ? item->GetTemplate()->SellPrice : GetVendorPrice(item->GetTemplate()->Quality);
+ // The AH cut needs to be added to the price, but we dont want a 100% chance to buy if the price is exactly AH default
+ itemPrice *= 1.4f;
- if (bidPrice <= minBidPrice)
- {
- if (inGameBuyPrice != 0 && bidPrice < inGameBuyPrice - (inGameBuyPrice / 30))
- chance = maxChance;
- else
- {
- if (bidPrice < maxBidablePrice)
- {
- ratio = maxBidablePrice / bidPrice;
- if (ratio < 3)
- chance = maxChance / 500 * ratio;
- else
- chance = maxChance / 500;
- }
- }
- }
- else if (bidPrice < (inGameBuyPrice - (inGameBuyPrice / 30)))
- chance = (maxChance / 10);
- else
+ // This value is between 0 and 100 and is used directly as the chance to buy or bid
+ // Value equal or above 100 means 100% chance and value below 0 means 0% chance
+ float chance = 100 / sqrt(itemBidPrice / float(itemPrice));
+
+ if (ahInfo)
{
- if (bidPrice < maxBidablePrice)
+ float avgBidPrice = ahInfo->TotalBidPrice / float(ahInfo->BidItemCount);
+
+ TC_LOG_DEBUG("ahbot", "AHBot: Bid average: %.1f biddable item count: %u", avgBidPrice, ahInfo->BidItemCount);
+
+ // If there are more than 5 items on AH of this entry, try weigh in the average bid price
+ if (ahInfo->BidItemCount >= 5)
{
- ratio = maxBidablePrice / bidPrice;
- if (ratio < 4)
- chance = maxChance / 1000 * ratio;
- else
- chance = maxChance / 1000;
+ chance *= 1 / sqrt(itemBidPrice / avgBidPrice);
}
}
- if (urand(1, chanceRatio) <= chance)
- {
- TC_LOG_DEBUG("ahbot", "AHBot: WIN BID! Chance = %u, num = %u.", chance, chanceRatio);
- return true;
- }
- else
- {
- TC_LOG_DEBUG("ahbot", "AHBot: LOOSE BID! Chance = %u, num = %u.", chance, chanceRatio);
- return false;
- }
-}
+ // If a player has bidded on item, have fifth of normal chance
+ if (auction->bidder)
+ chance = chance / 5;
-void AuctionBotBuyer::PlaceBidToEntry(AuctionEntry* auction, uint32 bidPrice)
-{
- TC_LOG_DEBUG("ahbot", "AHBot: Bid placed to entry %u, %.2fg", auction->Id, float(bidPrice) / 10000.0f);
- auction->bid = bidPrice;
+ // Add config weigh in for quality
+ chance *= GetChanceMultiplier(item->GetTemplate()->Quality) / 100.0f;
+
+ float rand = frand(0, 100);
+ bool win = rand <= chance;
+ TC_LOG_DEBUG("ahbot", "AHBot: %s BID! chance = %.2f, price = %u, bidprice = %u.", win ? "WIN" : "LOSE", chance, itemPrice, itemBidPrice);
+ return win;
}
-void AuctionBotBuyer::BuyEntry(AuctionEntry* auction)
+// Removes items from EligibleItems that we shouldnt buy or bid on
+// The last existed time on them should be older than now
+void AuctionBotBuyer::PrepareListOfEntry(BuyerConfiguration& config)
{
- TC_LOG_DEBUG("ahbot", "AHBot: Entry %u bought at %.2fg", auction->Id, float(auction->buyout) / 10000.0f);
- auction->bid = auction->buyout;
+ // now - 5 seconds to leave out all old entries but keep the ones just updated a moment ago
+ time_t now = time(nullptr) - 5;
+
+ for (CheckEntryMap::iterator itr = config.EligibleItems.begin(); itr != config.EligibleItems.end();)
+ {
+ if (itr->second.LastExist < now)
+ config.EligibleItems.erase(itr++);
+ else
+ ++itr;
+ }
+
+ TC_LOG_DEBUG("ahbot", "AHBot: EligibleItems size = %u", (uint32)config.EligibleItems.size());
}
-void AuctionBotBuyer::AddNewAuctionBuyerBotBid(BuyerConfiguration& config)
+// Tries to bid and buy items based on their prices and chances set in configs
+void AuctionBotBuyer::BuyAndBidItems(BuyerConfiguration& config)
{
+ time_t now = time(nullptr);
AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(config.GetHouseType());
+ CheckEntryMap& items = config.EligibleItems;
- PrepareListOfEntry(config);
-
- time_t now = time(nullptr);
- uint32 buyCycles;
- if (config.CheckedEntry.size() > sAuctionBotConfig->GetItemPerCycleBoost())
+ // Max amount of items to buy or bid
+ uint32 cycles = sAuctionBotConfig->GetItemPerCycleNormal();
+ if (items.size() > sAuctionBotConfig->GetItemPerCycleBoost())
{
- buyCycles = sAuctionBotConfig->GetItemPerCycleBoost();
+ // set more cycles if there is a huge influx of items
+ cycles = sAuctionBotConfig->GetItemPerCycleBoost();
TC_LOG_DEBUG("ahbot", "AHBot: Boost value used for Buyer! (if this happens often adjust both ItemsPerCycle in worldserver.conf)");
}
- else
- buyCycles = sAuctionBotConfig->GetItemPerCycleNormal();
- for (CheckEntryMap::iterator itr = config.CheckedEntry.begin(); itr != config.CheckedEntry.end();)
+ // Process items eligible to be bidded or bought
+ CheckEntryMap::iterator itr = items.begin();
+ while (cycles && itr != items.end())
{
AuctionEntry* auction = auctionHouse->GetAuction(itr->second.AuctionId);
- if (!auction) // is auction not active now
+ if (!auction)
{
- TC_LOG_DEBUG("ahbot", "AHBot: Entry %u doesn't exists, perhaps bought already?",
- itr->second.AuctionId);
-
- config.CheckedEntry.erase(itr++);
+ TC_LOG_DEBUG("ahbot", "AHBot: Entry %u doesn't exists, perhaps bought already?", itr->second.AuctionId);
+ items.erase(itr++);
continue;
}
- if (itr->second.LastChecked != 0 && (now - itr->second.LastChecked) <= _checkInterval)
+ // Check if the item has been checked once before
+ // If it has been checked and it was recently, skip it
+ if (itr->second.LastChecked && (now - itr->second.LastChecked) <= _checkInterval)
{
TC_LOG_DEBUG("ahbot", "AHBot: In time interval wait for entry %u!", auction->Id);
++itr;
continue;
}
- if (buyCycles == 0)
- break;
-
- uint32 maxChance = 5000;
-
Item* item = sAuctionMgr->GetAItem(auction->itemGUIDLow);
- if (!item) // auction item not accessible, possible auction in payment pending mode
+ if (!item)
{
- config.CheckedEntry.erase(itr++);
+ // auction item not accessible, possible auction in payment pending mode
+ items.erase(itr++);
continue;
}
- ItemTemplate const* prototype = item->GetTemplate();
-
- uint32 basePrice = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYPRICE_BUYER) ? prototype->BuyPrice : prototype->SellPrice;
- basePrice *= item->GetCount();
-
- uint32 maxBuyablePrice = (basePrice * config.BuyerPriceRatio) / 100;
- BuyerItemInfoMap::iterator sameItemItr = config.SameItemInfo.find(item->GetEntry());
- uint32 buyoutPrice = auction->buyout / item->GetCount();
-
+ // price to bid if bidding
uint32 bidPrice;
- uint32 bidPriceByItem;
- uint32 minBidPrice;
- uint32 minBuyPrice;
if (auction->bid >= auction->startbid)
{
- bidPrice = auction->GetAuctionOutBid();
- bidPriceByItem = auction->bid / item->GetCount();
+ // get bid price to outbid previous bidder
+ bidPrice = auction->bid + auction->GetAuctionOutBid();
}
else
{
+ // no previous bidders - use starting bid
bidPrice = auction->startbid;
- bidPriceByItem = auction->startbid / item->GetCount();
}
- double inGameBuyPrice;
- double inGameBidPrice;
- if (sameItemItr == config.SameItemInfo.end())
- {
- inGameBuyPrice = 0;
- inGameBidPrice = 0;
- minBidPrice = 0;
- minBuyPrice = 0;
- }
- else
- {
- if (sameItemItr->second.ItemCount == 1)
- maxBuyablePrice = maxBuyablePrice * 5; // if only one item exist can be bought if the price is high too.
- inGameBuyPrice = sameItemItr->second.BuyPrice / sameItemItr->second.ItemCount;
- inGameBidPrice = sameItemItr->second.BidPrice / sameItemItr->second.ItemCount;
- minBidPrice = sameItemItr->second.MinBidPrice;
- minBuyPrice = sameItemItr->second.MinBuyPrice;
- }
+ const BuyerItemInfo* ahInfo = nullptr;
+ BuyerItemInfoMap::const_iterator sameItemItr = config.SameItemInfo.find(item->GetEntry());
+ if (sameItemItr != config.SameItemInfo.end())
+ ahInfo = &sameItemItr->second;
- uint32 maxBidablePrice = maxBuyablePrice - (maxBuyablePrice / 30); // Max Bidable price defined to 70% of max buyable price
+ TC_LOG_DEBUG("ahbot", "AHBot: Rolling for AHentry %u:", auction->Id);
- TC_LOG_DEBUG("ahbot", "AHBot: Auction added with data:");
- TC_LOG_DEBUG("ahbot", "AHBot: MaxPrice of Entry %u is %.1fg.", itr->second.AuctionId, double(maxBuyablePrice) / 10000.0);
- TC_LOG_DEBUG("ahbot", "AHBot: GamePrice buy=%.1fg, bid=%.1fg.", inGameBuyPrice / 10000, inGameBidPrice / 10000);
- TC_LOG_DEBUG("ahbot", "AHBot: Minimal price see in AH Buy=%ug, Bid=%ug.",
- minBuyPrice / 10000, minBidPrice / 10000);
- TC_LOG_DEBUG("ahbot", "AHBot: Actual Entry price, Buy=%ug, Bid=%ug.", buyoutPrice / 10000, bidPrice / 10000);
+ // Roll buy and bid chances
+ bool successBuy = RollBuyChance(ahInfo, item, auction, bidPrice);
+ bool successBid = RollBidChance(ahInfo, item, auction, bidPrice);
- if (!auction->owner) // Original auction owner
- maxChance = maxChance / 5; // if Owner is AHBot this mean player placed bid on this auction. We divide by 5 chance for AhBuyer to place bid on it. (This make more challenge than ignore entry)
- if (auction->buyout != 0) // Is the item directly buyable?
- {
- if (IsBuyableEntry(buyoutPrice, inGameBuyPrice, maxBuyablePrice, minBuyPrice, maxChance, config.FactionChance))
- {
- if (IsBidableEntry(bidPriceByItem, inGameBuyPrice, maxBidablePrice, minBidPrice, maxChance / 2, config.FactionChance))
- {
- if (urand(0, 5) == 0)
- PlaceBidToEntry(auction, bidPrice);
- else
- BuyEntry(auction);
- }
- else
- BuyEntry(auction);
- }
- else if (IsBidableEntry(bidPriceByItem, inGameBuyPrice, maxBidablePrice, minBidPrice, maxChance / 2, config.FactionChance))
- PlaceBidToEntry(auction, bidPrice);
- }
- else if (IsBidableEntry(bidPriceByItem, inGameBuyPrice, maxBidablePrice, minBidPrice, maxChance, config.FactionChance))
- PlaceBidToEntry(auction, bidPrice);
+ // If roll bidding succesfully and bid price is above buyout -> buyout
+ // If roll for buying was successful but not for bid, buyout directly
+ // If roll bidding was also successful, buy the entry with 20% chance
+ // - Better bid than buy since the item is bought by bot if no player bids after
+ // Otherwise bid if roll for bid was successful
+ if ((auction->buyout && successBid && bidPrice >= auction->buyout) ||
+ (successBuy && (!successBid || urand(1, 5) == 1)))
+ BuyEntry(auction, auctionHouse); // buyout
+ else if (successBid)
+ PlaceBidToEntry(auction, bidPrice); // bid
itr->second.LastChecked = now;
- --buyCycles;
-
+ --cycles;
++itr;
}
+
+ // Clear not needed entries
+ config.SameItemInfo.clear();
}
-bool AuctionBotBuyer::Update(AuctionHouseType houseType)
+uint32 AuctionBotBuyer::GetVendorPrice(uint32 quality)
+{
+ switch (quality)
+ {
+ case ITEM_QUALITY_POOR:
+ return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_GRAY);
+ case ITEM_QUALITY_NORMAL:
+ return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_WHITE);
+ case ITEM_QUALITY_UNCOMMON:
+ return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_GREEN);
+ case ITEM_QUALITY_RARE:
+ return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_BLUE);
+ case ITEM_QUALITY_EPIC:
+ return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_PURPLE);
+ case ITEM_QUALITY_LEGENDARY:
+ return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_ORANGE);
+ case ITEM_QUALITY_ARTIFACT:
+ return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_YELLOW);
+ default:
+ return 1 * SILVER;
+ }
+}
+
+uint32 AuctionBotBuyer::GetChanceMultiplier(uint32 quality)
{
- if (sAuctionBotConfig->GetConfigBuyerEnabled(houseType))
+ switch (quality)
{
- TC_LOG_DEBUG("ahbot", "AHBot: %s buying ...", AuctionBotConfig::GetHouseTypeName(houseType));
- if (GetBuyableEntry(_houseConfig[houseType]) > 0)
- AddNewAuctionBuyerBotBid(_houseConfig[houseType]);
- return true;
+ case ITEM_QUALITY_POOR:
+ return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_GRAY);
+ case ITEM_QUALITY_NORMAL:
+ return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_WHITE);
+ case ITEM_QUALITY_UNCOMMON:
+ return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_GREEN);
+ case ITEM_QUALITY_RARE:
+ return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_BLUE);
+ case ITEM_QUALITY_EPIC:
+ return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_PURPLE);
+ case ITEM_QUALITY_LEGENDARY:
+ return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_ORANGE);
+ case ITEM_QUALITY_ARTIFACT:
+ return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_YELLOW);
+ default:
+ return 100;
}
+}
+
+// Buys the auction and does necessary actions to complete the buyout
+void AuctionBotBuyer::BuyEntry(AuctionEntry* auction, AuctionHouseObject* auctionHouse)
+{
+ TC_LOG_DEBUG("ahbot", "AHBot: Entry %u bought at %.2fg", auction->Id, float(auction->buyout) / GOLD);
+
+ SQLTransaction trans = CharacterDatabase.BeginTransaction();
+
+ // Send mail to previous bidder if any
+ if (auction->bidder)
+ sAuctionMgr->SendAuctionOutbiddedMail(auction, auction->buyout, NULL, trans);
+
+ // Set bot as bidder and set new bid amount
+ auction->bidder = 0;
+ auction->bid = auction->buyout;
+
+ // Mails must be under transaction control too to prevent data loss
+ sAuctionMgr->SendAuctionSalePendingMail(auction, trans);
+ sAuctionMgr->SendAuctionSuccessfulMail(auction, trans);
+ sAuctionMgr->SendAuctionWonMail(auction, trans);
+
+ // Delete auction from DB
+ auction->DeleteFromDB(trans);
+
+ // Remove auction item and auction from memory
+ sAuctionMgr->RemoveAItem(auction->itemGUIDLow);
+ auctionHouse->RemoveAuction(auction);
+
+ // Run SQLs
+ CharacterDatabase.CommitTransaction(trans);
+}
+
+// Bids on the auction and does the necessary actions for bidding
+void AuctionBotBuyer::PlaceBidToEntry(AuctionEntry* auction, uint32 bidPrice)
+{
+ TC_LOG_DEBUG("ahbot", "AHBot: Bid placed to entry %u, %.2fg", auction->Id, float(bidPrice) / GOLD);
+
+ SQLTransaction trans = CharacterDatabase.BeginTransaction();
+
+ // Send mail to previous bidder if any
+ if (auction->bidder)
+ sAuctionMgr->SendAuctionOutbiddedMail(auction, bidPrice, NULL, trans);
+
+ // Set bot as bidder and set new bid amount
+ auction->bidder = 0;
+ auction->bid = bidPrice;
+
+ // Update auction to DB
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_AUCTION_BID);
+ stmt->setUInt32(0, auction->bidder);
+ stmt->setUInt32(1, auction->bid);
+ stmt->setUInt32(2, auction->Id);
+ trans->Append(stmt);
- return false;
+ // Run SQLs
+ CharacterDatabase.CommitTransaction(trans);
}
diff --git a/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.h b/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.h
index bd8d6e46f0e..c030731cb40 100644
--- a/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.h
+++ b/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.h
@@ -24,7 +24,7 @@
struct BuyerAuctionEval
{
- BuyerAuctionEval(): AuctionId(0), LastChecked(0), LastExist(0) {}
+ BuyerAuctionEval() : AuctionId(0), LastChecked(0), LastExist(0) { }
uint32 AuctionId;
time_t LastChecked;
@@ -33,13 +33,14 @@ struct BuyerAuctionEval
struct BuyerItemInfo
{
- BuyerItemInfo(): ItemCount(0), BuyPrice(0), BidPrice(0), MinBuyPrice(0), MinBidPrice(0) {}
+ BuyerItemInfo() : BidItemCount(0), BuyItemCount(0), MinBuyPrice(0), MinBidPrice(0), TotalBuyPrice(0), TotalBidPrice(0) { }
- uint32 ItemCount;
- double BuyPrice;
- double BidPrice;
+ uint32 BidItemCount;
+ uint32 BuyItemCount;
uint32 MinBuyPrice;
uint32 MinBidPrice;
+ double TotalBuyPrice;
+ double TotalBidPrice;
};
typedef std::map<uint32, BuyerItemInfo> BuyerItemInfoMap;
@@ -47,7 +48,7 @@ typedef std::map<uint32, BuyerAuctionEval> CheckEntryMap;
struct BuyerConfiguration
{
- BuyerConfiguration(): FactionChance(3), BuyerEnabled(false), BuyerPriceRatio(100), _houseType(AUCTION_HOUSE_NEUTRAL) {}
+ BuyerConfiguration() : BuyerEnabled(false), _houseType(AUCTION_HOUSE_NEUTRAL) { }
void Initialize(AuctionHouseType houseType)
{
@@ -57,10 +58,8 @@ struct BuyerConfiguration
AuctionHouseType GetHouseType() const { return _houseType; }
BuyerItemInfoMap SameItemInfo;
- CheckEntryMap CheckedEntry;
- uint32 FactionChance;
+ CheckEntryMap EligibleItems;
bool BuyerEnabled;
- uint32 BuyerPriceRatio;
private:
AuctionHouseType _houseType;
@@ -78,19 +77,23 @@ public:
bool Update(AuctionHouseType houseType) override;
void LoadConfig();
- void AddNewAuctionBuyerBotBid(BuyerConfiguration& config);
+ void BuyAndBidItems(BuyerConfiguration& config);
private:
uint32 _checkInterval;
BuyerConfiguration _houseConfig[MAX_AUCTION_HOUSE_TYPE];
void LoadBuyerValues(BuyerConfiguration& config);
- bool IsBuyableEntry(uint32 buyoutPrice, double inGameBuyPrice, uint32 maxBuyablePrice, uint32 minBuyPrice, uint32 maxChance, uint32 chanceRatio);
- bool IsBidableEntry(uint32 bidPrice, double inGameBuyPrice, double maxBidablePrice, uint32 minBidPrice, uint32 maxChance, uint32 chanceRatio);
+
+ // ahInfo can be NULL
+ bool RollBuyChance(const BuyerItemInfo* ahInfo, const Item* item, const AuctionEntry* auction, uint32 bidPrice);
+ bool RollBidChance(const BuyerItemInfo* ahInfo, const Item* item, const AuctionEntry* auction, uint32 bidPrice);
void PlaceBidToEntry(AuctionEntry* auction, uint32 bidPrice);
- void BuyEntry(AuctionEntry* auction);
+ void BuyEntry(AuctionEntry* auction, AuctionHouseObject* auctionHouse);
void PrepareListOfEntry(BuyerConfiguration& config);
- uint32 GetBuyableEntry(BuyerConfiguration& config);
+ uint32 GetItemInformation(BuyerConfiguration& config);
+ uint32 GetVendorPrice(uint32 quality);
+ uint32 GetChanceMultiplier(uint32 quality);
};
#endif
diff --git a/src/server/game/AuctionHouseBot/AuctionHouseBotSeller.cpp b/src/server/game/AuctionHouseBot/AuctionHouseBotSeller.cpp
index dff4077d569..733bd5e9ec8 100644
--- a/src/server/game/AuctionHouseBot/AuctionHouseBotSeller.cpp
+++ b/src/server/game/AuctionHouseBot/AuctionHouseBotSeller.cpp
@@ -44,14 +44,14 @@ bool AuctionBotSeller::Initialize()
{
std::stringstream includeStream(sAuctionBotConfig->GetAHBotIncludes());
std::string temp;
- while (getline(includeStream, temp, ','))
+ while (std::getline(includeStream, temp, ','))
includeItems.push_back(atoi(temp.c_str()));
}
{
std::stringstream excludeStream(sAuctionBotConfig->GetAHBotExcludes());
std::string temp;
- while (getline(excludeStream, temp, ','))
+ while (std::getline(excludeStream, temp, ','))
excludeItems.push_back(atoi(temp.c_str()));
}
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index 161fca432b4..87cff6f2c37 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -4218,7 +4218,7 @@ void Player::RemoveSpellCategoryCooldown(uint32 cat, bool update /* = false */)
void Player::RemoveArenaSpellCooldowns(bool removeActivePetCooldowns)
{
- // remove cooldowns on spells that have <= 10 min CD
+ // remove cooldowns on spells that have < 10 min CD
SpellCooldowns::iterator itr, next;
for (itr = m_spellCooldowns.begin(); itr != m_spellCooldowns.end(); itr = next)
@@ -4226,10 +4226,10 @@ void Player::RemoveArenaSpellCooldowns(bool removeActivePetCooldowns)
next = itr;
++next;
SpellInfo const* entry = sSpellMgr->GetSpellInfo(itr->first);
- // check if spellentry is present and if the cooldown is less or equal to 10 min
+ // check if spellentry is present and if the cooldown is less than 10 min
if (entry &&
- entry->RecoveryTime <= 10 * MINUTE * IN_MILLISECONDS &&
- entry->CategoryRecoveryTime <= 10 * MINUTE * IN_MILLISECONDS)
+ entry->RecoveryTime < 10 * MINUTE * IN_MILLISECONDS &&
+ entry->CategoryRecoveryTime < 10 * MINUTE * IN_MILLISECONDS)
{
// remove & notify
RemoveSpellCooldown(itr->first, true);
@@ -19086,7 +19086,7 @@ bool Player::Satisfy(AccessRequirement const* ar, uint32 target_map, bool report
else if (mapDiff->hasErrorMessage) // if (missingAchievement) covered by this case
SendTransferAborted(target_map, TRANSFER_ABORT_DIFFICULTY, target_difficulty);
else if (missingItem)
- GetSession()->SendAreaTriggerMessage(GetSession()->GetTrinityString(LANG_LEVEL_MINREQUIRED_AND_ITEM), LevelMin, sObjectMgr->GetItemTemplate(missingItem)->Name1.c_str());
+ GetSession()->SendAreaTriggerMessage(GetSession()->GetTrinityString(LANG_LEVEL_MINREQUIRED_AND_ITEM), LevelMin, ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(missingItem))->Name1.c_str());
else if (LevelMin)
GetSession()->SendAreaTriggerMessage(GetSession()->GetTrinityString(LANG_LEVEL_MINREQUIRED), LevelMin);
}
@@ -22138,6 +22138,34 @@ void Player::SendCooldownEvent(SpellInfo const* spellInfo, uint32 itemId /*= 0*/
data << uint32(spellInfo->Id);
data << uint64(GetGUID());
SendDirectMessage(&data);
+
+ uint32 cat = spellInfo->GetCategory();
+ if (cat && spellInfo->CategoryRecoveryTime)
+ {
+ SpellCategoryStore::const_iterator ct = sSpellsByCategoryStore.find(cat);
+ if (ct != sSpellsByCategoryStore.end())
+ {
+ SpellCategorySet const& catSet = ct->second;
+ for (SpellCooldowns::const_iterator i = m_spellCooldowns.begin(); i != m_spellCooldowns.end(); ++i)
+ {
+ if (i->first == spellInfo->Id) // skip main spell, already handled above
+ continue;
+
+ SpellInfo const* spellInfo2 = sSpellMgr->GetSpellInfo(i->first);
+ if (!spellInfo2 || !spellInfo2->IsCooldownStartedOnEvent())
+ continue;
+
+ if (catSet.find(i->first) != catSet.end())
+ {
+ // Send activate cooldown timer (possible 0) at client side
+ WorldPacket data(SMSG_COOLDOWN_EVENT, 4 + 8);
+ data << uint32(i->first);
+ data << uint64(GetGUID());
+ SendDirectMessage(&data);
+ }
+ }
+ }
+ }
}
void Player::UpdatePotionCooldown(Spell* spell)
@@ -25047,6 +25075,7 @@ void Player::_LoadSkills(PreparedQueryResult result)
// SetPQuery(PLAYER_LOGIN_QUERY_LOADSKILLS, "SELECT skill, value, max FROM character_skills WHERE guid = '%u'", GUID_LOPART(m_guid));
uint32 count = 0;
+ std::unordered_map<uint32, uint32> loadedSkillValues;
if (result)
{
do
@@ -25114,8 +25143,7 @@ void Player::_LoadSkills(PreparedQueryResult result)
SetUInt32Value(PLAYER_SKILL_BONUS_INDEX(count), 0);
mSkillStatus.insert(SkillStatusMap::value_type(skill, SkillStatusData(count, SKILL_UNCHANGED)));
-
- LearnSkillRewardedSpells(skill, value);
+ loadedSkillValues[skill] = value;
++count;
@@ -25128,6 +25156,10 @@ void Player::_LoadSkills(PreparedQueryResult result)
while (result->NextRow());
}
+ // Learn skill rewarded spells after all skills have been loaded to prevent learning a skill from them before its loaded with proper value from DB
+ for (auto& skill : loadedSkillValues)
+ LearnSkillRewardedSpells(skill.first, skill.second);
+
for (; count < PLAYER_MAX_SKILLS; ++count)
{
SetUInt32Value(PLAYER_SKILL_INDEX(count), 0);
diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp
index d07ec212d0c..f0d7d039dd5 100644
--- a/src/server/game/Globals/ObjectMgr.cpp
+++ b/src/server/game/Globals/ObjectMgr.cpp
@@ -7078,6 +7078,7 @@ void ObjectMgr::LoadReputationSpilloverTemplate()
continue;
}
+ bool invalidSpilloverFaction = false;
for (uint32 i = 0; i < MAX_SPILLOVER_FACTIONS; ++i)
{
if (repTemplate.faction[i])
@@ -7087,47 +7088,28 @@ void ObjectMgr::LoadReputationSpilloverTemplate()
if (!factionSpillover)
{
TC_LOG_ERROR("sql.sql", "Spillover faction (faction.dbc) %u does not exist but is used in `reputation_spillover_template` for faction %u, skipping", repTemplate.faction[i], factionId);
- continue;
+ invalidSpilloverFaction = true;
+ break;
}
if (factionSpillover->reputationListID < 0)
{
TC_LOG_ERROR("sql.sql", "Spillover faction (faction.dbc) %u for faction %u in `reputation_spillover_template` can not be listed for client, and then useless, skipping", repTemplate.faction[i], factionId);
- continue;
+ invalidSpilloverFaction = true;
+ break;
}
if (repTemplate.faction_rank[i] >= MAX_REPUTATION_RANK)
{
TC_LOG_ERROR("sql.sql", "Rank %u used in `reputation_spillover_template` for spillover faction %u is not valid, skipping", repTemplate.faction_rank[i], repTemplate.faction[i]);
- continue;
+ invalidSpilloverFaction = true;
+ break;
}
}
}
- FactionEntry const* factionEntry0 = sFactionStore.LookupEntry(repTemplate.faction[0]);
- if (repTemplate.faction[0] && !factionEntry0)
- {
- TC_LOG_ERROR("sql.sql", "Faction (faction.dbc) %u does not exist but is used in `reputation_spillover_template`", repTemplate.faction[0]);
+ if (invalidSpilloverFaction)
continue;
- }
- FactionEntry const* factionEntry1 = sFactionStore.LookupEntry(repTemplate.faction[1]);
- if (repTemplate.faction[1] && !factionEntry1)
- {
- TC_LOG_ERROR("sql.sql", "Faction (faction.dbc) %u does not exist but is used in `reputation_spillover_template`", repTemplate.faction[1]);
- continue;
- }
- FactionEntry const* factionEntry2 = sFactionStore.LookupEntry(repTemplate.faction[2]);
- if (repTemplate.faction[2] && !factionEntry2)
- {
- TC_LOG_ERROR("sql.sql", "Faction (faction.dbc) %u does not exist but is used in `reputation_spillover_template`", repTemplate.faction[2]);
- continue;
- }
- FactionEntry const* factionEntry3 = sFactionStore.LookupEntry(repTemplate.faction[3]);
- if (repTemplate.faction[3] && !factionEntry3)
- {
- TC_LOG_ERROR("sql.sql", "Faction (faction.dbc) %u does not exist but is used in `reputation_spillover_template`", repTemplate.faction[3]);
- continue;
- }
_repSpilloverTemplateStore[factionId] = repTemplate;
diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp
index d9124551c63..5664cbb6e4b 100644
--- a/src/server/game/Groups/Group.cpp
+++ b/src/server/game/Groups/Group.cpp
@@ -1808,7 +1808,7 @@ GroupJoinBattlegroundResult Group::CanJoinBattlegroundQueue(Battleground const*
return ERR_BATTLEGROUND_NONE; // ERR_GROUP_JOIN_BATTLEGROUND_TOO_MANY handled on client side
// get a player as reference, to compare other players' stats to (arena team id, queue id based on level, etc.)
- Player* reference = GetFirstMember()->GetSource();
+ Player* reference = ASSERT_NOTNULL(GetFirstMember())->GetSource();
// no reference found, can't join this way
if (!reference)
return ERR_BATTLEGROUND_JOIN_FAILED;
diff --git a/src/server/game/Handlers/QuestHandler.cpp b/src/server/game/Handlers/QuestHandler.cpp
index d6d7e3b9876..b6157d6eb94 100644
--- a/src/server/game/Handlers/QuestHandler.cpp
+++ b/src/server/game/Handlers/QuestHandler.cpp
@@ -458,6 +458,12 @@ void WorldSession::HandleQuestConfirmAccept(WorldPacket& recvData)
if (!_player->IsInSameRaidWith(originalPlayer))
return;
+ if (!originalPlayer->CanShareQuest(questId))
+ return;
+
+ if (!_player->CanTakeQuest(quest, true))
+ return;
+
if (_player->CanAddQuest(quest, true))
_player->AddQuestAndCheckCompletion(quest, NULL); // NULL, this prevent DB script from duplicate running
diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h
index 0775a9a299a..685bcd338a9 100644
--- a/src/server/game/Miscellaneous/SharedDefines.h
+++ b/src/server/game/Miscellaneous/SharedDefines.h
@@ -427,7 +427,7 @@ enum SpellAttr4
SPELL_ATTR4_UNK4 = 0x00000010, // 4 This will no longer cause guards to attack on use??
SPELL_ATTR4_UNK5 = 0x00000020, // 5
SPELL_ATTR4_NOT_STEALABLE = 0x00000040, // 6 although such auras might be dispellable, they cannot be stolen
- SPELL_ATTR4_TRIGGERED = 0x00000080, // 7 spells forced to be triggered
+ SPELL_ATTR4_CAN_CAST_WHILE_CASTING = 0x00000080, // 7 Can be cast while another cast is in progress - see CanCastWhileCasting(SpellRec const*,CGUnit_C *,int &)
SPELL_ATTR4_FIXED_DAMAGE = 0x00000100, // 8 Ignores resilience and any (except mechanic related) damage or % damage taken auras on target.
SPELL_ATTR4_TRIGGER_ACTIVATE = 0x00000200, // 9 initially disabled / trigger activate from event (Execute, Riposte, Deep Freeze end other)
SPELL_ATTR4_SPELL_VS_EXTEND_COST = 0x00000400, // 10 Rogue Shiv have this flag
diff --git a/src/server/game/Reputation/ReputationMgr.cpp b/src/server/game/Reputation/ReputationMgr.cpp
index ad71381a1de..b99cb677ba0 100644
--- a/src/server/game/Reputation/ReputationMgr.cpp
+++ b/src/server/game/Reputation/ReputationMgr.cpp
@@ -297,7 +297,7 @@ bool ReputationMgr::SetReputation(FactionEntry const* factionEntry, int32 standi
{
// bonuses are already given, so just modify standing by rate
int32 spilloverRep = int32(standing * repTemplate->faction_rate[i]);
- SetOneFactionReputation(sFactionStore.LookupEntry(repTemplate->faction[i]), spilloverRep, incremental);
+ SetOneFactionReputation(sFactionStore.AssertEntry(repTemplate->faction[i]), spilloverRep, incremental);
}
}
}
diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp
index 8e415b06745..8f8295d57d1 100644
--- a/src/server/game/Spells/Spell.cpp
+++ b/src/server/game/Spells/Spell.cpp
@@ -574,8 +574,8 @@ m_caster((info->AttributesEx6 & SPELL_ATTR6_CAST_BY_CHARMER && caster->GetCharme
m_spellState = SPELL_STATE_NULL;
_triggeredCastFlags = triggerFlags;
- if (info->AttributesEx4 & SPELL_ATTR4_TRIGGERED)
- _triggeredCastFlags = TriggerCastFlags(TRIGGERED_FULL_MASK & ~TRIGGERED_IGNORE_EQUIPPED_ITEM_REQUIREMENT);
+ if (info->AttributesEx4 & SPELL_ATTR4_CAN_CAST_WHILE_CASTING)
+ _triggeredCastFlags = TriggerCastFlags(uint32(_triggeredCastFlags) | TRIGGERED_IGNORE_CAST_IN_PROGRESS | TRIGGERED_CAST_DIRECTLY);
m_CastItem = NULL;
m_castItemGUID.Clear();
diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp
index db16a7312ea..dd4453cc4c5 100644
--- a/src/server/game/Spells/SpellMgr.cpp
+++ b/src/server/game/Spells/SpellMgr.cpp
@@ -2959,6 +2959,8 @@ void SpellMgr::LoadSpellInfoCorrections()
switch (spellInfo->Id)
{
case 53096: // Quetz'lun's Judgment
+ case 70743: // AoD Special
+ case 70614: // AoD Special - Vegard
spellInfo->MaxAffectedTargets = 1;
break;
case 42436: // Drink! (Brewfest)
@@ -3729,7 +3731,7 @@ void SpellMgr::LoadSpellInfoCorrections()
case 45440: // Steam Tonk Controller
case 60256: // Collect Sample
// Crashes client on pressing ESC
- spellInfo->AttributesEx4 &= ~SPELL_ATTR4_TRIGGERED;
+ spellInfo->AttributesEx4 &= ~SPELL_ATTR4_CAN_CAST_WHILE_CASTING;
break;
// ISLE OF CONQUEST SPELLS
//
diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp
index 5d6cdb3fb63..ccd82aa3ef9 100644
--- a/src/server/scripts/Commands/cs_debug.cpp
+++ b/src/server/scripts/Commands/cs_debug.cpp
@@ -299,7 +299,7 @@ public:
else if (commentToken[1] == '/')
{
std::string str;
- getline(ifs, str);
+ std::getline(ifs, str);
continue;
}
// regular data
diff --git a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp
index 3db0e1092fd..326360428d2 100644
--- a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp
+++ b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp
@@ -174,7 +174,7 @@ public:
anchor->GetContactPoint(me, anchorX, anchorY, z, 1.0f);
playerGUID = target->GetGUID();
- Talk(SAY_EVENT_START);
+ Talk(SAY_EVENT_START, target);
}
void UpdateAI(uint32 diff) override
diff --git a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_muru.cpp b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_muru.cpp
index ea56494f0f9..421f3d771a0 100644
--- a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_muru.cpp
+++ b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_muru.cpp
@@ -207,7 +207,7 @@ public:
void DamageTaken(Unit* /*done_by*/, uint32 &damage) override
{
- if (damage > me->GetHealth() && events.IsInPhase(PHASE_ONE))
+ if (damage >= me->GetHealth() && events.IsInPhase(PHASE_ONE))
{
damage = 0;
me->RemoveAllAuras();
diff --git a/src/server/scripts/EasternKingdoms/zone_redridge_mountains.cpp b/src/server/scripts/EasternKingdoms/zone_redridge_mountains.cpp
index 898e3f9a2cb..ecfd705cf4d 100644
--- a/src/server/scripts/EasternKingdoms/zone_redridge_mountains.cpp
+++ b/src/server/scripts/EasternKingdoms/zone_redridge_mountains.cpp
@@ -92,7 +92,7 @@ public:
me->SetWalk(false);
break;
case 115:
- player->AreaExploredOrEventHappens(QUEST_MISSING_IN_ACTION);
+ player->GroupEventHappens(QUEST_MISSING_IN_ACTION, me);
timer = 2000;
phase = 4;
break;
diff --git a/src/server/scripts/Kalimdor/zone_bloodmyst_isle.cpp b/src/server/scripts/Kalimdor/zone_bloodmyst_isle.cpp
index 390fd3e529f..ca5e4697c35 100644
--- a/src/server/scripts/Kalimdor/zone_bloodmyst_isle.cpp
+++ b/src/server/scripts/Kalimdor/zone_bloodmyst_isle.cpp
@@ -29,6 +29,7 @@ EndContentData */
#include "ScriptMgr.h"
#include "ScriptedCreature.h"
+#include "PassiveAI.h"
#include "Player.h"
/*######
@@ -48,14 +49,18 @@ class npc_webbed_creature : public CreatureScript
public:
npc_webbed_creature() : CreatureScript("npc_webbed_creature") { }
- struct npc_webbed_creatureAI : public ScriptedAI
+ struct npc_webbed_creatureAI : public NullCreatureAI
{
- npc_webbed_creatureAI(Creature* creature) : ScriptedAI(creature) { }
+ npc_webbed_creatureAI(Creature* creature) : NullCreatureAI(creature) { }
void Reset() override { }
void EnterCombat(Unit* /*who*/) override { }
+ void AttackStart(Unit* /*who*/) override { }
+
+ void MoveInLineOfSight(Unit* /*who*/) override { }
+
void JustDied(Unit* killer) override
{
uint32 spawnCreatureID = 0;
diff --git a/src/server/scripts/Kalimdor/zone_mulgore.cpp b/src/server/scripts/Kalimdor/zone_mulgore.cpp
deleted file mode 100644
index 0d0ef218885..00000000000
--- a/src/server/scripts/Kalimdor/zone_mulgore.cpp
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
- * 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, see <http://www.gnu.org/licenses/>.
- */
-
-/* ScriptData
-SDName: Mulgore
-SD%Complete: 100
-SDComment: Support for quest: 11129, 772
-SDCategory: Mulgore
-EndScriptData */
-
-/* ContentData
-npc_kyle_frenzied
-npc_plains_vision
-EndContentData */
-
-#include "ScriptMgr.h"
-#include "ScriptedCreature.h"
-#include "ScriptedGossip.h"
-#include "Player.h"
-#include "SpellInfo.h"
-
-/*#####
-# npc_kyle_frenzied
-######*/
-
-enum KyleFrenzied
-{
- EMOTE_SEE_LUNCH = 0,
- EMOTE_EAT_LUNCH = 1,
- EMOTE_DANCE = 2,
-
- SPELL_LUNCH = 42222,
- NPC_KYLE_FRENZIED = 23616,
- NPC_KYLE_FRIENDLY = 23622,
- POINT_ID = 1
-};
-
-class npc_kyle_frenzied : public CreatureScript
-{
-public:
- npc_kyle_frenzied() : CreatureScript("npc_kyle_frenzied") { }
-
- CreatureAI* GetAI(Creature* creature) const override
- {
- return new npc_kyle_frenziedAI(creature);
- }
-
- struct npc_kyle_frenziedAI : public ScriptedAI
- {
- npc_kyle_frenziedAI(Creature* creature) : ScriptedAI(creature)
- {
- Initialize();
- }
-
- void Initialize()
- {
- EventActive = false;
- IsMovingToLunch = false;
- PlayerGUID.Clear();
- EventTimer = 5000;
- EventPhase = 0;
- }
-
- bool EventActive;
- bool IsMovingToLunch;
- ObjectGuid PlayerGUID;
- uint32 EventTimer;
- uint8 EventPhase;
-
- void Reset() override
- {
- Initialize();
-
- if (me->GetEntry() == NPC_KYLE_FRIENDLY)
- me->UpdateEntry(NPC_KYLE_FRENZIED);
- }
-
- void SpellHit(Unit* Caster, SpellInfo const* Spell) override
- {
- if (!me->GetVictim() && !EventActive && Spell->Id == SPELL_LUNCH)
- {
- if (Caster->GetTypeId() == TYPEID_PLAYER)
- PlayerGUID = Caster->GetGUID();
-
- if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE)
- {
- me->GetMotionMaster()->MovementExpired();
- me->GetMotionMaster()->MoveIdle();
- me->StopMoving();
- }
-
- EventActive = true;
- Talk(EMOTE_SEE_LUNCH);
- me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_CREATURE_SPECIAL);
- }
- }
-
- void MovementInform(uint32 Type, uint32 PointId) override
- {
- if (Type != POINT_MOTION_TYPE || !EventActive)
- return;
-
- if (PointId == POINT_ID)
- IsMovingToLunch = false;
- }
-
- void UpdateAI(uint32 diff) override
- {
- if (EventActive)
- {
- if (IsMovingToLunch)
- return;
-
- if (EventTimer <= diff)
- {
- EventTimer = 5000;
- ++EventPhase;
-
- switch (EventPhase)
- {
- case 1:
- if (Unit* unit = ObjectAccessor::GetUnit(*me, PlayerGUID))
- {
- if (GameObject* go = unit->GetGameObject(SPELL_LUNCH))
- {
- IsMovingToLunch = true;
- me->GetMotionMaster()->MovePoint(POINT_ID, go->GetPositionX(), go->GetPositionY(), go->GetPositionZ());
- }
- }
- break;
- case 2:
- Talk(EMOTE_EAT_LUNCH);
- me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_USE_STANDING);
- break;
- case 3:
- if (Player* unit = ObjectAccessor::GetPlayer(*me, PlayerGUID))
- unit->TalkedToCreature(me->GetEntry(), me->GetGUID());
-
- me->UpdateEntry(NPC_KYLE_FRIENDLY);
- break;
- case 4:
- EventTimer = 30000;
- Talk(EMOTE_DANCE);
- me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_DANCESPECIAL);
- break;
- case 5:
- me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_NONE);
- Reset();
- me->GetMotionMaster()->Clear();
- break;
- }
- }
- else
- EventTimer -= diff;
- }
- }
- };
-
-};
-
-/*#####
-# npc_plains_vision
-######*/
-
-Position const wpPlainVision[50] =
-{
- {-2226.32f, -408.095f, -9.36235f, 0.0f},
- {-2203.04f, -437.212f, -5.72498f, 0.0f},
- {-2163.91f, -457.851f, -7.09049f, 0.0f},
- {-2123.87f, -448.137f, -9.29591f, 0.0f},
- {-2104.66f, -427.166f, -6.49513f, 0.0f},
- {-2101.48f, -422.826f, -5.3567f, 0.0f},
- {-2097.56f, -417.083f, -7.16716f, 0.0f},
- {-2084.87f, -398.626f, -9.88973f, 0.0f},
- {-2072.71f, -382.324f, -10.2488f, 0.0f},
- {-2054.05f, -356.728f, -6.22468f, 0.0f},
- {-2051.8f, -353.645f, -5.35791f, 0.0f},
- {-2049.08f, -349.912f, -6.15723f, 0.0f},
- {-2030.6f, -310.724f, -9.59302f, 0.0f},
- {-2002.15f, -249.308f, -10.8124f, 0.0f},
- {-1972.85f, -195.811f, -10.6316f, 0.0f},
- {-1940.93f, -147.652f, -11.7055f, 0.0f},
- {-1888.06f, -81.943f, -11.4404f, 0.0f},
- {-1837.05f, -34.0109f, -12.258f, 0.0f},
- {-1796.12f, -14.6462f, -10.3581f, 0.0f},
- {-1732.61f, -4.27746f, -10.0213f, 0.0f},
- {-1688.94f, -0.829945f, -11.7103f, 0.0f},
- {-1681.32f, 13.0313f, -9.48056f, 0.0f},
- {-1677.04f, 36.8349f, -7.10318f, 0.0f},
- {-1675.2f, 68.559f, -8.95384f, 0.0f},
- {-1676.57f, 89.023f, -9.65104f, 0.0f},
- {-1678.16f, 110.939f, -10.1782f, 0.0f},
- {-1677.86f, 128.681f, -5.73869f, 0.0f},
- {-1675.27f, 144.324f, -3.47916f, 0.0f},
- {-1671.7f, 163.169f, -1.23098f, 0.0f},
- {-1666.61f, 181.584f, 5.26145f, 0.0f},
- {-1661.51f, 196.154f, 8.95252f, 0.0f},
- {-1655.47f, 210.811f, 8.38727f, 0.0f},
- {-1647.07f, 226.947f, 5.27755f, 0.0f},
- {-1621.65f, 232.91f, 2.69579f, 0.0f},
- {-1600.23f, 237.641f, 2.98539f, 0.0f},
- {-1576.07f, 242.546f, 4.66541f, 0.0f},
- {-1554.57f, 248.494f, 6.60377f, 0.0f},
- {-1547.53f, 259.302f, 10.6741f, 0.0f},
- {-1541.7f, 269.847f, 16.4418f, 0.0f},
- {-1539.83f, 278.989f, 21.0597f, 0.0f},
- {-1540.16f, 290.219f, 27.8247f, 0.0f},
- {-1538.99f, 298.983f, 34.0032f, 0.0f},
- {-1540.38f, 307.337f, 41.3557f, 0.0f},
- {-1536.61f, 314.884f, 48.0179f, 0.0f},
- {-1532.42f, 323.277f, 55.6667f, 0.0f},
- {-1528.77f, 329.774f, 61.1525f, 0.0f},
- {-1525.65f, 333.18f, 63.2161f, 0.0f},
- {-1517.01f, 350.713f, 62.4286f, 0.0f},
- {-1511.39f, 362.537f, 62.4539f, 0.0f},
- {-1508.68f, 366.822f, 62.733f, 0.0f}
-};
-
-class npc_plains_vision : public CreatureScript
-{
-public:
- npc_plains_vision() : CreatureScript("npc_plains_vision") { }
-
- CreatureAI* GetAI(Creature* creature) const override
- {
- return new npc_plains_visionAI(creature);
- }
-
- struct npc_plains_visionAI : public ScriptedAI
- {
- npc_plains_visionAI(Creature* creature) : ScriptedAI(creature)
- {
- Initialize();
- }
-
- void Initialize()
- {
- WayPointId = 0;
- newWaypoint = true;
- amountWP = 49;
- }
-
- bool newWaypoint;
- uint8 WayPointId;
- uint8 amountWP;
-
- void Reset() override
- {
- Initialize();
- }
-
- void EnterCombat(Unit* /*who*/) override { }
-
- void MovementInform(uint32 type, uint32 id) override
- {
- if (type != POINT_MOTION_TYPE)
- return;
-
- if (id < amountWP)
- {
- ++WayPointId;
- newWaypoint = true;
- }
- else
- {
- me->setDeathState(JUST_DIED);
- me->RemoveCorpse();
- }
- }
-
- void UpdateAI(uint32 /*diff*/) override
- {
- if (newWaypoint)
- {
- me->GetMotionMaster()->MovePoint(WayPointId, wpPlainVision[WayPointId]);
- newWaypoint = false;
- }
- }
- };
-
-};
-
-/*#####
-#
-######*/
-
-void AddSC_mulgore()
-{
- new npc_kyle_frenzied();
- new npc_plains_vision();
-}
diff --git a/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/boss_bronjahm.cpp b/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/boss_bronjahm.cpp
index 97c4372f0e6..9797ace2f8d 100644
--- a/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/boss_bronjahm.cpp
+++ b/src/server/scripts/Northrend/FrozenHalls/ForgeOfSouls/boss_bronjahm.cpp
@@ -82,10 +82,10 @@ class boss_bronjahm : public CreatureScript
instance->SetBossState(DATA_BRONJAHM, NOT_STARTED);
}
- void JustReachedHome() override
- {
- DoCast(me, SPELL_SOULSTORM_CHANNEL, true);
- }
+ void JustReachedHome() override
+ {
+ DoCast(me, SPELL_SOULSTORM_CHANNEL, true);
+ }
void EnterCombat(Unit* /*who*/) override
{
diff --git a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp
index 7b80db7dd39..9edde447f32 100644
--- a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp
+++ b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp
@@ -21,6 +21,7 @@
#include "SpellScript.h"
#include "Transport.h"
#include "Player.h"
+#include "MoveSplineInit.h"
#include "halls_of_reflection.h"
enum Text
@@ -342,6 +343,20 @@ class npc_jaina_or_sylvanas_intro_hor : public CreatureScript
public:
npc_jaina_or_sylvanas_intro_hor() : CreatureScript("npc_jaina_or_sylvanas_intro_hor") { }
+ bool OnGossipHello(Player* player, Creature* creature) override
+ {
+ // override default gossip
+ if (InstanceScript* instance = creature->GetInstanceScript())
+ if (instance->GetData(DATA_QUEL_DELAR_EVENT) == IN_PROGRESS || instance->GetData(DATA_QUEL_DELAR_EVENT) == SPECIAL)
+ {
+ player->PlayerTalkClass->ClearMenus();
+ return true;
+ }
+
+ // load default gossip
+ return false;
+ }
+
struct npc_jaina_or_sylvanas_intro_horAI : public ScriptedAI
{
npc_jaina_or_sylvanas_intro_horAI(Creature* creature) : ScriptedAI(creature)
@@ -1998,6 +2013,13 @@ class at_hor_intro_start : public AreaTriggerScript
if (_instance->GetData(DATA_INTRO_EVENT) == NOT_STARTED)
_instance->SetData(DATA_INTRO_EVENT, IN_PROGRESS);
+ if (player->HasAura(SPELL_QUEL_DELAR_COMPULSION) && (player->GetQuestStatus(QUEST_HALLS_OF_REFLECTION_ALLIANCE) == QUEST_STATUS_INCOMPLETE ||
+ player->GetQuestStatus(QUEST_HALLS_OF_REFLECTION_HORDE) == QUEST_STATUS_INCOMPLETE) && _instance->GetData(DATA_QUEL_DELAR_EVENT) == NOT_STARTED)
+ {
+ _instance->SetData(DATA_QUEL_DELAR_EVENT, IN_PROGRESS);
+ _instance->SetGuidData(DATA_QUEL_DELAR_INVOKER, player->GetGUID());
+ }
+
return true;
}
};
@@ -2330,6 +2352,395 @@ class npc_lumbering_abomination : public CreatureScript
}
};
+enum QuelDelarUther
+{
+ ACTION_UTHER_START_SCREAM = 1,
+ ACTION_UTHER_OUTRO = 2,
+
+ EVENT_UTHER_1 = 1,
+ EVENT_UTHER_2 = 2,
+ EVENT_UTHER_3 = 3,
+ EVENT_UTHER_4 = 4,
+ EVENT_UTHER_5 = 5,
+ EVENT_UTHER_6 = 6,
+ EVENT_UTHER_7 = 7,
+ EVENT_UTHER_8 = 8,
+ EVENT_UTHER_9 = 9,
+ EVENT_UTHER_10 = 10,
+ EVENT_UTHER_11 = 11,
+ EVENT_UTHER_FACING = 12,
+ EVENT_UTHER_KNEEL = 13,
+
+ SAY_UTHER_QUEL_DELAR_1 = 16,
+ SAY_UTHER_QUEL_DELAR_2 = 17,
+ SAY_UTHER_QUEL_DELAR_3 = 18,
+ SAY_UTHER_QUEL_DELAR_4 = 19,
+ SAY_UTHER_QUEL_DELAR_5 = 20,
+ SAY_UTHER_QUEL_DELAR_6 = 21,
+
+ SPELL_ESSENCE_OF_CAPTURED_1 = 73036
+};
+
+enum QuelDelarSword
+{
+ SPELL_WHIRLWIND_VISUAL = 70300,
+ SPELL_HEROIC_STRIKE = 29426,
+ SPELL_WHIRLWIND = 67716,
+ SPELL_BLADESTORM = 67541,
+
+ NPC_QUEL_DELAR = 37158,
+ POINT_TAKE_OFF = 1,
+
+ EVENT_QUEL_DELAR_INIT = 1,
+ EVENT_QUEL_DELAR_FLIGHT_INIT = 2,
+ EVENT_QUEL_DELAR_FLIGHT = 3,
+ EVENT_QUEL_DELAR_LAND = 4,
+ EVENT_QUEL_DELAR_FIGHT = 5,
+ EVENT_QUEL_DELAR_BLADESTORM = 6,
+ EVENT_QUEL_DELAR_HEROIC_STRIKE = 7,
+ EVENT_QUEL_DELAR_WHIRLWIND = 8,
+
+ SAY_QUEL_DELAR_SWORD = 0
+};
+
+enum QuelDelarMisc
+{
+ SAY_FROSTMOURNE_BUNNY = 0,
+ SPELL_QUEL_DELAR_WILL = 70698
+};
+
+Position const QuelDelarCenterPos = { 5309.259f, 2006.390f, 718.046f, 0.0f };
+Position const QuelDelarSummonPos = { 5298.473f, 1994.852f, 709.424f, 3.979351f };
+Position const QuelDelarMovement[] =
+{
+ { 5292.870f, 1998.950f, 718.046f, 0.0f },
+ { 5295.819f, 1991.912f, 707.707f, 0.0f },
+ { 5295.301f, 1989.782f, 708.696f, 0.0f }
+};
+
+Position const UtherQuelDelarMovement[] =
+{
+ { 5336.830f, 1981.700f, 709.319f, 0.0f },
+ { 5314.350f, 1993.440f, 707.726f, 0.0f }
+};
+
+class npc_uther_quel_delar : public CreatureScript
+{
+ public:
+ npc_uther_quel_delar() : CreatureScript("npc_uther_quel_delar") { }
+
+ struct npc_uther_quel_delarAI : public ScriptedAI
+ {
+ npc_uther_quel_delarAI(Creature* creature) : ScriptedAI(creature)
+ {
+ _instance = me->GetInstanceScript();
+ }
+
+ void Reset() override
+ {
+ // Prevent to break Uther in intro event during instance encounter
+ if (_instance->GetData(DATA_QUEL_DELAR_EVENT) != IN_PROGRESS && _instance->GetData(DATA_QUEL_DELAR_EVENT) != SPECIAL)
+ return;
+
+ _events.Reset();
+ _events.ScheduleEvent(EVENT_UTHER_1, 1);
+ }
+
+ void DamageTaken(Unit* /*attacker*/, uint32& damage) override
+ {
+ if (damage >= me->GetHealth())
+ damage = me->GetHealth() - 1;
+ }
+
+ void DoAction(int32 action) override
+ {
+ switch (action)
+ {
+ case ACTION_UTHER_START_SCREAM:
+ _instance->SetData(DATA_QUEL_DELAR_EVENT, SPECIAL);
+ _events.ScheduleEvent(EVENT_UTHER_2, 0);
+ break;
+ case ACTION_UTHER_OUTRO:
+ _events.ScheduleEvent(EVENT_UTHER_6, 0);
+ break;
+ default:
+ break;
+ }
+ }
+
+ void MovementInform(uint32 /*type*/, uint32 pointId) override
+ {
+ switch (pointId)
+ {
+ case 1:
+ _events.ScheduleEvent(EVENT_UTHER_FACING, 1000);
+ break;
+ default:
+ break;
+ }
+ }
+
+ void UpdateAI(uint32 diff) override
+ {
+ // Prevent to break Uther in intro event during instance encounter
+ if (_instance->GetData(DATA_QUEL_DELAR_EVENT) != IN_PROGRESS && _instance->GetData(DATA_QUEL_DELAR_EVENT) != SPECIAL)
+ return;
+
+ _events.Update(diff);
+
+ while (uint32 eventId = _events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_UTHER_1:
+ Talk(SAY_UTHER_QUEL_DELAR_1);
+ break;
+ case EVENT_UTHER_2:
+ if (Creature* bunny = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_FROSTMOURNE_ALTAR_BUNNY)))
+ if (Unit* target = ObjectAccessor::GetPlayer(*me, _instance->GetGuidData(DATA_QUEL_DELAR_INVOKER)))
+ bunny->CastSpell(target, SPELL_QUEL_DELAR_WILL, true);
+ _events.ScheduleEvent(EVENT_UTHER_3, 2000);
+ break;
+ case EVENT_UTHER_3:
+ me->SummonCreature(NPC_QUEL_DELAR, QuelDelarSummonPos);
+ _events.ScheduleEvent(EVENT_UTHER_4, 2000);
+ break;
+ case EVENT_UTHER_4:
+ Talk(SAY_UTHER_QUEL_DELAR_2);
+ _events.ScheduleEvent(EVENT_UTHER_5, 8000);
+ break;
+ case EVENT_UTHER_5:
+ me->GetMotionMaster()->MovePoint(1, UtherQuelDelarMovement[0]);
+ break;
+ case EVENT_UTHER_6:
+ me->SetWalk(true);
+ me->GetMotionMaster()->MovePoint(0, UtherQuelDelarMovement[1]);
+ _events.ScheduleEvent(EVENT_UTHER_7, 5000);
+ break;
+ case EVENT_UTHER_7:
+ Talk(SAY_UTHER_QUEL_DELAR_3);
+ _events.ScheduleEvent(EVENT_UTHER_8, 12000);
+ break;
+ case EVENT_UTHER_8:
+ Talk(SAY_UTHER_QUEL_DELAR_4);
+ _events.ScheduleEvent(EVENT_UTHER_9, 7000);
+ break;
+ case EVENT_UTHER_9:
+ Talk(SAY_UTHER_QUEL_DELAR_5);
+ _events.ScheduleEvent(EVENT_UTHER_10, 10000);
+ break;
+ case EVENT_UTHER_10:
+ Talk(SAY_UTHER_QUEL_DELAR_6);
+ _events.ScheduleEvent(EVENT_UTHER_11, 5000);
+ break;
+ case EVENT_UTHER_11:
+ DoCast(me, SPELL_ESSENCE_OF_CAPTURED_1, true);
+ me->DespawnOrUnsummon(3000);
+ _instance->SetData(DATA_QUEL_DELAR_EVENT, DONE);
+ break;
+ case EVENT_UTHER_FACING:
+ if (Creature* bunny = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_FROSTMOURNE_ALTAR_BUNNY)))
+ me->SetFacingToObject(bunny);
+ _events.ScheduleEvent(EVENT_UTHER_KNEEL, 1000);
+ break;
+ case EVENT_UTHER_KNEEL:
+ me->HandleEmoteCommand(EMOTE_STATE_KNEEL);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private:
+ EventMap _events;
+ InstanceScript* _instance;
+ };
+
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetHallsOfReflectionAI<npc_uther_quel_delarAI>(creature);
+ }
+};
+
+class npc_quel_delar_sword : public CreatureScript
+{
+ public:
+ npc_quel_delar_sword() : CreatureScript("npc_quel_delar_sword") { }
+
+ struct npc_quel_delar_swordAI : public ScriptedAI
+ {
+ npc_quel_delar_swordAI(Creature* creature) : ScriptedAI(creature)
+ {
+ _instance = me->GetInstanceScript();
+ me->SetDisplayId(me->GetCreatureTemplate()->Modelid2);
+ _intro = true;
+ }
+
+ void Reset() override
+ {
+ _events.Reset();
+ me->SetSpeed(MOVE_FLIGHT, 4.5f, true);
+ DoCast(SPELL_WHIRLWIND_VISUAL);
+ if (_intro)
+ _events.ScheduleEvent(EVENT_QUEL_DELAR_INIT, 0);
+ else
+ me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC);
+ }
+
+ void EnterCombat(Unit* /*victim*/) override
+ {
+ _events.ScheduleEvent(EVENT_QUEL_DELAR_HEROIC_STRIKE, 4000);
+ _events.ScheduleEvent(EVENT_QUEL_DELAR_BLADESTORM, 6000);
+ _events.ScheduleEvent(EVENT_QUEL_DELAR_WHIRLWIND, 6000);
+ }
+
+ void JustDied(Unit* /*killer*/) override
+ {
+ if (Creature* uther = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_UTHER_QUEL_DELAR)))
+ uther->AI()->DoAction(ACTION_UTHER_OUTRO);
+ }
+
+ void MovementInform(uint32 type, uint32 pointId) override
+ {
+ if (type != EFFECT_MOTION_TYPE)
+ return;
+
+ switch (pointId)
+ {
+ case POINT_TAKE_OFF:
+ _events.ScheduleEvent(EVENT_QUEL_DELAR_FLIGHT, 0);
+ break;
+ default:
+ break;
+ }
+ }
+
+ void UpdateAI(uint32 diff) override
+ {
+ _events.Update(diff);
+
+ if (me->HasUnitState(UNIT_STATE_CASTING))
+ return;
+
+ if (!UpdateVictim())
+ {
+ while (uint32 eventId = _events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_QUEL_DELAR_INIT:
+ if (Creature* bunny = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_FROSTMOURNE_ALTAR_BUNNY)))
+ bunny->AI()->Talk(SAY_FROSTMOURNE_BUNNY);
+ _intro = false;
+ _events.ScheduleEvent(EVENT_QUEL_DELAR_FLIGHT_INIT, 2500);
+ break;
+ case EVENT_QUEL_DELAR_FLIGHT_INIT:
+ me->GetMotionMaster()->MoveTakeoff(POINT_TAKE_OFF, QuelDelarMovement[0]);
+ break;
+ case EVENT_QUEL_DELAR_FLIGHT:
+ {
+ Movement::MoveSplineInit init(me);
+ FillCirclePath(QuelDelarCenterPos, 18.0f, 718.046f, init.Path(), true);
+ init.SetFly();
+ init.SetCyclic();
+ init.SetAnimation(Movement::ToFly);
+ init.Launch();
+
+ _events.ScheduleEvent(EVENT_QUEL_DELAR_LAND, 15000);
+ break;
+ }
+ case EVENT_QUEL_DELAR_LAND:
+ me->StopMoving();
+ me->GetMotionMaster()->Clear();
+ me->GetMotionMaster()->MoveLand(0, QuelDelarMovement[1]);
+ _events.ScheduleEvent(EVENT_QUEL_DELAR_FIGHT, 6000);
+ break;
+ case EVENT_QUEL_DELAR_FIGHT:
+ Talk(SAY_QUEL_DELAR_SWORD);
+ me->GetMotionMaster()->MovePoint(0, QuelDelarMovement[2]);
+ me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ else
+ {
+ while (uint32 eventId = _events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_QUEL_DELAR_BLADESTORM:
+ DoCast(me, SPELL_BLADESTORM);
+ _events.ScheduleEvent(EVENT_QUEL_DELAR_BLADESTORM, 10000);
+ break;
+ case EVENT_QUEL_DELAR_HEROIC_STRIKE:
+ DoCastVictim(SPELL_HEROIC_STRIKE);
+ _events.ScheduleEvent(EVENT_QUEL_DELAR_HEROIC_STRIKE, 6000);
+ break;
+ case EVENT_QUEL_DELAR_WHIRLWIND:
+ DoCastAOE(SPELL_WHIRLWIND);
+ _events.ScheduleEvent(EVENT_QUEL_DELAR_WHIRLWIND, 1000);
+ break;
+ default:
+ break;
+ }
+ }
+
+ DoMeleeAttackIfReady();
+ }
+ }
+
+ private:
+ void FillCirclePath(Position const& centerPos, float radius, float z, Movement::PointsArray& path, bool clockwise)
+ {
+ float step = clockwise ? -M_PI / 8.0f : M_PI / 8.0f;
+ float angle = centerPos.GetAngle(me->GetPositionX(), me->GetPositionY());
+
+ for (uint8 i = 0; i < 16; angle += step, ++i)
+ {
+ G3D::Vector3 point;
+ point.x = centerPos.GetPositionX() + radius * cosf(angle);
+ point.y = centerPos.GetPositionY() + radius * sinf(angle);
+ point.z = z;
+ path.push_back(point);
+ }
+ }
+
+ EventMap _events;
+ InstanceScript* _instance;
+ bool _intro;
+ };
+
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetHallsOfReflectionAI<npc_quel_delar_swordAI>(creature);
+ }
+};
+
+// 5660
+class at_hor_uther_quel_delar_start : public AreaTriggerScript
+{
+ public:
+ at_hor_uther_quel_delar_start() : AreaTriggerScript("at_hor_uther_quel_delar_start") { }
+
+ bool OnTrigger(Player* player, AreaTriggerEntry const* /*trigger*/) override
+ {
+ if (player->IsGameMaster())
+ return true;
+
+ InstanceScript* _instance = player->GetInstanceScript();
+
+ if (_instance->GetData(DATA_QUEL_DELAR_EVENT) == IN_PROGRESS)
+ if (Creature* uther = ObjectAccessor::GetCreature(*player, _instance->GetGuidData(DATA_UTHER_QUEL_DELAR)))
+ uther->AI()->DoAction(ACTION_UTHER_START_SCREAM);
+
+ return true;
+ }
+};
+
// 72900 - Start Halls of Reflection Quest AE
class spell_hor_start_halls_of_reflection_quest_ae : public SpellScriptLoader
{
@@ -2447,6 +2858,7 @@ void AddSC_halls_of_reflection()
new at_hor_waves_restarter();
new at_hor_impenetrable_door();
new at_hor_shadow_throne();
+ new at_hor_uther_quel_delar_start();
new npc_jaina_or_sylvanas_intro_hor();
new npc_jaina_or_sylvanas_escape_hor();
new npc_the_lich_king_escape_hor();
@@ -2461,6 +2873,8 @@ void AddSC_halls_of_reflection()
new npc_raging_ghoul();
new npc_risen_witch_doctor();
new npc_lumbering_abomination();
+ new npc_uther_quel_delar();
+ new npc_quel_delar_sword();
new spell_hor_start_halls_of_reflection_quest_ae();
new spell_hor_evasion();
new spell_hor_gunship_cannon_fire();
diff --git a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.h b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.h
index 9594617fa9a..d2f9ab5d262 100644
--- a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.h
+++ b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.h
@@ -46,7 +46,13 @@ enum DataTypes
DATA_ESCAPE_LEADER = 10,
DATA_ICEWALL = 11,
DATA_ICEWALL_TARGET = 12,
- DATA_GUNSHIP = 13
+ DATA_GUNSHIP = 13,
+
+ // Quest stuff
+ DATA_QUEL_DELAR_EVENT = 14,
+ DATA_FROSTMOURNE_ALTAR_BUNNY = 15,
+ DATA_UTHER_QUEL_DELAR = 16,
+ DATA_QUEL_DELAR_INVOKER = 17
};
enum CreatureIds
@@ -131,7 +137,8 @@ enum InstanceEvents
EVENT_NEXT_WAVE = 2,
EVENT_DO_WIPE = 3,
EVENT_ADD_WAVE = 4,
- EVENT_SPAWN_ESCAPE_EVENT = 5
+ EVENT_SPAWN_ESCAPE_EVENT = 5,
+ EVENT_QUEL_DELAR_SUMMON_UTHER = 6
};
enum InstanceEventIds
@@ -160,7 +167,17 @@ enum InstanceSpells
// Gunship
SPELL_GUNSHIP_CANNON_FIRE = 70017,
SPELL_GUNSHIP_CANNON_FIRE_MISSILE_ALLIANCE = 70021,
- SPELL_GUNSHIP_CANNON_FIRE_MISSILE_HORDE = 70246
+ SPELL_GUNSHIP_CANNON_FIRE_MISSILE_HORDE = 70246,
+
+ // Halls of Reflection quest
+ SPELL_QUEL_DELAR_COMPULSION = 70013,
+ SPELL_ESSENCE_OF_CAPTURED = 70720
+};
+
+enum InstanceQuests
+{
+ QUEST_HALLS_OF_REFLECTION_ALLIANCE = 24480,
+ QUEST_HALLS_OF_REFLECTION_HORDE = 24561
};
enum InstanceWorldStates
diff --git a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/instance_halls_of_reflection.cpp b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/instance_halls_of_reflection.cpp
index ea2a1539f1c..660a639487f 100644
--- a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/instance_halls_of_reflection.cpp
+++ b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/instance_halls_of_reflection.cpp
@@ -73,6 +73,8 @@ Position const SpawnPos[] =
{ 5299.250f, 2035.998f, 707.7781f, 5.026548f }
};
+Position const UtherQuelDalarPos = { 5302.001f, 1988.698f, 707.7781f, 3.700098f };
+
class instance_halls_of_reflection : public InstanceMapScript
{
public:
@@ -89,6 +91,7 @@ class instance_halls_of_reflection : public InstanceMapScript
_waveCount = 0;
_introState = NOT_STARTED;
_frostswornGeneralState = NOT_STARTED;
+ _quelDelarState = NOT_STARTED;
events.Reset();
}
@@ -159,6 +162,9 @@ class instance_halls_of_reflection : public InstanceMapScript
case NPC_ICE_WALL_TARGET:
IcewallTargetGUID = creature->GetGUID();
break;
+ case NPC_UTHER:
+ UtherGUID = creature->GetGUID();
+ break;
default:
break;
}
@@ -439,6 +445,18 @@ class instance_halls_of_reflection : public InstanceMapScript
HandleGameObject(ShadowThroneDoorGUID, true);
_frostswornGeneralState = data;
break;
+ case DATA_QUEL_DELAR_EVENT:
+ if (data == IN_PROGRESS)
+ {
+ if (_quelDelarState == NOT_STARTED)
+ {
+ if (Creature* bunny = instance->GetCreature(FrostmourneAltarBunnyGUID))
+ bunny->CastSpell((Unit*)NULL, SPELL_ESSENCE_OF_CAPTURED);
+ events.ScheduleEvent(EVENT_QUEL_DELAR_SUMMON_UTHER, 2000);
+ }
+ }
+ _quelDelarState = data;
+ break;
default:
break;
}
@@ -446,6 +464,18 @@ class instance_halls_of_reflection : public InstanceMapScript
SaveToDB();
}
+ void SetGuidData(uint32 type, ObjectGuid data) override
+ {
+ switch (type)
+ {
+ case DATA_QUEL_DELAR_INVOKER:
+ QuelDelarInvokerGUID = data;
+ break;
+ default:
+ break;
+ }
+ }
+
// wave scheduling, checked when wave npcs die
void OnUnitDeath(Unit* unit) override
{
@@ -491,6 +521,9 @@ class instance_halls_of_reflection : public InstanceMapScript
case EVENT_SPAWN_ESCAPE_EVENT:
SpawnEscapeEvent();
break;
+ case EVENT_QUEL_DELAR_SUMMON_UTHER:
+ instance->SummonCreature(NPC_UTHER, UtherQuelDalarPos);
+ break;
}
}
@@ -649,6 +682,8 @@ class instance_halls_of_reflection : public InstanceMapScript
return _introState;
case DATA_FROSTSWORN_GENERAL:
return _frostswornGeneralState;
+ case DATA_QUEL_DELAR_EVENT:
+ return _quelDelarState;
default:
break;
}
@@ -682,6 +717,12 @@ class instance_halls_of_reflection : public InstanceMapScript
return IcewallGUID;
case DATA_ICEWALL_TARGET:
return IcewallTargetGUID;
+ case DATA_FROSTMOURNE_ALTAR_BUNNY:
+ return FrostmourneAltarBunnyGUID;
+ case DATA_UTHER_QUEL_DELAR:
+ return UtherGUID;
+ case DATA_QUEL_DELAR_INVOKER:
+ return QuelDelarInvokerGUID;
default:
break;
}
@@ -691,7 +732,7 @@ class instance_halls_of_reflection : public InstanceMapScript
void WriteSaveDataMore(std::ostringstream& data) override
{
- data << _introState << ' ' << _frostswornGeneralState;
+ data << _introState << ' ' << _frostswornGeneralState << ' ' << _quelDelarState;
}
void ReadSaveDataMore(std::istringstream& data) override
@@ -708,6 +749,12 @@ class instance_halls_of_reflection : public InstanceMapScript
SetData(DATA_FROSTSWORN_GENERAL, DONE);
else
SetData(DATA_FROSTSWORN_GENERAL, NOT_STARTED);
+
+ data >> temp;
+ if (temp == DONE)
+ SetData(DATA_QUEL_DELAR_EVENT, DONE);
+ else
+ SetData(DATA_QUEL_DELAR_EVENT, NOT_STARTED);
}
private:
@@ -731,6 +778,7 @@ class instance_halls_of_reflection : public InstanceMapScript
uint32 _waveCount;
uint32 _introState;
uint32 _frostswornGeneralState;
+ uint32 _quelDelarState;
EventMap events;
GuidSet waveGuidList[8];
@@ -740,6 +788,8 @@ class instance_halls_of_reflection : public InstanceMapScript
ObjectGuid CaptainGUID;
ObjectGuid IcewallGUID;
ObjectGuid IcewallTargetGUID;
+ ObjectGuid QuelDelarInvokerGUID;
+ ObjectGuid UtherGUID;
GuidSet GunshipCannonGUIDs;
GuidSet GunshipStairGUIDs;
diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp
index 4c6fd0f2fcc..63082808e03 100644
--- a/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp
+++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp
@@ -1259,7 +1259,7 @@ class spell_putricide_mutated_plague : public SpellScriptLoader
return;
uint32 triggerSpell = GetSpellInfo()->Effects[aurEff->GetEffIndex()].TriggerSpell;
- SpellInfo const* spell = sSpellMgr->GetSpellInfo(triggerSpell);
+ SpellInfo const* spell = sSpellMgr->EnsureSpellInfo(triggerSpell);
spell = sSpellMgr->GetSpellForDifficultyFromSpell(spell, caster);
int32 damage = spell->Effects[EFFECT_0].CalcValue(caster);
diff --git a/src/server/scripts/Northrend/zone_zuldrak.cpp b/src/server/scripts/Northrend/zone_zuldrak.cpp
index f73930181f0..91c796a6e69 100644
--- a/src/server/scripts/Northrend/zone_zuldrak.cpp
+++ b/src/server/scripts/Northrend/zone_zuldrak.cpp
@@ -313,7 +313,7 @@ public:
{
player->KilledMonsterCredit(gymerDummy->GetEntry(), gymerDummy->GetGUID());
gymerDummy->CastSpell(gymerDummy, SPELL_GYMER_LOCK_EXPLOSION, true);
- gymerDummy->DespawnOrUnsummon();
+ gymerDummy->DespawnOrUnsummon(4 * IN_MILLISECONDS);
}
}
return true;
diff --git a/src/server/shared/DataStores/DBCStore.h b/src/server/shared/DataStores/DBCStore.h
index 10d4ff1bec9..1cb67a4235e 100644
--- a/src/server/shared/DataStores/DBCStore.h
+++ b/src/server/shared/DataStores/DBCStore.h
@@ -87,6 +87,13 @@ class DBCStorage
return (id >= nCount) ? NULL : indexTable.asT[id];
}
+ T const* AssertEntry(uint32 id) const
+ {
+ T const* entry = LookupEntry(id);
+ ASSERT(entry);
+ return entry;
+ }
+
uint32 GetNumRows() const { return nCount; }
char const* GetFormat() const { return fmt; }
uint32 GetFieldCount() const { return fieldCount; }
diff --git a/src/server/shared/Database/DatabaseWorkerPool.h b/src/server/shared/Database/DatabaseWorkerPool.h
index 5a06ad69a1d..f0ddbe91ad8 100644
--- a/src/server/shared/Database/DatabaseWorkerPool.h
+++ b/src/server/shared/Database/DatabaseWorkerPool.h
@@ -29,6 +29,8 @@
#include "QueryHolder.h"
#include "AdhocStatement.h"
+#include <mysqld_error.h>
+
#define MIN_MYSQL_SERVER_VERSION 50100u
#define MIN_MYSQL_CLIENT_VERSION 50100u
@@ -368,7 +370,8 @@ class DatabaseWorkerPool
void DirectCommitTransaction(SQLTransaction& transaction)
{
T* con = GetFreeConnection();
- if (con->ExecuteTransaction(transaction))
+ int errorCode = con->ExecuteTransaction(transaction);
+ if (!errorCode)
{
con->Unlock(); // OK, operation succesful
return;
@@ -376,12 +379,12 @@ class DatabaseWorkerPool
//! Handle MySQL Errno 1213 without extending deadlock to the core itself
/// @todo More elegant way
- if (con->GetLastError() == 1213)
+ if (errorCode == ER_LOCK_DEADLOCK)
{
uint8 loopBreaker = 5;
for (uint8 i = 0; i < loopBreaker; ++i)
{
- if (con->ExecuteTransaction(transaction))
+ if (!con->ExecuteTransaction(transaction))
break;
}
}
diff --git a/src/server/shared/Database/MySQLConnection.cpp b/src/server/shared/Database/MySQLConnection.cpp
index bea229df184..1a9f973d47b 100644
--- a/src/server/shared/Database/MySQLConnection.cpp
+++ b/src/server/shared/Database/MySQLConnection.cpp
@@ -359,11 +359,11 @@ void MySQLConnection::CommitTransaction()
Execute("COMMIT");
}
-bool MySQLConnection::ExecuteTransaction(SQLTransaction& transaction)
+int MySQLConnection::ExecuteTransaction(SQLTransaction& transaction)
{
std::list<SQLElementData> const& queries = transaction->m_queries;
if (queries.empty())
- return false;
+ return -1;
BeginTransaction();
@@ -380,8 +380,9 @@ bool MySQLConnection::ExecuteTransaction(SQLTransaction& transaction)
if (!Execute(stmt))
{
TC_LOG_WARN("sql.sql", "Transaction aborted. %u queries not executed.", (uint32)queries.size());
+ int errorCode = GetLastError();
RollbackTransaction();
- return false;
+ return errorCode;
}
}
break;
@@ -392,8 +393,9 @@ bool MySQLConnection::ExecuteTransaction(SQLTransaction& transaction)
if (!Execute(sql))
{
TC_LOG_WARN("sql.sql", "Transaction aborted. %u queries not executed.", (uint32)queries.size());
+ int errorCode = GetLastError();
RollbackTransaction();
- return false;
+ return errorCode;
}
}
break;
@@ -406,7 +408,7 @@ bool MySQLConnection::ExecuteTransaction(SQLTransaction& transaction)
// and not while iterating over every element.
CommitTransaction();
- return true;
+ return 0;
}
MySQLPreparedStatement* MySQLConnection::GetPreparedStatement(uint32 index)
diff --git a/src/server/shared/Database/MySQLConnection.h b/src/server/shared/Database/MySQLConnection.h
index 33f17d02228..d486f5b4679 100644
--- a/src/server/shared/Database/MySQLConnection.h
+++ b/src/server/shared/Database/MySQLConnection.h
@@ -86,7 +86,7 @@ class MySQLConnection
void BeginTransaction();
void RollbackTransaction();
void CommitTransaction();
- bool ExecuteTransaction(SQLTransaction& transaction);
+ int ExecuteTransaction(SQLTransaction& transaction);
operator bool () const { return m_Mysql != NULL; }
void Ping() { mysql_ping(m_Mysql); }
diff --git a/src/server/shared/Database/Transaction.cpp b/src/server/shared/Database/Transaction.cpp
index 3dee865267b..9f36d198bde 100644
--- a/src/server/shared/Database/Transaction.cpp
+++ b/src/server/shared/Database/Transaction.cpp
@@ -17,6 +17,9 @@
#include "DatabaseEnv.h"
#include "Transaction.h"
+#include <mysqld_error.h>
+
+std::mutex TransactionTask::_deadlockLock;
//- Append a raw ad-hoc query to the transaction
void Transaction::Append(const char* sql)
@@ -74,14 +77,17 @@ void Transaction::Cleanup()
bool TransactionTask::Execute()
{
- if (m_conn->ExecuteTransaction(m_trans))
+ int errorCode = m_conn->ExecuteTransaction(m_trans);
+ if (!errorCode)
return true;
- if (m_conn->GetLastError() == 1213)
+ if (errorCode == ER_LOCK_DEADLOCK)
{
+ // Make sure only 1 async thread retries a transaction so they don't keep dead-locking each other
+ std::lock_guard<std::mutex> lock(_deadlockLock);
uint8 loopBreaker = 5; // Handle MySQL Errno 1213 without extending deadlock to the core itself
for (uint8 i = 0; i < loopBreaker; ++i)
- if (m_conn->ExecuteTransaction(m_trans))
+ if (!m_conn->ExecuteTransaction(m_trans))
return true;
}
diff --git a/src/server/shared/Database/Transaction.h b/src/server/shared/Database/Transaction.h
index cf6aa98b386..83d59006ddc 100644
--- a/src/server/shared/Database/Transaction.h
+++ b/src/server/shared/Database/Transaction.h
@@ -66,6 +66,7 @@ class TransactionTask : public SQLOperation
bool Execute() override;
SQLTransaction m_trans;
+ static std::mutex _deadlockLock;
};
#endif
diff --git a/src/server/shared/Debugging/Errors.h b/src/server/shared/Debugging/Errors.h
index df770c2aa47..4d4624b63dd 100644
--- a/src/server/shared/Debugging/Errors.h
+++ b/src/server/shared/Debugging/Errors.h
@@ -49,4 +49,10 @@ namespace Trinity
#define ASSERT WPAssert
+template <typename T> inline T* ASSERT_NOTNULL(T* pointer)
+{
+ ASSERT(pointer);
+ return pointer;
+}
+
#endif
diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist
index eaef902addc..010c73779bc 100644
--- a/src/server/worldserver/worldserver.conf.dist
+++ b/src/server/worldserver/worldserver.conf.dist
@@ -3029,33 +3029,54 @@ AuctionHouseBot.Buyer.Horde.Enabled = 0
AuctionHouseBot.Buyer.Neutral.Enabled = 0
#
-# AuctionHouseBot.BuyPrice.Buyer
-# Description: Should the Buyer use BuyPrice or SellPrice to determine Bid Prices
-# Default: 1 - (use BuyPrice)
-# 0 - (use SellPrice)
-
-AuctionHouseBot.Buyer.Buyprice = 1
+# AuctionHouseBot.Buyer.Baseprice.QUALITY
+# Description: Base sellprices in copper for non priced items for each quality.
+# The default values are based on average item prices of each quality.
+# Defaults: Gray 3504
+# White 5429
+# Green 21752
+# Blue 36463
+# Purple 87124
+# Orange 214347
+# Yellow 407406
+
+AuctionHouseBot.Buyer.Baseprice.Gray = 3504
+AuctionHouseBot.Buyer.Baseprice.White = 5429
+AuctionHouseBot.Buyer.Baseprice.Green = 21752
+AuctionHouseBot.Buyer.Baseprice.Blue = 36463
+AuctionHouseBot.Buyer.Baseprice.Purple = 87124
+AuctionHouseBot.Buyer.Baseprice.Orange = 214347
+AuctionHouseBot.Buyer.Baseprice.Yellow = 407406
+
+#
+# AuctionHouseBot.Buyer.ChanceMultiplier.QUALITY
+# Description: Multipliers for the buy/bid chances for each quality. 100 means the chance is 100% of the original,
+# 1 would mean 1 % of the original and 200 would mean 200% of the original chance.
+# Defaults: Gray 100
+# White 100
+# Green 100
+# Blue 100
+# Purple 100
+# Orange 100
+# Yellow 100
+
+AuctionHouseBot.Buyer.ChanceMultiplier.Gray = 100
+AuctionHouseBot.Buyer.ChanceMultiplier.White = 100
+AuctionHouseBot.Buyer.ChanceMultiplier.Green = 100
+AuctionHouseBot.Buyer.ChanceMultiplier.Blue = 100
+AuctionHouseBot.Buyer.ChanceMultiplier.Purple = 100
+AuctionHouseBot.Buyer.ChanceMultiplier.Orange = 100
+AuctionHouseBot.Buyer.ChanceMultiplier.Yellow = 100
#
# AuctionHouseBot.Buyer.Recheck.Interval
-# Description: This specifies the time interval (in minutes) between two evaluations of the same selled item.
-# The lesser this value is, the more chances you give for item to be bought by ahbot.
+# Description: This specifies the time interval (in minutes) between two evaluations of the same sold item.
+# The smaller this value is, the more chances you give for an item to be bought by ahbot.
# Default: 20 (20min.)
AuctionHouseBot.Buyer.Recheck.Interval = 20
#
-# AuctionHouseBot.Buyer.Alliance.Chance.Ratio
-# Description: Chance ratio for the buyer to buy an item. Higher the value, lesser the probability
-# Example: 3 (1 out of 3 change, that is, 33%).
-# Default: 3
-#
-
-AuctionHouseBot.Buyer.Alliance.Chance.Ratio = 3
-AuctionHouseBot.Buyer.Horde.Chance.Ratio = 3
-AuctionHouseBot.Buyer.Neutral.Chance.Ratio = 3
-
-#
###################################################################################################
###################################################################################################