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

@ -8,18 +8,8 @@ import (
// ObjectManager manages all objects in the game world
type ObjectManager struct {
objects map[int32]*Object // Objects by database ID
// Zone-based indexing
objectsByZone map[string][]*Object // Objects grouped by zone
// Type-based indexing
interactiveObjects []*Object // Objects that can be interacted with
transportObjects []*Object // Objects that provide transport
merchantObjects []*Object // Objects that are merchants
collectorObjects []*Object // Objects that are collectors
// Thread safety
mutex sync.RWMutex
mutex sync.RWMutex // Thread safety
}
// NewObjectManager creates a new object manager
@ -27,10 +17,6 @@ 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),
}
}
@ -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 {
objects := om.objectsByZone[zoneName]
for i, obj := range objects {
if obj.GetDatabaseID() == databaseID {
om.objectsByZone[zoneName] = append(zoneObjects[:i], zoneObjects[i+1:]...)
// Remove from slice
om.objectsByZone[zoneName] = append(objects[:i], objects[i+1:]...)
break
}
}
// Clean up empty zone collection
// Clean up empty zone entries
if len(om.objectsByZone[zoneName]) == 0 {
delete(om.objectsByZone, zoneName)
}
}
}
// Remove from type-based collections
om.updateObjectIndices(object, false)
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 {
objects := om.objectsByZone[zoneName]
if objects == nil {
return []*Object{}
}
// Return a copy to prevent external modification
result := make([]*Object, len(objects))
copy(result, objects)
return result
}
return make([]*Object, 0)
// 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
var result []*Object
for _, object := range om.objects {
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
}
@ -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)
var result []*Object
for _, object := range om.objects {
if object.IsCollector() {
result = append(result, object)
}
}
return result
}
// GetObjectCount returns the total number of objects
func (om *ObjectManager) GetObjectCount() int {
om.mutex.RLock()
defer om.mutex.RUnlock()
return len(om.objects)
}
// GetZoneCount returns the number of zones with objects
func (om *ObjectManager) GetZoneCount() int {
om.mutex.RLock()
defer om.mutex.RUnlock()
return len(om.objectsByZone)
}
// GetObjectsByType returns objects filtered by specific criteria
func (om *ObjectManager) GetObjectsByType(objectType string) []*Object {
om.mutex.RLock()
defer om.mutex.RUnlock()
switch objectType {
case "interactive":
result := make([]*Object, len(om.interactiveObjects))
copy(result, om.interactiveObjects)
return result
case "transport":
result := make([]*Object, len(om.transportObjects))
copy(result, om.transportObjects)
return result
case "merchant":
result := make([]*Object, len(om.merchantObjects))
copy(result, om.merchantObjects)
return result
case "collector":
result := make([]*Object, len(om.collectorObjects))
copy(result, om.collectorObjects)
return result
default:
return make([]*Object, 0)
}
}
// FindObjectsInZone finds objects in a zone matching specific criteria
func (om *ObjectManager) FindObjectsInZone(zoneName string, filter func(*Object) bool) []*Object {
om.mutex.RLock()
defer om.mutex.RUnlock()
zoneObjects, exists := om.objectsByZone[zoneName]
if !exists {
return make([]*Object, 0)
}
result := make([]*Object, 0)
for _, obj := range zoneObjects {
if filter == nil || filter(obj) {
result = append(result, obj)
}
}
return result
}
// FindObjectByName finds the first object with a matching name (placeholder)
func (om *ObjectManager) FindObjectByName(name string) *Object {
om.mutex.RLock()
defer om.mutex.RUnlock()
// TODO: Implement name searching when spawn name system is integrated
// For now, return nil
return nil
}
// UpdateObject updates an existing object's properties
func (om *ObjectManager) UpdateObject(databaseID int32, updateFn func(*Object)) error {
// 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,168 +192,112 @@ 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 {
// Remove from old zone index
if oldZoneName != "" {
objects := om.objectsByZone[oldZoneName]
for i, obj := range objects {
if obj.GetDatabaseID() == databaseID {
om.objectsByZone[oldZone] = append(zoneObjects[:i], zoneObjects[i+1:]...)
om.objectsByZone[oldZoneName] = append(objects[:i], objects[i+1:]...)
break
}
}
// Clean up empty zone collection
if len(om.objectsByZone[oldZone]) == 0 {
delete(om.objectsByZone, oldZone)
}
}
}
// Add to new zone
if newZone != "" {
om.objectsByZone[newZone] = append(om.objectsByZone[newZone], object)
// 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
// GetObjectCount returns the total number of objects
func (om *ObjectManager) GetObjectCount() int {
om.mutex.RLock()
defer om.mutex.RUnlock()
return len(om.objects)
}
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)
// GetZoneCount returns the number of zones with objects
func (om *ObjectManager) GetZoneCount() int {
om.mutex.RLock()
defer om.mutex.RUnlock()
return len(om.objectsByZone)
}
// Clear zone collection
delete(om.objectsByZone, zoneName)
return count
}
// GetStatistics returns statistics about objects in the manager
func (om *ObjectManager) GetStatistics() map[string]any {
// 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)
stats := ObjectStatistics{
TotalObjects: len(om.objects),
ZoneCount: len(om.objectsByZone),
TypeCounts: make(map[string]int),
}
if object.GetTransporterID() > 0 {
om.transportObjects = append(om.transportObjects, object)
// Count by type
for _, object := range om.objects {
objectType := object.GetObjectType()
stats.TypeCounts[objectType]++
}
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)
// Count interactive objects
for _, object := range om.objects {
if object.CanInteract() {
stats.InteractiveObjects++
}
}
// rebuildIndicesForObject rebuilds type-based indices for an object (used after updates)
func (om *ObjectManager) rebuildIndicesForObject(object *Object) {
databaseID := object.GetDatabaseID()
// Remove from all type-based collections
om.interactiveObjects = removeObjectFromSlice(om.interactiveObjects, databaseID)
om.transportObjects = removeObjectFromSlice(om.transportObjects, databaseID)
om.merchantObjects = removeObjectFromSlice(om.merchantObjects, databaseID)
om.collectorObjects = removeObjectFromSlice(om.collectorObjects, databaseID)
// Re-add based on current properties
om.updateObjectIndices(object, true)
return stats
}
// 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
// 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
// Inherited spawn properties (placeholder until spawn integration)
databaseID int32
size int16
sizeOffset int8
// Merchant properties
merchantID int32
merchantType int8
merchantMinLevel int8
merchantMaxLevel int8
merchantMinLevel int32
merchantMaxLevel int32
isCollector bool
factionID int32
totalHP int32
totalPower int32
currentHP int32
currentPower int32
// Transport properties
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
appearanceActivityStatus int8
appearancePosState int8
appearanceDifficulty int8
appearanceShowCommandIcon int8
// 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{
Spawn: baseSpawn,
clickable: false,
deviceID: 0,
zoneName: "",
deviceID: DeviceIDNone,
appearanceActivityStatus: ObjectActivityStatus,
appearancePosState: ObjectPosState,
appearanceDifficulty: ObjectDifficulty,
appearanceShowCommandIcon: 0,
primaryCommands: make([]string, 0),
secondaryCommands: make([]string, 0),
}
}
// 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
// Update appearance to show/hide command icon
if clickable {
o.GetAppearanceData().ShowCommandIcon = ObjectShowCommandIcon
} else {
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
// 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")
}
// GetObjectInfo returns comprehensive information about the object
func (o *Object) GetObjectInfo() map[string]any {
if o.GetSpawnType() != ObjectSpawnType {
return fmt.Errorf("object must have spawn type %d", ObjectSpawnType)
}
return nil
}
// 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()
return info
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 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
}