ML: Fixed HotReload and RTL/LTR integration for UI Arabic

This commit is contained in:
rtw1x1
2026-02-18 20:00:05 +00:00
parent 5e5e49f048
commit c9052deb4d
16 changed files with 670 additions and 657 deletions

View File

@@ -55,9 +55,11 @@ class Console(object):
def ReloadLocale(self):
"Reload Locale"
reload(localeInfo)
reload(uiScriptLocale)
self.Print("RELOAD LOCALE")
import app
if app.ReloadLocale():
self.Print("RELOAD LOCALE OK")
else:
self.Print("RELOAD LOCALE FAILED")
def ReloadDevel(self):
"ReloadDevel"

View File

@@ -28,29 +28,7 @@ if EMOTION_VERSION == 2:
EMOTION_FRENCH_KISS = 52
EMOTION_SLAP = 53
EMOTION_DICT = {
EMOTION_CLAP : {"name": localeInfo.EMOTION_CLAP, "command":"/clap"},
EMOTION_DANCE_1 : {"name": localeInfo.EMOTION_DANCE_1, "command":"/dance1"},
EMOTION_DANCE_2 : {"name": localeInfo.EMOTION_DANCE_2, "command":"/dance2"},
EMOTION_DANCE_3 : {"name": localeInfo.EMOTION_DANCE_3, "command":"/dance3"},
EMOTION_DANCE_4 : {"name": localeInfo.EMOTION_DANCE_4, "command":"/dance4"},
EMOTION_DANCE_5 : {"name": localeInfo.EMOTION_DANCE_5, "command":"/dance5"},
EMOTION_DANCE_6 : {"name": localeInfo.EMOTION_DANCE_6, "command":"/dance6"},
EMOTION_CONGRATULATION : {"name": localeInfo.EMOTION_CONGRATULATION, "command":"/congratulation"},
EMOTION_FORGIVE : {"name": localeInfo.EMOTION_FORGIVE, "command":"/forgive"},
EMOTION_ANGRY : {"name": localeInfo.EMOTION_ANGRY, "command":"/angry"},
EMOTION_ATTRACTIVE : {"name": localeInfo.EMOTION_ATTRACTIVE, "command":"/attractive"},
EMOTION_SAD : {"name": localeInfo.EMOTION_SAD, "command":"/sad"},
EMOTION_SHY : {"name": localeInfo.EMOTION_SHY, "command":"/shy"},
EMOTION_CHEERUP : {"name": localeInfo.EMOTION_CHEERUP, "command":"/cheerup"},
EMOTION_BANTER : {"name": localeInfo.EMOTION_BANTER, "command":"/banter"},
EMOTION_JOY : {"name": localeInfo.EMOTION_JOY, "command":"/joy"},
EMOTION_CHEERS_1 : {"name": localeInfo.EMOTION_CHEERS_1, "command":"/cheer1"},
EMOTION_CHEERS_2 : {"name": localeInfo.EMOTION_CHEERS_2, "command":"/cheer2"},
EMOTION_KISS : {"name": localeInfo.EMOTION_CLAP_KISS, "command":"/kiss"},
EMOTION_FRENCH_KISS : {"name": localeInfo.EMOTION_FRENCH_KISS, "command":"/french_kiss"},
EMOTION_SLAP : {"name": localeInfo.EMOTION_SLAP, "command":"/slap"},
}
EMOTION_DICT = {}
ICON_DICT = {
EMOTION_CLAP : "d:/ymir work/ui/game/windows/emotion_clap.sub",
@@ -128,16 +106,7 @@ elif EMOTION_VERSION == 1:
EMOTION_FRENCH_KISS = 52
EMOTION_SLAP = 53
EMOTION_DICT = {
EMOTION_CLAP : {"name": localeInfo.EMOTION_CLAP, "command":"/clap"},
EMOTION_CHEERS_1 : {"name": localeInfo.EMOTION_CHEERS_1, "command":"/cheer1"},
EMOTION_CHEERS_2 : {"name": localeInfo.EMOTION_CHEERS_2, "command":"/cheer2"},
EMOTION_DANCE_1 : {"name": localeInfo.EMOTION_DANCE_1, "command":"/dance1"},
EMOTION_DANCE_2 : {"name": localeInfo.EMOTION_DANCE_2, "command":"/dance2"},
EMOTION_KISS : {"name": localeInfo.EMOTION_CLAP_KISS, "command":"/kiss"},
EMOTION_FRENCH_KISS : {"name": localeInfo.EMOTION_FRENCH_KISS, "command":"/french_kiss"},
EMOTION_SLAP : {"name": localeInfo.EMOTION_SLAP, "command":"/slap"},
}
EMOTION_DICT = {}
ICON_DICT = {
EMOTION_CLAP : "d:/ymir work/ui/game/windows/emotion_clap.sub",
@@ -183,14 +152,7 @@ else:
EMOTION_FRENCH_KISS = 52
EMOTION_SLAP = 53
EMOTION_DICT = {
EMOTION_CLAP : {"name": localeInfo.EMOTION_CLAP, "command":"/clap"},
EMOTION_CHEERS_1 : {"name": localeInfo.EMOTION_CHEERS_1, "command":"/cheer1"},
EMOTION_CHEERS_2 : {"name": localeInfo.EMOTION_CHEERS_2, "command":"/cheer2"},
EMOTION_KISS : {"name": localeInfo.EMOTION_CLAP_KISS, "command":"/kiss"},
EMOTION_FRENCH_KISS : {"name": localeInfo.EMOTION_FRENCH_KISS, "command":"/french_kiss"},
EMOTION_SLAP : {"name": localeInfo.EMOTION_SLAP, "command":"/slap"},
}
EMOTION_DICT = {}
ICON_DICT = {
EMOTION_CLAP : "d:/ymir work/ui/game/windows/emotion_clap.sub",
@@ -225,6 +187,56 @@ else:
}
def _RebuildLocaleStrings():
global EMOTION_DICT
if EMOTION_VERSION == 2:
EMOTION_DICT = {
EMOTION_CLAP : {"name": localeInfo.EMOTION_CLAP, "command":"/clap"},
EMOTION_DANCE_1 : {"name": localeInfo.EMOTION_DANCE_1, "command":"/dance1"},
EMOTION_DANCE_2 : {"name": localeInfo.EMOTION_DANCE_2, "command":"/dance2"},
EMOTION_DANCE_3 : {"name": localeInfo.EMOTION_DANCE_3, "command":"/dance3"},
EMOTION_DANCE_4 : {"name": localeInfo.EMOTION_DANCE_4, "command":"/dance4"},
EMOTION_DANCE_5 : {"name": localeInfo.EMOTION_DANCE_5, "command":"/dance5"},
EMOTION_DANCE_6 : {"name": localeInfo.EMOTION_DANCE_6, "command":"/dance6"},
EMOTION_CONGRATULATION : {"name": localeInfo.EMOTION_CONGRATULATION, "command":"/congratulation"},
EMOTION_FORGIVE : {"name": localeInfo.EMOTION_FORGIVE, "command":"/forgive"},
EMOTION_ANGRY : {"name": localeInfo.EMOTION_ANGRY, "command":"/angry"},
EMOTION_ATTRACTIVE : {"name": localeInfo.EMOTION_ATTRACTIVE, "command":"/attractive"},
EMOTION_SAD : {"name": localeInfo.EMOTION_SAD, "command":"/sad"},
EMOTION_SHY : {"name": localeInfo.EMOTION_SHY, "command":"/shy"},
EMOTION_CHEERUP : {"name": localeInfo.EMOTION_CHEERUP, "command":"/cheerup"},
EMOTION_BANTER : {"name": localeInfo.EMOTION_BANTER, "command":"/banter"},
EMOTION_JOY : {"name": localeInfo.EMOTION_JOY, "command":"/joy"},
EMOTION_CHEERS_1 : {"name": localeInfo.EMOTION_CHEERS_1, "command":"/cheer1"},
EMOTION_CHEERS_2 : {"name": localeInfo.EMOTION_CHEERS_2, "command":"/cheer2"},
EMOTION_KISS : {"name": localeInfo.EMOTION_CLAP_KISS, "command":"/kiss"},
EMOTION_FRENCH_KISS : {"name": localeInfo.EMOTION_FRENCH_KISS, "command":"/french_kiss"},
EMOTION_SLAP : {"name": localeInfo.EMOTION_SLAP, "command":"/slap"},
}
elif EMOTION_VERSION == 1:
EMOTION_DICT = {
EMOTION_CLAP : {"name": localeInfo.EMOTION_CLAP, "command":"/clap"},
EMOTION_CHEERS_1 : {"name": localeInfo.EMOTION_CHEERS_1, "command":"/cheer1"},
EMOTION_CHEERS_2 : {"name": localeInfo.EMOTION_CHEERS_2, "command":"/cheer2"},
EMOTION_DANCE_1 : {"name": localeInfo.EMOTION_DANCE_1, "command":"/dance1"},
EMOTION_DANCE_2 : {"name": localeInfo.EMOTION_DANCE_2, "command":"/dance2"},
EMOTION_KISS : {"name": localeInfo.EMOTION_CLAP_KISS, "command":"/kiss"},
EMOTION_FRENCH_KISS : {"name": localeInfo.EMOTION_FRENCH_KISS, "command":"/french_kiss"},
EMOTION_SLAP : {"name": localeInfo.EMOTION_SLAP, "command":"/slap"},
}
else:
EMOTION_DICT = {
EMOTION_CLAP : {"name": localeInfo.EMOTION_CLAP, "command":"/clap"},
EMOTION_CHEERS_1 : {"name": localeInfo.EMOTION_CHEERS_1, "command":"/cheer1"},
EMOTION_CHEERS_2 : {"name": localeInfo.EMOTION_CHEERS_2, "command":"/cheer2"},
EMOTION_KISS : {"name": localeInfo.EMOTION_CLAP_KISS, "command":"/kiss"},
EMOTION_FRENCH_KISS : {"name": localeInfo.EMOTION_FRENCH_KISS, "command":"/french_kiss"},
EMOTION_SLAP : {"name": localeInfo.EMOTION_SLAP, "command":"/slap"},
}
_RebuildLocaleStrings()
localeInfo.RegisterReloadCallback(_RebuildLocaleStrings)
def __RegisterSharedEmotionAnis(mode, path):
chrmgr.SetPathName(path)
chrmgr.RegisterMotionMode(mode)

View File

@@ -590,6 +590,9 @@ class LoginWindow(ui.ScriptWindow):
"""Handle locale change - save config, reload, and refresh UI"""
import dbg
# Remember old RTL state to detect direction change
wasRTL = app.IsRTL()
# 1) Save locale code to config/locale.cfg
try:
import os
@@ -609,7 +612,37 @@ class LoginWindow(ui.ScriptWindow):
dbg.TraceError("Locale changed successfully, refreshing UI...")
# 3) Refresh all UI text elements with new locale
# 3) If RTL/LTR direction changed, reload UI script in-place
# (different UI scripts, element positioning, text alignment)
if wasRTL != app.IsRTL():
dbg.TraceError("RTL/LTR direction changed, reloading UI script...")
# Save current state before Close() destroys references
wasOnLoginBoard = self.connectBoard and self.connectBoard.IsShow()
savedId = self.idEditLine.GetText() if self.idEditLine else ""
savedPwd = self.pwdEditLine.GetText() if self.pwdEditLine else ""
# Rebuild popup dialog for new direction
self.stream.popupWindow.Destroy()
self.stream.CreatePopupDialog()
# Close and reopen with correct RTL/LTR script
self.Close()
self.Open()
# Restore the board the user was on
if wasOnLoginBoard:
self.__OpenLoginBoard()
if savedId:
self.idEditLine.SetText(savedId)
if savedPwd:
self.pwdEditLine.SetText(savedPwd)
else:
self.__RefreshServerList()
self.__OpenServerBoard()
return
# 4) Same direction - just refresh text elements
self.__RefreshLocaleUI()
def __RefreshLocaleUI(self):
@@ -673,10 +706,8 @@ class LoginWindow(ui.ScriptWindow):
self.localeSelector.Show()
self.localeSelector.SetTop()
except:
# import dbg
# dbg.TraceError("LoginWindow.__RefreshLocaleUI failed")
pass
except Exception as e:
dbg.TraceError("LoginWindow.__RefreshLocaleUI failed: %s" % str(e))
def __SetServerInfo(self, name):
net.SetServerInfo(name.strip())

View File

@@ -20,7 +20,7 @@ FN_GM_MARK = '{:s}/effect/gm.mse'.format(APP_GET_LOCALE_PATH_COMMON)
MAP_TREE2 = 'MAP_TREE2'
ERROR_MARK_UPLOAD_NEED_RECONNECT = 'UploadMark: Reconnect to game'
ERROR_MARK_CHECK_NEED_RECONNECT = 'CheckMark: Reconnect to game'
ERROR_MARK_CHECK_NEED_RECONNECT = 'CheckMark: Reconnect to game'
VIRTUAL_KEY_ALPHABET_LOWERS = r"[1234567890]/qwertyuiop\=asdfghjkl;`'zxcvbnm.,"
VIRTUAL_KEY_ALPHABET_UPPERS = r"{1234567890}?QWERTYUIOP|+ASDFGHJKL:~'ZXCVBNM<>"
@@ -28,30 +28,60 @@ VIRTUAL_KEY_SYMBOLS = "!@#$%^&*()_+|{}:'<>?~"
VIRTUAL_KEY_NUMBERS = "1234567890-=\\[];',./`"
VIRTUAL_KEY_SYMBOLS_BR = "!@#$%^&*()_+|{}:'<>?~aaaaeeeiioooouuc"
# Hot-reload callback registry
_reloadCallbacks = []
def RegisterReloadCallback(callback):
"""Register a function to be called after locale strings are reloaded."""
if callback not in _reloadCallbacks:
_reloadCallbacks.append(callback)
# Multi-language hot-reload support
def LoadLocaleData():
"""
Reload all game locale text strings from locale_game.txt
Called by app.ReloadLocale() when the user changes language.
Reloads locale_game.txt and updates all module-level locale strings.
Reloads locale_game.txt and rebuilds all derived data structures.
NOTE: Does NOT fire reload callbacks. The C++ reload chain calls
localeInfo.LoadLocaleData() first, then uiScriptLocale.LoadLocaleData()
second. Callbacks are fired by uiScriptLocale.LoadLocaleData() after
BOTH modules are fully reloaded, so callbacks can safely read from
either module.
Returns:
True on success, False on failure
"""
try:
localePath = app.GetLocalePath()
localeFilePath = "{:s}/locale_game.txt".format(localePath)
global APP_GET_LOCALE_PATH, APP_GET_LOCALE_PATH_COMMON
APP_GET_LOCALE_PATH = app.GetLocalePath()
APP_GET_LOCALE_PATH_COMMON = app.GetLocalePathCommon()
# Reload locale_game.txt - this updates all global variables in this module
localeFilePath = "{:s}/locale_game.txt".format(APP_GET_LOCALE_PATH)
LoadLocaleFile(localeFilePath, globals())
_RebuildDerivedData()
return True
except:
# import dbg
# dbg.TraceError("localeInfo.LoadLocaleData failed")
except Exception as e:
dbg.TraceError("localeInfo.LoadLocaleData failed: %s" % str(e))
return False
def FireReloadCallbacks():
"""
Fire all registered reload callbacks.
Called by uiScriptLocale.LoadLocaleData() after both locale modules
are fully reloaded, ensuring callbacks see fresh data from both
localeInfo and uiScriptLocale.
"""
for cb in _reloadCallbacks:
try:
cb()
except Exception as e:
dbg.TraceError("localeInfo reload callback failed: %s" % str(e))
# Load locale_game.txt
def LoadLocaleFile(srcFileName, localeDict):
def SNA(text):
@@ -108,7 +138,7 @@ def LoadLocaleFile(srcFileName, localeDict):
dbg.LogBox("%s: line(%d): %s" % (srcFileName, lineIndex, line), "Error")
raise
LoadLocaleFile("{:s}/locale_game.txt".format(APP_GET_LOCALE_PATH), locals())
LoadLocaleFile("{:s}/locale_game.txt".format(APP_GET_LOCALE_PATH), globals())
try:
currentLocalePath = app.GetLocalePath()
@@ -117,182 +147,198 @@ try:
except Exception as e:
dbg.TraceError("localeInfo: Error loading C++ locale data: %s" % str(e))
# Option pvp messages
OPTION_PVPMODE_MESSAGE_DICT = {
0: PVP_MODE_NORMAL,
1: PVP_MODE_REVENGE,
2: PVP_MODE_KILL,
3: PVP_MODE_PROTECT,
4: PVP_MODE_GUILD,
}
def _RebuildDerivedData():
"""Rebuild all derived dicts/tuples/lists from current locale strings."""
global GUILD_BUILDING_LIST_TXT, FN_GM_MARK
global OPTION_PVPMODE_MESSAGE_DICT, WHISPER_ERROR, error
global JOBINFO_TITLE
global GUILDWAR_NORMAL_DESCLIST, GUILDWAR_WARP_DESCLIST, GUILDWAR_CTF_DESCLIST
global MODE_NAME_LIST, TITLE_NAME_LIST, LEVEL_LIST, HEALTH_LIST
global USE_SKILL_ERROR_TAIL_DICT, NOTIFY_MESSAGE, ATTACK_ERROR_TAIL_DICT
global SHOT_ERROR_TAIL_DICT, USE_SKILL_ERROR_CHAT_DICT
global SHOP_ERROR_DICT, STAT_MINUS_DESCRIPTION, MINIMAP_ZONE_NAME_DICT
# Whisper messages
WHISPER_ERROR = {
1: CANNOT_WHISPER_NOT_LOGON,
2: CANNOT_WHISPER_DEST_REFUSE,
3: CANNOT_WHISPER_SELF_REFUSE,
}
GUILD_BUILDING_LIST_TXT = '{:s}/GuildBuildingList.txt'.format(APP_GET_LOCALE_PATH)
FN_GM_MARK = '{:s}/effect/gm.mse'.format(APP_GET_LOCALE_PATH_COMMON)
# Exception of graphic device.
error = dict(
CREATE_WINDOW = GAME_INIT_ERROR_MAIN_WINDOW,
CREATE_CURSOR = GAME_INIT_ERROR_CURSOR,
CREATE_NETWORK = GAME_INIT_ERROR_NETWORK,
CREATE_ITEM_PROTO = GAME_INIT_ERROR_ITEM_PROTO,
CREATE_MOB_PROTO = GAME_INIT_ERROR_MOB_PROTO,
CREATE_NO_DIRECTX = GAME_INIT_ERROR_DIRECTX,
CREATE_DEVICE = GAME_INIT_ERROR_GRAPHICS_NOT_EXIST,
CREATE_NO_APPROPRIATE_DEVICE = GAME_INIT_ERROR_GRAPHICS_BAD_PERFORMANCE,
CREATE_FORMAT = GAME_INIT_ERROR_GRAPHICS_NOT_SUPPORT_32BIT,
NO_ERROR = str()
)
# Option pvp messages
OPTION_PVPMODE_MESSAGE_DICT = {
0: PVP_MODE_NORMAL,
1: PVP_MODE_REVENGE,
2: PVP_MODE_KILL,
3: PVP_MODE_PROTECT,
4: PVP_MODE_GUILD,
}
# Job information (none, skill_group1, skill_group2)
JOBINFO_TITLE = [
[JOB_WARRIOR0, JOB_WARRIOR1, JOB_WARRIOR2,],
[JOB_ASSASSIN0, JOB_ASSASSIN1, JOB_ASSASSIN2,],
[JOB_SURA0, JOB_SURA1, JOB_SURA2,],
[JOB_SHAMAN0, JOB_SHAMAN1, JOB_SHAMAN2,],
]
# Whisper messages
WHISPER_ERROR = {
1: CANNOT_WHISPER_NOT_LOGON,
2: CANNOT_WHISPER_DEST_REFUSE,
3: CANNOT_WHISPER_SELF_REFUSE,
}
#if app.ENABLE_WOLFMAN_CHARACTER:
#JOBINFO_TITLE += [[JOB_WOLFMAN0,JOB_WOLFMAN1,JOB_WOLFMAN2,],]
# Exception of graphic device.
error = dict(
CREATE_WINDOW = GAME_INIT_ERROR_MAIN_WINDOW,
CREATE_CURSOR = GAME_INIT_ERROR_CURSOR,
CREATE_NETWORK = GAME_INIT_ERROR_NETWORK,
CREATE_ITEM_PROTO = GAME_INIT_ERROR_ITEM_PROTO,
CREATE_MOB_PROTO = GAME_INIT_ERROR_MOB_PROTO,
CREATE_NO_DIRECTX = GAME_INIT_ERROR_DIRECTX,
CREATE_DEVICE = GAME_INIT_ERROR_GRAPHICS_NOT_EXIST,
CREATE_NO_APPROPRIATE_DEVICE = GAME_INIT_ERROR_GRAPHICS_BAD_PERFORMANCE,
CREATE_FORMAT = GAME_INIT_ERROR_GRAPHICS_NOT_SUPPORT_32BIT,
NO_ERROR = str()
)
# Guild war description
GUILDWAR_NORMAL_DESCLIST = (GUILD_WAR_USE_NORMAL_MAP, GUILD_WAR_LIMIT_30MIN, GUILD_WAR_WIN_CHECK_SCORE)
# Guild war warp description
GUILDWAR_WARP_DESCLIST = (GUILD_WAR_USE_BATTLE_MAP, GUILD_WAR_WIN_WIPE_OUT_GUILD, GUILD_WAR_REWARD_POTION)
# Guild war flag description
GUILDWAR_CTF_DESCLIST = (GUILD_WAR_USE_BATTLE_MAP, GUILD_WAR_WIN_TAKE_AWAY_FLAG1, GUILD_WAR_WIN_TAKE_AWAY_FLAG2, GUILD_WAR_REWARD_POTION)
# Job information (none, skill_group1, skill_group2)
JOBINFO_TITLE = [
[JOB_WARRIOR0, JOB_WARRIOR1, JOB_WARRIOR2,],
[JOB_ASSASSIN0, JOB_ASSASSIN1, JOB_ASSASSIN2,],
[JOB_SURA0, JOB_SURA1, JOB_SURA2,],
[JOB_SHAMAN0, JOB_SHAMAN1, JOB_SHAMAN2,],
]
# Mode of pvp options
MODE_NAME_LIST = (PVP_OPTION_NORMAL, PVP_OPTION_REVENGE, PVP_OPTION_KILL, PVP_OPTION_PROTECT,)
# Title name of alignment
TITLE_NAME_LIST = (PVP_LEVEL0, PVP_LEVEL1, PVP_LEVEL2, PVP_LEVEL3, PVP_LEVEL4, PVP_LEVEL5, PVP_LEVEL6, PVP_LEVEL7, PVP_LEVEL8,)
#if app.ENABLE_WOLFMAN_CHARACTER:
#JOBINFO_TITLE += [[JOB_WOLFMAN0,JOB_WOLFMAN1,JOB_WOLFMAN2,],]
# Horse levels
LEVEL_LIST = (str(), HORSE_LEVEL1, HORSE_LEVEL2, HORSE_LEVEL3)
# Horse health
HEALTH_LIST = (HORSE_HEALTH0, HORSE_HEALTH1, HORSE_HEALTH2, HORSE_HEALTH3)
# Guild war description
GUILDWAR_NORMAL_DESCLIST = (GUILD_WAR_USE_NORMAL_MAP, GUILD_WAR_LIMIT_30MIN, GUILD_WAR_WIN_CHECK_SCORE)
# Guild war warp description
GUILDWAR_WARP_DESCLIST = (GUILD_WAR_USE_BATTLE_MAP, GUILD_WAR_WIN_WIPE_OUT_GUILD, GUILD_WAR_REWARD_POTION)
# Guild war flag description
GUILDWAR_CTF_DESCLIST = (GUILD_WAR_USE_BATTLE_MAP, GUILD_WAR_WIN_TAKE_AWAY_FLAG1, GUILD_WAR_WIN_TAKE_AWAY_FLAG2, GUILD_WAR_REWARD_POTION)
# Use-skill messages
USE_SKILL_ERROR_TAIL_DICT = {
'IN_SAFE': CANNOT_SKILL_SELF_IN_SAFE,
'NEED_TARGET': CANNOT_SKILL_NEED_TARGET,
'NEED_EMPTY_BOTTLE': CANNOT_SKILL_NEED_EMPTY_BOTTLE,
'NEED_POISON_BOTTLE': CANNOT_SKILL_NEED_POISON_BOTTLE,
'REMOVE_FISHING_ROD': CANNOT_SKILL_REMOVE_FISHING_ROD,
'NOT_YET_LEARN': CANNOT_SKILL_NOT_YET_LEARN,
'NOT_MATCHABLE_WEAPON': CANNOT_SKILL_NOT_MATCHABLE_WEAPON,
'WAIT_COOLTIME': CANNOT_SKILL_WAIT_COOLTIME,
'NOT_ENOUGH_HP': CANNOT_SKILL_NOT_ENOUGH_HP,
'NOT_ENOUGH_SP': CANNOT_SKILL_NOT_ENOUGH_SP,
'CANNOT_USE_SELF': CANNOT_SKILL_USE_SELF,
'ONLY_FOR_ALLIANCE': CANNOT_SKILL_ONLY_FOR_ALLIANCE,
'CANNOT_ATTACK_ENEMY_IN_SAFE_AREA': CANNOT_SKILL_DEST_IN_SAFE,
'CANNOT_APPROACH': CANNOT_SKILL_APPROACH,
'CANNOT_ATTACK': CANNOT_SKILL_ATTACK,
'ONLY_FOR_CORPSE': CANNOT_SKILL_ONLY_FOR_CORPSE,
'EQUIP_FISHING_ROD': CANNOT_SKILL_EQUIP_FISHING_ROD,
'NOT_HORSE_SKILL': CANNOT_SKILL_NOT_HORSE_SKILL,
'HAVE_TO_RIDE': CANNOT_SKILL_HAVE_TO_RIDE,
}
# Mode of pvp options
MODE_NAME_LIST = (PVP_OPTION_NORMAL, PVP_OPTION_REVENGE, PVP_OPTION_KILL, PVP_OPTION_PROTECT,)
# Title name of alignment
TITLE_NAME_LIST = (PVP_LEVEL0, PVP_LEVEL1, PVP_LEVEL2, PVP_LEVEL3, PVP_LEVEL4, PVP_LEVEL5, PVP_LEVEL6, PVP_LEVEL7, PVP_LEVEL8,)
# Notify messages
NOTIFY_MESSAGE = {
'CANNOT_EQUIP_SHOP': CANNOT_EQUIP_IN_SHOP,
'CANNOT_EQUIP_EXCHANGE': CANNOT_EQUIP_IN_EXCHANGE,
}
# Horse levels
LEVEL_LIST = (str(), HORSE_LEVEL1, HORSE_LEVEL2, HORSE_LEVEL3)
# Horse health
HEALTH_LIST = (HORSE_HEALTH0, HORSE_HEALTH1, HORSE_HEALTH2, HORSE_HEALTH3)
# Attack messages
ATTACK_ERROR_TAIL_DICT = {
'IN_SAFE': CANNOT_ATTACK_SELF_IN_SAFE,
'DEST_IN_SAFE': CANNOT_ATTACK_DEST_IN_SAFE,
}
# Use-skill messages
USE_SKILL_ERROR_TAIL_DICT = {
'IN_SAFE': CANNOT_SKILL_SELF_IN_SAFE,
'NEED_TARGET': CANNOT_SKILL_NEED_TARGET,
'NEED_EMPTY_BOTTLE': CANNOT_SKILL_NEED_EMPTY_BOTTLE,
'NEED_POISON_BOTTLE': CANNOT_SKILL_NEED_POISON_BOTTLE,
'REMOVE_FISHING_ROD': CANNOT_SKILL_REMOVE_FISHING_ROD,
'NOT_YET_LEARN': CANNOT_SKILL_NOT_YET_LEARN,
'NOT_MATCHABLE_WEAPON': CANNOT_SKILL_NOT_MATCHABLE_WEAPON,
'WAIT_COOLTIME': CANNOT_SKILL_WAIT_COOLTIME,
'NOT_ENOUGH_HP': CANNOT_SKILL_NOT_ENOUGH_HP,
'NOT_ENOUGH_SP': CANNOT_SKILL_NOT_ENOUGH_SP,
'CANNOT_USE_SELF': CANNOT_SKILL_USE_SELF,
'ONLY_FOR_ALLIANCE': CANNOT_SKILL_ONLY_FOR_ALLIANCE,
'CANNOT_ATTACK_ENEMY_IN_SAFE_AREA': CANNOT_SKILL_DEST_IN_SAFE,
'CANNOT_APPROACH': CANNOT_SKILL_APPROACH,
'CANNOT_ATTACK': CANNOT_SKILL_ATTACK,
'ONLY_FOR_CORPSE': CANNOT_SKILL_ONLY_FOR_CORPSE,
'EQUIP_FISHING_ROD': CANNOT_SKILL_EQUIP_FISHING_ROD,
'NOT_HORSE_SKILL': CANNOT_SKILL_NOT_HORSE_SKILL,
'HAVE_TO_RIDE': CANNOT_SKILL_HAVE_TO_RIDE,
}
# Shot messages
SHOT_ERROR_TAIL_DICT = {
'EMPTY_ARROW': CANNOT_SHOOT_EMPTY_ARROW,
'IN_SAFE': CANNOT_SHOOT_SELF_IN_SAFE,
'DEST_IN_SAFE': CANNOT_SHOOT_DEST_IN_SAFE,
}
# Notify messages
NOTIFY_MESSAGE = {
'CANNOT_EQUIP_SHOP': CANNOT_EQUIP_IN_SHOP,
'CANNOT_EQUIP_EXCHANGE': CANNOT_EQUIP_IN_EXCHANGE,
}
# Skill messages
USE_SKILL_ERROR_CHAT_DICT = {
'NEED_EMPTY_BOTTLE': SKILL_NEED_EMPTY_BOTTLE,
'NEED_POISON_BOTTLE': SKILL_NEED_POISON_BOTTLE,
'ONLY_FOR_GUILD_WAR': SKILL_ONLY_FOR_GUILD_WAR,
}
# Attack messages
ATTACK_ERROR_TAIL_DICT = {
'IN_SAFE': CANNOT_ATTACK_SELF_IN_SAFE,
'DEST_IN_SAFE': CANNOT_ATTACK_DEST_IN_SAFE,
}
# Shop/private-shop messages
SHOP_ERROR_DICT = {
'NOT_ENOUGH_MONEY': SHOP_NOT_ENOUGH_MONEY,
'SOLDOUT': SHOP_SOLDOUT,
'INVENTORY_FULL': SHOP_INVENTORY_FULL,
'INVALID_POS': SHOP_INVALID_POS,
'NOT_ENOUGH_MONEY_EX': SHOP_NOT_ENOUGH_MONEY_EX,
}
# Shot messages
SHOT_ERROR_TAIL_DICT = {
'EMPTY_ARROW': CANNOT_SHOOT_EMPTY_ARROW,
'IN_SAFE': CANNOT_SHOOT_SELF_IN_SAFE,
'DEST_IN_SAFE': CANNOT_SHOOT_DEST_IN_SAFE,
}
# Character status description
STAT_MINUS_DESCRIPTION = {
'HTH-': STAT_MINUS_CON,
'INT-': STAT_MINUS_INT,
'STR-': STAT_MINUS_STR,
'DEX-': STAT_MINUS_DEX,
}
# Skill messages
USE_SKILL_ERROR_CHAT_DICT = {
'NEED_EMPTY_BOTTLE': SKILL_NEED_EMPTY_BOTTLE,
'NEED_POISON_BOTTLE': SKILL_NEED_POISON_BOTTLE,
'ONLY_FOR_GUILD_WAR': SKILL_ONLY_FOR_GUILD_WAR,
}
# MR-11: Complete map name list
# Map names
MINIMAP_ZONE_NAME_DICT = {
'metin2_map_a1': MAP_A1,
'map_a2': MAP_A2,
'metin2_map_a3': MAP_A3,
'metin2_map_b1': MAP_B1,
'map_b2': MAP_B2,
'metin2_map_b3': MAP_B3,
'metin2_map_c1': MAP_C1,
'map_c2': MAP_C2,
'metin2_map_c3': MAP_C3,
'map_n_snowm_01': MAP_SNOW,
'metin2_map_n_flame_01': MAP_FLAME,
'metin2_map_n_desert_01': MAP_DESERT,
'metin2_map_milgyo': MAP_TEMPLE,
'metin2_map_monkeydungeon': MAP_MONKEY_DUNGEON,
'metin2_map_monkeydungeon_02': MAP_MONKEY_DUNGEON2,
'metin2_map_monkeydungeon_03': MAP_MONKEY_DUNGEON3,
'metin2_map_spiderdungeon': MAP_SPIDER,
'metin2_map_spiderdungeon_02': MAP_SPIDERDUNGEON_02,
'metin2_map_spiderdungeon_03': MAP_SPIDERDUNGEON_03,
'metin2_map_deviltower1': MAP_DEVILTOWER1,
'metin2_map_devilsCatacomb': MAP_DEVILCATACOMB,
'metin2_map_guild_01': MAP_GUILD_01,
'metin2_map_guild_02': MAP_GUILD_02,
'metin2_map_guild_03': MAP_GUILD_03,
'metin2_guild_village_01': GUILD_VILLAGE_01,
'metin2_guild_village_02': GUILD_VILLAGE_02,
'metin2_guild_village_03': GUILD_VILLAGE_03,
'metin2_map_trent': MAP_TREE,
'metin2_map_trent02': MAP_TREE2,
'season1/metin2_map_WL_01': MAP_WL,
'season1/metin2_map_nusluck01': MAP_NUSLUCK,
'season1/metin2_map_oxevent': MAP_OXEVENT,
'metin2_map_wedding_01': MAP_WEDDING_01,
'metin2_map_bf': MAP_BATTLE_FIELD,
'metin2_map_bf_02': MAP_BATTLE_FIELD,
'metin2_map_bf_03': MAP_BATTLE_FIELD,
'Metin2_map_CapeDragonHead': MAP_CAPE,
'metin2_map_Mt_Thunder': MAP_THUNDER,
'metin2_map_dawnmistwood': MAP_DAWN,
'metin2_map_BayBlackSand': MAP_BAY,
'metin2_map_n_flame_dungeon_01': MAP_N_FLAME_DUNGEON_01,
'metin2_map_n_snow_dungeon_01': MAP_N_SNOW_DUNGEON_01,
'metin2_map_duel': MAP_DUEL,
'season2/metin2_map_skipia_dungeon_01': MAP_SKIPIA_DUNGEON_01,
'metin2_map_skipia_dungeon_02': MAP_SKIPIA_DUNGEON_02,
'metin2_map_skipia_dungeon_boss': MAP_SKIPIA_DUNGEON_BOSS,
'metin2_map_skipia_dungeon_boss2': MAP_SKIPIA_DUNGEON_BOSS_2,
}
# MR-11: -- END OF -- Complete map name list
# Shop/private-shop messages
SHOP_ERROR_DICT = {
'NOT_ENOUGH_MONEY': SHOP_NOT_ENOUGH_MONEY,
'SOLDOUT': SHOP_SOLDOUT,
'INVENTORY_FULL': SHOP_INVENTORY_FULL,
'INVALID_POS': SHOP_INVALID_POS,
'NOT_ENOUGH_MONEY_EX': SHOP_NOT_ENOUGH_MONEY_EX,
}
# Character status description
STAT_MINUS_DESCRIPTION = {
'HTH-': STAT_MINUS_CON,
'INT-': STAT_MINUS_INT,
'STR-': STAT_MINUS_STR,
'DEX-': STAT_MINUS_DEX,
}
# MR-11: Complete map name list
# Map names
MINIMAP_ZONE_NAME_DICT = {
'metin2_map_a1': MAP_A1,
'map_a2': MAP_A2,
'metin2_map_a3': MAP_A3,
'metin2_map_b1': MAP_B1,
'map_b2': MAP_B2,
'metin2_map_b3': MAP_B3,
'metin2_map_c1': MAP_C1,
'map_c2': MAP_C2,
'metin2_map_c3': MAP_C3,
'map_n_snowm_01': MAP_SNOW,
'metin2_map_n_flame_01': MAP_FLAME,
'metin2_map_n_desert_01': MAP_DESERT,
'metin2_map_milgyo': MAP_TEMPLE,
'metin2_map_monkeydungeon': MAP_MONKEY_DUNGEON,
'metin2_map_monkeydungeon_02': MAP_MONKEY_DUNGEON2,
'metin2_map_monkeydungeon_03': MAP_MONKEY_DUNGEON3,
'metin2_map_spiderdungeon': MAP_SPIDER,
'metin2_map_spiderdungeon_02': MAP_SPIDERDUNGEON_02,
'metin2_map_spiderdungeon_03': MAP_SPIDERDUNGEON_03,
'metin2_map_deviltower1': MAP_DEVILTOWER1,
'metin2_map_devilsCatacomb': MAP_DEVILCATACOMB,
'metin2_map_guild_01': MAP_GUILD_01,
'metin2_map_guild_02': MAP_GUILD_02,
'metin2_map_guild_03': MAP_GUILD_03,
'metin2_guild_village_01': GUILD_VILLAGE_01,
'metin2_guild_village_02': GUILD_VILLAGE_02,
'metin2_guild_village_03': GUILD_VILLAGE_03,
'metin2_map_trent': MAP_TREE,
'metin2_map_trent02': MAP_TREE2,
'season1/metin2_map_WL_01': MAP_WL,
'season1/metin2_map_nusluck01': MAP_NUSLUCK,
'season1/metin2_map_oxevent': MAP_OXEVENT,
'metin2_map_wedding_01': MAP_WEDDING_01,
'metin2_map_bf': MAP_BATTLE_FIELD,
'metin2_map_bf_02': MAP_BATTLE_FIELD,
'metin2_map_bf_03': MAP_BATTLE_FIELD,
'Metin2_map_CapeDragonHead': MAP_CAPE,
'metin2_map_Mt_Thunder': MAP_THUNDER,
'metin2_map_dawnmistwood': MAP_DAWN,
'metin2_map_BayBlackSand': MAP_BAY,
'metin2_map_n_flame_dungeon_01': MAP_N_FLAME_DUNGEON_01,
'metin2_map_n_snow_dungeon_01': MAP_N_SNOW_DUNGEON_01,
'metin2_map_duel': MAP_DUEL,
'season2/metin2_map_skipia_dungeon_01': MAP_SKIPIA_DUNGEON_01,
'metin2_map_skipia_dungeon_02': MAP_SKIPIA_DUNGEON_02,
'metin2_map_skipia_dungeon_boss': MAP_SKIPIA_DUNGEON_BOSS,
'metin2_map_skipia_dungeon_boss2': MAP_SKIPIA_DUNGEON_BOSS_2,
}
# MR-11: -- END OF -- Complete map name list
_RebuildDerivedData()
# Path of quest icon file
def GetLetterImageName():

View File

@@ -39,7 +39,9 @@ class PopupDialog(ui.ScriptWindow):
PythonScriptLoader = ui.PythonScriptLoader()
PythonScriptLoader.LoadScriptFile(self, "UIScript/PopupDialog.py")
def Open(self, Message, event = 0, ButtonName = localeInfo.UI_CANCEL):
def Open(self, Message, event = 0, ButtonName = None):
if ButtonName is None:
ButtonName = localeInfo.UI_CANCEL
if True == self.IsShow():
self.Close()

View File

@@ -42,22 +42,25 @@ class CharacterWindow(ui.ScriptWindow):
PAGE_HORSE = 3
SKILL_GROUP_NAME_DICT = {
playerSettingModule.JOB_WARRIOR : { 1 : localeInfo.SKILL_GROUP_WARRIOR_1, 2 : localeInfo.SKILL_GROUP_WARRIOR_2, },
playerSettingModule.JOB_ASSASSIN : { 1 : localeInfo.SKILL_GROUP_ASSASSIN_1, 2 : localeInfo.SKILL_GROUP_ASSASSIN_2, },
playerSettingModule.JOB_SURA : { 1 : localeInfo.SKILL_GROUP_SURA_1, 2 : localeInfo.SKILL_GROUP_SURA_2, },
playerSettingModule.JOB_SHAMAN : { 1 : localeInfo.SKILL_GROUP_SHAMAN_1, 2 : localeInfo.SKILL_GROUP_SHAMAN_2, },
}
SKILL_GROUP_NAME_DICT = {}
STAT_DESCRIPTION = {}
STAT_MINUS_DESCRIPTION = ""
STAT_DESCRIPTION = {
"HTH" : localeInfo.STAT_TOOLTIP_CON,
"INT" : localeInfo.STAT_TOOLTIP_INT,
"STR" : localeInfo.STAT_TOOLTIP_STR,
"DEX" : localeInfo.STAT_TOOLTIP_DEX,
}
STAT_MINUS_DESCRIPTION = localeInfo.STAT_MINUS_DESCRIPTION
@staticmethod
def _RebuildLocaleStrings():
CharacterWindow.SKILL_GROUP_NAME_DICT = {
playerSettingModule.JOB_WARRIOR : { 1 : localeInfo.SKILL_GROUP_WARRIOR_1, 2 : localeInfo.SKILL_GROUP_WARRIOR_2, },
playerSettingModule.JOB_ASSASSIN : { 1 : localeInfo.SKILL_GROUP_ASSASSIN_1, 2 : localeInfo.SKILL_GROUP_ASSASSIN_2, },
playerSettingModule.JOB_SURA : { 1 : localeInfo.SKILL_GROUP_SURA_1, 2 : localeInfo.SKILL_GROUP_SURA_2, },
playerSettingModule.JOB_SHAMAN : { 1 : localeInfo.SKILL_GROUP_SHAMAN_1, 2 : localeInfo.SKILL_GROUP_SHAMAN_2, },
}
CharacterWindow.STAT_DESCRIPTION = {
"HTH" : localeInfo.STAT_TOOLTIP_CON,
"INT" : localeInfo.STAT_TOOLTIP_INT,
"STR" : localeInfo.STAT_TOOLTIP_STR,
"DEX" : localeInfo.STAT_TOOLTIP_DEX,
}
CharacterWindow.STAT_MINUS_DESCRIPTION = localeInfo.STAT_MINUS_DESCRIPTION
def __init__(self):
ui.ScriptWindow.__init__(self)
@@ -1341,3 +1344,6 @@ class CharacterWindow(ui.ScriptWindow):
if startIndex != self.questShowingStartIndex:
self.questShowingStartIndex = startIndex
self.RefreshQuest()
CharacterWindow._RebuildLocaleStrings()
localeInfo.RegisterReloadCallback(CharacterWindow._RebuildLocaleStrings)

View File

@@ -102,10 +102,16 @@ class ChatModeButton(ui.Window):
## ChatLine
class ChatLine(ui.EditLine):
CHAT_MODE_NAME = { chat.CHAT_TYPE_TALKING : localeInfo.CHAT_NORMAL,
chat.CHAT_TYPE_PARTY : localeInfo.CHAT_PARTY,
chat.CHAT_TYPE_GUILD : localeInfo.CHAT_GUILD,
chat.CHAT_TYPE_SHOUT : localeInfo.CHAT_SHOUT, }
CHAT_MODE_NAME = {}
@staticmethod
def _RebuildLocaleStrings():
ChatLine.CHAT_MODE_NAME = {
chat.CHAT_TYPE_TALKING : localeInfo.CHAT_NORMAL,
chat.CHAT_TYPE_PARTY : localeInfo.CHAT_PARTY,
chat.CHAT_TYPE_GUILD : localeInfo.CHAT_GUILD,
chat.CHAT_TYPE_SHOUT : localeInfo.CHAT_SHOUT,
}
def __init__(self):
ui.EditLine.__init__(self)
@@ -1163,3 +1169,5 @@ class ChatLogWindow(ui.Window):
else:
self.interface.MakeHyperlinkTooltip(hyperlink)
ChatLine._RebuildLocaleStrings()
localeInfo.RegisterReloadCallback(ChatLine._RebuildLocaleStrings)

View File

@@ -683,10 +683,16 @@ class CommentSlot(ui.Window):
class GuildWindow(ui.ScriptWindow):
JOB_NAME = { 0 : localeInfo.JOB_WARRIOR,
JOB_NAME = {}
@staticmethod
def _RebuildLocaleStrings():
GuildWindow.JOB_NAME = {
0 : localeInfo.JOB_WARRIOR,
1 : localeInfo.JOB_ASSASSIN,
2 : localeInfo.JOB_SURA,
3 : localeInfo.JOB_SHAMAN, }
3 : localeInfo.JOB_SHAMAN,
}
GUILD_SKILL_PASSIVE_SLOT = 0
GUILD_SKILL_ACTIVE_SLOT = 1
@@ -2876,3 +2882,5 @@ Destroy building:
net.SendChatPacket("/build d vid")
"""
GuildWindow._RebuildLocaleStrings()
localeInfo.RegisterReloadCallback(GuildWindow._RebuildLocaleStrings)

View File

@@ -1,242 +1,64 @@
"""
Generic UI Locale Refresh System
This module provides automatic locale refresh for UI windows without hardcoding element names.
Provides RefreshByMapping and RebuildDictionary for hot-reload support.
"""
import dbg
import ui
class LocaleRefreshHelper:
"""
Helper class to automatically refresh UI text elements when locale changes.
Works by re-reading the original UI script and applying new locale strings.
"""
def __init__(self):
self.scriptCache = {} # Cache loaded UI scripts
def RefreshWindow(self, window, scriptPath):
"""
Automatically refresh all text elements in a window by re-reading the UI script.
Args:
window: The ui.ScriptWindow instance to refresh
scriptPath: Path to the UI script file (e.g., "UIScript/LoginWindow.py")
Returns:
Number of elements successfully refreshed
"""
import uiScriptLocale
import localeInfo
dbg.TraceError("LocaleRefreshHelper: Refreshing window from %s" % scriptPath)
# Load the UI script to get the original text definitions
try:
scriptData = self._LoadUIScript(scriptPath)
except Exception as e:
dbg.TraceError("LocaleRefreshHelper: Failed to load script %s: %s" % (scriptPath, str(e)))
return 0
# Recursively refresh all elements
refreshCount = self._RefreshElement(window, scriptData.get("window", {}), window)
dbg.TraceError("LocaleRefreshHelper: Refreshed %d elements" % refreshCount)
return refreshCount
def RefreshElementsByMapping(self, elementMap):
"""
Refresh UI elements using a manual mapping dictionary.
Useful for elements that can't be auto-detected.
Args:
elementMap: Dict of {element_instance: locale_string_name}
Example:
mapping = {
self.loginButton: "LOGIN_CONNECT",
self.exitButton: "LOGIN_EXIT"
}
helper.RefreshElementsByMapping(mapping)
"""
import uiScriptLocale
import localeInfo
refreshCount = 0
for element, localeKey in list(elementMap.items()):
try:
# Try uiScriptLocale first, then localeInfo
if hasattr(uiScriptLocale, localeKey):
text = getattr(uiScriptLocale, localeKey)
elif hasattr(localeInfo, localeKey):
text = getattr(localeInfo, localeKey)
else:
dbg.TraceError("LocaleRefreshHelper: Locale key not found: %s" % localeKey)
continue
# Set the text
if hasattr(element, 'SetText'):
element.SetText(text)
refreshCount += 1
except Exception as e:
dbg.TraceError("LocaleRefreshHelper: Failed to refresh element with key %s: %s" % (localeKey, str(e)))
return refreshCount
def RefreshDictionaries(self, targetDict, localeModule="localeInfo"):
"""
Rebuild a dictionary with fresh locale strings.
Useful for error message dictionaries, etc.
Args:
targetDict: Dictionary to rebuild with format {key: "LOCALE_CONSTANT_NAME"}
localeModule: Name of the locale module ("localeInfo" or "uiScriptLocale")
Returns:
New dictionary with fresh locale values
Example:
template = {
"WRONGPWD": "LOGIN_FAILURE_WRONG_PASSWORD",
"FULL": "LOGIN_FAILURE_TOO_MANY_USER"
}
newDict = helper.RefreshDictionaries(template)
"""
import localeInfo
import uiScriptLocale
module = localeInfo if localeModule == "localeInfo" else uiScriptLocale
newDict = {}
for key, localeKey in list(targetDict.items()):
if hasattr(module, localeKey):
newDict[key] = getattr(module, localeKey)
else:
dbg.TraceError("LocaleRefreshHelper: Locale key not found: %s" % localeKey)
return newDict
def _LoadUIScript(self, scriptPath):
"""Load and cache a UI script file."""
if scriptPath in self.scriptCache:
return self.scriptCache[scriptPath]
# Execute the UI script to get its data
scriptData = {}
try:
exec(compile(open(scriptPath, "rb").read(), scriptPath, 'exec'), scriptData)
self.scriptCache[scriptPath] = scriptData
except Exception as e:
dbg.TraceError("LocaleRefreshHelper: Failed to execute script %s: %s" % (scriptPath, str(e)))
raise
return scriptData
def _RefreshElement(self, windowInstance, elementDef, currentElement):
"""
Recursively refresh an element and its children.
Args:
windowInstance: The root window instance
elementDef: Element definition from UI script
currentElement: Current UI element instance
Returns:
Number of elements refreshed
"""
import uiScriptLocale
import localeInfo
refreshCount = 0
# If this element has text defined in the script, refresh it
if isinstance(elementDef, dict) and "text" in elementDef:
textDef = elementDef["text"]
# Check if it's a locale reference (starts with uiScriptLocale or localeInfo)
if isinstance(textDef, str):
text = self._ResolveLocaleString(textDef)
if text and hasattr(currentElement, 'SetText'):
try:
currentElement.SetText(text)
refreshCount += 1
except:
pass
# Recursively process children
if isinstance(elementDef, dict) and "children" in elementDef:
children = elementDef.get("children", [])
for childDef in children:
if isinstance(childDef, dict) and "name" in childDef:
childName = childDef["name"]
try:
childElement = windowInstance.GetChild(childName)
refreshCount += self._RefreshElement(windowInstance, childDef, childElement)
except:
pass
return refreshCount
def _ResolveLocaleString(self, textDef):
"""
Resolve a locale string reference to its current value.
Args:
textDef: String like "uiScriptLocale.LOGIN_CONNECT" or direct text
Returns:
The resolved locale string or None
"""
import uiScriptLocale
import localeInfo
# Check if it's a locale reference
if "uiScriptLocale." in str(textDef):
# Extract the attribute name
parts = str(textDef).split(".")
if len(parts) >= 2:
attrName = parts[-1]
if hasattr(uiScriptLocale, attrName):
return getattr(uiScriptLocale, attrName)
elif "localeInfo." in str(textDef):
parts = str(textDef).split(".")
if len(parts) >= 2:
attrName = parts[-1]
if hasattr(localeInfo, attrName):
return getattr(localeInfo, attrName)
return None
# Global helper instance for easy access
_globalHelper = LocaleRefreshHelper()
def RefreshWindowByScript(window, scriptPath):
"""
Convenience function to refresh a window using its UI script.
Args:
window: The ui.ScriptWindow instance
scriptPath: Path to UI script (e.g., "UIScript/LoginWindow.py")
"""
return _globalHelper.RefreshWindow(window, scriptPath)
def RefreshByMapping(elementMap):
"""
Convenience function to refresh elements by mapping.
Refresh UI elements using a mapping dictionary.
Args:
elementMap: Dict of {element: "LOCALE_KEY"}
elementMap: Dict of {element_instance: "LOCALE_KEY_NAME"}
Returns:
Number of elements successfully refreshed
"""
return _globalHelper.RefreshElementsByMapping(elementMap)
import uiScriptLocale
import localeInfo
refreshCount = 0
for element, localeKey in list(elementMap.items()):
try:
# Try uiScriptLocale first, then localeInfo
if hasattr(uiScriptLocale, localeKey):
text = getattr(uiScriptLocale, localeKey)
elif hasattr(localeInfo, localeKey):
text = getattr(localeInfo, localeKey)
else:
dbg.TraceError("LocaleRefresh: key not found: %s" % localeKey)
continue
if hasattr(element, 'SetText'):
element.SetText(text)
refreshCount += 1
except Exception as e:
dbg.TraceError("LocaleRefresh: failed for key %s: %s" % (localeKey, str(e)))
return refreshCount
def RebuildDictionary(template, localeModule="localeInfo"):
"""
Convenience function to rebuild a dictionary with fresh locale strings.
Rebuild a dictionary with fresh locale strings.
Args:
template: Dict of {key: "LOCALE_KEY"}
template: Dict of {key: "LOCALE_CONSTANT_NAME"}
localeModule: "localeInfo" or "uiScriptLocale"
Returns:
New dictionary with resolved locale values
"""
return _globalHelper.RefreshDictionaries(template, localeModule)
import localeInfo
import uiScriptLocale
module = localeInfo if localeModule == "localeInfo" else uiScriptLocale
newDict = {}
for key, localeKey in list(template.items()):
if hasattr(module, localeKey):
newDict[key] = getattr(module, localeKey)
else:
dbg.TraceError("LocaleRefresh: key not found: %s" % localeKey)
return newDict

View File

@@ -83,8 +83,8 @@ class LocaleSelector(ui.Window):
self.confirmDialog = None
for btn in self.flagButtons:
btn.SetEvent(None)
btn.Hide()
btn = None
self.flagButtons = []
if self.background:

View File

@@ -5,9 +5,59 @@ import localeInfo
LOCALE_PATH = uiScriptLocale.MAPNAME_PATH
class MapNameShower(ui.ExpandedImageBox):
# Static map name -> image suffix mapping (locale-independent)
_MAP_NAME_SUFFIXES = {
"metin2_map_a1": "a1.tga",
"map_a2": "a2.tga",
"metin2_map_a3": "a3.tga",
"metin2_map_b1": "b1.tga",
"map_b2": "b2.tga",
"metin2_map_b3": "b3.tga",
"metin2_map_c1": "c1.tga",
"map_c2": "c2.tga",
"metin2_map_c3": "c3.tga",
"map_n_snowm_01": "snow1.tga",
"metin2_map_deviltower1": "devil1_title.tga",
"metin2_map_n_flame_01": "frame1.tga",
"metin2_map_n_desert_01": "desert1.tga",
"metin2_map_milgyo": "milgyo.tga",
"metin2_map_monkeydungeon": "monkey1.tga",
"metin2_map_monkeydungeon_02": "monkey2.tga",
"metin2_map_monkeydungeon_03": "monkey3.tga",
"metin2_map_guild_01": "guild1.tga",
"metin2_map_guild_02": "guild2.tga",
"metin2_map_guild_03": "guild3.tga",
"metin2_map_trent": "trent.tga",
"metin2_map_trent02": "trent02.tga",
"season2/map_n_snowm_02": "snow2.tga",
"season2/metin2_map_a2_1": "a2_2.tga",
"season2/metin2_map_n_desert_02": "desert2.tga",
"season2/metin2_map_n_flame_02": "frame2.tga",
"season2/metin2_map_milgyo_a": "milgyo2.TGA",
"season2/metin2_map_trent_a": "trent_a.tga",
"season2/metin2_map_trent02_a": "trent02_a.tga",
"season2/metin2_map_skipia_dungeon_01": "skipia.tga",
"season2/metin2_map_skipia_dungeon_02": "skipia.tga",
"metin2_map_devilsCatacomb": "devil_basement.tga",
"metin2_guild_village_01": "a4.tga",
"metin2_guild_village_02": "b4.tga",
"metin2_guild_village_03": "c4.tga",
"metin2_map_BayBlackSand": "bay.tga",
"metin2_map_Mt_Thunder": "thunder.tga",
"metin2_map_dawnmistwood": "dawn.tga",
"Metin2_map_CapeDragonHead": "cape.tga",
"metin2_map_spiderdungeon": "spider1.tga",
"metin2_map_spiderdungeon_02": "spider1.tga",
"metin2_map_spiderdungeon_03": "spider1.tga",
}
MAP_NAME_IMAGE = {}
def _RebuildLocaleStrings():
global LOCALE_PATH
LOCALE_PATH = uiScriptLocale.MAPNAME_PATH
localeInfo.RegisterReloadCallback(_RebuildLocaleStrings)
class MapNameShower(ui.ExpandedImageBox):
STATE_HIDE = 0
STATE_FADE_IN = 1
@@ -15,51 +65,6 @@ class MapNameShower(ui.ExpandedImageBox):
STATE_FADE_OUT = 3
def __init__(self):
self.MAP_NAME_IMAGE = {
"metin2_map_a1" : LOCALE_PATH+"a1.tga",
"map_a2" : LOCALE_PATH+"a2.tga",
"metin2_map_a3" : LOCALE_PATH+"a3.tga",
"metin2_map_b1" : LOCALE_PATH+"b1.tga",
"map_b2" : LOCALE_PATH+"b2.tga",
"metin2_map_b3" : LOCALE_PATH+"b3.tga",
"metin2_map_c1" : LOCALE_PATH+"c1.tga",
"map_c2" : LOCALE_PATH+"c2.tga",
"metin2_map_c3" : LOCALE_PATH+"c3.tga",
"map_n_snowm_01" : LOCALE_PATH+"snow1.tga",
"metin2_map_deviltower1" : LOCALE_PATH+"devil1_title.tga",
"metin2_map_n_flame_01" : LOCALE_PATH+"frame1.tga",
"metin2_map_n_desert_01" : LOCALE_PATH+"desert1.tga",
"metin2_map_milgyo" : LOCALE_PATH+"milgyo.tga",
"metin2_map_monkeydungeon" : LOCALE_PATH+"monkey1.tga",
"metin2_map_monkeydungeon_02" : LOCALE_PATH+"monkey2.tga",
"metin2_map_monkeydungeon_03" : LOCALE_PATH+"monkey3.tga",
"metin2_map_guild_01" : LOCALE_PATH+"guild1.tga",
"metin2_map_guild_02" : LOCALE_PATH+"guild2.tga",
"metin2_map_guild_03" : LOCALE_PATH+"guild3.tga",
"metin2_map_trent" : LOCALE_PATH+"trent.tga",
"metin2_map_trent02" : LOCALE_PATH+"trent02.tga",
"season2/map_n_snowm_02": LOCALE_PATH+"snow2.tga",
"season2/metin2_map_a2_1": LOCALE_PATH+"a2_2.tga",
"season2/metin2_map_n_desert_02": LOCALE_PATH+"desert2.tga",
"season2/metin2_map_n_flame_02": LOCALE_PATH+"frame2.tga",
"season2/metin2_map_milgyo_a": LOCALE_PATH+"milgyo2.TGA",
"season2/metin2_map_trent_a": LOCALE_PATH+"trent_a.tga",
"season2/metin2_map_trent02_a": LOCALE_PATH+"trent02_a.tga",
"season2/metin2_map_skipia_dungeon_01": LOCALE_PATH+"skipia.tga",
"season2/metin2_map_skipia_dungeon_02": LOCALE_PATH+"skipia.tga",
"metin2_map_devilsCatacomb" : LOCALE_PATH+"devil_basement.tga",
"metin2_guild_village_01" : LOCALE_PATH+"a4.tga",
"metin2_guild_village_02" : LOCALE_PATH+"b4.tga",
"metin2_guild_village_03" : LOCALE_PATH+"c4.tga",
"metin2_map_BayBlackSand" : LOCALE_PATH+"bay.tga",
"metin2_map_Mt_Thunder" : LOCALE_PATH+"thunder.tga",
"metin2_map_dawnmistwood" : LOCALE_PATH+"dawn.tga",
"Metin2_map_CapeDragonHead" : LOCALE_PATH+"cape.tga",
"metin2_map_spiderdungeon" : LOCALE_PATH+"spider1.tga",
"metin2_map_spiderdungeon_02" : LOCALE_PATH+"spider1.tga",
"metin2_map_spiderdungeon_03" : LOCALE_PATH+"spider1.tga",
}
ui.ExpandedImageBox.__init__(self, "TOP_MOST")
self.AddFlag("not_pick")
self.__Initialize()
@@ -96,8 +101,9 @@ class MapNameShower(ui.ExpandedImageBox):
elif x > 56000 and y > 38000 and x < 68000 and y < 49000:
return 8
elif x > 56000 and y > 13000 and x < 68000 and y < 23000:
return 9
return 9
return 0
def __GetDevilBase(self, x, y):
if x > 3000 and y > 4500 and x < 45000 and y < 45000:
return 1
@@ -113,14 +119,15 @@ class MapNameShower(ui.ExpandedImageBox):
return 6
elif x > 5000 and y > 104900 and x < 15000 and y < 122000:
return 7
return 0
return 0
def ShowMapName(self, mapName, x, y):
if mapName not in self.MAP_NAME_IMAGE:
if mapName not in _MAP_NAME_SUFFIXES:
print((" [ERROR] - There is no map name image", mapName))
return
try:
self.LoadImage(self.MAP_NAME_IMAGE[mapName])
self.LoadImage(LOCALE_PATH + _MAP_NAME_SUFFIXES[mapName])
except RuntimeError:
return
@@ -197,8 +204,8 @@ class MapNameShower(ui.ExpandedImageBox):
if self.floorImage:
self.floorImage.Hide()
self.floorImage = None
if self.objectiveImage:
self.objectiveImage.Hide()
self.objectiveImage = None
self.objectiveImage = None
return

View File

@@ -28,15 +28,7 @@ class PartyMemberInfoBoard(ui.ScriptWindow):
#PARTY_AFFECT_REGEN_BONUS = 6
PARTY_AFFECT_INCREASE_AREA_150 = 7
PARTY_AFFECT_INCREASE_AREA_200 = 8
AFFECT_STRING_DICT = { PARTY_AFFECT_EXPERIENCE : localeInfo.PARTY_BONUS_EXP,
PARTY_AFFECT_ATTACKER : localeInfo.PARTY_BONUS_ATTACKER,
PARTY_AFFECT_TANKER : localeInfo.PARTY_BONUS_TANKER,
PARTY_AFFECT_BUFFER : localeInfo.PARTY_BONUS_BUFFER,
PARTY_AFFECT_SKILL_MASTER : localeInfo.PARTY_BONUS_SKILL_MASTER,
PARTY_AFFECT_BERSERKER : localeInfo.PARTY_BONUS_BERSERKER,
PARTY_AFFECT_DEFENDER : localeInfo.PARTY_BONUS_DEFENDER,
PARTY_AFFECT_INCREASE_AREA_150 : localeInfo.PARTY_INCREASE_AREA_150,
PARTY_AFFECT_INCREASE_AREA_200 : localeInfo.PARTY_INCREASE_AREA_200, }
AFFECT_STRING_DICT = {}
PARTY_SKILL_HEAL = 1
PARTY_SKILL_WARP = 2
@@ -55,12 +47,29 @@ class PartyMemberInfoBoard(ui.ScriptWindow):
MEMBER_BUTTON_WARP : "party_skill_warp",
MEMBER_BUTTON_EXPEL : "party_expel", }
STATE_NAME_DICT = { player.PARTY_STATE_ATTACKER : localeInfo.PARTY_SET_ATTACKER,
player.PARTY_STATE_BERSERKER : localeInfo.PARTY_SET_BERSERKER,
player.PARTY_STATE_TANKER : localeInfo.PARTY_SET_TANKER,
player.PARTY_STATE_DEFENDER : localeInfo.PARTY_SET_DEFENDER,
player.PARTY_STATE_BUFFER : localeInfo.PARTY_SET_BUFFER,
player.PARTY_STATE_SKILL_MASTER : localeInfo.PARTY_SET_SKILL_MASTER, }
STATE_NAME_DICT = {}
@staticmethod
def _RebuildLocaleStrings():
PartyMemberInfoBoard.AFFECT_STRING_DICT = {
PartyMemberInfoBoard.PARTY_AFFECT_EXPERIENCE : localeInfo.PARTY_BONUS_EXP,
PartyMemberInfoBoard.PARTY_AFFECT_ATTACKER : localeInfo.PARTY_BONUS_ATTACKER,
PartyMemberInfoBoard.PARTY_AFFECT_TANKER : localeInfo.PARTY_BONUS_TANKER,
PartyMemberInfoBoard.PARTY_AFFECT_BUFFER : localeInfo.PARTY_BONUS_BUFFER,
PartyMemberInfoBoard.PARTY_AFFECT_SKILL_MASTER : localeInfo.PARTY_BONUS_SKILL_MASTER,
PartyMemberInfoBoard.PARTY_AFFECT_BERSERKER : localeInfo.PARTY_BONUS_BERSERKER,
PartyMemberInfoBoard.PARTY_AFFECT_DEFENDER : localeInfo.PARTY_BONUS_DEFENDER,
PartyMemberInfoBoard.PARTY_AFFECT_INCREASE_AREA_150 : localeInfo.PARTY_INCREASE_AREA_150,
PartyMemberInfoBoard.PARTY_AFFECT_INCREASE_AREA_200 : localeInfo.PARTY_INCREASE_AREA_200,
}
PartyMemberInfoBoard.STATE_NAME_DICT = {
player.PARTY_STATE_ATTACKER : localeInfo.PARTY_SET_ATTACKER,
player.PARTY_STATE_BERSERKER : localeInfo.PARTY_SET_BERSERKER,
player.PARTY_STATE_TANKER : localeInfo.PARTY_SET_TANKER,
player.PARTY_STATE_DEFENDER : localeInfo.PARTY_SET_DEFENDER,
player.PARTY_STATE_BUFFER : localeInfo.PARTY_SET_BUFFER,
player.PARTY_STATE_SKILL_MASTER : localeInfo.PARTY_SET_SKILL_MASTER,
}
def __init__(self):
ui.ScriptWindow.__init__(self)
@@ -117,6 +126,9 @@ class PartyMemberInfoBoard(ui.ScriptWindow):
self.nameTextLine = None
self.gauge = None
self.stateButton = None
for img in self.partyAffectImageList:
img.OnMouseOverIn = None
img.OnMouseOverOut = None
self.partyAffectImageList = []
self.stateButtonDict = {}
@@ -388,7 +400,10 @@ class PartyMemberInfoBoard(ui.ScriptWindow):
class PartyMenu(ui.ThinBoard):
BUTTON_NAME = ( localeInfo.PARTY_HEAL_ALL_MEMBER, localeInfo.PARTY_BREAK_UP, localeInfo.PARTY_LEAVE )
# Locale-independent button identifiers (never use translated strings as dict keys)
BTN_HEAL_ALL = 0
BTN_BREAK_UP = 1
BTN_LEAVE = 2
def __init__(self):
ui.ThinBoard.__init__(self)
@@ -441,28 +456,28 @@ class PartyMenu(ui.ThinBoard):
self.ChangePartyParameter(self.distributionMode)
def __CreateButtons(self):
buttonConfig = (
(self.BTN_HEAL_ALL, "PARTY_HEAL_ALL_MEMBER",
"d:/ymir work/ui/game/windows/Party_Skill_Heal",
ui.__mem_func__(self.OnPartyUseSkill)),
(self.BTN_BREAK_UP, "PARTY_BREAK_UP",
"d:/ymir work/ui/game/windows/Party_Disband",
net.SendPartyExitPacket),
(self.BTN_LEAVE, "PARTY_LEAVE",
"d:/ymir work/ui/game/windows/Party_Exit",
net.SendPartyExitPacket),
)
for name in self.BUTTON_NAME:
for btnId, localeAttr, imgBase, event in buttonConfig:
button = ui.Button()
button.SetParent(self)
button.SetWindowHorizontalAlignCenter()
button.SetToolTipText(name)
self.buttonDict[name] = button
self.buttonDict[localeInfo.PARTY_HEAL_ALL_MEMBER].SetEvent(ui.__mem_func__(self.OnPartyUseSkill))
self.buttonDict[localeInfo.PARTY_HEAL_ALL_MEMBER].SetUpVisual("d:/ymir work/ui/game/windows/Party_Skill_Heal_01.sub")
self.buttonDict[localeInfo.PARTY_HEAL_ALL_MEMBER].SetOverVisual("d:/ymir work/ui/game/windows/Party_Skill_Heal_02.sub")
self.buttonDict[localeInfo.PARTY_HEAL_ALL_MEMBER].SetDownVisual("d:/ymir work/ui/game/windows/Party_Skill_Heal_03.sub")
self.buttonDict[localeInfo.PARTY_BREAK_UP].SetEvent(net.SendPartyExitPacket)
self.buttonDict[localeInfo.PARTY_BREAK_UP].SetUpVisual("d:/ymir work/ui/game/windows/Party_Disband_01.sub")
self.buttonDict[localeInfo.PARTY_BREAK_UP].SetOverVisual("d:/ymir work/ui/game/windows/Party_Disband_02.sub")
self.buttonDict[localeInfo.PARTY_BREAK_UP].SetDownVisual("d:/ymir work/ui/game/windows/Party_Disband_03.sub")
self.buttonDict[localeInfo.PARTY_LEAVE].SetEvent(net.SendPartyExitPacket)
self.buttonDict[localeInfo.PARTY_LEAVE].SetUpVisual("d:/ymir work/ui/game/windows/Party_Exit_01.sub")
self.buttonDict[localeInfo.PARTY_LEAVE].SetOverVisual("d:/ymir work/ui/game/windows/Party_Exit_02.sub")
self.buttonDict[localeInfo.PARTY_LEAVE].SetDownVisual("d:/ymir work/ui/game/windows/Party_Exit_03.sub")
button.SetToolTipText(getattr(localeInfo, localeAttr))
button.SetUpVisual(imgBase + "_01.sub")
button.SetOverVisual(imgBase + "_02.sub")
button.SetDownVisual(imgBase + "_03.sub")
button.SetEvent(event)
self.buttonDict[btnId] = button
def __ClearShowingButtons(self):
self.showingButtonList = []
@@ -507,19 +522,19 @@ class PartyMenu(ui.ThinBoard):
def ShowLeaderButton(self):
self.isLeader = True
self.__ClearShowingButtons()
self.__ShowButton(localeInfo.PARTY_BREAK_UP)
self.__ShowButton(self.BTN_BREAK_UP)
def ShowMemberButton(self):
self.isLeader = False
self.__ClearShowingButtons()
self.__ShowButton(localeInfo.PARTY_LEAVE)
self.__ShowButton(self.BTN_LEAVE)
def OnPartyUseSkill(self):
net.SendPartyUseSkillPacket(PartyMemberInfoBoard.PARTY_SKILL_HEAL, 0)
self.__HideButton(localeInfo.PARTY_HEAL_ALL_MEMBER)
self.__HideButton(self.BTN_HEAL_ALL)
def PartyHealReady(self):
self.__ShowButton(localeInfo.PARTY_HEAL_ALL_MEMBER)
self.__ShowButton(self.BTN_HEAL_ALL)
def __UpAllModeButtons(self):
for button in list(self.modeButtonList.values()):
@@ -735,3 +750,6 @@ class PartyWindow(ui.Window):
self.partyMenuButton.SetOverVisual("d:/ymir work/ui/game/windows/Party_Menu_Close_02.sub")
self.partyMenuButton.SetDownVisual("d:/ymir work/ui/game/windows/Party_Menu_Close_03.sub")
self.partyMenu.Show()
PartyMemberInfoBoard._RebuildLocaleStrings()
localeInfo.RegisterReloadCallback(PartyMemberInfoBoard._RebuildLocaleStrings)

View File

@@ -47,7 +47,7 @@ EMPIREDESC_B = "%s/empiredesc_b.txt" % (name)
EMPIREDESC_C = "%s/empiredesc_c.txt" % (name)
LOCALE_INTERFACE_FILE_NAME = "%s/locale_interface.txt" % (name)
LoadLocaleFile(LOCALE_INTERFACE_FILE_NAME, locals())
LoadLocaleFile(LOCALE_INTERFACE_FILE_NAME, globals())
def LoadLocaleData():
"""
@@ -56,6 +56,10 @@ def LoadLocaleData():
Called by app.ReloadLocale() when the user changes language.
Updates all locale-dependent paths and reloads locale_interface.txt.
This is the LAST Python module reloaded by the C++ reload chain,
so it fires localeInfo.FireReloadCallbacks() at the end to notify
all modules that both localeInfo and uiScriptLocale are fully loaded.
Returns:
True on success, False on failure
"""
@@ -88,8 +92,12 @@ def LoadLocaleData():
# Reload locale_interface.txt - this updates all UI strings
LoadLocaleFile(LOCALE_INTERFACE_FILE_NAME, globals())
# Fire reload callbacks now that BOTH modules are fully loaded
import localeInfo
localeInfo.FireReloadCallbacks()
return True
except:
# import dbg
# dbg.TraceError("uiScriptLocale.LoadLocaleData failed")
except Exception as e:
import dbg
dbg.TraceError("uiScriptLocale.LoadLocaleData failed: %s" % str(e))
return False

View File

@@ -3,8 +3,15 @@ import ui
import localeInfo
import uiScriptLocale
FILE_NAME_LEN = 20
DEFAULT_THEMA = localeInfo.MUSIC_METIN2_DEFAULT_THEMA
FILE_NAME_LEN = 20
DEFAULT_THEMA = ""
def _RebuildLocaleStrings():
global DEFAULT_THEMA
DEFAULT_THEMA = localeInfo.MUSIC_METIN2_DEFAULT_THEMA
_RebuildLocaleStrings()
localeInfo.RegisterReloadCallback(_RebuildLocaleStrings)
class Item(ui.ListBoxEx.Item):
def __init__(self, fileName):

View File

@@ -13,37 +13,62 @@ import uiCommon
class TargetBoard(ui.ThinBoard):
BUTTON_NAME_LIST = (
localeInfo.TARGET_BUTTON_WHISPER,
localeInfo.TARGET_BUTTON_EXCHANGE,
localeInfo.TARGET_BUTTON_FIGHT,
localeInfo.TARGET_BUTTON_ACCEPT_FIGHT,
localeInfo.TARGET_BUTTON_AVENGE,
localeInfo.TARGET_BUTTON_FRIEND,
localeInfo.TARGET_BUTTON_INVITE_PARTY,
localeInfo.TARGET_BUTTON_LEAVE_PARTY,
localeInfo.TARGET_BUTTON_EXCLUDE,
localeInfo.TARGET_BUTTON_INVITE_GUILD,
localeInfo.TARGET_BUTTON_REMOVE_GUILD,
localeInfo.TARGET_BUTTON_DISMOUNT,
localeInfo.TARGET_BUTTON_EXIT_OBSERVER,
localeInfo.TARGET_BUTTON_VIEW_EQUIPMENT,
localeInfo.TARGET_BUTTON_REQUEST_ENTER_PARTY,
localeInfo.TARGET_BUTTON_BUILDING_DESTROY,
localeInfo.TARGET_BUTTON_EMOTION_ALLOW,
"VOTE_BLOCK_CHAT",
# Locale-independent button identifiers (never use translated strings as dict keys)
BTN_WHISPER = 0
BTN_EXCHANGE = 1
BTN_FIGHT = 2
BTN_ACCEPT_FIGHT = 3
BTN_AVENGE = 4
BTN_FRIEND = 5
BTN_INVITE_PARTY = 6
BTN_LEAVE_PARTY = 7
BTN_EXCLUDE = 8
BTN_INVITE_GUILD = 9
BTN_REMOVE_GUILD = 10
BTN_DISMOUNT = 11
BTN_EXIT_OBSERVER = 12
BTN_VIEW_EQUIPMENT = 13
BTN_REQUEST_ENTER_PARTY = 14
BTN_BUILDING_DESTROY = 15
BTN_EMOTION_ALLOW = 16
BTN_VOTE_BLOCK_CHAT = 17
# (buttonId, localeInfo attribute name) — text resolved at __init__ time
BUTTON_CONFIG = (
(BTN_WHISPER, "TARGET_BUTTON_WHISPER"),
(BTN_EXCHANGE, "TARGET_BUTTON_EXCHANGE"),
(BTN_FIGHT, "TARGET_BUTTON_FIGHT"),
(BTN_ACCEPT_FIGHT, "TARGET_BUTTON_ACCEPT_FIGHT"),
(BTN_AVENGE, "TARGET_BUTTON_AVENGE"),
(BTN_FRIEND, "TARGET_BUTTON_FRIEND"),
(BTN_INVITE_PARTY, "TARGET_BUTTON_INVITE_PARTY"),
(BTN_LEAVE_PARTY, "TARGET_BUTTON_LEAVE_PARTY"),
(BTN_EXCLUDE, "TARGET_BUTTON_EXCLUDE"),
(BTN_INVITE_GUILD, "TARGET_BUTTON_INVITE_GUILD"),
(BTN_REMOVE_GUILD, "TARGET_BUTTON_REMOVE_GUILD"),
(BTN_DISMOUNT, "TARGET_BUTTON_DISMOUNT"),
(BTN_EXIT_OBSERVER, "TARGET_BUTTON_EXIT_OBSERVER"),
(BTN_VIEW_EQUIPMENT, "TARGET_BUTTON_VIEW_EQUIPMENT"),
(BTN_REQUEST_ENTER_PARTY, "TARGET_BUTTON_REQUEST_ENTER_PARTY"),
(BTN_BUILDING_DESTROY, "TARGET_BUTTON_BUILDING_DESTROY"),
(BTN_EMOTION_ALLOW, "TARGET_BUTTON_EMOTION_ALLOW"),
(BTN_VOTE_BLOCK_CHAT, "VOTE_BLOCK_CHAT"),
)
GRADE_NAME = {
nonplayer.PAWN : localeInfo.TARGET_LEVEL_PAWN,
nonplayer.S_PAWN : localeInfo.TARGET_LEVEL_S_PAWN,
nonplayer.KNIGHT : localeInfo.TARGET_LEVEL_KNIGHT,
nonplayer.S_KNIGHT : localeInfo.TARGET_LEVEL_S_KNIGHT,
nonplayer.BOSS : localeInfo.TARGET_LEVEL_BOSS,
nonplayer.KING : localeInfo.TARGET_LEVEL_KING,
}
GRADE_NAME = {}
EXCHANGE_LIMIT_RANGE = 3000
@staticmethod
def _RebuildLocaleStrings():
TargetBoard.GRADE_NAME = {
nonplayer.PAWN : localeInfo.TARGET_LEVEL_PAWN,
nonplayer.S_PAWN : localeInfo.TARGET_LEVEL_S_PAWN,
nonplayer.KNIGHT : localeInfo.TARGET_LEVEL_KNIGHT,
nonplayer.S_KNIGHT : localeInfo.TARGET_LEVEL_S_KNIGHT,
nonplayer.BOSS : localeInfo.TARGET_LEVEL_BOSS,
nonplayer.KING : localeInfo.TARGET_LEVEL_KING,
}
def __init__(self):
ui.ThinBoard.__init__(self)
@@ -80,7 +105,7 @@ class TargetBoard(ui.ThinBoard):
self.buttonDict = {}
self.showingButtonList = []
for buttonName in self.BUTTON_NAME_LIST:
for btnId, localeAttr in self.BUTTON_CONFIG:
button = ui.Button()
button.SetParent(self)
@@ -94,32 +119,31 @@ class TargetBoard(ui.ThinBoard):
button.SetDownVisual("d:/ymir work/ui/public/small_thin_button_03.sub")
button.SetWindowHorizontalAlignCenter()
button.SetText(buttonName)
button.SetText(getattr(localeInfo, localeAttr, localeAttr))
button.Hide()
self.buttonDict[buttonName] = button
self.buttonDict[btnId] = button
self.showingButtonList.append(button)
self.buttonDict[localeInfo.TARGET_BUTTON_WHISPER].SetEvent(ui.__mem_func__(self.OnWhisper))
self.buttonDict[localeInfo.TARGET_BUTTON_EXCHANGE].SetEvent(ui.__mem_func__(self.OnExchange))
self.buttonDict[localeInfo.TARGET_BUTTON_FIGHT].SetEvent(ui.__mem_func__(self.OnPVP))
self.buttonDict[localeInfo.TARGET_BUTTON_ACCEPT_FIGHT].SetEvent(ui.__mem_func__(self.OnPVP))
self.buttonDict[localeInfo.TARGET_BUTTON_AVENGE].SetEvent(ui.__mem_func__(self.OnPVP))
self.buttonDict[localeInfo.TARGET_BUTTON_FRIEND].SetEvent(ui.__mem_func__(self.OnAppendToMessenger))
self.buttonDict[localeInfo.TARGET_BUTTON_FRIEND].SetEvent(ui.__mem_func__(self.OnAppendToMessenger))
self.buttonDict[localeInfo.TARGET_BUTTON_INVITE_PARTY].SetEvent(ui.__mem_func__(self.OnPartyInvite))
self.buttonDict[localeInfo.TARGET_BUTTON_LEAVE_PARTY].SetEvent(ui.__mem_func__(self.OnPartyExit))
self.buttonDict[localeInfo.TARGET_BUTTON_EXCLUDE].SetEvent(ui.__mem_func__(self.OnPartyRemove))
self.buttonDict[self.BTN_WHISPER].SetEvent(ui.__mem_func__(self.OnWhisper))
self.buttonDict[self.BTN_EXCHANGE].SetEvent(ui.__mem_func__(self.OnExchange))
self.buttonDict[self.BTN_FIGHT].SetEvent(ui.__mem_func__(self.OnPVP))
self.buttonDict[self.BTN_ACCEPT_FIGHT].SetEvent(ui.__mem_func__(self.OnPVP))
self.buttonDict[self.BTN_AVENGE].SetEvent(ui.__mem_func__(self.OnPVP))
self.buttonDict[self.BTN_FRIEND].SetEvent(ui.__mem_func__(self.OnAppendToMessenger))
self.buttonDict[self.BTN_INVITE_PARTY].SetEvent(ui.__mem_func__(self.OnPartyInvite))
self.buttonDict[self.BTN_LEAVE_PARTY].SetEvent(ui.__mem_func__(self.OnPartyExit))
self.buttonDict[self.BTN_EXCLUDE].SetEvent(ui.__mem_func__(self.OnPartyRemove))
self.buttonDict[localeInfo.TARGET_BUTTON_INVITE_GUILD].SAFE_SetEvent(self.__OnGuildAddMember)
self.buttonDict[localeInfo.TARGET_BUTTON_REMOVE_GUILD].SAFE_SetEvent(self.__OnGuildRemoveMember)
self.buttonDict[localeInfo.TARGET_BUTTON_DISMOUNT].SAFE_SetEvent(self.__OnDismount)
self.buttonDict[localeInfo.TARGET_BUTTON_EXIT_OBSERVER].SAFE_SetEvent(self.__OnExitObserver)
self.buttonDict[localeInfo.TARGET_BUTTON_VIEW_EQUIPMENT].SAFE_SetEvent(self.__OnViewEquipment)
self.buttonDict[localeInfo.TARGET_BUTTON_REQUEST_ENTER_PARTY].SAFE_SetEvent(self.__OnRequestParty)
self.buttonDict[localeInfo.TARGET_BUTTON_BUILDING_DESTROY].SAFE_SetEvent(self.__OnDestroyBuilding)
self.buttonDict[localeInfo.TARGET_BUTTON_EMOTION_ALLOW].SAFE_SetEvent(self.__OnEmotionAllow)
self.buttonDict["VOTE_BLOCK_CHAT"].SetEvent(ui.__mem_func__(self.__OnVoteBlockChat))
self.buttonDict[self.BTN_INVITE_GUILD].SAFE_SetEvent(self.__OnGuildAddMember)
self.buttonDict[self.BTN_REMOVE_GUILD].SAFE_SetEvent(self.__OnGuildRemoveMember)
self.buttonDict[self.BTN_DISMOUNT].SAFE_SetEvent(self.__OnDismount)
self.buttonDict[self.BTN_EXIT_OBSERVER].SAFE_SetEvent(self.__OnExitObserver)
self.buttonDict[self.BTN_VIEW_EQUIPMENT].SAFE_SetEvent(self.__OnViewEquipment)
self.buttonDict[self.BTN_REQUEST_ENTER_PARTY].SAFE_SetEvent(self.__OnRequestParty)
self.buttonDict[self.BTN_BUILDING_DESTROY].SAFE_SetEvent(self.__OnDestroyBuilding)
self.buttonDict[self.BTN_EMOTION_ALLOW].SAFE_SetEvent(self.__OnEmotionAllow)
self.buttonDict[self.BTN_VOTE_BLOCK_CHAT].SetEvent(ui.__mem_func__(self.__OnVoteBlockChat))
self.name = name
self.hpGauge = hpGauge
@@ -182,8 +206,8 @@ class TargetBoard(ui.ThinBoard):
self.Show()
else:
self.HideAllButton()
self.__ShowButton(localeInfo.TARGET_BUTTON_WHISPER)
self.__ShowButton("VOTE_BLOCK_CHAT")
self.__ShowButton(self.BTN_WHISPER)
self.__ShowButton(self.BTN_VOTE_BLOCK_CHAT)
self.__ArrangeButtonPosition()
self.SetTargetName(name)
self.Show()
@@ -207,11 +231,11 @@ class TargetBoard(ui.ThinBoard):
self.HideAllButton()
if player.IsMountingHorse():
self.__ShowButton(localeInfo.TARGET_BUTTON_DISMOUNT)
self.__ShowButton(self.BTN_DISMOUNT)
canShow=1
if player.IsObserverMode():
self.__ShowButton(localeInfo.TARGET_BUTTON_EXIT_OBSERVER)
self.__ShowButton(self.BTN_EXIT_OBSERVER)
canShow=1
if canShow:
@@ -291,10 +315,10 @@ class TargetBoard(ui.ThinBoard):
def ShowDefaultButton(self):
self.isShowButton = True
self.showingButtonList.append(self.buttonDict[localeInfo.TARGET_BUTTON_WHISPER])
self.showingButtonList.append(self.buttonDict[localeInfo.TARGET_BUTTON_EXCHANGE])
self.showingButtonList.append(self.buttonDict[localeInfo.TARGET_BUTTON_FIGHT])
self.showingButtonList.append(self.buttonDict[localeInfo.TARGET_BUTTON_EMOTION_ALLOW])
self.showingButtonList.append(self.buttonDict[self.BTN_WHISPER])
self.showingButtonList.append(self.buttonDict[self.BTN_EXCHANGE])
self.showingButtonList.append(self.buttonDict[self.BTN_FIGHT])
self.showingButtonList.append(self.buttonDict[self.BTN_EMOTION_ALLOW])
for button in self.showingButtonList:
button.Show()
@@ -399,7 +423,7 @@ class TargetBoard(ui.ThinBoard):
self.HideAllButton()
if chr.INSTANCE_TYPE_BUILDING == chr.GetInstanceType(self.vid):
#self.__ShowButton(localeInfo.TARGET_BUTTON_BUILDING_DESTROY)
#self.__ShowButton(self.BTN_BUILDING_DESTROY)
#self.__ArrangeButtonPosition()
return
@@ -424,11 +448,11 @@ class TargetBoard(ui.ThinBoard):
guildAuthorityButtons = {
guild.AUTH_ADD_MEMBER: {
"text": localeInfo.TARGET_BUTTON_INVITE_GUILD,
"btn": self.BTN_INVITE_GUILD,
"condition": lambda: isNotGuildMember(self.nameString, self.vid),
},
guild.AUTH_REMOVE_MEMBER: {
"text": localeInfo.TARGET_BUTTON_REMOVE_GUILD,
"btn": self.BTN_REMOVE_GUILD,
"condition": lambda: isGuildMember(self.nameString, self.vid) and not isGuildMaster(self.nameString),
},
}
@@ -437,47 +461,47 @@ class TargetBoard(ui.ThinBoard):
hasAuthority = guild.MainPlayerHasAuthority(guildAuthority)
satisfiesCondition = guildButton["condition"]()
if hasAuthority and satisfiesCondition:
self.__ShowButton(guildButton["text"])
self.__ShowButton(guildButton["btn"])
if not messenger.IsFriendByName(self.nameString):
self.__ShowButton(localeInfo.TARGET_BUTTON_FRIEND)
self.__ShowButton(self.BTN_FRIEND)
if player.IsPartyMember(self.vid):
self.__HideButton(localeInfo.TARGET_BUTTON_FIGHT)
self.__HideButton(self.BTN_FIGHT)
if player.IsPartyLeader(self.vid):
self.__ShowButton(localeInfo.TARGET_BUTTON_LEAVE_PARTY)
self.__ShowButton(self.BTN_LEAVE_PARTY)
elif player.IsPartyLeader(player.GetMainCharacterIndex()):
self.__ShowButton(localeInfo.TARGET_BUTTON_EXCLUDE)
self.__ShowButton(self.BTN_EXCLUDE)
else:
if player.IsPartyMember(player.GetMainCharacterIndex()):
if player.IsPartyLeader(player.GetMainCharacterIndex()):
self.__ShowButton(localeInfo.TARGET_BUTTON_INVITE_PARTY)
self.__ShowButton(self.BTN_INVITE_PARTY)
else:
if chr.IsPartyMember(self.vid):
self.__ShowButton(localeInfo.TARGET_BUTTON_REQUEST_ENTER_PARTY)
self.__ShowButton(self.BTN_REQUEST_ENTER_PARTY)
else:
self.__ShowButton(localeInfo.TARGET_BUTTON_INVITE_PARTY)
self.__ShowButton(self.BTN_INVITE_PARTY)
if player.IsRevengeInstance(self.vid):
self.__HideButton(localeInfo.TARGET_BUTTON_FIGHT)
self.__ShowButton(localeInfo.TARGET_BUTTON_AVENGE)
self.__HideButton(self.BTN_FIGHT)
self.__ShowButton(self.BTN_AVENGE)
elif player.IsChallengeInstance(self.vid):
self.__HideButton(localeInfo.TARGET_BUTTON_FIGHT)
self.__ShowButton(localeInfo.TARGET_BUTTON_ACCEPT_FIGHT)
self.__HideButton(self.BTN_FIGHT)
self.__ShowButton(self.BTN_ACCEPT_FIGHT)
elif player.IsCantFightInstance(self.vid):
self.__HideButton(localeInfo.TARGET_BUTTON_FIGHT)
self.__HideButton(self.BTN_FIGHT)
if not player.IsSameEmpire(self.vid):
self.__HideButton(localeInfo.TARGET_BUTTON_INVITE_PARTY)
self.__HideButton(localeInfo.TARGET_BUTTON_FRIEND)
self.__HideButton(localeInfo.TARGET_BUTTON_FIGHT)
self.__HideButton(self.BTN_INVITE_PARTY)
self.__HideButton(self.BTN_FRIEND)
self.__HideButton(self.BTN_FIGHT)
distance = player.GetCharacterDistance(self.vid)
if distance > self.EXCHANGE_LIMIT_RANGE:
self.__HideButton(localeInfo.TARGET_BUTTON_EXCHANGE)
self.__HideButton(self.BTN_EXCHANGE)
self.__ArrangeButtonPosition()
self.__ArrangeButtonPosition()
@@ -500,7 +524,7 @@ class TargetBoard(ui.ThinBoard):
def OnUpdate(self):
if self.isShowButton:
exchangeButton = self.buttonDict[localeInfo.TARGET_BUTTON_EXCHANGE]
exchangeButton = self.buttonDict[self.BTN_EXCHANGE]
distance = player.GetCharacterDistance(self.vid)
if distance < 0:
@@ -513,3 +537,6 @@ class TargetBoard(ui.ThinBoard):
else:
if distance < self.EXCHANGE_LIMIT_RANGE:
self.RefreshButton()
TargetBoard._RebuildLocaleStrings()
localeInfo.RegisterReloadCallback(TargetBoard._RebuildLocaleStrings)

View File

@@ -2215,26 +2215,32 @@ class SkillToolTip(ToolTip):
( 26, 1, ),
( 32, 2, ), )
SKILL_GRADE_NAME = { player.SKILL_GRADE_MASTER : localeInfo.SKILL_GRADE_NAME_MASTER,
player.SKILL_GRADE_GRAND_MASTER : localeInfo.SKILL_GRADE_NAME_GRAND_MASTER,
player.SKILL_GRADE_PERFECT_MASTER : localeInfo.SKILL_GRADE_NAME_PERFECT_MASTER, }
AFFECT_NAME_DICT = {
"HP" : localeInfo.TOOLTIP_SKILL_AFFECT_ATT_POWER,
"ATT_GRADE" : localeInfo.TOOLTIP_SKILL_AFFECT_ATT_GRADE,
"DEF_GRADE" : localeInfo.TOOLTIP_SKILL_AFFECT_DEF_GRADE,
"ATT_SPEED" : localeInfo.TOOLTIP_SKILL_AFFECT_ATT_SPEED,
"MOV_SPEED" : localeInfo.TOOLTIP_SKILL_AFFECT_MOV_SPEED,
"DODGE" : localeInfo.TOOLTIP_SKILL_AFFECT_DODGE,
"RESIST_NORMAL" : localeInfo.TOOLTIP_SKILL_AFFECT_RESIST_NORMAL,
"REFLECT_MELEE" : localeInfo.TOOLTIP_SKILL_AFFECT_REFLECT_MELEE,
}
SKILL_GRADE_NAME = {}
AFFECT_NAME_DICT = {}
AFFECT_APPEND_TEXT_DICT = {
"DODGE" : "%",
"RESIST_NORMAL" : "%",
"REFLECT_MELEE" : "%",
}
@staticmethod
def _RebuildLocaleStrings():
SkillToolTip.SKILL_GRADE_NAME = {
player.SKILL_GRADE_MASTER : localeInfo.SKILL_GRADE_NAME_MASTER,
player.SKILL_GRADE_GRAND_MASTER : localeInfo.SKILL_GRADE_NAME_GRAND_MASTER,
player.SKILL_GRADE_PERFECT_MASTER : localeInfo.SKILL_GRADE_NAME_PERFECT_MASTER,
}
SkillToolTip.AFFECT_NAME_DICT = {
"HP" : localeInfo.TOOLTIP_SKILL_AFFECT_ATT_POWER,
"ATT_GRADE" : localeInfo.TOOLTIP_SKILL_AFFECT_ATT_GRADE,
"DEF_GRADE" : localeInfo.TOOLTIP_SKILL_AFFECT_DEF_GRADE,
"ATT_SPEED" : localeInfo.TOOLTIP_SKILL_AFFECT_ATT_SPEED,
"MOV_SPEED" : localeInfo.TOOLTIP_SKILL_AFFECT_MOV_SPEED,
"DODGE" : localeInfo.TOOLTIP_SKILL_AFFECT_DODGE,
"RESIST_NORMAL" : localeInfo.TOOLTIP_SKILL_AFFECT_RESIST_NORMAL,
"REFLECT_MELEE" : localeInfo.TOOLTIP_SKILL_AFFECT_REFLECT_MELEE,
}
def __init__(self):
ToolTip.__init__(self, self.SKILL_TOOL_TIP_WIDTH)
def __del__(self):
@@ -2705,5 +2711,8 @@ if __name__ == "__main__":
toolTip.AddItemData_Offline(10, desc, summ, 0, 0)
toolTip.Show()
app.Loop()
SkillToolTip._RebuildLocaleStrings()
localeInfo.RegisterReloadCallback(SkillToolTip._RebuildLocaleStrings)