Enable tooltip and countdown for all affects

This commit is contained in:
Mind Rapist
2026-02-18 18:48:29 +02:00
parent 5e5e49f048
commit f97568819c
3 changed files with 141 additions and 22 deletions

View File

@@ -7,14 +7,8 @@ This repository contains all client-side data, including locale files, configura
## 📋 Changelog ## 📋 Changelog
### 🐛 Bug Fixes ### 🐛 Bug Fixes
- **Font**: Fixed font for Arabic and Brazilian - **Affect tooltips**: ALL affects now display realtime countdowns, titles and are wrapped in tooltips! Realtime countdowns does not apply to infinite affects such as the Exorcism Scroll, the Concentrated Reading and the Medal of the Dragon (Death penalty prevention)
- **Loading**: Fixed loading background image for Arabic - **AFFECT_FIRE**: The Continuous Fire debuff has been added to the affects dictionary by name.
- **Crashes**: Fixed crashes for Arabic and Brazilian
### ⬆️ Improvements
- **Text hilighting**: Exposed text highlighting (selecting and clearing) to Python
- **Multiline dialog texts**: Question and Popup dialogs now support multiline translations! Translated strings containing `/n` or `\n` auto-break lines with trimming. Dialog height and position as well as inner element positions are auto-adjusting.
- **Refinement**: Updated translations for the refinement dialogs and added conditional failed messages (from official)
<br> <br>
<br> <br>

View File

@@ -753,8 +753,10 @@ class GameWindow(ui.ScriptWindow):
self.affectShower.ResetAffect(affect) self.affectShower.ResetAffect(affect)
# UNKNOWN_UPDATE # UNKNOWN_UPDATE
def BINARY_NEW_AddAffect(self, type, pointIdx, value, duration): # MR-16: Classic affect duration countdown
self.affectShower.BINARY_NEW_AddAffect(type, pointIdx, value, duration) def BINARY_NEW_AddAffect(self, type, pointIdx, value, duration, affFlag=0):
self.affectShower.BINARY_NEW_AddAffect(type, pointIdx, value, duration, affFlag)
# MR-16: Classic affect duration countdown
if chr.NEW_AFFECT_DRAGON_SOUL_DECK1 == type or chr.NEW_AFFECT_DRAGON_SOUL_DECK2 == type: if chr.NEW_AFFECT_DRAGON_SOUL_DECK1 == type or chr.NEW_AFFECT_DRAGON_SOUL_DECK2 == type:
self.interface.DragonSoulActivate(type - chr.NEW_AFFECT_DRAGON_SOUL_DECK1) self.interface.DragonSoulActivate(type - chr.NEW_AFFECT_DRAGON_SOUL_DECK1)

View File

