Skip to content

Item stat profiles: take the level bonus tables from the profile instead of the item's power-ups? #1303

Open

Description

@Mosch0512

Is your feature request related to a problem? Please describe.

#1290 adds item stat profiles: named sets of item formulas, in the shape of the MuMain client's item stat file, so that both sides can exchange them. A follow-up makes the game logic read them; #1288 checks that it computes the same values as today.

A profile also names a level bonus table for each stat it writes. For example, the profile default names "Defense Increase (Armors)" for the base defense. But today every base power-up of an item already names its own table (ItemBasePowerUpDefinition.BonusPerLevelTable). So after #1290, the level table of an item's stat is named in two places: on the item's power-up and in its profile. The configuration update makes them equal (an item with a table of its own gets a copy of its profile, e.g. thirdTierWing2), but later edits can make them differ, and then one of them is silently ignored.

The question for this issue: for the stats the client knows (damage, magic power, block rate, defense), should the game take the level table from the item's profile, as the client does, or keep taking it from the item's power-up, as today?

How the client does it (MuMain)

An item names its profile, and the profile names one table per stat from the table list of Data/Items/Stats/ItemStats.json. Items don't name tables.

// tables
{ "name": "defenseIncreaseArmors", "values": [0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 31, 36, 42, 49, 57, 66] }
// the profile "default"
"defense":    { "table": "defenseIncreaseArmors", "excellent": { "times": 12, "add": 4, "dropLevelDivisor": 5 }, … }
"magicPower": { "table": "magicPowerIncrease", "excellent": { "times": 25, "add": 5 }, "ancient": { "add": 2, "dropLevelDivisor": 60 }, "perLevelAfterHalving": 2 }
// an item, in Data/Items/Group08_Armor.json
{ "number": 9, "name": { "en": "Plate Armor", … }, "statProfile": "armor", … }

Magic power has one table for all staffs, sceptres and books ("Magic Power Increase", the same numbers as the armour table). The client adds the table and the excellent and ancient parts to the full magic power, halves the sum once, and adds perLevelAfterHalving × level (2 for staffs and books, 0 for sceptres).

How the server does it today (OpenMU)

  • Every base power-up names its table. ItemPowerUpFactory.GetBasePowerUpWrappers adds the table's value at the item's level to the base value: Plate Armor's "Base Defense" (30) points to "Defense Increase (Armors)", so its defense at +9 is 30 + 27 = 57.
  • The formulas are in code: ItemExtensions.GetRequirement (how requirements grow) and ItemPowerUpFactory.CreateExcellentAndAncientBasePowerUpWrappers (what excellent and ancient items get more).
  • Staffs store their rise already halved (Legendary Staff: 59 ÷ 2 = 29.5) and use two tables, "Staff Rise (odd)" and "Staff Rise (even)". They are the client's table halved plus 2 per level, rounded up or down by the parity of the magic power. So the server computes the Legendary Staff at +9 as 29.5 + 32 = 61.5 (excellent 75.5), where the client shows 61 (excellent 76).

The flows

Today:

flowchart LR
    item["Plate Armor"] --> powerUp["Base power-up: Base Defense, base value 30"]
    powerUp -->|"Bonus Per Level Table"| table["Defense Increase (Armors): +27 at +9"]
    code["Formulas in code: ItemExtensions, ItemPowerUpFactory"] --> extras["Requirements, excellent and ancient bonuses"]
Loading

After #1290 as it stands, two places name the table:

flowchart LR
    item["Plate Armor"] --> powerUp["Base Defense, base value 30"]
    powerUp -->|"Bonus Per Level Table (used by the game)"| table["Defense Increase (Armors)"]
    item -->|"Stat Profile"| armor["Profile armor"]
    armor -->|"laid over"| default["Profile default"]
    default -->|"table of Base Defense (only for the exchange)"| table
Loading

Proposed, as the client:

flowchart LR
    item["Plate Armor"] --> powerUp["Base Defense, base value 30, no table"]
    item -->|"Stat Profile"| armor["Profile armor: 5 strength per option level"]
    armor -->|"laid over"| default["Profile default: formulas and tables"]
    default -->|"table of Base Defense"| table["Defense Increase (Armors)"]
Loading

The client:

flowchart LR
    item["Item: Plate Armor, statProfile armor"] --> armor["ItemStats.json: profile armor"]
    armor -->|"laid over"| default["ItemStats.json: default"]
    default -->|"defense.table"| table["tables: defenseIncreaseArmors"]
Loading

Describe the solution you'd like

For the stats the client knows, an item only knows its profile, and the profile names the table:

  1. In the follow-up which lets the game read the profiles: base defense, physical damage, defense rate and shield defense take their level table from the item's profile (laid over default). The values stay the same, because the configuration update made the tables equal, and the comparison test of test(items): pin today's item requirement, power-up and price formulas for every item #1288 checks it.
  2. A magic power fix, as its own PR afterwards: staffs, sceptres and books compute like the client: the full magic power, the one table "Magic Power Increase", the sum halved once, plus 2 per level (0 for sceptres). That changes values by up to 0.5 to match the client (Legendary Staff +9: 61.5 → 61, excellent 75.5 → 76), and it needs a configuration update for the staffs', sceptres' and books' power-ups.
  3. A cleanup update: it removes BonusPerLevelTable from the base power-ups of these stats, so that the admin panel doesn't show a table which has no effect.

