extends Control class_name InventoryBackpack signal overlay_show_requested signal overlay_hide_requested signal item_selected(item_id: String) signal returning_to_idle signal skip_action_requested enum State { IDLE, OPEN, SELECTED, ACQUIRE, REMOVE } var _state: State = State.IDLE var _animating: bool = false var _active_tween: Tween = null var _floating_item_color: Color = Color(1, 1, 1, 0) var _home_position: Vector2 = Vector2(0, 0) @onready var backpack_icon: ColorRect = $BackpackIcon @onready var floating_item: ColorRect = $FloatingItem @onready var animation_player: AnimationPlayer = $AnimationPlayer func _ready() -> void: _home_position = backpack_icon.position floating_item.modulate = Color(1, 1, 1, 0) floating_item.visible = false InventoryManager.item_acquired.connect(_on_item_acquired) InventoryManager.item_removed.connect(_on_item_removed) InventoryManager.inventory_changed.connect(_on_inventory_changed) _update_floating_item() func get_state() -> State: return _state func is_busy() -> bool: return _animating func _update_floating_item() -> void: if InventoryManager.selected_item: var def = InventoryManager.get_item_definition(InventoryManager.selected_item) if def: floating_item.visible = true floating_item.modulate = Color(1, 0.6, 0.2, 1) else: floating_item.visible = true floating_item.modulate = Color(0.8, 0.8, 0.8, 1) else: floating_item.visible = false func transition_to(new_state: State) -> void: if _animating and new_state != _state: _kill_tween() match new_state: State.IDLE: _transition_to_idle() State.OPEN: _transition_to_open() State.SELECTED: _transition_to_selected() State.ACQUIRE: _transition_to_acquire() State.REMOVE: _transition_to_remove() func _transition_to_idle() -> void: if _state == State.IDLE: return _set_state(State.IDLE) overlay_hide_requested.emit() backpack_icon.modulate = Color(1, 1, 1, 1) floating_item.visible = false var tween = create_tween().bind_node(self) _active_tween = tween _animating = true tween.tween_property(backpack_icon, "rotation", 0.0, 0.35).set_trans(Tween.TRANS_LINEAR) tween.tween_property(backpack_icon, "position", _home_position, 0.35).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN) tween.tween_callback(_on_transition_complete.bind(State.IDLE)) func _transition_to_open() -> void: var can_open = _check_guards() if not can_open: if _state == State.IDLE: skip_action_requested.emit() return _set_state(State.OPEN) var tween = create_tween().bind_node(self) _active_tween = tween _animating = true tween.tween_property(backpack_icon, "rotation", PI / 4, 0.35).set_trans(Tween.TRANS_LINEAR) tween.tween_property(backpack_icon, "position", _home_position + Vector2(20, 20), 0.35).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN) tween.tween_callback(_on_transition_complete.bind(State.OPEN)) tween.tween_callback(overlay_show_requested.emit) func _transition_to_selected() -> void: if _state == State.SELECTED: return _set_state(State.SELECTED) _update_floating_item() var tween = create_tween().bind_node(self) _active_tween = tween _animating = true floating_item.visible = true floating_item.modulate = Color(1, 1, 1, 0) tween.tween_property(floating_item, "modulate", Color(1, 1, 1, 1), 0.5).set_trans(Tween.TRANS_LINEAR) tween.tween_callback(_on_transition_complete.bind(State.SELECTED)) func _transition_to_acquire() -> void: if _state == State.ACQUIRE or _state == State.REMOVE: _kill_tween() _set_state(State.ACQUIRE) var tween = create_tween().bind_node(self) _active_tween = tween _animating = true backpack_icon.modulate = Color(1, 1, 1, 1) tween.tween_property(backpack_icon, "rotation", PI / 4, 0.35).set_trans(Tween.TRANS_LINEAR) tween.tween_callback(_start_acquire_item_anim) func _start_acquire_item_anim() -> void: var tween = create_tween().bind_node(self) _active_tween = tween floating_item.visible = true floating_item.modulate = Color(1, 1, 1, 0) floating_item.position = Vector2(backpack_icon.position.x - 20, backpack_icon.position.y - 40) tween.tween_property(floating_item, "modulate", Color(1, 1, 1, 1), 0.5).set_trans(Tween.TRANS_LINEAR) tween.tween_property(floating_item, "position", backpack_icon.position, 0.5).set_trans(Tween.TRANS_LINEAR) tween.tween_callback(_on_acquire_complete) func _on_acquire_complete() -> void: floating_item.visible = false transition_to(State.IDLE) func _transition_to_remove() -> void: if _state == State.ACQUIRE or _state == State.REMOVE: _kill_tween() _set_state(State.REMOVE) var tween = create_tween().bind_node(self) _active_tween = tween _animating = true backpack_icon.modulate = Color(1, 1, 1, 1) tween.tween_property(backpack_icon, "rotation", PI / 4, 0.35).set_trans(Tween.TRANS_LINEAR) tween.tween_callback(_start_remove_item_anim) func _start_remove_item_anim() -> void: var tween = create_tween().bind_node(self) _active_tween = tween floating_item.visible = true floating_item.modulate = Color(1, 1, 1, 1) floating_item.position = backpack_icon.position tween.tween_property(floating_item, "position", Vector2(backpack_icon.position.x - 20, backpack_icon.position.y - 80), 0.5).set_trans(Tween.TRANS_LINEAR) tween.parallel().tween_property(floating_item, "modulate", Color(1, 1, 1, 0), 0.5).set_trans(Tween.TRANS_LINEAR) tween.tween_callback(_on_remove_complete) func _on_remove_complete() -> void: floating_item.visible = false var was_selected = InventoryManager.selected_item != "" transition_to(State.IDLE) if was_selected: InventoryManager.clear_selection() func _set_state(new_state: State) -> void: _state = new_state func _check_guards() -> bool: var main_game = get_node_or_null("/root/Node2D") if not main_game: return true if main_game.is_script_running: return false var fade = get_node_or_null("/root/Node2D/SceneDisplay/Fade") if fade and fade.modulate.a > 0.5: return false return true func _kill_tween() -> void: if _active_tween: _active_tween.kill() _active_tween = null _animating = false func _on_transition_complete(completed_state: State) -> void: _animating = false _active_tween = null if completed_state == State.OPEN: pass elif completed_state == State.IDLE: returning_to_idle.emit() func _on_item_acquired(item_id: String) -> void: if _state == State.IDLE: transition_to(State.ACQUIRE) func _on_item_removed(item_id: String) -> void: if _state == State.IDLE: if InventoryManager.selected_item == item_id: InventoryManager.clear_selection() _update_floating_item() transition_to(State.REMOVE) else: transition_to(State.REMOVE) elif _state == State.SELECTED and InventoryManager.selected_item == item_id: InventoryManager.clear_selection() _update_floating_item() transition_to(State.REMOVE) func _on_inventory_changed() -> void: _update_floating_item() func _gui_input(event: InputEvent) -> void: print("[BACKPACK] _gui_input: %s, state=%s" % [event, State.keys()[_state]]) if event is InputEventMouseButton and event.pressed and event.button_index == 1: if _state == State.IDLE: print("[BACKPACK] transitioning to OPEN") transition_to(State.OPEN) elif _state == State.OPEN: print("[BACKPACK] transitioning to IDLE") transition_to(State.IDLE) func _notification(what: int) -> void: if what == NOTIFICATION_PREDELETE: _kill_tween()