eq2go/internal/object/manager.go
2025-08-29 18:25:57 -05:00

304 lines
7.2 KiB
Go

package object
import (
"fmt"
"sync"
)
// ObjectManager manages all objects in the game world
type ObjectManager struct {
objects map[int32]*Object // Objects by database ID
objectsByZone map[string][]*Object // Objects grouped by zone
mutex sync.RWMutex // Thread safety
}
// NewObjectManager creates a new object manager
func NewObjectManager() *ObjectManager {
return &ObjectManager{
objects: make(map[int32]*Object),
objectsByZone: make(map[string][]*Object),
}
}
// AddObject adds an object to the manager
func (om *ObjectManager) AddObject(object *Object) error {
if object == nil {
return fmt.Errorf("cannot add nil object")
}
if err := object.Validate(); err != nil {
return fmt.Errorf("invalid object: %w", err)
}
om.mutex.Lock()
defer om.mutex.Unlock()
databaseID := object.GetDatabaseID()
if databaseID == 0 {
return fmt.Errorf("object must have a valid database ID")
}
// Check if object already exists
if _, exists := om.objects[databaseID]; exists {
return fmt.Errorf("object with database ID %d already exists", databaseID)
}
// Add to main index
om.objects[databaseID] = object
// Add to zone index
zoneName := object.GetZone()
if zoneName != "" {
om.objectsByZone[zoneName] = append(om.objectsByZone[zoneName], object)
}
return nil
}
// RemoveObject removes an object from the manager
func (om *ObjectManager) RemoveObject(databaseID int32) error {
om.mutex.Lock()
defer om.mutex.Unlock()
object, exists := om.objects[databaseID]
if !exists {
return fmt.Errorf("object with database ID %d not found", databaseID)
}
// Remove from main index
delete(om.objects, databaseID)
// Remove from zone index
zoneName := object.GetZone()
if zoneName != "" {
objects := om.objectsByZone[zoneName]
for i, obj := range objects {
if obj.GetDatabaseID() == databaseID {
// Remove from slice
om.objectsByZone[zoneName] = append(objects[:i], objects[i+1:]...)
break
}
}
// Clean up empty zone entries
if len(om.objectsByZone[zoneName]) == 0 {
delete(om.objectsByZone, zoneName)
}
}
return nil
}
// GetObject retrieves an object by database ID
func (om *ObjectManager) GetObject(databaseID int32) *Object {
om.mutex.RLock()
defer om.mutex.RUnlock()
return om.objects[databaseID]
}
// GetObjectsByZone retrieves all objects in a specific zone
func (om *ObjectManager) GetObjectsByZone(zoneName string) []*Object {
om.mutex.RLock()
defer om.mutex.RUnlock()
objects := om.objectsByZone[zoneName]
if objects == nil {
return []*Object{}
}
// Return a copy to prevent external modification
result := make([]*Object, len(objects))
copy(result, objects)
return result
}
// GetObjectsByType retrieves objects by their type
func (om *ObjectManager) GetObjectsByType(objectType string) []*Object {
om.mutex.RLock()
defer om.mutex.RUnlock()
var result []*Object
for _, object := range om.objects {
if object.GetObjectType() == objectType {
result = append(result, object)
}
}
return result
}
// GetInteractiveObjects returns all objects that can be interacted with
func (om *ObjectManager) GetInteractiveObjects() []*Object {
om.mutex.RLock()
defer om.mutex.RUnlock()
var result []*Object
for _, object := range om.objects {
if object.CanInteract() {
result = append(result, object)
}
}
return result
}
// GetMerchantObjects returns all merchant objects
func (om *ObjectManager) GetMerchantObjects() []*Object {
om.mutex.RLock()
defer om.mutex.RUnlock()
var result []*Object
for _, object := range om.objects {
if object.IsMerchant() {
result = append(result, object)
}
}
return result
}
// GetTransportObjects returns all transport objects
func (om *ObjectManager) GetTransportObjects() []*Object {
om.mutex.RLock()
defer om.mutex.RUnlock()
var result []*Object
for _, object := range om.objects {
if object.IsTransporter() {
result = append(result, object)
}
}
return result
}
// GetCollectorObjects returns all collector objects
func (om *ObjectManager) GetCollectorObjects() []*Object {
om.mutex.RLock()
defer om.mutex.RUnlock()
var result []*Object
for _, object := range om.objects {
if object.IsCollector() {
result = append(result, object)
}
}
return result
}
// UpdateObjectZone updates an object's zone (called when object moves zones)
func (om *ObjectManager) UpdateObjectZone(databaseID int32, newZoneName string) error {
om.mutex.Lock()
defer om.mutex.Unlock()
object, exists := om.objects[databaseID]
if !exists {
return fmt.Errorf("object with database ID %d not found", databaseID)
}
oldZoneName := object.GetZone()
// Remove from old zone index
if oldZoneName != "" {
objects := om.objectsByZone[oldZoneName]
for i, obj := range objects {
if obj.GetDatabaseID() == databaseID {
om.objectsByZone[oldZoneName] = append(objects[:i], objects[i+1:]...)
break
}
}
// Clean up empty zone entries
if len(om.objectsByZone[oldZoneName]) == 0 {
delete(om.objectsByZone, oldZoneName)
}
}
// Update object zone
object.SetZone(newZoneName)
// Add to new zone index
if newZoneName != "" {
om.objectsByZone[newZoneName] = append(om.objectsByZone[newZoneName], object)
}
return nil
}
// GetObjectCount returns the total number of objects
func (om *ObjectManager) GetObjectCount() int {
om.mutex.RLock()
defer om.mutex.RUnlock()
return len(om.objects)
}
// GetZoneCount returns the number of zones with objects
func (om *ObjectManager) GetZoneCount() int {
om.mutex.RLock()
defer om.mutex.RUnlock()
return len(om.objectsByZone)
}
// GetAllObjects returns all objects (use with caution for large datasets)
func (om *ObjectManager) GetAllObjects() []*Object {
om.mutex.RLock()
defer om.mutex.RUnlock()
result := make([]*Object, 0, len(om.objects))
for _, object := range om.objects {
result = append(result, object)
}
return result
}
// Clear removes all objects from the manager
func (om *ObjectManager) Clear() {
om.mutex.Lock()
defer om.mutex.Unlock()
om.objects = make(map[int32]*Object)
om.objectsByZone = make(map[string][]*Object)
}
// GetObjectStatistics returns statistics about objects in the manager
func (om *ObjectManager) GetObjectStatistics() ObjectStatistics {
om.mutex.RLock()
defer om.mutex.RUnlock()
stats := ObjectStatistics{
TotalObjects: len(om.objects),
ZoneCount: len(om.objectsByZone),
TypeCounts: make(map[string]int),
}
// Count by type
for _, object := range om.objects {
objectType := object.GetObjectType()
stats.TypeCounts[objectType]++
}
// Count interactive objects
for _, object := range om.objects {
if object.CanInteract() {
stats.InteractiveObjects++
}
}
return stats
}
// ObjectStatistics contains statistics about objects
type ObjectStatistics struct {
TotalObjects int `json:"total_objects"`
InteractiveObjects int `json:"interactive_objects"`
ZoneCount int `json:"zone_count"`
TypeCounts map[string]int `json:"type_counts"`
}
// Global object manager instance
var globalObjectManager *ObjectManager
var globalObjectManagerOnce sync.Once
// GetGlobalObjectManager returns the global object manager instance
func GetGlobalObjectManager() *ObjectManager {
globalObjectManagerOnce.Do(func() {
globalObjectManager = NewObjectManager()
})
return globalObjectManager
}