Other level tables (e.g. the running speed of some armour pieces) stay on their power-ups, until profiles can hold terms for any attribute (#1302). An item which needs a table of its own gets a profile of its own, as the configuration update of #1290 already does.

Situation The item's power-up names the table (today) The profile names the table (proposal, and the client)
Who decides an item's +level bonus each power-up of each item the item's profile, or default where it doesn't write it
Change the numbers for all armours edit the table the same
Give all armours another table change it on every armour's power-up change it once, in the profile
Give one item its own table edit its power-up give the item its own profile (a copy)
Create a new item pick the right table on each power-up pick a profile; its tables come with it
Can two places disagree? yes, once profiles exist; one is ignored no, for these stats only the profile names a table
Exchange with the client translate; report or split items whose table differs one to one
Staffs, sceptres, books two tables per kind, halving built in one table, computed like the client (after the fix)
Other stats with level tables the same mechanism for everything still on the power-up until #1302: two mechanisms for a while
Work none the follow-up, the magic power fix and a cleanup update

Describe alternatives you've considered

  • Keep the power-up's table as the one the game uses, and the profile's table only describes the items for the exchange. Nothing changes in how values are computed; the export reports items whose table differs from their profile's, and the import writes a changed profile table onto the items. Two places stay.
  • Move every level table into profiles at once, also running speed and the like. That needs terms for any attribute first (Item stat profiles: excellent and ancient terms for any attribute #1302).

Mockups

  1. Today: Plate Armor in the admin panel. The level table sits on the item's power-up, and the formulas are in the code.
Today: Plate Armor in the admin panel
  1. After: Plate Armor names its profile "armor". Its power-up keeps the base value; its own table pointer is removed by the cleanup update. ("2b-2" is the follow-up which lets the game read the profiles.)
After: Plate Armor with its profile
  1. After: the profile "default" with its level tables, excellent and ancient terms and requirement formulas, and the two drop level bonuses of the configuration.
After: the profile default
  1. After: the profile "armor" only writes 5 strength per option level. Plate Armor's values worked out, the same as today.
After: the profile armor
  1. A level table only holds the numbers per item level, on both sides. Below: the client's one magic power table next to the server's two staff tables.
What a level table holds
  1. The comparison: the item's power-up or the profile names the table.
The power-up or the profile names the table

Additional context

Questions for the maintainers:

  1. Should the profile name the level table for these stats (the proposal), or should the power-up stay the source?
  2. Is it fine that the magic power fix changes values by up to 0.5, to match what the client shows?
  3. Is it fine that a cleanup update removes BonusPerLevelTable from those power-ups?

Related: #1290 (the profiles), #1288 (the comparison test), #1302 (terms for any attribute), #1301 (the books' energy requirement), sven-n/MuMain#861 (the client's item sync), sven-n/MuMain#864 (the client side of #1302).

Activity

  1. Mosch0512 commented on Oct 26, 2026

    @Mosch0512
    ContributorAuthor

    A summary of the discussion on Discord (26.10.), so the decision is recorded here:

    • One approach: @sven-n would welcome one common approach, "maybe even one common for requirements and power ups". Stinkyfax and I prefer the profile too: one place to configure instead of one pointer per power-up, and a new item set can reuse a profile.
    • Modular, all use cases: Stinkyfax asked that it stays modular, so someone who adds items one by one doesn't have to work around the profiles. An item with values of its own gets a profile of its own, as the update of feat(items): add item stat profiles as configuration data #1290 already does for items with their own tables (e.g. thirdTierWing2). The follow-up which lets the game read the profiles (fork PR feat(items): the item formulas read the item stat profiles Mosch0512/OpenMU#5, the upstream PR follows) also warns at the start about items which can be worn but have no profile.
    • Still needed with the shared data of MuEditor? Yes. The exchange keeps the server's and the client's data equal; the profiles keep the same formula from being repeated within each side. The client has them already, so the exchange maps them one to one.

    Questions

    1. Can we take it as decided that the profile names the level table, as proposed in the description (the follow-up reads the formulas, then the magic power fix, then the cleanup update)?
    2. @sven-n, what did you mean by "one common for requirements and power ups"?
      • (a) The same as question 1: the requirement formulas are already on the profile, so with the tables there too, requirements and power-ups are configured the same way.
      • (b) One shape of formula for both: today a requirement formula (add, percent, level weight) and the stat terms (times, add, drop level divisor) are different types. That would be a model change, which I'd put into Item stat profiles: excellent and ancient terms for any attribute #1302 (terms for any attribute).

    Use cases the design should cover (please add any which are missing):

    • A new item set reuses an existing profile.
    • One item gets values of its own: it gets a profile of its own.
    • All armour changes at once: edit the profile armor.
    • A profile only writes what differs from default.

    Not part of this issue: Stinkyfax would like metadata such as the item tier or rarity, to build drop tables from it. I think that's a concept of its own rather than part of the stat profiles, which group items by how their stats grow (all armour sets share one), so it's worth its own issue.

  2. ze-dom commented on Oct 27, 2026

    @ze-dom
    Contributor

    Regarding number 2, it might be a precedent for turning every stat in the server as the client shows it (hp, mana, etc.), that is, rounded off. Not saying it's a negative thing, but it's a design decision which apparently @sven-n chose not to follow from the start, at least. The trend seems to be that the client and server will be evermore tightly coupled, so it might need to be revisited :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions