Skip to content

refactor(items): the tooltip's texts by item level come from the data - #819

Merged
Mosch0512 merged 29 commits into
sven-n:mainfrom
ezequielzacca:feat/item-description-levels
Oct 24, 2026
Merged

Mosch0512 merged 29 commits into
sven-n:mainfrom
ezequielzacca:feat/item-description-levels

Conversation

@ezequielzacca

@ezequielzacca ezequielzacca commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

This step's own changes: compare with #818. The diff of this PR also holds every earlier step of #815, since a PR from a fork can only target this repository's branches.

Summary

Step 4b of #815 (data-driven item tooltips). Stacked on #818 (→ #817 → #816 → #813): only the last commit, refactor(items): the tooltip's texts by item level come from the data, is this step.

A description line can be limited to item levels:

{"text": {"en": "Warp to the corresponding area after 3 seconds", …}, "levels": [1, 8]}
  • levels is [first, last], both included. Left out, the line shows at every level.
  • It's written on one line, and only for a line that has it.
  • Errors: anything other than two levels from 0 to 15 with the first not above the last ([8, 1], [0, 16], [3], 3, [1.5, 2]).
  • The tooltip draws only the lines of the item's level (ItemDescriptionLine::IsShownAt).

With it, the last fixed texts of RenderItemInfo()'s item chain are descriptions:

Item Lines
Town Portal scroll (14,10) "Warp to the corresponding area after 3 seconds" at +1 to +8
Contract Summon (13,7) Archer (+0), Spearman (+1), yellow
Life Stone (13,11) Scroll of Guardian (+0), Place Life Stone (+1), yellow
Loch's Feather (13,14) its two uses, +0 and +1
Fruits (13,15) the stat's two lines at each level +0 to +4; "It can be used with item removed." in dark red; "Can be equipped by Dark Lord" at +4
Scroll of Archangel, Blood Bone (13,16), (13,17) "Used when creating a Cloak of Invisibility"
Invisibility Cloak (13,18) its text, a blank line and two more lines
Spirit (13,31) Dark Horse (+0) and Dark Raven (+1) resurrection
Siege Potion (14,7) three dark red lines each at +0 and +1
Rena (14,21) its three uses, +1 to +3
  • Texts: all 11 languages, converted as in refactor(items): the wings', capes' and pets' tooltip values come from their data #818. A text the tooltip used as a printf format has %% as %; the Town Portal's %d is the 3 it printed. In Polish its line says "za 3 sekundy", the right plural for 3, where the game printed "za 3 sekund" ("%d sekund" with 3): kept on purpose. A text printed through %ls stays as it was. The Fruits' "ENG Increases 1~3 stat points" joins the stat's and the line's text with a space, as "%ls %ls" did.
  • Removed:
    • the cases (139 lines);
    • the 18 .resx texts no other code prints, in every language. No Lookup() range reaches their legacy ids. The stat names, Archer, Spearman, Dark Lord and the others still printed elsewhere stay.
  • One fix: the old code drew a stale line of the text list at the Contract Summon's, Life Stone's and Rena's levels without a text (+2 and up, Rena +0), and two at the Fruits' +5 to +15: it moved to the next line without writing one. Those levels show nothing now (Fruits only exist at +0 to +4).
  • Comparison test: "The tooltip texts by item level come from the descriptions as from the old code" compares every level 0–15 of the 11 items with the old code's lines and colors. It keeps a copy of the old texts.

The Armor of Guardsman's Chaos Castle table and the Lost Map / Symbol of Kundun stay code: they read the hero's level and other tables. They'd only move with step 5.

Screenshots

