2025-08-06 14:39:39 -05:00

333 lines
8.8 KiB
Go

package region
import (
"fmt"
"log"
"math"
"sync/atomic"
)
// NewRegionManager creates a new region manager for a zone
func NewRegionManager(zoneName string) *RegionManager {
return &RegionManager{
zoneName: zoneName,
regionMaps: NewRegionMapRange(zoneName),
activeNodes: make(map[string]*RegionNode),
playerRegions: make(map[int32]map[string]bool),
}
}
// AddVersionRange adds a version-specific region map
func (rm *RegionManager) AddVersionRange(minVersion, maxVersion int32, regionMap RegionMap) error {
return rm.regionMaps.AddVersionRange(minVersion, maxVersion, regionMap)
}
// GetRegionMap returns the appropriate region map for a client version
func (rm *RegionManager) GetRegionMap(version int32) RegionMap {
return rm.regionMaps.FindRegionByVersion(version)
}
// ReturnRegionType returns the region type at the given location for a client version
func (rm *RegionManager) ReturnRegionType(location [3]float32, gridID int32, version int32) WaterRegionType {
defer func() {
atomic.AddInt64(&rm.regionChecks, 1)
// Update average check time (simplified moving average)
// This would be implemented in a more sophisticated stats system
}()
regionMap := rm.GetRegionMap(version)
if regionMap == nil {
return RegionTypeNormal
}
return regionMap.ReturnRegionType(location, gridID)
}
// InWater checks if the location is in water for a client version
func (rm *RegionManager) InWater(location [3]float32, gridID int32, version int32) bool {
regionMap := rm.GetRegionMap(version)
if regionMap == nil {
return false
}
return regionMap.InWater(location, gridID)
}
// InLava checks if the location is in lava for a client version
func (rm *RegionManager) InLava(location [3]float32, gridID int32, version int32) bool {
regionMap := rm.GetRegionMap(version)
if regionMap == nil {
return false
}
return regionMap.InLava(location, gridID)
}
// InLiquid checks if the location is in any liquid for a client version
func (rm *RegionManager) InLiquid(location [3]float32, version int32) bool {
regionMap := rm.GetRegionMap(version)
if regionMap == nil {
return false
}
return regionMap.InLiquid(location)
}
// InPvP checks if the location is in a PvP area for a client version
func (rm *RegionManager) InPvP(location [3]float32, version int32) bool {
regionMap := rm.GetRegionMap(version)
if regionMap == nil {
return false
}
return regionMap.InPvP(location)
}
// InZoneLine checks if the location is in a zone line for a client version
func (rm *RegionManager) InZoneLine(location [3]float32, version int32) bool {
regionMap := rm.GetRegionMap(version)
if regionMap == nil {
return false
}
return regionMap.InZoneLine(location)
}
// AddRegionNode adds a new region node
func (rm *RegionManager) AddRegionNode(name, envName string, gridID, triggerWidgetID uint32, distance float32, position [3]float32) error {
rm.mutex.Lock()
defer rm.mutex.Unlock()
if _, exists := rm.activeNodes[name]; exists {
return fmt.Errorf("region node '%s' already exists", name)
}
node := &RegionNode{
Name: name,
EnvironmentName: envName,
GridID: gridID,
TriggerWidgetID: triggerWidgetID,
Distance: distance,
Position: position,
Active: true,
PlayerList: make(map[int32]bool),
}
rm.activeNodes[name] = node
atomic.AddInt32(&rm.activeRegions, 1)
log.Printf("[Region] Added region node '%s' at position (%.2f, %.2f, %.2f) in zone '%s'",
name, position[0], position[1], position[2], rm.zoneName)
return nil
}
// RemoveRegionNode removes a region node by name
func (rm *RegionManager) RemoveRegionNode(name string) error {
rm.mutex.Lock()
defer rm.mutex.Unlock()
node, exists := rm.activeNodes[name]
if !exists {
return fmt.Errorf("region node '%s' not found", name)
}
// Remove players from this region
node.mutex.Lock()
for playerID := range node.PlayerList {
rm.removePlayerFromRegion(playerID, name)
}
node.mutex.Unlock()
delete(rm.activeNodes, name)
atomic.AddInt32(&rm.activeRegions, -1)
log.Printf("[Region] Removed region node '%s' from zone '%s'", name, rm.zoneName)
return nil
}
// UpdatePlayerRegions updates the regions for a player based on their position
func (rm *RegionManager) UpdatePlayerRegions(playerID int32, position [3]float32, version int32) {
rm.mutex.RLock()
currentRegions := make(map[string]bool)
if playerRegions, exists := rm.playerRegions[playerID]; exists {
for regionName := range playerRegions {
currentRegions[regionName] = true
}
}
rm.mutex.RUnlock()
newRegions := make(map[string]bool)
// Check all active region nodes
rm.mutex.RLock()
for regionName, node := range rm.activeNodes {
if !node.Active {
continue
}
// Calculate distance to region
dx := position[0] - node.Position[0]
dy := position[1] - node.Position[1]
dz := position[2] - node.Position[2]
distance := float32(math.Sqrt(float64(dx*dx + dy*dy + dz*dz)))
// Check if player is within region
if distance <= node.Distance {
newRegions[regionName] = true
// Add player to region if not already there
if !currentRegions[regionName] {
rm.addPlayerToRegion(playerID, regionName, position)
}
}
}
rm.mutex.RUnlock()
// Remove player from regions they've left
for regionName := range currentRegions {
if !newRegions[regionName] {
rm.removePlayerFromRegion(playerID, regionName)
}
}
// Update player's region list
rm.mutex.Lock()
if len(newRegions) > 0 {
rm.playerRegions[playerID] = newRegions
} else {
delete(rm.playerRegions, playerID)
}
rm.mutex.Unlock()
}
// RemovePlayer removes a player from all regions
func (rm *RegionManager) RemovePlayer(playerID int32) {
rm.mutex.Lock()
defer rm.mutex.Unlock()
if playerRegions, exists := rm.playerRegions[playerID]; exists {
for regionName := range playerRegions {
if node, nodeExists := rm.activeNodes[regionName]; nodeExists {
node.mutex.Lock()
delete(node.PlayerList, playerID)
node.mutex.Unlock()
}
}
delete(rm.playerRegions, playerID)
}
}
// GetActiveRegions returns a list of active region names
func (rm *RegionManager) GetActiveRegions() []string {
rm.mutex.RLock()
defer rm.mutex.RUnlock()
regions := make([]string, 0, len(rm.activeNodes))
for name, node := range rm.activeNodes {
if node.Active {
regions = append(regions, name)
}
}
return regions
}
// GetPlayersInRegion returns a list of player IDs in the specified region
func (rm *RegionManager) GetPlayersInRegion(regionName string) []int32 {
rm.mutex.RLock()
node, exists := rm.activeNodes[regionName]
rm.mutex.RUnlock()
if !exists {
return nil
}
node.mutex.RLock()
defer node.mutex.RUnlock()
players := make([]int32, 0, len(node.PlayerList))
for playerID := range node.PlayerList {
players = append(players, playerID)
}
return players
}
// GetPlayerRegions returns the regions a player is currently in
func (rm *RegionManager) GetPlayerRegions(playerID int32) []string {
rm.mutex.RLock()
defer rm.mutex.RUnlock()
if playerRegions, exists := rm.playerRegions[playerID]; exists {
regions := make([]string, 0, len(playerRegions))
for regionName := range playerRegions {
regions = append(regions, regionName)
}
return regions
}
return nil
}
// GetStats returns current region statistics
func (rm *RegionManager) GetStats() *RegionStats {
rm.mutex.RLock()
defer rm.mutex.RUnlock()
return &RegionStats{
TotalRegionChecks: atomic.LoadInt64(&rm.regionChecks),
TotalRegionTransitions: atomic.LoadInt64(&rm.regionTransitions),
ActiveRegions: atomic.LoadInt32(&rm.activeRegions),
ActivePlayers: int32(len(rm.playerRegions)),
RegionMapsLoaded: int32(rm.regionMaps.GetLoadedMapCount()),
AverageCheckTime: 0.0, // Would be calculated from timing data
}
}
// ResetStats resets all region statistics
func (rm *RegionManager) ResetStats() {
atomic.StoreInt64(&rm.regionChecks, 0)
atomic.StoreInt64(&rm.regionTransitions, 0)
}
// GetZoneName returns the zone name for this region manager
func (rm *RegionManager) GetZoneName() string {
return rm.zoneName
}
// Private methods
func (rm *RegionManager) addPlayerToRegion(playerID int32, regionName string, position [3]float32) {
rm.mutex.RLock()
node, exists := rm.activeNodes[regionName]
rm.mutex.RUnlock()
if exists {
node.mutex.Lock()
node.PlayerList[playerID] = true
node.mutex.Unlock()
atomic.AddInt64(&rm.regionTransitions, 1)
log.Printf("[Region] Player %d entered region '%s' at position (%.2f, %.2f, %.2f)",
playerID, regionName, position[0], position[1], position[2])
}
}
func (rm *RegionManager) removePlayerFromRegion(playerID int32, regionName string) {
rm.mutex.RLock()
node, exists := rm.activeNodes[regionName]
rm.mutex.RUnlock()
if exists {
node.mutex.Lock()
delete(node.PlayerList, playerID)
node.mutex.Unlock()
atomic.AddInt64(&rm.regionTransitions, 1)
log.Printf("[Region] Player %d left region '%s'", playerID, regionName)
}
}