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/AI
- NPC/Race Types
- Object
## 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
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
}

View File

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

View File

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