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) } }