feat(Scripts/Commands): Add .debug loot command (#25164)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9d49639da1
commit
6ee7b5e3ae
3 changed files with 266 additions and 1 deletions
|
|
@ -0,0 +1,18 @@
|
|||
--
|
||||
DELETE FROM `command` WHERE `name` = 'debug loot';
|
||||
INSERT INTO `command` (`name`, `security`, `help`) VALUES
|
||||
('debug loot', 2, 'Syntax: .debug loot <type> <id> [count]\nSimulates loot generation for the given loot type and ID, outputting the results to chat without creating items.\nOptional count (1-100) repeats the simulation and shows aggregated drop rates.\nValid types: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player');
|
||||
|
||||
DELETE FROM `acore_string` WHERE `entry` IN (30099, 30100, 30101, 30102, 30103, 30104, 30105, 30106, 30107, 30108, 30109);
|
||||
INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, `locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`) VALUES
|
||||
(30099, 'Simulating loot for {} "{}" (ID: {})', '루팅 시뮬레이션: {} "{}" (ID: {})', 'Simulation du butin pour {} "{}" (ID: {})', 'Beutesimulation für {} "{}" (ID: {})', '模拟 {} "{}" 的战利品 (ID: {})', '模擬 {} "{}" 的戰利品 (ID: {})', 'Simulando botín para {} "{}" (ID: {})', 'Simulando botín para {} "{}" (ID: {})', 'Симуляция добычи для {} "{}" (ID: {})'),
|
||||
(30100, ' [{}] x{} - {} ({}, RandProp: {}, RandSuffix: {})', ' [{}] x{} - {} ({}, 랜덤속성: {}, 랜덤접미사: {})', ' [{}] x{} - {} ({}, PropAléa: {}, SuffAléa: {})', ' [{}] x{} - {} ({}, ZufEig: {}, ZufSuf: {})', ' [{}] x{} - {} ({}, 随机属性: {}, 随机后缀: {})', ' [{}] x{} - {} ({}, 隨機屬性: {}, 隨機後綴: {})', ' [{}] x{} - {} ({}, PropAleat: {}, SufAleat: {})', ' [{}] x{} - {} ({}, PropAleat: {}, SufAleat: {})', ' [{}] x{} - {} ({}, СлСвойство: {}, СлСуффикс: {})'),
|
||||
(30101, ' [{}] x{} - {} ({}) [Quest]', ' [{}] x{} - {} ({}) [퀘스트]', ' [{}] x{} - {} ({}) [Quête]', ' [{}] x{} - {} ({}) [Quest]', ' [{}] x{} - {} ({}) [任务]', ' [{}] x{} - {} ({}) [任務]', ' [{}] x{} - {} ({}) [Misión]', ' [{}] x{} - {} ({}) [Misión]', ' [{}] x{} - {} ({}) [Задание]'),
|
||||
(30102, 'Gold: {} copper ({}g {}s {}c)', '골드: {} 코퍼 ({}금 {}은 {}동)', 'Or: {} cuivre ({}po {}pa {}pc)', 'Gold: {} Kupfer ({}G {}S {}K)', '金币: {} 铜 ({}金 {}银 {}铜)', '金幣: {} 銅 ({}金 {}銀 {}銅)', 'Oro: {} cobre ({}o {}p {}c)', 'Oro: {} cobre ({}o {}p {}c)', 'Золото: {} медь ({}зол {}сер {}мед)'),
|
||||
(30103, 'No loot generated.', '생성된 전리품이 없습니다.', 'Aucun butin généré.', 'Keine Beute generiert.', '未生成战利品。', '未生成戰利品。', 'No se generó botín.', 'No se generó botín.', 'Добыча не сгенерирована.'),
|
||||
(30104, 'Invalid loot type "{}". Valid: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player', '잘못된 전리품 유형 "{}". 유효: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player', 'Type de butin invalide "{}". Valides: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player', 'Ungültiger Beutetyp "{}". Gültig: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player', '无效的战利品类型 "{}"。有效: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player', '無效的戰利品類型 "{}"。有效: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player', 'Tipo de botín inválido "{}". Válidos: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player', 'Tipo de botín inválido "{}". Válidos: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player', 'Неверный тип добычи "{}". Допустимые: creature, gameobject, fishing, item, pickpocketing, skinning, disenchant, prospecting, milling, spell, reference, mail, player'),
|
||||
(30105, 'No loot template found for {} ID {}.', '{}의 전리품 템플릿을 찾을 수 없습니다 (ID: {}).', 'Aucun modèle de butin trouvé pour {} ID {}.', 'Keine Beutevorlage gefunden für {} ID {}.', '未找到 {} ID {} 的战利品模板。', '未找到 {} ID {} 的戰利品模板。', 'No se encontró plantilla de botín para {} ID {}.', 'No se encontró plantilla de botín para {} ID {}.', 'Шаблон добычи не найден для {} ID {}.'),
|
||||
(30106, 'Simulating loot for {} "{}" (ID: {}) - {} iterations', '루팅 시뮬레이션: {} "{}" (ID: {}) - {} 반복', 'Simulation du butin pour {} "{}" (ID: {}) - {} itérations', 'Beutesimulation für {} "{}" (ID: {}) - {} Durchläufe', '模拟 {} "{}" 的战利品 (ID: {}) - {} 次迭代', '模擬 {} "{}" 的戰利品 (ID: {}) - {} 次迭代', 'Simulando botín para {} "{}" (ID: {}) - {} iteraciones', 'Simulando botín para {} "{}" (ID: {}) - {} iteraciones', 'Симуляция добычи для {} "{}" (ID: {}) - {} итераций'),
|
||||
(30107, ' [{}] x{} - {} ({}) - dropped {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) - 드롭 {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) - obtenu {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) - gefallen {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) - 掉落 {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) - 掉落 {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) - obtenido {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) - obtenido {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) - выпало {}/{} ({}.{}%)'),
|
||||
(30108, ' [{}] x{} - {} ({}) [Quest] - dropped {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) [퀘스트] - 드롭 {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) [Quête] - obtenu {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) [Quest] - gefallen {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) [任务] - 掉落 {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) [任務] - 掉落 {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) [Misión] - obtenido {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) [Misión] - obtenido {}/{} ({}.{}%)', ' [{}] x{} - {} ({}) [Задание] - выпало {}/{} ({}.{}%)'),
|
||||
(30109, 'Gold avg: {} copper ({}g {}s {}c) over {} iterations', '골드 평균: {} 코퍼 ({}금 {}은 {}동) {} 반복', 'Or moy: {} cuivre ({}po {}pa {}pc) sur {} itérations', 'Gold Durchschn.: {} Kupfer ({}G {}S {}K) über {} Durchläufe', '金币均值: {} 铜 ({}金 {}银 {}铜) 共 {} 次迭代', '金幣均值: {} 銅 ({}金 {}銀 {}銅) 共 {} 次迭代', 'Oro prom: {} cobre ({}o {}p {}c) en {} iteraciones', 'Oro prom: {} cobre ({}o {}p {}c) en {} iteraciones', 'Золото сред.: {} медь ({}зол {}сер {}мед) за {} итераций');
|
||||
|
|
@ -1391,6 +1391,19 @@ enum AcoreStrings
|
|||
|
||||
LANG_DEBUG_LFG_ON = 30096,
|
||||
LANG_DEBUG_LFG_OFF = 30097,
|
||||
LANG_DEBUG_LFG_CONF = 30098
|
||||
LANG_DEBUG_LFG_CONF = 30098,
|
||||
|
||||
// debug loot command
|
||||
LANG_DEBUG_LOOT_HEADER = 30099,
|
||||
LANG_DEBUG_LOOT_ITEM = 30100,
|
||||
LANG_DEBUG_LOOT_ITEM_QUEST = 30101,
|
||||
LANG_DEBUG_LOOT_GOLD = 30102,
|
||||
LANG_DEBUG_LOOT_EMPTY = 30103,
|
||||
LANG_DEBUG_LOOT_INVALID_TYPE = 30104,
|
||||
LANG_DEBUG_LOOT_NO_TEMPLATE = 30105,
|
||||
LANG_DEBUG_LOOT_HEADER_MULTI = 30106,
|
||||
LANG_DEBUG_LOOT_ITEM_MULTI = 30107,
|
||||
LANG_DEBUG_LOOT_ITEM_QUEST_MULTI = 30108,
|
||||
LANG_DEBUG_LOOT_GOLD_MULTI = 30109
|
||||
};
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -22,9 +22,11 @@
|
|||
#include "Chat.h"
|
||||
#include "CommandScript.h"
|
||||
#include "GridNotifiersImpl.h"
|
||||
#include "ItemTemplate.h"
|
||||
#include "LFGMgr.h"
|
||||
#include "Language.h"
|
||||
#include "Log.h"
|
||||
#include "LootMgr.h"
|
||||
#include "M2Stores.h"
|
||||
#include "MapMgr.h"
|
||||
#include "ObjectAccessor.h"
|
||||
|
|
@ -33,7 +35,9 @@
|
|||
#include "ScriptMgr.h"
|
||||
#include "Transport.h"
|
||||
#include "Warden.h"
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
using namespace Acore::ChatCommands;
|
||||
|
|
@ -96,6 +100,7 @@ public:
|
|||
{ "itemexpire", HandleDebugItemExpireCommand, SEC_ADMINISTRATOR, Console::No },
|
||||
{ "areatriggers", HandleDebugAreaTriggersCommand, SEC_ADMINISTRATOR, Console::No },
|
||||
{ "lfg", HandleDebugDungeonFinderCommand, SEC_ADMINISTRATOR, Console::Yes},
|
||||
{ "loot", HandleDebugLootCommand, SEC_GAMEMASTER, Console::Yes},
|
||||
{ "los", HandleDebugLoSCommand, SEC_ADMINISTRATOR, Console::No },
|
||||
{ "moveflags", HandleDebugMoveflagsCommand, SEC_ADMINISTRATOR, Console::No },
|
||||
{ "unitstate", HandleDebugUnitStateCommand, SEC_ADMINISTRATOR, Console::No },
|
||||
|
|
@ -1607,6 +1612,235 @@ public:
|
|||
handler->PSendSysMessage("Player count in zone {} ({}): {}.", zoneId, (zoneEntry ? zoneEntry->area_name[LOCALE_enUS] : "<unknown>"), player->GetMap()->GetPlayerCountInZone(zoneId));
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string GetLootSourceName(std::string const& type, uint32 lootId)
|
||||
{
|
||||
if (type == "creature" || type == "skinning" || type == "pickpocketing")
|
||||
{
|
||||
if (CreatureTemplate const* ct = sObjectMgr->GetCreatureTemplate(lootId))
|
||||
return ct->Name;
|
||||
}
|
||||
else if (type == "gameobject")
|
||||
{
|
||||
if (GameObjectTemplate const* gt = sObjectMgr->GetGameObjectTemplate(lootId))
|
||||
return gt->name;
|
||||
}
|
||||
else if (type == "item" || type == "disenchant" || type == "prospecting" || type == "milling")
|
||||
{
|
||||
if (ItemTemplate const* it = sObjectMgr->GetItemTemplate(lootId))
|
||||
return it->Name1;
|
||||
}
|
||||
else if (type == "fishing")
|
||||
{
|
||||
if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(lootId))
|
||||
return area->area_name[LOCALE_enUS];
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
static char const* GetItemQualityName(uint32 quality)
|
||||
{
|
||||
static char const* const qualityNames[MAX_ITEM_QUALITY] =
|
||||
{
|
||||
"Poor", "Normal", "Uncommon", "Rare",
|
||||
"Epic", "Legendary", "Artifact", "Heirloom"
|
||||
};
|
||||
|
||||
if (quality < MAX_ITEM_QUALITY)
|
||||
return qualityNames[quality];
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
static void GenerateLoot(Loot& loot, LootTemplate const* tab,
|
||||
LootStore const& store, Player* player, std::string const& type, uint32 lootId)
|
||||
{
|
||||
loot.clear();
|
||||
loot.items.reserve(MAX_NR_LOOT_ITEMS);
|
||||
loot.quest_items.reserve(MAX_NR_QUEST_ITEMS);
|
||||
tab->Process(loot, store, LOOT_MODE_DEFAULT, player);
|
||||
|
||||
if (type == "creature")
|
||||
{
|
||||
if (CreatureTemplate const* ct = sObjectMgr->GetCreatureTemplate(lootId))
|
||||
loot.generateMoneyLoot(ct->mingold, ct->maxgold);
|
||||
}
|
||||
else if (type == "gameobject")
|
||||
{
|
||||
if (GameObjectTemplateAddon const* addon = sObjectMgr->GetGameObjectTemplateAddon(lootId))
|
||||
loot.generateMoneyLoot(addon->mingold, addon->maxgold);
|
||||
}
|
||||
else if (type == "item")
|
||||
{
|
||||
if (ItemTemplate const* it = sObjectMgr->GetItemTemplate(lootId))
|
||||
loot.generateMoneyLoot(it->MinMoneyLoot, it->MaxMoneyLoot);
|
||||
}
|
||||
}
|
||||
|
||||
static bool HandleDebugLootCommand(ChatHandler* handler, std::string type, uint32 lootId, Optional<uint32> count)
|
||||
{
|
||||
static std::unordered_map<std::string, LootStore*> const lootStoreMap =
|
||||
{
|
||||
{ "creature", &LootTemplates_Creature },
|
||||
{ "gameobject", &LootTemplates_Gameobject },
|
||||
{ "fishing", &LootTemplates_Fishing },
|
||||
{ "item", &LootTemplates_Item },
|
||||
{ "pickpocketing", &LootTemplates_Pickpocketing },
|
||||
{ "skinning", &LootTemplates_Skinning },
|
||||
{ "disenchant", &LootTemplates_Disenchant },
|
||||
{ "prospecting", &LootTemplates_Prospecting },
|
||||
{ "milling", &LootTemplates_Milling },
|
||||
{ "spell", &LootTemplates_Spell },
|
||||
{ "reference", &LootTemplates_Reference },
|
||||
{ "mail", &LootTemplates_Mail },
|
||||
{ "player", &LootTemplates_Player }
|
||||
};
|
||||
|
||||
// Lowercase the type for case-insensitive matching
|
||||
std::transform(type.begin(), type.end(), type.begin(), ::tolower);
|
||||
|
||||
auto itr = lootStoreMap.find(type);
|
||||
if (itr == lootStoreMap.end())
|
||||
{
|
||||
handler->SendErrorMessage(LANG_DEBUG_LOOT_INVALID_TYPE, type);
|
||||
return false;
|
||||
}
|
||||
|
||||
LootStore const* store = itr->second;
|
||||
|
||||
LootTemplate const* tab = store->GetLootFor(lootId);
|
||||
if (!tab)
|
||||
{
|
||||
handler->SendErrorMessage(LANG_DEBUG_LOOT_NO_TEMPLATE, type, lootId);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 iterations = std::min(count.value_or(1), uint32(100));
|
||||
if (iterations == 0)
|
||||
iterations = 1;
|
||||
|
||||
Player* player = handler->GetPlayer();
|
||||
std::string sourceName = GetLootSourceName(type, lootId);
|
||||
|
||||
// Single iteration - original behavior
|
||||
if (iterations == 1)
|
||||
{
|
||||
Loot loot;
|
||||
GenerateLoot(loot, tab, *store, player, type, lootId);
|
||||
|
||||
handler->PSendSysMessage(LANG_DEBUG_LOOT_HEADER, type, sourceName, lootId);
|
||||
|
||||
if (loot.items.empty() && loot.quest_items.empty() && loot.gold == 0)
|
||||
{
|
||||
handler->PSendSysMessage(LANG_DEBUG_LOOT_EMPTY);
|
||||
return true;
|
||||
}
|
||||
|
||||
for (LootItem const& li : loot.items)
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(li.itemid);
|
||||
std::string name = proto ? proto->Name1 : "Unknown";
|
||||
char const* qualityName = GetItemQualityName(proto ? proto->Quality : 0);
|
||||
handler->PSendSysMessage(LANG_DEBUG_LOOT_ITEM,
|
||||
li.itemid, uint32(li.count), name, qualityName,
|
||||
li.randomPropertyId, li.randomSuffix);
|
||||
}
|
||||
|
||||
for (LootItem const& li : loot.quest_items)
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(li.itemid);
|
||||
std::string name = proto ? proto->Name1 : "Unknown";
|
||||
char const* qualityName = GetItemQualityName(proto ? proto->Quality : 0);
|
||||
handler->PSendSysMessage(LANG_DEBUG_LOOT_ITEM_QUEST,
|
||||
li.itemid, uint32(li.count), name, qualityName);
|
||||
}
|
||||
|
||||
if (loot.gold > 0)
|
||||
{
|
||||
uint32 gold = loot.gold / 10000;
|
||||
uint32 silver = (loot.gold % 10000) / 100;
|
||||
uint32 copper = loot.gold % 100;
|
||||
handler->PSendSysMessage(LANG_DEBUG_LOOT_GOLD,
|
||||
loot.gold, gold, silver, copper);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Multi iteration - aggregate results
|
||||
struct ItemStats
|
||||
{
|
||||
uint32 totalCount = 0;
|
||||
uint32 timesDropped = 0;
|
||||
bool questItem = false;
|
||||
};
|
||||
|
||||
std::map<uint32, ItemStats> itemStats;
|
||||
uint64 totalGold = 0;
|
||||
|
||||
for (uint32 i = 0; i < iterations; ++i)
|
||||
{
|
||||
Loot loot;
|
||||
GenerateLoot(loot, tab, *store, player, type, lootId);
|
||||
|
||||
std::set<uint32> seenThisRun;
|
||||
|
||||
for (LootItem const& li : loot.items)
|
||||
{
|
||||
itemStats[li.itemid].totalCount += li.count;
|
||||
if (seenThisRun.insert(li.itemid).second)
|
||||
itemStats[li.itemid].timesDropped++;
|
||||
}
|
||||
|
||||
for (LootItem const& li : loot.quest_items)
|
||||
{
|
||||
itemStats[li.itemid].totalCount += li.count;
|
||||
itemStats[li.itemid].questItem = true;
|
||||
if (seenThisRun.insert(li.itemid).second)
|
||||
itemStats[li.itemid].timesDropped++;
|
||||
}
|
||||
|
||||
totalGold += loot.gold;
|
||||
}
|
||||
|
||||
handler->PSendSysMessage(LANG_DEBUG_LOOT_HEADER_MULTI,
|
||||
type, sourceName, lootId, iterations);
|
||||
|
||||
if (itemStats.empty() && totalGold == 0)
|
||||
{
|
||||
handler->PSendSysMessage(LANG_DEBUG_LOOT_EMPTY);
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto const& [itemId, stats] : itemStats)
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
std::string name = proto ? proto->Name1 : "Unknown";
|
||||
char const* qualityName = GetItemQualityName(proto ? proto->Quality : 0);
|
||||
uint32 dropPct = (stats.timesDropped * 10000) / iterations;
|
||||
|
||||
if (stats.questItem)
|
||||
handler->PSendSysMessage(LANG_DEBUG_LOOT_ITEM_QUEST_MULTI,
|
||||
itemId, stats.totalCount, name, qualityName,
|
||||
stats.timesDropped, iterations, dropPct / 100, dropPct % 100);
|
||||
else
|
||||
handler->PSendSysMessage(LANG_DEBUG_LOOT_ITEM_MULTI,
|
||||
itemId, stats.totalCount, name, qualityName,
|
||||
stats.timesDropped, iterations, dropPct / 100, dropPct % 100);
|
||||
}
|
||||
|
||||
if (totalGold > 0)
|
||||
{
|
||||
uint32 avgGold = static_cast<uint32>(totalGold / iterations);
|
||||
uint32 gold = avgGold / 10000;
|
||||
uint32 silver = (avgGold % 10000) / 100;
|
||||
uint32 copper = avgGold % 100;
|
||||
handler->PSendSysMessage(LANG_DEBUG_LOOT_GOLD_MULTI,
|
||||
avgGold, gold, silver, copper, iterations);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_debug_commandscript()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue