mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 08:52:42 +00:00
Adv 2.0 D&D layer: Phases 1-8 + Phase 9 SP1+SP2
Combines all uncommitted D&D work into one checkpoint: Phases 1-8 (per gogobee_dnd_session_summary.md): - Race/class/stats system, !setup flow, !sheet, !respec - d20-vs-AC combat with race/class passives, auto-migration - XP/level-up, skill checks, NPC refund hooks - Equipment slot mapping, rarity, !rest short/long - Pre-arm active abilities, resource pools - Bestiary, !roll/!stats/!level, onboarding DM - Audit fixes A-I, flavor wiring under internal/flavor/ - Phase 8 weapon dice + armor AC tables (gogobee_equipment_appendix) Phase 9 SP1 — spell system foundations: - 79 SpellDefinitions (76 in-scope + 3 reaction-deferred) - dnd_known_spells, dnd_spell_slots tables - pending_cast / concentration_* columns on dnd_character - Slot tables for Mage/Cleric/Ranger, DC + attack-bonus math - Long-rest refreshes slots; respec wipes spell state - Auto-grant default known list on !setup confirm and auto-migration Phase 9 SP2 — !cast / !spells / !prepare commands: - Out-of-combat HEAL and UTILITY resolve immediately - Damage/control/buff queue as pending_cast for next combat - Audit-style save-then-debit, slot refund on save failure - !cast --drop, concentration supersession, prep-cap enforcement - Reaction spells refused with Phase 11 note SP3 (combat-time resolution of pending_cast) and SP4 (full prep/learn tests) are next. Build clean, full test suite green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
134
internal/plugin/dnd_equipment.go
Normal file
134
internal/plugin/dnd_equipment.go
Normal file
@@ -0,0 +1,134 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// dndSlotOrder controls display order for !sheet.
|
||||
var dndSlotOrder = []DnDSlot{
|
||||
DnDSlotHead, DnDSlotChest, 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"
|
||||
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:
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user