419 lines
11 KiB
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
|
|
} |