simplify object

This commit is contained in:
Sky Johnson 2025-08-29 18:25:57 -05:00
parent d74f309ade
commit bab7d9abf9
6 changed files with 400 additions and 1476 deletions

View File

@ -22,6 +22,7 @@ This document outlines how we successfully simplified the EverQuest II housing p
- NPC - NPC
- NPC/AI - NPC/AI
- NPC/Race Types - NPC/Race Types
- Object
## Before: Complex Architecture (8 Files, ~2000+ Lines) ## Before: Complex Architecture (8 Files, ~2000+ Lines)

View File

@ -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
)

View File

@ -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
}

View File

@ -1,320 +1,53 @@
package object package object
import ( // ObjectInterface defines the core interface for interactive objects
"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
type ObjectInterface interface { type ObjectInterface interface {
SpawnInterface // Object identification
// Object-specific properties
IsObject() bool IsObject() bool
GetObjectType() string
// Interaction capabilities
IsClickable() bool IsClickable() bool
SetClickable(bool) SetClickable(bool)
CanInteract() bool
GetInteractionType() int
// Device functionality
GetDeviceID() int8 GetDeviceID() int8
SetDeviceID(int8) SetDeviceID(int8)
// Interaction
HandleUse(clientID int32, command string) error
ShowsCommandIcon() bool
SetShowCommandIcon(bool)
// Merchant functionality // Merchant functionality
IsMerchant() bool
GetMerchantID() int32 GetMerchantID() int32
SetMerchantID(int32) SetMerchantID(int32)
GetMerchantType() int8 GetMerchantType() int8
SetMerchantType(int8) SetMerchantType(int8)
IsCollector() bool
SetCollector(bool)
// Transport functionality // Transport functionality
IsTransporter() bool
GetTransporterID() int32 GetTransporterID() int32
SetTransporterID(int32) SetTransporterID(int32)
// Copying // Collector functionality
Copy() *ObjectSpawn IsCollector() bool
SetCollector(bool)
// Validation
Validate() error
} }
// EntityInterface defines the interface for entities in trade/spell systems // ObjectManager interface for dependency injection
// ObjectSpawn implements this to integrate with existing systems type ObjectManagerInterface interface {
type EntityInterface interface { AddObject(*Object) error
GetID() int32 RemoveObject(int32) error
GetName() string GetObject(int32) *Object
IsPlayer() bool GetObjectsByZone(string) []*Object
IsBot() bool GetObjectsByType(string) []*Object
HasCoins(amount int64) bool GetInteractiveObjects() []*Object
GetClientVersion() int32 GetMerchantObjects() []*Object
} GetTransportObjects() []*Object
GetCollectorObjects() []*Object
// ItemInterface defines the interface for items that objects might provide GetObjectCount() int
// This allows objects to act as item sources (merchants, containers, etc.) Clear()
type ItemInterface interface { GetObjectStatistics() ObjectStatistics
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
}

View File

@ -7,30 +7,16 @@ import (
// ObjectManager manages all objects in the game world // ObjectManager manages all objects in the game world
type ObjectManager struct { type ObjectManager struct {
objects map[int32]*Object // Objects by database ID objects map[int32]*Object // Objects by database ID
// Zone-based indexing
objectsByZone map[string][]*Object // Objects grouped by zone objectsByZone map[string][]*Object // Objects grouped by zone
mutex sync.RWMutex // Thread safety
// 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 // NewObjectManager creates a new object manager
func NewObjectManager() *ObjectManager { func NewObjectManager() *ObjectManager {
return &ObjectManager{ return &ObjectManager{
objects: make(map[int32]*Object), objects: make(map[int32]*Object),
objectsByZone: make(map[string][]*Object), objectsByZone: make(map[string][]*Object),
interactiveObjects: make([]*Object, 0),
transportObjects: make([]*Object, 0),
merchantObjects: make([]*Object, 0),
collectorObjects: make([]*Object, 0),
} }
} }
@ -40,6 +26,10 @@ func (om *ObjectManager) AddObject(object *Object) error {
return fmt.Errorf("cannot add nil object") return fmt.Errorf("cannot add nil object")
} }
if err := object.Validate(); err != nil {
return fmt.Errorf("invalid object: %w", err)
}
om.mutex.Lock() om.mutex.Lock()
defer om.mutex.Unlock() 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) return fmt.Errorf("object with database ID %d already exists", databaseID)
} }
// Add to main collection // Add to main index
om.objects[databaseID] = object om.objects[databaseID] = object
// Add to zone collection // Add to zone index
zoneName := object.GetZoneName() zoneName := object.GetZone()
if zoneName != "" { if zoneName != "" {
om.objectsByZone[zoneName] = append(om.objectsByZone[zoneName], object) om.objectsByZone[zoneName] = append(om.objectsByZone[zoneName], object)
} }
// Add to type-based collections
om.updateObjectIndices(object, true)
return nil return nil
} }
@ -78,29 +65,26 @@ func (om *ObjectManager) RemoveObject(databaseID int32) error {
return fmt.Errorf("object with database ID %d not found", databaseID) return fmt.Errorf("object with database ID %d not found", databaseID)
} }
// Remove from main collection // Remove from main index
delete(om.objects, databaseID) delete(om.objects, databaseID)
// Remove from zone collection // Remove from zone index
zoneName := object.GetZoneName() zoneName := object.GetZone()
if zoneName != "" { if zoneName != "" {
if zoneObjects, exists := om.objectsByZone[zoneName]; exists { objects := om.objectsByZone[zoneName]
for i, obj := range zoneObjects { for i, obj := range objects {
if obj.GetDatabaseID() == databaseID { if obj.GetDatabaseID() == databaseID {
om.objectsByZone[zoneName] = append(zoneObjects[:i], zoneObjects[i+1:]...) // Remove from slice
break om.objectsByZone[zoneName] = append(objects[:i], objects[i+1:]...)
} break
}
// Clean up empty zone collection
if len(om.objectsByZone[zoneName]) == 0 {
delete(om.objectsByZone, zoneName)
} }
} }
}
// Remove from type-based collections // Clean up empty zone entries
om.updateObjectIndices(object, false) if len(om.objectsByZone[zoneName]) == 0 {
delete(om.objectsByZone, zoneName)
}
}
return nil return nil
} }
@ -109,7 +93,6 @@ func (om *ObjectManager) RemoveObject(databaseID int32) error {
func (om *ObjectManager) GetObject(databaseID int32) *Object { func (om *ObjectManager) GetObject(databaseID int32) *Object {
om.mutex.RLock() om.mutex.RLock()
defer om.mutex.RUnlock() defer om.mutex.RUnlock()
return om.objects[databaseID] return om.objects[databaseID]
} }
@ -118,14 +101,29 @@ func (om *ObjectManager) GetObjectsByZone(zoneName string) []*Object {
om.mutex.RLock() om.mutex.RLock()
defer om.mutex.RUnlock() defer om.mutex.RUnlock()
if objects, exists := om.objectsByZone[zoneName]; exists { objects := om.objectsByZone[zoneName]
// Return a copy to prevent external modification if objects == nil {
result := make([]*Object, len(objects)) return []*Object{}
copy(result, objects)
return result
} }
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 // GetInteractiveObjects returns all objects that can be interacted with
@ -133,18 +131,12 @@ func (om *ObjectManager) GetInteractiveObjects() []*Object {
om.mutex.RLock() om.mutex.RLock()
defer om.mutex.RUnlock() defer om.mutex.RUnlock()
result := make([]*Object, len(om.interactiveObjects)) var result []*Object
copy(result, om.interactiveObjects) for _, object := range om.objects {
return result if object.CanInteract() {
} result = append(result, object)
}
// 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 return result
} }
@ -153,8 +145,26 @@ func (om *ObjectManager) GetMerchantObjects() []*Object {
om.mutex.RLock() om.mutex.RLock()
defer om.mutex.RUnlock() defer om.mutex.RUnlock()
result := make([]*Object, len(om.merchantObjects)) var result []*Object
copy(result, om.merchantObjects) 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 return result
} }
@ -163,86 +173,17 @@ func (om *ObjectManager) GetCollectorObjects() []*Object {
om.mutex.RLock() om.mutex.RLock()
defer om.mutex.RUnlock() defer om.mutex.RUnlock()
result := make([]*Object, len(om.collectorObjects)) var result []*Object
copy(result, om.collectorObjects) for _, object := range om.objects {
return result if object.IsCollector() {
} result = append(result, object)
// 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 return result
} }
// FindObjectByName finds the first object with a matching name (placeholder) // UpdateObjectZone updates an object's zone (called when object moves zones)
func (om *ObjectManager) FindObjectByName(name string) *Object { func (om *ObjectManager) UpdateObjectZone(databaseID int32, newZoneName string) error {
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() om.mutex.Lock()
defer om.mutex.Unlock() 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) return fmt.Errorf("object with database ID %d not found", databaseID)
} }
// Store old zone for potential reindexing oldZoneName := object.GetZone()
oldZone := object.GetZoneName()
// Apply updates // Remove from old zone index
updateFn(object) if oldZoneName != "" {
objects := om.objectsByZone[oldZoneName]
// Check if zone changed and reindex if necessary for i, obj := range objects {
newZone := object.GetZoneName() if obj.GetDatabaseID() == databaseID {
if oldZone != newZone { om.objectsByZone[oldZoneName] = append(objects[:i], objects[i+1:]...)
// Remove from old zone break
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 // Clean up empty zone entries
if newZone != "" { if len(om.objectsByZone[oldZoneName]) == 0 {
om.objectsByZone[newZone] = append(om.objectsByZone[newZone], object) delete(om.objectsByZone, oldZoneName)
} }
} }
// Update type-based indices // Update object zone
om.rebuildIndicesForObject(object) object.SetZone(newZoneName)
// Add to new zone index
if newZoneName != "" {
om.objectsByZone[newZoneName] = append(om.objectsByZone[newZoneName], object)
}
return nil return nil
} }
// ClearZone removes all objects from a specific zone // GetObjectCount returns the total number of objects
func (om *ObjectManager) ClearZone(zoneName string) int { func (om *ObjectManager) GetObjectCount() int {
om.mutex.Lock() om.mutex.RLock()
defer om.mutex.Unlock() defer om.mutex.RUnlock()
return len(om.objects)
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 // GetZoneCount returns the number of zones with objects
func (om *ObjectManager) GetStatistics() map[string]any { 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() om.mutex.RLock()
defer om.mutex.RUnlock() defer om.mutex.RUnlock()
stats := make(map[string]any) result := make([]*Object, 0, len(om.objects))
stats["total_objects"] = len(om.objects) for _, object := range om.objects {
stats["zones_with_objects"] = len(om.objectsByZone) result = append(result, object)
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 result
return stats
} }
// Shutdown clears all objects and prepares for shutdown // Clear removes all objects from the manager
func (om *ObjectManager) Shutdown() { func (om *ObjectManager) Clear() {
om.mutex.Lock() om.mutex.Lock()
defer om.mutex.Unlock() defer om.mutex.Unlock()
om.objects = make(map[int32]*Object) om.objects = make(map[int32]*Object)
om.objectsByZone = make(map[string][]*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 stats := ObjectStatistics{
func (om *ObjectManager) updateObjectIndices(object *Object, add bool) { TotalObjects: len(om.objects),
if add { ZoneCount: len(om.objectsByZone),
// Add to type-based collections TypeCounts: make(map[string]int),
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) // Count by type
func (om *ObjectManager) rebuildIndicesForObject(object *Object) { for _, object := range om.objects {
databaseID := object.GetDatabaseID() objectType := object.GetObjectType()
stats.TypeCounts[objectType]++
}
// Remove from all type-based collections // Count interactive objects
om.interactiveObjects = removeObjectFromSlice(om.interactiveObjects, databaseID) for _, object := range om.objects {
om.transportObjects = removeObjectFromSlice(om.transportObjects, databaseID) if object.CanInteract() {
om.merchantObjects = removeObjectFromSlice(om.merchantObjects, databaseID) stats.InteractiveObjects++
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
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 // Global object manager instance
var globalObjectManager *ObjectManager 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 { func GetGlobalObjectManager() *ObjectManager {
initObjectManagerOnce.Do(func() { globalObjectManagerOnce.Do(func() {
globalObjectManager = NewObjectManager() globalObjectManager = NewObjectManager()
}) })
return globalObjectManager return globalObjectManager
} }

View File

@ -2,81 +2,87 @@ package object
import ( import (
"fmt" "fmt"
"math/rand"
"strings"
"sync" "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 // Object represents a game object that extends spawn functionality
// Converted from C++ Object class
type Object struct { type Object struct {
// Embed spawn functionality - TODO: Use actual spawn.Spawn when integrated *spawn.Spawn // Embed spawn functionality
// spawn.Spawn
// Object-specific properties // Object-specific properties
clickable bool // Whether the object can be clicked/interacted with 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
deviceID int8 // Device ID for interactive objects
// Inherited spawn properties (placeholder until spawn integration) // Merchant properties
databaseID int32 merchantID int32
size int16 merchantType int8
sizeOffset int8 merchantMinLevel int32
merchantID int32 merchantMaxLevel int32
merchantType int8 isCollector bool
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
// Appearance data placeholder - TODO: Use actual appearance struct // Transport properties
appearanceActivityStatus int8 transporterID int32
appearancePosState int8
appearanceDifficulty int8
appearanceShowCommandIcon int8
// Command lists - TODO: Use actual command structures // Zone tracking (since spawn doesn't expose zone methods yet)
primaryCommands []string zoneName string
secondaryCommands []string
// Thread safety // Thread safety
mutex sync.RWMutex mutex sync.RWMutex
} }
// NewObject creates a new object with default values // NewObject creates a new object with default values
// Converted from C++ Object::Object constructor
func NewObject() *Object { 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{ return &Object{
clickable: false, Spawn: baseSpawn,
zoneName: "", clickable: false,
deviceID: DeviceIDNone, deviceID: 0,
appearanceActivityStatus: ObjectActivityStatus, zoneName: "",
appearancePosState: ObjectPosState,
appearanceDifficulty: ObjectDifficulty,
appearanceShowCommandIcon: 0,
primaryCommands: make([]string, 0),
secondaryCommands: make([]string, 0),
} }
} }
// SetClickable sets whether the object can be clicked // Object-specific methods
func (o *Object) SetClickable(clickable bool) {
o.mutex.Lock() // IsObject returns true (implements ObjectInterface)
defer o.mutex.Unlock() func (o *Object) IsObject() bool {
o.clickable = clickable return true
} }
// IsClickable returns whether the object can be clicked // IsClickable returns whether the object can be clicked
@ -86,192 +92,41 @@ func (o *Object) IsClickable() bool {
return o.clickable return o.clickable
} }
// SetZone sets the zone name for this object // SetClickable sets whether the object can be clicked
// Converted from C++ Object::SetZone func (o *Object) SetClickable(clickable bool) {
func (o *Object) SetZone(zoneName string) {
o.mutex.Lock() o.mutex.Lock()
defer o.mutex.Unlock() defer o.mutex.Unlock()
o.zoneName = zoneName o.clickable = clickable
}
// GetZoneName returns the zone name for this object // Update appearance to show/hide command icon
func (o *Object) GetZoneName() string { if clickable {
o.mutex.RLock() o.GetAppearanceData().ShowCommandIcon = ObjectShowCommandIcon
defer o.mutex.RUnlock() } else {
return o.zoneName o.GetAppearanceData().ShowCommandIcon = 0
} }
// 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
} }
// GetDeviceID returns the device ID // GetDeviceID returns the device ID
// Converted from C++ Object::GetDeviceID
func (o *Object) GetDeviceID() int8 { func (o *Object) GetDeviceID() int8 {
o.mutex.RLock() o.mutex.RLock()
defer o.mutex.RUnlock() defer o.mutex.RUnlock()
return o.deviceID return o.deviceID
} }
// IsObject always returns true for Object instances // SetDeviceID sets the device ID
// Converted from C++ Object::IsObject func (o *Object) SetDeviceID(deviceID int8) {
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) {
o.mutex.Lock() o.mutex.Lock()
defer o.mutex.Unlock() defer o.mutex.Unlock()
o.isCollector = isCollector o.deviceID = deviceID
} }
// IsCollector returns whether this object is a collector // Merchant methods
func (o *Object) IsCollector() bool {
// GetMerchantID returns the merchant ID
func (o *Object) GetMerchantID() int32 {
o.mutex.RLock() o.mutex.RLock()
defer o.mutex.RUnlock() defer o.mutex.RUnlock()
return o.isCollector return o.merchantID
} }
// SetMerchantID sets the merchant ID // SetMerchantID sets the merchant ID
@ -281,11 +136,11 @@ func (o *Object) SetMerchantID(merchantID int32) {
o.merchantID = merchantID o.merchantID = merchantID
} }
// GetMerchantID returns the merchant ID // GetMerchantType returns the merchant type
func (o *Object) GetMerchantID() int32 { func (o *Object) GetMerchantType() int8 {
o.mutex.RLock() o.mutex.RLock()
defer o.mutex.RUnlock() defer o.mutex.RUnlock()
return o.merchantID return o.merchantType
} }
// SetMerchantType sets the merchant type // SetMerchantType sets the merchant type
@ -295,179 +150,34 @@ func (o *Object) SetMerchantType(merchantType int8) {
o.merchantType = merchantType o.merchantType = merchantType
} }
// GetMerchantType returns the merchant type // IsMerchant returns whether this object is a merchant
func (o *Object) GetMerchantType() int8 { func (o *Object) IsMerchant() bool {
o.mutex.RLock() o.mutex.RLock()
defer o.mutex.RUnlock() defer o.mutex.RUnlock()
return o.merchantType return o.merchantID > 0
} }
// SetMerchantLevelRange sets the merchant level range // IsCollector returns whether this object is a collector
func (o *Object) SetMerchantLevelRange(minLevel, maxLevel int8) { 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() o.mutex.Lock()
defer o.mutex.Unlock() defer o.mutex.Unlock()
o.merchantMinLevel = minLevel o.isCollector = isCollector
o.merchantMaxLevel = maxLevel
} }
// GetMerchantMinLevel returns the minimum merchant level // Transport methods
func (o *Object) GetMerchantMinLevel() int8 {
// GetTransporterID returns the transporter ID
func (o *Object) GetTransporterID() int32 {
o.mutex.RLock() o.mutex.RLock()
defer o.mutex.RUnlock() defer o.mutex.RUnlock()
return o.merchantMinLevel return o.transporterID
}
// 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
} }
// SetTransporterID sets the transporter ID // SetTransporterID sets the transporter ID
@ -477,127 +187,144 @@ func (o *Object) SetTransporterID(transporterID int32) {
o.transporterID = transporterID o.transporterID = transporterID
} }
// GetTransporterID returns the transporter ID // IsTransporter returns whether this object is a transporter
func (o *Object) GetTransporterID() int32 { func (o *Object) IsTransporter() bool {
o.mutex.RLock() o.mutex.RLock()
defer o.mutex.RUnlock() defer o.mutex.RUnlock()
return o.transporterID return o.transporterID > 0
} }
// SetSoundsDisabled sets whether sounds are disabled // Interaction methods
func (o *Object) SetSoundsDisabled(disabled bool) {
// 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() o.mutex.Lock()
defer o.mutex.Unlock() defer o.mutex.Unlock()
o.soundsDisabled = disabled o.zoneName = zoneName
} }
// IsSoundsDisabled returns whether sounds are disabled // Utility methods
func (o *Object) IsSoundsDisabled() bool {
o.mutex.RLock()
defer o.mutex.RUnlock()
return o.soundsDisabled
}
// SetOmittedByDBFlag sets the omitted by DB flag // GetObjectType returns a string describing the object type
func (o *Object) SetOmittedByDBFlag(omitted bool) { func (o *Object) GetObjectType() string {
o.mutex.Lock() switch {
defer o.mutex.Unlock() case o.IsTransporter():
o.omittedByDBFlag = omitted return "Transport"
} case o.IsMerchant():
return "Merchant"
// IsOmittedByDBFlag returns the omitted by DB flag case o.IsCollector():
func (o *Object) IsOmittedByDBFlag() bool { return "Collector"
o.mutex.RLock() case o.GetDeviceID() > 0:
defer o.mutex.RUnlock() return "Device"
return o.omittedByDBFlag case o.IsClickable():
} return "Interactive"
default:
// SetLootTier sets the loot tier return "Static"
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
} }
} }
// ShowsCommandIcon returns whether the command icon is shown // Validate checks if the object is properly configured
func (o *Object) ShowsCommandIcon() bool { func (o *Object) Validate() error {
o.mutex.RLock() if o.Spawn == nil {
defer o.mutex.RUnlock() return fmt.Errorf("object must have spawn data")
return o.appearanceShowCommandIcon == ObjectShowCommandIcon }
if o.GetSpawnType() != ObjectSpawnType {
return fmt.Errorf("object must have spawn type %d", ObjectSpawnType)
}
return nil
} }
// GetObjectInfo returns comprehensive information about the object // Clone creates a copy of the object
func (o *Object) GetObjectInfo() map[string]any { func (o *Object) Clone() *Object {
o.mutex.RLock() o.mutex.RLock()
defer o.mutex.RUnlock() defer o.mutex.RUnlock()
info := make(map[string]any) newObject := &Object{
info["clickable"] = o.clickable Spawn: o.Spawn, // Note: This is a shallow copy of the spawn
info["zone_name"] = o.zoneName clickable: o.clickable,
info["device_id"] = o.deviceID deviceID: o.deviceID,
info["database_id"] = o.databaseID merchantID: o.merchantID,
info["size"] = o.size merchantType: o.merchantType,
info["merchant_id"] = o.merchantID merchantMinLevel: o.merchantMinLevel,
info["transporter_id"] = o.transporterID merchantMaxLevel: o.merchantMaxLevel,
info["is_collector"] = o.isCollector isCollector: o.isCollector,
info["sounds_disabled"] = o.soundsDisabled transporterID: o.transporterID,
info["primary_commands"] = len(o.primaryCommands) }
info["secondary_commands"] = len(o.secondaryCommands)
info["shows_command_icon"] = o.ShowsCommandIcon()
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
}