diff options
Diffstat (limited to 'internal/world')
| -rw-r--r-- | internal/world/mob.go | 154 |
1 files changed, 146 insertions, 8 deletions
diff --git a/internal/world/mob.go b/internal/world/mob.go index cc2d2b2..58bf769 100644 --- a/internal/world/mob.go +++ b/internal/world/mob.go @@ -68,10 +68,13 @@ type MobDef struct { CompleteMessage string `yaml:"complete_message"` // shown on completion Talk *behavior.TalkConfig `yaml:"talk,omitempty"` + Shop *behavior.ShopConfig `yaml:"shop,omitempty"` } func (d *MobDef) IsTalkable() bool { return d.Talk != nil } +func (d *MobDef) HasShop() bool { return d.Shop != nil } + func (d *MobDef) IsTask() bool { return d.Kind == "task" } type MobInstance struct { @@ -128,17 +131,26 @@ type MobInstance struct { TalkConfig *behavior.TalkConfig - Owner string - OwnerOnly bool - SpawnedByTrigger bool - DespawnOnLeave bool - DespawnRooms []int - DespawnTicks float64 - DespawnCounter int + // Shop is the (shared, read-only) shop config from the def. Per-instance + // live stock is tracked in ShopStock; ShopRestock holds per-item countdown + // timers. All three are only mutated under MobStore.mu. + Shop *behavior.ShopConfig + ShopStock map[string]int + ShopRestock map[string]int + + Owner string + OwnerOnly bool + SpawnedByTrigger bool + DespawnOnLeave bool + DespawnRooms []int + DespawnTicks float64 + DespawnCounter int } func (m *MobInstance) IsTalkable() bool { return m.TalkConfig != nil } +func (m *MobInstance) HasShop() bool { return m.Shop != nil } + func (m *MobInstance) IsTask() bool { return m.Kind == "task" } func NewMobInstance(def *MobDef, instanceID string, roomID int, wanderRooms []int, wanderInterval float64) *MobInstance { @@ -146,7 +158,7 @@ func NewMobInstance(def *MobDef, instanceID string, roomID int, wanderRooms []in if def.Protected && hp <= 0 { hp = 1 } - return &MobInstance{ + inst := &MobInstance{ InstanceID: instanceID, DefID: def.ID, Name: def.Name, @@ -191,6 +203,20 @@ func NewMobInstance(def *MobDef, instanceID string, roomID int, wanderRooms []in CompleteMessage: def.CompleteMessage, TalkConfig: def.Talk, } + if def.Shop != nil { + inst.Shop = def.Shop + inst.ShopStock = make(map[string]int) + inst.ShopRestock = make(map[string]int) + for i := range def.Shop.Items { + it := &def.Shop.Items[i] + if it.ItemID == "" { + continue + } + inst.ShopStock[it.ItemID] = it.Stock + inst.ShopRestock[it.ItemID] = def.Shop.RestockInterval(it) + } + } + return inst } const ( @@ -330,6 +356,9 @@ func (s *MobStore) Tick() { defer s.mu.Unlock() for _, inst := range s.instances { + if inst.Shop != nil { + s.tickShopLocked(inst) + } if inst.HP <= 0 || inst.HP >= inst.MaxHP { inst.regenerateTick = 0 continue @@ -347,6 +376,115 @@ func (s *MobStore) Tick() { } } +// tickShopLocked advances restock timers for one shop instance. Current stock +// converges toward each item's target (configured stock for listed items, 0 for +// dynamically-acquired items) one unit per restock interval, in either +// direction. Caller must hold s.mu. +func (s *MobStore) tickShopLocked(inst *MobInstance) { + for id, cur := range inst.ShopStock { + target := 0 + var cfgItem *behavior.ShopItem + if cfgItem = inst.Shop.FindItem(id); cfgItem != nil { + target = cfgItem.Stock + } + if cur == target { + if cur == 0 && cfgItem == nil { + delete(inst.ShopStock, id) + delete(inst.ShopRestock, id) + } + continue + } + c := inst.ShopRestock[id] + if c <= 0 { + c = inst.Shop.RestockInterval(cfgItem) + } + c-- + if c <= 0 { + if cur < target { + cur++ + } else { + cur-- + } + inst.ShopStock[id] = cur + c = inst.Shop.RestockInterval(cfgItem) + } + inst.ShopRestock[id] = c + if cur == 0 && cfgItem == nil { + delete(inst.ShopStock, id) + delete(inst.ShopRestock, id) + } + } +} + +// ShopStockSnapshot returns a copy of the instance's current stock map. +func (s *MobStore) ShopStockSnapshot(instanceID string) map[string]int { + s.mu.Lock() + defer s.mu.Unlock() + inst := s.instances[instanceID] + if inst == nil || inst.ShopStock == nil { + return nil + } + out := make(map[string]int, len(inst.ShopStock)) + for k, v := range inst.ShopStock { + out[k] = v + } + return out +} + +// ShopStockOf returns the current stock the instance holds of an item. +func (s *MobStore) ShopStockOf(instanceID, itemID string) int { + s.mu.Lock() + defer s.mu.Unlock() + inst := s.instances[instanceID] + if inst == nil || inst.ShopStock == nil { + return 0 + } + return inst.ShopStock[itemID] +} + +// ShopTake removes up to qty of an item from the shop's stock, returning the +// amount actually taken (capped by availability). Used when a player buys. +func (s *MobStore) ShopTake(instanceID, itemID string, qty int) int { + s.mu.Lock() + defer s.mu.Unlock() + inst := s.instances[instanceID] + if inst == nil || inst.ShopStock == nil { + return 0 + } + have := inst.ShopStock[itemID] + if qty > have { + qty = have + } + if qty <= 0 { + return 0 + } + inst.ShopStock[itemID] = have - qty + return qty +} + +// ShopAdd adds qty of an item to the shop's stock. Used when a player sells. +func (s *MobStore) ShopAdd(instanceID, itemID string, qty int) { + if qty <= 0 { + return + } + s.mu.Lock() + defer s.mu.Unlock() + inst := s.instances[instanceID] + if inst == nil || inst.Shop == nil { + return + } + if inst.ShopStock == nil { + inst.ShopStock = make(map[string]int) + } + if inst.ShopRestock == nil { + inst.ShopRestock = make(map[string]int) + } + if _, ok := inst.ShopStock[itemID]; !ok { + inst.ShopRestock[itemID] = inst.Shop.RestockInterval(inst.Shop.FindItem(itemID)) + } + inst.ShopStock[itemID] += qty +} + func (s *MobStore) RollIdleDescription(inst *MobInstance) { def, err := s.LoadDef(inst.DefID) if err != nil { |
