Playerbot: BotGear uses bot name end-to-end + retry first-open resolution
The client's UnitGUID format is unreliable across builds (the parser kept
extracting the realm/sub field), so the request resolved the wrong player and
no GEAR response was ever returned. The bot's NAME is stable, so:
- Server: GEAR_* handlers resolve fields[0] as a name OR numeric guid-low via
ResolveGearBot(), and authorize via the resolved bot's guid.
- Addon: ResolveTarget returns UnitName('target'); all requests send the name;
responses are matched by name; slash handler retries RefreshTarget after
0.25s so the first open still populates when the unit is transiently
unavailable right after targeting.
This commit is contained in:
@@ -19,7 +19,7 @@ local BACKPACK_SLOTS = 16
|
||||
local MAX_BAGS = 4
|
||||
|
||||
-- State -------------------------------------------------------------
|
||||
local frame, botGuid, botName
|
||||
local frame, botName
|
||||
local equipButtons = {} -- [equipSlot] = button
|
||||
local bagButtons = {} -- [bagNum] = { [slot] = button }
|
||||
local equipData = {} -- [equipSlot] = {entry, quality}
|
||||
@@ -52,25 +52,18 @@ local function Send(mtype, ...)
|
||||
end
|
||||
|
||||
local function ResolveTarget()
|
||||
if not UnitExists("target") then return nil, nil end
|
||||
local guid = UnitGUID("target")
|
||||
if not guid then return nil, nil end
|
||||
-- Player guid format ends with the low counter as hex, e.g.
|
||||
-- "Player-1-00000258" or "Player-0000-00000258" / "Player-0-1-00000258".
|
||||
-- The character counter is the LAST '-' group (hex).
|
||||
local low = tonumber(string.match(guid, "%-([%x]+)$"), 16)
|
||||
if not low or low == 0 then return nil, nil end
|
||||
return low, UnitName("target")
|
||||
if not UnitExists("target") then return nil end
|
||||
return UnitName("target")
|
||||
end
|
||||
|
||||
local function RequestBags()
|
||||
if not botGuid then return end
|
||||
Send("GEAR_BAGS_REQ", tostring(botGuid))
|
||||
if not botName then return end
|
||||
Send("GEAR_BAGS_REQ", botName)
|
||||
end
|
||||
|
||||
local function RequestEquip()
|
||||
if not botGuid then return end
|
||||
Send("GEAR_EQUIP_REQ", tostring(botGuid))
|
||||
if not botName then return end
|
||||
Send("GEAR_EQUIP_REQ", botName)
|
||||
end
|
||||
|
||||
local function RefreshAll()
|
||||
@@ -135,12 +128,12 @@ local function CreateSlotButton(parent, x, y, slotType, bag, slot)
|
||||
if slotType == "bag" then
|
||||
if button == "RightButton" and IsShiftKeyDown() then
|
||||
-- Destroy (permanent)
|
||||
Send("GEAR_DESTROY_ITEM", tostring(botGuid), tostring(bag), tostring(slot))
|
||||
Send("GEAR_DESTROY_ITEM", botName, tostring(bag), tostring(slot))
|
||||
selected = nil
|
||||
C_Timer.After(0.3, RefreshAll)
|
||||
elseif button == "RightButton" then
|
||||
-- Equip
|
||||
Send("GEAR_EQUIP_ITEM", tostring(botGuid), tostring(bag), tostring(slot))
|
||||
Send("GEAR_EQUIP_ITEM", botName, tostring(bag), tostring(slot))
|
||||
C_Timer.After(0.3, RefreshAll)
|
||||
elseif button == "LeftButton" then
|
||||
-- Move: pick up / place
|
||||
@@ -152,7 +145,7 @@ local function CreateSlotButton(parent, x, y, slotType, bag, slot)
|
||||
selected = nil
|
||||
RefreshBags()
|
||||
else
|
||||
Send("GEAR_MOVE_ITEM", tostring(botGuid),
|
||||
Send("GEAR_MOVE_ITEM", botName,
|
||||
tostring(selected.bag), tostring(selected.slot),
|
||||
tostring(bag), tostring(slot))
|
||||
selected = nil
|
||||
@@ -160,7 +153,7 @@ local function CreateSlotButton(parent, x, y, slotType, bag, slot)
|
||||
end
|
||||
end
|
||||
elseif slotType == "equip" and button == "RightButton" then
|
||||
Send("GEAR_UNEQUIP_ITEM", tostring(botGuid), tostring(slot))
|
||||
Send("GEAR_UNEQUIP_ITEM", botName, tostring(slot))
|
||||
C_Timer.After(0.3, RefreshAll)
|
||||
end
|
||||
end)
|
||||
@@ -242,7 +235,7 @@ end
|
||||
|
||||
-- Wire protocol: <guid>|G|<gold>|B|<bag>|<entry>|<size>...|I|<bag>|<slot>|<entry>|<count>|<quality>...
|
||||
local function ParseBagsResp(guidStr, body)
|
||||
if not guidStr or tonumber(guidStr) ~= botGuid then return end
|
||||
if guidStr ~= botName then return end
|
||||
bagData = {}
|
||||
bagLayout = {}
|
||||
gold = 0
|
||||
@@ -276,7 +269,7 @@ local function ParseBagsResp(guidStr, body)
|
||||
end
|
||||
|
||||
local function ParseEquipResp(guidStr, body)
|
||||
if not guidStr or tonumber(guidStr) ~= botGuid then return end
|
||||
if guidStr ~= botName then return end
|
||||
equipData = {}
|
||||
if body then
|
||||
for part in body:gmatch("[^|]+") do
|
||||
@@ -294,12 +287,11 @@ end
|
||||
|
||||
-- UI ----------------------------------------------------------------
|
||||
local function RefreshTarget()
|
||||
local g, n = ResolveTarget()
|
||||
if g then
|
||||
botGuid = g
|
||||
local n = ResolveTarget()
|
||||
if n then
|
||||
botName = n
|
||||
if frame and frame.title then
|
||||
frame.title:SetText("Bot - " .. (n or "?"))
|
||||
frame.title:SetText("Bot - " .. n)
|
||||
end
|
||||
if modelActor then
|
||||
modelActor:SetUnit("target")
|
||||
@@ -406,5 +398,9 @@ SlashCmdList["BOTGEAR"] = function()
|
||||
else
|
||||
frame:Show()
|
||||
RefreshTarget()
|
||||
-- The target/unit can be transiently unavailable the instant the
|
||||
-- command runs (freshly targeted unit not fully resolved yet) —
|
||||
-- retry a beat later so the first open still populates.
|
||||
C_Timer.After(0.25, RefreshTarget)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -928,21 +928,34 @@ void HandleSelfToggle(uint32 client_seq, WorldSession* sess,
|
||||
SendFields(sess, client_seq, "SELF_RESP", resp);
|
||||
}
|
||||
|
||||
// Resolve the bot targeted by a GEAR request. <ident> is a numeric guid-low
|
||||
// (legacy) or a character name (current addon — the client's UnitGUID format
|
||||
// varies, the name does not).
|
||||
Player* ResolveGearBot(std::string const& ident)
|
||||
{
|
||||
if (ident.empty()) return nullptr;
|
||||
char* end = nullptr;
|
||||
unsigned long const low = std::strtoul(ident.c_str(), &end, 10);
|
||||
if (end && *end == '\0' && low != 0)
|
||||
return ObjectAccessor::FindConnectedPlayer(
|
||||
ObjectGuid::Create<HighGuid::Player>(static_cast<uint64>(low)));
|
||||
return ObjectAccessor::FindConnectedPlayerByName(ident);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// GEAR_BAGS_REQ — return the bot's backpack contents.
|
||||
// GEAR_BAGS_REQ <guid> — return gold, bag containers, and every item in the
|
||||
// backpack + the four equipped bags.
|
||||
// Payload: <guid>|G|<goldCopper>|B|<bagNum>|<bagEntry>|<bagSize>|I|<bagNum>|<slot>|<entry>|<count>|<quality>
|
||||
// GEAR_BAGS_REQ <ident> — return gold, bag containers, and every item in the
|
||||
// backpack + the four equipped bags. <ident> is a numeric guid-low (legacy) or
|
||||
// a character name (addon sends the name — robust across client guid formats).
|
||||
// Payload: <ident>|G|<goldCopper>|B|<bagNum>|<bagEntry>|<bagSize>|I|<bagNum>|<slot>|<entry>|<count>|<quality>
|
||||
// bagNum 0 = backpack, 1..4 = equipped bags (in bag-slot order).
|
||||
void HandleGearBagsReq(uint32 client_seq, WorldSession* sess,
|
||||
std::vector<std::string> const& fields)
|
||||
{
|
||||
if (fields.empty()) return;
|
||||
uint32 const guidLow = uint32(std::strtoul(fields[0].c_str(), nullptr, 10));
|
||||
ObjectGuid const botGuid = ObjectGuid::Create<HighGuid::Player>(guidLow);
|
||||
Player* bot = ObjectAccessor::FindConnectedPlayer(botGuid);
|
||||
Player* bot = ResolveGearBot(fields[0]);
|
||||
if (!bot) return;
|
||||
if (!Services::Owners().IsOwner(guidLow, sess->GetAccountId(),
|
||||
if (!Services::Owners().IsOwner(bot->GetGUID().GetCounter(), sess->GetAccountId(),
|
||||
sess->GetPlayer() ? sess->GetPlayer()->GetGUID().GetCounter() : 0u))
|
||||
return;
|
||||
|
||||
@@ -1011,11 +1024,9 @@ void HandleGearEquipReq(uint32 client_seq, WorldSession* sess,
|
||||
std::vector<std::string> const& fields)
|
||||
{
|
||||
if (fields.empty()) return;
|
||||
uint32 const guidLow = uint32(std::strtoul(fields[0].c_str(), nullptr, 10));
|
||||
ObjectGuid const botGuid = ObjectGuid::Create<HighGuid::Player>(guidLow);
|
||||
Player* bot = ObjectAccessor::FindConnectedPlayer(botGuid);
|
||||
Player* bot = ResolveGearBot(fields[0]);
|
||||
if (!bot) return;
|
||||
if (!Services::Owners().IsOwner(guidLow, sess->GetAccountId(),
|
||||
if (!Services::Owners().IsOwner(bot->GetGUID().GetCounter(), sess->GetAccountId(),
|
||||
sess->GetPlayer() ? sess->GetPlayer()->GetGUID().GetCounter() : 0u))
|
||||
return;
|
||||
|
||||
@@ -1041,7 +1052,6 @@ void HandleGearEquipItem(uint32 /*client_seq*/, WorldSession* sess,
|
||||
std::vector<std::string> const& fields)
|
||||
{
|
||||
if (fields.size() < 2) return;
|
||||
uint32 const guidLow = uint32(std::strtoul(fields[0].c_str(), nullptr, 10));
|
||||
uint8 srcBag = 0;
|
||||
uint8 srcSlot = 0;
|
||||
if (fields.size() >= 3)
|
||||
@@ -1052,10 +1062,9 @@ void HandleGearEquipItem(uint32 /*client_seq*/, WorldSession* sess,
|
||||
else
|
||||
srcSlot = uint8(std::strtoul(fields[1].c_str(), nullptr, 10));
|
||||
|
||||
ObjectGuid const botGuid = ObjectGuid::Create<HighGuid::Player>(guidLow);
|
||||
Player* bot = ObjectAccessor::FindConnectedPlayer(botGuid);
|
||||
Player* bot = ResolveGearBot(fields[0]);
|
||||
if (!bot) return;
|
||||
if (!Services::Owners().IsOwner(guidLow, sess->GetAccountId(),
|
||||
if (!Services::Owners().IsOwner(bot->GetGUID().GetCounter(), sess->GetAccountId(),
|
||||
sess->GetPlayer() ? sess->GetPlayer()->GetGUID().GetCounter() : 0u))
|
||||
return;
|
||||
|
||||
@@ -1112,12 +1121,10 @@ void HandleGearUnequipItem(uint32 /*client_seq*/, WorldSession* sess,
|
||||
std::vector<std::string> const& fields)
|
||||
{
|
||||
if (fields.size() < 2) return;
|
||||
uint32 const guidLow = uint32(std::strtoul(fields[0].c_str(), nullptr, 10));
|
||||
uint8 const equipSlot = uint8(std::strtoul(fields[1].c_str(), nullptr, 10));
|
||||
ObjectGuid const botGuid = ObjectGuid::Create<HighGuid::Player>(guidLow);
|
||||
Player* bot = ObjectAccessor::FindConnectedPlayer(botGuid);
|
||||
Player* bot = ResolveGearBot(fields[0]);
|
||||
if (!bot) return;
|
||||
if (!Services::Owners().IsOwner(guidLow, sess->GetAccountId(),
|
||||
if (!Services::Owners().IsOwner(bot->GetGUID().GetCounter(), sess->GetAccountId(),
|
||||
sess->GetPlayer() ? sess->GetPlayer()->GetGUID().GetCounter() : 0u))
|
||||
return;
|
||||
|
||||
@@ -1144,13 +1151,11 @@ void HandleGearDestroyItem(uint32 /*client_seq*/, WorldSession* sess,
|
||||
std::vector<std::string> const& fields)
|
||||
{
|
||||
if (fields.size() < 3) return;
|
||||
uint32 const guidLow = uint32(std::strtoul(fields[0].c_str(), nullptr, 10));
|
||||
uint8 const bagNum = uint8(std::strtoul(fields[1].c_str(), nullptr, 10));
|
||||
uint8 const slot = uint8(std::strtoul(fields[2].c_str(), nullptr, 10));
|
||||
ObjectGuid const botGuid = ObjectGuid::Create<HighGuid::Player>(guidLow);
|
||||
Player* bot = ObjectAccessor::FindConnectedPlayer(botGuid);
|
||||
Player* bot = ResolveGearBot(fields[0]);
|
||||
if (!bot) return;
|
||||
if (!Services::Owners().IsOwner(guidLow, sess->GetAccountId(),
|
||||
if (!Services::Owners().IsOwner(bot->GetGUID().GetCounter(), sess->GetAccountId(),
|
||||
sess->GetPlayer() ? sess->GetPlayer()->GetGUID().GetCounter() : 0u))
|
||||
return;
|
||||
|
||||
@@ -1168,15 +1173,13 @@ void HandleGearMoveItem(uint32 /*client_seq*/, WorldSession* sess,
|
||||
std::vector<std::string> const& fields)
|
||||
{
|
||||
if (fields.size() < 5) return;
|
||||
uint32 const guidLow = uint32(std::strtoul(fields[0].c_str(), nullptr, 10));
|
||||
uint8 const srcBag = uint8(std::strtoul(fields[1].c_str(), nullptr, 10));
|
||||
uint8 const srcSlot = uint8(std::strtoul(fields[2].c_str(), nullptr, 10));
|
||||
uint8 const dstBag = uint8(std::strtoul(fields[3].c_str(), nullptr, 10));
|
||||
uint8 const dstSlot = uint8(std::strtoul(fields[4].c_str(), nullptr, 10));
|
||||
ObjectGuid const botGuid = ObjectGuid::Create<HighGuid::Player>(guidLow);
|
||||
Player* bot = ObjectAccessor::FindConnectedPlayer(botGuid);
|
||||
Player* bot = ResolveGearBot(fields[0]);
|
||||
if (!bot) return;
|
||||
if (!Services::Owners().IsOwner(guidLow, sess->GetAccountId(),
|
||||
if (!Services::Owners().IsOwner(bot->GetGUID().GetCounter(), sess->GetAccountId(),
|
||||
sess->GetPlayer() ? sess->GetPlayer()->GetGUID().GetCounter() : 0u))
|
||||
return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user