From 9edc568e16741d443b67fbd23b0d91790085ced9 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Tue, 9 Jun 2026 17:15:13 -0400 Subject: combat, mobs, parsing, toggles, hard disconnect handling --- internal/world/mob.go | 185 ++++++++++++++++++++++++++++++++++++++---------- internal/world/world.go | 82 +++++++++++++++++++++ 2 files changed, 230 insertions(+), 37 deletions(-) (limited to 'internal/world') diff --git a/internal/world/mob.go b/internal/world/mob.go index 601cdc1..ebf97cf 100644 --- a/internal/world/mob.go +++ b/internal/world/mob.go @@ -2,8 +2,10 @@ package world import ( "fmt" + "math/rand" "os" "path/filepath" + "strings" "sync" "gopkg.in/yaml.v3" @@ -21,32 +23,89 @@ type DropTable struct { } type MobDef struct { - ID string `yaml:"id"` - Name string `yaml:"name"` - Attack int `yaml:"attack"` - Strength int `yaml:"strength"` - Defense int `yaml:"defense"` - HP int `yaml:"hp"` - Speed int `yaml:"speed"` - Aggressive bool `yaml:"aggressive"` - RespawnTicks int `yaml:"respawn_ticks"` - Drops DropTable `yaml:"drops"` + ID string `yaml:"id"` + Name string `yaml:"name"` + Description string `yaml:"description"` + IdleDescriptions []string `yaml:"idle_descriptions"` + CombatDescriptions []string `yaml:"combat_descriptions"` + Attack int `yaml:"attack"` + Strength int `yaml:"strength"` + Defense int `yaml:"defense"` + HP int `yaml:"hp"` + Speed int `yaml:"speed"` + Aggressive bool `yaml:"aggressive"` + Protected bool `yaml:"protected"` + Unique bool `yaml:"unique"` + RespawnTicks int `yaml:"respawn_ticks"` + Wander int `yaml:"wander"` + WanderTick int `yaml:"wander_tick"` + WanderChance float64 `yaml:"wander_chance"` + Drops DropTable `yaml:"drops"` } type MobInstance struct { - InstanceID string - DefID string - Name string - HP int - MaxHP int - Attack int - Strength int - Defense int - Speed int - Aggressive bool - RespawnTicks int - RoomID int - Drops DropTable + InstanceID string + DefID string + Name string + HP int + MaxHP int + Attack int + Strength int + Defense int + Speed int + Aggressive bool + Protected bool + Unique bool + RespawnTicks int + RoomID int + HomeRoomID int + Drops DropTable + IdleDescription string + Wander int + WanderTick int + WanderChance float64 + WanderTickCounter int + regenerateTick int +} + +const ( + MatchNone = 0 + MatchPrefix = 1 + MatchExact = 2 +) + +func WordPrefixMatch(input, name string) bool { + lower := strings.ToLower(input) + for _, word := range strings.Fields(name) { + if strings.HasPrefix(strings.ToLower(word), lower) { + return true + } + } + return false +} + +func (m *MobInstance) MatchQuality(input string) int { + lower := strings.ToLower(input) + if strings.ToLower(m.Name) == lower { + return MatchExact + } + if WordPrefixMatch(input, m.Name) { + return MatchPrefix + } + return MatchNone +} + +func pickIdleDescription(descriptions []string) string { + if len(descriptions) == 0 { + return "" + } + return descriptions[rand.Intn(len(descriptions))] +} + +func (m *MobInstance) StartRegen() { + if m.regenerateTick == 0 { + m.regenerateTick = 100 + } } type MobStore struct { @@ -126,6 +185,16 @@ func (s *MobStore) RemoveInstance(id string) { delete(s.instances, id) } +func (s *MobStore) AllInstances() []*MobInstance { + s.mu.Lock() + defer s.mu.Unlock() + var out []*MobInstance + for _, inst := range s.instances { + out = append(out, inst) + } + return out +} + func (s *MobStore) MobsInRoom(roomID int) []*MobInstance { s.mu.Lock() defer s.mu.Unlock() @@ -138,6 +207,40 @@ func (s *MobStore) MobsInRoom(roomID int) []*MobInstance { return out } +func (s *MobStore) Tick() { + s.mu.Lock() + defer s.mu.Unlock() + + for _, inst := range s.instances { + if inst.HP <= 0 || inst.HP >= inst.MaxHP { + inst.regenerateTick = 0 + continue + } + inst.regenerateTick-- + if inst.regenerateTick <= 0 { + inst.HP++ + if inst.HP >= inst.MaxHP { + inst.HP = inst.MaxHP + inst.regenerateTick = 0 + } else { + inst.regenerateTick = 100 + } + } + } +} + +func (s *MobStore) RollIdleDescription(inst *MobInstance) { + def, err := s.LoadDef(inst.DefID) + if err != nil { + return + } + inst.IdleDescription = pickIdleDescription(def.IdleDescriptions) + inst.Wander = def.Wander + inst.WanderTick = def.WanderTick + inst.WanderChance = def.WanderChance + inst.WanderTickCounter = 0 +} + func (s *MobStore) SeedMobs(roomID int, mobIDs []string) { type defWrapper struct { def *MobDef @@ -162,20 +265,28 @@ func (s *MobStore) SeedMobs(roomID int, mobIDs []string) { _ = inst continue } - s.instances[instID] = &MobInstance{ - InstanceID: instID, - DefID: defID, - Name: dw.def.Name, - HP: dw.def.HP, - MaxHP: dw.def.HP, - Attack: dw.def.Attack, - Strength: dw.def.Strength, - Defense: dw.def.Defense, - Speed: dw.def.Speed, - Aggressive: dw.def.Aggressive, - RespawnTicks: dw.def.RespawnTicks, - RoomID: roomID, - Drops: dw.def.Drops, + inst := &MobInstance{ + InstanceID: instID, + DefID: defID, + Name: dw.def.Name, + HP: dw.def.HP, + MaxHP: dw.def.HP, + Attack: dw.def.Attack, + Strength: dw.def.Strength, + Defense: dw.def.Defense, + Speed: dw.def.Speed, + Aggressive: dw.def.Aggressive, + Protected: dw.def.Protected, + Unique: dw.def.Unique, + RespawnTicks: dw.def.RespawnTicks, + Wander: dw.def.Wander, + WanderTick: dw.def.WanderTick, + WanderChance: dw.def.WanderChance, + RoomID: roomID, + HomeRoomID: roomID, + Drops: dw.def.Drops, } + inst.IdleDescription = pickIdleDescription(dw.def.IdleDescriptions) + s.instances[instID] = inst } } diff --git a/internal/world/world.go b/internal/world/world.go index 265a2f9..1429081 100644 --- a/internal/world/world.go +++ b/internal/world/world.go @@ -11,6 +11,14 @@ import ( ) const DropDespawnTicks = 1000 +const ReserveTicks = 100 + +type GroundItemInfo struct { + ItemID string + Quantity int + ReservedFor string + ReserveTimer int +} type groundEntry struct { itemID string @@ -20,6 +28,8 @@ type groundEntry struct { respawnQty int respawnDelay int despawnTimer int // >0 = counting down to despawn (dropped items) + reservedFor string + reserveTimer int // >0 = counting down reservation } type World struct { @@ -75,6 +85,27 @@ func (w *World) ResolveExit(input string) ExitDir { return "" } +func (w *World) GroundItemsDetailed(roomID int) []GroundItemInfo { + w.mu.Lock() + defer w.mu.Unlock() + var out []GroundItemInfo + for _, e := range w.groundItems[roomID] { + if e.quantity <= 0 { + continue + } + info := GroundItemInfo{ + ItemID: e.itemID, + Quantity: e.quantity, + } + if e.reserveTimer > 0 && e.reservedFor != "" { + info.ReservedFor = e.reservedFor + info.ReserveTimer = e.reserveTimer + } + out = append(out, info) + } + return out +} + func (w *World) GroundItems(roomID int) map[string]int { w.mu.Lock() defer w.mu.Unlock() @@ -87,6 +118,19 @@ func (w *World) GroundItems(roomID int) map[string]int { return out } +func (w *World) AddReservedGroundItem(roomID int, itemID string, qty int, owner string) { + w.mu.Lock() + defer w.mu.Unlock() + e := &groundEntry{ + itemID: itemID, + quantity: qty, + despawnTimer: DropDespawnTicks, + reservedFor: owner, + reserveTimer: ReserveTicks, + } + w.groundItems[roomID] = append(w.groundItems[roomID], e) +} + func (w *World) AddGroundItem(roomID int, itemID string, qty int) { w.mu.Lock() defer w.mu.Unlock() @@ -98,6 +142,38 @@ func (w *World) AddGroundItem(roomID int, itemID string, qty int) { w.groundItems[roomID] = append(w.groundItems[roomID], e) } +func (w *World) RemoveReservedGroundItem(roomID int, itemID string, qty int, owner string) (int, bool) { + w.mu.Lock() + defer w.mu.Unlock() + removed := 0 + remaining := qty + for _, e := range w.groundItems[roomID] { + if remaining <= 0 { + break + } + if !strings.EqualFold(e.itemID, itemID) { + continue + } + if e.quantity <= 0 { + continue + } + if e.reserveTimer > 0 && e.reservedFor != "" && e.reservedFor != owner { + return removed, false + } + take := e.quantity + if take > remaining { + take = remaining + } + e.quantity -= take + removed += take + remaining -= take + if e.isSpawn && e.quantity <= 0 && e.respawnDelay > 0 { + e.respawnTimer = e.respawnDelay + } + } + return removed, true +} + func (w *World) RemoveGroundItem(roomID int, itemID string, qty int) int { w.mu.Lock() defer w.mu.Unlock() @@ -177,6 +253,12 @@ func (w *World) Tick() { e.quantity = 0 } } + if e.reserveTimer > 0 { + e.reserveTimer-- + if e.reserveTimer <= 0 { + e.reservedFor = "" + } + } } } } -- cgit v1.2.3