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]any { om.mutex.RLock() defer om.mutex.RUnlock() stats := make(map[string]any) 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 }