режим предатель, преезд всего GlueXML в patch-ruRU-4
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
"""
|
||||
Wow.exe binary patcher for MoonWell custom features.
|
||||
|
||||
Patches:
|
||||
1. Script_CreateCharacter: reads lua_tonumber(L, 2) as outfitId,
|
||||
stores to global, passes to CMSG_CHAR_CREATE packet instead of 0.
|
||||
2. GetCharacterInfo: adds 11th return value — charFlags as a Lua number,
|
||||
read from struct_offset +0x170 (same field as ghost flag 0x2000).
|
||||
Lua checks bit 0x40000000 (CHARACTER_FLAG_UNK31) for traitor status.
|
||||
|
||||
Usage:
|
||||
python make_modes_in_exe.py <path_to_Wow.exe>
|
||||
|
||||
A backup (Wow.exe.bak) is created before patching.
|
||||
"""
|
||||
|
||||
import struct
|
||||
import sys
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PE helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _u16(data, off):
|
||||
return struct.unpack_from("<H", data, off)[0]
|
||||
|
||||
def _u32(data, off):
|
||||
return struct.unpack_from("<I", data, off)[0]
|
||||
|
||||
|
||||
def parse_pe_sections(data):
|
||||
if data[0:2] != b"MZ":
|
||||
raise ValueError("Not a valid PE file (missing MZ)")
|
||||
pe_off = _u32(data, 0x3C)
|
||||
if data[pe_off:pe_off + 4] != b"PE\x00\x00":
|
||||
raise ValueError("Not a valid PE file (missing PE signature)")
|
||||
coff = pe_off + 4
|
||||
num_secs = _u16(data, coff + 2)
|
||||
opt_size = _u16(data, coff + 16)
|
||||
opt = coff + 20
|
||||
magic = _u16(data, opt)
|
||||
if magic == 0x10b:
|
||||
image_base = _u32(data, opt + 28)
|
||||
elif magic == 0x20b:
|
||||
image_base = struct.unpack_from("<Q", data, opt + 24)[0]
|
||||
else:
|
||||
raise ValueError(f"Unknown PE magic: 0x{magic:04x}")
|
||||
sec_table = opt + opt_size
|
||||
sections = []
|
||||
for i in range(num_secs):
|
||||
o = sec_table + i * 40
|
||||
name = data[o:o + 8].rstrip(b"\x00").decode("ascii", errors="replace")
|
||||
virt_size = _u32(data, o + 8)
|
||||
virt_addr = _u32(data, o + 12)
|
||||
raw_size = _u32(data, o + 16)
|
||||
raw_off = _u32(data, o + 20)
|
||||
sections.append((name, virt_addr, virt_size, raw_off, raw_size))
|
||||
return image_base, sections
|
||||
|
||||
|
||||
def va_to_offset(va, sections, image_base):
|
||||
rva = va - image_base
|
||||
for name, va0, vs, ro, _ in sections:
|
||||
if va0 <= rva < va0 + vs:
|
||||
return ro + (rva - va0)
|
||||
raise ValueError(f"VA 0x{va:08x} not in any section")
|
||||
|
||||
|
||||
def offset_to_va(off, sections, image_base):
|
||||
for name, va0, _, ro, rs in sections:
|
||||
if ro <= off < ro + rs:
|
||||
return image_base + va0 + (off - ro)
|
||||
raise ValueError(f"File offset 0x{off:08x} not in any section")
|
||||
|
||||
|
||||
def find_cave(data, sections, image_base, size, search_from_va):
|
||||
"""Return VA of the first run of >= size consecutive CC bytes after search_from_va."""
|
||||
text = next((s for s in sections if s[0] == ".text"), None)
|
||||
if not text is None:
|
||||
_, va0, vs, ro, rs = text
|
||||
else:
|
||||
raise ValueError("No .text section")
|
||||
|
||||
rva_start = max(search_from_va - image_base, va0)
|
||||
off_start = ro + (rva_start - va0)
|
||||
off_end = ro + min(vs, rs)
|
||||
|
||||
i = off_start
|
||||
while i <= off_end - size:
|
||||
if data[i] != 0xcc:
|
||||
i += 1
|
||||
continue
|
||||
j = i
|
||||
while j < off_end and data[j] == 0xcc:
|
||||
j += 1
|
||||
if j - i >= size:
|
||||
return offset_to_va(i, sections, image_base)
|
||||
i = j
|
||||
raise ValueError(f"No {size}-byte code cave found from VA 0x{search_from_va:08x}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Patch 1 & 2: outfitId in CMSG_CHAR_CREATE (fixed addresses)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_FIXED_PATCHES = [
|
||||
(
|
||||
0x004e0c60,
|
||||
bytes.fromhex(
|
||||
"558bec568b75086a0156e8f1d23600"
|
||||
"83c40885c0740d6a006a0156e860d4"
|
||||
"360083c40c6a006a0156e853d43600"
|
||||
"50e8edf6ffff83c41033c05e5dc3cc"
|
||||
"cccccccc"
|
||||
),
|
||||
bytes.fromhex(
|
||||
"558bec568b75086a0256e8c1d33600"
|
||||
"db1c245883c404a221 42ac006a006a"
|
||||
"0156e85bd4360050e8f5f6ffff83c4"
|
||||
"1033c05e5dc38855f08a1521 42ac00"
|
||||
"8855f4c3"
|
||||
).replace(b" ", b""), # spaces stripped for readability
|
||||
"Script_CreateCharacter: read outfitId from Lua arg 2 -> global",
|
||||
),
|
||||
(
|
||||
0x004e042f,
|
||||
bytes.fromhex("8855f0c645f400"),
|
||||
bytes.fromhex("e85f08000090 90").replace(b" ", b""),
|
||||
"Packet builder: CALL helper -> load face + outfitId from global",
|
||||
),
|
||||
]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Patch 3: GetCharacterInfo — add 11th return value (charFlags as number)
|
||||
# ---------------------------------------------------------------------------
|
||||
#
|
||||
# Assembly context (FUN_004e3170 epilogue, valid branch):
|
||||
#
|
||||
# 004e31cd 56 PUSH ESI ← saves caller's ESI
|
||||
# 004e31ce 8b f0 MOV ESI, EAX ← ESI = char struct ptr
|
||||
# ... [function body — ESI used throughout for char struct] ...
|
||||
# 004e32e5 8b 8e 70010000 MOV ECX, [ESI+0x170] ← charFlags (ghost flag check)
|
||||
# ...
|
||||
# 004e332d 83 c4 2c ADD ESP, 0x2c ← clean accumulated args ← PATCH HERE
|
||||
# 004e3330 5e POP ESI ← restores caller's ESI (ESI valid until here)
|
||||
# 004e3331 b8 0a 00 00 00 MOV EAX, 0xa (dead after patch)
|
||||
# 004e3336 5f POP EDI ← JMP back target
|
||||
# 004e3337 8b e5 MOV ESP, EBP
|
||||
# 004e3339 5d POP EBP
|
||||
# 004e333a c3 RET
|
||||
#
|
||||
# KEY: ESI = char struct ptr up to and including ADD ESP,0x2c (004e332d).
|
||||
# After POP ESI (004e3330) it is caller's ESI — NOT char struct.
|
||||
# Previous approach: CALL cave at 004e3331 (after POP ESI) -> ESI already wrong.
|
||||
# Fix: JMP cave at 004e332d (before POP ESI) -> ESI still valid in cave.
|
||||
#
|
||||
# New approach: patch 5 bytes at 004e332d with JMP cave.
|
||||
# Old bytes: 83 c4 2c (ADD ESP,0x2c) + 5e (POP ESI) + b8 (MOV EAX first byte)
|
||||
# New bytes: e9 XX XX XX XX JMP cave
|
||||
# The 4 dead bytes at 004e3332-004e3335 are unreachable.
|
||||
#
|
||||
# Cave (30 bytes, found automatically via CC scan over entire .text):
|
||||
# 83 ec 08 SUB ESP, 8 ; make room for double
|
||||
# db 86 70 01 00 00 FILD [ESI+0x170] ; load charFlags into FPU (ESI valid here!)
|
||||
# dd 1c 24 FSTP [ESP] ; store as double
|
||||
# 57 PUSH EDI ; lua_State* L
|
||||
# e8 XX XX XX XX CALL FUN_0084e2a0 ; lua_pushnumber(L, charFlags)
|
||||
# 83 c4 38 ADD ESP, 0x38 ; merged: own 0xc + stolen 0x2c
|
||||
# 5e POP ESI ; stolen: restore caller's ESI
|
||||
# 6a 0b PUSH 0xb ; \ EAX = 11 in 3 bytes
|
||||
# 58 POP EAX ; / (vs MOV EAX,0xb = 5 bytes)
|
||||
# e9 XX XX XX XX JMP 004e3336 ; continue epilogue (POP EDI ...)
|
||||
#
|
||||
# Lua code reads info[11] and checks bit 0x40000000 (CHARACTER_FLAG_UNK31 = traitor).
|
||||
# charFlags confirmed at [ESI+0x170]: same field used for ghost flag (AND ECX,0x2000).
|
||||
|
||||
_CHARINFO_PATCH_VA = 0x004e332d # ADD ESP,0x2c (5 bytes including POP ESI + MOV EAX[0])
|
||||
_CHARINFO_JMPBACK_VA = 0x004e3336 # POP EDI — resume epilogue after cave
|
||||
_CHARINFO_CAVE_SIZE = 30
|
||||
_CHARINFO_CAVE_SEARCH_FROM = 0x00401000 # search entire .text for first 35-byte CC block
|
||||
_LUA_PUSHNUMBER_VA = 0x0084e2a0 # lua_pushnumber
|
||||
|
||||
|
||||
def _build_charinfo_patches(data, sections, image_base):
|
||||
"""
|
||||
Build the dynamic GetCharacterInfo patches.
|
||||
Returns list of (va, old_bytes, new_bytes, description), [] if already applied,
|
||||
or None on unrecoverable error.
|
||||
"""
|
||||
patch_off = va_to_offset(_CHARINFO_PATCH_VA, sections, image_base)
|
||||
current = bytes(data[patch_off:patch_off + 5])
|
||||
|
||||
# Already patched with new JMP approach?
|
||||
if current[0:1] == b"\xe9":
|
||||
return []
|
||||
|
||||
# Verify first 4 bytes look like the epilogue (ADD ESP,0x2c + POP ESI).
|
||||
# Accept both unpatched (b8 at [4]) and old-CALL-patched (e8 at [4]) states.
|
||||
if current[0:4] != bytes.fromhex("83c42c5e"):
|
||||
print(f"[WARN] 0x{_CHARINFO_PATCH_VA:08x}: unexpected bytes {current.hex(' ')}")
|
||||
print(" Expected ADD ESP,0x2c + POP ESI at this address.")
|
||||
print(" Restore Wow.exe.bak and re-run.")
|
||||
return None
|
||||
|
||||
old_b = current # exact 5 bytes currently in file (b8 or e8 at index 4)
|
||||
|
||||
# Find a 35-byte code cave in .text
|
||||
cave_va = find_cave(data, sections, image_base,
|
||||
_CHARINFO_CAVE_SIZE, _CHARINFO_CAVE_SEARCH_FROM)
|
||||
|
||||
# Cave layout offsets (30 bytes total):
|
||||
# 0 SUB ESP,8 (3)
|
||||
# 3 FILD [ESI+0x170] (6)
|
||||
# 9 FSTP [ESP] (3)
|
||||
# 12 PUSH EDI (1)
|
||||
# 13 CALL lua_pushnumber (5) — next instr at 18
|
||||
# 18 ADD ESP, 0x38 (3) merged: own 0xc + stolen 0x2c
|
||||
# 21 POP ESI (1) stolen
|
||||
# 22 PUSH 0xb (2) \ shorter than MOV EAX,0xb (5 bytes)
|
||||
# 24 POP EAX (1) /
|
||||
# 25 JMP 004e3336 (5) — next instr at cave_va+30
|
||||
call_rel = _LUA_PUSHNUMBER_VA - (cave_va + 18)
|
||||
jmp_back_rel = _CHARINFO_JMPBACK_VA - (cave_va + 30)
|
||||
|
||||
cave_bytes = (
|
||||
bytes.fromhex("83ec08") # SUB ESP, 8 (3)
|
||||
+ bytes.fromhex("db8670010000") # FILD [ESI+0x170] (6)
|
||||
+ bytes.fromhex("dd1c24") # FSTP [ESP] (3)
|
||||
+ bytes.fromhex("57") # PUSH EDI (1)
|
||||
+ b"\xe8" + struct.pack("<i", call_rel) # CALL lua_pushnumber (5)
|
||||
+ bytes.fromhex("83c438") # ADD ESP, 0x38 (0xc+0x2c) (3)
|
||||
+ bytes.fromhex("5e") # POP ESI [stolen] (1)
|
||||
+ bytes.fromhex("6a0b") # PUSH 0xb (2)
|
||||
+ bytes.fromhex("58") # POP EAX -> EAX=11 (1)
|
||||
+ b"\xe9" + struct.pack("<i", jmp_back_rel) # JMP 004e3336 (5)
|
||||
)
|
||||
assert len(cave_bytes) == _CHARINFO_CAVE_SIZE, \
|
||||
f"Cave size {len(cave_bytes)} != {_CHARINFO_CAVE_SIZE}"
|
||||
|
||||
# Patch: JMP cave (5 bytes at 004e332d)
|
||||
jmp_rel = cave_va - (_CHARINFO_PATCH_VA + 5)
|
||||
patch_new = b"\xe9" + struct.pack("<i", jmp_rel)
|
||||
|
||||
return [
|
||||
(
|
||||
cave_va,
|
||||
bytes([0xcc] * _CHARINFO_CAVE_SIZE),
|
||||
cave_bytes,
|
||||
f"GetCharacterInfo cave @ 0x{cave_va:08x}: push charFlags, stolen epilogue",
|
||||
),
|
||||
(
|
||||
_CHARINFO_PATCH_VA,
|
||||
old_b,
|
||||
patch_new,
|
||||
"GetCharacterInfo: JMP to cave before POP ESI (ESI=char struct), return 11",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print(f"Usage: python {Path(sys.argv[0]).name} <path_to_Wow.exe>")
|
||||
sys.exit(1)
|
||||
|
||||
exe_path = Path(sys.argv[1])
|
||||
if not exe_path.exists():
|
||||
print(f"Error: file not found: {exe_path}")
|
||||
sys.exit(1)
|
||||
|
||||
data = bytearray(exe_path.read_bytes())
|
||||
image_base, sections = parse_pe_sections(data)
|
||||
|
||||
print(f"Image base: 0x{image_base:08x}")
|
||||
print(f"Sections: {len(sections)}")
|
||||
for n, va, vs, ro, rs in sections:
|
||||
print(f" {n:8s} VA=0x{va:08x} VSize=0x{vs:08x} Raw=0x{ro:08x} RSize=0x{rs:08x}")
|
||||
print()
|
||||
|
||||
charinfo_patches = _build_charinfo_patches(data, sections, image_base)
|
||||
if charinfo_patches is None:
|
||||
sys.exit(1)
|
||||
all_patches = list(_FIXED_PATCHES) + charinfo_patches
|
||||
|
||||
# Verify all first
|
||||
ok = True
|
||||
for va, old_b, new_b, desc in all_patches:
|
||||
off = va_to_offset(va, sections, image_base)
|
||||
cur = bytes(data[off:off + len(old_b)])
|
||||
if cur == new_b:
|
||||
print(f"[SKIP] 0x{va:08x}: already patched — {desc}")
|
||||
elif cur == old_b:
|
||||
print(f"[ OK ] 0x{va:08x}: verified — {desc}")
|
||||
else:
|
||||
print(f"[FAIL] 0x{va:08x}: {desc}")
|
||||
print(f" Expected: {old_b.hex(' ')}")
|
||||
print(f" Found: {cur.hex(' ')}")
|
||||
ok = False
|
||||
|
||||
if not ok:
|
||||
print("\nByte mismatch — aborting. Is this the correct Wow.exe (3.3.5a build 12340)?")
|
||||
sys.exit(1)
|
||||
|
||||
# Backup
|
||||
bak = exe_path.with_suffix(".exe.bak")
|
||||
if not bak.exists():
|
||||
shutil.copy2(exe_path, bak)
|
||||
print(f"\nBackup: {bak}")
|
||||
else:
|
||||
print(f"\nBackup already exists: {bak}")
|
||||
|
||||
# Apply
|
||||
applied = 0
|
||||
for va, old_b, new_b, desc in all_patches:
|
||||
off = va_to_offset(va, sections, image_base)
|
||||
cur = bytes(data[off:off + len(old_b)])
|
||||
if cur == new_b:
|
||||
continue
|
||||
data[off:off + len(old_b)] = new_b
|
||||
applied += 1
|
||||
print(f"[PATCH] 0x{va:08x}: {desc}")
|
||||
|
||||
if applied == 0:
|
||||
print("\nNothing to patch.")
|
||||
else:
|
||||
exe_path.write_bytes(data)
|
||||
print(f"\n{applied} patch(es) written to {exe_path}")
|
||||
|
||||
print("Done!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+169
-11
@@ -16,6 +16,9 @@ PAID_FACTION_CHANGE = 3;
|
||||
PAID_SERVICE_CHARACTER_ID = nil;
|
||||
PAID_SERVICE_TYPE = nil;
|
||||
|
||||
GAME_MODE_NORMAL = 0;
|
||||
GAME_MODE_TRAITOR = 1;
|
||||
|
||||
FACTION_BACKDROP_COLOR_TABLE = {
|
||||
["Alliance"] = {0.5, 0.5, 0.5, 0.09, 0.09, 0.19},
|
||||
["Horde"] = {0.5, 0.2, 0.2, 0.19, 0.05, 0.05},
|
||||
@@ -81,6 +84,10 @@ local raceTooltips = {};
|
||||
local classTooltips = {};
|
||||
local detailedRaceTooltips = {};
|
||||
local detailedClassTooltips = {};
|
||||
local GAME_MODE_TILE_BACKGROUNDS = {
|
||||
[GAME_MODE_NORMAL] = "Interface\\GluesVideo\\Assets\\RealmSelection\\Realm1.blp",
|
||||
[GAME_MODE_TRAITOR] = "Interface\\GluesVideo\\Assets\\RealmSelection\\Realm2.blp",
|
||||
};
|
||||
|
||||
local function UTF8Chars(text)
|
||||
return string.gmatch(text or "", "[%z\1-\127\194-\244][\128-\191]*")
|
||||
@@ -636,6 +643,129 @@ function CharacterCreate_PositionAllButtons()
|
||||
CharacterCreate_PositionGenderButtons()
|
||||
end
|
||||
|
||||
function CharacterCreate_CreateWithGameMode(modeId)
|
||||
if PAID_SERVICE_TYPE then
|
||||
return;
|
||||
end
|
||||
|
||||
if modeId ~= GAME_MODE_NORMAL and modeId ~= GAME_MODE_TRAITOR then
|
||||
modeId = GAME_MODE_NORMAL;
|
||||
end
|
||||
|
||||
-- The patched Wow.exe reads the second argument as outfitId
|
||||
-- and writes it into the CMSG_CHAR_CREATE packet.
|
||||
local name = CharacterCreateNameEdit:GetText();
|
||||
message("DEBUG: CreateCharacter('" .. tostring(name) .. "', " .. tostring(modeId) .. ")");
|
||||
CreateCharacter(name, modeId);
|
||||
CharacterCreate.personalizationMode = false;
|
||||
CharacterCreate.gameModeSelectionMode = false;
|
||||
PlaySound("gsCharacterCreationCreateChar");
|
||||
end
|
||||
|
||||
function CharacterCreate_UpdateGameModeVisibility()
|
||||
if not CharacterCreateGameModeFrame then
|
||||
return;
|
||||
end
|
||||
|
||||
if CharacterCreate.personalizationMode and CharacterCreate.gameModeSelectionMode and not PAID_SERVICE_TYPE then
|
||||
CharacterCreateGameModeFrame:Show();
|
||||
else
|
||||
CharacterCreateGameModeFrame:Hide();
|
||||
end
|
||||
end
|
||||
|
||||
local function CharacterCreate_GetEnteredName()
|
||||
local text = "";
|
||||
|
||||
if CharacterCreateNameEdit and CharacterCreateNameEdit.GetText then
|
||||
text = CharacterCreateNameEdit:GetText() or "";
|
||||
end
|
||||
|
||||
text = string.gsub(text, "^%s+", "");
|
||||
text = string.gsub(text, "%s+$", "");
|
||||
return text;
|
||||
end
|
||||
|
||||
local function CharacterCreate_HasEnteredName()
|
||||
return UTF8Length(CharacterCreate_GetEnteredName()) >= MIN_CHAR_NAME_LENGTH;
|
||||
end
|
||||
|
||||
local function CharacterCreate_UpdatePersonalizationStep()
|
||||
if not CharacterCreate.personalizationMode then
|
||||
return;
|
||||
end
|
||||
|
||||
if CharacterCreate.gameModeSelectionMode and not CharacterCreate_HasEnteredName() then
|
||||
CharacterCreate.gameModeSelectionMode = false;
|
||||
end
|
||||
|
||||
local showGameModes = CharacterCreate.gameModeSelectionMode and not PAID_SERVICE_TYPE;
|
||||
|
||||
if showGameModes then
|
||||
CharacterCreateRotateLeft:Hide();
|
||||
CharacterCreateRotateRight:Hide();
|
||||
CharacterCreateRotateLeft30:Hide();
|
||||
CharacterCreateRotateRight30:Hide();
|
||||
CharCreateRandomizeButton:Hide();
|
||||
CharacterCreateNameEdit:Hide();
|
||||
CharacterCreateRandomName:Hide();
|
||||
CharCreateOkayButton:Hide();
|
||||
|
||||
for i=1, NUM_CHAR_CUSTOMIZATIONS do
|
||||
_G["CharacterCustomizationButtonFrame"..i]:Hide();
|
||||
end
|
||||
else
|
||||
CharacterCreateRotateLeft:Show();
|
||||
CharacterCreateRotateRight:Show();
|
||||
CharacterCreateRotateLeft30:Show();
|
||||
CharacterCreateRotateRight30:Show();
|
||||
CharCreateRandomizeButton:Show();
|
||||
CharacterCreateNameEdit:Show();
|
||||
CharacterCreateRandomName:Show();
|
||||
CharCreateOkayButton:Show();
|
||||
|
||||
for i=1, NUM_CHAR_CUSTOMIZATIONS do
|
||||
_G["CharacterCustomizationButtonFrame"..i]:Show();
|
||||
end
|
||||
end
|
||||
|
||||
CharacterCreate_UpdateGameModeVisibility();
|
||||
end
|
||||
|
||||
local function CharacterCreate_InitGameModeTiles()
|
||||
-- Background textures
|
||||
CharacterCreateGameModeNormalBg:SetTexture(GAME_MODE_TILE_BACKGROUNDS[GAME_MODE_NORMAL]);
|
||||
CharacterCreateGameModeTraitorBg:SetTexture(GAME_MODE_TILE_BACKGROUNDS[GAME_MODE_TRAITOR]);
|
||||
|
||||
-- Tile titles (MORPHEUS font)
|
||||
CharacterCreateGameModeNormalTitle:SetFont("Fonts\\MORPHEUS.TTF", 24, "");
|
||||
CharacterCreateGameModeNormalTitle:SetText("Обычный");
|
||||
CharacterCreateGameModeNormalTitle:SetTextColor(0.96, 0.94, 0.88);
|
||||
|
||||
CharacterCreateGameModeTraitorTitle:SetFont("Fonts\\MORPHEUS.TTF", 24, "");
|
||||
CharacterCreateGameModeTraitorTitle:SetText("Предатель");
|
||||
CharacterCreateGameModeTraitorTitle:SetTextColor(0.96, 0.94, 0.88);
|
||||
|
||||
-- Frame header
|
||||
CharacterCreateGameModeFrameTitle:SetFont("Fonts\\MORPHEUS.TTF", 28, "");
|
||||
CharacterCreateGameModeFrameTitle:SetText("ВЫБОР РЕЖИМА");
|
||||
CharacterCreateGameModeFrameTitle:SetTextColor(1.0, 0.82, 0.0);
|
||||
|
||||
CharacterCreateGameModeFrameSubtitle:SetText("Выберите режим для нового персонажа");
|
||||
CharacterCreateGameModeFrameSubtitle:SetTextColor(0.78, 0.72, 0.55);
|
||||
|
||||
-- Play buttons
|
||||
CharacterCreateGameModeNormalPlayButton:SetText("Играть");
|
||||
CharacterCreateGameModeNormalPlayButton:SetScript("OnClick", function()
|
||||
CharacterCreate_CreateWithGameMode(GAME_MODE_NORMAL);
|
||||
end);
|
||||
|
||||
CharacterCreateGameModeTraitorPlayButton:SetText("Играть");
|
||||
CharacterCreateGameModeTraitorPlayButton:SetScript("OnClick", function()
|
||||
CharacterCreate_CreateWithGameMode(GAME_MODE_TRAITOR);
|
||||
end);
|
||||
end
|
||||
|
||||
function CharacterCreate_OnLoad(self)
|
||||
self:SetSequence(0)
|
||||
self:SetCamera(0)
|
||||
@@ -646,9 +776,11 @@ function CharacterCreate_OnLoad(self)
|
||||
CharacterCreate.numClasses = 0
|
||||
CharacterCreate.selectedClass = 0
|
||||
CharacterCreate.selectedGender = 0
|
||||
CharacterCreate.gameModeSelectionMode = false
|
||||
CharacterCreate.personalizationMode = false
|
||||
CharacterCreate_MoveTexturesToBackground()
|
||||
CharacterCreate_SetupCustomButtons()
|
||||
CharacterCreate_InitGameModeTiles()
|
||||
|
||||
for i=1, NUM_CHAR_CUSTOMIZATIONS, 1 do
|
||||
_G["CharacterCustomizationButtonFrame"..i.."Text"]:SetText(_G["CHAR_CUSTOMIZATION"..i.."_DESC"])
|
||||
@@ -778,15 +910,12 @@ function CharacterCreate_TogglePersonalization()
|
||||
CharCreatePersonalizeButton:Hide();
|
||||
CharCreateOkayButton:Show();
|
||||
CharacterCreate_UpdateHairCustomization();
|
||||
CharCreateRandomizeButton:Show();
|
||||
CharacterCreateNameEdit:Show();
|
||||
CharacterCreateRandomName:Show();
|
||||
CharacterCreate.gameModeSelectionMode = false;
|
||||
CharacterCreate_UpdatePersonalizationStep();
|
||||
|
||||
for i=1, NUM_CHAR_CUSTOMIZATIONS do
|
||||
_G["CharacterCustomizationButtonFrame"..i]:Show();
|
||||
end
|
||||
else
|
||||
CharacterCreate.personalizationMode = false;
|
||||
CharacterCreate.gameModeSelectionMode = false;
|
||||
PlaySound("gsCharacterCreationCancel");
|
||||
CharacterCreateRaceButtonsContainer:Show();
|
||||
CharacterCreateClassButtonsContainer:Show();
|
||||
@@ -804,6 +933,7 @@ function CharacterCreate_TogglePersonalization()
|
||||
CharacterCreateRandomName:Hide();
|
||||
CharCreatePersonalizeButton:Show();
|
||||
CharCreateOkayButton:Hide();
|
||||
CharacterCreate_UpdateGameModeVisibility();
|
||||
|
||||
for i=1, NUM_CHAR_CUSTOMIZATIONS do
|
||||
_G["CharacterCustomizationButtonFrame"..i]:Hide();
|
||||
@@ -844,6 +974,7 @@ function CharacterCreate_OnShow()
|
||||
end
|
||||
|
||||
CharacterCreate.personalizationMode = false;
|
||||
CharacterCreate.gameModeSelectionMode = false;
|
||||
CharCreateOkayButton:Hide();
|
||||
|
||||
for i=1, NUM_CHAR_CUSTOMIZATIONS do
|
||||
@@ -865,6 +996,8 @@ function CharacterCreate_OnShow()
|
||||
|
||||
hideScheduled = true
|
||||
HideNameEditFrame:Show()
|
||||
CharacterCreate.gameModeSelectionMode = false;
|
||||
CharacterCreate_UpdateGameModeVisibility();
|
||||
CharacterCreate_PositionAllButtons()
|
||||
end
|
||||
|
||||
@@ -1316,6 +1449,9 @@ function SetCharacterClass(id)
|
||||
end
|
||||
|
||||
function CharacterCreate_OnChar()
|
||||
if CharacterCreate.personalizationMode then
|
||||
CharacterCreate_UpdatePersonalizationStep();
|
||||
end
|
||||
end
|
||||
|
||||
function CharacterCreate_UpdateButtonCheckedStates()
|
||||
@@ -1363,11 +1499,7 @@ end
|
||||
|
||||
function CharacterCreate_OnKeyDown(key)
|
||||
if ( key == "ESCAPE" ) then
|
||||
if (CharacterCreate.personalizationMode == true) then
|
||||
CharacterCreate_TogglePersonalization();
|
||||
else
|
||||
CharacterCreate_Back();
|
||||
end
|
||||
CharacterCreate_Back();
|
||||
elseif ( key == "ENTER" ) then
|
||||
CharacterCreate_Okay();
|
||||
elseif ( key == "PRINTSCREEN" ) then
|
||||
@@ -1381,18 +1513,42 @@ function CharacterCreate_UpdateModel(self)
|
||||
end
|
||||
|
||||
function CharacterCreate_Okay()
|
||||
if ( not PAID_SERVICE_TYPE and CharacterCreate.personalizationMode and not CharacterCreate.gameModeSelectionMode ) then
|
||||
if ( not CharacterCreate_HasEnteredName() ) then
|
||||
CharacterCreateNameEdit:SetFocus();
|
||||
return;
|
||||
end
|
||||
|
||||
CharacterCreate.gameModeSelectionMode = true;
|
||||
CharacterCreate_UpdatePersonalizationStep();
|
||||
return;
|
||||
end
|
||||
|
||||
-- In game mode selection — tile buttons handle character creation
|
||||
if ( not PAID_SERVICE_TYPE and CharacterCreate.gameModeSelectionMode ) then
|
||||
return;
|
||||
end
|
||||
|
||||
if ( PAID_SERVICE_TYPE ) then
|
||||
GlueDialog_Show("CONFIRM_PAID_SERVICE");
|
||||
else
|
||||
message("DEBUG: old path CreateCharacter (no outfitId!)");
|
||||
CreateCharacter(CharacterCreateNameEdit:GetText());
|
||||
end
|
||||
|
||||
CharacterCreate.personalizationMode = false;
|
||||
CharacterCreate.gameModeSelectionMode = false;
|
||||
PlaySound("gsCharacterCreationCreateChar");
|
||||
end
|
||||
|
||||
function CharacterCreate_Back()
|
||||
if (CharacterCreate.personalizationMode == true) then
|
||||
if (CharacterCreate.gameModeSelectionMode == true) then
|
||||
CharacterCreate.gameModeSelectionMode = false;
|
||||
CharacterCreate_UpdatePersonalizationStep();
|
||||
return;
|
||||
end
|
||||
|
||||
CharacterCreate_TogglePersonalization();
|
||||
CharacterCreate.personalizationMode = false;
|
||||
return;
|
||||
@@ -1482,6 +1638,7 @@ end
|
||||
|
||||
function CharacterCreate_ResetState()
|
||||
CharacterCreate.personalizationMode = false;
|
||||
CharacterCreate.gameModeSelectionMode = false;
|
||||
CharacterCreateRaceButtonsContainer:Show();
|
||||
CharacterCreateClassButtonsContainer:Show();
|
||||
CharacterCreateGenderButtonsContainer:Show();
|
||||
@@ -1498,6 +1655,7 @@ function CharacterCreate_ResetState()
|
||||
CharacterCreateRandomName:Hide();
|
||||
CharCreatePersonalizeButton:Show();
|
||||
CharCreateOkayButton:Hide();
|
||||
CharacterCreate_UpdateGameModeVisibility();
|
||||
|
||||
for i=1, NUM_CHAR_CUSTOMIZATIONS do
|
||||
_G["CharacterCustomizationButtonFrame"..i]:Hide();
|
||||
+85
@@ -187,6 +187,57 @@
|
||||
</Button>
|
||||
</Frames>
|
||||
</Frame>
|
||||
<!-- Game mode tile template -->
|
||||
<Frame name="CharacterCreateGameModeTileTemplate" virtual="true" enableMouse="false">
|
||||
<Size x="220" y="126"/>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentBg">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" x="4" y="-4"/>
|
||||
<Anchor point="BOTTOMRIGHT" x="-4" y="4"/>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="ARTWORK">
|
||||
<Texture name="$parentShade" file="Interface\Tooltips\UI-Tooltip-Background">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" x="4" y="-4"/>
|
||||
<Anchor point="BOTTOMRIGHT" x="-4" y="4"/>
|
||||
</Anchors>
|
||||
<Color r="0" g="0" b="0" a="0.18"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString name="$parentTitle" inherits="GlueFontNormalLarge" justifyH="CENTER">
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" x="0" y="16"/>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Button name="$parentPlayButton" inherits="GlueButtonSmallTemplate">
|
||||
<Size x="120" y="26"/>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM" x="0" y="12"/>
|
||||
</Anchors>
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
self:SetBackdrop({
|
||||
edgeFile = "Interface\\tooltips\\ui-tooltip-border-maw",
|
||||
tile = true,
|
||||
tileSize = 16,
|
||||
edgeSize = 16,
|
||||
insets = { left = 5, right = 5, top = 5, bottom = 5 }
|
||||
});
|
||||
self:SetBackdropBorderColor(0.92, 0.92, 0.92, 0.95);
|
||||
</OnLoad>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
|
||||
<ModelFFX name="CharacterCreate" toplevel="true" parent="GlueParent" setAllPoints="true" enableKeyboard="true" hidden="true">
|
||||
<Frames>
|
||||
<Frame name="CharacterCreateFrame" setAllPoints="true" enableMouse="true">
|
||||
@@ -683,6 +734,40 @@
|
||||
</HighlightTexture>
|
||||
</Button>
|
||||
|
||||
<!-- Game mode selection -->
|
||||
<Frame name="CharacterCreateGameModeFrame" hidden="true">
|
||||
<Size x="520" y="190"/>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM" relativeTo="CharacterCreateNameEdit" relativePoint="TOP" x="0" y="26"/>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString name="CharacterCreateGameModeFrameTitle" inherits="GlueFontNormalLarge" justifyH="CENTER">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" x="0" y="0"/>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString name="CharacterCreateGameModeFrameSubtitle" inherits="GlueFontHighlightSmall" justifyH="CENTER">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeTo="CharacterCreateGameModeFrameTitle" relativePoint="BOTTOM" x="0" y="-6"/>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Frame name="CharacterCreateGameModeNormal" inherits="CharacterCreateGameModeTileTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" x="-120" y="-54"/>
|
||||
</Anchors>
|
||||
</Frame>
|
||||
<Frame name="CharacterCreateGameModeTraitor" inherits="CharacterCreateGameModeTileTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" x="120" y="-54"/>
|
||||
</Anchors>
|
||||
</Frame>
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<Button name="CharCreatePersonalizeButton" inherits="GlueButtonTemplate" text="CHARACTER_CREATE_CUSTOMIZE">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMRIGHT" x="-40" y="20"/>
|
||||
+194
-3
@@ -8,6 +8,163 @@ CHARACTER_ROTATION_CONSTANT = 0.6;
|
||||
|
||||
MAX_CHARACTERS_DISPLAYED = 8;
|
||||
MAX_CHARACTERS_PER_REALM = 8;
|
||||
GAMEMODE_CHARACTER_FLAG_TRAITOR = 0x40000000;
|
||||
CHARACTER_SELECT_DEBUG_CHARFLAGS = true;
|
||||
|
||||
local function CharacterSelect_HasFlag(charFlags, flagMask)
|
||||
if ( type(charFlags) ~= "number" or type(flagMask) ~= "number" ) then
|
||||
return false;
|
||||
end
|
||||
|
||||
if ( bit and bit.band ) then
|
||||
return bit.band(charFlags, flagMask) ~= 0;
|
||||
end
|
||||
|
||||
if ( bit32 and bit32.band ) then
|
||||
return bit32.band(charFlags, flagMask) ~= 0;
|
||||
end
|
||||
|
||||
return math.floor(charFlags / flagMask) % 2 == 1;
|
||||
end
|
||||
|
||||
local function CharacterSelect_IsTraitorCharacter(charFlags)
|
||||
return CharacterSelect_HasFlag(charFlags, GAMEMODE_CHARACTER_FLAG_TRAITOR);
|
||||
end
|
||||
|
||||
local function CharacterSelect_FormatCharFlags(charFlags)
|
||||
if ( type(charFlags) ~= "number" ) then
|
||||
return "nil";
|
||||
end
|
||||
|
||||
charFlags = math.floor(charFlags);
|
||||
return tostring(charFlags).." ("..format("0x%X", charFlags)..")";
|
||||
end
|
||||
|
||||
local function CharacterSelect_GetCharacterInfoDebug(index)
|
||||
local info = { GetCharacterInfo(index) };
|
||||
local count = table.getn(info);
|
||||
local parts = {};
|
||||
|
||||
for i = 1, count do
|
||||
parts[i] = tostring(info[i]);
|
||||
end
|
||||
|
||||
return info, count, table.concat(parts, " | ");
|
||||
end
|
||||
|
||||
local function CharacterSelect_GetDisplayName(index)
|
||||
local info = { GetCharacterInfo(index) };
|
||||
local name = info[1];
|
||||
local charFlags = info[11];
|
||||
|
||||
if ( not name ) then
|
||||
return "";
|
||||
end
|
||||
|
||||
if ( CharacterSelect_IsTraitorCharacter(charFlags) ) then
|
||||
return name.." |cffd9534f[Предатель]|r";
|
||||
end
|
||||
|
||||
return name;
|
||||
end
|
||||
|
||||
local function CharacterSelect_ResetTraitorVisuals(button, buttonIndex)
|
||||
if ( not button ) then
|
||||
return;
|
||||
end
|
||||
|
||||
local generalBg = _G["CharSelectCharacterButton"..buttonIndex.."GeneralBackground"];
|
||||
if ( generalBg ) then
|
||||
generalBg:SetVertexColor(1, 1, 1);
|
||||
end
|
||||
|
||||
local background = _G["CharSelectCharacterButton"..buttonIndex.."Background"];
|
||||
if ( background ) then
|
||||
background:SetVertexColor(1, 1, 1);
|
||||
end
|
||||
|
||||
local traitorGlow = _G["CharSelectCharacterButton"..buttonIndex.."TraitorGlow"];
|
||||
if ( traitorGlow ) then
|
||||
traitorGlow:Hide();
|
||||
end
|
||||
|
||||
local traitorBadge = _G["CharSelectCharacterButton"..buttonIndex.."TraitorBadge"];
|
||||
if ( traitorBadge ) then
|
||||
traitorBadge:Hide();
|
||||
end
|
||||
|
||||
local debugFlagsText = _G["CharSelectCharacterButton"..buttonIndex.."DebugFlags"];
|
||||
if ( debugFlagsText ) then
|
||||
debugFlagsText:Hide();
|
||||
end
|
||||
end
|
||||
|
||||
local function CharacterSelect_UpdateTraitorVisuals(button, buttonIndex, isTraitor)
|
||||
if ( not button ) then
|
||||
return;
|
||||
end
|
||||
|
||||
CharacterSelect_ResetTraitorVisuals(button, buttonIndex);
|
||||
|
||||
if ( not isTraitor ) then
|
||||
return;
|
||||
end
|
||||
|
||||
local generalBg = _G["CharSelectCharacterButton"..buttonIndex.."GeneralBackground"];
|
||||
if ( generalBg ) then
|
||||
generalBg:SetVertexColor(1, 0.78, 0.78);
|
||||
end
|
||||
|
||||
local background = _G["CharSelectCharacterButton"..buttonIndex.."Background"];
|
||||
if ( background ) then
|
||||
background:SetVertexColor(1, 0.72, 0.72);
|
||||
end
|
||||
|
||||
local traitorGlow = _G["CharSelectCharacterButton"..buttonIndex.."TraitorGlow"];
|
||||
if ( not traitorGlow ) then
|
||||
traitorGlow = button:CreateTexture("CharSelectCharacterButton"..buttonIndex.."TraitorGlow", "OVERLAY", nil, 4);
|
||||
traitorGlow:ClearAllPoints();
|
||||
traitorGlow:SetPoint("TOPLEFT", button, "TOPLEFT", -18, 6);
|
||||
traitorGlow:SetSize(256, 70);
|
||||
traitorGlow:SetTexture("Interface\\Glues\\CharacterSelect\\Glue-CharacterSelect-Highlight");
|
||||
traitorGlow:SetBlendMode("ADD");
|
||||
end
|
||||
traitorGlow:SetVertexColor(1, 0.18, 0.18);
|
||||
traitorGlow:SetAlpha(0.55);
|
||||
traitorGlow:Show();
|
||||
|
||||
local traitorBadge = _G["CharSelectCharacterButton"..buttonIndex.."TraitorBadge"];
|
||||
if ( not traitorBadge ) then
|
||||
traitorBadge = button:CreateFontString("CharSelectCharacterButton"..buttonIndex.."TraitorBadge", "OVERLAY", "GlueFontNormalSmall");
|
||||
traitorBadge:ClearAllPoints();
|
||||
traitorBadge:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", -18, 10);
|
||||
traitorBadge:SetFont("Fonts\\FRIZQT__.TTF", 10, "OUTLINE");
|
||||
end
|
||||
traitorBadge:SetText("ПРЕДАТЕЛЬ");
|
||||
traitorBadge:SetTextColor(1, 0.25, 0.25);
|
||||
traitorBadge:Show();
|
||||
end
|
||||
|
||||
local function CharacterSelect_UpdateDebugCharFlags(button, buttonIndex, charFlags)
|
||||
local debugFlagsText = _G["CharSelectCharacterButton"..buttonIndex.."DebugFlags"];
|
||||
if ( not debugFlagsText ) then
|
||||
debugFlagsText = button:CreateFontString("CharSelectCharacterButton"..buttonIndex.."DebugFlags", "OVERLAY", "GlueFontDisableSmall");
|
||||
debugFlagsText:ClearAllPoints();
|
||||
debugFlagsText:SetPoint("BOTTOMLEFT", button, "BOTTOMLEFT", 0, 8);
|
||||
debugFlagsText:SetWidth(170);
|
||||
debugFlagsText:SetJustifyH("LEFT");
|
||||
debugFlagsText:SetWordWrap(false);
|
||||
debugFlagsText:SetFont("Fonts\\FRIZQT__.TTF", 8, "OUTLINE");
|
||||
end
|
||||
|
||||
if ( CHARACTER_SELECT_DEBUG_CHARFLAGS ) then
|
||||
debugFlagsText:SetText("flags="..CharacterSelect_FormatCharFlags(charFlags));
|
||||
debugFlagsText:SetTextColor(0.8, 0.8, 0.8);
|
||||
debugFlagsText:Show();
|
||||
else
|
||||
debugFlagsText:Hide();
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function CharacterSelect_OnLoad(self)
|
||||
@@ -339,13 +496,13 @@ function CharacterSelect_OnEvent(self, event, ...)
|
||||
UpdateAddonButton();
|
||||
elseif ( event == "CHARACTER_LIST_UPDATE" ) then
|
||||
UpdateCharacterList();
|
||||
CharSelectCharacterName:SetText(GetCharacterInfo(self.selectedIndex));
|
||||
CharSelectCharacterName:SetText(CharacterSelect_GetDisplayName(self.selectedIndex));
|
||||
elseif ( event == "UPDATE_SELECTED_CHARACTER" ) then
|
||||
local index = ...;
|
||||
if ( index == 0 ) then
|
||||
CharSelectCharacterName:SetText("");
|
||||
else
|
||||
CharSelectCharacterName:SetText(GetCharacterInfo(index));
|
||||
CharSelectCharacterName:SetText(CharacterSelect_GetDisplayName(index));
|
||||
self.selectedIndex = index;
|
||||
end
|
||||
UpdateCharacterSelection(self);
|
||||
@@ -393,10 +550,12 @@ function UpdateCharacterList()
|
||||
local numChars = GetNumCharacters();
|
||||
local index = 1;
|
||||
local coords;
|
||||
local debugCharFlagsLines = {};
|
||||
|
||||
for i = 1, MAX_CHARACTERS_DISPLAYED do
|
||||
local button = _G["CharSelectCharacterButton"..i];
|
||||
button:Show();
|
||||
CharacterSelect_ResetTraitorVisuals(button, i);
|
||||
end
|
||||
|
||||
_G["CharacterSelectCharacterFrame"]:SetHeight(620);
|
||||
@@ -532,7 +691,18 @@ function UpdateCharacterList()
|
||||
};
|
||||
|
||||
for i=1, numChars, 1 do
|
||||
local name, race, class, level, zone, sex, ghost, PCC, PRC, PFC = GetCharacterInfo(i);
|
||||
local info, infoCount, infoDebug = CharacterSelect_GetCharacterInfoDebug(i);
|
||||
local name = info[1];
|
||||
local race = info[2];
|
||||
local class = info[3];
|
||||
local level = info[4];
|
||||
local zone = info[5];
|
||||
local sex = info[6];
|
||||
local ghost = info[7];
|
||||
local PCC = info[8];
|
||||
local PRC = info[9];
|
||||
local PFC = info[10];
|
||||
local charFlags = info[11];
|
||||
local button = _G["CharSelectCharacterButton"..index];
|
||||
|
||||
if ( not name ) then
|
||||
@@ -581,6 +751,18 @@ function UpdateCharacterList()
|
||||
factionIcon:SetTexture(FACTION_ICONS["Horde"]);
|
||||
end
|
||||
factionIcon:Show();
|
||||
CharacterSelect_UpdateTraitorVisuals(button, index, CharacterSelect_IsTraitorCharacter(charFlags));
|
||||
CharacterSelect_UpdateDebugCharFlags(button, index, charFlags);
|
||||
if ( CHARACTER_SELECT_DEBUG_CHARFLAGS ) then
|
||||
table.insert(
|
||||
debugCharFlagsLines,
|
||||
"#" .. tostring(i) ..
|
||||
" " .. tostring(name) ..
|
||||
" returns=" .. tostring(infoCount) ..
|
||||
" flags=" .. CharacterSelect_FormatCharFlags(charFlags) ..
|
||||
" raw=[" .. infoDebug .. "]"
|
||||
);
|
||||
end
|
||||
|
||||
-- Nombre
|
||||
local nameText = _G["CharSelectCharacterButton"..index.."ButtonTextName"];
|
||||
@@ -741,6 +923,15 @@ function UpdateCharacterList()
|
||||
_G["CharSelectFactionChange"..i]:Hide();
|
||||
|
||||
button:Show();
|
||||
CharacterSelect_ResetTraitorVisuals(button, i);
|
||||
end
|
||||
|
||||
if ( CHARACTER_SELECT_DEBUG_CHARFLAGS and table.getn(debugCharFlagsLines) > 0 ) then
|
||||
local debugCharFlagsMessage = table.concat(debugCharFlagsLines, "\n");
|
||||
if ( CharacterSelect.lastCharFlagsDebugMessage ~= debugCharFlagsMessage ) then
|
||||
CharacterSelect.lastCharFlagsDebugMessage = debugCharFlagsMessage;
|
||||
message("CHAR_ENUM charFlags debug:\n" .. debugCharFlagsMessage);
|
||||
end
|
||||
end
|
||||
|
||||
if ( numChars == 0 ) then
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user