extends CanvasLayer const SLOT_SIZE: int = 48 const SLOT_MARGIN: int = 4 var inventory: Inventory = null var _hotbar_slots: Array = [] var _inv_slots: Array = [] var _inv_window: Control = null var _recipe_list: VBoxContainer = null var _is_open: bool = false func _ready() -> void: _build_hotbar() _build_inventory_window() func setup(inv: Inventory) -> void: inventory = inv inventory.inventory_changed.connect(_refresh) _refresh() func _build_hotbar() -> void: var hotbar_root := HBoxContainer.new() hotbar_root.name = "HotbarRoot" hotbar_root.add_theme_constant_override("separation", SLOT_MARGIN) var total_width: int = SLOT_SIZE * 9 + SLOT_MARGIN * 8 var anchor_node := Control.new() anchor_node.name = "HotbarAnchor" anchor_node.set_anchors_preset(Control.PRESET_BOTTOM_WIDE) anchor_node.custom_minimum_size = Vector2(total_width, SLOT_SIZE + 8) anchor_node.set_offset(SIDE_BOTTOM, -8) anchor_node.set_offset(SIDE_TOP, -(SLOT_SIZE + 16)) add_child(anchor_node) hotbar_root.set_anchors_preset(Control.PRESET_CENTER) hotbar_root.position = Vector2(-total_width / 2.0, 0) anchor_node.add_child(hotbar_root) for i: int in range(9): var slot := _make_slot_panel(i) hotbar_root.add_child(slot) _hotbar_slots.append(slot) func _make_slot_panel(index: int) -> PanelContainer: var panel := PanelContainer.new() panel.custom_minimum_size = Vector2(SLOT_SIZE, SLOT_SIZE) panel.set_meta("slot_index", index) var style_normal := StyleBoxFlat.new() style_normal.bg_color = Color(0.05, 0.1, 0.2, 0.85) style_normal.border_color = Color(0.0, 0.8, 0.8) style_normal.set_border_width_all(2) style_normal.set_corner_radius_all(3) panel.add_theme_stylebox_override("panel", style_normal) panel.set_meta("style_normal", style_normal) var style_selected := StyleBoxFlat.new() style_selected.bg_color = Color(0.05, 0.1, 0.2, 0.85) style_selected.border_color = Color(1.0, 0.9, 0.1) style_selected.set_border_width_all(3) style_selected.set_corner_radius_all(3) panel.set_meta("style_selected", style_selected) var vbox := VBoxContainer.new() vbox.set_anchors_preset(Control.PRESET_FULL_RECT) panel.add_child(vbox) var color_rect := ColorRect.new() color_rect.name = "ColorRect" color_rect.custom_minimum_size = Vector2(SLOT_SIZE - 8, SLOT_SIZE - 18) color_rect.color = Color(0, 0, 0, 0) vbox.add_child(color_rect) var count_label := Label.new() count_label.name = "CountLabel" count_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT count_label.add_theme_font_size_override("font_size", 10) count_label.text = "" vbox.add_child(count_label) return panel func _build_inventory_window() -> void: _inv_window = Control.new() _inv_window.name = "InventoryWindow" _inv_window.set_anchors_preset(Control.PRESET_FULL_RECT) _inv_window.visible = false add_child(_inv_window) # Dim background var bg := ColorRect.new() bg.set_anchors_preset(Control.PRESET_FULL_RECT) bg.color = Color(0, 0, 0, 0.55) _inv_window.add_child(bg) # Main HBox: left = slots, right = craft panel var hbox := HBoxContainer.new() hbox.set_anchors_preset(Control.PRESET_CENTER) hbox.add_theme_constant_override("separation", 16) _inv_window.add_child(hbox) # Center it hbox.set_offset(SIDE_LEFT, -360) hbox.set_offset(SIDE_RIGHT, 360) hbox.set_offset(SIDE_TOP, -240) hbox.set_offset(SIDE_BOTTOM, 240) # Left panel: inventory grid + hotbar row var left_vbox := VBoxContainer.new() left_vbox.add_theme_constant_override("separation", 8) hbox.add_child(left_vbox) var inv_label := Label.new() inv_label.text = "Inventaire" inv_label.add_theme_font_size_override("font_size", 14) left_vbox.add_child(inv_label) # 3 rows × 9 cols = 27 slots (indices 9..35) var inv_grid := GridContainer.new() inv_grid.columns = 9 inv_grid.add_theme_constant_override("h_separation", SLOT_MARGIN) inv_grid.add_theme_constant_override("v_separation", SLOT_MARGIN) left_vbox.add_child(inv_grid) for i: int in range(9, 36): var slot := _make_slot_panel(i) inv_grid.add_child(slot) _inv_slots.append(slot) # Separator var sep := HSeparator.new() left_vbox.add_child(sep) var hbar_label := Label.new() hbar_label.text = "Hotbar" hbar_label.add_theme_font_size_override("font_size", 12) left_vbox.add_child(hbar_label) # Hotbar row inside inventory (mirror of bottom hotbar) var hotbar_grid := HBoxContainer.new() hotbar_grid.add_theme_constant_override("separation", SLOT_MARGIN) left_vbox.add_child(hotbar_grid) for i: int in range(9): var slot := _make_slot_panel(i) hotbar_grid.add_child(slot) _inv_slots.append(slot) # also tracked here for refresh # Right panel: craft var right_vbox := VBoxContainer.new() right_vbox.custom_minimum_size = Vector2(200, 0) right_vbox.add_theme_constant_override("separation", 6) hbox.add_child(right_vbox) var craft_label := Label.new() craft_label.text = "Craft" craft_label.add_theme_font_size_override("font_size", 14) right_vbox.add_child(craft_label) var scroll := ScrollContainer.new() scroll.custom_minimum_size = Vector2(200, 380) right_vbox.add_child(scroll) _recipe_list = VBoxContainer.new() _recipe_list.add_theme_constant_override("separation", 6) scroll.add_child(_recipe_list) func _refresh() -> void: if inventory == null: return _refresh_hotbar_slots() if _is_open: _refresh_inv_slots() _refresh_recipes() func _refresh_hotbar_slots() -> void: for i: int in range(_hotbar_slots.size()): _update_slot_visual(_hotbar_slots[i], i) func _refresh_inv_slots() -> void: # _inv_slots contains: 27 inv slots (9..35) + 9 hotbar mirror (0..8) for i: int in range(27): _update_slot_visual(_inv_slots[i], i + 9) for i: int in range(9): _update_slot_visual(_inv_slots[27 + i], i) func _update_slot_visual(panel: PanelContainer, slot_index: int) -> void: var is_selected: bool = (slot_index == inventory.selected_hotbar and slot_index < 9) if is_selected: panel.add_theme_stylebox_override("panel", panel.get_meta("style_selected")) panel.scale = Vector2(1.1, 1.1) else: panel.add_theme_stylebox_override("panel", panel.get_meta("style_normal")) panel.scale = Vector2(1.0, 1.0) var slot_data: Variant = inventory.slots[slot_index] var color_rect: ColorRect = panel.get_node_or_null("VBoxContainer/ColorRect") var count_label: Label = panel.get_node_or_null("VBoxContainer/CountLabel") if color_rect == null or count_label == null: return if slot_data == null: color_rect.color = Color(0, 0, 0, 0) count_label.text = "" else: color_rect.color = ItemDatabase.get_item_color(slot_data["item_id"]) if slot_data["count"] > 1: count_label.text = str(slot_data["count"]) else: count_label.text = "" func _refresh_recipes() -> void: for child in _recipe_list.get_children(): child.queue_free() for i: int in range(CraftingRecipes.RECIPES.size()): var recipe: Dictionary = CraftingRecipes.RECIPES[i] var can_craft: bool = inventory.has_items(recipe["inputs"]) var recipe_panel := VBoxContainer.new() recipe_panel.add_theme_constant_override("separation", 2) _recipe_list.add_child(recipe_panel) var name_label := Label.new() name_label.text = recipe["name"] name_label.add_theme_font_size_override("font_size", 11) recipe_panel.add_child(name_label) # Inputs summary var inputs_text: String = "" for inp: Dictionary in recipe["inputs"]: inputs_text += "%s x%d " % [ItemDatabase.get_item_name(inp["item_id"]), inp["count"]] var inp_label := Label.new() inp_label.text = inputs_text.strip_edges() inp_label.add_theme_font_size_override("font_size", 9) inp_label.modulate = Color(0.7, 0.7, 0.7) recipe_panel.add_child(inp_label) var craft_btn := Button.new() craft_btn.text = "Creer" craft_btn.disabled = not can_craft var idx: int = i craft_btn.pressed.connect(func() -> void: _on_craft_pressed(idx)) recipe_panel.add_child(craft_btn) var sep := HSeparator.new() _recipe_list.add_child(sep) func _on_craft_pressed(recipe_index: int) -> void: CraftingRecipes.craft(inventory, recipe_index) func _input(event: InputEvent) -> void: if event.is_action_pressed("toggle_inventory"): _toggle_inventory() get_viewport().set_input_as_handled() func _toggle_inventory() -> void: _is_open = not _is_open _inv_window.visible = _is_open if _is_open: get_tree().paused = true Input.mouse_mode = Input.MOUSE_MODE_VISIBLE _refresh_inv_slots() _refresh_recipes() else: get_tree().paused = false Input.mouse_mode = Input.MOUSE_MODE_CAPTURED