---
name: wow-335a-ui
description: >
  Верстка и разработка UI-аддонов для World of Warcraft 3.3.5a (WotLK, build 12340).
  Используй этот скилл при любой работе с интерфейсом WoW 3.3.5: создание фреймов
  через XML или Lua CreateFrame(), позиционирование через SetPoint/якоря, слои
  (BACKGROUND/ARTWORK/OVERLAY/HIGHLIGHT), страты (frameStrata), текстуры, шрифты,
  кнопки, ScrollFrame, скрипты-события, структура аддона (TOC + XML + Lua),
  SavedVariables, работа с Blizzard API патча 3.3.5. Также применяй когда речь идёт
  об AzerothCore, кастомных интерфейсах для WoW-прайват серверов 3.3.5a, Eluna
  или модификации FrameXML. ОБЯЗАТЕЛЬНО применяй при задачах pixel-perfect переноса
  дизайна (Figma/PSD/скриншот) в WoW UI: пересчёт координат с учётом UIParent scale,
  конвертация цветов HEX→RGBA, подбор шрифтов, подготовка текстур (степени двойки,
  TexCoord-кроп), устранение расхождений между макетом и игровым интерфейсом.
---

# WoW 3.3.5a — Верстка UI (FrameXML / AddOn Development)

## Ключевые ограничения патча 3.3.5a

- **API версии**: `## Interface: 30300` (build 12340)
- **Lua версия**: Lua 5.1
- **Текстуры**: только степени двойки по ширине/высоте (8, 16, 32, 64, 128, 256, 512 px); максимум 512×512
- **Нет** `:SetSize()` — только `:SetWidth()` / `:SetHeight()` (или через XML `<Size>`)
- **Нет** `hooksecurefunc` для защищённых фреймов без taint
- Отсутствуют многие retail-API: нет `C_Timer`, нет `CreateFramePool`, нет `PixelUtil`
- Таймеры — через `OnUpdate` с накоплением `elapsed`

---

## Pixel-Perfect: перенос дизайна из Figma/PSD в WoW UI

### Главная проблема: система координат WoW ≠ пиксели экрана

WoW рисует UI в **виртуальных единицах**, а не в экранных пикселях. Размер виртуального экрана зафиксирован движком на **768 единиц по высоте**, ширина зависит от соотношения сторон:

| Разрешение | Виртуальная ширина | Виртуальная высота |
|-----------|-------------------|-------------------|
| 1024×768  | 1024              | 768               |
| 1280×960  | 1024              | 768               |
| 1920×1080 | 1365              | 768               |
| 2560×1440 | 1365              | 768               |

> **Вывод**: высота UIParent всегда = 768. Всё, что сделано в Figma на 768px по высоте, переносится 1:1 без пересчёта. При другой высоте холста в Figma — нужен коэффициент.

### Пересчёт координат из Figma в WoW

```lua
-- Формула: wow_value = figma_value * (768 / figma_canvas_height)
-- Если макет сделан на 1080px:
local SCALE = 768 / 1080  -- ≈ 0.711

local function px(figma_px)
    return math.floor(figma_px * SCALE + 0.5)  -- округление к ближайшему
end

-- Использование:
frame:SetWidth(px(400))   -- figma: 400px → wow: 284
frame:SetHeight(px(200))  -- figma: 200px → wow: 142
frame:SetPoint("TOPLEFT", UIParent, "TOPLEFT", px(50), -px(30))
```

> Если макет 768px в высоту — `SCALE = 1.0`, пересчёт не нужен.

### UIParent scale и как он ломает пиксели

Игрок может изменить масштаб UI в настройках. Это сдвигает все координаты.

```lua
-- Получить текущий масштаб UIParent
local uiScale = UIParent:GetScale()  -- обычно 0.64–1.0

-- Перевести экранные пиксели в виртуальные единицы WoW
local function screenPxToWow(px)
    local screenH = select(2, GetPhysicalScreenSize())  -- реальный размер экрана
    return px * (768 / screenH) / uiScale
end

-- Принудительно зафиксировать scale (использовать с умом!)
-- UIParent:SetScale(1.0)  -- сломает все стандартные фреймы
```

**Рекомендация**: не трогай `UIParent:SetScale()`. Вместо этого рисуй в виртуальных единицах и принимай, что у разных игроков масштаб разный. Для абсолютного pixel-perfect на конкретном разрешении — дай опцию ручного масштабирования фрейма в настройках.

```lua
-- Масштабируй только свой фрейм
frame:SetScale(0.85)
-- Тогда все дочерние элементы тоже масштабируются
```

---

### Цвета: Figma HEX/RGBA → WoW RGBA

WoW принимает цвета в диапазоне **0.0–1.0** (не 0–255).

```lua
-- Конвертер HEX → WoW RGBA
local function hex(str, a)
    str = str:gsub("#", "")
    local r = tonumber(str:sub(1,2), 16) / 255
    local g = tonumber(str:sub(3,4), 16) / 255
    local b = tonumber(str:sub(5,6), 16) / 255
    return r, g, b, a or 1.0
end

-- Использование:
tex:SetVertexColor(hex("#FF6A00"))         -- непрозрачный
tex:SetVertexColor(hex("#FF6A00", 0.8))    -- 80% непрозрачность
fs:SetTextColor(hex("#FFD100"))
frame:SetBackdropColor(hex("#1A1A2E", 0.95))
frame:SetBackdropBorderColor(hex("#4A90D9"))
```

**Figma opacity → WoW alpha**: значение из Figma (например, 75%) делится на 100 → `0.75`.

```lua
-- Figma: opacity 60%
frame:SetAlpha(0.60)
tex:SetAlpha(0.60)
```

---

### Шрифты: как приблизить к дизайну

WoW 3.3.5a поддерживает только `.ttf` шрифты, помещённые в папку аддона.

```lua
-- Кастомный шрифт из аддона
fs:SetFont("Interface\\AddOns\\MyAddon\\fonts\\MyFont.ttf", 14, "OUTLINE")
-- Флаги: "", "OUTLINE", "THICKOUTLINE", "MONOCHROME", "OUTLINE,MONOCHROME"
```

**Соответствие Figma → WoW размер шрифта**:

| Figma (макет 1080px) | WoW pt (при SCALE=0.711) |
|---------------------|--------------------------|
| 12 px | 9 |
| 14 px | 10 |
| 16 px | 11 |
| 18 px | 13 |
| 24 px | 17 |
| 32 px | 23 |

```lua
-- Автоматический пересчёт размера шрифта
local function fontSize(figma_pt)
    return math.max(8, math.floor(figma_pt * SCALE + 0.5))
end
fs:SetFont("Fonts\\FRIZQT__.TTF", fontSize(18), "OUTLINE")
```

> **Минимальный читаемый размер в WoW**: 8 pt. Ниже — нечитаемо.

**Встроенные шрифты WoW 3.3.5a**:
```
Fonts\FRIZQT__.TTF    — основной интерфейсный (без кириллицы!)
Fonts\MORPHEUS.ttf    — декоративный (заголовки)
Fonts\ARIALN.TTF      — Arial Narrow
Fonts\skurri.ttf      — шрифт повреждений
```

> **Кириллица**: встроенные шрифты WoW не поддерживают кириллицу. Положи в аддон `.ttf` с кириллицей (например, PT Sans, Roboto) и подключи явно.

---

### Текстуры: подготовка под ограничения движка

#### Требования к текстурам WoW 3.3.5a
- Ширина и высота — **степени двойки**: 2, 4, 8, 16, 32, 64, 128, 256, 512
- Максимум: **512×512 px**
- Формат: `.tga` (RGBA, 32-bit) или `.blp`
- Текстура не обязана быть квадратной: 512×64 — ок, 256×128 — ок

#### Как уместить большой элемент дизайна

Если элемент из Figma больше 512px — разрезай на части:

```
[512×256] [512×256]   ← верхняя половина (левая + правая)
[512×256] [512×256]   ← нижняя половина
```

