From 7c2f4e17a3dc15d658a786b0788e50af49d5da35 Mon Sep 17 00:00:00 2001 From: rtw1x1 Date: Wed, 21 Jan 2026 23:18:10 +0000 Subject: [PATCH 1/2] fix: DragonSoul Timer --- assets/root/uidragonsoul.py | 6 ++- assets/root/uitooltip.py | 78 ++++++++++++++++++++++++++++++++++--- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/assets/root/uidragonsoul.py b/assets/root/uidragonsoul.py index d0a9dac3..8bcca00a 100644 --- a/assets/root/uidragonsoul.py +++ b/assets/root/uidragonsoul.py @@ -206,10 +206,12 @@ class DragonSoulWindow(ui.ScriptWindow): self.inventoryTab[(page+3)%5].SetUp() self.inventoryTab[(page+4)%5].SetUp() self.RefreshBagSlotWindow() - + def SetItemToolTip(self, tooltipItem): self.tooltipItem = tooltipItem - + if tooltipItem: + tooltipItem.SetDragonSoulWindow(self) + def RefreshItemSlot(self): self.RefreshBagSlotWindow() self.RefreshEquipSlotWindow() diff --git a/assets/root/uitooltip.py b/assets/root/uitooltip.py index 8709c46c..1914d52a 100644 --- a/assets/root/uitooltip.py +++ b/assets/root/uitooltip.py @@ -499,10 +499,18 @@ class ItemToolTip(ToolTip): # When displaying item tooltip, if the current character cannot equip the item, force it to use Disable Color (already works this way but needed ability to turn it off) self.bCannotUseItemForceSetDisableColor = True + self._curItemWindowType = None + self._curItemSlotIndex = -1 + self._wearTimerCache = {} + self._dragonSoulWindow = None + def __del__(self): ToolTip.__del__(self) self.metinSlot = None + def SetDragonSoulWindow(self, wnd): + self._dragonSoulWindow = wnd + def SetCannotUseItemForceSetDisableColor(self, enable): self.bCannotUseItemForceSetDisableColor = enable @@ -568,6 +576,10 @@ class ItemToolTip(ToolTip): def ClearToolTip(self): self.isShopItem = False self.toolTipWidth = self.TOOL_TIP_WIDTH + + self._curItemWindowType = None + self._curItemSlotIndex = -1 + ToolTip.ClearToolTip(self) def SetInventoryItem(self, slotIndex, window_type = player.INVENTORY): @@ -584,6 +596,8 @@ class ItemToolTip(ToolTip): metinSlot = [player.GetItemMetinSocket(window_type, slotIndex, i) for i in xrange(player.METIN_SOCKET_MAX_NUM)] attrSlot = [player.GetItemAttribute(window_type, slotIndex, i) for i in xrange(player.ATTRIBUTE_SLOT_MAX_NUM)] + self._curItemWindowType = window_type + self._curItemSlotIndex = slotIndex self.AddItemData(itemVnum, metinSlot, attrSlot) def SetShopItem(self, slotIndex): @@ -593,6 +607,8 @@ class ItemToolTip(ToolTip): price = shop.GetItemPrice(slotIndex) self.ClearToolTip() + self._curItemWindowType = None + self._curItemSlotIndex = slotIndex self.isShopItem = True metinSlot = [] @@ -612,6 +628,8 @@ class ItemToolTip(ToolTip): price = shop.GetItemPrice(slotIndex) self.ClearToolTip() + self._curItemWindowType = None + self._curItemSlotIndex = slotIndex self.isShopItem = True metinSlot = [] @@ -630,6 +648,8 @@ class ItemToolTip(ToolTip): return self.ClearToolTip() + self._curItemWindowType = None + self._curItemSlotIndex = slotIndex metinSlot = [] for i in xrange(player.METIN_SOCKET_MAX_NUM): @@ -645,6 +665,8 @@ class ItemToolTip(ToolTip): return self.ClearToolTip() + self._curItemWindowType = None + self._curItemSlotIndex = slotIndex metinSlot = [] for i in xrange(player.METIN_SOCKET_MAX_NUM): @@ -661,6 +683,8 @@ class ItemToolTip(ToolTip): item.SelectItem(itemVnum) self.ClearToolTip() + self._curItemWindowType = None + self._curItemSlotIndex = privateShopSlotIndex self.AppendSellingPrice(shop.GetPrivateShopItemPrice(invenType, invenPos)) metinSlot = [] @@ -678,6 +702,8 @@ class ItemToolTip(ToolTip): return self.ClearToolTip() + self._curItemWindowType = None + self._curItemSlotIndex = slotIndex metinSlot = [] for i in xrange(player.METIN_SOCKET_MAX_NUM): metinSlot.append(safebox.GetItemMetinSocket(slotIndex, i)) @@ -693,6 +719,8 @@ class ItemToolTip(ToolTip): return self.ClearToolTip() + self._curItemWindowType = None + self._curItemSlotIndex = slotIndex metinSlot = [] for i in xrange(player.METIN_SOCKET_MAX_NUM): metinSlot.append(safebox.GetMallItemMetinSocket(slotIndex, i)) @@ -1263,7 +1291,7 @@ class ItemToolTip(ToolTip): self.AppendRealTimeStartFirstUseLastTime(item, metinSlot, i, limitValue2) elif item.LIMIT_TIMER_BASED_ON_WEAR == limitType: - self.AppendTimerBasedOnWearLastTime(metinSlot) + self.AppendTimerBasedOnWearLastTime(metinSlot, limitValue2) self.ShowToolTip() @@ -1886,13 +1914,53 @@ class ItemToolTip(ToolTip): return timeTextLine - def AppendTimerBasedOnWearLastTime(self, metinSlot): - if 0 == metinSlot[0]: + def AppendTimerBasedOnWearLastTime(self, metinSlot, getLimit): + remainSec = metinSlot[0] + + if 0 == remainSec: self.AppendSpace(5) self.AppendTextLine(localeInfo.CANNOT_USE, self.DISABLE_COLOR) + return + + # For Dragon Soul items, the timer only ticks when: + # 1. The item is EQUIPPED on a deck (window_type == player.INVENTORY for DS equip slots) + # 2. AND the deck is ACTIVATED + # Items in DS inventory bag (window_type == player.DRAGON_SOUL_INVENTORY) never tick. + item.SelectItem(self.itemVnum) + isDragonSoulItem = item.GetItemType() == item.ITEM_TYPE_DS + + if isDragonSoulItem: + isTimerActive = False + windowType = getattr(self, "_curItemWindowType", None) + # Only equipped DS items (in INVENTORY window, not DRAGON_SOUL_INVENTORY) can have active timers + isEquippedOnDeck = (windowType == player.INVENTORY) + if isEquippedOnDeck and self._dragonSoulWindow and getattr(self._dragonSoulWindow, "isActivated", False): + isTimerActive = True else: - endTime = app.GetGlobalTimeStamp() + metinSlot[0] - self.AppendMallItemLastTime(endTime, getLimit) + # Non-dragon soul items with LIMIT_TIMER_BASED_ON_WEAR + # Timer is active when equipped (this function is only called for such items) + isTimerActive = True + + # Not active -> show STATIC remaining time (no ticking) + if not isTimerActive: + self.AppendSpace(5) + self.AppendTextLine(localeInfo.LEFT_TIME + ": " + localeInfo.RTSecondToDHMS(remainSec), self.NORMAL_COLOR) + return + + # Active -> show real-time countdown + # Cache maintains stable display across re-hovers (server updates remainSec periodically) + now = app.GetGlobalTimeStamp() + slotIndex = getattr(self, "_curItemSlotIndex", -1) + key = (self.itemVnum, slotIndex) + + cache = self._wearTimerCache.get(key) + if cache and cache.get("remainSec") == remainSec: + endTime = cache["endTime"] + else: + endTime = now + remainSec + self._wearTimerCache[key] = {"remainSec": remainSec, "endTime": endTime} + + self.AppendMallItemLastTime(endTime, getLimit) def AppendRealTimeStartFirstUseLastTime(self, item, metinSlot, limitIndex, getLimit): useCount = metinSlot[1] From 52bed62a498c2190a70292df5f3342f637d0afa1 Mon Sep 17 00:00:00 2001 From: rtw1x1 Date: Wed, 21 Jan 2026 23:23:33 +0000 Subject: [PATCH 2/2] QoL: DragonSoul Timer Refactored --- assets/root/uidragonsoul.py | 2 + assets/root/uitooltip.py | 109 +++++++++++++++++------------------- 2 files changed, 53 insertions(+), 58 deletions(-) diff --git a/assets/root/uidragonsoul.py b/assets/root/uidragonsoul.py index 8bcca00a..0308d568 100644 --- a/assets/root/uidragonsoul.py +++ b/assets/root/uidragonsoul.py @@ -670,6 +670,8 @@ class DragonSoulWindow(ui.ScriptWindow): def DeactivateDragonSoul(self): self.isActivated = False self.activateButton.SetUp() + if self.tooltipItem: + self.tooltipItem.ClearDragonSoulTimeCache() # MR-3: Keyboard-enabled deck toggling def ActivateButtonClick(self, deckIndex = None): diff --git a/assets/root/uitooltip.py b/assets/root/uitooltip.py index 1914d52a..9f633b16 100644 --- a/assets/root/uitooltip.py +++ b/assets/root/uitooltip.py @@ -499,17 +499,20 @@ class ItemToolTip(ToolTip): # When displaying item tooltip, if the current character cannot equip the item, force it to use Disable Color (already works this way but needed ability to turn it off) self.bCannotUseItemForceSetDisableColor = True - self._curItemWindowType = None - self._curItemSlotIndex = -1 - self._wearTimerCache = {} - self._dragonSoulWindow = None + self.itemWindowType = None + self.itemSlotIndex = -1 + self.wndDragonSoul = None + self.dsActivatedTimeCache = {} def __del__(self): ToolTip.__del__(self) self.metinSlot = None def SetDragonSoulWindow(self, wnd): - self._dragonSoulWindow = wnd + self.wndDragonSoul = wnd + + def ClearDragonSoulTimeCache(self): + self.dsActivatedTimeCache = {} def SetCannotUseItemForceSetDisableColor(self, enable): self.bCannotUseItemForceSetDisableColor = enable @@ -576,10 +579,8 @@ class ItemToolTip(ToolTip): def ClearToolTip(self): self.isShopItem = False self.toolTipWidth = self.TOOL_TIP_WIDTH - - self._curItemWindowType = None - self._curItemSlotIndex = -1 - + self.itemWindowType = None + self.itemSlotIndex = -1 ToolTip.ClearToolTip(self) def SetInventoryItem(self, slotIndex, window_type = player.INVENTORY): @@ -596,8 +597,8 @@ class ItemToolTip(ToolTip): metinSlot = [player.GetItemMetinSocket(window_type, slotIndex, i) for i in xrange(player.METIN_SOCKET_MAX_NUM)] attrSlot = [player.GetItemAttribute(window_type, slotIndex, i) for i in xrange(player.ATTRIBUTE_SLOT_MAX_NUM)] - self._curItemWindowType = window_type - self._curItemSlotIndex = slotIndex + self.itemWindowType = window_type + self.itemSlotIndex = slotIndex self.AddItemData(itemVnum, metinSlot, attrSlot) def SetShopItem(self, slotIndex): @@ -607,8 +608,8 @@ class ItemToolTip(ToolTip): price = shop.GetItemPrice(slotIndex) self.ClearToolTip() - self._curItemWindowType = None - self._curItemSlotIndex = slotIndex + self.itemWindowType = None + self.itemSlotIndex = slotIndex self.isShopItem = True metinSlot = [] @@ -628,8 +629,8 @@ class ItemToolTip(ToolTip): price = shop.GetItemPrice(slotIndex) self.ClearToolTip() - self._curItemWindowType = None - self._curItemSlotIndex = slotIndex + self.itemWindowType = None + self.itemSlotIndex = slotIndex self.isShopItem = True metinSlot = [] @@ -648,8 +649,8 @@ class ItemToolTip(ToolTip): return self.ClearToolTip() - self._curItemWindowType = None - self._curItemSlotIndex = slotIndex + self.itemWindowType = None + self.itemSlotIndex = slotIndex metinSlot = [] for i in xrange(player.METIN_SOCKET_MAX_NUM): @@ -665,8 +666,8 @@ class ItemToolTip(ToolTip): return self.ClearToolTip() - self._curItemWindowType = None - self._curItemSlotIndex = slotIndex + self.itemWindowType = None + self.itemSlotIndex = slotIndex metinSlot = [] for i in xrange(player.METIN_SOCKET_MAX_NUM): @@ -683,8 +684,8 @@ class ItemToolTip(ToolTip): item.SelectItem(itemVnum) self.ClearToolTip() - self._curItemWindowType = None - self._curItemSlotIndex = privateShopSlotIndex + self.itemWindowType = None + self.itemSlotIndex = privateShopSlotIndex self.AppendSellingPrice(shop.GetPrivateShopItemPrice(invenType, invenPos)) metinSlot = [] @@ -702,8 +703,8 @@ class ItemToolTip(ToolTip): return self.ClearToolTip() - self._curItemWindowType = None - self._curItemSlotIndex = slotIndex + self.itemWindowType = None + self.itemSlotIndex = slotIndex metinSlot = [] for i in xrange(player.METIN_SOCKET_MAX_NUM): metinSlot.append(safebox.GetItemMetinSocket(slotIndex, i)) @@ -719,8 +720,8 @@ class ItemToolTip(ToolTip): return self.ClearToolTip() - self._curItemWindowType = None - self._curItemSlotIndex = slotIndex + self.itemWindowType = None + self.itemSlotIndex = slotIndex metinSlot = [] for i in xrange(player.METIN_SOCKET_MAX_NUM): metinSlot.append(safebox.GetMallItemMetinSocket(slotIndex, i)) @@ -1917,50 +1918,42 @@ class ItemToolTip(ToolTip): def AppendTimerBasedOnWearLastTime(self, metinSlot, getLimit): remainSec = metinSlot[0] - if 0 == remainSec: + if remainSec <= 0: self.AppendSpace(5) self.AppendTextLine(localeInfo.CANNOT_USE, self.DISABLE_COLOR) return - # For Dragon Soul items, the timer only ticks when: - # 1. The item is EQUIPPED on a deck (window_type == player.INVENTORY for DS equip slots) - # 2. AND the deck is ACTIVATED - # Items in DS inventory bag (window_type == player.DRAGON_SOUL_INVENTORY) never tick. - item.SelectItem(self.itemVnum) - isDragonSoulItem = item.GetItemType() == item.ITEM_TYPE_DS + isTimerActive = self.__IsTimerBasedOnWearActive() - if isDragonSoulItem: - isTimerActive = False - windowType = getattr(self, "_curItemWindowType", None) - # Only equipped DS items (in INVENTORY window, not DRAGON_SOUL_INVENTORY) can have active timers - isEquippedOnDeck = (windowType == player.INVENTORY) - if isEquippedOnDeck and self._dragonSoulWindow and getattr(self._dragonSoulWindow, "isActivated", False): - isTimerActive = True - else: - # Non-dragon soul items with LIMIT_TIMER_BASED_ON_WEAR - # Timer is active when equipped (this function is only called for such items) - isTimerActive = True - - # Not active -> show STATIC remaining time (no ticking) if not isTimerActive: self.AppendSpace(5) self.AppendTextLine(localeInfo.LEFT_TIME + ": " + localeInfo.RTSecondToDHMS(remainSec), self.NORMAL_COLOR) return - # Active -> show real-time countdown - # Cache maintains stable display across re-hovers (server updates remainSec periodically) - now = app.GetGlobalTimeStamp() - slotIndex = getattr(self, "_curItemSlotIndex", -1) - key = (self.itemVnum, slotIndex) - - cache = self._wearTimerCache.get(key) - if cache and cache.get("remainSec") == remainSec: - endTime = cache["endTime"] - else: - endTime = now + remainSec - self._wearTimerCache[key] = {"remainSec": remainSec, "endTime": endTime} - + endTime = self.__GetOrCreateCachedEndTime(remainSec) self.AppendMallItemLastTime(endTime, getLimit) + + def __IsTimerBasedOnWearActive(self): + item.SelectItem(self.itemVnum) + + if item.GetItemType() == item.ITEM_TYPE_DS: + isEquippedOnDeck = (self.itemWindowType == player.INVENTORY) + isDeckActivated = self.wndDragonSoul and self.wndDragonSoul.isActivated + return isEquippedOnDeck and isDeckActivated + + return True + + def __GetOrCreateCachedEndTime(self, remainSec): + key = (self.itemVnum, self.itemSlotIndex) + cache = self.dsActivatedTimeCache.get(key) + + if cache and cache["remainSec"] == remainSec: + return cache["endTime"] + + now = app.GetGlobalTimeStamp() + endTime = now + remainSec + self.dsActivatedTimeCache[key] = {"remainSec": remainSec, "endTime": endTime} + return endTime def AppendRealTimeStartFirstUseLastTime(self, item, metinSlot, limitIndex, getLimit): useCount = metinSlot[1]