Repository navigation
Conversation
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
force-pushed
the
feat/item-options-9a4
branch
from
November 15, 2026 17:42
216916c to
2ad2fa5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Options/Excellent.jsonreadstotalLevelinstead oflevel, 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 readstotalLevelsince 9a3.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.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):attackTypeancientBonusStrengthtotalStrength[5, 10]ancientBonusAgilitytotalAgility[5, 10]ancientBonusEnergytotalEnergy[5, 10]ancientBonusVitalitytotalVitality[5, 10]attackTypepicks 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,%das{value}. The vitality text keeps the game's "Increase stamina".CSItemOption::RenderDefaultOptionTextwrites the bonus definition's lines with its value at the bonus level (GetDefaultOptionText's switch onattackTypeand its* 5are gone), andgetAllAddState/getAllAddOptionStatesbyCompareadd the value without* 5(GetDefaultOptionValueis 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.WriteDefinitionOptionLineshands its line writing to a newWriteOptionLines, which the bonus uses too.totalStrength…totalVitalityof typeancientBonusto the set options' stat ids (AT_SET_OPTION_IMPROVE_STRENGTH…), which the stat code switches on. The design kept the stat fromattackType(section 4:SET_OPTION_*); with the attribute deciding, an editedAncient.jsonchanges 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.attackTypepicks 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); anattackTypeabove 4 is a warning of the item checks (it picks no bonus, shown in Item Basics); a nameattackTypepicks without anancientBonusdefinition 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 showsattackTypeas its byte; the editor's option windows come with 9a5.docs/item-data.mddescribes the fix, the types, the file, theattackTypefield and the checks.What the player sees differently
Checked
attackTypeadds 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.attackTypepicks (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 byattackType1 to 4 and none outside; the effect rows; the Data Checks for links, unlinked definitions (also an extra bonus and set options),attackTypeabove 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.ctestpasses all 4066 tests, among themtest_item_database(284 cases) andtest_item_database_files(434).test_item_database_files,test_item_databaseandrml_styling_regression_testspass at each.