eq2go/internal/object/manager.go

419 lines
11 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
// Zone-based indexing
objectsByZone map[string][]*Object // Objects grouped by zone
// Type-based indexing
interactiveObjects []*Object // Objects that can be interacted with
transportObjects []*Object // Objects that provide transport
merchantObjects []*Object // Objects that are merchants
collectorObjects []*Object // Objects that are collectors
// Thread safety
mutex sync.RWMutex
}
// NewObjectManager creates a new object manager
func NewObjectManager() *ObjectManager {
return &ObjectManager{
objects: make(map[int32]*Object),
objectsByZone: make(map[string][]*Object),
interactiveObjects: make([]*Object, 0),
transportObjects: make([]*Object, 0),
merchantObjects: make([]*Object, 0),
collectorObjects: make([]*Object, 0),
}
}
// AddObject adds an object to the manager
func (om *ObjectManager) AddObject(object *Object) error {
if object == nil {
return fmt.Errorf("cannot add nil object")
}
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 collection
om.objects[databaseID] = object
// Add to zone collection
zoneName := object.GetZoneName()
if zoneName != "" {
om.objectsByZone[zoneName] = append(om.objectsByZone[zoneName], object)
}
// Add to type-based collections
om.updateObjectIndices(object, true)
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 collection
delete(om.objects, databaseID)
// Remove from zone collection
zoneName := object.GetZoneName()
if zoneName != "" {
if zoneObjects, exists := om.objectsByZone[zoneName]; exists {
for i, obj := range zoneObjects {
if obj.GetDatabaseID() == databaseID {
om.objectsByZone[zoneName] = append(zoneObjects[:i], zoneObjects[i+1:]...)
break
}
}
// Clean up empty zone collection
if len(om.objectsByZone[zoneName]) == 0 {
delete(om.objectsByZone, zoneName)
}
}
}
// Remove from type-based collections
om.updateObjectIndices(object, false)
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()
if objects, exists := om.objectsByZone[zoneName]; exists {
// Return a copy to prevent external modification
result := make([]*Object, len(objects))
copy(result, objects)
return result
}
return make([]*Object, 0)
}
// GetInteractiveObjects returns all objects that can be interacted with
func (om *ObjectManager) GetInteractiveObjects() []*Object {
om.mutex.RLock()
defer om.mutex.RUnlock()
result := make([]*Object, len(om.interactiveObjects))
copy(result, om.interactiveObjects)
return result
}
// GetTransportObjects returns all objects that provide transport
func (om *ObjectManager) GetTransportObjects() []*Object {
om.mutex.RLock()
defer om.mutex.RUnlock()
result := make([]*Object, len(om.transportObjects))
copy(result, om.transportObjects)
return result
}
// GetMerchantObjects returns all merchant objects
func (om *ObjectManager) GetMerchantObjects() []*Object {
om.mutex.RLock()
defer om.mutex.RUnlock()
result := make([]*Object, len(om.merchantObjects))
copy(result, om.merchantObjects)
return result
}
// GetCollectorObjects returns all collector objects
func (om *ObjectManager) GetCollectorObjects() []*Object {
om.mutex.RLock()
defer om.mutex.RUnlock()
result := make([]*Object, len(om.collectorObjects))
copy(result, om.collectorObjects)
return result
}
// 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)
}
// GetObjectsByType returns objects filtered by specific criteria
func (om *ObjectManager) GetObjectsByType(objectType string) []*Object {
om.mutex.RLock()
defer om.mutex.RUnlock()
switch objectType {
case "interactive":
result := make([]*Object, len(om.interactiveObjects))
copy(result, om.interactiveObjects)
return result
case "transport":
result := make([]*Object, len(om.transportObjects))
copy(result, om.transportObjects)
return result
case "merchant":
result := make([]*Object, len(om.merchantObjects))
copy(result, om.merchantObjects)
return result
case "collector":
result := make([]*Object, len(om.collectorObjects))
copy(result, om.collectorObjects)
return result
default:
return make([]*Object, 0)
}
}
// FindObjectsInZone finds objects in a zone matching specific criteria
func (om *ObjectManager) FindObjectsInZone(zoneName string, filter func(*Object) bool) []*Object {
om.mutex.RLock()
defer om.mutex.RUnlock()
zoneObjects, exists := om.objectsByZone[zoneName]
if !exists {
return make([]*Object, 0)
}
result := make([]*Object, 0)
for _, obj := range zoneObjects {
if filter == nil || filter(obj) {
result = append(result, obj)
}
}
return result
}
// FindObjectByName finds the first object with a matching name (placeholder)
func (om *ObjectManager) FindObjectByName(name string) *Object {
om.mutex.RLock()
defer om.mutex.RUnlock()
// TODO: Implement name searching when spawn name system is integrated
// For now, return nil
return nil
}
// UpdateObject updates an existing object's properties
func (om *ObjectManager) UpdateObject(databaseID int32, updateFn func(*Object)) 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)
}
// Store old zone for potential reindexing
oldZone := object.GetZoneName()
// Apply updates
updateFn(object)
// Check if zone changed and reindex if necessary
newZone := object.GetZoneName()
if oldZone != newZone {
// Remove from old zone
if oldZone != "" {
if zoneObjects, exists := om.objectsByZone[oldZone]; exists {
for i, obj := range zoneObjects {
if obj.GetDatabaseID() == databaseID {
om.objectsByZone[oldZone] = append(zoneObjects[:i], zoneObjects[i+1:]...)
break
}
}
// Clean up empty zone collection
if len(om.objectsByZone[oldZone]) == 0 {
delete(om.objectsByZone, oldZone)
}
}
}
// Add to new zone
if newZone != "" {
om.objectsByZone[newZone] = append(om.objectsByZone[newZone], object)
}
}
// Update type-based indices
om.rebuildIndicesForObject(object)
return nil
}
// ClearZone removes all objects from a specific zone
func (om *ObjectManager) ClearZone(zoneName string) int {
om.mutex.Lock()
defer om.mutex.Unlock()
zoneObjects, exists := om.objectsByZone[zoneName]
if !exists {
return 0
}
count := len(zoneObjects)
// Remove objects from main collection and indices
for _, obj := range zoneObjects {
databaseID := obj.GetDatabaseID()
delete(om.objects, databaseID)
om.updateObjectIndices(obj, false)
}
// Clear zone collection
delete(om.objectsByZone, zoneName)
return count
}
// GetStatistics returns statistics about objects in the manager
func (om *ObjectManager) GetStatistics() map[string]interface{} {
om.mutex.RLock()
defer om.mutex.RUnlock()
stats := make(map[string]interface{})
stats["total_objects"] = len(om.objects)
stats["zones_with_objects"] = len(om.objectsByZone)
stats["interactive_objects"] = len(om.interactiveObjects)
stats["transport_objects"] = len(om.transportObjects)
stats["merchant_objects"] = len(om.merchantObjects)
stats["collector_objects"] = len(om.collectorObjects)
// Zone breakdown
zoneStats := make(map[string]int)
for zoneName, objects := range om.objectsByZone {
zoneStats[zoneName] = len(objects)
}
stats["objects_by_zone"] = zoneStats
return stats
}
// Shutdown clears all objects and prepares for shutdown
func (om *ObjectManager) Shutdown() {
om.mutex.Lock()
defer om.mutex.Unlock()
om.objects = make(map[int32]*Object)
om.objectsByZone = make(map[string][]*Object)
om.interactiveObjects = make([]*Object, 0)
om.transportObjects = make([]*Object, 0)
om.merchantObjects = make([]*Object, 0)
om.collectorObjects = make([]*Object, 0)
}
// Private helper methods
// updateObjectIndices updates type-based indices for an object
func (om *ObjectManager) updateObjectIndices(object *Object, add bool) {
if add {
// Add to type-based collections
if object.IsClickable() || len(object.GetPrimaryCommands()) > 0 || len(object.GetSecondaryCommands()) > 0 {
om.interactiveObjects = append(om.interactiveObjects, object)
}
if object.GetTransporterID() > 0 {
om.transportObjects = append(om.transportObjects, object)
}
if object.GetMerchantID() > 0 {
om.merchantObjects = append(om.merchantObjects, object)
}
if object.IsCollector() {
om.collectorObjects = append(om.collectorObjects, object)
}
} else {
// Remove from type-based collections
databaseID := object.GetDatabaseID()
om.interactiveObjects = removeObjectFromSlice(om.interactiveObjects, databaseID)
om.transportObjects = removeObjectFromSlice(om.transportObjects, databaseID)
om.merchantObjects = removeObjectFromSlice(om.merchantObjects, databaseID)
om.collectorObjects = removeObjectFromSlice(om.collectorObjects, databaseID)
}
}
// rebuildIndicesForObject rebuilds type-based indices for an object (used after updates)
func (om *ObjectManager) rebuildIndicesForObject(object *Object) {
databaseID := object.GetDatabaseID()
// Remove from all type-based collections
om.interactiveObjects = removeObjectFromSlice(om.interactiveObjects, databaseID)
om.transportObjects = removeObjectFromSlice(om.transportObjects, databaseID)
om.merchantObjects = removeObjectFromSlice(om.merchantObjects, databaseID)
om.collectorObjects = removeObjectFromSlice(om.collectorObjects, databaseID)
// Re-add based on current properties
om.updateObjectIndices(object, true)
}
// removeObjectFromSlice removes an object from a slice by database ID
func removeObjectFromSlice(slice []*Object, databaseID int32) []*Object {
for i, obj := range slice {
if obj.GetDatabaseID() == databaseID {
return append(slice[:i], slice[i+1:]...)
}
}
return slice
}
// Global object manager instance
var globalObjectManager *ObjectManager
var initObjectManagerOnce sync.Once
// GetGlobalObjectManager returns the global object manager (singleton)
func GetGlobalObjectManager() *ObjectManager {
initObjectManagerOnce.Do(func() {
globalObjectManager = NewObjectManager()
})
return globalObjectManager
}