diff --git a/project.godot b/project.godot index 4720d43..8c3d17a 100644 --- a/project.godot +++ b/project.godot @@ -83,7 +83,7 @@ toggle_inventory={ escape={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":16777217,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } diff --git a/scenes/PauseMenu.tscn b/scenes/PauseMenu.tscn new file mode 100644 index 0000000..b644567 --- /dev/null +++ b/scenes/PauseMenu.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://pause_menu"] + +[ext_resource type="Script" path="res://scripts/PauseMenu.gd" id="1_pause"] + +[node name="PauseMenu" type="Control"] +script = ExtResource("1_pause") diff --git a/scripts/Main.gd b/scripts/Main.gd index 94d1ced..207485e 100644 --- a/scripts/Main.gd +++ b/scripts/Main.gd @@ -7,6 +7,9 @@ extends Node3D var inventory: Inventory = Inventory.new() var _inventory_ui: CanvasLayer = null +var _pause_menu: Control = null + +var _last_chunk_update_pos: Vector3 = Vector3(99999, 99999, 99999) func _ready() -> void: @@ -29,6 +32,12 @@ func _ready() -> void: else: push_error("Main: could not load InventoryUI.tscn") + # Load and attach PauseMenu + var pause_scene: PackedScene = preload("res://scenes/PauseMenu.tscn") + _pause_menu = pause_scene.instantiate() as Control + add_child(_pause_menu) + _pause_menu.hide() + AudioManager.play_ambient_loop("underwater_ambient") AudioManager.play_music("underwater_theme") AudioManager.play_whale_call_random() @@ -51,10 +60,30 @@ func _ready() -> void: func _process(_delta: float) -> void: - world.update_player_position(dolphin.global_position) + # Throttle chunk updates: only when dolphin moved > 4m + if dolphin.global_position.distance_to(_last_chunk_update_pos) > 4.0: + world.update_player_position(dolphin.global_position) + _last_chunk_update_pos = dolphin.global_position plankton_follower.global_position = dolphin.global_position +func _unhandled_input(event: InputEvent) -> void: + if not event.is_action_pressed("escape"): + return + # Priority: close inventory first if open + if _inventory_ui != null and _inventory_ui.get("_is_open"): + _inventory_ui.call("_toggle_inventory") + get_viewport().set_input_as_handled() + return + # Toggle pause menu + if is_instance_valid(_pause_menu): + if _pause_menu.visible: + _pause_menu.hide_pause() + else: + _pause_menu.show_pause() + get_viewport().set_input_as_handled() + + func _on_block_break(hit_position: Vector3, _normal: Vector3) -> void: var broken_id: int = world.break_block(hit_position) if broken_id > 0: @@ -68,7 +97,17 @@ func _on_block_place(hit_position: Vector3, normal: Vector3) -> void: return if not ItemDatabase.is_placeable(selected["item_id"]): return + var place_pos := hit_position + normal * 0.5 + var block_coord := Vector3(floor(place_pos.x), floor(place_pos.y), floor(place_pos.z)) + var block_center := block_coord + Vector3(0.5, 0.5, 0.5) + + # Refuse if bloc overlaps player AABB (CapsuleShape3D r=0.4 h=2.0) + var dolphin_pos: Vector3 = dolphin.global_position + var dist := block_center - dolphin_pos + if abs(dist.x) < 1.0 and abs(dist.y) < 1.8 and abs(dist.z) < 1.0: + return # too close to player, silent refuse + if world.place_block(place_pos, selected["item_id"]): inventory.remove_item_from_slot(inventory.selected_hotbar, 1) diff --git a/scripts/PauseMenu.gd b/scripts/PauseMenu.gd new file mode 100644 index 0000000..be45452 --- /dev/null +++ b/scripts/PauseMenu.gd @@ -0,0 +1,67 @@ +extends Control + +func _ready() -> void: + process_mode = Node.PROCESS_MODE_ALWAYS + set_anchors_preset(Control.PRESET_FULL_RECT) + + # Semi-transparent background + var bg := ColorRect.new() + bg.set_anchors_preset(Control.PRESET_FULL_RECT) + bg.color = Color(0, 0, 0, 0.7) + add_child(bg) + + # Center VBox + var vbox := VBoxContainer.new() + vbox.set_anchors_preset(Control.PRESET_CENTER) + vbox.add_theme_constant_override("separation", 16) + vbox.set_offset(SIDE_LEFT, -120) + vbox.set_offset(SIDE_RIGHT, 120) + vbox.set_offset(SIDE_TOP, -120) + vbox.set_offset(SIDE_BOTTOM, 120) + add_child(vbox) + + var title := Label.new() + title.text = "PAUSE" + title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + title.add_theme_font_size_override("font_size", 48) + vbox.add_child(title) + + var btn_resume := Button.new() + btn_resume.text = "Reprendre" + btn_resume.pressed.connect(_on_resume) + vbox.add_child(btn_resume) + + var btn_menu := Button.new() + btn_menu.text = "Menu principal" + btn_menu.pressed.connect(_on_main_menu) + vbox.add_child(btn_menu) + + var btn_quit := Button.new() + btn_quit.text = "Quitter" + btn_quit.pressed.connect(_on_quit) + vbox.add_child(btn_quit) + + +func show_pause() -> void: + show() + get_tree().paused = true + Input.mouse_mode = Input.MOUSE_MODE_VISIBLE + + +func hide_pause() -> void: + hide() + get_tree().paused = false + Input.mouse_mode = Input.MOUSE_MODE_CAPTURED + + +func _on_resume() -> void: + hide_pause() + + +func _on_main_menu() -> void: + get_tree().paused = false + get_tree().change_scene_to_file("res://scenes/MainMenu.tscn") + + +func _on_quit() -> void: + get_tree().quit() diff --git a/scripts/dolphin/DolphinController.gd b/scripts/dolphin/DolphinController.gd index ccb4ffa..51c355e 100644 --- a/scripts/dolphin/DolphinController.gd +++ b/scripts/dolphin/DolphinController.gd @@ -77,13 +77,6 @@ func _setup_input_actions() -> void: ev.button_index = mouse_actions[action_name] InputMap.action_add_event(action_name, ev) - if not InputMap.has_action("escape"): - InputMap.add_action("escape") - var ev := InputEventKey.new() - ev.keycode = KEY_ESCAPE - InputMap.action_add_event("escape", ev) - - func _input(event: InputEvent) -> void: if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED: var motion := event as InputEventMouseMotion @@ -107,11 +100,6 @@ func _input(event: InputEvent) -> void: _do_raycast_place() if event.is_action_pressed("echolocate"): _trigger_echolocation() - if event.is_action_pressed("escape"): - if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED: - Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) - else: - Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) func _physics_process(delta: float) -> void: @@ -252,9 +240,9 @@ func _raycast(reach: float) -> Dictionary: func take_damage(amount: float) -> void: health = max(0.0, health - amount) - emit_signal("stats_changed", oxygen, health, hunger) + stats_changed.emit(oxygen, health, hunger) if health <= 0.0: - emit_signal("player_died") + player_died.emit() func _trigger_echolocation() -> void: diff --git a/scripts/inventory/InventoryUI.gd b/scripts/inventory/InventoryUI.gd index da2f3f1..c09ca53 100644 --- a/scripts/inventory/InventoryUI.gd +++ b/scripts/inventory/InventoryUI.gd @@ -266,6 +266,9 @@ func _input(event: InputEvent) -> void: if event.is_action_pressed("toggle_inventory"): _toggle_inventory() get_viewport().set_input_as_handled() + elif event.is_action_pressed("escape") and _is_open: + _toggle_inventory() + get_viewport().set_input_as_handled() func _toggle_inventory() -> void: diff --git a/scripts/mobs/FishSchool.gd b/scripts/mobs/FishSchool.gd index 1aeefbc..36c2d85 100644 --- a/scripts/mobs/FishSchool.gd +++ b/scripts/mobs/FishSchool.gd @@ -8,6 +8,7 @@ var _fish_meshes: Array[MeshInstance3D] = [] var _fish_velocities: Array[Vector3] = [] var _time: float = 0.0 var _player: CharacterBody3D = null +var _boid_update_timer: float = 0.0 func _ready() -> void: _find_player() @@ -62,6 +63,17 @@ func _process(delta: float) -> void: if _player == null: _find_player() + # Throttle boid calculations to 10 Hz + _boid_update_timer += delta + if _boid_update_timer >= 0.1: + _update_boids(_boid_update_timer) + _boid_update_timer = 0.0 + + # Apply velocities every frame for smooth movement + _apply_velocities(delta) + + +func _update_boids(dt: float) -> void: for i: int in range(_fish_meshes.size()): var fish: MeshInstance3D = _fish_meshes[i] var vel: Vector3 = _fish_velocities[i] @@ -91,12 +103,12 @@ func _process(delta: float) -> void: avg_vel /= float(neighbor_count) var cohesion_force: Vector3 = (avg_pos - fish.position).normalized() * 0.5 var alignment_force: Vector3 = avg_vel.normalized() * 0.3 - vel += (cohesion_force + alignment_force) * delta * 2.0 + vel += (cohesion_force + alignment_force) * dt * 2.0 # --- Global cohesion: return to school center --- - var center_offset: Vector3 = fish.position # positions are local to school node + var center_offset: Vector3 = fish.position if center_offset.length() > school_radius: - vel += -center_offset.normalized() * 1.5 * delta * 4.0 + vel += -center_offset.normalized() * 1.5 * dt * 4.0 # --- Player avoidance --- if is_instance_valid(_player): @@ -104,7 +116,7 @@ func _process(delta: float) -> void: var dist_to_player: float = world_fish_pos.distance_to(_player.global_position) if dist_to_player < 3.0: var flee: Vector3 = (world_fish_pos - _player.global_position).normalized() - vel += flee * 4.0 * delta * (3.0 - dist_to_player) + vel += flee * 4.0 * dt * (3.0 - dist_to_player) # Clamp speed if vel.length() > swim_speed * 1.5: @@ -113,6 +125,12 @@ func _process(delta: float) -> void: vel = vel.normalized() * 0.3 _fish_velocities[i] = vel + + +func _apply_velocities(delta: float) -> void: + for i: int in range(_fish_meshes.size()): + var fish: MeshInstance3D = _fish_meshes[i] + var vel: Vector3 = _fish_velocities[i] fish.position += vel * delta # Rotate mesh to face velocity direction diff --git a/scripts/world/ChunkManager.gd b/scripts/world/ChunkManager.gd index 4c26402..64ab977 100644 --- a/scripts/world/ChunkManager.gd +++ b/scripts/world/ChunkManager.gd @@ -3,7 +3,7 @@ class_name ChunkManager const CHUNK_SIZE: int = 16 -@export var render_distance: int = 4 +@export var render_distance: int = 2 @export var world_seed: int = 12345 var chunks: Dictionary = {} @@ -36,6 +36,26 @@ func update_player_position(pos: Vector3) -> void: if not chunks.has(coord): _load_chunk(coord) +func _regen_border_neighbors(chunk_coord: Vector3i, local: Vector3i) -> void: + # Regen adjacent chunk only if block is on the border face + var offsets: Array[Vector3i] = [] + if local.x == 0: + offsets.append(Vector3i(-1, 0, 0)) + elif local.x == CHUNK_SIZE - 1: + offsets.append(Vector3i(1, 0, 0)) + if local.y == 0: + offsets.append(Vector3i(0, -1, 0)) + elif local.y == CHUNK_SIZE - 1: + offsets.append(Vector3i(0, 1, 0)) + if local.z == 0: + offsets.append(Vector3i(0, 0, -1)) + elif local.z == CHUNK_SIZE - 1: + offsets.append(Vector3i(0, 0, 1)) + for offset: Vector3i in offsets: + var neighbor: Vector3i = chunk_coord + offset + if chunks.has(neighbor): + chunks[neighbor].generate_mesh() + func break_block(world_pos: Vector3) -> int: var chunk_coord: Vector3i = world_to_chunk_coord(world_pos) if not chunks.has(chunk_coord): @@ -47,6 +67,7 @@ func break_block(world_pos: Vector3) -> int: return 0 chunk.set_block(local.x, local.y, local.z, BlockDatabase.BlockType.AIR) chunk.generate_mesh() + _regen_border_neighbors(chunk_coord, local) return old_id func place_block(world_pos: Vector3, block_id: int) -> bool: @@ -60,6 +81,7 @@ func place_block(world_pos: Vector3, block_id: int) -> bool: return false chunk.set_block(local.x, local.y, local.z, block_id) chunk.generate_mesh() + _regen_border_neighbors(chunk_coord, local) return true func get_block(world_pos: Vector3) -> int: