diff --git a/SIMPLIFICATION.md b/SIMPLIFICATION.md index 634c692..cf2a45c 100644 --- a/SIMPLIFICATION.md +++ b/SIMPLIFICATION.md @@ -22,6 +22,7 @@ This document outlines how we successfully simplified the EverQuest II housing p - NPC - NPC/AI - NPC/Race Types +- Object ## Before: Complex Architecture (8 Files, ~2000+ Lines) diff --git a/internal/object/constants.go b/internal/object/constants.go deleted file mode 100644 index 247a180..0000000 --- a/internal/object/constants.go +++ /dev/null @@ -1,35 +0,0 @@ -package object - -// Object constants converted from C++ Object.cpp -const ( - // Object spawn type (from C++ constructor) - ObjectSpawnType = 2 - - // Object appearance defaults (from C++ constructor) - ObjectActivityStatus = 64 // Default activity status - ObjectPosState = 1 // Default position state - ObjectDifficulty = 0 // Default difficulty - - // Object interaction constants - ObjectShowCommandIcon = 1 // Show command icon when interactable -) - -// Object device ID constants -const ( - DeviceIDNone = 0 // No device ID assigned - DeviceIDDefault = 0 // Default device ID -) - -// Object state constants -const ( - ObjectStateInactive = 0 // Object is inactive - ObjectStateActive = 1 // Object is active and usable -) - -// Object interaction types -const ( - InteractionTypeNone = 0 // No interaction available - InteractionTypeCommand = 1 // Command-based interaction - InteractionTypeTransport = 2 // Transport/teleport interaction - InteractionTypeDevice = 3 // Device-based interaction -) diff --git a/internal/object/integration.go b/internal/object/integration.go deleted file mode 100644 index f2d06a6..0000000 --- a/internal/object/integration.go +++ /dev/null @@ -1,387 +0,0 @@ -package object - -import ( - "fmt" - - "eq2emu/internal/spawn" -) - -// ObjectSpawn represents an object that extends spawn functionality -// This properly integrates with the existing spawn system -type ObjectSpawn struct { - *spawn.Spawn // Embed the spawn functionality - - // Object-specific properties - clickable bool // Whether the object can be clicked/interacted with - deviceID int8 // Device ID for interactive objects - - // Merchant properties (duplicated from spawn since fields are unexported) - merchantID int32 - merchantType int8 - merchantMinLevel int32 - merchantMaxLevel int32 - isCollector bool - - // Transport properties (duplicated from spawn since fields are unexported) - transporterID int32 -} - -// NewObjectSpawn creates a new object spawn with default values -func NewObjectSpawn() *ObjectSpawn { - // Create base spawn - baseSpawn := spawn.NewSpawn() - - // Set object-specific spawn properties - baseSpawn.SetSpawnType(ObjectSpawnType) - - // Set object appearance defaults - appearance := baseSpawn.GetAppearanceData() - appearance.ActivityStatus = ObjectActivityStatus - appearance.Pos.State = ObjectPosState - appearance.Difficulty = ObjectDifficulty - // Note: No SetAppearance method, but appearance is modified by reference - - return &ObjectSpawn{ - Spawn: baseSpawn, - clickable: false, - deviceID: DeviceIDNone, - } -} - -// SetClickable sets whether the object can be clicked -func (os *ObjectSpawn) SetClickable(clickable bool) { - os.clickable = clickable -} - -// IsClickable returns whether the object can be clicked -func (os *ObjectSpawn) IsClickable() bool { - return os.clickable -} - -// SetDeviceID sets the device ID for interactive objects -func (os *ObjectSpawn) SetDeviceID(deviceID int8) { - os.deviceID = deviceID -} - -// GetDeviceID returns the device ID -func (os *ObjectSpawn) GetDeviceID() int8 { - return os.deviceID -} - -// IsObject always returns true for object spawns -func (os *ObjectSpawn) IsObject() bool { - return true -} - -// Copy creates a deep copy of the object spawn -func (os *ObjectSpawn) Copy() *ObjectSpawn { - // Create new object spawn with new spawn - newObjectSpawn := NewObjectSpawn() - - // Copy properties from original - newObjectSpawn.clickable = os.clickable - newObjectSpawn.deviceID = os.deviceID - - // Copy spawn properties - newObjectSpawn.SetDatabaseID(os.GetDatabaseID()) - newObjectSpawn.SetID(os.GetID()) - newObjectSpawn.SetName(os.GetName()) - newObjectSpawn.SetLevel(os.GetLevel()) - newObjectSpawn.SetSize(os.GetSize()) - newObjectSpawn.SetSpawnType(os.GetSpawnType()) - newObjectSpawn.SetX(os.GetX()) - newObjectSpawn.SetY(os.GetY(), false) - newObjectSpawn.SetZ(os.GetZ()) - newObjectSpawn.SetHeading(int16(os.GetHeading()), int16(os.GetHeading())) - newObjectSpawn.SetFactionID(os.GetFactionID()) - newObjectSpawn.SetTarget(os.GetTarget()) - newObjectSpawn.SetAlive(os.IsAlive()) - - // Copy merchant properties if they exist - if os.merchantID > 0 { - newObjectSpawn.merchantID = os.merchantID - newObjectSpawn.merchantType = os.merchantType - newObjectSpawn.merchantMinLevel = os.merchantMinLevel - newObjectSpawn.merchantMaxLevel = os.merchantMaxLevel - } - - // Copy transport properties if they exist - if os.transporterID > 0 { - newObjectSpawn.transporterID = os.transporterID - } - - newObjectSpawn.isCollector = os.isCollector - - return newObjectSpawn -} - -// HandleUse processes object interaction by a client -func (os *ObjectSpawn) HandleUse(clientID int32, command string) error { - // Use the base object's HandleUse logic but with spawn integration - object := &Object{} - - // Copy relevant properties for handling - object.clickable = os.clickable - object.deviceID = os.deviceID - object.transporterID = os.transporterID - object.appearanceShowCommandIcon = int8(0) - if os.GetAppearanceData().ShowCommandIcon == 1 { - object.appearanceShowCommandIcon = ObjectShowCommandIcon - } - - // TODO: Copy command lists when they're integrated with spawn system - - return object.HandleUse(clientID, command) -} - -// SetShowCommandIcon sets whether to show the command icon -func (os *ObjectSpawn) SetShowCommandIcon(show bool) { - appearance := os.GetAppearanceData() - if show { - appearance.ShowCommandIcon = ObjectShowCommandIcon - } else { - appearance.ShowCommandIcon = 0 - } - // Appearance is modified by reference, no need to set it back -} - -// ShowsCommandIcon returns whether the command icon is shown -func (os *ObjectSpawn) ShowsCommandIcon() bool { - return os.GetAppearanceData().ShowCommandIcon == ObjectShowCommandIcon -} - -// GetObjectInfo returns comprehensive information about the object spawn -func (os *ObjectSpawn) GetObjectInfo() map[string]any { - info := make(map[string]any) - - // Add spawn info - info["spawn_id"] = os.GetID() - info["database_id"] = os.GetDatabaseID() - info["zone_name"] = os.GetZoneName() - info["spawn_type"] = os.GetSpawnType() - info["size"] = os.GetSize() - - // Add object-specific info - info["clickable"] = os.clickable - info["device_id"] = os.deviceID - info["shows_command_icon"] = os.ShowsCommandIcon() - info["transporter_id"] = os.transporterID - info["merchant_id"] = os.merchantID - info["is_collector"] = os.isCollector - - // Add position info - appearance := os.GetAppearanceData() - info["x"] = appearance.Pos.X - info["y"] = appearance.Pos.Y - info["z"] = appearance.Pos.Z - info["heading"] = appearance.Pos.Dir1 - - return info -} - -// Getter and setter methods for object-specific properties - -// GetMerchantID returns the merchant ID -func (os *ObjectSpawn) GetMerchantID() int32 { - return os.merchantID -} - -// SetMerchantID sets the merchant ID -func (os *ObjectSpawn) SetMerchantID(merchantID int32) { - os.merchantID = merchantID -} - -// GetMerchantType returns the merchant type -func (os *ObjectSpawn) GetMerchantType() int8 { - return os.merchantType -} - -// SetMerchantType sets the merchant type -func (os *ObjectSpawn) SetMerchantType(merchantType int8) { - os.merchantType = merchantType -} - -// GetMerchantMinLevel returns the minimum merchant level -func (os *ObjectSpawn) GetMerchantMinLevel() int8 { - return int8(os.merchantMinLevel) -} - -// GetMerchantMaxLevel returns the maximum merchant level -func (os *ObjectSpawn) GetMerchantMaxLevel() int8 { - return int8(os.merchantMaxLevel) -} - -// SetMerchantLevelRange sets the merchant level range -func (os *ObjectSpawn) SetMerchantLevelRange(minLevel, maxLevel int8) { - os.merchantMinLevel = int32(minLevel) - os.merchantMaxLevel = int32(maxLevel) -} - -// GetTransporterID returns the transporter ID -func (os *ObjectSpawn) GetTransporterID() int32 { - return os.transporterID -} - -// SetTransporterID sets the transporter ID -func (os *ObjectSpawn) SetTransporterID(transporterID int32) { - os.transporterID = transporterID -} - -// IsCollector returns whether this object is a collector -func (os *ObjectSpawn) IsCollector() bool { - return os.isCollector -} - -// SetCollector sets whether this object is a collector -func (os *ObjectSpawn) SetCollector(isCollector bool) { - os.isCollector = isCollector -} - -// GetZoneName returns the zone name (from spawn system) -func (os *ObjectSpawn) GetZoneName() string { - // TODO: Implement when zone system is integrated - // For now return empty string - return "" -} - -// SetZoneName sets the zone name (placeholder for spawn system) -func (os *ObjectSpawn) SetZoneName(zoneName string) { - // TODO: Implement when zone system is integrated - // This would be handled by the spawn system -} - -// ObjectSpawnManager manages object spawns specifically -type ObjectSpawnManager struct { - objects map[int32]*ObjectSpawn // Object spawns by spawn ID - // TODO: Add reference to spawn manager when it exists -} - -// NewObjectSpawnManager creates a new object spawn manager -func NewObjectSpawnManager() *ObjectSpawnManager { - return &ObjectSpawnManager{ - objects: make(map[int32]*ObjectSpawn), - } -} - -// AddObjectSpawn adds an object spawn to the manager -func (osm *ObjectSpawnManager) AddObjectSpawn(objectSpawn *ObjectSpawn) error { - if objectSpawn == nil { - return fmt.Errorf("cannot add nil object spawn") - } - - // Add to object tracking - osm.objects[objectSpawn.GetID()] = objectSpawn - - // TODO: Add to global spawn manager when it exists - return nil -} - -// RemoveObjectSpawn removes an object spawn from the manager -func (osm *ObjectSpawnManager) RemoveObjectSpawn(spawnID int32) error { - // Remove from object tracking - if _, exists := osm.objects[spawnID]; !exists { - return fmt.Errorf("object spawn %d not found", spawnID) - } - - delete(osm.objects, spawnID) - - // TODO: Remove from global spawn manager when it exists - return nil -} - -// GetObjectSpawn retrieves an object spawn by ID -func (osm *ObjectSpawnManager) GetObjectSpawn(spawnID int32) *ObjectSpawn { - return osm.objects[spawnID] -} - -// GetObjectSpawnsByZone returns all object spawns in a zone -func (osm *ObjectSpawnManager) GetObjectSpawnsByZone(zoneName string) []*ObjectSpawn { - result := make([]*ObjectSpawn, 0) - - // TODO: Filter by zone when zone system is implemented - // For now, return empty slice - return result -} - -// GetInteractiveObjectSpawns returns all interactive object spawns -func (osm *ObjectSpawnManager) GetInteractiveObjectSpawns() []*ObjectSpawn { - result := make([]*ObjectSpawn, 0) - - for _, objectSpawn := range osm.objects { - if objectSpawn.IsClickable() || objectSpawn.ShowsCommandIcon() { - result = append(result, objectSpawn) - } - } - - return result -} - -// ProcessObjectInteraction handles interaction with an object spawn -func (osm *ObjectSpawnManager) ProcessObjectInteraction(spawnID, clientID int32, command string) error { - objectSpawn := osm.GetObjectSpawn(spawnID) - if objectSpawn == nil { - return fmt.Errorf("object spawn %d not found", spawnID) - } - - return objectSpawn.HandleUse(clientID, command) -} - -// ConvertSpawnToObject converts a regular spawn to an object spawn (if applicable) -func ConvertSpawnToObject(spawn *spawn.Spawn) *ObjectSpawn { - if spawn.GetSpawnType() != ObjectSpawnType { - return nil - } - - objectSpawn := &ObjectSpawn{ - Spawn: spawn, - clickable: false, // Default, should be loaded from data - deviceID: DeviceIDNone, - } - - // Set clickable based on appearance flags or other indicators - appearance := spawn.GetAppearanceData() - if appearance.ShowCommandIcon == ObjectShowCommandIcon { - objectSpawn.clickable = true - } - - return objectSpawn -} - -// LoadObjectSpawnFromData loads object spawn data from database/config -// This would be called when loading spawns from the database -func LoadObjectSpawnFromData(spawnData map[string]any) *ObjectSpawn { - objectSpawn := NewObjectSpawn() - - // Load basic spawn data - if databaseID, ok := spawnData["database_id"].(int32); ok { - objectSpawn.SetDatabaseID(databaseID) - } - - if zoneName, ok := spawnData["zone"].(string); ok { - objectSpawn.SetZoneName(zoneName) - } - - // Load object-specific data - if clickable, ok := spawnData["clickable"].(bool); ok { - objectSpawn.SetClickable(clickable) - } - - if deviceID, ok := spawnData["device_id"].(int8); ok { - objectSpawn.SetDeviceID(deviceID) - } - - // Load position data - if x, ok := spawnData["x"].(float32); ok { - objectSpawn.SetX(x) - } - if y, ok := spawnData["y"].(float32); ok { - objectSpawn.SetY(y, false) - } - if z, ok := spawnData["z"].(float32); ok { - objectSpawn.SetZ(z) - } - - // TODO: Load other properties as needed - - return objectSpawn -} diff --git a/internal/object/interfaces.go b/internal/object/interfaces.go index 4f71d72..8280274 100644 --- a/internal/object/interfaces.go +++ b/internal/object/interfaces.go @@ -1,320 +1,53 @@ package object -import ( - "time" -) - -// SpawnInterface defines the interface that spawn-based objects must implement -// This allows objects to integrate with systems expecting spawn entities -type SpawnInterface interface { - // Basic identification - GetID() int32 - GetDatabaseID() int32 - - // Zone and positioning - GetZoneName() string - SetZoneName(string) - GetX() float32 - GetY() float32 - GetZ() float32 - GetHeading() float32 - - // Spawn properties - GetSpawnType() int8 - SetSpawnType(int8) - GetSize() int16 - SetSize(int16) - - // State flags - IsAlive() bool - SetAlive(bool) - IsRunning() bool - SetRunning(bool) - - // Entity properties for spell/trade integration - GetFactionID() int32 - SetFactionID(int32) - GetTarget() int32 - SetTarget(int32) -} - -// ObjectInterface defines the interface for interactive objects +// ObjectInterface defines the core interface for interactive objects type ObjectInterface interface { - SpawnInterface - - // Object-specific properties + // Object identification IsObject() bool + GetObjectType() string + + // Interaction capabilities IsClickable() bool SetClickable(bool) + CanInteract() bool + GetInteractionType() int + + // Device functionality GetDeviceID() int8 SetDeviceID(int8) - // Interaction - HandleUse(clientID int32, command string) error - ShowsCommandIcon() bool - SetShowCommandIcon(bool) - // Merchant functionality + IsMerchant() bool GetMerchantID() int32 SetMerchantID(int32) GetMerchantType() int8 SetMerchantType(int8) - IsCollector() bool - SetCollector(bool) // Transport functionality + IsTransporter() bool GetTransporterID() int32 SetTransporterID(int32) - // Copying - Copy() *ObjectSpawn + // Collector functionality + IsCollector() bool + SetCollector(bool) + + // Validation + Validate() error } -// EntityInterface defines the interface for entities in trade/spell systems -// ObjectSpawn implements this to integrate with existing systems -type EntityInterface interface { - GetID() int32 - GetName() string - IsPlayer() bool - IsBot() bool - HasCoins(amount int64) bool - GetClientVersion() int32 -} - -// ItemInterface defines the interface for items that objects might provide -// This allows objects to act as item sources (merchants, containers, etc.) -type ItemInterface interface { - GetID() int32 - GetName() string - GetQuantity() int32 - GetIcon(version int32) int32 - IsNoTrade() bool - IsHeirloom() bool - IsAttuned() bool - GetCreationTime() time.Time - GetGroupCharacterIDs() []int32 -} - -// ObjectSpawnAsEntity is an adapter that makes ObjectSpawn implement EntityInterface -// This allows objects to be used in trade and spell systems -type ObjectSpawnAsEntity struct { - *ObjectSpawn - name string - isPlayer bool - isBot bool - coinsAmount int64 - clientVersion int32 -} - -// NewObjectSpawnAsEntity creates an entity adapter for an object spawn -func NewObjectSpawnAsEntity(objectSpawn *ObjectSpawn) *ObjectSpawnAsEntity { - return &ObjectSpawnAsEntity{ - ObjectSpawn: objectSpawn, - name: "Object", // Default name, should be loaded from data - isPlayer: false, - isBot: false, - coinsAmount: 0, - clientVersion: 1000, - } -} - -// EntityInterface implementation for ObjectSpawnAsEntity - -// GetName returns the object's name -func (osae *ObjectSpawnAsEntity) GetName() string { - return osae.name -} - -// SetName sets the object's name -func (osae *ObjectSpawnAsEntity) SetName(name string) { - osae.name = name -} - -// IsPlayer returns false for objects -func (osae *ObjectSpawnAsEntity) IsPlayer() bool { - return osae.isPlayer -} - -// IsBot returns whether this object behaves like a bot -func (osae *ObjectSpawnAsEntity) IsBot() bool { - return osae.isBot -} - -// SetBot sets whether this object behaves like a bot -func (osae *ObjectSpawnAsEntity) SetBot(isBot bool) { - osae.isBot = isBot -} - -// HasCoins returns whether the object has sufficient coins (for merchant objects) -func (osae *ObjectSpawnAsEntity) HasCoins(amount int64) bool { - return osae.coinsAmount >= amount -} - -// SetCoins sets the object's coin amount (for merchant objects) -func (osae *ObjectSpawnAsEntity) SetCoins(amount int64) { - osae.coinsAmount = amount -} - -// GetClientVersion returns the client version (default for objects) -func (osae *ObjectSpawnAsEntity) GetClientVersion() int32 { - return osae.clientVersion -} - -// SetClientVersion sets the client version -func (osae *ObjectSpawnAsEntity) SetClientVersion(version int32) { - osae.clientVersion = version -} - -// ObjectItem represents an item provided by an object (merchants, containers, etc.) -type ObjectItem struct { - id int32 - name string - quantity int32 - iconID int32 - noTrade bool - heirloom bool - attuned bool - creationTime time.Time - groupCharacterIDs []int32 -} - -// NewObjectItem creates a new object item -func NewObjectItem(id int32, name string, quantity int32) *ObjectItem { - return &ObjectItem{ - id: id, - name: name, - quantity: quantity, - iconID: 0, - noTrade: false, - heirloom: false, - attuned: false, - creationTime: time.Now(), - groupCharacterIDs: make([]int32, 0), - } -} - -// ItemInterface implementation for ObjectItem - -// GetID returns the item ID -func (oi *ObjectItem) GetID() int32 { - return oi.id -} - -// GetName returns the item name -func (oi *ObjectItem) GetName() string { - return oi.name -} - -// GetQuantity returns the item quantity -func (oi *ObjectItem) GetQuantity() int32 { - return oi.quantity -} - -// SetQuantity sets the item quantity -func (oi *ObjectItem) SetQuantity(quantity int32) { - oi.quantity = quantity -} - -// GetIcon returns the item icon ID -func (oi *ObjectItem) GetIcon(version int32) int32 { - return oi.iconID -} - -// SetIcon sets the item icon ID -func (oi *ObjectItem) SetIcon(iconID int32) { - oi.iconID = iconID -} - -// IsNoTrade returns whether the item is no-trade -func (oi *ObjectItem) IsNoTrade() bool { - return oi.noTrade -} - -// SetNoTrade sets whether the item is no-trade -func (oi *ObjectItem) SetNoTrade(noTrade bool) { - oi.noTrade = noTrade -} - -// IsHeirloom returns whether the item is heirloom -func (oi *ObjectItem) IsHeirloom() bool { - return oi.heirloom -} - -// SetHeirloom sets whether the item is heirloom -func (oi *ObjectItem) SetHeirloom(heirloom bool) { - oi.heirloom = heirloom -} - -// IsAttuned returns whether the item is attuned -func (oi *ObjectItem) IsAttuned() bool { - return oi.attuned -} - -// SetAttuned sets whether the item is attuned -func (oi *ObjectItem) SetAttuned(attuned bool) { - oi.attuned = attuned -} - -// GetCreationTime returns when the item was created -func (oi *ObjectItem) GetCreationTime() time.Time { - return oi.creationTime -} - -// SetCreationTime sets when the item was created -func (oi *ObjectItem) SetCreationTime(creationTime time.Time) { - oi.creationTime = creationTime -} - -// GetGroupCharacterIDs returns the group character IDs for heirloom sharing -func (oi *ObjectItem) GetGroupCharacterIDs() []int32 { - return oi.groupCharacterIDs -} - -// SetGroupCharacterIDs sets the group character IDs for heirloom sharing -func (oi *ObjectItem) SetGroupCharacterIDs(characterIDs []int32) { - oi.groupCharacterIDs = make([]int32, len(characterIDs)) - copy(oi.groupCharacterIDs, characterIDs) -} - -// Utility functions for integration - -// CreateMerchantObjectSpawn creates an object spawn configured as a merchant -func CreateMerchantObjectSpawn(merchantID int32, merchantType int8) *ObjectSpawn { - objectSpawn := NewObjectSpawn() - objectSpawn.SetMerchantID(merchantID) - objectSpawn.SetMerchantType(merchantType) - objectSpawn.SetClickable(true) - objectSpawn.SetShowCommandIcon(true) - - return objectSpawn -} - -// CreateTransportObjectSpawn creates an object spawn configured as a transporter -func CreateTransportObjectSpawn(transporterID int32) *ObjectSpawn { - objectSpawn := NewObjectSpawn() - objectSpawn.SetTransporterID(transporterID) - objectSpawn.SetClickable(true) - objectSpawn.SetShowCommandIcon(true) - - return objectSpawn -} - -// CreateDeviceObjectSpawn creates an object spawn configured as an interactive device -func CreateDeviceObjectSpawn(deviceID int8) *ObjectSpawn { - objectSpawn := NewObjectSpawn() - objectSpawn.SetDeviceID(deviceID) - objectSpawn.SetClickable(true) - objectSpawn.SetShowCommandIcon(true) - - return objectSpawn -} - -// CreateCollectorObjectSpawn creates an object spawn configured as a collector -func CreateCollectorObjectSpawn() *ObjectSpawn { - objectSpawn := NewObjectSpawn() - objectSpawn.SetCollector(true) - objectSpawn.SetClickable(true) - objectSpawn.SetShowCommandIcon(true) - - return objectSpawn -} +// ObjectManager interface for dependency injection +type ObjectManagerInterface interface { + AddObject(*Object) error + RemoveObject(int32) error + GetObject(int32) *Object + GetObjectsByZone(string) []*Object + GetObjectsByType(string) []*Object + GetInteractiveObjects() []*Object + GetMerchantObjects() []*Object + GetTransportObjects() []*Object + GetCollectorObjects() []*Object + GetObjectCount() int + Clear() + GetObjectStatistics() ObjectStatistics +} \ No newline at end of file diff --git a/internal/object/manager.go b/internal/object/manager.go index ee51676..ef76d1a 100644 --- a/internal/object/manager.go +++ b/internal/object/manager.go @@ -7,30 +7,16 @@ import ( // ObjectManager manages all objects in the game world type ObjectManager struct { - objects map[int32]*Object // Objects by database ID - - // Zone-based indexing + objects map[int32]*Object // Objects by database ID 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 + 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), - interactiveObjects: make([]*Object, 0), - transportObjects: make([]*Object, 0), - merchantObjects: make([]*Object, 0), - collectorObjects: make([]*Object, 0), + objects: make(map[int32]*Object), + objectsByZone: make(map[string][]*Object), } } @@ -40,6 +26,10 @@ func (om *ObjectManager) AddObject(object *Object) error { 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() @@ -53,18 +43,15 @@ func (om *ObjectManager) AddObject(object *Object) error { return fmt.Errorf("object with database ID %d already exists", databaseID) } - // Add to main collection + // Add to main index om.objects[databaseID] = object - // Add to zone collection - zoneName := object.GetZoneName() + // Add to zone index + zoneName := object.GetZone() if zoneName != "" { om.objectsByZone[zoneName] = append(om.objectsByZone[zoneName], object) } - // Add to type-based collections - om.updateObjectIndices(object, true) - return nil } @@ -78,29 +65,26 @@ func (om *ObjectManager) RemoveObject(databaseID int32) error { return fmt.Errorf("object with database ID %d not found", databaseID) } - // Remove from main collection + // Remove from main index delete(om.objects, databaseID) - // Remove from zone collection - zoneName := object.GetZoneName() + // Remove from zone index + zoneName := object.GetZone() 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) + 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 } } - } - // Remove from type-based collections - om.updateObjectIndices(object, false) + // Clean up empty zone entries + if len(om.objectsByZone[zoneName]) == 0 { + delete(om.objectsByZone, zoneName) + } + } return nil } @@ -109,7 +93,6 @@ func (om *ObjectManager) RemoveObject(databaseID int32) error { func (om *ObjectManager) GetObject(databaseID int32) *Object { om.mutex.RLock() defer om.mutex.RUnlock() - return om.objects[databaseID] } @@ -118,14 +101,29 @@ 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 + objects := om.objectsByZone[zoneName] + if objects == nil { + return []*Object{} } - return make([]*Object, 0) + // 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 @@ -133,18 +131,12 @@ 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) + var result []*Object + for _, object := range om.objects { + if object.CanInteract() { + result = append(result, object) + } + } return result } @@ -153,8 +145,26 @@ func (om *ObjectManager) GetMerchantObjects() []*Object { om.mutex.RLock() defer om.mutex.RUnlock() - result := make([]*Object, len(om.merchantObjects)) - copy(result, om.merchantObjects) + 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 } @@ -163,86 +173,17 @@ 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) + var result []*Object + for _, object := range om.objects { + if object.IsCollector() { + result = append(result, object) } } - 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 { +// 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() @@ -251,169 +192,113 @@ func (om *ObjectManager) UpdateObject(databaseID int32, updateFn func(*Object)) return fmt.Errorf("object with database ID %d not found", databaseID) } - // Store old zone for potential reindexing - oldZone := object.GetZoneName() + oldZoneName := object.GetZone() - // 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) - } + // 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 } } - // Add to new zone - if newZone != "" { - om.objectsByZone[newZone] = append(om.objectsByZone[newZone], object) + // Clean up empty zone entries + if len(om.objectsByZone[oldZoneName]) == 0 { + delete(om.objectsByZone, oldZoneName) } } - // Update type-based indices - om.rebuildIndicesForObject(object) + // Update object zone + object.SetZone(newZoneName) + + // Add to new zone index + if newZoneName != "" { + om.objectsByZone[newZoneName] = append(om.objectsByZone[newZoneName], 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 +// GetObjectCount returns the total number of objects +func (om *ObjectManager) GetObjectCount() int { + om.mutex.RLock() + defer om.mutex.RUnlock() + return len(om.objects) } -// GetStatistics returns statistics about objects in the manager -func (om *ObjectManager) GetStatistics() map[string]any { +// 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() - 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) + result := make([]*Object, 0, len(om.objects)) + for _, object := range om.objects { + result = append(result, object) } - stats["objects_by_zone"] = zoneStats - - return stats + return result } -// Shutdown clears all objects and prepares for shutdown -func (om *ObjectManager) Shutdown() { +// 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) - om.interactiveObjects = make([]*Object, 0) - om.transportObjects = make([]*Object, 0) - om.merchantObjects = make([]*Object, 0) - om.collectorObjects = make([]*Object, 0) } -// Private helper methods +// GetObjectStatistics returns statistics about objects in the manager +func (om *ObjectManager) GetObjectStatistics() ObjectStatistics { + om.mutex.RLock() + defer om.mutex.RUnlock() -// 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) + stats := ObjectStatistics{ + TotalObjects: len(om.objects), + ZoneCount: len(om.objectsByZone), + TypeCounts: make(map[string]int), } -} -// rebuildIndicesForObject rebuilds type-based indices for an object (used after updates) -func (om *ObjectManager) rebuildIndicesForObject(object *Object) { - databaseID := object.GetDatabaseID() + // Count by type + for _, object := range om.objects { + objectType := object.GetObjectType() + stats.TypeCounts[objectType]++ + } - // 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:]...) + // Count interactive objects + for _, object := range om.objects { + if object.CanInteract() { + stats.InteractiveObjects++ } } - return slice + + 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 initObjectManagerOnce sync.Once +var globalObjectManagerOnce sync.Once -// GetGlobalObjectManager returns the global object manager (singleton) +// GetGlobalObjectManager returns the global object manager instance func GetGlobalObjectManager() *ObjectManager { - initObjectManagerOnce.Do(func() { + globalObjectManagerOnce.Do(func() { globalObjectManager = NewObjectManager() }) return globalObjectManager -} +} \ No newline at end of file diff --git a/internal/object/object.go b/internal/object/object.go index 9a7b226..83ac42d 100644 --- a/internal/object/object.go +++ b/internal/object/object.go @@ -2,81 +2,87 @@ package object import ( "fmt" - "math/rand" - "strings" "sync" + + "eq2emu/internal/spawn" +) + +// Object constants +const ( + // Object spawn type + ObjectSpawnType = 2 + + // Object appearance defaults + ObjectActivityStatus = 64 + ObjectPosState = 1 + ObjectDifficulty = 0 + + // Object interaction constants + ObjectShowCommandIcon = 1 + + // Object states + ObjectStateInactive = 0 + ObjectStateActive = 1 + + // Interaction types + InteractionTypeNone = 0 + InteractionTypeCommand = 1 + InteractionTypeTransport = 2 + InteractionTypeDevice = 3 ) // Object represents a game object that extends spawn functionality -// Converted from C++ Object class type Object struct { - // Embed spawn functionality - TODO: Use actual spawn.Spawn when integrated - // spawn.Spawn + *spawn.Spawn // Embed spawn functionality // Object-specific properties - clickable bool // Whether the object can be clicked/interacted with - zoneName string // Name of the zone this object belongs to - deviceID int8 // Device ID for interactive objects + clickable bool // Whether the object can be clicked/interacted with + deviceID int8 // Device ID for interactive objects - // Inherited spawn properties (placeholder until spawn integration) - databaseID int32 - size int16 - sizeOffset int8 - merchantID int32 - merchantType int8 - merchantMinLevel int8 - merchantMaxLevel int8 - isCollector bool - factionID int32 - totalHP int32 - totalPower int32 - currentHP int32 - currentPower int32 - transporterID int32 - soundsDisabled bool - omittedByDBFlag bool - lootTier int8 - lootDropType int8 - spawnScript string - spawnScriptSetDB bool - primaryCommandListID int32 - secondaryCommandListID int32 + // Merchant properties + merchantID int32 + merchantType int8 + merchantMinLevel int32 + merchantMaxLevel int32 + isCollector bool - // Appearance data placeholder - TODO: Use actual appearance struct - appearanceActivityStatus int8 - appearancePosState int8 - appearanceDifficulty int8 - appearanceShowCommandIcon int8 + // Transport properties + transporterID int32 - // Command lists - TODO: Use actual command structures - primaryCommands []string - secondaryCommands []string + // Zone tracking (since spawn doesn't expose zone methods yet) + zoneName string // Thread safety mutex sync.RWMutex } // NewObject creates a new object with default values -// Converted from C++ Object::Object constructor func NewObject() *Object { + // Create base spawn + baseSpawn := spawn.NewSpawn() + + // Set object-specific spawn properties + baseSpawn.SetSpawnType(ObjectSpawnType) + + // Set object appearance defaults + appearance := baseSpawn.GetAppearanceData() + appearance.ActivityStatus = ObjectActivityStatus + appearance.Pos.State = ObjectPosState + appearance.Difficulty = ObjectDifficulty + return &Object{ - clickable: false, - zoneName: "", - deviceID: DeviceIDNone, - appearanceActivityStatus: ObjectActivityStatus, - appearancePosState: ObjectPosState, - appearanceDifficulty: ObjectDifficulty, - appearanceShowCommandIcon: 0, - primaryCommands: make([]string, 0), - secondaryCommands: make([]string, 0), + Spawn: baseSpawn, + clickable: false, + deviceID: 0, + zoneName: "", } } -// SetClickable sets whether the object can be clicked -func (o *Object) SetClickable(clickable bool) { - o.mutex.Lock() - defer o.mutex.Unlock() - o.clickable = clickable +// Object-specific methods + +// IsObject returns true (implements ObjectInterface) +func (o *Object) IsObject() bool { + return true } // IsClickable returns whether the object can be clicked @@ -86,192 +92,41 @@ func (o *Object) IsClickable() bool { return o.clickable } -// SetZone sets the zone name for this object -// Converted from C++ Object::SetZone -func (o *Object) SetZone(zoneName string) { +// SetClickable sets whether the object can be clicked +func (o *Object) SetClickable(clickable bool) { o.mutex.Lock() defer o.mutex.Unlock() - o.zoneName = zoneName -} + o.clickable = clickable -// GetZoneName returns the zone name for this object -func (o *Object) GetZoneName() string { - o.mutex.RLock() - defer o.mutex.RUnlock() - return o.zoneName -} - -// SetDeviceID sets the device ID for interactive objects -// Converted from C++ Object::SetDeviceID -func (o *Object) SetDeviceID(deviceID int8) { - o.mutex.Lock() - defer o.mutex.Unlock() - o.deviceID = deviceID + // Update appearance to show/hide command icon + if clickable { + o.GetAppearanceData().ShowCommandIcon = ObjectShowCommandIcon + } else { + o.GetAppearanceData().ShowCommandIcon = 0 + } } // GetDeviceID returns the device ID -// Converted from C++ Object::GetDeviceID func (o *Object) GetDeviceID() int8 { o.mutex.RLock() defer o.mutex.RUnlock() return o.deviceID } -// IsObject always returns true for Object instances -// Converted from C++ Object::IsObject -func (o *Object) IsObject() bool { - return true -} - -// Copy creates a deep copy of the object -// Converted from C++ Object::Copy -func (o *Object) Copy() *Object { - o.mutex.RLock() - defer o.mutex.RUnlock() - - newObject := NewObject() - - // Copy basic properties - newObject.clickable = o.clickable - newObject.zoneName = o.zoneName - newObject.deviceID = o.deviceID - newObject.databaseID = o.databaseID - newObject.merchantID = o.merchantID - newObject.merchantType = o.merchantType - newObject.merchantMinLevel = o.merchantMinLevel - newObject.merchantMaxLevel = o.merchantMaxLevel - newObject.isCollector = o.isCollector - newObject.factionID = o.factionID - newObject.totalHP = o.totalHP - newObject.totalPower = o.totalPower - newObject.currentHP = o.currentHP - newObject.currentPower = o.currentPower - newObject.transporterID = o.transporterID - newObject.soundsDisabled = o.soundsDisabled - newObject.omittedByDBFlag = o.omittedByDBFlag - newObject.lootTier = o.lootTier - newObject.lootDropType = o.lootDropType - newObject.spawnScript = o.spawnScript - newObject.spawnScriptSetDB = o.spawnScriptSetDB - newObject.primaryCommandListID = o.primaryCommandListID - newObject.secondaryCommandListID = o.secondaryCommandListID - - // Copy appearance data - newObject.appearanceActivityStatus = o.appearanceActivityStatus - newObject.appearancePosState = o.appearancePosState - newObject.appearanceDifficulty = o.appearanceDifficulty - newObject.appearanceShowCommandIcon = o.appearanceShowCommandIcon - - // Handle size with random offset (from C++ logic) - if o.sizeOffset > 0 { - offset := o.sizeOffset + 1 - tmpSize := int32(o.size) + (rand.Int31n(int32(offset)) - rand.Int31n(int32(offset))) - if tmpSize < 0 { - tmpSize = 1 - } else if tmpSize >= 0xFFFF { - tmpSize = 0xFFFF - } - newObject.size = int16(tmpSize) - } else { - newObject.size = o.size - } - - // Copy command lists - newObject.primaryCommands = make([]string, len(o.primaryCommands)) - copy(newObject.primaryCommands, o.primaryCommands) - newObject.secondaryCommands = make([]string, len(o.secondaryCommands)) - copy(newObject.secondaryCommands, o.secondaryCommands) - - return newObject -} - -// HandleUse processes object interaction by a client -// Converted from C++ Object::HandleUse -func (o *Object) HandleUse(clientID int32, command string) error { - o.mutex.RLock() - defer o.mutex.RUnlock() - - // TODO: Implement transport destination handling when zone system is available - // This would check for transporter ID and process teleportation - if o.transporterID > 0 { - // Handle transport logic - return o.handleTransport(clientID) - } - - // Handle command-based interaction - if len(command) > 0 && o.appearanceShowCommandIcon == ObjectShowCommandIcon { - return o.handleCommand(clientID, command) - } - - return fmt.Errorf("object is not interactive") -} - -// handleTransport processes transport/teleport functionality -func (o *Object) handleTransport(clientID int32) error { - // TODO: Implement when zone and transport systems are available - // This would: - // 1. Get transport destinations for this object - // 2. Present options to the client - // 3. Process teleportation request - - return fmt.Errorf("transport system not yet implemented") -} - -// handleCommand processes command-based object interaction -func (o *Object) handleCommand(clientID int32, command string) error { - // TODO: Implement when entity command system is available - // This would: - // 1. Find the entity command by name - // 2. Validate client permissions - // 3. Execute the command - - command = strings.TrimSpace(strings.ToLower(command)) - - // Check if command exists in primary or secondary commands - for _, cmd := range o.primaryCommands { - if strings.ToLower(cmd) == command { - return o.executeCommand(clientID, cmd) - } - } - - for _, cmd := range o.secondaryCommands { - if strings.ToLower(cmd) == command { - return o.executeCommand(clientID, cmd) - } - } - - return fmt.Errorf("command '%s' not found", command) -} - -// executeCommand executes a specific command for a client -func (o *Object) executeCommand(clientID int32, command string) error { - // TODO: Implement actual command execution when command system is available - // For now, just return success for valid commands - return nil -} - -// Serialize returns packet data for this object -// Converted from C++ Object::serialize -func (o *Object) Serialize(playerID int32, version int16) ([]byte, error) { - // TODO: Implement actual serialization when packet system is available - // This would call the spawn serialization method - return nil, fmt.Errorf("serialization not yet implemented") -} - -// Getter/Setter methods for inherited spawn properties - -// SetCollector sets whether this object is a collector -func (o *Object) SetCollector(isCollector bool) { +// SetDeviceID sets the device ID +func (o *Object) SetDeviceID(deviceID int8) { o.mutex.Lock() defer o.mutex.Unlock() - o.isCollector = isCollector + o.deviceID = deviceID } -// IsCollector returns whether this object is a collector -func (o *Object) IsCollector() bool { +// Merchant methods + +// GetMerchantID returns the merchant ID +func (o *Object) GetMerchantID() int32 { o.mutex.RLock() defer o.mutex.RUnlock() - return o.isCollector + return o.merchantID } // SetMerchantID sets the merchant ID @@ -281,11 +136,11 @@ func (o *Object) SetMerchantID(merchantID int32) { o.merchantID = merchantID } -// GetMerchantID returns the merchant ID -func (o *Object) GetMerchantID() int32 { +// GetMerchantType returns the merchant type +func (o *Object) GetMerchantType() int8 { o.mutex.RLock() defer o.mutex.RUnlock() - return o.merchantID + return o.merchantType } // SetMerchantType sets the merchant type @@ -295,179 +150,34 @@ func (o *Object) SetMerchantType(merchantType int8) { o.merchantType = merchantType } -// GetMerchantType returns the merchant type -func (o *Object) GetMerchantType() int8 { +// IsMerchant returns whether this object is a merchant +func (o *Object) IsMerchant() bool { o.mutex.RLock() defer o.mutex.RUnlock() - return o.merchantType + return o.merchantID > 0 } -// SetMerchantLevelRange sets the merchant level range -func (o *Object) SetMerchantLevelRange(minLevel, maxLevel int8) { +// IsCollector returns whether this object is a collector +func (o *Object) IsCollector() bool { + o.mutex.RLock() + defer o.mutex.RUnlock() + return o.isCollector +} + +// SetCollector sets whether this object is a collector +func (o *Object) SetCollector(isCollector bool) { o.mutex.Lock() defer o.mutex.Unlock() - o.merchantMinLevel = minLevel - o.merchantMaxLevel = maxLevel + o.isCollector = isCollector } -// GetMerchantMinLevel returns the minimum merchant level -func (o *Object) GetMerchantMinLevel() int8 { +// Transport methods + +// GetTransporterID returns the transporter ID +func (o *Object) GetTransporterID() int32 { o.mutex.RLock() defer o.mutex.RUnlock() - return o.merchantMinLevel -} - -// GetMerchantMaxLevel returns the maximum merchant level -func (o *Object) GetMerchantMaxLevel() int8 { - o.mutex.RLock() - defer o.mutex.RUnlock() - return o.merchantMaxLevel -} - -// SetSize sets the object size -func (o *Object) SetSize(size int16) { - o.mutex.Lock() - defer o.mutex.Unlock() - o.size = size -} - -// GetSize returns the object size -func (o *Object) GetSize() int16 { - o.mutex.RLock() - defer o.mutex.RUnlock() - return o.size -} - -// SetSizeOffset sets the size randomization offset -func (o *Object) SetSizeOffset(offset int8) { - o.mutex.Lock() - defer o.mutex.Unlock() - o.sizeOffset = offset -} - -// GetSizeOffset returns the size randomization offset -func (o *Object) GetSizeOffset() int8 { - o.mutex.RLock() - defer o.mutex.RUnlock() - return o.sizeOffset -} - -// SetPrimaryCommands sets the primary command list -func (o *Object) SetPrimaryCommands(commands []string) { - o.mutex.Lock() - defer o.mutex.Unlock() - o.primaryCommands = make([]string, len(commands)) - copy(o.primaryCommands, commands) -} - -// GetPrimaryCommands returns the primary command list -func (o *Object) GetPrimaryCommands() []string { - o.mutex.RLock() - defer o.mutex.RUnlock() - commands := make([]string, len(o.primaryCommands)) - copy(commands, o.primaryCommands) - return commands -} - -// SetSecondaryCommands sets the secondary command list -func (o *Object) SetSecondaryCommands(commands []string) { - o.mutex.Lock() - defer o.mutex.Unlock() - o.secondaryCommands = make([]string, len(commands)) - copy(o.secondaryCommands, commands) -} - -// GetSecondaryCommands returns the secondary command list -func (o *Object) GetSecondaryCommands() []string { - o.mutex.RLock() - defer o.mutex.RUnlock() - commands := make([]string, len(o.secondaryCommands)) - copy(commands, o.secondaryCommands) - return commands -} - -// SetDatabaseID sets the database ID -func (o *Object) SetDatabaseID(id int32) { - o.mutex.Lock() - defer o.mutex.Unlock() - o.databaseID = id -} - -// GetDatabaseID returns the database ID -func (o *Object) GetDatabaseID() int32 { - o.mutex.RLock() - defer o.mutex.RUnlock() - return o.databaseID -} - -// SetFactionID sets the faction ID -func (o *Object) SetFactionID(factionID int32) { - o.mutex.Lock() - defer o.mutex.Unlock() - o.factionID = factionID -} - -// GetFactionID returns the faction ID -func (o *Object) GetFactionID() int32 { - o.mutex.RLock() - defer o.mutex.RUnlock() - return o.factionID -} - -// SetTotalHP sets the total hit points -func (o *Object) SetTotalHP(hp int32) { - o.mutex.Lock() - defer o.mutex.Unlock() - o.totalHP = hp -} - -// GetTotalHP returns the total hit points -func (o *Object) GetTotalHP() int32 { - o.mutex.RLock() - defer o.mutex.RUnlock() - return o.totalHP -} - -// SetTotalPower sets the total power -func (o *Object) SetTotalPower(power int32) { - o.mutex.Lock() - defer o.mutex.Unlock() - o.totalPower = power -} - -// GetTotalPower returns the total power -func (o *Object) GetTotalPower() int32 { - o.mutex.RLock() - defer o.mutex.RUnlock() - return o.totalPower -} - -// SetHP sets the current hit points -func (o *Object) SetHP(hp int32) { - o.mutex.Lock() - defer o.mutex.Unlock() - o.currentHP = hp -} - -// GetHP returns the current hit points -func (o *Object) GetHP() int32 { - o.mutex.RLock() - defer o.mutex.RUnlock() - return o.currentHP -} - -// SetPower sets the current power -func (o *Object) SetPower(power int32) { - o.mutex.Lock() - defer o.mutex.Unlock() - o.currentPower = power -} - -// GetPower returns the current power -func (o *Object) GetPower() int32 { - o.mutex.RLock() - defer o.mutex.RUnlock() - return o.currentPower + return o.transporterID } // SetTransporterID sets the transporter ID @@ -477,127 +187,144 @@ func (o *Object) SetTransporterID(transporterID int32) { o.transporterID = transporterID } -// GetTransporterID returns the transporter ID -func (o *Object) GetTransporterID() int32 { +// IsTransporter returns whether this object is a transporter +func (o *Object) IsTransporter() bool { o.mutex.RLock() defer o.mutex.RUnlock() - return o.transporterID + return o.transporterID > 0 } -// SetSoundsDisabled sets whether sounds are disabled -func (o *Object) SetSoundsDisabled(disabled bool) { +// Interaction methods + +// GetInteractionType returns the type of interaction this object supports +func (o *Object) GetInteractionType() int { + o.mutex.RLock() + defer o.mutex.RUnlock() + + if o.transporterID > 0 { + return InteractionTypeTransport + } + if o.deviceID > 0 { + return InteractionTypeDevice + } + if o.clickable { + return InteractionTypeCommand + } + return InteractionTypeNone +} + +// CanInteract returns whether this object can be interacted with +func (o *Object) CanInteract() bool { + return o.GetInteractionType() != InteractionTypeNone +} + +// Zone methods + +// GetZone returns the zone name +func (o *Object) GetZone() string { + o.mutex.RLock() + defer o.mutex.RUnlock() + return o.zoneName +} + +// SetZone sets the zone name +func (o *Object) SetZone(zoneName string) { o.mutex.Lock() defer o.mutex.Unlock() - o.soundsDisabled = disabled + o.zoneName = zoneName } -// IsSoundsDisabled returns whether sounds are disabled -func (o *Object) IsSoundsDisabled() bool { - o.mutex.RLock() - defer o.mutex.RUnlock() - return o.soundsDisabled -} +// Utility methods -// SetOmittedByDBFlag sets the omitted by DB flag -func (o *Object) SetOmittedByDBFlag(omitted bool) { - o.mutex.Lock() - defer o.mutex.Unlock() - o.omittedByDBFlag = omitted -} - -// IsOmittedByDBFlag returns the omitted by DB flag -func (o *Object) IsOmittedByDBFlag() bool { - o.mutex.RLock() - defer o.mutex.RUnlock() - return o.omittedByDBFlag -} - -// SetLootTier sets the loot tier -func (o *Object) SetLootTier(tier int8) { - o.mutex.Lock() - defer o.mutex.Unlock() - o.lootTier = tier -} - -// GetLootTier returns the loot tier -func (o *Object) GetLootTier() int8 { - o.mutex.RLock() - defer o.mutex.RUnlock() - return o.lootTier -} - -// SetLootDropType sets the loot drop type -func (o *Object) SetLootDropType(dropType int8) { - o.mutex.Lock() - defer o.mutex.Unlock() - o.lootDropType = dropType -} - -// GetLootDropType returns the loot drop type -func (o *Object) GetLootDropType() int8 { - o.mutex.RLock() - defer o.mutex.RUnlock() - return o.lootDropType -} - -// SetSpawnScript sets the spawn script -func (o *Object) SetSpawnScript(script string) { - o.mutex.Lock() - defer o.mutex.Unlock() - o.spawnScript = script - o.spawnScriptSetDB = len(script) > 0 -} - -// GetSpawnScript returns the spawn script -func (o *Object) GetSpawnScript() string { - o.mutex.RLock() - defer o.mutex.RUnlock() - return o.spawnScript -} - -// GetSpawnScriptSetDB returns whether spawn script is set from DB -func (o *Object) GetSpawnScriptSetDB() bool { - o.mutex.RLock() - defer o.mutex.RUnlock() - return o.spawnScriptSetDB -} - -// SetShowCommandIcon sets whether to show the command icon -func (o *Object) SetShowCommandIcon(show bool) { - o.mutex.Lock() - defer o.mutex.Unlock() - if show { - o.appearanceShowCommandIcon = ObjectShowCommandIcon - } else { - o.appearanceShowCommandIcon = 0 +// GetObjectType returns a string describing the object type +func (o *Object) GetObjectType() string { + switch { + case o.IsTransporter(): + return "Transport" + case o.IsMerchant(): + return "Merchant" + case o.IsCollector(): + return "Collector" + case o.GetDeviceID() > 0: + return "Device" + case o.IsClickable(): + return "Interactive" + default: + return "Static" } } -// ShowsCommandIcon returns whether the command icon is shown -func (o *Object) ShowsCommandIcon() bool { - o.mutex.RLock() - defer o.mutex.RUnlock() - return o.appearanceShowCommandIcon == ObjectShowCommandIcon +// Validate checks if the object is properly configured +func (o *Object) Validate() error { + if o.Spawn == nil { + return fmt.Errorf("object must have spawn data") + } + + if o.GetSpawnType() != ObjectSpawnType { + return fmt.Errorf("object must have spawn type %d", ObjectSpawnType) + } + + return nil } -// GetObjectInfo returns comprehensive information about the object -func (o *Object) GetObjectInfo() map[string]any { +// Clone creates a copy of the object +func (o *Object) Clone() *Object { o.mutex.RLock() defer o.mutex.RUnlock() - info := make(map[string]any) - info["clickable"] = o.clickable - info["zone_name"] = o.zoneName - info["device_id"] = o.deviceID - info["database_id"] = o.databaseID - info["size"] = o.size - info["merchant_id"] = o.merchantID - info["transporter_id"] = o.transporterID - info["is_collector"] = o.isCollector - info["sounds_disabled"] = o.soundsDisabled - info["primary_commands"] = len(o.primaryCommands) - info["secondary_commands"] = len(o.secondaryCommands) - info["shows_command_icon"] = o.ShowsCommandIcon() + newObject := &Object{ + Spawn: o.Spawn, // Note: This is a shallow copy of the spawn + clickable: o.clickable, + deviceID: o.deviceID, + merchantID: o.merchantID, + merchantType: o.merchantType, + merchantMinLevel: o.merchantMinLevel, + merchantMaxLevel: o.merchantMaxLevel, + isCollector: o.isCollector, + transporterID: o.transporterID, + } - return info + return newObject } + +// Factory functions for different object types + +// CreateMerchantObject creates an object configured as a merchant +func CreateMerchantObject(merchantID int32, merchantType int8) *Object { + obj := NewObject() + obj.SetMerchantID(merchantID) + obj.SetMerchantType(merchantType) + obj.SetClickable(true) + return obj +} + +// CreateTransportObject creates an object configured as a transporter +func CreateTransportObject(transporterID int32) *Object { + obj := NewObject() + obj.SetTransporterID(transporterID) + obj.SetClickable(true) + return obj +} + +// CreateDeviceObject creates an object configured as a device +func CreateDeviceObject(deviceID int8) *Object { + obj := NewObject() + obj.SetDeviceID(deviceID) + obj.SetClickable(true) + return obj +} + +// CreateCollectorObject creates an object configured as a collector +func CreateCollectorObject() *Object { + obj := NewObject() + obj.SetCollector(true) + obj.SetClickable(true) + return obj +} + +// CreateStaticObject creates a non-interactive object +func CreateStaticObject() *Object { + obj := NewObject() + obj.SetClickable(false) + return obj +} \ No newline at end of file