Skip to content

feat(items): the ancient bonus in data - #176

Open
Mosch0512 wants to merge 17 commits into
mainfrom
feat/item-options-9a4
Open

Mosch0512 wants to merge 17 commits into
mainfrom
feat/item-options-9a4

Conversation

@Mosch0512

@Mosch0512 Mosch0512 commented Nov 13, 2026 •

Copy link
Copy Markdown
Owner

The fourth pull request of the item options design (docs/superpowers/specs/2026-11-04-item-options-design.md, 9a4, after 9a3 sven-n#1098), with the fix that comes right after 9a3: the excellent "+level / 20" counts level plus master level (O26), and the ancient bonus moves from code into data (O14). The move keeps every tooltip and every ancient item's stats; the two changes it needs come before it in commits of their own, listed under "What the player sees differently".

What changes

  • Excellent "+level / 20" (fix, O22/O26, P16): the two options' term in Options/Excellent.json reads totalLevel instead of level, so it divides level plus master level, rounded down, as the decompiled Season 6 servers and OpenMU do. Only the data changes; the client already reads totalLevel since 9a3.
  • Ancient bonus above level 2 (fix, before the move): the code multiplied the bonus level by 5, so a level of 3 to 15, which the ancient byte holds but no server sends, gave +15 to +75. A level above 2 now counts as level 2, as the definition's list will give it.
  • Type ancientBonus: the ancient byte's bonus level (ITEM::AncientBonusOption, the high four bits of the open source client's ancient byte) is the level; a definition has one option without a number, its value taken from the list at that level.
  • Type ancientOption: known to the loader for the item sets (phase 10), its options numbered from 1. No definition has it yet; the set options stay where they are.
  • Data/Items/Options/Ancient.json: four definitions with OpenMU's labels (OpenMU builds their GUIDs from the attribute: 00000083-0030-<first two bytes of the attribute's id>) and no server values (OpenMU gives the bonus through the sets, not randomly):
Name Attribute Value attackType
ancientBonusStrength totalStrength [5, 10] 1
ancientBonusAgility totalAgility [5, 10] 2
ancientBonusEnergy totalEnergy [5, 10] 3
ancientBonusVitality totalVitality [5, 10] 4
  • Which item has which bonus: no item links them. Until the item sets link a bonus to each item of a set, the item's attackType picks the definition by these names (O14), as the code before picked the stat; the option database looks the four names up when it loads.
  • Tooltips/OptionTexts.json: four texts (increaseStrength, increaseAgility, increaseEnergy, increaseVitality) from the game's "Increase strength +%d" and the others in their 11 languages, %d as {value}. The vitality text keeps the game's "Increase stamina".
  • The game: CSItemOption::RenderDefaultOptionText writes the bonus definition's lines with its value at the bonus level (GetDefaultOptionText's switch on attackType and its * 5 are gone), and getAllAddState / getAllAddOptionStatesbyCompare add the value without * 5 (GetDefaultOptionValue is gone), through one helper that the set options use too (AddToStat). The "Increase attribute damage" line of the elemental rings and pendants stays in code until the item sets (design section 15), now only below a bonus line. WriteDefinitionOptionLines hands its line writing to a new WriteOptionLines, which the bonus uses too.
  • The stat a bonus raises comes from its definition's attribute: the effects table maps totalStrength … totalVitality of type ancientBonus to the set options' stat ids (AT_SET_OPTION_IMPROVE_STRENGTH …), which the stat code switches on. The design kept the stat from attackType (section 4: SET_OPTION_*); with the attribute deciding, an edited Ancient.json changes the stat as well as the line, and an attribute the client doesn't add gets the existing "the client computes no … this way" warning.
  • Data Checks: the four ancient bonus definitions attackType picks get no "no item and no option rule links" warning (any other ancient bonus or set options do); an item that links an ancient type is a warning (the link does nothing); an attackType above 4 is a warning of the item checks (it picks no bonus, shown in Item Basics); a name attackType picks without an ancientBonus definition is a warning naming the file (those items show and add no bonus). The reader stops on a numbered ancient bonus option ("an ancientBonus option has no number"), a second one, and an ancient set option numbered 0. Item Basics still shows attackType as its byte; the editor's option windows come with 9a5.
  • docs/item-data.md describes the fix, the types, the file, the attackType field and the checks.

