aboutsummaryrefslogtreecommitdiff
path: root/internal/game
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-09 05:27:10 -0400
committerhistoria <[not public]>2026-07-09 05:27:10 -0400
commitc705ae942573984784ef501bf8198f61f5206ddd (patch)
treec964066f3a244002bf75cb50a877630f46496f81 /internal/game
parent74153c7814fc4cef988066ef04e38731a943bc12 (diff)
downloadthehouseoficarus-c705ae942573984784ef501bf8198f61f5206ddd.tar.gz
refactor: simplify yaml, remove support for old scalar fields
Diffstat (limited to 'internal/game')
-rw-r--r--internal/game/act.go28
-rw-r--r--internal/game/cmd_room_remove_test.go62
-rw-r--r--internal/game/core_course.go60
-rw-r--r--internal/game/core_course_test.go45
-rw-r--r--internal/game/core_flags.go7
-rw-r--r--internal/game/core_flagstore.go10
-rw-r--r--internal/game/map_test.go48
-rw-r--r--internal/game/sys_triggers.go5
8 files changed, 99 insertions, 166 deletions
diff --git a/internal/game/act.go b/internal/game/act.go
index ddeed04..91f05a8 100644
--- a/internal/game/act.go
+++ b/internal/game/act.go
@@ -7,6 +7,7 @@ import (
"strings"
"thehouseoficarus/internal/behavior"
+ "thehouseoficarus/internal/engine"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/object"
"thehouseoficarus/internal/player"
@@ -332,7 +333,7 @@ func flagMatches(present bool, val, want any, not bool) bool {
if want == nil {
match = present && isTruthy(val)
} else {
- match = present && valuesEqual(val, want)
+ match = present && engine.ValuesEqual(val, want)
}
if not {
return !match
@@ -340,31 +341,6 @@ func flagMatches(present bool, val, want any, not bool) bool {
return match
}
-// valuesEqual compares two any-typed values, normalizing numeric types (int,
-// int64, float64) so that e.g. int(3) == int64(3) == float64(3). Non-numeric
-// types fall back to ==.
-func valuesEqual(a, b any) bool {
- ai, aok := numericValue(a)
- bi, bok := numericValue(b)
- if aok && bok {
- return ai == bi
- }
- return a == b
-}
-
-// numericValue returns the value as a float64 if it is a numeric type.
-func numericValue(v any) (float64, bool) {
- switch x := v.(type) {
- case int:
- return float64(x), true
- case int64:
- return float64(x), true
- case float64:
- return x, true
- }
- return 0, false
-}
-
func isTruthy(v any) bool {
switch x := v.(type) {
case nil:
diff --git a/internal/game/cmd_room_remove_test.go b/internal/game/cmd_room_remove_test.go
index 1868c30..a13af8a 100644
--- a/internal/game/cmd_room_remove_test.go
+++ b/internal/game/cmd_room_remove_test.go
@@ -85,9 +85,9 @@ func reloadRoom(t *testing.T, g *Game, id int) *world.Room {
func TestRoomRemoveSuccessPull(t *testing.T) {
dir := t.TempDir()
- writeRemoveRoomFile(t, dir, 1, "name: A\nexits:\n east: 2\n")
- writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n east: 3\n west: 1\n")
- writeRemoveRoomFile(t, dir, 3, "name: C\nexits:\n west: 2\n")
+ writeRemoveRoomFile(t, dir, 1, "name: A\nexits:\n east: {room: 2}\n")
+ writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n east: {room: 3}\n west: {room: 1}\n")
+ writeRemoveRoomFile(t, dir, 3, "name: C\nexits:\n west: {room: 2}\n")
g := newRemoveGame(t, dir)
sess := newRemoveSession(&player.Player{Name: "tester", RoomID: 1})
@@ -113,9 +113,9 @@ func TestRoomRemoveSuccessPull(t *testing.T) {
func TestRoomRemoveDiagonalPull(t *testing.T) {
dir := t.TempDir()
- writeRemoveRoomFile(t, dir, 1, "name: A\nexits:\n northeast: 2\n")
- writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n northeast: 3\n southwest: 1\n")
- writeRemoveRoomFile(t, dir, 3, "name: C\nexits:\n southwest: 2\n")
+ writeRemoveRoomFile(t, dir, 1, "name: A\nexits:\n northeast: {room: 2}\n")
+ writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n northeast: {room: 3}\n southwest: {room: 1}\n")
+ writeRemoveRoomFile(t, dir, 3, "name: C\nexits:\n southwest: {room: 2}\n")
g := newRemoveGame(t, dir)
sess := newRemoveSession(&player.Player{Name: "tester", RoomID: 1})
@@ -145,9 +145,9 @@ func TestRoomRemoveNoExit(t *testing.T) {
func TestRoomRemoveNotInsertChain(t *testing.T) {
dir := t.TempDir()
// B does not lead back to A via west.
- writeRemoveRoomFile(t, dir, 1, "name: A\nexits:\n east: 2\n")
- writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n east: 3\n")
- writeRemoveRoomFile(t, dir, 3, "name: C\nexits:\n west: 2\n")
+ writeRemoveRoomFile(t, dir, 1, "name: A\nexits:\n east: {room: 2}\n")
+ writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n east: {room: 3}\n")
+ writeRemoveRoomFile(t, dir, 3, "name: C\nexits:\n west: {room: 2}\n")
g := newRemoveGame(t, dir)
sess := newRemoveSession(&player.Player{Name: "tester", RoomID: 1})
@@ -162,8 +162,8 @@ func TestRoomRemoveNotInsertChain(t *testing.T) {
func TestRoomRemoveDeadEndFarRoom(t *testing.T) {
dir := t.TempDir()
// B leads back to A but has no forward east exit.
- writeRemoveRoomFile(t, dir, 1, "name: A\nexits:\n east: 2\n")
- writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n west: 1\n")
+ writeRemoveRoomFile(t, dir, 1, "name: A\nexits:\n east: {room: 2}\n")
+ writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n west: {room: 1}\n")
g := newRemoveGame(t, dir)
sess := newRemoveSession(&player.Player{Name: "tester", RoomID: 1})
@@ -177,10 +177,10 @@ func TestRoomRemoveDeadEndFarRoom(t *testing.T) {
func TestRoomRemoveOtherExitsInB(t *testing.T) {
dir := t.TempDir()
// B has east (fwd), west (back), AND up (extra).
- writeRemoveRoomFile(t, dir, 1, "name: A\nexits:\n east: 2\n")
- writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n east: 3\n west: 1\n up: 4\n")
- writeRemoveRoomFile(t, dir, 3, "name: C\nexits:\n west: 2\n")
- writeRemoveRoomFile(t, dir, 4, "name: D\nexits:\n down: 2\n")
+ writeRemoveRoomFile(t, dir, 1, "name: A\nexits:\n east: {room: 2}\n")
+ writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n east: {room: 3}\n west: {room: 1}\n up: {room: 4}\n")
+ writeRemoveRoomFile(t, dir, 3, "name: C\nexits:\n west: {room: 2}\n")
+ writeRemoveRoomFile(t, dir, 4, "name: D\nexits:\n down: {room: 2}\n")
g := newRemoveGame(t, dir)
sess := newRemoveSession(&player.Player{Name: "tester", RoomID: 1})
@@ -194,8 +194,8 @@ func TestRoomRemoveOtherExitsInB(t *testing.T) {
func TestRoomRemoveFarReciprocityBroken(t *testing.T) {
dir := t.TempDir()
// C's west does not point back to B.
- writeRemoveRoomFile(t, dir, 1, "name: A\nexits:\n east: 2\n")
- writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n east: 3\n west: 1\n")
+ writeRemoveRoomFile(t, dir, 1, "name: A\nexits:\n east: {room: 2}\n")
+ writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n east: {room: 3}\n west: {room: 1}\n")
writeRemoveRoomFile(t, dir, 3, "name: C\nexits:\n")
g := newRemoveGame(t, dir)
sess := newRemoveSession(&player.Player{Name: "tester", RoomID: 1})
@@ -210,10 +210,10 @@ func TestRoomRemoveFarReciprocityBroken(t *testing.T) {
func TestRoomRemoveExtraInboundEdge(t *testing.T) {
dir := t.TempDir()
// A clean chain A-east->B-east->C, but an unrelated room 5 also points to B.
- writeRemoveRoomFile(t, dir, 1, "name: A\nexits:\n east: 2\n")
- writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n east: 3\n west: 1\n")
- writeRemoveRoomFile(t, dir, 3, "name: C\nexits:\n west: 2\n")
- writeRemoveRoomFile(t, dir, 5, "name: E\nexits:\n north: 2\n")
+ writeRemoveRoomFile(t, dir, 1, "name: A\nexits:\n east: {room: 2}\n")
+ writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n east: {room: 3}\n west: {room: 1}\n")
+ writeRemoveRoomFile(t, dir, 3, "name: C\nexits:\n west: {room: 2}\n")
+ writeRemoveRoomFile(t, dir, 5, "name: E\nexits:\n north: {room: 2}\n")
g := newRemoveGame(t, dir)
sess := newRemoveSession(&player.Player{Name: "tester", RoomID: 1})
@@ -228,12 +228,12 @@ func TestRoomRemoveExtraInboundEdge(t *testing.T) {
// pulls room 6 onto room 5's cell, which must be rejected.
func TestRoomRemoveOverlap(t *testing.T) {
dir := t.TempDir()
- writeRemoveRoomFile(t, dir, 1, "name: A\nexits:\n east: 2\n south: 4\n")
- writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n east: 3\n west: 1\n")
- writeRemoveRoomFile(t, dir, 3, "name: C\nexits:\n south: 6\n west: 2\n")
- writeRemoveRoomFile(t, dir, 4, "name: D\nexits:\n east: 5\n north: 1\n")
- writeRemoveRoomFile(t, dir, 5, "name: E\nexits:\n west: 4\n")
- writeRemoveRoomFile(t, dir, 6, "name: F\nexits:\n north: 3\n")
+ writeRemoveRoomFile(t, dir, 1, "name: A\nexits:\n east: {room: 2}\n south: {room: 4}\n")
+ writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n east: {room: 3}\n west: {room: 1}\n")
+ writeRemoveRoomFile(t, dir, 3, "name: C\nexits:\n south: {room: 6}\n west: {room: 2}\n")
+ writeRemoveRoomFile(t, dir, 4, "name: D\nexits:\n east: {room: 5}\n north: {room: 1}\n")
+ writeRemoveRoomFile(t, dir, 5, "name: E\nexits:\n west: {room: 4}\n")
+ writeRemoveRoomFile(t, dir, 6, "name: F\nexits:\n north: {room: 3}\n")
g := newRemoveGame(t, dir)
sess := newRemoveSession(&player.Player{Name: "tester", RoomID: 1})
@@ -275,7 +275,7 @@ exits:
blocked_message: "A rock blocks the way."
hidden: true
`)
- writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n east: 3\n west: 1\n")
+ writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n east: {room: 3}\n west: {room: 1}\n")
writeRemoveRoomFile(t, dir, 3, `name: C
exits:
west:
@@ -310,9 +310,9 @@ exits:
// moved to A and notified. The caller stays in A.
func TestRoomRemoveRelocatesPlayersInB(t *testing.T) {
dir := t.TempDir()
- writeRemoveRoomFile(t, dir, 1, "name: A\nexits:\n east: 2\n")
- writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n east: 3\n west: 1\n")
- writeRemoveRoomFile(t, dir, 3, "name: C\nexits:\n west: 2\n")
+ writeRemoveRoomFile(t, dir, 1, "name: A\nexits:\n east: {room: 2}\n")
+ writeRemoveRoomFile(t, dir, 2, "name: B\nexits:\n east: {room: 3}\n west: {room: 1}\n")
+ writeRemoveRoomFile(t, dir, 3, "name: C\nexits:\n west: {room: 2}\n")
g := newRemoveGame(t, dir)
caller := newRemoveSession(&player.Player{Name: "builder", RoomID: 1})
diff --git a/internal/game/core_course.go b/internal/game/core_course.go
index 031eff5..6394b99 100644
--- a/internal/game/core_course.go
+++ b/internal/game/core_course.go
@@ -21,21 +21,17 @@ type ObstaclePhase struct {
}
// ObstacleDef is the YAML representation of a single agility obstacle.
-// Two equivalent forms are supported:
-// - Legacy: Messages (exactly 3) + TicksPerPhase + fail at phase index 1.
-// - Preferred: Phases, an explicit ordered list with per-phase delays and
-// an optional fail_check flag (at most one phase should carry it).
+// Phases is the ordered list of advancement steps, each with a per-phase
+// delay and an optional fail_check flag (at most one phase should carry it).
type ObstacleDef struct {
- RoomID int `yaml:"room_id"`
- Verb string `yaml:"verb"`
- XP int `yaml:"xp"`
- FailDamage [2]int `yaml:"fail_damage"`
- FailChance *float64 `yaml:"fail_chance,omitempty"`
- 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"`
+ RoomID int `yaml:"room_id"`
+ Verb string `yaml:"verb"`
+ XP int `yaml:"xp"`
+ FailDamage [2]int `yaml:"fail_damage"`
+ FailChance *float64 `yaml:"fail_chance,omitempty"`
+ Phases []ObstaclePhase `yaml:"phases"`
+ ExitDir string `yaml:"exit_dir"`
+ OnFailRoom int `yaml:"on_fail,omitempty"`
}
type CourseConfig struct {
@@ -140,33 +136,13 @@ func (cs *CourseStore) GetObstacle(roomID int) *ObstacleInfo {
}
// resolvePhases converts an ObstacleDef into a normalized PhaseInfo list.
-// Preferred form: explicit Phases. Legacy form: Messages with a shared
-// TicksPerPhase and the failure check at the middle (index 1) phase.
func resolvePhases(obs ObstacleDef) []PhaseInfo {
- if len(obs.Phases) > 0 {
- phases := make([]PhaseInfo, 0, len(obs.Phases))
- for _, p := range obs.Phases {
- phases = append(phases, PhaseInfo{
- Message: p.Message,
- Delay: p.Delay,
- FailCheck: p.FailCheck,
- })
- }
- return phases
- }
- msgs := obs.Messages
- phases := make([]PhaseInfo, 0, len(msgs))
- for i, m := range msgs {
- var delay float64
- if i == 0 {
- delay = 0
- } else {
- delay = obs.TicksPerPhase
- }
+ phases := make([]PhaseInfo, 0, len(obs.Phases))
+ for _, p := range obs.Phases {
phases = append(phases, PhaseInfo{
- Message: m,
- Delay: delay,
- FailCheck: i == 1,
+ Message: p.Message,
+ Delay: p.Delay,
+ FailCheck: p.FailCheck,
})
}
return phases
@@ -197,12 +173,6 @@ func (cs *CourseStore) resolveExitTarget(roomID int, dir string) int {
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) {
diff --git a/internal/game/core_course_test.go b/internal/game/core_course_test.go
index 64ec8ad..93f49f5 100644
--- a/internal/game/core_course_test.go
+++ b/internal/game/core_course_test.go
@@ -6,7 +6,7 @@ import (
"testing"
)
-func TestResolvePhasesPrefersExplicitPhases(t *testing.T) {
+func TestResolvePhases(t *testing.T) {
obs := ObstacleDef{
RoomID: 5,
Verb: "climb",
@@ -15,9 +15,6 @@ func TestResolvePhasesPrefersExplicitPhases(t *testing.T) {
{Message: "middle", Delay: 2, FailCheck: true},
{Message: "end", Delay: 1.5},
},
- // Legacy fields set as a decoy; should be ignored when Phases present.
- TicksPerPhase: 99,
- Messages: []string{"ignored1", "ignored2", "ignored3"},
}
got := resolvePhases(obs)
if len(got) != 3 {
@@ -34,28 +31,6 @@ func TestResolvePhasesPrefersExplicitPhases(t *testing.T) {
}
}
-func TestResolvePhasesLegacyMigration(t *testing.T) {
- obs := ObstacleDef{
- RoomID: 7,
- Verb: "jump",
- TicksPerPhase: 2,
- Messages: []string{"a", "b", "c"},
- }
- got := resolvePhases(obs)
- if len(got) != 3 {
- t.Fatalf("expected 3 legacy phases, got %d", len(got))
- }
- if got[0].Message != "a" || got[0].Delay != 0 || got[0].FailCheck {
- t.Errorf("legacy phase 0 wrong: %+v", got[0])
- }
- if got[1].Message != "b" || got[1].Delay != 2 || !got[1].FailCheck {
- t.Errorf("legacy phase 1 must carry fail_check at index 1: %+v", got[1])
- }
- if got[2].Message != "c" || got[2].Delay != 2 || got[2].FailCheck {
- t.Errorf("legacy phase 2 wrong: %+v", got[2])
- }
-}
-
func TestCourseStoreLoadsWithExplicitFailChance(t *testing.T) {
dir := t.TempDir()
coursesDir := filepath.Join(dir, "courses")
@@ -85,8 +60,14 @@ obstacles:
verb: jump
xp: 14
fail_damage: [1, 3]
- messages: ["p0", "p1", "p2"]
- ticks_per_phase: 1
+ phases:
+ - message: "p0"
+ delay: 0
+ - message: "p1"
+ delay: 1
+ fail_check: true
+ - message: "p2"
+ delay: 1
`)
if err := os.WriteFile(filepath.Join(coursesDir, "testcourse.yaml"), yaml, 0644); err != nil {
t.Fatal(err)
@@ -116,16 +97,16 @@ obstacles:
t.Errorf("first obstacle should have no completion xp, got %d", o1.CompletionXP)
}
- // Obstacle 2: legacy migration, derived fail chance (nil)
+ // Obstacle 2: explicit phases, derived fail chance (nil)
o2 := cs.GetObstacle(102)
if o2 == nil {
t.Fatal("expected obstacle for room 102")
}
if len(o2.Phases) != 3 || !o2.Phases[1].FailCheck {
- t.Errorf("legacy obstacle phases not migrated: %+v", o2.Phases)
+ t.Errorf("obstacle 2 phases wrong: %+v", o2.Phases)
}
if o2.FailChance != nil {
- t.Errorf("legacy obstacle should have nil (derived) fail_chance, got %v", *o2.FailChance)
+ t.Errorf("obstacle 2 should have nil (derived) fail_chance, got %v", *o2.FailChance)
}
if o2.CompletionXP != 50 {
t.Errorf("last obstacle should carry completion_xp, got %d", o2.CompletionXP)
@@ -137,4 +118,4 @@ obstacles:
if cs.GetObstacle(999) != nil {
t.Error("expected nil for unrelated room")
}
-} \ No newline at end of file
+}
diff --git a/internal/game/core_flags.go b/internal/game/core_flags.go
index 5e1c812..c78f3c3 100644
--- a/internal/game/core_flags.go
+++ b/internal/game/core_flags.go
@@ -1,6 +1,9 @@
package game
-import "thehouseoficarus/internal/player"
+import (
+ "thehouseoficarus/internal/engine"
+ "thehouseoficarus/internal/player"
+)
func getPlayerFlagInt(p *player.Player, key string) int {
if p.Flags == nil {
@@ -37,7 +40,7 @@ func (g *Game) setPlayerFlag(p *player.Player, key string, val any) {
p.EnsureFlags()
old, existed := p.Flags[key]
p.Flags[key] = val
- if !existed || !valuesEqual(old, val) {
+ if !existed || !engine.ValuesEqual(old, val) {
g.firePlayerFlagTrigger(p, key, val)
}
}
diff --git a/internal/game/core_flagstore.go b/internal/game/core_flagstore.go
index 18578b7..5dd8d00 100644
--- a/internal/game/core_flagstore.go
+++ b/internal/game/core_flagstore.go
@@ -1,6 +1,10 @@
package game
-import "sync"
+import (
+ "sync"
+
+ "thehouseoficarus/internal/engine"
+)
// GlobalFlagChangeCallback is invoked when a global flag value actually changes
// (old value differs from new, or new flag is created with a truthy value).
@@ -39,7 +43,7 @@ func (f *GlobalFlagStore) Set(name string, value any) {
cbs := f.callbacks
f.mu.Unlock()
- if !existed || !valuesEqual(old, value) {
+ if !existed || !engine.ValuesEqual(old, value) {
for _, cb := range cbs {
cb(name, value)
}
@@ -52,7 +56,7 @@ func (f *GlobalFlagStore) SetAll(m map[string]any) {
for k, v := range m {
old, existed := f.flags[k]
f.flags[k] = v
- if !existed || !valuesEqual(old, v) {
+ if !existed || !engine.ValuesEqual(old, v) {
changed[k] = v
}
}
diff --git a/internal/game/map_test.go b/internal/game/map_test.go
index 2975551..d13cbdc 100644
--- a/internal/game/map_test.go
+++ b/internal/game/map_test.go
@@ -46,14 +46,14 @@ func TestMapConnectorGlyphs(t *testing.T) {
}{
{
name: "bidirectional bar",
- room1: "name: One\nexits:\n east: 2\n",
- room2: "name: Two\nexits:\n west: 1\n",
+ room1: "name: One\nexits:\n east: {room: 2}\n",
+ room2: "name: Two\nexits:\n west: {room: 1}\n",
wantPresent: "-",
wantAbsent: []string{"X", "<", ">"},
},
{
name: "one-way east arrow",
- room1: "name: One\nexits:\n east: 2\n",
+ room1: "name: One\nexits:\n east: {room: 2}\n",
room2: "name: Two\n",
wantPresent: ">",
wantAbsent: []string{"X", "-", "<"},
@@ -61,7 +61,7 @@ func TestMapConnectorGlyphs(t *testing.T) {
{
name: "forward blocked, reverse open -> X (shortest path is blocked)",
room1: condEast,
- room2: "name: Two\nexits:\n west: 1\n",
+ room2: "name: Two\nexits:\n west: {room: 1}\n",
flagOpen: false,
wantPresent: "X",
wantAbsent: []string{"-", "<", ">"},
@@ -77,15 +77,15 @@ func TestMapConnectorGlyphs(t *testing.T) {
{
name: "conditional unblocked -> bar",
room1: condEast,
- room2: "name: Two\nexits:\n west: 1\n",
+ room2: "name: Two\nexits:\n west: {room: 1}\n",
flagOpen: true,
wantPresent: "-",
wantAbsent: []string{"X", "<", ">"},
},
{
name: "outward open, inward blocked -> outward arrow (dist rules)",
- room1: "name: One\nexits:\n east: 2\n",
- room2: "name: Two\nexits:\n west: 1\n north: 3\n",
+ room1: "name: One\nexits:\n east: {room: 2}\n",
+ room2: "name: Two\nexits:\n west: {room: 1}\n north: {room: 3}\n",
room3: condSouth,
flagOpen: false,
// dist[room1]=0, dist[room2]=1, dist[room3]=2.
@@ -96,21 +96,21 @@ func TestMapConnectorGlyphs(t *testing.T) {
},
{
name: "bidirectional diagonal NE-SW",
- room1: "name: One\nexits:\n northeast: 2\n",
- room2: "name: Two\nexits:\n southwest: 1\n",
+ room1: "name: One\nexits:\n northeast: {room: 2}\n",
+ room2: "name: Two\nexits:\n southwest: {room: 1}\n",
wantPresent: "/",
wantAbsent: []string{"X", "\\"},
},
{
name: "bidirectional diagonal NW-SE",
- room1: "name: One\nexits:\n northwest: 2\n",
- room2: "name: Two\nexits:\n southeast: 1\n",
+ room1: "name: One\nexits:\n northwest: {room: 2}\n",
+ room2: "name: Two\nexits:\n southeast: {room: 1}\n",
wantPresent: "\\",
wantAbsent: []string{"X", "/"},
},
{
name: "one-way NE arrow",
- room1: "name: One\nexits:\n northeast: 2\n",
+ room1: "name: One\nexits:\n northeast: {room: 2}\n",
room2: "name: Two\n",
wantPresent: "/",
wantAbsent: []string{"X", "\\"},
@@ -134,15 +134,15 @@ func TestMapConnectorGlyphs(t *testing.T) {
{
name: "diagonal conditional unblocked -> bar",
room1: condNE,
- room2: "name: Two\nexits:\n southwest: 1\n",
+ room2: "name: Two\nexits:\n southwest: {room: 1}\n",
flagOpen: true,
wantPresent: "/",
wantAbsent: []string{"X", "\\"},
},
{
name: "criss-crossed diagonal paths show X",
- room1: "name: One\nexits:\n east: 2\n south: 3\n southeast: 4\n",
- room2: "name: Two\nexits:\n southwest: 3\n",
+ room1: "name: One\nexits:\n east: {room: 2}\n south: {room: 3}\n southeast: {room: 4}\n",
+ room2: "name: Two\nexits:\n southwest: {room: 3}\n",
room3: "name: Three\n",
room4: "name: Four\n",
wantPresent: "X",
@@ -187,10 +187,10 @@ func TestMapConnectorGlyphs(t *testing.T) {
// both bar glyphs and arrow glyphs.
func TestMapDiagonalOneWayCrossing(t *testing.T) {
dir := t.TempDir()
- writeTempRoom(t, dir, 1, "name: One\nexits:\n north: 3\n northeast: 2\n")
+ writeTempRoom(t, dir, 1, "name: One\nexits:\n north: {room: 3}\n northeast: {room: 2}\n")
writeTempRoom(t, dir, 2, "name: Two\n")
- writeTempRoom(t, dir, 3, "name: Three\nexits:\n southeast: 4\n")
- writeTempRoom(t, dir, 4, "name: Four\nexits:\n northwest: 3\n")
+ writeTempRoom(t, dir, 3, "name: Three\nexits:\n southeast: {room: 4}\n")
+ writeTempRoom(t, dir, 4, "name: Four\nexits:\n northwest: {room: 3}\n")
g := &Game{Deps: Deps{World: world.New(dir)}}
sess := &net.Session{Player: &player.Player{Options: map[string]any{"unicode": true}}}
@@ -396,10 +396,10 @@ func TestMap3DDisconnectedComponent(t *testing.T) {
// tower2base→up→tower2 (1,0,0). Tower 2 is at the same z=0 as tower 1 but
// unreachable via horizontal exits. The 3D map should still show it.
dir := t.TempDir()
- writeTempRoom(t, dir, 1, "name: Tower One\nexits:\n down: 2\n")
- writeTempRoom(t, dir, 2, "name: Bridge\nexits:\n east: 3\n up: 1\n")
- writeTempRoom(t, dir, 3, "name: Tower Two Base\nexits:\n up: 4\n west: 2\n")
- writeTempRoom(t, dir, 4, "name: Tower Two\nexits:\n down: 3\n")
+ writeTempRoom(t, dir, 1, "name: Tower One\nexits:\n down: {room: 2}\n")
+ writeTempRoom(t, dir, 2, "name: Bridge\nexits:\n east: {room: 3}\n up: {room: 1}\n")
+ writeTempRoom(t, dir, 3, "name: Tower Two Base\nexits:\n up: {room: 4}\n west: {room: 2}\n")
+ writeTempRoom(t, dir, 4, "name: Tower Two\nexits:\n down: {room: 3}\n")
g := &Game{Deps: Deps{World: world.New(dir)}, GlobalFlags: NewGlobalFlagStore()}
mg := mapGlyphsForPlayer(false)
@@ -423,8 +423,8 @@ func TestMap3DDifferentZExcluded(t *testing.T) {
// Room 1 at (0,0,0) with up to room 2 at (0,0,1). Room 2 is at z=1,
// different from the player's z=0 level — should NOT show on the map.
dir := t.TempDir()
- writeTempRoom(t, dir, 1, "name: Ground\nexits:\n up: 2\n")
- writeTempRoom(t, dir, 2, "name: Upper\nexits:\n down: 1\n")
+ writeTempRoom(t, dir, 1, "name: Ground\nexits:\n up: {room: 2}\n")
+ writeTempRoom(t, dir, 2, "name: Upper\nexits:\n down: {room: 1}\n")
g := &Game{Deps: Deps{World: world.New(dir)}, GlobalFlags: NewGlobalFlagStore()}
mg := mapGlyphsForPlayer(false)
diff --git a/internal/game/sys_triggers.go b/internal/game/sys_triggers.go
index 2e8b877..6a1e224 100644
--- a/internal/game/sys_triggers.go
+++ b/internal/game/sys_triggers.go
@@ -8,7 +8,6 @@ import (
"thehouseoficarus/internal/engine"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/player"
- "thehouseoficarus/internal/world"
)
func (g *Game) TriggerSeqTick() {
@@ -150,7 +149,7 @@ func (g *Game) executeGlobalTriggerStep(step *behavior.StepAction, roomID int, f
g.applyStepAction(nil, nil, step, roomID, scopeGlobal, flagValue)
}
-func (g *Game) spawnTriggerMob(sess *net.Session, p *player.Player, cfg *world.SpawnMobConfig, roomID int) {
+func (g *Game) spawnTriggerMob(sess *net.Session, p *player.Player, cfg *behavior.SpawnMobConfig, roomID int) {
if cfg.ID == "" {
return
}
@@ -166,7 +165,7 @@ func (g *Game) spawnTriggerMob(sess *net.Session, p *player.Player, cfg *world.S
sess.WriteLine(g.colorize(sess, "broadcast", mobDisplayName(inst, true)+" appears!"))
}
-func (g *Game) spawnWorldTriggerMob(cfg *world.SpawnMobConfig, roomID int) {
+func (g *Game) spawnWorldTriggerMob(cfg *behavior.SpawnMobConfig, roomID int) {
if cfg.ID == "" {
return
}