Conversation
- Items keep naming their looks (render style, effects); what a named look is made of moves from code into Data/Effects later, as lists of building blocks (D25, new phase 13 "Looks in data"). 4c3 names its effects like 4c2 names its styles. - A named look is shared: the editor lists the items that use a look and warns with that list before a change to a shared look is saved. - Section 9 gets the look editor (preview, bones by number and name, bone picking, items of a look); phase 6 shows the looks of an item and the items that share a look, read only. - Bones: the names in the .bmd files are export names (biped names on armor, otherwise Bone01, Box02, zx12, ...; in 396 of the 778 item model files the bone called BoneNN is not bone NN), so looks use bone numbers and the editor shows both. - The cleanup (now phase 14) writes follow-up design documents: the particle and effect system of ZzzEffect as data, and the looks of monsters and NPCs (their glow, the helper NPC plate look, the player transformations).
…D26) - D26: anything that several things use is defined once, with a name, and referenced by that name; editors show where it is used, warn with the list of users before a shared definition changes, make copies for variants, rename with the references, and refuse to delete what is still used. - New section "Beyond items": the effect code has about 3,950 callers (maps and monsters, characters and items, skills, network), so the areas that use it are planned together, each with its own design document: FX1 effect catalogue (names and creation values of effect, particle, lightning and sprite types), SK1 skills as data, SK2 skill looks, MN monsters and NPCs, FX2 effect behavior as data, later map objects, buffs, pets and sounds. Items, skills, monsters and NPCs share one look format (D25) and one look editor. - The phases table lists these areas; item phase 13 depends on FX1. - 4c3 brings a read-only look view to MuEditor (the looks of an item, the items of a look); phase 6 builds on it.
The parts of the items design that go beyond items move into their own document, 2026-09-29-data-driven-content-roadmap-design.md, which stays while its areas are open (the items design is removed after its last phase): - the shared decisions D25 (looks in data, one look format for items, skills, monsters and NPCs) and D26 (shared definitions), with their numbers kept; - the bones: export names in the item models, readable biped names on characters, bone numbers in the data; - the areas and their order (FX1 effect catalogue, item phase 13, SK1 skills, SK2 skill looks, MN monsters and NPCs, FX2 effect behavior, later map objects, buffs, pets and sounds), each with its own design document when its work starts; - the shared editor parts (look editor, effect browser and editor). The items design keeps short pointers to it, and its cleanup moves what later areas still need into the roadmap.
What item models do besides being drawn comes from "effect" in
Data/Items/Models instead of the type chain of RenderPartObjectEffect:
- The 29 item branches of the chain (80 items) become 27 named effects
in Render/Items/ItemEffects.cpp, moved unchanged; the Siege Potion and
the Contract share one. An effect places sprites, particles and
lightning on bones (the Devil's Key and Invitation, Rena, the wings),
changes values of the drawing (a pulsing glow mesh, a mesh hidden by
level, the level potions glow like) or draws the model itself (the
Dark Lord's scrolls, fruits, spirit, ...).
- The shine of 10 items below +3 becomes three effects: harmonyShine
(Jewel of Harmony, Moonstone Pendant, Illusion Sorcerer Covenant),
sealShine (the seals) and cursedCastleWater.
- The socket seeds and spheres and zen get "glow": {"level": 0}, which
is what their level checks did; the checks go.
- The effects of the event models of level variants (no model entries
yet) stay in ZzzObject.cpp, in ApplyEventModelEffect.
- An effect that does not exist is a warning when the models are opened;
the model loader gets the checks of the look names from ZzzOpenData.
- ItemModelTable (ItemModelLookup.h): one value per item type, taken
from the item model database after each build of it, for the render
styles and the effects.
- MuEditor: a read-only view of the looks above the item table, with the
model file, glow, render style and effect of the selected item and the
items that share a look (clicking one selects it).
A temporary recorder ran RenderPartObjectEffect for every model slot, on
an item object (set up with ItemObjectAttribute) and on a player object,
at the levels 0, 3, 7, 9, 13, 14 and 15, and recorded the branch of the
chain, the level after it and the drawing path that follows; the same
with the new code. All the same, except: on a player object the new code
does what it does on the item object, where the old checks looked at the
object's item (79 item slots, none of them drawn as a character part),
and zen reaches the drawing with level 0 from its glow level, which draws
it plainly at every level as before.
Mosch0512
left a comment
There was a problem hiding this comment.
Verdict: changes requested. (Posted as a comment: GitHub does not allow requesting changes on your own PR.)
Moving the chain itself looks right. I checked the effect of every model against the removed branches (all 52 match), each effect's Applied/Drawn against the old returns, and the glow level 0 of seeds, spheres and zen against the old level checks. Link objects, the inventory and ground items draw with o->Type == Type, so looking the effect up by Type instead of o->Type changes nothing in game.
Required:
- Looks list: a click often does not select or show the item (search filter, scroll position).
- An open Looks list squeezes the item table.
ApplyBloodBone:binstead ofModels[o->Type].ItemRenderStyles.cppstill includes<span>and<vector>(CODING_RULES rule 7).ItemModelTablein its own file (rule 6).- The Looks section in the usage docs, "Editing items (MuEditor)" in
docs/item-data.md(rule 11).
The other inline comments are non-blocking.
| { | ||
| if (usesLook(models[itemType]) && ImGui::Selectable(g_ItemDatabase.GetLogName(itemType).c_str(), false)) | ||
| { | ||
| CItemEditorTable::RequestScrollToIndex(itemType); |
There was a problem hiding this comment.
Required. A click here often does not select or show the item:
ItemEditorTable.cpponly handles the request when the item is inm_filteredItems. With a search active (search "seal", select Seal of Ascension, click Master Seal of Wealth undersealShine) or for an item without a name, the request is dropped and nothing happens.- When the row is found,
SetScrollY(GetTextLineHeightWithSpacing() * (row + 1))assumes rows one text line high, but the rows areInputIntfields with frame and cell padding, about 4px taller each. For items of groups 12 to 14 (rows in the thousands) the table stops hundreds of rows short, so the selected row is off screen.
The description says clicking selects the item in the table. Suggestion: always set the selection (and say so, or clear the search, when the search hides the item), and scroll with clipper.IncludeItemByIndex(row) before Step() plus ImGui::SetScrollHereY() when that row is drawn.
| ImGui::Separator(); | ||
|
|
||
| // The looks of the selected item (read only). | ||
| CItemEditorLooks::Render(m_selectedRow); |
There was a problem hiding this comment.
Required. The Looks section sits above the item table in the same window. The table scrolls (ScrollY) with an outer height of 0, which Dear ImGui aligns to the bottom of the window, so it only gets the height that is left. Opening a long list (the socketSeedSphere style is used by 30 items) shrinks the table down to 1px until the list is closed. Suggestion: draw the lists in a child or list box with a fixed height, or give the table a minimum height.
| b->RenderBody(RENDER_TEXTURE, o->Alpha, o->BlendMesh, o->BlendMeshLight, o->BlendMeshTexCoordU, | ||
| o->BlendMeshTexCoordV, o->HiddenMesh); | ||
| Vector(.9f, .1f, .1f, b->BodyLight); | ||
| Models[o->Type].StreamMesh = 0; |
There was a problem hiding this comment.
Required. Models[o->Type] is how the old chain named the drawn model; the effect is now picked by Type, and b is &Models[Type]. When o->Type != Type (like the player object in your recorder), this sets and resets StreamMesh of another model, and the Blood Bone draws its bright pass without the stream mesh. b->StreamMesh, as in ApplyBillOfBalrog, fixes it.
| } | ||
| // The render style of every item type, taken from the item model database | ||
| // after each build of it. | ||
| ItemModelTable<const RenderStyle*> g_itemStyles{[](const Data::Items::ItemModelDefinition& model) |
There was a problem hiding this comment.
Required. With the table moved to ItemModelTable, this file no longer uses std::span or std::vector, but still includes <span> and <vector> (lines 12 and 14). CODING_RULES rule 7: "Keep includes minimal — only what the file actually uses."
| // One value per item type, taken from the item model database after each | ||
| // build of it (the database counts its builds), e.g. the render style of | ||
| // every item. Drawing looks values up by model slot. | ||
| template <typename T> class ItemModelTable |
There was a problem hiding this comment.
Required. CODING_RULES rule 6: "Each new class or significant struct/type gets its own file, named after the class." ItemModelTable is a class that two systems use (render styles and effects), so it would go into ItemModelTable.h; ItemModelLookup.h keeps the lookup functions.
| RenderPartObjectBodyColor2(b, o, Type, 1.f, RENDER_CHROME4 | RENDER_BRIGHT | (RenderType & RENDER_EXTRA), 1.f); | ||
| } | ||
|
|
||
| void RenderHarmonyShine(BMD* b, OBJECT* o, int Type, float Alpha, int RenderType, float* Light) |
There was a problem hiding this comment.
Non-blocking. RenderHarmonyShine and RenderSealShine only differ in the light factor (1 and 0.9), and ApplyDevilsKey and ApplyDevilsInvitation only in the bones (1, 2 and 9, 10). One function each, taking the factor or the bones, would do. It is moved code, so this can also wait for phase 13.
| Vector(1.f, 1.f, 1.f, b->BodyLight); | ||
| b->StreamMesh = 0; | ||
| b->RenderMesh(0, RENDER_TEXTURE, o->Alpha, -1, o->BlendMeshLight, o->BlendMeshTexCoordU, o->BlendMeshTexCoordV); | ||
| if (Level == 1) |
There was a problem hiding this comment.
Non-blocking. The empty if (Level == 1) {} comes from the old code; a switch with cases 2 and 3 says the same without it.
| - change values of the drawing: a pulsing glow mesh (`wingsOfDragon`, | ||
| `wingsOfSoul`, `redSpirit`, `staffOfKundun`, `divineSet`), a mesh hidden | ||
| by level (`hiddenMeshByLevel` of the Siege Potion and the Contract, | ||
| `hideMesh1`), the level potions glow like (`potion`: +7 with any level); |
There was a problem hiding this comment.
Non-blocking. "+7 with any level": ApplyPotion only changes levels above 0, a +0 potion stays at 0 (your test checks that too). Maybe "+7 at every level above 0".
| `RenderPartObjectEffect` (about 80 item branches, 31 distinct). | ||
| - **4c3 Item effects:** `effect` names what the model does besides | ||
| being drawn, from the type chain of `RenderPartObjectEffect`: 29 item | ||
| branches for 80 items become 27 effects in |
There was a problem hiding this comment.
Non-blocking. I count 81 items: 42 with an effect that runs before the drawing (52 models with an effect minus the 10 shines) and 39 socket seeds and spheres (12/60–65, 70–72, 100–129). The PR description says 80 too.
| Render::Items::Effects::Result effect = Render::Items::Effects::Apply(b, o, Type, Alpha, Level); | ||
| if (effect == Render::Items::Effects::Result::None) | ||
| { | ||
| effect = ApplyEventModelEffect(b, o, Type, Level, ItemLevel); |
There was a problem hiding this comment.
Non-blocking. The tests call Effects::Apply directly; how RenderPartObjectEffect puts the pieces together is not tested (event models only when the item has no effect, the return after Drawn, the shine only below +3). If it is cheap, a small test of that order would keep it from breaking unnoticed.
- The shine of 10 items below +3 belongs to their render style: values (the light factor and two shine passes) that the drawing code in RenderPartObjectEffect applies, instead of three effects that called back into RenderPartObjectBody. The seals, the Illusion Sorcerer Covenant and the Cursed Castle water have a style already; the Jewel of Harmony and the Moonstone Pendant get harmonyShine (drawn plainly otherwise). "effect" now only means what runs before the drawing. - ItemModelTable in its own header; ItemRenderStyles.cpp drops the includes it no longer uses. - The Blood Bone sets the stream mesh of the drawn model (b), as the Bill of Balrog does. - ApplyPartObjectEffect: the effect of the item's model entry, or of the event model, as a function of its own with a test of that order. - A look check that is left out of LookNames reports all names instead of crashing. - The level switch of meshesPerLevel without its empty branch. - MuEditor Looks: clicking an item always selects it (and clears the search when it hides the item); the table scrolls to its row with the clipper instead of guessing the row height; the lists sit in boxes of fixed height and the table keeps a minimum height; the lists and the glow text are built when the selection or the model data changes, not every frame. - docs: the Looks section under "Editing items (MuEditor)", the shine under the render styles, 81 items (42 with an effect, 39 seeds and spheres), "+7 at every level above 0".
What an item does every frame before it is drawn is an item effect: "itemEffect" in Data/Items/Models, Render::Items::ItemEffects in the code, ItemEffectUnknown for a name that does not exist. The namespace no longer hides Render::Effects (the effect registry) inside Render::Items, and "effect" stays free for the effect types of the effect catalogue (FX1, effectTypes in the data), which item effects create instances of.
Mosch0512
left a comment
There was a problem hiding this comment.
Verdict: ready to merge. (Posted as a comment: GitHub does not allow approving your own PR.)
Re-review of 121e0c4 and 2e118d8. All six required items of the first review are fixed:
- Looks list: a click always selects the item and clears the search when it hides it; the table scrolls with
clipper.IncludeItemByIndexandSetScrollHereY. - The lists are list boxes of at most 8 rows, and the table keeps a minimum height.
ApplyBloodBoneusesb->StreamMesh.ItemRenderStyles.cppno longer includes<span>and<vector>.ItemModelTablehas its ownItemModelTable.h.- The Looks section is described under "Editing items (MuEditor)".
The non-blocking ones are fixed too: LookNames defaults to NoLookExists; the shine below +3 is now values of the render style (checked against the old code for all 10 items), so ItemEffects no longer calls back into ZzzObject.cpp; the rename to itemEffect / Render::Items::ItemEffects; the Looks cache; the switch in meshesPerLevel; both doc fixes (81 items, "+7 at every level above 0"); and the test of ApplyPartObjectEffect. Only ApplyDevilsKey / ApplyDevilsInvitation are still two copies, which is fine until phase 13.
Two new notes inline, both non-blocking.
| void Refresh(int itemType, const ItemModelDefinition& model) | ||
| { | ||
| const int databaseVersion = g_ItemModelDatabase.GetVersion(); | ||
| if (g_cache.itemType == itemType && g_cache.databaseVersion == databaseVersion) |
There was a problem hiding this comment.
Non-blocking. The cache only follows the model database, but the names in the lists (GetLogName, the neutral name) come from g_ItemDatabase, which the editor changes without rebuilding the model database. After renaming an item, moving one with the Index column (OnItemsSwapped) or Import from bmd, an open list keeps the old names until another item is selected; after a move, clicking an entry selects whatever now has that item type. Resetting the cache on those edits (or also keying it on a change counter of the item database) would keep it current.
| } | ||
|
|
||
| // Styles that shine below +3. | ||
| constexpr RenderStyle(const char* styleName, StyleFunction recipe, const ShineBelowPlus3& shineBelowPlus3) |
There was a problem hiding this comment.
Non-blocking. This constructor keeps the address of a const& parameter, while the textured one below takes a pointer. RenderStyles[] is const, not constexpr, so a later entry written as {"newShine", RenderPlainly, ShineBelowPlus3{...}} compiles and leaves shine pointing at a temporary that is gone after the initializer. Taking const ShineBelowPlus3* in both constructors, as the textured one does, makes the need for a named constant visible and keeps the two the same.
- The item database counts its changes (GetVersion); the Looks section of the item editor also rebuilds its lists when the items change, so a renamed or moved item or a bmd import shows at once. - The render style constructor for the shine below +3 takes a pointer, like the textured one, so a style cannot keep the address of a temporary shine.
Phase 4c3 of the data-driven items (design doc D24, see
docs/superpowers/specs/2026-09-25-data-driven-items-design.md): what item models do before they are drawn comes from the item model files instead of the type chain ofRenderPartObjectEffect. Builds on phase 4c2 (sven-n#658). With it, phase 4c (glow, render styles, effects) is complete.It also adds the plan beyond items: a new roadmap document for effects, skills, monsters and NPCs, and how their data is shared and edited.
What changes
"itemEffect"value insrc/bin/Data/Items/Models/*.json(42 models have one). An item effect is what an item does every frame before it is drawn; the effect types of the planned effect catalogue (FX1,effectTypes) are what it creates instances of, so the two keep different names:{ "number": 37, "file": "Data/Item/wing09.bmd", "textureFolders": ["Item"], ..., "itemEffect": "wingOfEternal" }Render/Items/ItemEffects(namespaceRender::Items::ItemEffects, so it does not hideRender::Effects, the effect registry): 28 of the 29 item branches of the chain (42 items) become 27 named effects, the code moved unchanged. The Siege Potion and the Contract share one. An effect runs before the model is drawn and:wingsOfDragon,staffOfKundun,divineSet, …), a mesh hidden by level (hiddenMeshByLevel,hideMesh1), or the level potions glow like (potion);fruits,spirit,bloodBone,invisibilityCloak,firecracker,gmGift,meshesPerLevel).harmonyShine, which only shines (138 styles now)."glow": {"level": 0}, which is what their level checks did. The checks go.ApplyEventModelEffectinZzzObject.cpp.ZzzOpenData(LookNames), so the data layer does not call the drawing code. A test checks that the item effects of the shipped models exist.ItemModelTable(ItemModelTable.h): one value per item type, taken from the item model database after each build of it. The render styles and the effects both use it (the table code of 4c2 moves there).ApplyPartObjectEffectruns the effect of the item's model entry, or for the event models theirs;RenderPartObjectEffectreturns when it drew the model.Editor.en.resxonly; other languages fall back to English, as for the bmd import and export.ZzzObject.cpp, and the zen and seal/jewel checks of the plain drawing below +3.Nothing changes while playing.
Plan beyond items (docs)
docs/superpowers/specs/2026-09-29-data-driven-content-roadmap-design.md:Bone01orzx12, and in 396 files the bone called "BoneNN" is not bone NN.docs/item-data.md: explainsitemEffect, the shine below +3 of the render styles, the glow level 0 of seeds, spheres and zen, the warning, and the Looks section of the item editor.Verification
A temporary recorder (not part of this PR) marked every branch of the old chain and every drawing path after it (the plain drawing and the shine below +3, the level glow). It ran
RenderPartObjectEffectfor every model slot:ItemObjectAttribute, like the game's) and on a player object;It recorded the branch of the chain, the level after it and the drawing path that follows, and did the same for the new code (the effects mapped to their first branch).
All 141,231 other records are the same. The only differences:
o->Type). None of these items is drawn as a part of a character: weapons and wings are link objects with their own object, and potions, seeds, seals and scrolls are never on a character. The same holds for the look calls: the effect code, the inventory and the ground items draw witho->Typeequal to the model.The recording was made before the review moved the shine below +3 into the render styles. That move is checked by a test instead: each of the 10 items has the light factor and the two shine passes of its old branch, and the drawing code applies them in the old order (the light, the model, the passes).
Tests
test_item_model_json.cpp: write/read ofitemEffect(afterrenderStyle), errors for values that are not a name.test_item_model_problems.cpp: the warning for an item effect that does not exist.test_item_model_files.cpp:ItemEffects::Applychanges the level of potions and the hidden mesh by level, returns "drawn" for effects that draw the model and "none" for other items and models;ApplyPartObjectEffect: the item's effect first, the event model only when the item has none;Test plan