118 lines
5.7 KiB
GDScript
118 lines
5.7 KiB
GDScript
class_name ControlBindings
|
|
extends RefCounted
|
|
|
|
const PATH := "user://controls.cfg"
|
|
const ACTIONS := ["forward", "back", "left", "right", "jump", "sprint", "crouch", "fire", "aim", "reload", "switch", "grenade", "flash", "melee", "interact", "shield", "ultimate", "scoreboard", "pause", "fullscreen"]
|
|
const LABELS := ["Vorwärts", "Rückwärts", "Links", "Rechts", "Springen / Hochziehen", "Sprinten", "Ducken / Rutschen", "Schießen", "Zielen / ADS", "Nachladen", "Waffe wechseln", "Frag-Granate", "Flashbang", "Nahkampf", "Interagieren / Seilaufzug", "Energieschild", "Claymore", "Scoreboard", "Menü / Pause", "Vollbild"]
|
|
const KEYS := [KEY_W, KEY_S, KEY_A, KEY_D, KEY_SPACE, KEY_SHIFT, KEY_CTRL, 0, 0, KEY_R, KEY_Q, KEY_G, KEY_F, KEY_V, KEY_E, KEY_B, KEY_H, KEY_TAB, KEY_ESCAPE, KEY_F11]
|
|
const HELD := ["forward", "back", "left", "right", "sprint", "crouch", "fire", "aim", "scoreboard"]
|
|
static var bindings: Dictionary = {}
|
|
static var hints: Dictionary = {}
|
|
|
|
static func defaults() -> void:
|
|
bindings.clear()
|
|
for index in ACTIONS.size():
|
|
var action: String = ACTIONS[index]
|
|
bindings[action] = [{"kind": "key", "code": int(KEYS[index])}, {}]
|
|
if action == "fire": bindings[action][0] = {"kind": "mouse", "code": MOUSE_BUTTON_LEFT}
|
|
if action == "aim": bindings[action][0] = {"kind": "mouse", "code": MOUSE_BUTTON_RIGHT}
|
|
apply()
|
|
|
|
static func setup(path: String = PATH) -> void:
|
|
defaults()
|
|
var config := ConfigFile.new()
|
|
if config.load(path) != OK: return
|
|
for action: String in ACTIONS:
|
|
if not config.has_section_key("bindings", action): continue
|
|
var slots: Variant = config.get_value("bindings", action, null)
|
|
if not slots is Array or slots.size() != 2: continue
|
|
var valid := true
|
|
for item: Variant in slots:
|
|
if not item is Dictionary or (not item.is_empty() and clean(item).is_empty()): valid = false
|
|
if valid and allowed(action, clean(slots[0])) and allowed(action, clean(slots[1])):
|
|
bindings[action] = [clean(slots[0]), clean(slots[1])]
|
|
# A corrupted configuration must never remove the way out of a match.
|
|
if bindings.pause[0].is_empty() and bindings.pause[1].is_empty(): bindings.pause[0] = {"kind": "key", "code": KEY_ESCAPE}
|
|
apply()
|
|
|
|
static func clean(value: Dictionary) -> Dictionary:
|
|
var code: Variant = value.get("code", null)
|
|
if not code is int or code <= 0: return {}
|
|
var kind := str(value.get("kind", ""))
|
|
if kind == "mouse" and code <= MOUSE_BUTTON_XBUTTON2: return {"kind": kind, "code": code}
|
|
if kind == "key" and code < (1 << 25): return {"kind": kind, "code": code}
|
|
return {}
|
|
|
|
static func encode(event: InputEvent) -> Dictionary:
|
|
if event is InputEventKey:
|
|
return {"kind": "key", "code": int(event.physical_keycode if event.physical_keycode != 0 else event.keycode)}
|
|
if event is InputEventMouseButton:
|
|
return clean({"kind": "mouse", "code": int(event.button_index)})
|
|
return {}
|
|
|
|
static func decode(value: Dictionary) -> InputEvent:
|
|
if value.is_empty(): return null
|
|
if value.kind == "key":
|
|
var event := InputEventKey.new()
|
|
event.physical_keycode = int(value.code)
|
|
return event
|
|
var event := InputEventMouseButton.new()
|
|
event.button_index = int(value.code)
|
|
return event
|
|
|
|
static func apply() -> void:
|
|
hints.clear()
|
|
for action: String in ACTIONS:
|
|
if not InputMap.has_action(action): InputMap.add_action(action)
|
|
InputMap.action_erase_events(action)
|
|
for slot: Dictionary in bindings[action]:
|
|
var event := decode(slot)
|
|
if event != null: InputMap.action_add_event(action, event)
|
|
hints[action] = "—"
|
|
for slot: Dictionary in bindings[action]:
|
|
if not slot.is_empty():
|
|
hints[action] = label(slot)
|
|
break
|
|
|
|
static func conflicts(action: String, slot: int, event: Dictionary) -> Array[Dictionary]:
|
|
var result: Array[Dictionary] = []
|
|
for other: String in ACTIONS:
|
|
for index in 2:
|
|
if other == action and index == slot: continue
|
|
if not event.is_empty() and bindings[other][index] == event: result.append({"action": other, "slot": index})
|
|
return result
|
|
|
|
static func assign(action: String, slot: int, event: Dictionary, swap: bool = false) -> bool:
|
|
if not action in ACTIONS or slot < 0 or slot > 1: return false
|
|
var value := clean(event)
|
|
if not event.is_empty() and value.is_empty(): return false
|
|
if not allowed(action, value): return false
|
|
if action == "pause" and value.is_empty() and bindings[action][1 - slot].is_empty(): return false
|
|
var collisions := conflicts(action, slot, value)
|
|
if not collisions.is_empty() and not swap: return false
|
|
var old: Dictionary = bindings[action][slot].duplicate()
|
|
for collision in collisions:
|
|
if not allowed(str(collision.action), old): return false
|
|
if collision.action == "pause" and old.is_empty() and bindings.pause[1 - int(collision.slot)].is_empty(): return false
|
|
for collision in collisions: bindings[collision.action][collision.slot] = old.duplicate()
|
|
bindings[action][slot] = value
|
|
apply()
|
|
return true
|
|
|
|
static func allowed(action: String, value: Dictionary) -> bool:
|
|
return value.is_empty() or not (value.kind == "mouse" and int(value.code) in [MOUSE_BUTTON_WHEEL_UP, MOUSE_BUTTON_WHEEL_DOWN, MOUSE_BUTTON_WHEEL_LEFT, MOUSE_BUTTON_WHEEL_RIGHT] and action in HELD)
|
|
|
|
static func save(path: String = PATH) -> Error:
|
|
var config := ConfigFile.new()
|
|
for action: String in ACTIONS: config.set_value("bindings", action, bindings[action])
|
|
return config.save(path)
|
|
|
|
static func label(value: Dictionary) -> String:
|
|
if value.is_empty(): return "—"
|
|
if value.kind == "mouse": return ["", "Maus 1", "Maus 2", "Maus 3", "Rad ↑", "Rad ↓", "Rad ←", "Rad →", "Maus 4", "Maus 5"][int(value.code)]
|
|
var key := int(value.code) if DisplayServer.get_name() == "headless" else DisplayServer.keyboard_get_keycode_from_physical(int(value.code))
|
|
return OS.get_keycode_string(key if key != 0 else int(value.code))
|
|
|
|
static func hint(action: String) -> String:
|
|
return str(hints.get(action, action))
|