mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 08:52:42 +00:00
R2: !revisit <N> -- walk back one edge, for +1 threat
Retires forward-only navigation. `!revisit <N>` (alias `!zone revisit`) walks one graph edge back to a room in VisitedNodes, chosen by the number the player reads off !map's Path strip. Edges are directed, but a corridor you walked down is a corridor you can walk back up, so adjacency checks both directions. !map now prints the legal targets. Cost is +1 threat, not the discount the plan specced. There was nothing to discount: movement charges neither threat nor supplies, and a cleared room fires no combat, so backtracking was free. +1 mirrors harvest noise -- less than a fight (+5) or an elite (+8). Standalone runs have no threat clock and pay nothing. A siege guard refuses the move at threat >= 99: siege is one-way sticky, and a player must not be able to strand their own expedition on a move they made to go pick up a herb. The plan claimed terminal CombatSession rows already prevent re-triggering a cleared room. They gate only Elite and Boss, at the doorway. An Exploration room re-entered resolveCombatRoom unconditionally and a Trap room re-armed -- so revisit would have been an infinite farm: step back, advance, repeat. resolveRoom now early-returns on an already-cleared room. No-op forward-only, since advance clears a room only after resolving it. Autopilot is ticker-driven with no on/off switch, so it would have walked the player straight back out of the room they revisited. grantAutorunGrace stamps last_autorun_at to buy one cooldown window. That is a stopgap; R5 still owes the real policy. Fork re-pick stays deferred to R3: revisit refuses while a fork is pending, because advance and `!zone go` resolve node_choices without checking which node the player is standing on.
This commit is contained in:
@@ -59,6 +59,8 @@ func (p *AdventurePlugin) handleDnDZoneCmd(ctx MessageContext, args string) erro
|
||||
return p.zoneCmdMap(ctx)
|
||||
case "advance", "next", "a":
|
||||
return p.zoneCmdAdvance(ctx)
|
||||
case "revisit", "back":
|
||||
return p.handleRevisitCmd(ctx, rest)
|
||||
case "abandon", "leave", "quit":
|
||||
return p.zoneCmdAbandon(ctx)
|
||||
case "taunt":
|
||||
@@ -81,6 +83,7 @@ func zoneHelpText() string {
|
||||
b.WriteString("`!zone map` — show the room layout\n")
|
||||
b.WriteString("`!zone advance` — resolve the current room and move on\n")
|
||||
b.WriteString("`!zone go <n>` — at a fork, take path #n\n")
|
||||
b.WriteString("`!revisit <n>` — walk back to a room you've already cleared\n")
|
||||
b.WriteString("`!zone abandon` — end the active run (no rewards)\n")
|
||||
b.WriteString("`!zone taunt` — poke TwinBee (they'll remember)\n")
|
||||
b.WriteString("`!zone compliment` — flatter TwinBee (they'll like that)\n")
|
||||
@@ -311,6 +314,13 @@ func (p *AdventurePlugin) zoneCmdMap(ctx MessageContext) error {
|
||||
if path := renderVisitedPath(g, run); path != "" {
|
||||
b.WriteString("\n**Path:** " + path)
|
||||
}
|
||||
if targets := revisitTargets(g, run); len(targets) > 0 {
|
||||
nums := make([]string, len(targets))
|
||||
for i, n := range targets {
|
||||
nums[i] = fmt.Sprintf("`!revisit %d`", n)
|
||||
}
|
||||
b.WriteString("\n**Back to:** " + strings.Join(nums, " · "))
|
||||
}
|
||||
return p.SendDM(ctx.Sender, b.String())
|
||||
}
|
||||
// No registered graph (defensive — every zone has one post-G8).
|
||||
@@ -933,6 +943,21 @@ func (p *AdventurePlugin) streamFlowThen(userID id.UserID, phaseMessages []strin
|
||||
// non-combat rooms (entry, trap), phases is nil and outcome carries the
|
||||
// resolution narration.
|
||||
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.
|
||||
//
|
||||
// The revisit plan assumed terminal CombatSession rows already gated this.
|
||||
// They only gate Elite/Boss (checked at the doorway in advanceOnceWithOpts);
|
||||
// an Exploration room re-enters resolveCombatRoom unconditionally, and a
|
||||
// Trap room re-arms. Without this, `!revisit` would be a loot exploit:
|
||||
// walk back one room, advance, farm the same enemy forever.
|
||||
//
|
||||
// A no-op under forward-only navigation — advance clears the current room
|
||||
// only after resolving it, so the room being resolved is never already in
|
||||
// RoomsCleared.
|
||||
if run.RoomIsCleared(run.CurrentRoom) {
|
||||
return
|
||||
}
|
||||
switch run.CurrentRoomType() {
|
||||
case RoomEntry:
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user