aboutsummaryrefslogtreecommitdiff
path: root/internal/world
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-11 04:46:27 -0400
committerhistoria <[not public]>2026-06-11 08:58:37 +0000
commit1b9e2da3b3c438d8dc53d3489725dd5ba0022777 (patch)
tree62264f24420bf4cfaa734942c65cc8dd45ba3d3b /internal/world
parent06c02a697e5daf8832a462de5970dd4c0e13a02c (diff)
downloadthehouseoficarus-1b9e2da3b3c438d8dc53d3489725dd5ba0022777.tar.gz
feat: web client, get/drop updates and fixes
Diffstat (limited to 'internal/world')
-rw-r--r--internal/world/mob.go46
-rw-r--r--internal/world/room.go21
-rw-r--r--internal/world/world.go107
3 files changed, 132 insertions, 42 deletions
diff --git a/internal/world/mob.go b/internal/world/mob.go
index 9dfec59..59c8a20 100644
--- a/internal/world/mob.go
+++ b/internal/world/mob.go
@@ -33,8 +33,6 @@ type MobDef struct {
Protected bool `yaml:"protected"`
Unique bool `yaml:"unique"`
RespawnTicks int `yaml:"respawn_ticks"`
- WanderRooms []int `yaml:"wander_rooms"`
- WanderInterval int `yaml:"wander_interval"`
Drops DropTable `yaml:"drops"`
}
@@ -70,13 +68,24 @@ const (
)
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
+ 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 false
+ return true
}
func (m *MobInstance) MatchQuality(input string) int {
@@ -198,20 +207,19 @@ func (s *MobStore) RollIdleDescription(inst *MobInstance) {
return
}
inst.IdleDescription = pickIdleDescription(def.IdleDescriptions)
- inst.WanderRooms = def.WanderRooms
- inst.WanderInterval = def.WanderInterval
inst.WanderTickCounter = 0
}
-func (s *MobStore) SeedMobs(roomID int, mobIDs []string) {
+func (s *MobStore) SeedMobs(roomID int, entries []RoomMob) {
type defWrapper struct {
def *MobDef
err error
+ rm RoomMob
}
- defs := make([]defWrapper, len(mobIDs))
- for i, defID := range mobIDs {
- d, err := s.LoadDef(defID)
- defs[i] = defWrapper{d, err}
+ defs := make([]defWrapper, len(entries))
+ for i, rm := range entries {
+ d, err := s.LoadDef(rm.ID)
+ defs[i] = defWrapper{d, err, rm}
}
s.mu.Lock()
@@ -220,11 +228,9 @@ func (s *MobStore) SeedMobs(roomID int, mobIDs []string) {
if dw.err != nil {
continue
}
- defID := mobIDs[i]
+ defID := dw.rm.ID
instID := fmt.Sprintf("%s_%d_%d", defID, roomID, i)
- if inst, exists := s.instances[instID]; exists {
- // Already exists — skip (respawn is handled by timers)
- _ = inst
+ if _, exists := s.instances[instID]; exists {
continue
}
inst := &MobInstance{
@@ -242,8 +248,8 @@ func (s *MobStore) SeedMobs(roomID int, mobIDs []string) {
Protected: dw.def.Protected,
Unique: dw.def.Unique,
RespawnTicks: dw.def.RespawnTicks,
- WanderRooms: dw.def.WanderRooms,
- WanderInterval: dw.def.WanderInterval,
+ WanderRooms: dw.rm.WanderRooms,
+ WanderInterval: dw.rm.WanderInterval,
RoomID: roomID,
HomeRoomID: roomID,
Drops: dw.def.Drops,
diff --git a/internal/world/room.go b/internal/world/room.go
index 58fa82b..46fb876 100644
--- a/internal/world/room.go
+++ b/internal/world/room.go
@@ -73,10 +73,29 @@ type Room struct {
Exits map[ExitDir]ExitDef `yaml:"exits"`
Objects []RoomObject `yaml:"objects"`
Spawns []SpawnDef `yaml:"spawns"`
- Mobs []string `yaml:"mobs"`
+ Mobs []RoomMob `yaml:"mobs"`
OnEnter []EnterStep `yaml:"on_enter"`
}
+type RoomMob struct {
+ ID string `yaml:"id"`
+ WanderRooms []int `yaml:"wander_rooms"`
+ WanderInterval int `yaml:"wander_interval"`
+}
+
+func (rm *RoomMob) UnmarshalYAML(value *yaml.Node) error {
+ if value.Kind == yaml.ScalarNode {
+ var s string
+ if err := value.Decode(&s); err != nil {
+ return err
+ }
+ rm.ID = s
+ return nil
+ }
+ type raw RoomMob
+ return value.Decode((*raw)(rm))
+}
+
type EnterStep struct {
Message string `yaml:"message"`
Condition *action.Condition `yaml:"condition"`
diff --git a/internal/world/world.go b/internal/world/world.go
index 71fd518..c8270b1 100644
--- a/internal/world/world.go
+++ b/internal/world/world.go
@@ -61,6 +61,8 @@ type ObjState struct {
WanderRooms []int
WanderInterval int
WanderCounter int
+ SharedMax int
+ SharedTimer int
}
func (w *World) ObjStateKey(roomID int, defID string, index int) string {
@@ -148,6 +150,8 @@ func (w *World) FindObjInstances(roomID int, name string) []ObjState {
RoomID: st.RoomID,
Depleted: st.Depleted,
DepleteTimer: st.DepleteTimer,
+ SharedMax: st.SharedMax,
+ SharedTimer: st.SharedTimer,
})
_ = key
}
@@ -161,21 +165,26 @@ func (w *World) FindObjInstances(roomID int, name string) []ObjState {
}
func wordMatchesObj(lower, defID, objName string) bool {
- // Check against display name (space-separated words)
- for _, word := range strings.Fields(objName) {
- if strings.HasPrefix(strings.ToLower(word), lower) {
- return true
- }
+ if WordPrefixMatch(lower, objName) {
+ return true
}
- // Check defID both as whole and split by underscores
- if strings.HasPrefix(strings.ToLower(defID), lower) {
+ nameWords := strings.Fields(strings.ToLower(objName))
+ inputWords := strings.Fields(strings.ToLower(lower))
+
+ // Check defID as whole (bidirectional)
+ if strings.HasPrefix(strings.ToLower(defID), lower) || strings.HasPrefix(lower, strings.ToLower(defID)) {
return true
}
+ // Check each defID part (split by underscore) against each input word (bidirectional)
for _, part := range strings.Split(defID, "_") {
- if strings.HasPrefix(strings.ToLower(part), lower) {
- return true
+ for _, iw := range inputWords {
+ pl := strings.ToLower(part)
+ if strings.HasPrefix(pl, iw) || strings.HasPrefix(iw, pl) {
+ return true
+ }
}
}
+ _ = nameWords
return false
}
@@ -202,6 +211,30 @@ func (w *World) SetObjWander(roomID int, defID string, rooms []int, interval int
}
}
+func (w *World) SetObjSharedDeplete(roomID int, defID string, max int) {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+ for key, st := range w.objStates {
+ if st.RoomID == roomID && st.DefID == defID && st.SharedMax == 0 {
+ st.SharedMax = max
+ st.SharedTimer = max
+ }
+ _ = key
+ }
+}
+
+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 int) {
w.mu.Lock()
defer w.mu.Unlock()
@@ -239,7 +272,7 @@ func (w *World) LoadRoom(id int) (*Room, error) {
room.Spawns = make([]SpawnDef, 0)
}
if room.Mobs == nil {
- room.Mobs = make([]string, 0)
+ room.Mobs = make([]RoomMob, 0)
}
return &room, nil
}
@@ -305,6 +338,14 @@ func (w *World) AddReservedGroundItem(roomID int, itemID string, qty int, owner
func (w *World) AddGroundItem(roomID int, itemID string, qty int) {
w.mu.Lock()
defer w.mu.Unlock()
+ for _, e := range w.groundItems[roomID] {
+ if e.quantity > 0 && strings.EqualFold(e.itemID, itemID) &&
+ (e.reserveTimer <= 0 || e.reservedFor == "") {
+ e.quantity += qty
+ e.despawnTimer = DropDespawnTicks
+ return
+ }
+ }
e := &groundEntry{
itemID: itemID,
quantity: qty,
@@ -395,14 +436,28 @@ func (w *World) SeedGroundItems(roomID int) {
defer w.mu.Unlock()
for _, s := range room.Spawns {
- e := &groundEntry{
- itemID: s.ItemID,
- quantity: s.Quantity,
- isSpawn: true,
- respawnDelay: s.RespawnTicks,
- respawnQty: s.Quantity,
+ merged := false
+ for _, e := range w.groundItems[roomID] {
+ if e.quantity > 0 && strings.EqualFold(e.itemID, s.ItemID) &&
+ (e.reserveTimer <= 0 || e.reservedFor == "") {
+ e.quantity += s.Quantity
+ e.respawnQty += s.Quantity
+ e.isSpawn = true
+ e.respawnDelay = s.RespawnTicks
+ merged = true
+ break
+ }
+ }
+ if !merged {
+ e := &groundEntry{
+ itemID: s.ItemID,
+ quantity: s.Quantity,
+ isSpawn: true,
+ respawnDelay: s.RespawnTicks,
+ respawnQty: s.Quantity,
+ }
+ w.groundItems[roomID] = append(w.groundItems[roomID], e)
}
- w.groundItems[roomID] = append(w.groundItems[roomID], e)
}
}
@@ -412,12 +467,19 @@ func (w *World) Tick() {
for _, entries := range w.groundItems {
for _, e := range entries {
- if e.respawnTimer > 0 {
- e.respawnTimer--
- if e.respawnTimer <= 0 {
- e.quantity = e.respawnQty
+ 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 == "") {
+ e.quantity += other.quantity
+ other.quantity = 0
+ }
}
}
+ }
if e.despawnTimer > 0 {
e.despawnTimer--
if e.despawnTimer <= 0 {
@@ -439,6 +501,9 @@ func (w *World) Tick() {
if st.DepleteTimer <= 0 {
st.Depleted = false
st.JustRespawned = true
+ if st.SharedMax > 0 {
+ st.SharedTimer = st.SharedMax
+ }
}
}
}