extends Node ## XR Tools Rumble (Controllers) Manager Script ## ## This script uses the controller's existing rumble intensity variable, ## and allows you to rumble the controller for a certain amount ## of time 'beats'. ## ## Example: something hits you while you're mowing the lawn, ## i.e. a short intense rumble happens during long low rumble. ## Name in the OpenXR Action Map for haptics const HAPTIC_ACTION := &"haptic" # TODO: Migrate # Shorthand for all trackers, in use to be substituted with _queues.keys() const ALL_TRACKERS := [&"all"] # A Queue Per Haptic device (Dictionary) var _queues: Dictionary = {} ## Add support for is_xr_class func is_xr_class(p_name: String) -> bool: return p_name == "XRToolsRumbleManager" ## Get the default Haptics Scale value func get_default_haptics_scale() -> float: var default = 1.0 # Check if the project has overridden the addon's default if ProjectSettings.has_setting("godot_xr_tools/input/haptics_scale"): default = ProjectSettings.get_setting("godot_xr_tools/input/haptics_scale") if default < 0.0 or default > 1.0: # out of bounds? reset to default default = 1.0 return default ## Used to convert gamepad magnitudes to equivalent XR haptic magnitude func combine_magnitudes(weak: float, strong: float) -> float: if strong >= 0.01: return 0.5 + clamp(strong / 2, 0.0, 0.5) return clamp(weak / 2, 0.0, 0.5) # On Ready func _ready(): if Engine.is_editor_hint(): return # Some rumble events are active during pause process_mode = PROCESS_MODE_ALWAYS # Create a queues for standard controllers _queues[&"left_hand"] = XRToolsRumbleManagerQueue.new() _queues[&"right_hand"] = XRToolsRumbleManagerQueue.new() # Determine how much to - and perform the - rumbles each tick func _process(delta: float) -> void: if Engine.is_editor_hint(): return # We'll be subtracting this from the event remaining ms var delta_ms = int(delta * 1000) for tracker_name in _queues: var haptic_queue : XRToolsRumbleManagerQueue = _queues[tracker_name] # default to noXRToolsRumbleManagerQueuensure it's a float, or it rounds to all or nothing!) var magnitude: float = 0.0 # Iterate over the events for key in haptic_queue.events.keys(): var event : XRToolsRumbleEvent = haptic_queue.events[key] # if we're paused and it's not supposed to be active, skip if get_tree().paused and not event.active_during_pause: continue # If we've passed the threshold from positive to negative, the event is done if !event.indefinite and haptic_queue.time_remaining[key] < 0: clear(key, [tracker_name]) continue # Reduce the time remaining haptic_queue.time_remaining[key] -= delta_ms # If it's of greater magnitude, update left magnitude to be set if event.magnitude > magnitude: magnitude = event.magnitude # scale the final magnitude magnitude *= XRToolsUserSettings.haptics_scale # Make that tracker rumble if magnitude > 0: XRServer.primary_interface.trigger_haptic_pulse( HAPTIC_ACTION, tracker_name, # if the tracker name isn't valid, it will error but continue 0, magnitude, 0.1, 0) # Add an event func add(event_key: Variant, event: XRToolsRumbleEvent, trackers: Array = ALL_TRACKERS) -> void: if not event_key: push_error("Event key is invalid!") return if not event: clear(event_key, trackers) return # Substitube the shorthand for all trackers with the real thing if trackers == ALL_TRACKERS: trackers = _queues.keys() for tracker in trackers: if tracker is XRNode3D: tracker = tracker.tracker # Create queue first time a target is suggested if not _queues.has(tracker): _queues[tracker] = XRToolsRumbleManagerQueue.new() # Add the event and it's remaining time to the respective queues _queues[tracker].events[event_key] = event _queues[tracker].time_remaining[event_key] = event.duration_ms # Remove an event func clear(event_key: Variant, trackers: Array = ALL_TRACKERS) -> void: if not event_key: push_error("Event key is invalid!") return # Substitube the shorthand for all trackers with the real thing if trackers == ALL_TRACKERS: trackers = _queues.keys() for tracker in trackers: if tracker is XRNode3D: tracker = tracker.tracker # Ignore if the queue doesn't exist if not _queues.has(tracker): continue # Remove the event and it's remaining time from the respective queues _queues[tracker].events.erase(event_key) _queues[tracker].time_remaining.erase(event_key)