diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 199091c55..e17f79a87 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -561,7 +561,7 @@ SET(common_headers json_config.h light_source.h linked_list.h - loottable.h + loot.h mail_oplist.h md5.h memory_buffer.h diff --git a/common/content/world_content_service.cpp b/common/content/world_content_service.cpp index 5bd19ba96..8669902ef 100644 --- a/common/content/world_content_service.cpp +++ b/common/content/world_content_service.cpp @@ -22,7 +22,6 @@ #include "../database.h" #include "../rulesys.h" #include "../eqemu_logsys.h" -#include "../loottable.h" #include "../repositories/content_flags_repository.h" diff --git a/common/content/world_content_service.h b/common/content/world_content_service.h index f2661831a..fb336da02 100644 --- a/common/content/world_content_service.h +++ b/common/content/world_content_service.h @@ -23,11 +23,17 @@ #include #include -#include "../loottable.h" #include "../repositories/content_flags_repository.h" class Database; +struct ContentFlags { + int16 min_expansion; + int16 max_expansion; + std::string content_flags; + std::string content_flags_disabled; +}; + namespace Expansion { static const int EXPANSION_ALL = -1; static const int EXPANSION_FILTER_MAX = 99; diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index b2025df82..f651e937c 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -5553,28 +5553,6 @@ struct MercenaryMerchantResponse_Struct { /*0004*/ }; -struct ServerLootItem_Struct { - uint32 item_id; // uint32 item_id; - int16 equip_slot; // int16 equip_slot; - uint16 charges; // uint8 charges; - uint16 lootslot; // uint16 lootslot; - uint32 aug_1; // uint32 aug_1; - uint32 aug_2; // uint32 aug_2; - uint32 aug_3; // uint32 aug_3; - uint32 aug_4; // uint32 aug_4; - uint32 aug_5; // uint32 aug_5; - uint32 aug_6; // uint32 aug_5; - bool attuned; - std::string custom_data; - uint32 ornamenticon {}; - uint32 ornamentidfile {}; - uint32 ornament_hero_model {}; - uint16 trivial_min_level; - uint16 trivial_max_level; - uint16 npc_min_level; - uint16 npc_max_level; -}; - //Found in client near a ref to the string: //"Got a broadcast message for ... %s ...\n" struct ClientMarqueeMessage_Struct { @@ -5593,9 +5571,6 @@ struct ClientMarqueeMessage_Struct { }; -typedef std::list ItemList; - - struct fling_struct { /* 00 */ uint32 collision; // 0 collision is off, anything else it's on /* 04 */ int32 travel_time; // ms -- UF we need to calc this, RoF+ -1 auto calcs diff --git a/common/loot.h b/common/loot.h new file mode 100644 index 000000000..ba36c364f --- /dev/null +++ b/common/loot.h @@ -0,0 +1,33 @@ +#ifndef CODE_LOOT_H +#define CODE_LOOT_H + +#include +#include +#include "../common/types.h" + +struct LootItem { + uint32 item_id; + int16 equip_slot; + uint16 charges; + uint16 lootslot; + uint32 aug_1; + uint32 aug_2; + uint32 aug_3; + uint32 aug_4; + uint32 aug_5; + uint32 aug_6; + bool attuned; + std::string custom_data; + uint32 ornamenticon{}; + uint32 ornamentidfile{}; + uint32 ornament_hero_model{}; + uint16 trivial_min_level; + uint16 trivial_max_level; + uint16 npc_min_level; + uint16 npc_max_level; +}; + +typedef std::list LootItems; + + +#endif //CODE_LOOT_H diff --git a/common/loottable.h b/common/loottable.h deleted file mode 100644 index c46fe0634..000000000 --- a/common/loottable.h +++ /dev/null @@ -1,68 +0,0 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2013 EQEMu Development Team (http://eqemu.org) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY except by those people which sell it, which - are required to give you total support for your newly bought product; - without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef _EQEMU_LOOTTABLE_H -#define _EQEMU_LOOTTABLE_H - -#include "types.h" - -#pragma pack(1) -struct LootTableEntries_Struct { - uint32 lootdrop_id; - uint8 droplimit; - uint8 mindrop; - uint8 multiplier; - float probability; -}; - -struct ContentFlags { - int16 min_expansion; - int16 max_expansion; - char content_flags[100]; - char content_flags_disabled[100]; -}; - -struct LootTable_Struct { - uint32 mincash; - uint32 maxcash; - uint32 avgcoin; - uint32 NumEntries; - ContentFlags content_flags; - LootTableEntries_Struct Entries[0]; -}; - -struct LootDropEntries_Struct { - uint32 item_id; - int8 item_charges; - uint8 equip_item; - float chance; - uint16 trivial_min_level; - uint16 trivial_max_level; - uint16 npc_min_level; - uint16 npc_max_level; - uint8 multiplier; -}; - -struct LootDrop_Struct { - uint32 NumEntries; - ContentFlags content_flags; - LootDropEntries_Struct Entries[0]; -}; -#pragma pack() - -#endif diff --git a/common/repositories/lootdrop_entries_repository.h b/common/repositories/lootdrop_entries_repository.h index 398ab46a4..8f1c21b81 100644 --- a/common/repositories/lootdrop_entries_repository.h +++ b/common/repositories/lootdrop_entries_repository.h @@ -8,42 +8,24 @@ class LootdropEntriesRepository: public BaseLootdropEntriesRepository { public: - /** - * This file was auto generated and can be modified and extended upon - * - * Base repository methods are automatically - * generated in the "base" version of this repository. The base repository - * is immutable and to be left untouched, while methods in this class - * are used as extension methods for more specific persistence-layer - * accessors or mutators. - * - * Base Methods (Subject to be expanded upon in time) - * - * Note: Not all tables are designed appropriately to fit functionality with all base methods - * - * InsertOne - * UpdateOne - * DeleteOne - * FindOne - * GetWhere(std::string where_filter) - * DeleteWhere(std::string where_filter) - * InsertMany - * All - * - * Example custom methods in a repository - * - * LootdropEntriesRepository::GetByZoneAndVersion(int zone_id, int zone_version) - * LootdropEntriesRepository::GetWhereNeverExpires() - * LootdropEntriesRepository::GetWhereXAndY() - * LootdropEntriesRepository::DeleteWhereXAndY() - * - * Most of the above could be covered by base methods, but if you as a developer - * find yourself re-using logic for other parts of the code, its best to just make a - * method that can be re-used easily elsewhere especially if it can use a base repository - * method and encapsulate filters there - */ + static LootdropEntries NewNpcEntity() + { + LootdropEntries e{}; - // Custom extended repository methods here + e.lootdrop_id = 0; + e.item_id = 0; + e.item_charges = 1; + e.equip_item = 1; + e.chance = 0; + e.disabled_chance = 0; + e.trivial_min_level = 0; + e.trivial_max_level = 0; + e.multiplier = 0; + e.npc_min_level = 0; + e.npc_max_level = 0; + + return e; + } }; diff --git a/common/say_link.h b/common/say_link.h index 6470c1c64..e70cff561 100644 --- a/common/say_link.h +++ b/common/say_link.h @@ -24,8 +24,9 @@ #include #include "repositories/saylink_repository.h" +#include "loot.h" -struct ServerLootItem_Struct; +struct LootItem; namespace EQ { @@ -73,7 +74,7 @@ namespace EQ void SetLinkType(saylink::SayLinkType link_type) { m_LinkType = link_type; } void SetItemData(const EQ::ItemData* item_data) { m_ItemData = item_data; } - void SetLootData(const ServerLootItem_Struct* loot_data) { m_LootData = loot_data; } + void SetLootData(const LootItem* loot_data) { m_LootData = loot_data; } void SetItemInst(const ItemInstance* item_inst) { m_ItemInst = item_inst; } // mainly for saylinks..but, not limited to @@ -112,9 +113,9 @@ namespace EQ void generate_text(); int m_LinkType; - const ItemData* m_ItemData; - const ServerLootItem_Struct* m_LootData; - const ItemInstance* m_ItemInst; + const ItemData * m_ItemData; + const LootItem * m_LootData; + const ItemInstance * m_ItemInst; SayLinkBody_Struct m_LinkBodyStruct; SayLinkProxy_Struct m_LinkProxyStruct; bool m_TaskUse; diff --git a/common/servertalk.h b/common/servertalk.h index 6a2693217..a6e158997 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -252,6 +252,7 @@ #define ServerOP_ReloadZoneData 0x4124 #define ServerOP_ReloadDataBucketsCache 0x4125 #define ServerOP_ReloadFactions 0x4126 +#define ServerOP_ReloadLoot 0x4127 #define ServerOP_CZDialogueWindow 0x4500 #define ServerOP_CZLDoNUpdate 0x4501 diff --git a/common/shareddb.cpp b/common/shareddb.cpp index d0c8cc084..fed089f0b 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -30,7 +30,6 @@ #include "features.h" #include "ipc_mutex.h" #include "inventory_profile.h" -#include "loottable.h" #include "memory_mapped_file.h" #include "mysql.h" #include "rulesys.h" @@ -2086,294 +2085,6 @@ const BaseDataStruct* SharedDatabase::GetBaseData(int lvl, int cl) const return bd; } -void SharedDatabase::GetLootTableInfo(uint32 &loot_table_count, uint32 &max_loot_table, uint32 &loot_table_entries) { - loot_table_count = 0; - max_loot_table = 0; - loot_table_entries = 0; - const std::string query = - fmt::format( - "SELECT COUNT(*), MAX(id), (SELECT COUNT(*) FROM loottable_entries) FROM loottable WHERE TRUE {}", - ContentFilterCriteria::apply() - ); - auto results = QueryDatabase(query); - if (!results.Success()) { - return; - } - - if (results.RowCount() == 0) - return; - - auto& row = results.begin(); - - loot_table_count = Strings::ToUnsignedInt(row[0]); - max_loot_table = Strings::ToUnsignedInt(row[1] ? row[1] : "0"); - loot_table_entries = Strings::ToUnsignedInt(row[2]); -} - -void SharedDatabase::GetLootDropInfo(uint32 &loot_drop_count, uint32 &max_loot_drop, uint32 &loot_drop_entries) { - loot_drop_count = 0; - max_loot_drop = 0; - loot_drop_entries = 0; - - const std::string query = fmt::format( - "SELECT COUNT(*), MAX(id), (SELECT COUNT(*) FROM lootdrop_entries) FROM lootdrop WHERE TRUE {}", - ContentFilterCriteria::apply() - ); - - auto results = QueryDatabase(query); - if (!results.Success()) { - return; - } - - if (results.RowCount() == 0) - return; - - auto& row =results.begin(); - - loot_drop_count = Strings::ToUnsignedInt(row[0]); - max_loot_drop = Strings::ToUnsignedInt(row[1] ? row[1] : "0"); - loot_drop_entries = Strings::ToUnsignedInt(row[2]); -} - -void SharedDatabase::LoadLootTables(void *data, uint32 size) { - EQ::FixedMemoryVariableHashSet hash(static_cast(data), size); - - uint8 loot_table[sizeof(LootTable_Struct) + (sizeof(LootTableEntries_Struct) * 128)]; - LootTable_Struct *lt = reinterpret_cast(loot_table); - - const std::string query = fmt::format( - SQL( - SELECT - loottable.id, - loottable.mincash, - loottable.maxcash, - loottable.avgcoin, - loottable_entries.lootdrop_id, - loottable_entries.multiplier, - loottable_entries.droplimit, - loottable_entries.mindrop, - loottable_entries.probability, - loottable.min_expansion, - loottable.max_expansion, - loottable.content_flags, - loottable.content_flags_disabled - FROM - loottable - LEFT JOIN loottable_entries ON loottable.id = loottable_entries.loottable_id - WHERE TRUE {} - ORDER BY - id - ), - ContentFilterCriteria::apply() - ); - - auto results = QueryDatabase(query); - if (!results.Success()) { - return; - } - - uint32 current_id = 0; - uint32 current_entry = 0; - - for (auto& row = results.begin(); row != results.end(); ++row) { - const uint32 id = Strings::ToUnsignedInt(row[0]); - if (id != current_id) { - if (current_id != 0) { - hash.insert( - current_id, - loot_table, - (sizeof(LootTable_Struct) + (sizeof(LootTableEntries_Struct) * lt->NumEntries))); - } - - memset(loot_table, 0, sizeof(LootTable_Struct) + (sizeof(LootTableEntries_Struct) * 128)); - current_entry = 0; - current_id = id; - lt->mincash = Strings::ToUnsignedInt(row[1]); - lt->maxcash = Strings::ToUnsignedInt(row[2]); - lt->avgcoin = Strings::ToUnsignedInt(row[3]); - - lt->content_flags.min_expansion = static_cast(Strings::ToInt(row[9])); - lt->content_flags.max_expansion = static_cast(Strings::ToInt(row[10])); - - strn0cpy(lt->content_flags.content_flags, row[11], sizeof(lt->content_flags.content_flags)); - strn0cpy(lt->content_flags.content_flags_disabled, row[12], sizeof(lt->content_flags.content_flags_disabled)); - } - - if (current_entry > 128) { - continue; - } - - if (!row[4]) { - continue; - } - - lt->Entries[current_entry].lootdrop_id = Strings::ToUnsignedInt(row[4]); - lt->Entries[current_entry].multiplier = static_cast(Strings::ToUnsignedInt(row[5])); - lt->Entries[current_entry].droplimit = static_cast(Strings::ToUnsignedInt(row[6])); - lt->Entries[current_entry].mindrop = static_cast(Strings::ToUnsignedInt(row[7])); - lt->Entries[current_entry].probability = Strings::ToFloat(row[8]); - - ++(lt->NumEntries); - ++current_entry; - } - - if (current_id != 0) { - hash.insert( - current_id, - loot_table, - (sizeof(LootTable_Struct) + (sizeof(LootTableEntries_Struct) * lt->NumEntries)) - ); - } - -} - -void SharedDatabase::LoadLootDrops(void *data, uint32 size) { - - EQ::FixedMemoryVariableHashSet hash(static_cast(data), size); - uint8 loot_drop[sizeof(LootDrop_Struct) + (sizeof(LootDropEntries_Struct) * 1260)]; - LootDrop_Struct *p_loot_drop_struct = reinterpret_cast(loot_drop); - - const std::string query = fmt::format( - SQL( - SELECT - lootdrop.id, - lootdrop_entries.item_id, - lootdrop_entries.item_charges, - lootdrop_entries.equip_item, - lootdrop_entries.chance, - lootdrop_entries.trivial_min_level, - lootdrop_entries.trivial_max_level, - lootdrop_entries.npc_min_level, - lootdrop_entries.npc_max_level, - lootdrop_entries.multiplier, - lootdrop.min_expansion, - lootdrop.max_expansion, - lootdrop.content_flags, - lootdrop.content_flags_disabled - FROM - lootdrop - JOIN lootdrop_entries ON lootdrop.id = lootdrop_entries.lootdrop_id - WHERE - TRUE {} - ORDER BY - lootdrop_id - ), - ContentFilterCriteria::apply() - ); - - auto results = QueryDatabase(query); - if (!results.Success()) { - return; - } - - uint32 current_id = 0; - uint32 current_entry = 0; - - for (auto& row = results.begin(); row != results.end(); ++row) { - const auto id = Strings::ToUnsignedInt(row[0]); - if (id != current_id) { - if (current_id != 0) { - hash.insert( - current_id, - loot_drop, - (sizeof(LootDrop_Struct) + (sizeof(LootDropEntries_Struct) * p_loot_drop_struct->NumEntries))); - } - - memset(loot_drop, 0, sizeof(LootDrop_Struct) + (sizeof(LootDropEntries_Struct) * 1260)); - current_entry = 0; - current_id = id; - - p_loot_drop_struct->content_flags.min_expansion = static_cast(Strings::ToInt(row[10])); - p_loot_drop_struct->content_flags.max_expansion = static_cast(Strings::ToUnsignedInt(row[11])); - - strn0cpy(p_loot_drop_struct->content_flags.content_flags, row[12], sizeof(p_loot_drop_struct->content_flags.content_flags)); - strn0cpy(p_loot_drop_struct->content_flags.content_flags_disabled, row[13], sizeof(p_loot_drop_struct->content_flags.content_flags_disabled)); - } - - if (current_entry >= 1260) { - continue; - } - - p_loot_drop_struct->Entries[current_entry].item_id = Strings::ToUnsignedInt(row[1]); - p_loot_drop_struct->Entries[current_entry].item_charges = static_cast(Strings::ToUnsignedInt(row[2])); - p_loot_drop_struct->Entries[current_entry].equip_item = static_cast(Strings::ToUnsignedInt(row[3])); - p_loot_drop_struct->Entries[current_entry].chance = Strings::ToFloat(row[4]); - p_loot_drop_struct->Entries[current_entry].trivial_min_level = static_cast(Strings::ToUnsignedInt(row[5])); - p_loot_drop_struct->Entries[current_entry].trivial_max_level = static_cast(Strings::ToUnsignedInt(row[6])); - p_loot_drop_struct->Entries[current_entry].npc_min_level = static_cast(Strings::ToUnsignedInt(row[7])); - p_loot_drop_struct->Entries[current_entry].npc_max_level = static_cast(Strings::ToUnsignedInt(row[8])); - p_loot_drop_struct->Entries[current_entry].multiplier = static_cast(Strings::ToUnsignedInt(row[9])); - - ++(p_loot_drop_struct->NumEntries); - ++current_entry; - } - - if(current_id != 0) - hash.insert(current_id, loot_drop, (sizeof(LootDrop_Struct) + (sizeof(LootDropEntries_Struct) * p_loot_drop_struct->NumEntries))); - -} - -bool SharedDatabase::LoadLoot(const std::string &prefix) { - loot_table_mmf.reset(nullptr); - loot_drop_mmf.reset(nullptr); - - try { - const auto Config = EQEmuConfig::get(); - EQ::IPCMutex mutex("loot"); - mutex.Lock(); - std::string file_name_lt = fmt::format("{}/{}{}", path.GetSharedMemoryPath(), prefix, std::string("loot_table")); - - loot_table_mmf = std::make_unique(file_name_lt); - loot_table_hash = std::make_unique>( - static_cast(loot_table_mmf->Get()), - loot_table_mmf->Size()); - - LogInfo("Loaded loot tables via shared memory"); - - std::string file_name_ld = fmt::format("{}/{}{}", path.GetSharedMemoryPath(), prefix, std::string("loot_drop")); - loot_drop_mmf = std::make_unique(file_name_ld); - loot_drop_hash = std::make_unique>( - static_cast(loot_drop_mmf->Get()), - loot_drop_mmf->Size()); - mutex.Unlock(); - } catch(std::exception &ex) { - LogError("Error loading loot: {}", ex.what()); - return false; - } - - return true; -} - -const LootTable_Struct* SharedDatabase::GetLootTable(uint32 loottable_id) const -{ - if(!loot_table_hash) - return nullptr; - - try { - if(loot_table_hash->exists(loottable_id)) { - return &loot_table_hash->at(loottable_id); - } - } catch(std::exception &ex) { - LogError("Could not get loot table: {}", ex.what()); - } - return nullptr; -} - -const LootDrop_Struct* SharedDatabase::GetLootDrop(uint32 lootdrop_id) const -{ - if(!loot_drop_hash) - return nullptr; - - try { - if(loot_drop_hash->exists(lootdrop_id)) { - return &loot_drop_hash->at(lootdrop_id); - } - } catch(std::exception &ex) { - LogError("Could not get loot drop: {}", ex.what()); - } - return nullptr; -} - void SharedDatabase::LoadCharacterInspectMessage(uint32 character_id, InspectMessage_Struct* message) { const std::string query = StringFormat("SELECT `inspect_message` FROM `character_inspect_messages` WHERE `id` = %u LIMIT 1", character_id); auto results = QueryDatabase(query); diff --git a/common/shareddb.h b/common/shareddb.h index 455f8288a..5103f5a58 100644 --- a/common/shareddb.h +++ b/common/shareddb.h @@ -41,8 +41,6 @@ struct PlayerProfile_Struct; struct SPDat_Spell_Struct; struct NPCFactionList; struct FactionAssociations; -struct LootTable_Struct; -struct LootDrop_Struct; namespace EQ @@ -164,17 +162,6 @@ public: uint32 GetSharedItemsCount() { return m_shared_items_count; } uint32 GetItemsCount(); - /** - * loot - */ - void GetLootTableInfo(uint32 &loot_table_count, uint32 &max_loot_table, uint32 &loot_table_entries); - void GetLootDropInfo(uint32 &loot_drop_count, uint32 &max_loot_drop, uint32 &loot_drop_entries); - void LoadLootTables(void *data, uint32 size); - void LoadLootDrops(void *data, uint32 size); - bool LoadLoot(const std::string &prefix); - const LootTable_Struct *GetLootTable(uint32 loottable_id) const; - const LootDrop_Struct *GetLootDrop(uint32 lootdrop_id) const; - /** * skills */ @@ -212,19 +199,15 @@ public: protected: - std::unique_ptr skill_caps_mmf; - std::unique_ptr items_mmf; - std::unique_ptr> items_hash; - std::unique_ptr faction_mmf; - std::unique_ptr> faction_hash; - std::unique_ptr faction_associations_mmf; - std::unique_ptr> faction_associations_hash; - std::unique_ptr loot_table_mmf; - std::unique_ptr> loot_table_hash; - std::unique_ptr loot_drop_mmf; - std::unique_ptr> loot_drop_hash; - std::unique_ptr base_data_mmf; - std::unique_ptr spells_mmf; + std::unique_ptr skill_caps_mmf; + std::unique_ptr items_mmf; + std::unique_ptr> items_hash; + std::unique_ptr faction_mmf; + std::unique_ptr> faction_hash; + std::unique_ptr faction_associations_mmf; + std::unique_ptr> faction_associations_hash; + std::unique_ptr base_data_mmf; + std::unique_ptr spells_mmf; public: void SetSharedItemsCount(uint32 shared_items_count); diff --git a/shared_memory/CMakeLists.txt b/shared_memory/CMakeLists.txt index 2332e40c6..2fdf314c6 100644 --- a/shared_memory/CMakeLists.txt +++ b/shared_memory/CMakeLists.txt @@ -3,7 +3,6 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.12) SET(shared_memory_sources base_data.cpp items.cpp - loot.cpp main.cpp spells.cpp skill_caps.cpp @@ -12,7 +11,6 @@ SET(shared_memory_sources SET(shared_memory_headers base_data.h items.h - loot.h spells.h skill_caps.h ) diff --git a/shared_memory/loot.cpp b/shared_memory/loot.cpp deleted file mode 100644 index a8ab94326..000000000 --- a/shared_memory/loot.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2013 EQEMu Development Team (http://eqemulator.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; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY except by those people which sell it, which - are required to give you total support for your newly bought product; - without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "loot.h" -#include "../common/global_define.h" -#include "../common/shareddb.h" -#include "../common/ipc_mutex.h" -#include "../common/memory_mapped_file.h" -#include "../common/eqemu_exception.h" -#include "../common/fixed_memory_variable_hash_set.h" -#include "../common/loottable.h" - -void LoadLoot(SharedDatabase *database, const std::string &prefix) { - EQ::IPCMutex mutex("loot"); - mutex.Lock(); - - uint32 loot_table_count, loot_table_max, loot_table_entries_count; - uint32 loot_drop_count, loot_drop_max, loot_drop_entries_count; - database->GetLootTableInfo(loot_table_count, loot_table_max, loot_table_entries_count); - database->GetLootDropInfo(loot_drop_count, loot_drop_max, loot_drop_entries_count); - - uint32 loot_table_size = (3 * sizeof(uint32)) + //header - ((loot_table_max + 1) * sizeof(uint32)) + //offset list - (loot_table_count * sizeof(LootTable_Struct)) + //loot table headers - (loot_table_entries_count * sizeof(LootTableEntries_Struct)); //number of loot table entries - - uint32 loot_drop_size = (3 * sizeof(uint32)) + //header - ((loot_drop_max + 1) * sizeof(uint32)) + //offset list - (loot_drop_count * sizeof(LootDrop_Struct)) + //loot table headers - (loot_drop_entries_count * sizeof(LootDropEntries_Struct)); //number of loot table entries - - auto Config = EQEmuConfig::get(); - std::string file_name_lt = Config->SharedMemDir + prefix + std::string("loot_table"); - std::string file_name_ld = Config->SharedMemDir + prefix + std::string("loot_drop"); - - EQ::MemoryMappedFile mmf_loot_table(file_name_lt, loot_table_size); - EQ::MemoryMappedFile mmf_loot_drop(file_name_ld, loot_drop_size); - mmf_loot_table.ZeroFile(); - mmf_loot_drop.ZeroFile(); - - EQ::FixedMemoryVariableHashSet loot_table_hash(reinterpret_cast(mmf_loot_table.Get()), - loot_table_size, loot_table_max); - - EQ::FixedMemoryVariableHashSet loot_drop_hash(reinterpret_cast(mmf_loot_drop.Get()), - loot_drop_size, loot_drop_max); - - database->LoadLootTables(mmf_loot_table.Get(), loot_table_max); - database->LoadLootDrops(mmf_loot_drop.Get(), loot_drop_max); - mutex.Unlock(); -} diff --git a/shared_memory/loot.h b/shared_memory/loot.h deleted file mode 100644 index 694bba7ca..000000000 --- a/shared_memory/loot.h +++ /dev/null @@ -1,28 +0,0 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2013 EQEMu Development Team (http://eqemulator.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; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY except by those people which sell it, which - are required to give you total support for your newly bought product; - without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __EQEMU_SHARED_MEMORY_LOOT_H -#define __EQEMU_SHARED_MEMORY_LOOT_H - -#include -#include "../common/eqemu_config.h" - -class SharedDatabase; -void LoadLoot(SharedDatabase *database, const std::string &prefix); - -#endif diff --git a/shared_memory/main.cpp b/shared_memory/main.cpp index acbed503a..12022ac21 100644 --- a/shared_memory/main.cpp +++ b/shared_memory/main.cpp @@ -28,7 +28,6 @@ #include "../common/eqemu_exception.h" #include "../common/strings.h" #include "items.h" -#include "loot.h" #include "skill_caps.h" #include "spells.h" #include "base_data.h" @@ -183,7 +182,6 @@ int main(int argc, char **argv) bool load_all = true; bool load_items = false; - bool load_loot = false; bool load_skill_caps = false; bool load_spells = false; bool load_bd = false; @@ -205,13 +203,6 @@ int main(int argc, char **argv) } break; - case 'l': - if (strcasecmp("loot", argv[i]) == 0) { - load_loot = true; - load_all = false; - } - break; - case 's': if (strcasecmp("skill_caps", argv[i]) == 0) { load_skill_caps = true; @@ -252,16 +243,6 @@ int main(int argc, char **argv) } } - if (load_all || load_loot) { - LogInfo("Loading loot"); - try { - LoadLoot(&content_db, hotfix_name); - } catch (std::exception &ex) { - LogError("{}", ex.what()); - return 1; - } - } - if (load_all || load_skill_caps) { LogInfo("Loading skill caps"); try { diff --git a/world/eqemu_api_world_data_service.cpp b/world/eqemu_api_world_data_service.cpp index db26feed4..ba01936ed 100644 --- a/world/eqemu_api_world_data_service.cpp +++ b/world/eqemu_api_world_data_service.cpp @@ -145,6 +145,7 @@ std::vector reload_types = { Reload{.command = "ground_spawns", .opcode = ServerOP_ReloadGroundSpawns, .desc = "Ground Spawns"}, Reload{.command = "level_mods", .opcode = ServerOP_ReloadLevelEXPMods, .desc = "Level Mods"}, Reload{.command = "logs", .opcode = ServerOP_ReloadLogs, .desc = "Log Settings"}, + Reload{.command = "loot", .opcode = ServerOP_ReloadLoot, .desc = "Loot"}, Reload{.command = "merchants", .opcode = ServerOP_ReloadMerchants, .desc = "Merchants"}, Reload{.command = "npc_emotes", .opcode = ServerOP_ReloadNPCEmotes, .desc = "NPC Emotes"}, Reload{.command = "objects", .opcode = ServerOP_ReloadObjects, .desc = "Objects"}, diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 1799101d0..380cac6da 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1404,6 +1404,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_ReloadWorld: case ServerOP_ReloadZonePoints: case ServerOP_ReloadZoneData: + case ServerOP_ReloadLoot: case ServerOP_RezzPlayerAccept: case ServerOP_SpawnStatusChange: case ServerOP_UpdateSpawn: diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 2c4891c61..759adf0a1 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -48,7 +48,7 @@ SET(zone_sources heal_rotation.cpp horse.cpp inventory.cpp - loottables.cpp + loot.cpp lua_bot.cpp lua_bit.cpp lua_corpse.cpp @@ -87,7 +87,7 @@ SET(zone_sources hate_list.cpp horse.cpp inventory.cpp - loottables.cpp + loot.cpp main.cpp map.cpp merc.cpp diff --git a/zone/attack.cpp b/zone/attack.cpp index 15b6bdc0c..f05fef3ea 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -2831,7 +2831,7 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy corpse = new Corpse( this, - &itemlist, + &m_loot_items, GetNPCTypeID(), &NPCTypedata, ( diff --git a/zone/bot_commands/pickpocket.cpp b/zone/bot_commands/pickpocket.cpp index fe6715e2d..5aec32383 100644 --- a/zone/bot_commands/pickpocket.cpp +++ b/zone/bot_commands/pickpocket.cpp @@ -84,7 +84,7 @@ void bot_command_pickpocket(Client *c, const Seperator *sep) // Steal item while (steal_item) { std::vector> loot_selection; // - for (auto item_iter: target_npc->itemlist) { + for (auto item_iter: target_npc->GetLootItems()) { if (!item_iter || !item_iter->item_id) { continue; } diff --git a/zone/client.cpp b/zone/client.cpp index badfcaefd..5b11803ff 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9099,6 +9099,7 @@ void Client::ShowDevToolsMenu() menu_reload_four += Saylink::Silent("#reload logs", "Level Based Experience Modifiers"); menu_reload_four += " | " + Saylink::Silent("#reload logs", "Log Settings"); + menu_reload_four += " | " + Saylink::Silent("#reload Loot", "Loot"); menu_reload_five += Saylink::Silent("#reload merchants", "Merchants"); menu_reload_five += " | " + Saylink::Silent("#reload npc_emotes", "NPC Emotes"); @@ -10667,7 +10668,7 @@ std::vector Client::GetPartyMembers() return clients_to_update; } -void Client::SummonBaggedItems(uint32 bag_item_id, const std::vector& bag_items) +void Client::SummonBaggedItems(uint32 bag_item_id, const std::vector& bag_items) { if (bag_items.empty()) { @@ -11118,6 +11119,16 @@ void Client::SendReloadCommandMessages() { ).c_str() ); + auto loot_link = Saylink::Silent("#reload loot"); + + Message( + Chat::White, + fmt::format( + "Usage: {} - Reloads Loot globally", + loot_link + ).c_str() + ); + auto merchants_link = Saylink::Silent("#reload merchants"); Message( diff --git a/zone/client.h b/zone/client.h index 3871adf28..aaad0657e 100644 --- a/zone/client.h +++ b/zone/client.h @@ -986,11 +986,11 @@ public: bool SwapItem(MoveItem_Struct* move_in); void SwapItemResync(MoveItem_Struct* move_slots); void QSSwapItemAuditor(MoveItem_Struct* move_in, bool postaction_call = false); - void PutLootInInventory(int16 slot_id, const EQ::ItemInstance &inst, ServerLootItem_Struct** bag_item_data = 0); - bool AutoPutLootInInventory(EQ::ItemInstance& inst, bool try_worn = false, bool try_cursor = true, ServerLootItem_Struct** bag_item_data = 0); + void PutLootInInventory(int16 slot_id, const EQ::ItemInstance &inst, LootItem** bag_item_data = 0); + bool AutoPutLootInInventory(EQ::ItemInstance& inst, bool try_worn = false, bool try_cursor = true, LootItem** bag_item_data = 0); bool SummonItem(uint32 item_id, int16 charges = -1, uint32 aug1 = 0, uint32 aug2 = 0, uint32 aug3 = 0, uint32 aug4 = 0, uint32 aug5 = 0, uint32 aug6 = 0, bool attuned = false, uint16 to_slot = EQ::invslot::slotCursor, uint32 ornament_icon = 0, uint32 ornament_idfile = 0, uint32 ornament_hero_model = 0); void SummonItemIntoInventory(uint32 item_id, int16 charges = -1, uint32 aug1 = 0, uint32 aug2 = 0, uint32 aug3 = 0, uint32 aug4 = 0, uint32 aug5 = 0, uint32 aug6 = 0, bool is_attuned = false); - void SummonBaggedItems(uint32 bag_item_id, const std::vector& bag_items); + void SummonBaggedItems(uint32 bag_item_id, const std::vector& bag_items); void SetStats(uint8 type,int16 set_val); void IncStats(uint8 type,int16 increase_val); void DropItem(int16 slot_id, bool recurse = true); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index d39cf0886..8bf6164ce 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -10061,7 +10061,7 @@ void Client::Handle_OP_LootItem(const EQApplicationPacket *app) return; } - entity->CastToCorpse()->LootItem(this, app); + entity->CastToCorpse()->LootCorpseItem(this, app); } void Client::Handle_OP_LootRequest(const EQApplicationPacket *app) diff --git a/zone/common.h b/zone/common.h index 39ff112d1..d5b2f9a95 100644 --- a/zone/common.h +++ b/zone/common.h @@ -870,5 +870,6 @@ struct DataBucketCache uint32_t bucket_expires; }; + #endif diff --git a/zone/corpse.cpp b/zone/corpse.cpp index e65d2f876..85f84db60 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -81,9 +81,9 @@ Corpse* Corpse::LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std: return nullptr; } - ItemList itemlist; + LootItems itemlist; for (auto &item: ce.items) { - auto tmp = new ServerLootItem_Struct; + auto tmp = new LootItem; tmp->equip_slot = item.equip_slot; tmp->item_id = item.item_id; @@ -159,7 +159,7 @@ Corpse* Corpse::LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std: Corpse::Corpse( NPC *in_npc, - ItemList *in_itemlist, + LootItems *in_itemlist, uint32 in_npctypeid, const NPCType **in_npctypedata, uint32 in_decaytime @@ -561,7 +561,7 @@ void Corpse::MoveItemToCorpse(Client *client, EQ::ItemInstance *inst, int16 equi } // To be called from LoadFromDBData -Corpse::Corpse(uint32 in_dbid, uint32 in_charid, const char* in_charname, ItemList* in_itemlist, uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_plat, const glm::vec4& position, float in_size, uint8 in_gender, uint16 in_race, uint8 in_class, uint8 in_deity, uint8 in_level, uint8 in_texture, uint8 in_helmtexture,uint32 in_rezexp, bool wasAtGraveyard) : Mob( +Corpse::Corpse(uint32 in_dbid, uint32 in_charid, const char* in_charname, LootItems* in_itemlist, uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_plat, const glm::vec4& position, float in_size, uint8 in_gender, uint16 in_race, uint8 in_class, uint8 in_deity, uint8 in_level, uint8 in_texture, uint8 in_helmtexture, uint32 in_rezexp, bool wasAtGraveyard) : Mob( "Unnamed_Corpse", // in_name "", // in_lastname 0, // in_cur_hp @@ -619,11 +619,11 @@ Corpse::Corpse(uint32 in_dbid, uint32 in_charid, const char* in_charname, ItemLi 0, // in_heroic_strikethrough false // in_keeps_sold_items ), - corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)), - corpse_rez_timer(RuleI(Character, CorpseResTimeMS)), - corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)), - corpse_graveyard_timer(RuleI(Zone, GraveyardTimeMS)), - loot_cooldown_timer(10) + corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)), + corpse_rez_timer(RuleI(Character, CorpseResTimeMS)), + corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)), + corpse_graveyard_timer(RuleI(Zone, GraveyardTimeMS)), + loot_cooldown_timer(10) { LoadPlayerCorpseDecayTime(in_dbid); @@ -667,11 +667,11 @@ Corpse::~Corpse() { if (is_player_corpse && !(player_corpse_depop && corpse_db_id == 0)) { Save(); } - ItemList::iterator cur,end; + LootItems::iterator cur, end; cur = itemlist.begin(); end = itemlist.end(); for(; cur != end; ++cur) { - ServerLootItem_Struct* item = *cur; + LootItem * item = *cur; safe_delete(item); } itemlist.clear(); @@ -831,7 +831,7 @@ void Corpse::AddItem(uint32 itemnum, is_corpse_changed = true; - auto item = new ServerLootItem_Struct; + auto item = new LootItem; item->item_id = itemnum; item->charges = charges; @@ -853,10 +853,10 @@ void Corpse::AddItem(uint32 itemnum, UpdateEquipmentLight(); } -ServerLootItem_Struct* Corpse::GetItem(uint16 lootslot, ServerLootItem_Struct** bag_item_data) { - ServerLootItem_Struct *sitem = nullptr, *sitem2 = nullptr; +LootItem* Corpse::GetItem(uint16 lootslot, LootItem** bag_item_data) { + LootItem *sitem = nullptr, *sitem2 = nullptr; - ItemList::iterator cur,end; + LootItems::iterator cur, end; cur = itemlist.begin(); end = itemlist.end(); for(; cur != end; ++cur) { @@ -883,11 +883,11 @@ ServerLootItem_Struct* Corpse::GetItem(uint16 lootslot, ServerLootItem_Struct** } uint32 Corpse::GetWornItem(int16 equipSlot) const { - ItemList::const_iterator cur,end; + LootItems::const_iterator cur, end; cur = itemlist.begin(); end = itemlist.end(); for(; cur != end; ++cur) { - ServerLootItem_Struct* item = *cur; + LootItem * item = *cur; if (item->equip_slot == equipSlot) { return item->item_id; } @@ -900,11 +900,11 @@ void Corpse::RemoveItem(uint16 lootslot) { if (lootslot == 0xFFFF) return; - ItemList::iterator cur,end; + LootItems::iterator cur, end; cur = itemlist.begin(); end = itemlist.end(); for (; cur != end; ++cur) { - ServerLootItem_Struct* sitem = *cur; + LootItem * sitem = *cur; if (sitem->lootslot == lootslot) { RemoveItem(sitem); return; @@ -912,7 +912,7 @@ void Corpse::RemoveItem(uint16 lootslot) { } } -void Corpse::RemoveItem(ServerLootItem_Struct* item_data) +void Corpse::RemoveItem(LootItem* item_data) { for (auto iter = itemlist.begin(); iter != itemlist.end(); ++iter) { auto sitem = *iter; @@ -945,7 +945,7 @@ void Corpse::RemoveItemByID(uint32 item_id, int quantity) { int removed_count = 0; for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { - ServerLootItem_Struct* sitem = *current_item; + LootItem * sitem = *current_item; if (removed_count == quantity) { break; } @@ -1370,7 +1370,7 @@ void Corpse::MakeLootRequestPackets(Client* client, const EQApplicationPacket* a SendLootReqErrorPacket(client, LootResponse::LootAll); } -void Corpse::LootItem(Client *client, const EQApplicationPacket *app) +void Corpse::LootCorpseItem(Client *client, const EQApplicationPacket *app) { if (!client) { return; @@ -1444,8 +1444,8 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app) } const EQ::ItemData *item = nullptr; - EQ::ItemInstance *inst = nullptr; - ServerLootItem_Struct *item_data = nullptr, *bag_item_data[10] = {}; + EQ::ItemInstance *inst = nullptr; + LootItem *item_data = nullptr, *bag_item_data[10] = {}; memset(bag_item_data, 0, sizeof(bag_item_data)); if (GetPlayerKillItem() > 1) { @@ -1805,7 +1805,7 @@ bool Corpse::HasItem(uint32 item_id) { } for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { - ServerLootItem_Struct* loot_item = *current_item; + LootItem * loot_item = *current_item; if (!loot_item) { LogError("Corpse::HasItem() - ItemList error, null item"); continue; @@ -1830,7 +1830,7 @@ uint16 Corpse::CountItem(uint32 item_id) { } for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { - ServerLootItem_Struct* loot_item = *current_item; + LootItem * loot_item = *current_item; if (!loot_item) { LogError("Corpse::CountItem() - ItemList error, null item"); continue; @@ -1850,7 +1850,7 @@ uint16 Corpse::CountItem(uint32 item_id) { uint32 Corpse::GetItemIDBySlot(uint16 loot_slot) { for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { - ServerLootItem_Struct* loot_item = *current_item; + LootItem * loot_item = *current_item; if (loot_item->lootslot == loot_slot) { return loot_item->item_id; } @@ -1858,9 +1858,9 @@ uint32 Corpse::GetItemIDBySlot(uint16 loot_slot) { return 0; } -uint16 Corpse::GetFirstSlotByItemID(uint32 item_id) { +uint16 Corpse::GetFirstLootSlotByItemID(uint32 item_id) { for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { - ServerLootItem_Struct* loot_item = *current_item; + LootItem * loot_item = *current_item; if (loot_item->item_id == item_id) { return loot_item->lootslot; } @@ -2098,7 +2098,7 @@ bool Corpse::MovePlayerCorpseToNonInstance() std::vector Corpse::GetLootList() { std::vector corpse_items; for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { - ServerLootItem_Struct* loot_item = *current_item; + LootItem * loot_item = *current_item; if (!loot_item) { LogError("Corpse::GetLootList() - ItemList error, null item"); continue; diff --git a/zone/corpse.h b/zone/corpse.h index c9f7b4652..4b7c7bb40 100644 --- a/zone/corpse.h +++ b/zone/corpse.h @@ -21,6 +21,7 @@ #include "mob.h" #include "client.h" +#include "../common/loot.h" class EQApplicationPacket; class Group; @@ -43,9 +44,9 @@ class Corpse : public Mob { static void SendEndLootErrorPacket(Client* client); static void SendLootReqErrorPacket(Client* client, LootResponse response = LootResponse::NotAtThisTime); - Corpse(NPC* in_npc, ItemList* in_itemlist, uint32 in_npctypeid, const NPCType** in_npctypedata, uint32 in_decaytime = 600000); + Corpse(NPC* in_npc, LootItems* in_itemlist, uint32 in_npctypeid, const NPCType** in_npctypedata, uint32 in_decaytime = 600000); Corpse(Client* client, int32 in_rezexp); - Corpse(uint32 in_corpseid, uint32 in_charid, const char* in_charname, ItemList* in_itemlist, uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_plat, const glm::vec4& position, float in_size, uint8 in_gender, uint16 in_race, uint8 in_class, uint8 in_deity, uint8 in_level, uint8 in_texture, uint8 in_helmtexture, uint32 in_rezexp, bool wasAtGraveyard = false); + Corpse(uint32 in_corpseid, uint32 in_charid, const char* in_charname, LootItems* in_itemlist, uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_plat, const glm::vec4& position, float in_size, uint8 in_gender, uint16 in_race, uint8 in_class, uint8 in_deity, uint8 in_level, uint8 in_texture, uint8 in_helmtexture, uint32 in_rezexp, bool wasAtGraveyard = false); ~Corpse(); static Corpse* LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std::string in_charname, const glm::vec4& position, std::string time_of_death, bool rezzed, bool was_at_graveyard, uint32 guild_consent_id); @@ -93,35 +94,37 @@ class Corpse : public Mob { void LoadPlayerCorpseDecayTime(uint32 dbid); /* Corpse: Items */ - uint32 GetWornItem(int16 equipSlot) const; - ServerLootItem_Struct* GetItem(uint16 lootslot, ServerLootItem_Struct** bag_item_data = 0); - void SetPlayerKillItemID(int32 pk_item_id) { player_kill_item = pk_item_id; } - int32 GetPlayerKillItem() { return player_kill_item; } - void RemoveItem(uint16 lootslot); - void RemoveItem(ServerLootItem_Struct* item_data); - void RemoveItemByID(uint32 item_id, int quantity = 1); - void AddItem(uint32 itemnum, - uint16 charges, - int16 slot = 0, - uint32 aug1 = 0, - uint32 aug2 = 0, - uint32 aug3 = 0, - uint32 aug4 = 0, - uint32 aug5 = 0, - uint32 aug6 = 0, - bool attuned = false, - const std::string &custom_data = std::string(), + uint32 GetWornItem(int16 equipSlot) const; + LootItem *GetItem(uint16 lootslot, LootItem **bag_item_data = 0); + void SetPlayerKillItemID(int32 pk_item_id) { player_kill_item = pk_item_id; } + int32 GetPlayerKillItem() { return player_kill_item; } + void RemoveItem(uint16 lootslot); + void RemoveItem(LootItem *item_data); + void RemoveItemByID(uint32 item_id, int quantity = 1); + void AddItem( + uint32 itemnum, + uint16 charges, + int16 slot = 0, + uint32 aug1 = 0, + uint32 aug2 = 0, + uint32 aug3 = 0, + uint32 aug4 = 0, + uint32 aug5 = 0, + uint32 aug6 = 0, + bool attuned = false, + const std::string &custom_data = std::string(), uint32 ornamenticon = 0, uint32 ornamentidfile = 0, - uint32 ornament_hero_model = 0); + uint32 ornament_hero_model = 0 + ); /* Corpse: Coin */ - void SetCash(uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_platinum); - void RemoveCash(); - uint32 GetCopper() { return copper; } - uint32 GetSilver() { return silver; } - uint32 GetGold() { return gold; } - uint32 GetPlatinum() { return platinum; } + void SetCash(uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_platinum); + void RemoveCash(); + uint32 GetCopper() { return copper; } + uint32 GetSilver() { return silver; } + uint32 GetGold() { return gold; } + uint32 GetPlatinum() { return platinum; } /* Corpse: Resurrection */ bool IsRezzed() { return rez; } @@ -134,9 +137,9 @@ class Corpse : public Mob { bool HasItem(uint32 item_id); uint16 CountItem(uint32 item_id); uint32 GetItemIDBySlot(uint16 loot_slot); - uint16 GetFirstSlotByItemID(uint32 item_id); + uint16 GetFirstLootSlotByItemID(uint32 item_id); std::vector GetLootList(); - void LootItem(Client* client, const EQApplicationPacket* app); + void LootCorpseItem(Client* client, const EQApplicationPacket* app); void EndLoot(Client* client, const EQApplicationPacket* app); void MakeLootRequestPackets(Client* client, const EQApplicationPacket* app); void AllowPlayerLoot(Mob *them, uint8 slot); @@ -167,26 +170,26 @@ protected: void MoveItemToCorpse(Client *client, EQ::ItemInstance *inst, int16 equipSlot, std::list &removedList); private: - bool is_player_corpse; /* Determines if Player Corpse or not */ - bool is_corpse_changed; /* Determines if corpse has changed or not */ - bool is_locked; /* Determines if corpse is locked */ - int32 player_kill_item; /* Determines if Player Kill Item */ - uint32 corpse_db_id; /* Corpse Database ID (Player Corpse) */ - uint32 char_id; /* Character ID */ - uint32 consented_group_id = 0; - uint32 consented_raid_id = 0; - uint32 consented_guild_id = 0; - ItemList itemlist; /* Internal Item list used for corpses */ - uint32 copper; - uint32 silver; - uint32 gold; - uint32 platinum; - bool player_corpse_depop; /* Sets up Corpse::Process to depop the player corpse */ - uint32 being_looted_by; /* Determines what the corpse is being looted by internally for logic */ - uint32 rez_experience; /* Amount of experience that the corpse would rez for */ - bool rez; - bool become_npc; - int allowed_looters[MAX_LOOTERS]; /* People allowed to loot the corpse, character id */ + bool is_player_corpse; /* Determines if Player Corpse or not */ + bool is_corpse_changed; /* Determines if corpse has changed or not */ + bool is_locked; /* Determines if corpse is locked */ + int32 player_kill_item; /* Determines if Player Kill Item */ + uint32 corpse_db_id; /* Corpse Database ID (Player Corpse) */ + uint32 char_id; /* Character ID */ + uint32 consented_group_id = 0; + uint32 consented_raid_id = 0; + uint32 consented_guild_id = 0; + LootItems itemlist; /* Internal Item list used for corpses */ + uint32 copper; + uint32 silver; + uint32 gold; + uint32 platinum; + bool player_corpse_depop; /* Sets up Corpse::Process to depop the player corpse */ + uint32 being_looted_by; /* Determines what the corpse is being looted by internally for logic */ + uint32 rez_experience; /* Amount of experience that the corpse would rez for */ + bool rez; + bool become_npc; + int allowed_looters[MAX_LOOTERS]; /* People allowed to loot the corpse, character id */ Timer corpse_decay_timer; /* The amount of time in millseconds in which a corpse will take to decay (Depop/Poof) */ Timer corpse_rez_timer; /* The amount of time in millseconds in which a corpse can be rezzed */ Timer corpse_delay_timer; diff --git a/zone/gm_commands/lootsim.cpp b/zone/gm_commands/lootsim.cpp index e7d28b7fc..aed6b6eae 100755 --- a/zone/gm_commands/lootsim.cpp +++ b/zone/gm_commands/lootsim.cpp @@ -51,36 +51,37 @@ void command_lootsim(Client *c, const Seperator *sep) c->SendChatLineBreak(); // npc level loot table - auto loot_table = database.GetLootTable(loottable_id); + auto loot_table = zone->GetLootTable(loottable_id); if (!loot_table) { c->Message(Chat::Red, "Loot table not found"); return; } - for (uint32 i = 0; i < loot_table->NumEntries; i++) { - auto le = loot_table->Entries[i]; + auto le = zone->GetLootTableEntries(loottable_id); + // translate above for loop using loot_table_entries + for (auto &e: le) { c->Message( Chat::White, fmt::format( "# Lootdrop ID [{}] drop_limit [{}] min_drop [{}] mult [{}] probability [{}]", - le.lootdrop_id, - le.droplimit, - le.mindrop, - le.multiplier, - le.probability + e.lootdrop_id, + e.droplimit, + e.mindrop, + e.multiplier, + e.probability ).c_str() ); - auto loot_drop = database.GetLootDrop(le.lootdrop_id); - if (!loot_drop) { + auto loot_drop = zone->GetLootdrop(e.lootdrop_id); + if (!loot_drop.id) { continue; } - for (uint32 ei = 0; ei < loot_drop->NumEntries; ei++) { - auto e = loot_drop->Entries[ei]; - int rolled_count = npc->GetRolledItemCount(e.item_id); - const EQ::ItemData *item = database.GetItem(e.item_id); + auto loot_drop_entries = zone->GetLootdropEntries(e.lootdrop_id); + for (auto &f: loot_drop_entries) { + int rolled_count = npc->GetRolledItemCount(f.item_id); + const EQ::ItemData *item = database.GetItem(f.item_id); EQ::SayLinkEngine linker; linker.SetLinkType(EQ::saylink::SayLinkItemData); @@ -91,10 +92,10 @@ void command_lootsim(Client *c, const Seperator *sep) c->Message( Chat::White, fmt::format( - "-- [{}] item_id [{}] chance [{}] rolled_count [{}] ({:.2f}%) name [{}]", - ei, - e.item_id, - e.chance, + "-- lootdrop_id [{}] item_id [{}] chance [{}] rolled_count [{}] ({:.2f}%) name [{}]", + f.lootdrop_id, + f.item_id, + f.chance, rolled_count, rolled_percentage, linker.GenerateLink() @@ -103,7 +104,6 @@ void command_lootsim(Client *c, const Seperator *sep) } } - // global loot auto tables = zone->GetGlobalLootTables(npc); if (!tables.empty()) { @@ -116,36 +116,37 @@ void command_lootsim(Client *c, const Seperator *sep) c->Message(Chat::White, fmt::format("# Global Loot Table ID [{}]", id).c_str()); c->SendChatLineBreak(); - loot_table = database.GetLootTable(id); + loot_table = zone->GetLootTable(loottable_id); if (!loot_table) { c->Message(Chat::Red, fmt::format("Global Loot table not found [{}]", id).c_str()); continue; } - for (uint32 i = 0; i < loot_table->NumEntries; i++) { - auto le = loot_table->Entries[i]; + le = zone->GetLootTableEntries(loottable_id); + // translate above for loop using loot_table_entries + for (auto &e: le) { c->Message( Chat::White, fmt::format( "# Lootdrop ID [{}] drop_limit [{}] min_drop [{}] mult [{}] probability [{}]", - le.lootdrop_id, - le.droplimit, - le.mindrop, - le.multiplier, - le.probability + e.lootdrop_id, + e.droplimit, + e.mindrop, + e.multiplier, + e.probability ).c_str() ); - auto loot_drop = database.GetLootDrop(le.lootdrop_id); - if (!loot_drop) { + auto loot_drop = zone->GetLootdrop(e.lootdrop_id); + if (!loot_drop.id) { continue; } - for (uint32 ei = 0; ei < loot_drop->NumEntries; ei++) { - auto e = loot_drop->Entries[ei]; - int rolled_count = npc->GetRolledItemCount(e.item_id); - const EQ::ItemData *item = database.GetItem(e.item_id); + auto loot_drop_entries = zone->GetLootdropEntries(e.lootdrop_id); + for (auto &f: loot_drop_entries) { + int rolled_count = npc->GetRolledItemCount(f.item_id); + const EQ::ItemData *item = database.GetItem(f.item_id); EQ::SayLinkEngine linker; linker.SetLinkType(EQ::saylink::SayLinkItemData); @@ -156,10 +157,10 @@ void command_lootsim(Client *c, const Seperator *sep) c->Message( Chat::White, fmt::format( - "-- [{}] item_id [{}] chance [{}] rolled_count [{}] ({:.2f}%) name [{}]", - ei, - e.item_id, - e.chance, + "-- lootdrop_id [{}] item_id [{}] chance [{}] rolled_count [{}] ({:.2f}%) name [{}]", + f.lootdrop_id, + f.item_id, + f.chance, rolled_count, rolled_percentage, linker.GenerateLink() diff --git a/zone/gm_commands/npcloot.cpp b/zone/gm_commands/npcloot.cpp index 2bb8873bb..55ae48faf 100755 --- a/zone/gm_commands/npcloot.cpp +++ b/zone/gm_commands/npcloot.cpp @@ -118,7 +118,7 @@ void command_npcloot(Client *c, const Seperator *sep) uint16 gold = sep->IsNumber(3) ? EQ::Clamp(Strings::ToInt(sep->arg[3]), 0, 65535) : 0; uint16 silver = sep->IsNumber(4) ? EQ::Clamp(Strings::ToInt(sep->arg[4]), 0, 65535) : 0; uint16 copper = sep->IsNumber(5) ? EQ::Clamp(Strings::ToInt(sep->arg[5]), 0, 65535) : 0; - target->AddCash( + target->AddLootCash( copper, silver, gold, diff --git a/zone/gm_commands/reload.cpp b/zone/gm_commands/reload.cpp index b07f9a9da..ae1df2e06 100644 --- a/zone/gm_commands/reload.cpp +++ b/zone/gm_commands/reload.cpp @@ -25,6 +25,7 @@ void command_reload(Client *c, const Seperator *sep) bool is_ground_spawns = !strcasecmp(sep->arg[1], "ground_spawns"); bool is_level_mods = !strcasecmp(sep->arg[1], "level_mods"); bool is_logs = !strcasecmp(sep->arg[1], "logs") || is_logs_reload_alias; + bool is_loot = !strcasecmp(sep->arg[1], "loot"); bool is_merchants = !strcasecmp(sep->arg[1], "merchants"); bool is_npc_emotes = !strcasecmp(sep->arg[1], "npc_emotes"); bool is_objects = !strcasecmp(sep->arg[1], "objects"); @@ -55,6 +56,7 @@ void command_reload(Client *c, const Seperator *sep) !is_ground_spawns && !is_level_mods && !is_logs && + !is_loot && !is_merchants && !is_npc_emotes && !is_objects && @@ -119,6 +121,9 @@ void command_reload(Client *c, const Seperator *sep) } else if (is_logs) { c->Message(Chat::White, "Attempting to reload Log Settings globally."); pack = new ServerPacket(ServerOP_ReloadLogs, 0); + } else if (is_loot) { + c->Message(Chat::White, "Attempting to reload Loot globally."); + pack = new ServerPacket(ServerOP_ReloadLoot, 0); } else if (is_merchants) { c->Message(Chat::White, "Attempting to reload Merchants globally."); pack = new ServerPacket(ServerOP_ReloadMerchants, 0); diff --git a/zone/gm_commands/show/zone_loot.cpp b/zone/gm_commands/show/zone_loot.cpp index 961ff9876..6a4beb063 100644 --- a/zone/gm_commands/show/zone_loot.cpp +++ b/zone/gm_commands/show/zone_loot.cpp @@ -4,13 +4,13 @@ void ShowZoneLoot(Client *c, const Seperator *sep) { const uint32 search_item_id = sep->IsNumber(2) ? Strings::ToUnsignedInt(sep->arg[2]) : 0; - std::vector> v; + std::vector> v; uint32 loot_count = 0; uint32 loot_number = 1; for (auto npc_entity: entity_list.GetNPCList()) { - auto il = npc_entity.second->GetItemList(); + auto il = npc_entity.second->GetLootItems(); v.emplace_back(std::make_pair(npc_entity.second, il)); } diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 928850fa6..4962b8a5c 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -1184,7 +1184,7 @@ bool Client::PutItemInInventory(int16 slot_id, const EQ::ItemInstance& inst, boo // a lot of wasted checks and calls coded above... } -void Client::PutLootInInventory(int16 slot_id, const EQ::ItemInstance &inst, ServerLootItem_Struct** bag_item_data) +void Client::PutLootInInventory(int16 slot_id, const EQ::ItemInstance &inst, LootItem** bag_item_data) { LogInventory("Putting loot item [{}] ([{}]) into slot [{}]", inst.GetItem()->Name, inst.GetItem()->ID, slot_id); @@ -1296,7 +1296,7 @@ bool Client::TryStacking(EQ::ItemInstance* item, uint8 type, bool try_worn, bool // Locate an available space in inventory to place an item // and then put the item there // The change will be saved to the database -bool Client::AutoPutLootInInventory(EQ::ItemInstance& inst, bool try_worn, bool try_cursor, ServerLootItem_Struct** bag_item_data) +bool Client::AutoPutLootInInventory(EQ::ItemInstance& inst, bool try_worn, bool try_cursor, LootItem** bag_item_data) { // #1: Try to auto equip if (try_worn && inst.IsEquipable(GetBaseRace(), GetClass()) && inst.GetItem()->ReqLevel <= level && (!inst.GetItem()->Attuneable || inst.IsAttuned()) && inst.GetItem()->ItemType != EQ::item::ItemTypeAugmentation) { diff --git a/zone/loot.cpp b/zone/loot.cpp new file mode 100644 index 000000000..670a88c90 --- /dev/null +++ b/zone/loot.cpp @@ -0,0 +1,912 @@ +#include "../common/global_define.h" +#include "../common/data_verification.h" + +#include "../common/loot.h" +#include "client.h" +#include "entity.h" +#include "mob.h" +#include "npc.h" +#include "zonedb.h" +#include "global_loot_manager.h" +#include "../common/repositories/criteria/content_filter_criteria.h" +#include "../common/repositories/global_loot_repository.h" +#include "quest_parser_collection.h" + +#ifdef _WINDOWS +#define snprintf _snprintf +#endif + +void NPC::AddLootTable(uint32 loottable_id, bool is_global) +{ + // check if it's a GM spawn + if (!npctype_id) { + return; + } + + if (!is_global) { + m_loot_copper = 0; + m_loot_silver = 0; + m_loot_gold = 0; + m_loot_platinum = 0; + } + + zone->LoadLootTable(loottable_id); + + const auto *l = zone->GetLootTable(loottable_id); + if (!l) { + return; + } + + auto content_flags = ContentFlags{ + .min_expansion = l->min_expansion, + .max_expansion = l->max_expansion, + .content_flags = l->content_flags, + .content_flags_disabled = l->content_flags_disabled + }; + + if (!content_service.DoesPassContentFiltering(content_flags)) { + return; + } + + uint32 min_cash = l->mincash; + uint32 max_cash = l->maxcash; + if (min_cash > max_cash) { + const uint32 t = min_cash; + min_cash = max_cash; + max_cash = t; + } + + uint32 cash = 0; + if (!is_global) { + if (max_cash > 0 && l->avgcoin > 0 && EQ::ValueWithin(l->avgcoin, min_cash, max_cash)) { + const float upper_chance = static_cast(l->avgcoin - min_cash) / + static_cast(max_cash - min_cash); + const float avg_cash_roll = static_cast(zone->random.Real(0.0, 1.0)); + + if (avg_cash_roll < upper_chance) { + cash = zone->random.Int(l->avgcoin, max_cash); + } + else { + cash = zone->random.Int(min_cash, l->avgcoin); + } + } + else { + cash = zone->random.Int(min_cash, max_cash); + } + } + + if (cash != 0) { + m_loot_platinum = cash / 1000; + cash -= m_loot_platinum * 1000; + + m_loot_gold = cash / 100; + cash -= m_loot_gold * 100; + + m_loot_silver = cash / 10; + cash -= m_loot_silver * 10; + + m_loot_copper = cash; + } + + const uint32 global_loot_multiplier = RuleI(Zone, GlobalLootMultiplier); + for (auto <e: zone->GetLootTableEntries(loottable_id)) { + for (uint32 k = 1; k <= (lte.multiplier * global_loot_multiplier); k++) { + const uint8 drop_limit = lte.droplimit; + const uint8 minimum_drop = lte.mindrop; + const float probability = lte.probability; + + float drop_chance = 0.0f; + if (EQ::ValueWithin(probability, 0.0f, 100.0f)) { + drop_chance = static_cast(zone->random.Real(0.0, 100.0)); + } + + if (probability != 0.0 && (probability == 100.0 || drop_chance <= probability)) { + AddLootDropTable(lte.lootdrop_id, drop_limit, minimum_drop); + } + } + } + + LogLootDetail("Loaded [{}] Loot Table [{}]", GetCleanName(), loottable_id); +} + +void NPC::AddLootDropTable(uint32 lootdrop_id, uint8 drop_limit, uint8 min_drop) +{ + const auto l = zone->GetLootdrop(lootdrop_id); + const auto le = zone->GetLootdropEntries(lootdrop_id); + + auto content_flags = ContentFlags{ + .min_expansion = l.min_expansion, + .max_expansion = l.max_expansion, + .content_flags = l.content_flags, + .content_flags_disabled = l.content_flags_disabled + }; + + if (l.id == 0 || le.empty() || !content_service.DoesPassContentFiltering(content_flags)) { + return; + } + + // if this lootdrop is droplimit=0 and mindrop 0, scan list once and return + if (drop_limit == 0 && min_drop == 0) { + for (const auto &e: le) { + for (int j = 0; j < e.multiplier; ++j) { + if (zone->random.Real(0.0, 100.0) <= e.chance && MeetsLootDropLevelRequirements(e, true)) { + const EQ::ItemData *database_item = database.GetItem(e.item_id); + AddLootDrop(database_item, e); + } + } + } + + return; + } + + if (le.size() > 100 && drop_limit == 0) { + drop_limit = 10; + } + + if (drop_limit < min_drop) { + drop_limit = min_drop; + } + + float roll_t = 0.0f; + float no_loot_prob = 1.0f; + bool roll_table_chance_bypass = false; + bool active_item_list = false; + + for (const auto &e: le) { + const EQ::ItemData *db_item = database.GetItem(e.item_id); + if (db_item && MeetsLootDropLevelRequirements(e)) { + roll_t += e.chance; + + if (e.chance >= 100) { + roll_table_chance_bypass = true; + } + else { + no_loot_prob *= (100 - e.chance) / 100.0f; + } + + active_item_list = true; + } + } + + if (!active_item_list) { + return; + } + + // This will pick one item per iteration until mindrop. + // Don't let the compare against chance fool you. + // The roll isn't 0-100, its 0-total and it picks the item, we're just + // looping to find the lucky item, descremening otherwise. This is ok, + // items with chance 60 are 6 times more likely than items chance 10. + int drops = 0; + + // translate above for loop using l and le + for (int i = 0; i < drop_limit; ++i) { + if (drops < min_drop || roll_table_chance_bypass || (float) zone->random.Real(0.0, 1.0) >= no_loot_prob) { + float roll = (float) zone->random.Real(0.0, roll_t); + for (const auto &e: le) { + const auto *db_item = database.GetItem(e.item_id); + if (db_item) { + // if it doesn't meet the requirements do nothing + if (!MeetsLootDropLevelRequirements(e)) { + continue; + } + + if (roll < e.chance) { + AddLootDrop(db_item, e); + drops++; + + uint8 charges = e.multiplier; + charges = EQ::ClampLower(charges, static_cast(1)); + + for (int k = 1; k < charges; ++k) { + float c_roll = static_cast(zone->random.Real(0.0, 100.0)); + if (c_roll <= e.chance) { + AddLootDrop(db_item, e); + } + } + + break; + } + else { + roll -= e.chance; + } + } + } + } + } + + UpdateEquipmentLight(); +} + +bool NPC::MeetsLootDropLevelRequirements(LootdropEntriesRepository::LootdropEntries loot_drop, bool verbose) +{ + if (loot_drop.npc_min_level > 0 && GetLevel() < loot_drop.npc_min_level) { + if (verbose) { + LogLootDetail( + "NPC [{}] does not meet loot_drop level requirements (min_level) level [{}] current [{}] for item [{}]", + GetCleanName(), + loot_drop.npc_min_level, + GetLevel(), + database.CreateItemLink(loot_drop.item_id) + ); + } + return false; + } + + if (loot_drop.npc_max_level > 0 && GetLevel() > loot_drop.npc_max_level) { + if (verbose) { + LogLootDetail( + "NPC [{}] does not meet loot_drop level requirements (max_level) level [{}] current [{}] for item [{}]", + GetCleanName(), + loot_drop.npc_max_level, + GetLevel(), + database.CreateItemLink(loot_drop.item_id) + ); + } + return false; + } + + return true; +} + +//if itemlist is null, just send wear changes +void NPC::AddLootDrop( + const EQ::ItemData *item2, + LootdropEntriesRepository::LootdropEntries loot_drop, + bool wear_change, + uint32 augment_one, + uint32 augment_two, + uint32 augment_three, + uint32 augment_four, + uint32 augment_five, + uint32 augment_six +) +{ + if (!item2) { + return; + } + + auto item = new LootItem; + + if (LogSys.log_settings[Logs::Loot].is_category_enabled == 1) { + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkItemData); + linker.SetItemData(item2); + + LogLoot( + "NPC [{}] Item ({}) [{}] charges [{}] chance [{}] trivial min/max [{}/{}] npc min/max [{}/{}]", + GetName(), + item2->ID, + linker.GenerateLink(), + loot_drop.item_charges, + loot_drop.chance, + loot_drop.trivial_min_level, + loot_drop.trivial_max_level, + loot_drop.npc_min_level, + loot_drop.npc_max_level + ); + } + + EQApplicationPacket *outapp = nullptr; + WearChange_Struct *p_wear_change_struct = nullptr; + if (wear_change) { + outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct)); + p_wear_change_struct = (WearChange_Struct *) outapp->pBuffer; + p_wear_change_struct->spawn_id = GetID(); + p_wear_change_struct->material = 0; + } + + item->item_id = item2->ID; + item->charges = loot_drop.item_charges; + item->aug_1 = augment_one; + item->aug_2 = augment_two; + item->aug_3 = augment_three; + item->aug_4 = augment_four; + item->aug_5 = augment_five; + item->aug_6 = augment_six; + item->attuned = false; + item->trivial_min_level = loot_drop.trivial_min_level; + item->trivial_max_level = loot_drop.trivial_max_level; + item->equip_slot = EQ::invslot::SLOT_INVALID; + + // unsure if required to equip, YOLO for now + if (item2->ItemType == EQ::item::ItemTypeBow) { + SetBowEquipped(true); + } + + if (item2->ItemType == EQ::item::ItemTypeArrow) { + SetArrowEquipped(true); + } + + bool found = false; // track if we found an empty slot we fit into + + int found_slot = INVALID_INDEX; // for multi-slot items + + auto *inst = database.CreateItem( + item2->ID, + loot_drop.item_charges, + augment_one, + augment_two, + augment_three, + augment_four, + augment_five, + augment_six + ); + + if (!inst) { + return; + } + + if (loot_drop.equip_item > 0) { + uint8 equipment_slot = UINT8_MAX; + const EQ::ItemData *compitem = nullptr; + + // Equip rules are as follows: + // If the item has the NoPet flag set it will not be equipped. + // An empty slot takes priority. The first empty one that an item can + // fit into will be the one picked for the item. + // AC is the primary choice for which item gets picked for a slot. + // If AC is identical HP is considered next. + // If an item can fit into multiple slots we'll pick the last one where + // it is an improvement. + + if (!item2->NoPet) { + for (int i = EQ::invslot::EQUIPMENT_BEGIN; !found && i <= EQ::invslot::EQUIPMENT_END; i++) { + const uint32 slots = (1 << i); + if (item2->Slots & slots) { + if (equipment[i]) { + compitem = database.GetItem(equipment[i]); + if (item2->AC > compitem->AC || (item2->AC == compitem->AC && item2->HP > compitem->HP)) { + // item would be an upgrade + // check if we're multi-slot, if yes then we have to keep + // looking in case any of the other slots we can fit into are empty. + if (item2->Slots != slots) { + found_slot = i; + } + else { + // Unequip old item + auto *old_item = GetItem(i); + + old_item->equip_slot = EQ::invslot::SLOT_INVALID; + + equipment[i] = item2->ID; + + found_slot = i; + found = true; + } + } + } + else { + equipment[i] = item2->ID; + + found_slot = i; + found = true; + } + } + } + } + + // Possible slot was found but not selected. Pick it now. + if (!found && found_slot >= 0) { + equipment[found_slot] = item2->ID; + + found = true; + } + + uint32 equipment_material; + if ( + item2->Material <= 0 || + ( + item2->Slots & ( + (1 << EQ::invslot::slotPrimary) | + (1 << EQ::invslot::slotSecondary) + ) + ) + ) { + equipment_material = Strings::ToUnsignedInt(&item2->IDFile[2]); + } + else { + equipment_material = item2->Material; + } + + if (found_slot == EQ::invslot::slotPrimary) { + equipment_slot = EQ::textures::weaponPrimary; + + if (item2->Damage > 0) { + SendAddPlayerState(PlayerState::PrimaryWeaponEquipped); + + if (!RuleB(Combat, ClassicNPCBackstab)) { + SetFacestab(true); + } + } + + if (item2->IsType2HWeapon()) { + SetTwoHanderEquipped(true); + } + } + else if ( + found_slot == EQ::invslot::slotSecondary && + ( + GetOwner() || + (CanThisClassDualWield() && zone->random.Roll(NPC_DW_CHANCE)) || + item2->Damage == 0 + ) && + ( + item2->IsType1HWeapon() || + item2->ItemType == EQ::item::ItemTypeShield || + item2->ItemType == EQ::item::ItemTypeLight + ) + ) { + equipment_slot = EQ::textures::weaponSecondary; + + if (item2->Damage > 0) { + SendAddPlayerState(PlayerState::SecondaryWeaponEquipped); + } + } + else if (found_slot == EQ::invslot::slotHead) { + equipment_slot = EQ::textures::armorHead; + } + else if (found_slot == EQ::invslot::slotChest) { + equipment_slot = EQ::textures::armorChest; + } + else if (found_slot == EQ::invslot::slotArms) { + equipment_slot = EQ::textures::armorArms; + } + else if (EQ::ValueWithin(found_slot, EQ::invslot::slotWrist1, EQ::invslot::slotWrist2)) { + equipment_slot = EQ::textures::armorWrist; + } + else if (found_slot == EQ::invslot::slotHands) { + equipment_slot = EQ::textures::armorHands; + } + else if (found_slot == EQ::invslot::slotLegs) { + equipment_slot = EQ::textures::armorLegs; + } + else if (found_slot == EQ::invslot::slotFeet) { + equipment_slot = EQ::textures::armorFeet; + } + + if (equipment_slot != UINT8_MAX) { + if (wear_change) { + p_wear_change_struct->wear_slot_id = equipment_slot; + p_wear_change_struct->material = equipment_material; + } + } + + if (found) { + item->equip_slot = found_slot; + } + } + + if (found_slot != INVALID_INDEX) { + GetInv().PutItem(found_slot, *inst); + } + + if (parse->HasQuestSub(GetNPCTypeID(), EVENT_LOOT_ADDED)) { + std::vector args = {inst}; + parse->EventNPC(EVENT_LOOT_ADDED, this, nullptr, "", 0, &args); + } + + m_loot_items.push_back(item); + + if (found) { + CalcBonuses(); + } + + if (IsRecordLootStats()) { + m_rolled_items.emplace_back(item->item_id); + } + + if (wear_change && outapp) { + entity_list.QueueClients(this, outapp); + safe_delete(outapp); + } + + UpdateEquipmentLight(); + + if (UpdateActiveLight()) { + SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); + } + + safe_delete(inst); +} + +void NPC::AddItem(const EQ::ItemData *item, uint16 charges, bool equip_item) +{ + auto l = LootdropEntriesRepository::NewNpcEntity(); + + l.equip_item = static_cast(equip_item ? 1 : 0); + l.item_charges = charges; + + AddLootDrop(item, l, true); +} + +void NPC::AddItem( + uint32 item_id, + uint16 charges, + bool equip_item, + uint32 augment_one, + uint32 augment_two, + uint32 augment_three, + uint32 augment_four, + uint32 augment_five, + uint32 augment_six +) +{ + const auto *item = database.GetItem(item_id); + if (!item) { + return; + } + + auto l = LootdropEntriesRepository::NewNpcEntity(); + + l.equip_item = static_cast(equip_item ? 1 : 0); + l.item_charges = charges; + + AddLootDrop( + item, + l, + true, + augment_one, + augment_two, + augment_three, + augment_four, + augment_five, + augment_six + ); +} + +void NPC::AddLootTable() +{ + AddLootTable(m_loottable_id); +} + +void NPC::CheckGlobalLootTables() +{ + const auto &l = zone->GetGlobalLootTables(this); + for (const auto &e: l) { + AddLootTable(e, true); + } +} + +void ZoneDatabase::LoadGlobalLoot() +{ + const auto &l = GlobalLootRepository::GetWhere( + *this, + fmt::format( + "`enabled` = 1 {}", + ContentFilterCriteria::apply() + ) + ); + + if (l.empty()) { + return; + } + + LogInfo( + "Loaded [{}] Global Loot Entr{}.", + Strings::Commify(l.size()), + l.size() != 1 ? "ies" : "y" + ); + + const std::string &zone_id = std::to_string(zone->GetZoneID()); + + for (const auto &e: l) { + if (!e.zone.empty()) { + const auto &zones = Strings::Split(e.zone, "|"); + + if (!Strings::Contains(zones, zone_id)) { + continue; + } + } + + GlobalLootEntry gle(e.id, e.loottable_id, e.description); + + if (e.min_level) { + gle.AddRule(GlobalLoot::RuleTypes::LevelMin, e.min_level); + } + + if (e.max_level) { + gle.AddRule(GlobalLoot::RuleTypes::LevelMax, e.max_level); + } + + if (e.rare) { + gle.AddRule(GlobalLoot::RuleTypes::Rare, e.rare); + } + + if (e.raid) { + gle.AddRule(GlobalLoot::RuleTypes::Raid, e.raid); + } + + if (!e.race.empty()) { + const auto &races = Strings::Split(e.race, "|"); + + for (const auto &r: races) { + gle.AddRule(GlobalLoot::RuleTypes::Race, Strings::ToInt(r)); + } + } + + if (!e.class_.empty()) { + const auto &classes = Strings::Split(e.class_, "|"); + + for (const auto &c: classes) { + gle.AddRule(GlobalLoot::RuleTypes::Class, Strings::ToInt(c)); + } + } + + if (!e.bodytype.empty()) { + const auto &bodytypes = Strings::Split(e.bodytype, "|"); + + for (const auto &b: bodytypes) { + gle.AddRule(GlobalLoot::RuleTypes::BodyType, Strings::ToInt(b)); + } + } + + if (e.hot_zone) { + gle.AddRule(GlobalLoot::RuleTypes::HotZone, e.hot_zone); + } + + zone->AddGlobalLootEntry(gle); + } +} + + +LootItem *NPC::GetItem(int slot_id) +{ + LootItems::iterator cur, end; + cur = m_loot_items.begin(); + end = m_loot_items.end(); + for (; cur != end; ++cur) { + LootItem *item = *cur; + if (item->equip_slot == slot_id) { + return item; + } + } + return (nullptr); +} + +void NPC::RemoveItem(uint32 item_id, uint16 quantity, uint16 slot) +{ + LootItems::iterator cur, end; + cur = m_loot_items.begin(); + end = m_loot_items.end(); + for (; cur != end; ++cur) { + LootItem *item = *cur; + if (item->item_id == item_id && slot <= 0 && quantity <= 0) { + m_loot_items.erase(cur); + UpdateEquipmentLight(); + if (UpdateActiveLight()) { SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); } + return; + } + else if (item->item_id == item_id && item->equip_slot == slot && quantity >= 1) { + if (item->charges <= quantity) { + m_loot_items.erase(cur); + UpdateEquipmentLight(); + if (UpdateActiveLight()) { SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); } + } + else { + item->charges -= quantity; + } + return; + } + } +} + +void NPC::CheckTrivialMinMaxLevelDrop(Mob *killer) +{ + if (killer == nullptr || !killer->IsClient()) { + return; + } + + uint16 killer_level = killer->GetLevel(); + uint8 material; + + auto cur = m_loot_items.begin(); + while (cur != m_loot_items.end()) { + if (!(*cur)) { + return; + } + + uint16 trivial_min_level = (*cur)->trivial_min_level; + uint16 trivial_max_level = (*cur)->trivial_max_level; + bool fits_trivial_criteria = ( + (trivial_min_level > 0 && killer_level < trivial_min_level) || + (trivial_max_level > 0 && killer_level > trivial_max_level) + ); + + if (fits_trivial_criteria) { + material = EQ::InventoryProfile::CalcMaterialFromSlot((*cur)->equip_slot); + if (material != EQ::textures::materialInvalid) { + SendWearChange(material); + } + + cur = m_loot_items.erase(cur); + continue; + } + ++cur; + } + + UpdateEquipmentLight(); + if (UpdateActiveLight()) { + SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); + } +} + +void NPC::ClearLootItems() +{ + LootItems::iterator cur, end; + cur = m_loot_items.begin(); + end = m_loot_items.end(); + for (; cur != end; ++cur) { + LootItem *item = *cur; + safe_delete(item); + } + m_loot_items.clear(); + + UpdateEquipmentLight(); + if (UpdateActiveLight()) { + SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); + } +} + +void NPC::QueryLoot(Client *to, bool is_pet_query) +{ + if (!m_loot_items.empty()) { + if (!is_pet_query) { + to->Message( + Chat::White, + fmt::format( + "Loot | {} ({}) ID: {} Loottable ID: {}", + GetName(), + GetID(), + GetNPCTypeID(), + GetLoottableID() + ).c_str() + ); + } + + int item_count = 0; + + for (auto current_item: m_loot_items) { + int item_number = (item_count + 1); + if (!current_item) { + LogError("ItemList error, null item."); + continue; + } + + if (!current_item->item_id || !database.GetItem(current_item->item_id)) { + LogError("Database error, invalid item."); + continue; + } + + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkLootItem); + linker.SetLootData(current_item); + + to->Message( + Chat::White, + fmt::format( + "Item {} | {} ({}){}", + item_number, + linker.GenerateLink().c_str(), + current_item->item_id, + ( + current_item->charges > 1 ? + fmt::format( + " Amount: {}", + current_item->charges + ) : + "" + ) + ).c_str() + ); + item_count++; + } + } + + if (!is_pet_query) { + if (m_loot_platinum || m_loot_gold || m_loot_silver || m_loot_copper) { + to->Message( + Chat::White, + fmt::format( + "Money | {}", + Strings::Money( + m_loot_platinum, + m_loot_gold, + m_loot_silver, + m_loot_copper + ) + ).c_str() + ); + } + } +} + +bool NPC::HasItem(uint32 item_id) +{ + if (!database.GetItem(item_id)) { + return false; + } + + for (auto loot_item: m_loot_items) { + if (!loot_item) { + LogError("NPC::HasItem() - ItemList error, null item"); + continue; + } + + if (!loot_item->item_id || !database.GetItem(loot_item->item_id)) { + LogError("NPC::HasItem() - Database error, invalid item"); + continue; + } + + if (loot_item->item_id == item_id) { + return true; + } + } + return false; +} + +uint16 NPC::CountItem(uint32 item_id) +{ + uint16 item_count = 0; + if (!database.GetItem(item_id)) { + return item_count; + } + + for (auto loot_item: m_loot_items) { + if (!loot_item) { + LogError("NPC::CountItem() - ItemList error, null item"); + continue; + } + + if (!loot_item->item_id || !database.GetItem(loot_item->item_id)) { + LogError("NPC::CountItem() - Database error, invalid item"); + continue; + } + + if (loot_item->item_id == item_id) { + item_count += loot_item->charges > 0 ? loot_item->charges : 1; + } + } + return item_count; +} + +uint32 NPC::GetLootItemIDBySlot(uint16 loot_slot) +{ + for (auto loot_item: m_loot_items) { + if (loot_item->lootslot == loot_slot) { + return loot_item->item_id; + } + } + return 0; +} + +uint16 NPC::GetFirstLootSlotByItemID(uint32 item_id) +{ + for (auto loot_item: m_loot_items) { + if (loot_item->item_id == item_id) { + return loot_item->lootslot; + } + } + return 0; +} + +void NPC::AddLootCash( + uint32 in_copper, + uint32 in_silver, + uint32 in_gold, + uint32 in_platinum +) +{ + m_loot_copper = in_copper >= 0 ? in_copper : 0; + m_loot_silver = in_silver >= 0 ? in_silver : 0; + m_loot_gold = in_gold >= 0 ? in_gold : 0; + m_loot_platinum = in_platinum >= 0 ? in_platinum : 0; +} + +void NPC::RemoveLootCash() +{ + m_loot_copper = 0; + m_loot_silver = 0; + m_loot_gold = 0; + m_loot_platinum = 0; +} diff --git a/zone/loottables.cpp b/zone/loottables.cpp deleted file mode 100644 index d28cbecc4..000000000 --- a/zone/loottables.cpp +++ /dev/null @@ -1,714 +0,0 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY except by those people which sell it, which - are required to give you total support for your newly bought product; - without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "../common/global_define.h" -#include "../common/loottable.h" -#include "../common/data_verification.h" - -#include "client.h" -#include "entity.h" -#include "mob.h" -#include "npc.h" -#include "zonedb.h" -#include "global_loot_manager.h" -#include "../common/repositories/criteria/content_filter_criteria.h" -#include "../common/repositories/global_loot_repository.h" -#include "quest_parser_collection.h" - -#ifdef _WINDOWS -#define snprintf _snprintf -#endif - -// Queries the loottable: adds item & coin to the npc -void ZoneDatabase::AddLootTableToNPC( - NPC *npc, - uint32 loottable_id, - ItemList *itemlist, - uint32 *copper, - uint32 *silver, - uint32 *gold, - uint32 *plat -) -{ - const bool is_global = ( - copper == nullptr && - silver == nullptr && - gold == nullptr && - plat == nullptr - ); - if (!is_global) { - *copper = 0; - *silver = 0; - *gold = 0; - *plat = 0; - } - - const auto *lts = database.GetLootTable(loottable_id); - if (!lts) { - return; - } - - if (!content_service.DoesPassContentFiltering(lts->content_flags)) { - return; - } - - uint32 min_cash = lts->mincash; - uint32 max_cash = lts->maxcash; - if (min_cash > max_cash) { - const uint32 t = min_cash; - min_cash = max_cash; - max_cash = t; - } - - uint32 cash = 0; - if (!is_global) { - if ( - max_cash > 0 && - lts->avgcoin > 0 && - EQ::ValueWithin(lts->avgcoin, min_cash, max_cash) - ) { - const float upper_chance = static_cast(lts->avgcoin - min_cash) / static_cast(max_cash - min_cash); - const float avg_cash_roll = static_cast(zone->random.Real(0.0, 1.0)); - - if (avg_cash_roll < upper_chance) { - cash = zone->random.Int(lts->avgcoin, max_cash); - } else { - cash = zone->random.Int(min_cash, lts->avgcoin); - } - } else { - cash = zone->random.Int(min_cash, max_cash); - } - } - - if (cash != 0) { - *plat = cash / 1000; - cash -= *plat * 1000; - - *gold = cash / 100; - cash -= *gold * 100; - - *silver = cash / 10; - cash -= *silver * 10; - - *copper = cash; - } - - const uint32 global_loot_multiplier = RuleI(Zone, GlobalLootMultiplier); - - for (uint32 i = 0; i < lts->NumEntries; i++) { - for (uint32 k = 1; k <= (lts->Entries[i].multiplier * global_loot_multiplier); k++) { - const uint8 drop_limit = lts->Entries[i].droplimit; - const uint8 minimum_drop = lts->Entries[i].mindrop; - - //LootTable Entry probability - const float probability = lts->Entries[i].probability; - - float drop_chance = 0.0f; - if (EQ::ValueWithin(probability, 0.0f, 100.0f)) { - drop_chance = static_cast(zone->random.Real(0.0, 100.0)); - } - - if (probability != 0.0 && (probability == 100.0 || drop_chance <= probability)) { - AddLootDropToNPC(npc, lts->Entries[i].lootdrop_id, itemlist, drop_limit, minimum_drop); - } - } - } -} - -// Called by AddLootTableToNPC -// maxdrops = size of the array npcd -void ZoneDatabase::AddLootDropToNPC(NPC *npc, uint32 lootdrop_id, ItemList *item_list, uint8 droplimit, uint8 mindrop) -{ - const auto *lds = GetLootDrop(lootdrop_id); - if ( - !lds || - lds->NumEntries == 0 || - !content_service.DoesPassContentFiltering(lds->content_flags) - ) { - return; - } - - // if this lootdrop is droplimit=0 and mindrop 0, scan list once and return - if (droplimit == 0 && mindrop == 0) { - for (uint32 i = 0; i < lds->NumEntries; ++i) { - const uint8 charges = lds->Entries[i].multiplier; - for (int j = 0; j < charges; ++j) { - if ( - zone->random.Real(0.0, 100.0) <= lds->Entries[i].chance && - npc->MeetsLootDropLevelRequirements(lds->Entries[i], true) - ) { - const EQ::ItemData *database_item = GetItem(lds->Entries[i].item_id); - npc->AddLootDrop( - database_item, - item_list, - lds->Entries[i] - ); - } - } - } - return; - } - - if (lds->NumEntries > 100 && droplimit == 0) { - droplimit = 10; - } - - if (droplimit < mindrop) { - droplimit = mindrop; - } - - float roll_t = 0.0f; - float no_loot_prob = 1.0f; - bool roll_table_chance_bypass = false; - bool active_item_list = false; - - for (uint32 i = 0; i < lds->NumEntries; ++i) { - const EQ::ItemData *db_item = GetItem(lds->Entries[i].item_id); - if (db_item && npc->MeetsLootDropLevelRequirements(lds->Entries[i])) { - roll_t += lds->Entries[i].chance; - - if (lds->Entries[i].chance >= 100) { - roll_table_chance_bypass = true; - } else { - no_loot_prob *= (100 - lds->Entries[i].chance) / 100.0f; - } - - active_item_list = true; - } - } - - if (!active_item_list) { - return; - } - - // This will pick one item per iteration until mindrop. - // Don't let the compare against chance fool you. - // The roll isn't 0-100, its 0-total and it picks the item, we're just - // looping to find the lucky item, descremening otherwise. This is ok, - // items with chance 60 are 6 times more likely than items chance 10. - int drops = 0; - - for (int i = 0; i < droplimit; ++i) { - if (drops < mindrop || roll_table_chance_bypass || (float) zone->random.Real(0.0, 1.0) >= no_loot_prob) { - float roll = (float) zone->random.Real(0.0, roll_t); - for (uint32 j = 0; j < lds->NumEntries; ++j) { - const auto *db_item = GetItem(lds->Entries[j].item_id); - if (db_item) { - // if it doesn't meet the requirements do nothing - if (!npc->MeetsLootDropLevelRequirements(lds->Entries[j])) { - continue; - } - - if (roll < lds->Entries[j].chance) { - npc->AddLootDrop( - db_item, - item_list, - lds->Entries[j] - ); - drops++; - - uint8 charges = lds->Entries[i].multiplier; - charges = EQ::ClampLower(charges, static_cast(1)); - - for (int k = 1; k < charges; ++k) { - float c_roll = static_cast(zone->random.Real(0.0, 100.0)); - if (c_roll <= lds->Entries[i].chance) { - npc->AddLootDrop( - db_item, - item_list, - lds->Entries[i] - ); - } - } - - j = lds->NumEntries; - break; - } else { - roll -= lds->Entries[j].chance; - } - } - } - } - } - - npc->UpdateEquipmentLight(); - // no wearchange associated with this function..so, this should not be needed - //if (npc->UpdateActiveLightValue()) - // npc->SendAppearancePacket(AppearanceType::Light, npc->GetActiveLightValue()); -} - -bool NPC::MeetsLootDropLevelRequirements(LootDropEntries_Struct loot_drop, bool verbose) -{ - if (loot_drop.npc_min_level > 0 && GetLevel() < loot_drop.npc_min_level) { - if (verbose) { - LogLootDetail( - "NPC [{}] does not meet loot_drop level requirements (min_level) level [{}] current [{}] for item [{}]", - GetCleanName(), - loot_drop.npc_min_level, - GetLevel(), - database.CreateItemLink(loot_drop.item_id) - ); - } - return false; - } - - if (loot_drop.npc_max_level > 0 && GetLevel() > loot_drop.npc_max_level) { - if (verbose) { - LogLootDetail( - "NPC [{}] does not meet loot_drop level requirements (max_level) level [{}] current [{}] for item [{}]", - GetCleanName(), - loot_drop.npc_max_level, - GetLevel(), - database.CreateItemLink(loot_drop.item_id) - ); - } - return false; - } - - return true; -} - -LootDropEntries_Struct NPC::NewLootDropEntry() -{ - LootDropEntries_Struct loot_drop{}; - loot_drop.item_id = 0; - loot_drop.item_charges = 1; - loot_drop.equip_item = 1; - loot_drop.chance = 0; - loot_drop.trivial_min_level = 0; - loot_drop.trivial_max_level = 0; - loot_drop.npc_min_level = 0; - loot_drop.npc_max_level = 0; - loot_drop.multiplier = 0; - - return loot_drop; -} - -//if itemlist is null, just send wear changes -void NPC::AddLootDrop( - const EQ::ItemData *item2, - ItemList *itemlist, - LootDropEntries_Struct loot_drop, - bool wear_change, - uint32 augment_one, - uint32 augment_two, - uint32 augment_three, - uint32 augment_four, - uint32 augment_five, - uint32 augment_six -) -{ - if (!item2) { - return; - } - - if (!itemlist && !wear_change) { - return; - } - - auto item = new ServerLootItem_Struct; - - if (LogSys.log_settings[Logs::Loot].is_category_enabled == 1) { - EQ::SayLinkEngine linker; - linker.SetLinkType(EQ::saylink::SayLinkItemData); - linker.SetItemData(item2); - - LogLoot( - "NPC [{}] Item ({}) [{}] charges [{}] chance [{}] trivial min/max [{}/{}] npc min/max [{}/{}]", - GetName(), - item2->ID, - linker.GenerateLink(), - loot_drop.item_charges, - loot_drop.chance, - loot_drop.trivial_min_level, - loot_drop.trivial_max_level, - loot_drop.npc_min_level, - loot_drop.npc_max_level - ); - } - - EQApplicationPacket *outapp = nullptr; - WearChange_Struct *p_wear_change_struct = nullptr; - if (wear_change) { - outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct)); - p_wear_change_struct = (WearChange_Struct *) outapp->pBuffer; - p_wear_change_struct->spawn_id = GetID(); - p_wear_change_struct->material = 0; - } - - item->item_id = item2->ID; - item->charges = loot_drop.item_charges; - item->aug_1 = augment_one; - item->aug_2 = augment_two; - item->aug_3 = augment_three; - item->aug_4 = augment_four; - item->aug_5 = augment_five; - item->aug_6 = augment_six; - item->attuned = false; - item->trivial_min_level = loot_drop.trivial_min_level; - item->trivial_max_level = loot_drop.trivial_max_level; - item->equip_slot = EQ::invslot::SLOT_INVALID; - - // unsure if required to equip, YOLO for now - if (item2->ItemType == EQ::item::ItemTypeBow) { - SetBowEquipped(true); - } - - if (item2->ItemType == EQ::item::ItemTypeArrow) { - SetArrowEquipped(true); - } - - bool found = false; // track if we found an empty slot we fit into - - int found_slot = INVALID_INDEX; // for multi-slot items - - auto *inst = database.CreateItem( - item2->ID, - loot_drop.item_charges, - augment_one, - augment_two, - augment_three, - augment_four, - augment_five, - augment_six - ); - - if (!inst) { - return; - } - - if (loot_drop.equip_item > 0) { - uint8 equipment_slot = UINT8_MAX; - const EQ::ItemData *compitem = nullptr; - - // Equip rules are as follows: - // If the item has the NoPet flag set it will not be equipped. - // An empty slot takes priority. The first empty one that an item can - // fit into will be the one picked for the item. - // AC is the primary choice for which item gets picked for a slot. - // If AC is identical HP is considered next. - // If an item can fit into multiple slots we'll pick the last one where - // it is an improvement. - - if (!item2->NoPet) { - for ( - int i = EQ::invslot::EQUIPMENT_BEGIN; - !found && i <= EQ::invslot::EQUIPMENT_END; - i++ - ) { - const uint32 slots = (1 << i); - if (item2->Slots & slots) { - if (equipment[i]) { - compitem = database.GetItem(equipment[i]); - if ( - item2->AC > compitem->AC || - (item2->AC == compitem->AC && item2->HP > compitem->HP) - ) { - // item would be an upgrade - // check if we're multi-slot, if yes then we have to keep - // looking in case any of the other slots we can fit into are empty. - if (item2->Slots != slots) { - found_slot = i; - } else { - // Unequip old item - auto *old_item = GetItem(i); - - old_item->equip_slot = EQ::invslot::SLOT_INVALID; - - equipment[i] = item2->ID; - - found_slot = i; - found = true; - } - } - } else { - equipment[i] = item2->ID; - - found_slot = i; - found = true; - } - } - } - } - - // Possible slot was found but not selected. Pick it now. - if (!found && found_slot >= 0) { - equipment[found_slot] = item2->ID; - - found = true; - } - - uint32 equipment_material; - if ( - item2->Material <= 0 || - ( - item2->Slots & ( - (1 << EQ::invslot::slotPrimary) | - (1 << EQ::invslot::slotSecondary) - ) - ) - ) { - equipment_material = Strings::ToUnsignedInt(&item2->IDFile[2]); - } else { - equipment_material = item2->Material; - } - - if (found_slot == EQ::invslot::slotPrimary) { - equipment_slot = EQ::textures::weaponPrimary; - - if (item2->Damage > 0) { - SendAddPlayerState(PlayerState::PrimaryWeaponEquipped); - - if (!RuleB(Combat, ClassicNPCBackstab)) { - SetFacestab(true); - } - } - - if (item2->IsType2HWeapon()) { - SetTwoHanderEquipped(true); - } - } else if ( - found_slot == EQ::invslot::slotSecondary && - ( - GetOwner() || - (CanThisClassDualWield() && zone->random.Roll(NPC_DW_CHANCE)) || - item2->Damage == 0 - ) && - ( - item2->IsType1HWeapon() || - item2->ItemType == EQ::item::ItemTypeShield || - item2->ItemType == EQ::item::ItemTypeLight - ) - ) { - equipment_slot = EQ::textures::weaponSecondary; - - if (item2->Damage > 0) { - SendAddPlayerState(PlayerState::SecondaryWeaponEquipped); - } - } else if (found_slot == EQ::invslot::slotHead) { - equipment_slot = EQ::textures::armorHead; - } else if (found_slot == EQ::invslot::slotChest) { - equipment_slot = EQ::textures::armorChest; - } else if (found_slot == EQ::invslot::slotArms) { - equipment_slot = EQ::textures::armorArms; - } else if (EQ::ValueWithin(found_slot, EQ::invslot::slotWrist1, EQ::invslot::slotWrist2)) { - equipment_slot = EQ::textures::armorWrist; - } else if (found_slot == EQ::invslot::slotHands) { - equipment_slot = EQ::textures::armorHands; - } else if (found_slot == EQ::invslot::slotLegs) { - equipment_slot = EQ::textures::armorLegs; - } else if (found_slot == EQ::invslot::slotFeet) { - equipment_slot = EQ::textures::armorFeet; - } - - if (equipment_slot != UINT8_MAX) { - if (wear_change) { - p_wear_change_struct->wear_slot_id = equipment_slot; - p_wear_change_struct->material = equipment_material; - } - } - - if (found) { - item->equip_slot = found_slot; - } - } - - if (itemlist) { - if (found_slot != INVALID_INDEX) { - GetInv().PutItem(found_slot, *inst); - } - - if (parse->HasQuestSub(GetNPCTypeID(), EVENT_LOOT_ADDED)) { - std::vector args = { inst }; - parse->EventNPC(EVENT_LOOT_ADDED, this, nullptr, "", 0, &args); - } - - itemlist->push_back(item); - } else { - safe_delete(item); - } - - if (found) { - CalcBonuses(); - } - - if (IsRecordLootStats()) { - m_rolled_items.emplace_back(item->item_id); - } - - if (wear_change && outapp) { - entity_list.QueueClients(this, outapp); - safe_delete(outapp); - } - - UpdateEquipmentLight(); - - if (UpdateActiveLight()) { - SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); - } -} - -void NPC::AddItem(const EQ::ItemData *item, uint16 charges, bool equip_item) -{ - auto loot_drop_entry = NPC::NewLootDropEntry(); - - loot_drop_entry.equip_item = static_cast(equip_item ? 1 : 0); - loot_drop_entry.item_charges = charges; - - AddLootDrop(item, &itemlist, loot_drop_entry, true); -} - -void NPC::AddItem( - uint32 item_id, - uint16 charges, - bool equip_item, - uint32 augment_one, - uint32 augment_two, - uint32 augment_three, - uint32 augment_four, - uint32 augment_five, - uint32 augment_six -) -{ - const auto *item = database.GetItem(item_id); - if (!item) { - return; - } - - auto loot_drop_entry = NPC::NewLootDropEntry(); - - loot_drop_entry.equip_item = static_cast(equip_item ? 1 : 0); - loot_drop_entry.item_charges = charges; - - AddLootDrop( - item, - &itemlist, - loot_drop_entry, - true, - augment_one, - augment_two, - augment_three, - augment_four, - augment_five, - augment_six - ); -} - -void NPC::AddLootTable() -{ - if (npctype_id != 0) { // check if it's a GM spawn - database.AddLootTableToNPC(this, loottable_id, &itemlist, &copper, &silver, &gold, &platinum); - } -} - -void NPC::AddLootTable(uint32 loottable_id) -{ - if (npctype_id != 0) { // check if it's a GM spawn - database.AddLootTableToNPC(this, loottable_id, &itemlist, &copper, &silver, &gold, &platinum); - } -} - -void NPC::CheckGlobalLootTables() -{ - const auto& l = zone->GetGlobalLootTables(this); - - for (const auto& e : l) { - database.AddLootTableToNPC(this, e, &itemlist, nullptr, nullptr, nullptr, nullptr); - } -} - -void ZoneDatabase::LoadGlobalLoot() -{ - const auto& l = GlobalLootRepository::GetWhere( - *this, - fmt::format( - "`enabled` = 1 {}", - ContentFilterCriteria::apply() - ) - ); - - if (l.empty()) { - return; - } - - LogInfo( - "Loaded [{}] Global Loot Entr{}.", - Strings::Commify(l.size()), - l.size() != 1 ? "ies" : "y" - ); - - const std::string& zone_id = std::to_string(zone->GetZoneID()); - - for (const auto& e : l) { - if (!e.zone.empty()) { - const auto& zones = Strings::Split(e.zone, "|"); - - if (!Strings::Contains(zones, zone_id)) { - continue; - } - } - - GlobalLootEntry gle(e.id, e.loottable_id, e.description); - - if (e.min_level) { - gle.AddRule(GlobalLoot::RuleTypes::LevelMin, e.min_level); - } - - if (e.max_level) { - gle.AddRule(GlobalLoot::RuleTypes::LevelMax, e.max_level); - } - - if (e.rare) { - gle.AddRule(GlobalLoot::RuleTypes::Rare, e.rare); - } - - if (e.raid) { - gle.AddRule(GlobalLoot::RuleTypes::Raid, e.raid); - } - - if (!e.race.empty()) { - const auto& races = Strings::Split(e.race, "|"); - - for (const auto& r : races) { - gle.AddRule(GlobalLoot::RuleTypes::Race, Strings::ToInt(r)); - } - } - - if (!e.class_.empty()) { - const auto& classes = Strings::Split(e.class_, "|"); - - for (const auto& c : classes) { - gle.AddRule(GlobalLoot::RuleTypes::Class, Strings::ToInt(c)); - } - } - - if (!e.bodytype.empty()) { - const auto& bodytypes = Strings::Split(e.bodytype, "|"); - - for (const auto& b : bodytypes) { - gle.AddRule(GlobalLoot::RuleTypes::BodyType, Strings::ToInt(b)); - } - } - - if (e.hot_zone) { - gle.AddRule(GlobalLoot::RuleTypes::HotZone, e.hot_zone); - } - - zone->AddGlobalLootEntry(gle); - } -} diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 2ad5d28b0..3de1f548a 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -2345,7 +2345,7 @@ void Lua_Client::SummonBaggedItems(uint32 bag_item_id, luabind::adl::object bag_ return; } - std::vector bagged_items; + std::vector bagged_items; luabind::raw_iterator end; // raw_iterator uses lua_rawget for (luabind::raw_iterator it(bag_items_table); it != end; ++it) @@ -2354,7 +2354,7 @@ void Lua_Client::SummonBaggedItems(uint32 bag_item_id, luabind::adl::object bag_ if (luabind::type(*it) == LUA_TTABLE) { // no need to try/catch, quest lua parser already catches exceptions - ServerLootItem_Struct item{}; + LootItem item{}; item.item_id = luabind::object_cast((*it)["item_id"]); item.charges = luabind::object_cast((*it)["charges"]); item.attuned = luabind::type((*it)["attuned"]) != LUA_TNIL ? luabind::object_cast((*it)["attuned"]) : 0; diff --git a/zone/lua_corpse.cpp b/zone/lua_corpse.cpp index b1bb650ad..321e4e543 100644 --- a/zone/lua_corpse.cpp +++ b/zone/lua_corpse.cpp @@ -112,7 +112,7 @@ void Lua_Corpse::SetCash(uint32 copper, uint32 silver, uint32 gold, uint32 plati self->SetCash(copper, silver, gold, platinum); } -void Lua_Corpse::RemoveCash() { +void Lua_Corpse::RemoveLootCash() { Lua_Safe_Call_Void(); self->RemoveCash(); } @@ -187,9 +187,9 @@ uint32 Lua_Corpse::GetItemIDBySlot(uint16 loot_slot) { return self->GetItemIDBySlot(loot_slot); } -uint16 Lua_Corpse::GetFirstSlotByItemID(uint32 item_id) { +uint16 Lua_Corpse::GetFirstLootSlotByItemID(uint32 item_id) { Lua_Safe_Call_Int(); - return self->GetFirstSlotByItemID(item_id); + return self->GetFirstLootSlotByItemID(item_id); } void Lua_Corpse::RemoveItemByID(uint32 item_id) { @@ -199,14 +199,14 @@ void Lua_Corpse::RemoveItemByID(uint32 item_id) { void Lua_Corpse::RemoveItemByID(uint32 item_id, int quantity) { Lua_Safe_Call_Void(); - self->RemoveItemByID(item_id, quantity); + self->RemoveItemByID(item_id, quantity); } Lua_Corpse_Loot_List Lua_Corpse::GetLootList(lua_State* L) { Lua_Safe_Call_Class(Lua_Corpse_Loot_List); Lua_Corpse_Loot_List ret; auto loot_list = self->GetLootList(); - + for (auto item_id : loot_list) { ret.entries.push_back(item_id); } @@ -234,7 +234,7 @@ luabind::scope lua_register_corpse() { .def("GetCopper", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::GetCopper) .def("GetDBID", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::GetDBID) .def("GetDecayTime", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::GetDecayTime) - .def("GetFirstSlotByItemID", (uint16(Lua_Corpse::*)(uint32))&Lua_Corpse::GetFirstSlotByItemID) + .def("GetFirstSlotByItemID", (uint16(Lua_Corpse::*)(uint32))&Lua_Corpse::GetFirstLootSlotByItemID) .def("GetGold", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::GetGold) .def("GetItemIDBySlot", (uint32(Lua_Corpse::*)(uint16))&Lua_Corpse::GetItemIDBySlot) .def("GetLootList", (Lua_Corpse_Loot_List(Lua_Corpse::*)(lua_State* L))&Lua_Corpse::GetLootList) @@ -247,7 +247,7 @@ luabind::scope lua_register_corpse() { .def("IsLocked", (bool(Lua_Corpse::*)(void))&Lua_Corpse::IsLocked) .def("IsRezzed", (bool(Lua_Corpse::*)(void))&Lua_Corpse::IsRezzed) .def("Lock", (void(Lua_Corpse::*)(void))&Lua_Corpse::Lock) - .def("RemoveCash", (void(Lua_Corpse::*)(void))&Lua_Corpse::RemoveCash) + .def("RemoveCash", (void(Lua_Corpse::*)(void))&Lua_Corpse::RemoveLootCash) .def("RemoveItem", (void(Lua_Corpse::*)(uint16))&Lua_Corpse::RemoveItem) .def("RemoveItemByID", (void(Lua_Corpse::*)(uint32))&Lua_Corpse::RemoveItemByID) .def("RemoveItemByID", (void(Lua_Corpse::*)(uint32,int))&Lua_Corpse::RemoveItemByID) diff --git a/zone/lua_corpse.h b/zone/lua_corpse.h index e569f7b7c..9ad63e979 100644 --- a/zone/lua_corpse.h +++ b/zone/lua_corpse.h @@ -49,7 +49,7 @@ public: void RemoveItemByID(uint32 item_id); void RemoveItemByID(uint32 item_id, int quantity); void SetCash(uint32 copper, uint32 silver, uint32 gold, uint32 platinum); - void RemoveCash(); + void RemoveLootCash(); bool IsEmpty(); void ResetDecayTimer(); void SetDecayTimer(uint32 decaytime); @@ -64,7 +64,7 @@ public: bool HasItem(uint32 item_id); uint16 CountItem(uint32 item_id); uint32 GetItemIDBySlot(uint16 loot_slot); - uint16 GetFirstSlotByItemID(uint32 item_id); + uint16 GetFirstLootSlotByItemID(uint32 item_id); Lua_Corpse_Loot_List GetLootList(lua_State* L); }; diff --git a/zone/lua_npc.cpp b/zone/lua_npc.cpp index ab078beae..aa94b9d57 100644 --- a/zone/lua_npc.cpp +++ b/zone/lua_npc.cpp @@ -87,19 +87,19 @@ void Lua_NPC::RemoveItem(int item_id, int quantity, int slot) { self->RemoveItem(item_id, quantity, slot); } -void Lua_NPC::ClearItemList() { +void Lua_NPC::ClearLootItems() { Lua_Safe_Call_Void(); - self->ClearItemList(); + self->ClearLootItems(); } -void Lua_NPC::AddCash(uint32 copper, uint32 silver, uint32 gold, uint32 platinum) { +void Lua_NPC::AddLootCash(uint32 copper, uint32 silver, uint32 gold, uint32 platinum) { Lua_Safe_Call_Void(); - self->AddCash(copper, silver, gold, platinum); + self->AddLootCash(copper, silver, gold, platinum); } -void Lua_NPC::RemoveCash() { +void Lua_NPC::RemoveLootCash() { Lua_Safe_Call_Void(); - self->RemoveCash(); + self->RemoveLootCash(); } int Lua_NPC::CountLoot() { @@ -603,16 +603,16 @@ uint16 Lua_NPC::CountItem(uint32 item_id) return self->CountItem(item_id); } -uint32 Lua_NPC::GetItemIDBySlot(uint16 loot_slot) +uint32 Lua_NPC::GetLootItemIDBySlot(uint16 loot_slot) { Lua_Safe_Call_Int(); - return self->GetItemIDBySlot(loot_slot); + return self->GetLootItemIDBySlot(loot_slot); } -uint16 Lua_NPC::GetFirstSlotByItemID(uint32 item_id) +uint16 Lua_NPC::GetFirstLootSlotByItemID(uint32 item_id) { Lua_Safe_Call_Int(); - return self->GetFirstSlotByItemID(item_id); + return self->GetFirstLootSlotByItemID(item_id); } float Lua_NPC::GetHealScale() @@ -833,7 +833,7 @@ luabind::scope lua_register_npc() { .def("AddAISpell", (void(Lua_NPC::*)(int,int,int,int,int,int))&Lua_NPC::AddAISpell) .def("AddAISpell", (void(Lua_NPC::*)(int,int,int,int,int,int,int,int))&Lua_NPC::AddAISpell) .def("AddAISpellEffect", (void(Lua_NPC::*)(int,int,int,int))&Lua_NPC::AddAISpellEffect) - .def("AddCash", (void(Lua_NPC::*)(uint32,uint32,uint32,uint32))&Lua_NPC::AddCash) + .def("AddCash", (void(Lua_NPC::*)(uint32,uint32,uint32,uint32))&Lua_NPC::AddLootCash) .def("AddItem", (void(Lua_NPC::*)(int,int))&Lua_NPC::AddItem) .def("AddItem", (void(Lua_NPC::*)(int,int,bool))&Lua_NPC::AddItem) .def("AddItem", (void(Lua_NPC::*)(int,int,bool,int))&Lua_NPC::AddItem) @@ -848,7 +848,7 @@ luabind::scope lua_register_npc() { .def("CalculateNewWaypoint", (void(Lua_NPC::*)(void))&Lua_NPC::CalculateNewWaypoint) .def("ChangeLastName", (void(Lua_NPC::*)(std::string))&Lua_NPC::ChangeLastName) .def("CheckNPCFactionAlly", (int(Lua_NPC::*)(int))&Lua_NPC::CheckNPCFactionAlly) - .def("ClearItemList", (void(Lua_NPC::*)(void))&Lua_NPC::ClearItemList) + .def("ClearItemList", (void(Lua_NPC::*)(void))&Lua_NPC::ClearLootItems) .def("ClearLastName", (void(Lua_NPC::*)(void))&Lua_NPC::ClearLastName) .def("CountItem", (uint16(Lua_NPC::*)(uint32))&Lua_NPC::CountItem) .def("CountLoot", (int(Lua_NPC::*)(void))&Lua_NPC::CountLoot) @@ -863,7 +863,7 @@ luabind::scope lua_register_npc() { .def("GetBucketExpires", (std::string(Lua_NPC::*)(std::string))&Lua_NPC::GetBucketExpires) .def("GetBucketRemaining", (std::string(Lua_NPC::*)(std::string))&Lua_NPC::GetBucketRemaining) .def("GetCopper", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetCopper) - .def("GetFirstSlotByItemID", (uint16(Lua_NPC::*)(uint32))&Lua_NPC::GetFirstSlotByItemID) + .def("GetFirstSlotByItemID", (uint16(Lua_NPC::*)(uint32))&Lua_NPC::GetFirstLootSlotByItemID) .def("GetFollowCanRun", (bool(Lua_NPC::*)(void))&Lua_NPC::GetFollowCanRun) .def("GetFollowDistance", (int(Lua_NPC::*)(void))&Lua_NPC::GetFollowDistance) .def("GetFollowID", (int(Lua_NPC::*)(void))&Lua_NPC::GetFollowID) @@ -873,7 +873,7 @@ luabind::scope lua_register_npc() { .def("GetGuardPointY", (float(Lua_NPC::*)(void))&Lua_NPC::GetGuardPointY) .def("GetGuardPointZ", (float(Lua_NPC::*)(void))&Lua_NPC::GetGuardPointZ) .def("GetHealScale", (float(Lua_NPC::*)(void))&Lua_NPC::GetHealScale) - .def("GetItemIDBySlot", (uint32(Lua_NPC::*)(uint16))&Lua_NPC::GetItemIDBySlot) + .def("GetItemIDBySlot", (uint32(Lua_NPC::*)(uint16)) &Lua_NPC::GetLootItemIDBySlot) .def("GetKeepsSoldItems", (bool(Lua_NPC::*)(void))&Lua_NPC::GetKeepsSoldItems) .def("GetLootList", (Lua_NPC_Loot_List(Lua_NPC::*)(lua_State* L))&Lua_NPC::GetLootList) .def("GetLoottableID", (int(Lua_NPC::*)(void))&Lua_NPC::GetLoottableID) @@ -935,7 +935,7 @@ luabind::scope lua_register_npc() { .def("ReloadSpells", (void(Lua_NPC::*)(void))&Lua_NPC::ReloadSpells) .def("RemoveAISpell", (void(Lua_NPC::*)(int))&Lua_NPC::RemoveAISpell) .def("RemoveAISpellEffect", (void(Lua_NPC::*)(int))&Lua_NPC::RemoveAISpellEffect) - .def("RemoveCash", (void(Lua_NPC::*)(void))&Lua_NPC::RemoveCash) + .def("RemoveCash", (void(Lua_NPC::*)(void))&Lua_NPC::RemoveLootCash) .def("RemoveItem", (void(Lua_NPC::*)(int))&Lua_NPC::RemoveItem) .def("RemoveItem", (void(Lua_NPC::*)(int,int))&Lua_NPC::RemoveItem) .def("RemoveItem", (void(Lua_NPC::*)(int,int,int))&Lua_NPC::RemoveItem) diff --git a/zone/lua_npc.h b/zone/lua_npc.h index cc4743fba..ae711e7c4 100644 --- a/zone/lua_npc.h +++ b/zone/lua_npc.h @@ -44,9 +44,9 @@ public: void RemoveItem(int item_id); void RemoveItem(int item_id, int quantity); void RemoveItem(int item_id, int quantity, int slot); - void ClearItemList(); - void AddCash(uint32 copper, uint32 silver, uint32 gold, uint32 platinum); - void RemoveCash(); + void ClearLootItems(); + void AddLootCash(uint32 copper, uint32 silver, uint32 gold, uint32 platinum); + void RemoveLootCash(); int CountLoot(); int GetLoottableID(); uint32 GetCopper(); @@ -147,8 +147,8 @@ public: void ClearLastName(); bool HasItem(uint32 item_id); uint16 CountItem(uint32 item_id); - uint32 GetItemIDBySlot(uint16 slot_id); - uint16 GetFirstSlotByItemID(uint32 item_id); + uint32 GetLootItemIDBySlot(uint16 loot_slot); + uint16 GetFirstLootSlotByItemID(uint32 item_id); float GetHealScale(); float GetSpellScale(); Lua_NPC_Loot_List GetLootList(lua_State* L); diff --git a/zone/main.cpp b/zone/main.cpp index 13dbce7da..dcbb3b7b1 100644 --- a/zone/main.cpp +++ b/zone/main.cpp @@ -371,11 +371,7 @@ int main(int argc, char **argv) LogError("Loading items failed!"); LogError("Failed. But ignoring error and going on.."); } - - if (!database.LoadLoot(hotfix_name)) { - LogError("Loading loot failed!"); - return 1; - } + if (!content_db.LoadSkillCaps(std::string(hotfix_name))) { LogError("Loading skill caps failed!"); return 1; diff --git a/zone/merc.cpp b/zone/merc.cpp index df1275ce4..6376a37f1 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -4647,7 +4647,7 @@ void Merc::UpdateEquipmentLight() } uint8 general_light_type = 0; - for (auto iter = itemlist.begin(); iter != itemlist.end(); ++iter) { + for (auto iter = m_loot_items.begin(); iter != m_loot_items.end(); ++iter) { auto item = database.GetItem((*iter)->item_id); if (item == nullptr) { continue; } diff --git a/zone/npc.cpp b/zone/npc.cpp index 1f05f582a..5278fa75b 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -169,10 +169,10 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi SetTaunting(false); proximity = nullptr; - copper = 0; - silver = 0; - gold = 0; - platinum = 0; + m_loot_copper = 0; + m_loot_silver = 0; + m_loot_gold = 0; + m_loot_platinum = 0; max_dmg = npc_type_data->max_dmg; min_dmg = npc_type_data->min_dmg; attack_count = npc_type_data->attack_count; @@ -282,14 +282,14 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi m_roambox.delay = 1000; m_roambox.min_delay = 1000; - p_depop = false; - loottable_id = npc_type_data->loottable_id; - skip_global_loot = npc_type_data->skip_global_loot; - skip_auto_scale = npc_type_data->skip_auto_scale; - rare_spawn = npc_type_data->rare_spawn; - no_target_hotkey = npc_type_data->no_target_hotkey; - primary_faction = 0; - faction_amount = npc_type_data->faction_amount; + p_depop = false; + m_loottable_id = npc_type_data->loottable_id; + m_skip_global_loot = npc_type_data->skip_global_loot; + m_skip_auto_scale = npc_type_data->skip_auto_scale; + rare_spawn = npc_type_data->rare_spawn; + no_target_hotkey = npc_type_data->no_target_hotkey; + primary_faction = 0; + faction_amount = npc_type_data->faction_amount; SetNPCFactionID(npc_type_data->npc_faction_id); @@ -519,11 +519,15 @@ NPC::~NPC() safe_delete(NPCTypedata_ours); - for (auto* e : itemlist) { - safe_delete(e); + LootItems::iterator cur, end; + cur = m_loot_items.begin(); + end = m_loot_items.end(); + for (; cur != end; ++cur) { + LootItem *item = *cur; + safe_delete(item); } - itemlist.clear(); + m_loot_items.clear(); faction_list.clear(); safe_delete(reface_timer); @@ -570,265 +574,6 @@ void NPC::SetTarget(Mob* mob) { Mob::SetTarget(mob); } -ServerLootItem_Struct* NPC::GetItem(int slot_id) { - ItemList::iterator cur,end; - cur = itemlist.begin(); - end = itemlist.end(); - for(; cur != end; ++cur) { - ServerLootItem_Struct* item = *cur; - if (item->equip_slot == slot_id) { - return item; - } - } - return(nullptr); -} - -void NPC::RemoveItem(uint32 item_id, uint16 quantity, uint16 slot) { - ItemList::iterator cur,end; - cur = itemlist.begin(); - end = itemlist.end(); - for(; cur != end; ++cur) { - ServerLootItem_Struct* item = *cur; - if (item->item_id == item_id && slot <= 0 && quantity <= 0) { - itemlist.erase(cur); - UpdateEquipmentLight(); - if (UpdateActiveLight()) { SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); } - return; - } - else if (item->item_id == item_id && item->equip_slot == slot && quantity >= 1) { - if (item->charges <= quantity) { - itemlist.erase(cur); - UpdateEquipmentLight(); - if (UpdateActiveLight()) { SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); } - } - else { - item->charges -= quantity; - } - return; - } - } -} - -void NPC::CheckTrivialMinMaxLevelDrop(Mob *killer) -{ - if (killer == nullptr || !killer->IsClient()) { - return; - } - - uint16 killer_level = killer->GetLevel(); - uint8 material; - - auto cur = itemlist.begin(); - while (cur != itemlist.end()) { - if (!(*cur)) { - return; - } - - uint16 trivial_min_level = (*cur)->trivial_min_level; - uint16 trivial_max_level = (*cur)->trivial_max_level; - bool fits_trivial_criteria = ( - (trivial_min_level > 0 && killer_level < trivial_min_level) || - (trivial_max_level > 0 && killer_level > trivial_max_level) - ); - - if (fits_trivial_criteria) { - material = EQ::InventoryProfile::CalcMaterialFromSlot((*cur)->equip_slot); - if (material != EQ::textures::materialInvalid) { - SendWearChange(material); - } - - cur = itemlist.erase(cur); - continue; - } - ++cur; - } - - UpdateEquipmentLight(); - if (UpdateActiveLight()) { - SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); - } -} - -void NPC::ClearItemList() { - ItemList::iterator cur,end; - cur = itemlist.begin(); - end = itemlist.end(); - for(; cur != end; ++cur) { - ServerLootItem_Struct* item = *cur; - safe_delete(item); - } - itemlist.clear(); - - UpdateEquipmentLight(); - if (UpdateActiveLight()) - SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); -} - -void NPC::QueryLoot(Client* to, bool is_pet_query) -{ - if (!itemlist.empty()) { - if (!is_pet_query) { - to->Message( - Chat::White, - fmt::format( - "Loot | {} ({}) ID: {} Loottable ID: {}", - GetName(), - GetID(), - GetNPCTypeID(), - GetLoottableID() - ).c_str() - ); - } - - int item_count = 0; - for (auto current_item : itemlist) { - int item_number = (item_count + 1); - if (!current_item) { - LogError("NPC::QueryLoot() - ItemList error, null item."); - continue; - } - - if (!current_item->item_id || !database.GetItem(current_item->item_id)) { - LogError("NPC::QueryLoot() - Database error, invalid item."); - continue; - } - - EQ::SayLinkEngine linker; - linker.SetLinkType(EQ::saylink::SayLinkLootItem); - linker.SetLootData(current_item); - - to->Message( - Chat::White, - fmt::format( - "Item {} | {} ({}){}", - item_number, - linker.GenerateLink().c_str(), - current_item->item_id, - ( - current_item->charges > 1 ? - fmt::format( - " Amount: {}", - current_item->charges - ) : - "" - ) - ).c_str() - ); - item_count++; - } - } - - if (!is_pet_query) { - if ( - platinum || - gold || - silver || - copper - ) { - to->Message( - Chat::White, - fmt::format( - "Money | {}", - Strings::Money( - platinum, - gold, - silver, - copper - ) - ).c_str() - ); - } - } -} - -bool NPC::HasItem(uint32 item_id) { - if (!database.GetItem(item_id)) { - return false; - } - - for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { - ServerLootItem_Struct* loot_item = *current_item; - if (!loot_item) { - LogError("NPC::HasItem() - ItemList error, null item"); - continue; - } - - if (!loot_item->item_id || !database.GetItem(loot_item->item_id)) { - LogError("NPC::HasItem() - Database error, invalid item"); - continue; - } - - if (loot_item->item_id == item_id) { - return true; - } - } - return false; -} - -uint16 NPC::CountItem(uint32 item_id) { - uint16 item_count = 0; - if (!database.GetItem(item_id)) { - return item_count; - } - - for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { - ServerLootItem_Struct* loot_item = *current_item; - if (!loot_item) { - LogError("NPC::CountItem() - ItemList error, null item"); - continue; - } - - if (!loot_item->item_id || !database.GetItem(loot_item->item_id)) { - LogError("NPC::CountItem() - Database error, invalid item"); - continue; - } - - if (loot_item->item_id == item_id) { - item_count += loot_item->charges > 0 ? loot_item->charges : 1; - } - } - return item_count; -} - -uint32 NPC::GetItemIDBySlot(uint16 loot_slot) { - for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { - ServerLootItem_Struct* loot_item = *current_item; - if (loot_item->lootslot == loot_slot) { - return loot_item->item_id; - } - } - return 0; -} - -uint16 NPC::GetFirstSlotByItemID(uint32 item_id) { - for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { - ServerLootItem_Struct* loot_item = *current_item; - if (loot_item->item_id == item_id) { - return loot_item->lootslot; - } - } - return 0; -} - -void NPC::AddCash( - uint32 in_copper, - uint32 in_silver, - uint32 in_gold, - uint32 in_platinum -) { - copper = in_copper >= 0 ? in_copper : 0; - silver = in_silver >= 0 ? in_silver : 0; - gold = in_gold >= 0 ? in_gold : 0; - platinum = in_platinum >= 0 ? in_platinum : 0; -} - -void NPC::RemoveCash() { - copper = 0; - silver = 0; - gold = 0; - platinum = 0; -} - bool NPC::Process() { if (p_depop) @@ -1059,7 +804,7 @@ bool NPC::Process() } uint32 NPC::CountLoot() { - return(itemlist.size()); + return(m_loot_items.size()); } void NPC::UpdateEquipmentLight() @@ -1080,7 +825,7 @@ void NPC::UpdateEquipmentLight() } uint8 general_light_type = 0; - for (auto iter = itemlist.begin(); iter != itemlist.end(); ++iter) { + for (auto iter = m_loot_items.begin(); iter != m_loot_items.end(); ++iter) { auto item = database.GetItem((*iter)->item_id); if (item == nullptr) { continue; } @@ -1891,14 +1636,15 @@ void NPC::PickPocket(Client* thief) int steal_chance = steal_skill * 100 / (5 * over_level + 5); // Determine whether to steal money or an item. - uint32 money[6] = { 0, ((steal_skill >= 125) ? (GetPlatinum()) : (0)), ((steal_skill >= 60) ? (GetGold()) : (0)), GetSilver(), GetCopper(), 0 }; + uint32 money[6] = {0, ((steal_skill >= 125) ? (GetPlatinum()) : (0)), ((steal_skill >= 60) ? (GetGold()) : (0)), GetSilver(), + GetCopper(), 0 }; bool has_coin = ((money[PickPocketPlatinum] | money[PickPocketGold] | money[PickPocketSilver] | money[PickPocketCopper]) != 0); bool steal_item = (steal_skill >= steal_chance && (zone->random.Roll(50) || !has_coin)); // still needs to have FindFreeSlot vs PutItemInInventory issue worked out while (steal_item) { std::vector> loot_selection; // - for (auto item_iter : itemlist) { + for (auto item_iter : m_loot_items) { if (!item_iter || !item_iter->item_id) continue; @@ -2007,13 +1753,13 @@ void NPC::Disarm(Client* client, int chance) { weapon = database.GetItem(equipment[eslot]); if (weapon) { if (!weapon->Magic && weapon->NoDrop != 0) { - int16 charges = -1; - ItemList::iterator cur, end; - cur = itemlist.begin(); - end = itemlist.end(); + int16 charges = -1; + LootItems::iterator cur, end; + cur = m_loot_items.begin(); + end = m_loot_items.end(); // Get charges for the item in the loot table for (; cur != end; cur++) { - ServerLootItem_Struct* citem = *cur; + LootItem * citem = *cur; if (citem->item_id == weapon->ID) { charges = citem->charges; break; @@ -2656,7 +2402,7 @@ void NPC::ModifyNPCStat(const std::string& stat, const std::string& value) return; } else if (stat_lower == "loottable_id") { - loottable_id = Strings::ToFloat(value); + m_loottable_id = Strings::ToFloat(value); return; } else if (stat_lower == "healscale") { @@ -2804,7 +2550,7 @@ float NPC::GetNPCStat(const std::string& stat) return slow_mitigation; } else if (stat_lower == "loottable_id") { - return loottable_id; + return m_loottable_id; } else if (stat_lower == "healscale") { return healscale; @@ -2859,7 +2605,7 @@ void NPC::LevelScale() { if (RuleB(NPC, NewLevelScaling)) { if (scalerate == 0 || maxlevel <= 25) { // Don't add HP to dynamically scaled NPCs since this will be calculated later - if (max_hp > 0 || skip_auto_scale) + if (max_hp > 0 || m_skip_auto_scale) { // pre-pop seems to scale by 20 HP increments while newer by 100 // We also don't want 100 increments on newer noobie zones, check level @@ -2875,7 +2621,7 @@ void NPC::LevelScale() { } // Don't add max_dmg to dynamically scaled NPCs since this will be calculated later - if (max_dmg > 0 || skip_auto_scale) + if (max_dmg > 0 || m_skip_auto_scale) { max_dmg += (random_level - level) * 2; } @@ -3762,8 +3508,8 @@ bool NPC::IsGuard() std::vector NPC::GetLootList() { std::vector npc_items; - for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { - ServerLootItem_Struct* loot_item = *current_item; + for (auto current_item = m_loot_items.begin(); current_item != m_loot_items.end(); ++current_item) { + LootItem * loot_item = *current_item; if (!loot_item) { LogError("NPC::GetLootList() - ItemList error, null item"); continue; diff --git a/zone/npc.h b/zone/npc.h index 844198ed7..9e936e1f2 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -25,8 +25,11 @@ #include "zonedb.h" #include "../common/zone_store.h" #include "zonedump.h" -#include "../common/loottable.h" #include "../common/repositories/npc_faction_entries_repository.h" +#include "../common/repositories/loottable_repository.h" +#include "../common/repositories/loottable_entries_repository.h" +#include "../common/repositories/lootdrop_repository.h" +#include "../common/repositories/lootdrop_entries_repository.h" #include #include @@ -193,6 +196,7 @@ public: virtual void SpellProcess(); virtual void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho); + // loot void AddItem(const EQ::ItemData *item, uint16 charges, bool equip_item = true); void AddItem( uint32 item_id, @@ -205,39 +209,37 @@ public: uint32 augment_five = 0, uint32 augment_six = 0 ); - void AddLootTable(); - void AddLootTable(uint32 loottable_id); - void CheckGlobalLootTables(); - void DescribeAggro(Client *to_who, Mob *mob, bool verbose); - void RemoveItem(uint32 item_id, uint16 quantity = 0, uint16 slot = 0); - void CheckTrivialMinMaxLevelDrop(Mob *killer); - void ClearItemList(); - inline const ItemList &GetItemList() { return itemlist; } - ServerLootItem_Struct* GetItem(int slot_id); - void AddCash(uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_platinum); - void RemoveCash(); - void QueryLoot(Client* to, bool is_pet_query = false); - bool HasItem(uint32 item_id); - uint16 CountItem(uint32 item_id); - uint32 GetItemIDBySlot(uint16 loot_slot); - uint16 GetFirstSlotByItemID(uint32 item_id); + void AddLootTable(); + void AddLootTable(uint32 loottable_id, bool is_global = false); + void AddLootDropTable(uint32 lootdrop_id, uint8 drop_limit, uint8 min_drop); + void CheckGlobalLootTables(); + void RemoveItem(uint32 item_id, uint16 quantity = 0, uint16 slot = 0); + void CheckTrivialMinMaxLevelDrop(Mob *killer); + void ClearLootItems(); + inline const LootItems &GetLootItems() { return m_loot_items; } + LootItem *GetItem(int slot_id); + void AddLootCash(uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_platinum); + void RemoveLootCash(); + void QueryLoot(Client *to, bool is_pet_query = false); + bool HasItem(uint32 item_id); + uint16 CountItem(uint32 item_id); + uint32 GetLootItemIDBySlot(uint16 loot_slot); + uint16 GetFirstLootSlotByItemID(uint32 item_id); std::vector GetLootList(); - uint32 CountLoot(); - inline uint32 GetLoottableID() const { return loottable_id; } + uint32 CountLoot(); + inline uint32 GetLoottableID() const { return m_loottable_id; } + inline bool DropsGlobalLoot() const { return !m_skip_global_loot; } + inline uint32 GetCopper() const { return m_loot_copper; } + inline uint32 GetSilver() const { return m_loot_silver; } + inline uint32 GetGold() const { return m_loot_gold; } + inline uint32 GetPlatinum() const { return m_loot_platinum; } + inline void SetCopper(uint32 amt) { m_loot_copper = amt; } + inline void SetSilver(uint32 amt) { m_loot_silver = amt; } + inline void SetGold(uint32 amt) { m_loot_gold = amt; } + inline void SetPlatinum(uint32 amt) { m_loot_platinum = amt; } + + void DescribeAggro(Client *to_who, Mob *mob, bool verbose); virtual void UpdateEquipmentLight(); - inline bool DropsGlobalLoot() const { return !skip_global_loot; } - - inline uint32 GetCopper() const { return copper; } - inline uint32 GetSilver() const { return silver; } - inline uint32 GetGold() const { return gold; } - inline uint32 GetPlatinum() const { return platinum; } - - inline void SetCopper(uint32 amt) { copper = amt; } - inline void SetSilver(uint32 amt) { silver = amt; } - inline void SetGold(uint32 amt) { gold = amt; } - inline void SetPlatinum(uint32 amt) { platinum = amt; } - - virtual int64 CalcMaxMana(); void SetGrid(int32 grid_){ grid=grid_; } void SetSpawnGroupId(uint32 sg2){ spawn_group_id =sg2; } @@ -322,8 +324,7 @@ public: void AddLootDrop( const EQ::ItemData *item2, - ItemList *itemlist, - LootDropEntries_Struct loot_drop, + LootdropEntriesRepository::LootdropEntries loot_drop, bool wear_change = false, uint32 augment_one = 0, uint32 augment_two = 0, @@ -333,7 +334,7 @@ public: uint32 augment_six = 0 ); - bool MeetsLootDropLevelRequirements(LootDropEntries_Struct loot_drop, bool verbose=false); + bool MeetsLootDropLevelRequirements(LootdropEntriesRepository::LootdropEntries loot_drop, bool verbose=false); void CheckSignal(); @@ -412,8 +413,6 @@ public: float GetProximityMaxZ(); bool IsProximitySet(); - ItemList itemlist; //kathgar - why is this public? Doing other things or I would check the code - NPCProximity* proximity; Spawn2* respawn2; QGlobalCache *GetQGlobals() { return qGlobals; } @@ -539,13 +538,13 @@ public: inline bool GetAlwaysAggro() { return always_aggro; } inline bool GetNPCAggro() { return npc_aggro; } inline bool GetIgnoreDespawn() { return ignore_despawn; } - inline bool GetSkipGlobalLoot() { return skip_global_loot; } + inline bool GetSkipGlobalLoot() { return m_skip_global_loot; } std::unique_ptr AIautocastspell_timer; virtual int GetStuckBehavior() const { return NPCTypedata_ours ? NPCTypedata_ours->stuck_behavior : NPCTypedata->stuck_behavior; } - inline bool IsSkipAutoScale() const { return skip_auto_scale; } + inline bool IsSkipAutoScale() const { return m_skip_auto_scale; } void ScaleNPC(uint8 npc_level, bool always_scale = false, bool override_special_abilities = false); @@ -554,8 +553,6 @@ public: void SendPositionToClients(); - static LootDropEntries_Struct NewLootDropEntry(); - bool CanPathTo(float x, float y, float z); protected: @@ -567,13 +564,17 @@ protected: friend class EntityList; friend class Aura; - uint32 copper; - uint32 silver; - uint32 gold; - uint32 platinum; - int32 grid; - uint32 spawn_group_id; - uint16 wp_m; + + int32 grid; + uint32 spawn_group_id; + uint16 wp_m; + + // loot + uint32 m_loot_copper; + uint32 m_loot_silver; + uint32 m_loot_gold; + uint32 m_loot_platinum; + LootItems m_loot_items; std::list faction_list; @@ -697,9 +698,9 @@ protected: private: - uint32 loottable_id; - bool skip_global_loot; - bool skip_auto_scale; + uint32 m_loottable_id; + bool m_skip_global_loot; + bool m_skip_auto_scale; bool p_depop; bool m_record_loot_stats; std::vector m_rolled_items = {}; diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 66ed0bf2c..6852fa598 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -2263,14 +2263,14 @@ void Perl_Client_UntrainDiscBySpellID(Client* self, uint16 spell_id, bool update void Perl_Client_SummonBaggedItems(Client* self, uint32 bag_item_id, perl::reference bag_items_ref) // @categories Inventory and Items, Script Utility { - std::vector bagged_items; + std::vector bagged_items; perl::array bag_items = bag_items_ref; for (perl::hash bag_item : bag_items) // only works if all elements are hashrefs { if (bag_item.exists("item_id") && bag_item.exists("charges")) { - ServerLootItem_Struct item{}; + LootItem item{}; item.item_id = bag_item["item_id"]; item.charges = bag_item["charges"]; item.attuned = bag_item.exists("attuned") ? bag_item["attuned"] : 0; diff --git a/zone/perl_npc.cpp b/zone/perl_npc.cpp index 4e25693b0..b8ebeb540 100644 --- a/zone/perl_npc.cpp +++ b/zone/perl_npc.cpp @@ -86,19 +86,19 @@ void Perl_NPC_RemoveItem(NPC* self, uint32 item_id, uint16 quantity, uint16 slot self->RemoveItem(item_id, quantity, slot_id); } -void Perl_NPC_ClearItemList(NPC* self) // @categories Inventory and Items +void Perl_NPC_ClearLootItems(NPC* self) // @categories Inventory and Items { - self->ClearItemList(); + self->ClearLootItems(); } -void Perl_NPC_AddCash(NPC* self, uint32 copper, uint32 silver, uint32 gold, uint32 platinum) // @categories Currency and Points +void Perl_NPC_AddLootCash(NPC* self, uint32 copper, uint32 silver, uint32 gold, uint32 platinum) // @categories Currency and Points { - self->AddCash(copper, silver, gold, platinum); + self->AddLootCash(copper, silver, gold, platinum); } -void Perl_NPC_RemoveCash(NPC* self) // @categories Currency and Points +void Perl_NPC_RemoveLootCash(NPC* self) // @categories Currency and Points { - self->RemoveCash(); + self->RemoveLootCash(); } uint32_t Perl_NPC_CountLoot(NPC* self) // @categories Inventory and Items @@ -620,14 +620,14 @@ int Perl_NPC_CountItem(NPC* self, uint32 item_id) return self->CountItem(item_id); } -uint32_t Perl_NPC_GetItemIDBySlot(NPC* self, uint16 loot_slot) +uint32_t Perl_NPC_GetLootItemIDBySlot(NPC* self, uint16 loot_slot) { - return self->GetItemIDBySlot(loot_slot); + return self->GetLootItemIDBySlot(loot_slot); } -int Perl_NPC_GetFirstSlotByItemID(NPC* self, uint32 item_id) +int Perl_NPC_GetFirstLootSlotByItemID(NPC* self, uint32 item_id) { - return self->GetFirstSlotByItemID(item_id); + return self->GetFirstLootSlotByItemID(item_id); } float Perl_NPC_GetHealScale(NPC* self) // @categories Stats and Attributes @@ -799,7 +799,7 @@ void perl_register_npc() package.add("AddAISpell", (void(*)(NPC*, int16, uint16, uint32, int, int, int16))&Perl_NPC_AddSpellToNPCList); package.add("AddAISpell", (void(*)(NPC*, int16, uint16, uint32, int, int, int16, int8, int8))&Perl_NPC_AddSpellToNPCList); package.add("AddAISpellEffect", &Perl_NPC_AddAISpellEffect); - package.add("AddCash", &Perl_NPC_AddCash); + package.add("AddCash", &Perl_NPC_AddLootCash); package.add("AddDefensiveProc", &Perl_NPC_AddDefensiveProc); package.add("AddItem", (void(*)(NPC*, uint32))&Perl_NPC_AddItem); package.add("AddItem", (void(*)(NPC*, uint32, uint16))&Perl_NPC_AddItem); @@ -818,7 +818,7 @@ void perl_register_npc() package.add("CalculateNewWaypoint", &Perl_NPC_CalculateNewWaypoint); package.add("ChangeLastName", &Perl_NPC_ChangeLastName); package.add("CheckNPCFactionAlly", &Perl_NPC_CheckNPCFactionAlly); - package.add("ClearItemList", &Perl_NPC_ClearItemList); + package.add("ClearItemList", &Perl_NPC_ClearLootItems); package.add("ClearLastName", &Perl_NPC_ClearLastName); package.add("CountItem", &Perl_NPC_CountItem); package.add("CountLoot", &Perl_NPC_CountLoot); @@ -830,14 +830,14 @@ void perl_register_npc() package.add("GetAvoidanceRating", &Perl_NPC_GetAvoidanceRating); package.add("GetCombatState", &Perl_NPC_GetCombatState); package.add("GetCopper", &Perl_NPC_GetCopper); - package.add("GetFirstSlotByItemID", &Perl_NPC_GetFirstSlotByItemID); + package.add("GetFirstSlotByItemID", &Perl_NPC_GetFirstLootSlotByItemID); package.add("GetGold", &Perl_NPC_GetGold); package.add("GetGrid", &Perl_NPC_GetGrid); package.add("GetGuardPointX", &Perl_NPC_GetGuardPointX); package.add("GetGuardPointY", &Perl_NPC_GetGuardPointY); package.add("GetGuardPointZ", &Perl_NPC_GetGuardPointZ); package.add("GetHealScale", &Perl_NPC_GetHealScale); - package.add("GetItemIDBySlot", &Perl_NPC_GetItemIDBySlot); + package.add("GetItemIDBySlot", &Perl_NPC_GetLootItemIDBySlot); package.add("GetKeepsSoldItems", &Perl_NPC_GetKeepsSoldItems); package.add("GetLDoNLockedSkill", &Perl_NPC_GetLDoNLockedSkill); package.add("GetLDoNTrapType", &Perl_NPC_GetLDoNTrapType); @@ -900,7 +900,7 @@ void perl_register_npc() package.add("ReloadSpells", &Perl_NPC_ReloadSpells); package.add("RemoveAISpell", &Perl_NPC_RemoveSpellFromNPCList); package.add("RemoveAISpellEffect", &Perl_NPC_RemoveAISpellEffect); - package.add("RemoveCash", &Perl_NPC_RemoveCash); + package.add("RemoveCash", &Perl_NPC_RemoveLootCash); package.add("RemoveDefensiveProc", &Perl_NPC_RemoveDefensiveProc); package.add("RemoveFromHateList", &Perl_NPC_RemoveFromHateList); package.add("RemoveItem", (void(*)(NPC*, uint32))&Perl_NPC_RemoveItem); diff --git a/zone/perl_player_corpse.cpp b/zone/perl_player_corpse.cpp index 6e347b05f..60de30201 100644 --- a/zone/perl_player_corpse.cpp +++ b/zone/perl_player_corpse.cpp @@ -86,7 +86,7 @@ void Perl_Corpse_SetCash(Corpse* self, uint16 copper, uint16 silver, uint16 gold self->SetCash(copper, silver, gold, platinum); } -void Perl_Corpse_RemoveCash(Corpse* self) // @categories Currency and Points, Corpse +void Perl_Corpse_RemoveLootCash(Corpse* self) // @categories Currency and Points, Corpse { self->RemoveCash(); } @@ -166,14 +166,14 @@ int Perl_Corpse_CountItem(Corpse* self, uint32_t item_id) // @categories Script return self->CountItem(item_id); } -uint32_t Perl_Corpse_GetItemIDBySlot(Corpse* self, uint16_t loot_slot) // @categories Script Utility +uint32_t Perl_Corpse_GetLootItemIDBySlot(Corpse* self, uint16_t loot_slot) // @categories Script Utility { return self->GetItemIDBySlot(loot_slot); } -int Perl_Corpse_GetFirstSlotByItemID(Corpse* self, uint32_t item_id) // @categories Script Utility +int Perl_Corpse_GetFirstLootSlotByItemID(Corpse* self, uint32_t item_id) // @categories Script Utility { - return self->GetFirstSlotByItemID(item_id); + return self->GetFirstLootSlotByItemID(item_id); } void Perl_Corpse_RemoveItemByID(Corpse* self, uint32_t item_id) // @categories Script Utility @@ -219,9 +219,9 @@ void perl_register_corpse() package.add("GetCopper", &Perl_Corpse_GetCopper); package.add("GetDBID", &Perl_Corpse_GetDBID); package.add("GetDecayTime", &Perl_Corpse_GetDecayTime); - package.add("GetFirstSlotByItemID", &Perl_Corpse_GetFirstSlotByItemID); + package.add("GetFirstSlotByItemID", &Perl_Corpse_GetFirstLootSlotByItemID); package.add("GetGold", &Perl_Corpse_GetGold); - package.add("GetItemIDBySlot", &Perl_Corpse_GetItemIDBySlot); + package.add("GetItemIDBySlot", &Perl_Corpse_GetLootItemIDBySlot); package.add("GetLootList", &Perl_Corpse_GetLootList); package.add("GetOwnerName", &Perl_Corpse_GetOwnerName); package.add("GetPlatinum", &Perl_Corpse_GetPlatinum); @@ -232,7 +232,7 @@ void perl_register_corpse() package.add("IsLocked", &Perl_Corpse_IsLocked); package.add("IsRezzed", &Perl_Corpse_IsRezzed); package.add("Lock", &Perl_Corpse_Lock); - package.add("RemoveCash", &Perl_Corpse_RemoveCash); + package.add("RemoveCash", &Perl_Corpse_RemoveLootCash); package.add("RemoveItem", &Perl_Corpse_RemoveItem); package.add("RemoveItemByID", (void(*)(Corpse*, uint32_t))&Perl_Corpse_RemoveItemByID); package.add("RemoveItemByID", (void(*)(Corpse*, uint32_t, int))&Perl_Corpse_RemoveItemByID); diff --git a/zone/pets.cpp b/zone/pets.cpp index 2bda14235..dd383f826 100644 --- a/zone/pets.cpp +++ b/zone/pets.cpp @@ -271,7 +271,7 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, for (int i = EQ::invslot::EQUIPMENT_BEGIN; i <= EQ::invslot::EQUIPMENT_END; i++) if (petinv[i]) { item = database.GetItem(petinv[i]); - npc->AddLootDrop(item, &npc->itemlist, NPC::NewLootDropEntry(), true); + npc->AddLootDrop(item, LootdropEntriesRepository::NewNpcEntity(), true); } } @@ -580,7 +580,7 @@ void NPC::SetPetState(SpellBuff_Struct *pet_buffs, uint32 *items) { bool petCanHaveNoDrop = (RuleB(Pets, CanTakeNoDrop) && _CLIENTPET(this) && GetPetType() <= petOther); if (!noDrop || petCanHaveNoDrop) { - AddLootDrop(item2, &itemlist, NPC::NewLootDropEntry(), true); + AddLootDrop(item2, LootdropEntriesRepository::NewNpcEntity(), true); } } } diff --git a/zone/sidecar_api/loot_simulator_controller.cpp b/zone/sidecar_api/loot_simulator_controller.cpp index f2d4f53fb..1a91ab3fd 100644 --- a/zone/sidecar_api/loot_simulator_controller.cpp +++ b/zone/sidecar_api/loot_simulator_controller.cpp @@ -2,7 +2,7 @@ #include "../../common/json/json.hpp" #include "../zone.h" -extern Zone* zone; +extern Zone *zone; void SidecarApi::LootSimulatorController(const httplib::Request &req, httplib::Response &res) { @@ -17,7 +17,7 @@ void SidecarApi::LootSimulatorController(const httplib::Request &req, httplib::R auto npc_type = content_db.LoadNPCTypesData(npc_id); if (npc_type) { - auto npc = new NPC( + auto npc = new NPC( npc_type, nullptr, glm::vec4(0, 0, 0, 0), @@ -47,51 +47,64 @@ void SidecarApi::LootSimulatorController(const httplib::Request &req, httplib::R entity_list.AddNPC(npc); - j["data"]["loottable_id"] = loottable_id; + j["data"]["loottable_id"] = loottable_id; j["data"]["npc_id"] = npc_id; j["data"]["npc_name"] = npc->GetCleanName(); j["data"]["rolled_items_count"] = npc->GetRolledItems().size(); j["data"]["iterations"] = iterations; // npc level loot table - auto loot_table = database.GetLootTable(loottable_id); + auto loot_table = zone->GetLootTable(loottable_id); if (!loot_table) { res.status = 400; j["error"] = fmt::format("Loot table not found [{}]", loottable_id); res.set_content(j.dump(), "application/json"); return; } - for (uint32 i = 0; i < loot_table->NumEntries; i++) { - auto le = loot_table->Entries[i]; - nlohmann::json jle; - jle["lootdrop_id"] = le.lootdrop_id; - jle["droplimit"] = le.droplimit; - jle["mindrop"] = le.mindrop; - jle["multiplier"] = le.multiplier; - jle["probability"] = le.probability; + auto le = zone->GetLootTableEntries(loottable_id); - auto loot_drop = database.GetLootDrop(le.lootdrop_id); - if (!loot_drop) { + // translate above for loop using le + for (auto &e: le) { + auto loot_drop = zone->GetLootdrop(e.lootdrop_id); + if (!loot_drop.id) { continue; } - for (uint32 ei = 0; ei < loot_drop->NumEntries; ei++) { - auto e = loot_drop->Entries[ei]; - int rolled_count = npc->GetRolledItemCount(e.item_id); - const EQ::ItemData *item = database.GetItem(e.item_id); + LogLootDetail( + "# Lootdrop ID [{}] drop_limit [{}] min_drop [{}] mult [{}] probability [{}]", + e.lootdrop_id, + e.droplimit, + e.mindrop, + e.multiplier, + e.probability + ); + nlohmann::json jle; + jle["lootdrop_id"] = e.lootdrop_id; + jle["droplimit"] = e.droplimit; + jle["mindrop"] = e.mindrop; + jle["multiplier"] = e.multiplier; + jle["probability"] = e.probability; + + auto loot_drop_entries = zone->GetLootdropEntries(e.lootdrop_id); + int slot = 0; + + for (auto &f: loot_drop_entries) { + int rolled_count = npc->GetRolledItemCount(f.item_id); + const EQ::ItemData *item = database.GetItem(f.item_id); auto rolled_percentage = (float) ((float) ((float) rolled_count / (float) iterations) * 100); nlohmann::json drop; - drop["slot"] = ei; - drop["item_id"] = e.item_id; + drop["slot"] = slot; + drop["item_id"] = f.item_id; drop["item_name"] = item->Name; - drop["chance"] = fmt::format("{:.2f}", e.chance); + drop["chance"] = fmt::format("{:.2f}", f.chance); drop["simulate_rolled_count"] = rolled_count; drop["simulate_rolled_percentage"] = fmt::format("{:.2f}", rolled_percentage); jle["drops"].push_back(drop); + slot++; } j["lootdrops"].push_back(jle); @@ -99,66 +112,58 @@ void SidecarApi::LootSimulatorController(const httplib::Request &req, httplib::R // global loot for (auto &id: zone->GetGlobalLootTables(npc)) { - loot_table = database.GetLootTable(id); + loot_table = zone->GetLootTable(id); if (!loot_table) { LogInfo("Global Loot table not found [{}]", id); continue; } - for (uint32 i = 0; i < loot_table->NumEntries; i++) { - auto le = loot_table->Entries[i]; + le = zone->GetLootTableEntries(id); - LogInfo( - "# Lootdrop ID [{}] drop_limit [{}] min_drop [{}] mult [{}] probability [{}]", - le.lootdrop_id, - le.droplimit, - le.mindrop, - le.multiplier, - le.probability - ); - - nlohmann::json jle; - jle["lootdrop_id"] = le.lootdrop_id; - jle["droplimit"] = le.droplimit; - jle["mindrop"] = le.mindrop; - jle["multiplier"] = le.multiplier; - jle["probability"] = le.probability; - - auto loot_drop = database.GetLootDrop(le.lootdrop_id); - if (!loot_drop) { + // translate above for loop using le + for (auto &e: le) { + auto loot_drop = zone->GetLootdrop(e.lootdrop_id); + if (!loot_drop.id) { continue; } - for (uint32 ei = 0; ei < loot_drop->NumEntries; ei++) { - auto e = loot_drop->Entries[ei]; - int rolled_count = npc->GetRolledItemCount(e.item_id); - const EQ::ItemData *item = database.GetItem(e.item_id); + LogLootDetail( + "# Lootdrop ID [{}] drop_limit [{}] min_drop [{}] mult [{}] probability [{}]", + e.lootdrop_id, + e.droplimit, + e.mindrop, + e.multiplier, + e.probability + ); - auto rolled_percentage = (float) ((float) ((float) rolled_count / (float) iterations) * - 100); + nlohmann::json jle; + jle["lootdrop_id"] = e.lootdrop_id; + jle["droplimit"] = e.droplimit; + jle["mindrop"] = e.mindrop; + jle["multiplier"] = e.multiplier; + jle["probability"] = e.probability; + auto loot_drop_entries = zone->GetLootdropEntries(e.lootdrop_id); + int slot = 0; - LogInfo( - "-- [{}] item_id [{}] chance [{}] rolled_count [{}] ({:.2f}%) name [{}]", - ei, - e.item_id, - e.chance, - rolled_count, - rolled_percentage, - item->Name - ); + for (auto &f: loot_drop_entries) { + int rolled_count = npc->GetRolledItemCount(f.item_id); + const EQ::ItemData *item = database.GetItem(f.item_id); + + auto rolled_percentage = (float) ((float) ((float) rolled_count / (float) iterations) * 100); nlohmann::json drop; - drop["slot"] = ei; - drop["item_id"] = e.item_id; + drop["slot"] = slot; + drop["item_id"] = f.item_id; drop["item_name"] = item->Name; - drop["chance"] = fmt::format("{:.2f}", e.chance); + drop["chance"] = fmt::format("{:.2f}", f.chance); drop["simulate_rolled_count"] = rolled_count; drop["simulate_rolled_percentage"] = fmt::format("{:.2f}", rolled_percentage); jle["drops"].push_back(drop); - - j["global"]["lootdrops"].push_back(jle); + slot++; } + + j["global"]["lootdrops"].push_back(jle); } } j["data"]["time"] = benchmark.elapsed(); diff --git a/zone/sidecar_api/sidecar_api.cpp b/zone/sidecar_api/sidecar_api.cpp index d50c27ac5..d6e6596c9 100644 --- a/zone/sidecar_api/sidecar_api.cpp +++ b/zone/sidecar_api/sidecar_api.cpp @@ -2,7 +2,6 @@ #include "../../common/http/httplib.h" #include "../../common/eqemu_logsys.h" #include "../zonedb.h" -#include "../../shared_memory/loot.h" #include "../../common/process.h" #include "../common.h" #include "../zone.h" @@ -58,11 +57,6 @@ void SidecarApi::BootWebserver(int port, const std::string &key) std::cout << output << "\n"; } - LogInfo("Loading loot tables"); - if (!database.LoadLoot(hotfix_name)) { - LogError("Loading loot failed!"); - } - // bootup a fake zone Zone::Bootup(ZoneID("qrg"), 0, false); zone->StopShutdownTimer(); diff --git a/zone/trading.cpp b/zone/trading.cpp index b7392a885..3ec9be522 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -814,13 +814,12 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st ((is_pet && (!bagitem->IsQuestItem() || pets_can_take_quest_items) || !is_pet)))) { - auto loot_drop_entry = NPC::NewLootDropEntry(); + auto loot_drop_entry = LootdropEntriesRepository::NewNpcEntity(); loot_drop_entry.equip_item = 1; loot_drop_entry.item_charges = static_cast(baginst->GetCharges()); tradingWith->CastToNPC()->AddLootDrop( bagitem, - &tradingWith->CastToNPC()->itemlist, loot_drop_entry, true ); @@ -842,13 +841,12 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st } } - auto new_loot_drop_entry = NPC::NewLootDropEntry(); + auto new_loot_drop_entry = LootdropEntriesRepository::NewNpcEntity(); new_loot_drop_entry.equip_item = 1; new_loot_drop_entry.item_charges = static_cast(inst->GetCharges()); tradingWith->CastToNPC()->AddLootDrop( item, - &tradingWith->CastToNPC()->itemlist, new_loot_drop_entry, true ); diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 22a2513b8..37b388772 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -2015,6 +2015,14 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) player_event_logs.ReloadSettings(); break; } + case ServerOP_ReloadLoot: + { + if (zone && zone->IsLoaded()) { + zone->SendReloadMessage("Loot"); + zone->ReloadLootTables(); + } + break; + } case ServerOP_ReloadMerchants: { if (zone && zone->IsLoaded()) { zone->SendReloadMessage("Merchants"); @@ -3508,11 +3516,6 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) LogError("Loading items failed!"); } - LogInfo("Loading loot tables"); - if (!content_db.LoadLoot(hotfix_name)) { - LogError("Loading loot failed!"); - } - LogInfo("Loading skill caps"); if (!content_db.LoadSkillCaps(std::string(hotfix_name))) { LogError("Loading skill caps failed!"); diff --git a/zone/zone.cpp b/zone/zone.cpp index 48533e7e7..9e7d7c46e 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -3240,3 +3240,5 @@ void Zone::SetSecondsBeforeIdle(uint32 seconds_before_idle) { Zone::m_seconds_before_idle = seconds_before_idle; } + +#include "zone_loot.cpp" diff --git a/zone/zone.h b/zone/zone.h index dec231648..7531701b6 100755 --- a/zone/zone.h +++ b/zone/zone.h @@ -41,6 +41,10 @@ #include "../common/repositories/npc_faction_repository.h" #include "../common/repositories/npc_faction_entries_repository.h" #include "../common/repositories/faction_association_repository.h" +#include "../common/repositories/loottable_repository.h" +#include "../common/repositories/loottable_entries_repository.h" +#include "../common/repositories/lootdrop_repository.h" +#include "../common/repositories/lootdrop_entries_repository.h" struct EXPModifier { @@ -425,6 +429,16 @@ public: void ReloadFactionAssociations(); FactionAssociationRepository::FactionAssociation* GetFactionAssociation(const uint32 faction_id); + // loot + void LoadLootTable(const uint32 loottable_id); + void LoadLootTables(const std::vector& loottable_ids); + void ClearLootTables(); + void ReloadLootTables(); + LoottableRepository::Loottable *GetLootTable(const uint32 loottable_id); + std::vector GetLootTableEntries(const uint32 loottable_id) const; + LootdropRepository::Lootdrop GetLootdrop(const uint32 lootdrop_id) const; + std::vector GetLootdropEntries(const uint32 lootdrop_id) const; + private: bool allow_mercs; bool can_bind; @@ -479,6 +493,12 @@ private: std::vector m_npc_factions = { }; std::vector m_npc_faction_entries = { }; std::vector m_faction_associations = { }; + + // loot + std::vector m_loottables = {}; + std::vector m_loottable_entries = {}; + std::vector m_lootdrops = {}; + std::vector m_lootdrop_entries = {}; }; #endif diff --git a/zone/zone_loot.cpp b/zone/zone_loot.cpp new file mode 100644 index 000000000..3b2d133f4 --- /dev/null +++ b/zone/zone_loot.cpp @@ -0,0 +1,200 @@ +#include +#include "zone.h" +#include "../common/repositories/loottable_repository.h" +#include "../common/repositories/loottable_entries_repository.h" +#include "../common/repositories/lootdrop_repository.h" +#include "../common/repositories/lootdrop_entries_repository.h" + +void Zone::LoadLootTables(const std::vector &loottable_ids) +{ + BenchTimer timer; + + // check if table is already loaded + for (const auto &e: loottable_ids) { + for (const auto &f: m_loottables) { + if (e == f.id) { + LogLootDetail("Loottable [{}] already loaded", e); + return; + } + } + } + + if (loottable_ids.empty()) { + LogLootDetail("No loottables to load"); + return; + } + + auto loottables = LoottableRepository::GetWhere( + content_db, + fmt::format( + "id IN ({})", + Strings::Join(loottable_ids, ",") + ) + ); + + auto loottable_entries = LoottableEntriesRepository::GetWhere( + content_db, + fmt::format( + "loottable_id IN ({})", + Strings::Join(loottable_ids, ",") + ) + ); + + std::vector lootdrop_ids; + for (const auto &e: loottable_entries) { + if (std::find( + lootdrop_ids.begin(), + lootdrop_ids.end(), + e.lootdrop_id + ) == lootdrop_ids.end()) { + lootdrop_ids.push_back(e.lootdrop_id); + } + } + + if (lootdrop_ids.empty()) { + LogLoot("No lootdrops to load for loottable(s) [{}]", Strings::Join(loottable_ids, ",")); + return; + } + + auto lootdrops = LootdropRepository::GetWhere( + content_db, + fmt::format( + "id IN ({})", + Strings::Join(lootdrop_ids, ",") + ) + ); + + auto lootdrop_entries = LootdropEntriesRepository::GetWhere( + content_db, + fmt::format( + "lootdrop_id IN ({})", + Strings::Join(lootdrop_ids, ",") + ) + ); + + // emplace back tables to m_loottables if not exists + for (const auto &e: loottables) { + bool has_table = false; + + for (const auto &l: m_loottables) { + if (e.id == l.id) { + has_table = true; + break; + } + } + if (!has_table) { + // add loottable + m_loottables.emplace_back(e); + + // add loottable entries + for (const auto &f: loottable_entries) { + if (e.id == f.loottable_id) { + m_loottable_entries.emplace_back(f); + + // add lootdrop + for (const auto &g: lootdrops) { + if (f.lootdrop_id == g.id) { + m_lootdrops.emplace_back(g); + + // add lootdrop entries + for (const auto &h: lootdrop_entries) { + if (g.id == h.lootdrop_id) { + m_lootdrop_entries.emplace_back(h); + } + } + } + } + } + } + } + } + + if (loottable_ids.size() > 1) { + LogInfo("Loaded [{}] loottables ({}s)", m_loottables.size(), std::to_string(timer.elapsed())); + } +} + +void Zone::LoadLootTable(const uint32 loottable_id) +{ + if (loottable_id == 0) { + return; + } + + LoadLootTables({loottable_id}); +} + +void Zone::ClearLootTables() +{ + m_loottables.clear(); + m_loottable_entries.clear(); + m_lootdrops.clear(); + m_lootdrop_entries.clear(); +} + +void Zone::ReloadLootTables() +{ + ClearLootTables(); + + std::vector loottable_ids = {}; + for (const auto& n : entity_list.GetNPCList()) { + // only add loottable if it's not already in the list + if (n.second->GetLoottableID() != 0) { + if (std::find( + loottable_ids.begin(), + loottable_ids.end(), + n.second->GetLoottableID() + ) == loottable_ids.end()) { + loottable_ids.push_back(n.second->GetLoottableID()); + } + } + } + + LoadLootTables(loottable_ids); +} + +LoottableRepository::Loottable *Zone::GetLootTable(const uint32 loottable_id) +{ + for (auto &e: m_loottables) { + if (e.id == loottable_id) { + return &e; + } + } + + return nullptr; +} + +std::vector Zone::GetLootTableEntries(const uint32 loottable_id) const +{ + std::vector entries = {}; + for (const auto &e: m_loottable_entries) { + if (e.loottable_id == loottable_id) { + entries.emplace_back(e); + } + } + + return entries; +} + +LootdropRepository::Lootdrop Zone::GetLootdrop(const uint32 lootdrop_id) const +{ + for (const auto &e: m_lootdrops) { + if (e.id == lootdrop_id) { + return e; + } + } + + return {}; +} + +std::vector Zone::GetLootdropEntries(const uint32 lootdrop_id) const +{ + std::vector entries = {}; + for (const auto &e: m_lootdrop_entries) { + if (e.lootdrop_id == lootdrop_id) { + entries.emplace_back(e); + } + } + + return entries; +} + diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 50755459e..203a53f39 100755 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -1747,6 +1747,7 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load std::vector npc_ids; std::vector npc_faction_ids; + std::vector loottable_ids; for (NpcTypesRepository::NpcTypes &n : NpcTypesRepository::GetWhere((Database &) content_db, filter)) { NPCType *t; @@ -1799,6 +1800,13 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load t->special_abilities[0] = '\0'; } + if (n.loottable_id > 0) { + // check if we already have this loottable_id before inserting it + if (std::find(loottable_ids.begin(), loottable_ids.end(), n.loottable_id) == loottable_ids.end()) { + loottable_ids.emplace_back(n.loottable_id); + } + } + t->npc_spells_id = n.npc_spells_id; t->npc_spells_effects_id = n.npc_spells_effects_id; t->d_melee_texture1 = n.d_melee_texture1; @@ -1984,6 +1992,8 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load zone->LoadNPCFactionAssociations(npc_faction_ids); } + zone->LoadLootTables(loottable_ids); + return npc; } diff --git a/zone/zonedb.h b/zone/zonedb.h index 03711a6cd..8cb4f0048 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -367,7 +367,7 @@ namespace RaidLootTypes { } class ZoneDatabase : public SharedDatabase { - typedef std::list ItemList; + typedef std::list ItemList; public: ZoneDatabase(); ZoneDatabase(const char* host, const char* user, const char* passwd, const char* database,uint32 port);