2025-08-07 12:59:39 -05:00

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)
}