Files
pncdsl/scene.trigger.go
2026-05-25 22:27:53 +02:00

83 lines
2.2 KiB
Go

package pncdsl
// Trigger fires Do when its When condition first becomes true after the
// trigger arms (on scene enter). Rising-edge semantics: the Do action is
// queued only on a false→true transition, so a condition that becomes
// true and stays true fires exactly once per arming. Once=true makes the
// trigger fire at most once per arming regardless of further toggles.
type Trigger struct {
Name string
When Condition
Do Action
Once bool
}
// triggerState is the per-trigger bookkeeping for the active scene.
// Cleared on scene enter and on Load.
type triggerState struct {
lastTrue bool
fired bool
}
// resetTriggers re-arms every trigger on the current scene — called on
// scene change and after Load. Triggers are not pre-armed: the first
// frame samples their condition fresh, so a Flag that's already true
// will fire the trigger on the rising-edge of the FIRST sample (we treat
// "no prior sample" as false).
func (g *Game) resetTriggers() {
g.triggerStates = make(map[string]*triggerState)
if g.currentScene == "" {
return
}
s, ok := g.SceneManager.Get(g.currentScene)
if !ok {
return
}
for _, t := range s.Triggers {
if t.Name == "" {
continue
}
g.triggerStates[t.Name] = &triggerState{}
}
}
// tickTriggers evaluates every armed trigger on the current scene. The
// engine calls this only when the script slot is idle (so we never
// preempt a running cutscene). The first matching rising-edge fires the
// trigger's Do action and consumes the script slot for this frame.
func (g *Game) tickTriggers() {
if g.scriptRunner != nil || g.currentScene == "" {
return
}
s, ok := g.SceneManager.Get(g.currentScene)
if !ok {
return
}
ctx := g.makeCtx()
for _, t := range s.Triggers {
if t.Name == "" || t.When == nil {
continue
}
st := g.triggerStates[t.Name]
if st == nil {
st = &triggerState{}
g.triggerStates[t.Name] = st
}
if st.fired && t.Once {
continue
}
now := t.When.Eval(ctx)
// Rising edge: was false, now true.
if now && !st.lastTrue {
st.fired = true
st.lastTrue = true
if t.Do != nil {
g.queueAction(t.Do, "trigger "+t.Name)
return // one trigger per frame keeps ordering deterministic
}
continue
}
st.lastTrue = now
}
}