aboutsummaryrefslogtreecommitdiff
path: root/internal/behavior/behavior.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/behavior/behavior.go
parentb3d4c616f59ad2519f3a0b77e3b47d6571cd2486 (diff)
downloadthehouseoficarus-ecba7f726f70b37126d852c38c7e3eec7b04d730.tar.gz
feat: old standalone trigger systems completely unified into trigger->condition->action system
Diffstat (limited to 'internal/behavior/behavior.go')
-rw-r--r--internal/behavior/behavior.go189
1 files changed, 94 insertions, 95 deletions
diff --git a/internal/behavior/behavior.go b/internal/behavior/behavior.go
index 12eef0b..75f836b 100644
--- a/internal/behavior/behavior.go
+++ b/internal/behavior/behavior.go
@@ -1,21 +1,21 @@
package behavior
type GatherConfig struct {
- Skill string `yaml:"skill"`
- Tools []string `yaml:"tools"`
- NoToolSpeed float64 `yaml:"no_tool_speed,omitempty"`
- Bait string `yaml:"bait"`
+ Skill string `yaml:"skill"`
+ Tools []string `yaml:"tools"`
+ NoToolSpeed float64 `yaml:"no_tool_speed,omitempty"`
+ Bait string `yaml:"bait"`
Success SuccessFormula `yaml:"success"`
- GatherMessage string `yaml:"gather_message"`
- DepletedMessage string `yaml:"depleted_message"`
- ExhaustedMessage string `yaml:"exhausted_message"`
- FailMessage string `yaml:"fail_message"`
- Drops []DropEntry `yaml:"drops"`
- RespawnTimer float64 `yaml:"respawn_timer"`
- RespawnBroadcast string `yaml:"respawn_broadcast"`
- DepleteTimer float64 `yaml:"deplete_timer"`
- NestChance int `yaml:"nest_chance"`
- BroadcastMessage string `yaml:"broadcast_message"`
+ GatherMessage string `yaml:"gather_message"`
+ DepletedMessage string `yaml:"depleted_message"`
+ ExhaustedMessage string `yaml:"exhausted_message"`
+ FailMessage string `yaml:"fail_message"`
+ Drops []DropEntry `yaml:"drops"`
+ RespawnTimer float64 `yaml:"respawn_timer"`
+ RespawnBroadcast string `yaml:"respawn_broadcast"`
+ DepleteTimer float64 `yaml:"deplete_timer"`
+ NestChance int `yaml:"nest_chance"`
+ BroadcastMessage string `yaml:"broadcast_message"`
}
type SuccessFormula struct {
@@ -32,88 +32,83 @@ type TalkNode struct {
Messages []string `yaml:"messages,omitempty"`
Condition *Condition `yaml:"condition,omitempty"`
Options []TalkOption `yaml:"options"`
- Action *NodeAction `yaml:"action"`
+ Action *Step `yaml:"action,omitempty"`
Goto string `yaml:"goto,omitempty"`
}
type TalkOption struct {
- Text string `yaml:"text"`
- Goto string `yaml:"goto"`
- Condition *Condition `yaml:"condition"`
- Action *NodeAction `yaml:"action,omitempty"`
-}
-
-// NodeAction is the inline-only effects payload — used by talk nodes,
-// talk options, and any callers that need a simple "do these effects now"
-// primitive (use/look/kill interactions, exits). It is the embedded
-// subset of StepAction (the universal superset used by on_enter and triggers).
-type NodeAction struct {
- SetGlobalFlags map[string]any `yaml:"set_global_flags,omitempty"`
- SetPlayerFlags map[string]any `yaml:"set_player_flags,omitempty"`
- GiveItem string `yaml:"give_item,omitempty"`
- TakeItem string `yaml:"take_item,omitempty"`
- Teleport int `yaml:"teleport,omitempty"`
- Heal int `yaml:"heal,omitempty"`
- Credits int `yaml:"credits,omitempty"`
- ApsNode bool `yaml:"aps_node,omitempty"`
-}
-
-// DelayedMessage is a single message with a delay (in ticks) to wait before
-// showing it. Used by the interaction sequencer (on_use / on_look / on_kill /
-// on_traverse); on_enter and trigger steps emit their Messages list
-// immediately when the step fires (use the step's own Delay for inter-step
-// spacing).
-type DelayedMessage struct {
- Message string `yaml:"message,omitempty"`
- Delay int `yaml:"delay,omitempty"`
-}
-
-// Interaction is one conditional trigger entry shared by on_use, on_look
-// (objects) and on_kill (mobs). Item filters by the appropriate held/weapon
-// item depending on the interaction kind (see itemMatch on applyInteraction).
-// The first entry whose item filter and Condition pass wins and fires exactly
-// once.
+ Text string `yaml:"text"`
+ Goto string `yaml:"goto"`
+ Condition *Condition `yaml:"condition"`
+ Action *Step `yaml:"action,omitempty"`
+}
+
+// Step is the universal effect primitive — one struct shared across every
+// effect executor in the game: talk nodes/options, on_use/on_look/on_kill/
+// on_enter/on_exit/on_traverse triggers, and on_flag_change/on_global_flag_change
+// triggers. A trigger's effects are expressed as an ordered list of Steps;
+// spacing between steps is the Wait field (a pure {wait: N} step pauses the
+// sequence for N ticks before the next step fires).
//
-// Action is a full StepAction so on_kill may broadcast a kill announcement or
-// spawn a follow-up mob; inline callers (use/look) simply leave the
-// sequence-only fields empty.
-type Interaction struct {
- Item string `yaml:"item_id,omitempty"`
- Condition *Condition `yaml:"condition,omitempty"`
- Action *StepAction `yaml:"action,omitempty"`
-}
-
-// StepAction is the universal effect primitive — one superset struct shared
-// across all effect executors (on_enter steps, room/global triggers, talk
-// nodes, use/look/kill interactions, exit traversal). The embedded
-// NodeAction holds the inline-only effects (flags/items/teleport/heal/credits/
-// aps_node); the additional fields are the sequence-only effects (messages,
-// broadcasts, mob spawn/despawn, delay). Condition is evaluated per-step by
-// the on_enter / trigger schedulers; inline callers ignore it (the gating
-// happens at the parent Interaction.ExitDef level instead).
-type StepAction struct {
- NodeAction `yaml:",inline"`
- Condition *Condition `yaml:"condition,omitempty"`
- Messages []DelayedMessage `yaml:"messages,omitempty"`
- Broadcast string `yaml:"broadcast,omitempty"`
- BroadcastGlobal string `yaml:"broadcast_global,omitempty"`
- SpawnMob *SpawnMobConfig `yaml:"spawn_mob,omitempty"`
- DespawnMob string `yaml:"despawn_mob,omitempty"`
- Delay int `yaml:"delay,omitempty"`
-}
-
-// IsTimed reports whether the step carries sequence semantics (any non-zero
-// field means it needs per-tick scheduling rather than synchronous printing).
-func (s StepAction) IsTimed() bool {
- return s.Delay > 0 || len(s.Messages) > 0 || s.Broadcast != "" || s.BroadcastGlobal != "" ||
+// Condition is evaluated per-step by the sequence scheduler; an inline caller
+// (talk node, synchronous use/look/kill) fires a single step without the
+// scheduler and ignores the per-step Condition (its gating is done at the
+// parent Trigger level instead).
+type Step struct {
+ Condition *Condition `yaml:"condition,omitempty"`
+ Wait int `yaml:"wait,omitempty"`
+ Messages []string `yaml:"messages,omitempty"`
+ Broadcast string `yaml:"broadcast,omitempty"`
+ BroadcastGlobal string `yaml:"broadcast_global,omitempty"`
+ SpawnMob *SpawnMobConfig `yaml:"spawn_mob,omitempty"`
+ DespawnMob string `yaml:"despawn_mob,omitempty"`
+ SetGlobalFlags map[string]any `yaml:"set_global_flags,omitempty"`
+ SetPlayerFlags map[string]any `yaml:"set_player_flags,omitempty"`
+ GiveItem string `yaml:"give_item,omitempty"`
+ TakeItem string `yaml:"take_item,omitempty"`
+ Teleport int `yaml:"teleport,omitempty"`
+ Heal int `yaml:"heal,omitempty"`
+ Credits int `yaml:"credits,omitempty"`
+ ApsNode bool `yaml:"aps_node,omitempty"`
+}
+
+// HasEffects reports whether the step carries any effect beyond a pure wait.
+func (s Step) HasEffects() bool {
+ return len(s.Messages) > 0 || s.Broadcast != "" || s.BroadcastGlobal != "" ||
s.SpawnMob != nil || s.DespawnMob != "" ||
len(s.SetGlobalFlags) > 0 || len(s.SetPlayerFlags) > 0 ||
s.GiveItem != "" || s.TakeItem != "" || s.Teleport != 0 || s.Heal != 0 ||
s.Credits != 0 || s.ApsNode
}
-// SpawnMobConfig configures a transient mob spawned by a trigger or on_enter
-// step. The mob id is required; other fields are optional.
+// Trigger is the one conditional wrapper used by every event block in the
+// game: on_use, on_look, on_kill, on_enter, on_exit, on_traverse,
+// on_flag_change, and on_global_flag_change. Entries in a block are walked
+// top-to-bottom; the first whose ItemID filter and Condition pass wins and
+// its Steps run as a scripted sequence.
+//
+// For verb blocks only ItemID/Condition/Steps/Lock are meaningful. For
+// on_flag_change/on_global_flag_change blocks, OnPlayerFlag/OnGlobalFlag (and
+// optional Value) declare the flag subscription; Condition provides an
+// additional gate (use the Room condition to scope a flag trigger to a room).
+//
+// Lock, when true, makes the sequence atomic: the player is blocked from any
+// verb (except quit) until it completes, and the sequence is saved and resumed
+// across disconnect. Unlocked sequences (the default) are interruptable by any
+// verb and are not persisted.
+type Trigger struct {
+ Lock bool `yaml:"lock,omitempty"`
+ ItemID string `yaml:"item_id,omitempty"`
+ Condition *Condition `yaml:"condition,omitempty"`
+ Steps []Step `yaml:"steps"`
+ OnPlayerFlag string `yaml:"on_player_flag,omitempty"`
+ OnGlobalFlag string `yaml:"on_global_flag,omitempty"`
+ Value any `yaml:"value,omitempty"`
+ DedupKey string `yaml:"-"` // runtime only — disambiguates flag-trigger dedup keys
+}
+
+// SpawnMobConfig configures a transient mob spawned by a trigger step. The
+// mob id is required; other fields are optional.
type SpawnMobConfig struct {
ID string `yaml:"id"`
OwnerOnly bool `yaml:"owner_only"`
@@ -210,15 +205,19 @@ func (c *ShopConfig) FindItem(itemID string) *ShopItem {
return nil
}
+// Condition is the single predicate struct used everywhere a gate is needed:
+// exits, descriptions, talk nodes/options, trigger entries, and per-step
+// conditions. The atomic predicates combine via AllOf/AnyOf nesting and the
+// Not inverter. Room matches when the triggering player is currently in that
+// room (ignored for global-flag triggers with no player).
type Condition struct {
- GlobalFlag string `yaml:"global_flag"`
- Value any `yaml:"value"`
- Not bool `yaml:"not"`
- PlayerFlag string `yaml:"player_flag"`
- HasItem string `yaml:"has_item"`
- MinCredits int `yaml:"min_credits"`
- AllOf []Condition `yaml:"all_of"`
- AnyOf []Condition `yaml:"any_of"`
+ GlobalFlag string `yaml:"global_flag,omitempty"`
+ PlayerFlag string `yaml:"player_flag,omitempty"`
+ Room int `yaml:"room,omitempty"`
+ Value any `yaml:"value,omitempty"`
+ Not bool `yaml:"not,omitempty"`
+ HasItem string `yaml:"has_item,omitempty"`
+ MinCredits int `yaml:"min_credits,omitempty"`
+ AllOf []Condition `yaml:"all_of,omitempty"`
+ AnyOf []Condition `yaml:"any_of,omitempty"`
}
-
-