From e6b4cb9f5816c6ee239233bc85f74ee446a161c2 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Wed, 1 Jul 2026 16:37:45 -0400 Subject: feat: shop system overhauled, gui conversation tree editor overhauled --- internal/behavior/behavior.go | 128 ++++++++++++++++++++++++++++++++---------- 1 file changed, 99 insertions(+), 29 deletions(-) (limited to 'internal/behavior/behavior.go') diff --git a/internal/behavior/behavior.go b/internal/behavior/behavior.go index 2ef7fae..646495a 100644 --- a/internal/behavior/behavior.go +++ b/internal/behavior/behavior.go @@ -28,8 +28,7 @@ type TalkConfig struct { } type TalkNode struct { - Message MessageList `yaml:"message"` - Sequence []string `yaml:"sequence,omitempty"` + Messages []string `yaml:"messages,omitempty"` Condition *Condition `yaml:"condition,omitempty"` Options []TalkOption `yaml:"options"` Action *NodeAction `yaml:"action"` @@ -37,9 +36,9 @@ type TalkNode struct { } type TalkOption struct { - Text string `yaml:"text"` - Goto string `yaml:"goto"` - Condition *Condition `yaml:"condition"` + Text string `yaml:"text"` + Goto string `yaml:"goto"` + Condition *Condition `yaml:"condition"` Action *NodeAction `yaml:"action,omitempty"` } @@ -50,31 +49,102 @@ type NodeAction struct { TakeItem string `yaml:"take_item"` Teleport int `yaml:"teleport"` Heal int `yaml:"heal"` - Cost int `yaml:"cost"` - Shop *ShopConfig `yaml:"shop"` - AssignTask bool `yaml:"assign_task"` - SkipTask bool `yaml:"skip_task"` - ExtendTask bool `yaml:"extend_task"` + Credits int `yaml:"credits"` ReputationCost int `yaml:"reputation_cost"` - Sawmill bool `yaml:"sawmill"` ApsNode bool `yaml:"aps_node"` } +// ShopConfig is a root-level mob property (mob YAML `shop:`). Buy prices are +// always 100% of the item's value; sell prices follow the OSRS store formula: +// +// sell = value * max(10, buy_percentage - stock*change_percentage) / 100 +// +// where stock is the shop's current holding of that item. BuyPercentage +// defaults to 40 and ChangePercentage to 3 when unset (see ShopBuyPercentage / +// ShopChangePercentage). type ShopConfig struct { - Message string `yaml:"message"` - Items []ShopItem `yaml:"items"` + Message string `yaml:"message"` + BuyPercentage float64 `yaml:"buy_percentage"` // sell price at zero stock, % of value (default 40) + ChangePercentage float64 `yaml:"change_percentage"` // sell price drop per unit of stock, % of value (default 3) + RestockTicks float64 `yaml:"restock_ticks"` // shop-wide default restock interval + BuysAnything *bool `yaml:"buys_anything"` // buy items not in the list (default true) + Items []ShopItem `yaml:"items"` } type ShopItem struct { - ItemID string `yaml:"item_id"` - BuyPrice int `yaml:"buy_price"` - SellPrice int `yaml:"sell_price"` + ItemID string `yaml:"item_id"` + Stock int `yaml:"stock"` // target/max stock; also starting stock + RestockTicks float64 `yaml:"restock_ticks"` // per-item restock interval (overrides shop default) +} + +// Shop pricing defaults, applied when the YAML leaves a field at zero. +const ( + ShopBuyPercentage = 40.0 + ShopChangePercentage = 3.0 + ShopMinSellPercent = 10.0 + ShopDefaultRestock = 100.0 +) + +// EffectiveBuyPercentage returns BuyPercentage or the default. +func (c *ShopConfig) EffectiveBuyPercentage() float64 { + if c.BuyPercentage > 0 { + return c.BuyPercentage + } + return ShopBuyPercentage +} + +// EffectiveChangePercentage returns ChangePercentage or the default. +func (c *ShopConfig) EffectiveChangePercentage() float64 { + if c.ChangePercentage > 0 { + return c.ChangePercentage + } + return ShopChangePercentage +} + +// Buys reports whether the shop will buy items not in its item list. +func (c *ShopConfig) Buys() bool { + return c.BuysAnything == nil || *c.BuysAnything +} + +// SellPrice computes the per-unit price the shop pays for an item of the given +// value when the shop currently holds `stock` of it. +func (c *ShopConfig) SellPrice(value, stock int) int { + if stock < 0 { + stock = 0 + } + pct := c.EffectiveBuyPercentage() - float64(stock)*c.EffectiveChangePercentage() + if pct < ShopMinSellPercent { + pct = ShopMinSellPercent + } + return int(float64(value) * pct / 100.0) +} + +// RestockInterval returns the effective restock interval (in ticks) for an item, +// honoring the per-item override, then the shop default, then the global default. +func (c *ShopConfig) RestockInterval(item *ShopItem) int { + if item != nil && item.RestockTicks > 0 { + return int(item.RestockTicks) + } + if c.RestockTicks > 0 { + return int(c.RestockTicks) + } + return int(ShopDefaultRestock) +} + +// FindItem returns the configured shop item with the given id, or nil. +func (c *ShopConfig) FindItem(itemID string) *ShopItem { + for i := range c.Items { + if c.Items[i].ItemID == itemID { + return &c.Items[i] + } + } + return nil } type Condition struct { Flag string `yaml:"flag"` Value any `yaml:"value"` - Not bool `yaml:"not"` + Not bool `yaml:"not"` PlayerFlag string `yaml:"player_flag"` HasItem string `yaml:"has_item"` MinCredits int `yaml:"min_credits"` @@ -83,16 +153,16 @@ type Condition struct { } type UseConfig struct { - StartMessage string `yaml:"start_message"` - TicksPerCycle float64 `yaml:"ticks_per_cycle"` - Consume map[string]int `yaml:"consume"` - Reward DropEntry `yaml:"reward"` - FailMessage string `yaml:"fail_message"` - Success *SuccessFormula `yaml:"success"` - Skill string `yaml:"skill"` - Level int `yaml:"level"` - XP int `yaml:"xp"` - SuccessMessage string `yaml:"success_message"` - EndMessage string `yaml:"end_message"` - BroadcastMessage string `yaml:"broadcast_message"` + StartMessage string `yaml:"start_message"` + TicksPerCycle float64 `yaml:"ticks_per_cycle"` + Consume map[string]int `yaml:"consume"` + Reward DropEntry `yaml:"reward"` + FailMessage string `yaml:"fail_message"` + Success *SuccessFormula `yaml:"success"` + Skill string `yaml:"skill"` + Level int `yaml:"level"` + XP int `yaml:"xp"` + SuccessMessage string `yaml:"success_message"` + EndMessage string `yaml:"end_message"` + BroadcastMessage string `yaml:"broadcast_message"` } -- cgit v1.2.3