Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
ff18a84
fix(items): the excellent level / 20 counts level plus master level
Mosch0512 Nov 13, 2026
70cbe45
test(items): snapshot the ancient bonus at levels 1 to 3
Mosch0512 Nov 13, 2026
1a93c3b
test(items): every ancient item's bonus raises the stat of its attack…
Mosch0512 Nov 13, 2026
226b1e6
fix(items): an ancient bonus level above 2 counts as level 2
Mosch0512 Nov 13, 2026
7490683
feat(items): the ancient bonus in data
Mosch0512 Nov 13, 2026
66dcb16
fix(items): only the ancient bonus definitions attackType picks skip …
Mosch0512 Nov 13, 2026
e9ea6cb
fix(items): the elemental rings' second bonus line only follows a bon…
Mosch0512 Nov 13, 2026
bdc08b2
feat(items): a Data Check for an attackType that picks no ancient bonus
Mosch0512 Nov 13, 2026
8def17a
fix(items): the ancient types sort before option names of no definition
Mosch0512 Nov 13, 2026
7183ede
fix(items): the option file messages say "an ancientBonus"
Mosch0512 Nov 13, 2026
9089fb0
refactor(items): the ancient bonus name check is a check of its own
Mosch0512 Nov 13, 2026
b8c4983
fix(items): the ancient bonus name check names the file
Mosch0512 Nov 13, 2026
8ab0555
docs(items): the set options come from the set, not from attackType
Mosch0512 Nov 13, 2026
a60bfce
refactor(items): the ancient bonus takes no option slot
Mosch0512 Nov 13, 2026
00d1890
refactor(items): one helper adds a set option's or ancient bonus's stat
Mosch0512 Nov 13, 2026
6de9097
refactor(items): the ancient bonus lookup's size follows its names
Mosch0512 Nov 13, 2026
2ad2fa5
refactor(tests): the worn items scope uses the shared character machi…
Mosch0512 Nov 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 46 additions & 14 deletions docs/item-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ variants or option words. An online keyword need not select the same items offli
| `excellentBonus` | A fixed excellent bonus of damage and magic power instead of the [item stat file's](#item-stats-by-level) excellent term; left out = the term. The Chaos Dragon Axe has 15, the Chaos Nature Bow 30 and the Chaos Lightning Staff 25. The bmd import keeps it |
| `excellentRaisesLevel` | `false`: excellent options do not raise the level requirement by the [item stat file's](#item-stats-by-level) `excellentLevelAdd`. Left out = they do. The shipped data has it on the Dragon Knight, Venom Mist, Sylphid Ray, Volcano, Sunlight and Storm Blitz armour, the Bone Blade, Explosion Blade, Soleil Scepter, Sylph Wind Bow, Grand Viper Staff and Storm Blitz Stick, the lucky items and the Horn of Fenrir. The bmd import keeps it |
| `defense`, `magicDefense`, `blockRate` | Armor and shield values |
| `attackType` | Attack type used by the client |
| `attackType` | Which [ancient bonus](#item-options) an item of an ancient set has: 1 strength, 2 agility, 3 energy, 4 vitality, until the item sets link one |
| `sellValue` | Value factor used by the price calculation of some items |
| `buyPrice` | Fixed price; when set, it replaces the calculated price |
| `requirements` | `level`, `strength`, `dexterity`, `energy`, `vitality`, `leadership`. The stat requirements of the orbs, crystals and scrolls (groups 12 and 15, without a slot) are the values the game asks for, as OpenMU stores them: their profiles keep them as they are. The original client's bmd files hold weights for them, which Import from bmd turns into the values and Export as bmd and Export as S6E3 turn back (the smallest weight that gives each value). Which ones convert follows the item's profile: a requirement it keeps as it is while the default grows it (an orb given the default profile holds weights like any item) |
Expand Down Expand Up @@ -927,11 +927,12 @@ definitions: the files of `Data/Items/Options` hold the definitions, one
file per option group, and each item names the ones it can have in its
`options` list. Which of them an item has comes from the server: its item
data turns them on (luck by the luck flag, the additional option by its
level, the excellent options by the excellent byte). Luck, the additional
option of the items that are no wings and the excellent options are in data
level, the excellent options by the excellent byte, the ancient bonus by
the ancient byte's bonus level). Luck, the additional option of the items
that are no wings, the excellent options and the ancient bonus are in data
so far; the wings' and the Horn of Dinorant's options, the Wizard's rings',
the harmony, guardian, socket and pet options and the ancient bonus are
still code and move into data one group at a time.
the harmony, guardian, socket and pet options and the ancient set options
are still code and move into data one group at a time.

```json
{
Expand Down Expand Up @@ -959,9 +960,13 @@ still code and move into data one group at a time.
low four bits, 1 to 15; Season 6 sends 1 to 4), its options numbered
(`"number": 0`) by the option type the server sends in the byte's high
four bits, their value by that level; `excellent` is the excellent byte,
its options numbered 1 to 6, each turned on by the byte's bit number - 1.
Each option group brings its type when it moves into data. An item links
at most one definition of a type.
its options numbered 1 to 6, each turned on by the byte's bit number - 1;
`ancientBonus` is the ancient byte's bonus level (its high four bits),
with one option without a number, its value by that level;
`ancientOption` is an ancient set's options, numbered from 1 (no
definition has it yet: the item sets bring them). Each option group
brings its type when it moves into data. An item links at most one
definition of a type.
- `displayName`: the definition's name by language, English first.
- `openMu` and `server`: OpenMU's labels of the same definition (its GUID
and its English name, which the exchange with the server will match by)
Expand Down Expand Up @@ -1039,8 +1044,9 @@ known difference from OpenMU) and the pendants 13/12, 13/25 and 13/27.
Arrows and bolts have none, as on Season 6 and OpenMU.

The Zen option is +30 %, Season 6's value; OpenMU has +40 %. "Level / 20"
divides the hero's level, as the client did; OpenMU divides level plus
master level (`totalLevel`).
divides level plus master level (`totalLevel`), rounded down, as the
Season 6 servers and OpenMU do; the client before divided the hero's level
only, so a level 400 hero with 200 master levels now gets +30, not +20.

"Excellent" (the name's prefix and green colour, the excellent +15
durability, the tooltip state `excellent`, the ground label's excellent
Expand All @@ -1057,6 +1063,26 @@ cent (what wear takes off the stats) reads it only for weapons, shields
and armour, whose byte holds excellent options; wings pass no excellent
flags to it.

`Options/Ancient.json` holds the ancient bonus, as OpenMU's "Ancient Bonus
of Total Strength" and the others: an item of an ancient set raises a stat
by its bonus level, +5 at level 1 and +10 at level 2.

| Name | Attribute | Value | `attackType` |
|---|---|---|---|
| `ancientBonusStrength` | `totalStrength` | `[5, 10]` | 1 |
| `ancientBonusAgility` | `totalAgility` | `[5, 10]` | 2 |
| `ancientBonusEnergy` | `totalEnergy` | `[5, 10]` | 3 |
| `ancientBonusVitality` | `totalVitality` | `[5, 10]` | 4 |

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 (the last
column), as the code before them picked the stat. The attribute decides the
stat the bonus adds to (strength, agility, energy, vitality; another one
adds nothing), and the value list its value; a bonus level above the list
takes its last entry (servers send 1 and 2; the ancient byte holds up to
15). The elemental rings and pendants keep their second line,
"Increase Attribute Damage", in code until the item sets.

`Tooltips/OptionTexts.json` holds the lines' texts:

```json
Expand Down Expand Up @@ -1188,8 +1214,9 @@ an option without `attribute` or with none or more than one of `add`,
0 to 65535 or that has `perLevel`, a term (`per`) without `attribute` or
without exactly one of `times` and `divisor` (whole numbers from 1), a
number used twice in a definition, an additional option's number above 15,
an excellent option's number outside 1 to 6, a luck definition with more
than one option or a numbered one, OpenMU
an excellent option's number outside 1 to 6, an ancient set option's
number below 1, a luck or ancient bonus definition with more than one
option or a numbered one, OpenMU
labels or server values of the wrong kind, and a rule naming a
definition that does not exist or a condition it does not have. Errors:
an item linking a definition that does not exist (the link is left out)
Expand All @@ -1203,8 +1230,13 @@ unknown fields, an attribute the client does not compute, a term on a hero
attribute the client does not read, a text with `{divisor}` on an option
without a term that has a divisor (the line shows 0 there) or with several
(it shows the first one's), a definition no
item and no rule links, a text no option names and a text in a language
the game has no texts in.
item and no rule links (but the four ancient bonus definitions the item's
`attackType` picks), an item linking an ancient
bonus or set options (the link does nothing), an `attackType` above 4 (it
picks no ancient bonus), an ancient bonus name
`attackType` picks that no `ancientBonus` definition has (those items show
and add no bonus), a text no option names and a text in a language the
game has no texts in.

---

Expand Down
1 change: 1 addition & 0 deletions src/MuEditor/UI/DataChecks/FileCheckArea.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ std::unique_ptr<DataCheckArea> CreateItemOptionCheckArea()
if (!Data::Items::HasFatal(issues))
{
Data::Items::ValidateOptionDefinitionUse(g_ItemDatabase.GetAllSlots(), options, issues);
Data::Items::ValidateAncientBonusNames(options, issues);
}
return issues;
},
Expand Down
89 changes: 89 additions & 0 deletions src/bin/Data/Items/Options/Ancient.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
{
"formatVersion": 1,
"definitions": [
{
"name": "ancientBonusStrength",
"type": "ancientBonus",
"displayName": {
"en": "Ancient bonus: strength",
"de": "Antiker Bonus: Stärke",
"es": "Bonificación antigua: fuerza",
"id": "Bonus kuno: kekuatan",
"ja": "エンシェントボーナス:力",
"pl": "Starożytny bonus: siła",
"pt": "Bônus ancestral: força",
"ru": "Древний бонус: сила",
"tl": "Sinaunang bonus: lakas",
"uk": "Стародавній бонус: сила",
"zh-TW": "遠古加成:力量"
},
"openMu": {"id": "00000083-0030-09a3-0000-000000000000", "name": "Ancient Bonus of Total Strength"},
"options": [
{"attribute": "totalStrength", "add": {"values": [5, 10]}, "text": "increaseStrength"}
]
},
{
"name": "ancientBonusAgility",
"type": "ancientBonus",
"displayName": {
"en": "Ancient bonus: agility",
"de": "Antiker Bonus: Beweglichkeit",
"es": "Bonificación antigua: agilidad",
"id": "Bonus kuno: kelincahan",
"ja": "エンシェントボーナス:敏捷",
"pl": "Starożytny bonus: zwinność",
"pt": "Bônus ancestral: agilidade",
"ru": "Древний бонус: ловкость",
"tl": "Sinaunang bonus: liksi",
"uk": "Стародавній бонус: спритність",
"zh-TW": "遠古加成:敏捷"
},
"openMu": {"id": "00000083-0030-1207-0000-000000000000", "name": "Ancient Bonus of Total Agility"},
"options": [
{"attribute": "totalAgility", "add": {"values": [5, 10]}, "text": "increaseAgility"}
]
},
{
"name": "ancientBonusEnergy",
"type": "ancientBonus",
"displayName": {
"en": "Ancient bonus: energy",
"de": "Antiker Bonus: Energie",
"es": "Bonificación antigua: energía",
"id": "Bonus kuno: energi",
"ja": "エンシェントボーナス:エナジー",
"pl": "Starożytny bonus: energia",
"pt": "Bônus ancestral: energia",
"ru": "Древний бонус: энергия",
"tl": "Sinaunang bonus: enerhiya",
"uk": "Стародавній бонус: енергія",
"zh-TW": "遠古加成:能量"
},
"openMu": {"id": "00000083-0030-6b45-0000-000000000000", "name": "Ancient Bonus of Total Energy"},
"options": [
{"attribute": "totalEnergy", "add": {"values": [5, 10]}, "text": "increaseEnergy"}
]
},
{
"name": "ancientBonusVitality",
"type": "ancientBonus",
"displayName": {
"en": "Ancient bonus: vitality",
"de": "Antiker Bonus: Vitalität",
"es": "Bonificación antigua: vitalidad",
"id": "Bonus kuno: vitalitas",
"ja": "エンシェントボーナス:体力",
"pl": "Starożytny bonus: witalność",
"pt": "Bônus ancestral: vitalidade",
"ru": "Древний бонус: выносливость",
"tl": "Sinaunang bonus: tibay",
"uk": "Стародавній бонус: витривалість",
"zh-TW": "遠古加成:體力"
},
"openMu": {"id": "00000083-0030-76e4-0000-000000000000", "name": "Ancient Bonus of Total Vitality"},
"options": [
{"attribute": "totalVitality", "add": {"values": [5, 10]}, "text": "increaseVitality"}
]
}
]
}
4 changes: 2 additions & 2 deletions src/bin/Data/Items/Options/Excellent.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
{"number": 2, "attribute": "healthAfterMonsterKillMultiplier", "add": {"values": 8}, "text": "lifeAfterKill"},
{"number": 3, "attribute": "attackSpeedAny", "add": {"values": 7}, "text": "increaseAttackSpeed"},
{"number": 4, "attribute": "physicalBaseDmgIncrease", "percent": {"values": 2}, "text": "increaseDamagePercent"},
{"number": 5, "attribute": "physicalBaseDmg", "add": {"values": 0, "per": [{"attribute": "level", "divisor": 20}]}, "text": "increaseDamageLevel20"},
{"number": 5, "attribute": "physicalBaseDmg", "add": {"values": 0, "per": [{"attribute": "totalLevel", "divisor": 20}]}, "text": "increaseDamageLevel20"},
{"number": 6, "attribute": "excellentDamageChance", "percent": {"values": 10}, "text": "excellentDamageRate"}
]
},
Expand All @@ -78,7 +78,7 @@
{"number": 2, "attribute": "healthAfterMonsterKillMultiplier", "add": {"values": 8}, "text": "lifeAfterKill"},
{"number": 3, "attribute": "attackSpeedAny", "add": {"values": 7}, "text": "increaseAttackSpeed"},
{"number": 4, "attribute": "wizardryBaseDmgIncrease", "percent": {"values": 2}, "text": "increaseWizardryPercent"},
{"number": 5, "attribute": "wizardryBaseDmg", "add": {"values": 0, "per": [{"attribute": "level", "divisor": 20}]}, "text": "increaseWizardryLevel20"},
{"number": 5, "attribute": "wizardryBaseDmg", "add": {"values": 0, "per": [{"attribute": "totalLevel", "divisor": 20}]}, "text": "increaseWizardryLevel20"},
{"number": 6, "attribute": "excellentDamageChance", "percent": {"values": 10}, "text": "excellentDamageRate"}
]
}
Expand Down
52 changes: 52 additions & 0 deletions src/bin/Data/Items/Tooltips/OptionTexts.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,19 @@
"uk": "Відмінний рівень шкоди +{value}%",
"zh-TW": "優 破損率+{value}%"
},
"increaseAgility": {
"en": "Increase agility +{value}",
"de": "Beweglichkeit erhöhen +{value}",
"es": "Aumentar la agilidad +{value}",
"id": "Meningkatkan kelincahan +{value}",
"ja": "敏捷増加 +{value}",
"pl": "Zwiększa zwinność +{value}",
"pt": "Aumente a agilidade +{value}",
"ru": "Увеличение ловкости +{value}",
"tl": "Dagdagan ang liksi +{value}",
"uk": "Збільшення спритності +{value}",
"zh-TW": "增加敏捷性+{value}"
},
"increaseAttackSpeed": {
"en": "Increase Attacking(Wizardry)speed +{value}",
"de": "Erhöht Angriffsgeschwindigkeit (Zauberei) +{value}",
Expand Down Expand Up @@ -157,6 +170,19 @@
"uk": "Збільшення шкоди +{value}%",
"zh-TW": "增加傷害+{value}%"
},
"increaseEnergy": {
"en": "Increase energy +{value}",
"de": "Energie erhöhen +{value}",
"es": "Aumentar energía +{value}",
"id": "Meningkatkan energi +{value}",
"ja": "エナジー増加 +{value}",
"pl": "Zwiększa energię +{value}",
"pt": "Aumentar energia +{value}",
"ru": "Увеличение энергии +{value}",
"tl": "Dagdagan ang enerhiya +{value}",
"uk": "Збільшити енергію +{value}",
"zh-TW": "增加能量+{value}"
},
"increaseMaxHp": {
"en": "Increase Max HP +{value}%",
"de": "Erhöht max. HP +{value}%",
Expand All @@ -183,6 +209,32 @@
"uk": "Збільшення максимальної мани +{value}%",
"zh-TW": "增加最大法力 +{value}%"
},
"increaseStrength": {
"en": "Increase strength +{value}",
"de": "Stärke erhöhen +{value}",
"es": "Aumentar la fuerza +{value}",
"id": "Meningkatkan kekuatan +{value}",
"ja": "力増加 +{value}",
"pl": "Zwiększa siłę +{value}",
"pt": "Aumentar força +{value}",
"ru": "Увеличение силы +{value}",
"tl": "Dagdagan ang lakas +{value}",
"uk": "Збільшити силу +{value}",
"zh-TW": "增加強度+{value}"
},
"increaseVitality": {
"en": "Increase stamina +{value}",
"de": "Vitalität erhöhen +{value}",
"es": "Aumentar la resistencia +{value}",
"id": "Meningkatkan stamina +{value}",
"ja": "体力増加 +{value}",
"pl": "Zwiększa wytrzymałość +{value}",
"pt": "Aumenta a resistência +{value}",
"ru": "Увеличение выносливости +{value}",
"tl": "Dagdagan ang tibay +{value}",
"uk": "Підвищення витривалості +{value}",
"zh-TW": "增加體力+{value}"
},
"increaseWizardryLevel20": {
"en": "Increase Wizardry Dmg +level/{divisor}",
"de": "Erhöht Zaubereischaden +Level/{divisor}",
Expand Down
1 change: 1 addition & 0 deletions src/source/Data/DataHandler/ItemData/ItemDataHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ ItemOptionDatabase LoadItemOptions(std::vector<ItemDefinition>& items, std::vect
}
ValidateItemOptionLinks(items, options, issues);
ValidateOptionDefinitionUse(items, options, issues);
ValidateAncientBonusNames(options, issues);
for (ItemDefinition& item : items)
{
options.OrderLinks(item.options);
Expand Down
15 changes: 15 additions & 0 deletions src/source/Data/GameData/ItemData/ItemDataValidation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "ItemDescriptionPlaceholders.h"
#include "ItemEnumNames.h"
#include "ItemJsonFormat.h"
#include "ItemOptionDatabase.h"
#include "ItemModelJsonFormat.h"
#include "ItemTooltipLines.h"
#include "ItemType.h"
Expand Down Expand Up @@ -225,6 +226,19 @@ void ValidateExcellentBonus(const ItemDefinition& definition, std::vector<ItemDa
}
}

// attackType picks the item's ancient bonus definition (AncientBonusNames, 1 to 4) until the item sets link one.
void ValidateAttackType(const ItemDefinition& definition, std::vector<ItemDataIssue>& issues)
{
if (definition.attackType > AncientBonusNames.size())
{
AddIssue(
issues, ItemDataIssueSeverity::Warning, definition,
std::to_string(definition.attackType) +
" picks no ancient bonus (0: none, 1 strength, 2 agility, 3 energy, 4 vitality): the item has none",
"attackType");
}
}

// Item files saved before the stat profiles have none; the item then has the
// default until an edit or a bmd import gives it the profile its data gives.
void ValidateStatProfileGiven(const ItemDefinition& definition, const ItemStatProfiles& profiles,
Expand Down Expand Up @@ -366,6 +380,7 @@ void ValidateItems(std::span<const ItemDefinition> items, std::vector<ItemDataIs
ValidateClassRequirements(definition, issues);
ValidateExcellentBonus(definition, issues);
ValidateExcellentRaisesLevel(definition, issues);
ValidateAttackType(definition, issues);
ValidateRequirementsByLevel(definition, issues);
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/source/Data/GameData/ItemData/ItemEnumNames.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,12 @@ constexpr std::array<EnumName<ItemRequirement>, ItemRequirementCount> ItemRequir
{ItemRequirement::Leadership, "leadership"},
}};

constexpr std::array<EnumName<ItemOptionType>, 3> ItemOptionTypeNames = {{
constexpr std::array<EnumName<ItemOptionType>, 5> ItemOptionTypeNames = {{
{ItemOptionType::Luck, "luck"},
{ItemOptionType::Option, "option"},
{ItemOptionType::Excellent, "excellent"},
{ItemOptionType::AncientOption, "ancientOption"},
{ItemOptionType::AncientBonus, "ancientBonus"},
}};

constexpr std::array<EnumName<ItemTextColor>, ItemTextColorCount> ItemTextColorNames = {{
Expand Down
Loading
Loading