```lua
-- Сборка из 4 текстур
local tl = frame:CreateTexture(nil, "BACKGROUND")
tl:SetTexture("Interface\\AddOns\\MyAddon\\bg_tl")
tl:SetPoint("TOPLEFT", frame, "TOPLEFT")
tl:SetWidth(256); tl:SetHeight(128)

local tr = frame:CreateTexture(nil, "BACKGROUND")
tr:SetTexture("Interface\\AddOns\\MyAddon\\bg_tr")
tr:SetPoint("TOPRIGHT", frame, "TOPRIGHT")
tr:SetWidth(256); tr:SetHeight(128)
-- ... и так далее
```

#### TexCoord — показать только часть текстуры

Если в одном файле несколько элементов (спрайт-лист), используй `SetTexCoord`:

```lua
-- SetTexCoord(left, right, top, bottom) — от 0.0 до 1.0
-- Пример: иконка занимает правый нижний квадрант 256×256 атласа
tex:SetTexCoord(0.5, 1.0, 0.5, 1.0)

-- Формула для ячейки в сетке (col/row с нуля):
local cols, rows = 4, 4
local col, row = 2, 1  -- третья колонка, вторая строка
tex:SetTexCoord(
    col/cols,         -- left
    (col+1)/cols,     -- right
    row/rows,         -- top
    (row+1)/rows      -- bottom
)
```

> **Важно**: при SetTexCoord текстура тайлится внутри заданного Size фрейма. SetAllPoints растягивает — SetWidth/SetHeight + SetPoint = точный размер.

---

### Позиционирование: от Figma к SetPoint

В Figma позиция элемента — это X,Y от левого верхнего угла холста (или родителя). В WoW — якорная система.

**Алгоритм переноса**:

1. Определи ближайший угол/сторону в Figma (от чего считать)
2. Вычисли отступ от этой стороны
3. Пересчитай через `px()`

```lua
-- Figma (холст 1365×768):
-- Элемент: X=50, Y=30, W=200, H=100
-- → привязка от TOPLEFT UIParent

frame:SetWidth(px(200))
frame:SetHeight(px(100))
frame:SetPoint("TOPLEFT", UIParent, "TOPLEFT", px(50), -px(30))
--                                              ^^^      ^^^
--                                              X offset  Y offset (отрицательный! вниз)
```

> **Знаки осей WoW**: X растёт вправо (+), Y растёт вверх (+). Отступ вниз от TOPLEFT — **отрицательный**.

| Figma anchor | WoW SetPoint |
|-------------|-------------|
| Top-Left | `"TOPLEFT", UIParent, "TOPLEFT", x, -y` |
| Top-Right | `"TOPRIGHT", UIParent, "TOPRIGHT", -x, -y` |
| Bottom-Left | `"BOTTOMLEFT", UIParent, "BOTTOMLEFT", x, y` |
| Bottom-Right | `"BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", -x, y` |
| Center | `"CENTER", UIParent, "CENTER", x - W/2_canvas, y` |

---

### Вспомогательный модуль: px-утилиты (вставь в начало Lua)

```lua
-- PixelHelper — pixel-perfect вёрстка из Figma
local PH = {}

PH.CANVAS_HEIGHT = 768  -- высота холста в Figma (измени если другая!)

function PH.scale()
    return 768 / PH.CANVAS_HEIGHT
end

-- Пересчёт px из Figma → WoW единицы
function PH.px(v)
    return math.floor(v * PH.scale() + 0.5)
end

-- Цвет из HEX (#RRGGBB или #RRGGBBAA)
function PH.color(hex, alpha)
    hex = hex:gsub("#","")
    local r = tonumber(hex:sub(1,2),16)/255
    local g = tonumber(hex:sub(3,4),16)/255
    local b = tonumber(hex:sub(5,6),16)/255
    local a = alpha or (hex:len() == 8 and tonumber(hex:sub(7,8),16)/255) or 1
    return r, g, b, a
end

-- Размер шрифта из Figma → WoW
function PH.font(pt)
    return math.max(8, math.floor(pt * PH.scale() + 0.5))
end

-- Установка якоря с автопересчётом
function PH.point(frame, point, relativeTo, relPoint, x, y)
    frame:SetPoint(point, relativeTo, relPoint, PH.px(x or 0), PH.px(y or 0))
end

-- Размер фрейма из Figma
function PH.size(frame, w, h)
    frame:SetWidth(PH.px(w))
    frame:SetHeight(PH.px(h))
end

-- Экспорт глобально (или оставь локальным)
MyAddon_PH = PH
```

