simplify object
This commit is contained in:
parent
d74f309ade
commit
bab7d9abf9
@ -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)
|
||||
|
||||
|
@ -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
|
||||
)
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user