gd: added input addon
This commit is contained in:
307
addons/guide/remapping/guide_remapper.gd
Normal file
307
addons/guide/remapping/guide_remapper.gd
Normal file
@ -0,0 +1,307 @@
|
||||
class_name GUIDERemapper
|
||||
|
||||
## Emitted when the bound input of an item changes.
|
||||
signal item_changed(item:ConfigItem, input:GUIDEInput)
|
||||
|
||||
var _remapping_config:GUIDERemappingConfig = GUIDERemappingConfig.new()
|
||||
var _mapping_contexts:Array[GUIDEMappingContext] = []
|
||||
|
||||
const GUIDESet = preload("../guide_set.gd")
|
||||
|
||||
## Loads the default bindings as they are currently configured in the mapping contexts and a mapping
|
||||
## config for editing. Note that the given mapping config will not be modified, so editing can be
|
||||
## cancelled. Call get_mapping_config to get the modified mapping config.
|
||||
func initialize(mapping_contexts:Array[GUIDEMappingContext], remapping_config:GUIDERemappingConfig):
|
||||
_remapping_config = remapping_config.duplicate() if remapping_config != null else GUIDERemappingConfig.new()
|
||||
|
||||
_mapping_contexts.clear()
|
||||
|
||||
for mapping_context in mapping_contexts:
|
||||
if not is_instance_valid(mapping_context):
|
||||
push_error("Cannot add null mapping context. Ignoring.")
|
||||
return
|
||||
_mapping_contexts.append(mapping_context)
|
||||
|
||||
|
||||
## Returns the mapping config with all modifications applied.
|
||||
func get_mapping_config() -> GUIDERemappingConfig:
|
||||
return _remapping_config.duplicate()
|
||||
|
||||
|
||||
func set_custom_data(key:Variant, value:Variant):
|
||||
_remapping_config.custom_data[key] = value
|
||||
|
||||
|
||||
func get_custom_data(key:Variant, default:Variant = null) -> Variant:
|
||||
return _remapping_config.custom_data.get(key, default)
|
||||
|
||||
|
||||
func remove_custom_data(key:Variant) -> void:
|
||||
_remapping_config.custom_data.erase(key)
|
||||
|
||||
|
||||
## Returns all remappable items. Can be filtered by context, display category or
|
||||
## action.
|
||||
func get_remappable_items(context:GUIDEMappingContext = null,
|
||||
display_category:String = "",
|
||||
action:GUIDEAction = null) -> Array[ConfigItem]:
|
||||
|
||||
if action != null and not action.is_remappable:
|
||||
push_warning("Action filter was set but filtered action is not remappable.")
|
||||
return []
|
||||
|
||||
|
||||
var result:Array[ConfigItem] = []
|
||||
for a_context:GUIDEMappingContext in _mapping_contexts:
|
||||
if context != null and context != a_context:
|
||||
continue
|
||||
for action_mapping:GUIDEActionMapping in a_context.mappings:
|
||||
var mapped_action:GUIDEAction = action_mapping.action
|
||||
# filter non-remappable actions
|
||||
if not mapped_action.is_remappable:
|
||||
continue
|
||||
|
||||
# if action filter is set, only pick mappings for this action
|
||||
if action != null and action != mapped_action:
|
||||
continue
|
||||
|
||||
# make config items
|
||||
for index:int in action_mapping.input_mappings.size():
|
||||
var input_mapping:GUIDEInputMapping = action_mapping.input_mappings[index]
|
||||
if input_mapping.override_action_settings and not input_mapping.is_remappable:
|
||||
# skip non-remappable items
|
||||
continue
|
||||
|
||||
# Calculate effective display category
|
||||
var effective_display_category:String = \
|
||||
_get_effective_display_category(mapped_action, input_mapping)
|
||||
|
||||
# if display category filter is set, only pick mappings
|
||||
# in this category
|
||||
if display_category.length() > 0 and effective_display_category != display_category:
|
||||
continue
|
||||
|
||||
var item = ConfigItem.new(a_context, action_mapping.action, index, input_mapping)
|
||||
item_changed.connect(item._item_changed)
|
||||
result.append(item)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
static func _get_effective_display_category(action:GUIDEAction, input_mapping:GUIDEInputMapping) -> String:
|
||||
var result:String = ""
|
||||
if input_mapping.override_action_settings:
|
||||
result = input_mapping.display_category
|
||||
|
||||
if result.is_empty():
|
||||
result = action.display_category
|
||||
|
||||
return result
|
||||
|
||||
|
||||
static func _get_effective_display_name(action:GUIDEAction, input_mapping:GUIDEInputMapping) -> String:
|
||||
var result:String = ""
|
||||
if input_mapping.override_action_settings:
|
||||
result = input_mapping.display_name
|
||||
|
||||
if result.is_empty():
|
||||
result = action.display_name
|
||||
|
||||
return result
|
||||
|
||||
static func _is_effectively_remappable(action:GUIDEAction, input_mapping:GUIDEInputMapping) -> bool:
|
||||
return action.is_remappable and ((not input_mapping.override_action_settings) or input_mapping.is_remappable)
|
||||
|
||||
|
||||
static func _get_effective_value_type(action:GUIDEAction, input_mapping:GUIDEInputMapping) -> GUIDEAction.GUIDEActionValueType:
|
||||
if input_mapping.override_action_settings and input_mapping.input != null:
|
||||
return input_mapping.input._native_value_type()
|
||||
|
||||
return action.action_value_type
|
||||
|
||||
|
||||
## Returns a list of all collisions in all contexts when this new input would be applied to the config item.
|
||||
func get_input_collisions(item:ConfigItem, input:GUIDEInput) -> Array[ConfigItem]:
|
||||
if not _check_item(item):
|
||||
return []
|
||||
var result:Array[ConfigItem] = []
|
||||
|
||||
if input == null:
|
||||
# no item collides with absent input
|
||||
return result
|
||||
|
||||
# walk over all known contexts and find any mappings.
|
||||
for context:GUIDEMappingContext in _mapping_contexts:
|
||||
for action_mapping:GUIDEActionMapping in context.mappings:
|
||||
for index:int in action_mapping.input_mappings.size():
|
||||
var action := action_mapping.action
|
||||
if context == item.context and action == item.action and index == item.index:
|
||||
# collisions with self are allowed
|
||||
continue
|
||||
|
||||
var input_mapping:GUIDEInputMapping = action_mapping.input_mappings[index]
|
||||
var bound_input:GUIDEInput = input_mapping.input
|
||||
# check if this is currently overridden
|
||||
if _remapping_config._has(context, action, index):
|
||||
bound_input = _remapping_config._get_bound_input_or_null(context, action, index)
|
||||
|
||||
# We have a collision
|
||||
if bound_input != null and bound_input.is_same_as(input):
|
||||
var collision_item := ConfigItem.new(context, action, index, input_mapping)
|
||||
item_changed.connect(collision_item._item_changed)
|
||||
result.append(collision_item)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
## Gets the input currently bound to the action in the given context. Can be null if the input
|
||||
## is currently not bound.
|
||||
func get_bound_input_or_null(item:ConfigItem) -> GUIDEInput:
|
||||
if not _check_item(item):
|
||||
return null
|
||||
|
||||
# If the remapping config has a binding for this, this binding wins.
|
||||
if _remapping_config._has(item.context, item.action, item.index):
|
||||
return _remapping_config._get_bound_input_or_null(item.context, item.action, item.index)
|
||||
|
||||
# otherwise return the default binding for this action in the context
|
||||
for action_mapping:GUIDEActionMapping in item.context.mappings:
|
||||
if action_mapping.action == item.action:
|
||||
if action_mapping.input_mappings.size() > item.index:
|
||||
return action_mapping.input_mappings[item.index].input
|
||||
else:
|
||||
push_error("Action mapping does not have an index of ", item.index , ".")
|
||||
|
||||
return null
|
||||
|
||||
## Sets the bound input to the new value for the given config item. Ignores collisions
|
||||
## because collision resolution is highly game specific. Use get_input_collisions to find
|
||||
## potential collisions and then resolve them in a way that suits the game. Note that
|
||||
## bound input can be set to null, which deliberately unbinds the input. If you want
|
||||
## to restore the defaults, call restore_default instead.
|
||||
func set_bound_input(item:ConfigItem, input:GUIDEInput) -> void:
|
||||
if not _check_item(item):
|
||||
return
|
||||
|
||||
# first remove any custom binding we have
|
||||
_remapping_config._clear(item.context, item.action, item.index)
|
||||
|
||||
# Now check if the input is the same as the default
|
||||
var bound_input:GUIDEInput = get_bound_input_or_null(item)
|
||||
|
||||
if bound_input == null and input == null:
|
||||
item_changed.emit(item, input)
|
||||
return # nothing to do
|
||||
|
||||
if bound_input == null:
|
||||
_remapping_config._bind(item.context, item.action, input, item.index)
|
||||
item_changed.emit(item, input)
|
||||
return
|
||||
|
||||
if bound_input != null and input != null and bound_input.is_same_as(input):
|
||||
item_changed.emit(item, input)
|
||||
return # nothing to do
|
||||
|
||||
_remapping_config._bind(item.context, item.action, input, item.index)
|
||||
item_changed.emit(item, input)
|
||||
|
||||
|
||||
## Returns the default binding for the given config item.
|
||||
func get_default_input(item:ConfigItem) -> GUIDEInput:
|
||||
if not _check_item(item):
|
||||
return null
|
||||
|
||||
for mapping:GUIDEActionMapping in item.context.mappings:
|
||||
if mapping.action == item.action:
|
||||
# _check_item verifies the index exists, so no need to check here.
|
||||
return mapping.input_mappings[item.index].input
|
||||
|
||||
return null
|
||||
|
||||
|
||||
## Restores the default binding for the given config item. Note that this may
|
||||
## introduce a conflict if other bindings have bound conflicting input. You can
|
||||
## call get_default_input for the given item to get the default input and then
|
||||
## call get_input_collisions for that to find out whether you would get a collision.
|
||||
func restore_default_for(item:ConfigItem) -> void:
|
||||
if not _check_item(item):
|
||||
return
|
||||
|
||||
_remapping_config._clear(item.context, item.action, item.index)
|
||||
item_changed.emit(item, get_bound_input_or_null(item))
|
||||
|
||||
|
||||
|
||||
## Verifies that the given item is valid.
|
||||
func _check_item(item:ConfigItem) -> bool:
|
||||
if not _mapping_contexts.has(item.context):
|
||||
push_error("Given context is not known to this mapper. Did you call initialize()?")
|
||||
return false
|
||||
|
||||
var action_found := false
|
||||
var size_ok := false
|
||||
for mapping in item.context.mappings:
|
||||
if mapping.action == item.action:
|
||||
action_found = true
|
||||
if mapping.input_mappings.size() > item.index and item.index >= 0:
|
||||
size_ok = true
|
||||
break
|
||||
|
||||
if not action_found:
|
||||
push_error("Given action does not belong to the given context.")
|
||||
return false
|
||||
|
||||
if not size_ok:
|
||||
push_error("Given index does not exist for the given action's input binding.")
|
||||
|
||||
|
||||
if not item.action.is_remappable:
|
||||
push_error("Given action is not remappable.")
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
|
||||
class ConfigItem:
|
||||
## Emitted when the input to this item has changed.
|
||||
signal changed(input:GUIDEInput)
|
||||
|
||||
var _input_mapping:GUIDEInputMapping
|
||||
|
||||
## The display category for this config item
|
||||
var display_category:String:
|
||||
get: return GUIDERemapper._get_effective_display_category(action, _input_mapping)
|
||||
|
||||
## The display name for this config item.
|
||||
var display_name:String:
|
||||
get: return GUIDERemapper._get_effective_display_name(action, _input_mapping)
|
||||
|
||||
## Whether this item is remappable.
|
||||
var is_remappable:bool:
|
||||
get: return GUIDERemapper._is_effectively_remappable(action, _input_mapping)
|
||||
|
||||
## The value type for this config item.
|
||||
var value_type:GUIDEAction.GUIDEActionValueType:
|
||||
get: return GUIDERemapper._get_effective_value_type(action, _input_mapping)
|
||||
|
||||
var context:GUIDEMappingContext
|
||||
var action:GUIDEAction
|
||||
var index:int
|
||||
|
||||
func _init(context:GUIDEMappingContext, action:GUIDEAction, index:int, input_mapping:GUIDEInputMapping):
|
||||
self.context = context
|
||||
self.action = action
|
||||
self.index = index
|
||||
_input_mapping = input_mapping
|
||||
|
||||
## Checks whether this config item is the same as some other
|
||||
## e.g. refers to the same input mapping.
|
||||
func is_same_as(other:ConfigItem) -> bool:
|
||||
return context == other.context and \
|
||||
action == other.action and \
|
||||
index == other.index
|
||||
|
||||
func _item_changed(item:ConfigItem, input:GUIDEInput):
|
||||
if item.is_same_as(self):
|
||||
changed.emit(input)
|
||||
|
Reference in New Issue
Block a user