aboutsummaryrefslogtreecommitdiff
path: root/internal/world
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-19 18:20:26 -0400
committerhistoria <[not public]>2026-06-19 18:20:26 -0400
commit0ea4eec5dd2122c6704216971eb1249276297867 (patch)
tree430fbbef04e837820f1d031c190f52ecd6112e25 /internal/world
parent3a58125d14bb307f861b38c6c5b0a63babde7e08 (diff)
downloadthehouseoficarus-0ea4eec5dd2122c6704216971eb1249276297867.tar.gz
shop fixes, 'toggle' completely removed, movement speed increased
Diffstat (limited to 'internal/world')
-rw-r--r--internal/world/ground.go189
-rw-r--r--internal/world/mob.go41
-rw-r--r--internal/world/objects.go332
-rw-r--r--internal/world/world.go506
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()
}