package ground_spawn import ( "sync" ) // MasterList is a specialized ground spawn master list optimized for: // - Fast zone-based lookups (O(1)) // - Fast skill-based lookups (O(1)) // - Spatial grid queries for proximity searches // - Set intersection operations for complex queries // - Fast state-based queries (available/depleted) type MasterList struct { // Core storage spawns map[int32]*GroundSpawn // ID -> GroundSpawn mutex sync.RWMutex // Category indices for O(1) lookups byZone map[int32][]*GroundSpawn // Zone ID -> spawns bySkill map[string][]*GroundSpawn // Skill -> spawns // State indices for O(1) filtering availableSpawns []*GroundSpawn // Available spawns (cached) depletedSpawns []*GroundSpawn // Depleted spawns (cached) stateStale bool // Whether state caches need refresh // Statistics cache stats *Statistics statsStale bool // Spatial grid for proximity queries (grid size = 100 units) spatialGrid map[gridKey][]*GroundSpawn gridSize float32 } // gridKey represents a spatial grid cell type gridKey struct { x, y int32 } // NewMasterList creates a new specialized ground spawn master list func NewMasterList() *MasterList { return &MasterList{ spawns: make(map[int32]*GroundSpawn), byZone: make(map[int32][]*GroundSpawn), bySkill: make(map[string][]*GroundSpawn), spatialGrid: make(map[gridKey][]*GroundSpawn), gridSize: 100.0, // 100 unit grid cells stateStale: true, // Initial state needs refresh statsStale: true, // Initial stats need refresh } } // getGridKey returns the grid cell for given coordinates func (ml *MasterList) getGridKey(x, y float32) gridKey { return gridKey{ x: int32(x / ml.gridSize), y: int32(y / ml.gridSize), } } // refreshStateCache updates the available/depleted spawn caches func (ml *MasterList) refreshStateCache() { if !ml.stateStale { return } // Clear existing caches ml.availableSpawns = ml.availableSpawns[:0] ml.depletedSpawns = ml.depletedSpawns[:0] // Rebuild caches for _, gs := range ml.spawns { if gs.IsAvailable() { ml.availableSpawns = append(ml.availableSpawns, gs) } else if gs.IsDepleted() { ml.depletedSpawns = append(ml.depletedSpawns, gs) } } ml.stateStale = false } // refreshStatsCache updates the statistics cache func (ml *MasterList) refreshStatsCache() { if !ml.statsStale { return } var availableSpawns int zoneMap := make(map[int32]int) skillMap := make(map[string]int64) // Single pass through all spawns for _, gs := range ml.spawns { if gs.IsAvailable() { availableSpawns++ } zoneMap[gs.ZoneID]++ skillMap[gs.CollectionSkill]++ } ml.stats = &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, } ml.statsStale = false } // AddGroundSpawn adds a ground spawn with full indexing func (ml *MasterList) AddGroundSpawn(gs *GroundSpawn) bool { ml.mutex.Lock() defer ml.mutex.Unlock() // Check if exists if _, exists := ml.spawns[gs.GroundSpawnID]; exists { return false } // Add to core storage ml.spawns[gs.GroundSpawnID] = gs // Update zone index ml.byZone[gs.ZoneID] = append(ml.byZone[gs.ZoneID], gs) // Update skill index ml.bySkill[gs.CollectionSkill] = append(ml.bySkill[gs.CollectionSkill], gs) // Update spatial grid gridKey := ml.getGridKey(gs.X, gs.Y) ml.spatialGrid[gridKey] = append(ml.spatialGrid[gridKey], gs) // Invalidate state and stats caches ml.stateStale = true ml.statsStale = true return true } // GetGroundSpawn retrieves by ID (O(1)) func (ml *MasterList) GetGroundSpawn(id int32) *GroundSpawn { ml.mutex.RLock() defer ml.mutex.RUnlock() return ml.spawns[id] } // GetByZone returns all spawns in a zone (O(1)) func (ml *MasterList) GetByZone(zoneID int32) []*GroundSpawn { ml.mutex.RLock() defer ml.mutex.RUnlock() return ml.byZone[zoneID] // Return slice directly for performance } // GetBySkill returns all spawns for a skill (O(1)) func (ml *MasterList) GetBySkill(skill string) []*GroundSpawn { ml.mutex.RLock() defer ml.mutex.RUnlock() return ml.bySkill[skill] // Return slice directly for performance } // GetByZoneAndSkill returns spawns matching both zone and skill (set intersection) func (ml *MasterList) GetByZoneAndSkill(zoneID int32, skill string) []*GroundSpawn { ml.mutex.RLock() defer ml.mutex.RUnlock() zoneSpawns := ml.byZone[zoneID] skillSpawns := ml.bySkill[skill] // Use smaller set for iteration efficiency if len(zoneSpawns) > len(skillSpawns) { zoneSpawns, skillSpawns = skillSpawns, zoneSpawns } // Set intersection using map lookup skillSet := make(map[*GroundSpawn]struct{}, len(skillSpawns)) for _, gs := range skillSpawns { skillSet[gs] = struct{}{} } var result []*GroundSpawn for _, gs := range zoneSpawns { if _, exists := skillSet[gs]; exists { result = append(result, gs) } } return result } // GetNearby returns spawns within radius of given coordinates using spatial grid func (ml *MasterList) GetNearby(x, y float32, radius float32) []*GroundSpawn { ml.mutex.RLock() defer ml.mutex.RUnlock() // Calculate grid search bounds minX := int32((x - radius) / ml.gridSize) maxX := int32((x + radius) / ml.gridSize) minY := int32((y - radius) / ml.gridSize) maxY := int32((y + radius) / ml.gridSize) var candidates []*GroundSpawn // Check all grid cells in range for gx := minX; gx <= maxX; gx++ { for gy := minY; gy <= maxY; gy++ { key := gridKey{x: gx, y: gy} if spawns, exists := ml.spatialGrid[key]; exists { candidates = append(candidates, spawns...) } } } // Filter by exact distance radiusSquared := radius * radius var result []*GroundSpawn for _, gs := range candidates { dx := gs.X - x dy := gs.Y - y if dx*dx+dy*dy <= radiusSquared { result = append(result, gs) } } return result } // GetAvailableSpawns returns harvestable spawns using cached results func (ml *MasterList) GetAvailableSpawns() []*GroundSpawn { ml.mutex.Lock() // Need write lock to potentially update cache defer ml.mutex.Unlock() ml.refreshStateCache() // Return a copy to prevent external modification result := make([]*GroundSpawn, len(ml.availableSpawns)) copy(result, ml.availableSpawns) return result } // GetDepletedSpawns returns depleted spawns using cached results func (ml *MasterList) GetDepletedSpawns() []*GroundSpawn { ml.mutex.Lock() // Need write lock to potentially update cache defer ml.mutex.Unlock() ml.refreshStateCache() // Return a copy to prevent external modification result := make([]*GroundSpawn, len(ml.depletedSpawns)) copy(result, ml.depletedSpawns) return result } // GetStatistics returns system statistics using cached results func (ml *MasterList) GetStatistics() *Statistics { ml.mutex.Lock() // Need write lock to potentially update cache defer ml.mutex.Unlock() ml.refreshStatsCache() // Return a copy to prevent external modification return &Statistics{ TotalHarvests: ml.stats.TotalHarvests, SuccessfulHarvests: ml.stats.SuccessfulHarvests, RareItemsHarvested: ml.stats.RareItemsHarvested, SkillUpsGenerated: ml.stats.SkillUpsGenerated, HarvestsBySkill: ml.stats.HarvestsBySkill, ActiveGroundSpawns: ml.stats.ActiveGroundSpawns, GroundSpawnsByZone: ml.stats.GroundSpawnsByZone, } } // RemoveGroundSpawn removes a spawn and updates all indices func (ml *MasterList) RemoveGroundSpawn(id int32) bool { ml.mutex.Lock() defer ml.mutex.Unlock() gs, exists := ml.spawns[id] if !exists { return false } // Remove from core storage delete(ml.spawns, id) // Remove from zone index zoneSpawns := ml.byZone[gs.ZoneID] for i, spawn := range zoneSpawns { if spawn.GroundSpawnID == id { ml.byZone[gs.ZoneID] = append(zoneSpawns[:i], zoneSpawns[i+1:]...) break } } // Remove from skill index skillSpawns := ml.bySkill[gs.CollectionSkill] for i, spawn := range skillSpawns { if spawn.GroundSpawnID == id { ml.bySkill[gs.CollectionSkill] = append(skillSpawns[:i], skillSpawns[i+1:]...) break } } // Remove from spatial grid gridKey := ml.getGridKey(gs.X, gs.Y) gridSpawns := ml.spatialGrid[gridKey] for i, spawn := range gridSpawns { if spawn.GroundSpawnID == id { ml.spatialGrid[gridKey] = append(gridSpawns[:i], gridSpawns[i+1:]...) break } } // Invalidate state and stats caches ml.stateStale = true ml.statsStale = true return true } // Size returns the total number of spawns func (ml *MasterList) Size() int { ml.mutex.RLock() defer ml.mutex.RUnlock() return len(ml.spawns) } // InvalidateStateCache marks the state and stats caches as stale // Call this when spawn states change (e.g., after harvesting, respawning) func (ml *MasterList) InvalidateStateCache() { ml.mutex.Lock() defer ml.mutex.Unlock() ml.stateStale = true ml.statsStale = true }