package spawn import ( "sync" ) // Spawn entry types const ( SpawnEntryTypeNPC = 0 SpawnEntryTypeObject = 1 SpawnEntryTypeWidget = 2 SpawnEntryTypeSign = 3 SpawnEntryTypeGroundSpawn = 4 ) // SpawnEntry represents a possible spawn at a location with its configuration type SpawnEntry struct { SpawnEntryID int32 // Unique identifier for this spawn entry SpawnLocationID int32 // ID of the location this entry belongs to SpawnType int8 // Type of spawn (NPC, Object, Widget, etc.) SpawnID int32 // ID of the actual spawn template SpawnPercentage float32 // Chance this spawn will appear Respawn int32 // Base respawn time in seconds RespawnOffsetLow int32 // Minimum random offset for respawn RespawnOffsetHigh int32 // Maximum random offset for respawn DuplicatedSpawn bool // Whether this spawn can appear multiple times ExpireTime int32 // Time before spawn expires (0 = permanent) ExpireOffset int32 // Random offset for expire time // Spawn location overrides - these override the base spawn's stats LevelOverride int32 // Override spawn level HPOverride int32 // Override spawn HP MPOverride int32 // Override spawn MP/Power StrengthOverride int32 // Override strength stat StaminaOverride int32 // Override stamina stat WisdomOverride int32 // Override wisdom stat IntelligenceOverride int32 // Override intelligence stat AgilityOverride int32 // Override agility stat HeatOverride int32 // Override heat resistance ColdOverride int32 // Override cold resistance MagicOverride int32 // Override magic resistance MentalOverride int32 // Override mental resistance DivineOverride int32 // Override divine resistance DiseaseOverride int32 // Override disease resistance PoisonOverride int32 // Override poison resistance DifficultyOverride int32 // Override encounter level/difficulty } // NewSpawnEntry creates a new spawn entry with default values func NewSpawnEntry() *SpawnEntry { return &SpawnEntry{ SpawnEntryID: 0, SpawnLocationID: 0, SpawnType: SpawnEntryTypeNPC, SpawnID: 0, SpawnPercentage: 100.0, Respawn: 600, // 10 minutes default RespawnOffsetLow: 0, RespawnOffsetHigh: 0, DuplicatedSpawn: true, ExpireTime: 0, ExpireOffset: 0, LevelOverride: 0, HPOverride: 0, MPOverride: 0, StrengthOverride: 0, StaminaOverride: 0, WisdomOverride: 0, IntelligenceOverride: 0, AgilityOverride: 0, HeatOverride: 0, ColdOverride: 0, MagicOverride: 0, MentalOverride: 0, DivineOverride: 0, DiseaseOverride: 0, PoisonOverride: 0, DifficultyOverride: 0, } } // GetSpawnTypeName returns a human-readable name for the spawn type func (se *SpawnEntry) GetSpawnTypeName() string { switch se.SpawnType { case SpawnEntryTypeNPC: return "NPC" case SpawnEntryTypeObject: return "Object" case SpawnEntryTypeWidget: return "Widget" case SpawnEntryTypeSign: return "Sign" case SpawnEntryTypeGroundSpawn: return "GroundSpawn" default: return "Unknown" } } // HasStatOverrides returns true if this entry has any stat overrides configured func (se *SpawnEntry) HasStatOverrides() bool { return se.LevelOverride != 0 || se.HPOverride != 0 || se.MPOverride != 0 || se.StrengthOverride != 0 || se.StaminaOverride != 0 || se.WisdomOverride != 0 || se.IntelligenceOverride != 0 || se.AgilityOverride != 0 || se.HeatOverride != 0 || se.ColdOverride != 0 || se.MagicOverride != 0 || se.MentalOverride != 0 || se.DivineOverride != 0 || se.DiseaseOverride != 0 || se.PoisonOverride != 0 || se.DifficultyOverride != 0 } // GetActualRespawnTime calculates the actual respawn time including random offset func (se *SpawnEntry) GetActualRespawnTime() int32 { if se.RespawnOffsetLow == 0 && se.RespawnOffsetHigh == 0 { return se.Respawn } // TODO: Implement random number generation // For now, return base respawn time return se.Respawn } // SpawnLocation represents a location in the world where spawns can appear type SpawnLocation struct { // Position data X float32 // X coordinate in world space Y float32 // Y coordinate in world space Z float32 // Z coordinate in world space Heading float32 // Direction the spawn faces Pitch float32 // Pitch angle Roll float32 // Roll angle // Offset ranges for randomizing spawn positions XOffset float32 // Random X offset range (+/-) YOffset float32 // Random Y offset range (+/-) ZOffset float32 // Random Z offset range (+/-) // Location metadata PlacementID int32 // Unique placement identifier GridID int32 // Grid cell this location belongs to Script string // Lua script to run for this location Conditional int8 // Conditional flag for spawn logic // Spawn management Entities []*SpawnEntry // List of possible spawns at this location TotalPercentage float32 // Sum of all spawn percentages // Thread safety mutex sync.RWMutex } // NewSpawnLocation creates a new spawn location with default values func NewSpawnLocation() *SpawnLocation { return &SpawnLocation{ X: 0.0, Y: 0.0, Z: 0.0, Heading: 0.0, Pitch: 0.0, Roll: 0.0, XOffset: 0.0, YOffset: 0.0, ZOffset: 0.0, PlacementID: 0, GridID: 0, Script: "", Conditional: 0, Entities: make([]*SpawnEntry, 0), TotalPercentage: 0.0, } } // AddSpawnEntry adds a spawn entry to this location func (sl *SpawnLocation) AddSpawnEntry(entry *SpawnEntry) { if entry == nil { return } sl.mutex.Lock() defer sl.mutex.Unlock() sl.Entities = append(sl.Entities, entry) sl.TotalPercentage += entry.SpawnPercentage } // RemoveSpawnEntry removes a spawn entry by its ID func (sl *SpawnLocation) RemoveSpawnEntry(entryID int32) bool { sl.mutex.Lock() defer sl.mutex.Unlock() for i, entry := range sl.Entities { if entry.SpawnEntryID == entryID { // Remove from slice sl.Entities = append(sl.Entities[:i], sl.Entities[i+1:]...) sl.TotalPercentage -= entry.SpawnPercentage return true } } return false } // GetSpawnEntries returns a copy of all spawn entries func (sl *SpawnLocation) GetSpawnEntries() []*SpawnEntry { sl.mutex.RLock() defer sl.mutex.RUnlock() entries := make([]*SpawnEntry, len(sl.Entities)) copy(entries, sl.Entities) return entries } // GetSpawnEntryCount returns the number of spawn entries at this location func (sl *SpawnLocation) GetSpawnEntryCount() int { sl.mutex.RLock() defer sl.mutex.RUnlock() return len(sl.Entities) } // GetSpawnEntryByID finds a spawn entry by its ID func (sl *SpawnLocation) GetSpawnEntryByID(entryID int32) *SpawnEntry { sl.mutex.RLock() defer sl.mutex.RUnlock() for _, entry := range sl.Entities { if entry.SpawnEntryID == entryID { return entry } } return nil } // CalculateRandomPosition returns a randomized position within the offset ranges // TODO: Implement proper random number generation func (sl *SpawnLocation) CalculateRandomPosition() (float32, float32, float32) { sl.mutex.RLock() defer sl.mutex.RUnlock() // For now, return base position // In full implementation, would add random offsets within ranges return sl.X, sl.Y, sl.Z } // SelectRandomSpawn selects a spawn entry based on spawn percentages // Returns nil if no spawn is selected (based on random chance) func (sl *SpawnLocation) SelectRandomSpawn() *SpawnEntry { sl.mutex.RLock() defer sl.mutex.RUnlock() if len(sl.Entities) == 0 || sl.TotalPercentage <= 0 { return nil } // TODO: Implement proper random selection based on percentages // For now, return first entry if any exist if len(sl.Entities) > 0 { return sl.Entities[0] } return nil } // HasScript returns true if this location has a Lua script func (sl *SpawnLocation) HasScript() bool { return sl.Script != "" } // IsConditional returns true if this location has conditional spawning func (sl *SpawnLocation) IsConditional() bool { return sl.Conditional != 0 } // GetDistance calculates distance from this location to specified coordinates func (sl *SpawnLocation) GetDistance(x, y, z float32, ignoreY bool) float32 { dx := sl.X - x dy := sl.Y - y dz := sl.Z - z if ignoreY { return float32(dx*dx + dz*dz) } return float32(dx*dx + dy*dy + dz*dz) } // SetPosition updates the location's position coordinates func (sl *SpawnLocation) SetPosition(x, y, z float32) { sl.mutex.Lock() defer sl.mutex.Unlock() sl.X = x sl.Y = y sl.Z = z } // SetRotation updates the location's rotation angles func (sl *SpawnLocation) SetRotation(heading, pitch, roll float32) { sl.mutex.Lock() defer sl.mutex.Unlock() sl.Heading = heading sl.Pitch = pitch sl.Roll = roll } // SetOffsets updates the location's position offset ranges func (sl *SpawnLocation) SetOffsets(xOffset, yOffset, zOffset float32) { sl.mutex.Lock() defer sl.mutex.Unlock() sl.XOffset = xOffset sl.YOffset = yOffset sl.ZOffset = zOffset } // RecalculateTotalPercentage recalculates the total spawn percentage // Should be called if spawn entry percentages are modified directly func (sl *SpawnLocation) RecalculateTotalPercentage() { sl.mutex.Lock() defer sl.mutex.Unlock() sl.TotalPercentage = 0.0 for _, entry := range sl.Entities { sl.TotalPercentage += entry.SpawnPercentage } } // Cleanup releases resources used by this spawn location func (sl *SpawnLocation) Cleanup() { sl.mutex.Lock() defer sl.mutex.Unlock() // Clear spawn entries sl.Entities = nil sl.TotalPercentage = 0.0 } // SpawnLocationManager manages collections of spawn locations type SpawnLocationManager struct { locations map[int32]*SpawnLocation // Key is placement ID mutex sync.RWMutex } // NewSpawnLocationManager creates a new spawn location manager func NewSpawnLocationManager() *SpawnLocationManager { return &SpawnLocationManager{ locations: make(map[int32]*SpawnLocation), } } // AddLocation adds a spawn location to the manager func (slm *SpawnLocationManager) AddLocation(placementID int32, location *SpawnLocation) { if location == nil { return } slm.mutex.Lock() defer slm.mutex.Unlock() location.PlacementID = placementID slm.locations[placementID] = location } // RemoveLocation removes a spawn location by placement ID func (slm *SpawnLocationManager) RemoveLocation(placementID int32) bool { slm.mutex.Lock() defer slm.mutex.Unlock() if location, exists := slm.locations[placementID]; exists { location.Cleanup() delete(slm.locations, placementID) return true } return false } // GetLocation retrieves a spawn location by placement ID func (slm *SpawnLocationManager) GetLocation(placementID int32) *SpawnLocation { slm.mutex.RLock() defer slm.mutex.RUnlock() return slm.locations[placementID] } // GetAllLocations returns a copy of all spawn locations func (slm *SpawnLocationManager) GetAllLocations() map[int32]*SpawnLocation { slm.mutex.RLock() defer slm.mutex.RUnlock() locations := make(map[int32]*SpawnLocation) for id, location := range slm.locations { locations[id] = location } return locations } // GetLocationCount returns the number of spawn locations func (slm *SpawnLocationManager) GetLocationCount() int { slm.mutex.RLock() defer slm.mutex.RUnlock() return len(slm.locations) } // GetLocationsInRange returns all locations within the specified distance of coordinates func (slm *SpawnLocationManager) GetLocationsInRange(x, y, z, maxDistance float32, ignoreY bool) []*SpawnLocation { slm.mutex.RLock() defer slm.mutex.RUnlock() var locations []*SpawnLocation maxDistanceSquared := maxDistance * maxDistance for _, location := range slm.locations { distance := location.GetDistance(x, y, z, ignoreY) if distance <= maxDistanceSquared { locations = append(locations, location) } } return locations } // GetLocationsByGridID returns all locations in a specific grid func (slm *SpawnLocationManager) GetLocationsByGridID(gridID int32) []*SpawnLocation { slm.mutex.RLock() defer slm.mutex.RUnlock() var locations []*SpawnLocation for _, location := range slm.locations { if location.GridID == gridID { locations = append(locations, location) } } return locations } // Clear removes all spawn locations func (slm *SpawnLocationManager) Clear() { slm.mutex.Lock() defer slm.mutex.Unlock() // Cleanup all locations for _, location := range slm.locations { location.Cleanup() } slm.locations = make(map[int32]*SpawnLocation) }