diff options
Diffstat (limited to 'internal/game/sys_escape_test.go')
| -rw-r--r-- | internal/game/sys_escape_test.go | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/internal/game/sys_escape_test.go b/internal/game/sys_escape_test.go new file mode 100644 index 0000000..f07701c --- /dev/null +++ b/internal/game/sys_escape_test.go @@ -0,0 +1,303 @@ +package game + +import ( + "os" + "testing" + + "thehouseoficarus/internal/combat" + "thehouseoficarus/internal/engine" + "thehouseoficarus/internal/game/hacking" + "thehouseoficarus/internal/item" + "thehouseoficarus/internal/net" + "thehouseoficarus/internal/player" + "thehouseoficarus/internal/world" +) + +type escapeTestConn struct { + data []byte +} + +func (c *escapeTestConn) ReadMessage() (string, error) { return "", nil } +func (c *escapeTestConn) Write(b []byte) (int, error) { c.data = append(c.data, b...); return len(b), nil } +func (c *escapeTestConn) Close() error { return nil } +func (c *escapeTestConn) SetEcho(bool) error { return nil } + +func escapeTestGame() *Game { + tmpDir, err := os.MkdirTemp("", "escape_test") + if err != nil { + panic(err) + } + dataDir := "../../data" + return &Game{ + Combat: combat.NewTracker(), + safespot: NewSafespotManager(), + hackingStates: make(map[string]*hacking.Session), + Hub: net.NewHub(), + Deps: Deps{ + World: world.New(dataDir), + MobStore: world.NewMobStore(dataDir), + ItemStore: item.NewItemStore(dataDir), + AccountStore: player.NewAccountStore(tmpDir), + Ticks: engine.New(), + }, + } +} + +func escapeTestSession(p *player.Player) *net.Session { + return &net.Session{Player: p, Conn: &escapeTestConn{}} +} + +func TestClearEscape(t *testing.T) { + p := player.New("esc") + p.EscapeDir = "north" + p.EscapeTarget = 42 + p.EscapeIsWalk = true + + p.ClearEscape() + if p.EscapeDir != "" { + t.Error("ClearEscape should clear EscapeDir") + } + if p.EscapeTarget != 0 { + t.Error("ClearEscape should clear EscapeTarget") + } + if p.EscapeIsWalk { + t.Error("ClearEscape should clear EscapeIsWalk") + } +} + +func TestClearMoveStateClearsEscape(t *testing.T) { + p := player.New("esc2") + p.EscapeDir = "south" + p.EscapeTarget = 7 + + p.ClearMoveState() + if p.EscapeDir != "" { + t.Error("ClearMoveState should clear Escape fields") + } +} + +func TestBeginEscapeMoveWithEnergy(t *testing.T) { + g := escapeTestGame() + p := player.New("run") + p.EscapeDir = "north" + p.EscapeTarget = 10 + p.EscapeIsWalk = false + + sess := escapeTestSession(p) + g.beginEscapeMove(sess, p) + + if p.EscapeDir != "" { + t.Error("beginEscapeMove should clear EscapeDir") + } + if p.MoveDirection != "north" { + t.Errorf("beginEscapeMove should set MoveDirection, got %q", p.MoveDirection) + } + if p.MoveTarget != 10 { + t.Errorf("beginEscapeMove should set MoveTarget, got %d", p.MoveTarget) + } + // With energy (agility 1 gives 3 run energy): 1 tick. + if p.MoveTicks != 1 { + t.Errorf("expected MoveTicks=1 with energy, got %d", p.MoveTicks) + } +} + +func TestBeginEscapeMoveWithoutEnergy(t *testing.T) { + g := escapeTestGame() + p := player.New("walk") + p.RunEnergy = 0 + p.EscapeDir = "east" + p.EscapeTarget = 5 + p.EscapeIsWalk = false + + sess := escapeTestSession(p) + g.beginEscapeMove(sess, p) + + if p.MoveTicks != 2 { + t.Errorf("expected MoveTicks=2 without energy, got %d", p.MoveTicks) + } + if p.EscapeDir != "" { + t.Error("EscapeDir should be cleared") + } +} + +func TestBeginEscapeMoveWithCapeOfAgility(t *testing.T) { + g := escapeTestGame() + p := player.New("cape") + p.Equipment[item.SlotBack] = capeOfAgilityID + + // moveTicks returns 0 for Cape of Agility, which triggers the instant + // completeMove path in beginEscapeMove. The sys_energy_test.go tests + // already cover moveTicks returning 0 for Cape. This test verifies + // the call site doesn't regress. + ticks := g.moveTicks(p, false) + if ticks != 0 { + t.Errorf("Cape of Agility should give 0 ticks, got %d", ticks) + } +} + +func TestApplyMobHitCancelsEscape(t *testing.T) { + g := escapeTestGame() + p := player.New("ouch") + p.EscapeDir = "south" + p.EscapeTarget = 3 + p.WalkSequence = []string{"s", "s", "s"} + + g.Combat.Enter(p.Name, "testmob") + + mob := &world.MobInstance{ + InstanceID: "testmob", + DefID: "test_def", + Name: "a goblin", + } + + sess := escapeTestSession(p) + g.applyMobHit(sess, p, mob, "slash", 10) + + if p.EscapeDir != "" { + t.Error("mob hit should clear EscapeDir") + } + if p.EscapeTarget != 0 { + t.Error("mob hit should clear EscapeTarget") + } + if len(p.WalkSequence) != 0 { + t.Errorf("mob hit should cancel WalkSequence, got %d entries", len(p.WalkSequence)) + } + if cs := g.Combat.Get(p.Name); cs == nil || !cs.Active { + t.Error("combat should still be active after cancelling escape on hit") + } +} + +func TestApplyMobMissFiresEscape(t *testing.T) { + g := escapeTestGame() + p := player.New("lucky") + p.EscapeDir = "east" + p.EscapeTarget = 8 + p.EscapeIsWalk = false + + g.Combat.Enter(p.Name, "testmob") + + mob := &world.MobInstance{ + InstanceID: "testmob", + DefID: "test_def", + Name: "a goblin", + } + + sess := escapeTestSession(p) + g.applyMobMiss(sess, p, mob) + + if p.EscapeDir != "" { + t.Error("mob miss should trigger escape, clearing EscapeDir") + } + if p.MoveDirection != "east" { + t.Errorf("MoveDirection should be set from escape, got %q", p.MoveDirection) + } + if p.MoveTicks != 1 { + t.Errorf("expected MoveTicks=1 after miss-triggered escape, got %d", p.MoveTicks) + } +} + +func TestEscapeReaimSilently(t *testing.T) { + p := player.New("reaim") + p.EscapeDir = "north" + p.EscapeTarget = 1 + + p.EscapeDir = "south" + p.EscapeTarget = 2 + + if p.EscapeDir != "south" { + t.Error("re-aim should update EscapeDir") + } + if p.EscapeTarget != 2 { + t.Error("re-aim should update EscapeTarget") + } +} + +func TestDoAttackAbortsFlee(t *testing.T) { + g := escapeTestGame() + p := player.New("attacker") + p.EscapeDir = "west" + p.EscapeTarget = 4 + + g.Combat.Enter(p.Name, "testmob") + + sess := escapeTestSession(p) + g.doAttack(sess, "goblin") + + if p.EscapeDir != "" { + t.Error("doAttack should clear EscapeDir even when already in combat") + } +} + +func TestEscapeClearedOnStop(t *testing.T) { + p := player.New("stopper") + p.EscapeDir = "north" + p.EscapeTarget = 5 + + p.ClearMoveState() + + if p.EscapeDir != "" { + t.Error("ClearMoveState should clear Escape fields") + } +} + +func TestPlayerActionDisplayShowsPreparingToFlee(t *testing.T) { + g := escapeTestGame() + p := player.New("fleer") + p.EscapeDir = "north" + + display := g.playerActionDisplay(p) + if display != "preparing to flee" { + t.Errorf("expected 'preparing to flee', got %q", display) + } +} + +func TestEndCombatFiresEscapeOnMobDeath(t *testing.T) { + g := escapeTestGame() + p := player.New("finisher") + p.EscapeDir = "east" + p.EscapeTarget = 10 + + mob := &world.MobInstance{ + InstanceID: "finisher_mob", + DefID: "test_def", + Name: "a goblin", + HP: 0, + } + + sess := escapeTestSession(p) + g.endCombat(sess, p, mob) + + if p.EscapeDir != "" { + t.Error("endCombat should fire escape, clearing EscapeDir") + } + if p.MoveTicks != 1 { + t.Errorf("expected MoveTicks=1 after endCombat escape, got %d", p.MoveTicks) + } + if p.MoveDirection != "east" { + t.Errorf("expected MoveDirection from escape, got %q", p.MoveDirection) + } +} + +func TestEndCombatDoesNotFireEscapeIfMobAlive(t *testing.T) { + g := escapeTestGame() + p := player.New("patient") + p.EscapeDir = "east" + p.EscapeTarget = 10 + + mob := &world.MobInstance{ + InstanceID: "alive_mob", + DefID: "test_def", + Name: "a goblin", + HP: 5, + } + + sess := escapeTestSession(p) + g.endCombat(sess, p, mob) + + if p.EscapeDir != "east" { + t.Error("escape should still be pending when mob lives") + } + if p.MoveDirection != "" { + t.Error("MoveDirection should NOT be set when escape not fired") + } +} |
