diff options
| author | historia <[not public]> | 2026-07-09 22:15:54 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-07-09 22:15:54 -0400 |
| commit | ecba7f726f70b37126d852c38c7e3eec7b04d730 (patch) | |
| tree | 9129215c6e5015336fde1116395336d82bb952d1 /internal/game/act_room.go | |
| parent | b3d4c616f59ad2519f3a0b77e3b47d6571cd2486 (diff) | |
| download | thehouseoficarus-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.go | 178 |
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 +} |
