588 lines
14 KiB
Go
588 lines
14 KiB
Go
package widget
|
|
|
|
import (
|
|
"math"
|
|
"strings"
|
|
"sync"
|
|
|
|
"eq2emu/internal/spawn"
|
|
)
|
|
|
|
// Widget represents an interactive spawn object like doors and lifts
|
|
// Extends the base Spawn with widget-specific functionality
|
|
type Widget struct {
|
|
*spawn.Spawn // Embedded spawn for basic functionality
|
|
|
|
// Widget identification
|
|
widgetID int32 // Unique widget identifier
|
|
widgetType int8 // Type of widget (door, lift, etc.)
|
|
|
|
// Widget positioning
|
|
widgetX float32 // Widget-specific X coordinate
|
|
widgetY float32 // Widget-specific Y coordinate
|
|
widgetZ float32 // Widget-specific Z coordinate
|
|
includeLocation bool // Whether to include location in updates
|
|
includeHeading bool // Whether to include heading in updates
|
|
|
|
// Door/movement states
|
|
isOpen bool // Current open/closed state
|
|
openHeading float32 // Heading when open
|
|
closedHeading float32 // Heading when closed
|
|
openX float32 // X position when open
|
|
openY float32 // Y position when open
|
|
openZ float32 // Z position when open
|
|
closeX float32 // X position when closed
|
|
closeY float32 // Y position when closed (from close_y in C++)
|
|
closeZ float32 // Z position when closed
|
|
|
|
// Linked widgets
|
|
actionSpawn *Widget // Spawn triggered by this widget
|
|
actionSpawnID int32 // ID of action spawn
|
|
linkedSpawn *Widget // Linked widget (opens/closes together)
|
|
linkedSpawnID int32 // ID of linked spawn
|
|
|
|
// Sounds and timing
|
|
openSound string // Sound played when opening
|
|
closeSound string // Sound played when closing
|
|
openDuration int16 // How long widget stays open (seconds)
|
|
|
|
// House integration
|
|
houseID int32 // Associated house ID
|
|
|
|
// Multi-floor lift support
|
|
multiFloorLift bool // Whether this is a multi-floor lift
|
|
|
|
// Thread safety
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// NewWidget creates a new widget instance
|
|
func NewWidget() *Widget {
|
|
w := &Widget{
|
|
Spawn: spawn.NewSpawn(),
|
|
widgetID: 0,
|
|
widgetType: WidgetTypeGeneric,
|
|
widgetX: 0,
|
|
widgetY: 0,
|
|
widgetZ: 0,
|
|
includeLocation: true,
|
|
includeHeading: true,
|
|
isOpen: false,
|
|
openHeading: DefaultOpenHeading,
|
|
closedHeading: DefaultClosedHeading,
|
|
openX: 0,
|
|
openY: 0,
|
|
openZ: 0,
|
|
closeX: 0,
|
|
closeY: 0,
|
|
closeZ: 0,
|
|
openDuration: DefaultOpenDuration,
|
|
multiFloorLift: false,
|
|
}
|
|
|
|
// Set spawn-specific defaults for widgets
|
|
w.SetSpawnType(2) // Widget spawn type
|
|
w.SetActivityStatus(DefaultActivityClosed)
|
|
w.SetPosState(1)
|
|
|
|
return w
|
|
}
|
|
|
|
// IsWidget returns true to identify this as a widget
|
|
func (w *Widget) IsWidget() bool {
|
|
return true
|
|
}
|
|
|
|
// Widget ID methods
|
|
|
|
// GetWidgetID returns the widget ID
|
|
func (w *Widget) GetWidgetID() int32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.widgetID
|
|
}
|
|
|
|
// SetWidgetID sets the widget ID
|
|
func (w *Widget) SetWidgetID(id int32) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.widgetID = id
|
|
}
|
|
|
|
// Widget position methods
|
|
|
|
// GetWidgetX returns the widget X coordinate
|
|
func (w *Widget) GetWidgetX() float32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.widgetX
|
|
}
|
|
|
|
// SetWidgetX sets the widget X coordinate
|
|
func (w *Widget) SetWidgetX(x float32) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.widgetX = x
|
|
}
|
|
|
|
// GetWidgetY returns the widget Y coordinate
|
|
func (w *Widget) GetWidgetY() float32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.widgetY
|
|
}
|
|
|
|
// SetWidgetY sets the widget Y coordinate
|
|
func (w *Widget) SetWidgetY(y float32) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.widgetY = y
|
|
}
|
|
|
|
// GetWidgetZ returns the widget Z coordinate
|
|
func (w *Widget) GetWidgetZ() float32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.widgetZ
|
|
}
|
|
|
|
// SetWidgetZ sets the widget Z coordinate
|
|
func (w *Widget) SetWidgetZ(z float32) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.widgetZ = z
|
|
}
|
|
|
|
// Location/heading inclusion methods
|
|
|
|
// GetIncludeLocation returns whether to include location in updates
|
|
func (w *Widget) GetIncludeLocation() bool {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.includeLocation
|
|
}
|
|
|
|
// SetIncludeLocation sets whether to include location in updates
|
|
func (w *Widget) SetIncludeLocation(include bool) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.includeLocation = include
|
|
}
|
|
|
|
// GetIncludeHeading returns whether to include heading in updates
|
|
func (w *Widget) GetIncludeHeading() bool {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.includeHeading
|
|
}
|
|
|
|
// SetIncludeHeading sets whether to include heading in updates
|
|
func (w *Widget) SetIncludeHeading(include bool) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.includeHeading = include
|
|
}
|
|
|
|
// Widget type methods
|
|
|
|
// GetWidgetType returns the widget type
|
|
func (w *Widget) GetWidgetType() int8 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.widgetType
|
|
}
|
|
|
|
// SetWidgetType sets the widget type
|
|
func (w *Widget) SetWidgetType(widgetType int8) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.widgetType = widgetType
|
|
}
|
|
|
|
// SetWidgetIcon sets the widget icon (appearance)
|
|
func (w *Widget) SetWidgetIcon(icon int8) {
|
|
w.SetIcon(icon)
|
|
}
|
|
|
|
// Open/close state methods
|
|
|
|
// IsOpen returns whether the widget is open
|
|
func (w *Widget) IsOpen() bool {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.isOpen
|
|
}
|
|
|
|
// setOpenState sets the open state (internal use)
|
|
func (w *Widget) setOpenState(open bool) {
|
|
w.isOpen = open
|
|
}
|
|
|
|
// Heading methods
|
|
|
|
// GetOpenHeading returns the heading when open
|
|
func (w *Widget) GetOpenHeading() float32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.openHeading
|
|
}
|
|
|
|
// SetOpenHeading sets the heading when open
|
|
func (w *Widget) SetOpenHeading(heading float32) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.openHeading = heading
|
|
}
|
|
|
|
// GetClosedHeading returns the heading when closed
|
|
func (w *Widget) GetClosedHeading() float32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.closedHeading
|
|
}
|
|
|
|
// SetClosedHeading sets the heading when closed
|
|
func (w *Widget) SetClosedHeading(heading float32) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.closedHeading = heading
|
|
}
|
|
|
|
// Position methods for open/close states
|
|
|
|
// GetOpenX returns the X position when open
|
|
func (w *Widget) GetOpenX() float32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.openX
|
|
}
|
|
|
|
// SetOpenX sets the X position when open
|
|
func (w *Widget) SetOpenX(x float32) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.openX = x
|
|
}
|
|
|
|
// GetOpenY returns the Y position when open
|
|
func (w *Widget) GetOpenY() float32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.openY
|
|
}
|
|
|
|
// SetOpenY sets the Y position when open
|
|
func (w *Widget) SetOpenY(y float32) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.openY = y
|
|
}
|
|
|
|
// GetOpenZ returns the Z position when open
|
|
func (w *Widget) GetOpenZ() float32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.openZ
|
|
}
|
|
|
|
// SetOpenZ sets the Z position when open
|
|
func (w *Widget) SetOpenZ(z float32) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.openZ = z
|
|
}
|
|
|
|
// GetCloseX returns the X position when closed
|
|
func (w *Widget) GetCloseX() float32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.closeX
|
|
}
|
|
|
|
// SetCloseX sets the X position when closed
|
|
func (w *Widget) SetCloseX(x float32) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.closeX = x
|
|
}
|
|
|
|
// GetCloseY returns the Y position when closed
|
|
func (w *Widget) GetCloseY() float32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.closeY
|
|
}
|
|
|
|
// SetCloseY sets the Y position when closed
|
|
func (w *Widget) SetCloseY(y float32) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.closeY = y
|
|
}
|
|
|
|
// GetCloseZ returns the Z position when closed
|
|
func (w *Widget) GetCloseZ() float32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.closeZ
|
|
}
|
|
|
|
// SetCloseZ sets the Z position when closed
|
|
func (w *Widget) SetCloseZ(z float32) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.closeZ = z
|
|
}
|
|
|
|
// Linked spawn methods
|
|
|
|
// GetActionSpawnID returns the action spawn ID
|
|
func (w *Widget) GetActionSpawnID() int32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.actionSpawnID
|
|
}
|
|
|
|
// SetActionSpawnID sets the action spawn ID
|
|
func (w *Widget) SetActionSpawnID(id int32) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.actionSpawnID = id
|
|
}
|
|
|
|
// GetLinkedSpawnID returns the linked spawn ID
|
|
func (w *Widget) GetLinkedSpawnID() int32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.linkedSpawnID
|
|
}
|
|
|
|
// SetLinkedSpawnID sets the linked spawn ID
|
|
func (w *Widget) SetLinkedSpawnID(id int32) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.linkedSpawnID = id
|
|
}
|
|
|
|
// Sound methods
|
|
|
|
// GetOpenSound returns the sound played when opening
|
|
func (w *Widget) GetOpenSound() string {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.openSound
|
|
}
|
|
|
|
// SetOpenSound sets the sound played when opening
|
|
func (w *Widget) SetOpenSound(sound string) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.openSound = sound
|
|
}
|
|
|
|
// GetCloseSound returns the sound played when closing
|
|
func (w *Widget) GetCloseSound() string {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.closeSound
|
|
}
|
|
|
|
// SetCloseSound sets the sound played when closing
|
|
func (w *Widget) SetCloseSound(sound string) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.closeSound = sound
|
|
}
|
|
|
|
// Duration methods
|
|
|
|
// GetOpenDuration returns how long the widget stays open
|
|
func (w *Widget) GetOpenDuration() int16 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.openDuration
|
|
}
|
|
|
|
// SetOpenDuration sets how long the widget stays open
|
|
func (w *Widget) SetOpenDuration(duration int16) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.openDuration = duration
|
|
}
|
|
|
|
// House methods
|
|
|
|
// GetHouseID returns the associated house ID
|
|
func (w *Widget) GetHouseID() int32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.houseID
|
|
}
|
|
|
|
// SetHouseID sets the associated house ID
|
|
func (w *Widget) SetHouseID(id int32) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.houseID = id
|
|
}
|
|
|
|
// Multi-floor lift methods
|
|
|
|
// GetMultiFloorLift returns whether this is a multi-floor lift
|
|
func (w *Widget) GetMultiFloorLift() bool {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.multiFloorLift
|
|
}
|
|
|
|
// SetMultiFloorLift sets whether this is a multi-floor lift
|
|
func (w *Widget) SetMultiFloorLift(multiFloor bool) {
|
|
w.mutex.Lock()
|
|
defer w.mutex.Unlock()
|
|
w.multiFloorLift = multiFloor
|
|
}
|
|
|
|
// Copy creates a copy of this widget
|
|
func (w *Widget) Copy() *Widget {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
|
|
newWidget := NewWidget()
|
|
|
|
// Copy spawn data
|
|
w.CopySpawnData(newWidget.Spawn)
|
|
|
|
// Handle open state for appearance
|
|
if w.openY > 0 {
|
|
newWidget.SetPosState(0)
|
|
}
|
|
|
|
// Copy widget-specific data
|
|
newWidget.widgetID = w.widgetID
|
|
newWidget.widgetType = w.widgetType
|
|
newWidget.widgetX = w.widgetX
|
|
newWidget.widgetY = w.widgetY
|
|
newWidget.widgetZ = w.widgetZ
|
|
newWidget.includeLocation = w.includeLocation
|
|
newWidget.includeHeading = w.includeHeading
|
|
newWidget.openHeading = w.openHeading
|
|
newWidget.closedHeading = w.closedHeading
|
|
newWidget.openX = w.openX
|
|
newWidget.openY = w.openY
|
|
newWidget.openZ = w.openZ
|
|
newWidget.closeX = w.closeX
|
|
newWidget.closeY = w.closeY
|
|
newWidget.closeZ = w.closeZ
|
|
newWidget.openSound = w.openSound
|
|
newWidget.closeSound = w.closeSound
|
|
newWidget.openDuration = w.openDuration
|
|
newWidget.actionSpawnID = w.actionSpawnID
|
|
newWidget.linkedSpawnID = w.linkedSpawnID
|
|
newWidget.houseID = w.houseID
|
|
newWidget.multiFloorLift = w.multiFloorLift
|
|
|
|
return newWidget
|
|
}
|
|
|
|
// Activity status methods
|
|
|
|
// GetActivityStatus returns the current activity status
|
|
func (w *Widget) GetActivityStatus() int32 {
|
|
// For now, return based on open state
|
|
if w.IsOpen() {
|
|
return DefaultActivityOpen
|
|
}
|
|
return DefaultActivityClosed
|
|
}
|
|
|
|
// SetActivityStatus sets the activity status
|
|
func (w *Widget) SetActivityStatus(status int32) {
|
|
// This method is called but not fully implemented yet
|
|
// It would normally update some activity state
|
|
}
|
|
|
|
// SetPosState sets the position state
|
|
func (w *Widget) SetPosState(state int32) {
|
|
// This method is called but not fully implemented yet
|
|
// It would normally update some position state
|
|
}
|
|
|
|
// AddRunningLocation adds a running location for movement
|
|
func (w *Widget) AddRunningLocation(x, y, z, speed float32) {
|
|
// For now, just set the position directly
|
|
// In a full implementation, this would add smooth movement
|
|
w.SetX(x)
|
|
w.SetY(y, false)
|
|
w.SetZ(z)
|
|
}
|
|
|
|
// Original position methods (for reset functionality)
|
|
|
|
// GetSpawnOrigX returns the original X position
|
|
func (w *Widget) GetSpawnOrigX() float32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.closeX
|
|
}
|
|
|
|
// GetSpawnOrigY returns the original Y position
|
|
func (w *Widget) GetSpawnOrigY() float32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.closeY
|
|
}
|
|
|
|
// GetSpawnOrigZ returns the original Z position
|
|
func (w *Widget) GetSpawnOrigZ() float32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.closeZ
|
|
}
|
|
|
|
// GetSpawnOrigHeading returns the original heading
|
|
func (w *Widget) GetSpawnOrigHeading() float32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.closedHeading
|
|
}
|
|
|
|
// GetTransporterID returns the transporter ID for this widget
|
|
func (w *Widget) GetTransporterID() int32 {
|
|
w.mutex.RLock()
|
|
defer w.mutex.RUnlock()
|
|
return w.widgetID // For transport widgets, use widget ID as transporter ID
|
|
}
|
|
|
|
// SetIcon sets the icon for this widget (visual appearance)
|
|
func (w *Widget) SetIcon(icon int8) {
|
|
// This method is called but not implemented in the base spawn
|
|
// In a full implementation, this would update the visual appearance
|
|
}
|
|
|
|
// CopySpawnData copies spawn data from another spawn
|
|
func (w *Widget) CopySpawnData(src *spawn.Spawn) {
|
|
// Copy basic spawn data
|
|
w.SetName(src.GetName())
|
|
w.SetX(src.GetX())
|
|
w.SetY(src.GetY(), false)
|
|
w.SetZ(src.GetZ())
|
|
w.SetHeadingFromFloat(src.GetHeading())
|
|
w.SetLevel(src.GetLevel())
|
|
w.SetClass(src.GetClass())
|
|
w.SetRace(src.GetRace())
|
|
w.SetGender(src.GetGender())
|
|
}
|
|
|
|
// calculateDistance calculates 3D distance between two points
|
|
func calculateDistance(x1, y1, z1, x2, y2, z2 float32) float32 {
|
|
dx := x2 - x1
|
|
dy := y2 - y1
|
|
dz := z2 - z1
|
|
return float32(math.Sqrt(float64(dx*dx + dy*dy + dz*dz)))
|
|
}
|
|
|
|
// Helper method to check if a string command matches (case-insensitive)
|
|
func isCommand(command, expected string) bool {
|
|
return strings.EqualFold(strings.TrimSpace(command), expected)
|
|
}
|