neon-sprawl/client/scripts/hotbar_cast_slot_resolver.gd

45 lines
1.7 KiB
GDScript

extends Object
## Pure resolver for digit hotbar → cast intent (NEO-31). [code]main.gd[/code] delegates here so
## GdUnit can cover unbound slots and [code]lockedTargetId[/code] sourcing without booting
## the scene.
## No [code]class_name[/code] — match [code]prototype_target_constants.gd[/code] preload pattern.
const KEY_OK := "ok"
const KEY_REASON := "reason"
const KEY_SLOT_INDEX := "slotIndex"
const KEY_ABILITY_ID := "abilityId"
const KEY_TARGET_ID := "targetId"
const REASON_INVALID_SLOT := "invalid_slot_index"
const REASON_EMPTY_SLOT := "empty_slot"
## [param slots] snapshot from [code]HotbarState.slots_snapshot()[/code] (fixed length, entries
## [code]null[/code] or [String] ability ids).
## [param target_cached_state] from [code]TargetSelectionClient.cached_state()[/code]
## (may be empty).
## Returns [code]{ "ok": true, "slotIndex", "abilityId", "targetId" }[/code] or
## [code]{ "ok": false, "reason" }[/code].
static func resolve(slot_index: int, slots: Array, target_cached_state: Dictionary) -> Dictionary:
if slot_index < 0 or slot_index >= slots.size():
return {KEY_OK: false, KEY_REASON: REASON_INVALID_SLOT}
var ability_variant: Variant = slots[slot_index]
if (
ability_variant == null
or not (ability_variant is String)
or (ability_variant as String).strip_edges().is_empty()
):
return {KEY_OK: false, KEY_REASON: REASON_EMPTY_SLOT}
var ability_id: String = (ability_variant as String).strip_edges()
var target_id: Variant = null
var locked: Variant = target_cached_state.get("lockedTargetId", null)
if locked is String and not (locked as String).is_empty():
target_id = locked as String
return {
KEY_OK: true,
KEY_SLOT_INDEX: slot_index,
KEY_ABILITY_ID: ability_id,
KEY_TARGET_ID: target_id,
}