604 lines
16 KiB
Go
604 lines
16 KiB
Go
package object
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// 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
|
|
|
|
// 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
|
|
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
|
|
|
|
// 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
|
|
|
|
// Thread safety
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// NewObject creates a new object with default values
|
|
// Converted from C++ Object::Object constructor
|
|
func NewObject() *Object {
|
|
return &Object{
|
|
clickable: false,
|
|
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
|
|
}
|
|
|
|
// IsClickable returns whether the object can be clicked
|
|
func (o *Object) IsClickable() bool {
|
|
o.mutex.RLock()
|
|
defer o.mutex.RUnlock()
|
|
return o.clickable
|
|
}
|
|
|
|
// SetZone sets the zone name for this object
|
|
// Converted from C++ Object::SetZone
|
|
func (o *Object) SetZone(zoneName string) {
|
|
o.mutex.Lock()
|
|
defer o.mutex.Unlock()
|
|
o.zoneName = zoneName
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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) {
|
|
o.mutex.Lock()
|
|
defer o.mutex.Unlock()
|
|
o.isCollector = isCollector
|
|
}
|
|
|
|
// IsCollector returns whether this object is a collector
|
|
func (o *Object) IsCollector() bool {
|
|
o.mutex.RLock()
|
|
defer o.mutex.RUnlock()
|
|
return o.isCollector
|
|
}
|
|
|
|
// SetMerchantID sets the merchant ID
|
|
func (o *Object) SetMerchantID(merchantID int32) {
|
|
o.mutex.Lock()
|
|
defer o.mutex.Unlock()
|
|
o.merchantID = merchantID
|
|
}
|
|
|
|
// GetMerchantID returns the merchant ID
|
|
func (o *Object) GetMerchantID() int32 {
|
|
o.mutex.RLock()
|
|
defer o.mutex.RUnlock()
|
|
return o.merchantID
|
|
}
|
|
|
|
// SetMerchantType sets the merchant type
|
|
func (o *Object) SetMerchantType(merchantType int8) {
|
|
o.mutex.Lock()
|
|
defer o.mutex.Unlock()
|
|
o.merchantType = merchantType
|
|
}
|
|
|
|
// GetMerchantType returns the merchant type
|
|
func (o *Object) GetMerchantType() int8 {
|
|
o.mutex.RLock()
|
|
defer o.mutex.RUnlock()
|
|
return o.merchantType
|
|
}
|
|
|
|
// SetMerchantLevelRange sets the merchant level range
|
|
func (o *Object) SetMerchantLevelRange(minLevel, maxLevel int8) {
|
|
o.mutex.Lock()
|
|
defer o.mutex.Unlock()
|
|
o.merchantMinLevel = minLevel
|
|
o.merchantMaxLevel = maxLevel
|
|
}
|
|
|
|
// GetMerchantMinLevel returns the minimum merchant level
|
|
func (o *Object) GetMerchantMinLevel() int8 {
|
|
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
|
|
}
|
|
|
|
// SetTransporterID sets the transporter ID
|
|
func (o *Object) SetTransporterID(transporterID int32) {
|
|
o.mutex.Lock()
|
|
defer o.mutex.Unlock()
|
|
o.transporterID = transporterID
|
|
}
|
|
|
|
// GetTransporterID returns the transporter ID
|
|
func (o *Object) GetTransporterID() int32 {
|
|
o.mutex.RLock()
|
|
defer o.mutex.RUnlock()
|
|
return o.transporterID
|
|
}
|
|
|
|
// SetSoundsDisabled sets whether sounds are disabled
|
|
func (o *Object) SetSoundsDisabled(disabled bool) {
|
|
o.mutex.Lock()
|
|
defer o.mutex.Unlock()
|
|
o.soundsDisabled = disabled
|
|
}
|
|
|
|
// IsSoundsDisabled returns whether sounds are disabled
|
|
func (o *Object) IsSoundsDisabled() bool {
|
|
o.mutex.RLock()
|
|
defer o.mutex.RUnlock()
|
|
return o.soundsDisabled
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// ShowsCommandIcon returns whether the command icon is shown
|
|
func (o *Object) ShowsCommandIcon() bool {
|
|
o.mutex.RLock()
|
|
defer o.mutex.RUnlock()
|
|
return o.appearanceShowCommandIcon == ObjectShowCommandIcon
|
|
}
|
|
|
|
// GetObjectInfo returns comprehensive information about the object
|
|
func (o *Object) GetObjectInfo() map[string]any {
|
|
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
|
|
}
|