package ground_spawn import ( "fmt" "sync" "time" ) // NewManager creates a new ground spawn manager func NewManager(database Database, logger Logger) *Manager { return &Manager{ groundSpawns: make(map[int32]*GroundSpawn), spawnsByZone: make(map[int32][]*GroundSpawn), entriesByID: make(map[int32][]*GroundSpawnEntry), itemsByID: make(map[int32][]*GroundSpawnEntryItem), respawnQueue: make(map[int32]time.Time), database: database, logger: logger, harvestsBySkill: make(map[string]int64), } } // Initialize loads ground spawn data from database func (m *Manager) Initialize() error { if m.logger != nil { m.logger.LogInfo("Initializing ground spawn manager...") } if m.database == nil { if m.logger != nil { m.logger.LogWarning("No database provided, starting with empty ground spawn list") } return nil } // Load ground spawns from database groundSpawns, err := m.database.LoadAllGroundSpawns() if err != nil { return fmt.Errorf("failed to load ground spawns from database: %w", err) } m.mutex.Lock() defer m.mutex.Unlock() for _, gs := range groundSpawns { m.groundSpawns[gs.GetID()] = gs // Group by zone (placeholder - zone ID would come from spawn location) zoneID := int32(1) // TODO: Get actual zone ID from spawn m.spawnsByZone[zoneID] = append(m.spawnsByZone[zoneID], gs) // Load harvest entries and items if err := m.loadGroundSpawnData(gs); err != nil && m.logger != nil { m.logger.LogWarning("Failed to load data for ground spawn %d: %v", gs.GetID(), err) } } if m.logger != nil { m.logger.LogInfo("Loaded %d ground spawns from database", len(groundSpawns)) } return nil } // loadGroundSpawnData loads entries and items for a ground spawn func (m *Manager) loadGroundSpawnData(gs *GroundSpawn) error { groundspawnID := gs.GetGroundSpawnEntryID() // Load harvest entries entries, err := m.database.LoadGroundSpawnEntries(groundspawnID) if err != nil { return fmt.Errorf("failed to load entries for groundspawn %d: %w", groundspawnID, err) } m.entriesByID[groundspawnID] = entries // Load harvest items items, err := m.database.LoadGroundSpawnItems(groundspawnID) if err != nil { return fmt.Errorf("failed to load items for groundspawn %d: %w", groundspawnID, err) } m.itemsByID[groundspawnID] = items return nil } // CreateGroundSpawn creates a new ground spawn func (m *Manager) CreateGroundSpawn(config GroundSpawnConfig) *GroundSpawn { gs := NewGroundSpawn(config) m.mutex.Lock() defer m.mutex.Unlock() // Generate ID (placeholder implementation) newID := int32(len(m.groundSpawns) + 1) gs.SetID(newID) // Store ground spawn m.groundSpawns[newID] = gs // Group by zone zoneID := int32(1) // TODO: Get actual zone ID from config.Location m.spawnsByZone[zoneID] = append(m.spawnsByZone[zoneID], gs) if m.logger != nil { m.logger.LogInfo("Created ground spawn %d: %s", newID, gs.GetName()) } return gs } // GetGroundSpawn returns a ground spawn by ID func (m *Manager) GetGroundSpawn(id int32) *GroundSpawn { m.mutex.RLock() defer m.mutex.RUnlock() return m.groundSpawns[id] } // GetGroundSpawnsByZone returns all ground spawns in a zone func (m *Manager) GetGroundSpawnsByZone(zoneID int32) []*GroundSpawn { m.mutex.RLock() defer m.mutex.RUnlock() spawns := m.spawnsByZone[zoneID] if spawns == nil { return []*GroundSpawn{} } // Return a copy to prevent external modification result := make([]*GroundSpawn, len(spawns)) copy(result, spawns) return result } // ProcessHarvest handles harvesting for a player func (m *Manager) ProcessHarvest(gs *GroundSpawn, player *Player) (*HarvestResult, error) { if gs == nil { return nil, fmt.Errorf("ground spawn cannot be nil") } if player == nil { return nil, fmt.Errorf("player cannot be nil") } // Record statistics m.mutex.Lock() m.totalHarvests++ skill := gs.GetCollectionSkill() m.harvestsBySkill[skill]++ m.mutex.Unlock() // Build harvest context context, err := m.buildHarvestContext(gs, player) if err != nil { return nil, fmt.Errorf("failed to build harvest context: %w", err) } // Process the harvest result, err := gs.ProcessHarvest(context) if err != nil { return nil, fmt.Errorf("harvest processing failed: %w", err) } // Update statistics if result != nil && result.Success { m.mutex.Lock() m.successfulHarvests++ // Count rare items for _, item := range result.ItemsAwarded { if item.IsRare { m.rareItemsHarvested++ } } if result.SkillGained { m.skillUpsGenerated++ } m.mutex.Unlock() } // Handle respawn if depleted if gs.IsDepleted() { m.scheduleRespawn(gs) } return result, nil } // buildHarvestContext creates a harvest context for processing func (m *Manager) buildHarvestContext(gs *GroundSpawn, player *Player) (*HarvestContext, error) { groundspawnID := gs.GetGroundSpawnEntryID() m.mutex.RLock() entries := m.entriesByID[groundspawnID] items := m.itemsByID[groundspawnID] m.mutex.RUnlock() if entries == nil || len(entries) == 0 { return nil, fmt.Errorf("no harvest entries found for groundspawn %d", groundspawnID) } if items == nil || len(items) == 0 { return nil, fmt.Errorf("no harvest items found for groundspawn %d", groundspawnID) } // Get player skill skillName := gs.GetCollectionSkill() if skillName == SkillCollecting { skillName = SkillGathering // Collections use gathering skill } playerSkill := player.GetSkillByName(skillName) if playerSkill == nil { return nil, fmt.Errorf("player lacks required skill: %s", skillName) } // Calculate total skill (base + bonuses) totalSkill := playerSkill.GetCurrentValue() // TODO: Add stat bonuses when stat system is integrated // Find max skill required var maxSkillRequired int16 for _, entry := range entries { if entry.MinSkillLevel > maxSkillRequired { maxSkillRequired = entry.MinSkillLevel } } return &HarvestContext{ Player: player, GroundSpawn: gs, PlayerSkill: playerSkill, TotalSkill: totalSkill, GroundSpawnEntries: entries, GroundSpawnItems: items, IsCollection: gs.GetCollectionSkill() == SkillCollecting, MaxSkillRequired: maxSkillRequired, }, nil } // scheduleRespawn schedules a ground spawn for respawn func (m *Manager) scheduleRespawn(gs *GroundSpawn) { if gs == nil { return } // TODO: Get respawn timer from configuration or database respawnDelay := 5 * time.Minute // Default 5 minutes respawnTime := time.Now().Add(respawnDelay) m.mutex.Lock() m.respawnQueue[gs.GetID()] = respawnTime m.mutex.Unlock() if m.logger != nil { m.logger.LogDebug("Scheduled ground spawn %d for respawn at %v", gs.GetID(), respawnTime) } } // ProcessRespawns handles ground spawn respawning func (m *Manager) ProcessRespawns() { now := time.Now() var toRespawn []int32 m.mutex.Lock() for spawnID, respawnTime := range m.respawnQueue { if now.After(respawnTime) { toRespawn = append(toRespawn, spawnID) delete(m.respawnQueue, spawnID) } } m.mutex.Unlock() // Respawn outside of lock for _, spawnID := range toRespawn { if gs := m.GetGroundSpawn(spawnID); gs != nil { gs.Respawn() if m.logger != nil { m.logger.LogDebug("Ground spawn %d respawned", spawnID) } } } } // GetStatistics returns ground spawn system statistics func (m *Manager) GetStatistics() *HarvestStatistics { m.mutex.RLock() defer m.mutex.RUnlock() // Count spawns by zone spawnsByZone := make(map[int32]int) for zoneID, spawns := range m.spawnsByZone { spawnsByZone[zoneID] = len(spawns) } // Copy harvests by skill harvestsBySkill := make(map[string]int64) for skill, count := range m.harvestsBySkill { harvestsBySkill[skill] = count } return &HarvestStatistics{ TotalHarvests: m.totalHarvests, SuccessfulHarvests: m.successfulHarvests, RareItemsHarvested: m.rareItemsHarvested, SkillUpsGenerated: m.skillUpsGenerated, HarvestsBySkill: harvestsBySkill, ActiveGroundSpawns: len(m.groundSpawns), GroundSpawnsByZone: spawnsByZone, } } // ResetStatistics resets all statistics func (m *Manager) ResetStatistics() { m.mutex.Lock() defer m.mutex.Unlock() m.totalHarvests = 0 m.successfulHarvests = 0 m.rareItemsHarvested = 0 m.skillUpsGenerated = 0 m.harvestsBySkill = make(map[string]int64) } // AddGroundSpawn adds a ground spawn to the manager func (m *Manager) AddGroundSpawn(gs *GroundSpawn) error { if gs == nil { return fmt.Errorf("ground spawn cannot be nil") } m.mutex.Lock() defer m.mutex.Unlock() // Check if ID is already used if _, exists := m.groundSpawns[gs.GetID()]; exists { return fmt.Errorf("ground spawn with ID %d already exists", gs.GetID()) } m.groundSpawns[gs.GetID()] = gs // Group by zone (placeholder) zoneID := int32(1) // TODO: Get actual zone ID m.spawnsByZone[zoneID] = append(m.spawnsByZone[zoneID], gs) // Load harvest data if database is available if m.database != nil { if err := m.loadGroundSpawnData(gs); err != nil && m.logger != nil { m.logger.LogWarning("Failed to load data for ground spawn %d: %v", gs.GetID(), err) } } return nil } // RemoveGroundSpawn removes a ground spawn from the manager func (m *Manager) RemoveGroundSpawn(id int32) bool { m.mutex.Lock() defer m.mutex.Unlock() gs, exists := m.groundSpawns[id] if !exists { return false } delete(m.groundSpawns, id) delete(m.respawnQueue, id) // Remove from zone list // TODO: Get actual zone ID from ground spawn zoneID := int32(1) if spawns, exists := m.spawnsByZone[zoneID]; exists { for i, spawn := range spawns { if spawn.GetID() == id { m.spawnsByZone[zoneID] = append(spawns[:i], spawns[i+1:]...) break } } } // Clean up harvest data if gs != nil { groundspawnID := gs.GetGroundSpawnEntryID() delete(m.entriesByID, groundspawnID) delete(m.itemsByID, groundspawnID) } return true } // GetGroundSpawnCount returns the total number of ground spawns func (m *Manager) GetGroundSpawnCount() int { m.mutex.RLock() defer m.mutex.RUnlock() return len(m.groundSpawns) } // GetActiveGroundSpawns returns all active (harvestable) ground spawns func (m *Manager) GetActiveGroundSpawns() []*GroundSpawn { m.mutex.RLock() defer m.mutex.RUnlock() var active []*GroundSpawn for _, gs := range m.groundSpawns { if gs.IsAvailable() { active = append(active, gs) } } return active } // GetDepletedGroundSpawns returns all depleted ground spawns func (m *Manager) GetDepletedGroundSpawns() []*GroundSpawn { m.mutex.RLock() defer m.mutex.RUnlock() var depleted []*GroundSpawn for _, gs := range m.groundSpawns { if gs.IsDepleted() { depleted = append(depleted, gs) } } return depleted } // ProcessCommand handles ground spawn management commands func (m *Manager) ProcessCommand(command string, args []string) (string, error) { switch command { case "stats": return m.handleStatsCommand(args) case "list": return m.handleListCommand(args) case "respawn": return m.handleRespawnCommand(args) case "info": return m.handleInfoCommand(args) case "reload": return m.handleReloadCommand(args) default: return "", fmt.Errorf("unknown ground spawn command: %s", command) } } // handleStatsCommand shows ground spawn system statistics func (m *Manager) handleStatsCommand(args []string) (string, error) { stats := m.GetStatistics() result := "Ground Spawn System Statistics:\n" result += fmt.Sprintf("Total Harvests: %d\n", stats.TotalHarvests) result += fmt.Sprintf("Successful Harvests: %d\n", stats.SuccessfulHarvests) result += fmt.Sprintf("Rare Items Harvested: %d\n", stats.RareItemsHarvested) result += fmt.Sprintf("Skill Ups Generated: %d\n", stats.SkillUpsGenerated) result += fmt.Sprintf("Active Ground Spawns: %d\n", stats.ActiveGroundSpawns) if len(stats.HarvestsBySkill) > 0 { result += "\nHarvests by Skill:\n" for skill, count := range stats.HarvestsBySkill { result += fmt.Sprintf(" %s: %d\n", skill, count) } } return result, nil } // handleListCommand lists ground spawns func (m *Manager) handleListCommand(args []string) (string, error) { count := m.GetGroundSpawnCount() if count == 0 { return "No ground spawns loaded.", nil } active := m.GetActiveGroundSpawns() depleted := m.GetDepletedGroundSpawns() result := fmt.Sprintf("Ground Spawns (Total: %d, Active: %d, Depleted: %d):\n", count, len(active), len(depleted)) // Show first 10 active spawns shown := 0 for _, gs := range active { if shown >= 10 { result += "... (and more)\n" break } result += fmt.Sprintf(" %d: %s (%s) - %d harvests remaining\n", gs.GetID(), gs.GetName(), gs.GetCollectionSkill(), gs.GetNumberHarvests()) shown++ } return result, nil } // handleRespawnCommand respawns ground spawns func (m *Manager) handleRespawnCommand(args []string) (string, error) { if len(args) > 0 { // Respawn specific ground spawn var spawnID int32 if _, err := fmt.Sscanf(args[0], "%d", &spawnID); err != nil { return "", fmt.Errorf("invalid ground spawn ID: %s", args[0]) } gs := m.GetGroundSpawn(spawnID) if gs == nil { return fmt.Sprintf("Ground spawn %d not found.", spawnID), nil } gs.Respawn() return fmt.Sprintf("Ground spawn %d respawned.", spawnID), nil } // Respawn all depleted spawns depleted := m.GetDepletedGroundSpawns() for _, gs := range depleted { gs.Respawn() } return fmt.Sprintf("Respawned %d depleted ground spawns.", len(depleted)), nil } // handleInfoCommand shows information about a specific ground spawn func (m *Manager) handleInfoCommand(args []string) (string, error) { if len(args) == 0 { return "", fmt.Errorf("ground spawn ID required") } var spawnID int32 if _, err := fmt.Sscanf(args[0], "%d", &spawnID); err != nil { return "", fmt.Errorf("invalid ground spawn ID: %s", args[0]) } gs := m.GetGroundSpawn(spawnID) if gs == nil { return fmt.Sprintf("Ground spawn %d not found.", spawnID), nil } result := fmt.Sprintf("Ground Spawn Information:\n") result += fmt.Sprintf("ID: %d\n", gs.GetID()) result += fmt.Sprintf("Name: %s\n", gs.GetName()) result += fmt.Sprintf("Collection Skill: %s\n", gs.GetCollectionSkill()) result += fmt.Sprintf("Harvests Remaining: %d\n", gs.GetNumberHarvests()) result += fmt.Sprintf("Attempts per Harvest: %d\n", gs.GetAttemptsPerHarvest()) result += fmt.Sprintf("Ground Spawn Entry ID: %d\n", gs.GetGroundSpawnEntryID()) result += fmt.Sprintf("Available: %v\n", gs.IsAvailable()) result += fmt.Sprintf("Depleted: %v\n", gs.IsDepleted()) return result, nil } // handleReloadCommand reloads ground spawns from database func (m *Manager) handleReloadCommand(args []string) (string, error) { if m.database == nil { return "", fmt.Errorf("no database available") } // Clear current data m.mutex.Lock() m.groundSpawns = make(map[int32]*GroundSpawn) m.spawnsByZone = make(map[int32][]*GroundSpawn) m.entriesByID = make(map[int32][]*GroundSpawnEntry) m.itemsByID = make(map[int32][]*GroundSpawnEntryItem) m.respawnQueue = make(map[int32]time.Time) m.mutex.Unlock() // Reload from database if err := m.Initialize(); err != nil { return "", fmt.Errorf("failed to reload ground spawns: %w", err) } count := m.GetGroundSpawnCount() return fmt.Sprintf("Successfully reloaded %d ground spawns from database.", count), nil } // Shutdown gracefully shuts down the manager func (m *Manager) Shutdown() { if m.logger != nil { m.logger.LogInfo("Shutting down ground spawn manager...") } m.mutex.Lock() defer m.mutex.Unlock() // Clear all data m.groundSpawns = make(map[int32]*GroundSpawn) m.spawnsByZone = make(map[int32][]*GroundSpawn) m.entriesByID = make(map[int32][]*GroundSpawnEntry) m.itemsByID = make(map[int32][]*GroundSpawnEntryItem) m.respawnQueue = make(map[int32]time.Time) }