aboutsummaryrefslogtreecommitdiff
path: root/internal/game/act_room.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-08 19:59:14 -0400
committerhistoria <[not public]>2026-07-08 19:59:14 -0400
commit09d325dcf5e779eab5550d3fd3377bde50101428 (patch)
treea433be903aabbf1d2eadce2aacdac12a9eb8dde0 /internal/game/act_room.go
parent9184377301c2604e003f36426788c032fb0ca524 (diff)
downloadthehouseoficarus-09d325dcf5e779eab5550d3fd3377bde50101428.tar.gz
feat: unify on use, on look, and on kill. all support same conditions/actions now.
Diffstat (limited to 'internal/game/act_room.go')
-rw-r--r--internal/game/act_room.go92
1 files changed, 20 insertions, 72 deletions
diff --git a/internal/game/act_room.go b/internal/game/act_room.go
index da74d6e..2b08bfa 100644
--- a/internal/game/act_room.go
+++ b/internal/game/act_room.go
@@ -4,10 +4,9 @@ import (
"fmt"
"strings"
- "thehouseoficarus/internal/color"
+ "thehouseoficarus/internal/behavior"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/player"
- "thehouseoficarus/internal/world"
)
// enterSeq is an in-flight on_enter stepped sequence for a single player. It is
@@ -16,13 +15,13 @@ type enterSeq struct {
sess *net.Session
playerName string
roomID int
- steps []world.EnterStep
+ 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"
-// (no delay, no flag mutation) the messages are printed synchronously.
+// (any non-zero effect field) the messages are printed synchronously.
// Otherwise the surviving steps become a scheduled enter sequence driven
// by EnterSeqTick.
func (g *Game) runEnterSteps(sess *net.Session, roomID int) {
@@ -35,7 +34,7 @@ func (g *Game) runEnterSteps(sess *net.Session, roomID int) {
return
}
- var steps []world.EnterStep
+ var steps []behavior.StepAction
sequenced := false
for _, step := range room.OnEnter {
if step.Condition != nil && !g.checkCondition(sess, step.Condition) {
@@ -51,6 +50,7 @@ func (g *Game) runEnterSteps(sess *net.Session, roomID int) {
}
if !sequenced {
+ // Pure message-only steps — print synchronously, no side effects.
for _, step := range steps {
if step.Message != "" {
sess.WriteLine(step.Message)
@@ -65,7 +65,7 @@ func (g *Game) runEnterSteps(sess *net.Session, roomID int) {
// 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 []world.EnterStep) {
+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
@@ -98,7 +98,7 @@ func (g *Game) EnterSeqTick() {
continue
}
- var jobs []world.EnterStep
+ var jobs []behavior.StepAction
done := false
g.enterMu.Lock()
@@ -123,8 +123,8 @@ func (g *Game) EnterSeqTick() {
}
g.enterMu.Unlock()
- for _, step := range jobs {
- g.fireEnterStep(seq, step)
+ for i := range jobs {
+ g.fireEnterStep(seq, &jobs[i])
}
if done {
g.completeEnterSeq(seq)
@@ -132,71 +132,16 @@ func (g *Game) EnterSeqTick() {
}
}
-func (g *Game) fireEnterStep(seq *enterSeq, step world.EnterStep) {
+// 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
}
- seqSpec := g.resolveColor(seq.sess, "sequence")
- broadcastSpec := g.resolveColor(seq.sess, "broadcast")
- mode := g.colorMode(seq.sess)
- if step.Message != "" {
- msg := expandTemplate(step.Message, p.Name, nil)
- msg = color.ExpandTagsDefault(mode, seqSpec, msg)
- seq.sess.WriteLine(msg)
- }
- if step.Broadcast != "" && g.Hub != nil {
- msg := expandTemplate(step.Broadcast, p.Name, nil)
- msg = color.ExpandTagsDefault(mode, broadcastSpec, msg)
- for _, other := range g.Hub.PlayersInRoom(seq.roomID) {
- other.WriteLine(g.colorize(other, "broadcast", msg))
- }
- }
- if step.BroadcastGlobal != "" && g.Hub != nil {
- msg := expandTemplate(step.BroadcastGlobal, p.Name, nil)
- msg = color.ExpandTagsDefault(mode, broadcastSpec, msg)
- for _, other := range g.Hub.AllSessions() {
- if other.Player != nil {
- other.WriteLine(g.colorize(other, "broadcast", msg))
- }
- }
- }
- if len(step.SetGlobalFlags) > 0 || len(step.SetPlayerFlags) > 0 {
- g.applyFlagMutations(p, step.SetGlobalFlags, step.SetPlayerFlags)
- g.AccountStore.SaveCharacter(p)
- }
- if step.SpawnMob != nil {
- g.spawnTriggerMob(seq.sess, p, step.SpawnMob, seq.roomID)
- }
- if step.DespawnMob != "" {
- g.despawnTriggerMobs(step.DespawnMob, p.Name)
- }
- if step.GiveItem != "" {
- slot := p.FirstFreeSlot()
- if slot == -1 {
- seq.sess.WriteLine("Your inventory is too full to receive that.")
- } else {
- p.SetInvSlot(slot, &player.InventorySlot{ItemID: step.GiveItem, Quantity: 1})
- g.AccountStore.SaveCharacter(p)
- }
- }
- if step.TakeItem != "" {
- if p.HasItem(step.TakeItem) {
- p.RemoveItem(step.TakeItem, 1)
- g.AccountStore.SaveCharacter(p)
- }
- }
- if step.Heal > 0 {
- p.HP += step.Heal
- if maxHP := p.MaxHP(); p.HP > maxHP {
- p.HP = maxHP
- }
- g.AccountStore.SaveCharacter(p)
- seq.sess.WriteLine(fmt.Sprintf("You regain %d hitpoints.", step.Heal))
- }
- if step.Teleport > 0 {
- g.teleportPlayer(seq.sess, p, step.Teleport)
- }
+ g.applyStepAction(seq.sess, p, step, seq.roomID, scopePlayerSeq, nil)
}
func (g *Game) completeEnterSeq(seq *enterSeq) {
@@ -229,8 +174,11 @@ func (g *Game) isCharLive(name string, sess *net.Session) bool {
return g.loggedInChars[name] == sess
}
-// applyFlagMutations sets global and player flags, shared by on_enter steps and
-// exit traversal.
+// 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).
func (g *Game) applyFlagMutations(p *player.Player, setGlobalFlags, setPlayerFlags map[string]any) {
g.GlobalFlags.SetAll(setGlobalFlags)
if len(setPlayerFlags) > 0 {