mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 16:51:29 +00:00
[Loot] Add #lootsim (Loot Simulator) command (#2375)
* [Loot] Add #lootsim (Loot Simulator) command * Validation * Add global loot
This commit is contained in:
parent
6232a64cdb
commit
607871a7ac
@ -198,6 +198,7 @@ int command_init(void)
|
||||
command_add("level", "[Level] - Set your target's level", AccountStatus::Steward, command_level) ||
|
||||
command_add("list", "[npcs|players|corpses|doors|objects] [search] - Search entities", AccountStatus::ApprenticeGuide, command_list) ||
|
||||
command_add("listpetition", "List petitions", AccountStatus::Guide, command_listpetition) ||
|
||||
command_add("lootsim", "[npc_type_id] [loottable_id] [iterations] - Runs benchmark simulations using real loot logic to report numbers and data", AccountStatus::GMImpossible, command_lootsim) ||
|
||||
command_add("load_shared_memory", "[shared_memory_name] - Reloads shared memory and uses the input as output", AccountStatus::GMImpossible, command_load_shared_memory) ||
|
||||
command_add("loc", "Print out your or your target's current location and heading", AccountStatus::Player, command_loc) ||
|
||||
command_add("logs", "Manage anything to do with logs", AccountStatus::GMImpossible, command_logs) ||
|
||||
@ -1035,6 +1036,7 @@ void command_bot(Client *c, const Seperator *sep)
|
||||
#include "gm_commands/level.cpp"
|
||||
#include "gm_commands/list.cpp"
|
||||
#include "gm_commands/listpetition.cpp"
|
||||
#include "gm_commands/lootsim.cpp"
|
||||
#include "gm_commands/loc.cpp"
|
||||
#include "gm_commands/logcommand.cpp"
|
||||
#include "gm_commands/logs.cpp"
|
||||
|
||||
@ -137,6 +137,7 @@ void command_lastname(Client *c, const Seperator *sep);
|
||||
void command_level(Client *c, const Seperator *sep);
|
||||
void command_list(Client *c, const Seperator *sep);
|
||||
void command_listpetition(Client *c, const Seperator *sep);
|
||||
void command_lootsim(Client *c, const Seperator *sep);
|
||||
void command_load_shared_memory(Client *c, const Seperator *sep);
|
||||
void command_loc(Client *c, const Seperator *sep);
|
||||
void command_logs(Client *c, const Seperator *sep);
|
||||
|
||||
198
zone/gm_commands/lootsim.cpp
Executable file
198
zone/gm_commands/lootsim.cpp
Executable file
@ -0,0 +1,198 @@
|
||||
#include "../client.h"
|
||||
|
||||
void command_lootsim(Client *c, const Seperator *sep)
|
||||
{
|
||||
int arguments = sep->argnum;
|
||||
if (arguments < 3 || !sep->IsNumber(1) || !sep->IsNumber(2) || !sep->IsNumber(3)) {
|
||||
c->Message(Chat::White, "Usage: #lootsim [npc_type_id] [loottable_id] [iterations] [loot_log_enabled=0]");
|
||||
return;
|
||||
}
|
||||
|
||||
auto npc_id = std::stoul(sep->arg[1]);
|
||||
auto loottable_id = std::stoul(sep->arg[2]);
|
||||
auto iterations = std::stoul(sep->arg[3]) > 1000 ? 1000 : std::stoul(sep->arg[3]);
|
||||
auto log_enabled = arguments > 3 ? std::stoul(sep->arg[4]) : false;
|
||||
|
||||
// temporarily disable loot logging unless set explicitly
|
||||
LogSys.log_settings[Logs::Loot].log_to_console = log_enabled ? LogSys.log_settings[Logs::Loot].log_to_console : 0;
|
||||
LogSys.log_settings[Logs::Loot].log_to_file = log_enabled ? LogSys.log_settings[Logs::Loot].log_to_file : 0;
|
||||
LogSys.log_settings[Logs::Loot].log_to_gmsay = log_enabled ? LogSys.log_settings[Logs::Loot].log_to_gmsay : 0;
|
||||
|
||||
auto npc_type = content_db.LoadNPCTypesData(npc_id);
|
||||
if (npc_type) {
|
||||
auto npc = new NPC(npc_type, nullptr, c->GetPosition(), GravityBehavior::Water);
|
||||
if (npc) {
|
||||
|
||||
BenchTimer benchmark;
|
||||
|
||||
npc->SetRecordLootStats(true);
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
npc->AddLootTable(loottable_id);
|
||||
|
||||
for (auto &id: zone->GetGlobalLootTables(npc)) {
|
||||
npc->AddLootTable(id);
|
||||
}
|
||||
}
|
||||
|
||||
entity_list.AddNPC(npc);
|
||||
|
||||
c->SendChatLineBreak();
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"# [Loot Simulator] NPC [{}] ({}) Loot Table ID [{}] Dropped Items [{}] iterations [{}]",
|
||||
npc->GetCleanName(),
|
||||
npc_id,
|
||||
loottable_id,
|
||||
npc->GetRolledItems().size(),
|
||||
iterations
|
||||
).c_str()
|
||||
);
|
||||
c->SendChatLineBreak();
|
||||
|
||||
// npc level loot table
|
||||
auto loot_table = database.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];
|
||||
|
||||
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
|
||||
).c_str()
|
||||
);
|
||||
|
||||
auto loot_drop = database.GetLootDrop(le.lootdrop_id);
|
||||
if (!loot_drop) {
|
||||
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);
|
||||
|
||||
EQ::SayLinkEngine linker;
|
||||
linker.SetLinkType(EQ::saylink::SayLinkItemData);
|
||||
linker.SetItemData(item);
|
||||
|
||||
auto rolled_percentage = (float) ((float) ((float) rolled_count / (float) iterations) * 100);
|
||||
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"-- [{}] item_id [{}] chance [{}] rolled_count [{}] ({:.2f}%) name [{}]",
|
||||
ei,
|
||||
e.item_id,
|
||||
e.chance,
|
||||
rolled_count,
|
||||
rolled_percentage,
|
||||
linker.GenerateLink()
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// global loot
|
||||
auto tables = zone->GetGlobalLootTables(npc);
|
||||
if (!tables.empty()) {
|
||||
c->SendChatLineBreak();
|
||||
c->Message(Chat::White, "# [Loot Simulator] Global Loot");
|
||||
}
|
||||
|
||||
for (auto &id: tables) {
|
||||
c->SendChatLineBreak();
|
||||
c->Message(Chat::White, fmt::format("# Global Loot Table ID [{}]", id).c_str());
|
||||
c->SendChatLineBreak();
|
||||
|
||||
loot_table = database.GetLootTable(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];
|
||||
|
||||
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
|
||||
).c_str()
|
||||
);
|
||||
|
||||
auto loot_drop = database.GetLootDrop(le.lootdrop_id);
|
||||
if (!loot_drop) {
|
||||
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);
|
||||
|
||||
EQ::SayLinkEngine linker;
|
||||
linker.SetLinkType(EQ::saylink::SayLinkItemData);
|
||||
linker.SetItemData(item);
|
||||
|
||||
auto rolled_percentage = (float) ((float) ((float) rolled_count / (float) iterations) * 100);
|
||||
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"-- [{}] item_id [{}] chance [{}] rolled_count [{}] ({:.2f}%) name [{}]",
|
||||
ei,
|
||||
e.item_id,
|
||||
e.chance,
|
||||
rolled_count,
|
||||
rolled_percentage,
|
||||
linker.GenerateLink()
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
c->SendChatLineBreak();
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"# Global Loot Benchmark End [{}] iterations took [{}](s)",
|
||||
iterations,
|
||||
benchmark.elapsed()
|
||||
).c_str()
|
||||
);
|
||||
c->SendChatLineBreak();
|
||||
|
||||
LogSys.LoadLogDatabaseSettings();
|
||||
}
|
||||
}
|
||||
else {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Failed to spawn NPC ID {}.",
|
||||
npc_id
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
#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) {
|
||||
void ZoneDatabase::AddLootTableToNPC(NPC* npc, uint32 loottable_id, ItemList* itemlist, uint32* copper, uint32* silver, uint32* gold, uint32* plat) {
|
||||
const LootTable_Struct* lts = nullptr;
|
||||
// global loot passes nullptr for these
|
||||
bool bGlobal = copper == nullptr && silver == nullptr && gold == nullptr && plat == nullptr;
|
||||
@ -559,6 +559,10 @@ void NPC::AddLootDrop(
|
||||
}
|
||||
else safe_delete(item);
|
||||
|
||||
if (IsRecordLootStats()) {
|
||||
m_rolled_items.emplace_back(item->item_id);
|
||||
}
|
||||
|
||||
if (wear_change && outapp) {
|
||||
entity_list.QueueClients(this, outapp);
|
||||
safe_delete(outapp);
|
||||
|
||||
33
zone/npc.cpp
33
zone/npc.cpp
@ -264,6 +264,7 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
|
||||
HasAISpell = false;
|
||||
HasAISpellEffects = false;
|
||||
innate_proc_spell_id = 0;
|
||||
m_record_loot_stats = false;
|
||||
|
||||
if (GetClass() == MERCENARY_MASTER && RuleB(Mercs, AllowMercs)) {
|
||||
LoadMercTypes();
|
||||
@ -3689,3 +3690,35 @@ std::vector<int> NPC::GetLootList() {
|
||||
}
|
||||
return npc_items;
|
||||
}
|
||||
|
||||
bool NPC::IsRecordLootStats() const
|
||||
{
|
||||
return m_record_loot_stats;
|
||||
}
|
||||
|
||||
void NPC::SetRecordLootStats(bool record_loot_stats)
|
||||
{
|
||||
NPC::m_record_loot_stats = record_loot_stats;
|
||||
}
|
||||
|
||||
void NPC::FlushLootStats()
|
||||
{
|
||||
m_rolled_items = {};
|
||||
}
|
||||
|
||||
const std::vector<uint32> &NPC::GetRolledItems() const
|
||||
{
|
||||
return m_rolled_items;
|
||||
}
|
||||
|
||||
int NPC::GetRolledItemCount(uint32 item_id)
|
||||
{
|
||||
int rolled_count = 0;
|
||||
for (auto &e: m_rolled_items) {
|
||||
if (item_id == e) {
|
||||
rolled_count++;
|
||||
}
|
||||
}
|
||||
|
||||
return rolled_count;
|
||||
}
|
||||
|
||||
17
zone/npc.h
17
zone/npc.h
@ -109,6 +109,13 @@ public:
|
||||
static bool SpawnZoneController();
|
||||
static int8 GetAILevel(bool iForceReRead = false);
|
||||
|
||||
// loot recording / simulator
|
||||
bool IsRecordLootStats() const;
|
||||
void SetRecordLootStats(bool record_loot_stats);
|
||||
void FlushLootStats();
|
||||
const std::vector<uint32> &GetRolledItems() const;
|
||||
int GetRolledItemCount(uint32 item_id);
|
||||
|
||||
NPC(const NPCType* npc_type_data, Spawn2* respawn, const glm::vec4& position, GravityBehavior iflymode, bool IsCorpse = false);
|
||||
|
||||
virtual ~NPC();
|
||||
@ -678,10 +685,12 @@ protected:
|
||||
|
||||
|
||||
private:
|
||||
uint32 loottable_id;
|
||||
bool skip_global_loot;
|
||||
bool skip_auto_scale;
|
||||
bool p_depop;
|
||||
uint32 loottable_id;
|
||||
bool skip_global_loot;
|
||||
bool skip_auto_scale;
|
||||
bool p_depop;
|
||||
bool m_record_loot_stats;
|
||||
std::vector<uint32> m_rolled_items = {};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user