274 lines
6.8 KiB
Plaintext
274 lines
6.8 KiB
Plaintext
// Prosperon Input System
|
|
// Engine-driven input with user pairing and possession dispatch
|
|
|
|
var backend = use('input/backends/sdl3')
|
|
var devices = use('input/devices')
|
|
var bindings_mod = use('input/bindings')
|
|
var router_mod = use('input/router')
|
|
|
|
// Default UI-focused action map
|
|
var default_action_map = {
|
|
'ui_up': ['w', 'up', 'gamepad_dpup'],
|
|
'ui_down': ['s', 'down', 'gamepad_dpdown'],
|
|
'ui_left': ['a', 'left', 'gamepad_dpleft'],
|
|
'ui_right': ['d', 'right', 'gamepad_dpright'],
|
|
'confirm': ['return', 'space', 'mouse_button_left', 'gamepad_a'],
|
|
'cancel': ['escape', 'gamepad_b'],
|
|
'menu': ['escape', 'gamepad_start']
|
|
}
|
|
|
|
var default_display_names = {
|
|
'ui_up': 'UI Up',
|
|
'ui_down': 'UI Down',
|
|
'ui_left': 'UI Left',
|
|
'ui_right': 'UI Right',
|
|
'confirm': 'Confirm',
|
|
'cancel': 'Cancel',
|
|
'menu': 'Menu'
|
|
}
|
|
|
|
// Module state
|
|
var _users = []
|
|
var _config = {
|
|
max_users: 1,
|
|
pairing: 'last_used',
|
|
emacs: true,
|
|
gestures: true,
|
|
action_map: default_action_map,
|
|
display_names: default_display_names
|
|
}
|
|
|
|
var _initialized = false
|
|
var _window_callback = null
|
|
|
|
// Create an input user
|
|
function create_user(index, config) {
|
|
var action_map = {}
|
|
var display_names = {}
|
|
|
|
// Merge defaults with config
|
|
for (var k in default_action_map) {
|
|
action_map[k] = array(default_action_map[k])
|
|
display_names[k] = default_display_names[k]
|
|
}
|
|
if (config.action_map) {
|
|
for (var k in config.action_map) {
|
|
var val = config.action_map[k]
|
|
action_map[k] = is_array(val) ? array(val) : [val]
|
|
}
|
|
}
|
|
if (config.display_names) {
|
|
for (var k in config.display_names) {
|
|
display_names[k] = config.display_names[k]
|
|
}
|
|
}
|
|
|
|
var user = {
|
|
index: index,
|
|
paired_devices: [],
|
|
active_device: null,
|
|
bindings: bindings_mod.make(action_map, display_names),
|
|
router: null,
|
|
control_stack: [],
|
|
|
|
// Get current device kind
|
|
get device_kind() {
|
|
if (!this.active_device) return 'keyboard'
|
|
return devices.kind(this.active_device)
|
|
},
|
|
|
|
// Get current gamepad type
|
|
get gamepad_type() {
|
|
if (!this.active_device) return null
|
|
return devices.gamepad_type(this.active_device)
|
|
},
|
|
|
|
// Get action down state
|
|
get down() {
|
|
return this.router ? this.router.down : {}
|
|
},
|
|
|
|
// Possess an entity (clears stack, sets as sole target)
|
|
possess: function(entity) {
|
|
this.control_stack = [entity]
|
|
},
|
|
|
|
// Push entity onto control stack
|
|
push: function(entity) {
|
|
this.control_stack.push(entity)
|
|
},
|
|
|
|
// Pop from control stack
|
|
pop: function() {
|
|
if (length(this.control_stack) > 1) {
|
|
return this.control_stack.pop()
|
|
}
|
|
return null
|
|
},
|
|
|
|
// Get current control target
|
|
get target() {
|
|
return length(this.control_stack) > 0
|
|
? this.control_stack[length(this.control_stack) - 1]
|
|
: null
|
|
},
|
|
|
|
// Dispatch action to current target
|
|
dispatch: function(action, data) {
|
|
var target = this.target
|
|
if (target && target.on_input) {
|
|
target.on_input(action, data)
|
|
}
|
|
},
|
|
|
|
// Get icon for action using current device
|
|
get_icon_for_action: function(action) {
|
|
return this.bindings.get_icon_for_action(action, this.device_kind, this.gamepad_type)
|
|
},
|
|
|
|
// Get primary binding for action using current device
|
|
get_primary_binding: function(action) {
|
|
return this.bindings.get_primary_binding(action, this.device_kind)
|
|
}
|
|
}
|
|
|
|
// Create router
|
|
user.router = router_mod.make(user, {
|
|
emacs: config.emacs,
|
|
gestures: config.gestures,
|
|
swipe_min_dist: config.swipe_min_dist,
|
|
swipe_max_time: config.swipe_max_time,
|
|
pinch_threshold: config.pinch_threshold
|
|
})
|
|
|
|
// Load saved bindings
|
|
user.bindings.load()
|
|
|
|
return user
|
|
}
|
|
|
|
// Pick user based on pairing policy
|
|
function pick_user(canon) {
|
|
if (length(_users) == 0) return null
|
|
|
|
// For last_used: always user 0, just update active device
|
|
if (_config.pairing == 'last_used') {
|
|
var user = _users[0]
|
|
|
|
// Only switch on button press, not axis/motion
|
|
if (canon.kind == 'button' && canon.pressed) {
|
|
if (user.active_device != canon.device_id) {
|
|
// Release all held actions when switching device
|
|
var old_down = user.router.down
|
|
for (var action in old_down) {
|
|
if (old_down[action]) {
|
|
user.dispatch(action, { pressed: false, released: true, time: canon.time })
|
|
}
|
|
}
|
|
|
|
user.active_device = canon.device_id
|
|
if (find(user.paired_devices, canon.device_id) == null) {
|
|
user.paired_devices.push(canon.device_id)
|
|
}
|
|
}
|
|
}
|
|
|
|
return user
|
|
}
|
|
|
|
// For explicit pairing: find user paired to this device
|
|
for (var i = 0; i < length(_users); i++) {
|
|
if (find(_users[i].paired_devices, canon.device_id) != null) {
|
|
_users[i].active_device = canon.device_id
|
|
return _users[i]
|
|
}
|
|
}
|
|
|
|
// Unpaired device - could implement join logic here
|
|
return null
|
|
}
|
|
|
|
// Configure the input system
|
|
function configure(opts) {
|
|
opts = opts || {}
|
|
|
|
_config.max_users = opts.max_users || 1
|
|
_config.pairing = opts.pairing || 'last_used'
|
|
_config.emacs = opts.emacs != false
|
|
_config.gestures = opts.gestures != false
|
|
|
|
if (opts.action_map) _config.action_map = opts.action_map
|
|
if (opts.display_names) _config.display_names = opts.display_names
|
|
if (opts.on_window) _window_callback = opts.on_window
|
|
|
|
// Copy gesture config
|
|
_config.swipe_min_dist = opts.swipe_min_dist
|
|
_config.swipe_max_time = opts.swipe_max_time
|
|
_config.pinch_threshold = opts.pinch_threshold
|
|
|
|
// Create users
|
|
_users = []
|
|
for (var i = 0; i < _config.max_users; i++) {
|
|
_users.push(create_user(i, _config))
|
|
}
|
|
|
|
_initialized = true
|
|
}
|
|
|
|
// Ingest a raw SDL event
|
|
function ingest(raw_evt) {
|
|
if (!_initialized) {
|
|
configure({})
|
|
}
|
|
|
|
// Translate to canonical format
|
|
var canon = backend.translate(raw_evt)
|
|
if (!canon) return
|
|
|
|
// Handle window events specially
|
|
if (canon.kind == 'window') {
|
|
if (_window_callback) {
|
|
_window_callback(canon)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Handle device events
|
|
if (canon.kind == 'device') {
|
|
if (canon.control == 'connected') {
|
|
devices.register(canon)
|
|
} else if (canon.control == 'disconnected') {
|
|
devices.unregister(canon.device_id)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Register device
|
|
devices.register(canon)
|
|
|
|
// Pick user and route
|
|
var user = pick_user(canon)
|
|
if (user) {
|
|
user.router.handle(canon)
|
|
}
|
|
}
|
|
|
|
// Get user by index
|
|
function user(index) {
|
|
return _users[index]
|
|
}
|
|
|
|
return {
|
|
configure: configure,
|
|
ingest: ingest,
|
|
user: user,
|
|
|
|
get player1() { return _users[0] },
|
|
get player2() { return _users[1] },
|
|
get player3() { return _users[2] },
|
|
get player4() { return _users[3] },
|
|
|
|
// Re-export for convenience
|
|
devices: devices,
|
|
backend: backend
|
|
} |