What the player sees differently

  • "Level / 20" gives more to a master: a level 400 hero with 200 master levels gets +30 instead of +20, in the character window and the stats. Heroes without a master level see no change.
  • A bonus level above 2 counts as level 2 (+10) where the code before gave 5 per level. Season 6 sends 1 and 2 (OpenMU's bonus has levels 1 and 2), so no server item changes.

Checked

  • The tooltip snapshot got three variants, the ancient bonus at levels 1 to 3, recorded with the code before (every item's hash changes once there, as each has 48 tooltips more); the level fix re-recorded the items whose level 3 changes from +15 to +10, and the move kept every hash.
  • A test on the code before the move: each of the 334 items with an attackType adds 5 per bonus level to its own stat (61 strength, 24 agility, 15 energy, 234 vitality), 10 from level 2 on after the fix, and a broken one nothing; the move keeps it.
  • New tests: the bonus comes from the definition attackType picks (strength, vitality), +5 / +10, the last entry above the list, none at level 0, with its line from the text ("Increase strength +10", "Increase stamina +5", blue); the stats add it for a worn item and not for a broken one, an edited list as written, to the stat another attribute names, and nothing for an attribute the client doesn't add; the excellent level / 20 of a master hero with the shipped data ((85 + 30) / 20 = 5); the reader reads and writes back both types and stops on the cases above; the database finds the bonus by attackType 1 to 4 and none outside; the effect rows; the Data Checks for links, unlinked definitions (also an extra bonus and set options), attackType above 4 and missing names with their file; the elemental rings' second line with and without the bonus line; the link order with the ancient types; an empty database picks no bonus.
  • Windows x64 Release (editor build), on main 1211f03: every target builds without warnings, and ctest passes all 4066 tests, among them test_item_database (284 cases) and test_item_database_files (434).
  • Linux (Ubuntu 24.04, GCC 13, editor build), on main 1211f03: each of the 17 commits builds, and test_item_database_files, test_item_database and rml_styling_regression_tests pass at each.
  • clang-format 21.1.8 on the changed lines passes. cppcheck 2.13 on the changed files passes (CI runs 2.19).
  • Not tried in game.

The excellent "+level / 20" damage and wizardry options divide level plus master level (totalLevel), rounded down,
as the Season 6 servers and OpenMU compute it (design O26, P16). The client divided the hero's level only, so a
level 400 hero with 200 master levels gets +30 now instead of +20. The change is the term in Options/Excellent.json;
the test checks a master hero with the shipped data.
The tooltip snapshot gets three variants, an ancient item with its bonus at level 1, 2 and 3, recorded with the code
before the bonus moves into data, so the move must keep every tooltip. Servers send levels 1 and 2; the ancient byte
holds more. Every item's hash changes once here, as each item has three tooltips more per level.
…Type

A test of the code before the ancient bonus moves into data: each of the 334 items with an attackType adds 5 per
bonus level to its stat (61 strength, 24 agility, 15 energy, 234 vitality), and a broken one adds nothing. The stats
are taken as the difference to wearing nothing, so set options an earlier test left don't count.
The ancient bonus has two levels on Season 6 and OpenMU, +5 and +10. The client multiplied whatever level the
ancient byte held by 5, so a level of 3 to 15, which no server sends but the byte holds, gave +15 to +75. A level above
2 now counts as the second, as the bonus's value list will give it when it moves into data. The snapshot re-records
the items whose bonus level 3 changes from +15 to +10.
The ancient bonus (+5 / +10 of a stat on an item of an ancient set) moves from code into data, as OpenMU's "Ancient
Bonus of Total Strength" and the others: Options/Ancient.json holds four definitions of the new type ancientBonus, each
one option without a number, its value list [5, 10] taken at the ancient byte's bonus level (the last entry above
it, as the code before counted a level above 2).

- Until the item sets link a bonus to each item of a set (phase 10), the item's attackType picks the definition by its
  name (1 strength, 2 agility, 3 energy, 4 vitality), looked up when the options load; no item links them.
- The definition's attribute decides the stat the bonus adds to (totalStrength ... totalVitality, through the effects
  table); its value list decides how much.
