weapon slots and loadout done

This commit is contained in:
bee
2026-07-07 23:04:20 +02:00
parent a40d0bfdd3
commit c4f2c0a202
21 changed files with 227 additions and 3 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
TODO: 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. WeaponDef resource + WeaponSlot + one dumb gun that prints instead of shooting.
Projectile base (with group self-registration) + real firing. Projectile base (with group self-registration) + real firing.
Loadout-driven mounting. Loadout-driven mounting.
+15
View File
@@ -1,6 +1,9 @@
[gd_scene format=3 uid="uid://cieiwihia8t2w"] [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://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"] [sub_resource type="BoxMesh" id="BoxMesh_crate"]
size = Vector3(1, 1, 5) size = Vector3(1, 1, 5)
@@ -16,6 +19,10 @@ size = Vector3(1, 1, 5)
collision_layer = 2 collision_layer = 2
mass = 5.0 mass = 5.0
script = ExtResource("1_7h74f") script = ExtResource("1_7h74f")
loadout = Dictionary[StringName, ExtResource("2_anwow")]({
&"BowMount": ExtResource("3_henu8"),
&"SternMount": ExtResource("3_henu8")
})
buoyancy = 4.0 buoyancy = 4.0
full_force_depth = 0.8 full_force_depth = 0.8
probe_extents = Vector3(0.35, 0, 2.25) 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] [node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=18200711]
shape = SubResource("BoxShape_crate") 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")
+1
View File
@@ -74,3 +74,4 @@ current_controlled = NodePath("../Boat")
world_manager = NodePath("../WorldManager") world_manager = NodePath("../WorldManager")
camera_rig = NodePath("../CameraRig") camera_rig = NodePath("../CameraRig")
hud = NodePath("../Hud") hud = NodePath("../Hud")
throttle_ramp = null
+16
View File
@@ -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)
+5
View File
@@ -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) "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] [layer_names]
+9
View File
@@ -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"
+12
View File
@@ -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"
+17 -1
View File
@@ -7,10 +7,21 @@ extends FloatingBody
@export var throttle_response := 1.5 # how fast the engine spools up/down @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 rudder_strength := 2.5 # turning torque at speed
@export var keel_grip := 3.0 # resistance to sliding sideways @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 desired_throttle := 0.0 # move_toward target for actual throttle
var steer_input := 0.0 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: func _physics_process(delta: float) -> void:
super(delta) super(delta)
@@ -40,3 +51,8 @@ func _physics_process(delta: float) -> void:
right = right.normalized() right = right.normalized()
var lateral_speed := linear_velocity.dot(right) var lateral_speed := linear_velocity.dot(right)
apply_central_force(-right * lateral_speed * keel_grip * mass * submerged_ratio) 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()
+1 -1
View File
@@ -24,7 +24,7 @@ extends RigidBody3D
var _probe_offsets: Array[Vector3] = [] # local space var _probe_offsets: Array[Vector3] = [] # local space
var _ocean: Ocean 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 _gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
var submerged_ratio := 0.0 var submerged_ratio := 0.0
+4
View File
@@ -31,6 +31,7 @@ extends Node
var desired_throttle := 0.0 var desired_throttle := 0.0
func _ready() -> void: func _ready() -> void:
if current_controlled != null: # Only happens on intial loading if boat already moves if current_controlled != null: # Only happens on intial loading if boat already moves
desired_throttle = current_controlled.throttle desired_throttle = current_controlled.throttle
@@ -52,6 +53,9 @@ func _process(delta: float) -> void:
current_controlled.desired_throttle = desired_throttle current_controlled.desired_throttle = desired_throttle
current_controlled.steer_input = steer_input current_controlled.steer_input = steer_input
if Input.is_action_pressed("fire"):
current_controlled.fire_weapons()
func _possess() -> void: func _possess() -> void:
camera_rig.target = current_controlled camera_rig.target = current_controlled
world_manager.focus = current_controlled world_manager.focus = current_controlled
+31
View File
@@ -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()
+1
View File
@@ -0,0 +1 @@
uid://i5lxsp35533c
+11
View File
@@ -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
@@ -0,0 +1 @@
uid://k4add7rmdxw3
@@ -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
@@ -0,0 +1 @@
uid://vccbk7tbujhn
+31
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
uid://bcedvro1n83c0
+25
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
uid://ba5ums3xj42h
+28
View File
@@ -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")