Skip to content

Show mana-crystal charge on inventory and quickslot icons #7

Description

@zoeyrose

Important

This preserved product issue is now part of the fresh MIT replacement program. Final implementation owner: atrinik/client. Legacy C/SDL2, packet, global-state, and file-path details below are historical evidence only.

Replacement implementation contract

Preserve the charge presentation and update behavior. Add typed authoritative charge/capacity/revision fields in Game Protocol 1 and render inventory/quickslot overlays through shared renderer resources; remove classic packet-version and C-struct assumptions.

New implementation and tests are independent MIT work unless an exact contribution by an approved MIT provenance grantor is admitted through the recorded file-level MIT grant. Do not copy or mechanically translate other GPL source/tests. Preserve every player-facing, accessibility, disclosure, and performance design decision below.

Required verification

  • Add deterministic Rust and/or Go tests at the owning boundary plus cross-language protocol fixtures for new fields.
  • Exercise malformed/stale/reordered inputs and lifecycle failure without partial state.
  • Use the shared renderer rather than a client/editor fork.
  • Validate through a wrapper-managed replacement scenario where the feature is interactive.
Preserved product/design specification and historical implementation notes

Summary

Show the charge level of carried mana crystals directly on their inventory and quickslot icons so players do not have to examine a crystal after every use.

Render a compact mana-blue charge meter over the existing icon. The meter should have an obvious empty track, fill continuously from 0% to 100%, and update immediately when mana enters or leaves the crystal. This should reuse the existing crystal artwork and animation; no new faces are required.

Existing behavior and implementation seams

  • Power crystals are object type POWER_CRYSTAL / 156.
  • server/src/types/power_crystal.c stores current charge in object::stats.sp and capacity in object::stats.maxsp, then changes both when the crystal is applied.
  • server/src/types/player.c:examine() is currently the only player-facing charge presentation. It derives descriptions such as empty, half full, and fully charged from the same fields.
  • The ordinary item packets in server/src/socket/item.c:add_object_to_packet() send the object type but do not send crystal charge or capacity. UPD_EXTRA already carries type-specific payloads for spells, skills, forces, and poisoning, making it the natural extension point for power-crystal metadata.
  • The client-side object in client/src/include/item.h consequently has no crystal charge state.
  • client/src/gui/widgets/inventory.c:object_show_inventory() draws both inventory icons and the objects referenced by client/src/gui/widgets/quickslots.c, so one overlay implementation covers both presentations.
  • The four existing power_crystal faces are a looping glow animation. Animation phase is not charge state and should remain independent of the new meter.

Proposed implementation

Protocol and authoritative state

Extend the legacy server-to-client item payload for POWER_CRYSTAL objects under UPD_EXTRA:

  1. For a POWER_CRYSTAL record with UPD_EXTRA, append required current_charge and capacity fields in that order as network-byte-order uint16 values. Both values must be in the server fields' non-negative int16 range and current_charge <= capacity before serialization.
  2. Decode them into explicit charge/capacity fields on the client item object.
  3. Reject a truncated type-specific payload and defensively clamp the visual fraction to [0, capacity]. A zero-capacity crystal renders as empty and must never divide by zero.
  4. Include this type-specific extra data in the initial player-inventory item record as well as later updates.
  5. Bump the strict legacy SOCKET_VERSION and document the new field order and widths in doc/ADS/ADS-2; client and server already reject mismatched versions.

The server remains authoritative. Do not infer crystal charge from player mana changes, animation frames, item names, or the number of times the quickslot was used.

Update lifecycle

  • In server/src/types/power_crystal.c, send UPD_EXTRA after an application actually changes stats.sp.
  • Audit every in-repository mutation path. In particular, the Old Outpost enhancement script creates the upgraded Gandyld crystal and copies old.sp into new.sp; its resulting item record must contain the preserved charge.
  • Ensure an inventory replay, reconnect, move between inventory/container, and quickslot reference all resolve to the same current charge.
  • Do not send redundant updates when applying a full crystal while the player is full or an empty crystal while the player lacks mana.

Icon presentation

In object_show_inventory():

  • Draw the meter only for TYPE_POWER_CRYSTAL.
  • Use a slim, high-contrast dark track with mana-blue fill near the bottom edge of the 32x32 icon.
  • Reserve clear states for 0% and 100%; non-zero charge should always produce at least one visible fill pixel after rounding.
  • Keep the meter legible with the animated face and existing magical, locked, cursed, applied, selection, and quickslot-key overlays. Adjust draw order or inset placement rather than hiding status markers.
  • Use the same rendering in the main inventory and quickslots. The below/ground-item view does not need charge metadata or a meter unless its item records are deliberately extended too.
  • Preserve examination text as the detailed capacity/description view; the icon is an at-a-glance indicator, not a replacement for it.

Alternatives considered

  • Select a face or animation frame on the server: the existing frames depict a looping glow, not increasing fullness. Reusing them as charge buckets would make animation phase and state ambiguous, while new face sets would multiply content and attribution work.
  • Append charge to the item name: this adds visual churn, consumes inventory text space, and does not help the icon-only quickslot view.
  • Client-side prediction: applying a crystal does not tell the client enough to reconstruct the transfer reliably, and server or script changes would desynchronize it.

Tests and validation

  • Add packet encode/decode coverage for empty, partial, full, and zero-capacity crystals, plus malformed/truncated payload handling.
  • Verify initial inventory synchronization and immediate ITEM_UPDATE synchronization after charging and draining.
  • Verify no update is emitted when charge does not change.
  • Verify Gandyld crystal upgrades preserve both authoritative charge and the displayed fraction when capacity changes from 50 to 100 to 200.
  • Verify reconnect/inventory replay and moving a crystal through a container retain the correct display.
  • Visually inspect 0%, a small non-zero charge, about 50%, almost full, and 100% in both inventory and quickslots, including identified magical and inventory-locked crystals.
  • Build both legacy client and server, run focused server/protocol tests, and update ADS-2 in the implementation change.

Acceptance criteria

  • Every carried mana crystal shows an at-a-glance charge meter in inventory and quickslots.
  • The meter updates immediately after mana is transferred in either direction.
  • Empty, partial, and full states are visually distinct without examining the item.
  • The display is derived only from server-authoritative current charge and capacity.
  • Existing crystal animation and item-status overlays remain readable.
  • Initial inventory load, reconnect, containers, quickslot references, and Gandyld upgrades stay synchronized.
  • Zero-capacity and malformed values cannot cause division by zero, overflow, or out-of-bounds rendering.
  • The protocol version and ADS-2 specification are updated with automated packet coverage.

Shared item-adornment ownership

Implement the charge meter through the reusable item-adornment/layout helper shared with atrinik/server#6. The helper owns reserved icon regions, clipping, scale-aware geometry, and deterministic composition for charge bars, rarity frames, quantities, selection, applied/unpaid/locked state, magical/cursed/damned/trapped markers, container-chain cues, cooldowns, inventory, and quickslots. atrinik/server#29 consumes the same typed presentation metadata for comparison/tooltips.

Protocol-epoch coordination

Coordinate this wire change through atrinik/atrinik#168's next classic protocol epoch with atrinik/server#26, atrinik/server#25, atrinik/atrinik#156, #13, #9, #7, and atrinik/server#6 where practical. Do not reserve an isolated numeric version in advance. Land atrinik/atrinik#190's bounded packet primitives first where this payload uses them, then update all current producers, consumers, bots, fixtures, tests, and ADS-2 together.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Fields

    Priority

    None yet

    Start date

    None yet

    Target date

    None yet

    Effort

    None yet

    Projects

    Status
    Backlog

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions