[Loot] Remove from shared memory, simplification (#3988)

* First pass of pulling loot out of shared memory, functional

* More code cleanup

* More cleanup

* More cleanup

* More cleanup

* Add loot reload type

* Reload, logging

* Update npc.h

* Cleanup

* Logging, don't load attempt to load loottable id 0

* Update worldserver.cpp

* Update client.cpp

* Update zone_loot.cpp

* PR feedback

* Update zone.cpp

* Memory leak suggestion

* Update CMakeLists.txt

* Post rebase issues
This commit is contained in:
Chris Miles 2024-02-05 15:17:53 -06:00 committed by GitHub
parent fcbf5cae47
commit c654c1d674
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 1595 additions and 1890 deletions

View File

@ -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

View File

@ -22,7 +22,6 @@
#include "../database.h"
#include "../rulesys.h"
#include "../eqemu_logsys.h"
#include "../loottable.h"
#include "../repositories/content_flags_repository.h"

View File

@ -23,11 +23,17 @@
#include <string>
#include <vector>
#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;

View File

@ -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<ServerLootItem_Struct*> 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

33
common/loot.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef CODE_LOOT_H
#define CODE_LOOT_H
#include <list>
#include <string>
#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<LootItem*> LootItems;
#endif //CODE_LOOT_H

View File

@ -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

View File

@ -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;
}
};

View File

@ -24,8 +24,9 @@
#include <string>
#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;

View File

@ -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

View File

