mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
Correct Loot rolling behavior (#2190)
# Pull Request This fixes the loot rolling behavior issue created by #2068 . Introduce the ability for enchanter bots to disenchant items they dont need, and roll need on recipes they also need. Make it so ITEM_USAGE_AH ensures the item is not BOP. Try to reduce the call for item_usage in CalculateRollVote by passing usage if available. --- ## Design Philosophy We prioritize **stability, performance, and predictability** over behavioral realism. Complex player-mimicking logic is intentionally limited due to its negative impact on scalability, maintainability, and long-term robustness. Excessive processing overhead can lead to server hiccups, increased CPU usage, and degraded performance for all participants. Because every action and decision tree is executed **per bot and per trigger**, even small increases in logic complexity can scale poorly and negatively affect both players and world (random) bots. Bots are not expected to behave perfectly, and perfect simulation of human decision-making is not a project goal. Increased behavioral realism often introduces disproportionate cost, reduced predictability, and significantly higher maintenance overhead. Every additional branch of logic increases long-term responsibility. All decision paths must be tested, validated, and maintained continuously as the system evolves. If advanced or AI-intensive behavior is introduced, the **default configuration must remain the lightweight decision model**. More complex behavior should only be available as an **explicit opt-in option**, clearly documented as having a measurable performance cost. Principles: - **Stability before intelligence** A stable system is always preferred over a smarter one. - **Performance is a shared resource** Any increase in bot cost affects all players and all bots. - **Simple logic scales better than smart logic** Predictable behavior under load is more valuable than perfect decisions. - **Complexity must justify itself** If a feature cannot clearly explain its cost, it should not exist. - **Defaults must be cheap** Expensive behavior must always be optional and clearly communicated. - **Bots should look reasonable, not perfect** The goal is believable behavior, not human simulation. Before submitting, confirm that this change aligns with those principles. --- ## Feature Evaluation Please answer the following: - Describe the **minimum logic** required to achieve the intended behavior? -- Add a new check that downgrades greed rolls to desired levels, or bools for the other two options. - Describe the **cheapest implementation** that produces an acceptable result? -- As implemented. - Describe the **runtime cost** when this logic executes across many bots? -- Same as before. Item usage is the heaviest part, and that hasnt changed to accommodate this. --- ## How to Test the Changes - multiple bots in a group with group loot on, do a dungeon or something. One bot should be an enchanter. ## Complexity & Impact Does this change add new decision branches? - - [ ] No - - [x] Yes (**explain below**) Does this change increase per-bot or per-tick processing? - - [X] No - - [ ] Yes (**describe and justify impact**) Could this logic scale poorly under load? - - [X] No - - [ ] Yes (**explain why**) --- ## Defaults & Configuration Does this change modify default bot behavior? - - [ ] No - - [X] Yes (**explain why**) - - - Corrects the looting behavior to original design. If this introduces more advanced or AI-heavy logic: - - [ ] Lightweight mode remains the default - - [X] More complex behavior is optional and thereby configurable --- ## AI Assistance Was AI assistance (e.g. ChatGPT or similar tools) used while working on this change? - - [x] No - - [ ] Yes (**explain below**) --- ## Final Checklist - - [x] Stability is not compromised - - [x] Performance impact is understood, tested, and acceptable - - [x] Added logic complexity is justified and explained - - [x] Documentation updated if needed --- ## Notes for Reviewers Anything that significantly improves realism at the cost of stability or performance should be carefully discussed before merging.
This commit is contained in:
parent
a473432b8f
commit
2ce8993986
@ -298,9 +298,24 @@ AiPlayerbot.TwoRoundsGearInit = 0
|
||||
# Default: 0 (disabled)
|
||||
AiPlayerbot.FreeMethodLoot = 0
|
||||
|
||||
# Bots' loot roll level (0 = pass, 1 = greed, 2 = need)
|
||||
# Bots' Roll level bots will use for items they Need (0 = pass, 1 = greed, 2 = need)
|
||||
# Default: 1 (greed)
|
||||
AiPlayerbot.LootRollLevel = 1
|
||||
AiPlayerbot.LootNeedRollLevel = 1
|
||||
|
||||
# Enable bots to roll GREED on items (global toggle)
|
||||
# If disabled, bots will PASS instead of GREED on all items
|
||||
# Default: 0 (disabled - bots only NEED or PASS)
|
||||
AiPlayerbot.LootGreedRollLevel = 0
|
||||
|
||||
# Enable bots to roll on recipes. Will NEED on learnable profession recipes they don't already know
|
||||
# Bots will roll GREED on BoE recipes they can't learn if LootRollGreed is enabled.
|
||||
# Default: 0 (disabled)
|
||||
AiPlayerbot.LootRollRecipe = 0
|
||||
|
||||
# Bots with enchanting will roll DISENCHANT instead of GREED on disenchantable items
|
||||
# If disabled, bots will GREED on disenchantable items instead
|
||||
# Default: 0 (disabled)
|
||||
AiPlayerbot.LootRollDisenchant = 0
|
||||
|
||||
#
|
||||
#
|
||||
|
||||
@ -22,10 +22,10 @@ bool LootRollAction::Execute(Event /*event*/)
|
||||
std::vector<Roll*> rolls = group->GetRolls();
|
||||
for (Roll*& roll : rolls)
|
||||
{
|
||||
if (roll->playerVote.find(bot->GetGUID())->second != NOT_EMITED_YET)
|
||||
{
|
||||
auto voteItr = roll->playerVote.find(bot->GetGUID());
|
||||
if (voteItr == roll->playerVote.end() || voteItr->second != NOT_EMITED_YET)
|
||||
continue;
|
||||
}
|
||||
|
||||
ObjectGuid guid = roll->itemGUID;
|
||||
uint32 itemId = roll->itemid;
|
||||
int32 randomProperty = 0;
|
||||
@ -41,27 +41,22 @@ bool LootRollAction::Execute(Event /*event*/)
|
||||
|
||||
std::string itemUsageParam;
|
||||
if (randomProperty != 0)
|
||||
{
|
||||
itemUsageParam = std::to_string(itemId) + "," + std::to_string(randomProperty);
|
||||
}
|
||||
else
|
||||
{
|
||||
itemUsageParam = std::to_string(itemId);
|
||||
}
|
||||
|
||||
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", itemUsageParam);
|
||||
|
||||
// Armor Tokens are classed as MISC JUNK (Class 15, Subclass 0), luckily no other items I found have class bits and epic quality.
|
||||
if (proto->Class == ITEM_CLASS_MISC && proto->SubClass == ITEM_SUBCLASS_JUNK && proto->Quality == ITEM_QUALITY_EPIC)
|
||||
{
|
||||
if (CanBotUseToken(proto, bot))
|
||||
{
|
||||
vote = NEED; // Eligible for "Need"
|
||||
}
|
||||
else
|
||||
{
|
||||
vote = GREED; // Not eligible, so "Greed"
|
||||
}
|
||||
}
|
||||
else if (usage == ITEM_USAGE_DISENCHANT)
|
||||
vote = sPlayerbotAIConfig.lootRollDisenchant ? DISENCHANT : GREED;
|
||||
else
|
||||
{
|
||||
switch (proto->Class)
|
||||
@ -69,40 +64,34 @@ bool LootRollAction::Execute(Event /*event*/)
|
||||
case ITEM_CLASS_WEAPON:
|
||||
case ITEM_CLASS_ARMOR:
|
||||
if (usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_BAD_EQUIP)
|
||||
{
|
||||
vote = NEED;
|
||||
}
|
||||
else if (usage != ITEM_USAGE_NONE)
|
||||
{
|
||||
vote = GREED;
|
||||
}
|
||||
break;
|
||||
case ITEM_CLASS_RECIPE:
|
||||
if (!sPlayerbotAIConfig.lootRollRecipe)
|
||||
vote = PASS;
|
||||
else if (usage == ITEM_USAGE_SKILL)
|
||||
vote = NEED; // Bot can learn this recipe
|
||||
else if (proto->Bonding != BIND_WHEN_PICKED_UP)
|
||||
vote = GREED; // BoE recipe bot can't learn - GREED for AH/trade
|
||||
break;
|
||||
default:
|
||||
if (StoreLootAction::IsLootAllowed(itemId, botAI))
|
||||
vote = CalculateRollVote(proto); // Ensure correct Need/Greed behavior
|
||||
vote = CalculateRollVote(proto, usage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sPlayerbotAIConfig.lootRollLevel == 0)
|
||||
if (vote == NEED)
|
||||
{
|
||||
if (sPlayerbotAIConfig.lootNeedRollLevel == 0 || RollUniqueCheck(proto, bot))
|
||||
vote = PASS;
|
||||
else if (sPlayerbotAIConfig.lootNeedRollLevel == 1)
|
||||
vote = GREED;
|
||||
}
|
||||
else if (vote == GREED && !sPlayerbotAIConfig.lootGreedRollLevel)
|
||||
vote = PASS;
|
||||
}
|
||||
else if (sPlayerbotAIConfig.lootRollLevel == 1)
|
||||
{
|
||||
// Level 1 = "greed" mode: bots greed on useful items but never need
|
||||
// Only downgrade NEED to GREED, preserve GREED votes as-is
|
||||
if (vote == NEED)
|
||||
{
|
||||
if (RollUniqueCheck(proto, bot))
|
||||
{
|
||||
vote = PASS;
|
||||
}
|
||||
else
|
||||
{
|
||||
vote = GREED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (group->GetLootMethod())
|
||||
{
|
||||
case MASTER_LOOT:
|
||||
@ -120,11 +109,14 @@ bool LootRollAction::Execute(Event /*event*/)
|
||||
return false;
|
||||
}
|
||||
|
||||
RollVote LootRollAction::CalculateRollVote(ItemTemplate const* proto)
|
||||
RollVote LootRollAction::CalculateRollVote(ItemTemplate const* proto, ItemUsage usage)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << proto->ItemId;
|
||||
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", out.str());
|
||||
if (usage == ITEM_USAGE_NONE)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << proto->ItemId;
|
||||
usage = AI_VALUE2(ItemUsage, "item usage", out.str());
|
||||
}
|
||||
|
||||
RollVote needVote = PASS;
|
||||
switch (usage)
|
||||
@ -137,11 +129,13 @@ RollVote LootRollAction::CalculateRollVote(ItemTemplate const* proto)
|
||||
break;
|
||||
case ITEM_USAGE_SKILL:
|
||||
case ITEM_USAGE_USE:
|
||||
case ITEM_USAGE_DISENCHANT:
|
||||
case ITEM_USAGE_AH:
|
||||
case ITEM_USAGE_VENDOR:
|
||||
needVote = GREED;
|
||||
break;
|
||||
case ITEM_USAGE_DISENCHANT:
|
||||
needVote = sPlayerbotAIConfig.lootRollDisenchant ? DISENCHANT : GREED;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -195,9 +189,7 @@ bool CanBotUseToken(ItemTemplate const* proto, Player* bot)
|
||||
|
||||
// Check if the bot's class is allowed to use the token
|
||||
if (proto->AllowableClass & botClassMask)
|
||||
{
|
||||
return true; // Bot's class is eligible to use this token
|
||||
}
|
||||
|
||||
return false; // Bot's class cannot use this token
|
||||
}
|
||||
@ -213,13 +205,9 @@ bool RollUniqueCheck(ItemTemplate const* proto, Player* bot)
|
||||
// Determine if the unique item is already equipped
|
||||
bool isEquipped = (totalItemCount > bagItemCount);
|
||||
if (isEquipped && proto->HasFlag(ITEM_FLAG_UNIQUE_EQUIPPABLE))
|
||||
{
|
||||
return true; // Unique Item is already equipped
|
||||
}
|
||||
else if (proto->HasFlag(ITEM_FLAG_UNIQUE_EQUIPPABLE) && (bagItemCount > 1))
|
||||
{
|
||||
return true; // Unique item already in bag, don't roll for it
|
||||
}
|
||||
return false; // Item is not equipped or in bags, roll for it
|
||||
}
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ public:
|
||||
bool Execute(Event event) override;
|
||||
|
||||
protected:
|
||||
RollVote CalculateRollVote(ItemTemplate const* proto);
|
||||
RollVote CalculateRollVote(ItemTemplate const* proto, ItemUsage usage = ITEM_USAGE_NONE);
|
||||
};
|
||||
|
||||
bool CanBotUseToken(ItemTemplate const* proto, Player* bot);
|
||||
|
||||
@ -153,9 +153,8 @@ ItemUsage ItemUsageValue::Calculate()
|
||||
// Need to add something like free bagspace or item value.
|
||||
if (proto->SellPrice > 0)
|
||||
{
|
||||
if (proto->Quality >= ITEM_QUALITY_NORMAL && !isSoulbound)
|
||||
if (proto->Quality >= ITEM_QUALITY_NORMAL && !isSoulbound && proto->Bonding != BIND_WHEN_PICKED_UP)
|
||||
return ITEM_USAGE_AH;
|
||||
|
||||
else
|
||||
return ITEM_USAGE_VENDOR;
|
||||
}
|
||||
|
||||
@ -620,7 +620,10 @@ bool PlayerbotAIConfig::Initialize()
|
||||
|
||||
// SPP automation
|
||||
freeMethodLoot = sConfigMgr->GetOption<bool>("AiPlayerbot.FreeMethodLoot", false);
|
||||
lootRollLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.LootRollLevel", 1);
|
||||
lootNeedRollLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.LootNeedRollLevel", 1);
|
||||
lootRollRecipe = sConfigMgr->GetOption<bool>("AiPlayerbot.LootRollRecipe", false);
|
||||
lootRollDisenchant = sConfigMgr->GetOption<bool>("AiPlayerbot.LootRollDisenchant", false);
|
||||
lootGreedRollLevel = sConfigMgr->GetOption<bool>("AiPlayerbot.LootGreedRollLevel", false);
|
||||
autoPickReward = sConfigMgr->GetOption<std::string>("AiPlayerbot.AutoPickReward", "yes");
|
||||
autoEquipUpgradeLoot = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoEquipUpgradeLoot", true);
|
||||
equipUpgradeThreshold = sConfigMgr->GetOption<float>("AiPlayerbot.EquipUpgradeThreshold", 1.1f);
|
||||
|
||||
@ -346,7 +346,10 @@ public:
|
||||
uint32 botActiveAloneSmartScaleWhenMaxLevel;
|
||||
|
||||
bool freeMethodLoot;
|
||||
int32 lootRollLevel;
|
||||
int32 lootNeedRollLevel;
|
||||
bool lootGreedRollLevel;
|
||||
bool lootRollRecipe;
|
||||
bool lootRollDisenchant;
|
||||
std::string autoPickReward;
|
||||
bool autoEquipUpgradeLoot;
|
||||
float equipUpgradeThreshold;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user