# LootDisplay 拾取窗口接管 — 技术要点 ## 最终成功方案 **核心原则:不替换原生按钮的交互逻辑,只替换视觉层,重新定位原生按钮。** 在 Turtle WoW (1.12 魔兽私服) 中,`LootSlot()` 是一个**受保护的 C 端函数**, 只接受来自原生 `LootButton1~4`(由 FrameXML 中 `LootButtonTemplate` 创建的按钮) 的内置 `OnClick` 处理器调用。任何 addon 自建按钮(无论是否使用模板)都**无法**成功 调用 `LootSlot()`。 --- ## 失败方案及原因 ### 方案 1:LootButtonTemplate 自定义按钮 ```lua local btn = CreateFrame("Button", "NanamiLootBtn1", lootFrame, "LootButtonTemplate") btn:SetScript("OnClick", function() LootSlot(this.slot) end) ``` **失败原因**:虽然使用了 `LootButtonTemplate`,但按钮是 addon 动态创建的, 不是 FrameXML 在加载期创建的原生 `LootButton1~4`。Turtle WoW 的 C 端可能检查 调用来源是否为受信任的原生按钮,导致 `LootSlot()` 静默失败。 ### 方案 2:纯自定义 Button + 直接调用 LootSlot ```lua local btn = CreateFrame("Button", nil, lootFrame) btn:SetScript("OnClick", function() LootSlot(this.slot) end) ``` **失败原因**:与方案 1 相同。`LootSlot()` 只信任原生按钮的事件上下文。 截图可验证 `GameTooltip:SetLootItem()` 正常工作(tooltip 能显示),说明 拾取会话本身是活跃的,纯粹是 `LootSlot()` 拒绝执行。 ### 方案 3:完全禁用 LootFrame + 自定义按钮 ```lua LootFrame:UnregisterAllEvents() LootFrame:Hide() ``` **失败原因**:在禁用 LootFrame 的基础上使用自定义按钮调 `LootSlot()`, 同样因为 C 端保护而失败。另外隐藏 LootFrame 后 C 端可能也认为拾取会话无效。 --- ## 成功方案:原生按钮重定位 ### 架构概览 ``` ┌─ NanamiLootFrame (自定义视觉框架) ──────┐ │ ┌─ 视觉行 row1 (EnableMouse=false) ──┐ │ │ │ icon + name + quality bar │ │ ← 玩家看到的 │ │ ┌─ LootButton1 (alpha=0) ───────┐ │ │ │ │ │ 原生 OnClick → LootSlot() │ │ │ ← 玩家点击的 │ │ └───────────────────────────────┘ │ │ │ └────────────────────────────────────┘ │ │ ┌─ 视觉行 row2 ... ──┐ │ │ └────────────────────────────────────┘ │ └─────────────────────────────────────────┘ ┌─ LootFrame (原生, alpha=0, 不可交互) ───┐ │ (存在但不可见,维持拾取会话) │ └─────────────────────────────────────────┘ ``` ### 关键步骤 #### 1. 保存原始 `LootFrame_Update` 引用 ```lua origLootFrameUpdate = LootFrame_Update ``` **不能**将 `LootFrame_Update` 替换为空函数。这个函数负责为 `LootButton1~4` 设置 `SetID()`、slot 数据、以及关键的内置 `OnClick` 处理器。 #### 2. 让原生 LootFrame 保持"活跃但不可见" ```lua -- 清除 OnHide 防止 CloseLoot() 被意外调用 LootFrame:SetScript("OnHide", function() end) -- Show hook:每次 Show 后强制 alpha=0 local origShow = LootFrame.Show LootFrame.Show = function(self) origShow(self) self:SetAlpha(0) self:EnableMouse(false) end -- Hide hook:我们的框架显示期间阻止隐藏 local origHide = LootFrame.Hide LootFrame.Hide = function(self) if lootFrame and lootFrame:IsShown() then return end origHide(self) end ``` **为什么不能 Hide/UnregisterAllEvents**: - `LootFrame:Hide()` 的 XML OnHide 会调用 `CloseLoot()`,立即终止拾取会话 - C 端可能检查 `LootFrame:IsShown()` 来判断拾取是否合法 - 原生 `LootButton1~4` 是 `LootFrame` 的子框架,父框架隐藏则子框架不可交互 #### 3. ShowLootPage 的双阶段流程 **阶段 A — 构建视觉层**:设置自定义行的图标、名称、品质颜色。 视觉行 `EnableMouse(false)`,不拦截任何鼠标事件。 **阶段 B — 设置原生按钮并重定位**: ```lua -- 同步页码 LootFrame.page = page if not LootFrame:IsShown() then LootFrame:Show() end -- 让原生代码完整设置按钮状态(ID、OnClick 等) origLootFrameUpdate() -- 将原生按钮移到我们的视觉行上 for i = 1, 4 do local nb = _G["LootButton" .. i] local row = lootRows[i] if nb and row and row:IsShown() and row._qualColor then nb:ClearAllPoints() nb:SetPoint("TOPLEFT", row, "TOPLEFT", 0, 0) nb:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", 0, 0) nb:SetFrameStrata("FULLSCREEN_DIALOG") nb:SetFrameLevel(row:GetFrameLevel() + 10) nb:SetAlpha(0) -- 不可见 nb:EnableMouse(true) -- 可点击 nb:Show() end end ``` #### 4. Hook 全局 `LootFrame_Update` 保持一致性 ```lua LootFrame_Update = function() origLootFrameUpdate() -- 原生设置 -- 如果我们的框架在显示,重定位按钮 if lootFrame and lootFrame:IsShown() then for i = 1, 4 do -- 同样的重定位逻辑 end end end ``` 这确保**任何来源**的 `LootFrame_Update` 调用(包括 `LOOT_SLOT_CLEARED` 事件后 引擎的自动调用)都会以按钮在正确位置结束,解决了"拾取一个物品后无法继续拾取"的问题。 --- ## 事件流程梳理 ### 打开拾取 ``` 玩家右键尸体 → C 引擎创建拾取会话 → LOOT_OPENED 事件 → 原生 LootFrame_OnEvent → LootFrame:Show() [hook: alpha=0] → LootFrame_Update [hook: 原生设置 + 重定位] → 我们的 LOOT_OPENED handler → UpdateLootFrame → ShowLootPage → 设置视觉行 → origLootFrameUpdate() → 重定位按钮 ``` ### 拾取物品 ``` 玩家点击 LootButton1 (alpha=0, 覆盖在视觉行上) → 原生 LootButton_OnClick → LootSlot(this:GetID()) ← 受信任的调用 → 物品拾取成功 → LOOT_SLOT_CLEARED 事件 → 原生 handler → LootFrame_Update [hook: 原生重设按钮 + 重定位] → 我们的 handler → UpdateLootFrame → ShowLootPage → 刷新视觉 + 重定位 ``` ### 关闭拾取 ``` 玩家走开 / 按 ESC / 点关闭按钮 → CloseLoot() 或 LOOT_CLOSED 事件 → CloseLootFrame() → 隐藏自定义框架 (设 _closingLoot 标志) → 隐藏原生按钮 → 允许 LootFrame:Hide() ``` --- ## 关键技术教训 | 教训 | 说明 | |------|------| | `LootSlot()` 是受保护的 | Turtle WoW 中只有原生按钮的内置 OnClick 能成功调用 | | 不能隐藏 LootFrame | OnHide (XML) 会调用 `CloseLoot()` 终止会话 | | 不能禁用 LootFrame_Update | 这个函数负责设置按钮的 ID 和交互能力 | | 视觉与交互分离 | 自定义行负责视觉 (EnableMouse=false),原生按钮负责交互 (alpha=0) | | Hook 先调原始再改位置 | `LootFrame_Update` hook 先跑原生逻辑,再重定位按钮到自定义行上 | | OnHide 需要防重入 | `_closingLoot` 标志防止 OnHide → CloseLoot → LOOT_CLOSED → CloseLootFrame 循环 | | 页码必须同步 | `LootFrame.page` 必须与自定义分页同步,否则原生按钮 ID 计算错误 |