**Использование**:
```lua
local px = MyAddon_PH.px
local color = MyAddon_PH.color
local pxfont = MyAddon_PH.font

MyAddon_PH.size(frame, 400, 250)
MyAddon_PH.point(frame, "TOPLEFT", UIParent, "TOPLEFT", 50, -30)
frame:SetBackdropColor(color("#1A1A2E", 0.95))
fs:SetTextColor(color("#FFD100"))
fs:SetFont("Interface\\AddOns\\MyAddon\\fonts\\MyFont.ttf", pxfont(16), "OUTLINE")
```

---

### Чеклист pixel-perfect переноса

- [ ] Холст Figma = 768px в высоту (или задан `PH.CANVAS_HEIGHT`)
- [ ] Все размеры и отступы пересчитаны через `px()`
- [ ] Цвета конвертированы через `color()`
- [ ] Текстуры нарезаны до 512×512, размеры кратны степени двойки
- [ ] Шрифты `.ttf` подключены из папки аддона (если кириллица или кастомный шрифт)
- [ ] Размеры шрифтов пересчитаны через `font()`
- [ ] Y-отступы от верхних якорей — **отрицательные**
- [ ] Проверено на scale UIParent ≠ 1.0 (изменить в настройках игры для теста)
- [ ] Текстуры без SetAllPoints — точный SetWidth/SetHeight + SetPoint

---

```
MyAddon/
├── MyAddon.toc       ← обязателен, имя файла = имя папки
├── MyAddon.xml       ← верстка фреймов (опционально)
└── MyAddon.lua       ← логика
```

### TOC-файл (3.3.5a)

```toc
## Interface: 30300
## Title: My Addon
## Notes: Описание аддона
## Author: Ilyas
## Version: 1.0
## SavedVariables: MyAddonDB

MyAddon.xml
MyAddon.lua
```

> Файлы загружаются **в порядке перечисления**. XML раньше Lua — фреймы существуют к моменту выполнения скриптов.

---

## XML-верстка

### Минимальный шаблон XML-файла

```xml
<Ui xmlns="http://www.blizzard.com/wow/ui/"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">

  <Script file="MyAddon.lua"/>

  <Frame name="MyAddon_Frame" parent="UIParent" toplevel="true" enableMouse="true" movable="true">
    <Size>
      <AbsDimension x="300" y="200"/>
    </Size>
    <Anchors>
      <Anchor point="CENTER"/>
    </Anchors>
    <Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background"
              edgeFile="Interface\DialogFrame\UI-DialogBox-Border"
              tile="true">
      <BackgroundInsets>
        <AbsInset left="11" right="12" top="12" bottom="11"/>
      </BackgroundInsets>
      <TileSize><AbsValue val="32"/></TileSize>
      <EdgeSize><AbsValue val="32"/></EdgeSize>
    </Backdrop>
    <Layers>
      <Layer level="ARTWORK">
        <FontString name="$parentTitle" inherits="GameFontNormalLarge" text="Заголовок">
          <Anchors>
            <Anchor point="TOP"><Offset><AbsDimension x="0" y="-16"/></Offset></Anchor>
          </Anchors>
        </FontString>
      </Layer>
    </Layers>
    <Frames>
      <Button name="$parentClose" inherits="UIPanelCloseButton">
        <Anchors>
          <Anchor point="TOPRIGHT"><Offset><AbsDimension x="4" y="4"/></Offset></Anchor>
        </Anchors>
      </Button>
    </Frames>
    <Scripts>
      <OnLoad>MyAddon_Frame_OnLoad(self)</OnLoad>
      <OnMouseDown>self:StartMoving()</OnMouseDown>
      <OnMouseUp>self:StopMovingOrSizing()</OnMouseUp>
    </Scripts>
  </Frame>

</Ui>
```

---

## Точки якоря (FRAMEPOINT)