@@ -269,6 +269,14 @@ class AutoPotionImage(ui.ExpandedImageBox):
class AffectImage(ui.ExpandedImageBox): class AffectImage(ui.ExpandedImageBox):
# MR-16: Canonical item vnum for each infinite-duration affect (proto lookup, no inventory)
INFINITE_AFFECT_ITEM_VNUM = {
chr.NEW_AFFECT_NO_DEATH_PENALTY : 71004,
chr.NEW_AFFECT_SKILL_BOOK_BONUS : 71094,
chr.NEW_AFFECT_SKILL_BOOK_NO_DELAY : 71001,
}
# MR-16: -- END OF -- Canonical item vnum for each infinite-duration affect
def __init__(self): def __init__(self):
ui.ExpandedImageBox.__init__(self) ui.ExpandedImageBox.__init__(self)
@@ -407,9 +415,14 @@ class AffectImage(ui.ExpandedImageBox):
self.toolTip.ShowToolTip() self.toolTip.ShowToolTip()
return return
# MR-16: Classic affect duration countdown
if self.__ShouldShowInfiniteToolTip():
return
# MR-16: -- END OF -- Classic affect duration countdown
self.SetToolTipText(self.description, 0, 40) self.SetToolTipText(self.description, 0, 40)
#독일버전에서 시간을 제거하기 위해서 사용 # Used to suppress the time countdown display (German version)
def __UpdateDescription2(self): def __UpdateDescription2(self):
if not self.description: if not self.description:
return return
@@ -428,6 +441,12 @@ class AffectImage(ui.ExpandedImageBox):
def __ShouldShowTimedToolTip(self): def __ShouldShowTimedToolTip(self):
return self.isClocked and self.endTime > 0 and not self.__IsAutoPotionAffect() return self.isClocked and self.endTime > 0 and not self.__IsAutoPotionAffect()
# MR-16: Classic affect duration countdown
def __ShouldShowInfiniteToolTip(self):
return (self.isClocked and self.description and self.endTime == 0
and not self.__IsAutoPotionAffect() and not self.__IsDragonSoulAffect())
# MR-16: -- END OF -- Classic affect duration countdown
def __UpdateTimedDescription(self, remainSec): def __UpdateTimedDescription(self, remainSec):
if not self.description: if not self.description:
return return
@@ -439,6 +458,34 @@ class AffectImage(ui.ExpandedImageBox):
self.toolTip.AppendTextLine("(%s : %s)" % (localeInfo.LEFT_TIME, localeInfo.RTSecondToDHMS(remainSec))) self.toolTip.AppendTextLine("(%s : %s)" % (localeInfo.LEFT_TIME, localeInfo.RTSecondToDHMS(remainSec)))
self.toolTip.ResizeToolTip() self.toolTip.ResizeToolTip()
# MR-16: Classic affect duration countdown
def __GetInfiniteAffectItemName(self):
vnum = self.INFINITE_AFFECT_ITEM_VNUM.get(self.affect)
if not vnum:
return None
item.SelectItem(vnum)
return item.GetItemName()
def __UpdateInfiniteDescription(self):
if not self.description:
return
self.__EnsureToolTip()
self.toolTip.ClearToolTip()
itemName = self.__GetInfiniteAffectItemName()
if itemName:
self.toolTip.SetTitle(itemName)
self.toolTip.AppendTextLine(self.description)
self.toolTip.ResizeToolTip()
# MR-16: -- END OF -- Classic affect duration countdown
def __IsDragonSoulAffect(self): def __IsDragonSoulAffect(self):
return self.affect in (chr.NEW_AFFECT_DRAGON_SOUL_DECK1, chr.NEW_AFFECT_DRAGON_SOUL_DECK2) return self.affect in (chr.NEW_AFFECT_DRAGON_SOUL_DECK1, chr.NEW_AFFECT_DRAGON_SOUL_DECK2)
@@ -520,12 +567,14 @@ class AffectImage(ui.ExpandedImageBox):
if self.toolTip: if self.toolTip:
self.toolTip.ShowToolTip() self.toolTip.ShowToolTip()
return return
if self.__IsDragonSoulAffect(): if self.__IsDragonSoulAffect():
self.__UpdateDragonSoulDescription() self.__UpdateDragonSoulDescription()
if self.toolTip: if self.toolTip:
self.toolTip.ShowToolTip() self.toolTip.ShowToolTip()
return return
if self.__ShouldShowTimedToolTip(): if self.__ShouldShowTimedToolTip():
remainSec = max(0, self.endTime - app.GetGlobalTimeStamp()) remainSec = max(0, self.endTime - app.GetGlobalTimeStamp())
self.__UpdateTimedDescription(remainSec) self.__UpdateTimedDescription(remainSec)
@@ -533,6 +582,16 @@ class AffectImage(ui.ExpandedImageBox):
if self.toolTip: if self.toolTip:
self.toolTip.ShowToolTip() self.toolTip.ShowToolTip()
return return
# MR-16: Classic affect duration countdown
if self.__ShouldShowInfiniteToolTip():
self.__UpdateInfiniteDescription()
if self.toolTip:
self.toolTip.ShowToolTip()
return
# MR-16: -- END OF -- Classic affect duration countdown
if self.toolTipText: if self.toolTipText:
self.toolTipText.Show() self.toolTipText.Show()
@@ -580,7 +639,9 @@ class AffectShower(ui.Window):
chr.AFFECT_JEUNGRYEOK : (localeInfo.SKILL_JEUNGRYEOK, "d:/ymir work/ui/skill/shaman/jeungryeok_03.sub",), chr.AFFECT_JEUNGRYEOK : (localeInfo.SKILL_JEUNGRYEOK, "d:/ymir work/ui/skill/shaman/jeungryeok_03.sub",),
chr.AFFECT_PABEOP : (localeInfo.SKILL_PABEOP, "d:/ymir work/ui/skill/sura/pabeop_03.sub",), chr.AFFECT_PABEOP : (localeInfo.SKILL_PABEOP, "d:/ymir work/ui/skill/sura/pabeop_03.sub",),
chr.AFFECT_FALLEN_CHEONGEUN : (localeInfo.SKILL_CHEONGEUN, "d:/ymir work/ui/skill/warrior/cheongeun_03.sub",), chr.AFFECT_FALLEN_CHEONGEUN : (localeInfo.SKILL_CHEONGEUN, "d:/ymir work/ui/skill/warrior/cheongeun_03.sub",),
28 : (localeInfo.SKILL_FIRE, "d:/ymir work/ui/skill/sura/hwayeom_03.sub",), # MR-16: Added AFFECT_FIRE to Affects Shower
chr.AFFECT_FIRE : (localeInfo.SKILL_FIRE, "d:/ymir work/ui/skill/sura/hwayeom_03.sub",),
# MR-16: -- END OF -- Added AFFECT_FIRE to Affects Shower
chr.AFFECT_CHINA_FIREWORK : (localeInfo.SKILL_POWERFUL_STRIKE, "d:/ymir work/ui/skill/common/affect/powerfulstrike.sub",), chr.AFFECT_CHINA_FIREWORK : (localeInfo.SKILL_POWERFUL_STRIKE, "d:/ymir work/ui/skill/common/affect/powerfulstrike.sub",),
#64 - END #64 - END
@@ -597,7 +658,7 @@ class AffectShower(ui.Window):
chr.NEW_AFFECT_SKILL_BOOK_BONUS : (localeInfo.TOOLTIP_APPLY_SKILL_BOOK_BONUS, "d:/ymir work/ui/skill/common/affect/gold_premium.sub"), chr.NEW_AFFECT_SKILL_BOOK_BONUS : (localeInfo.TOOLTIP_APPLY_SKILL_BOOK_BONUS, "d:/ymir work/ui/skill/common/affect/gold_premium.sub"),
chr.NEW_AFFECT_SKILL_BOOK_NO_DELAY : (localeInfo.TOOLTIP_APPLY_SKILL_BOOK_NO_DELAY, "d:/ymir work/ui/skill/common/affect/gold_premium.sub"), chr.NEW_AFFECT_SKILL_BOOK_NO_DELAY : (localeInfo.TOOLTIP_APPLY_SKILL_BOOK_NO_DELAY, "d:/ymir work/ui/skill/common/affect/gold_premium.sub"),
# <EFBFBD>ڵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> hp, sp # Auto HP/SP recovery affects
chr.NEW_AFFECT_AUTO_HP_RECOVERY : (localeInfo.TOOLTIP_AUTO_POTION_REST, "d:/ymir work/ui/pattern/auto_hpgauge/05.dds"), chr.NEW_AFFECT_AUTO_HP_RECOVERY : (localeInfo.TOOLTIP_AUTO_POTION_REST, "d:/ymir work/ui/pattern/auto_hpgauge/05.dds"),
chr.NEW_AFFECT_AUTO_SP_RECOVERY : (localeInfo.TOOLTIP_AUTO_POTION_REST, "d:/ymir work/ui/pattern/auto_spgauge/05.dds"), chr.NEW_AFFECT_AUTO_SP_RECOVERY : (localeInfo.TOOLTIP_AUTO_POTION_REST, "d:/ymir work/ui/pattern/auto_spgauge/05.dds"),
#chr.NEW_AFFECT_AUTO_HP_RECOVERY : (localeInfo.TOOLTIP_AUTO_POTION_REST, "d:/ymir work/ui/skill/common/affect/gold_premium.sub"), #chr.NEW_AFFECT_AUTO_HP_RECOVERY : (localeInfo.TOOLTIP_AUTO_POTION_REST, "d:/ymir work/ui/skill/common/affect/gold_premium.sub"),
@@ -621,7 +682,7 @@ class AffectShower(ui.Window):
} }
if app.ENABLE_DRAGON_SOUL_SYSTEM: if app.ENABLE_DRAGON_SOUL_SYSTEM:
# <EFBFBD><EFBFBD>ȥ<EFBFBD><EFBFBD> õ, <20><> <20><>. # Dragon Soul Sky Deck, Ground Deck.
AFFECT_DATA_DICT[chr.NEW_AFFECT_DRAGON_SOUL_DECK1] = (localeInfo.TOOLTIP_DRAGON_SOUL_DECK1, "d:/ymir work/ui/dragonsoul/buff_ds_sky1.tga") AFFECT_DATA_DICT[chr.NEW_AFFECT_DRAGON_SOUL_DECK1] = (localeInfo.TOOLTIP_DRAGON_SOUL_DECK1, "d:/ymir work/ui/dragonsoul/buff_ds_sky1.tga")
AFFECT_DATA_DICT[chr.NEW_AFFECT_DRAGON_SOUL_DECK2] = (localeInfo.TOOLTIP_DRAGON_SOUL_DECK2, "d:/ymir work/ui/dragonsoul/buff_ds_land1.tga") AFFECT_DATA_DICT[chr.NEW_AFFECT_DRAGON_SOUL_DECK2] = (localeInfo.TOOLTIP_DRAGON_SOUL_DECK2, "d:/ymir work/ui/dragonsoul/buff_ds_land1.tga")
@@ -637,6 +698,9 @@ class AffectShower(ui.Window):
self.lovePointImage = None self.lovePointImage = None
self.autoPotionImageHP = AutoPotionImage() self.autoPotionImageHP = AutoPotionImage()
self.autoPotionImageSP = AutoPotionImage() self.autoPotionImageSP = AutoPotionImage()
# MR-16: Classic affect duration countdown
self.pendingClassicDurations = {}
# MR-16: -- END OF -- Classic affect duration countdown
self.SetPosition(10, 10) self.SetPosition(10, 10)
self.Show() self.Show()
@@ -645,21 +709,46 @@ class AffectShower(ui.Window):
self.horseImage = None self.horseImage = None
self.lovePointImage = None self.lovePointImage = None
self.affectImageDict = {} self.affectImageDict = {}
# MR-16: Classic affect duration countdown
self.pendingClassicDurations = {}
# MR-16: -- END OF -- Classic affect duration countdown
self.__ArrangeImageList() self.__ArrangeImageList()
def ClearAffects(self): ## <EFBFBD><EFBFBD>ų <20><><EFBFBD><EFBFBD>Ʈ<EFBFBD><C6AE> <20><><EFBFBD>۴ϴ<DBB4>. def ClearAffects(self): ## Clears skill affects on death; MALL (non-skill) affects survive.
self.living_affectImageDict={} self.living_affectImageDict = {}
for key, image in list(self.affectImageDict.items()): for key, image in list(self.affectImageDict.items()):
if not image.IsSkillAffect(): if not image.IsSkillAffect():
self.living_affectImageDict[key] = image self.living_affectImageDict[key] = image
self.affectImageDict = self.living_affectImageDict self.affectImageDict = self.living_affectImageDict
# MR-16: Classic affect duration countdown
self.pendingClassicDurations = {}
# MR-16: -- END OF -- Classic affect duration countdown
self.__ArrangeImageList() self.__ArrangeImageList()
def BINARY_NEW_AddAffect(self, type, pointIdx, value, duration): def BINARY_NEW_AddAffect(self, type, pointIdx, value, duration, affFlag = 0):
print(("BINARY_NEW_AddAffect", type, pointIdx, value, duration)) print(("BINARY_NEW_AddAffect", type, pointIdx, value, duration, affFlag))
if type < 500: if type < 500:
# MR-16: Classic affect duration countdown
# affFlag is the server-side AFF_* enum value (1-based).
# The client's chr.AFFECT_* enum is 0-based, so affBit = affFlag - 1.
# This covers all classic affects without any static mapping.
if 0 < duration <= self.INFINITE_AFFECT_DURATION and affFlag > 0:
affBit = affFlag - 1
if affBit in self.AFFECT_DATA_DICT:
if affBit in self.affectImageDict:
# Icon already exists (CHARACTER_UPDATE arrived first) - update directly
self.__ApplyClassicDuration(affBit, duration)
else:
# Icon not yet created - store for when __AppendAffect fires
self.pendingClassicDurations[affBit] = duration
# MR-16: -- END OF -- Classic affect duration countdown
return return
if type == chr.NEW_AFFECT_MALL: if type == chr.NEW_AFFECT_MALL:
@@ -673,7 +762,7 @@ class AffectShower(ui.Window):
if affect not in self.AFFECT_DATA_DICT: if affect not in self.AFFECT_DATA_DICT:
return return
## <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><>ȣ, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Duration <20><> 0 <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>Ѵ<EFBFBD>. ## As an exception, the following affects have their Duration forced to 0.
if affect == chr.NEW_AFFECT_NO_DEATH_PENALTY or\ if affect == chr.NEW_AFFECT_NO_DEATH_PENALTY or\
affect == chr.NEW_AFFECT_SKILL_BOOK_BONUS or\ affect == chr.NEW_AFFECT_SKILL_BOOK_BONUS or\
affect == chr.NEW_AFFECT_AUTO_SP_RECOVERY or\ affect == chr.NEW_AFFECT_AUTO_SP_RECOVERY or\
@@ -846,6 +935,32 @@ class AffectShower(ui.Window):
image.Show() image.Show()
self.affectImageDict[affect] = image self.affectImageDict[affect] = image
# MR-16: Classic affect duration countdown
# Apply pending duration if AFFECT_ADD arrived before CHARACTER_UPDATE
if affect in self.pendingClassicDurations:
duration = self.pendingClassicDurations.pop(affect)
self.__ApplyClassicDuration(affect, duration)
# MR-16: -- END OF -- Classic affect duration countdown
# MR-16: Classic affect duration countdown
def __ApplyClassicDuration(self, affBit, duration):
image = self.affectImageDict.get(affBit)
if not image:
return
affectData = self.AFFECT_DATA_DICT.get(affBit)
if not affectData:
return
name = affectData[0]
skillIndex = player.AffectIndexToSkillIndex(affBit)
if 0 != skillIndex:
name = skill.GetSkillName(skillIndex)
image.SetDescription(name)
image.SetDuration(duration)
# MR-16: -- END OF -- Classic affect duration countdown
def __RemoveAffect(self, affect): def __RemoveAffect(self, affect):
""" """
if affect == chr.NEW_AFFECT_AUTO_SP_RECOVERY: if affect == chr.NEW_AFFECT_AUTO_SP_RECOVERY:
@@ -897,8 +1012,16 @@ class AffectShower(ui.Window):
image.UpdateAutoPotionDescription() image.UpdateAutoPotionDescription()
continue continue
# MR-16: Classic affect duration countdown
if not image.IsSkillAffect(): if not image.IsSkillAffect():
image.UpdateDescription() image.UpdateDescription()
continue
# Classic (skill) affects also need UpdateDescription when they
# carry a timed duration captured from the AFFECT_ADD packet.
if image.endTime > 0:
image.UpdateDescription()
# MR-16: -- END OF -- Classic affect duration countdown
except Exception as e: except Exception as e:
print(("AffectShower::OnUpdate error : ", e)) print(("AffectShower::OnUpdate error : ", e))
# MR-12: -- END OF -- Fix realtime countdown auto-start # MR-12: -- END OF -- Fix realtime countdown auto-start