mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 16:51:29 +00:00
[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:
parent
fcbf5cae47
commit
c654c1d674
@ -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
|
||||
|
||||
@ -22,7 +22,6 @@
|
||||
#include "../database.h"
|
||||
#include "../rulesys.h"
|
||||
#include "../eqemu_logsys.h"
|
||||
#include "../loottable.h"
|
||||
#include "../repositories/content_flags_repository.h"
|
||||
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
33
common/loot.h
Normal 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
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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();
|
||||
}
|
||||
@ -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
|
||||
@ -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 {
|
||||
|
||||
@ -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"},
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
(
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -870,5 +870,6 @@ struct DataBucketCache
|
||||
uint32_t bucket_expires;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
101
zone/corpse.h
101
zone/corpse.h
@ -21,6 +21,7 @@
|
||||
|
||||
#include "mob.h"
|
||||
#include "client.h"
|
||||
#include "../common/loot.h"
|
||||
|
||||
class EQApplicationPacket;
|
||||
class Group;
|
||||
@ -43,9 +44,9 @@ class Corpse : public Mob {
|
||||
static void SendEndLootErrorPacket(Client* client);
|
||||
static void SendLootReqErrorPacket(Client* client, LootResponse response = LootResponse::NotAtThisTime);
|
||||
|
||||
Corpse(NPC* in_npc, ItemList* in_itemlist, uint32 in_npctypeid, const NPCType** in_npctypedata, uint32 in_decaytime = 600000);
|
||||
Corpse(NPC* in_npc, LootItems* in_itemlist, uint32 in_npctypeid, const NPCType** in_npctypedata, uint32 in_decaytime = 600000);
|
||||
Corpse(Client* client, int32 in_rezexp);
|
||||
Corpse(uint32 in_corpseid, uint32 in_charid, const char* in_charname, ItemList* in_itemlist, uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_plat, const glm::vec4& position, float in_size, uint8 in_gender, uint16 in_race, uint8 in_class, uint8 in_deity, uint8 in_level, uint8 in_texture, uint8 in_helmtexture, uint32 in_rezexp, bool wasAtGraveyard = false);
|
||||
Corpse(uint32 in_corpseid, uint32 in_charid, const char* in_charname, LootItems* in_itemlist, uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_plat, const glm::vec4& position, float in_size, uint8 in_gender, uint16 in_race, uint8 in_class, uint8 in_deity, uint8 in_level, uint8 in_texture, uint8 in_helmtexture, uint32 in_rezexp, bool wasAtGraveyard = false);
|
||||
|
||||
~Corpse();
|
||||
static Corpse* LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std::string in_charname, const glm::vec4& position, std::string time_of_death, bool rezzed, bool was_at_graveyard, uint32 guild_consent_id);
|
||||
@ -93,35 +94,37 @@ class Corpse : public Mob {
|
||||
void LoadPlayerCorpseDecayTime(uint32 dbid);
|
||||
|
||||
/* Corpse: Items */
|
||||
uint32 GetWornItem(int16 equipSlot) const;
|
||||
ServerLootItem_Struct* GetItem(uint16 lootslot, ServerLootItem_Struct** bag_item_data = 0);
|
||||
void SetPlayerKillItemID(int32 pk_item_id) { player_kill_item = pk_item_id; }
|
||||
int32 GetPlayerKillItem() { return player_kill_item; }
|
||||
void RemoveItem(uint16 lootslot);
|
||||
void RemoveItem(ServerLootItem_Struct* item_data);
|
||||
void RemoveItemByID(uint32 item_id, int quantity = 1);
|
||||
void AddItem(uint32 itemnum,
|
||||
uint16 charges,
|
||||
int16 slot = 0,
|
||||
uint32 aug1 = 0,
|
||||
uint32 aug2 = 0,
|
||||
uint32 aug3 = 0,
|
||||
uint32 aug4 = 0,
|
||||
uint32 aug5 = 0,
|
||||
uint32 aug6 = 0,
|
||||
bool attuned = false,
|
||||
const std::string &custom_data = std::string(),
|
||||
uint32 GetWornItem(int16 equipSlot) const;
|
||||
LootItem *GetItem(uint16 lootslot, LootItem **bag_item_data = 0);
|
||||
void SetPlayerKillItemID(int32 pk_item_id) { player_kill_item = pk_item_id; }
|
||||
int32 GetPlayerKillItem() { return player_kill_item; }
|
||||
void RemoveItem(uint16 lootslot);
|
||||
void RemoveItem(LootItem *item_data);
|
||||
void RemoveItemByID(uint32 item_id, int quantity = 1);
|
||||
void AddItem(
|
||||
uint32 itemnum,
|
||||
uint16 charges,
|
||||
int16 slot = 0,
|
||||
uint32 aug1 = 0,
|
||||
uint32 aug2 = 0,
|
||||
uint32 aug3 = 0,
|
||||
uint32 aug4 = 0,
|
||||
uint32 aug5 = 0,
|
||||
uint32 aug6 = 0,
|
||||
bool attuned = false,
|
||||
const std::string &custom_data = std::string(),
|
||||
uint32 ornamenticon = 0,
|
||||
uint32 ornamentidfile = 0,
|
||||
uint32 ornament_hero_model = 0);
|
||||
uint32 ornament_hero_model = 0
|
||||
);
|
||||
|
||||
/* Corpse: Coin */
|
||||
void SetCash(uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_platinum);
|
||||
void RemoveCash();
|
||||
uint32 GetCopper() { return copper; }
|
||||
uint32 GetSilver() { return silver; }
|
||||
uint32 GetGold() { return gold; }
|
||||
uint32 GetPlatinum() { return platinum; }
|
||||
void SetCash(uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_platinum);
|
||||
void RemoveCash();
|
||||
uint32 GetCopper() { return copper; }
|
||||
uint32 GetSilver() { return silver; }
|
||||
uint32 GetGold() { return gold; }
|
||||
uint32 GetPlatinum() { return platinum; }
|
||||
|
||||
/* Corpse: Resurrection */
|
||||
bool IsRezzed() { return rez; }
|
||||
@ -134,9 +137,9 @@ class Corpse : public Mob {
|
||||
bool HasItem(uint32 item_id);
|
||||
uint16 CountItem(uint32 item_id);
|
||||
uint32 GetItemIDBySlot(uint16 loot_slot);
|
||||
uint16 GetFirstSlotByItemID(uint32 item_id);
|
||||
uint16 GetFirstLootSlotByItemID(uint32 item_id);
|
||||
std::vector<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;
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
|
||||
@ -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
912
zone/loot.cpp
Normal 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 <e: zone->GetLootTableEntries(loottable_id)) {
|
||||
for (uint32 k = 1; k <= (lte.multiplier * global_loot_multiplier); k++) {
|
||||
const uint8 drop_limit = lte.droplimit;
|
||||
const uint8 minimum_drop = lte.mindrop;
|
||||
const float probability = lte.probability;
|
||||
|
||||
float drop_chance = 0.0f;
|
||||
if (EQ::ValueWithin(probability, 0.0f, 100.0f)) {
|
||||
drop_chance = static_cast<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;
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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; }
|
||||
|
||||
|
||||
324
zone/npc.cpp
324
zone/npc.cpp
@ -169,10 +169,10 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
|
||||
|
||||
SetTaunting(false);
|
||||
proximity = nullptr;
|
||||
copper = 0;
|
||||
silver = 0;
|
||||
gold = 0;
|
||||
platinum = 0;
|
||||
m_loot_copper = 0;
|
||||
m_loot_silver = 0;
|
||||
m_loot_gold = 0;
|
||||
m_loot_platinum = 0;
|
||||
max_dmg = npc_type_data->max_dmg;
|
||||
min_dmg = npc_type_data->min_dmg;
|
||||
attack_count = npc_type_data->attack_count;
|
||||
@ -282,14 +282,14 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
|
||||
m_roambox.delay = 1000;
|
||||
m_roambox.min_delay = 1000;
|
||||
|
||||
p_depop = false;
|
||||
loottable_id = npc_type_data->loottable_id;
|
||||
skip_global_loot = npc_type_data->skip_global_loot;
|
||||
skip_auto_scale = npc_type_data->skip_auto_scale;
|
||||
rare_spawn = npc_type_data->rare_spawn;
|
||||
no_target_hotkey = npc_type_data->no_target_hotkey;
|
||||
primary_faction = 0;
|
||||
faction_amount = npc_type_data->faction_amount;
|
||||
p_depop = false;
|
||||
m_loottable_id = npc_type_data->loottable_id;
|
||||
m_skip_global_loot = npc_type_data->skip_global_loot;
|
||||
m_skip_auto_scale = npc_type_data->skip_auto_scale;
|
||||
rare_spawn = npc_type_data->rare_spawn;
|
||||
no_target_hotkey = npc_type_data->no_target_hotkey;
|
||||
primary_faction = 0;
|
||||
faction_amount = npc_type_data->faction_amount;
|
||||
|
||||
SetNPCFactionID(npc_type_data->npc_faction_id);
|
||||
|
||||
@ -519,11 +519,15 @@ NPC::~NPC()
|
||||
|
||||
safe_delete(NPCTypedata_ours);
|
||||
|
||||
for (auto* e : itemlist) {
|
||||
safe_delete(e);
|
||||
LootItems::iterator cur, end;
|
||||
cur = m_loot_items.begin();
|
||||
end = m_loot_items.end();
|
||||
for (; cur != end; ++cur) {
|
||||
LootItem *item = *cur;
|
||||
safe_delete(item);
|
||||
}
|
||||
|
||||
itemlist.clear();
|
||||
m_loot_items.clear();
|
||||
faction_list.clear();
|
||||
|
||||
safe_delete(reface_timer);
|
||||
@ -570,265 +574,6 @@ void NPC::SetTarget(Mob* mob) {
|
||||
Mob::SetTarget(mob);
|
||||
}
|
||||
|
||||
ServerLootItem_Struct* NPC::GetItem(int slot_id) {
|
||||
ItemList::iterator cur,end;
|
||||
cur = itemlist.begin();
|
||||
end = itemlist.end();
|
||||
for(; cur != end; ++cur) {
|
||||
ServerLootItem_Struct* item = *cur;
|
||||
if (item->equip_slot == slot_id) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return(nullptr);
|
||||
}
|
||||
|
||||
void NPC::RemoveItem(uint32 item_id, uint16 quantity, uint16 slot) {
|
||||
ItemList::iterator cur,end;
|
||||
cur = itemlist.begin();
|
||||
end = itemlist.end();
|
||||
for(; cur != end; ++cur) {
|
||||
ServerLootItem_Struct* item = *cur;
|
||||
if (item->item_id == item_id && slot <= 0 && quantity <= 0) {
|
||||
itemlist.erase(cur);
|
||||
UpdateEquipmentLight();
|
||||
if (UpdateActiveLight()) { SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); }
|
||||
return;
|
||||
}
|
||||
else if (item->item_id == item_id && item->equip_slot == slot && quantity >= 1) {
|
||||
if (item->charges <= quantity) {
|
||||
itemlist.erase(cur);
|
||||
UpdateEquipmentLight();
|
||||
if (UpdateActiveLight()) { SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); }
|
||||
}
|
||||
else {
|
||||
item->charges -= quantity;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NPC::CheckTrivialMinMaxLevelDrop(Mob *killer)
|
||||
{
|
||||
if (killer == nullptr || !killer->IsClient()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16 killer_level = killer->GetLevel();
|
||||
uint8 material;
|
||||
|
||||
auto cur = itemlist.begin();
|
||||
while (cur != itemlist.end()) {
|
||||
if (!(*cur)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16 trivial_min_level = (*cur)->trivial_min_level;
|
||||
uint16 trivial_max_level = (*cur)->trivial_max_level;
|
||||
bool fits_trivial_criteria = (
|
||||
(trivial_min_level > 0 && killer_level < trivial_min_level) ||
|
||||
(trivial_max_level > 0 && killer_level > trivial_max_level)
|
||||
);
|
||||
|
||||
if (fits_trivial_criteria) {
|
||||
material = EQ::InventoryProfile::CalcMaterialFromSlot((*cur)->equip_slot);
|
||||
if (material != EQ::textures::materialInvalid) {
|
||||
SendWearChange(material);
|
||||
}
|
||||
|
||||
cur = itemlist.erase(cur);
|
||||
continue;
|
||||
}
|
||||
++cur;
|
||||
}
|
||||
|
||||
UpdateEquipmentLight();
|
||||
if (UpdateActiveLight()) {
|
||||
SendAppearancePacket(AppearanceType::Light, GetActiveLightType());
|
||||
}
|
||||
}
|
||||
|
||||
void NPC::ClearItemList() {
|
||||
ItemList::iterator cur,end;
|
||||
cur = itemlist.begin();
|
||||
end = itemlist.end();
|
||||
for(; cur != end; ++cur) {
|
||||
ServerLootItem_Struct* item = *cur;
|
||||
safe_delete(item);
|
||||
}
|
||||
itemlist.clear();
|
||||
|
||||
UpdateEquipmentLight();
|
||||
if (UpdateActiveLight())
|
||||
SendAppearancePacket(AppearanceType::Light, GetActiveLightType());
|
||||
}
|
||||
|
||||
void NPC::QueryLoot(Client* to, bool is_pet_query)
|
||||
{
|
||||
if (!itemlist.empty()) {
|
||||
if (!is_pet_query) {
|
||||
to->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Loot | {} ({}) ID: {} Loottable ID: {}",
|
||||
GetName(),
|
||||
GetID(),
|
||||
GetNPCTypeID(),
|
||||
GetLoottableID()
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
|
||||
int item_count = 0;
|
||||
for (auto current_item : itemlist) {
|
||||
int item_number = (item_count + 1);
|
||||
if (!current_item) {
|
||||
LogError("NPC::QueryLoot() - ItemList error, null item.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!current_item->item_id || !database.GetItem(current_item->item_id)) {
|
||||
LogError("NPC::QueryLoot() - Database error, invalid item.");
|
||||
continue;
|
||||
}
|
||||
|
||||
EQ::SayLinkEngine linker;
|
||||
linker.SetLinkType(EQ::saylink::SayLinkLootItem);
|
||||
linker.SetLootData(current_item);
|
||||
|
||||
to->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Item {} | {} ({}){}",
|
||||
item_number,
|
||||
linker.GenerateLink().c_str(),
|
||||
current_item->item_id,
|
||||
(
|
||||
current_item->charges > 1 ?
|
||||
fmt::format(
|
||||
" Amount: {}",
|
||||
current_item->charges
|
||||
) :
|
||||
""
|
||||
)
|
||||
).c_str()
|
||||
);
|
||||
item_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_pet_query) {
|
||||
if (
|
||||
platinum ||
|
||||
gold ||
|
||||
silver ||
|
||||
copper
|
||||
) {
|
||||
to->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Money | {}",
|
||||
Strings::Money(
|
||||
platinum,
|
||||
gold,
|
||||
silver,
|
||||
copper
|
||||
)
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NPC::HasItem(uint32 item_id) {
|
||||
if (!database.GetItem(item_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) {
|
||||
ServerLootItem_Struct* loot_item = *current_item;
|
||||
if (!loot_item) {
|
||||
LogError("NPC::HasItem() - ItemList error, null item");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!loot_item->item_id || !database.GetItem(loot_item->item_id)) {
|
||||
LogError("NPC::HasItem() - Database error, invalid item");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (loot_item->item_id == item_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16 NPC::CountItem(uint32 item_id) {
|
||||
uint16 item_count = 0;
|
||||
if (!database.GetItem(item_id)) {
|
||||
return item_count;
|
||||
}
|
||||
|
||||
for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) {
|
||||
ServerLootItem_Struct* loot_item = *current_item;
|
||||
if (!loot_item) {
|
||||
LogError("NPC::CountItem() - ItemList error, null item");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!loot_item->item_id || !database.GetItem(loot_item->item_id)) {
|
||||
LogError("NPC::CountItem() - Database error, invalid item");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (loot_item->item_id == item_id) {
|
||||
item_count += loot_item->charges > 0 ? loot_item->charges : 1;
|
||||
}
|
||||
}
|
||||
return item_count;
|
||||
}
|
||||
|
||||
uint32 NPC::GetItemIDBySlot(uint16 loot_slot) {
|
||||
for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) {
|
||||
ServerLootItem_Struct* loot_item = *current_item;
|
||||
if (loot_item->lootslot == loot_slot) {
|
||||
return loot_item->item_id;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16 NPC::GetFirstSlotByItemID(uint32 item_id) {
|
||||
for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) {
|
||||
ServerLootItem_Struct* loot_item = *current_item;
|
||||
if (loot_item->item_id == item_id) {
|
||||
return loot_item->lootslot;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void NPC::AddCash(
|
||||
uint32 in_copper,
|
||||
uint32 in_silver,
|
||||
uint32 in_gold,
|
||||
uint32 in_platinum
|
||||
) {
|
||||
copper = in_copper >= 0 ? in_copper : 0;
|
||||
silver = in_silver >= 0 ? in_silver : 0;
|
||||
gold = in_gold >= 0 ? in_gold : 0;
|
||||
platinum = in_platinum >= 0 ? in_platinum : 0;
|
||||
}
|
||||
|
||||
void NPC::RemoveCash() {
|
||||
copper = 0;
|
||||
silver = 0;
|
||||
gold = 0;
|
||||
platinum = 0;
|
||||
}
|
||||
|
||||
bool NPC::Process()
|
||||
{
|
||||
if (p_depop)
|
||||
@ -1059,7 +804,7 @@ bool NPC::Process()
|
||||
}
|
||||
|
||||
uint32 NPC::CountLoot() {
|
||||
return(itemlist.size());
|
||||
return(m_loot_items.size());
|
||||
}
|
||||
|
||||
void NPC::UpdateEquipmentLight()
|
||||
@ -1080,7 +825,7 @@ void NPC::UpdateEquipmentLight()
|
||||
}
|
||||
|
||||
uint8 general_light_type = 0;
|
||||
for (auto iter = itemlist.begin(); iter != itemlist.end(); ++iter) {
|
||||
for (auto iter = m_loot_items.begin(); iter != m_loot_items.end(); ++iter) {
|
||||
auto item = database.GetItem((*iter)->item_id);
|
||||
if (item == nullptr) { continue; }
|
||||
|
||||
@ -1891,14 +1636,15 @@ void NPC::PickPocket(Client* thief)
|
||||
int steal_chance = steal_skill * 100 / (5 * over_level + 5);
|
||||
|
||||
// Determine whether to steal money or an item.
|
||||
uint32 money[6] = { 0, ((steal_skill >= 125) ? (GetPlatinum()) : (0)), ((steal_skill >= 60) ? (GetGold()) : (0)), GetSilver(), GetCopper(), 0 };
|
||||
uint32 money[6] = {0, ((steal_skill >= 125) ? (GetPlatinum()) : (0)), ((steal_skill >= 60) ? (GetGold()) : (0)), GetSilver(),
|
||||
GetCopper(), 0 };
|
||||
bool has_coin = ((money[PickPocketPlatinum] | money[PickPocketGold] | money[PickPocketSilver] | money[PickPocketCopper]) != 0);
|
||||
bool steal_item = (steal_skill >= steal_chance && (zone->random.Roll(50) || !has_coin));
|
||||
|
||||
// still needs to have FindFreeSlot vs PutItemInInventory issue worked out
|
||||
while (steal_item) {
|
||||
std::vector<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;
|
||||
|
||||
103
zone/npc.h
103
zone/npc.h
@ -25,8 +25,11 @@
|
||||
#include "zonedb.h"
|
||||
#include "../common/zone_store.h"
|
||||
#include "zonedump.h"
|
||||
#include "../common/loottable.h"
|
||||
#include "../common/repositories/npc_faction_entries_repository.h"
|
||||
#include "../common/repositories/loottable_repository.h"
|
||||
#include "../common/repositories/loottable_entries_repository.h"
|
||||
#include "../common/repositories/lootdrop_repository.h"
|
||||
#include "../common/repositories/lootdrop_entries_repository.h"
|
||||
|
||||
#include <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 = {};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
);
|
||||
|
||||
@ -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!");
|
||||
|
||||
@ -3240,3 +3240,5 @@ void Zone::SetSecondsBeforeIdle(uint32 seconds_before_idle)
|
||||
{
|
||||
Zone::m_seconds_before_idle = seconds_before_idle;
|
||||
}
|
||||
|
||||
#include "zone_loot.cpp"
|
||||
|
||||
20
zone/zone.h
20
zone/zone.h
@ -41,6 +41,10 @@
|
||||
#include "../common/repositories/npc_faction_repository.h"
|
||||
#include "../common/repositories/npc_faction_entries_repository.h"
|
||||
#include "../common/repositories/faction_association_repository.h"
|
||||
#include "../common/repositories/loottable_repository.h"
|
||||
#include "../common/repositories/loottable_entries_repository.h"
|
||||
#include "../common/repositories/lootdrop_repository.h"
|
||||
#include "../common/repositories/lootdrop_entries_repository.h"
|
||||
|
||||
struct EXPModifier
|
||||
{
|
||||
@ -425,6 +429,16 @@ public:
|
||||
void ReloadFactionAssociations();
|
||||
FactionAssociationRepository::FactionAssociation* GetFactionAssociation(const uint32 faction_id);
|
||||
|
||||
// loot
|
||||
void LoadLootTable(const uint32 loottable_id);
|
||||
void LoadLootTables(const std::vector<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
200
zone/zone_loot.cpp
Normal 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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user