aboutsummaryrefslogtreecommitdiff
path: root/internal/game/act_interaction_seq_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/act_interaction_seq_test.go')
-rw-r--r--internal/game/act_interaction_seq_test.go216
1 files changed, 216 insertions, 0 deletions
diff --git a/internal/game/act_interaction_seq_test.go b/internal/game/act_interaction_seq_test.go
new file mode 100644
index 0000000..c3fc8fa
--- /dev/null
+++ b/internal/game/act_interaction_seq_test.go
@@ -0,0 +1,216 @@
+package game
+
+import (
+ "io"
+ "testing"
+
+ "thehouseoficarus/internal/behavior"
+ "thehouseoficarus/internal/combat"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+)
+
+type nopConn struct{}
+
+func (nopConn) ReadMessage() (string, error) { return "", io.EOF }
+func (nopConn) Write(p []byte) (int, error) { return len(p), nil }
+func (nopConn) Close() error { return nil }
+func (nopConn) SetEcho(bool) error { return nil }
+
+func newTestGame() *Game {
+ return &Game{
+ GlobalFlags: NewGlobalFlagStore(),
+ Combat: combat.NewTracker(),
+ }
+}
+
+func newTestGameWithStore(t *testing.T) *Game {
+ g := newTestGame()
+ g.AccountStore = player.NewAccountStore(t.TempDir())
+ return g
+}
+
+func newTestSession(p *player.Player, acc *player.Account) *net.Session {
+ if p.Options == nil {
+ p.Options = make(map[string]any)
+ }
+ return &net.Session{Player: p, Conn: nopConn{}, State: net.StateGame, Account: acc}
+}
+
+func newTestPlayer(name string) *player.Player {
+ return &player.Player{
+ Name: name,
+ RoomID: 100,
+ Skills: map[player.SkillName]int{player.Hitpoints: player.XPForLevel(99)},
+ HP: 50,
+ Options: make(map[string]any),
+ }
+}
+
+func TestRunInteractionNoMessagesAppliesInstantly(t *testing.T) {
+ g := newTestGameWithStore(t)
+ p := newTestPlayer("test")
+ sess := newTestSession(p, nil)
+
+ step := &behavior.StepAction{
+ NodeAction: behavior.NodeAction{Heal: 10},
+ }
+
+ g.runInteraction(sess, p, step, 100, scopePlayer, nil)
+
+ if p.Action != nil {
+ t.Error("expected no pending action for message-less interaction")
+ }
+ if p.HP != 60 {
+ t.Errorf("expected HP 60 after heal, got %d", p.HP)
+ }
+}
+
+func TestRunInteractionWithMessagesStartsSequence(t *testing.T) {
+ g := newTestGame()
+ p := newTestPlayer("test")
+ p.Credits = 100
+ sess := newTestSession(p, nil)
+
+ step := &behavior.StepAction{
+ NodeAction: behavior.NodeAction{Credits: 50},
+ Messages: []behavior.DelayedMessage{
+ {Message: "You feel a strange energy.", Delay: 3},
+ },
+ }
+
+ g.runInteraction(sess, p, step, 100, scopePlayer, nil)
+
+ if p.Action == nil {
+ t.Fatal("expected pending action for message-bearing interaction")
+ }
+ if p.Action.Type != behavior.TypeInteractionSeq {
+ t.Errorf("expected TypeInteractionSeq, got %v", p.Action.Type)
+ }
+ if p.Action.WaitLeft != 3 {
+ t.Errorf("expected WaitLeft 3, got %d", p.Action.WaitLeft)
+ }
+ if p.Credits != 100 {
+ t.Errorf("expected credits unchanged (100), got %d", p.Credits)
+ }
+}
+
+func TestAdvanceInteractionSeqDrainsZeroDelay(t *testing.T) {
+ g := newTestGameWithStore(t)
+ p := newTestPlayer("test")
+ sess := newTestSession(p, nil)
+
+ step := &behavior.StepAction{
+ NodeAction: behavior.NodeAction{Heal: 10},
+ Messages: []behavior.DelayedMessage{
+ {Message: "First.", Delay: 0},
+ {Message: "Second.", Delay: 0},
+ {Message: "Third.", Delay: 0},
+ },
+ }
+
+ g.startInteractionSeq(sess, p, step, 100, scopePlayer, nil)
+
+ if p.Action == nil || p.Action.WaitLeft != 1 {
+ t.Fatalf("expected WaitLeft 1 (delay 0 floored), got Action:%v", p.Action)
+ }
+
+ g.advanceInteractionSeq(sess, p)
+
+ if p.Action != nil {
+ t.Error("expected action cancelled after draining all messages")
+ }
+ if p.HP != 60 {
+ t.Errorf("expected HP 60 after heal, got %d", p.HP)
+ }
+}
+
+func TestAdvanceInteractionSeqHonorsDelay(t *testing.T) {
+ g := newTestGameWithStore(t)
+ p := newTestPlayer("test")
+ sess := newTestSession(p, nil)
+
+ step := &behavior.StepAction{
+ NodeAction: behavior.NodeAction{Heal: 10},
+ Messages: []behavior.DelayedMessage{
+ {Message: "First.", Delay: 2},
+ {Message: "Second.", Delay: 3},
+ },
+ }
+
+ g.startInteractionSeq(sess, p, step, 100, scopePlayer, nil)
+
+ if p.Action == nil || p.Action.WaitLeft != 2 {
+ t.Fatalf("expected WaitLeft 2, got %v", p.Action)
+ }
+ p.Action.WaitLeft = 1
+
+ g.advanceInteractionSeq(sess, p)
+
+ if p.Action == nil {
+ t.Fatal("expected action still running (second message pending)")
+ }
+ if p.Action.WaitLeft != 3 {
+ t.Errorf("expected WaitLeft 3 (second message delay), got %d", p.Action.WaitLeft)
+ }
+ if p.HP != 50 {
+ t.Error("heal should not have fired yet")
+ }
+
+ g.advanceInteractionSeq(sess, p)
+
+ if p.Action != nil {
+ t.Error("expected action cancelled after last message + heal")
+ }
+ if p.HP != 60 {
+ t.Errorf("expected HP 60 after heal, got %d", p.HP)
+ }
+}
+
+func TestInteractionSeqCancelPreventsTrailingEffects(t *testing.T) {
+ g := newTestGame()
+ p := newTestPlayer("test")
+ sess := newTestSession(p, nil)
+
+ step := &behavior.StepAction{
+ NodeAction: behavior.NodeAction{Heal: 10},
+ Messages: []behavior.DelayedMessage{
+ {Message: "Hello.", Delay: 5},
+ },
+ }
+
+ g.startInteractionSeq(sess, p, step, 100, scopePlayer, nil)
+
+ if p.Action == nil {
+ t.Fatal("expected pending action")
+ }
+
+ g.cancelAction(p)
+
+ if p.Action != nil {
+ t.Error("expected action to be cancelled")
+ }
+ if p.HP != 50 {
+ t.Error("heal should not have fired after cancel")
+ }
+}
+
+func TestApplyStepActionEmitsAllMessages(t *testing.T) {
+ g := newTestGameWithStore(t)
+ p := newTestPlayer("test")
+ sess := newTestSession(p, nil)
+
+ step := &behavior.StepAction{
+ NodeAction: behavior.NodeAction{Heal: 10},
+ Messages: []behavior.DelayedMessage{
+ {Message: "A.", Delay: 99},
+ {Message: "B.", Delay: 99},
+ },
+ }
+
+ g.applyStepAction(sess, p, step, 100, scopePlayer, nil)
+
+ if p.HP != 60 {
+ t.Errorf("expected HP 60 after heal, got %d", p.HP)
+ }
+}