package ground_spawn import ( "fmt" "eq2emu/internal/common" "eq2emu/internal/database" "zombiezen.com/go/sqlite" ) // MasterList manages all ground spawns using the generic base type MasterList struct { *common.MasterList[int32, *GroundSpawn] } // NewMasterList creates a new ground spawn master list func NewMasterList() *MasterList { return &MasterList{ MasterList: common.NewMasterList[int32, *GroundSpawn](), } } // AddGroundSpawn adds a ground spawn to the master list func (ml *MasterList) AddGroundSpawn(gs *GroundSpawn) bool { return ml.MasterList.Add(gs) } // GetGroundSpawn retrieves a ground spawn by ID func (ml *MasterList) GetGroundSpawn(groundSpawnID int32) *GroundSpawn { return ml.MasterList.Get(groundSpawnID) } // RemoveGroundSpawn removes a ground spawn by ID func (ml *MasterList) RemoveGroundSpawn(groundSpawnID int32) bool { return ml.MasterList.Remove(groundSpawnID) } // GetByZone returns all ground spawns in a specific zone func (ml *MasterList) GetByZone(zoneID int32) []*GroundSpawn { return ml.MasterList.Filter(func(gs *GroundSpawn) bool { return gs.ZoneID == zoneID }) } // GetBySkill returns all ground spawns that require a specific skill func (ml *MasterList) GetBySkill(skill string) []*GroundSpawn { return ml.MasterList.Filter(func(gs *GroundSpawn) bool { return gs.CollectionSkill == skill }) } // GetAvailableSpawns returns all harvestable ground spawns func (ml *MasterList) GetAvailableSpawns() []*GroundSpawn { return ml.MasterList.Filter(func(gs *GroundSpawn) bool { return gs.IsAvailable() }) } // GetDepletedSpawns returns all depleted ground spawns func (ml *MasterList) GetDepletedSpawns() []*GroundSpawn { return ml.MasterList.Filter(func(gs *GroundSpawn) bool { return gs.IsDepleted() }) } // LoadFromDatabase loads all ground spawns from database func (ml *MasterList) LoadFromDatabase(db *database.Database) error { if db.GetType() == database.SQLite { return ml.loadFromSQLite(db) } return ml.loadFromMySQL(db) } func (ml *MasterList) loadFromSQLite(db *database.Database) error { return db.ExecTransient(` SELECT id, groundspawn_id, name, collection_skill, number_harvests, attempts_per_harvest, randomize_heading, respawn_time, x, y, z, heading, zone_id, grid_id FROM ground_spawns ORDER BY groundspawn_id `, func(stmt *sqlite.Stmt) error { gs := &GroundSpawn{ ID: stmt.ColumnInt32(0), GroundSpawnID: stmt.ColumnInt32(1), Name: stmt.ColumnText(2), CollectionSkill: stmt.ColumnText(3), NumberHarvests: int8(stmt.ColumnInt32(4)), AttemptsPerHarvest: int8(stmt.ColumnInt32(5)), RandomizeHeading: stmt.ColumnBool(6), RespawnTime: stmt.ColumnInt32(7), X: float32(stmt.ColumnFloat(8)), Y: float32(stmt.ColumnFloat(9)), Z: float32(stmt.ColumnFloat(10)), Heading: float32(stmt.ColumnFloat(11)), ZoneID: stmt.ColumnInt32(12), GridID: stmt.ColumnInt32(13), db: db, isNew: false, IsAlive: true, CurrentHarvests: int8(stmt.ColumnInt32(4)), // Initialize to max harvests HarvestEntries: make([]*HarvestEntry, 0), HarvestItems: make([]*HarvestEntryItem, 0), } // Load harvest data for this ground spawn if err := gs.loadHarvestData(); err != nil { return fmt.Errorf("failed to load harvest data for ground spawn %d: %w", gs.GroundSpawnID, err) } ml.MasterList.Add(gs) return nil }) } func (ml *MasterList) loadFromMySQL(db *database.Database) error { rows, err := db.Query(` SELECT id, groundspawn_id, name, collection_skill, number_harvests, attempts_per_harvest, randomize_heading, respawn_time, x, y, z, heading, zone_id, grid_id FROM ground_spawns ORDER BY groundspawn_id `) if err != nil { return fmt.Errorf("failed to query ground spawns: %w", err) } defer rows.Close() for rows.Next() { gs := &GroundSpawn{ db: db, isNew: false, IsAlive: true, HarvestEntries: make([]*HarvestEntry, 0), HarvestItems: make([]*HarvestEntryItem, 0), } err := rows.Scan(&gs.ID, &gs.GroundSpawnID, &gs.Name, &gs.CollectionSkill, &gs.NumberHarvests, &gs.AttemptsPerHarvest, &gs.RandomizeHeading, &gs.RespawnTime, &gs.X, &gs.Y, &gs.Z, &gs.Heading, &gs.ZoneID, &gs.GridID) if err != nil { return fmt.Errorf("failed to scan ground spawn: %w", err) } // Initialize current harvests to max gs.CurrentHarvests = gs.NumberHarvests // Load harvest data for this ground spawn if err := gs.loadHarvestData(); err != nil { return fmt.Errorf("failed to load harvest data for ground spawn %d: %w", gs.GroundSpawnID, err) } ml.MasterList.Add(gs) } return rows.Err() } // GetStatistics returns statistics about the ground spawn system func (ml *MasterList) GetStatistics() *Statistics { availableSpawns := len(ml.GetAvailableSpawns()) // Count by zone zoneMap := make(map[int32]int) skillMap := make(map[string]int64) ml.MasterList.ForEach(func(id int32, gs *GroundSpawn) { zoneMap[gs.ZoneID]++ skillMap[gs.CollectionSkill]++ }) return &Statistics{ TotalHarvests: 0, // Would need to be tracked separately SuccessfulHarvests: 0, // Would need to be tracked separately RareItemsHarvested: 0, // Would need to be tracked separately SkillUpsGenerated: 0, // Would need to be tracked separately HarvestsBySkill: skillMap, ActiveGroundSpawns: availableSpawns, GroundSpawnsByZone: zoneMap, } }