592 lines
16 KiB
Go
592 lines
16 KiB
Go
package ground_spawn
|
|
|
|
import (
|
|
"fmt"
|
|
"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)
|
|
}
|