e504f88167
Three features layered onto the Turtle WoW baseline: - Account manager (AccountLogin.lua / .xml): saved-account list backed by Windows Credential Manager via ClassicAPI. Pass-through to LoginWithSavedAccount; per-account 'last used' timestamp; password field hides itself when the typed name matches a saved entry and a 'Change password' button reveals it on demand. - Character reordering (CharacterSelect.lua / .xml): drag-to-reorder the character list, persisted via ClassicAPI's GetSavedCharacterOrder / SetSavedCharacterOrder. - Glue Lua console (GlueConsole.lua / .xml, new files): in-glue RunScript console reachable from the title and char-select screens. Currently no key opens the console — the backtick binding was removed pending a ClassicAPI EditBox arrow/key script handler (see TODO.md section 96).
709 lines
23 KiB
Lua
709 lines
23 KiB
Lua
CHARACTER_SELECT_ROTATION_START_X = nil;
|
|
CHARACTER_SELECT_INITIAL_FACING = nil;
|
|
|
|
CHARACTER_ROTATION_CONSTANT = 0.6;
|
|
|
|
MAX_CHARACTERS_DISPLAYED = 10;
|
|
MAX_CHARACTERS_PER_REALM = 10;
|
|
|
|
AUTO_DRAG_TIME = 0.5;
|
|
|
|
-- Per-account character autoselect was removed alongside the plaintext
|
|
-- credential store; the AutologinSaveCharacterButton checkbox in
|
|
-- CharacterSelect.xml is now permanently hidden, and the two functions
|
|
-- below are no-op wrappers around `EnterWorld` to keep the existing
|
|
-- call sites working.
|
|
function Autologin_OnCharactersLoad()
|
|
if ( AutologinSaveCharacterButton ) then
|
|
AutologinSaveCharacterButton:Hide();
|
|
end
|
|
end
|
|
|
|
function Autologin_EnterWorld()
|
|
EnterWorld();
|
|
end
|
|
|
|
function CharacterSelect_OnLoad()
|
|
this:SetSequence(0);
|
|
this:SetCamera(0);
|
|
|
|
this.createIndex = 0;
|
|
this.selectedIndex = 0;
|
|
this.selectLast = 0;
|
|
this.currentModel = "";
|
|
this.translationTable = {};
|
|
this.orderChanged = nil;
|
|
this.pressDownButton = nil;
|
|
this.pressDownTime = 0;
|
|
this.draggedIndex = nil;
|
|
this.suppressNextClick = nil;
|
|
this:RegisterEvent("ADDON_LIST_UPDATE");
|
|
this:RegisterEvent("CHARACTER_LIST_UPDATE");
|
|
this:RegisterEvent("UPDATE_SELECTED_CHARACTER");
|
|
this:RegisterEvent("SELECT_LAST_CHARACTER");
|
|
this:RegisterEvent("SELECT_FIRST_CHARACTER");
|
|
this:RegisterEvent("SUGGEST_REALM");
|
|
this:RegisterEvent("FORCE_RENAME_CHARACTER");
|
|
|
|
CharacterSelect:SetModel("Interface\\Glues\\Models\\UI_Orc\\UI_Orc.mdx");
|
|
|
|
local fogInfo = CharModelFogInfo["ORC"];
|
|
CharacterSelect:SetFogColor(fogInfo.r, fogInfo.g, fogInfo.b);
|
|
CharacterSelect:SetFogNear(0);
|
|
CharacterSelect:SetFogFar(fogInfo.far);
|
|
|
|
SetCharSelectModelFrame("CharacterSelect");
|
|
--CharSelectModel:SetLight(1, 0, 0, -0.707, -0.707, 0.7, 1.0, 1.0, 1.0, 0.8, 1.0, 1.0, 0.8);
|
|
|
|
-- Color edit box backdrops
|
|
local backdropColor = DEFAULT_TOOLTIP_COLOR;
|
|
CharacterSelectCharacterFrame:SetBackdropBorderColor(backdropColor[1], backdropColor[2], backdropColor[3]);
|
|
CharacterSelectCharacterFrame:SetBackdropColor(backdropColor[4], backdropColor[5], backdropColor[6], 0.85);
|
|
|
|
end
|
|
|
|
function CharacterSelect_OnShow()
|
|
CurrentGlueMusic = "Sound\\Music\\GlueScreenMusic\\wow_main_theme.mp3";
|
|
|
|
UpdateAddonButton();
|
|
|
|
local serverName, isPVP, isRP = GetServerName();
|
|
local connected = IsConnectedToServer();
|
|
local serverType = "";
|
|
if ( serverName ) then
|
|
if( not connected ) then
|
|
serverName = serverName.."\n("..TEXT(SERVER_DOWN)..")";
|
|
end
|
|
if ( isPVP ) then
|
|
if ( isRP ) then
|
|
serverType = RPPVP_PARENTHESES;
|
|
else
|
|
serverType = PVP_PARENTHESES;
|
|
end
|
|
elseif ( isRP ) then
|
|
serverType = RP_PARENTHESES;
|
|
end
|
|
CharSelectRealmName:SetText(serverName.." "..serverType);
|
|
CharSelectRealmName:Show();
|
|
else
|
|
CharSelectRealmName:Hide();
|
|
end
|
|
|
|
if ( connected ) then
|
|
GetCharacterListUpdate();
|
|
else
|
|
UpdateCharacterList();
|
|
end
|
|
|
|
-- Gameroom billing stuff (For Korea and China only)
|
|
if ( SHOW_GAMEROOM_BILLING_FRAME ) then
|
|
local paymentPlan, hasFallBackBillingMethod, isGameRoom = GetBillingPlan();
|
|
if ( paymentPlan == 0 ) then
|
|
-- No payment plan
|
|
GameRoomBillingFrame:Hide();
|
|
else
|
|
local billingTimeLeft = GetBillingTimeRemaining();
|
|
-- Set default text for the payment plan
|
|
local billingText = getglobal("BILLING_TEXT"..paymentPlan);
|
|
if ( paymentPlan == 1 ) then
|
|
-- Recurring account
|
|
billingTimeLeft = ceil(billingTimeLeft/(60 * 24));
|
|
if ( billingTimeLeft == 1 ) then
|
|
billingText = BILLING_TIME_LEFT_LAST_DAY;
|
|
end
|
|
elseif ( paymentPlan == 2 ) then
|
|
-- Free account
|
|
if ( billingTimeLeft < (24 * 60) ) then
|
|
billingText = format(BILLING_FREE_TIME_EXPIRE, billingTimeLeft.." "..GetText("MINUTES_ABBR", nil, billingTimeLeft));
|
|
end
|
|
elseif ( paymentPlan == 3 ) then
|
|
-- Fixed but not recurring
|
|
if ( isGameRoom == 1 ) then
|
|
if ( billingTimeLeft <= 30 ) then
|
|
billingText = BILLING_GAMEROOM_EXPIRE;
|
|
else
|
|
billingText = format(BILLING_FIXED_IGR, MinutesToTime(billingTimeLeft, 1));
|
|
end
|
|
else
|
|
-- personal fixed plan
|
|
if ( billingTimeLeft < (24 * 60) ) then
|
|
billingText = BILLING_FIXED_LASTDAY;
|
|
else
|
|
billingText = format(billingText, MinutesToTime(billingTimeLeft));
|
|
end
|
|
end
|
|
elseif ( paymentPlan == 4 ) then
|
|
-- Usage plan
|
|
if ( isGameRoom == 1 ) then
|
|
-- game room usage plan
|
|
if ( billingTimeLeft <= 600 ) then
|
|
billingText = BILLING_GAMEROOM_EXPIRE;
|
|
else
|
|
billingText = BILLING_IGR_USAGE;
|
|
end
|
|
else
|
|
-- personal usage plan
|
|
if ( billingTimeLeft <= 30 ) then
|
|
billingText = BILLING_TIME_LEFT_30_MINS;
|
|
else
|
|
billingText = format(billingText, billingTimeLeft);
|
|
end
|
|
end
|
|
end
|
|
-- If fallback payment method add a note that says so
|
|
if ( hasFallBackBillingMethod == 1 ) then
|
|
billingText = billingText.."\n\n"..BILLING_HAS_FALLBACK_PAYMENT;
|
|
end
|
|
GameRoomBillingFrameText:SetText(billingText);
|
|
GameRoomBillingFrame:SetHeight(GameRoomBillingFrameText:GetHeight() + 26);
|
|
GameRoomBillingFrame:Show();
|
|
end
|
|
end
|
|
|
|
-- fadein the character select ui
|
|
GlueFrameFadeIn(CharacterSelectUI, CHARACTER_SELECT_FADE_IN)
|
|
end
|
|
|
|
function CharacterSelect_OnHide()
|
|
CharacterDeleteDialog:Hide();
|
|
CharacterRenameDialog:Hide();
|
|
end
|
|
|
|
function CharacterSelect_OnKeyDown()
|
|
if ( arg1 == "ESCAPE" ) then
|
|
CharacterSelect_Exit();
|
|
elseif ( arg1 == "ENTER" ) then
|
|
CharacterSelect_EnterWorld();
|
|
elseif ( arg1 == "PRINTSCREEN" ) then
|
|
Screenshot();
|
|
elseif ( arg1 == "UP" or arg1 == "LEFT" ) then
|
|
local numChars = GetNumCharacters();
|
|
if ( numChars > 1 ) then
|
|
if ( this.selectedIndex > 1 ) then
|
|
CharacterSelect_SelectCharacter(this.selectedIndex - 1);
|
|
else
|
|
CharacterSelect_SelectCharacter(numChars);
|
|
end
|
|
end
|
|
elseif ( arg1 == "DOWN" or arg1 == "RIGHT" ) then
|
|
local numChars = GetNumCharacters();
|
|
if ( numChars > 1 ) then
|
|
if ( this.selectedIndex < GetNumCharacters() ) then
|
|
CharacterSelect_SelectCharacter(this.selectedIndex + 1);
|
|
else
|
|
CharacterSelect_SelectCharacter(1);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function CharacterSelect_OnEvent()
|
|
if ( event == "ADDON_LIST_UPDATE" ) then
|
|
UpdateAddonButton();
|
|
elseif ( event == "CHARACTER_LIST_UPDATE" ) then
|
|
CharacterSelect_RebuildTranslationTable();
|
|
CharacterOrder_Apply();
|
|
UpdateCharacterList();
|
|
CharSelectCharacterName:SetText(GetCharacterInfo(GetCharIDFromIndex(this.selectedIndex)));
|
|
Autologin_OnCharactersLoad();
|
|
elseif ( event == "UPDATE_SELECTED_CHARACTER" ) then
|
|
-- arg1 is a server-assigned charID; translate to a display index
|
|
-- so `selectedIndex` always means "position in the displayed list".
|
|
if ( arg1 == 0 ) then
|
|
CharSelectCharacterName:SetText("");
|
|
else
|
|
CharSelectCharacterName:SetText(GetCharacterInfo(arg1));
|
|
this.selectedIndex = GetIndexFromCharID(arg1);
|
|
end
|
|
UpdateCharacterSelection();
|
|
elseif ( event == "SELECT_LAST_CHARACTER" ) then
|
|
this.selectLast = 1;
|
|
elseif ( event == "SELECT_FIRST_CHARACTER" ) then
|
|
CharacterSelect_SelectCharacter(1, 1);
|
|
elseif ( event == "SUGGEST_REALM" ) then
|
|
local name = GetRealmInfo(arg1, arg2);
|
|
RealmWizard.suggestedRealmName = name;
|
|
RealmWizard.suggestedCategory = arg1;
|
|
RealmWizard.suggestedID = arg2;
|
|
GlueDialog_Show("SUGGEST_REALM");
|
|
elseif ( event == "FORCE_RENAME_CHARACTER" ) then
|
|
CharacterRenameDialog:Show();
|
|
CharacterRenameText1:SetText(getglobal(arg1));
|
|
end
|
|
end
|
|
|
|
function CharacterSelect_UpdateModel()
|
|
UpdateSelectionCustomizationScene();
|
|
this:AdvanceTime();
|
|
end
|
|
|
|
function UpdateCharacterSelection()
|
|
-- Drag-aware: during a drag, dim every slot except the one currently
|
|
-- holding the dragged character, and override the highlight to track
|
|
-- the drag rather than the engine-side "selected" character.
|
|
local draggedIndex = CharacterSelect.draggedIndex;
|
|
local highlightIndex = draggedIndex or CharacterSelect.selectedIndex;
|
|
|
|
for i=1, MAX_CHARACTERS_DISPLAYED, 1 do
|
|
local btn = getglobal("CharSelectCharacterButton"..i);
|
|
btn:UnlockHighlight();
|
|
if ( draggedIndex and i ~= draggedIndex ) then
|
|
btn:SetAlpha(0.6);
|
|
else
|
|
btn:SetAlpha(1);
|
|
end
|
|
end
|
|
|
|
if ( highlightIndex and (highlightIndex > 0) and (highlightIndex <= MAX_CHARACTERS_DISPLAYED) ) then
|
|
getglobal("CharSelectCharacterButton"..highlightIndex):LockHighlight();
|
|
end
|
|
end
|
|
|
|
function UpdateCharacterList()
|
|
local numChars = GetNumCharacters();
|
|
local index = 1;
|
|
local coords;
|
|
for i=1, numChars, 1 do
|
|
local name, race, class, level, zone, fileString, gender, ghost = GetCharacterInfo(GetCharIDFromIndex(i));
|
|
if ( gender == 0 ) then
|
|
gender = "MALE";
|
|
else
|
|
gender = "FEMALE";
|
|
end
|
|
local button = getglobal("CharSelectCharacterButton"..index);
|
|
if ( not name ) then
|
|
button:SetText("ERROR - Tell Jeremy");
|
|
else
|
|
if ( not zone ) then
|
|
zone = "";
|
|
end
|
|
local classToken = TW_CLASS_TOKEN and TW_CLASS_TOKEN[class];
|
|
if ( classToken and CLASS_COLORS[classToken] ) then
|
|
class = CLASS_COLORS[classToken] .. class .. "|r";
|
|
end
|
|
getglobal("CharSelectCharacterButton"..index.."ButtonTextName"):SetText(name);
|
|
if( ghost ) then
|
|
getglobal("CharSelectCharacterButton"..index.."ButtonTextInfo"):SetText(format(TEXT(CHARACTER_SELECT_INFO_GHOST), level, class));
|
|
else
|
|
getglobal("CharSelectCharacterButton"..index.."ButtonTextInfo"):SetText(format(TEXT(CHARACTER_SELECT_INFO), level, class));
|
|
end
|
|
getglobal("CharSelectCharacterButton"..index.."ButtonTextLocation"):SetText(zone);
|
|
end
|
|
button:Show();
|
|
|
|
index = index + 1;
|
|
if ( index > MAX_CHARACTERS_DISPLAYED ) then
|
|
break;
|
|
end
|
|
end
|
|
|
|
if ( numChars == 0 ) then
|
|
CharacterSelectDeleteButton:Disable();
|
|
CharSelectEnterWorldButton:Disable();
|
|
else
|
|
CharacterSelectDeleteButton:Enable();
|
|
CharSelectEnterWorldButton:Enable();
|
|
end
|
|
|
|
CharacterSelect.createIndex = 0;
|
|
CharSelectCreateCharacterButton:Hide();
|
|
|
|
local connected = IsConnectedToServer();
|
|
for i=index, MAX_CHARACTERS_DISPLAYED, 1 do
|
|
local button = getglobal("CharSelectCharacterButton"..index);
|
|
if ( (CharacterSelect.createIndex == 0) and (numChars < MAX_CHARACTERS_PER_REALM) ) then
|
|
CharacterSelect.createIndex = index;
|
|
if ( connected ) then
|
|
--If can create characters position and show the create button
|
|
CharSelectCreateCharacterButton:SetID(index);
|
|
--CharSelectCreateCharacterButton:SetPoint("TOP", button, "TOP", 0, -5);
|
|
CharSelectCreateCharacterButton:Show();
|
|
end
|
|
end
|
|
button:Hide();
|
|
index = index + 1;
|
|
end
|
|
|
|
if ( numChars == 0 ) then
|
|
CharacterSelect.selectedIndex = 0;
|
|
return;
|
|
end
|
|
|
|
if ( CharacterSelect.selectLast == 1 ) then
|
|
CharacterSelect.selectLast = 0;
|
|
CharacterSelect_SelectCharacter(numChars, 1);
|
|
return;
|
|
end
|
|
|
|
if ( (CharacterSelect.selectedIndex == 0) or (CharacterSelect.selectedIndex > numChars) ) then
|
|
CharacterSelect.selectedIndex = 1;
|
|
end
|
|
CharacterSelect_SelectCharacter(CharacterSelect.selectedIndex, 1);
|
|
end
|
|
|
|
function CharacterSelect_OnChar()
|
|
end
|
|
|
|
function CharacterSelectButton_OnClick()
|
|
-- A drag just ended on top of this button — eat the click so the
|
|
-- drop doesn't also re-select the character at the new slot.
|
|
if ( CharacterSelect.suppressNextClick ) then
|
|
CharacterSelect.suppressNextClick = nil;
|
|
return;
|
|
end
|
|
local id = this:GetID();
|
|
if ( id ~= CharacterSelect.selectedIndex ) then
|
|
CharacterSelect_SelectCharacter(id);
|
|
end
|
|
end
|
|
|
|
function CharacterSelectButton_OnDoubleClick()
|
|
if ( CharacterSelect.suppressNextClick ) then
|
|
CharacterSelect.suppressNextClick = nil;
|
|
return;
|
|
end
|
|
local id = this:GetID();
|
|
if ( id ~= CharacterSelect.selectedIndex ) then
|
|
CharacterSelect_SelectCharacter(id);
|
|
end
|
|
CharacterSelect_EnterWorld();
|
|
end
|
|
|
|
function CharacterSelect_TabResize()
|
|
local buttonMiddle = getglobal(this:GetName().."Middle");
|
|
local buttonMiddleDisabled = getglobal(this:GetName().."MiddleDisabled");
|
|
local width = this:GetTextWidth() - 8;
|
|
local leftWidth = getglobal(this:GetName().."Left"):GetWidth();
|
|
buttonMiddle:SetWidth(width);
|
|
buttonMiddleDisabled:SetWidth(width);
|
|
this:SetWidth(width + (2 * leftWidth));
|
|
end
|
|
|
|
function CharacterSelect_SelectCharacter(id, noCreate)
|
|
if ( id == CharacterSelect.createIndex ) then
|
|
if ( not noCreate ) then
|
|
PlaySound("gsCharacterSelectionCreateNew");
|
|
--ResetRaceSelect();
|
|
--UpdateSelectedRace(nil);
|
|
SetGlueScreen("charcreate");
|
|
end
|
|
else
|
|
local charID = GetCharIDFromIndex(id);
|
|
local name, race, class, level, zone, fileString = GetCharacterInfo(charID);
|
|
|
|
if ( fileString ~= CharacterSelect.currentModel ) then
|
|
CharacterSelect.currentModel = fileString;
|
|
SetBackgroundModel(CharacterSelect, fileString);
|
|
end
|
|
SelectCharacter(charID);
|
|
end
|
|
end
|
|
|
|
function CharacterDeleteDialog_OnShow()
|
|
local name, race, class, level = GetCharacterInfo(GetCharIDFromIndex(CharacterSelect.selectedIndex));
|
|
CharacterDeleteText1:SetText(format(TEXT(CONFIRM_CHAR_DELETE), name, level, class));
|
|
CharacterDeleteBackground:SetHeight(16 + CharacterDeleteText1:GetHeight() + CharacterDeleteText2:GetHeight() + 23 + CharacterDeleteEditBox:GetHeight() + 8 + CharacterDeleteButton1:GetHeight() + 16);
|
|
CharacterDeleteButton1:Disable();
|
|
end
|
|
|
|
function CharacterSelect_EnterWorld()
|
|
PlaySound("gsCharacterSelectionEnterWorld");
|
|
Autologin_EnterWorld();
|
|
end
|
|
|
|
function CharacterSelect_Exit()
|
|
PlaySound("gsCharacterSelectionExit");
|
|
DisconnectFromServer();
|
|
SetGlueScreen("login");
|
|
end
|
|
|
|
function CharacterSelect_AccountOptions()
|
|
PlaySound("gsCharacterSelectionAcctOptions");
|
|
end
|
|
|
|
function CharacterSelect_TechSupport()
|
|
PlaySound("gsCharacterSelectionAcctOptions");
|
|
LaunchURL(TEXT(TECH_SUPPORT_URL));
|
|
end
|
|
|
|
function CharacterSelect_Delete()
|
|
PlaySound("gsCharacterSelectionDelCharacter");
|
|
if ( CharacterSelect.selectedIndex > 0 ) then
|
|
CharacterDeleteDialog:Show();
|
|
end
|
|
end
|
|
|
|
function CharacterSelect_ChangeRealm()
|
|
PlaySound("gsLoginChangeRealmSelect");
|
|
RequestRealmList(1);
|
|
end
|
|
|
|
function CharacterSelectFrame_OnMouseDown(button)
|
|
if ( button == "LeftButton" ) then
|
|
CHARACTER_SELECT_ROTATION_START_X = GetCursorPosition();
|
|
CHARACTER_SELECT_INITIAL_FACING = GetCharacterSelectFacing();
|
|
end
|
|
end
|
|
|
|
function CharacterSelectFrame_OnMouseUp(button)
|
|
if ( button == "LeftButton" ) then
|
|
CHARACTER_SELECT_ROTATION_START_X = nil
|
|
end
|
|
end
|
|
|
|
function CharacterSelectFrame_OnUpdate()
|
|
if ( CHARACTER_SELECT_ROTATION_START_X ) then
|
|
local x = GetCursorPosition();
|
|
local diff = (x - CHARACTER_SELECT_ROTATION_START_X) * CHARACTER_ROTATION_CONSTANT;
|
|
CHARACTER_SELECT_ROTATION_START_X = GetCursorPosition();
|
|
SetCharacterSelectFacing(GetCharacterSelectFacing() + diff);
|
|
end
|
|
|
|
-- Press-and-hold detector: a character button held past AUTO_DRAG_TIME
|
|
-- promotes into drag mode. Released sooner, OnMouseUp clears the
|
|
-- button and the click flows normally.
|
|
if ( CharacterSelect.pressDownButton ) then
|
|
CharacterSelect.pressDownTime = CharacterSelect.pressDownTime + arg1;
|
|
if ( CharacterSelect.pressDownTime >= AUTO_DRAG_TIME ) then
|
|
CharacterSelectButton_OnDragStart(CharacterSelect.pressDownButton);
|
|
end
|
|
end
|
|
end
|
|
|
|
function CharacterSelectRotateRight_OnUpdate()
|
|
if ( this:GetButtonState() == "PUSHED" ) then
|
|
SetCharacterSelectFacing(GetCharacterSelectFacing() + CHARACTER_FACING_INCREMENT);
|
|
end
|
|
end
|
|
|
|
function CharacterSelectRotateLeft_OnUpdate()
|
|
if ( this:GetButtonState() == "PUSHED" ) then
|
|
SetCharacterSelectFacing(GetCharacterSelectFacing() - CHARACTER_FACING_INCREMENT);
|
|
end
|
|
end
|
|
|
|
function CharacterSelect_ManageAccount()
|
|
PlaySound("gsCharacterSelectionAcctOptions");
|
|
LaunchURL(TEXT(AUTH_NO_TIME_URL));
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Character reordering
|
|
-- ---------------------------------------------------------------------------
|
|
--
|
|
-- `CharacterSelect.translationTable[displayIndex] = charID` maps the slot
|
|
-- a button occupies on screen to the server-assigned character ID expected
|
|
-- by `GetCharacterInfo` / `SelectCharacter` / `DeleteCharacter` / etc.
|
|
-- Default identity table (1->1, 2->2, ...) gets reshuffled by MoveCharacter
|
|
-- and restored on every CHARACTER_LIST_UPDATE.
|
|
|
|
function GetCharIDFromIndex(index)
|
|
return CharacterSelect.translationTable[index] or index;
|
|
end
|
|
|
|
function GetIndexFromCharID(charID)
|
|
-- Fast path while the table is still identity.
|
|
if ( not CharacterSelect.orderChanged ) then
|
|
return charID;
|
|
end
|
|
for index = 1, table.getn(CharacterSelect.translationTable) do
|
|
if ( CharacterSelect.translationTable[index] == charID ) then
|
|
return index;
|
|
end
|
|
end
|
|
return 0;
|
|
end
|
|
|
|
function CharacterSelect_RebuildTranslationTable()
|
|
CharacterSelect.translationTable = {};
|
|
CharacterSelect.orderChanged = nil;
|
|
local numChars = GetNumCharacters();
|
|
for i = 1, numChars do
|
|
table.insert(CharacterSelect.translationTable, i);
|
|
end
|
|
end
|
|
|
|
function MoveCharacter(originIndex, targetIndex, fromDrag)
|
|
CharacterSelect.orderChanged = 1;
|
|
local n = table.getn(CharacterSelect.translationTable);
|
|
if ( n < 2 ) then return; end
|
|
if ( targetIndex < 1 ) then
|
|
targetIndex = n;
|
|
elseif ( targetIndex > n ) then
|
|
targetIndex = 1;
|
|
end
|
|
if ( originIndex == CharacterSelect.selectedIndex ) then
|
|
CharacterSelect.selectedIndex = targetIndex;
|
|
elseif ( targetIndex == CharacterSelect.selectedIndex ) then
|
|
CharacterSelect.selectedIndex = originIndex;
|
|
end
|
|
local t = CharacterSelect.translationTable;
|
|
t[originIndex], t[targetIndex] = t[targetIndex], t[originIndex];
|
|
if ( fromDrag ) then
|
|
CharacterSelect.draggedIndex = targetIndex;
|
|
end
|
|
UpdateCharacterSelection();
|
|
UpdateCharacterList();
|
|
CharacterOrder_Save();
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Drag handlers
|
|
-- ---------------------------------------------------------------------------
|
|
--
|
|
-- 1.12 glue widgets don't expose `RegisterForDrag`, so we synthesize drag
|
|
-- mode by watching how long a character button is held: any press past
|
|
-- AUTO_DRAG_TIME enters drag mode. The dragged button's OnUpdate then
|
|
-- watches the cursor's Y position and calls MoveCharacter when it crosses
|
|
-- into another slot. Mouse-up tears everything down.
|
|
|
|
function CharacterSelectButton_OnMouseDown()
|
|
CharacterSelect.pressDownButton = this;
|
|
CharacterSelect.pressDownTime = 0;
|
|
end
|
|
|
|
function CharacterSelectButton_OnMouseUp()
|
|
if ( CharacterSelect.draggedIndex ) then
|
|
CharacterSelectButton_OnDragStop(this);
|
|
-- The drag concluded on a button; the engine still dispatches
|
|
-- OnClick (and OnDoubleClick) afterwards. Suppress one.
|
|
CharacterSelect.suppressNextClick = 1;
|
|
end
|
|
CharacterSelect.pressDownButton = nil;
|
|
end
|
|
|
|
function CharacterSelectButton_OnDragStart(button)
|
|
if ( GetNumCharacters() < 2 ) then return; end
|
|
CharacterSelect.pressDownButton = nil;
|
|
CharacterSelect.draggedIndex = button:GetID();
|
|
-- Cache slot geometry once per drag from the live buttons. Stride
|
|
-- accounts for the 13px visual overlap defined in CharacterSelect.xml.
|
|
CharacterSelect.dragListTop = CharSelectCharacterButton1:GetTop();
|
|
CharacterSelect.dragRowStride = CharSelectCharacterButton1:GetTop() - CharSelectCharacterButton2:GetTop();
|
|
if ( not CharacterSelect.dragRowStride or CharacterSelect.dragRowStride <= 0 ) then
|
|
-- Fallback if button2 wasn't ready (single character, etc.) —
|
|
-- shouldn't reach here because of the >=2 guard above, but be safe.
|
|
CharacterSelect.dragRowStride = 57;
|
|
end
|
|
button:SetScript("OnUpdate", CharacterSelectButton_OnDragUpdate);
|
|
UpdateCharacterSelection();
|
|
end
|
|
|
|
function CharacterSelectButton_OnDragUpdate()
|
|
if ( not CharacterSelect.draggedIndex ) then
|
|
CharacterSelectButton_OnDragStop(this);
|
|
return;
|
|
end
|
|
local _, cursorY = GetCursorPosition();
|
|
local top = CharacterSelect.dragListTop;
|
|
local stride = CharacterSelect.dragRowStride;
|
|
if ( cursorY <= top ) then
|
|
local hoverIndex = math.floor((top - cursorY) / stride) + 1;
|
|
local hover = getglobal("CharSelectCharacterButton"..hoverIndex);
|
|
if ( hover and hover:IsShown() and hoverIndex ~= CharacterSelect.draggedIndex ) then
|
|
if ( hoverIndex > CharacterSelect.draggedIndex ) then
|
|
MoveCharacter(CharacterSelect.draggedIndex, CharacterSelect.draggedIndex + 1, 1);
|
|
else
|
|
MoveCharacter(CharacterSelect.draggedIndex, CharacterSelect.draggedIndex - 1, 1);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function CharacterSelectButton_OnDragStop(button)
|
|
CharacterSelect.pressDownButton = nil;
|
|
CharacterSelect.draggedIndex = nil;
|
|
if ( button ) then
|
|
button:SetScript("OnUpdate", nil);
|
|
end
|
|
-- draggedIndex is now nil, so UpdateCharacterSelection restores
|
|
-- full alpha on every slot and highlights only the selected one.
|
|
UpdateCharacterSelection();
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Delete / Rename wrappers (selectedIndex is now a display index, so
|
|
-- engine calls expecting a charID need translation)
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
function CharacterSelect_DeleteCharacter()
|
|
DeleteCharacter(GetCharIDFromIndex(CharacterSelect.selectedIndex));
|
|
CharacterDeleteDialog:Hide();
|
|
end
|
|
|
|
function CharacterSelect_RenameCharacter()
|
|
if ( RenameCharacter(GetCharIDFromIndex(CharacterSelect.selectedIndex), CharacterRenameEditBox:GetText()) ) then
|
|
CharacterRenameDialog:Hide();
|
|
end
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Persistence — relies on ClassicAPI's glue bindings
|
|
-- (GetSavedCharacterOrder / SetSavedCharacterOrder). Both calls are
|
|
-- guarded so the drag feature is functional in-session even before the
|
|
-- DLL binding ships; persistence kicks in automatically once it does.
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
function CharacterOrder_Load()
|
|
if ( type(GetSavedCharacterOrder) ~= "function" ) then return ""; end
|
|
local realm = GetServerName() or "";
|
|
if ( realm == "" ) then return ""; end
|
|
return GetSavedCharacterOrder(realm) or "";
|
|
end
|
|
|
|
function CharacterOrder_Apply()
|
|
local saved = CharacterOrder_Load();
|
|
if ( not saved or saved == "" ) then return; end
|
|
local numChars = GetNumCharacters();
|
|
if ( numChars < 2 ) then return; end
|
|
|
|
-- Build name -> charID lookup against the server's natural order.
|
|
-- The translation table is identity at this point (set by
|
|
-- RebuildTranslationTable), so charID == display index here.
|
|
local nameToID = {};
|
|
for charID = 1, numChars do
|
|
local name = GetCharacterInfo(charID);
|
|
if ( name ) then nameToID[name] = charID; end
|
|
end
|
|
|
|
local newOrder = {};
|
|
local claimed = {};
|
|
for name in string.gfind(saved, "([^|]+)") do
|
|
local charID = nameToID[name];
|
|
if ( charID and not claimed[charID] ) then
|
|
table.insert(newOrder, charID);
|
|
claimed[charID] = 1;
|
|
end
|
|
end
|
|
-- Append any characters that exist on the server but weren't in the
|
|
-- saved order (newly created since last save).
|
|
for charID = 1, numChars do
|
|
if ( not claimed[charID] ) then
|
|
table.insert(newOrder, charID);
|
|
end
|
|
end
|
|
|
|
-- Only adopt the rebuilt order if it actually differs from identity.
|
|
local differs = false;
|
|
for i = 1, table.getn(newOrder) do
|
|
if ( newOrder[i] ~= i ) then differs = true; break; end
|
|
end
|
|
if ( differs ) then
|
|
CharacterSelect.translationTable = newOrder;
|
|
CharacterSelect.orderChanged = 1;
|
|
end
|
|
end
|
|
|
|
function CharacterOrder_Save()
|
|
if ( type(SetSavedCharacterOrder) ~= "function" ) then return; end
|
|
local realm = GetServerName() or "";
|
|
if ( realm == "" ) then return; end
|
|
local numChars = GetNumCharacters();
|
|
local names = {};
|
|
for i = 1, numChars do
|
|
local name = GetCharacterInfo(GetCharIDFromIndex(i));
|
|
if ( name ) then table.insert(names, name); end
|
|
end
|
|
SetSavedCharacterOrder(realm, table.concat(names, "|"));
|
|
end
|