режим предатель, преезд всего 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_CHARACTER_ID = nil;
|
||||||
PAID_SERVICE_TYPE = nil;
|
PAID_SERVICE_TYPE = nil;
|
||||||
|
|
||||||
|
GAME_MODE_NORMAL = 0;
|
||||||
|
GAME_MODE_TRAITOR = 1;
|
||||||
|
|
||||||
FACTION_BACKDROP_COLOR_TABLE = {
|
FACTION_BACKDROP_COLOR_TABLE = {
|
||||||
["Alliance"] = {0.5, 0.5, 0.5, 0.09, 0.09, 0.19},
|
["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},
|
["Horde"] = {0.5, 0.2, 0.2, 0.19, 0.05, 0.05},
|
||||||
@@ -81,6 +84,10 @@ local raceTooltips = {};
|
|||||||
local classTooltips = {};
|
local classTooltips = {};
|
||||||
local detailedRaceTooltips = {};
|
local detailedRaceTooltips = {};
|
||||||
local detailedClassTooltips = {};
|
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)
|
local function UTF8Chars(text)
|
||||||
return string.gmatch(text or "", "[%z\1-\127\194-\244][\128-\191]*")
|
return string.gmatch(text or "", "[%z\1-\127\194-\244][\128-\191]*")
|
||||||
@@ -636,6 +643,129 @@ function CharacterCreate_PositionAllButtons()
|
|||||||
CharacterCreate_PositionGenderButtons()
|
CharacterCreate_PositionGenderButtons()
|
||||||
end
|
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)
|
function CharacterCreate_OnLoad(self)
|
||||||
self:SetSequence(0)
|
self:SetSequence(0)
|
||||||
self:SetCamera(0)
|
self:SetCamera(0)
|
||||||
@@ -646,9 +776,11 @@ function CharacterCreate_OnLoad(self)
|
|||||||
CharacterCreate.numClasses = 0
|
CharacterCreate.numClasses = 0
|
||||||
CharacterCreate.selectedClass = 0
|
CharacterCreate.selectedClass = 0
|
||||||
CharacterCreate.selectedGender = 0
|
CharacterCreate.selectedGender = 0
|
||||||
|
CharacterCreate.gameModeSelectionMode = false
|
||||||
CharacterCreate.personalizationMode = false
|
CharacterCreate.personalizationMode = false
|
||||||
CharacterCreate_MoveTexturesToBackground()
|
CharacterCreate_MoveTexturesToBackground()
|
||||||
CharacterCreate_SetupCustomButtons()
|
CharacterCreate_SetupCustomButtons()
|
||||||
|
CharacterCreate_InitGameModeTiles()
|
||||||
|
|
||||||
for i=1, NUM_CHAR_CUSTOMIZATIONS, 1 do
|
for i=1, NUM_CHAR_CUSTOMIZATIONS, 1 do
|
||||||
_G["CharacterCustomizationButtonFrame"..i.."Text"]:SetText(_G["CHAR_CUSTOMIZATION"..i.."_DESC"])
|
_G["CharacterCustomizationButtonFrame"..i.."Text"]:SetText(_G["CHAR_CUSTOMIZATION"..i.."_DESC"])
|
||||||
@@ -778,15 +910,12 @@ function CharacterCreate_TogglePersonalization()
|
|||||||
CharCreatePersonalizeButton:Hide();
|
CharCreatePersonalizeButton:Hide();
|
||||||
CharCreateOkayButton:Show();
|
CharCreateOkayButton:Show();
|
||||||
CharacterCreate_UpdateHairCustomization();
|
CharacterCreate_UpdateHairCustomization();
|
||||||
CharCreateRandomizeButton:Show();
|
CharacterCreate.gameModeSelectionMode = false;
|
||||||
CharacterCreateNameEdit:Show();
|
CharacterCreate_UpdatePersonalizationStep();
|
||||||
CharacterCreateRandomName:Show();
|
|
||||||
|
|
||||||
for i=1, NUM_CHAR_CUSTOMIZATIONS do
|
|
||||||
_G["CharacterCustomizationButtonFrame"..i]:Show();
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
CharacterCreate.personalizationMode = false;
|
CharacterCreate.personalizationMode = false;
|
||||||
|
CharacterCreate.gameModeSelectionMode = false;
|
||||||
PlaySound("gsCharacterCreationCancel");
|
PlaySound("gsCharacterCreationCancel");
|
||||||
CharacterCreateRaceButtonsContainer:Show();
|
CharacterCreateRaceButtonsContainer:Show();
|
||||||
CharacterCreateClassButtonsContainer:Show();
|
CharacterCreateClassButtonsContainer:Show();
|
||||||
@@ -804,6 +933,7 @@ function CharacterCreate_TogglePersonalization()
|
|||||||
CharacterCreateRandomName:Hide();
|
CharacterCreateRandomName:Hide();
|
||||||
CharCreatePersonalizeButton:Show();
|
CharCreatePersonalizeButton:Show();
|
||||||
CharCreateOkayButton:Hide();
|
CharCreateOkayButton:Hide();
|
||||||
|
CharacterCreate_UpdateGameModeVisibility();
|
||||||
|
|
||||||
for i=1, NUM_CHAR_CUSTOMIZATIONS do
|
for i=1, NUM_CHAR_CUSTOMIZATIONS do
|
||||||
_G["CharacterCustomizationButtonFrame"..i]:Hide();
|
_G["CharacterCustomizationButtonFrame"..i]:Hide();
|
||||||
@@ -844,6 +974,7 @@ function CharacterCreate_OnShow()
|
|||||||
end
|
end
|
||||||
|
|
||||||
CharacterCreate.personalizationMode = false;
|
CharacterCreate.personalizationMode = false;
|
||||||
|
CharacterCreate.gameModeSelectionMode = false;
|
||||||
CharCreateOkayButton:Hide();
|
CharCreateOkayButton:Hide();
|
||||||
|
|
||||||
for i=1, NUM_CHAR_CUSTOMIZATIONS do
|
for i=1, NUM_CHAR_CUSTOMIZATIONS do
|
||||||
@@ -865,6 +996,8 @@ function CharacterCreate_OnShow()
|
|||||||
|
|
||||||
hideScheduled = true
|
hideScheduled = true
|
||||||
HideNameEditFrame:Show()
|
HideNameEditFrame:Show()
|
||||||
|
CharacterCreate.gameModeSelectionMode = false;
|
||||||
|
CharacterCreate_UpdateGameModeVisibility();
|
||||||
CharacterCreate_PositionAllButtons()
|
CharacterCreate_PositionAllButtons()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1316,6 +1449,9 @@ function SetCharacterClass(id)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function CharacterCreate_OnChar()
|
function CharacterCreate_OnChar()
|
||||||
|
if CharacterCreate.personalizationMode then
|
||||||
|
CharacterCreate_UpdatePersonalizationStep();
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function CharacterCreate_UpdateButtonCheckedStates()
|
function CharacterCreate_UpdateButtonCheckedStates()
|
||||||
@@ -1363,11 +1499,7 @@ end
|
|||||||
|
|
||||||
function CharacterCreate_OnKeyDown(key)
|
function CharacterCreate_OnKeyDown(key)
|
||||||
if ( key == "ESCAPE" ) then
|
if ( key == "ESCAPE" ) then
|
||||||
if (CharacterCreate.personalizationMode == true) then
|
CharacterCreate_Back();
|
||||||
CharacterCreate_TogglePersonalization();
|
|
||||||
else
|
|
||||||
CharacterCreate_Back();
|
|
||||||
end
|
|
||||||
elseif ( key == "ENTER" ) then
|
elseif ( key == "ENTER" ) then
|
||||||
CharacterCreate_Okay();
|
CharacterCreate_Okay();
|
||||||
elseif ( key == "PRINTSCREEN" ) then
|
elseif ( key == "PRINTSCREEN" ) then
|
||||||
@@ -1381,18 +1513,42 @@ function CharacterCreate_UpdateModel(self)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function CharacterCreate_Okay()
|
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
|
if ( PAID_SERVICE_TYPE ) then
|
||||||
GlueDialog_Show("CONFIRM_PAID_SERVICE");
|
GlueDialog_Show("CONFIRM_PAID_SERVICE");
|
||||||
else
|
else
|
||||||
|
message("DEBUG: old path CreateCharacter (no outfitId!)");
|
||||||
CreateCharacter(CharacterCreateNameEdit:GetText());
|
CreateCharacter(CharacterCreateNameEdit:GetText());
|
||||||
end
|
end
|
||||||
|
|
||||||
CharacterCreate.personalizationMode = false;
|
CharacterCreate.personalizationMode = false;
|
||||||
|
CharacterCreate.gameModeSelectionMode = false;
|
||||||
PlaySound("gsCharacterCreationCreateChar");
|
PlaySound("gsCharacterCreationCreateChar");
|
||||||
end
|
end
|
||||||
|
|
||||||
function CharacterCreate_Back()
|
function CharacterCreate_Back()
|
||||||
if (CharacterCreate.personalizationMode == true) then
|
if (CharacterCreate.personalizationMode == true) then
|
||||||
|
if (CharacterCreate.gameModeSelectionMode == true) then
|
||||||
|
CharacterCreate.gameModeSelectionMode = false;
|
||||||
|
CharacterCreate_UpdatePersonalizationStep();
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
CharacterCreate_TogglePersonalization();
|
CharacterCreate_TogglePersonalization();
|
||||||
CharacterCreate.personalizationMode = false;
|
CharacterCreate.personalizationMode = false;
|
||||||
return;
|
return;
|
||||||
@@ -1482,6 +1638,7 @@ end
|
|||||||
|
|
||||||
function CharacterCreate_ResetState()
|
function CharacterCreate_ResetState()
|
||||||
CharacterCreate.personalizationMode = false;
|
CharacterCreate.personalizationMode = false;
|
||||||
|
CharacterCreate.gameModeSelectionMode = false;
|
||||||
CharacterCreateRaceButtonsContainer:Show();
|
CharacterCreateRaceButtonsContainer:Show();
|
||||||
CharacterCreateClassButtonsContainer:Show();
|
CharacterCreateClassButtonsContainer:Show();
|
||||||
CharacterCreateGenderButtonsContainer:Show();
|
CharacterCreateGenderButtonsContainer:Show();
|
||||||
@@ -1498,6 +1655,7 @@ function CharacterCreate_ResetState()
|
|||||||
CharacterCreateRandomName:Hide();
|
CharacterCreateRandomName:Hide();
|
||||||
CharCreatePersonalizeButton:Show();
|
CharCreatePersonalizeButton:Show();
|
||||||
CharCreateOkayButton:Hide();
|
CharCreateOkayButton:Hide();
|
||||||
|
CharacterCreate_UpdateGameModeVisibility();
|
||||||
|
|
||||||
for i=1, NUM_CHAR_CUSTOMIZATIONS do
|
for i=1, NUM_CHAR_CUSTOMIZATIONS do
|
||||||
_G["CharacterCustomizationButtonFrame"..i]:Hide();
|
_G["CharacterCustomizationButtonFrame"..i]:Hide();
|
||||||
+85
@@ -187,6 +187,57 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</Frames>
|
</Frames>
|
||||||
</Frame>
|
</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">
|
<ModelFFX name="CharacterCreate" toplevel="true" parent="GlueParent" setAllPoints="true" enableKeyboard="true" hidden="true">
|
||||||
<Frames>
|
<Frames>
|
||||||
<Frame name="CharacterCreateFrame" setAllPoints="true" enableMouse="true">
|
<Frame name="CharacterCreateFrame" setAllPoints="true" enableMouse="true">
|
||||||
@@ -683,6 +734,40 @@
|
|||||||
</HighlightTexture>
|
</HighlightTexture>
|
||||||
</Button>
|
</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">
|
<Button name="CharCreatePersonalizeButton" inherits="GlueButtonTemplate" text="CHARACTER_CREATE_CUSTOMIZE">
|
||||||
<Anchors>
|
<Anchors>
|
||||||
<Anchor point="BOTTOMRIGHT" x="-40" y="20"/>
|
<Anchor point="BOTTOMRIGHT" x="-40" y="20"/>
|
||||||
+194
-3
@@ -8,6 +8,163 @@ CHARACTER_ROTATION_CONSTANT = 0.6;
|
|||||||
|
|
||||||
MAX_CHARACTERS_DISPLAYED = 8;
|
MAX_CHARACTERS_DISPLAYED = 8;
|
||||||
MAX_CHARACTERS_PER_REALM = 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)
|
function CharacterSelect_OnLoad(self)
|
||||||
@@ -339,13 +496,13 @@ function CharacterSelect_OnEvent(self, event, ...)
|
|||||||
UpdateAddonButton();
|
UpdateAddonButton();
|
||||||
elseif ( event == "CHARACTER_LIST_UPDATE" ) then
|
elseif ( event == "CHARACTER_LIST_UPDATE" ) then
|
||||||
UpdateCharacterList();
|
UpdateCharacterList();
|
||||||
CharSelectCharacterName:SetText(GetCharacterInfo(self.selectedIndex));
|
CharSelectCharacterName:SetText(CharacterSelect_GetDisplayName(self.selectedIndex));
|
||||||
elseif ( event == "UPDATE_SELECTED_CHARACTER" ) then
|
elseif ( event == "UPDATE_SELECTED_CHARACTER" ) then
|
||||||
local index = ...;
|
local index = ...;
|
||||||
if ( index == 0 ) then
|
if ( index == 0 ) then
|
||||||
CharSelectCharacterName:SetText("");
|
CharSelectCharacterName:SetText("");
|
||||||
else
|
else
|
||||||
CharSelectCharacterName:SetText(GetCharacterInfo(index));
|
CharSelectCharacterName:SetText(CharacterSelect_GetDisplayName(index));
|
||||||
self.selectedIndex = index;
|
self.selectedIndex = index;
|
||||||
end
|
end
|
||||||
UpdateCharacterSelection(self);
|
UpdateCharacterSelection(self);
|
||||||
@@ -393,10 +550,12 @@ function UpdateCharacterList()
|
|||||||
local numChars = GetNumCharacters();
|
local numChars = GetNumCharacters();
|
||||||
local index = 1;
|
local index = 1;
|
||||||
local coords;
|
local coords;
|
||||||
|
local debugCharFlagsLines = {};
|
||||||
|
|
||||||
for i = 1, MAX_CHARACTERS_DISPLAYED do
|
for i = 1, MAX_CHARACTERS_DISPLAYED do
|
||||||
local button = _G["CharSelectCharacterButton"..i];
|
local button = _G["CharSelectCharacterButton"..i];
|
||||||
button:Show();
|
button:Show();
|
||||||
|
CharacterSelect_ResetTraitorVisuals(button, i);
|
||||||
end
|
end
|
||||||
|
|
||||||
_G["CharacterSelectCharacterFrame"]:SetHeight(620);
|
_G["CharacterSelectCharacterFrame"]:SetHeight(620);
|
||||||
@@ -532,7 +691,18 @@ function UpdateCharacterList()
|
|||||||
};
|
};
|
||||||
|
|
||||||
for i=1, numChars, 1 do
|
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];
|
local button = _G["CharSelectCharacterButton"..index];
|
||||||
|
|
||||||
if ( not name ) then
|
if ( not name ) then
|
||||||
@@ -581,6 +751,18 @@ function UpdateCharacterList()
|
|||||||
factionIcon:SetTexture(FACTION_ICONS["Horde"]);
|
factionIcon:SetTexture(FACTION_ICONS["Horde"]);
|
||||||
end
|
end
|
||||||
factionIcon:Show();
|
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
|
-- Nombre
|
||||||
local nameText = _G["CharSelectCharacterButton"..index.."ButtonTextName"];
|
local nameText = _G["CharSelectCharacterButton"..index.."ButtonTextName"];
|
||||||
@@ -741,6 +923,15 @@ function UpdateCharacterList()
|
|||||||
_G["CharSelectFactionChange"..i]:Hide();
|
_G["CharSelectFactionChange"..i]:Hide();
|
||||||
|
|
||||||
button:Show();
|
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
|
end
|
||||||
|
|
||||||
if ( numChars == 0 ) then
|
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