diff --git a/assets/root/consolemodule.py b/assets/root/consolemodule.py index d367da85..679bee67 100644 --- a/assets/root/consolemodule.py +++ b/assets/root/consolemodule.py @@ -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" diff --git a/assets/root/emotion.py b/assets/root/emotion.py index 596a6c49..6eea5f78 100644 --- a/assets/root/emotion.py +++ b/assets/root/emotion.py @@ -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) diff --git a/assets/root/intrologin.py b/assets/root/intrologin.py index 91f0f8e4..77b3cf99 100644 --- a/assets/root/intrologin.py +++ b/assets/root/intrologin.py @@ -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()) diff --git a/assets/root/localeinfo.py b/assets/root/localeinfo.py index 721a9240..b01434d0 100644 --- a/assets/root/localeinfo.py +++ b/assets/root/localeinfo.py @@ -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(): diff --git a/assets/root/networkmodule.py b/assets/root/networkmodule.py index 29b363fa..601dd755 100644 --- a/assets/root/networkmodule.py +++ b/assets/root/networkmodule.py @@ -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() diff --git a/assets/root/uicharacter.py b/assets/root/uicharacter.py index acf1dc8a..085809b9 100644 --- a/assets/root/uicharacter.py +++ b/assets/root/uicharacter.py @@ -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) diff --git a/assets/root/uichat.py b/assets/root/uichat.py index ec08edea..f0fb39e0 100644 --- a/assets/root/uichat.py +++ b/assets/root/uichat.py @@ -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) diff --git a/assets/root/uiguild.py b/assets/root/uiguild.py index d90e9ab0..0089b7b7 100644 --- a/assets/root/uiguild.py +++ b/assets/root/uiguild.py @@ -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) diff --git a/assets/root/uilocalerefresh.py b/assets/root/uilocalerefresh.py index 5632d16d..9dc97ae8 100644 --- a/assets/root/uilocalerefresh.py +++ b/assets/root/uilocalerefresh.py @@ -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 diff --git a/assets/root/uilocaleselector.py b/assets/root/uilocaleselector.py index c40e3083..0c822a15 100644 --- a/assets/root/uilocaleselector.py +++ b/assets/root/uilocaleselector.py @@ -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: diff --git a/assets/root/uimapnameshower.py b/assets/root/uimapnameshower.py index cb49a93b..5b9d15d3 100644 --- a/assets/root/uimapnameshower.py +++ b/assets/root/uimapnameshower.py @@ -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 diff --git a/assets/root/uiparty.py b/assets/root/uiparty.py index ca2fdc47..0cbff95e 100644 --- a/assets/root/uiparty.py +++ b/assets/root/uiparty.py @@ -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) diff --git a/assets/root/uiscriptlocale.py b/assets/root/uiscriptlocale.py index 77d76c45..542dda1b 100644 --- a/assets/root/uiscriptlocale.py +++ b/assets/root/uiscriptlocale.py @@ -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 \ No newline at end of file diff --git a/assets/root/uiselectmusic.py b/assets/root/uiselectmusic.py index 56263cc5..26bd5e32 100644 --- a/assets/root/uiselectmusic.py +++ b/assets/root/uiselectmusic.py @@ -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): diff --git a/assets/root/uitarget.py b/assets/root/uitarget.py index 38be7ce8..ec6dcf11 100644 --- a/assets/root/uitarget.py +++ b/assets/root/uitarget.py @@ -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) diff --git a/assets/root/uitooltip.py b/assets/root/uitooltip.py index e526a260..3ff2dc74 100644 --- a/assets/root/uitooltip.py +++ b/assets/root/uitooltip.py @@ -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)