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