package world import ( "fmt" "os" "path/filepath" "sync" "gopkg.in/yaml.v3" ) type LootEntry struct { ItemID string `yaml:"item_id"` Weight int `yaml:"weight"` Quantity int `yaml:"quantity"` } type DropTable struct { Remains string `yaml:"remains"` Loot []LootEntry `yaml:"loot"` } 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"` } 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 } type MobStore struct { dataDir string mu sync.Mutex defs map[string]*MobDef instances map[string]*MobInstance } func NewMobStore(dataDir string) *MobStore { return &MobStore{ dataDir: dataDir, defs: make(map[string]*MobDef), instances: make(map[string]*MobInstance), } } func (s *MobStore) LoadDef(id string) (*MobDef, error) { s.mu.Lock() if def, ok := s.defs[id]; ok { s.mu.Unlock() return def, nil } s.mu.Unlock() path := filepath.Join(s.dataDir, "mobs", id+".yaml") data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read mob %s: %w", id, err) } var def MobDef if err := yaml.Unmarshal(data, &def); err != nil { return nil, fmt.Errorf("parse mob %s: %w", id, err) } s.mu.Lock() s.defs[id] = &def s.mu.Unlock() return &def, nil } func (s *MobStore) SpawnMob(defID string, roomID int, instanceID string) (*MobInstance, error) { def, err := s.LoadDef(defID) if err != nil { return nil, err } s.mu.Lock() defer s.mu.Unlock() inst := &MobInstance{ InstanceID: instanceID, DefID: defID, Name: def.Name, HP: def.HP, MaxHP: def.HP, Attack: def.Attack, Strength: def.Strength, Defense: def.Defense, Speed: def.Speed, Aggressive: def.Aggressive, RespawnTicks: def.RespawnTicks, RoomID: roomID, Drops: def.Drops, } s.instances[instanceID] = inst return inst, nil } func (s *MobStore) GetInstance(id string) *MobInstance { s.mu.Lock() defer s.mu.Unlock() return s.instances[id] } func (s *MobStore) RemoveInstance(id string) { s.mu.Lock() defer s.mu.Unlock() delete(s.instances, id) } func (s *MobStore) MobsInRoom(roomID int) []*MobInstance { s.mu.Lock() defer s.mu.Unlock() var out []*MobInstance for _, inst := range s.instances { if inst.RoomID == roomID && inst.HP > 0 { out = append(out, inst) } } return out } func (s *MobStore) SeedMobs(roomID int, mobIDs []string) { type defWrapper struct { def *MobDef err error } defs := make([]defWrapper, len(mobIDs)) for i, defID := range mobIDs { d, err := s.LoadDef(defID) defs[i] = defWrapper{d, err} } s.mu.Lock() defer s.mu.Unlock() for i, dw := range defs { if dw.err != nil { continue } defID := mobIDs[i] 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 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, } } }