diff --git a/hub.tscn b/hub.tscn new file mode 100644 index 0000000..eaa9868 --- /dev/null +++ b/hub.tscn @@ -0,0 +1,33 @@ +[gd_scene format=3 uid="uid://bjsiquu3pu2yi"] + +[ext_resource type="Script" uid="uid://b8105tebdqgu7" path="res://scripts/hub.gd" id="1_l10or"] + +[node name="Hub" type="Control" unique_id=1025413344] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_l10or") + +[node name="CenterContainer" type="CenterContainer" parent="." unique_id=1963957334] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer" unique_id=1923587537] +layout_mode = 2 + +[node name="Dock" type="Label" parent="CenterContainer/VBoxContainer" unique_id=196076069] +layout_mode = 2 +text = "Dock" + +[node name="StartMissionButton" type="Button" parent="CenterContainer/VBoxContainer" unique_id=1251733940] +layout_mode = 2 +text = "Start Mission" + +[connection signal="pressed" from="CenterContainer/VBoxContainer/StartMissionButton" to="." method="_on_start_mission_button_pressed"] diff --git a/main.tscn b/main.tscn index 4aba1e1..c411858 100644 --- a/main.tscn +++ b/main.tscn @@ -1,5 +1,6 @@ [gd_scene format=3 uid="uid://jven27ktracb"] +[ext_resource type="Script" uid="uid://ck3d6hhcoia15" path="res://scripts/mission.gd" id="1_272bh"] [ext_resource type="PackedScene" uid="uid://c6n3dg530t8oc" path="res://ocean.tscn" id="1_lquwl"] [ext_resource type="PackedScene" uid="uid://cieiwihia8t2w" path="res://boat.tscn" id="2_7mycd"] [ext_resource type="Script" uid="uid://46jiwugeccqg" path="res://scripts/camera_rig.gd" id="4_1bvp3"] @@ -38,7 +39,8 @@ material = SubResource("StandardMaterial3D_j2yfa") radius = 0.3 height = 0.6 -[node name="Main" type="Node3D" unique_id=1242452333] +[node name="Main" type="Node3D" unique_id=1242452333 groups=["mission"]] +script = ExtResource("1_272bh") [node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=431656791] environment = SubResource("Environment_1") diff --git a/ocean.tscn b/ocean.tscn index 9355965..21cd72b 100644 --- a/ocean.tscn +++ b/ocean.tscn @@ -9,12 +9,11 @@ material_override = ExtResource("1_j38dg") script = ExtResource("2_nb2vx") sea_height = 1.0 -sea_choppy = 8.0 metadata/follow_step = 1.0 -[node name="StaticBody3D" type="StaticBody3D" parent="." unique_id=1520290282 groups=["water"]] +[node name="Surface" type="StaticBody3D" parent="." unique_id=1520290282 groups=["water"]] collision_layer = 32 collision_mask = 0 -[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D" unique_id=629201170] +[node name="CollisionShape3D" type="CollisionShape3D" parent="Surface" unique_id=629201170] shape = SubResource("WorldBoundaryShape3D_r1f0u") diff --git a/project.godot b/project.godot index 789bac1..c32dcfa 100644 --- a/project.godot +++ b/project.godot @@ -11,12 +11,13 @@ config_version=5 [application] config/name="boatgame" -run/main_scene="res://main.tscn" +run/main_scene="uid://bjsiquu3pu2yi" config/features=PackedStringArray("4.7", "Forward Plus") [autoload] Game="*uid://dtkqmj8h5mg8y" +GameState="*uid://xpfddtyfru40" [display] @@ -70,6 +71,11 @@ debug_possess_next={ "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":4194334,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } +debug_end_mission={ +"deadzone": 0.2, +"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":4194335,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} [layer_names] diff --git a/scripts/ai_controller.gd b/scripts/ai_controller.gd new file mode 100644 index 0000000..2e29249 --- /dev/null +++ b/scripts/ai_controller.gd @@ -0,0 +1,65 @@ +class_name AIController +extends Node + +@export var standoff_distance := 30.0 # throttle down inside this +@export var fire_range := 120.0 # open fire inside this +@export var full_rudder_angle_deg := 20.0 # bearing error at which rudder saturates + +var _boat: Boat +var _target: Boat # node reference — shift-safe; never store a position + + +func _ready() -> void: + _boat = get_parent() as Boat + + +func _physics_process(_delta: float) -> void: + if _boat == null: + return + if _player_has_boat(): + return # player possessed us — write nothing, resume when they leave + + if not is_instance_valid(_target): + _target = _pick_target() + if _target == null: + _boat.desired_throttle = 0.0 + _boat.steer_input = 0.0 + _boat.aim_point = null # stow turrets, per the aim contract + _boat.fire_intents[0] = false + return + + # All geometry computed fresh this tick — origin-shift safe. + var to_target: Vector3 = _target.global_position - _boat.global_position + to_target.y = 0.0 + var distance := to_target.length() + + var forward := -_boat.global_basis.z + forward.y = 0.0 + + var bearing_error := forward.signed_angle_to(to_target, Vector3.UP) + _boat.steer_input = clampf( + bearing_error / deg_to_rad(full_rudder_angle_deg), -1.0, 1.0) + + _boat.desired_throttle = 1.0 if distance > standoff_distance else 0.2 + + _boat.aim_point = _target.global_position + _boat.fire_intents[0] = distance < fire_range + + +func _player_has_boat() -> bool: + var pc := get_tree().get_first_node_in_group(&"player_controllers") as PlayerController + return pc != null and pc.current_controlled == _boat + + +func _pick_target() -> Boat: + var best: Boat = null + var best_d := INF + for node in get_tree().get_nodes_in_group(&"boats"): + var other := node as Boat + if other == null or other == _boat: + continue + var d := _boat.global_position.distance_squared_to(other.global_position) + if d < best_d: + best_d = d + best = other + return best diff --git a/scripts/ai_controller.gd.uid b/scripts/ai_controller.gd.uid new file mode 100644 index 0000000..f318724 --- /dev/null +++ b/scripts/ai_controller.gd.uid @@ -0,0 +1 @@ +uid://hvfw24jpikc0 diff --git a/scripts/game.gd b/scripts/game.gd index 6cc77a7..bf5313d 100644 --- a/scripts/game.gd +++ b/scripts/game.gd @@ -4,10 +4,12 @@ extends Node const BOAT_SCENE := preload("res://boat.tscn") func _unhandled_input(event: InputEvent) -> void: - if event.is_action_pressed("restart"): + if event.is_action_pressed(&"restart"): get_tree().reload_current_scene() - if event.is_action_pressed(&"debug_spawn_boat"): + if event.is_action_pressed(&"debug_spawn_boat") and _is_in_mission(): _debug_spawn_boat() + if event.is_action_pressed(&"debug_end_mission") and _is_in_mission(): + GameState.return_to_hub({"outcome": "aborted"}) func _debug_spawn_boat() -> void: var focus := get_tree().get_first_node_in_group(&"boats") as Node3D @@ -17,4 +19,12 @@ func _debug_spawn_boat() -> void: boat.position = focus.global_position + focus.global_basis.x * 10.0 + Vector3.UP * 2.0 else: boat.position = Vector3(0, 3, 0) + + var ai := AIController.new() + ai.name = "AIController" + boat.add_child(ai) get_tree().current_scene.add_child(boat, true) + +func _is_in_mission() -> bool: + var scene := get_tree().current_scene + return scene != null and scene.is_in_group(&"mission") diff --git a/scripts/game_state.gd b/scripts/game_state.gd new file mode 100644 index 0000000..498fa67 --- /dev/null +++ b/scripts/game_state.gd @@ -0,0 +1,22 @@ +# scripts/game_state.gd — autoloaded as "GameState" +extends Node + +const HUB_SCENE := "res://hub.tscn" + +# Written by the hub before starting a mission, read by the mission in _ready(). +# Later this becomes a MissionDef reference; a Dictionary is the v1 stand-in. +var mission_params: Dictionary = {} + +# Written by the mission before returning, read by the hub. +var last_mission_result: Dictionary = {} + + +func start_mission(scene_path: String, params: Dictionary = {}) -> void: + mission_params = params + last_mission_result = {} + get_tree().change_scene_to_file(scene_path) + +func return_to_hub(result: Dictionary = {}) -> void: + last_mission_result = result + mission_params = {} + get_tree().change_scene_to_file(HUB_SCENE) diff --git a/scripts/game_state.gd.uid b/scripts/game_state.gd.uid new file mode 100644 index 0000000..b1d2eb9 --- /dev/null +++ b/scripts/game_state.gd.uid @@ -0,0 +1 @@ +uid://xpfddtyfru40 diff --git a/scripts/hub.gd b/scripts/hub.gd new file mode 100644 index 0000000..0e35f10 --- /dev/null +++ b/scripts/hub.gd @@ -0,0 +1,9 @@ +extends Control + + +func _ready() -> void: + if not GameState.last_mission_result.is_empty(): + print("Mission result: ", GameState.last_mission_result) + +func _on_start_mission_button_pressed() -> void: + GameState.start_mission("res://main.tscn", {"difficulty": 1}) diff --git a/scripts/hub.gd.uid b/scripts/hub.gd.uid new file mode 100644 index 0000000..2a49a6d --- /dev/null +++ b/scripts/hub.gd.uid @@ -0,0 +1 @@ +uid://b8105tebdqgu7 diff --git a/scripts/mission.gd b/scripts/mission.gd new file mode 100644 index 0000000..4397cdc --- /dev/null +++ b/scripts/mission.gd @@ -0,0 +1,11 @@ +extends Node3D + +@onready var _ocean: Ocean = $Ocean + +func _ready() -> void: + var params: Dictionary = GameState.mission_params + _ocean.sea_height = params.get("sea_height", _ocean.sea_height) + _ocean.sea_choppy = params.get("sea_choppy", _ocean.sea_choppy) + _ocean.sea_freq = params.get("sea_freq", _ocean.sea_freq) + _ocean.sea_speed = params.get("sea_speed", _ocean.sea_speed) + print("Mission started with params: ", params) diff --git a/scripts/mission.gd.uid b/scripts/mission.gd.uid new file mode 100644 index 0000000..5e5bde6 --- /dev/null +++ b/scripts/mission.gd.uid @@ -0,0 +1 @@ +uid://ck3d6hhcoia15 diff --git a/scripts/player_controller.gd b/scripts/player_controller.gd index 0874c18..8087085 100644 --- a/scripts/player_controller.gd +++ b/scripts/player_controller.gd @@ -43,6 +43,7 @@ var desired_throttle := 0.0 func _ready() -> void: if current_controlled != null: # Only happens on intial loading if boat already moves desired_throttle = current_controlled.throttle + add_to_group(&"player_controllers") _possess() func _physics_process(_delta: float) -> void: