NPC characters - Articulated rig (hips/knees, shoulders/elbows/hands) replaces rigid capsules; the walk cycle bends knees and elbows and flees with a longer stride. - Layered outfits (jacket/vest/coat/dress), extended accessories and hand items (bag, umbrella, phone, camera, clipboard, cup), face details and five hair silhouettes, all deterministic per profile seed. - Idle behaviour layers (phone, clipboard, crossed arms, look-around), breathing and weight shift; escalation raises hands or points, and noticed NPCs turn toward the guard. - Reaction symbols above the head (!!, ?!, !, ?), door-queue offsets assigned by GameManager, three LOD tiers with hysteresis and a billboard impostor. - smoke_npc_budget.gd: 16 avatars = 534 meshes, ~6.9 ms average (50 ms budget). Player movement - Separate accel/decel, step-up assist for kerbs and the entrance steps, and a forward-axis fix: W was inverted (get_axis(forward, back) is negative). - Head bob, landing dip and precision-walk FOV, gated by persisted head-bob and invert-Y settings; precision chip and tick; surface-aware footsteps and a landing cue forwarded to AudioFeedback. - Gamepad support (sticks, deadzone, face buttons, bumpers), a control legend that follows the active device, and named actions for the remaining keys. - Key/button remapping in Settings, persisted as input_bindings and applied by InputBindings (mouse and gamepad axes are never removed). - smoke_movement.gd (windowed): climbs the steps, checks precision speed, chip, footsteps and landing; new 11-precision-walk capture and catalogue entry. Verification: headless 208/208, Python tooling 56/56, all 11 smokes pass, compare 11 good / 0 bad, movement judge match. Version 0.5.0. Only optional Future ideas remain in TODO.
68 lines
2.5 KiB
GDScript
68 lines
2.5 KiB
GDScript
class_name InputBindings
|
|
extends RefCounted
|
|
## Key/button remaps persisted through the settings panel. Games keep working
|
|
## with the defaults in `project.godot`; only actions the player rebinds are
|
|
## rewritten, and remaps never remove mouse or gamepad axis events.
|
|
|
|
const REMAPPABLE := [
|
|
"move_forward", "move_back", "move_left", "move_right",
|
|
"walk_slow", "inspect", "wand", "radio_emergency", "action_menu",
|
|
]
|
|
|
|
## Turn an input event into a storable descriptor, or {} when it is not a key or
|
|
## pad button (mouse buttons and axes are deliberately not remappable here).
|
|
static func descriptor_from_event(event: InputEvent) -> Dictionary:
|
|
if event is InputEventKey:
|
|
var key := event as InputEventKey
|
|
var code := key.physical_keycode if key.physical_keycode != 0 else key.keycode
|
|
if code == 0:
|
|
return {}
|
|
return {"type": "key", "code": int(code)}
|
|
if event is InputEventJoypadButton:
|
|
return {"type": "joy", "button": int((event as InputEventJoypadButton).button_index)}
|
|
return {}
|
|
|
|
static func label_for(descriptor: Dictionary) -> String:
|
|
match str(descriptor.get("type", "")):
|
|
"key":
|
|
return OS.get_keycode_string(int(descriptor.get("code", 0)))
|
|
"joy":
|
|
return "Pad %d" % int(descriptor.get("button", 0))
|
|
_:
|
|
return ""
|
|
|
|
## First key/button currently bound to the action, used to label the UI.
|
|
static func current_descriptor(action: String) -> Dictionary:
|
|
if not InputMap.has_action(action):
|
|
return {}
|
|
for event in InputMap.action_get_events(action):
|
|
var descriptor := descriptor_from_event(event)
|
|
if not descriptor.is_empty():
|
|
return descriptor
|
|
return {}
|
|
|
|
## Rewrite the actions present in `bindings`. Existing key/button events for
|
|
## those actions are replaced; mouse and joypad-axis events are left alone.
|
|
static func apply(bindings: Dictionary) -> void:
|
|
for action in bindings.keys():
|
|
if not InputMap.has_action(action):
|
|
continue
|
|
var descriptor: Dictionary = bindings[action]
|
|
if descriptor.is_empty():
|
|
continue
|
|
for event in InputMap.action_get_events(action):
|
|
if event is InputEventKey or event is InputEventJoypadButton:
|
|
InputMap.action_erase_event(action, event)
|
|
InputMap.action_add_event(action, event_for(descriptor))
|
|
|
|
static func event_for(descriptor: Dictionary) -> InputEvent:
|
|
match str(descriptor.get("type", "")):
|
|
"joy":
|
|
var button := InputEventJoypadButton.new()
|
|
button.button_index = int(descriptor.get("button", 0))
|
|
return button
|
|
_:
|
|
var key := InputEventKey.new()
|
|
key.physical_keycode = int(descriptor.get("code", 0))
|
|
return key
|