aboutsummaryrefslogtreecommitdiff
path: root/internal/game/act_interaction_seq.go
blob: 8b515c4d2d99f192d7f730b7db9b231b3071bb10 (plain)
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
package game

import (
	"thehouseoficarus/internal/behavior"
	"thehouseoficarus/internal/net"
	"thehouseoficarus/internal/player"
)

// interactionSeqData is the payload carried by p.Action.Data for an in-flight
// delayed-message interaction sequence (on_use, on_look, on_kill, or
// on_traverse). Messages are emitted one-by-one with per-message delays; the
// trailing StepAction effects fire only after every message has been shown. If
// the player is interrupted (move, quit, new action, combat) cancelAction
// clears the pending Action and the trailing effects never run.
type interactionSeqData struct {
	Msgs    []behavior.DelayedMessage
	Idx     int
	Step    *behavior.StepAction
	RoomID  int
	Scope   effectScope
	FlagVal any
}

// startInteractionSeq fires the first delayed message (on the next tick,
// matching the trigger scheduler's "delay = ticks to wait before this step
// fires" rule with a floor of 1 so the first message ALWAYS arrives next tick
// even when delay is 0). The trailing non-message effects run once the last
// message has been emitted.
func (g *Game) startInteractionSeq(sess *net.Session, p *player.Player, step *behavior.StepAction, roomID int, sc effectScope, flagValue any) {
	g.cancelAction(p)

	firstDelay := 0
	if len(step.Messages) > 0 {
		firstDelay = step.Messages[0].Delay
	}
	if firstDelay < 1 {
		firstDelay = 1
	}

	// Strip Messages from step before stashing — applyStepAction would otherwise
	// re-emit them when the trailing effects run.
	clone := *step
	clone.Messages = nil

	p.Action = &behavior.Action{
		Type:     behavior.TypeInteractionSeq,
		WaitLeft: firstDelay,
		Data: &interactionSeqData{
			Msgs:    step.Messages,
			Idx:     0,
			Step:    &clone,
			RoomID:  roomID,
			Scope:   sc,
			FlagVal: flagValue,
		},
	}
}

// advanceInteractionSeq is the per-tick handler for TypeInteractionSeq
// actions. Each invocation drains any zero-delay messages that are ready, emits
// the current delayed message, then sets the wait for the next message (if any).
// After the last message the trailing effects fire and the action is cancelled.
func (g *Game) advanceInteractionSeq(sess *net.Session, p *player.Player) {
	d, ok := p.Action.Data.(*interactionSeqData)
	if !ok {
		g.cancelAction(p)
		return
	}

	var playerName string
	if p != nil {
		playerName = p.Name
	}

	// Drain zero-delay messages that have accumulated, then emit the current
	// (delayed) one. Matches the trigger scheduler's drain-zero-delay pattern.
	for d.Idx < len(d.Msgs) {
		msg := d.Msgs[d.Idx]
		d.Idx++
		g.writePlayerMessage(sess, msg.Message, playerName, d.FlagVal)

		if d.Idx >= len(d.Msgs) {
			break
		}
		next := d.Msgs[d.Idx].Delay
		if next > 0 {
			p.Action.WaitLeft = next
			return
		}
	}

	// All messages emitted — fire trailing effects.
	if d.Step != nil {
		g.applyStepAction(sess, p, d.Step, d.RoomID, d.Scope, d.FlagVal)
	}
	g.cancelAction(p)
}