Before (#818) and after, the same.

Command Fruits (+4)

Legacy theme Modern theme
Before
After
The item data of the "after" images (13,15)

src/bin/Data/Items/Group13_Helper.json:

{
  "number": 15,
  "name": {
    "en": "Fruits",
    "de": "Früchte",
    "es": "Fruto",
    "pt": "Fruta"
  },
  "tags": ["valuable"],
  "description": [
    {
      "text": {
        "en": "ENG Increases 1~3 stat points",
        "de": "ENG Erhöht 1~3 Statuspunkte",
        "es": "ESP Aumenta 1~3 puntos de estadísticas",
        "id": "Bahasa Inggris Meningkatkan 1~3 poin stat",
        "ja": "エナジー ステータス1~3ポイント生成",
        "pl": "ENG Zwiększa 1-3 punkty statystyk",
        "pt": "POR Aumenta 1~3 pontos de estatísticas",
        "ru": "АНГЛ Увеличивает 1~3 очка характеристик.",
        "tl": "ENG Tumataas ng 1~3 stat point",
        "uk": "ENG Збільшує 1–3 очки статистики",
        "zh-TW": "英語 增加1~3點屬性點"
      },
      "levels": [0, 0]
    },
    {
      "text": {
        "en": "ENG Possible to decrease stat 1~9 point",
        "de": "ENG Verringert einen Wert um 1~9 Punkte",
        "es": "ESP Es posible disminuir las estadísticas de 1 a 9 puntos.",
        "id": "Bahasa Inggris Kemungkinan untuk menurunkan stat 1~9 poin",
        "ja": "エナジー 減少ステータス1~9ポイント可能",
        "pl": "ENG Możliwe zmniejszenie statystyki o 1~9 punktów",
        "pt": "POR Possível diminuir estatísticas de 1 a 9 pontos",
        "ru": "АНГЛ Возможно понижение показателя с 1 по 9.",
        "tl": "ENG Posibleng bawasan ang stat 1~9 point",
        "uk": "ENG Можливе зниження характеристики на 1~9 балів",
        "zh-TW": "英語 可減少屬性1~9點"
      },
      "levels": [0, 0]
    },
    {
      "text": {
        "en": "STA Increases 1~3 stat points",
        "de": "VIT Erhöht 1~3 Statuspunkte",
        "es": "STA Aumenta 1~3 puntos de estadísticas",
        "id": "STA Meningkatkan 1~3 poin stat",
        "ja": "体力 ステータス1~3ポイント生成",
        "pl": "STA Zwiększa 1-3 punkty statystyk",
        "pt": "STA Aumenta 1~3 pontos de estatísticas",
        "ru": "СТА Увеличивает 1~3 очка характеристик.",
        "tl": "STA Tumataas ng 1~3 stat point",
        "uk": "STA Збільшує 1–3 очки статистики",
        "zh-TW": "史塔 增加1~3點屬性點"
      },
      "levels": [1, 1]
    },
    {
      "text": {
        "en": "STA Possible to decrease stat 1~9 point",
        "de": "VIT Verringert einen Wert um 1~9 Punkte",
        "es": "STA Es posible disminuir las estadísticas de 1 a 9 puntos.",
        "id": "STA Kemungkinan untuk menurunkan stat 1~9 poin",
        "ja": "体力 減少ステータス1~9ポイント可能",
        "pl": "STA Możliwe zmniejszenie statystyki o 1~9 punktów",
        "pt": "STA Possível diminuir estatísticas de 1 a 9 pontos",
        "ru": "СТА Возможно понижение показателя с 1 по 9.",
        "tl": "STA Posibleng bawasan ang stat 1~9 point",
        "uk": "STA Можливе зниження характеристики на 1~9 балів",
        "zh-TW": "史塔 可減少屬性1~9點"
      },
      "levels": [1, 1]
    },
    {
      "text": {
        "en": "AGI Increases 1~3 stat points",
        "de": "BEW Erhöht 1~3 Statuspunkte",
        "es": "AGI Aumenta 1~3 puntos de estadísticas",
        "id": "AGI Meningkatkan 1~3 poin stat",
        "ja": "敏捷性 ステータス1~3ポイント生成",
        "pl": "AGI Zwiększa 1-3 punkty statystyk",
        "pt": "AGI Aumenta 1~3 pontos de estatísticas",
        "ru": "ОИИ Увеличивает 1~3 очка характеристик.",
        "tl": "AGI Tumataas ng 1~3 stat point",
        "uk": "AGI Збільшує 1–3 очки статистики",
        "zh-TW": "通用人工智慧 增加1~3點屬性點"
      },
      "levels": [2, 2]
    },
    {
      "text": {
        "en": "AGI Possible to decrease stat 1~9 point",
        "de": "BEW Verringert einen Wert um 1~9 Punkte",
        "es": "AGI Es posible disminuir las estadísticas de 1 a 9 puntos.",
        "id": "AGI Kemungkinan untuk menurunkan stat 1~9 poin",
        "ja": "敏捷性 減少ステータス1~9ポイント可能",
        "pl": "AGI Możliwe zmniejszenie statystyki o 1~9 punktów",
        "pt": "AGI Possível diminuir estatísticas de 1 a 9 pontos",
        "ru": "ОИИ Возможно понижение показателя с 1 по 9.",
        "tl": "AGI Posibleng bawasan ang stat 1~9 point",
        "uk": "AGI Можливе зниження характеристики на 1~9 балів",
        "zh-TW": "通用人工智慧 可減少屬性1~9點"
      },
      "levels": [2, 2]
    },
    {
      "text": {
        "en": "STR Increases 1~3 stat points",
        "de": "STÄ Erhöht 1~3 Statuspunkte",
        "es": "FUE Aumenta 1~3 puntos de estadísticas",
        "id": "STR Meningkatkan 1~3 poin stat",
        "ja": "力 ステータス1~3ポイント生成",
        "pl": "STR Zwiększa 1-3 punkty statystyk",
        "pt": "FOR Aumenta 1~3 pontos de estatísticas",
        "ru": "СТР Увеличивает 1~3 очка характеристик.",
        "tl": "STR Tumataas ng 1~3 stat point",
        "uk": "ВУЛ Збільшує 1–3 очки статистики",
        "zh-TW": "STR 增加1~3點屬性點"
      },
      "levels": [3, 3]
    },
    {
      "text": {
        "en": "STR Possible to decrease stat 1~9 point",
        "de": "STÄ Verringert einen Wert um 1~9 Punkte",
        "es": "FUE Es posible disminuir las estadísticas de 1 a 9 puntos.",
        "id": "STR Kemungkinan untuk menurunkan stat 1~9 poin",
        "ja": "力 減少ステータス1~9ポイント可能",
        "pl": "STR Możliwe zmniejszenie statystyki o 1~9 punktów",
        "pt": "FOR Possível diminuir estatísticas de 1 a 9 pontos",
        "ru": "СТР Возможно понижение показателя с 1 по 9.",
        "tl": "STR Posibleng bawasan ang stat 1~9 point",
        "uk": "ВУЛ Можливе зниження характеристики на 1~9 балів",
        "zh-TW": "STR 可減少屬性1~9點"
      },
      "levels": [3, 3]
    },
    {
      "text": {
        "en": "Command Increases 1~3 stat points",
        "de": "Führung Erhöht 1~3 Statuspunkte",
        "es": "Dominio Aumenta 1~3 puntos de estadísticas",
        "id": "Memerintah Meningkatkan 1~3 poin stat",
        "ja": "統率 ステータス1~3ポイント生成",
        "pl": "Rozkaz Zwiększa 1-3 punkty statystyk",
        "pt": "Comando Aumenta 1~3 pontos de estatísticas",
        "ru": "Команда Увеличивает 1~3 очка характеристик.",
        "tl": "Utos Tumataas ng 1~3 stat point",
        "uk": "Команда Збільшує 1–3 очки статистики",
        "zh-TW": "命令 增加1~3點屬性點"
      },
      "levels": [4, 4]
    },
    {
      "text": {
        "en": "Command Possible to decrease stat 1~9 point",
        "de": "Führung Verringert einen Wert um 1~9 Punkte",
        "es": "Dominio Es posible disminuir las estadísticas de 1 a 9 puntos.",
        "id": "Memerintah Kemungkinan untuk menurunkan stat 1~9 poin",
        "ja": "統率 減少ステータス1~9ポイント可能",
        "pl": "Rozkaz Możliwe zmniejszenie statystyki o 1~9 punktów",
        "pt": "Comando Possível diminuir estatísticas de 1 a 9 pontos",
        "ru": "Команда Возможно понижение показателя с 1 по 9.",
        "tl": "Utos Posibleng bawasan ang stat 1~9 point",
        "uk": "Команда Можливе зниження характеристики на 1~9 балів",
        "zh-TW": "命令 可減少屬性1~9點"
      },
      "levels": [4, 4]
    },
    {
      "text": {
        "en": "It can be used with item removed.",
        "de": "Kann nur verwendet werden, wenn die Ausrüstung abgelegt ist.",
        "es": "Se puede utilizar sin el artículo.",
        "id": "Dapat digunakan dengan item dilepas.",
        "ja": "アイテムの未装備の際、使用可能",
        "pl": "Można go używać po usunięciu przedmiotu.",
        "pt": "Pode ser usado com item removido.",
        "ru": "Его можно использовать, если предмет удален.",
        "tl": "Maaari itong magamit kapag tinanggal ang item.",
        "uk": "Його можна використовувати з вилученим предметом.",
        "zh-TW": "它可以在移除物品的情況下使用。"
      },
      "color": "darkRed",
      "levels": [0, 4]
    },
    {
      "text": {
        "en": "Can be equipped by Dark Lord",
        "de": "Kann von Dunkler Lord ausgerüstet werden",
        "es": "Puede ser equipado con señor oscuro",
        "id": "Dapat dilengkapi dengan Pangeran Kegelapan",
        "ja": "ダークロード使用可能",
        "pl": "Może być wyposażony w Dark Lord",
        "pt": "Pode ser equipado por Senhor das Trevas",
        "ru": "Может быть оснащен Темный Лорд.",
        "tl": "Maaaring gamitan ng Dark Lord",
        "uk": "Може бути оснащений Темний Лорд",
        "zh-TW": "可由黑暗領主配備"
      },
      "levels": [4, 4]
    }
  ]
  …
}

Town Portal Scroll +3

Legacy theme Modern theme
Before
After
The item data of the "after" images (14,10)

src/bin/Data/Items/Group14_Potion.json:

{
  "number": 10,
  "name": {
    "en": "Town Portal Scroll",
    "de": "Stadtportal-Schriftrolle",
    "es": "Rollo del Portal del Pueblo",
    "pt": "Perg. de Portal para a Cidade"
  },
  "description": [
    {
      "text": {
        "en": "Warp to the corresponding area after 3 seconds",
        "de": "Warp in den entsprechenden Bereich in 3 Sekunden",
        "es": "Deformarse al área correspondiente después de 3 segundos",
        "id": "Warp ke area yang sesuai setelah 3 detik",
        "ja": "3秒後該当地域に移動",
        "pl": "Przeskocz do odpowiedniego obszaru po 3 sekundach",
        "pt": "Warp para a área correspondente após 3 segundos",
        "ru": "Переместитесь в соответствующую область через 3 секунд.",
        "tl": "Warp sa kaukulang lugar pagkatapos ng 3 segundo",
        "uk": "Деформація до відповідної області через 3 секунд",
        "zh-TW": "3秒後扭曲到對應區域"
      },
      "levels": [1, 8]
    }
  ]
  …
}

Siege Potion +1 (Potion of soul)

Legacy theme Modern theme
Before
After
The item data of the "after" images (14,7)

src/bin/Data/Items/Group14_Potion.json:

{
  "number": 7,
  "name": {
    "en": "Siege Potion",
    "de": "Belagerungstrank",
    "es": "Pócima de Asedio",
    "pt": "Poção de sítio"
  },
  "description": [
    {
      "text": {
        "en": "Damage +20% increase effect",
        "de": "Effekt: Schaden +20%",
        "es": "Daño +20% iefecto de aumento",
        "id": "Kerusakan +20% imeningkatkan efek",
        "ja": "攻撃力+20%上昇效果",
        "pl": "Efekt zwiększenia obrażeń o 20%",
        "pt": "Dano +20% infeito de aumento",
        "ru": "Урон +20% inувеличение эффекта",
        "tl": "Pinsala +20% ipagtaas na epekto",
        "uk": "Пошкодження +20% inпосилення ефекту",
        "zh-TW": "傷害+20% i效果增加"
      },
      "color": "darkRed",
      "levels": [0, 0]
    },
    {
      "text": {
        "en": "Duration 60 seconds",
        "de": "Dauer 60 Sekunden",
        "es": "Duración 60 segundos",
        "id": "Durasi 60 detik",
        "ja": "持続時間120秒",
        "pl": "Czas trwania 60 sekund",
        "pt": "Duração 60 segundos",
        "ru": "Продолжительность 60 секунд",
        "tl": "Tagal 60 segundo",
        "uk": "Тривалість 60 секунд",
        "zh-TW": "持續時間 60 秒"
      },
      "color": "darkRed",
      "levels": [0, 0]
    },
    {
      "text": {
        "en": "Only applicable for castle gate and statue",
        "de": "Gilt nur für Burgtor und Statue",
        "es": "Sólo aplicable a la puerta del castillo y a la estatua.",
        "id": "Hanya berlaku untuk gerbang kastil dan patung",
        "ja": "城門、石像のみ適用",
        "pl": "Dotyczy tylko bramy zamkowej i posągu",
        "pt": "Aplicável apenas para portão de castelo e estátua",
        "ru": "Применимо только для ворот замка и статуи.",
        "tl": "Naaangkop lamang para sa gate ng kastilyo at rebulto",
        "uk": "Застосовується лише для воріт і статуї замку",
        "zh-TW": "僅適用於城堡大門和雕像"
      },
      "color": "darkRed",
      "levels": [0, 0]
    },
    {
      "text": {
        "en": "Increase +8 AG recovery speed",
        "de": "Erhöht die AG-Regeneration um +8",
        "es": "Aumentar la velocidad de recuperación de +8 AG",
        "id": "Meningkatkan kecepatan pemulihan +8 AG",
        "ja": "AG回復のスピード増加+8",
        "pl": "Zwiększa prędkość odzyskiwania +8 AG",
        "pt": "Aumenta a velocidade de recuperação de +8 AG",
        "ru": "Увеличение скорости восстановления AG на +8.",
        "tl": "Taasan ang +8 AG na bilis ng pagbawi",
        "uk": "Збільшити швидкість відновлення AG на +8",
        "zh-TW": "增加+8 AG恢復速度"
      },
      "color": "darkRed",
      "levels": [1, 1]
    },
    {
      "text": {
        "en": "Increase resistance of Lightning and Ice",
        "de": "Erhöht die Resistenz gegen Blitz und Eis",
        "es": "Aumenta la resistencia de Rayo y Hielo.",
        "id": "Meningkatkan ketahanan terhadap Petir dan Es",
        "ja": "サンダー、アイス抵抗力向上",
        "pl": "Zwiększa odporność na błyskawice i lód",
        "pt": "Aumenta a resistência de Raios e Gelo",
        "ru": "Увеличение сопротивления молнии и льду.",
        "tl": "Dagdagan ang resistensya ng Kidlat at Yelo",
        "uk": "Збільшити опір блискавки та льоду",
        "zh-TW": "增加閃電和冰的抗性"
      },
      "color": "darkRed",
      "levels": [1, 1]
    },
    {
      "text": {
        "en": "Attacking speed will increase +20",
        "de": "Die Angriffsgeschwindigkeit wird um +20 erhöht",
        "es": "La velocidad de ataque aumentará +20",
        "id": "Kecepatan menyerang akan meningkat +20",
        "ja": "攻撃スピードも+20増加します。",
        "pl": "Szybkość ataku wzrośnie o +20",
        "pt": "A velocidade de ataque aumentará +20",
        "ru": "Скорость атаки увеличится +20.",
        "tl": "Ang bilis ng pag-atake ay tataas ang +20",
        "uk": "Швидкість атаки збільшиться на +20",
        "zh-TW": "攻擊速度增加+20"
      },
      "color": "darkRed",
      "levels": [1, 1]
    }
  ]
  …
}

Invisibility Cloak +1

Legacy theme Modern theme
Before
After
The item data of the "after" images (13,18)

src/bin/Data/Items/Group13_Helper.json:

{
  "number": 18,
  "name": {
    "en": "Invisibility Cloak",
    "de": "Umhang der Unsichtbarkeit",
    "es": "Manto de Invisibilidad",
    "pt": "Manto da invisibilidade"
  },
  "description": [
    {
      "text": {
        "en": "Used when entering Blood Castle",
        "de": "Wird beim Betreten der Blutburg verwendet",
        "es": "Se utiliza al entrar al Castillo de Sangre.",
        "id": "Digunakan saat memasuki Blood Castle",
        "ja": "ブラッドキャッスルに入る時に使われます。",
        "pl": "Używane podczas wchodzenia do Blood Castle",
        "pt": "Usado ao entrar no Castelo de Sangue",
        "ru": "Используется при входе в Кровавый Замок.",
        "tl": "Ginagamit kapag pumapasok sa Blood Castle",
        "uk": "Використовується при вході в Кривавий замок",
        "zh-TW": "進入血色城堡時使用"
      }
    },
    {
      "text": ""
    },
    {
      "text": {
        "en": "The remaining time is shown",
        "de": "Die verbleibende Zeit wird angezeigt",
        "es": "Se muestra el tiempo restante",
        "id": "Waktu yang tersisa ditampilkan",
        "ja": "マウスボタンを右クリックすれば",
        "pl": "Wyświetlany jest pozostały czas",
        "pt": "O tempo restante é mostrado",
        "ru": "Оставшееся время показано",
        "tl": "Ang natitirang oras ay ipinapakita",
        "uk": "Показано час, що залишився",
        "zh-TW": "顯示剩餘時間"
      }
    },
    {
      "text": {
        "en": "when you right click on your mouse.",
        "de": "wenn Sie mit der rechten Maustaste klicken.",
        "es": "cuando haces clic derecho en tu mouse.",
        "id": "ketika Anda mengklik kanan pada mouse Anda.",
        "ja": "残った時間を見ることができます。",
        "pl": "po kliknięciu prawym przyciskiem myszy.",
        "pt": "quando você clica com o botão direito do mouse.",
        "ru": "когда вы щелкаете правой кнопкой мыши.",
        "tl": "kapag nag-right click ka sa iyong mouse.",
        "uk": "коли ви клацаєте правою кнопкою миші.",
        "zh-TW": "當您右鍵單擊滑鼠時。"
      }
    }
  ]
  …
}

Related issues

Step 4b of #815. Builds on #818, #817, #816 and #813.

Checklist

  • I have read and followed docs/CODING_RULES.md.
  • I have built the project locally (see docs/build/README.md).
    • Windows x64 Release: all 988 tests pass, including the comparison test and "A description line's item levels are kept through write and read, and checked".
    • Changed lines pass clang-format 21.1.8 as CI checks them.
    • In game (Release x64 against a local OpenMU), the four items above were compared before and after in both themes.
  • Changes are focused on one concern; the diff is as small as it reasonably can be.

…m their data

The damage increase and absorption the tooltip code computed for the
wings, capes and pets are their descriptions now, with placeholders over
11 new level bonus tables of Stats/ItemStats.json (wingDamageIncrease1st
is 12 + 2 per level, ...): 24 wings and capes, plus Horn of Dinorant,
Guardian Angel and Imp, whose fixed numbers are written in the text.
Their cases leave RenderItemInfo; the two .resx texts no code uses any
more go too (the shared "Increase %d%% of Damage" and "Increase speed"
stay, other code prints them).

The printf texts become data as the game showed them: %d is the
placeholder or the number, %% is %. Two translations of the Imp's text
had broken escapes ("% %", shown as %; Portuguese "% o", read as an
octal number); they read as plain text now.

A table no stat uses is no longer a warning of the stat file alone: it
can be one for descriptions, so ValidateDescriptionPlaceholders warns
about a table that neither a stat nor a description uses.

A test compares every one of the 27 items at every level from +0 to +15
with the old formulas.
A description line can be limited to item levels: "levels": [1, 8]
shows it from +1 to +8 (both included; left out, at every level). The
tooltip draws only the lines of the item's level.

With it, the last fixed texts of RenderItemInfo's item chain are
descriptions: Town Portal scroll (+1 to +8), Spirit, Loch's Feather,
Fruits, Siege Potion, Contract Summon, Life Stone and Rena by level,
and Scroll of Archangel, Blood Bone and Invisibility Cloak, which have
one text at every level. Their cases go, and so do the 18 .resx texts
no other code prints (no Lookup() range reaches their legacy ids).

The old code drew a stale line of the text list at the Contract
Summon's, Life Stone's and Rena's levels without a text (+2 and up,
Rena +0); those levels show none now. Otherwise nothing changes: a test
compares every level of the 11 items with the old code's lines and
colors.