- The tooltip line is the definition's text (increaseStrength ... increaseVitality, from the game's texts); the
  elemental rings and pendants keep their second line in code until the item sets.
- The type ancientOption is known to the loader (numbered from 1) for the item sets; no definition has it yet.
- Data Checks: no "no item links" warning for the ancient types, a warning for an item that links one, and a warning
  for an attackType name without an ancientBonus definition.
- The tooltip snapshot and every ancient item's stats stay as they were; docs and tests for all of it.
…the unlinked warning

Every ancientBonus and ancientOption definition skipped the "no item and no option rule links" warning, so an extra
one (an ancientBonusCharisma, or any set options before the item sets) loaded unused without a word. Now only the four
definitions the items' attackType picks by name skip it.
…us line

When the ancient bonus wrote no line (its text missing from the option texts), the elemental rings and pendants still
showed "Increase Attribute Damage" alone. It now belongs to the bonus line, and is written only below the last
tooltip row.
attackType is now only what picks an item's ancient bonus definition (1 to 4), so a value above 4 gives an ancient
item no bonus. The Data Check warns about it. Item Basics still shows the field as its byte; the editor's option
windows come with 9a5.
The ancient types weren't in the order the item's option links are sorted in, so a link to one was sorted with the
names of no definition, by name, and an unknown name could come before it, against "names of no definition go last".
They now follow the excellent options.
The reader wrote "a" before every type name, so the ancient bonus read "a ancientBonus option has no number" and
"a ancientBonus definition has one option". The article now follows the name.
The warning for an ancient bonus name without a definition ran inside ValidateOptionDefinitionUse, so the test of the
unlinked definitions, whose options have no ancient bonus, had to stop checking that it found nothing else. It is now
ValidateAncientBonusNames, run next to it in the game and in the editor's Data Checks, and that test checks for no
issues again.
Its warning named "Options" where every other option problem names "Options/<file>". It now names the file of a
definition of that name of another type, or Options/Ancient.json, where the four definitions belong.
A comment of the link check said the item's attackType picks its ancient bonus and set options; the set options come from the item's set (the ancient byte's set number).
The ancient types take no option slots (IsTurnedOn gives them none), so GetOptionLevel's ancient bonus case never
ran; FindAncientBonus takes the value at the bonus level itself, and the case is gone. IsOptionTurnedOn lists the
ancient types as turning nothing on, and a type it doesn't list turns nothing on either. The ancient bonus effect rows
write no slot value, as the comment next to them says.
AddAncientBonus repeated the switch of AddStatsBySetOptions over the stat ids. Both now call AddToStat, which adds a
value to the stat an effect raises.
The option database kept the four ancient bonus indexes in an array of a written-out 4 with four written-out
NoDefinition; its size now is AncientBonusNames' and an empty database fills it with NoDefinition.
…ne scope

WornItemsScope kept its own copy of a test's character machine; it now holds a CharacterMachineScope, as the ancient
bonus stat tests do. The critical damage tests' EquipmentScope does more (a hero of its own, the set and socket bonuses
added up again) and keeps its own.
@Mosch0512
Mosch0512 force-pushed the feat/item-options-9a4 branch from 216916c to 2ad2fa5 Compare November 15, 2026 17:42
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.

1 participant