mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 00:52:40 +00:00
N3/P6d: a party where every member can see the dungeon
Every ownership lookup in the adventure module keys on a user id, and a party member owns neither the expedition row nor the zone run. So each player-facing read quietly told them they were not playing. Rewire them. Reads a member should see resolve through activeExpeditionFor / activeZoneRunFor. Leader-only actions answer with copy that names the leader instead of denying the expedition. Three busy-guards had to start refusing a member outright: !zone enter, !expedition start and !sell all keyed on the sender's own row, so a seated member could open a private dungeon, outfit a rival expedition, or run a shop from the boss room. Four things the rewire itself exposed: !resources looks like a read but seed-persists harvest nodes, and saveHarvestNodes rewrites the entire region_state blob — kills, event gates, temporal stack — last-write-wins. Reaching it as a member would revert the leader's walk from a stale snapshot. Persist only for the owner; seedRoomNodes is pure, so a member re-derives the same nodes. !zone taunt moves the party's shared mood, which is intended and safe: applyMoodEvent lands an atomic delta. Its neighbour applyMoodDecayIfStale writes an absolute gm_mood from the caller's snapshot, and every command takes the *sender's* lock — a member running it against the leader's run holds the wrong mutex. The owner check now lives on that function. A seat outlives status='active'. releaseParty deliberately skips the seven-day 'extracting' limbo, so the roster persists while activeExpeditionFor goes blind — long enough for a member to open a run that wins every lookup once the leader !resumes. seatedExpeditionFor spans both statuses; it is what the busy-guards ask. !expedition run was still member-blind. It is the same walk as !zone advance, reached by its other name. isPartyMember replaces `run != nil && !isLeader`: activeZoneRunFor reports isLeader=false for a player with no run anywhere, so the bare test sends a solo player to go ask their leader. Golden byte-identical; solo T1 expedition clears end-to-end.
This commit is contained in:
@@ -76,16 +76,23 @@ func revisitZoneRun(runID, targetNode string, visited []string) (int, error) {
|
||||
// `!zone revisit <N>`). N is the 1-indexed room number from `!map`'s Path
|
||||
// strip.
|
||||
func (p *AdventurePlugin) handleRevisitCmd(ctx MessageContext, rest string) error {
|
||||
run, err := getActiveZoneRun(ctx.Sender)
|
||||
run, isLeader, err := activeZoneRunFor(ctx.Sender)
|
||||
if err != nil {
|
||||
return p.SendDM(ctx.Sender, "Couldn't read run state: "+err.Error())
|
||||
}
|
||||
if run == nil {
|
||||
return p.SendDM(ctx.Sender, "No active zone run. Use `!zone enter <id>`.")
|
||||
}
|
||||
if cs, _ := getActiveCombatSession(ctx.Sender); cs != nil {
|
||||
// The fight outranks the path: a member swinging at a monster should hear
|
||||
// about the monster, not about who picks the route.
|
||||
if cs, _ := activeCombatSessionFor(ctx.Sender); cs != nil {
|
||||
return p.SendDM(ctx.Sender, "⚔️ Finish the fight first — `!attack` or `!flee`.")
|
||||
}
|
||||
if !isLeader {
|
||||
// Backtracking moves the whole party and costs the whole party a point
|
||||
// of threat. Same call as the fork: the leader's.
|
||||
return p.SendDM(ctx.Sender, msgLeaderPicksPath)
|
||||
}
|
||||
// A pending fork lives at the node the player is standing on, and both
|
||||
// `!zone advance` and `!zone go` resolve it without checking where that
|
||||
// is. Walking away would leave a prompt pointing at a room the player
|
||||
|
||||
Reference in New Issue
Block a user