diff options
| author | historia <[not public]> | 2026-06-19 18:20:26 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-19 18:20:26 -0400 |
| commit | 0ea4eec5dd2122c6704216971eb1249276297867 (patch) | |
| tree | 430fbbef04e837820f1d031c190f52ecd6112e25 /internal/world | |
| parent | 3a58125d14bb307f861b38c6c5b0a63babde7e08 (diff) | |
| download | thehouseoficarus-0ea4eec5dd2122c6704216971eb1249276297867.tar.gz | |
shop fixes, 'toggle' completely removed, movement speed increased
Diffstat (limited to 'internal/world')
| -rw-r--r-- | internal/world/ground.go | 189 | ||||
| -rw-r--r-- | internal/world/mob.go | 41 | ||||
| -rw-r--r-- | internal/world/objects.go | 332 | ||||
| -rw-r--r-- | internal/world/world.go | 506 |
4 files changed, 536 insertions, 532 deletions
diff --git a/internal/world/ground.go b/internal/world/ground.go new file mode 100644 index 0000000..6c3ecf3 --- /dev/null +++ b/internal/world/ground.go @@ -0,0 +1,189 @@ +package world + +import ( + "strings" +) + +const DropDespawnTicks = 1000 +const ReserveTicks = 100 + +type GroundItemInfo struct { + ItemID string + Quantity int + ReservedFor string + ReserveTimer int + DespawnTimer int + IsSpawn bool +} + +type groundEntry struct { + itemID string + quantity int + isSpawn bool + respawnTimer int // >0 = counting down to respawn + respawnQty int + respawnDelay int + despawnTimer int // >0 = counting down to despawn (dropped items) + reservedFor string + reserveTimer int // >0 = counting down reservation +} + +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, + DespawnTimer: e.despawnTimer, + IsSpawn: e.isSpawn, + } + 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() + out := make(map[string]int) + for _, e := range w.groundItems[roomID] { + if e.quantity > 0 { + out[e.itemID] += e.quantity + } + } + 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() + e := &groundEntry{ + itemID: itemID, + quantity: qty, + despawnTimer: DropDespawnTicks, + } + 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() + + 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 + } + 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 +} + +func (w *World) tickGroundItemsLocked() { + for _, entries := range w.groundItems { + for _, e := range entries { + if e.respawnTimer > 0 { + e.respawnTimer-- + if e.respawnTimer <= 0 { + e.quantity = e.respawnQty + w.mergeNearbyItemsLocked(entries, e) + } + } + if e.despawnTimer > 0 { + e.despawnTimer-- + if e.despawnTimer <= 0 { + e.quantity = 0 + } + } + if e.reserveTimer > 0 { + e.reserveTimer-- + if e.reserveTimer <= 0 { + e.reservedFor = "" + } + } + } + } +} + +func (w *World) mergeNearbyItemsLocked(entries []*groundEntry, target *groundEntry) { + for _, other := range entries { + if other != target && other.quantity > 0 && strings.EqualFold(other.itemID, target.itemID) && + (other.reserveTimer <= 0 || other.reservedFor == "") && + other.despawnTimer <= 0 { + target.quantity += other.quantity + other.quantity = 0 + } + } +} diff --git a/internal/world/mob.go b/internal/world/mob.go index ce9eddd..f7988ce 100644 --- a/internal/world/mob.go +++ b/internal/world/mob.go @@ -12,7 +12,7 @@ import ( "gopkg.in/yaml.v3" ) -type DropTable struct { +type MobDropTable struct { Remains string `yaml:"remains"` Loot []action.DropEntry `yaml:"loot"` } @@ -21,7 +21,6 @@ type MobDef struct { ID string `yaml:"id"` Name string `yaml:"name"` Description string `yaml:"description"` - BehaviorID string `yaml:"behavior"` IdleDescriptions []string `yaml:"idle_descriptions"` CombatDescriptions []string `yaml:"combat_descriptions"` Attack int `yaml:"attack"` @@ -35,7 +34,7 @@ type MobDef struct { Protected bool `yaml:"protected"` Unique bool `yaml:"unique"` RespawnTicks float64 `yaml:"respawn_ticks"` - Drops DropTable `yaml:"drops"` + Drops MobDropTable `yaml:"drops"` AttackBonus int `yaml:"attack_bonus"` StrengthBonus int `yaml:"strength_bonus"` @@ -56,13 +55,16 @@ type MobDef struct { AssassinLevel int `yaml:"assassin_level"` FinishingBlow string `yaml:"finishing_blow"` DamageWithout string `yaml:"damage_without"` + + Talk *action.TalkConfig `yaml:"talk,omitempty"` } +func (d *MobDef) IsTalkable() bool { return d.Talk != nil } + type MobInstance struct { InstanceID string DefID string Name string - BehaviorID string HP int MaxHP int Attack int @@ -77,7 +79,7 @@ type MobInstance struct { RespawnTicks float64 RoomID int HomeRoomID int - Drops DropTable + Drops MobDropTable IdleDescription string WanderRooms []int WanderInterval float64 @@ -103,41 +105,24 @@ type MobInstance struct { AssassinLevel int FinishingBlow string DamageWithout string + + TalkConfig *action.TalkConfig } +func (m *MobInstance) IsTalkable() bool { return m.TalkConfig != nil } + const ( MatchNone = 0 MatchPrefix = 1 MatchExact = 2 ) -func WordPrefixMatch(input, name string) bool { - inputWords := strings.Fields(strings.ToLower(input)) - if len(inputWords) == 0 { - return false - } - nameWords := strings.Fields(strings.ToLower(name)) - for _, iw := range inputWords { - found := false - for _, nw := range nameWords { - if strings.HasPrefix(nw, iw) || strings.HasPrefix(iw, nw) { - found = true - break - } - } - if !found { - return false - } - } - return true -} - func (m *MobInstance) MatchQuality(input string) int { lower := strings.ToLower(input) if strings.ToLower(m.Name) == lower { return MatchExact } - if WordPrefixMatch(input, m.Name) { + if action.WordPrefixMatch(input, m.Name) { return MatchPrefix } return MatchNone @@ -281,7 +266,6 @@ func (s *MobStore) SeedMobs(roomID int, entries []RoomMob) { InstanceID: instID, DefID: defID, Name: dw.def.Name, - BehaviorID: dw.def.BehaviorID, HP: dw.def.HP, MaxHP: dw.def.HP, Attack: dw.def.Attack, @@ -315,6 +299,7 @@ func (s *MobStore) SeedMobs(roomID int, entries []RoomMob) { AssassinLevel: dw.def.AssassinLevel, FinishingBlow: dw.def.FinishingBlow, DamageWithout: dw.def.DamageWithout, + TalkConfig: dw.def.Talk, } inst.IdleDescription = pickIdleDescription(dw.def.IdleDescriptions) s.instances[instID] = inst diff --git a/internal/world/objects.go b/internal/world/objects.go new file mode 100644 index 0000000..568ea40 --- /dev/null +++ b/internal/world/objects.go @@ -0,0 +1,332 @@ +package world + +import ( + "fmt" + "math/rand" + "sort" + "strings" + + "thehouseoficarus/internal/action" +) + +type ObjState struct { + Depleted bool + DepleteTimer float64 + DefID string + Name string + Index int + RoomID int + JustRespawned bool + WanderRooms []int + WanderInterval float64 + WanderCounter int + SharedMax float64 + SharedTimer int + Quality float64 +} + +type ObjMove struct { + DefID string + Name string + FromRoom int + ToRoom int +} + +func (w *World) ObjStateKey(roomID int, defID string, index int) string { + return fmt.Sprintf("%d:%s:%d", roomID, defID, index) +} + +func (w *World) EnsureObjectStates(roomID int, defIDs []string) { + w.mu.Lock() + defer w.mu.Unlock() + w.ensureObjectStatesLocked(roomID, defIDs) +} + +func (w *World) ensureObjectStatesLocked(roomID int, defIDs []string) { + if w.objStates == nil { + w.objStates = make(map[string]*ObjState) + } + counts := make(map[string]int) + for _, defID := range defIDs { + idx := counts[defID] + counts[defID]++ + key := w.ObjStateKey(roomID, defID, idx) + if _, exists := w.objStates[key]; !exists { + w.objStates[key] = &ObjState{ + DefID: defID, + Name: defID, + Index: idx, + RoomID: roomID, + } + } + } +} + +func (w *World) AddObjInstance(roomID int, defID string, quality float64) { + w.mu.Lock() + defer w.mu.Unlock() + if w.objStates == nil { + w.objStates = make(map[string]*ObjState) + } + idx := 0 + for { + key := w.ObjStateKey(roomID, defID, idx) + if _, exists := w.objStates[key]; !exists { + w.objStates[key] = &ObjState{ + DefID: defID, + Name: defID, + Index: idx, + RoomID: roomID, + Quality: quality, + } + return + } + idx++ + } +} + +func (w *World) RemoveObjInstance(roomID int, defID string) { + w.mu.Lock() + defer w.mu.Unlock() + idx := 0 + for { + key := w.ObjStateKey(roomID, defID, idx) + if _, exists := w.objStates[key]; !exists { + return + } + delete(w.objStates, key) + idx++ + } +} + +func (w *World) AllFireStates() []*ObjState { + w.mu.Lock() + defer w.mu.Unlock() + var out []*ObjState + for _, st := range w.objStates { + if st.DefID == "fire" && st.Quality > 0 { + out = append(out, st) + } + } + return out +} + +func (w *World) GetObjState(roomID int, defID string, index int) *ObjState { + w.mu.Lock() + defer w.mu.Unlock() + if w.objStates == nil { + return nil + } + return w.objStates[w.ObjStateKey(roomID, defID, index)] +} + +func (w *World) GetObjStateByKey(key string) *ObjState { + w.mu.Lock() + defer w.mu.Unlock() + if w.objStates == nil { + return nil + } + return w.objStates[key] +} + +func (w *World) AllObjInstances(roomID int) []ObjState { + w.mu.Lock() + defer w.mu.Unlock() + var out []ObjState + for _, st := range w.objStates { + if st.RoomID == roomID { + out = append(out, *st) + } + } + sort.Slice(out, func(i, j int) bool { + if out[i].DefID != out[j].DefID { + return out[i].DefID < out[j].DefID + } + return out[i].Index < out[j].Index + }) + return out +} + +func (w *World) FindObjInstances(roomID int, name string) []ObjState { + w.mu.Lock() + defer w.mu.Unlock() + var out []ObjState + lower := strings.ToLower(name) + for _, st := range w.objStates { + if st.RoomID != roomID { + continue + } + if !wordMatchesObj(lower, st.DefID, st.Name) { + continue + } + out = append(out, ObjState{ + DefID: st.DefID, + Name: st.Name, + Index: st.Index, + RoomID: st.RoomID, + Depleted: st.Depleted, + DepleteTimer: st.DepleteTimer, + SharedMax: st.SharedMax, + SharedTimer: st.SharedTimer, + Quality: st.Quality, + }) + } + sort.Slice(out, func(i, j int) bool { + if out[i].DefID != out[j].DefID { + return out[i].DefID < out[j].DefID + } + return out[i].Index < out[j].Index + }) + return out +} + +func wordMatchesObj(lower, defID, objName string) bool { + if action.WordPrefixMatch(lower, objName) { + return true + } + inputWords := strings.Fields(strings.ToLower(lower)) + + if strings.HasPrefix(strings.ToLower(defID), lower) || strings.HasPrefix(lower, strings.ToLower(defID)) { + return true + } + for _, part := range strings.Split(defID, "_") { + for _, iw := range inputWords { + pl := strings.ToLower(part) + if strings.HasPrefix(pl, iw) || strings.HasPrefix(iw, pl) { + return true + } + } + } + return false +} + +func (w *World) SetObjName(roomID int, defID string, name string) { + w.mu.Lock() + defer w.mu.Unlock() + for _, st := range w.objStates { + if st.RoomID == roomID && st.DefID == defID { + st.Name = name + } + } +} + +func (w *World) SetObjWander(roomID int, defID string, rooms []int, interval float64) { + w.mu.Lock() + defer w.mu.Unlock() + for _, st := range w.objStates { + if st.RoomID == roomID && st.DefID == defID { + st.WanderRooms = rooms + st.WanderInterval = interval + } + } +} + +func (w *World) SetObjDepleteTimer(roomID int, defID string, max float64) { + w.mu.Lock() + defer w.mu.Unlock() + for _, st := range w.objStates { + if st.RoomID == roomID && st.DefID == defID && st.SharedMax == 0 { + st.SharedMax = max + st.SharedTimer = int(max) + } + } +} + +func (w *World) AllSharedObjStates() []*ObjState { + w.mu.Lock() + defer w.mu.Unlock() + var out []*ObjState + for _, st := range w.objStates { + if st.SharedMax > 0 { + out = append(out, st) + } + } + return out +} + +func (w *World) SetObjDepleted(roomID int, defID string, index int, delay float64) { + w.mu.Lock() + defer w.mu.Unlock() + key := w.ObjStateKey(roomID, defID, index) + if st, ok := w.objStates[key]; ok { + st.Depleted = true + st.DepleteTimer = delay + } +} + +func (w *World) FlushObjRespawns() []ObjState { + w.mu.Lock() + defer w.mu.Unlock() + var out []ObjState + for _, st := range w.objStates { + if st.JustRespawned { + out = append(out, *st) + st.JustRespawned = false + } + } + return out +} + +func (w *World) TickObjWander() { + w.mu.Lock() + defer w.mu.Unlock() + var moves []struct { + st *ObjState + toRoom int + } + for _, st := range w.objStates { + if len(st.WanderRooms) == 0 || st.WanderInterval <= 0 { + continue + } + st.WanderCounter++ + if float64(st.WanderCounter) < st.WanderInterval { + continue + } + st.WanderCounter = 0 + toRoom := st.WanderRooms[rand.Intn(len(st.WanderRooms))] + if toRoom == st.RoomID { + continue + } + moves = append(moves, struct { + st *ObjState + toRoom int + }{st, toRoom}) + } + for _, m := range moves { + oldKey := w.ObjStateKey(m.st.RoomID, m.st.DefID, m.st.Index) + fromRoom := m.st.RoomID + m.st.RoomID = m.toRoom + newKey := w.ObjStateKey(m.st.RoomID, m.st.DefID, m.st.Index) + delete(w.objStates, oldKey) + w.objStates[newKey] = m.st + w.objMoves = append(w.objMoves, ObjMove{ + DefID: m.st.DefID, + Name: m.st.Name, + FromRoom: fromRoom, + ToRoom: m.toRoom, + }) + } +} + +func (w *World) FlushObjMoves() []ObjMove { + w.mu.Lock() + defer w.mu.Unlock() + out := w.objMoves + w.objMoves = nil + return out +} + +func (w *World) tickObjStatesLocked() { + for _, st := range w.objStates { + if st.Depleted && st.DepleteTimer > 0 { + st.DepleteTimer-- + if st.DepleteTimer <= 0 { + st.Depleted = false + st.JustRespawned = true + if st.SharedMax > 0 { + st.SharedTimer = int(st.SharedMax) + } + } + } + } +} diff --git a/internal/world/world.go b/internal/world/world.go index f918293..3094c54 100644 --- a/internal/world/world.go +++ b/internal/world/world.go @@ -2,40 +2,14 @@ package world import ( "fmt" - "math/rand" "os" "path/filepath" - "sort" "strings" "sync" "gopkg.in/yaml.v3" ) -const DropDespawnTicks = 1000 -const ReserveTicks = 100 - -type GroundItemInfo struct { - ItemID string - Quantity int - ReservedFor string - ReserveTimer int - DespawnTimer int - IsSpawn bool -} - -type groundEntry struct { - itemID string - quantity int - isSpawn bool - respawnTimer int // >0 = counting down to respawn - respawnQty int - respawnDelay int - despawnTimer int // >0 = counting down to despawn (dropped items) - reservedFor string - reserveTimer int // >0 = counting down reservation -} - type World struct { dataDir string mu sync.Mutex @@ -45,255 +19,6 @@ type World struct { objMoves []ObjMove } -type ObjMove struct { - DefID string - Name string - FromRoom int - ToRoom int -} - -type ObjState struct { - Depleted bool - DepleteTimer float64 - DefID string - Name string - Index int - RoomID int - JustRespawned bool - WanderRooms []int - WanderInterval float64 - WanderCounter int - SharedMax float64 - SharedTimer int - Quality float64 -} - -func (w *World) ObjStateKey(roomID int, defID string, index int) string { - return fmt.Sprintf("%d:%s:%d", roomID, defID, index) -} - -func (w *World) objStateKey(roomID int, defID string, index int) string { - return w.ObjStateKey(roomID, defID, index) -} - -func (w *World) EnsureObjectStates(roomID int, defIDs []string) { - w.mu.Lock() - defer w.mu.Unlock() - w.ensureObjectStatesLocked(roomID, defIDs) -} - -func (w *World) ensureObjectStatesLocked(roomID int, defIDs []string) { - if w.objStates == nil { - w.objStates = make(map[string]*ObjState) - } - counts := make(map[string]int) - for _, defID := range defIDs { - idx := counts[defID] - counts[defID]++ - key := w.objStateKey(roomID, defID, idx) - if _, exists := w.objStates[key]; !exists { - w.objStates[key] = &ObjState{ - DefID: defID, - Name: defID, - Index: idx, - RoomID: roomID, - } - } - } -} - -func (w *World) AddObjInstance(roomID int, defID string, quality float64) { - w.mu.Lock() - defer w.mu.Unlock() - if w.objStates == nil { - w.objStates = make(map[string]*ObjState) - } - idx := 0 - for { - key := w.objStateKey(roomID, defID, idx) - if _, exists := w.objStates[key]; !exists { - w.objStates[key] = &ObjState{ - DefID: defID, - Name: defID, - Index: idx, - RoomID: roomID, - Quality: quality, - } - return - } - idx++ - } -} - -func (w *World) RemoveObjInstance(roomID int, defID string) { - w.mu.Lock() - defer w.mu.Unlock() - idx := 0 - for { - key := w.objStateKey(roomID, defID, idx) - if _, exists := w.objStates[key]; !exists { - return - } - delete(w.objStates, key) - idx++ - } -} - -func (w *World) AllFireStates() []*ObjState { - w.mu.Lock() - defer w.mu.Unlock() - var out []*ObjState - for _, st := range w.objStates { - if st.DefID == "fire" && st.Quality > 0 { - out = append(out, st) - } - } - return out -} - -func (w *World) GetObjState(roomID int, defID string, index int) *ObjState { - w.mu.Lock() - defer w.mu.Unlock() - if w.objStates == nil { - return nil - } - return w.objStates[w.objStateKey(roomID, defID, index)] -} - -func (w *World) GetObjStateByKey(key string) *ObjState { - w.mu.Lock() - defer w.mu.Unlock() - if w.objStates == nil { - return nil - } - return w.objStates[key] -} - -func (w *World) AllObjInstances(roomID int) []ObjState { - w.mu.Lock() - defer w.mu.Unlock() - var out []ObjState - for _, st := range w.objStates { - if st.RoomID == roomID { - out = append(out, *st) - } - } - sort.Slice(out, func(i, j int) bool { - if out[i].DefID != out[j].DefID { - return out[i].DefID < out[j].DefID - } - return out[i].Index < out[j].Index - }) - return out -} - -func (w *World) FindObjInstances(roomID int, name string) []ObjState { - w.mu.Lock() - defer w.mu.Unlock() - var out []ObjState - lower := strings.ToLower(name) - for _, st := range w.objStates { - if st.RoomID != roomID { - continue - } - if !wordMatchesObj(lower, st.DefID, st.Name) { - continue - } - out = append(out, ObjState{ - DefID: st.DefID, - Name: st.Name, - Index: st.Index, - RoomID: st.RoomID, - Depleted: st.Depleted, - DepleteTimer: st.DepleteTimer, - SharedMax: st.SharedMax, - SharedTimer: st.SharedTimer, - Quality: st.Quality, - }) - } - sort.Slice(out, func(i, j int) bool { - if out[i].DefID != out[j].DefID { - return out[i].DefID < out[j].DefID - } - return out[i].Index < out[j].Index - }) - return out -} - -func wordMatchesObj(lower, defID, objName string) bool { - if WordPrefixMatch(lower, objName) { - return true - } - inputWords := strings.Fields(strings.ToLower(lower)) - - if strings.HasPrefix(strings.ToLower(defID), lower) || strings.HasPrefix(lower, strings.ToLower(defID)) { - return true - } - for _, part := range strings.Split(defID, "_") { - for _, iw := range inputWords { - pl := strings.ToLower(part) - if strings.HasPrefix(pl, iw) || strings.HasPrefix(iw, pl) { - return true - } - } - } - return false -} - -func (w *World) SetObjName(roomID int, defID string, name string) { - w.mu.Lock() - defer w.mu.Unlock() - for _, st := range w.objStates { - if st.RoomID == roomID && st.DefID == defID { - st.Name = name - } - } -} - -func (w *World) SetObjWander(roomID int, defID string, rooms []int, interval float64) { - w.mu.Lock() - defer w.mu.Unlock() - for _, st := range w.objStates { - if st.RoomID == roomID && st.DefID == defID { - st.WanderRooms = rooms - st.WanderInterval = interval - } - } -} - -func (w *World) SetObjDepleteTimer(roomID int, defID string, max float64) { - w.mu.Lock() - defer w.mu.Unlock() - for _, st := range w.objStates { - if st.RoomID == roomID && st.DefID == defID && st.SharedMax == 0 { - st.SharedMax = max - st.SharedTimer = int(max) - } - } -} - -func (w *World) AllSharedObjStates() []*ObjState { - w.mu.Lock() - defer w.mu.Unlock() - var out []*ObjState - for _, st := range w.objStates { - if st.SharedMax > 0 { - out = append(out, st) - } - } - return out -} - -func (w *World) SetObjDepleted(roomID int, defID string, index int, delay float64) { - w.mu.Lock() - defer w.mu.Unlock() - key := w.objStateKey(roomID, defID, index) - if st, ok := w.objStates[key]; ok { - st.Depleted = true - st.DepleteTimer = delay - } -} - func New(dataDir string) *World { return &World{ dataDir: dataDir, @@ -342,129 +67,6 @@ 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, - DespawnTimer: e.despawnTimer, - IsSpawn: e.isSpawn, - } - 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() - out := make(map[string]int) - for _, e := range w.groundItems[roomID] { - if e.quantity > 0 { - out[e.itemID] += e.quantity - } - } - 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() - e := &groundEntry{ - itemID: itemID, - quantity: qty, - despawnTimer: DropDespawnTicks, - } - 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() - - 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 - } - 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 -} - func (w *World) SeedGroundItems(roomID int) { w.mu.Lock() if w.seeded[roomID] { @@ -512,110 +114,6 @@ func (w *World) SeedGroundItems(roomID int) { func (w *World) Tick() { w.mu.Lock() defer w.mu.Unlock() - - for _, entries := range w.groundItems { - for _, e := range entries { - if e.respawnTimer > 0 { - e.respawnTimer-- - if e.respawnTimer <= 0 { - e.quantity = e.respawnQty - for _, other := range entries { - if other != e && other.quantity > 0 && strings.EqualFold(other.itemID, e.itemID) && - (other.reserveTimer <= 0 || other.reservedFor == "") && - other.despawnTimer <= 0 { - e.quantity += other.quantity - other.quantity = 0 - } - } - } - } - if e.despawnTimer > 0 { - e.despawnTimer-- - if e.despawnTimer <= 0 { - e.quantity = 0 - } - } - if e.reserveTimer > 0 { - e.reserveTimer-- - if e.reserveTimer <= 0 { - e.reservedFor = "" - } - } - } - } - - for _, st := range w.objStates { - if st.Depleted && st.DepleteTimer > 0 { - st.DepleteTimer-- - if st.DepleteTimer <= 0 { - st.Depleted = false - st.JustRespawned = true - if st.SharedMax > 0 { - st.SharedTimer = int(st.SharedMax) - } - } - } - } -} - -func (w *World) FlushObjRespawns() []ObjState { - w.mu.Lock() - defer w.mu.Unlock() - var out []ObjState - for _, st := range w.objStates { - if st.JustRespawned { - out = append(out, *st) - st.JustRespawned = false - } - } - return out -} - -func (w *World) TickObjWander() { - w.mu.Lock() - defer w.mu.Unlock() - var moves []struct { - st *ObjState - toRoom int - } - for _, st := range w.objStates { - if len(st.WanderRooms) == 0 || st.WanderInterval <= 0 { - continue - } - st.WanderCounter++ - if float64(st.WanderCounter) < st.WanderInterval { - continue - } - st.WanderCounter = 0 - toRoom := st.WanderRooms[rand.Intn(len(st.WanderRooms))] - if toRoom == st.RoomID { - continue - } - moves = append(moves, struct { - st *ObjState - toRoom int - }{st, toRoom}) - } - for _, m := range moves { - oldKey := w.objStateKey(m.st.RoomID, m.st.DefID, m.st.Index) - fromRoom := m.st.RoomID - m.st.RoomID = m.toRoom - newKey := w.objStateKey(m.st.RoomID, m.st.DefID, m.st.Index) - delete(w.objStates, oldKey) - w.objStates[newKey] = m.st - w.objMoves = append(w.objMoves, ObjMove{ - DefID: m.st.DefID, - Name: m.st.Name, - FromRoom: fromRoom, - ToRoom: m.toRoom, - }) - } -} - -func (w *World) FlushObjMoves() []ObjMove { - w.mu.Lock() - defer w.mu.Unlock() - out := w.objMoves - w.objMoves = nil - return out + w.tickGroundItemsLocked() + w.tickObjStatesLocked() } |
