mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
N5/D4: secret content pass — treasure-cache secret rooms + cross-zone keys
Secret rooms were dead content: every NodeKindSecret node silently collapsed to a normal exploration fight and its authored LootBias (1.5-3.0) was never read at runtime. D4 makes them what they read as — no-combat treasure caches. resolveRoom now diverts a secret node (keyed off the graph node, since CurrentRoomType has already lost the kind) to resolveSecretRoom before the RoomType switch — shared by manual !zone advance, !expedition run autopilot, and the sim. Each secret pays a guaranteed journal page (the D1a grant hook built "for secret rooms"), a LootBias-weighted treasure roll (floored at elite weight), and a guaranteed zone-tier consumable cache, with bespoke in-world discovery flavor. Underdark was the only T2+ zone with no secret; added at throne_gallery on the universal R4 tail (Lost Reliquary, Perception DC 17), merging to throne_steps so it's length-neutral. Two cross-zone keys: the Sunken Temple's Coral Reliquary grants a Sunken Sigil that opens a Sealed Reliquary in Manor Blackspire; the Underforge's Forge Vault grants an Underforge Seal that opens a Sealed Vault in the Underdark. Keys are persistent inventory items matched against LockKey key_id; grants are idempotent. Graph validator + no-soft-lock pass on both touched graphs; combat golden byte-identical; go build/vet/test green repo-wide. Claude-Session: https://claude.ai/code/session_017mEwUmmS7aQTP2NQXj6rUa
This commit is contained in:
@@ -988,6 +988,25 @@ func (p *AdventurePlugin) streamFlowThen(userID id.UserID, phaseMessages []strin
|
||||
// inter-phase delays — see resolveCombatRoom for the contract. For
|
||||
// non-combat rooms (entry, trap), phases is nil and outcome carries the
|
||||
// resolution narration.
|
||||
// currentSecretNode returns the run's current graph node when it is a secret
|
||||
// room, so resolveRoom can divert it to the treasure-cache resolver. Returns
|
||||
// false for a non-secret node, an unknown zone, or a legacy row that never got
|
||||
// a CurrentNode written (those pre-date branching graphs and have no secrets).
|
||||
func currentSecretNode(run *DungeonRun) (ZoneNode, bool) {
|
||||
if run.CurrentNode == "" {
|
||||
return ZoneNode{}, false
|
||||
}
|
||||
g, ok := loadZoneGraph(run.ZoneID)
|
||||
if !ok {
|
||||
return ZoneNode{}, false
|
||||
}
|
||||
n, ok := g.Nodes[run.CurrentNode]
|
||||
if !ok || n.Kind != NodeKindSecret {
|
||||
return ZoneNode{}, false
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
func (p *AdventurePlugin) resolveRoom(userID id.UserID, run *DungeonRun, zone ZoneDefinition, compact bool) (intro string, phases []string, outcome string, ended bool, err error) {
|
||||
// Revisit R2 — a cleared room stays cleared. Advancing out of a room the
|
||||
// player backtracked into must not re-roll its combat or re-arm its trap.
|
||||
@@ -1004,6 +1023,14 @@ func (p *AdventurePlugin) resolveRoom(userID id.UserID, run *DungeonRun, zone Zo
|
||||
if run.RoomIsCleared(run.CurrentRoom) {
|
||||
return
|
||||
}
|
||||
// N5/D4 — a secret room is a no-combat treasure cache, not the exploration
|
||||
// fight its RoomType collapses to. Keyed off the graph node (which still
|
||||
// carries NodeKindSecret) rather than CurrentRoomType (which does not), so
|
||||
// it must be checked before the RoomType switch below.
|
||||
if node, ok := currentSecretNode(run); ok {
|
||||
outcome = p.resolveSecretRoom(userID, run, zone, node)
|
||||
return
|
||||
}
|
||||
switch run.CurrentRoomType() {
|
||||
case RoomEntry:
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user