eq2go/internal/object/object.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
}