# 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 计算错误 |
