diff --git a/client/scripts/item_definitions_client.gd b/client/scripts/item_definitions_client.gd index 9decc87..e70a04c 100644 --- a/client/scripts/item_definitions_client.gd +++ b/client/scripts/item_definitions_client.gd @@ -1,7 +1,7 @@ extends Node -## NEO-72: fetches [code]GET /game/world/item-definitions[/code] (NEO-53) and caches id → display metadata -## for inventory HUD labels. +## NEO-72: fetches [code]GET /game/world/item-definitions[/code] (NEO-53). +## Caches id → display metadata for inventory HUD labels. signal definitions_ready(definitions_by_id: Dictionary) diff --git a/client/scripts/main.gd b/client/scripts/main.gd index 3eb7e75..fd986d6 100644 --- a/client/scripts/main.gd +++ b/client/scripts/main.gd @@ -321,7 +321,9 @@ func _setup_inventory_sync() -> void: if _inventory_client.has_signal("inventory_received"): _inventory_client.connect("inventory_received", Callable(self, "_on_inventory_received")) if _inventory_client.has_signal("inventory_sync_failed"): - _inventory_client.connect("inventory_sync_failed", Callable(self, "_on_inventory_sync_failed")) + _inventory_client.connect( + "inventory_sync_failed", Callable(self, "_on_inventory_sync_failed") + ) _render_inventory_label() if _item_defs_client.has_method("request_sync_from_server"): _item_defs_client.call("request_sync_from_server") @@ -391,7 +393,9 @@ func _render_inventory_label() -> void: _inventory_label.text = "\n".join(lines) -func _format_inventory_slot_lines(raw_slots: Variant, always_show_empty_stub: bool) -> PackedStringArray: +func _format_inventory_slot_lines( + raw_slots: Variant, always_show_empty_stub: bool +) -> PackedStringArray: var out: PackedStringArray = PackedStringArray() if raw_slots == null or not raw_slots is Array: if always_show_empty_stub: @@ -408,15 +412,23 @@ func _format_inventory_slot_lines(raw_slots: Variant, always_show_empty_stub: bo out.append(" slot 0: — empty") continue var item_id: String = str(slot.get("itemId", "")) - var label: String = item_id - if is_instance_valid(_item_defs_client) and _item_defs_client.has_method("display_name_for"): - label = str(_item_defs_client.call("display_name_for", item_id)) + var label: String = _inventory_item_label(item_id) out.append(" slot %d: %s x%d" % [slot_index, label, quantity]) return out +func _inventory_item_label(item_id: String) -> String: + if not is_instance_valid(_item_defs_client): + return item_id + if not _item_defs_client.has_method("display_name_for"): + return item_id + return str(_item_defs_client.call("display_name_for", item_id)) + + func _request_inventory_refresh() -> void: - if is_instance_valid(_inventory_client) and _inventory_client.has_method("request_sync_from_server"): + if not is_instance_valid(_inventory_client): + return + if _inventory_client.has_method("request_sync_from_server"): _inventory_client.call("request_sync_from_server") @@ -541,38 +553,65 @@ func _on_move_rejected(reason_code: String) -> void: ## **F9** / **F10** are debugger shortcuts in the embedded game; use the Input Map action. ## For a true **freed-node** occluder test, reload the scene or remove the body in the editor. func _unhandled_key_input(event: InputEvent) -> void: - for slot_digit in range(1, 9): - var action_name := "hotbar_slot_%d" % slot_digit - if event.is_action_pressed(action_name): - _request_hotbar_cast_slot(slot_digit - 1) - return - # NEO-25: route interact keys here — `InteractionRequestClient._input` is unreliable in the - # embedded Game dock / focus order; `_unhandled_key_input` on the scene root still fires. - if event.is_action_pressed("interact"): - _forward_interact_post("post_interact_terminal") + if _try_route_gameplay_key_input(event): return - if event.is_action_pressed("interact_secondary"): - _forward_interact_post("post_interact_resource") - return - if event.is_action_pressed("inventory_refresh"): - _request_inventory_refresh() - return - if event is InputEventKey: - var k := event as InputEventKey - if k.pressed and not k.echo and (k.physical_keycode == KEY_E or k.keycode == KEY_E): - _forward_interact_post("post_interact_terminal") - return - if k.pressed and not k.echo and (k.physical_keycode == KEY_R or k.keycode == KEY_R): - _forward_interact_post("post_interact_resource") - return - if k.pressed and not k.echo and (k.physical_keycode == KEY_I or k.keycode == KEY_I): - _request_inventory_refresh() - return if not event.is_action_pressed("dev_toggle_occluder_obstacle"): return call_deferred("_dev_toggle_obstacle_smoke_deferred") +## NEO-25 / NEO-72: route interact + inventory keys here — `InteractionRequestClient._input` +## is unreliable in the embedded Game dock; `_unhandled_key_input` on the scene root still fires. +func _try_route_gameplay_key_input(event: InputEvent) -> bool: + var hotbar_slot := _hotbar_slot_index_from_event(event) + if hotbar_slot >= 0: + _request_hotbar_cast_slot(hotbar_slot) + return true + if _try_interact_key_input(event): + return true + if _try_inventory_refresh_input(event): + return true + return false + + +func _try_interact_key_input(event: InputEvent) -> bool: + if event.is_action_pressed("interact"): + _forward_interact_post("post_interact_terminal") + return true + if event.is_action_pressed("interact_secondary"): + _forward_interact_post("post_interact_resource") + return true + if event is InputEventKey: + var k := event as InputEventKey + if k.pressed and not k.echo: + if k.physical_keycode == KEY_E or k.keycode == KEY_E: + _forward_interact_post("post_interact_terminal") + return true + if k.physical_keycode == KEY_R or k.keycode == KEY_R: + _forward_interact_post("post_interact_resource") + return true + return false + + +func _hotbar_slot_index_from_event(event: InputEvent) -> int: + for slot_digit in range(1, 9): + if event.is_action_pressed("hotbar_slot_%d" % slot_digit): + return slot_digit - 1 + return -1 + + +func _try_inventory_refresh_input(event: InputEvent) -> bool: + if event.is_action_pressed("inventory_refresh"): + _request_inventory_refresh() + return true + if event is InputEventKey: + var k := event as InputEventKey + if k.pressed and not k.echo and (k.physical_keycode == KEY_I or k.keycode == KEY_I): + _request_inventory_refresh() + return true + return false + + func _request_hotbar_cast_slot(slot_index: int) -> void: if not is_instance_valid(_hotbar_state) or not _hotbar_state.has_method("slots_snapshot"): return diff --git a/client/test/inventory_client_test.gd b/client/test/inventory_client_test.gd index 21247e5..2b0820a 100644 --- a/client/test/inventory_client_test.gd +++ b/client/test/inventory_client_test.gd @@ -42,9 +42,12 @@ static func _empty_inventory_json() -> String: bag_parts.append('{"slotIndex":%d,"quantity":0}' % i) var bag: String = ", ".join(bag_parts) return ( - '{"schemaVersion":1,"playerId":"dev-local-1","bagSlots":[%s],' - + '"equipmentSlots":[{"slotIndex":0,"quantity":0}]}' - ) % bag + ( + '{"schemaVersion":1,"playerId":"dev-local-1","bagSlots":[%s],' + + '"equipmentSlots":[{"slotIndex":0,"quantity":0}]}' + ) + % bag + ) func _make_client(transport: Node) -> Node: