aboutsummaryrefslogtreecommitdiff
path: root/internal/game/core_course.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-07 21:22:39 -0400
committerhistoria <[not public]>2026-07-07 21:22:39 -0400
commit74803657d587b1f93b739c1ee25bb8d62df95413 (patch)
tree0166974468c452bb13454d37d4b83d2e681446c0 /internal/game/core_course.go
parentbae42946d24bd533cdbc1b271ce14acd752689d0 (diff)
downloadthehouseoficarus-74803657d587b1f93b739c1ee25bb8d62df95413.tar.gz
fix: agility course creation fixed and improved in admin web gui
Diffstat (limited to 'internal/game/core_course.go')
-rw-r--r--internal/game/core_course.go75
1 files changed, 75 insertions, 0 deletions
diff --git a/internal/game/core_course.go b/internal/game/core_course.go
index 54894fd..031eff5 100644
--- a/internal/game/core_course.go
+++ b/internal/game/core_course.go
@@ -1,11 +1,14 @@
package game
import (
+ "log"
+ "os"
"path/filepath"
"sync"
"gopkg.in/yaml.v3"
"thehouseoficarus/internal/behavior"
+ "thehouseoficarus/internal/world"
)
// ObstaclePhase is a single phase of an obstacle's advancement sequence.
@@ -31,6 +34,8 @@ type ObstacleDef struct {
TicksPerPhase float64 `yaml:"ticks_per_phase"`
Messages []string `yaml:"messages"`
Phases []ObstaclePhase `yaml:"phases"`
+ ExitDir string `yaml:"exit_dir"`
+ OnFailRoom int `yaml:"on_fail,omitempty"`
}
type CourseConfig struct {
@@ -62,7 +67,9 @@ type ObstacleInfo struct {
FailDamage [2]int
NextRoom int
StartRoom int
+ OnFailRoom int
RequiredLevel int
+ ExitDir string
}
var obstacleVerbs = map[string]bool{}
@@ -85,6 +92,7 @@ type CourseStore struct {
courses map[string]*CourseConfig
roomToObstacle map[int]*ObstacleInfo
loaded bool
+ world *world.World
}
func NewCourseStore(dataDir string) *CourseStore {
@@ -94,6 +102,10 @@ func NewCourseStore(dataDir string) *CourseStore {
}
}
+func (cs *CourseStore) SetWorld(w *world.World) {
+ cs.world = w
+}
+
func (cs *CourseStore) LoadAll() {
cs.mu.Lock()
defer cs.mu.Unlock()
@@ -160,6 +172,52 @@ func resolvePhases(obs ObstacleDef) []PhaseInfo {
return phases
}
+func (cs *CourseStore) resolveExitTarget(roomID int, dir string) int {
+ if cs.world == nil || dir == "" {
+ return 0
+ }
+ path, ok := cs.world.GetRoomPath(roomID)
+ if !ok {
+ return 0
+ }
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return 0
+ }
+ var m map[string]any
+ if err := yaml.Unmarshal(data, &m); err != nil {
+ return 0
+ }
+ exits, _ := m["exits"].(map[string]any)
+ if exits == nil {
+ return 0
+ }
+ v, ok := exits[dir]
+ if !ok {
+ return 0
+ }
+ switch x := v.(type) {
+ case int:
+ return x
+ case int64:
+ return int(x)
+ case float64:
+ return int(x)
+ case map[string]any:
+ if r, ok := x["room"]; ok {
+ switch y := r.(type) {
+ case int:
+ return y
+ case int64:
+ return int(y)
+ case float64:
+ return int(y)
+ }
+ }
+ }
+ return 0
+}
+
func (cs *CourseStore) loadAllLocked() {
cs.loaded = true
cs.roomToObstacle = make(map[int]*ObstacleInfo)
@@ -178,6 +236,16 @@ func (cs *CourseStore) loadAllLocked() {
nextRoom := 0
if i < totalObstacles-1 {
nextRoom = cfg.Obstacles[i+1].RoomID
+ } else if obs.ExitDir != "" {
+ resolved := cs.resolveExitTarget(obs.RoomID, obs.ExitDir)
+ if resolved != 0 {
+ nextRoom = resolved
+ } else {
+ log.Printf("[WARNING] Course %q: last obstacle room %d has exit_dir=%q but no exit in that direction; falling back to start_room %d", cfg.ID, obs.RoomID, obs.ExitDir, cfg.StartRoom)
+ nextRoom = cfg.StartRoom
+ }
+ } else {
+ nextRoom = cfg.StartRoom
}
completionXP := 0
@@ -185,6 +253,11 @@ func (cs *CourseStore) loadAllLocked() {
completionXP = cfg.CompletionXP
}
+ onFailRoom := obs.OnFailRoom
+ if onFailRoom == 0 {
+ onFailRoom = cfg.StartRoom
+ }
+
info := &ObstacleInfo{
CourseID: cfg.ID,
CourseName: cfg.Name,
@@ -198,7 +271,9 @@ func (cs *CourseStore) loadAllLocked() {
FailDamage: obs.FailDamage,
NextRoom: nextRoom,
StartRoom: cfg.StartRoom,
+ OnFailRoom: onFailRoom,
RequiredLevel: cfg.RequiredLevel,
+ ExitDir: obs.ExitDir,
}
cs.roomToObstacle[obs.RoomID] = info
localVerbs[obs.Verb] = true