8 Commits

782 changed files with 35022 additions and 23 deletions
+4
View File
@@ -6,3 +6,7 @@ AWS_DEFAULT_REGION=
AWS_BUCKET=
AWS_ENDPOINT=
AWS_USE_PATH_STYLE_ENDPOINT=
PRODUCTION_REALMLIST=
PTR_REALMLIST=
LOCAL_REALMLIST=
+202
View File
@@ -6,8 +6,11 @@
// boot phase, before GlueXML is loaded.
#include "core/Logger.hpp"
#include "core/Hook.hpp"
#include "core/Mem.hpp"
#include "runtime/LuaBindings.hpp"
#include "runtime/ModuleInstall.hpp"
#include "offsets/engine/Gx.hpp"
#include <windows.h>
@@ -20,6 +23,199 @@ namespace moonwell
{
namespace
{
using GxSetProjectionFn = wxl::offsets::engine::gx::GxSetProjectionFn;
GxSetProjectionFn g_nextSetProjection = nullptr;
// FrameXML's stock SetCreature only accepts a creature entry and waits
// for its client cache record. Encounter Journal already has the exact
// CreatureDisplayInfo ID, so accept it as an optional second argument.
constexpr uintptr_t kSetCreature = 0x00597960;
constexpr uintptr_t kGetCurrentModelFrame = 0x004A81B0;
constexpr uintptr_t kModelFrameTypeToken = 0x00C0E4D4;
constexpr uintptr_t kApplyCreatureCacheRecord = 0x00597700;
using SetCreatureFn = int(__cdecl*)(void* state);
using ApplyCreatureCacheRecordFn = void(__fastcall*)(void* frame, void* edx,
const void* cacheRecord);
SetCreatureFn g_nextSetCreature = nullptr;
alignas(4) std::array<uint32_t, 10> g_encounterJournalCreatureRecord{};
void* GetCurrentModelFrame(void* state, uint32_t typeToken)
{
// FrameScript_GetObject takes the type token on the stack, but the
// 3.3.5 client also expects lua_State in ESI (an internal calling
// convention not expressible with a regular C function pointer).
void* frame = nullptr;
__asm
{
mov esi, state
push typeToken
mov eax, kGetCurrentModelFrame
call eax
add esp, 4
mov frame, eax
}
return frame;
}
int __cdecl SetCreatureDisplayInfoHook(void* state)
{
const int result = g_nextSetCreature ? g_nextSetCreature(state) : 0;
if (!state || !wxl::runtime::lua::IsNumber(state, 3))
return result;
const double requestedDisplayInfo = wxl::runtime::lua::ToNumber(state, 3);
if (requestedDisplayInfo <= 0.0 || requestedDisplayInfo > 4294967295.0)
return result;
const auto displayInfo = static_cast<uint32_t>(requestedDisplayInfo);
__try
{
const uint32_t typeToken = *reinterpret_cast<const uint32_t*>(kModelFrameTypeToken);
if (!typeToken)
return result;
void* frame = GetCurrentModelFrame(state, typeToken);
if (!frame)
return result;
// Both the initial loader and the later character-appearance
// pass read displayInfo at +0x24. Keep this record alive and
// attach it to the frame: character models use it asynchronously
// to resolve CreatureDisplayInfoExtra, baked skin and equipment.
g_encounterJournalCreatureRecord.fill(0);
g_encounterJournalCreatureRecord[9] = displayInfo;
*reinterpret_cast<const void**>(static_cast<uint8_t*>(frame) + 0x378) =
g_encounterJournalCreatureRecord.data();
reinterpret_cast<ApplyCreatureCacheRecordFn>(kApplyCreatureCacheRecord)(
frame, nullptr, g_encounterJournalCreatureRecord.data());
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
WLOG_ERROR("moonwell: SetCreature displayInfo bridge failed for %u", displayInfo);
}
return result;
}
void InstallEncounterJournalModelPreview()
{
if (!wxl::core::hook::Install("MoonWellSetCreatureDisplayInfo", kSetCreature,
&SetCreatureDisplayInfoHook, &g_nextSetCreature))
{
WLOG_ERROR("moonwell: encounter journal model hook installation failed");
return;
}
WLOG_INFO("moonwell: encounter journal displayInfo model bridge installed");
}
struct CameraTransition
{
float zoom = 1.0f;
float verticalOffset = 0.0f;
float startZoom = 1.0f;
float startVerticalOffset = 0.0f;
float targetZoom = 1.0f;
float targetVerticalOffset = 0.0f;
DWORD startedAt = 0;
DWORD duration = 0;
} g_characterCreateCamera;
void UpdateCharacterCreateCamera(DWORD now)
{
auto& camera = g_characterCreateCamera;
if (!camera.duration)
{
camera.zoom = camera.targetZoom;
camera.verticalOffset = camera.targetVerticalOffset;
return;
}
const DWORD elapsed = now - camera.startedAt;
float t = static_cast<float>(elapsed) / static_cast<float>(camera.duration);
if (t >= 1.0f)
{
camera.zoom = camera.targetZoom;
camera.verticalOffset = camera.targetVerticalOffset;
camera.duration = 0;
return;
}
t = t * t * (3.0f - 2.0f * t);
camera.zoom = camera.startZoom + (camera.targetZoom - camera.startZoom) * t;
camera.verticalOffset = camera.startVerticalOffset
+ (camera.targetVerticalOffset - camera.startVerticalOffset) * t;
}
int __cdecl SetCharacterCreateCamera(void* state)
{
const bool enabled = state && wxl::runtime::lua::IsNumber(state, 1)
&& wxl::runtime::lua::ToNumber(state, 1) != 0.0;
const float faceZoom = state && wxl::runtime::lua::IsNumber(state, 2)
? static_cast<float>(wxl::runtime::lua::ToNumber(state, 2)) : 2.0f;
const float faceVerticalOffset = state && wxl::runtime::lua::IsNumber(state, 3)
? static_cast<float>(wxl::runtime::lua::ToNumber(state, 3)) : -0.65f;
double requestedDuration = state && wxl::runtime::lua::IsNumber(state, 4)
? wxl::runtime::lua::ToNumber(state, 4) : 500.0;
if (requestedDuration < 0.0) requestedDuration = 0.0;
if (requestedDuration > 2000.0) requestedDuration = 2000.0;
const DWORD now = GetTickCount();
UpdateCharacterCreateCamera(now);
auto& camera = g_characterCreateCamera;
camera.startZoom = camera.zoom;
camera.startVerticalOffset = camera.verticalOffset;
camera.targetZoom = enabled && faceZoom > 1.0f ? faceZoom : 1.0f;
camera.targetVerticalOffset = enabled ? faceVerticalOffset : 0.0f;
camera.startedAt = now;
camera.duration = static_cast<DWORD>(requestedDuration);
UpdateCharacterCreateCamera(now);
return 0;
}
void __fastcall CharacterCreateProjectionHook(void* self, void* edx, const void* projection)
{
if (!g_nextSetProjection)
return;
UpdateCharacterCreateCamera(GetTickCount());
const auto& camera = g_characterCreateCamera;
if (projection && (camera.zoom != 1.0f || camera.verticalOffset != 0.0f))
{
const float* source = static_cast<const float*>(projection);
if (source[11] > 0.5f)
{
float adjusted[16];
std::memcpy(adjusted, source, sizeof(adjusted));
adjusted[0] *= camera.zoom;
adjusted[5] *= camera.zoom;
adjusted[9] += camera.verticalOffset;
g_nextSetProjection(self, edx, adjusted);
return;
}
}
g_nextSetProjection(self, edx, projection);
}
void InstallCharacterCreateCamera()
{
namespace gx = wxl::offsets::engine::gx;
void** vtable = reinterpret_cast<void**>(gx::kGxDeviceVTable);
void** slot = &vtable[gx::kGxSetProjectionSlot];
if (*slot == reinterpret_cast<void*>(&CharacterCreateProjectionHook))
return;
g_nextSetProjection = reinterpret_cast<GxSetProjectionFn>(*slot);
void* replacement = reinterpret_cast<void*>(&CharacterCreateProjectionHook);
if (!g_nextSetProjection || !wxl::core::mem::Patch(slot, &replacement, sizeof(replacement)))
{
g_nextSetProjection = nullptr;
WLOG_ERROR("moonwell: character-create projection hook installation failed");
return;
}
WLOG_INFO("moonwell: smooth character-create camera installed");
}
struct Patch
{
const char* name;
@@ -196,7 +392,13 @@ namespace moonwell
{
Registration()
{
wxl::runtime::lua::RegisterFunction(
"MoonWellSetCharacterCreateCamera", &SetCharacterCreateCamera);
wxl::runtime::modules::RegisterBoot("moonwell", &InstallBoot);
wxl::runtime::modules::Register(
"moonwell-character-create-camera", &InstallCharacterCreateCamera);
wxl::runtime::modules::Register(
"moonwell-encounter-journal-models", &InstallEncounterJournalModelPreview);
wxl::runtime::modules::Register("moonwell-log-flush", &InstallRuntimeLogFlush);
}
} g_registration;
+21
View File
@@ -1,3 +1,9 @@
param(
[Parameter(Mandatory=$false)]
[ValidateSet("production", "ptr", "local")]
[string]$Env = "local"
)
# Stop on errors
$ErrorActionPreference = "Stop"
@@ -81,6 +87,21 @@ if ($LASTEXITCODE -ge 8) {
Write-Host "MPQ archives deployed to $WOW_HOME"
# --- Write realmlist.wtf based on selected environment
$realmlist = switch ($Env) {
"production" { $env:PRODUCTION_REALMLIST }
"ptr" { $env:PTR_REALMLIST }
"local" { $env:LOCAL_REALMLIST }
}
if ($realmlist) {
$realmlistPath = Join-Path $WOW_HOME "Data\ruRU\realmlist.wtf"
Set-Content -Path $realmlistPath -Value "set realmlist $realmlist" -Encoding ASCII
Write-Host "Realmlist ($Env): $realmlist"
} else {
Write-Warning "REALMLIST for '$Env' is not set in .env -- skipping realmlist.wtf"
}
# --- Run WoW reload script
Write-Host "Launching WoW..."
cmd /c $RELOAD_SCRIPT
@@ -676,6 +676,44 @@ function CharacterCreate_UpdateGameModeVisibility()
end
end
-- The stock creation scene frames each race at a different height. Projection
-- zoom therefore needs a per-race vertical target to keep the face in frame.
-- Values are indexed by GetSelectedSex(): 2 male, 3 female.
local CHARACTER_CREATE_CAMERA_PROFILES = {
HUMAN = { [2] = -0.28, [3] = -0.20 },
ORC = { [2] = -0.50, [3] = -0.30 },
DWARF = { [2] = 0.30, [3] = 0.38 },
NIGHTELF = { [2] = -0.65, [3] = -0.65 },
SCOURGE = { [2] = 0.18, [3] = 0.27 },
TAUREN = { [2] = -0.78, [3] = -0.62 },
GNOME = { [2] = 0.64, [3] = 0.72 },
TROLL = { [2] = -0.70, [3] = -0.55 },
BLOODELF = { [2] = -0.30, [3] = -0.22 },
DRAENEI = { [2] = -0.62, [3] = -0.55 },
};
local function CharacterCreate_GetCameraProfile()
local _, raceFile = GetNameForRace();
local raceProfile = raceFile and CHARACTER_CREATE_CAMERA_PROFILES[strupper(raceFile)];
local sex = GetSelectedSex();
return 2.0, raceProfile and raceProfile[sex] or -0.20;
end
local function CharacterCreate_UpdateCamera(immediate)
local showFace = CharacterCreate.personalizationMode and not CharacterCreate.gameModeSelectionMode;
local mode = showFace and 1 or 0;
if CharacterCreate.cameraMode == mode and not immediate then
return;
end
CharacterCreate.cameraMode = mode;
CharacterCreate:SetCamera(0);
if MoonWellSetCharacterCreateCamera then
local zoom, verticalOffset = CharacterCreate_GetCameraProfile();
MoonWellSetCharacterCreateCamera(mode, zoom, verticalOffset, immediate and 0 or 500);
end
end
local function CharacterCreate_GetEnteredName()
local text = "";
@@ -701,6 +739,8 @@ local function CharacterCreate_UpdatePersonalizationStep()
CharacterCreate.gameModeSelectionMode = false;
end
CharacterCreate_UpdateCamera();
local showGameModes = CharacterCreate.gameModeSelectionMode and not PAID_SERVICE_TYPE;
if showGameModes then
@@ -943,6 +983,7 @@ function CharacterCreate_TogglePersonalization()
CharCreatePersonalizeButton:Show();
CharCreateOkayButton:Hide();
CharacterCreate_UpdateGameModeVisibility();
CharacterCreate_UpdateCamera();
for i=1, NUM_CHAR_CUSTOMIZATIONS do
_G["CharacterCustomizationButtonFrame"..i]:Hide();
@@ -1001,7 +1042,7 @@ function CharacterCreate_OnShow()
CharacterChangeFixup();
CharacterCreate_CreateGenderButtonTextures();
CharacterCreate_UpdateButtonCheckedStates();
CharacterCreate_ResetState();
CharacterCreate_ResetState(true);
hideScheduled = true
HideNameEditFrame:Show()
@@ -1013,7 +1054,7 @@ end
function CharacterCreate_OnHide()
PAID_SERVICE_CHARACTER_ID = nil;
PAID_SERVICE_TYPE = nil;
CharacterCreate_ResetState();
CharacterCreate_ResetState(true);
for button, tooltip in pairs(raceTooltips) do
if tooltip then tooltip:Hide() end
@@ -1518,6 +1559,7 @@ end
function CharacterCreate_UpdateModel(self)
UpdateCustomizationScene();
CharacterCreate_UpdateCamera();
self:AdvanceTime();
end
@@ -1644,9 +1686,10 @@ function SetCharacterGender(sex)
CharacterCreate_UpdateButtonCheckedStates();
end
function CharacterCreate_ResetState()
function CharacterCreate_ResetState(immediate)
CharacterCreate.personalizationMode = false;
CharacterCreate.gameModeSelectionMode = false;
CharacterCreate_UpdateCamera(immediate);
CharacterCreateRaceButtonsContainer:Show();
CharacterCreateClassButtonsContainer:Show();
CharacterCreateGenderButtonsContainer:Show();
@@ -69,25 +69,26 @@ local MainMenuBarBackpackButton = _G.MainMenuBarBackpackButton;
local HelpMicroButton = _G.HelpMicroButton;
local KeyRingButton = _G.KeyRingButton;
-- Button collections (dynamically set based on server)
local MICRO_BUTTONS
-- Button collections (rebuilt after addons create optional custom buttons).
local function BuildMicroButtonList()
if isAscensionServer then
return {
_G.CharacterMicroButton,
_G.SpellbookMicroButton,
_G.TalentMicroButton,
_G.AchievementMicroButton,
_G.QuestLogMicroButton,
_G.SocialsMicroButton,
_G.LFDMicroButton,
_G.EncounterJournalMicroButton,
_G.PathToAscensionMicroButton,
_G.ChallengesMicroButton,
_G.MainMenuMicroButton,
_G.HelpMicroButton
}
end
if isAscensionServer then
MICRO_BUTTONS = {
_G.CharacterMicroButton,
_G.SpellbookMicroButton,
_G.TalentMicroButton,
_G.AchievementMicroButton,
_G.QuestLogMicroButton,
_G.SocialsMicroButton,
_G.LFDMicroButton,
_G.PathToAscensionMicroButton,
_G.ChallengesMicroButton,
_G.MainMenuMicroButton,
_G.HelpMicroButton
}
else
MICRO_BUTTONS = {
return {
_G.CharacterMicroButton,
_G.SpellbookMicroButton,
_G.TalentMicroButton,
@@ -95,6 +96,7 @@ else
_G.QuestLogMicroButton,
_G.SocialsMicroButton,
_G.LFDMicroButton,
_G.EncounterJournalMicroButton,
_G.CollectionsMicroButton,
_G.PVPMicroButton,
_G.MainMenuMicroButton,
@@ -102,6 +104,7 @@ else
}
end
local MICRO_BUTTONS = BuildMicroButtonList()
local bagslots = {_G.CharacterBag0Slot, _G.CharacterBag1Slot, _G.CharacterBag2Slot, _G.CharacterBag3Slot};
@@ -783,6 +786,8 @@ local function ApplyMicromenuSystem()
return
end
MICRO_BUTTONS = BuildMicroButtonList()
-- Store original states first
StoreOriginalMicroButtonStates()
@@ -911,6 +916,8 @@ local function ApplyMicromenuSystem()
or (_G.PVPParentFrame and _G.PVPParentFrame:IsVisible() and true or false)
or (_G.BattlefieldFrame and _G.BattlefieldFrame:IsVisible() and true or false)
or (_G.HonorFrame and _G.HonorFrame:IsVisible() and true or false)
elseif buttonName == "EncounterJournal" then
return (_G.EncounterJournal and _G.EncounterJournal:IsVisible() and true or false)
end
return pressed
@@ -919,6 +926,152 @@ local function ApplyMicromenuSystem()
-- ============================================================================
-- SECTION 5: SPECIALIZED BUTTON SETUP
-- ============================================================================
local function SetupEncounterJournalButton(button)
local iconTexture = 'Interface\\EncounterJournal\\UI-EJ-PortraitIcon'
local backgroundTexture = 'Interface\\AddOns\\DragonUI\\Textures\\Micromenu\\uimicromenu2x'
local buttonWidth, buttonHeight = button:GetSize()
local dx, dy = -1, 1
local offX, offY = button:GetPushedTextOffset()
if not button.DragonUIEncounterJournalIcon then
button.DragonUIEncounterJournalIcon = button:CreateTexture(nil, 'ARTWORK')
end
local icon = button.DragonUIEncounterJournalIcon
icon:SetTexture(iconTexture)
icon:SetTexCoord(0, 1, 0, 1)
icon:ClearAllPoints()
icon:SetPoint('CENTER', button, 'CENTER', 0, 0)
icon:SetSize(buttonWidth - 8, buttonWidth - 8)
icon:SetAlpha(1)
icon:Show()
local highlightTexture = button:GetHighlightTexture()
if highlightTexture then
highlightTexture:SetTexture(iconTexture)
highlightTexture:SetTexCoord(0, 1, 0, 1)
highlightTexture:ClearAllPoints()
highlightTexture:SetAllPoints(icon)
highlightTexture:SetBlendMode('ADD')
highlightTexture:SetAlpha(0.55)
end
if not button.DragonUIBackground then
local bg = button:CreateTexture(nil, 'BACKGROUND')
bg:SetTexture(backgroundTexture)
bg:SetSize(buttonWidth, buttonHeight + 1)
bg:SetTexCoord(0.0654297, 0.12793, 0.330078, 0.490234)
bg:SetPoint('CENTER', dx, dy)
button.DragonUIBackground = bg
local bgPushed = button:CreateTexture(nil, 'BACKGROUND')
bgPushed:SetTexture(backgroundTexture)
bgPushed:SetSize(buttonWidth, buttonHeight + 1)
bgPushed:SetTexCoord(0.0654297, 0.12793, 0.494141, 0.654297)
bgPushed:SetPoint('CENTER', dx + offX, dy + offY)
bgPushed:Hide()
button.DragonUIBackgroundPushed = bgPushed
else
button.DragonUIBackground:SetTexture(backgroundTexture)
button.DragonUIBackground:SetTexCoord(0.0654297, 0.12793, 0.330078, 0.490234)
button.DragonUIBackground:ClearAllPoints()
button.DragonUIBackground:SetPoint('CENTER', dx, dy)
button.DragonUIBackground:SetSize(buttonWidth, buttonHeight + 1)
if button.DragonUIBackgroundPushed then
button.DragonUIBackgroundPushed:SetTexture(backgroundTexture)
button.DragonUIBackgroundPushed:SetTexCoord(0.0654297, 0.12793, 0.494141, 0.654297)
button.DragonUIBackgroundPushed:ClearAllPoints()
button.DragonUIBackgroundPushed:SetPoint('CENTER', dx + offX, dy + offY)
button.DragonUIBackgroundPushed:SetSize(buttonWidth, buttonHeight + 1)
end
end
button.dragonUIState = button.dragonUIState or {}
button.dragonUIState.pushed = IsSpecialMicroButtonActive(button, "EncounterJournal")
button.dragonUILastState = button.dragonUIState.pushed
button.dragonUITimer = button.dragonUITimer or 0
button.HandleDragonUIState = function()
local state = button.dragonUIState
local hlTex = button:GetHighlightTexture()
if state and state.pushed then
if icon then
icon:ClearAllPoints()
icon:SetPoint('CENTER', button, 'CENTER', offX, offY)
icon:SetAlpha(0.7)
end
if button.DragonUIBackground then
button.DragonUIBackground:Hide()
end
if button.DragonUIBackgroundPushed then
button.DragonUIBackgroundPushed:Show()
end
if hlTex then
hlTex:ClearAllPoints()
hlTex:SetPoint('TOPLEFT', icon, 'TOPLEFT', 0, 0)
hlTex:SetPoint('BOTTOMRIGHT', icon, 'BOTTOMRIGHT', 0, 0)
end
else
if icon then
icon:ClearAllPoints()
icon:SetPoint('CENTER', button, 'CENTER', 0, 0)
icon:SetAlpha(1)
end
if button.DragonUIBackground then
button.DragonUIBackground:Show()
end
if button.DragonUIBackgroundPushed then
button.DragonUIBackgroundPushed:Hide()
end
if hlTex then
hlTex:ClearAllPoints()
hlTex:SetAllPoints(icon)
end
end
end
button:SetScript('OnUpdate', function(self, elapsed)
self.dragonUITimer = (self.dragonUITimer or 0) + elapsed
if self.dragonUITimer >= 0.1 then
self.dragonUITimer = 0
local currentState = IsSpecialMicroButtonActive(self, "EncounterJournal")
if currentState ~= self.dragonUILastState then
self.dragonUILastState = currentState
if self.dragonUIState then
self.dragonUIState.pushed = currentState
end
if self.HandleDragonUIState then
self.HandleDragonUIState()
end
end
end
end)
if not button.DragonUIStateHooks then
button:HookScript('OnMouseDown', function(self)
if self.dragonUIState then
self.dragonUIState.pushed = true
end
if self.HandleDragonUIState then
self.HandleDragonUIState()
end
end)
button:HookScript('OnMouseUp', function(self)
local currentState = IsSpecialMicroButtonActive(self, "EncounterJournal")
if self.dragonUIState then
self.dragonUIState.pushed = currentState
end
if self.HandleDragonUIState then
self.HandleDragonUIState()
end
end)
button.DragonUIStateHooks = true
end
button.HandleDragonUIState()
end
local function SetupPVPButton(button)
-- Mirror the Character button pattern:
-- Instead of fighting WoW's internal NormalTexture alpha management,
@@ -1582,6 +1735,8 @@ local function ApplyMicromenuSystem()
end
local function setupMicroButtons(xOffset)
MICRO_BUTTONS = BuildMicroButtonList()
local buttonxOffset = 0
local useGrayscale = addon.db.profile.micromenu.grayscale_icons
@@ -1705,9 +1860,11 @@ local function ApplyMicromenuSystem()
local isCharacterButton = (buttonName == "Character")
local isPVPButton = (buttonName == "PVP")
local isEncounterJournalButton = (buttonName == "EncounterJournal")
local upCoords = not isCharacterButton and not isPVPButton and GetColoredTextureCoords(name, "Up") or nil
local shouldUseGrayscale = useGrayscale or (not isPVPButton and not upCoords and not isCharacterButton)
local upCoords = not isCharacterButton and not isPVPButton and not isEncounterJournalButton and GetColoredTextureCoords(name, "Up") or nil
local shouldUseGrayscale = (useGrayscale and not isEncounterJournalButton) or
(not isPVPButton and not isEncounterJournalButton and not upCoords and not isCharacterButton)
if shouldUseGrayscale then
-- Grayscale icons
@@ -1741,6 +1898,8 @@ local function ApplyMicromenuSystem()
end
elseif isPVPButton then
SetupPVPButton(button)
elseif isEncounterJournalButton then
SetupEncounterJournalButton(button)
elseif isCharacterButton then
SetupCharacterButton(button)
else
@@ -2245,6 +2404,8 @@ end
return
end
MICRO_BUTTONS = BuildMicroButtonList()
local useGrayscale = addon.db.profile.micromenu.grayscale_icons
local configMode = useGrayscale and "grayscale" or "normal"
local config = addon.db.profile.micromenu[configMode]
@@ -0,0 +1,605 @@
local SHOW_RECOMMENDATIONS = true
local DENSITY = "regular"
local ACCENT = { r = 1.0, g = 0.84, b = 0.42 }
local ROMAN = { "I","II","III","IV","V","VI","VII","VIII","IX","X" }
local T = "Interface\\AddOns\\MoonWellClient\\EncounterJournal\\Textures\\PlayerGuide\\"
local BIOME_TEX = {
fire = T .. "Biome-Fire",
arcane = T .. "Biome-Arcane",
storm = T .. "Biome-Storm",
shadow = T .. "Biome-Shadow",
nature = T .. "Biome-Nature",
holy = T .. "Biome-Holy",
}
local BADGE_TEX = {
active = T .. "StageBadge-Active",
done = T .. "StageBadge-Done",
locked = T .. "StageBadge-Locked",
}
local TYPE_ICON = {
level = T .. "TypeIcon-Level",
quest = T .. "TypeIcon-Quest",
quest_chain = T .. "TypeIcon-QuestChain",
dungeon = T .. "TypeIcon-Dungeon",
raid = T .. "TypeIcon-Raid",
boss = T .. "TypeIcon-Boss",
achievement = T .. "TypeIcon-Achievement",
reputation = T .. "TypeIcon-Reputation",
profession = T .. "TypeIcon-Profession",
item = T .. "TypeIcon-Item",
currency = T .. "TypeIcon-Currency",
custom = T .. "TypeIcon-Custom",
done = T .. "TypeIcon-Done",
locked = T .. "TypeIcon-Locked",
}
local function showOrHide(frame, show)
if not frame then return end
if show then frame:Show() else frame:Hide() end
end
local function getScrollBar(scrollFrame)
if not scrollFrame then return nil end
return scrollFrame.ScrollBar or _G[scrollFrame:GetName() .. "ScrollBar"]
end
local function updateScrollBar(scrollFrame, contentHeight, contentWidth)
if not scrollFrame then return end
local viewHeight = scrollFrame:GetHeight() or 0
local viewWidth = scrollFrame:GetWidth() or 0
local scrollBar = getScrollBar(scrollFrame)
if scrollFrame.ScrollChild and viewWidth > 0 then
scrollFrame.ScrollChild:SetWidth(math.max(1, contentWidth or viewWidth))
end
if scrollBar then
if contentHeight and viewHeight > 0 and contentHeight > viewHeight + 2 then
scrollBar:Show()
else
scrollBar:Hide()
end
end
end
local function scrollFrameOnMouseWheel(scrollFrame, delta)
if not scrollFrame then return end
if ScrollFrameTemplate_OnMouseWheel then
ScrollFrameTemplate_OnMouseWheel(scrollFrame, delta)
return
end
local scrollBar = getScrollBar(scrollFrame)
if scrollBar then
local minValue, maxValue = scrollBar:GetMinMaxValues()
local step = scrollBar:GetValueStep() or 20
scrollBar:SetValue(math.min(maxValue, math.max(minValue, scrollBar:GetValue() - delta * step)))
end
end
local function hideScrollBar(scrollFrame)
local scrollBar = getScrollBar(scrollFrame)
if scrollBar then scrollBar:Hide() end
end
local function getBodyChild()
local body = PlayerGuideFrame and PlayerGuideFrame.BodyScroll
return body and body.ScrollChild
end
local resolveEncounterJournalInstanceID
local function getRecommendationArtwork(data)
if type(data) ~= "table" then return nil end
local artwork = data.artwork or data.cover or data.coverImage or data.buttonImage or data.bgImage or data.icon
if artwork and artwork ~= "" then return artwork end
if not resolveEncounterJournalInstanceID or not EJ_GetInstanceInfo then return nil end
local instanceID = resolveEncounterJournalInstanceID(data)
if not instanceID then return nil end
local ok, _, _, bgImage, buttonImage, loreImage, buttonSmallImage = pcall(EJ_GetInstanceInfo, instanceID)
if ok then
return buttonImage or bgImage or loreImage or buttonSmallImage
end
end
-- ─────────────────────────────────── Цепочка этапов ──
local stageBadges = {}
local function buildStageChain(stages)
if not PlayerGuideFrame then return end
local container = PlayerGuideFrame.StageChain
for i = (#stages + 1), #stageBadges do stageBadges[i]:Hide() end
if #stages == 0 then return end
local width = container:GetWidth()
if width == 0 then width = 600 end
local slot = width / #stages
for i, s in ipairs(stages) do
local badge = stageBadges[i]
if not badge then
badge = CreateFrame("Frame", "PlayerGuideStageBadge"..i, container, "MW_PG_StageBadgeTemplate")
stageBadges[i] = badge
end
badge:ClearAllPoints()
badge:SetPoint("LEFT", container, "LEFT", (i - 0.5) * slot - 19, 0)
badge:Show()
badge.Roman:SetText(ROMAN[s.id] or tostring(s.id))
badge.Label:SetText(s.short or s.name or "")
local status = s.status or "locked"
badge.BadgeBG:SetTexture(BADGE_TEX[status] or BADGE_TEX.locked)
if status == "active" then
badge.Roman:SetTextColor(1.0, 0.91, 0.63)
badge.Label:SetTextColor(1.0, 0.84, 0.42)
elseif status == "done" then
badge.Roman:SetText("v")
badge.Roman:SetTextColor(0.79, 0.65, 0.38)
badge.Label:SetTextColor(0.79, 0.65, 0.38)
else
badge.Roman:SetTextColor(0.40, 0.34, 0.22)
badge.Label:SetTextColor(0.40, 0.34, 0.22)
end
end
end
-- ─────────────────────────────────── Карточки целей ──
local objectiveCards = {}
local CARD_HEIGHT = { compact = 54, regular = 70, cozy = 82 }
local CARD_GAP = { compact = 6, regular = 8, cozy = 10 }
local function renderObjectives()
local section = PlayerGuideFrame.ObjectivesSection
local parent = section.ScrollFrame.ScrollChild
local n = C_PlayerGuide.GetNumObjectives()
local width = 0
if PlayerGuideFrame.BodyScroll then
width = PlayerGuideFrame.BodyScroll:GetWidth()
end
if width == 0 then width = section:GetWidth() end
if width == 0 then width = section.ScrollFrame:GetWidth() end
if width == 0 then width = 860 end
-- Body child right inset 8, section right inset 0, scrollframe left/right insets 6/8.
-- The card then keeps 8px equal visual padding on both sides of the objective row.
parent:SetWidth(math.max(1, width - 30))
for i = (n + 1), #objectiveCards do objectiveCards[i]:Hide() end
local h = CARD_HEIGHT[DENSITY] or 44
local gap = CARD_GAP[DENSITY] or 4
local done = 0
for i = 1, n do
local o = C_PlayerGuide.GetObjectiveInfo(i)
if not o then break end
local card = objectiveCards[i]
if not card then
card = CreateFrame("Frame", "PlayerGuideObjective"..i, parent, "MW_PG_ObjectiveCardTemplate")
objectiveCards[i] = card
end
card:ClearAllPoints()
card:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, -(i - 1) * (h + gap))
card:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, -(i - 1) * (h + gap))
card:SetHeight(h)
card.objectiveData = o
card:Show()
if card.SetBackdropColor then
card:SetBackdropColor(0, 0, 0, 0.72)
card:SetBackdropBorderColor(0.64, 0.45, 0.15, 0.95)
end
card.TypeLabel:SetText(_G["MW_PG_TYPE_" .. string.upper(o.type or "custom")] or string.upper(o.type or ""))
card.Title:SetText(o.title or "")
card.Subtitle:SetText(o.subtitle or o.description or "")
card.ProgressBar:SetMinMaxValues(0, o.required or 1)
card.ProgressBar:SetValue(o.progress or 0)
card.Count:SetText(string.format("%d / %d", o.progress or 0, o.required or 1))
if o.completed then
done = done + 1
card.Icon:SetTexture(TYPE_ICON.done)
card.Icon:SetTexCoord(0, 1, 0, 1)
card.Title:SetTextColor(0.62, 0.84, 0.47)
card.TypeLabel:SetTextColor(0.49, 0.65, 0.35)
card.Count:SetTextColor(0.62, 0.84, 0.47)
card.ProgressBar:SetStatusBarColor(0.55, 0.85, 0.40)
card.IconBorder:SetVertexColor(0.40, 0.65, 0.30, 0.7)
if card.SetBackdropBorderColor then
card:SetBackdropBorderColor(0.26, 0.52, 0.18, 0.9)
end
else
card.Icon:SetTexture(TYPE_ICON[o.type] or TYPE_ICON.custom)
card.Icon:SetTexCoord(0, 1, 0, 1)
card.Title:SetTextColor(1.0, 0.91, 0.63)
card.TypeLabel:SetTextColor(0.63, 0.46, 0.19)
card.Count:SetTextColor(0.79, 0.65, 0.38)
card.ProgressBar:SetStatusBarColor(ACCENT.r, ACCENT.g, ACCENT.b)
card.IconBorder:SetVertexColor(ACCENT.r, ACCENT.g, ACCENT.b, 0.55)
end
if o.required == 1 and (o.progress or 0) == 0 then
card.ProgressBar:Hide()
else
card.ProgressBar:Show()
end
end
parent:SetHeight(math.max(1, n * h + math.max(0, n - 1) * gap))
section:SetHeight(48 + parent:GetHeight())
hideScrollBar(section.ScrollFrame)
section.Count:SetText(string.format(" %d / %d", done, n))
end
-- ─────────────────────────────── Карточки рекомендаций ──
local recCards = {}
local function renderRecommendations()
local section = PlayerGuideFrame.RecommendationsSection
if not SHOW_RECOMMENDATIONS then section:Hide(); return end
local parent = section.ScrollFrame.ScrollChild
local n = C_PlayerGuide.GetNumRecommendations()
if n == 0 then section:Hide(); return end
section:Show()
for i = (n + 1), #recCards do recCards[i]:Hide() end
local cardW = 176
local gap = 16
for i = 1, n do
local r = C_PlayerGuide.GetRecommendationInfo(i)
if not r then break end
local card = recCards[i]
if not card then
card = CreateFrame("Button", "PlayerGuideRec"..i, parent, "MW_PG_RecCardTemplate")
recCards[i] = card
end
card:ClearAllPoints()
card:SetPoint("TOPLEFT", parent, "TOPLEFT", (i - 1) * (cardW + gap), 0)
card.recData = r
card:Show()
if card.SetBackdropColor then
card:SetBackdropColor(0, 0, 0, 0.88)
card:SetBackdropBorderColor(0.64, 0.45, 0.15, 0.95)
end
local artwork = getRecommendationArtwork(r)
card.Artwork:SetTexCoord(0, 0.68359375, 0, 0.7421875)
card.Artwork:SetVertexColor(1, 1, 1, 1)
if not artwork or not card.Artwork:SetTexture(artwork) then
card.Artwork:SetTexture(BIOME_TEX[r.biome] or BIOME_TEX.fire)
end
card.Name:SetText(r.name or r.title or "")
card.Loc:SetText(r.loc or r.description or "")
card.Level:SetText(r.level or "")
end
parent:SetWidth(math.max(1, n * cardW + math.max(0, n - 1) * gap))
parent:SetHeight(116)
section:SetHeight(162)
hideScrollBar(section.ScrollFrame)
section.Count:SetText(" " .. n)
end
local function updateBodyContent()
local F = PlayerGuideFrame
local child = getBodyChild()
if not F or not child then return end
local width = F.BodyScroll:GetWidth()
if width == 0 then width = 860 end
child:SetWidth(math.max(1, width - 8))
local y = 0
F.ObjectivesSection:ClearAllPoints()
F.ObjectivesSection:SetPoint("TOPLEFT", child, "TOPLEFT", 0, y)
F.ObjectivesSection:SetPoint("TOPRIGHT", child, "TOPRIGHT", 0, y)
y = y - F.ObjectivesSection:GetHeight() - 16
if F.RecommendationsSection:IsShown() then
F.RecommendationsSection:ClearAllPoints()
F.RecommendationsSection:SetPoint("TOPLEFT", child, "TOPLEFT", 0, y)
F.RecommendationsSection:SetPoint("TOPRIGHT", child, "TOPRIGHT", 0, y)
y = y - F.RecommendationsSection:GetHeight()
end
local contentHeight = math.max(1, -y + 8)
child:SetHeight(contentHeight)
updateScrollBar(F.BodyScroll, contentHeight, child:GetWidth())
end
-- ─────────────────────────────────────── Layout ──
local function applyLayout(self, hasChain)
local chain = self.StageChain
local hdr = self.Header
local body = self.BodyScroll
chain:ClearAllPoints()
chain:SetPoint("TOPLEFT", self, "TOPLEFT", 28, -8)
chain:SetPoint("TOPRIGHT", self, "TOPRIGHT", -28, -8)
hdr:ClearAllPoints()
if hasChain then
hdr:SetPoint("TOPLEFT", chain, "BOTTOMLEFT", 0, -6)
hdr:SetPoint("TOPRIGHT", chain, "BOTTOMRIGHT", 0, -6)
else
hdr:SetPoint("TOPLEFT", self, "TOPLEFT", 30, -68)
hdr:SetPoint("TOPRIGHT", self, "TOPRIGHT", -30, -68)
end
body:ClearAllPoints()
body:SetPoint("TOPLEFT", hdr, "BOTTOMLEFT", -4, -8)
body:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", -30, 16)
end
-- ─────────────────────────────────────── Show/hide ──
local function showState(state)
local F = PlayerGuideFrame
local hasData = (state == "data")
-- StageChain visibility controlled in refresh() based on #stages
showOrHide(F.Header, hasData)
showOrHide(F.BodyScroll, hasData)
showOrHide(F.ObjectivesSection, hasData)
showOrHide(F.RecommendationsSection, hasData and SHOW_RECOMMENDATIONS)
showOrHide(F.EmptyState, state == "empty")
showOrHide(F.LoadingState, state == "loading")
if not hasData then showOrHide(F.StageChain, false) end
end
-- ─────────────────────────────────────── Render ──
local function refresh()
if not PlayerGuideFrame then return end
local state = C_PlayerGuide.GetState()
showState(state)
if state ~= "data" then return end
local id, name, desc, progress = C_PlayerGuide.GetStageInfo()
local stages = C_PlayerGuide.GetStages() or {}
local F = PlayerGuideFrame
if #stages > 0 then
F.Header.Pretitle:SetText(string.format(
MW_PLAYER_GUIDE_STAGE_OF or "ЭТАП %d ИЗ %d", id or 0, #stages))
else
F.Header.Pretitle:SetText(string.format("ЭТАП %d", id or 0))
end
F.Header.Title:SetText(name or "")
F.Header.Description:SetText(desc or "")
F.Header.ProgressLabel:SetText(MW_PLAYER_GUIDE_STAGE_PROGRESS or "ПРОГРЕСС ЭТАПА")
F.Header.ProgressPercent:SetText((progress or 0) .. "%")
F.Header.ProgressBar:SetMinMaxValues(0, 100)
F.Header.ProgressBar:SetValue(progress or 0)
local hasChain = #stages > 0
showOrHide(F.StageChain, hasChain)
applyLayout(F, hasChain)
buildStageChain(stages)
updateBodyContent()
renderObjectives()
renderRecommendations()
updateBodyContent()
end
-- ─────────────────────────────────── XML callbacks ──
function MW_PlayerGuide_OnLoad(self)
EncounterJournal.playerGuideFrame = self
self:EnableMouse(true)
self:EnableMouseWheel(true)
local bodyChild = self.BodyScroll and self.BodyScroll.ScrollChild
if bodyChild then
self.ObjectivesSection:SetParent(bodyChild)
self.RecommendationsSection:SetParent(bodyChild)
end
applyLayout(self, false)
local objScrollBar = getScrollBar(self.ObjectivesSection.ScrollFrame)
if objScrollBar then objScrollBar:Hide() end
local recScrollBar = getScrollBar(self.RecommendationsSection.ScrollFrame)
if recScrollBar then recScrollBar:Hide() end
hideScrollBar(self.BodyScroll)
C_PlayerGuide.RegisterListener(function() refresh() end)
end
function MW_PlayerGuide_OnMouseWheel(self, delta)
if self.BodyScroll and self.BodyScroll:IsShown() then
scrollFrameOnMouseWheel(self.BodyScroll, delta)
end
end
function MW_PlayerGuide_OnShow()
if not C_PlayerGuide.HasData() then
C_PlayerGuide.SetLoading()
C_PlayerGuide.RequestRefresh()
end
refresh()
end
function MW_PlayerGuide_RefreshButton_OnClick()
C_PlayerGuide.SetLoading()
C_PlayerGuide.RequestRefresh()
end
local function validEncounterJournalInstanceID(value)
local instanceID = tonumber(value)
if not instanceID or instanceID <= 0 or not EJ_GetInstanceInfo then return nil end
local ok, instanceName = pcall(EJ_GetInstanceInfo, instanceID)
if ok and instanceName then return instanceID end
end
local function mapIDToEncounterJournalInstanceID(value)
local mapID = tonumber(value)
if not mapID or mapID <= 0 then return nil end
if not C_EncounterJournal or not C_EncounterJournal.GetInstanceIDByMapID then return nil end
local ok, instanceID = pcall(C_EncounterJournal.GetInstanceIDByMapID, mapID)
if ok then
return validEncounterJournalInstanceID(instanceID)
end
end
function resolveEncounterJournalInstanceID(data)
if type(data) == "number" or type(data) == "string" then
return validEncounterJournalInstanceID(data) or mapIDToEncounterJournalInstanceID(data)
end
if type(data) ~= "table" then return nil end
local instanceID =
validEncounterJournalInstanceID(data.ej_instanceID) or
validEncounterJournalInstanceID(data.ejInstanceID) or
validEncounterJournalInstanceID(data.journalInstanceID) or
validEncounterJournalInstanceID(data.instanceID) or
validEncounterJournalInstanceID(data.dungeonID) or
validEncounterJournalInstanceID(data.targetID)
if instanceID then return instanceID end
return mapIDToEncounterJournalInstanceID(data.mapID) or
mapIDToEncounterJournalInstanceID(data.worldMapAreaID) or
mapIDToEncounterJournalInstanceID(data.instanceID) or
mapIDToEncounterJournalInstanceID(data.dungeonID) or
mapIDToEncounterJournalInstanceID(data.targetID)
end
local function requestOpenTarget(data)
if type(data) == "table" and data.id and C_PlayerGuide and C_PlayerGuide.RequestOpenTarget then
C_PlayerGuide.RequestOpenTarget(data.id)
end
end
local function openEncounterJournalInstance(data, encounterID)
if not EncounterJournal then return nil end
local instanceID = resolveEncounterJournalInstanceID(data)
if not instanceID then
requestOpenTarget(data)
return nil
end
if type(data) == "table" and not encounterID then
encounterID = data.encounterID
end
if not EncounterJournal:IsShown() then
ShowUIPanel(EncounterJournal)
end
if EncounterJournal.encounter then
EncounterJournal.encounter:Show()
end
if EncounterJournal.instanceSelect then
EJ_ContentTab_SelectAppropriateInstanceTab(instanceID)
end
if NavBar_Reset and EncounterJournal.navBar then
NavBar_Reset(EncounterJournal.navBar)
end
EncounterJournal_DisplayInstance(instanceID)
if encounterID and encounterID > 0 then
EncounterJournal_DisplayEncounter(encounterID)
end
end
function MW_PlayerGuide_Objective_OnClick(self)
local o = self.objectiveData
if not o then return end
if o.type == "dungeon" or o.type == "raid" then
openEncounterJournalInstance(o)
elseif o.type == "boss" then
if o.encounterID and o.encounterID > 0 then
if not EncounterJournal:IsShown() then ShowUIPanel(EncounterJournal) end
EncounterJournal_DisplayEncounter(o.encounterID)
end
elseif o.type == "achievement" then
if o.achievementID then
if AchievementFrame_LoadUI then AchievementFrame_LoadUI() end
if AchievementFrame_SelectAchievement then
AchievementFrame_SelectAchievement(o.achievementID)
end
end
elseif o.type == "item" then
if o.itemID and o.itemID > 0 then
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetItemByID(o.itemID)
GameTooltip:Show()
end
elseif o.type == "quest" or o.type == "quest_chain" then
local qid = o.questID or o.questChainID or o.targetID
if qid and qid > 0 then
pcall(SetItemRef, "quest:" .. qid .. ":-1", "", "LeftButton")
end
else
C_PlayerGuide.RequestOpenTarget(o.id)
end
end
function MW_PlayerGuide_Objective_OnEnter(self)
local o = self.objectiveData
if not o then return end
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:AddLine(o.title or "", 1, 0.91, 0.63)
if o.subtitle and o.subtitle ~= "" then
GameTooltip:AddLine(o.subtitle, 0.66, 0.59, 0.47, true)
end
if not o.completed then
GameTooltip:AddLine(
string.format("Прогресс: %d / %d", o.progress or 0, o.required or 1),
0.79, 0.65, 0.38)
end
GameTooltip:Show()
end
function MW_PlayerGuide_Objective_OnLeave() GameTooltip:Hide() end
function MW_PlayerGuide_Rec_OnClick(self)
local r = self.recData
if r then openEncounterJournalInstance(r) end
end
function MW_PlayerGuide_Rec_OnEnter(self)
local r = self.recData
if not r then return end
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:AddLine(r.name or r.title or "", 1, 0.91, 0.63)
if r.loc then GameTooltip:AddLine(r.loc, 0.66, 0.59, 0.47) end
if r.level then GameTooltip:AddLine("Уровень: " .. r.level, 0.79, 0.65, 0.38) end
GameTooltip:Show()
end
function MW_PlayerGuide_Rec_OnLeave() GameTooltip:Hide() end
-- ─────────────────────────────── Экспорт для отладки ──
MW_PlayerGuide = {
Refresh = refresh,
InjectDevData = function() C_PlayerGuide._InjectDevPayload() end,
}
@@ -0,0 +1,490 @@
<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="PlayerGuide.lua"/>
<!-- ───────── Шрифты ───────── -->
<Font name="MW_PG_Title" inherits="GameFontNormalHuge" virtual="true">
<Color r="1.0" g="0.84" b="0.42"/>
<Shadow><Color r="0" g="0" b="0"/><Offset><AbsDimension x="1" y="-1"/></Offset></Shadow>
</Font>
<Font name="MW_PG_Section" inherits="QuestFont_Large" virtual="true">
<Color r="1.0" g="0.84" b="0.42"/>
<Shadow><Color r="0" g="0" b="0"/><Offset><AbsDimension x="1" y="-1"/></Offset></Shadow>
</Font>
<Font name="MW_PG_Body" inherits="GameFontHighlight" virtual="true">
<Color r="0.85" g="0.78" b="0.63"/>
</Font>
<Font name="MW_PG_Caps" inherits="GameFontNormalSmall" virtual="true">
<Color r="0.63" g="0.46" b="0.19"/>
</Font>
<Font name="MW_PG_Italic" inherits="QuestFont" virtual="true">
<Color r="0.66" g="0.59" b="0.47"/>
</Font>
<!-- ───────── Карточка цели ───────── -->
<!-- Высота = 60px (DENSITY "regular"). Позиции: TypeLabel+Title строка 1, Subtitle строка 2, бар внизу. -->
<Frame name="MW_PG_ObjectiveCardTemplate" virtual="true">
<Size><AbsDimension x="0" y="70"/></Size>
<Backdrop bgFile="Interface\Buttons\WHITE8X8"
edgeFile="Interface\Buttons\WHITE8X8"
tile="true">
<BackgroundInsets><AbsInset left="0" right="0" top="0" bottom="0"/></BackgroundInsets>
<EdgeSize><AbsValue val="1"/></EdgeSize>
<TileSize><AbsValue val="64"/></TileSize>
</Backdrop>
<Layers>
<Layer level="BACKGROUND">
<!-- Рамка иконки — фоновый слой, центрируется по LEFT -->
<Texture name="$parentIconBorder" parentKey="IconBorder" file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="50" y="50"/></Size>
<Anchors>
<Anchor point="LEFT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor>
</Anchors>
<TexCoords left="0.2" right="0.8" top="0.2" bottom="0.8"/>
<Color r="1.0" g="0.84" b="0.42" a="0.55"/>
</Texture>
</Layer>
<Layer level="ARTWORK">
<Texture name="$parentIcon" parentKey="Icon" file="Interface\Icons\INV_Misc_QuestionMark">
<Size><AbsDimension x="34" y="34"/></Size>
<Anchors>
<Anchor point="LEFT"><Offset><AbsDimension x="20" y="0"/></Offset></Anchor>
</Anchors>
</Texture>
</Layer>
<Layer level="OVERLAY">
<!-- Строка 1: TypeLabel (префикс) + Title (основной текст) на одной строке -->
<FontString name="$parentTypeLabel" parentKey="TypeLabel" inherits="MW_PG_Caps" justifyH="LEFT">
<Size><AbsDimension x="90" y="12"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="72" y="-16"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="$parentTitle" parentKey="Title" inherits="GameFontNormal" justifyH="LEFT">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="185" y="-16"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-118" y="-16"/></Offset></Anchor>
</Anchors>
<Color r="1.0" g="0.91" b="0.63"/>
</FontString>
<!-- Строка 2: Subtitle (описание/прогресс) -->
<FontString name="$parentSubtitle" parentKey="Subtitle" inherits="MW_PG_Italic" justifyH="LEFT">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="72" y="-36"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-118" y="-36"/></Offset></Anchor>
</Anchors>
</FontString>
<!-- Count: мелко снизу справа -->
<FontString name="$parentCount" parentKey="Count" inherits="GameFontNormalSmall" justifyH="RIGHT">
<Size><AbsDimension x="110" y="10"/></Size>
<Anchors>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-18" y="10"/></Offset></Anchor>
</Anchors>
<Color r="0.63" g="0.46" b="0.19"/>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- Прогресс-бар: тонкая полоска у самого низа карточки -->
<StatusBar name="$parentProgressBar" parentKey="ProgressBar">
<Size><AbsDimension x="0" y="4"/></Size>
<Anchors>
<Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="72" y="8"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-118" y="8"/></Offset></Anchor>
</Anchors>
<BarTexture file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\ProgressBar-Fill"/>
<BarColor r="1.0" g="0.79" b="0.29"/>
<Layers>
<Layer level="BACKGROUND">
<Texture setAllPoints="true"
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\ProgressBar-BG"/>
</Layer>
</Layers>
</StatusBar>
</Frames>
<Scripts>
<OnClick function="MW_PlayerGuide_Objective_OnClick"/>
<OnEnter function="MW_PlayerGuide_Objective_OnEnter"/>
<OnLeave function="MW_PlayerGuide_Objective_OnLeave"/>
</Scripts>
</Frame>
<!-- ───────── Карточка рекомендации ───────── -->
<Button name="MW_PG_RecCardTemplate" virtual="true">
<!-- Height 96: fits in the 100px visible area of RecommendationsSection scrollframe -->
<Size><AbsDimension x="176" y="116"/></Size>
<Backdrop bgFile="Interface\Buttons\WHITE8X8"
edgeFile="Interface\Buttons\WHITE8X8"
tile="true">
<BackgroundInsets><AbsInset left="0" right="0" top="0" bottom="0"/></BackgroundInsets>
<EdgeSize><AbsValue val="1"/></EdgeSize>
<TileSize><AbsValue val="64"/></TileSize>
</Backdrop>
<Layers>
<Layer level="ARTWORK">
<!-- Artwork: 138x56, top offset 5 → bottom at y=61 from card top -->
<Texture name="$parentArtwork" parentKey="Artwork" file="Interface\Buttons\WHITE8X8" setAllPoints="true">
<Color r="1" g="1" b="1"/>
</Texture>
</Layer>
<Layer level="OVERLAY">
<!-- Level: top-right corner of artwork (artwork starts at y=-5 → level at TOPRIGHT+(-4,-9)) -->
<Texture name="$parentTextBackdrop" parentKey="TextBackdrop" file="Interface\Buttons\WHITE8X8">
<Size><AbsDimension x="176" y="42"/></Size>
<Anchors>
<Anchor point="BOTTOM"/>
</Anchors>
<Color r="0" g="0" b="0" a="0.72"/>
</Texture>
<FontString name="$parentLevel" parentKey="Level" inherits="GameFontNormalSmall" justifyH="RIGHT">
<Size><AbsDimension x="80" y="12"/></Size>
<Anchors>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-4" y="-9"/></Offset></Anchor>
</Anchors>
<Color r="1.0" g="0.91" b="0.63"/>
</FontString>
<!-- Name: 4px below artwork (artwork bottom y=61, name at y=65) -->
<FontString name="$parentName" parentKey="Name" inherits="GameFontNormal" justifyH="LEFT">
<Size><AbsDimension x="162" y="16"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="8" y="-82"/></Offset></Anchor>
</Anchors>
<Color r="1.0" g="0.91" b="0.63"/>
</FontString>
<!-- Loc: 3px below name (name bottom y=79, loc at y=82) -->
<FontString name="$parentLoc" parentKey="Loc" inherits="MW_PG_Italic" justifyH="LEFT">
<Size><AbsDimension x="162" y="16"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="8" y="-98"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Scripts>
<OnClick function="MW_PlayerGuide_Rec_OnClick"/>
<OnEnter function="MW_PlayerGuide_Rec_OnEnter"/>
<OnLeave function="MW_PlayerGuide_Rec_OnLeave"/>
</Scripts>
</Button>
<!-- ───────── Бейдж этапа ───────── -->
<Frame name="MW_PG_StageBadgeTemplate" virtual="true">
<Size><AbsDimension x="38" y="38"/></Size>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentBadgeBG" parentKey="BadgeBG"
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\StageBadge-Locked">
<Size><AbsDimension x="60" y="60"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
</Texture>
</Layer>
<Layer level="OVERLAY">
<FontString name="$parentRoman" parentKey="Roman" inherits="GameFontNormalLarge" justifyH="CENTER">
<Anchors><Anchor point="CENTER"/></Anchors>
<Color r="1.0" g="0.91" b="0.63"/>
</FontString>
<FontString name="$parentLabel" parentKey="Label" inherits="MW_PG_Caps" justifyH="CENTER">
<Size><AbsDimension x="100" y="10"/></Size>
<Anchors>
<Anchor point="TOP" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<!-- ───────── Корневой фрейм ───────── -->
<Frame name="PlayerGuideFrame" hidden="true" parent="EncounterJournal">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="EncounterJournalInset" relativePoint="TOPLEFT">
<Offset><AbsDimension x="5" y="-4"/></Offset>
</Anchor>
<Anchor point="BOTTOMRIGHT" relativeTo="EncounterJournalInset" relativePoint="BOTTOMRIGHT">
<Offset><AbsDimension x="-3" y="0"/></Offset>
</Anchor>
</Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture parentKey="BackgroundPage"
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Background-Page"
setAllPoints="true">
<TexCoords left="0" right="1" top="0" bottom="1"/>
</Texture>
</Layer>
<Layer level="BORDER">
<Texture parentKey="TabDivider"
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Section-Divider">
<Size><AbsDimension x="0" y="16"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="28" y="-52"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-28" y="-52"/></Offset></Anchor>
</Anchors>
</Texture>
</Layer>
</Layers>
<Frames>
<!-- 1) Цепочка этапов — позиция задаётся Lua в OnLoad -->
<Frame name="PlayerGuideStageChain" parentKey="StageChain">
<Size><AbsDimension x="0" y="62"/></Size>
</Frame>
<!-- 2) Заголовок этапа — позиция задаётся Lua в OnLoad -->
<Frame name="PlayerGuideHeader" parentKey="Header">
<Size><AbsDimension x="0" y="82"/></Size>
<Layers>
<Layer level="OVERLAY">
<FontString name="PlayerGuideHeaderPretitle" parentKey="Pretitle" inherits="MW_PG_Caps" justifyH="LEFT">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="PlayerGuideHeaderTitle" parentKey="Title" inherits="MW_PG_Title" justifyH="LEFT">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="2" y="-18"/></Offset></Anchor>
<Anchor point="RIGHT"><Offset><AbsDimension x="-260" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="PlayerGuideHeaderDesc" parentKey="Description" inherits="MW_PG_Body" justifyH="LEFT">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="4" y="-48"/></Offset></Anchor>
<Anchor point="RIGHT"><Offset><AbsDimension x="-260" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="PlayerGuideHeaderProgLabel" parentKey="ProgressLabel" inherits="MW_PG_Caps" justifyH="RIGHT">
<Anchors>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-2" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="PlayerGuideHeaderProgPct" parentKey="ProgressPercent" inherits="MW_PG_Title" justifyH="RIGHT">
<Size><AbsDimension x="240" y="28"/></Size>
<Anchors>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-2" y="-14"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<StatusBar name="PlayerGuideHeaderProgBar" parentKey="ProgressBar">
<Size><AbsDimension x="240" y="10"/></Size>
<Anchors>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-2" y="-52"/></Offset></Anchor>
</Anchors>
<BarTexture file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\ProgressBar-Fill"/>
<BarColor r="1.0" g="0.79" b="0.29"/>
<Layers>
<Layer level="BACKGROUND">
<Texture setAllPoints="true"
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\ProgressBar-BG"/>
</Layer>
</Layers>
</StatusBar>
<Button name="PlayerGuideHeaderRefresh" parentKey="RefreshButton" inherits="UIPanelButtonTemplate2" text="MW_PLAYER_GUIDE_REFRESH">
<Size><AbsDimension x="100" y="22"/></Size>
<Anchors>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-2" y="-72"/></Offset></Anchor>
</Anchors>
<Scripts>
<OnLoad>
local T = "Interface\\AddOns\\MoonWellClient\\EncounterJournal\\Textures\\PlayerGuide\\"
self:SetNormalTexture(T.."Button-Up")
self:SetPushedTexture(T.."Button-Down")
self:SetHighlightTexture(T.."Button-Highlight")
</OnLoad>
<OnClick function="MW_PlayerGuide_RefreshButton_OnClick"/>
</Scripts>
</Button>
</Frames>
</Frame>
<!-- 3) Задачи этапа — позиция задаётся Lua в OnLoad -->
<ScrollFrame name="PlayerGuideBodyScroll" parentKey="BodyScroll" inherits="UIPanelScrollFrameTemplate">
<ScrollChild>
<Frame parentKey="ScrollChild">
<Size><AbsDimension x="860" y="1"/></Size>
</Frame>
</ScrollChild>
</ScrollFrame>
<Frame name="PlayerGuideObjSection" parentKey="ObjectivesSection">
<Layers>
<Layer level="OVERLAY">
<Texture name="PlayerGuideObjOrnament" parentKey="Ornament"
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Corner-Ornament">
<Size><AbsDimension x="20" y="20"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="2" y="-1"/></Offset></Anchor>
</Anchors>
</Texture>
<FontString name="PlayerGuideObjHeading" parentKey="Heading" inherits="MW_PG_Section" justifyH="LEFT" text="MW_PLAYER_GUIDE_OBJECTIVES">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="26" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="PlayerGuideObjCount" parentKey="Count" inherits="MW_PG_Caps" justifyH="LEFT">
<Anchors>
<Anchor point="LEFT" relativeTo="PlayerGuideObjHeading" relativePoint="RIGHT">
<Offset><AbsDimension x="6" y="-2"/></Offset>
</Anchor>
</Anchors>
</FontString>
<Texture name="PlayerGuideObjDivider" parentKey="Divider"
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Section-Divider">
<Size><AbsDimension x="0" y="16"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="-24"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-4" y="-24"/></Offset></Anchor>
</Anchors>
</Texture>
</Layer>
</Layers>
<Frames>
<ScrollFrame name="PlayerGuideObjScroll" parentKey="ScrollFrame" inherits="UIPanelScrollFrameTemplate">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="6" y="-48"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-8" y="0"/></Offset></Anchor>
</Anchors>
<ScrollChild>
<Frame parentKey="ScrollChild">
<Size><AbsDimension x="600" y="200"/></Size>
</Frame>
</ScrollChild>
</ScrollFrame>
</Frames>
</Frame>
<!-- 4) Подходящие подземелья — позиция задаётся Lua в OnLoad -->
<Frame name="PlayerGuideRecsSection" parentKey="RecommendationsSection">
<Size><AbsDimension x="0" y="150"/></Size>
<Layers>
<Layer level="OVERLAY">
<Texture name="PlayerGuideRecsOrnament" parentKey="Ornament"
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Corner-Ornament">
<Size><AbsDimension x="20" y="20"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="2" y="-1"/></Offset></Anchor>
</Anchors>
</Texture>
<FontString name="PlayerGuideRecsHeading" parentKey="Heading" inherits="MW_PG_Section" justifyH="LEFT" text="MW_PLAYER_GUIDE_RECS">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="26" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="PlayerGuideRecsCount" parentKey="Count" inherits="MW_PG_Caps" justifyH="LEFT">
<Anchors>
<Anchor point="LEFT" relativeTo="PlayerGuideRecsHeading" relativePoint="RIGHT">
<Offset><AbsDimension x="6" y="-2"/></Offset>
</Anchor>
</Anchors>
</FontString>
<Texture name="PlayerGuideRecsDivider" parentKey="Divider"
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Section-Divider">
<Size><AbsDimension x="0" y="16"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="-24"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-4" y="-24"/></Offset></Anchor>
</Anchors>
</Texture>
</Layer>
</Layers>
<Frames>
<ScrollFrame name="PlayerGuideRecScroll" parentKey="ScrollFrame" inherits="UIPanelScrollFrameTemplate">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="6" y="-46"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-36" y="0"/></Offset></Anchor>
</Anchors>
<ScrollChild>
<Frame parentKey="ScrollChild">
<Size><AbsDimension x="900" y="96"/></Size>
</Frame>
</ScrollChild>
</ScrollFrame>
</Frames>
</Frame>
<!-- 5) Пустое состояние -->
<Frame name="PlayerGuideEmptyState" parentKey="EmptyState" hidden="true">
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="0" y="20"/></Offset></Anchor></Anchors>
<Size><AbsDimension x="460" y="200"/></Size>
<Layers>
<Layer level="OVERLAY">
<FontString name="PlayerGuideEmptyTitle" parentKey="Title" inherits="MW_PG_Title" justifyH="CENTER" text="MW_PLAYER_GUIDE_EMPTY_TITLE">
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="0" y="-20"/></Offset></Anchor></Anchors>
<Size><AbsDimension x="460" y="28"/></Size>
</FontString>
<FontString name="PlayerGuideEmptyBody" parentKey="Body" inherits="MW_PG_Body" justifyH="CENTER" text="MW_PLAYER_GUIDE_EMPTY_BODY">
<Anchors>
<Anchor point="TOP" relativeTo="PlayerGuideEmptyTitle" relativePoint="BOTTOM">
<Offset><AbsDimension x="0" y="-10"/></Offset>
</Anchor>
</Anchors>
<Size><AbsDimension x="420" y="60"/></Size>
</FontString>
</Layer>
</Layers>
<Frames>
<Button name="PlayerGuideEmptyRefresh" parentKey="RefreshButton" inherits="UIPanelButtonTemplate2" text="MW_PLAYER_GUIDE_REFRESH">
<Size><AbsDimension x="120" y="24"/></Size>
<Anchors>
<Anchor point="TOP" relativeTo="PlayerGuideEmptyBody" relativePoint="BOTTOM">
<Offset><AbsDimension x="0" y="-14"/></Offset>
</Anchor>
</Anchors>
<Scripts>
<OnLoad>
local T = "Interface\\AddOns\\MoonWellClient\\EncounterJournal\\Textures\\PlayerGuide\\"
self:SetNormalTexture(T.."Button-Up")
self:SetPushedTexture(T.."Button-Down")
self:SetHighlightTexture(T.."Button-Highlight")
</OnLoad>
<OnClick function="MW_PlayerGuide_RefreshButton_OnClick"/>
</Scripts>
</Button>
</Frames>
</Frame>
<!-- 6) Загрузка -->
<Frame name="PlayerGuideLoadingState" parentKey="LoadingState" hidden="true">
<Anchors><Anchor point="CENTER"/></Anchors>
<Size><AbsDimension x="300" y="80"/></Size>
<Layers>
<Layer level="OVERLAY">
<FontString name="PlayerGuideLoadingTitle" parentKey="Title" inherits="MW_PG_Section" justifyH="CENTER" text="MW_PLAYER_GUIDE_LOADING_TITLE">
<Anchors><Anchor point="TOP"/></Anchors>
<Size><AbsDimension x="300" y="20"/></Size>
</FontString>
<FontString name="PlayerGuideLoadingBody" parentKey="Body" inherits="MW_PG_Italic" justifyH="CENTER" text="MW_PLAYER_GUIDE_LOADING_BODY">
<Anchors>
<Anchor point="TOP" relativeTo="PlayerGuideLoadingTitle" relativePoint="BOTTOM">
<Offset><AbsDimension x="0" y="-6"/></Offset>
</Anchor>
</Anchors>
<Size><AbsDimension x="300" y="16"/></Size>
</FontString>
</Layer>
</Layers>
</Frame>
</Frames>
<Scripts>
<OnLoad function="MW_PlayerGuide_OnLoad"/>
<OnShow function="MW_PlayerGuide_OnShow"/>
<OnMouseWheel function="MW_PlayerGuide_OnMouseWheel"/>
</Scripts>
</Frame>
</Ui>
@@ -0,0 +1,905 @@
-- Compatibility shims for the Sirus Encounter Journal backport on 3.3.5a.
if not tIndexOf then
function tIndexOf(tbl, value)
if not tbl then
return nil
end
for index, entry in ipairs(tbl) do
if entry == value then
return index
end
end
return nil
end
end
if not CopyTable then
function CopyTable(tbl)
if type(tbl) ~= "table" then
return tbl
end
local copy = {}
for key, value in pairs(tbl) do
copy[key] = CopyTable(value)
end
return copy
end
end
if not table.wipe then
function table.wipe(tbl)
for key in pairs(tbl) do
tbl[key] = nil
end
return tbl
end
end
if not tCompare then
function tCompare(lhsTable, rhsTable, depth)
depth = depth or 1
for key, value in pairs(lhsTable) do
if type(value) == "table" then
local rhsValue = rhsTable[key]
if type(rhsValue) ~= "table" then
return false
end
if depth > 1 then
if not tCompare(value, rhsValue, depth - 1) then
return false
end
end
elseif value ~= rhsTable[key] then
return false
end
end
for key in pairs(rhsTable) do
if lhsTable[key] == nil then
return false
end
end
return true
end
end
tsort = tsort or table.sort
bit = bit or {}
if not bit.band then
function bit.band(a, b)
local result = 0
local bitValue = 1
a = a or 0
b = b or 0
while a > 0 or b > 0 do
local aBit = a % 2
local bBit = b % 2
if aBit == 1 and bBit == 1 then
result = result + bitValue
end
a = math.floor(a / 2)
b = math.floor(b / 2)
bitValue = bitValue * 2
end
return result
end
end
bitband = bitband or bit.band
if not string.trim then
function string.trim(value)
return value and value:match("^%s*(.-)%s*$") or value
end
end
utf8 = utf8 or {}
utf8.find = utf8.find or string.find
utf8.upper = utf8.upper or string.upper
LE_EXPANSION_CLASSIC = LE_EXPANSION_CLASSIC or 0
LE_EXPANSION_BURNING_CRUSADE = LE_EXPANSION_BURNING_CRUSADE or 1
LE_EXPANSION_WRATH_OF_THE_LICH_KING = LE_EXPANSION_WRATH_OF_THE_LICH_KING or 2
LE_EXPANSION_CATACLYSM = LE_EXPANSION_CATACLYSM or 3
LE_EXPANSION_MISTS_OF_PANDARIA = LE_EXPANSION_MISTS_OF_PANDARIA or 4
LE_EXPANSION_WARLORDS_OF_DRAENOR = LE_EXPANSION_WARLORDS_OF_DRAENOR or 5
LE_EXPANSION_LEGION = LE_EXPANSION_LEGION or 6
LE_EXPANSION_BATTLE_FOR_AZEROTH = LE_EXPANSION_BATTLE_FOR_AZEROTH or 7
LE_EXPANSION_SHADOWLANDS = LE_EXPANSION_SHADOWLANDS or 8
LE_EXPANSION_DRAGONFLIGHT = LE_EXPANSION_DRAGONFLIGHT or 9
LE_EXPANSION_WAR_WITHIN = LE_EXPANSION_WAR_WITHIN or 10
LE_EXPANSION_MIDNIGHT = LE_EXPANSION_MIDNIGHT or 11
LE_EXPANSION_LEVEL_CURRENT = LE_EXPANSION_LEVEL_CURRENT or LE_EXPANSION_WRATH_OF_THE_LICH_KING
MAX_PLAYER_LEVEL_TABLE = MAX_PLAYER_LEVEL_TABLE or {}
MAX_PLAYER_LEVEL_TABLE[LE_EXPANSION_CLASSIC] = MAX_PLAYER_LEVEL_TABLE[LE_EXPANSION_CLASSIC] or 60
MAX_PLAYER_LEVEL_TABLE[LE_EXPANSION_BURNING_CRUSADE] = MAX_PLAYER_LEVEL_TABLE[LE_EXPANSION_BURNING_CRUSADE] or 70
MAX_PLAYER_LEVEL_TABLE[LE_EXPANSION_WRATH_OF_THE_LICH_KING] = MAX_PLAYER_LEVEL_TABLE[LE_EXPANSION_WRATH_OF_THE_LICH_KING] or 80
PLAYER_FACTION_GROUP = PLAYER_FACTION_GROUP or {}
PLAYER_FACTION_GROUP.Alliance = PLAYER_FACTION_GROUP.Alliance or 1
PLAYER_FACTION_GROUP.Horde = PLAYER_FACTION_GROUP.Horde or 2
PLAYER_FACTION_GROUP.Neutral = PLAYER_FACTION_GROUP.Neutral or 3
PLAYER_FACTION_GROUP.Renegade = PLAYER_FACTION_GROUP.Renegade or 4
QUESTION_MARK_ICON = QUESTION_MARK_ICON or "Interface\\Icons\\INV_Misc_QuestionMark"
CLASS_ID_WARRIOR = CLASS_ID_WARRIOR or 1
CLASS_ID_PALADIN = CLASS_ID_PALADIN or 2
CLASS_ID_HUNTER = CLASS_ID_HUNTER or 3
CLASS_ID_ROGUE = CLASS_ID_ROGUE or 4
CLASS_ID_PRIEST = CLASS_ID_PRIEST or 5
CLASS_ID_DEATHKNIGHT = CLASS_ID_DEATHKNIGHT or 6
CLASS_ID_SHAMAN = CLASS_ID_SHAMAN or 7
CLASS_ID_MAGE = CLASS_ID_MAGE or 8
CLASS_ID_WARLOCK = CLASS_ID_WARLOCK or 9
CLASS_ID_DRUID = CLASS_ID_DRUID or 11
if not SetParentFrameLevel then
function SetParentFrameLevel(frame, offset)
frame:SetFrameLevel(frame:GetParent():GetFrameLevel() + (offset or 0))
end
end
if not IsOnGlueScreen then
function IsOnGlueScreen() return false end
end
if not RegisterCustomEvent then
function RegisterCustomEvent() end
end
if not UnregisterCustomEvent then
function UnregisterCustomEvent() end
end
C_Timer = C_Timer or {}
if not C_Timer.NewTimer then
local timerFrame
local timers = {}
local function EnsureTimerFrame()
if timerFrame or not CreateFrame then
return
end
timerFrame = CreateFrame("Frame")
timerFrame:SetScript("OnUpdate", function(_, elapsed)
for timer in pairs(timers) do
timer.remaining = timer.remaining - elapsed
if timer.remaining <= 0 then
timers[timer] = nil
if not timer.cancelled then
timer.callback()
end
end
end
end)
end
function C_Timer.NewTimer(seconds, callback)
local timer = {
remaining = seconds or 0,
callback = callback or function() end,
}
function timer:Cancel()
self.cancelled = true
timers[self] = nil
end
EnsureTimerFrame()
timers[timer] = true
return timer
end
end
C_Timer.After = C_Timer.After or function(seconds, callback)
return C_Timer.NewTimer(seconds, callback)
end
if not IsInterfaceDevClient then
function IsInterfaceDevClient()
return false
end
end
if not IsGMAccount then
function IsGMAccount()
return false
end
end
if not FireCustomClientEvent then
function FireCustomClientEvent(event, ...)
if EventRegistry and EventRegistry.TriggerEvent then
EventRegistry:TriggerEvent(event, ...)
end
end
end
EventRegistry = EventRegistry or {}
EventRegistry.registry = EventRegistry.registry or {}
EventRegistry.TriggerEvent = EventRegistry.TriggerEvent or function(self, event, ...)
local callbacks = self.registry[event]
if callbacks then
for _, callback in ipairs(callbacks) do
callback(...)
end
end
end
EventRegistry.RegisterCallback = EventRegistry.RegisterCallback or function(self, event, callback)
self.registry[event] = self.registry[event] or {}
table.insert(self.registry[event], callback)
end
C_Service = C_Service or {}
C_Service.GetRealmID = function() return 0 end
C_Service.GetRealmStage = C_Service.GetRealmStage or function() return 0 end
C_Service.GetCustomValue = C_Service.GetCustomValue or function() return 0 end
C_Service.IsInGMMode = C_Service.IsInGMMode or IsGMAccount
C_Service.IsGMAccount = C_Service.IsGMAccount or IsGMAccount
C_Service.IsRenegadeRealm = C_Service.IsRenegadeRealm or function() return false end
C_Service.IsHardcoreEnabledOnRealm = C_Service.IsHardcoreEnabledOnRealm or function() return false end
C_MythicPlus = C_MythicPlus or {}
C_MythicPlus.IsMythicPlusActive = C_MythicPlus.IsMythicPlusActive or function() return false end
C_PvP = C_PvP or {}
C_LFGList = C_LFGList or {}
C_LFGList.IsPremadeGroupFinderEnabled = C_LFGList.IsPremadeGroupFinderEnabled or function() return false end
if not MicroButtonPulseStop then
function MicroButtonPulseStop() end
end
if not EJSuggestFrame_OpenFrame then
function EJSuggestFrame_OpenFrame() end
end
if not EJSuggestTab_GetPlayerTierIndex then
function EJSuggestTab_GetPlayerTierIndex()
if EJ_GetCurrentTier then
return EJ_GetCurrentTier()
end
return 1
end
end
if not ChatEdit_TryInsertChatLink then
function ChatEdit_TryInsertChatLink()
return false
end
end
if not SharedTooltip_SetBackdropStyle then
function SharedTooltip_SetBackdropStyle(tooltip)
if not tooltip then
return
end
if tooltip.SetBackdropBorderColor and TOOLTIP_DEFAULT_COLOR then
tooltip:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b)
end
if tooltip.SetBackdropColor and TOOLTIP_DEFAULT_BACKGROUND_COLOR then
tooltip:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
end
end
end
if not HelpPlate_Hide then
function HelpPlate_Hide() end
end
if not HelpPlate_Show then
function HelpPlate_Show() end
end
-- 3.3.5 has no GetNumClasses/GetClassInfo (added in Cataclysm). Sirus EJ uses
-- them to populate the loot class filter dropdown.
local MOONWELL_CLASS_INFO = {
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.WARRIOR or "Warrior", "WARRIOR", CLASS_ID_WARRIOR},
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.PALADIN or "Paladin", "PALADIN", CLASS_ID_PALADIN},
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.HUNTER or "Hunter", "HUNTER", CLASS_ID_HUNTER},
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.ROGUE or "Rogue", "ROGUE", CLASS_ID_ROGUE},
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.PRIEST or "Priest", "PRIEST", CLASS_ID_PRIEST},
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.DEATHKNIGHT or "Death Knight", "DEATHKNIGHT", CLASS_ID_DEATHKNIGHT},
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.SHAMAN or "Shaman", "SHAMAN", CLASS_ID_SHAMAN},
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.MAGE or "Mage", "MAGE", CLASS_ID_MAGE},
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.WARLOCK or "Warlock", "WARLOCK", CLASS_ID_WARLOCK},
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.DRUID or "Druid", "DRUID", CLASS_ID_DRUID},
}
if not GetNumClasses then
function GetNumClasses()
return #MOONWELL_CLASS_INFO
end
end
if not GetClassInfo then
function GetClassInfo(index)
local info = MOONWELL_CLASS_INFO[index]
if info then
return info[1], info[2], info[3]
end
end
end
CLASS_ID_DEMONHUNTER = CLASS_ID_DEMONHUNTER or 12
local function EnsureColorMethods(color)
if type(color) ~= "table" then
return color
end
color.GetRGB = color.GetRGB or function(self)
return self.r or 1, self.g or 1, self.b or 1
end
color.GetRGBA = color.GetRGBA or function(self)
return self.r or 1, self.g or 1, self.b or 1, self.a or 1
end
color.WrapTextInColorCode = color.WrapTextInColorCode or function(self, text)
if self.colorStr then
return "|c" .. self.colorStr .. text .. "|r"
end
return text
end
return color
end
EnsureColorMethods(NORMAL_FONT_COLOR)
EnsureColorMethods(HIGHLIGHT_FONT_COLOR)
EnsureColorMethods(RED_FONT_COLOR)
EnsureColorMethods(GREEN_FONT_COLOR)
EnsureColorMethods(DISABLED_FONT_COLOR)
LOOTJOURNAL_SOURCE_TOOLTIP_HEAD = LOOTJOURNAL_SOURCE_TOOLTIP_HEAD or "Способ получения:"
RETURN_TO_DEFAULT = RETURN_TO_DEFAULT or "Настройки по умолчанию"
GUIDEBOOK = GUIDEBOOK or "Путеводитель"
INSTANCES = INSTANCES or "Подземелья"
RAIDS = RAIDS or "Рейды"
LOOT_JOURNAL_ITEM_SETS = LOOT_JOURNAL_ITEM_SETS or "Комплекты"
-- Format string used by EncounterJournal_SetLootButton to label the loot
-- source ("Босс: <name>"). Sirus ships it via locale globals; default to a
-- compact form on 3.3.5.
BOSS_INFO_STRING = BOSS_INFO_STRING or "Босс: %s"
ITEM_CLASS_2 = ITEM_CLASS_2 or 2
ITEM_CLASS_4 = ITEM_CLASS_4 or 4
for _, classID in ipairs({0, 2, 4, 5, 7, 9, 11, 12, 13, 14, 15}) do
for subclassID = 0, 20 do
local key = string.format("ITEM_SUB_CLASS_%d_%d", classID, subclassID)
if _G[key] == nil then
_G[key] = subclassID
end
end
end
LE_ITEM_FILTER_TYPE_HEAD = LE_ITEM_FILTER_TYPE_HEAD or 1
LE_ITEM_FILTER_TYPE_NECK = LE_ITEM_FILTER_TYPE_NECK or 2
LE_ITEM_FILTER_TYPE_SHOULDER = LE_ITEM_FILTER_TYPE_SHOULDER or 3
LE_ITEM_FILTER_TYPE_CLOAK = LE_ITEM_FILTER_TYPE_CLOAK or 16
LE_ITEM_FILTER_TYPE_CHEST = LE_ITEM_FILTER_TYPE_CHEST or 5
LE_ITEM_FILTER_TYPE_WRIST = LE_ITEM_FILTER_TYPE_WRIST or 9
LE_ITEM_FILTER_TYPE_HAND = LE_ITEM_FILTER_TYPE_HAND or 10
LE_ITEM_FILTER_TYPE_WAIST = LE_ITEM_FILTER_TYPE_WAIST or 6
LE_ITEM_FILTER_TYPE_LEGS = LE_ITEM_FILTER_TYPE_LEGS or 7
LE_ITEM_FILTER_TYPE_FEET = LE_ITEM_FILTER_TYPE_FEET or 8
LE_ITEM_FILTER_TYPE_MAIN_HAND = LE_ITEM_FILTER_TYPE_MAIN_HAND or 21
LE_ITEM_FILTER_TYPE_OFF_HAND = LE_ITEM_FILTER_TYPE_OFF_HAND or 22
LE_ITEM_FILTER_TYPE_2HWEAPON = LE_ITEM_FILTER_TYPE_2HWEAPON or 17
LE_ITEM_FILTER_TYPE_HWEAPON = LE_ITEM_FILTER_TYPE_HWEAPON or 13
LE_ITEM_FILTER_TYPE_RANGED = LE_ITEM_FILTER_TYPE_RANGED or 15
LE_ITEM_FILTER_TYPE_FINGER = LE_ITEM_FILTER_TYPE_FINGER or 11
LE_ITEM_FILTER_TYPE_TRINKET = LE_ITEM_FILTER_TYPE_TRINKET or 12
LE_ITEM_QUALITY_COMMON = LE_ITEM_QUALITY_COMMON or 1
BAG_ITEM_QUALITY_COLORS = BAG_ITEM_QUALITY_COLORS or {}
local itemQualityColors = ITEM_QUALITY_COLORS or {}
for quality = 0, 7 do
local color = BAG_ITEM_QUALITY_COLORS[quality] or itemQualityColors[quality] or NORMAL_FONT_COLOR
BAG_ITEM_QUALITY_COLORS[quality] = EnsureColorMethods(color or {})
end
if not SetItemButtonQuality then
function SetItemButtonQuality(button, quality)
if not button or not button.IconBorder then
return
end
local color = quality and BAG_ITEM_QUALITY_COLORS[quality]
if color then
button.IconBorder:Show()
button.IconBorder:SetVertexColor(color.r or 1, color.g or 1, color.b or 1)
else
button.IconBorder:Hide()
end
end
end
Enum = Enum or {}
Enum.ItemQuality = Enum.ItemQuality or {
Poor = 0,
Common = 1,
Uncommon = 2,
Rare = 3,
Epic = 4,
Legendary = 5,
Artifact = 6,
Heirloom = 7,
}
Enum.ModelType = Enum.ModelType or {
M2 = 0,
Unit = 1,
Creature = 2,
Item = 3,
ItemSet = 4,
Illusion = 5,
ItemTransmog = 6,
Customization = 7,
}
Enum.HardcoreDeathReason = Enum.HardcoreDeathReason or {
FAILED_DEATH = 1,
RESTORE = 2,
}
local ITEM_CACHE_FIELD = {
NAME_ENGB = 1,
NAME_RURU = 2,
RARITY = 3,
ILEVEL = 4,
MINLEVEL = 5,
TYPE = 6,
SUBTYPE = 7,
STACKCOUNT = 8,
EQUIPLOC = 9,
TEXTURE = 10,
VENDORPRICE = 11,
VENDORPRIC = 11,
}
local ITEM_ID_FIELD = 0
local ITEM_LINK_FIELD = -1
Enum.ItemCacheField = Enum.ItemCacheField or {}
for key, value in pairs(ITEM_CACHE_FIELD) do
Enum.ItemCacheField[key] = value
end
Enum.ItemCacheField.ITEM_ID = ITEM_ID_FIELD
SHARED_INVTYPE_BY_ID = SHARED_INVTYPE_BY_ID or {
[0] = "",
[1] = "INVTYPE_HEAD",
[2] = "INVTYPE_NECK",
[3] = "INVTYPE_SHOULDER",
[4] = "INVTYPE_BODY",
[5] = "INVTYPE_CHEST",
[6] = "INVTYPE_WAIST",
[7] = "INVTYPE_LEGS",
[8] = "INVTYPE_FEET",
[9] = "INVTYPE_WRIST",
[10] = "INVTYPE_HAND",
[11] = "INVTYPE_FINGER",
[12] = "INVTYPE_TRINKET",
[13] = "INVTYPE_WEAPON",
[14] = "INVTYPE_SHIELD",
[15] = "INVTYPE_RANGED",
[16] = "INVTYPE_CLOAK",
[17] = "INVTYPE_2HWEAPON",
[18] = "INVTYPE_BAG",
[19] = "INVTYPE_TABARD",
[20] = "INVTYPE_ROBE",
[21] = "INVTYPE_WEAPONMAINHAND",
[22] = "INVTYPE_WEAPONOFFHAND",
[23] = "INVTYPE_HOLDABLE",
[24] = "INVTYPE_AMMO",
[25] = "INVTYPE_THROWN",
[26] = "INVTYPE_RANGEDRIGHT",
[27] = "INVTYPE_QUIVER",
[28] = "INVTYPE_RELIC",
}
C_FactionManager = C_FactionManager or {}
C_FactionManager.GetFactionInfoOriginal = C_FactionManager.GetFactionInfoOriginal or function()
local faction = UnitFactionGroup and UnitFactionGroup("player")
return PLAYER_FACTION_GROUP[faction] or PLAYER_FACTION_GROUP.Alliance
end
C_FactionManager.RegisterCallback = C_FactionManager.RegisterCallback or function(callback)
if callback then
callback()
end
end
C_Item = C_Item or {}
local ExistingCItemGetItemInfo = C_Item.GetItemInfo
local ExistingCItemRequestServerCache = C_Item.RequestServerCache
local ExistingCItemGetItemIDFromString = C_Item.GetItemIDFromString
local ITEM_CACHE_CALLBACKS = {}
local ITEM_QUALITY_HEX = {
[0] = "ff9d9d9d",
[1] = "ffffffff",
[2] = "ff1eff00",
[3] = "ff0070dd",
[4] = "ffa335ee",
[5] = "ffff8000",
[6] = "ffe6cc80",
[7] = "ffe6cc80",
}
local ITEMS_CACHE_INITIALIZED
local function GetItemIDFromAny(item)
if type(item) == "number" then
return item
end
if type(item) == "string" then
local itemsCache = _G.ItemsCache
if type(itemsCache) == "table" and type(itemsCache[item]) == "table" then
return itemsCache[item][ITEM_ID_FIELD]
end
return tonumber(item:match("item:(%d+)") or item:match("^(%d+)$"))
end
end
local function CreateItemCacheLink(itemName, itemID, itemRarity, itemMinLevel)
return string.format("|c%s|Hitem:%d:0:0:0:0:0:0:0:%d|h[%s]|h|r",
ITEM_QUALITY_HEX[itemRarity] or "ffffffff",
itemID or 0,
itemMinLevel or 0,
itemName or UNKNOWN or ""
)
end
local function EnsureItemsCache()
if ITEMS_CACHE_INITIALIZED then
return _G.ItemsCache
end
local itemsCache = type(_G.ItemsCache) == "table" and _G.ItemsCache or nil
local fileIndex = 1
local itemCacheTable = _G["ItemsCache" .. fileIndex]
while type(itemCacheTable) == "table" do
if not itemsCache then
itemsCache = itemCacheTable
_G.ItemsCache = itemsCache
elseif itemCacheTable ~= itemsCache then
for itemID, itemData in pairs(itemCacheTable) do
itemsCache[itemID] = itemData
end
table.wipe(itemCacheTable)
end
_G["ItemsCache" .. fileIndex] = nil
fileIndex = fileIndex + 1
itemCacheTable = _G["ItemsCache" .. fileIndex]
end
if itemsCache then
local localeIndex = GetLocale and GetLocale() == "ruRU" and ITEM_CACHE_FIELD.NAME_RURU or ITEM_CACHE_FIELD.NAME_ENGB
local namedItems = {}
for itemID, itemData in pairs(itemsCache) do
if type(itemID) == "number" and type(itemData) == "table" then
itemData[ITEM_ID_FIELD] = itemID
itemData[ITEM_LINK_FIELD] = nil
local itemName = itemData[localeIndex]
if itemName and itemName ~= "" then
namedItems[itemName] = itemData
end
end
end
setmetatable(itemsCache, {__index = namedItems})
end
ITEMS_CACHE_INITIALIZED = true
return itemsCache
end
function C_Item.GetItemInfoCache(item)
local itemsCache = EnsureItemsCache()
if not itemsCache then
return
end
local itemID = GetItemIDFromAny(item)
if not itemID then
return
end
local cacheData = itemsCache[itemID]
if type(cacheData) ~= "table" then
return
end
local localeIndex = GetLocale and GetLocale() == "ruRU" and ITEM_CACHE_FIELD.NAME_RURU or ITEM_CACHE_FIELD.NAME_ENGB
local itemName = cacheData[localeIndex] or cacheData[ITEM_CACHE_FIELD.NAME_ENGB]
if not itemName or itemName == "" then
return
end
local itemRarity = cacheData[ITEM_CACHE_FIELD.RARITY] or LE_ITEM_QUALITY_COMMON
local itemMinLevel = cacheData[ITEM_CACHE_FIELD.MINLEVEL] or 0
local classID = cacheData[ITEM_CACHE_FIELD.TYPE]
local subclassID = cacheData[ITEM_CACHE_FIELD.SUBTYPE]
local equipLocID = cacheData[ITEM_CACHE_FIELD.EQUIPLOC] or 0
if not cacheData[ITEM_LINK_FIELD] then
cacheData[ITEM_LINK_FIELD] = CreateItemCacheLink(itemName, itemID, itemRarity, itemMinLevel)
end
return itemName,
cacheData[ITEM_LINK_FIELD],
itemRarity,
cacheData[ITEM_CACHE_FIELD.ILEVEL],
itemMinLevel,
_G["ITEM_CLASS_" .. tostring(classID)] or classID,
_G["ITEM_SUB_CLASS_" .. tostring(classID) .. "_" .. tostring(subclassID)] or subclassID,
cacheData[ITEM_CACHE_FIELD.STACKCOUNT],
SHARED_INVTYPE_BY_ID[equipLocID],
"Interface\\Icons\\" .. tostring(cacheData[ITEM_CACHE_FIELD.TEXTURE] or "INV_Misc_QuestionMark"),
cacheData[ITEM_CACHE_FIELD.VENDORPRICE],
itemID,
classID,
subclassID,
equipLocID
end
local function GetItemInfoCompat(item)
local name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice = GetItemInfo(item)
if name then
return name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice
end
return C_Item.GetItemInfoCache(item)
end
local function FireItemCacheCallbacks(itemID)
local callbacks = ITEM_CACHE_CALLBACKS[itemID]
if not callbacks then
return
end
local name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice = GetItemInfoCompat(itemID)
if not name then
return
end
ITEM_CACHE_CALLBACKS[itemID] = nil
for _, callback in ipairs(callbacks) do
callback(itemID, name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice)
end
end
local function QueueItemCacheCallback(item, callback)
if not callback then
return
end
local itemID = tonumber(item)
if not itemID and type(item) == "string" then
itemID = tonumber(item:match("item:(%d+)") or item:match("^(%d+)$"))
end
if not itemID then
return
end
ITEM_CACHE_CALLBACKS[itemID] = ITEM_CACHE_CALLBACKS[itemID] or {}
table.insert(ITEM_CACHE_CALLBACKS[itemID], callback)
end
local itemCacheFrame
local function EnsureItemCacheFrame()
if itemCacheFrame or not CreateFrame then
return
end
itemCacheFrame = CreateFrame("Frame")
itemCacheFrame:RegisterEvent("GET_ITEM_INFO_RECEIVED")
itemCacheFrame:SetScript("OnEvent", function(_, _, itemID)
if itemID then
FireItemCacheCallbacks(itemID)
else
for queuedItemID in pairs(ITEM_CACHE_CALLBACKS) do
FireItemCacheCallbacks(queuedItemID)
end
end
end)
end
C_Item.GetItemInfo = function(item, skipClientCache, callback, ...)
local name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice
if ExistingCItemGetItemInfo then
name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice = ExistingCItemGetItemInfo(item, skipClientCache, nil, ...)
end
if not name then
name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice = GetItemInfoCompat(item)
end
if callback then
if name then
callback(GetItemIDFromAny(item) or item, name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice)
else
QueueItemCacheCallback(item, callback)
EnsureItemCacheFrame()
end
end
return name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice
end
C_Item.RequestServerCache = function(item, callback)
local name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice = GetItemInfoCompat(item)
if callback then
if name then
callback(GetItemIDFromAny(item) or item, name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice)
elseif ExistingCItemRequestServerCache then
return ExistingCItemRequestServerCache(item, callback)
else
QueueItemCacheCallback(item, callback)
EnsureItemCacheFrame()
end
end
return name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice
end
C_Item.GetItemIDFromString = function(item)
local itemID = ExistingCItemGetItemIDFromString and ExistingCItemGetItemIDFromString(item)
if itemID then
return itemID
end
EnsureItemsCache()
return GetItemIDFromAny(item)
end
EnsureItemsCache()
if not Mixin then
function Mixin(object, ...)
for i = 1, select("#", ...) do
local mixin = select(i, ...)
for key, value in pairs(mixin) do
object[key] = value
end
end
return object
end
end
if not CreateFromMixins then
function CreateFromMixins(...)
return Mixin({}, ...)
end
end
if not CreateAndInitFromMixin then
function CreateAndInitFromMixin(mixin, ...)
local object = CreateFromMixins(mixin)
object:Init(...)
return object
end
end
if not InlineHyperlinkFrame_SimpleHTMLAsFontString_OnLoad then
function InlineHyperlinkFrame_SimpleHTMLAsFontString_OnLoad(self)
if PKBT_SimpleHTMLAsFontStringMixin then
Mixin(self, PKBT_SimpleHTMLAsFontStringMixin)
if self.OnLoadSimpleHTMLAsFontString then
self:OnLoadSimpleHTMLAsFontString()
end
elseif self.GetRegions and not self:GetRegions() then
local meta = getmetatable(self)
local methods = meta and meta.__index
if methods and methods.SetText then
methods.SetText(self, "")
end
end
end
end
local function InstallTextureAtlasShim()
if not CreateFrame then
return
end
local texture = CreateFrame("Frame"):CreateTexture(nil, "ARTWORK")
local meta = getmetatable(texture)
local methods = meta and meta.__index
local atlasToTexture = {
["adventureguide-pane-large"] = {335, 337, 0.001953, 0.656250, 0.320312, 0.978516, "Interface\\EncounterJournal\\AdventureGuide"},
["adventureguide-pane-small"] = {344, 161, 0.001953, 0.673828, 0.001953, 0.316406, "Interface\\EncounterJournal\\AdventureGuide"},
["adventureguide-ring"] = {94, 95, 0.775391, 0.958984, 0.001953, 0.187500, "Interface\\EncounterJournal\\AdventureGuide"},
["adventureguide-rewardring"] = {48, 48, 0.677734, 0.771484, 0.001953, 0.095703, "Interface\\EncounterJournal\\AdventureGuide"},
["adventureguide-redx"] = {35, 34, 0.677734, 0.746094, 0.175781, 0.242188, "Interface\\EncounterJournal\\AdventureGuide"},
["UI-EJ-Classic"] = {786, 425, 0.000977, 0.768555, 0.417969, 0.833008, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds1"},
["UI-EJ-BurningCrusade"] = {786, 425, 0.000977, 0.768555, 0.000977, 0.416016, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds1"},
["UI-EJ-WrathoftheLichKing"] = {786, 425, 0.000977, 0.768555, 0.417969, 0.833008, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds2"},
["UI-EJ-Cataclysm"] = {786, 425, 0.000977, 0.768555, 0.000977, 0.416016, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds2"},
["UI-EJ-MistsofPandaria"] = {786, 425, 0.000977, 0.768555, 0.000977, 0.416016, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds3"},
["UI-EJ-WarlordsofDraenor"] = {786, 425, 0.000977, 0.768555, 0.417969, 0.833008, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds3"},
["UI-EJ-BattleforAzeroth"] = {786, 425, 0.000977, 0.768555, 0.000977, 0.416016, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds4"},
["UI-EJ-Legion"] = {786, 425, 0.000977, 0.768555, 0.417969, 0.833008, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds4"},
["UI-EJ-Dragonflight"] = {786, 425, 0.000977, 0.768555, 0.000977, 0.416016, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds5"},
["UI-EJ-Shadowlands"] = {786, 425, 0.000977, 0.768555, 0.417969, 0.833008, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds5"},
["UI-EJ-TheWarWithin"] = {786, 425, 0.000977, 0.768555, 0.000977, 0.416016, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds6"},
["UI-EJ-Midnight"] = {786, 425, 0.000977, 0.768555, 0.417969, 0.833008, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds6"},
}
if type(methods) == "table" and not methods.SetAtlas then
methods.SetAtlas = function(self, atlas, useAtlasSize)
local atlasInfo = atlasToTexture[atlas]
if atlasInfo then
self:SetTexture(atlasInfo[7])
self:SetTexCoord(atlasInfo[3], atlasInfo[4], atlasInfo[5], atlasInfo[6])
if useAtlasSize then
self:SetSize(atlasInfo[1], atlasInfo[2])
end
elseif SetAtlasTexture then
local ok = pcall(SetAtlasTexture, self, atlas)
if not ok then
self:SetTexture(nil)
end
else
self:SetTexture(nil)
end
end
end
end
InstallTextureAtlasShim()
local function InstallFrameMethodShims()
if not CreateFrame then
return
end
local frame = CreateFrame("Frame")
local meta = getmetatable(frame)
local methods = meta and meta.__index
if type(methods) ~= "table" or methods.SetParentArray then
return
end
methods.SetParentArray = function(self, arrayName)
local parent = self:GetParent()
if not parent or not arrayName then
return
end
parent[arrayName] = parent[arrayName] or {}
table.insert(parent[arrayName], self)
end
end
InstallFrameMethodShims()
if not EncounterJournal_SetSearchPreviewSelection then
function EncounterJournal_SetSearchPreviewSelection(selectedIndex)
if not EncounterJournal or not EncounterJournal.searchBox then
return
end
local searchBox = EncounterJournal.searchBox
searchBox.selectedIndex = selectedIndex or 1
if searchBox.searchPreview then
for index, button in ipairs(searchBox.searchPreview) do
if button.selectedTexture then
if index == searchBox.selectedIndex then
button.selectedTexture:Show()
else
button.selectedTexture:Hide()
end
end
end
end
if searchBox.showAllResults and searchBox.showAllResults.selectedTexture then
searchBox.showAllResults.selectedTexture:Hide()
end
end
end
@@ -0,0 +1,79 @@
-- Optional AIO bridge: lets the server push fresh Encounter Journal data at runtime.
-- The static fallback is loaded from Generated_EncounterJournal.lua, so the journal works
-- without ever calling ApplyEncounterJournalData.
local function ApplyEncounterJournalData(data)
if type(data) ~= "table" then
return
end
local function NormalizeTierInstances(tiers, tierInstances)
if type(tiers) ~= "table" or type(tierInstances) ~= "table" then
return tierInstances
end
local tierIDs = {}
for _, tierInfo in ipairs(tiers) do
if type(tierInfo) == "table" then
tierIDs[tierInfo[1]] = true
end
end
local hasTierKey = false
for tierID in pairs(tierIDs) do
if tierInstances[tierID] then
hasTierKey = true
break
end
end
if not hasTierKey then
return tierInstances
end
local normalized = {}
for tierID, instanceList in pairs(tierInstances) do
if tierIDs[tierID] and type(instanceList) == "table" then
for _, instanceID in ipairs(instanceList) do
normalized[instanceID] = normalized[instanceID] or {}
table.insert(normalized[instanceID], tierID)
end
else
normalized[tierID] = instanceList
end
end
return normalized
end
local tiers = data.JOURNALTIER or data.tiers or JOURNALTIER
local tierInstances = data.JOURNALTIERXINSTANCE or data.tierInstances or JOURNALTIERXINSTANCE
JOURNALINSTANCE = data.JOURNALINSTANCE or data.instances or JOURNALINSTANCE
JOURNALENCOUNTER = data.JOURNALENCOUNTER or data.encounters or JOURNALENCOUNTER
JOURNALENCOUNTERCREATURE = data.JOURNALENCOUNTERCREATURE or data.creatures or JOURNALENCOUNTERCREATURE
JOURNALENCOUNTERITEM = data.JOURNALENCOUNTERITEM or data.items or JOURNALENCOUNTERITEM
JOURNALENCOUNTERSECTION = data.JOURNALENCOUNTERSECTION or data.sections or JOURNALENCOUNTERSECTION
JOURNALTIER = tiers
JOURNALTIERXINSTANCE = NormalizeTierInstances(tiers, tierInstances)
JOURNAACTUALRAIDS = data.JOURNAACTUALRAIDS or data.actualRaids or JOURNAACTUALRAIDS
if C_EncounterJournal and C_EncounterJournal.ReloadData then
C_EncounterJournal.ReloadData()
end
end
MoonWell_ApplyEncounterJournalData = ApplyEncounterJournalData
local staticData = MoonWellEncounterJournalData or EncounterJournalData
if staticData then
ApplyEncounterJournalData(staticData)
end
if AIO then
local Handler = AIO.AddHandlers("MoonWellEncounterJournal", {})
function Handler.SetData(_, data)
ApplyEncounterJournalData(data)
end
end
@@ -0,0 +1,157 @@
-- Hide the Sirus Encounter Journal panels we don't ship: suggested content,
-- LootJournal/ItemBrowser, search box, retail-era top tabs. Dungeons + Raids stay.
-- The shipped Sirus tab layout assumes all four content tabs are visible; this
-- skin repositions the two tabs we keep.
local function HideFrame(frame)
if frame then
frame:Hide()
end
end
local function HideTabKeepEnabled(tab)
if not tab then
return
end
tab:Hide()
-- Keep the tab enabled so Sirus EJ_OnShow does not fall back to the
-- suggest-tier code path (EJSuggestTab_GetPlayerTierIndex etc).
end
local function ConfigureTierTab(tab, text)
if not tab then
return
end
tab:SetText(text or "")
if tab.SetNormalFontObject and GameFontNormal then
tab:SetNormalFontObject(GameFontNormal)
end
if tab.SetHighlightFontObject and GameFontHighlight then
tab:SetHighlightFontObject(GameFontHighlight)
end
if tab.SetDisabledFontObject and GameFontNormal then
tab:SetDisabledFontObject(GameFontNormal)
end
local fontString = tab:GetFontString()
if fontString then
fontString:SetText(text or "")
fontString:ClearAllPoints()
fontString:SetPoint("CENTER", tab, "CENTER", 0, -3)
if fontString.SetDrawLayer then
fontString:SetDrawLayer("OVERLAY", 7)
end
end
tab:SetWidth(math.max((tab:GetTextWidth() or 0) + 28, 92))
end
local function PositionVisibleTierTabs(instanceSelect)
local guidebookTab = instanceSelect and instanceSelect.guidebookTab
local dungeonsTab = instanceSelect and instanceSelect.dungeonsTab
local raidsTab = instanceSelect and instanceSelect.raidsTab
if not dungeonsTab or not raidsTab then
return
end
ConfigureTierTab(guidebookTab, GUIDEBOOK or "Guidebook")
ConfigureTierTab(dungeonsTab, INSTANCES or "Instances")
ConfigureTierTab(raidsTab, RAIDS or "Raids")
dungeonsTab:ClearAllPoints()
dungeonsTab:SetPoint("BOTTOMLEFT", instanceSelect, "TOPLEFT", 150, -45)
raidsTab:ClearAllPoints()
raidsTab:SetPoint("BOTTOMLEFT", dungeonsTab, "BOTTOMRIGHT", 22, 0)
end
local function GetVisibleSelectedTabID(instanceSelect)
if not instanceSelect then
return
end
local selectedTab = instanceSelect.selectedTab
local dungeonsTab = instanceSelect.dungeonsTab
local raidsTab = instanceSelect.raidsTab
if raidsTab and selectedTab == raidsTab.id then
return selectedTab
end
if dungeonsTab then
dungeonsTab.id = dungeonsTab.id or dungeonsTab:GetID()
return dungeonsTab.id
end
end
local function SelectVisibleInstanceTab(journal)
local instanceSelect = journal and journal.instanceSelect
if not instanceSelect then
return
end
local selectedTab = GetVisibleSelectedTabID(instanceSelect)
if selectedTab then
instanceSelect.selectedTab = selectedTab
end
if journal:IsShown()
and instanceSelect:IsShown()
and (not journal.encounter or not journal.encounter:IsShown())
and selectedTab
and EJ_ContentTab_Select
then
EJ_ContentTab_Select(selectedTab)
end
end
local function ApplyDungeonAndRaidOnlySkin()
local journal = EncounterJournal
if not journal then
return
end
-- Sirus EJ_OnLoad sets numTabs at the very end; if anything earlier in
-- OnLoad bails out, PanelTemplates_UpdateTabs blows up later with a nil
-- for-limit when OnShow calls PanelTemplates_SetTab. Guard it.
if journal.numTabs == nil then
journal.numTabs = 4
end
HideFrame(journal.searchBox)
HideFrame(journal.searchResults)
HideFrame(journal.suggestFrame)
HideFrame(journal.LootJournal)
HideFrame(journal.LootJournalItems)
HideFrame(journal.TutorialButton)
for index = 1, 4 do
HideFrame(_G["EncounterJournalTab" .. index])
end
local instanceSelect = journal.instanceSelect
if instanceSelect then
HideTabKeepEnabled(instanceSelect.suggestTab)
HideTabKeepEnabled(instanceSelect.LootJournalTab)
-- Sirus EJ_OnShow forwards instanceSelect.selectedTab into
-- EJ_ContentTab_Select; if it's nil the tab loop matches nothing and
-- selectedTab.selectedGlow blows up. Default to the dungeons tab.
local dungeonsTab = instanceSelect.dungeonsTab
if dungeonsTab then
dungeonsTab.id = dungeonsTab.id or dungeonsTab:GetID()
end
PositionVisibleTierTabs(instanceSelect)
SelectVisibleInstanceTab(journal)
end
end
MoonWell_EncounterJournalApplyDungeonMode = ApplyDungeonAndRaidOnlySkin
if EncounterJournal then
ApplyDungeonAndRaidOnlySkin()
EncounterJournal:HookScript("OnShow", ApplyDungeonAndRaidOnlySkin)
end
@@ -0,0 +1,34 @@
<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">
<Font name="QuestFont_Super_Huge" font="Fonts\MORPHEUS.ttf" virtual="true">
<FontHeight val="24"/>
<Color r="1" g=".82" b="0"/>
</Font>
<Font name="QuestFont_Super_Huge2" font="Fonts\MORPHEUS.ttf" virtual="true">
<FontHeight val="26"/>
<Shadow>
<Offset x="2" y="-2"/>
<Color r="0" g="0" b="0"/>
</Shadow>
</Font>
<Font name="GameFontNormalMed1" inherits="GameFontNormal" virtual="true">
<FontHeight val="12"/>
<Shadow>
<Offset x="1" y="-1"/>
<Color r="0" g="0" b="0"/>
</Shadow>
<Color r="1" g=".82" b="0"/>
</Font>
<Font name="QuestFont22" font="Fonts\MORPHEUS.ttf" virtual="true">
<FontHeight val="22"/>
<Color r="1" g=".82" b="0"/>
</Font>
<Font name="QuestFont_Enormous" font="Fonts\MORPHEUS.ttf" virtual="true">
<FontHeight val="30"/>
<Color r="1" g=".82" b="0"/>
</Font>
<Font name="DestinyFontHuge" font="Fonts\MORPHEUS.ttf" virtual="true">
<FontHeight val="32"/>
<Color r="1" g=".82" b="0"/>
</Font>
</Ui>
@@ -0,0 +1,150 @@
-- Minimal Frame metatable extensions used by Sirus EJ. Replaces Sirus
-- SharedExtendedMethods.lua, which clobbers SetUnit/PlayerModel methods with
-- retail-only API (ResetAllSpells, SetSpell, SetUnitRaw, GetDisplayIDs,
-- C_CustomizationsCollection.*) and breaks TabardFrame/DressUpFrame on 3.3.5.
local function getMeta(objType)
local frame = CreateFrame(objType)
frame:Hide()
return getmetatable(frame).__index
end
local function RegisterCustomEventNoop() end
local function UnregisterCustomEventNoop() end
local function SetShown(self, shown)
if shown then
self:Show()
else
self:Hide()
end
end
local function SetParentArray(self, arrayName)
local parent = self:GetParent()
if not parent or not arrayName then
return
end
parent[arrayName] = parent[arrayName] or {}
table.insert(parent[arrayName], self)
end
local function SetEnabled(self, enabled)
if enabled then
self:Enable()
else
self:Disable()
end
end
-- Sirus EJ calls Frame/Button:IsMouseOverEx() (Cataclysm+ retail-only).
-- Map to the 3.3.5 IsMouseOver().
local function IsMouseOverEx(self)
return self:IsMouseOver()
end
local metas = {
getMeta("Frame"),
getMeta("Button"),
getMeta("CheckButton"),
getMeta("EditBox"),
getMeta("Slider"),
getMeta("StatusBar"),
getMeta("ScrollFrame"),
getMeta("MessageFrame"),
getMeta("ScrollingMessageFrame"),
}
for _, meta in ipairs(metas) do
if type(meta) == "table" then
if not meta.RegisterCustomEvent then
meta.RegisterCustomEvent = RegisterCustomEventNoop
end
if not meta.UnregisterCustomEvent then
meta.UnregisterCustomEvent = UnregisterCustomEventNoop
end
if not meta.SetShown then
meta.SetShown = SetShown
end
if not meta.SetParentArray then
meta.SetParentArray = SetParentArray
end
if not meta.SetEnabled then
meta.SetEnabled = SetEnabled
end
if not meta.IsMouseOverEx then
meta.IsMouseOverEx = IsMouseOverEx
end
end
end
-- Sirus EJ calls FontString:IsTruncated() (Cataclysm+) to detect overflow for
-- header tooltips. Always return false on 3.3.5 — we just don't show the
-- "expand" tooltip.
do
local frame = CreateFrame("Frame")
local fs = frame:CreateFontString()
local meta = getmetatable(fs)
local methods = meta and meta.__index
if type(methods) == "table" and not methods.IsTruncated then
methods.IsTruncated = function() return false end
end
end
-- Sirus EJ description fields are SimpleHTML frames; the EJ logic does
-- `self.description:GetText()` (Cataclysm+ on SimpleHTML). 3.3.5 SimpleHTML
-- only has SetText, no GetText. Hook SetText to remember the last text and
-- expose GetText that returns it.
do
local ok, simple = pcall(CreateFrame, "SimpleHTML")
if ok and simple then
local meta = getmetatable(simple)
local methods = meta and meta.__index
if type(methods) == "table" then
local origSetText = methods.SetText
if origSetText and not methods.GetText then
methods.SetText = function(self, text, ...)
self.__moonwellText = text
return origSetText(self, text, ...)
end
methods.GetText = function(self)
return self.__moonwellText
end
end
end
end
end
-- Sirus EJ calls Texture:SetPortrait(displayID) for boss/creature portraits.
-- 3.3.5 has no SetPortraitTextureFromCreatureDisplayID equivalent, so leave
-- the texture blank — the static UI-EJ-BOSS-* icon set elsewhere is enough.
do
local portraitByDisplayInfo = {}
local data = MoonWellEncounterJournalData
if data and data.creatures then
for _, creatureList in pairs(data.creatures) do
for _, creature in ipairs(creatureList) do
local displayInfo = creature[3]
local icon = creature[4]
if displayInfo and displayInfo > 0 and icon and icon ~= "" then
portraitByDisplayInfo[displayInfo] = icon
end
end
end
end
-- Ogom is a nested encounter variant and has no top-level portrait row.
portraitByDisplayInfo[6709] = [[Interface\EncounterJournal\UI-EJ-BOSS-Jammalan the Prophet]]
MoonWellEncounterPortraitByDisplayInfo = portraitByDisplayInfo
local frame = CreateFrame("Frame")
local texture = frame:CreateTexture()
local meta = getmetatable(texture)
local methods = meta and meta.__index
if type(methods) == "table" and not methods.SetPortrait then
methods.SetPortrait = function(self, displayInfo)
self:SetTexture(portraitByDisplayInfo[displayInfo] or QUESTION_MARK_ICON)
self:SetTexCoord(0, 1, 0, 1)
end
end
end
@@ -0,0 +1,41 @@
-- Force C_EncounterJournal to copy the freshly loaded JOURNAL* globals into its
-- internal locals. Without this the EJ data API returns nil and the instance
-- list comes up empty.
if C_EncounterJournal and C_EncounterJournal.ReloadData then
C_EncounterJournal.ReloadData()
end
-- EJ_GetLootFilter returns nil until a class filter is set; Sirus
-- EncounterJournal_UpdateFilterString does `if classID > 0 then`, which blows
-- up when classID is nil. Coerce to 0.
if EJ_GetLootFilter then
local _GetLootFilter = EJ_GetLootFilter
function EJ_GetLootFilter()
return _GetLootFilter() or 0
end
end
-- Sirus C_AdventureJournal sometimes fails to fully initialize on 3.3.5
-- because PRIVATE.Initialize() inside Utils/C_AdventureJournal.lua hits an
-- unsupported API mid-way and aborts before `C_AdventureJournal = {}` is
-- reached. Provide a no-op stub so suggestFrame OnShow doesn't blow up.
C_AdventureJournal = C_AdventureJournal or {}
local function noop() end
C_AdventureJournal.UpdateSuggestions = C_AdventureJournal.UpdateSuggestions or noop
C_AdventureJournal.GetPrimaryOffset = C_AdventureJournal.GetPrimaryOffset or function() return 0 end
C_AdventureJournal.SetPrimaryOffset = C_AdventureJournal.SetPrimaryOffset or noop
C_AdventureJournal.GetNumAvailableSuggestions = C_AdventureJournal.GetNumAvailableSuggestions or function() return 0 end
C_AdventureJournal.GetSuggestion = C_AdventureJournal.GetSuggestion or function() end
C_AdventureJournal.CanBeShown = C_AdventureJournal.CanBeShown or function() return true end
C_AdventureJournal.ReloadData = C_AdventureJournal.ReloadData or noop
C_AdventureJournal.ActivateEntry = C_AdventureJournal.ActivateEntry or noop
C_AdventureJournal.GetSuggestions = C_AdventureJournal.GetSuggestions or function(out)
if type(out) == "table" then
for k in pairs(out) do out[k] = nil end
end
return out or {}
end
C_AdventureJournal.GetReward = C_AdventureJournal.GetReward or function() end
C_AdventureJournal.GetPrimaryReward = C_AdventureJournal.GetPrimaryReward or function() end
C_AdventureJournal.GetSecondaryReward = C_AdventureJournal.GetSecondaryReward or function() end
@@ -0,0 +1,69 @@
-- Explicit entry point for 3.3.5 clients, which do not have the retail
-- EncounterJournal micro button.
local function MoonWell_ToggleEncounterJournal()
if not EncounterJournal then
return
end
if EncounterJournal:IsShown() then
HideUIPanel(EncounterJournal)
return
end
if MoonWell_EncounterJournalApplyDungeonMode then
MoonWell_EncounterJournalApplyDungeonMode()
end
ShowUIPanel(EncounterJournal)
if EJPlayerGuide_OpenFrame then
EJPlayerGuide_OpenFrame()
elseif EncounterJournal.instanceSelect and EncounterJournal.instanceSelect.guideTab and EJ_ContentTab_Select then
EJ_ContentTab_Select(EncounterJournal.instanceSelect.guideTab:GetID())
end
end
SLASH_MOONWELLENCOUNTERJOURNAL1 = "/ej"
SLASH_MOONWELLENCOUNTERJOURNAL2 = "/journal"
SLASH_MOONWELLENCOUNTERJOURNAL3 = "/encounterjournal"
SlashCmdList.MOONWELLENCOUNTERJOURNAL = MoonWell_ToggleEncounterJournal
MoonWell_ToggleEncounterJournal = MoonWell_ToggleEncounterJournal
local function MoonWell_EncounterJournalMicroButton_OnEnter(self)
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:AddLine("Путеводитель по приключениям", 1, 1, 1)
GameTooltip:Show()
end
local function MoonWell_CreateEncounterJournalMicroButton()
local button = EncounterJournalMicroButton
if not button then
local iconTexture = "Interface\\EncounterJournal\\UI-EJ-PortraitIcon"
button = CreateFrame("Button", "EncounterJournalMicroButton", MainMenuBarArtFrame or UIParent)
button:SetSize(32, 40)
button:SetPoint("BOTTOMLEFT", LFDMicroButton or MainMenuMicroButton or HelpMicroButton or UIParent, "BOTTOMRIGHT", 0, 0)
button:RegisterForClicks("LeftButtonUp")
button:SetNormalTexture(iconTexture)
button:SetPushedTexture(iconTexture)
button:SetDisabledTexture(iconTexture)
button:SetHighlightTexture(iconTexture)
button:SetPushedTextOffset(1, -1)
local highlight = button:GetHighlightTexture()
if highlight then
highlight:SetBlendMode("ADD")
highlight:SetAlpha(0.55)
end
end
button:SetScript("OnClick", MoonWell_ToggleEncounterJournal)
button:SetScript("OnEnter", MoonWell_EncounterJournalMicroButton_OnEnter)
button:SetScript("OnLeave", GameTooltip_Hide)
button:Show()
return button
end
MoonWell_CreateEncounterJournalMicroButton()
@@ -0,0 +1,24 @@
<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">
<!--
Sirus EncounterJournalScrollBarTemplate inherits MinimalScrollBarTemplate
and references self.trackBG inside its inline OnLoad. On 3.3.5 the
parentKey="trackBG" attribute on the inherited template does not
propagate, leaving self.trackBG nil and crashing OnLoad. Define the
template here ourselves with trackBG as a direct child texture, before
Sirus Custom_EncounterJournal.xml gets a chance to register its own.
-->
<Slider name="EncounterJournalScrollBarTemplate" inherits="MinimalScrollBarTemplate" parentKey="ScrollBar" virtual="true">
<Anchors>
<Anchor point="TOPRIGHT" x="0" y="-17"/>
<Anchor point="BOTTOMRIGHT" x="0" y="17"/>
</Anchors>
<Layers>
<Layer level="ARTWORK">
<Texture parentKey="trackBG" setAllPoints="true">
<Color r="0.2" g="0.13" b="0.08" a="0.25"/>
</Texture>
</Layer>
</Layers>
</Slider>
</Ui>
@@ -0,0 +1,40 @@
-- Sirus EJ XML for lootScroll/searchResults.scrollFrame omits the scrollUp /
-- scrollDown buttons that the stock 3.3.5 HybridScrollFrame implementation
-- requires (HybridScrollFrame_Update / HybridScrollFrame_UpdateButtonStates
-- index them directly). Wire up no-op buttons so the stock helpers don't
-- index nil and the rest of the EJ flow works.
local function AttachFakeScrollButtons(scrollFrame)
if not scrollFrame then
return
end
if not scrollFrame.scrollUp then
local btn = CreateFrame("Button", nil, scrollFrame)
btn:Hide()
btn:SetSize(1, 1)
btn:SetPoint("TOPLEFT")
scrollFrame.scrollUp = btn
end
if not scrollFrame.scrollDown then
local btn = CreateFrame("Button", nil, scrollFrame)
btn:Hide()
btn:SetSize(1, 1)
btn:SetPoint("BOTTOMLEFT")
scrollFrame.scrollDown = btn
end
if scrollFrame.scrollBar and not scrollFrame.scrollBar.thumbTexture then
scrollFrame.scrollBar.thumbTexture = scrollFrame.scrollBar:GetThumbTexture()
or scrollFrame.scrollBar:CreateTexture(nil, "ARTWORK")
end
if scrollFrame.scrollBar and not scrollFrame.scrollBar.trackBG then
scrollFrame.scrollBar.trackBG = scrollFrame.scrollBar:CreateTexture(nil, "BACKGROUND")
end
end
if EncounterJournal then
AttachFakeScrollButtons(EncounterJournal.encounter
and EncounterJournal.encounter.info
and EncounterJournal.encounter.info.lootScroll)
AttachFakeScrollButtons(EncounterJournal.searchResults
and EncounterJournal.searchResults.scrollFrame)
end
@@ -0,0 +1,38 @@
-- Restrict Sirus EJ data tables to expansions that exist on 3.3.5a (Classic, TBC, WotLK).
-- Must run AFTER Generated_EncounterJournal.lua (where the globals are populated) and BEFORE
-- Utils/C_EncounterJournal.lua reads them into locals on first ReloadData.
local ALLOWED_TIER_IDS = {
[68] = true, -- Classic
[70] = true, -- Burning Crusade
[72] = true, -- Wrath of the Lich King
}
local TIER_FIELD_ID = 1
if type(JOURNALTIER) == "table" then
local filtered = {}
for _, tierInfo in ipairs(JOURNALTIER) do
if ALLOWED_TIER_IDS[tierInfo[TIER_FIELD_ID]] then
filtered[#filtered + 1] = tierInfo
end
end
JOURNALTIER = filtered
end
if type(JOURNALTIERXINSTANCE) == "table" then
for instanceID, tierList in pairs(JOURNALTIERXINSTANCE) do
local kept
for _, tierID in ipairs(tierList) do
if ALLOWED_TIER_IDS[tierID] then
kept = kept or {}
kept[#kept + 1] = tierID
end
end
if kept then
JOURNALTIERXINSTANCE[instanceID] = kept
else
JOURNALTIERXINSTANCE[instanceID] = nil
end
end
end
@@ -0,0 +1,6 @@
-- Keep tier tables available for the 3.3.5a dungeon-list shim.
-- C_EncounterJournal imports Sirus globals into locals and clears the globals.
MoonWell_EncounterJournalTiers = CopyTable(JOURNALTIER or {})
MoonWell_EncounterJournalTierInstances = CopyTable(JOURNALTIERXINSTANCE or {})
@@ -0,0 +1,75 @@
ADVENTURE_JOURNAL = "Путеводитель по приключениям"
AJ_ACTION_TEXT_JOIN_BATTLE = "Вступить в бой"
AJ_ACTION_TEXT_JOIN_GROUP = "Вступить в группу"
AJ_ACTION_TEXT_OPEN_EJ = "Открыть журнал"
AJ_ACTION_TEXT_SHOW_QUEST = "Открыть задания"
AJ_ACTION_TEXT_START_HUNT = "Открыть охоту"
AJ_ACTION_TEXT_START_QUEST = "Начать задание"
AJ_LFG_REWARD_DEFAULT_IRANGE_TEXT = "%s: здесь можно получить предметы экипировки %i - %i уровней."
AJ_LFG_REWARD_DEFAULT_TEXT = "%s: здесь можно получить предметы экипировки %i-го уровня."
AJ_LFG_REWARD_DIFFICULTY_IRANGE_TEXT = "%s (%s): здесь можно получить предметы экипировки %i - %i уровней."
AJ_LFG_REWARD_DIFFICULTY_TEXT = "%s (%s): здесь можно получить предметы экипировки %i-го уровня."
AJ_PRIMARY_REWARD_TEXT = "Награда:"
AJ_REWARD_CLICK_TEXT = "Щелкните для просмотра всех наград."
AJ_SAMPLE_REWARD_TEXT = "\n\nПример награды:"
AJ_SUGGESTED_CONTENT_TAB = "Рекомендуемый контент"
EJ_CLASS_FILTER = "Фильтр по классу: %s"
EJ_FILTER_ALL_CLASS = "Все классы"
EJ_INSTANCE_REQUIREMENT_ACHIEVEMENTS = "Достижения:"
EJ_INSTANCE_REQUIREMENT_DIFFICULTY = "Сложность: %s"
EJ_INSTANCE_REQUIREMENT_ITEM_LEVEL = "Средний уровень предметов: %s"
EJ_INSTANCE_REQUIREMENT_LABLE = "Требования:"
EJ_INSTANCE_REQUIREMENT_LEVEL = "Уровень: %s"
EJ_INSTANCE_REQUIREMENT_LEVEL_RANGE = "Уровень: %s-%s"
EJ_INSTANCE_REQUIREMENT_NONE = "Вход свободный"
EJ_INSTANCE_REQUIREMENT_QUESTS = "Задания:"
EJ_INSTANCE_REQUIREMENT_RAID_GROUP = "Для входа требуется быть участником рейдовой группы"
EJ_SET_ITEM_LEVEL = "|cffcc4040[%s]|r Уровень предмета: %d"
ENCOUNTER_JOURNAL_ABILITY = "Способность"
ENCOUNTER_JOURNAL_ENCOUNTER = "Босс"
ENCOUNTER_JOURNAL_ENCOUNTER_ADD = "Помощник"
ENCOUNTER_JOURNAL_FIND_GROUP = "Найти\nгруппу"
ENCOUNTER_JOURNAL_FIND_RAID = "Найти\nрейд"
ENCOUNTER_JOURNAL_INSTANCE = "Подземелье"
ENCOUNTER_JOURNAL_ITEM = "Предмет"
ENCOUNTER_JOURNAL_SEARCH_RESULTS = "Результаты поиска для \"%s\" (%d)"
ENCOUNTER_JOURNAL_SHOW_MAP = "Показать\nкарту"
ENCOUNTER_JOURNAL_SHOW_SEARCH_RESULTS = "Показать все результаты: %d"
LOOT_JOURNAL_ITEM_SETS = "Комплекты"
ENCOUNTER_JOURNAL_NAVIGATION_HOME = "Главная"
-- Путеводитель — вкладка
MW_PLAYER_GUIDE = "Путеводитель"
MW_PLAYER_GUIDE_REFRESH = "Обновить"
-- Заголовки секций
MW_PLAYER_GUIDE_OBJECTIVES = "Задачи этапа"
MW_PLAYER_GUIDE_RECS = "Подходящие подземелья"
MW_PLAYER_GUIDE_REWARDS = "Награды за этап"
-- Шапка этапа
MW_PLAYER_GUIDE_STAGE_OF = "ЭТАП %d ИЗ %d"
MW_PLAYER_GUIDE_STAGE_PROGRESS = "ПРОГРЕСС ЭТАПА"
-- Пустое состояние
MW_PLAYER_GUIDE_EMPTY_TITLE = "Путеводитель ещё не открыт"
MW_PLAYER_GUIDE_EMPTY_BODY = "Данные о вашем прогрессе ещё не получены с сервера. " ..
"Это может занять несколько секунд после входа в игру."
-- Загрузка
MW_PLAYER_GUIDE_LOADING_TITLE = "ЗАПРАШИВАЕМ ХРОНИКИ"
MW_PLAYER_GUIDE_LOADING_BODY = "Ожидание данных от Путеводителя"
-- Caps-плашки типов целей
MW_PG_TYPE_LEVEL = "УРОВЕНЬ"
MW_PG_TYPE_QUEST = "ЗАДАНИЕ"
MW_PG_TYPE_QUEST_CHAIN = "ЦЕПОЧКА"
MW_PG_TYPE_DUNGEON = "ПОДЗЕМЕЛЬЕ"
MW_PG_TYPE_RAID = "РЕЙД"
MW_PG_TYPE_BOSS = "БОСС"
MW_PG_TYPE_ACHIEVEMENT = "ДОСТИЖЕНИЕ"
MW_PG_TYPE_REPUTATION = "РЕПУТАЦИЯ"
MW_PG_TYPE_PROFESSION = "ПРОФЕССИЯ"
MW_PG_TYPE_ITEM = "ПРЕДМЕТ"
MW_PG_TYPE_CURRENCY = "ВАЛЮТА"
MW_PG_TYPE_CUSTOM = "ЗАДАЧА"
@@ -0,0 +1,427 @@
--[[-----------------------------------------------------------------------------------------------
For a hybrid scroll frame with buttons of varying size, set .dynamic on the scroll frame
to be a function which will take the offset and return:
1. how many buttons the offset is completely past
2. how many pixels the offset is into the topmost button
So with buttons of size 20, .dynamic(0) should return 0,0 and .dynamic(34) should return 1,14
-----------------------------------------------------------------------------------------------]]--
local round = function (num) return math.floor(num + .5); end
local function HybridScrollFrame_SetVerticalScrollOffset(self, offset)
if self.SetVerticalScroll then
self:SetVerticalScroll(offset);
end
end
local function HybridScrollFrame_UpdateScrollChildRectSafe(self)
if self.UpdateScrollChildRect then
self:UpdateScrollChildRect();
end
end
local function HybridScrollFrame_GetScrollFrame(self)
if not self then
return self;
end
if self.SetVerticalScroll then
return self;
end
local name = self.GetName and self:GetName();
local scrollFrame = self.Container or (name and _G[name.."Container"]);
return scrollFrame or self;
end
local function HybridScrollFrame_GetScrollBar(self)
if self.scrollBar then
return self.scrollBar;
end
local name = self.GetName and self:GetName();
local scrollBar = self.ScrollBar or (name and (_G[name.."ScrollBar"] or _G[name.."ContainerScrollBar"]));
if scrollBar then
self.scrollBar = scrollBar;
end
return scrollBar;
end
function HybridScrollFrame_OnLoad (self)
self:EnableMouse(true);
end
function HybridScrollFrameScrollUp_OnLoad (self)
self:GetParent():GetParent().scrollUp = self;
self:Disable();
self:RegisterForClicks("LeftButtonUp", "LeftButtonDown");
self.direction = 1;
end
function HybridScrollFrameScrollDown_OnLoad (self)
self:GetParent():GetParent().scrollDown = self;
self:Disable();
self:RegisterForClicks("LeftButtonUp", "LeftButtonDown");
self.direction = -1;
end
function HybridScrollFrame_OnValueChanged (self, value)
local scrollFrame = self;
if self.SetMinMaxValues and self.GetParent then
scrollFrame = self:GetParent();
end
scrollFrame = HybridScrollFrame_GetScrollFrame(scrollFrame);
HybridScrollFrame_SetOffset(scrollFrame, value);
HybridScrollFrame_UpdateButtonStates(scrollFrame, value);
end
function HybridScrollFrame_UpdateButtonStates (self, currValue)
local scrollBar = HybridScrollFrame_GetScrollBar(self);
if ( not scrollBar ) then
return;
end
local scrollBarName = scrollBar.GetName and scrollBar:GetName();
local scrollUp = self.scrollUp or scrollBar.ScrollUpButton or (scrollBarName and _G[scrollBarName.."ScrollUpButton"]);
local scrollDown = self.scrollDown or scrollBar.ScrollDownButton or (scrollBarName and _G[scrollBarName.."ScrollDownButton"]);
if ( not currValue ) then
currValue = scrollBar:GetValue();
end
if ( scrollUp ) then
scrollUp:Enable();
end
if ( scrollDown ) then
scrollDown:Enable();
end
local minVal, maxVal = scrollBar:GetMinMaxValues();
if ( currValue >= maxVal ) then
if ( scrollBar.thumbTexture ) then
scrollBar.thumbTexture:Show();
end
if ( scrollDown ) then
scrollDown:Disable()
end
end
if ( currValue <= minVal ) then
if ( scrollBar.thumbTexture ) then
scrollBar.thumbTexture:Show();
end
if ( scrollUp ) then
scrollUp:Disable();
end
end
end
function HybridScrollFrame_OnMouseWheel (self, delta, stepSize)
self = HybridScrollFrame_GetScrollFrame(self);
local scrollBar = HybridScrollFrame_GetScrollBar(self);
if ( not scrollBar or not scrollBar:IsVisible() ) then
return;
end
local isEnabled = not scrollBar.IsEnabled or scrollBar:IsEnabled();
if ( not isEnabled or isEnabled == 0 ) then
return;
end
local minVal, maxVal = scrollBar:GetMinMaxValues();
minVal = minVal or 0;
maxVal = maxVal or self.range or 0;
stepSize = stepSize or self.stepSize or self.buttonHeight or 1;
if ( delta == 1 ) then
scrollBar:SetValue(max(minVal, scrollBar:GetValue() - stepSize));
else
scrollBar:SetValue(min(maxVal, scrollBar:GetValue() + stepSize));
end
end
function HybridScrollFrameScrollButton_OnUpdate (self, elapsed)
self.timeSinceLast = self.timeSinceLast + elapsed;
if ( self.timeSinceLast >= ( self.updateInterval or 0.08 ) ) then
if ( not IsMouseButtonDown("LeftButton") ) then
self:SetScript("OnUpdate", nil);
elseif ( self:IsMouseOver() ) then
local parent = self.parent or self:GetParent():GetParent();
HybridScrollFrame_OnMouseWheel (parent, self.direction, (self.stepSize or parent.buttonHeight/3));
self.timeSinceLast = 0;
end
end
end
function HybridScrollFrameScrollButton_OnClick (self, button, down)
local parent = self.parent or self:GetParent():GetParent();
if ( down ) then
if IsMouseButtonDown then
self.timeSinceLast = (self.timeToStart or -0.2);
self:SetScript("OnUpdate", HybridScrollFrameScrollButton_OnUpdate);
end
HybridScrollFrame_OnMouseWheel (parent, self.direction);
PlaySound("UChatScrollButton");
else
self:SetScript("OnUpdate", nil);
end
end
function HybridScrollFrame_SetPercentageHeight(self, percentageHeight)
local offset = percentageHeight * (self.totalHeight or 0);
HybridScrollFrame_SetOffset(self, offset);
end
function HybridScrollFrame_GetVisiblePercentage(self)
local totalHeight = (self.totalHeight or 0);
if totalHeight == 0 then
return 0;
end
return self.scrollChild:GetHeight() / totalHeight;
end
function HybridScrollFrame_GetScrollPercentage(self)
local scrollableHeight = (self.totalHeight or 0) - self.scrollChild:GetHeight();
if scrollableHeight == 0 then
return 0;
end
return (self.offset or 0) / scrollableHeight;
end
function HybridScrollFrame_Update (self, totalHeight, displayedHeight)
local range = floor(totalHeight - self:GetHeight() + 0.5);
local scrollBar = HybridScrollFrame_GetScrollBar(self);
if ( range > 0 and scrollBar ) then
local minVal, maxVal = scrollBar:GetMinMaxValues();
if ( math.floor(scrollBar:GetValue()) >= math.floor(maxVal) ) then
scrollBar:SetMinMaxValues(0, range);
if ( math.floor(scrollBar:GetValue()) ~= math.floor(range) ) then
scrollBar:SetValue(range);
else
HybridScrollFrame_SetOffset(self, range); -- If we've scrolled to the bottom, we need to recalculate the offset.
end
else
scrollBar:SetMinMaxValues(0, range)
end
scrollBar:Enable();
HybridScrollFrame_UpdateButtonStates(self);
scrollBar:Show();
elseif ( scrollBar ) then
scrollBar:SetValue(0);
if ( scrollBar.doNotHide ) then
scrollBar:Disable();
if ( self.scrollUp ) then
self.scrollUp:Disable();
end
if ( self.scrollDown ) then
self.scrollDown:Disable();
end
if ( scrollBar.thumbTexture ) then
scrollBar.thumbTexture:Hide();
end
else
scrollBar:Hide();
end
end
self.range = range;
self.totalHeight = totalHeight;
self.scrollChild:SetHeight(displayedHeight);
HybridScrollFrame_UpdateScrollChildRectSafe(self);
end
function HybridScrollFrame_GetOffset (self)
return math.floor(self.offset or 0), (self.offset or 0);
end
function HybridScrollFrameScrollChild_OnLoad (self)
self:GetParent().scrollChild = self;
end
function HybridScrollFrame_ExpandButton (self, offset, height)
self.largeButtonTop = round(offset);
self.largeButtonHeight = round(height)
local scrollBar = HybridScrollFrame_GetScrollBar(self);
HybridScrollFrame_SetOffset(self, scrollBar and scrollBar:GetValue() or 0);
end
function HybridScrollFrame_CollapseButton (self)
self.largeButtonTop = nil;
self.largeButtonHeight = nil;
end
function HybridScrollFrame_SetOffset (self, offset)
local buttons = self.buttons
local buttonHeight = self.buttonHeight;
local element, overflow;
if ( not buttonHeight and buttons and buttons[1] ) then
buttonHeight = round(buttons[1]:GetHeight());
self.buttonHeight = buttonHeight;
end
if ( not buttonHeight or buttonHeight == 0 ) then
self.offset = 0;
HybridScrollFrame_SetVerticalScrollOffset(self, 0);
return;
end
local scrollHeight = 0;
local largeButtonTop = self.largeButtonTop
if ( self.dynamic ) then --This is for frames where buttons will have different heights
if ( offset < buttonHeight ) then
-- a little optimization
element = 0;
scrollHeight = offset;
else
element, scrollHeight = self.dynamic(offset);
end
elseif ( largeButtonTop and offset >= largeButtonTop ) then
local largeButtonHeight = self.largeButtonHeight;
-- Initial offset...
element = largeButtonTop / buttonHeight;
if ( offset >= (largeButtonTop + largeButtonHeight) ) then
element = element + 1;
local leftovers = (offset - (largeButtonTop + largeButtonHeight) );
element = element + ( leftovers / buttonHeight );
overflow = element - math.floor(element);
scrollHeight = overflow * buttonHeight;
else
scrollHeight = math.abs(offset - largeButtonTop);
end
else
element = offset / buttonHeight;
overflow = element - math.floor(element);
scrollHeight = overflow * buttonHeight;
end
if ( math.floor(self.offset or 0) ~= math.floor(element) and self.update ) then
self.offset = element;
self:update();
else
self.offset = element;
end
HybridScrollFrame_SetVerticalScrollOffset(self, scrollHeight);
end
function HybridScrollFrame_CreateButtons (self, buttonTemplate, initialOffsetX, initialOffsetY, initialPoint, initialRelative, offsetX, offsetY, point, relativePoint)
local scrollChild = self.scrollChild;
local button, buttonHeight, buttons, numButtons;
local parentName = self:GetName();
local buttonName = parentName and (parentName .. "Button") or nil;
initialPoint = initialPoint or "TOPLEFT";
initialRelative = initialRelative or "TOPLEFT";
point = point or "TOPLEFT";
relativePoint = relativePoint or "BOTTOMLEFT";
offsetX = offsetX or 0;
offsetY = offsetY or 0;
if ( self.buttons ) then
buttons = self.buttons;
buttonHeight = buttons[1]:GetHeight();
else
button = CreateFrame("BUTTON", buttonName and (buttonName .. 1) or nil, scrollChild, buttonTemplate);
buttonHeight = button:GetHeight();
button:SetPoint(initialPoint, scrollChild, initialRelative, initialOffsetX, initialOffsetY);
buttons = {}
tinsert(buttons, button);
end
self.buttonHeight = round(buttonHeight) - offsetY;
numButtons = math.ceil(self:GetHeight() / buttonHeight) + 1;
for i = #buttons + 1, numButtons do
button = CreateFrame("BUTTON", buttonName and (buttonName .. i) or nil, scrollChild, buttonTemplate);
button:SetPoint(point, buttons[i-1], relativePoint, offsetX, offsetY);
tinsert(buttons, button);
end
scrollChild:SetWidth(self:GetWidth())
scrollChild:SetHeight(numButtons * buttonHeight);
HybridScrollFrame_SetVerticalScrollOffset(self, 0);
HybridScrollFrame_UpdateScrollChildRectSafe(self);
self.buttons = buttons;
local scrollBar = HybridScrollFrame_GetScrollBar(self);
if scrollBar then
scrollBar:SetMinMaxValues(0, numButtons * buttonHeight)
scrollBar.buttonHeight = buttonHeight;
scrollBar:SetValueStep(.005);
scrollBar:SetValue(0);
end
end
function HybridScrollFrame_GetButtonIndex(self, button)
return tIndexOf(self.buttons, button);
end
function HybridScrollFrame_GetButtons (self)
return self.buttons;
end
function HybridScrollFrame_SetDoNotHideScrollBar (self, doNotHide)
local scrollBar = HybridScrollFrame_GetScrollBar(self);
if not scrollBar or scrollBar.doNotHide == doNotHide then
return;
end
scrollBar.doNotHide = doNotHide;
HybridScrollFrame_Update(self, self.totalHeight or 0, self.scrollChild:GetHeight());
end
function HybridScrollFrame_ScrollToIndex(self, index, getHeightFunc)
local scrollBar = HybridScrollFrame_GetScrollBar(self);
if not scrollBar then
return;
end
local totalHeight = 0;
local scrollFrameHeight = self:GetHeight();
for i = 1, index do
local entryHeight = getHeightFunc(i);
if i == index then
local offset = 0;
-- we don't need to do anything if the entry is fully displayed with the scroll all the way up
if ( totalHeight + entryHeight > scrollFrameHeight ) then
if ( entryHeight > scrollFrameHeight ) then
-- this entry is larger than the entire scrollframe, put it at the top
offset = totalHeight;
else
-- otherwise place it in the center
local diff = scrollFrameHeight - entryHeight;
offset = totalHeight - diff / 2;
end
-- because of valuestep our positioning might change
-- we'll do the adjustment ourselves to make sure the entry ends up above the center rather than below
local valueStep = scrollBar:GetValueStep();
offset = offset + valueStep - mod(offset, valueStep);
-- but if we ended up moving the entry so high up that its top is not visible, move it back down
if ( offset > totalHeight ) then
offset = offset - valueStep;
end
end
scrollBar:SetValue(offset);
break;
end
totalHeight = totalHeight + entryHeight;
end
end
function HybridScrollBar_Disable(scrollBar)
scrollBar:Disable();
local scrollDownButton = scrollBar.ScrollDownButton or _G[scrollBar:GetName().."ScrollDownButton"];
if scrollDownButton then
scrollDownButton:Disable();
end
local scrollUpButton = scrollBar.ScrollUpButton or _G[scrollBar:GetName().."ScrollUpButton"];
if scrollUpButton then
scrollUpButton:Disable();
end
end
@@ -0,0 +1,220 @@
<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="HybridScrollFrame.lua"/>
<Texture name="HybridScrollBarButton" virtual="true">
<TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/>
</Texture>
<Slider name="HybridScrollBarBackgroundTemplate" parentKey="scrollBar" virtual="true">
<Size x="20" y="0"/>
<Anchors>
<Anchor point="TOPLEFT" relativePoint="TOPRIGHT" x="0" y="-18"/>
<Anchor point="BOTTOMLEFT" relativePoint="BOTTOMRIGHT" x="0" y="16"/>
</Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentBG" setAllPoints="true" hidden="true" parentKey="trackBG">
<Color r="0" g="0" b="0" a=".85"/>
</Texture>
</Layer>
<Layer level="ARTWORK">
<Texture name="$parentTop" parentKey="ScrollBarTop" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
<Size x="27" y="48"/>
<Anchors>
<Anchor point="TOPLEFT" x="-4" y="17"/>
</Anchors>
<TexCoords left="0" right="0.484375" top="0" bottom=".20"/>
</Texture>
<Texture name="$parentBottom" parentKey="ScrollBarBottom" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
<Size x="27" y="64"/>
<Anchors>
<Anchor point="BOTTOMLEFT" x="-4" y="-15"/>
</Anchors>
<TexCoords left="0.515625" right="1.0" top="0.1440625" bottom="0.4140625"/>
</Texture>
<Texture name="$parentMiddle" parentKey="ScrollBarMiddle" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentTop" relativePoint="BOTTOMLEFT"/>
<Anchor point="BOTTOMRIGHT" relativeTo="$parentBottom" relativePoint="TOPRIGHT"/>
</Anchors>
<TexCoords left="0" right="0.484375" top="0.1640625" bottom="1"/>
</Texture>
</Layer>
</Layers>
<ThumbTexture name="$parentThumbTexture" file="Interface\Buttons\UI-ScrollBar-Knob" parentKey="thumbTexture">
<Size x="18" y="24"/>
<TexCoords left="0.20" right="0.80" top="0.125" bottom="0.875"/>
</ThumbTexture>
</Slider>
<Slider name="HybridScrollBarTemplate" inherits="HybridScrollBarBackgroundTemplate" virtual="true">
<Frames>
<Button name="$parentScrollUpButton" parentKey="ScrollUpButton" inherits="UIPanelScrollUpButtonTemplate">
<Anchors>
<Anchor point="BOTTOM" relativePoint="TOP" x="0" y="-2"/>
</Anchors>
<Scripts>
<OnLoad function="HybridScrollFrameScrollUp_OnLoad"/>
<OnClick function ="HybridScrollFrameScrollButton_OnClick"/>
</Scripts>
</Button>
<Button name="$parentScrollDownButton" parentKey="ScrollDownButton" inherits="UIPanelScrollDownButtonTemplate">
<Anchors>
<Anchor point="TOP" relativePoint="BOTTOM" x="0" y="2"/>
</Anchors>
<Scripts>
<OnLoad function="HybridScrollFrameScrollDown_OnLoad"/>
<OnClick function="HybridScrollFrameScrollButton_OnClick"/>
</Scripts>
</Button>
</Frames>
<Scripts>
<OnValueChanged function="HybridScrollFrame_OnValueChanged"/>
</Scripts>
</Slider>
<Slider name="HybridScrollBarTrimTemplate" parentKey="scrollBar" virtual="true">
<Size x="20" y="0"/>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentBG" setAllPoints="true" hidden="true" parentKey="trackBG">
<Color r="0" g="0" b="0" a=".85"/>
</Texture>
</Layer>
<Layer level="ARTWORK">
<Texture name="$parentTop" parentKey="Top" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
<Size x="24" y="48"/>
<Anchors>
<Anchor point="TOPLEFT" x="-4" y="17"/>
</Anchors>
<TexCoords left="0" right="0.45" top="0" bottom=".20"/>
</Texture>
<Texture name="$parentBottom" parentKey="Bottom" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
<Size x="24" y="64"/>
<Anchors>
<Anchor point="BOTTOMLEFT" x="-4" y="-15"/>
</Anchors>
<TexCoords left="0.515625" right="0.97" top="0.1440625" bottom="0.4140625"/>
</Texture>
<Texture name="$parentMiddle" parentKey="Middle" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentTop" relativePoint="BOTTOMLEFT"/>
<Anchor point="BOTTOMRIGHT" relativeTo="$parentBottom" relativePoint="TOPRIGHT"/>
</Anchors>
<TexCoords left="0" right="0.45" top="0.1640625" bottom="1"/>
</Texture>
</Layer>
</Layers>
<Frames>
<Button name="$parentScrollUpButton" parentKey="UpButton" inherits="UIPanelScrollUpButtonTemplate">
<Anchors>
<Anchor point="BOTTOM" relativePoint="TOP" x="0" y="-2"/>
</Anchors>
<Scripts>
<OnLoad function="HybridScrollFrameScrollUp_OnLoad"/>
<OnClick function="HybridScrollFrameScrollButton_OnClick"/>
</Scripts>
</Button>
<Button name="$parentScrollDownButton" parentKey="DownButton" inherits="UIPanelScrollDownButtonTemplate">
<Anchors>
<Anchor point="TOP" relativePoint="BOTTOM" x="0" y="2"/>
</Anchors>
<Scripts>
<OnLoad function="HybridScrollFrameScrollDown_OnLoad"/>
<OnClick function="HybridScrollFrameScrollButton_OnClick"/>
</Scripts>
</Button>
</Frames>
<Scripts>
<OnValueChanged function="HybridScrollFrame_OnValueChanged"/>
</Scripts>
<ThumbTexture name="$parentThumbTexture" inherits="HybridScrollBarButton" file="Interface\Buttons\UI-ScrollBar-Knob" parentKey="thumbTexture">
<Size x="18" y="24"/>
<TexCoords left="0.20" right="0.80" top="0.125" bottom="0.875"/>
</ThumbTexture>
</Slider>
<Slider name="MinimalHybridScrollBarTemplate" parentKey="scrollBar" virtual="true">
<Size x="22" y="0"/>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentTrack" parentKey="trackBG">
<Anchors>
<Anchor point="TOPLEFT" x="0" y="17"/>
<Anchor point="BOTTOMRIGHT" x="0" y="-17"/>
</Anchors>
<Color r="0" g="0" b="0" a="1"/>
</Texture>
</Layer>
</Layers>
<Frames>
<Button name="$parentScrollUpButton" inherits="UIPanelScrollUpButtonTemplate">
<Anchors>
<Anchor point="TOP" x="0" y="15"/>
</Anchors>
<Scripts>
<OnLoad function="HybridScrollFrameScrollUp_OnLoad"/>
<OnClick function="HybridScrollFrameScrollButton_OnClick"/>
</Scripts>
</Button>
<Button name="$parentScrollDownButton" inherits="UIPanelScrollDownButtonTemplate">
<Anchors>
<Anchor point="BOTTOM" x="0" y="-15"/>
</Anchors>
<Scripts>
<OnLoad function="HybridScrollFrameScrollDown_OnLoad"/>
<OnClick function="HybridScrollFrameScrollButton_OnClick"/>
</Scripts>
</Button>
</Frames>
<Scripts>
<OnValueChanged function="HybridScrollFrame_OnValueChanged"/>
</Scripts>
<ThumbTexture name="$parentThumbTexture" inherits="HybridScrollBarButton" file="Interface\Buttons\UI-ScrollBar-Knob" parentKey="thumbTexture">
<Size x="18" y="24"/>
<TexCoords left="0.20" right="0.80" top="0.125" bottom="0.875"/>
</ThumbTexture>
</Slider>
<ScrollFrame name="HybridScrollFrameTemplate" virtual="true">
<Scripts>
<OnLoad function="HybridScrollFrame_OnLoad"/>
<OnMouseWheel function="HybridScrollFrame_OnMouseWheel"/>
</Scripts>
<ScrollChild>
<Frame name="$parentScrollChild" parentKey="ScrollChild">
<Scripts>
<OnLoad function="HybridScrollFrameScrollChild_OnLoad"/>
</Scripts>
</Frame>
</ScrollChild>
</ScrollFrame>
<ScrollFrame name="BasicHybridScrollFrameTemplate" inherits="HybridScrollFrameTemplate" virtual="true">
<Anchors>
<Anchor point="TOPLEFT"/>
<Anchor point="BOTTOMRIGHT"/>
</Anchors>
<Frames>
<Slider name="$parentScrollBar" parentKey="ScrollBar" inherits="HybridScrollBarTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativePoint="TOPRIGHT" x="0" y="-17"/>
<Anchor point="BOTTOMLEFT" relativePoint="BOTTOMRIGHT" x="0" y="12"/>
</Anchors>
</Slider>
</Frames>
</ScrollFrame>
<!-- This is a scrollframe with no border and a black texture for a track -->
<ScrollFrame name="MinimalHybridScrollFrameTemplate" inherits="HybridScrollFrameTemplate" virtual="true">
<Frames>
<Slider name="$parentScrollBar" inherits="MinimalHybridScrollBarTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativePoint="TOPRIGHT" x="0" y="-17"/>
<Anchor point="BOTTOMLEFT" relativePoint="BOTTOMRIGHT" x="0" y="17"/>
</Anchors>
</Slider>
</Frames>
</ScrollFrame>
</Ui>
@@ -0,0 +1,355 @@
NAVBAR_WIDTHBUFFER = 20;
function NavBar_Initialize(self, template, homeData, homeButton, overflowButton)
self.template = template;
self.freeButtons = {};
self.navList = {};
self.widthBuffer = NAVBAR_WIDTHBUFFER;
local name = self:GetName();
if not self.dropDown then
local dropDownName = name and name.."DropDown" or nil;
self.dropDown = CreateFrame("Frame", dropDownName, self, "UIDropDownMenuTemplate");
UIDropDownMenu_Initialize(self.dropDown, NavBar_DropDown_Initialize, "MENU");
end
if not homeButton then
local homeButtonName = name and name.."HomeButton" or nil;
homeButton = CreateFrame("BUTTON", homeButtonName, self, self.template);
homeButton:SetWidth(homeButton.text:GetStringWidth()+30);
end
homeButton:SetText(homeData.name or HOME);
if not overflowButton then
local overflowButtonName = name and name.."OverflowButton" or nil;
overflowButton = CreateFrame("BUTTON", overflowButtonName, self, self.template);
overflowButton:SetWidth(30);
-- LOOK AT CLICK
end
if not homeButton:GetScript("OnEnter") then
homeButton:SetScript("OnEnter", NavBar_ButtonOnEnter);
end
if not homeButton:GetScript("OnLeave") then
homeButton:SetScript("OnLeave", NavBar_ButtonOnLeave);
end
if ( self.oldStyle ) then
homeButton:RegisterForClicks("LeftButtonUp", "RightButtonUp");
overflowButton:RegisterForClicks("LeftButtonUp", "RightButtonUp");
else
homeButton:RegisterForClicks("LeftButtonUp");
end
overflowButton.listFunc = NavBar_ListOverFlowButtons;
homeButton:ClearAllPoints();
overflowButton:ClearAllPoints();
homeButton:SetPoint("LEFT", self, "LEFT", 0, 0);
overflowButton:SetPoint("LEFT", self, "LEFT", 0, 0);
overflowButton:Hide();
homeButton.oldClick = homeButton:GetScript("OnClick");
homeButton:SetScript("OnClick", NavBar_ButtonOnClick);
overflowButton:SetScript("OnMouseDown", NavBar_ToggleMenu);
self.homeButton = homeButton;
self.overflowButton = overflowButton;
self.navList[#self.navList+1] = homeButton;
homeButton.myclick = homeData.OnClick;
homeButton.listFunc = homeData.listFunc;
homeButton.data = homeData;
homeButton:Show();
end
function NavBar_Reset(self)
for index=2,#self.navList do
self.navList[index]:Hide();
tinsert(self.freeButtons, self.navList[index])
self.navList[index] = nil;
end
NavBar_CheckLength(self);
end
function NavBar_AddButton(self, buttonData)
local navButton = self.freeButtons[#self.freeButtons];
if navButton then
self.freeButtons[#self.freeButtons] = nil;
end
if not navButton then
local name = self:GetName();
local buttonName = name and name.."Button"..(#self.navList+1);
navButton = CreateFrame("BUTTON", buttonName, self, self.template);
navButton.oldClick = navButton:GetScript("OnClick");
navButton:SetScript("OnClick", NavBar_ButtonOnClick);
if ( self.oldStyle ) then
navButton:RegisterForClicks("LeftButtonUp", "RightButtonUp");
else
navButton:RegisterForClicks("LeftButtonUp");
end
if not navButton:GetScript("OnEnter") then
navButton:SetScript("OnEnter", NavBar_ButtonOnEnter);
end
if not navButton:GetScript("OnLeave") then
navButton:SetScript("OnLeave", NavBar_ButtonOnLeave);
end
end
--Set up the button
local navParent = self.navList[#self.navList];
self.navList[#self.navList+1] = navButton;
navButton.navParent = navParent;
navButton:SetText(buttonData.name);
local buttonExtraWidth;
navButton.MenuArrowButton:Hide();
buttonExtraWidth = 30;
navButton.text:SetWidth(self.textMaxWidth or 0);
navButton:SetWidth(navButton.text:GetStringWidth() + buttonExtraWidth);
navButton.myclick = buttonData.OnClick;
navButton.listFunc = buttonData.listFunc;
navButton.id = buttonData.id;
navButton.data = buttonData;
navButton:Show();
NavBar_CheckLength(self);
navButton.MenuArrowButton:SetEnabled(not buttonData.isDisabled)
end
function NavBar_ClearTrailingButtons(list, freeList, button)
for index=#list,1,-1 do
if not list[index] or button == list[index] then
break
end
list[index]:Hide();
tinsert(freeList, list[index])
list[index] = nil;
end
NavBar_CheckLength(button:GetParent());
end
function NavBar_OpenTo(self, id)
local button;
local found = false;
for i=1, #self.navList do
button = self.navList[i];
if (button.id and button.id == id) then
found = true;
break;
end
end
if (found) then
NavBar_ClearTrailingButtons(self.navList, self.freeButtons, button)
end
end
function NavBar_ButtonOnClick(self, button)
local parent = self:GetParent()
CloseDropDownMenus();
if button == "LeftButton" then
NavBar_ClearTrailingButtons(parent.navList, parent.freeButtons, self);
if self.oldClick then
self:oldClick(button);
end
if self.myclick then
self:myclick(button);
end
elseif button == "RightButton" then
NavBar_ToggleMenu(self);
end
end
function NavBar_ButtonOnEnter(self)
if self.text:IsTruncated() then
GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT");
GameTooltip:AddLine(self.text:GetText(), NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, true);
GameTooltip:Show();
end
end
function NavBar_ButtonOnLeave(self)
if self.text:IsTruncated() then
GameTooltip:Hide();
end
end
function NavBar_CheckLength(self)
local width = 0;
local collapsedWidth;
local maxWidth = self:GetWidth() - self.widthBuffer;
local xoffset;
local lastShown;
local collapsed = false;
for i=#self.navList,1,-1 do
local currentWidth = width;
width = width + self.navList[i]:GetWidth();
if width > maxWidth then
self.navList[i]:Hide();
collapsed = true;
if not collapsedWidth then -- store the width for adding the offset button
collapsedWidth = currentWidth;
end
else
self.navList[i]:Show();
if lastShown then
local lastButton = self.navList[lastShown];
xoffset = self.navList[i].xoffset or 0
lastButton:SetPoint("LEFT", self.navList[i], "RIGHT", xoffset, 0);
self.navList[i]:SetFrameLevel(lastButton:GetFrameLevel()+1);
else
self.navList[i]:SetFrameLevel(self:GetFrameLevel()+1);
end
lastShown = i;
end
if i<#self.navList then
if self.navList[i].selected then
self.navList[i].selected:Hide();
end
self.navList[i]:Enable();
else
if self.navList[i].selected then
self.navList[i].selected:Show();
end
self.navList[i]:SetButtonState("NORMAL");
self.navList[i]:Disable();
end
end
if self.navList[1] then
local frameLevel = self.navList[1]:GetFrameLevel()
if frameLevel >= self.overlay:GetFrameLevel() then
self.overlay:SetFrameLevel(frameLevel + 1)
end
end
if collapsed then
if collapsedWidth + self.overflowButton:GetWidth() > maxWidth then
--No room for the overflow button
self.navList[lastShown]:Hide();
lastShown = lastShown + 1;
end
self.overflowButton:Show();
--There should only ever be no lastShown if a single button is longer than maxWidth by itself.
if ( lastShown ) then
local lastButton = self.navList[lastShown];
--There should only ever be no lastButton when there is lastShown if a single button is less than maxWidth
--but it's width plus the width of the overflow button is longer than maxWidth. In this case the single button
--is hidden to make room for the overflowButton.
if ( lastButton ) then
xoffset = self.overflowButton.xoffset or 0
lastButton:SetPoint("LEFT", self.overflowButton, "RIGHT", xoffset, 0);
self.overflowButton:SetFrameLevel(lastButton:GetFrameLevel()+1);
end
end
else
self.overflowButton:Hide();
end
end
function NavBar_ListOverFlowButtons(self, index)
local navBar = self:GetParent();
local button = navBar.navList[index];
if not button:IsShown() then
return button:GetText(), NavBar_OverflowItemOnClick;
end
end
function NavBar_ToggleMenu(self, button)
if ( self:GetParent().dropDown.buttonOwner ~= self ) then
CloseDropDownMenus();
end
self:GetParent().dropDown.buttonOwner = self;
ToggleDropDownMenu(nil, nil, self:GetParent().dropDown, self, 20, 3);
end
function NavBar_DropDown_Initialize(self, level)
local navButton = self.buttonOwner;
if not navButton or not navButton.listFunc then
return;
end
local info = UIDropDownMenu_CreateInfo();
info.func = NavBar_DropDown_Click;
info.owner = navButton;
info.notCheckable = true;
local index = 1;
local text, func = navButton:listFunc(index);
while text do
info.text = text;
info.arg1 = index;
info.arg2 = func;
UIDropDownMenu_AddButton(info, level);
index = index + 1;
text, func = navButton:listFunc(index);
end
end
function NavBar_DropDown_Click(self, index, func)
local navButton = self.owner;
local navBar = navButton:GetParent();
if func ~= NavBar_OverflowItemOnClick then
NavBar_ClearTrailingButtons(navBar.navList, navBar.freeButtons, navButton);
end
if type(func) == "function" then
func(self, index, navBar);
end
end
function NavBar_OverflowItemOnClick(junk, index, navBar)
local button = navBar.navList[index];
if button then
button:Click();
end
end
function NavBar_ReconsultationSize( self )
self.xoffset = -15
local newWidth = min(128, self.text:GetStringWidth()+50)
local texCoordoffsetX = (newWidth/128)*0.25
self:GetNormalTexture():SetTexCoord(0.70312500-texCoordoffsetX, 0.70312500, 0.00781250, 0.24218750)
self:GetPushedTexture():SetTexCoord(0.70312500-texCoordoffsetX, 0.70312500, 0.25781250, 0.49218750)
self:GetHighlightTexture():SetTexCoord(0.70312500-texCoordoffsetX, 0.71312500, 0.50781250, 0.74218750)
self:SetWidth(newWidth)
end
@@ -0,0 +1,241 @@
<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="NavigationBar.lua"/>
<Button name="NavButtonTemplate" motionScriptsWhileDisabled="true" virtual="true">
<Size x="80" y="30"/>
<Layers>
<Layer level="OVERLAY">
<Texture file="Interface\HelpFrame\CS_HelpTextures" parentKey="arrowUp">
<Size x="21" y="30"/>
<Anchors>
<Anchor point="LEFT" relativePoint="RIGHT" x="0" y="0"/>
</Anchors>
<TexCoords left="0.88867188" right="0.92968750" top="0.29687500" bottom="0.53125000"/>
</Texture>
<Texture file="Interface\HelpFrame\CS_HelpTextures" parentKey="arrowDown">
<Size x="21" y="30"/>
<Anchors>
<Anchor point="LEFT" relativePoint="RIGHT" x="0" y="0"/>
</Anchors>
<TexCoords left="0.63281250" right="0.67382813" top="0.75781250" bottom="0.99218750"/>
</Texture>
<Texture file="Interface\HelpFrame\CS_HelpTextures" parentKey="selected" hidden="true">
<Anchors>
<Anchor point="TOPLEFT" x="0" y="0"/>
<Anchor point="BOTTOMRIGHT" x="0" y="0"/>
</Anchors>
<TexCoords left="0.00195313" right="0.25195313" top="0.37500000" bottom="0.64062500"/>
</Texture>
</Layer>
</Layers>
<Frames>
<Button parentKey="MenuArrowButton" hidden="true">
<Size x="27" y="31"/>
<Anchors>
<Anchor point="RIGHT" relativePoint="TOPRIGHT" x="-2" y="-15"/>
</Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture parentKey="Art" file="Interface\Buttons\SquareButtonTextures">
<Size x="12" y="12"/>
<Anchors>
<Anchor point="CENTER" x="0" y="-1"/>
</Anchors>
<TexCoords left="0.45312500" right="0.64062500" bottom="0.01562500" top="0.20312500"/>
</Texture>
</Layer>
</Layers>
<NormalTexture file="Interface\Buttons\UI-SquareButton-Up" parentKey="NormalTexture" alpha="0">
<Size x="32" y="32"/>
<Anchors>
<Anchor point="CENTER"/>
</Anchors>
</NormalTexture>
<PushedTexture file="Interface\Buttons\UI-SquareButton-Down" parentKey="PushedTexture" alpha="0">
<Size x="32" y="32"/>
<Anchors>
<Anchor point="CENTER"/>
</Anchors>
</PushedTexture>
<HighlightTexture file="Interface\Buttons\UI-Common-MouseHilight" alphaMode="ADD">
<Size x="32" y="32"/>
<Anchors>
<Anchor point="CENTER"/>
</Anchors>
</HighlightTexture>
<Scripts>
<OnMouseDown>
if self:IsEnabled() == 1 then
self.Art:SetPoint("CENTER", -1, -2);
end
</OnMouseDown>
<OnMouseUp>
if self:IsEnabled() == 1 then
self.Art:SetPoint("CENTER", 0, -1);
PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
NavBar_ToggleMenu(self:GetParent());
end
</OnMouseUp>
<OnEnter>
self.NormalTexture:SetAlpha(1);
self.PushedTexture:SetAlpha(1);
</OnEnter>
<OnLeave>
self.NormalTexture:SetAlpha(0);
self.PushedTexture:SetAlpha(0);
</OnLeave>
<OnEnable>
self.NormalTexture:SetDesaturated(false)
self.PushedTexture:SetDesaturated(false)
self.Art:SetDesaturated(false)
</OnEnable>
<OnDisable>
self.NormalTexture:SetDesaturated(true)
self.PushedTexture:SetDesaturated(true)
self.Art:SetDesaturated(true)
</OnDisable>
</Scripts>
</Button>
</Frames>
<Scripts>
<OnMouseDown>
if self:IsEnabled() == 1 then
self.arrowUp:Hide();
self.arrowDown:Show();
end
</OnMouseDown>
<OnMouseUp>
self.arrowDown:Hide();
self.arrowUp:Show();
</OnMouseUp>
<OnClick>
PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
</OnClick>
<OnEnable>
self.arrowDown:Hide();
self.arrowUp:Show();
</OnEnable>
<OnDisable>
self.arrowDown:Hide();
self.arrowUp:Show();
</OnDisable>
</Scripts>
<NormalTexture file="Interface\HelpFrame\CS_HelpTextures_Tile" parentKey="NormalTexture" horizTile="true">
<TexCoords top="0.06250000" bottom="0.12109375"/>
</NormalTexture>
<PushedTexture file="Interface\HelpFrame\CS_HelpTextures_Tile" parentKey="PushedTexture" horizTile="true">
<TexCoords top="0.12500000" bottom="0.18359375"/>
</PushedTexture>
<HighlightTexture file="Interface\HelpFrame\CS_HelpTextures" parentKey="HighlightTexture" alphaMode="ADD">
<TexCoords left="0.00195313" right="0.25195313" top="0.65625000" bottom="0.92187500"/>
</HighlightTexture>
<ButtonText name="$parentText" inherits="GameFontNormal" justifyH="LEFT" parentKey="text">
<Size x="0" y="12"/>
<Anchors>
<Anchor point="LEFT" x="20" y="0"/>
</Anchors>
</ButtonText>
</Button>
<Frame name="NavBarTemplate" parentKey="navBar" virtual="true">
<Size x="300" y="34"/>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\HelpFrame\CS_HelpTextures_Tile" horizTile="true">
<Anchors>
<Anchor point="TOPLEFT" relativePoint="TOPLEFT" x="0" y="0"/>
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT" x="0" y="0"/>
</Anchors>
<TexCoords top="0.18750000" bottom="0.25390625"/>
</Texture>
</Layer>
</Layers>
<Frames>
<Frame name="$parentOverlay" parentKey="overlay">
<Anchors>
<Anchor point="TOPLEFT" x="0" y="0"/>
<Anchor point="BOTTOMRIGHT" x="0" y="0"/>
</Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture file="Interface\HelpFrame\CS_HelpTextures_Tile" horizTile="true">
<Anchors>
<Anchor point="TOPLEFT" x="0" y="0"/>
<Anchor point="BOTTOMRIGHT" x="0" y="0"/>
</Anchors>
<TexCoords top="0.25781250" bottom="0.32421875"/>
</Texture>
</Layer>
</Layers>
<Scripts>
<OnLoad>
self:SetFrameLevel(50);
</OnLoad>
</Scripts>
</Frame>
<Button name="$parentOverflowButton" parentKey="overflow">
<Size x="44" y="30"/>
<Scripts>
<OnLoad>
self.xoffset = -18
</OnLoad>
<OnClick>
PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
</OnClick>
</Scripts>
<NormalTexture file="Interface\HelpFrame\CS_HelpTextures">
<TexCoords left="0.54296875" right="0.62890625" top="0.75781250" bottom="0.99218750"/>
</NormalTexture>
<PushedTexture file="Interface\HelpFrame\CS_HelpTextures">
<TexCoords left="0.45312500" right="0.53906250" top="0.75781250" bottom="0.99218750"/>
</PushedTexture>
<HighlightTexture file="Interface\HelpFrame\CS_HelpTextures" alphaMode="ADD" alpha="0.4">
<TexCoords left="0.54296875" right="0.62890625" top="0.75781250" bottom="0.99218750"/>
</HighlightTexture>
</Button>
<Button name="$parentHomeButton" parentKey="home" text="NAVIGATIONBAR_HOME">
<Size x="128" y="30"/>
<Layers>
<Layer level="OVERLAY">
<Texture name="$parentLeft" file="Interface\Common\ShadowOverlay-Left">
<Size x="30" y="30"/>
<Anchors>
<Anchor point="LEFT" x="0" y="0"/>
</Anchors>
</Texture>
</Layer>
</Layers>
<Scripts>
<OnLoad>
self.xoffset = -15;
local newWidth = min(128, self.text:GetStringWidth()+50)
local texCoordoffsetX = (newWidth/128)*0.25;
self:GetNormalTexture():SetTexCoord(0.70312500-texCoordoffsetX, 0.70312500, 0.00781250, 0.24218750);
self:GetPushedTexture():SetTexCoord(0.70312500-texCoordoffsetX, 0.70312500, 0.25781250, 0.49218750);
self:GetHighlightTexture():SetTexCoord(0.70312500-texCoordoffsetX, 0.71312500, 0.50781250, 0.74218750);
self:SetWidth(newWidth);
</OnLoad>
<OnClick>
PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
</OnClick>
</Scripts>
<NormalTexture file="Interface\HelpFrame\CS_HelpTextures">
</NormalTexture>
<PushedTexture file="Interface\HelpFrame\CS_HelpTextures">
</PushedTexture>
<HighlightTexture file="Interface\HelpFrame\CS_HelpTextures" alphaMode="ADD">
</HighlightTexture>
<ButtonText name="$parentText" inherits="GameFontNormal" justifyH="LEFT" parentKey="text">
<Size x="0" y="12"/>
<Anchors>
<Anchor point="LEFT" x="10" y="0"/>
<Anchor point="RIGHT" x="-30" y="0"/>
</Anchors>
</ButtonText>
</Button>
</Frames>
</Frame>
</Ui>
@@ -0,0 +1,117 @@
local SetPortraitTexture = SetPortraitTexture
local SetBagPortraitTexture = SetBagPortraitTexture
TitledPanelMixin = {};
function TitledPanelMixin:GetTitleText()
return self.TitleContainer.TitleText;
end
function TitledPanelMixin:SetTitleColor(color)
self:GetTitleText():SetTextColor(color:GetRGBA());
end
function TitledPanelMixin:SetTitle(title)
self:GetTitleText():SetText(title);
end
function TitledPanelMixin:SetTitleFormatted(fmt, ...)
self:GetTitleText():SetFormattedText(fmt, ...);
end
function TitledPanelMixin:SetTitleMaxLinesAndHeight(maxLines, height)
self:GetTitleText():SetMaxLines(maxLines);
self:GetTitleText():SetHeight(height);
end
function TitledPanelMixin:SetTitleOffsets(leftOffset, rightOffset)
self.TitleContainer:SetPoint("TOPLEFT", self, "TOPLEFT", leftOffset or 58, -1);
self.TitleContainer:SetPoint("TOPRIGHT", self, "TOPRIGHT", rightOffset or -24, -1);
end
DefaultPanelMixin = CreateFromMixins(TitledPanelMixin);
PortraitFrameMixin = CreateFromMixins(TitledPanelMixin);
function PortraitFrameMixin:GetPortrait()
return self.portrait;
end
function PortraitFrameMixin:GetTitleText()
return self.TitleText;
end
function PortraitFrameMixin:SetBorder(layoutName)
local layout = NineSliceUtil.GetLayout(layoutName);
NineSliceUtil.ApplyLayout(self.NineSlice, layout);
end
function PortraitFrameMixin:SetPortraitToAsset(texture)
SetPortraitToTexture(self:GetPortrait(), texture);
end
function PortraitFrameMixin:SetPortraitToUnit(unit)
SetPortraitTexture(self:GetPortrait(), unit);
end
function PortraitFrameMixin:SetPortraitToBag(bagID)
SetBagPortraitTexture(self:GetPortrait(), bagID);
end
function PortraitFrameMixin:SetPortraitTextureRaw(texture)
self:GetPortrait():SetTexture(texture);
end
function PortraitFrameMixin:SetPortraitAtlasRaw(atlas, ...)
self:GetPortrait():SetAtlas(atlas, ...);
end
function PortraitFrameMixin:SetPortraitToClassIcon(classFilename)
self:SetPortraitToAsset("Interface/TargetingFrame/UI-Classes-Circles");
local left, right, bottom, top = unpack(CLASS_ICON_TCOORDS[string.upper(classFilename)]);
self:SetPortraitTexCoord(left, right, bottom, top);
end
function PortraitFrameMixin:SetPortraitTexCoord(...)
self:GetPortrait():SetTexCoord(...);
end
function PortraitFrameMixin:SetPortraitShown(shown)
self:GetPortrait():SetShown(shown);
end
function PortraitFrameMixin:SetPortraitTextureSizeAndOffset(size, offsetX, offsetY)
local portrait = self:GetPortrait();
portrait:SetSize(size, size);
portrait:SetPoint("TOPLEFT", self, "TOPLEFT", offsetX, offsetY);
end
do
local function SetFrameLevelInternal(frame, level)
if frame then
-- assertsafe(level < 10000, "Base level needs to be low enough to accomodate the existing layering"); -- TODO: Should become a constant
frame:SetFrameLevel(level);
end
end
function PortraitFrameMixin:SetFrameLevelsFromBaseLevel(baseLevel)
SetFrameLevelInternal(self.NineSlice, baseLevel + 2);
SetFrameLevelInternal(self.PortraitContainer, baseLevel + 1);
SetFrameLevelInternal(self.TitleContainer, baseLevel + 3);
SetFrameLevelInternal(self.CloseButton, baseLevel + 3);
end
end
PortraitFrameFlatBaseMixin = {};
function PortraitFrameFlatBaseMixin:SetBackgroundColor(color)
if self.Bg then
local bg = self.Bg;
color = color or PANEL_BACKGROUND_COLOR;
local r, g, b, a = color:GetRGBA();
bg.BottomLeft:SetVertexColor(r, g, b, a);
bg.BottomRight:SetVertexColor(r, g, b, a);
bg.BottomEdge:SetColorTexture(r, g, b, a);
bg.TopSection:SetColorTexture(r, g, b, a);
end
end
@@ -0,0 +1,365 @@
-- SOUND KITS
SOUNDKIT = {
LOOT_WINDOW_COIN_SOUND = 120,
INTERFACE_SOUND_LOST_TARGET_UNIT = 684,
GS_TITLE_OPTIONS = 778,
GS_TITLE_CREDITS = 790,
GS_TITLE_OPTION_OK = 798,
GS_TITLE_OPTION_EXIT = 799,
GS_LOGIN = 800,
GS_LOGIN_NEW_ACCOUNT = 801,
GS_LOGIN_CHANGE_REALM_OK = 805,
GS_LOGIN_CHANGE_REALM_CANCEL = 807,
GS_CHARACTER_SELECTION_ENTER_WORLD = 809,
GS_CHARACTER_SELECTION_DEL_CHARACTER = 810,
GS_CHARACTER_SELECTION_ACCT_OPTIONS = 811,
GS_CHARACTER_SELECTION_EXIT = 812,
GS_CHARACTER_SELECTION_CREATE_NEW = 813,
GS_CHARACTER_CREATION_CLASS = 814,
GS_CHARACTER_CREATION_LOOK = 817,
GS_CHARACTER_CREATION_CREATE_CHAR = 818,
GS_CHARACTER_CREATION_CANCEL = 819,
IG_MINIMAP_OPEN = 821,
IG_MINIMAP_CLOSE = 822,
IG_MINIMAP_ZOOM_IN = 823,
IG_MINIMAP_ZOOM_OUT = 824,
IG_CHAT_EMOTE_BUTTON = 825,
IG_CHAT_SCROLL_UP = 826,
IG_CHAT_SCROLL_DOWN = 827,
IG_CHAT_BOTTOM = 828,
IG_SPELLBOOK_OPEN = 829,
IG_SPELLBOOK_CLOSE = 830,
IG_ABILITY_OPEN = 834,
IG_ABILITY_CLOSE = 835,
IG_ABILITY_PAGE_TURN = 836,
IG_ABILITY_ICON_DROP = 838,
IG_CHARACTER_INFO_OPEN = 839,
IG_CHARACTER_INFO_CLOSE = 840,
IG_CHARACTER_INFO_TAB = 841,
IG_QUEST_LOG_OPEN = 844,
IG_QUEST_LOG_CLOSE = 845,
IG_QUEST_LOG_ABANDON_QUEST = 846,
IG_MAINMENU_OPEN = 850,
IG_MAINMENU_CLOSE = 851,
IG_MAINMENU_OPTION = 852,
IG_MAINMENU_LOGOUT = 853,
IG_MAINMENU_QUIT = 854,
IG_MAINMENU_CONTINUE = 855,
IG_MAINMENU_OPTION_CHECKBOX_ON = 856,
IG_MAINMENU_OPTION_CHECKBOX_OFF = 857,
IG_MAINMENU_OPTION_FAER_TAB = 858,
IG_INVENTORY_ROTATE_CHARACTER = 861,
IG_BACKPACK_OPEN = 862,
IG_BACKPACK_CLOSE = 863,
IG_BACKPACK_COIN_SELECT = 864,
IG_BACKPACK_COIN_OK = 865,
IG_BACKPACK_COIN_CANCEL = 866,
IG_CHARACTER_NPC_SELECT = 867,
IG_CREATURE_NEUTRAL_SELECT = 871,
IG_CREATURE_AGGRO_SELECT = 873,
IG_QUEST_LIST_OPEN = 875,
IG_QUEST_LIST_CLOSE = 876,
IG_QUEST_LIST_SELECT = 877,
IG_QUEST_LIST_COMPLETE = 878,
IG_QUEST_CANCEL = 879,
IG_PLAYER_INVITE = 880,
MONEY_FRAME_OPEN = 891,
MONEY_FRAME_CLOSE = 892,
U_CHAT_SCROLL_BUTTON = 1115,
PUT_DOWN_SMALL_CHAIN = 1212,
LOOT_WINDOW_OPEN_EMPTY = 1264,
TELL_MESSAGE = 3081,
MAP_PING = 3175,
FISHING_REEL_IN = 3407,
IG_PVP_UPDATE = 4574,
AUCTION_WINDOW_OPEN = 5274,
AUCTION_WINDOW_CLOSE = 5275,
TUTORIAL_POPUP = 7355,
ITEM_REPAIR = 7994,
PVP_ENTER_QUEUE = 8458,
PVP_THROUGH_QUEUE = 8459,
KEY_RING_OPEN = 8938,
KEY_RING_CLOSE = 8939,
RAID_WARNING = 8959,
READY_CHECK = 8960,
GLUESCREEN_INTRO = 9902,
AMB_GLUESCREEN_HUMAN = 9903,
AMB_GLUESCREEN_ORC = 9905,
AMB_GLUESCREEN_TAUREN = 9906,
AMB_GLUESCREEN_DWARF = 9907,
AMB_GLUESCREEN_NIGHTELF = 9908,
AMB_GLUESCREEN_UNDEAD = 9909,
AMB_GLUESCREEN_BLOODELF = 9910,
AMB_GLUESCREEN_DRAENEI = 9911,
JEWEL_CRAFTING_FINALIZE = 10590,
MENU_CREDITS01 = 10763,
MENU_CREDITS02 = 10804,
GUILD_VAULT_OPEN = 12188,
GUILD_VAULT_CLOSE = 12189,
RAID_BOSS_EMOTE_WARNING = 12197,
GUILD_BANK_OPEN_BAG = 12206,
GS_LICH_KING = 12765,
ALARM_CLOCK_WARNING_2 = 12867,
ALARM_CLOCK_WARNING_3 = 12889,
MENU_CREDITS03 = 13822,
ACHIEVEMENT_MENU_OPEN = 13832,
ACHIEVEMENT_MENU_CLOSE = 13833,
BARBERSHOP_HAIRCUT = 13873,
BARBERSHOP_SIT = 14148,
GM_CHAT_WARNING = 15273,
LFG_REWARDS = 17316,
LFG_ROLE_CHECK = 17317,
LFG_DENIED = 17341,
UI_BNET_TOAST = 18019,
ALARM_CLOCK_WARNING_1 = 18871,
AMB_GLUESCREEN_WORGEN = 20169,
AMB_GLUESCREEN_GOBLIN = 20170,
AMB_GLUESCREEN_TROLL = 21136,
AMB_GLUESCREEN_GNOME = 21137,
UI_POWER_AURA_GENERIC = 23287,
UI_REFORGING_REFORGE = 23291,
UI_AUTO_QUEST_COMPLETE = 23404,
GS_CATACLYSM = 23640,
MENU_CREDITS04 = 23812,
UI_BATTLEGROUND_COUNTDOWN_TIMER = 25477,
UI_BATTLEGROUND_COUNTDOWN_FINISHED = 25478,
UI_VOID_STORAGE_UNLOCK = 25711,
UI_VOID_STORAGE_DEPOSIT = 25712,
UI_VOID_STORAGE_WITHDRAW = 25713,
UI_TRANSMOGRIFY_UNDO = 25715,
UI_ETHEREAL_WINDOW_OPEN = 25716,
UI_ETHEREAL_WINDOW_CLOSE = 25717,
UI_TRANSMOGRIFY_REDO = 25738,
UI_VOID_STORAGE_BOTH = 25744,
AMB_GLUESCREEN_PANDAREN = 25848,
MUS_50_HEART_OF_PANDARIA_MAINTITLE = 28509,
UI_PET_BATTLES_TRAP_READY = 28814,
UI_EPICLOOT_TOAST = 31578,
UI_BONUS_LOOT_ROLL_START = 31579,
UI_BONUS_LOOT_ROLL_LOOP = 31580,
UI_BONUS_LOOT_ROLL_END = 31581,
UI_PET_BATTLE_START = 31584,
UI_SCENARIO_ENDING = 31754,
UI_SCENARIO_STAGE_END = 31757,
MENU_CREDITS05 = 32015,
UI_PET_BATTLE_CAMERA_MOVE_IN = 32047,
UI_PET_BATTLE_CAMERA_MOVE_OUT = 32052,
AMB_50_GLUESCREEN_ALLIANCE = 32412,
AMB_50_GLUESCREEN_HORDE = 32413,
AMB_50_GLUESCREEN_PANDAREN_NEUTRAL = 32414,
UI_CHALLENGES_NEW_RECORD = 33338,
MENU_CREDITS06 = 34020,
UI_LOSS_OF_CONTROL_START = 34468,
UI_PET_BATTLES_PVP_THROUGH_QUEUE = 36609,
AMB_GLUESCREEN_DEATHKNIGHT = 37056,
UI_RAID_BOSS_WHISPER_WARNING = 37666,
UI_DIG_SITE_COMPLETION_TOAST = 38326,
UI_IG_STORE_PAGE_NAV_BUTTON = 39511,
UI_IG_STORE_WINDOW_OPEN_BUTTON = 39512,
UI_IG_STORE_WINDOW_CLOSE_BUTTON = 39513,
UI_IG_STORE_CANCEL_BUTTON = 39514,
UI_IG_STORE_BUY_BUTTON = 39515,
UI_IG_STORE_CONFIRM_PURCHASE_BUTTON = 39516,
UI_IG_STORE_PURCHASE_DELIVERED_TOAST_01 = 39517,
MUS_60_MAIN_TITLE = 40169,
UI_GARRISON_MISSION_COMPLETE_ENCOUNTER_FAIL = 43501,
UI_GARRISON_MISSION_COMPLETE_MISSION_SUCCESS = 43502,
UI_GARRISON_MISSION_COMPLETE_MISSION_FAIL_STINGER = 43503,
UI_GARRISON_MISSION_THREAT_COUNTERED = 43505,
UI_GARRISON_MISSION_100_PERCENT_CHANCE_REACHED_NOT_USED = 43507,
UI_QUEST_ROLLING_FORWARD_01 = 43936,
UI_BAG_SORTING_01 = 43937,
UI_TOYBOX_TABS = 43938,
UI_GARRISON_TOAST_INVASION_ALERT = 44292,
UI_GARRISON_TOAST_MISSION_COMPLETE = 44294,
UI_GARRISON_TOAST_BUILDING_COMPLETE = 44295,
UI_GARRISON_TOAST_FOLLOWER_GAINED = 44296,
UI_GARRISON_NAV_TABS = 44297,
UI_GARRISON_GARRISON_REPORT_OPEN = 44298,
UI_GARRISON_GARRISON_REPORT_CLOSE = 44299,
UI_GARRISON_ARCHITECT_TABLE_OPEN = 44300,
UI_GARRISON_ARCHITECT_TABLE_CLOSE = 44301,
UI_GARRISON_ARCHITECT_TABLE_UPGRADE = 44302,
UI_GARRISON_ARCHITECT_TABLE_UPGRADE_CANCEL = 44304,
UI_GARRISON_ARCHITECT_TABLE_UPGRADE_START = 44305,
UI_GARRISON_ARCHITECT_TABLE_PLOT_SELECT = 44306,
UI_GARRISON_ARCHITECT_TABLE_BUILDING_SELECT = 44307,
UI_GARRISON_ARCHITECT_TABLE_BUILDING_PLACEMENT = 44308,
UI_GARRISON_COMMAND_TABLE_OPEN = 44311,
UI_GARRISON_COMMAND_TABLE_CLOSE = 44312,
UI_GARRISON_COMMAND_TABLE_MISSION_CLOSE = 44313,
UI_GARRISON_COMMAND_TABLE_NAV_NEXT = 44314,
UI_GARRISON_COMMAND_TABLE_SELECT_MISSION = 44315,
UI_GARRISON_COMMAND_TABLE_SELECT_FOLLOWER = 44316,
UI_GARRISON_COMMAND_TABLE_FOLLOWER_ABILITY_OPEN = 44317,
UI_GARRISON_COMMAND_TABLE_FOLLOWER_ABILITY_CLOSE = 44318,
UI_GARRISON_COMMAND_TABLE_ASSIGN_FOLLOWER = 44319,
UI_GARRISON_COMMAND_TABLE_UNASSIGN_FOLLOWER = 44320,
UI_GARRISON_COMMAND_TABLE_SLOT_CHAMPION = 72546,
UI_GARRISON_COMMAND_TABLE_SLOT_TROOP = 72547,
UI_GARRISON_COMMAND_TABLE_REDUCED_SUCCESS_CHANCE = 44321,
UI_GARRISON_COMMAND_TABLE_100_SUCCESS = 44322,
UI_GARRISON_COMMAND_TABLE_MISSION_START = 44323,
UI_GARRISON_COMMAND_TABLE_VIEW_MISSION_REPORT = 44324,
UI_GARRISON_COMMAND_TABLE_MISSION_SUCCESS_STINGER = 44330,
UI_GARRISON_COMMAND_TABLE_CHEST_UNLOCK = 44331,
UI_GARRISON_COMMAND_TABLE_CHEST_UNLOCK_GOLD_SUCCESS = 44332,
UI_GARRISON_MONUMENTS_OPEN = 44344,
UI_BONUS_EVENT_SYSTEM_VIGNETTES = 45142,
UI_GARRISON_COMMAND_TABLE_FOLLOWER_LEVEL_UP = 46893,
UI_GARRISON_ARCHITECT_TABLE_BUILDING_PLACEMENT_ERROR = 47355,
UI_GARRISON_MONUMENTS_CLOSE = 47373,
AMB_GLUESCREEN_WARLORDS_OF_DRAENOR = 47544,
MUS_1_0_MAINTITLE_ORIGINAL = 47598,
UI_GROUP_FINDER_RECEIVE_APPLICATION = 47615,
UI_GARRISON_MISSION_ENCOUNTER_ANIMATION_GENERIC = 47704,
UI_GARRISON_START_WORK_ORDER = 47972,
UI_GARRISON_SHIPMENTS_WINDOW_OPEN = 48191,
UI_GARRISON_SHIPMENTS_WINDOW_CLOSE = 48192,
UI_GARRISON_MONUMENTS_NAV = 48942,
UI_RAID_BOSS_DEFEATED = 50111,
UI_PERSONAL_LOOT_BANNER = 50893,
UI_GARRISON_FOLLOWER_LEARN_TRAIT = 51324,
UI_GARRISON_SHIPYARD_PLACE_CARRIER = 51385,
UI_GARRISON_SHIPYARD_PLACE_GALLEON = 51387,
UI_GARRISON_SHIPYARD_PLACE_DREADNOUGHT = 51388,
UI_GARRISON_SHIPYARD_PLACE_SUBMARINE = 51389,
UI_GARRISON_SHIPYARD_PLACE_LANDING_CRAFT = 51390,
UI_GARRISON_SHIPYARD_START_MISSION = 51401,
UI_RAID_LOOT_TOAST_LESSER_ITEM_WON = 51402,
UI_WARFORGED_ITEM_LOOT_TOAST = 51561,
UI_GARRISON_COMMAND_TABLE_INCREASED_SUCCESS_CHANCE = 51570,
UI_GARRISON_SHIPYARD_DECOMISSION_SHIP = 51871,
UI_70_ARTIFACT_FORGE_TRAIT_FIRST_TRAIT = 54126,
UI_70_ARTIFACT_FORGE_RELIC_PLACE = 54128,
UI_70_ARTIFACT_FORGE_APPEARANCE_COLOR_SELECT = 54130,
UI_70_ARTIFACT_FORGE_APPEARANCE_LOCKED = 54131,
UI_70_ARTIFACT_FORGE_APPEARANCE_APPEARANCE_CHANGE = 54132,
UI_70_ARTIFACT_FORGE_TOAST_TRAIT_AVAILABLE = 54133,
UI_70_ARTIFACT_FORGE_APPEARANCE_APPEARANCE_UNLOCK = 54139,
UI_70_ARTIFACT_FORGE_TRAIT_GOLD_TRAIT = 54125,
UI_72_ARTIFACT_FORGE_FINAL_TRAIT_UNLOCKED = 83682,
UI_70_ARTIFACT_FORGE_TRAIT_FINALRANK = 54127,
UI_70_ARTIFACT_FORGE_TRAIT_RANKUP = 54129,
AMB_GLUESCREEN_DEMONHUNTER = 56352,
MUS_70_MAIN_TITLE = 56353,
MENU_CREDITS07 = 56354,
UI_TRANSMOG_ITEM_CLICK = 62538,
UI_TRANSMOG_PAGE_TURN = 62539,
UI_TRANSMOG_GEAR_SLOT_CLICK = 62540,
UI_TRANSMOG_REVERTING_GEAR_SLOT = 62541,
UI_TRANSMOG_APPLY = 62542,
UI_TRANSMOG_CLOSE_WINDOW = 62543,
UI_TRANSMOG_OPEN_WINDOW = 62544,
UI_LEGENDARY_LOOT_TOAST = 63971,
UI_STORE_UNWRAP = 64329,
AMB_GLUESCREEN_LEGION = 71535,
UI_MISSION_200_PERCENT = 72548,
UI_MISSION_MAP_ZOOM = 72549,
UI_70_BOOST_THANKSFORPLAYING_SMALLER = 72978,
UI_70_BOOST_THANKSFORPLAYING = 72977,
UI_WORLDQUEST_START = 73275,
UI_WORLDQUEST_MAP_SELECT = 73276,
UI_WORLDQUEST_COMPLETE = 73277,
UI_ORDERHALL_TALENT_SELECT = 73279,
UI_ORDERHALL_TALENT_READY_TOAST = 73280,
UI_ORDERHALL_TALENT_READY_CHECK = 73281,
UI_ORDERHALL_TALENT_NUKE_FROM_ORBIT = 73282,
UI_ORDERHALL_TALENT_WINDOW_OPEN = 73914,
UI_ORDERHALL_TALENT_WINDOW_CLOSE = 73915,
UI_PROFESSIONS_WINDOW_OPEN = 73917,
UI_PROFESSIONS_WINDOW_CLOSE = 73918,
UI_PROFESSIONS_NEW_RECIPE_LEARNED_TOAST = 73919,
UI_70_CHALLENGE_MODE_SOCKET_PAGE_OPEN = 74421,
UI_70_CHALLENGE_MODE_SOCKET_PAGE_CLOSE = 74423,
UI_70_CHALLENGE_MODE_SOCKET_PAGE_SOCKET = 74431,
UI_70_CHALLENGE_MODE_SOCKET_PAGE_ACTIVATE_BUTTON = 74432,
UI_70_CHALLENGE_MODE_KEYSTONE_UPGRADE = 74437,
UI_70_CHALLENGE_MODE_NEW_RECORD = 74438,
UI_70_CHALLENGE_MODE_SOCKET_PAGE_REMOVE_KEYSTONE = 74525,
UI_70_CHALLENGE_MODE_COMPLETE_NO_UPGRADE = 74526,
UI_MISSION_SUCCESS_CHEERS = 74702,
UI_PVP_HONOR_PRESTIGE_OPEN_WINDOW = 76995,
UI_PVP_HONOR_PRESTIGE_WINDOW_CLOSE = 77002,
UI_PVP_HONOR_PRESTIGE_RANK_UP = 77003,
UI_71_SOCIAL_QUEUEING_TOAST = 79739,
UI_72_ARTIFACT_FORGE_ACTIVATE_FINAL_TIER = 83681,
UI_72_BUILDINGS_CONTRIBUTE_POWER_MENU_CLICK = 84240,
UI_72_BUILDING_CONTRIBUTION_TABLE_OPEN = 84368,
UI_72_BUILDINGS_CONTRIBUTION_TABLE_CLOSE = 84369,
UI_72_BUILDINGS_CONTRIBUTE_RESOURCES = 84378,
UI_72_ARTIFACT_FORGE_FINAL_TRAIT_REFUND_START = 83684,
UI_72_ARTIFACT_FORGE_FINAL_TRAIT_REFUND_LOOP = 83685,
UI_73_ARTIFACT_RELICS_TRAIT_SELECT_AND_REVEAL = 89685,
UI_73_ARTIFACT_RELICS_TRAIT_SELECT_ONLY = 89686,
UI_73_ARTIFACT_RELICS_TRAIT_REVEAL_ONLY = 90080,
PUT_DOWN_GEMS = 1204,
PICK_UP_GEMS = 1221,
UI_73_ARTIFACT_OVERLOADED_HIGHEST = 97301,
UI_73_ARTIFACT_OVERLOADED_HIGH = 97471,
UI_73_ARTIFACT_OVERLOADED_MEDIUM = 97470,
UI_73_ARTIFACT_OVERLOADED_LOW = 97469,
UI_73_ARTIFACT_OVERLOADED_ORB_HIGHEST = 97559,
UI_73_ARTIFACT_OVERLOADED_ORB_HIGH = 97558,
UI_73_ARTIFACT_OVERLOADED_ORB_MEDIUM = 97557,
UI_73_ARTIFACT_OVERLOADED_ORB_LOW = 97556,
UI_73_ARTIFACT_OVERLOADED_ORB_IMPACT_MEDIUM = 97597,
UI_73_ARTIFACT_OVERLOADED_ORB_IMPACT_LOW = 97598,
AMB_GLUESCREEN_VOIDELF = 97324,
AMB_GLUESCREEN_LIGHTFORGEDDRAENEI = 97325,
AMB_GLUESCREEN_NIGHTBORNE = 97326,
AMB_GLUESCREEN_HIGHMOUNTAINTAUREN = 97327,
UI_VOICECHAT_DEAFENOFF = 110990,
UI_VOICECHAT_DEAFENON = 110989,
UI_VOICECHAT_MUTEOFF = 110988,
UI_VOICECHAT_MUTEON = 110987,
UI_VOICECHAT_MUTEOTHEROFF = 111080,
UI_VOICECHAT_MUTEOTHERON = 111081,
UI_VOICECHAT_MEMBERLEAVECHANNEL = 110986,
UI_VOICECHAT_MEMBERJOINCHANNEL = 110985,
UI_VOICECHAT_STOPTALK = 110984,
UI_VOICECHAT_TALKSTART = 110983,
UI_VOICECHAT_LEAVECHANNEL = 110982,
UI_VOICECHAT_JOINCHANNEL = 110981,
AMB_GLUESCREEN_BATTLE_FOR_AZEROTH = 113894,
AMB_GLUESCREEN_MAGHARORC = 113556,
AMB_GLUESCREEN_DARKIRONDWARF = 113557,
MUS_80_MAIN_TITLE = 113559,
MENU_CREDITS08 = 113560,
UI_AZERITE_EMPOWERED_ITEM_LOOT_TOAST = 118238,
UI_80_AZERITEARMOR_SELECTBUFF = 114993,
UI_80_AZERITEARMOR_BUFFAVAILABLE = 116657,
UI_80_AZERITEARMOR_ROTATION_LOOP = 114994,
UI_80_AZERITEARMOR_ROTATIONENDS = 114995,
UI_80_AZERITEARMOR_ROTATIONENDS_FINALTRAIT = 116233,
UI_80_AZERITEARMOR_ROTATIONENDCLICKS = 115314,
UI_80_AZERITEARMOR_ROTATIONSTARTCLICKS = 115358,
UI_80_AZERITEARMOR_FIRSTTIMEFLOURISH = 118264,
UI_80_AZERITEARMOR_REFORGE_ETHEREALWINDOW_OPEN = 114990,
UI_80_AZERITEARMOR_REFORGE_ETHEREALWINDOW_CLOSE = 114991,
UI_80_AZERITEARMOR_REFORGE = 114992,
UI_80_ISLANDS_AZERITECOLLECTION_START = 115003,
UI_80_ISLANDS_AZERITECOLLECTION_LOOP = 115004,
UI_80_ISLANDS_AZERITECOLLECTION_STOP = 115005,
UI_80_SCRAPPING_WINDOW_CLOSE = 115487,
UI_80_SCRAPPING_WINDOW_OPEN = 115488,
UI_WARMODE_ACTIVATE = 118563,
UI_WARMODE_DECTIVATE = 118564,
UI_80_ISLANDS_TABLE_OPEN = 118610,
UI_80_ISLANDS_TABLE_CLOSE = 118611,
UI_80_ISLANDS_TABLE_SELECT_DIFFICULTY = 118612,
UI_80_ISLANDS_TABLE_FIND_GROUP = 118613,
UI_80_ISLANDS_TABLE_FIND_GROUP_PVP = 118668,
UI_80_ISLANDS_TUTORIAL_CLOSE = 119726,
UI_GARRISON_MISSION_COMPLETE_ENCOUNTER_CHANCE = 0, -- missing SoundKit entry!
UI_COUNTDOWN_BAR_STATE_STARTS = 158958,
UI_COUNTDOWN_BAR_STATE_FINISHED = 158959,
UI_COUNTDOWN_MEDIUM_NUMBER_FINISHED = 158960,
UI_COUNTDOWN_TIMER = 191352,
UI_COUNTDOWN_FINISHED = 158566,
};
@@ -0,0 +1,436 @@
C_PlayerGuide = C_PlayerGuide or {}
local L = C_PlayerGuide
local PREFIX = "MWPG"
local SEP = "\t"
local currentData = nil
local lastError = nil
local uiState = "loading" -- "data" | "empty" | "loading"
local listeners = {}
local inbox = {} -- [cmd] = { total, got, chunks }
-- ===== minimal JSON decoder =====
local function jsonDecode(s)
if type(s) ~= "string" then return nil end
local pos = 1
local parseValue, parseString, parseNumber, parseObject, parseArray
local function skipWS()
local _, e = s:find("^%s*", pos)
if e then pos = e + 1 end
end
parseString = function()
pos = pos + 1
local result = {}
while pos <= #s do
local c = s:sub(pos, pos)
if c == '"' then
pos = pos + 1
return table.concat(result)
elseif c == '\\' then
pos = pos + 1
local esc = s:sub(pos, pos)
pos = pos + 1
if esc == '"' then result[#result+1] = '"'
elseif esc == '\\' then result[#result+1] = '\\'
elseif esc == '/' then result[#result+1] = '/'
elseif esc == 'n' then result[#result+1] = '\n'
elseif esc == 'r' then result[#result+1] = '\r'
elseif esc == 't' then result[#result+1] = '\t'
elseif esc == 'u' then
local hex = s:sub(pos, pos + 3)
pos = pos + 4
local code = tonumber(hex, 16) or 0
if code < 0x80 then
result[#result+1] = string.char(code)
elseif code < 0x800 then
result[#result+1] = string.char(
0xC0 + math.floor(code / 64),
0x80 + (code % 64))
else
result[#result+1] = string.char(
0xE0 + math.floor(code / 4096),
0x80 + math.floor((code % 4096) / 64),
0x80 + (code % 64))
end
else
result[#result+1] = esc
end
else
result[#result+1] = c
pos = pos + 1
end
end
return table.concat(result)
end
parseNumber = function()
local numStr = s:match("^-?%d+%.?%d*[eE]?[+-]?%d*", pos)
if not numStr then return 0 end
pos = pos + #numStr
return tonumber(numStr) or 0
end
parseArray = function()
pos = pos + 1
local arr = {}
skipWS()
if s:sub(pos, pos) == ']' then pos = pos + 1; return arr end
while pos <= #s do
arr[#arr+1] = parseValue()
skipWS()
local c = s:sub(pos, pos)
if c == ']' then pos = pos + 1; break
elseif c == ',' then pos = pos + 1 end
end
return arr
end
parseObject = function()
pos = pos + 1
local obj = {}
skipWS()
if s:sub(pos, pos) == '}' then pos = pos + 1; return obj end
while pos <= #s do
skipWS()
local key = parseString()
skipWS()
pos = pos + 1
local val = parseValue()
if key ~= nil then obj[key] = val end
skipWS()
local c = s:sub(pos, pos)
if c == '}' then pos = pos + 1; break
elseif c == ',' then pos = pos + 1 end
end
return obj
end
parseValue = function()
skipWS()
local c = s:sub(pos, pos)
if c == '"' then return parseString()
elseif c == '{' then return parseObject()
elseif c == '[' then return parseArray()
elseif c == 't' then pos = pos + 4; return true
elseif c == 'f' then pos = pos + 5; return false
elseif c == 'n' then pos = pos + 4; return nil
else return parseNumber()
end
end
local ok, result = pcall(parseValue)
if ok then return result end
geterrorhandler()("[PlayerGuide] JSON parse error: " .. tostring(result))
return nil
end
-- ===== internal =====
local function notify()
for _, fn in ipairs(listeners) do
pcall(fn, uiState, currentData)
end
end
local function isTable(v) return type(v) == "table" end
local function validate(data)
if not isTable(data) then return false, "payload is not a table" end
if data.stageID and type(data.stageID) ~= "number" then return false, "stageID is not a number" end
if data.progress and type(data.progress) ~= "number" then return false, "progress is not a number" end
if data.objectives and not isTable(data.objectives) then return false, "objectives is not a table" end
if isTable(data.objectives) then
local seen = {}
for i, o in ipairs(data.objectives) do
if not isTable(o) then return false, "objective #" .. i .. " is not a table" end
if o.id and seen[o.id] then return false, "duplicate objective id: " .. tostring(o.id) end
if o.id then seen[o.id] = true end
end
end
return true
end
-- ===== send =====
local function sendCmd(cmd, arg)
local body = cmd
if arg ~= nil then body = body .. SEP .. tostring(arg) end
local player = UnitName("player")
if player then SendAddonMessage(PREFIX, body, "WHISPER", player) end
end
-- ===== receive dispatch =====
local function onAddonMessage(prefix, message, channel)
if prefix ~= PREFIX or channel ~= "WHISPER" then return end
local cmd, seq, total, body =
message:match("^([^\t]+)\t(%d+)\t(%d+)\t(.*)$")
if not cmd then return end
seq = tonumber(seq) or 0
total = tonumber(total) or 0
if seq == 0 or total == 0 then return end
local box = inbox[cmd]
if not box or box.total ~= total then
box = { total = total, got = 0, chunks = {} }
inbox[cmd] = box
end
if box.chunks[seq] == nil then
box.chunks[seq] = body
box.got = box.got + 1
end
if box.got < total then return end
local full = table.concat(box.chunks)
inbox[cmd] = nil
if cmd == "SetData" then
local data = jsonDecode(full)
L.SetData(data)
elseif cmd == "SetStage" then
local stage = jsonDecode(full)
if isTable(stage) and L.HasData() then
currentData.stageID = stage.stageID or currentData.stageID
currentData.stageName = stage.stageName or currentData.stageName
currentData.stageDescription = stage.stageDescription or currentData.stageDescription
currentData.progress = stage.progress or currentData.progress
if stage.stages then currentData.stages = stage.stages end
if stage.objectives then currentData.objectives = stage.objectives end
if stage.recommendations then currentData.recommendations = stage.recommendations end
notify()
end
elseif cmd == "UpdateObjective" then
local obj = jsonDecode(full)
if isTable(obj) and obj.id and L.HasData() then
local objectives = currentData.objectives or {}
for i, existing in ipairs(objectives) do
if existing.id == obj.id then
objectives[i] = obj
break
end
end
notify()
end
elseif cmd == "Notify" then
if DEFAULT_CHAT_FRAME and full ~= "" then
DEFAULT_CHAT_FRAME:AddMessage("|cffffd76a[Путеводитель]|r " .. full)
end
end
end
local _frame = CreateFrame("Frame")
_frame:RegisterEvent("CHAT_MSG_ADDON")
_frame:RegisterEvent("PLAYER_LOGIN")
_frame:SetScript("OnEvent", function(_, event, ...)
if event == "CHAT_MSG_ADDON" then
onAddonMessage(...)
elseif event == "PLAYER_LOGIN" then
L.RequestRefresh()
end
end)
-- ===== public API =====
function L.RegisterListener(fn)
if type(fn) == "function" then table.insert(listeners, fn) end
end
function L.GetState() return uiState end
function L.GetLastError() return lastError end
function L.HasData() return uiState == "data" and currentData ~= nil end
function L.SetData(data)
local ok, err = validate(data)
if not ok then
lastError = err
uiState = "empty"
notify()
return false, err
end
currentData = data
lastError = nil
uiState = "data"
notify()
return true
end
function L.Clear()
currentData = nil
lastError = nil
uiState = "empty"
notify()
end
function L.SetLoading()
if uiState ~= "data" then
uiState = "loading"
notify()
end
end
function L.GetStageInfo()
local d = currentData
if not d then return nil end
return d.stageID, d.stageName, d.stageDescription, d.progress or 0
end
function L.GetStages()
local d = currentData
if not d then return nil end
return d.stages or {}
end
function L.GetNumObjectives()
local d = currentData
if not d or not d.objectives then return 0 end
return #d.objectives
end
function L.GetObjectiveInfo(index)
local d = currentData
if not d or not d.objectives then return nil end
local o = d.objectives[index]
if not o then return nil end
return {
id = o.id,
type = o.type or "custom",
title = o.title or "",
description = o.description or o.subtitle or "",
subtitle = o.subtitle,
completed = o.completed and true or false,
progress = o.progress or 0,
required = o.required or 1,
targetID = o.targetID,
questID = o.questID,
questChainID = o.questChainID,
instanceID = o.instanceID,
ej_instanceID = o.ej_instanceID or o.ejInstanceID,
journalInstanceID = o.journalInstanceID,
mapID = o.mapID,
worldMapAreaID = o.worldMapAreaID,
dungeonID = o.dungeonID,
encounterID = o.encounterID,
achievementID = o.achievementID,
factionID = o.factionID,
itemID = o.itemID,
currencyID = o.currencyID,
order = o.order or index,
}
end
function L.GetNumRecommendations()
local d = currentData
if not d or not d.recommendations then return 0 end
return #d.recommendations
end
function L.GetRecommendationInfo(index)
local d = currentData
if not d or not d.recommendations then return nil end
local r = d.recommendations[index]
if not r then return nil end
return {
id = r.id,
type = r.type or "dungeon",
title = r.title or r.name or "",
name = r.name or r.title or "",
description = r.description or r.loc or "",
loc = r.loc,
level = r.level,
biome = r.biome,
artwork = r.artwork,
cover = r.cover,
coverImage = r.coverImage,
bgImage = r.bgImage,
buttonImage = r.buttonImage,
icon = r.icon,
instanceID = r.instanceID,
ej_instanceID = r.ej_instanceID or r.ejInstanceID,
journalInstanceID = r.journalInstanceID,
mapID = r.mapID,
worldMapAreaID = r.worldMapAreaID,
targetID = r.targetID,
dungeonID = r.dungeonID,
encounterID = r.encounterID,
priority = r.priority or 0,
}
end
function L.GetNumRewards()
local d = currentData
if not d or not d.rewards then return 0 end
return #d.rewards
end
function L.GetRewardInfo(index)
local d = currentData
if not d or not d.rewards then return nil end
return d.rewards[index]
end
function L.RequestRefresh()
L.SetLoading()
sendCmd("RequestRefresh")
end
function L.RequestTrackObjective(objectiveID)
sendCmd("RequestTrackObjective", objectiveID)
end
function L.RequestOpenTarget(objectiveID)
sendCmd("RequestOpenTarget", objectiveID)
end
function L._InjectDevPayload()
L.SetData({
version = 1,
stageID = 5,
stageName = "Подготовка к Нордсколу",
stageDescription =
"Завершите ключевые цепочки заданий в Запределье и докажите готовность к походу на север.",
progress = 62,
stages = {
{ id = 1, name = "Начало пути", short = "Начало пути", status = "done" },
{ id = 2, name = "Огненные Недра", short = "Огн. Недра", status = "done" },
{ id = 3, name = "Тёмный портал", short = "Тёмный портал", status = "done" },
{ id = 4, name = "Покорение Запределья", short = "Запределье", status = "done" },
{ id = 5, name = "Подготовка к Нордсколу",short = "К Нордсколу", status = "active" },
{ id = 6, name = "Заря Нордскола", short = "Нордскол", status = "locked" },
{ id = 7, name = "Цитадель Ледяной Короны",short = "Ледяная Корона",status = "locked" },
},
objectives = {
{ id = 1, type = "level", title = "Достигнуть 68 уровня",
subtitle = "Можно отправляться в Нордскол.",
completed = true, progress = 68, required = 68, order = 1 },
{ id = 2, type = "quest_chain", title = "Цепочка «Поиск виверны» в Награнде",
subtitle = "Кисть Шамана · Награнд",
completed = false, progress = 5, required = 8, order = 2 },
{ id = 3, type = "dungeon", title = "Пройти Кузню Крови",
subtitle = "Цитадель Адского Пламени · 61–64",
completed = false, progress = 0, required = 1, order = 3, instanceID = 256 },
{ id = 4, type = "dungeon", title = "Пройти Подземелья Аукенай",
subtitle = "Аукиндон · 6466",
completed = false, progress = 0, required = 1, order = 4, instanceID = 257 },
{ id = 5, type = "reputation", title = "Дружелюбие с Кенарийской экспедицией",
subtitle = "Нейтрально сейчас · Долина Призрачной Луны",
completed = false, progress = 1200, required = 3000, order = 5, factionID = 942 },
},
recommendations = {
{ id = 1, type = "dungeon", name = "Кузня Крови", loc = "Цитадель Адского Пламени", level = "6164", biome = "fire" },
{ id = 2, type = "dungeon", name = "Подземелья Аукенай", loc = "Аукиндон", level = "6466", biome = "arcane" },
{ id = 3, type = "dungeon", name = "Сетеккские залы", loc = "Награнд", level = "6769", biome = "storm" },
{ id = 4, type = "dungeon", name = "Тёмный Лабиринт", loc = "Аукиндон", level = "6870", biome = "shadow" },
{ id = 5, type = "dungeon", name = "Ботаника", loc = "Долина Призрачной Луны", level = "6970", biome = "nature" },
},
rewards = {},
})
end
@@ -9,3 +9,23 @@
## X-Hidden: 1
MoonWellClient.lua
EncounterJournal\EncounterJournalCompat.lua
EncounterJournal\SharedXML\SoundKitConstants.lua
EncounterJournal\Generated_EncounterJournalData.lua
EncounterJournal\EncounterJournalData.lua
EncounterJournal\EncounterJournalTierFilter.lua
EncounterJournal\EncounterJournalTierSnapshot.lua
EncounterJournal\GlobalStrings.lua
EncounterJournal\Utils\C_EncounterJournal.lua
EncounterJournal\Utils\C_PlayerGuide.lua
EncounterJournal\EncounterJournalInit.lua
EncounterJournal\EncounterJournalFrameMethods.lua
EncounterJournal\EncounterJournalFonts.xml
EncounterJournal\SharedXML\SharedUIPanelTemplates.xml
EncounterJournal\SharedXML\HybridScrollFrame.xml
EncounterJournal\SharedXML\NavigationBar.xml
EncounterJournal\Custom_EncounterJournal\Custom_EncounterJournal.xml
EncounterJournal\Custom_EncounterJournal\PlayerGuide.xml
EncounterJournal\EncounterJournalScrollFix.lua
EncounterJournal\EncounterJournalDungeonMode.lua
EncounterJournal\EncounterJournalOpen.lua

Some files were not shown because too many files have changed in this diff Show More