From c4f2c0a202b9cd89af3b34425956dd40ee8581cf Mon Sep 17 00:00:00 2001 From: bee Date: Tue, 7 Jul 2026 23:04:20 +0200 Subject: [PATCH] weapon slots and loadout done --- README.md | 2 +- boat.tscn | 15 +++++++++ main.tscn | 1 + mark12.tscn | 16 ++++++++++ project.godot | 5 +++ resources/ammo/5inch_shell.tres | 9 ++++++ resources/weapons/mark12.tres | 12 +++++++ scripts/boat.gd | 18 ++++++++++- scripts/floating_body.gd | 2 +- scripts/player_controller.gd | 4 +++ scripts/projectile.gd | 31 +++++++++++++++++++ scripts/projectile.gd.uid | 1 + scripts/resource_definitions/ammo_def.gd | 11 +++++++ scripts/resource_definitions/ammo_def.gd.uid | 1 + scripts/resource_definitions/weapon_def.gd | 15 +++++++++ .../resource_definitions/weapon_def.gd.uid | 1 + scripts/weapon.gd | 31 +++++++++++++++++++ scripts/weapon.gd.uid | 1 + scripts/weapon_slot.gd | 25 +++++++++++++++ scripts/weapon_slot.gd.uid | 1 + shell.tscn | 28 +++++++++++++++++ 21 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 mark12.tscn create mode 100644 resources/ammo/5inch_shell.tres create mode 100644 resources/weapons/mark12.tres create mode 100644 scripts/projectile.gd create mode 100644 scripts/projectile.gd.uid create mode 100644 scripts/resource_definitions/ammo_def.gd create mode 100644 scripts/resource_definitions/ammo_def.gd.uid create mode 100644 scripts/resource_definitions/weapon_def.gd create mode 100644 scripts/resource_definitions/weapon_def.gd.uid create mode 100644 scripts/weapon.gd create mode 100644 scripts/weapon.gd.uid create mode 100644 scripts/weapon_slot.gd create mode 100644 scripts/weapon_slot.gd.uid create mode 100644 shell.tscn diff --git a/README.md b/README.md index 014d4ad..275f948 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ TODO: -Controller/possession refactor (input out of Boat). +DONE Controller/possession refactor (input out of Boat). WeaponDef resource + WeaponSlot + one dumb gun that prints instead of shooting. Projectile base (with group self-registration) + real firing. Loadout-driven mounting. diff --git a/boat.tscn b/boat.tscn index 45ee024..67ecc1f 100644 --- a/boat.tscn +++ b/boat.tscn @@ -1,6 +1,9 @@ [gd_scene format=3 uid="uid://cieiwihia8t2w"] [ext_resource type="Script" uid="uid://c0n1x050jvspr" path="res://scripts/boat.gd" id="1_7h74f"] +[ext_resource type="Script" uid="uid://vccbk7tbujhn" path="res://scripts/resource_definitions/weapon_def.gd" id="2_anwow"] +[ext_resource type="Resource" uid="uid://cie5thl0u84ly" path="res://resources/weapons/mark12.tres" id="3_henu8"] +[ext_resource type="Script" uid="uid://ba5ums3xj42h" path="res://scripts/weapon_slot.gd" id="4_1jsln"] [sub_resource type="BoxMesh" id="BoxMesh_crate"] size = Vector3(1, 1, 5) @@ -16,6 +19,10 @@ size = Vector3(1, 1, 5) collision_layer = 2 mass = 5.0 script = ExtResource("1_7h74f") +loadout = Dictionary[StringName, ExtResource("2_anwow")]({ +&"BowMount": ExtResource("3_henu8"), +&"SternMount": ExtResource("3_henu8") +}) buoyancy = 4.0 full_force_depth = 0.8 probe_extents = Vector3(0.35, 0, 2.25) @@ -27,3 +34,11 @@ surface_material_override/0 = SubResource("Material_crate") [node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=18200711] shape = SubResource("BoxShape_crate") + +[node name="BowMount" type="Node3D" parent="." unique_id=1196033377] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.56350744, -1.6800777) +script = ExtResource("4_1jsln") + +[node name="SternMount" type="Node3D" parent="." unique_id=909881913] +transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 0.5398402, 2.0672112) +script = ExtResource("4_1jsln") diff --git a/main.tscn b/main.tscn index e12978e..1f8d901 100644 --- a/main.tscn +++ b/main.tscn @@ -74,3 +74,4 @@ current_controlled = NodePath("../Boat") world_manager = NodePath("../WorldManager") camera_rig = NodePath("../CameraRig") hud = NodePath("../Hud") +throttle_ramp = null diff --git a/mark12.tscn b/mark12.tscn new file mode 100644 index 0000000..14a4a41 --- /dev/null +++ b/mark12.tscn @@ -0,0 +1,16 @@ +[gd_scene format=3 uid="uid://d1fb1p83uhpwv"] + +[ext_resource type="Script" uid="uid://bcedvro1n83c0" path="res://scripts/weapon.gd" id="1_pko6q"] + +[sub_resource type="BoxMesh" id="BoxMesh_byv2x"] +size = Vector3(0.5, 0.5, 0.5) + +[node name="Mark12" type="Node3D" unique_id=2057435618] +script = ExtResource("1_pko6q") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=522024219] +mesh = SubResource("BoxMesh_byv2x") + +[node name="Muzzle" type="Marker3D" parent="." unique_id=471595781] +unique_name_in_owner = true +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.2726481) diff --git a/project.godot b/project.godot index 2bb3827..a79f228 100644 --- a/project.godot +++ b/project.godot @@ -55,6 +55,11 @@ restart={ "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":4194332,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } +fire={ +"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":49,"key_label":0,"unicode":49,"location":0,"echo":false,"script":null) +] +} [layer_names] diff --git a/resources/ammo/5inch_shell.tres b/resources/ammo/5inch_shell.tres new file mode 100644 index 0000000..30461c2 --- /dev/null +++ b/resources/ammo/5inch_shell.tres @@ -0,0 +1,9 @@ +[gd_resource type="Resource" script_class="AmmoDef" format=3 uid="uid://c85q1rbyiayh1"] + +[ext_resource type="PackedScene" uid="uid://3us2hr60a7ke" path="res://shell.tscn" id="1_upqqq"] +[ext_resource type="Script" uid="uid://k4add7rmdxw3" path="res://scripts/resource_definitions/ammo_def.gd" id="2_upqqq"] + +[resource] +script = ExtResource("2_upqqq") +projectile_scene = ExtResource("1_upqqq") +metadata/_custom_type_script = "uid://k4add7rmdxw3" diff --git a/resources/weapons/mark12.tres b/resources/weapons/mark12.tres new file mode 100644 index 0000000..40f7ae4 --- /dev/null +++ b/resources/weapons/mark12.tres @@ -0,0 +1,12 @@ +[gd_resource type="Resource" script_class="WeaponDef" format=3 uid="uid://cie5thl0u84ly"] + +[ext_resource type="Resource" uid="uid://c85q1rbyiayh1" path="res://resources/ammo/5inch_shell.tres" id="1_q60cw"] +[ext_resource type="Script" uid="uid://vccbk7tbujhn" path="res://scripts/resource_definitions/weapon_def.gd" id="2_q60cw"] +[ext_resource type="PackedScene" uid="uid://d1fb1p83uhpwv" path="res://mark12.tscn" id="3_omejh"] + +[resource] +script = ExtResource("2_q60cw") +display_name = "Mark 12" +weapon_scene = ExtResource("3_omejh") +ammo = ExtResource("1_q60cw") +metadata/_custom_type_script = "uid://vccbk7tbujhn" diff --git a/scripts/boat.gd b/scripts/boat.gd index 2e9c86d..ffbc48c 100644 --- a/scripts/boat.gd +++ b/scripts/boat.gd @@ -7,10 +7,21 @@ extends FloatingBody @export var throttle_response := 1.5 # how fast the engine spools up/down @export var rudder_strength := 2.5 # turning torque at speed @export var keel_grip := 3.0 # resistance to sliding sideways +@export var loadout: Dictionary[StringName, WeaponDef] = {} var desired_throttle := 0.0 # move_toward target for actual throttle var steer_input := 0.0 -var throttle := 0.0 +var throttle := 0.0 # current throttle state of ship +var _slots: Array[WeaponSlot] = [] + +func _ready() -> void: + super() + for child in get_children(): + if child is WeaponSlot: + _slots.append(child) + var def: WeaponDef = loadout.get(child.name) + if def != null: + child.mount(def) func _physics_process(delta: float) -> void: super(delta) @@ -40,3 +51,8 @@ func _physics_process(delta: float) -> void: right = right.normalized() var lateral_speed := linear_velocity.dot(right) apply_central_force(-right * lateral_speed * keel_grip * mass * submerged_ratio) + +func fire_weapons() -> void: + for slot in _slots: + if slot.weapon != null: + slot.weapon.try_fire() diff --git a/scripts/floating_body.gd b/scripts/floating_body.gd index 89783a0..e0b8485 100644 --- a/scripts/floating_body.gd +++ b/scripts/floating_body.gd @@ -24,7 +24,7 @@ extends RigidBody3D var _probe_offsets: Array[Vector3] = [] # local space var _ocean: Ocean -var _probes: Array[Marker3D] = [] +# var _probes: Array[Marker3D] = [] TODO: Probably unused var _gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity") var submerged_ratio := 0.0 diff --git a/scripts/player_controller.gd b/scripts/player_controller.gd index def67e0..d24dcc1 100644 --- a/scripts/player_controller.gd +++ b/scripts/player_controller.gd @@ -31,6 +31,7 @@ extends Node 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 @@ -52,6 +53,9 @@ func _process(delta: float) -> void: current_controlled.desired_throttle = desired_throttle current_controlled.steer_input = steer_input + if Input.is_action_pressed("fire"): + current_controlled.fire_weapons() + func _possess() -> void: camera_rig.target = current_controlled world_manager.focus = current_controlled diff --git a/scripts/projectile.gd b/scripts/projectile.gd new file mode 100644 index 0000000..105cd35 --- /dev/null +++ b/scripts/projectile.gd @@ -0,0 +1,31 @@ +class_name Projectile +extends RigidBody3D + +var damage := 0.0 # set by launch(), read by whatever we hit +var _time_left := 10.0 + +func _ready() -> void: + add_to_group("shift_with_origin") + body_entered.connect(_on_body_entered) + +## Call after add_child(). Configures this projectile from a def and sends it flying. +func launch(def: AmmoDef, muzzle: Transform3D, speed_scale := 1.0) -> void: + damage = def.damage + gravity_scale = def.gravity_scale + _time_left = def.lifetime + global_transform = muzzle + linear_velocity = -muzzle.basis.z * def.muzzle_speed * speed_scale + reset_physics_interpolation() + +func _physics_process(delta: float) -> void: + if Vector3.UP.cross(linear_velocity).length_squared() > 0.01: + look_at(global_position + linear_velocity) + + _time_left -= delta + if _time_left <= 0.0: + queue_free() + +func _on_body_entered(body: Node) -> void: + if body.has_method("take_damage"): + body.take_damage(damage) + queue_free() diff --git a/scripts/projectile.gd.uid b/scripts/projectile.gd.uid new file mode 100644 index 0000000..2cf5cab --- /dev/null +++ b/scripts/projectile.gd.uid @@ -0,0 +1 @@ +uid://i5lxsp35533c diff --git a/scripts/resource_definitions/ammo_def.gd b/scripts/resource_definitions/ammo_def.gd new file mode 100644 index 0000000..71b7695 --- /dev/null +++ b/scripts/resource_definitions/ammo_def.gd @@ -0,0 +1,11 @@ +class_name AmmoDef +extends Resource + +## Data-only description of a weapon type. Never mutated at runtime. + +@export var display_name := "Ammo Shell" +@export var projectile_scene: PackedScene +@export var damage := 10.0 +@export var muzzle_speed := 30.0 # m/s, imparted at spawn +@export var gravity_scale := 1.0 # 1.0 = arcs like a real cannonball +@export var lifetime := 8.0 # seconds before self-despawn diff --git a/scripts/resource_definitions/ammo_def.gd.uid b/scripts/resource_definitions/ammo_def.gd.uid new file mode 100644 index 0000000..42116b1 --- /dev/null +++ b/scripts/resource_definitions/ammo_def.gd.uid @@ -0,0 +1 @@ +uid://k4add7rmdxw3 diff --git a/scripts/resource_definitions/weapon_def.gd b/scripts/resource_definitions/weapon_def.gd new file mode 100644 index 0000000..5b92423 --- /dev/null +++ b/scripts/resource_definitions/weapon_def.gd @@ -0,0 +1,15 @@ +class_name WeaponDef +extends Resource + +## Data-only description of a weapon type. Never mutated at runtime. + +enum Size {ANCILLIARY, SMALL, MEDIUM, LARGE, HUGE} +enum Type {BALLISTIC, ENERGY, MISSILE, TORPEDO} + +@export var display_name := "Weapon Cannon" +@export var weapon_scene: PackedScene # the mounted body — consumed by slots in Part 3 +@export var ammo: AmmoDef # what it fires +@export var fire_interval := 1.5 # seconds between shots +@export var muzzle_speed_scale := 1.0 # multiplier over ammo.muzzle_speed (barrel length) +@export var size := Size.MEDIUM +@export var type := Type.BALLISTIC diff --git a/scripts/resource_definitions/weapon_def.gd.uid b/scripts/resource_definitions/weapon_def.gd.uid new file mode 100644 index 0000000..f37a70c --- /dev/null +++ b/scripts/resource_definitions/weapon_def.gd.uid @@ -0,0 +1 @@ +uid://vccbk7tbujhn diff --git a/scripts/weapon.gd b/scripts/weapon.gd new file mode 100644 index 0000000..8b9d893 --- /dev/null +++ b/scripts/weapon.gd @@ -0,0 +1,31 @@ +class_name Weapon +extends Node3D + +@export var def: WeaponDef + +var shooter: PhysicsBody3D # Resolved in _ready(): nearest ancestor body + +var _cooldown := 0.0 + +@onready var _muzzle: Marker3D = %Muzzle + +func _ready() -> void: + # Fallback: nearest ancestor body is the shooter (hand-placed weapons). + var n := get_parent() + while shooter == null and n != null: + shooter = n as PhysicsBody3D + n = n.get_parent() + +func _physics_process(delta: float) -> void: + _cooldown = maxf(_cooldown - delta, 0.0) + +func try_fire() -> bool: + if _cooldown > 0.0 or def == null or def.ammo == null: + return false + _cooldown = def.fire_interval + var p: Projectile = def.ammo.projectile_scene.instantiate() + get_tree().current_scene.add_child(p) + if shooter != null: + p.add_collision_exception_with(shooter) + p.launch(def.ammo, _muzzle.global_transform, def.muzzle_speed_scale) + return true diff --git a/scripts/weapon.gd.uid b/scripts/weapon.gd.uid new file mode 100644 index 0000000..663217f --- /dev/null +++ b/scripts/weapon.gd.uid @@ -0,0 +1 @@ +uid://bcedvro1n83c0 diff --git a/scripts/weapon_slot.gd b/scripts/weapon_slot.gd new file mode 100644 index 0000000..4f4c47c --- /dev/null +++ b/scripts/weapon_slot.gd @@ -0,0 +1,25 @@ +class_name WeaponSlot +extends Node3D + +@export var allowed_sizes: Array[WeaponDef.Size] = [WeaponDef.Size.MEDIUM] +@export var allowed_types: Array[WeaponDef.Type] = [WeaponDef.Type.BALLISTIC] + +var weapon: Weapon = null # currently mounted, if any + +func can_mount(def: WeaponDef) -> bool: + return def != null and def.weapon_scene != null \ + and def.size in allowed_sizes and def.type in allowed_types + +func mount(def: WeaponDef) -> bool: + if not can_mount(def): + return false + unmount() + weapon = def.weapon_scene.instantiate() + weapon.def = def + add_child(weapon) + return true + +func unmount() -> void: + if weapon != null: + weapon.queue_free() + weapon = null diff --git a/scripts/weapon_slot.gd.uid b/scripts/weapon_slot.gd.uid new file mode 100644 index 0000000..bff75c7 --- /dev/null +++ b/scripts/weapon_slot.gd.uid @@ -0,0 +1 @@ +uid://ba5ums3xj42h diff --git a/shell.tscn b/shell.tscn new file mode 100644 index 0000000..9918ae8 --- /dev/null +++ b/shell.tscn @@ -0,0 +1,28 @@ +[gd_scene format=3 uid="uid://3us2hr60a7ke"] + +[ext_resource type="Script" uid="uid://i5lxsp35533c" path="res://scripts/projectile.gd" id="1_6fuaf"] + +[sub_resource type="CylinderMesh" id="CylinderMesh_byv2x"] +top_radius = 0.1 +bottom_radius = 0.1 +height = 0.5 + +[sub_resource type="CylinderShape3D" id="CylinderShape3D_byv2x"] +height = 0.5 +radius = 0.1 + +[node name="Shell" type="RigidBody3D" unique_id=893187332] +collision_layer = 8 +collision_mask = 7 +continuous_cd = true +contact_monitor = true +max_contacts_reported = 1 +script = ExtResource("1_6fuaf") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=1876944354] +transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0, 0, 0) +mesh = SubResource("CylinderMesh_byv2x") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1799909098] +transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0, 0, 0) +shape = SubResource("CylinderShape3D_byv2x")