aboutsummaryrefslogtreecommitdiff
path: root/internal/behavior
diff options
context:
space:
mode:
Diffstat (limited to 'internal/behavior')
-rw-r--r--internal/behavior/behavior.go128
-rw-r--r--internal/behavior/types.go24
2 files changed, 99 insertions, 53 deletions
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"`
}
diff --git a/internal/behavior/types.go b/internal/behavior/types.go
index c59a95e..29e2631 100644
--- a/internal/behavior/types.go
+++ b/internal/behavior/types.go
@@ -1,10 +1,7 @@
package behavior
import (
- "math/rand"
"strings"
-
- "gopkg.in/yaml.v3"
)
func WordPrefixMatch(input, name string) bool {
@@ -224,24 +221,3 @@ type DropEntry struct {
type DropTableDef struct {
Drops []DropEntry `yaml:"drops"`
}
-
-type MessageList []string
-
-func (ml *MessageList) UnmarshalYAML(value *yaml.Node) error {
- if value.Kind == yaml.ScalarNode {
- var s string
- if err := value.Decode(&s); err != nil {
- return err
- }
- *ml = MessageList{s}
- return nil
- }
- return value.Decode((*[]string)(ml))
-}
-
-func (ml MessageList) Pick() string {
- if len(ml) == 0 {
- return ""
- }
- return ml[rand.Intn(len(ml))]
-}