feat: paperdoll UI + equipment model (layers, conflicts, OnEquip) (#106) #116

Merged
marco merged 4 commits from feat/paperdoll-ui into main 2026-07-20 21:41:33 +02:00
Owner

Implements the bulk of #106 — paperdoll UI + the equipment model — on feat/paperdoll-ui.

Client — composable gumps + paperdoll

  • Reusable gump framework (not one-off panels): Gump container + a GumpElement family (background / picture / item-icon / label / hotspot / button with callback), with per-window dragging as a property (Draggable). Ready for future vendor/NPC/dialog windows.
  • Paperdoll (Ctrl+P): the UOML frame with the body + worn equipment drawn into its character slot (hand-tuned), draggable, double-click a backpack item to equip, double-click the character to unequip, Close button. Worn art resolved by convention (worn_<kind>_<gender>) — no per-item map.
  • Gump-id manifest moved to data (gumps.json): adding art = a JSON row + just pack, no code.

Server — equipment model (ModernUO-informed)

  • EquipSlot{Weapon,Armor} -> EquipLayer{OneHanded,TwoHanded,Torso} + an IEquippable interface (Layer + OnEquip/OnUnequip).
  • Conflict resolution via a small EquipConflicts table: equipping displaces conflicting layers (a 2H weapon and a 1H weapon can't coexist); freed items return to the backpack. Added Greatsword (2H) to exercise it.
  • A weapon's OnEquip arms the wearer (Mobile.Arm), OnUnequip disarms; effects are derived (re-applied on restore, never persisted). Replaces the old CombatSystem.ApplyWeapon path.
  • Orchestration in ItemRegistry (a component); World stays a thin facade.
  • Server sends a CannotEquip cue for non-equippable items; ProtocolVersion bumped; item save bumped to v2 (legacy Armor slot -> Torso).

Tests / gates

  • New: Equip_ConflictingHandLayers_DisplaceEachOther (World), Equip_NonEquippableItem_SendsCannotEquipNotice (integration), PaperdollLayout tests. All green (GameServer 170, Client.Core 65, whole suite passing); whole solution builds 0 warnings; CSharpier clean.

Deferred (remaining for #106, follow-ups)

  • Ground double-click equip (double-click a ground item <=1 tile -> pickup+equip): clean form wants a server EquipOutcome (Equipped/OutOfRange/CannotEquip) to give the right cue — a focused change.
  • Namespace reorg into logical domains (IsoMmo.Client.UI / .Gumps / …) crossing the Core/Client assemblies — mechanical, its own PR.

Closes most of #106.

Implements the bulk of #106 — paperdoll UI + the equipment model — on `feat/paperdoll-ui`. ## Client — composable gumps + paperdoll - **Reusable gump framework** (not one-off panels): `Gump` container + a `GumpElement` family (background / picture / item-icon / label / hotspot / **button with callback**), with **per-window dragging** as a property (`Draggable`). Ready for future vendor/NPC/dialog windows. - **Paperdoll** (Ctrl+P): the UOML frame with the body + worn equipment drawn into its character slot (hand-tuned), draggable, **double-click a backpack item to equip**, **double-click the character to unequip**, Close button. Worn art resolved **by convention** (`worn_<kind>_<gender>`) — no per-item map. - Gump-id manifest moved to **data** (`gumps.json`): adding art = a JSON row + `just pack`, no code. ## Server — equipment model (ModernUO-informed) - `EquipSlot{Weapon,Armor}` -> **`EquipLayer{OneHanded,TwoHanded,Torso}`** + an **`IEquippable`** interface (`Layer` + `OnEquip`/`OnUnequip`). - **Conflict resolution** via a small `EquipConflicts` table: equipping displaces conflicting layers (a 2H weapon and a 1H weapon can't coexist); freed items return to the backpack. Added **`Greatsword`** (2H) to exercise it. - A weapon's **OnEquip arms the wearer** (`Mobile.Arm`), OnUnequip disarms; effects are **derived** (re-applied on restore, never persisted). Replaces the old `CombatSystem.ApplyWeapon` path. - Orchestration in `ItemRegistry` (a component); `World` stays a thin facade. - Server sends a **`CannotEquip`** cue for non-equippable items; **`ProtocolVersion` bumped**; item save bumped to **v2** (legacy Armor slot -> Torso). ## Tests / gates - New: `Equip_ConflictingHandLayers_DisplaceEachOther` (World), `Equip_NonEquippableItem_SendsCannotEquipNotice` (integration), `PaperdollLayout` tests. **All green** (GameServer 170, Client.Core 65, whole suite passing); whole solution builds **0 warnings**; CSharpier clean. ## Deferred (remaining for #106, follow-ups) - **Ground double-click equip** (double-click a ground item <=1 tile -> pickup+equip): clean form wants a server `EquipOutcome` (Equipped/OutOfRange/CannotEquip) to give the right cue — a focused change. - **Namespace reorg** into logical domains (`IsoMmo.Client.UI` / `.Gumps` / …) crossing the Core/Client assemblies — mechanical, its own PR. Closes most of #106.
Client-side gump element framework (Gump container + GumpElement family:
background/picture/item-icon/label/hotspot/button with callbacks) that the
paperdoll is composed from — reusable for future vendor/NPC/dialog windows.
Paperdoll (Ctrl+P) draws the UOML frame + body + worn gumps (found by convention
worn_<kind>_<gender>, no per-item map); double-click a backpack item to equip,
double-click the character to take off the topmost. Gump-id manifest moved to
data (gumps.json); server sends a CannotEquip cue; ProtocolVersion bumped.
Gump base gains per-window dragging (grab the top strip); GumpPicture can fit to
height so the UO body fills the paperdoll's character slot; the slot fractions and
the Close button (on the right-hand scroll) are hand-tuned to the 0x07D0 frame. Worn
gump resolved by convention (worn_<kind>_male), double-click the character to unequip.
feat(equip): EquipLayer + IEquippable model with conflict resolution + OnEquip hooks
All checks were successful
ci / Lint & Test (pull_request) Successful in 1m41s
c3ddd8be9d
Replace EquipSlot{Weapon,Armor} with EquipLayer{OneHanded,TwoHanded,Torso} + an
IEquippable interface (Layer + OnEquip/OnUnequip). Equipping displaces conflicting
layers (a 2H weapon and a 1H weapon can't coexist) via a small EquipConflicts table;
freed items return to the backpack. A weapon's OnEquip arms the wearer (Mobile.Arm),
OnUnequip disarms — effects are derived, re-applied on restore, never persisted.
Adds Greatsword (2H) to exercise the conflict. Item save bumped to v2 (legacy Armor
slot -> Torso). World stays a thin facade; the orchestration lives in ItemRegistry.
feat(paperdoll): enlarge the character figure ~30%
All checks were successful
ci / Lint & Test (pull_request) Successful in 1m51s
8f56528a68
marco merged commit e4ae31dc15 into main 2026-07-20 21:41:33 +02:00
marco deleted branch feat/paperdoll-ui 2026-07-20 21:41:33 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
marco/IsoMmo!116
No description provided.