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
|
package game
import (
"fmt"
"strings"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/player"
)
// 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 {
return
}
p := sess.Player
if p == nil {
return
}
g.runTrigger(sess, p, room.OnEnter, roomID, nil, true)
}
// 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 := sess.Player
if p == nil {
return
}
g.runTrigger(sess, p, room.OnExit, roomID, nil, false)
}
// applyFlagMutations sets global and player flags. It is the player-scoped
// 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 {
for k, v := range setPlayerFlags {
g.setPlayerFlag(p, k, v)
}
}
}
func (g *Game) BroadcastRespawns() {
respawns := g.World.FlushObjRespawns()
for _, st := range respawns {
def, err := g.ObjectStore.Load(st.DefID)
if err != nil || !def.IsGatherable() {
continue
}
cfg := def.Gather
if cfg.RespawnBroadcast == "" {
continue
}
name := def.Name
if idx := st.Index + 1; len(g.World.FindObjInstances(st.RoomID, st.DefID)) > 1 {
name += fmt.Sprintf(" [%d]", idx)
}
msg := strings.ReplaceAll(cfg.RespawnBroadcast, "{name}", name)
if g.Hub != nil {
for _, sess := range g.Hub.PlayersInRoom(st.RoomID) {
sess.WriteLine(g.colorize(sess, "broadcast", msg))
}
}
}
}
// 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
}
|