592 lines
16 KiB
Go

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)
}