misty
This commit is contained in:
278
input.cm
278
input.cm
@@ -1,12 +1,274 @@
|
||||
var sdl_input = use('sdl3/input')
|
||||
var emacs = use('emacs')
|
||||
var gestures = use('gestures')
|
||||
// Prosperon Input System
|
||||
// Engine-driven input with user pairing and possession dispatch
|
||||
|
||||
return {
|
||||
gamepad_id_to_tyle: sdl_input.gamepad_id_to_type,
|
||||
make: function() {
|
||||
return {
|
||||
|
||||
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 (this.control_stack.length > 1) {
|
||||
return this.control_stack.pop()
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
// Get current control target
|
||||
get target() {
|
||||
return this.control_stack.length > 0
|
||||
? this.control_stack[this.control_stack.length - 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 (_users.length == 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 < _users.length; 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
|
||||
}
|
||||
Reference in New Issue
Block a user