mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-16 07:30:42 +01:00
Core/Loot: Implemented currency loot
This commit is contained in:
@@ -3016,6 +3016,7 @@ DROP TABLE IF EXISTS `item_loot_items`;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `item_loot_items` (
|
||||
`container_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'guid of container (item_instance.guid)',
|
||||
`item_type` tinyint NOT NULL DEFAULT '0' COMMENT 'item or currency',
|
||||
`item_id` int unsigned NOT NULL DEFAULT '0' COMMENT 'loot item entry (item_instance.itemEntry)',
|
||||
`item_count` int NOT NULL DEFAULT '0' COMMENT 'stack size',
|
||||
`item_index` int unsigned NOT NULL DEFAULT '0',
|
||||
@@ -3028,7 +3029,7 @@ CREATE TABLE `item_loot_items` (
|
||||
`rnd_bonus` int unsigned NOT NULL DEFAULT '0' COMMENT 'random bonus list added when originally rolled',
|
||||
`context` tinyint unsigned NOT NULL DEFAULT '0',
|
||||
`bonus_list_ids` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Space separated list of bonus list ids',
|
||||
PRIMARY KEY (`container_id`,`item_id`)
|
||||
PRIMARY KEY (`container_id`,`item_type`,`item_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
@@ -3744,7 +3745,8 @@ INSERT INTO `updates` VALUES
|
||||
('2024_05_11_00_characters.sql','A65765D87C1BA181561A6517040DC1A3A8103B71','ARCHIVED','2024-05-11 03:06:52',0),
|
||||
('2024_07_31_00_characters.sql','F7E7AE0B8077CB9A1EA0AE4F49693BB05A742AC3','RELEASED','2024-07-31 16:18:36',0),
|
||||
('2024_08_04_00_characters.sql','7D153C59998416E6EA1455086730A2321AD0F2A8','RELEASED','2024-08-04 17:58:59',0),
|
||||
('2024_08_05_00_characters.sql','7E4AE28F9EC370A1B22DBD8DD718EE027A321F33','RELEASED','2024-08-05 11:19:40',0);
|
||||
('2024_08_05_00_characters.sql','7E4AE28F9EC370A1B22DBD8DD718EE027A321F33','RELEASED','2024-08-05 11:19:40',0),
|
||||
('2024_08_26_00_characters.sql','68EEBE1D639D59B24F5121008C2D103CA67FFC9A','RELEASED','2024-08-26 00:49:08',0);
|
||||
/*!40000 ALTER TABLE `updates` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE `item_loot_items` ADD `item_type` tinyint NOT NULL DEFAULT 0 COMMENT 'item or currency' AFTER `container_id`;
|
||||
ALTER TABLE `item_loot_items` DROP PRIMARY KEY;
|
||||
ALTER TABLE `item_loot_items` ADD PRIMARY KEY (`container_id`,`item_type`,`item_id`);
|
||||
3
sql/updates/world/master/2024_08_26_00_world.sql
Normal file
3
sql/updates/world/master/2024_08_26_00_world.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
DELETE FROM `trinity_string` WHERE `entry`=296;
|
||||
INSERT INTO `trinity_string` (`entry`,`content_default`,`content_loc1`,`content_loc2`,`content_loc3`,`content_loc4`,`content_loc5`,`content_loc6`,`content_loc7`,`content_loc8`) VALUES
|
||||
(296,'├%.*s %dx |c%08x|Hcurrency:%d:%d|h[%s]|h|r (#%05d)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
|
||||
@@ -691,10 +691,10 @@ void CharacterDatabaseConnection::DoPrepareStatements()
|
||||
PrepareStatement(CHAR_DEL_CHAR_CUF_PROFILES, "DELETE FROM character_cuf_profiles WHERE guid = ?", CONNECTION_ASYNC);
|
||||
|
||||
// Items that hold loot or money
|
||||
PrepareStatement(CHAR_SEL_ITEMCONTAINER_ITEMS, "SELECT container_id, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_bonus, context, bonus_list_ids FROM item_loot_items", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_SEL_ITEMCONTAINER_ITEMS, "SELECT container_id, item_type, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_bonus, context, bonus_list_ids FROM item_loot_items", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_DEL_ITEMCONTAINER_ITEMS, "DELETE FROM item_loot_items WHERE container_id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_ITEMCONTAINER_ITEM, "DELETE FROM item_loot_items WHERE container_id = ? AND item_id = ? AND item_count = ? AND item_index = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_INS_ITEMCONTAINER_ITEMS, "INSERT INTO item_loot_items (container_id, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_bonus, context, bonus_list_ids) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_ITEMCONTAINER_ITEM, "DELETE FROM item_loot_items WHERE container_id = ? AND item_type = ? AND item_id = ? AND item_count = ? AND item_index = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_INS_ITEMCONTAINER_ITEMS, "INSERT INTO item_loot_items (container_id, item_type, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_bonus, context, bonus_list_ids) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_ITEMCONTAINER_MONEY, "SELECT container_id, money FROM item_loot_money", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_DEL_ITEMCONTAINER_MONEY, "DELETE FROM item_loot_money WHERE container_id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_INS_ITEMCONTAINER_MONEY, "INSERT INTO item_loot_money (container_id, money) VALUES (?, ?)", CONNECTION_ASYNC);
|
||||
|
||||
@@ -16960,6 +16960,44 @@ QuestObjective const* Player::GetQuestObjectiveForItem(uint32 itemId, bool onlyI
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Player::HasQuestForCurrency(uint32 currencyId) const
|
||||
{
|
||||
auto isCompletableObjective = [this](QuestObjectiveStatusData const& objectiveStatus)
|
||||
{
|
||||
Quest const* qInfo = sObjectMgr->GetQuestTemplate(objectiveStatus.QuestStatusItr->first);
|
||||
QuestObjective const* objective = sObjectMgr->GetQuestObjective(objectiveStatus.ObjectiveId);
|
||||
if (!qInfo || !objective || !IsQuestObjectiveCompletable(objectiveStatus.QuestStatusItr->second.Slot, qInfo, *objective))
|
||||
return false;
|
||||
|
||||
// hide quest if player is in raid-group and quest is no raid quest
|
||||
if (GetGroup() && GetGroup()->isRaidGroup() && !qInfo->IsAllowedInRaid(GetMap()->GetDifficultyID()))
|
||||
if (!InBattleground()) //there are two ways.. we can make every bg-quest a raidquest, or add this code here.. i don't know if this can be exploited by other quests, but i think all other quests depend on a specific area.. but keep this in mind, if something strange happens later
|
||||
return false;
|
||||
|
||||
if (!IsQuestObjectiveComplete(objectiveStatus.QuestStatusItr->second.Slot, qInfo, *objective))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
auto hasObjectiveTypeForCurrency = [&](QuestObjectiveType type)
|
||||
{
|
||||
return std::ranges::any_of(Trinity::Containers::MapEqualRange(m_questObjectiveStatus, { type, currencyId }),
|
||||
isCompletableObjective, &QuestObjectiveStatusMap::value_type::second);
|
||||
};
|
||||
|
||||
if (hasObjectiveTypeForCurrency(QUEST_OBJECTIVE_CURRENCY))
|
||||
return true;
|
||||
|
||||
if (hasObjectiveTypeForCurrency(QUEST_OBJECTIVE_HAVE_CURRENCY))
|
||||
return true;
|
||||
|
||||
if (hasObjectiveTypeForCurrency(QUEST_OBJECTIVE_OBTAIN_CURRENCY))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 Player::GetQuestObjectiveData(QuestObjective const& objective) const
|
||||
{
|
||||
uint16 slot = FindQuestSlot(objective.QuestID);
|
||||
@@ -26601,53 +26639,65 @@ void Player::StoreLootItem(ObjectGuid lootWorldObjectGuid, uint8 lootSlot, Loot*
|
||||
return;
|
||||
}
|
||||
|
||||
ItemPosCountVec dest;
|
||||
InventoryResult msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, item->itemid, item->count);
|
||||
if (msg == EQUIP_ERR_OK)
|
||||
switch (item->type)
|
||||
{
|
||||
Item* newitem = StoreNewItem(dest, item->itemid, true, item->randomBonusListId, item->GetAllowedLooters(), item->context);
|
||||
|
||||
if (ffaItem)
|
||||
case LootItemType::Item:
|
||||
{
|
||||
//freeforall case, notify only one player of the removal
|
||||
ffaItem->is_looted = true;
|
||||
SendNotifyLootItemRemoved(loot->GetGUID(), loot->GetOwnerGUID(), lootSlot);
|
||||
ItemPosCountVec dest;
|
||||
InventoryResult msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, item->itemid, item->count);
|
||||
if (msg != EQUIP_ERR_OK)
|
||||
{
|
||||
SendEquipError(msg, nullptr, nullptr, item->itemid);
|
||||
return;
|
||||
}
|
||||
|
||||
Item* newitem = StoreNewItem(dest, item->itemid, true, item->randomBonusListId, item->GetAllowedLooters(), item->context);
|
||||
|
||||
if (newitem && (newitem->GetQuality() > ITEM_QUALITY_EPIC || (newitem->GetQuality() == ITEM_QUALITY_EPIC && newitem->GetItemLevel(this) >= MinNewsItemLevel)))
|
||||
if (Guild* guild = GetGuild())
|
||||
guild->AddGuildNews(GUILD_NEWS_ITEM_LOOTED, GetGUID(), 0, item->itemid);
|
||||
|
||||
// if aeLooting then we must delay sending out item so that it appears properly stacked in chat
|
||||
if (!aeResult || !newitem)
|
||||
{
|
||||
SendNewItem(newitem, uint32(item->count), false, false, true, loot->GetDungeonEncounterId());
|
||||
UpdateCriteria(CriteriaType::LootItem, item->itemid, item->count);
|
||||
UpdateCriteria(CriteriaType::GetLootByType, item->itemid, item->count, GetLootTypeForClient(loot->loot_type));
|
||||
UpdateCriteria(CriteriaType::LootAnyItem, item->itemid, item->count);
|
||||
}
|
||||
else
|
||||
aeResult->Add(newitem, item->count, GetLootTypeForClient(loot->loot_type), loot->GetDungeonEncounterId());
|
||||
|
||||
if (newitem)
|
||||
ApplyItemLootedSpell(newitem, true);
|
||||
else
|
||||
ApplyItemLootedSpell(sObjectMgr->GetItemTemplate(item->itemid));
|
||||
|
||||
break;
|
||||
}
|
||||
else //not freeforall, notify everyone
|
||||
loot->NotifyItemRemoved(lootSlot, GetMap());
|
||||
|
||||
//if only one person is supposed to loot the item, then set it to looted
|
||||
if (!item->freeforall)
|
||||
item->is_looted = true;
|
||||
|
||||
--loot->unlootedCount;
|
||||
|
||||
if (newitem && (newitem->GetQuality() > ITEM_QUALITY_EPIC || (newitem->GetQuality() == ITEM_QUALITY_EPIC && newitem->GetItemLevel(this) >= MinNewsItemLevel)))
|
||||
if (Guild* guild = GetGuild())
|
||||
guild->AddGuildNews(GUILD_NEWS_ITEM_LOOTED, GetGUID(), 0, item->itemid);
|
||||
|
||||
// if aeLooting then we must delay sending out item so that it appears properly stacked in chat
|
||||
if (!aeResult || !newitem)
|
||||
{
|
||||
SendNewItem(newitem, uint32(item->count), false, false, true, loot->GetDungeonEncounterId());
|
||||
UpdateCriteria(CriteriaType::LootItem, item->itemid, item->count);
|
||||
UpdateCriteria(CriteriaType::GetLootByType, item->itemid, item->count, GetLootTypeForClient(loot->loot_type));
|
||||
UpdateCriteria(CriteriaType::LootAnyItem, item->itemid, item->count);
|
||||
}
|
||||
else
|
||||
aeResult->Add(newitem, item->count, GetLootTypeForClient(loot->loot_type), loot->GetDungeonEncounterId());
|
||||
|
||||
// LootItem is being removed (looted) from the container, delete it from the DB.
|
||||
if (loot->loot_type == LOOT_ITEM)
|
||||
sLootItemStorage->RemoveStoredLootItemForContainer(lootWorldObjectGuid.GetCounter(), item->itemid, item->count, item->LootListId);
|
||||
|
||||
if (newitem)
|
||||
ApplyItemLootedSpell(newitem, true);
|
||||
else
|
||||
ApplyItemLootedSpell(sObjectMgr->GetItemTemplate(item->itemid));
|
||||
case LootItemType::Currency:
|
||||
ModifyCurrency(item->itemid, item->count, CurrencyGainSource::Loot);
|
||||
break;
|
||||
}
|
||||
else
|
||||
SendEquipError(msg, nullptr, nullptr, item->itemid);
|
||||
|
||||
if (ffaItem)
|
||||
{
|
||||
//freeforall case, notify only one player of the removal
|
||||
ffaItem->is_looted = true;
|
||||
SendNotifyLootItemRemoved(loot->GetGUID(), loot->GetOwnerGUID(), lootSlot);
|
||||
}
|
||||
else //not freeforall, notify everyone
|
||||
loot->NotifyItemRemoved(lootSlot, GetMap());
|
||||
|
||||
//if only one person is supposed to loot the item, then set it to looted
|
||||
if (!item->freeforall)
|
||||
item->is_looted = true;
|
||||
|
||||
--loot->unlootedCount;
|
||||
|
||||
// LootItem is being removed (looted) from the container, delete it from the DB.
|
||||
if (loot->loot_type == LOOT_ITEM)
|
||||
sLootItemStorage->RemoveStoredLootItemForContainer(lootWorldObjectGuid.GetCounter(), item->type, item->itemid, item->count, item->LootListId);
|
||||
}
|
||||
|
||||
void Player::_LoadSkills(PreparedQueryResult result)
|
||||
|
||||
@@ -1726,6 +1726,7 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
|
||||
bool HasQuestForItem(uint32 itemId) const;
|
||||
QuestObjective const* GetQuestObjectiveForItem(uint32 itemId, bool onlyIncomplete) const;
|
||||
bool HasQuestForGO(int32 goId) const;
|
||||
bool HasQuestForCurrency(uint32 currencyId) const;
|
||||
void UpdateVisibleObjectInteractions(bool allUnits, bool onlySpellClicks, bool gameObjectQuestGiverStatus, bool questObjectiveGameObjects);
|
||||
bool CanShareQuest(uint32 questId) const;
|
||||
|
||||
|
||||
@@ -441,12 +441,18 @@ void WorldSession::HandleLootMasterGiveOpcode(WorldPackets::Loot::MasterLootItem
|
||||
|
||||
if (req.LootListID >= loot->items.size())
|
||||
{
|
||||
_player->SendLootError(req.Object, loot->GetOwnerGUID(), LOOT_ERROR_MASTER_OTHER);
|
||||
TC_LOG_DEBUG("loot", "MasterLootItem: Player {} might be using a hack! (slot {}, size {})",
|
||||
GetPlayer()->GetName(), req.LootListID, loot->items.size());
|
||||
return;
|
||||
}
|
||||
|
||||
LootItem& item = loot->items[req.LootListID];
|
||||
if (item.type != LootItemType::Item)
|
||||
{
|
||||
_player->SendLootError(req.Object, loot->GetOwnerGUID(), LOOT_ERROR_MASTER_OTHER);
|
||||
return;
|
||||
}
|
||||
|
||||
ItemPosCountVec dest;
|
||||
InventoryResult msg = target->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, item.itemid, item.count);
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
|
||||
#include "Loot.h"
|
||||
#include "Containers.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "DB2Stores.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "GameTime.h"
|
||||
#include "Group.h"
|
||||
#include "Item.h"
|
||||
@@ -40,26 +40,26 @@
|
||||
//
|
||||
|
||||
// Constructor, copies most fields from LootStoreItem and generates random count
|
||||
LootItem::LootItem(LootStoreItem const& li)
|
||||
LootItem::LootItem(LootStoreItem const& li) : itemid(li.itemid), conditions(li.conditions), needs_quest(li.needs_quest)
|
||||
{
|
||||
itemid = li.itemid;
|
||||
LootListId = 0;
|
||||
conditions = li.conditions;
|
||||
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemid);
|
||||
freeforall = proto && proto->HasFlag(ITEM_FLAG_MULTI_DROP);
|
||||
follow_loot_rules = !li.needs_quest || (proto && proto->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES));
|
||||
|
||||
needs_quest = li.needs_quest;
|
||||
|
||||
randomBonusListId = GenerateItemRandomBonusListId(itemid);
|
||||
context = ItemContext::NONE;
|
||||
count = 0;
|
||||
is_looted = false;
|
||||
is_blocked = false;
|
||||
is_underthreshold = false;
|
||||
is_counted = false;
|
||||
rollWinnerGUID = ObjectGuid::Empty;
|
||||
switch (li.type)
|
||||
{
|
||||
case LootStoreItem::Type::Item:
|
||||
{
|
||||
randomBonusListId = GenerateItemRandomBonusListId(itemid);
|
||||
type = LootItemType::Item;
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemid);
|
||||
freeforall = proto && proto->HasFlag(ITEM_FLAG_MULTI_DROP);
|
||||
follow_loot_rules = !li.needs_quest || (proto && proto->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES));
|
||||
break;
|
||||
}
|
||||
case LootStoreItem::Type::Currency:
|
||||
type = LootItemType::Currency;
|
||||
freeforall = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LootItem::LootItem(LootItem const&) = default;
|
||||
@@ -71,10 +71,35 @@ LootItem::~LootItem() = default;
|
||||
// Basic checks for player/item compatibility - if false no chance to see the item in the loot
|
||||
bool LootItem::AllowedForPlayer(Player const* player, Loot const* loot) const
|
||||
{
|
||||
return AllowedForPlayer(player, loot, itemid, needs_quest, follow_loot_rules, false, conditions);
|
||||
switch (type)
|
||||
{
|
||||
case LootItemType::Item:
|
||||
return ItemAllowedForPlayer(player, loot, itemid, needs_quest, follow_loot_rules, false, conditions);
|
||||
case LootItemType::Currency:
|
||||
return CurrencyAllowedForPlayer(player, itemid, needs_quest, conditions);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LootItem::AllowedForPlayer(Player const* player, Loot const* loot, uint32 itemid, bool needs_quest, bool follow_loot_rules, bool strictUsabilityCheck,
|
||||
bool LootItem::AllowedForPlayer(Player const* player, LootStoreItem const& lootStoreItem, bool strictUsabilityCheck)
|
||||
{
|
||||
switch (lootStoreItem.type)
|
||||
{
|
||||
case LootStoreItem::Type::Item:
|
||||
return ItemAllowedForPlayer(player, nullptr, lootStoreItem.itemid, lootStoreItem.needs_quest,
|
||||
!lootStoreItem.needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(lootStoreItem.itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES),
|
||||
strictUsabilityCheck, lootStoreItem.conditions);
|
||||
case LootStoreItem::Type::Currency:
|
||||
return CurrencyAllowedForPlayer(player, lootStoreItem.itemid, lootStoreItem.needs_quest, lootStoreItem.conditions);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LootItem::ItemAllowedForPlayer(Player const* player, Loot const* loot, uint32 itemid, bool needs_quest, bool follow_loot_rules, bool strictUsabilityCheck,
|
||||
ConditionsReference const& conditions)
|
||||
{
|
||||
// DB conditions check
|
||||
@@ -128,14 +153,38 @@ bool LootItem::AllowedForPlayer(Player const* player, Loot const* loot, uint32 i
|
||||
return true;
|
||||
}
|
||||
|
||||
void LootItem::AddAllowedLooter(const Player* player)
|
||||
bool LootItem::CurrencyAllowedForPlayer(Player const* player, uint32 currencyId, bool needs_quest, ConditionsReference const& conditions)
|
||||
{
|
||||
// DB conditions check
|
||||
if (!conditions.Meets(player))
|
||||
return false;
|
||||
|
||||
CurrencyTypesEntry const* currency = sCurrencyTypesStore.LookupEntry(currencyId);
|
||||
if (!currency)
|
||||
return false;
|
||||
|
||||
// not show loot for not own team
|
||||
if (currency->GetFlags().HasFlag(CurrencyTypesFlags::IsHordeOnly) && player->GetTeam() != HORDE)
|
||||
return false;
|
||||
|
||||
if (currency->GetFlags().HasFlag(CurrencyTypesFlags::IsAllianceOnly) && player->GetTeam() != ALLIANCE)
|
||||
return false;
|
||||
|
||||
// check quest requirements
|
||||
if (needs_quest && !player->HasQuestForCurrency(currencyId))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LootItem::AddAllowedLooter(Player const* player)
|
||||
{
|
||||
allowedGUIDs.insert(player->GetGUID());
|
||||
}
|
||||
|
||||
bool LootItem::HasAllowedLooter(ObjectGuid const& looter) const
|
||||
{
|
||||
return allowedGUIDs.find(looter) != allowedGUIDs.end();
|
||||
return allowedGUIDs.contains(looter);
|
||||
}
|
||||
|
||||
Optional<LootSlotType> LootItem::GetUiTypeForPlayer(Player const* player, Loot const& loot) const
|
||||
@@ -143,7 +192,7 @@ Optional<LootSlotType> LootItem::GetUiTypeForPlayer(Player const* player, Loot c
|
||||
if (is_looted)
|
||||
return {};
|
||||
|
||||
if (allowedGUIDs.find(player->GetGUID()) == allowedGUIDs.end())
|
||||
if (!allowedGUIDs.contains(player->GetGUID()))
|
||||
return {};
|
||||
|
||||
if (freeforall)
|
||||
@@ -604,7 +653,8 @@ void LootRoll::Finish(RollVoteMap::const_iterator winnerItr)
|
||||
{
|
||||
for (uint32 i = 0; i < loot.items.size(); ++i)
|
||||
if (LootItem* disenchantLoot = loot.LootItemInSlot(i, player))
|
||||
player->SendItemRetrievalMail(disenchantLoot->itemid, disenchantLoot->count, disenchantLoot->context);
|
||||
if (disenchantLoot->type == LootItemType::Item)
|
||||
player->SendItemRetrievalMail(disenchantLoot->itemid, disenchantLoot->count, disenchantLoot->context);
|
||||
}
|
||||
else
|
||||
m_loot->NotifyItemRemoved(m_lootItem->LootListId, m_map);
|
||||
@@ -791,7 +841,7 @@ bool Loot::FillLoot(uint32 lootId, LootStore const& store, Player* lootOwner, bo
|
||||
|
||||
for (LootItem& item : items)
|
||||
{
|
||||
if (!item.follow_loot_rules || item.freeforall)
|
||||
if (!item.follow_loot_rules || item.freeforall || item.type != LootItemType::Item)
|
||||
continue;
|
||||
|
||||
if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item.itemid))
|
||||
@@ -826,23 +876,40 @@ bool Loot::FillLoot(uint32 lootId, LootStore const& store, Player* lootOwner, bo
|
||||
// Inserts the item into the loot (called by LootTemplate processors)
|
||||
void Loot::AddItem(LootStoreItem const& item)
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item.itemid);
|
||||
if (!proto)
|
||||
return;
|
||||
|
||||
uint32 count = urand(item.mincount, item.maxcount);
|
||||
uint32 stacks = count / proto->GetMaxStackSize() + ((count % proto->GetMaxStackSize()) ? 1 : 0);
|
||||
|
||||
for (uint32 i = 0; i < stacks && items.size() < MAX_NR_LOOT_ITEMS; ++i)
|
||||
switch (item.type)
|
||||
{
|
||||
LootItem generatedLoot(item);
|
||||
generatedLoot.context = _itemContext;
|
||||
generatedLoot.count = std::min(count, proto->GetMaxStackSize());
|
||||
generatedLoot.LootListId = items.size();
|
||||
generatedLoot.BonusListIDs = ItemBonusMgr::GetBonusListsForItem(generatedLoot.itemid, _itemContext);
|
||||
case LootStoreItem::Type::Item:
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item.itemid);
|
||||
if (!proto)
|
||||
return;
|
||||
|
||||
items.push_back(generatedLoot);
|
||||
count -= proto->GetMaxStackSize();
|
||||
uint32 count = urand(item.mincount, item.maxcount);
|
||||
uint32 stacks = count / proto->GetMaxStackSize() + ((count % proto->GetMaxStackSize()) ? 1 : 0);
|
||||
|
||||
for (uint32 i = 0; i < stacks && items.size() < MAX_NR_LOOT_ITEMS; ++i)
|
||||
{
|
||||
LootItem generatedLoot(item);
|
||||
generatedLoot.context = _itemContext;
|
||||
generatedLoot.count = std::min(count, proto->GetMaxStackSize());
|
||||
generatedLoot.LootListId = items.size();
|
||||
generatedLoot.BonusListIDs = ItemBonusMgr::GetBonusListsForItem(generatedLoot.itemid, _itemContext);
|
||||
|
||||
items.push_back(generatedLoot);
|
||||
count -= proto->GetMaxStackSize();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LootStoreItem::Type::Currency:
|
||||
{
|
||||
LootItem generatedLoot(item);
|
||||
generatedLoot.count = urand(item.mincount, item.maxcount);
|
||||
generatedLoot.LootListId = items.size();
|
||||
items.push_back(generatedLoot);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -867,17 +934,36 @@ bool Loot::AutoStore(Player* player, uint8 bag, uint8 slot, bool broadcast, bool
|
||||
if (!lootItem->rollWinnerGUID.IsEmpty() && lootItem->rollWinnerGUID != GetGUID())
|
||||
continue;
|
||||
|
||||
ItemPosCountVec dest;
|
||||
InventoryResult msg = player->CanStoreNewItem(bag, slot, dest, lootItem->itemid, lootItem->count);
|
||||
if (msg != EQUIP_ERR_OK && slot != NULL_SLOT)
|
||||
msg = player->CanStoreNewItem(bag, NULL_SLOT, dest, lootItem->itemid, lootItem->count);
|
||||
if (msg != EQUIP_ERR_OK && bag != NULL_BAG)
|
||||
msg = player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, lootItem->itemid, lootItem->count);
|
||||
if (msg != EQUIP_ERR_OK)
|
||||
switch (lootItem->type)
|
||||
{
|
||||
player->SendEquipError(msg, nullptr, nullptr, lootItem->itemid);
|
||||
allLooted = false;
|
||||
continue;
|
||||
case LootItemType::Item:
|
||||
{
|
||||
ItemPosCountVec dest;
|
||||
InventoryResult msg = player->CanStoreNewItem(bag, slot, dest, lootItem->itemid, lootItem->count);
|
||||
if (msg != EQUIP_ERR_OK && slot != NULL_SLOT)
|
||||
msg = player->CanStoreNewItem(bag, NULL_SLOT, dest, lootItem->itemid, lootItem->count);
|
||||
if (msg != EQUIP_ERR_OK && bag != NULL_BAG)
|
||||
msg = player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, lootItem->itemid, lootItem->count);
|
||||
if (msg != EQUIP_ERR_OK)
|
||||
{
|
||||
player->SendEquipError(msg, nullptr, nullptr, lootItem->itemid);
|
||||
allLooted = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Item* pItem = player->StoreNewItem(dest, lootItem->itemid, true, lootItem->randomBonusListId, GuidSet(), lootItem->context, &lootItem->BonusListIDs))
|
||||
{
|
||||
player->SendNewItem(pItem, lootItem->count, false, createdByPlayer, broadcast, GetDungeonEncounterId());
|
||||
player->ApplyItemLootedSpell(pItem, true);
|
||||
}
|
||||
else
|
||||
player->ApplyItemLootedSpell(sObjectMgr->GetItemTemplate(lootItem->itemid));
|
||||
|
||||
break;
|
||||
}
|
||||
case LootItemType::Currency:
|
||||
player->ModifyCurrency(lootItem->itemid, lootItem->count, CurrencyGainSource::Loot);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ffaitem)
|
||||
@@ -887,14 +973,6 @@ bool Loot::AutoStore(Player* player, uint8 bag, uint8 slot, bool broadcast, bool
|
||||
lootItem->is_looted = true;
|
||||
|
||||
--unlootedCount;
|
||||
|
||||
if (Item* pItem = player->StoreNewItem(dest, lootItem->itemid, true, lootItem->randomBonusListId, GuidSet(), lootItem->context, &lootItem->BonusListIDs))
|
||||
{
|
||||
player->SendNewItem(pItem, lootItem->count, false, createdByPlayer, broadcast, GetDungeonEncounterId());
|
||||
player->ApplyItemLootedSpell(pItem, true);
|
||||
}
|
||||
else
|
||||
player->ApplyItemLootedSpell(sObjectMgr->GetItemTemplate(lootItem->itemid));
|
||||
}
|
||||
|
||||
return allLooted;
|
||||
@@ -966,18 +1044,12 @@ bool Loot::hasItemFor(Player const* player) const
|
||||
{
|
||||
// quest items
|
||||
for (LootItem const& lootItem : items)
|
||||
if (!lootItem.is_looted && !lootItem.follow_loot_rules && lootItem.GetAllowedLooters().find(player->GetGUID()) != lootItem.GetAllowedLooters().end())
|
||||
if (!lootItem.is_looted && !lootItem.follow_loot_rules && lootItem.GetAllowedLooters().contains(player->GetGUID()))
|
||||
return true;
|
||||
|
||||
if (NotNormalLootItemList const* ffaItems = Trinity::Containers::MapGetValuePtr(GetPlayerFFAItems(), player->GetGUID()))
|
||||
{
|
||||
bool hasFfaItem = std::ranges::any_of(*ffaItems, [&](NotNormalLootItem const& ffaItem)
|
||||
{
|
||||
return !ffaItem.is_looted;
|
||||
});
|
||||
if (hasFfaItem)
|
||||
if (std::ranges::any_of(*ffaItems, std::identity(), &NotNormalLootItem::is_looted))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -1004,11 +1076,33 @@ void Loot::BuildLootResponse(WorldPackets::Loot::LootResponse& packet, Player co
|
||||
if (!uiType)
|
||||
continue;
|
||||
|
||||
WorldPackets::Loot::LootItemData& lootItem = packet.Items.emplace_back();
|
||||
lootItem.LootListID = item.LootListId;
|
||||
lootItem.UIType = *uiType;
|
||||
lootItem.Quantity = item.count;
|
||||
lootItem.Loot.Initialize(item);
|
||||
switch (item.type)
|
||||
{
|
||||
case LootItemType::Item:
|
||||
{
|
||||
WorldPackets::Loot::LootItemData& lootItem = packet.Items.emplace_back();
|
||||
lootItem.LootListID = item.LootListId;
|
||||
lootItem.UIType = *uiType;
|
||||
lootItem.Quantity = item.count;
|
||||
lootItem.Loot.Initialize(item);
|
||||
break;
|
||||
}
|
||||
case LootItemType::Currency:
|
||||
{
|
||||
WorldPackets::Loot::LootCurrency& lootCurrency = packet.Currencies.emplace_back();
|
||||
lootCurrency.CurrencyID = item.itemid;
|
||||
lootCurrency.Quantity = item.count;
|
||||
lootCurrency.LootListID = item.LootListId;
|
||||
lootCurrency.UIType = *uiType;
|
||||
|
||||
// fake visible quantity for SPELL_AURA_MOD_CURRENCY_CATEGORY_GAIN_PCT - handled in Player::ModifyCurrency
|
||||
lootCurrency.Quantity = float(lootCurrency.Quantity) * viewer->GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_CURRENCY_CATEGORY_GAIN_PCT, sCurrencyTypesStore.AssertEntry(item.itemid)->CategoryID);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "Define.h"
|
||||
#include "Duration.h"
|
||||
#include "ItemEnchantmentMgr.h"
|
||||
#include "LootItemType.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "Optional.h"
|
||||
#include "SharedDefines.h"
|
||||
@@ -174,30 +175,30 @@ enum class LootRollIneligibilityReason : uint32
|
||||
|
||||
struct TC_GAME_API LootItem
|
||||
{
|
||||
uint32 itemid;
|
||||
uint32 LootListId;
|
||||
ItemRandomBonusListId randomBonusListId;
|
||||
uint32 itemid = 0;
|
||||
uint32 LootListId = 0;
|
||||
ItemRandomBonusListId randomBonusListId = 0;
|
||||
std::vector<int32> BonusListIDs;
|
||||
ItemContext context;
|
||||
ItemContext context = ItemContext::NONE;
|
||||
ConditionsReference conditions; // additional loot condition
|
||||
GuidSet allowedGUIDs;
|
||||
ObjectGuid rollWinnerGUID; // Stores the guid of person who won loot, if his bags are full only he can see the item in loot list!
|
||||
uint8 count : 8;
|
||||
bool is_looted : 1;
|
||||
bool is_blocked : 1;
|
||||
bool freeforall : 1; // free for all
|
||||
bool is_underthreshold : 1;
|
||||
bool is_counted : 1;
|
||||
bool needs_quest : 1; // quest drop
|
||||
bool follow_loot_rules : 1;
|
||||
uint32 count = 0;
|
||||
LootItemType type = LootItemType::Item;
|
||||
bool is_looted : 1 = false;
|
||||
bool is_blocked : 1 = false;
|
||||
bool freeforall : 1 = false; // free for all
|
||||
bool is_underthreshold : 1 = false;
|
||||
bool is_counted : 1 = false;
|
||||
bool needs_quest : 1 = false; // quest drop
|
||||
bool follow_loot_rules : 1 = false;
|
||||
|
||||
// Constructor, copies most fields from LootStoreItem, generates random count and random suffixes/properties
|
||||
// Should be called for non-reference LootStoreItem entries only (reference = 0)
|
||||
// Should be called for non-reference LootStoreItem entries only
|
||||
explicit LootItem(LootStoreItem const& li);
|
||||
|
||||
// Empty constructor for creating an empty LootItem to be filled in with DB data
|
||||
LootItem() : itemid(0), LootListId(0), randomBonusListId(0), context(ItemContext::NONE), count(0), is_looted(false), is_blocked(false),
|
||||
freeforall(false), is_underthreshold(false), is_counted(false), needs_quest(false), follow_loot_rules(false) { }
|
||||
LootItem() = default;
|
||||
|
||||
LootItem(LootItem const&);
|
||||
LootItem(LootItem&&) noexcept;
|
||||
@@ -207,8 +208,10 @@ struct TC_GAME_API LootItem
|
||||
|
||||
// Basic checks for player/item compatibility - if false no chance to see the item in the loot - used only for loot generation
|
||||
bool AllowedForPlayer(Player const* player, Loot const* loot) const;
|
||||
static bool AllowedForPlayer(Player const* player, Loot const* loot, uint32 itemid, bool needs_quest, bool follow_loot_rules, bool strictUsabilityCheck,
|
||||
static bool AllowedForPlayer(Player const* player, LootStoreItem const& lootStoreItem, bool strictUsabilityCheck);
|
||||
static bool ItemAllowedForPlayer(Player const* player, Loot const* loot, uint32 itemid, bool needs_quest, bool follow_loot_rules, bool strictUsabilityCheck,
|
||||
ConditionsReference const& conditions);
|
||||
static bool CurrencyAllowedForPlayer(Player const* player, uint32 currencyId, bool needs_quest, ConditionsReference const& conditions);
|
||||
void AddAllowedLooter(Player const* player);
|
||||
GuidSet const& GetAllowedLooters() const { return allowedGUIDs; }
|
||||
bool HasAllowedLooter(ObjectGuid const& looter) const;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "ObjectMgr.h"
|
||||
#include "Player.h"
|
||||
#include "StringConvert.h"
|
||||
#include "Util.h"
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -70,18 +71,19 @@ void LootItemStorage::LoadStorageFromDB()
|
||||
StoredLootContainer& storedContainer = _lootItemStore.try_emplace(key, key).first->second;
|
||||
|
||||
LootItem lootItem;
|
||||
lootItem.itemid = fields[1].GetUInt32();
|
||||
lootItem.count = fields[2].GetUInt32();
|
||||
lootItem.LootListId = fields[3].GetUInt32();
|
||||
lootItem.follow_loot_rules = fields[4].GetBool();
|
||||
lootItem.freeforall = fields[5].GetBool();
|
||||
lootItem.is_blocked = fields[6].GetBool();
|
||||
lootItem.is_counted = fields[7].GetBool();
|
||||
lootItem.is_underthreshold = fields[8].GetBool();
|
||||
lootItem.needs_quest = fields[9].GetBool();
|
||||
lootItem.randomBonusListId = fields[10].GetUInt32();
|
||||
lootItem.context = ItemContext(fields[11].GetUInt8());
|
||||
for (std::string_view bonusList : Trinity::Tokenize(fields[12].GetStringView(), ' ', false))
|
||||
lootItem.type = static_cast<LootItemType>(fields[1].GetInt8());
|
||||
lootItem.itemid = fields[2].GetUInt32();
|
||||
lootItem.count = fields[3].GetUInt32();
|
||||
lootItem.LootListId = fields[4].GetUInt32();
|
||||
lootItem.follow_loot_rules = fields[5].GetBool();
|
||||
lootItem.freeforall = fields[6].GetBool();
|
||||
lootItem.is_blocked = fields[7].GetBool();
|
||||
lootItem.is_counted = fields[8].GetBool();
|
||||
lootItem.is_underthreshold = fields[9].GetBool();
|
||||
lootItem.needs_quest = fields[10].GetBool();
|
||||
lootItem.randomBonusListId = fields[11].GetUInt32();
|
||||
lootItem.context = ItemContext(fields[12].GetUInt8());
|
||||
for (std::string_view bonusList : Trinity::Tokenize(fields[13].GetStringView(), ' ', false))
|
||||
if (Optional<int32> bonusListID = Trinity::StringTo<int32>(bonusList))
|
||||
lootItem.BonusListIDs.push_back(*bonusListID);
|
||||
|
||||
@@ -226,7 +228,7 @@ void LootItemStorage::RemoveStoredLootForContainer(uint64 containerId)
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
}
|
||||
|
||||
void LootItemStorage::RemoveStoredLootItemForContainer(uint64 containerId, uint32 itemId, uint32 count, uint32 itemIndex)
|
||||
void LootItemStorage::RemoveStoredLootItemForContainer(uint64 containerId, LootItemType type, uint32 itemId, uint32 count, uint32 itemIndex)
|
||||
{
|
||||
// write
|
||||
std::unique_lock<std::shared_mutex> lock(*GetLock());
|
||||
@@ -235,7 +237,7 @@ void LootItemStorage::RemoveStoredLootItemForContainer(uint64 containerId, uint3
|
||||
if (itr == _lootItemStore.end())
|
||||
return;
|
||||
|
||||
itr->second.RemoveItem(itemId, count, itemIndex);
|
||||
itr->second.RemoveItem(type, itemId, count, itemIndex);
|
||||
}
|
||||
|
||||
void LootItemStorage::AddNewStoredLoot(uint64 containerId, Loot* loot, Player* player)
|
||||
@@ -301,23 +303,24 @@ void StoredLootContainer::AddLootItem(LootItem const& lootItem, CharacterDatabas
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEMCONTAINER_ITEMS);
|
||||
|
||||
// container_id, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix
|
||||
// container_id, item_type, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix
|
||||
stmt->setUInt64(0, _containerId);
|
||||
stmt->setUInt32(1, lootItem.itemid);
|
||||
stmt->setUInt32(2, lootItem.count);
|
||||
stmt->setUInt32(3, lootItem.LootListId);
|
||||
stmt->setBool(4, lootItem.follow_loot_rules);
|
||||
stmt->setBool(5, lootItem.freeforall);
|
||||
stmt->setBool(6, lootItem.is_blocked);
|
||||
stmt->setBool(7, lootItem.is_counted);
|
||||
stmt->setBool(8, lootItem.is_underthreshold);
|
||||
stmt->setBool(9, lootItem.needs_quest);
|
||||
stmt->setInt32(10, lootItem.randomBonusListId);
|
||||
stmt->setUInt8(11, AsUnderlyingType(lootItem.context));
|
||||
stmt->setInt8(1, AsUnderlyingType(lootItem.type));
|
||||
stmt->setUInt32(2, lootItem.itemid);
|
||||
stmt->setUInt32(3, lootItem.count);
|
||||
stmt->setUInt32(4, lootItem.LootListId);
|
||||
stmt->setBool(5, lootItem.follow_loot_rules);
|
||||
stmt->setBool(6, lootItem.freeforall);
|
||||
stmt->setBool(7, lootItem.is_blocked);
|
||||
stmt->setBool(8, lootItem.is_counted);
|
||||
stmt->setBool(9, lootItem.is_underthreshold);
|
||||
stmt->setBool(10, lootItem.needs_quest);
|
||||
stmt->setInt32(11, lootItem.randomBonusListId);
|
||||
stmt->setUInt8(13, AsUnderlyingType(lootItem.context));
|
||||
std::ostringstream bonusListIDs;
|
||||
for (int32 bonusListID : lootItem.BonusListIDs)
|
||||
bonusListIDs << bonusListID << ' ';
|
||||
stmt->setString(12, bonusListIDs.str());
|
||||
stmt->setString(13, bonusListIDs.str());
|
||||
trans->Append(stmt);
|
||||
}
|
||||
|
||||
@@ -346,7 +349,7 @@ void StoredLootContainer::RemoveMoney()
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
|
||||
void StoredLootContainer::RemoveItem(uint32 itemId, uint32 count, uint32 itemIndex)
|
||||
void StoredLootContainer::RemoveItem(LootItemType type, uint32 itemId, uint32 count, uint32 itemIndex)
|
||||
{
|
||||
auto bounds = _lootItems.equal_range(itemId);
|
||||
for (auto itr = bounds.first; itr != bounds.second; ++itr)
|
||||
@@ -361,8 +364,9 @@ void StoredLootContainer::RemoveItem(uint32 itemId, uint32 count, uint32 itemInd
|
||||
// Deletes a single item associated with an openable item from the DB
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEM);
|
||||
stmt->setUInt64(0, _containerId);
|
||||
stmt->setUInt32(1, itemId);
|
||||
stmt->setUInt32(2, count);
|
||||
stmt->setUInt32(3, itemIndex);
|
||||
stmt->setInt8(1, AsUnderlyingType(type));
|
||||
stmt->setUInt32(2, itemId);
|
||||
stmt->setUInt32(3, count);
|
||||
stmt->setUInt32(4, itemIndex);
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "DatabaseEnvFwd.h"
|
||||
#include "DBCEnums.h"
|
||||
#include "ItemEnchantmentMgr.h"
|
||||
#include "LootItemType.h"
|
||||
|
||||
#include <shared_mutex>
|
||||
#include <unordered_map>
|
||||
@@ -61,7 +62,7 @@ class StoredLootContainer
|
||||
void AddMoney(uint32 money, CharacterDatabaseTransaction trans);
|
||||
|
||||
void RemoveMoney();
|
||||
void RemoveItem(uint32 itemId, uint32 count, uint32 itemIndex);
|
||||
void RemoveItem(LootItemType type, uint32 itemId, uint32 count, uint32 itemIndex);
|
||||
|
||||
uint64 GetContainer() const { return _containerId; }
|
||||
uint32 GetMoney() const { return _money; }
|
||||
@@ -83,7 +84,7 @@ class LootItemStorage
|
||||
bool LoadStoredLoot(Item* item, Player* player);
|
||||
void RemoveStoredMoneyForContainer(uint64 containerId);
|
||||
void RemoveStoredLootForContainer(uint64 containerId);
|
||||
void RemoveStoredLootItemForContainer(uint64 containerId, uint32 itemId, uint32 count, uint32 itemIndex);
|
||||
void RemoveStoredLootItemForContainer(uint64 containerId, LootItemType type, uint32 itemId, uint32 count, uint32 itemIndex);
|
||||
void AddNewStoredLoot(uint64 containerId, Loot* loot, Player* player);
|
||||
|
||||
private:
|
||||
|
||||
@@ -66,9 +66,7 @@ struct LootGroupInvalidSelector
|
||||
if (!(item->lootmode & _lootMode))
|
||||
return true;
|
||||
|
||||
if (_personalLooter && !LootItem::AllowedForPlayer(_personalLooter, nullptr, item->itemid, item->needs_quest,
|
||||
!item->needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(item->itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES),
|
||||
true, item->conditions))
|
||||
if (_personalLooter && !LootItem::AllowedForPlayer(_personalLooter, *item, true))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
@@ -265,6 +263,14 @@ bool LootStoreItem::Roll(bool rate) const
|
||||
}
|
||||
case Type::Reference:
|
||||
return roll_chance_f(chance * (rate ? sWorld->getRate(RATE_DROP_ITEM_REFERENCED) : 1.0f));
|
||||
case Type::Currency:
|
||||
{
|
||||
CurrencyTypesEntry const* currency = sCurrencyTypesStore.AssertEntry(itemid);
|
||||
|
||||
float qualityModifier = currency && rate && QualityToRate[currency->Quality] != MAX_RATES ? sWorld->getRate(QualityToRate[currency->Quality]) : 1.0f;
|
||||
|
||||
return roll_chance_f(chance * qualityModifier);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -327,6 +333,38 @@ bool LootStoreItem::IsValid(LootStore const& store, uint32 entry) const
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case Type::Currency:
|
||||
{
|
||||
if (!sCurrencyTypesStore.HasRecord(itemid))
|
||||
{
|
||||
TC_LOG_ERROR("sql.sql", "Table '{}' Entry {} ItemType {} Item {}: currency does not exist - skipped",
|
||||
store.GetName(), entry, type, itemid);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (chance == 0 && groupid == 0) // Zero chance is allowed for grouped entries only
|
||||
{
|
||||
TC_LOG_ERROR("sql.sql", "Table '{}' Entry {} ItemType {} Item {}: equal-chanced grouped entry, but group not defined - skipped",
|
||||
store.GetName(), entry, type, itemid);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (chance != 0 && chance < 0.0001f) // loot with low chance
|
||||
{
|
||||
TC_LOG_ERROR("sql.sql", "Table '{}' Entry {} ItemType {} Item {}: low chance ({}) - skipped",
|
||||
store.GetName(), entry, type, itemid, chance);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (maxcount < mincount) // wrong max count
|
||||
{
|
||||
TC_LOG_ERROR("sql.sql", "Table '{}' Entry {} ItemType {} Item {}: MaxCount ({}) less that MinCount ({}) - skipped",
|
||||
store.GetName(), entry, type, itemid, int32(maxcount), mincount);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
TC_LOG_ERROR("sql.sql", "Table '{}' Entry {} Item {}: invalid ItemType {}, skipped",
|
||||
store.GetName(), entry, itemid, type);
|
||||
@@ -387,15 +425,11 @@ LootStoreItem const* LootTemplate::LootGroup::Roll(uint16 lootMode, Player const
|
||||
bool LootTemplate::LootGroup::HasDropForPlayer(Player const* player, bool strictUsabilityCheck) const
|
||||
{
|
||||
for (std::unique_ptr<LootStoreItem> const& lootStoreItem : ExplicitlyChanced)
|
||||
if (LootItem::AllowedForPlayer(player, nullptr, lootStoreItem->itemid, lootStoreItem->needs_quest,
|
||||
!lootStoreItem->needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(lootStoreItem->itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES),
|
||||
strictUsabilityCheck, lootStoreItem->conditions))
|
||||
if (LootItem::AllowedForPlayer(player, *lootStoreItem, strictUsabilityCheck))
|
||||
return true;
|
||||
|
||||
for (std::unique_ptr<LootStoreItem> const& lootStoreItem : EqualChanced)
|
||||
if (LootItem::AllowedForPlayer(player, nullptr, lootStoreItem->itemid, lootStoreItem->needs_quest,
|
||||
!lootStoreItem->needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(lootStoreItem->itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES),
|
||||
strictUsabilityCheck, lootStoreItem->conditions))
|
||||
if (LootItem::AllowedForPlayer(player, *lootStoreItem, strictUsabilityCheck))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
@@ -416,10 +450,24 @@ bool LootTemplate::LootGroup::HasQuestDrop() const
|
||||
// True if group includes at least 1 quest drop entry for active quests of the player
|
||||
bool LootTemplate::LootGroup::HasQuestDropForPlayer(Player const* player) const
|
||||
{
|
||||
if (std::ranges::any_of(ExplicitlyChanced, [player](uint32 itemId) { return player->HasQuestForItem(itemId); }, &LootStoreItem::itemid))
|
||||
auto hasQuestForLootItem = [player](std::unique_ptr<LootStoreItem> const& item)
|
||||
{
|
||||
switch (item->type)
|
||||
{
|
||||
case LootStoreItem::Type::Item:
|
||||
return player->HasQuestForItem(item->itemid);
|
||||
case LootStoreItem::Type::Currency:
|
||||
return player->HasQuestForCurrency(item->itemid);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (std::ranges::any_of(ExplicitlyChanced, hasQuestForLootItem))
|
||||
return true;
|
||||
|
||||
if (std::ranges::any_of(EqualChanced, [player](uint32 itemId) { return player->HasQuestForItem(itemId); }, &LootStoreItem::itemid))
|
||||
if (std::ranges::any_of(EqualChanced, hasQuestForLootItem))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
@@ -519,6 +567,22 @@ void LootTemplate::CopyConditions(LootItem* li) const
|
||||
// Copies the conditions list from a template item to a LootItemData
|
||||
for (std::unique_ptr<LootStoreItem> const& item : Entries)
|
||||
{
|
||||
switch (item->type)
|
||||
{
|
||||
case LootStoreItem::Type::Item:
|
||||
if (li->type != LootItemType::Item)
|
||||
continue;
|
||||
break;
|
||||
case LootStoreItem::Type::Reference:
|
||||
continue;
|
||||
case LootStoreItem::Type::Currency:
|
||||
if (li->type != LootItemType::Currency)
|
||||
continue;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (item->itemid != li->itemid)
|
||||
continue;
|
||||
|
||||
@@ -554,12 +618,10 @@ void LootTemplate::Process(Loot& loot, bool rate, uint16 lootMode, uint8 groupId
|
||||
switch (item->type)
|
||||
{
|
||||
case LootStoreItem::Type::Item:
|
||||
case LootStoreItem::Type::Currency:
|
||||
// Plain entries (not a reference, not grouped)
|
||||
// Chance is already checked, just add
|
||||
if (!personalLooter
|
||||
|| LootItem::AllowedForPlayer(personalLooter, nullptr, item->itemid, item->needs_quest,
|
||||
!item->needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(item->itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES),
|
||||
true, item->conditions))
|
||||
if (!personalLooter || LootItem::AllowedForPlayer(personalLooter, *item, true))
|
||||
loot.AddItem(*item);
|
||||
|
||||
break;
|
||||
@@ -616,9 +678,7 @@ void LootTemplate::ProcessPersonalLoot(std::unordered_map<Player*, std::unique_p
|
||||
// Chance is already checked, just add
|
||||
std::vector<Player*> lootersForItem = getLootersForItem([&](Player const* looter)
|
||||
{
|
||||
return LootItem::AllowedForPlayer(looter, nullptr, item->itemid, item->needs_quest,
|
||||
!item->needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(item->itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES),
|
||||
true, item->conditions);
|
||||
return LootItem::AllowedForPlayer(looter, *item, true);
|
||||
});
|
||||
|
||||
if (!lootersForItem.empty())
|
||||
@@ -668,6 +728,19 @@ void LootTemplate::ProcessPersonalLoot(std::unordered_map<Player*, std::unique_p
|
||||
|
||||
break;
|
||||
}
|
||||
case LootStoreItem::Type::Currency:
|
||||
{
|
||||
// Plain entries (not a reference, not grouped)
|
||||
// Chance is already checked, just add
|
||||
std::vector<Player*> lootersForItem = getLootersForItem([&](Player const* looter)
|
||||
{
|
||||
return LootItem::AllowedForPlayer(looter, *item, true);
|
||||
});
|
||||
|
||||
for (Player* looter : lootersForItem)
|
||||
personalLoot[looter]->AddItem(*item);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -712,9 +785,8 @@ bool LootTemplate::HasDropForPlayer(Player const* player, uint8 groupId, bool st
|
||||
switch (lootStoreItem->type)
|
||||
{
|
||||
case LootStoreItem::Type::Item:
|
||||
if (LootItem::AllowedForPlayer(player, nullptr, lootStoreItem->itemid, lootStoreItem->needs_quest,
|
||||
!lootStoreItem->needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(lootStoreItem->itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES),
|
||||
strictUsabilityCheck, lootStoreItem->conditions))
|
||||
case LootStoreItem::Type::Currency:
|
||||
if (LootItem::AllowedForPlayer(player, *lootStoreItem, strictUsabilityCheck))
|
||||
return true; // active quest drop found
|
||||
break;
|
||||
case LootStoreItem::Type::Reference:
|
||||
@@ -758,6 +830,7 @@ bool LootTemplate::HasQuestDrop(LootTemplateMap const& store, uint8 groupId) con
|
||||
switch (item->type)
|
||||
{
|
||||
case LootStoreItem::Type::Item:
|
||||
case LootStoreItem::Type::Currency:
|
||||
if (item->needs_quest)
|
||||
return true; // quest drop found
|
||||
break;
|
||||
@@ -815,6 +888,10 @@ bool LootTemplate::HasQuestDropForPlayer(LootTemplateMap const& store, Player co
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
case LootStoreItem::Type::Currency:
|
||||
if (player->HasQuestForCurrency(item->itemid))
|
||||
return true; // active quest drop found
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ struct TC_GAME_API LootStoreItem
|
||||
{
|
||||
Item = 0,
|
||||
Reference = 1,
|
||||
Currency = 2,
|
||||
};
|
||||
|
||||
uint32 itemid; // id of the item
|
||||
|
||||
@@ -344,7 +344,7 @@ enum TrinityStrings
|
||||
LANG_COMMAND_NPC_SHOWLOOT_LABEL_2 = 293,
|
||||
LANG_COMMAND_NPC_SHOWLOOT_SUBLABEL = 294,
|
||||
LANG_COMMAND_NPC_SHOWLOOT_ENTRY_2 = 295,
|
||||
// 296 free
|
||||
LANG_COMMAND_NPC_SHOWLOOT_CURRENCY = 296,
|
||||
|
||||
// END
|
||||
LANG_COMMAND_WANDER_DISTANCE = 297,
|
||||
|
||||
@@ -39,6 +39,8 @@ EndScriptData */
|
||||
#include "Util.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
using namespace Trinity::ChatCommands;
|
||||
|
||||
#if TRINITY_COMPILER == TRINITY_COMPILER_GNU
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
@@ -923,12 +925,9 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleModifyCurrencyCommand(ChatHandler* handler, const char* args)
|
||||
static bool HandleModifyCurrencyCommand(ChatHandler* handler, CurrencyTypesEntry const* currency, int32 amount)
|
||||
{
|
||||
if (!*args)
|
||||
return false;
|
||||
|
||||
Player* target = handler->getSelectedPlayer();
|
||||
Player* target = handler->getSelectedPlayerOrSelf();
|
||||
if (!target)
|
||||
{
|
||||
handler->PSendSysMessage(LANG_PLAYER_NOT_FOUND);
|
||||
@@ -936,16 +935,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 currencyId = atoi(strtok((char*)args, " "));
|
||||
const CurrencyTypesEntry* currencyType = sCurrencyTypesStore.LookupEntry(currencyId);
|
||||
if (!currencyType)
|
||||
return false;
|
||||
|
||||
uint32 amount = atoi(strtok(nullptr, " "));
|
||||
if (!amount)
|
||||
return false;
|
||||
|
||||
target->ModifyCurrency(currencyId, amount, CurrencyGainSource::Cheat, CurrencyDestroyReason::Cheat);
|
||||
target->ModifyCurrency(currency->ID, amount, CurrencyGainSource::Cheat, CurrencyDestroyReason::Cheat);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -23,12 +23,13 @@ Category: commandscripts
|
||||
EndScriptData */
|
||||
|
||||
#include "ScriptMgr.h"
|
||||
#include "CharacterCache.h"
|
||||
#include "Chat.h"
|
||||
#include "ChatCommand.h"
|
||||
#include "CreatureAI.h"
|
||||
#include "CreatureGroups.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "DB2Stores.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "FollowMovementGenerator.h"
|
||||
#include "GameTime.h"
|
||||
#include "Language.h"
|
||||
@@ -1165,7 +1166,7 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _ShowLootEntry(ChatHandler* handler, uint32 itemId, uint8 itemCount, bool alternateString = false)
|
||||
static void _ShowLootEntry(ChatHandler* handler, uint32 itemId, uint32 itemCount, bool alternateString = false)
|
||||
{
|
||||
ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(itemId);
|
||||
char const* name = nullptr;
|
||||
@@ -1177,6 +1178,18 @@ public:
|
||||
itemCount, ItemQualityColors[itemTemplate ? static_cast<ItemQualities>(itemTemplate->GetQuality()) : ITEM_QUALITY_POOR], itemId, name, itemId);
|
||||
}
|
||||
|
||||
static void _ShowLootCurrencyEntry(ChatHandler* handler, uint32 currencyId, uint32 count, bool alternateString = false)
|
||||
{
|
||||
CurrencyTypesEntry const* currency = sCurrencyTypesStore.LookupEntry(currencyId);
|
||||
char const* name = nullptr;
|
||||
if (currency)
|
||||
name = currency->Name[handler->GetSessionDbcLocale()];
|
||||
if (!name)
|
||||
name = "Unknown currency";
|
||||
handler->PSendSysMessage(LANG_COMMAND_NPC_SHOWLOOT_CURRENCY, alternateString ? 6 : 3 /*number of bytes from following string*/, "\u2500\u2500",
|
||||
count, ItemQualityColors[currency ? static_cast<ItemQualities>(currency->Quality) : ITEM_QUALITY_POOR], currencyId, count, name, currencyId);
|
||||
}
|
||||
|
||||
static void _IterateNotNormalLootMap(ChatHandler* handler, NotNormalLootItemMap const& map, std::vector<LootItem> const& items)
|
||||
{
|
||||
for (NotNormalLootItemMap::value_type const& pair : map)
|
||||
@@ -1189,11 +1202,72 @@ public:
|
||||
for (auto it = pair.second->cbegin(); it != pair.second->cend(); ++it)
|
||||
{
|
||||
LootItem const& item = items[it->LootListId];
|
||||
if (!(it->is_looted) && !item.is_looted)
|
||||
_ShowLootEntry(handler, item.itemid, item.count, true);
|
||||
if (!it->is_looted && !item.is_looted)
|
||||
{
|
||||
switch (item.type)
|
||||
{
|
||||
case LootItemType::Item:
|
||||
_ShowLootEntry(handler, item.itemid, item.count, true);
|
||||
break;
|
||||
case LootItemType::Currency:
|
||||
_ShowLootCurrencyEntry(handler, item.itemid, item.count, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _ShowLootContents(ChatHandler* handler, bool all, Loot const* loot)
|
||||
{
|
||||
handler->PSendSysMessage(LANG_COMMAND_NPC_SHOWLOOT_MONEY, loot->gold / GOLD, (loot->gold % GOLD) / SILVER, loot->gold % SILVER);
|
||||
|
||||
if (!all)
|
||||
{
|
||||
handler->PSendSysMessage(LANG_COMMAND_NPC_SHOWLOOT_LABEL, "Standard items", loot->items.size());
|
||||
for (LootItem const& item : loot->items)
|
||||
{
|
||||
if (!item.is_looted)
|
||||
{
|
||||
switch (item.type)
|
||||
{
|
||||
case LootItemType::Item:
|
||||
_ShowLootEntry(handler, item.itemid, item.count);
|
||||
break;
|
||||
case LootItemType::Currency:
|
||||
_ShowLootCurrencyEntry(handler, item.itemid, item.count);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
handler->PSendSysMessage(LANG_COMMAND_NPC_SHOWLOOT_LABEL, "Standard items", loot->items.size());
|
||||
for (LootItem const& item : loot->items)
|
||||
{
|
||||
if (!item.is_looted && !item.freeforall && item.conditions.IsEmpty())
|
||||
{
|
||||
switch (item.type)
|
||||
{
|
||||
case LootItemType::Item:
|
||||
_ShowLootEntry(handler, item.itemid, item.count);
|
||||
break;
|
||||
case LootItemType::Currency:
|
||||
_ShowLootCurrencyEntry(handler, item.itemid, item.count);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!loot->GetPlayerFFAItems().empty())
|
||||
{
|
||||
handler->PSendSysMessage(LANG_COMMAND_NPC_SHOWLOOT_LABEL_2, "FFA items per allowed player");
|
||||
_IterateNotNormalLootMap(handler, loot->GetPlayerFFAItems(), loot->items);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool HandleNpcShowLootCommand(ChatHandler* handler, Optional<EXACT_SEQUENCE("all")> all)
|
||||
{
|
||||
Creature* creatureTarget = handler->getSelectedCreature();
|
||||
@@ -1204,8 +1278,16 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!creatureTarget->isDead())
|
||||
{
|
||||
handler->PSendSysMessage(LANG_COMMAND_NOT_DEAD_OR_NO_LOOT, creatureTarget->GetName().c_str());
|
||||
handler->SetSentErrorMessage(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
Loot const* loot = creatureTarget->m_loot.get();
|
||||
if (!creatureTarget->isDead() || !loot || loot->isLooted())
|
||||
if ((!loot || loot->isLooted())
|
||||
&& !std::ranges::count_if(creatureTarget->m_personalLoot, std::not_fn(&Loot::isLooted), &std::unordered_map<ObjectGuid, std::unique_ptr<Loot>>::value_type::second))
|
||||
{
|
||||
handler->PSendSysMessage(LANG_COMMAND_NOT_DEAD_OR_NO_LOOT, creatureTarget->GetName().c_str());
|
||||
handler->SetSentErrorMessage(true);
|
||||
@@ -1213,26 +1295,18 @@ public:
|
||||
}
|
||||
|
||||
handler->PSendSysMessage(LANG_COMMAND_NPC_SHOWLOOT_HEADER, creatureTarget->GetName().c_str(), creatureTarget->GetEntry());
|
||||
handler->PSendSysMessage(LANG_COMMAND_NPC_SHOWLOOT_MONEY, loot->gold / GOLD, (loot->gold % GOLD) / SILVER, loot->gold % SILVER);
|
||||
|
||||
if (!all)
|
||||
{
|
||||
handler->PSendSysMessage(LANG_COMMAND_NPC_SHOWLOOT_LABEL, "Standard items", loot->items.size());
|
||||
for (LootItem const& item : loot->items)
|
||||
if (!item.is_looted)
|
||||
_ShowLootEntry(handler, item.itemid, item.count);
|
||||
}
|
||||
if (loot)
|
||||
_ShowLootContents(handler, all.has_value(), loot);
|
||||
else
|
||||
{
|
||||
handler->PSendSysMessage(LANG_COMMAND_NPC_SHOWLOOT_LABEL, "Standard items", loot->items.size());
|
||||
for (LootItem const& item : loot->items)
|
||||
if (!item.is_looted && !item.freeforall && item.conditions.IsEmpty())
|
||||
_ShowLootEntry(handler, item.itemid, item.count);
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
if (!loot->GetPlayerFFAItems().empty())
|
||||
for (auto const& [lootOwner, personalLoot] : creatureTarget->m_personalLoot)
|
||||
{
|
||||
handler->PSendSysMessage(LANG_COMMAND_NPC_SHOWLOOT_LABEL_2, "FFA items per allowed player");
|
||||
_IterateNotNormalLootMap(handler, loot->GetPlayerFFAItems(), loot->items);
|
||||
CharacterCacheEntry const* character = sCharacterCache->GetCharacterCacheByGuid(lootOwner);
|
||||
handler->PSendSysMessage(LANG_COMMAND_NPC_SHOWLOOT_LABEL_2, Trinity::StringFormat("Personal loot for {}", character ? character->Name : ""sv));
|
||||
_ShowLootContents(handler, all.has_value(), personalLoot.get());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1549,8 +1549,20 @@ struct npc_murloc_spearhunter_watershaper : public ScriptedAI
|
||||
|
||||
for (LootItem const& lootItem : loot->items)
|
||||
{
|
||||
if (lootItem.itemid == ITEM_STITCHED_CLOTH_SHOES || lootItem.itemid == ITEM_STITCHED_LEATHER_BOOTS || lootItem.itemid == ITEM_LINKED_MAIL_BOOTS || lootItem.itemid == ITEM_DENTED_PLATE_BOOTS)
|
||||
player->SetRewardedQuest(QUEST_MURLOC_HIDEAWAY_BOOTS_DROPPED);
|
||||
if (lootItem.type != LootItemType::Item)
|
||||
continue;
|
||||
|
||||
switch (lootItem.itemid)
|
||||
{
|
||||
case ITEM_STITCHED_CLOTH_SHOES:
|
||||
case ITEM_STITCHED_LEATHER_BOOTS:
|
||||
case ITEM_LINKED_MAIL_BOOTS:
|
||||
case ITEM_DENTED_PLATE_BOOTS:
|
||||
player->SetRewardedQuest(QUEST_MURLOC_HIDEAWAY_BOOTS_DROPPED);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4559,10 +4571,19 @@ struct npc_quilboar_warrior : public ScriptedAI
|
||||
|
||||
for (LootItem const& lootItem : loot->items)
|
||||
{
|
||||
if (lootItem.itemid == ITEM_STITCHED_CLOTH_TUNIC || lootItem.itemid == ITEM_STITCHED_LEATHER_TUNIC || lootItem.itemid == ITEM_LINKED_MAIL_HAUBERK || lootItem.itemid == ITEM_DENTED_CHESTPLATE)
|
||||
if (lootItem.type != LootItemType::Item)
|
||||
continue;
|
||||
|
||||
switch (lootItem.itemid)
|
||||
{
|
||||
player->SetRewardedQuest(QUEST_BRIARPATCH_CHEST_DROPPED);
|
||||
break;
|
||||
case ITEM_STITCHED_CLOTH_TUNIC:
|
||||
case ITEM_STITCHED_LEATHER_TUNIC:
|
||||
case ITEM_LINKED_MAIL_HAUBERK:
|
||||
case ITEM_DENTED_CHESTPLATE:
|
||||
player->SetRewardedQuest(QUEST_BRIARPATCH_CHEST_DROPPED);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4628,10 +4649,19 @@ struct npc_quilboar_geomancer : public ScriptedAI
|
||||
|
||||
for (LootItem const& lootItem : loot->items)
|
||||
{
|
||||
if (lootItem.itemid == ITEM_STITCHED_CLOTH_TUNIC || lootItem.itemid == ITEM_STITCHED_LEATHER_TUNIC || lootItem.itemid == ITEM_LINKED_MAIL_HAUBERK || lootItem.itemid == ITEM_DENTED_CHESTPLATE)
|
||||
if (lootItem.type != LootItemType::Item)
|
||||
continue;
|
||||
|
||||
switch (lootItem.itemid)
|
||||
{
|
||||
player->SetRewardedQuest(QUEST_BRIARPATCH_CHEST_DROPPED);
|
||||
break;
|
||||
case ITEM_STITCHED_CLOTH_TUNIC:
|
||||
case ITEM_STITCHED_LEATHER_TUNIC:
|
||||
case ITEM_LINKED_MAIL_HAUBERK:
|
||||
case ITEM_DENTED_CHESTPLATE:
|
||||
player->SetRewardedQuest(QUEST_BRIARPATCH_CHEST_DROPPED);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4707,7 +4737,7 @@ struct npc_ogre_overseer : public ScriptedAI
|
||||
|
||||
for (LootItem const& lootItem : loot->items)
|
||||
{
|
||||
if (lootItem.itemid == ITEM_BATTERED_CLOAK)
|
||||
if (lootItem.type == LootItemType::Item && lootItem.itemid == ITEM_BATTERED_CLOAK)
|
||||
{
|
||||
player->SetRewardedQuest(QUEST_BRIARPATCH_OVERSEER_CLOAK_DROPPED);
|
||||
break;
|
||||
@@ -6487,7 +6517,7 @@ struct npc_torgok_q55879 : public ScriptedAI
|
||||
{
|
||||
for (LootItem const& lootItem : loot->items)
|
||||
{
|
||||
if (lootItem.itemid == ITEM_TORGOKS_REAGENT_POUCH)
|
||||
if (lootItem.type == LootItemType::Item && lootItem.itemid == ITEM_TORGOKS_REAGENT_POUCH)
|
||||
{
|
||||
player->SetRewardedQuest(QUEST_TORGOKS_REAGENT_POUCH_DROPPED);
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user