@ -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<LootTable_Struct> hash(static_cast<uint8*>(data), size);
uint8 loot_table[sizeof(LootTable_Struct) + (sizeof(LootTableEntries_Struct) * 128)];
LootTable_Struct *lt = reinterpret_cast<LootTable_Struct*>(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<int16>(Strings::ToInt(row[9]));
lt->content_flags.max_expansion = static_cast<int16>(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<uint8>(Strings::ToUnsignedInt(row[5]));
lt->Entries[current_entry].droplimit = static_cast<uint8>(Strings::ToUnsignedInt(row[6]));
lt->Entries[current_entry].mindrop = static_cast<uint8>(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<LootDrop_Struct> hash(static_cast<uint8*>(data), size);
uint8 loot_drop[sizeof(LootDrop_Struct) + (sizeof(LootDropEntries_Struct) * 1260)];
LootDrop_Struct *p_loot_drop_struct = reinterpret_cast<LootDrop_Struct*>(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<int16>(Strings::ToInt(row[10]));
p_loot_drop_struct->content_flags.max_expansion = static_cast<int16>(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<int8>(Strings::ToUnsignedInt(row[2]));
p_loot_drop_struct->Entries[current_entry].equip_item = static_cast<uint8>(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<uint16>(Strings::ToUnsignedInt(row[5]));
p_loot_drop_struct->Entries[current_entry].trivial_max_level = static_cast<uint16>(Strings::ToUnsignedInt(row[6]));
p_loot_drop_struct->Entries[current_entry].npc_min_level = static_cast<uint16>(Strings::ToUnsignedInt(row[7]));
p_loot_drop_struct->Entries[current_entry].npc_max_level = static_cast<uint16>(Strings::ToUnsignedInt(row[8]));
p_loot_drop_struct->Entries[current_entry].multiplier = static_cast<uint8>(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<EQ::MemoryMappedFile>(file_name_lt);
loot_table_hash = std::make_unique<EQ::FixedMemoryVariableHashSet<LootTable_Struct>>(
static_cast<uint8*>(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<EQ::MemoryMappedFile>(file_name_ld);
loot_drop_hash = std::make_unique<EQ::FixedMemoryVariableHashSet<LootDrop_Struct>>(
static_cast<uint8*>(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);

View File

@ -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<EQ::MemoryMappedFile> skill_caps_mmf;
std::unique_ptr<EQ::MemoryMappedFile> items_mmf;
std::unique_ptr<EQ::FixedMemoryHashSet<EQ::ItemData>> items_hash;
std::unique_ptr<EQ::MemoryMappedFile> faction_mmf;
std::unique_ptr<EQ::FixedMemoryHashSet<NPCFactionList>> faction_hash;
std::unique_ptr<EQ::MemoryMappedFile> faction_associations_mmf;
std::unique_ptr<EQ::FixedMemoryHashSet<FactionAssociations>> faction_associations_hash;
std::unique_ptr<EQ::MemoryMappedFile> loot_table_mmf;
std::unique_ptr<EQ::FixedMemoryVariableHashSet<LootTable_Struct>> loot_table_hash;
std::unique_ptr<EQ::MemoryMappedFile> loot_drop_mmf;
std::unique_ptr<EQ::FixedMemoryVariableHashSet<LootDrop_Struct>> loot_drop_hash;
std::unique_ptr<EQ::MemoryMappedFile> base_data_mmf;
std::unique_ptr<EQ::MemoryMappedFile> spells_mmf;
std::unique_ptr<EQ::MemoryMappedFile> skill_caps_mmf;
std::unique_ptr<EQ::MemoryMappedFile> items_mmf;
std::unique_ptr<EQ::FixedMemoryHashSet<EQ::ItemData>> items_hash;
std::unique_ptr<EQ::MemoryMappedFile> faction_mmf;
std::unique_ptr<EQ::FixedMemoryHashSet<NPCFactionList>> faction_hash;
std::unique_ptr<EQ::MemoryMappedFile> faction_associations_mmf;
std::unique_ptr<EQ::FixedMemoryHashSet<FactionAssociations>> faction_associations_hash;
std::unique_ptr<EQ::MemoryMappedFile> base_data_mmf;
std::unique_ptr<EQ::MemoryMappedFile> spells_mmf;
public:
void SetSharedItemsCount(uint32 shared_items_count);

View File

@ -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
)

View File

@ -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<LootTable_Struct> loot_table_hash(reinterpret_cast<byte*>(mmf_loot_table.Get()),
loot_table_size, loot_table_max);
EQ::FixedMemoryVariableHashSet<LootDrop_Struct> loot_drop_hash(reinterpret_cast<byte*>(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();
}

View File

@ -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 <string>
#include "../common/eqemu_config.h"
class SharedDatabase;
void LoadLoot(SharedDatabase *database, const std::string &prefix);
#endif

View File

@ -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 {

View File

@ -145,6 +145,7 @@ std::vector<Reload> 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"},

View File

@ -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:

View File

@ -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

View File

@ -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,
(

View File

@ -84,7 +84,7 @@ void bot_command_pickpocket(Client *c, const Seperator *sep)
// Steal item
while (steal_item) {
std::vector<std::pair<const EQ::ItemData *, uint16>> loot_selection; // <const ItemData*, charges>
for (auto item_iter: target_npc->itemlist) {
for (auto item_iter: target_npc->GetLootItems()) {
if (!item_iter || !item_iter->item_id) {
continue;
}

View File

@ -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 *> Client::GetPartyMembers()
return clients_to_update;
}
void Client::SummonBaggedItems(uint32 bag_item_id, const std::vector<ServerLootItem_Struct>& bag_items)
void Client::SummonBaggedItems(uint32 bag_item_id, const std::vector<LootItem>& 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(

View File

@ -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<ServerLootItem_Struct>& bag_items);
void SummonBaggedItems(uint32 bag_item_id, const std::vector<LootItem>& bag_items);
void SetStats(uint8 type,int16 set_val);
void IncStats(uint8 type,int16 increase_val);
void DropItem(int16 slot_id, bool recurse = true);

View File

@ -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)

View File

@ -870,5 +870,6 @@ struct DataBucketCache
uint32_t bucket_expires;
};
#endif

View File

@ -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<int> Corpse::GetLootList() {
std::vector<int> 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;

View File

@ -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<int> 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<uint32> &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;

View File

@ -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()

View File

@ -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,

View File

@ -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);

View File

@ -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<std::pair<NPC *, ItemList>> v;
std::vector<std::pair<NPC *, LootItems>> 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));
}

View File

@ -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) {

912
zone/loot.cpp Normal file
View File

@ -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<float>(l->avgcoin - min_cash) /
static_cast<float>(max_cash - min_cash);
const float avg_cash_roll = static_cast<float>(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 &lte: 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<float>(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<uint8>(1));
for (int k = 1; k < charges; ++k) {
float c_roll = static_cast<float>(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<std::any> 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<uint8>(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<uint8>(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;
}

View File

@ -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<float>(lts->avgcoin - min_cash) / static_cast<float>(max_cash - min_cash);
const float avg_cash_roll = static_cast<float>(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<float>(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<uint8>(1));
for (int k = 1; k < charges; ++k) {
float c_roll = static_cast<float>(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<std::any> 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<uint8>(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<uint8>(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);
}
}

View File

@ -2345,7 +2345,7 @@ void Lua_Client::SummonBaggedItems(uint32 bag_item_id, luabind::adl::object bag_
return;
}
std::vector<ServerLootItem_Struct> bagged_items;
std::vector<LootItem> 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<uint32>((*it)["item_id"]);
item.charges = luabind::object_cast<int16>((*it)["charges"]);
item.attuned = luabind::type((*it)["attuned"]) != LUA_TNIL ? luabind::object_cast<uint8>((*it)["attuned"]) : 0;

View File

@ -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)

View File

@ -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);
};

View File

@ -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)

View File

@ -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);

View File

@ -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;

View File

@ -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; }

View File

@ -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<std::pair<const EQ::ItemData*, uint16>> loot_selection; // <const ItemData*, charges>
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<int> NPC::GetLootList() {
std::vector<int> 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;

View File

@ -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 <deque>
#include <list>
@ -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<int> 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<Timer> 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<NpcFactionEntriesRepository::NpcFactionEntries> 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<uint32> m_rolled_items = {};

View File

@ -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<ServerLootItem_Struct> bagged_items;
std::vector<LootItem> 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;

View File

@ -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);

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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();

View File

@ -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();

View File

@ -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<int8>(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<int8>(inst->GetCharges());
tradingWith->CastToNPC()->AddLootDrop(
item,
&tradingWith->CastToNPC()->itemlist,
new_loot_drop_entry,
true
);

View File

@ -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!");

View File

@ -3240,3 +3240,5 @@ void Zone::SetSecondsBeforeIdle(uint32 seconds_before_idle)
{
Zone::m_seconds_before_idle = seconds_before_idle;
}
#include "zone_loot.cpp"

View File

@ -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<uint32>& loottable_ids);
void ClearLootTables();
void ReloadLootTables();
LoottableRepository::Loottable *GetLootTable(const uint32 loottable_id);
std::vector<LoottableEntriesRepository::LoottableEntries> GetLootTableEntries(const uint32 loottable_id) const;
LootdropRepository::Lootdrop GetLootdrop(const uint32 lootdrop_id) const;
std::vector<LootdropEntriesRepository::LootdropEntries> GetLootdropEntries(const uint32 lootdrop_id) const;
private:
bool allow_mercs;
bool can_bind;
@ -479,6 +493,12 @@ private:
std::vector<NpcFactionRepository::NpcFaction> m_npc_factions = { };
std::vector<NpcFactionEntriesRepository::NpcFactionEntries> m_npc_faction_entries = { };
std::vector<FactionAssociationRepository::FactionAssociation> m_faction_associations = { };
// loot
std::vector<LoottableRepository::Loottable> m_loottables = {};
std::vector<LoottableEntriesRepository::LoottableEntries> m_loottable_entries = {};
std::vector<LootdropRepository::Lootdrop> m_lootdrops = {};
std::vector<LootdropEntriesRepository::LootdropEntries> m_lootdrop_entries = {};
};
#endif

200
zone/zone_loot.cpp Normal file
View File

@ -0,0 +1,200 @@
#include <vector>
#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<uint32> &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<uint32> 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<uint32> 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<LoottableEntriesRepository::LoottableEntries> Zone::GetLootTableEntries(const uint32 loottable_id) const
{
std::vector<LoottableEntriesRepository::LoottableEntries> 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<LootdropEntriesRepository::LootdropEntries> Zone::GetLootdropEntries(const uint32 lootdrop_id) const
{
std::vector<LootdropEntriesRepository::LootdropEntries> entries = {};
for (const auto &e: m_lootdrop_entries) {
if (e.lootdrop_id == lootdrop_id) {
entries.emplace_back(e);
}
}
return entries;
}

View File

@ -1747,6 +1747,7 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load
std::vector<uint32> npc_ids;
std::vector<uint32> npc_faction_ids;
std::vector<uint32> 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;
}

View File

@ -367,7 +367,7 @@ namespace RaidLootTypes {
}
class ZoneDatabase : public SharedDatabase {
typedef std::list<ServerLootItem_Struct*> ItemList;
typedef std::list<LootItem*> ItemList;
public:
ZoneDatabase();
ZoneDatabase(const char* host, const char* user, const char* passwd, const char* database,uint32 port);