The Armor of Guardsman's Chaos Castle table and the Lost Map / Symbol
of Kundun stay code: they read the hero and other tables.
…at/item-tooltip-values-to-data

# Conflicts:
#	docs/item-data.md
#	src/Localization/Game.pl.resx
#	src/source/Data/GameData/ItemData/ItemStatProfiles.cpp
#	src/source/Data/GameData/ItemData/ItemStatsJson.cpp
…/item-description-levels

# Conflicts:
#	src/Localization/Game.pl.resx
…at/item-tooltip-values-to-data

# Conflicts:
#	docs/item-data.md
…/item-description-levels

# Conflicts:
#	docs/item-data.md
#	src/source/Engine/Object/ZzzInventory.cpp
…at/item-tooltip-values-to-data

# Conflicts:
#	docs/item-data.md
…/item-description-levels

# Conflicts:
#	docs/item-data.md
#	src/source/Engine/Object/ZzzInventory.cpp
…at/item-tooltip-values-to-data

# Conflicts:
#	docs/item-data.md
…/item-description-levels

# Conflicts:
#	docs/item-data.md
…at/item-tooltip-values-to-data

# Conflicts:
#	docs/item-data.md
#	src/source/Data/GameData/ItemData/ItemDataValidation.cpp
#	src/source/Data/GameData/ItemData/ItemDataValidation.h
…ip-values-to-data

