diff options
Diffstat (limited to 'internal/behavior')
| -rw-r--r-- | internal/behavior/behavior.go | 189 | ||||
| -rw-r--r-- | internal/behavior/types.go | 59 |
2 files changed, 123 insertions, 125 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"` } - - diff --git a/internal/behavior/types.go b/internal/behavior/types.go index f451400..7aba045 100644 --- a/internal/behavior/types.go +++ b/internal/behavior/types.go @@ -28,30 +28,29 @@ func WordPrefixMatch(input, name string) bool { type ActionType string const ( - TypeGather ActionType = "gather" - TypeSteal ActionType = "steal" - TypeTalk ActionType = "talk" - TypeBurn ActionType = "burn" - TypeStoke ActionType = "stoke" - TypeSearch ActionType = "search" - TypeIdentify ActionType = "identify" - TypePlant ActionType = "plant" - TypeHarvest ActionType = "harvest" - TypeRake ActionType = "rake" - TypeWater ActionType = "water" - TypeCure ActionType = "cure" - TypeObstacle ActionType = "obstacle" - TypeFletch ActionType = "fletch" - TypeClean ActionType = "cleaning" - TypeCook ActionType = "cook" - TypeSmelt ActionType = "smelting" - TypeSmith ActionType = "smith" - TypeCraft ActionType = "craft" - TypeCombine ActionType = "combine" - TypeMix ActionType = "mix" - TypeConstruct ActionType = "construct" - TypeTriggerModule ActionType = "trigger_module" - TypeInteractionSeq ActionType = "interaction_seq" + TypeGather ActionType = "gather" + TypeSteal ActionType = "steal" + TypeTalk ActionType = "talk" + TypeBurn ActionType = "burn" + TypeStoke ActionType = "stoke" + TypeSearch ActionType = "search" + TypeIdentify ActionType = "identify" + TypePlant ActionType = "plant" + TypeHarvest ActionType = "harvest" + TypeRake ActionType = "rake" + TypeWater ActionType = "water" + TypeCure ActionType = "cure" + TypeObstacle ActionType = "obstacle" + TypeFletch ActionType = "fletch" + TypeClean ActionType = "cleaning" + TypeCook ActionType = "cook" + TypeSmelt ActionType = "smelting" + TypeSmith ActionType = "smith" + TypeCraft ActionType = "craft" + TypeCombine ActionType = "combine" + TypeMix ActionType = "mix" + TypeConstruct ActionType = "construct" + TypeTriggerModule ActionType = "trigger_module" ) type Action struct { @@ -111,7 +110,7 @@ type TalkData struct { EstateDirectory bool StealGuard bool PendingChoice string - PendingAction *NodeAction + PendingAction *Step CancelTalk bool PendingSeq bool PendingChosen bool @@ -143,11 +142,11 @@ type ObstacleData struct { CompletionXP int // FailChance is the resolved failure probability for this attempt. // nil => derived at runtime from agility level vs RequiredLevel. - FailChance *float64 - FailDamageMin int - FailDamageMax int - Phases []PhaseData - RequiredLevel int + FailChance *float64 + FailDamageMin int + FailDamageMax int + Phases []PhaseData + RequiredLevel int } // PhaseData is the runtime form of an obstacle phase. |