| Точка | Расположение |
|-------|-------------|
| `TOPLEFT` | Верхний левый угол |
| `TOP` | Верхний центр |
| `TOPRIGHT` | Верхний правый угол |
| `LEFT` | Левый центр |
| `CENTER` | Центр |
| `RIGHT` | Правый центр |
| `BOTTOMLEFT` | Нижний левый угол |
| `BOTTOM` | Нижний центр |
| `BOTTOMRIGHT` | Нижний правый угол |

### SetPoint через Lua

```lua
-- frame:SetPoint(point, relativeTo, relativePoint, offsetX, offsetY)
frame:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 10, -10)
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
frame:SetPoint("BOTTOMRIGHT", anotherFrame, "TOPRIGHT", 0, 5)

-- Сброс якорей перед переустановкой
frame:ClearAllPoints()
frame:SetPoint("CENTER", UIParent, "CENTER")
```

> **Правило**: фрейм должен иметь достаточно якорей для однозначного определения позиции **и** размера. Либо Size + один якорь, либо два якоря задающих оба измерения.

---

## Слои и страты

### Draw Layers (внутри фрейма, сверху вниз по видимости)

```
BACKGROUND (0) → BORDER (1) → ARTWORK (2) → OVERLAY (3) → HIGHLIGHT (4)
```

- `HIGHLIGHT` — автоматически показывается при наведении мыши (требует `enableMouse="true"`)
- FontString всегда рисуется **поверх** Texture в том же слое
- Порядок текстур в одном слое **не гарантирован** → разноси по слоям

### Frame Strata (между фреймами)

```
WORLD → BACKGROUND → LOW → MEDIUM → HIGH → DIALOG → FULLSCREEN → FULLSCREEN_DIALOG → TOOLTIP
```

```lua
frame:SetFrameStrata("DIALOG")
frame:SetFrameLevel(5)  -- внутри страты (0–255)
```

---

## Виджеты, события, Blizzard API

> Полный справочник — в `references/widgets-and-api.md`.
> Читай его когда нужны: типы фреймов, Button/EditBox/ScrollFrame/StatusBar, события OnEvent/OnUpdate, SavedVariables, пути к текстурам, типичные ошибки.

---

## Шаблонный аддон «с нуля» (минимальный рабочий пример)

**MyAddon.toc**
```toc
## Interface: 30300
## Title: MyAddon
## SavedVariables: MyAddonDB
MyAddon.lua
```

**MyAddon.lua**
```lua
local ADDON_NAME = "MyAddon"

local f = CreateFrame("Frame", ADDON_NAME.."_Frame", UIParent)
f:SetWidth(250)
f:SetHeight(150)
f:SetPoint("CENTER")
f:SetFrameStrata("DIALOG")
f:SetMovable(true)
f:EnableMouse(true)
f:SetBackdrop({
    bgFile   = "Interface\\DialogFrame\\UI-DialogBox-Background",
    edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
    tile = true, tileSize = 32, edgeSize = 32,
    insets = {left=11, right=12, top=12, bottom=11}
})
f:Hide()

-- Заголовок
local title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
title:SetPoint("TOP", f, "TOP", 0, -12)
title:SetText("My Addon")

-- Закрытие
local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
close:SetPoint("TOPRIGHT", f, "TOPRIGHT", 4, 4)
close:SetScript("OnClick", function() f:Hide() end)

-- Перетаскивание
f:SetScript("OnMouseDown", function(self, btn)
    if btn == "LeftButton" then self:StartMoving() end
end)
f:SetScript("OnMouseUp", function(self) self:StopMovingOrSizing() end)

-- Инициализация
f:RegisterEvent("ADDON_LOADED")
f:SetScript("OnEvent", function(self, event, name)
    if event == "ADDON_LOADED" and name == ADDON_NAME then
        MyAddonDB = MyAddonDB or {}
        print("|cff00ff00"..ADDON_NAME.."|r загружен. /myaddon")
    end
end)

-- Слэш-команда
SLASH_MYADDON1 = "/myaddon"
SlashCmdList["MYADDON"] = function()
    if f:IsShown() then f:Hide() else f:Show() end
end
```
