mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 16:42:41 +00:00
Adv 2.0 D&D Phase R R1: Legacy activity-loop deprecation
Retires the standalone !adventure dungeon/mine/forage/fish daily loop in favor of !expedition. Gate intercepts legacy 1/2/3/4 + word-form input and DMs a deprecation notice; town services (shop/blacksmith/rest/thom) stay on !adventure. - Delete dead resolveActivity (269 lines) + parseActivityLocation; add isLegacyActivityInput + renderLegacyActivityDeprecation. - Morning-DM menu rewritten to point at !expedition and the !forage etc. harvest commands; writeAdvLocationLines removed (sole caller). - New dnd_r1_migration.go runs archiveOrphanZoneRuns at Init: archives any active dnd_zone_run not linked to an active expedition (via exp.run_id or region_state.region_runs). Idempotent. - Pre-existing flake in TestPickEveningRecap_BossOverridesAll fixed (E6c pool added entries without literal "boss"; widen substring set). - Internal helpers (resolveAdvAction, advEligibleLocations, AdvLocation tier data, consumable drops) preserved — babysit/scheduler still use them passively. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
108
internal/plugin/dnd_r1_migration.go
Normal file
108
internal/plugin/dnd_r1_migration.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gogobee/internal/db"
|
||||
)
|
||||
|
||||
// archiveOrphanZoneRuns is the Phase R1 deprecation migration. The old
|
||||
// `!adventure dungeon` activity could leave dnd_zone_run rows in active
|
||||
// state that aren't tied to any expedition. R2+ guarantees every new run
|
||||
// is spawned by ensureRegionRun and pointed at by an active expedition
|
||||
// (either as exp.run_id or via RegionState["region_runs"]). Anything
|
||||
// active in dnd_zone_run that doesn't appear in that preserved set is a
|
||||
// leftover from the legacy path and gets archived.
|
||||
//
|
||||
// Idempotent: re-running the migration after the orphan set is empty is
|
||||
// a no-op. Safe to call on every startup.
|
||||
//
|
||||
// Returns the number of rows updated.
|
||||
func archiveOrphanZoneRuns() (int, error) {
|
||||
preserved, err := loadActiveRegionRunIDs()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("collect preserved run ids: %w", err)
|
||||
}
|
||||
|
||||
if len(preserved) == 0 {
|
||||
res, err := db.Get().Exec(`
|
||||
UPDATE dnd_zone_run
|
||||
SET abandoned = 1,
|
||||
completed_at = COALESCE(completed_at, CURRENT_TIMESTAMP),
|
||||
last_action_at = CURRENT_TIMESTAMP
|
||||
WHERE abandoned = 0`)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, _ := res.RowsAffected()
|
||||
return int(n), nil
|
||||
}
|
||||
|
||||
placeholders := make([]string, 0, len(preserved))
|
||||
args := make([]any, 0, len(preserved))
|
||||
for id := range preserved {
|
||||
placeholders = append(placeholders, "?")
|
||||
args = append(args, id)
|
||||
}
|
||||
query := `
|
||||
UPDATE dnd_zone_run
|
||||
SET abandoned = 1,
|
||||
completed_at = COALESCE(completed_at, CURRENT_TIMESTAMP),
|
||||
last_action_at = CURRENT_TIMESTAMP
|
||||
WHERE abandoned = 0
|
||||
AND run_id NOT IN (` + strings.Join(placeholders, ",") + `)`
|
||||
res, err := db.Get().Exec(query, args...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, _ := res.RowsAffected()
|
||||
return int(n), nil
|
||||
}
|
||||
|
||||
// loadActiveRegionRunIDs scans every active expedition and returns the set
|
||||
// of run IDs that should be preserved: each expedition's exp.run_id plus
|
||||
// every value in its RegionState["region_runs"] map.
|
||||
func loadActiveRegionRunIDs() (map[string]struct{}, error) {
|
||||
rows, err := db.Get().Query(`
|
||||
SELECT run_id, region_state
|
||||
FROM dnd_expedition
|
||||
WHERE status = 'active'`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
out := map[string]struct{}{}
|
||||
for rows.Next() {
|
||||
var runID, regionJSON sql.NullString
|
||||
if err := rows.Scan(&runID, ®ionJSON); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if runID.Valid && runID.String != "" {
|
||||
out[runID.String] = struct{}{}
|
||||
}
|
||||
if !regionJSON.Valid || regionJSON.String == "" {
|
||||
continue
|
||||
}
|
||||
var state map[string]any
|
||||
if err := json.Unmarshal([]byte(regionJSON.String), &state); err != nil {
|
||||
continue
|
||||
}
|
||||
raw, ok := state[regionStateRegionRuns]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch v := raw.(type) {
|
||||
case map[string]any:
|
||||
for _, vv := range v {
|
||||
if s, ok := vv.(string); ok && s != "" {
|
||||
out[s] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
Reference in New Issue
Block a user