eq2go/internal/spawn/spawn_lists.go

447 lines
12 KiB
Go

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