aboutsummaryrefslogtreecommitdiff
path: root/internal/game/act_room.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-09 22:15:54 -0400
committerhistoria <[not public]>2026-07-09 22:15:54 -0400
commitecba7f726f70b37126d852c38c7e3eec7b04d730 (patch)
tree9129215c6e5015336fde1116395336d82bb952d1 /internal/game/act_room.go
parentb3d4c616f59ad2519f3a0b77e3b47d6571cd2486 (diff)
downloadthehouseoficarus-ecba7f726f70b37126d852c38c7e3eec7b04d730.tar.gz
feat: old standalone trigger systems completely unified into trigger->condition->action system
Diffstat (limited to 'internal/game/act_room.go')
-rw-r--r--internal/game/act_room.go178
1 files changed, 24 insertions, 154 deletions
diff --git a/internal/game/act_room.go b/internal/game/act_room.go
index a6acc21..889ce85 100644
--- a/internal/game/act_room.go
+++ b/internal/game/act_room.go
@@ -4,26 +4,13 @@ import (
"fmt"
"strings"
- "thehouseoficarus/internal/behavior"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/player"
)
-// enterSeq is an in-flight on_enter stepped sequence for a single player. It is
-// driven one step at a time by EnterSeqTick.
-type enterSeq struct {
- sess *net.Session
- playerName string
- roomID int
- steps []behavior.StepAction
- wait int
-}
-
-// runEnterSteps evaluates a room's on_enter script for the player. Steps whose
-// condition fails are dropped. If none of the surviving steps are "timed"
-// (any non-zero effect field) the messages are printed synchronously.
-// Otherwise the surviving steps become a scheduled enter sequence driven
-// by EnterSeqTick.
+// runEnterSteps evaluates a room's on_enter block for the player. The first
+// trigger whose Condition passes wins; its Steps run as a scripted sequence
+// (room-locked, so steps are skipped if the player immediately leaves).
func (g *Game) runEnterSteps(sess *net.Session, roomID int) {
room, err := g.World.LoadRoom(roomID)
if err != nil || len(room.OnEnter) == 0 {
@@ -33,154 +20,29 @@ func (g *Game) runEnterSteps(sess *net.Session, roomID int) {
if p == nil {
return
}
-
- var steps []behavior.StepAction
- sequenced := false
- for _, step := range room.OnEnter {
- if step.Condition != nil && !g.checkCondition(sess, step.Condition) {
- continue
- }
- steps = append(steps, step)
- if step.IsTimed() {
- sequenced = true
- }
- }
- if len(steps) == 0 {
- return
- }
-
- if !sequenced {
- // Pure message-only steps — print synchronously, no side effects.
- for _, step := range steps {
- for _, msg := range step.Messages {
- if msg.Message != "" {
- sess.WriteLine(msg.Message)
- }
- }
- }
- return
- }
-
- g.startEnterSeq(sess, p, roomID, steps)
+ g.runTrigger(sess, p, room.OnEnter, roomID, nil, true)
}
-// startEnterSeq registers a scheduled on_enter sequence and persists a marker
-// on the player so the sequence can be resumed on reconnect if interrupted
-// (disconnect, server restart).
-func (g *Game) startEnterSeq(sess *net.Session, p *player.Player, roomID int, steps []behavior.StepAction) {
- g.cancelEnterSeq(p.Name)
- if p.EnterSeqRoom != roomID {
- p.EnterSeqRoom = roomID
- g.AccountStore.SaveCharacter(p)
- }
- g.enterMu.Lock()
- g.enterSeqs[p.Name] = &enterSeq{
- sess: sess,
- playerName: p.Name,
- roomID: roomID,
- steps: steps,
- wait: steps[0].Delay,
- }
- g.enterMu.Unlock()
-}
-
-// EnterSeqTick advances every in-flight enter sequence by one tick. Sequences
-// for a player who is no longer connected are paused (left for resume);
-// sequences are removed from the map on completion or cancellation.
-func (g *Game) EnterSeqTick() {
- g.enterMu.Lock()
- seqs := make([]*enterSeq, 0, len(g.enterSeqs))
- for _, s := range g.enterSeqs {
- seqs = append(seqs, s)
- }
- g.enterMu.Unlock()
-
- for _, seq := range seqs {
- if !g.isCharLive(seq.playerName, seq.sess) {
- continue
- }
-
- var jobs []behavior.StepAction
- done := false
-
- g.enterMu.Lock()
- if g.enterSeqs[seq.playerName] != seq {
- g.enterMu.Unlock()
- continue
- }
- seq.wait--
- for seq.wait <= 0 && len(seq.steps) > 0 {
- step := seq.steps[0]
- seq.steps = seq.steps[1:]
- jobs = append(jobs, step)
- if len(seq.steps) > 0 {
- seq.wait = seq.steps[0].Delay
- } else {
- seq.wait = 0
- }
- }
- if len(seq.steps) == 0 {
- done = true
- delete(g.enterSeqs, seq.playerName)
- }
- g.enterMu.Unlock()
-
- for i := range jobs {
- g.fireEnterStep(seq, &jobs[i])
- }
- if done {
- g.completeEnterSeq(seq)
- }
- }
-}
-
-// fireEnterStep runs one on_enter step's effects via the unified
-// applyStepAction executor. The room-lock check (player must still be in the
-// room that the sequence is being played for) lives here, before dispatch —
-// applyStepAction itself doesn't re-check it.
-func (g *Game) fireEnterStep(seq *enterSeq, step *behavior.StepAction) {
- p := seq.sess.Player
- if p == nil || p.RoomID != seq.roomID {
- return
- }
- g.applyStepAction(seq.sess, p, step, seq.roomID, scopePlayerSeq, nil)
-}
-
-func (g *Game) completeEnterSeq(seq *enterSeq) {
- if !g.isCharLive(seq.playerName, seq.sess) {
+// runExitSteps evaluates a room's on_exit block for the player. It fires the
+// first matching trigger as a non-room-locked sequence using the room being
+// left as the reference room (broadcasts/spawn resolve to the old room). It is
+// invoked before the player's RoomID is updated to the destination.
+func (g *Game) runExitSteps(sess *net.Session, roomID int) {
+ room, err := g.World.LoadRoom(roomID)
+ if err != nil || len(room.OnExit) == 0 {
return
}
- p := seq.sess.Player
+ p := sess.Player
if p == nil {
return
}
- if p.EnterSeqRoom == seq.roomID {
- p.EnterSeqRoom = 0
- g.AccountStore.SaveCharacter(p)
- }
- g.writePrompt(seq.sess)
-}
-
-// cancelEnterSeq drops any in-flight enter sequence for the player. It does not
-// touch the persisted EnterSeqRoom marker, so a sequence cancelled by
-// disconnect can still be resumed on reconnect.
-func (g *Game) cancelEnterSeq(name string) {
- g.enterMu.Lock()
- delete(g.enterSeqs, name)
- g.enterMu.Unlock()
-}
-
-func (g *Game) isCharLive(name string, sess *net.Session) bool {
- g.charsMu.Lock()
- defer g.charsMu.Unlock()
- return g.loggedInChars[name] == sess
+ g.runTrigger(sess, p, room.OnExit, roomID, nil, false)
}
// applyFlagMutations sets global and player flags. It is the player-scoped
-// (scopePlayer / scopePlayerSeq) flag step of applyStepAction, called once
-// per non-global step that carries set_global_flags and/or set_player_flags.
-// scopeGlobal sets globals directly in applyStepAction step 4 instead (so
-// world-scoped cascades don't reach back into this helper).
+// flag step of applyStep, called once per step that carries set_global_flags
+// and/or set_player_flags. scopeGlobal sets globals directly in applyStep
+// instead (so world-scoped cascades don't reach back into this helper).
func (g *Game) applyFlagMutations(p *player.Player, setGlobalFlags, setPlayerFlags map[string]any) {
g.GlobalFlags.SetAll(setGlobalFlags)
if len(setPlayerFlags) > 0 {
@@ -213,3 +75,11 @@ func (g *Game) BroadcastRespawns() {
}
}
}
+
+// isCharLive reports whether the named player is currently connected via the
+// given session (used by older helpers that still reference it).
+func (g *Game) isCharLive(name string, sess *net.Session) bool {
+ g.charsMu.Lock()
+ defer g.charsMu.Unlock()
+ return g.loggedInChars[name] == sess
+}