# Conflicts:
#	tests/data/test_item_description_placeholders.cpp
… names its tier's tables

The unused-table warning moves out of the placeholder check into
ValidateTableUses, run where that check is (Load, Save Items, bmd import).
It also warns about a wing whose description names other tables than most of
the other wings of its tier, cape tag and group: the tooltip code chose the
wings' damage and absorption lines by the tier, so a new wing without them, or
a re-tiered wing that keeps its old tier's tables, is pointed out.
…t are the game's

"Zwiększa prędkość" as the game's "Increase speed" text has it, and the
Imp's Portuguese text without its broken "de % of".

@sven-n sven-n left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/source/Data/GameData/ItemData/ItemJsonFormat.cpp, ReadDescriptionLevels: isLevel calls value.get<int>() on every JSON integer before validating its range. nlohmann JSON accepts integer values outside the C++ int range; please use a range-safe integer conversion/comparison (including unsigned JSON integers) so malformed levels values reliably produce a validation error instead of narrowing/wrapping. Add cases for very large positive/negative integers. Reviewed against AGENTS.md and CODING_RULES.md; not build-tested.

Mosch0512 and others added 7 commits October 23, 2026 19:15
…s own item table

Run in one process after a test that loaded the item data (the MU
Helper pickup filter tests do), the comparison test crashed: it
switches the UI language for every language, the item data follows
each switch and refills ItemAttribute, which is null outside a
ClientDataScope. CTest runs each test case in its own process, so CI
did not show it. The test now runs inside a ClientDataScope, as
ClientDataScope's comment asks of a test that switches the language.
… a tie, and checks a lone wing against the other groups

