mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 16:42:41 +00:00
B4: Slot classifier no longer treats "Springing" / "Snaring" / "Devouring"
as ring matches; tokenises by word boundary instead of raw substring.
Adds DnDSlotCloak so cloaks/capes/mantles/wings stop evicting body armor
from the chest slot. Regenerated magic_items_srd_data.go: boots_of_*,
gloves_of_missile_snaring, bag_of_devouring, talismans, and 6 cloaks all
land in the right slot.
R4: equipMagicItem swap-back returns the prior occupant at full Value
instead of half — swapping a curio shouldn't tax it.
R5: Attunement count is recomputed *after* the swap-back so freeing the
prior occupant's bond opens the slot for the incoming item.
R1: Inventory tags magic_item rows with 🔮 + rarity label and prints a
single equip-magic footer when any are present.
R6: Sheet's Magic Items block marks unbonded items as **(inactive)**
with the reason (cap full vs unbonded), so over-cap items aren't silent.
R7: New activeMagicItemsLine surfaces a one-shot "your curios stir: …"
at combat-start in both the dungeon path and !fight, mirroring the way
class passives are surfaced.
R8/R9: dropMagicItemLoot pretty-prints rarity, drops "wondrous", calls
attunement "needs bonding", appends "auto-uses in combat" for
potions/scrolls, and routes persistence errors to slog instead of
leaking %v into chat.
R2/R3: Curios shelf now shows "Very Rare" not "very_rare", drops the
bare "wondrous" word (the effect line carries it), renders the codified
magicItemEffectSummary above the SRD desc, and ends with a one-line
plain-language "what is bonding" footnote.
R10: Curios stock day flips at 06:00 UTC instead of midnight so EU
players don't see a fresh shelf at 1 a.m. mid-session.
R11: Curios buy resolver disambiguates fuzzy matches — typing "ring"
when several rings are on the shelf lists candidates instead of
silently selling the first.
P1: Greeting grid pairs Curios with an Exit chip so the 2-column
emoji layout doesn't dangle.
P2: Equip-magic empty state trimmed to one line.
P4 (back-from-curios reprompt) deferred — the existing back-flow is
correct, just verbose; not worth the surface-area expansion this
session.
Tests: word-boundary classifier, cloak/chest coexistence, full-value
swap-back. go test ./... + go vet clean.
142 lines
4.3 KiB
Go
142 lines
4.3 KiB
Go
package plugin
|
|
|
|
// Phase 4 — D&D equipment view.
|
|
//
|
|
// Strategy per v1.1 §7.3: legacy adventure_equipment is the source of truth
|
|
// for what a player has equipped. The D&D layer maps the 5-slot legacy
|
|
// scheme onto the 10-slot D&D scheme at read time. No migration writes
|
|
// happen. The five new slots (legs, hands, ring_1, ring_2, amulet) are
|
|
// reserved for future drops and stay empty for now.
|
|
|
|
// DnDSlot is the canonical D&D equipment slot. legacy adventure_equipment
|
|
// stores items under EquipmentSlot (weapon/armor/helmet/boots/tool); we
|
|
// map those into D&D slots in mapLegacySlot.
|
|
type DnDSlot string
|
|
|
|
const (
|
|
DnDSlotHead DnDSlot = "head"
|
|
DnDSlotChest DnDSlot = "chest"
|
|
DnDSlotLegs DnDSlot = "legs"
|
|
DnDSlotHands DnDSlot = "hands"
|
|
DnDSlotFeet DnDSlot = "feet"
|
|
DnDSlotMainHand DnDSlot = "main_hand"
|
|
DnDSlotOffHand DnDSlot = "off_hand"
|
|
DnDSlotRing1 DnDSlot = "ring_1"
|
|
DnDSlotRing2 DnDSlot = "ring_2"
|
|
DnDSlotAmulet DnDSlot = "amulet"
|
|
// DnDSlotCloak is a magic-item-only slot. Cloaks/capes/mantles drape over
|
|
// chest armor in 5e fiction, so giving them their own slot lets a player
|
|
// wear a Cloak of Protection on top of a Mithral Plate without one
|
|
// silently evicting the other. The legacy equipment mapper never targets
|
|
// this slot — only magic_items_srd_data.go writes to it.
|
|
DnDSlotCloak DnDSlot = "cloak"
|
|
)
|
|
|
|
// dndSlotOrder controls display order for !sheet.
|
|
var dndSlotOrder = []DnDSlot{
|
|
DnDSlotHead, DnDSlotChest, DnDSlotCloak, DnDSlotLegs, DnDSlotHands, DnDSlotFeet,
|
|
DnDSlotMainHand, DnDSlotOffHand,
|
|
DnDSlotRing1, DnDSlotRing2, DnDSlotAmulet,
|
|
}
|
|
|
|
// mapLegacySlot translates a legacy EquipmentSlot into the D&D slot it
|
|
// occupies in the new view. The legacy `tool` slot is mapped to off_hand
|
|
// since tools (pickaxes, fishing rods, etc.) function as off-hand items.
|
|
func mapLegacySlot(s EquipmentSlot) DnDSlot {
|
|
switch s {
|
|
case SlotWeapon:
|
|
return DnDSlotMainHand
|
|
case SlotArmor:
|
|
return DnDSlotChest
|
|
case SlotHelmet:
|
|
return DnDSlotHead
|
|
case SlotBoots:
|
|
return DnDSlotFeet
|
|
case SlotTool:
|
|
return DnDSlotOffHand
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// ── Rarity inference ─────────────────────────────────────────────────────────
|
|
|
|
// DnDRarity tags an item for D&D-style display. Inferred from the legacy
|
|
// tier + masterwork flag. New drops with explicit dnd_rarity (Phase 4+
|
|
// loot generation) skip this inference.
|
|
type DnDRarity string
|
|
|
|
const (
|
|
RarityCommon DnDRarity = "Common"
|
|
RarityUncommon DnDRarity = "Uncommon"
|
|
RarityRare DnDRarity = "Rare"
|
|
RarityEpic DnDRarity = "Epic"
|
|
RarityVeryRare DnDRarity = "VeryRare"
|
|
RarityLegendary DnDRarity = "Legendary"
|
|
)
|
|
|
|
// rarityIcon — leading symbol for !sheet rendering (color stand-ins for
|
|
// terminals that don't render emoji color squares well).
|
|
func rarityIcon(r DnDRarity) string {
|
|
switch r {
|
|
case RarityCommon:
|
|
return "⬜"
|
|
case RarityUncommon:
|
|
return "🟩"
|
|
case RarityRare:
|
|
return "🟦"
|
|
case RarityEpic, RarityVeryRare:
|
|
return "🟪"
|
|
case RarityLegendary:
|
|
return "🟧"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// inferRarity maps legacy gear (tier + masterwork + arena_set) to a D&D
|
|
// rarity tier. Used at read time when dnd_rarity is empty.
|
|
func inferRarity(tier int, masterwork bool, arenaTier int) DnDRarity {
|
|
if masterwork {
|
|
// Masterwork is the legacy version of "this gear is special" —
|
|
// always treat as at least Rare to match its mechanical weight.
|
|
if tier >= 5 {
|
|
return RarityEpic
|
|
}
|
|
return RarityRare
|
|
}
|
|
if arenaTier > 0 {
|
|
// Arena set gear: rarity scales with arena tier.
|
|
switch arenaTier {
|
|
case 1, 2:
|
|
return RarityUncommon
|
|
case 3:
|
|
return RarityRare
|
|
case 4:
|
|
return RarityEpic
|
|
default:
|
|
return RarityLegendary
|
|
}
|
|
}
|
|
switch {
|
|
case tier <= 2:
|
|
return RarityCommon
|
|
case tier <= 4:
|
|
return RarityUncommon
|
|
case tier <= 6:
|
|
return RarityRare
|
|
case tier <= 8:
|
|
return RarityEpic
|
|
default:
|
|
return RarityLegendary
|
|
}
|
|
}
|
|
|
|
// equipmentRarity returns the rarity to display for an AdvEquipment row.
|
|
// Honors any explicit dnd_rarity on the row (Phase 4+ loot drops);
|
|
// otherwise falls back to inferRarity.
|
|
func equipmentRarity(eq *AdvEquipment) DnDRarity {
|
|
if eq == nil {
|
|
return RarityCommon
|
|
}
|
|
return inferRarity(eq.Tier, eq.Masterwork, eq.ArenaTier)
|
|
}
|