1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
package game
import (
"path/filepath"
"sync"
"gopkg.in/yaml.v3"
"thehouseoficarus/internal/behavior"
)
type ObstacleDef struct {
RoomID int `yaml:"room_id"`
Verb string `yaml:"verb"`
TicksPerPhase float64 `yaml:"ticks_per_phase"`
XP int `yaml:"xp"`
FailDamage [2]int `yaml:"fail_damage"`
Messages []string `yaml:"messages"`
}
type CourseConfig struct {
ID string `yaml:"id"`
Name string `yaml:"name"`
RequiredLevel int `yaml:"required_level"`
StartRoom int `yaml:"start_room"`
CompletionXP int `yaml:"completion_xp"`
Obstacles []ObstacleDef `yaml:"obstacles"`
}
type ObstacleInfo struct {
CourseID string
CourseName string
ObstacleIndex int
TotalObstacles int
Verb string
Messages []string
TicksPerPhase float64
ObstacleXP int
CompletionXP int
FailDamage [2]int
NextRoom int
StartRoom int
RequiredLevel int
}
var obstacleVerbs = map[string]bool{}
var verbGerund = map[string]string{
"scramble": "scrambling",
"jump": "jumping",
"swing": "swinging",
"balance": "balancing",
"climb": "climbing",
"crawl": "crawling",
"vault": "vaulting",
"leap": "leaping",
"slide": "sliding",
}
type CourseStore struct {
dataDir string
mu sync.Mutex
courses map[string]*CourseConfig
roomToObstacle map[int]*ObstacleInfo
loaded bool
}
func NewCourseStore(dataDir string) *CourseStore {
return &CourseStore{
dataDir: dataDir,
courses: make(map[string]*CourseConfig),
}
}
func (cs *CourseStore) LoadAll() {
cs.mu.Lock()
defer cs.mu.Unlock()
cs.loadAllLocked()
}
func (cs *CourseStore) ReloadAll() {
cs.mu.Lock()
cs.loaded = false
cs.courses = make(map[string]*CourseConfig)
cs.mu.Unlock()
cs.LoadAll()
}
// AllCourses returns the full course config map, loading on first access.
func (cs *CourseStore) AllCourses() map[string]*CourseConfig {
cs.mu.Lock()
defer cs.mu.Unlock()
if !cs.loaded {
cs.loadAllLocked()
}
return cs.courses
}
func (cs *CourseStore) GetObstacle(roomID int) *ObstacleInfo {
cs.mu.Lock()
defer cs.mu.Unlock()
if !cs.loaded {
cs.loadAllLocked()
}
return cs.roomToObstacle[roomID]
}
func (cs *CourseStore) loadAllLocked() {
cs.loaded = true
cs.roomToObstacle = make(map[int]*ObstacleInfo)
localVerbs := make(map[string]bool)
behavior.WalkYAMLDir(filepath.Join(cs.dataDir, "courses"), func(path, id string, data []byte) error {
var cfg CourseConfig
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil
}
cfg.ID = id
cs.courses[cfg.ID] = &cfg
totalObstacles := len(cfg.Obstacles)
for i, obs := range cfg.Obstacles {
nextRoom := 0
if i < totalObstacles-1 {
nextRoom = cfg.Obstacles[i+1].RoomID
}
completionXP := 0
if i == totalObstacles-1 {
completionXP = cfg.CompletionXP
}
info := &ObstacleInfo{
CourseID: cfg.ID,
CourseName: cfg.Name,
ObstacleIndex: i,
TotalObstacles: totalObstacles,
Verb: obs.Verb,
Messages: obs.Messages,
TicksPerPhase: obs.TicksPerPhase,
ObstacleXP: obs.XP,
CompletionXP: completionXP,
FailDamage: obs.FailDamage,
NextRoom: nextRoom,
StartRoom: cfg.StartRoom,
RequiredLevel: cfg.RequiredLevel,
}
cs.roomToObstacle[obs.RoomID] = info
localVerbs[obs.Verb] = true
}
return nil
})
obstacleVerbs = localVerbs
}
|