Following the review: a wing is compared by its lines (their texts, tables
included), so one that loses or gains a line without a table is pointed out
too; wings that split evenly get one warning that they don't agree, so neither
side is blamed; wings without lines count as a set of lines; and a wing alone
in its tier, cape tag and group is compared with its tier's wings in the other
groups that have more than one (Cape of Fighter and Cape of Lord, each alone,
aren't compared with each other). The warning is in the docs, and the stat
file test's comment names ValidateTableUses.
The wing check took the lines most wings of a tier have as the ones the
others are measured by, and wings without lines counted as one set of
lines. New wings imported without their lines (a bmd import gives new
items none), more of them than the wings with lines, then made "no
lines" the majority: the wings with the right lines were pointed out and
the new ones were not. Wings without lines no longer set the lines the
others are compared with, so the new ones are the ones pointed out; a
tie among the wings with lines is still reported as such.
They were read as an int before the range check, so a number like 2^32 + 1 wrapped into level 1.
@ezequielzacca

Copy link
Copy Markdown
Contributor Author

Thanks, fixed in a16c57f. ReadDescriptionLevels now reads each level with ReadWholeNumber (as long long, which also handles unsigned JSON integers) before the range check, so a number past int no longer wraps into a level; 2^32 + 1 used to come out as level 1. The levels test now also covers [4294967297, 4294967298], [-4294967295, 2], [0, 18446744073709551615] and [-9223372036854775808, 2], each an error on description[0].levels.

@Mosch0512 Mosch0512 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: changes requested for one item; the rest is non-blocking. The step itself does what it says:

  • levels: read, written on one line and checked as described. After sven-n's review, numbers outside int's range are an error too (a16c57f, with tests for them).
  • Texts by level: the 11 items show the old code's lines and colors at every level from +0 to +15. That holds in all 11 languages too, apart from the Polish lines below.
  • Order: none of these items had a description before, so their lines stay where the old cases wrote them.
  • The stale-line fix is right. Rena +0, Contract Summon and Life Stone +2 and up moved to the next row without writing one, so they showed a leftover row of the text list.
  • Removed texts: no code or data can still reach the 18 removed ids. I checked every computed Lookup() range and the text ids in the mix recipes, skill and socket files. Two leftovers can't reach them in practice: the map name fallback 30 + iMap reaches 157 only for map 127, and the mastery text 1080 + MasteryType reaches 1099 only for type 19, while the skill files only have 0–6.

Required

  • Three Polish Fruits texts are the ones main replaced on 2026-10-18, not what the game prints today (inline, Group13_Helper.json:718).

Non-blocking

  • The comparison test checks English only, though most moved lines come from texts that stay in .resx (inline, test_item_database_files.cpp:535).
  • Nothing tests that the tooltip skips a line outside its levels (inline, ZzzInventory.cpp:1639).
  • Machine-translation errors moved as they were, now one-line data fixes (inline, Group14_Potion.json:130).
  • The test's note on stale lines leaves out Fruits +5 to +15 (inline, test_item_database_files.cpp:525).
  • The Polish Town Portal line is a grammar fix that the description doesn't mention (inline, Group14_Potion.json:278).
  • levels and the design's level variants (inline, item-data.md:219).

What I checked:

  • every moved line against printf of the old .resx texts, for each item, level and language;
  • the old cases around the description call, for order and colors;
  • every Lookup() with a computed number, and the data files that hold text ids;
  • the levels reader, writer and round trip.

Built and ran on Windows x64 Debug at da3c3bb: test_item_database and test_item_database_files pass as whole binaries, and the changed lines pass clang-format 21.1.8. I didn't rebuild after a16c57f or run the game.

Comment thread src/bin/Data/Items/Group13_Helper.json Outdated
"es": "Dominio Aumenta 1~3 puntos de estadísticas",
"id": "Memerintah Meningkatkan 1~3 poin stat",
"ja": "統率 ステータス1~3ポイント生成",
"pl": "Rozkaz Zwiększa 1-3 punkty statystyk",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Required. Three of the Fruits' Polish texts are the ones main replaced on 2026-10-18 in 17fc723 ("correct mistranslated meanings in the Polish game text"). The game prints other texts today:

  • "Rozkaz" (lines 718 and 734) should be "Dowodzenie", the Command stat as I18N::Game::Command prints it.
  • "Można go używać po usunięciu przedmiotu." (line 750) should be "Można użyć po zdjęciu ekwipunku."
  • "Może być wyposażony w Dark Lord" (line 767, "can be equipped with Dark Lord") should be "Może być używany przez: Dark Lord", as "Can be equipped by %s" prints it.

Every other language matches what the old code printed, at every level. They were probably converted from a .resx from before that commit.

{
if (line.IsShownAt(level))
{
lines.push_back({FillPlaceholders(line.text.GetNeutral(), level, profiles), line.color});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking. This compares the English text only. Most of the moved lines come from texts that stay in .resx, because other code prints them:

  • the stat names;
  • "It can be used with item removed." and "Can be equipped by %s" with "Dark Lord";
  • Archer, Spearman, Scroll of Guardian and Place Life Stone;
  • the Cloak's last two lines.

Comparing those with I18N::Game in every language inside a ClientDataScope, as #818's wing test does, would have caught the Polish lines.

const int lastRow = std::min(TextNum + MaxItemDescriptionRows, static_cast<int>(std::size(TextList)));
for (const Data::Items::ItemDescriptionLine& line : definition.description)
{
if (!line.IsShownAt(level))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking. Nothing tests this continue: the comparison test and the levels test call IsShownAt directly. Without it, every test stays green while each Fruit shows all 12 lines and the Siege Potion all 6. A test_item_descriptions case with a [1, 8] line written at +0 and at +1 would cover it.

"text": {
"en": "Damage +20% increase effect",
"de": "Effekt: Schaden +20%",
"es": "Daño +20% iefecto de aumento",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, follow-up. Machine-translation errors moved as the game showed them:

  • Siege Potion: "+20% iefecto" in 7 languages (also "imeningkatkan", "infeito", "inувеличение", "ipagtaas", "inпосилення", "i效果增加"). The "i" of "%% increase" leaked into the translations.
  • Fruits, from line 588:
    • ENG as "Bahasa Inggris" (id), "ESP" (es), "POR" (pt), "АНГЛ" (ru) and "英語" (zh-TW), each the name of a language;
    • AGI as "通用人工智慧" (zh-TW) and "ОИИ" (ru), "artificial general intelligence";
    • STR as "ВУЛ" (uk, "street").

As data, each is a one-line fix now, maybe for #828. The stat names stay wrong in the .resx too, where other code prints them.

const auto found = std::find_if(result.items.begin(), result.items.end(), [&](const ItemDefinition& item)
{ return item.group == group && item.number == number; });
REQUIRE(found != result.items.end());
// Every level the items have. The old code also wrote an empty line at the Contract Summon's, Life Stone's and

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking. The note names Contract Summon, Life Stone and Rena. But for Fruits +5 to +15 the old code also wrote two stale rows and "It can be used with item removed.". OldLevelLines returns nothing there (level <= 4), so for those levels the test describes the new behavior, not the old. That's harmless, since Fruits only exist at +0 to +4; it's only the note, and the PR description's "One fix".

"es": "Deformarse al área correspondiente después de 3 segundos",
"id": "Warp ke area yang sesuai setelah 3 detik",
"ja": "3秒後該当地域に移動",
"pl": "Teleportacja do wybranego obszaru za 3 sekundy",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking. The Polish line says "za 3 sekundy", while the game printed "za 3 sekund" ("%d sekund" with 3). "Sekundy" is the right plural for 3, so it's worth keeping. But the PR description says the texts are as printed, so a line there would explain it.

Comment thread docs/item-data.md
- **`color`** is one of the tooltip colors: `white` (left out), `blue`,
`red`, `yellow`, `green`, `darkRed`, `purple`, `darkBlue`, `darkYellow`,
`greenBlue`, `gray`, `redPurple`, `violet`, `orange`.
- **`levels`** shows the line only at those item levels, both included (`[1,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking. Nearly every levels use here is a level variant that the items design says becomes an item of its own later:

  • the five Fruits;
  • Potion of Bless and Potion of Soul;
  • Archer and Spearman;
  • Scroll of Guardian and Life Stone;
  • the two Spirits;
  • Crest of Monarch;
  • Rena +1 to +3.

That's fine for step 4b. A sentence saying these lines move into those items then would keep the two plans from reading as contradictory.

…or every language and the levels

Three Polish Fruits texts were from before main corrected them (17fc723): Dowodzenie, "Można użyć po zdjęciu
ekwipunku." and "Może być używany przez: Dark Lord". A test now compares the level lines whose texts other code still
prints with the game's texts in every language, another that a line is only written at its levels. The levels note
says the level variants' lines move into their items later.
@ezequielzacca

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough check.

Required, done: the three Polish Fruits texts are main's since 17fc723: "Dowodzenie" (both stat lines at +4), "Można użyć po zdjęciu ekwipunku." and "Może być używany przez: Dark Lord".

Non-blocking:

  • Every language: a new test, "The tooltip texts by item level are the game's texts in every language", compares the lines whose texts other code still prints with I18N::Game in every locale, inside a ClientDataScope as the wing test does: Archer, Spearman, Scroll of Guardian, Place Life Stone, the Fruits' stat names, "It can be used with item removed." and "Can be equipped by %s" with Dark Lord. It fails on the old Polish lines. The Cloak's last two lines aren't in it: a later PR of the stack removes those texts, and the test has to build there too.
  • The continue: "A description line is written only at the item levels it is shown at" writes a [1, 8] line at +0, +1, +8 and +9 through WriteItemDescriptionLines. Without the continue it fails.
  • The stale-line note now names the Fruits' +5 to +15 too, and says Fruits only exist at +0 to +4.
  • levels and the level variants: item-data.md says most of today's uses are level variants, whose lines move into those items later and lose their levels.
  • Polish Town Portal: the PR description now says "za 3 sekundy" is the right plural for 3, kept on purpose.
  • Machine-translation errors: left as the game showed them here, as you suggest; I'll fix them as data in fix(items): damage texts without a stray "of", and the Fruits' stat words #828.

@Mosch0512
Mosch0512 merged commit 050d3ef into sven-n:main Oct 24, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants