diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9cf9f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Godot 4+ specific ignores +.godot/ + +# Godot-specific ignores +.import/ +export.cfg +export_presets.cfg + +# Blender +*.blend1 + +# Imported translations (automatically generated from CSV files) +*.translation + +# Added by Squinternator: weird *.tmp files added by the script editor +*.tmp + +# Added by Squinternator: *.dll files compiled by the windows build +addons/godot-jolt/windows/~godot-jolt_windows-x64_editor.dll +addons/terrain_3d/bin/~libterrain.windows.debug.x86_64.dll diff --git a/characters/player/player.gd b/characters/player/player.gd index 611434c..a82f088 100644 --- a/characters/player/player.gd +++ b/characters/player/player.gd @@ -15,6 +15,14 @@ const jetpack_force_factor : float = 2 var g : float = ProjectSettings.get_setting("physics/3d/default_gravity") # in m/s² var gravity : Vector3 = g * ProjectSettings.get_setting("physics/3d/default_gravity_vector") +@export var player_id = 1: + set(id): + player_id = id + $PlayerInput.set_multiplayer_authority(id) + +@onready var input = $PlayerInput +@onready var camera = $SpringArm3D/Camera3D + # floor detection @onready var hud = $HUD @onready var shape_cast = $ShapeCast3D @@ -25,15 +33,19 @@ signal energy_changed(energy) func _ready(): energy_changed.connect(hud._on_energy_changed) + if player_id == multiplayer.get_unique_id(): + camera.current = true + else: + $HUD.hide() -func is_on_floor(): +func is_on_floor() -> bool: return shape_cast.is_colliding() func is_skiing() -> bool: - return Input.is_action_pressed("ski") + return input.skiing func handle_jetpack(delta, direction): - if Input.is_action_pressed("jump_and_jet"): + if input.jetting: if energy > 0: var up_vector = Vector3.UP * jetpack_vertical_force * jetpack_force_factor var side_vector = direction * jetpack_horizontal_force * jetpack_force_factor @@ -41,26 +53,29 @@ func handle_jetpack(delta, direction): energy -= energy_drain_rate * delta else: energy += energy_charge_rate * delta - + energy = clamp(energy, 0, max_energy) energy_changed.emit(energy) +func _process(delta): + %SpringArm3D.global_transform.basis = Basis.from_euler(Vector3(input.camera_rotation.y, input.camera_rotation.x, 0.0)) + func _physics_process(delta): # retrieve user's direction vector - var _input_dir = Input.get_vector("left", "right", "forward", "backward") + var _input_dir = input.direction # compute direction in local space var _direction = (transform.basis * Vector3(_input_dir.x, 0, _input_dir.y)).normalized() # adjust direction based on spring arm rotation _direction = _direction.rotated(Vector3.UP, $SpringArm3D.rotation.y) - + handle_jetpack(delta, _direction) - + # handle ski if is_skiing(): physics_material_override.friction = 0 else: physics_material_override.friction = 1 - + if is_on_floor(): if not _direction.is_zero_approx() and not is_skiing(): # retrieve collision normal @@ -71,11 +86,13 @@ func _physics_process(delta): if slope_angle <= max_floor_angle: # adjust direction based on the floor normal to align with the slope _direction = _direction.slide(normal) - + linear_velocity = lerp(linear_velocity, _direction * ground_speed, .1) - - if Input.is_action_just_pressed("jump_and_jet"): + + if input.jumping: linear_velocity.y = sqrt(2 * abs((mass * gravity * delta).y) * 1) else: pass - + + input.jumping = false + diff --git a/characters/player/player.tscn b/characters/player/player.tscn index 77cd902..24463fa 100644 --- a/characters/player/player.tscn +++ b/characters/player/player.tscn @@ -1,59 +1,103 @@ -[gd_scene load_steps=9 format=3 uid="uid://cbhx1xme0sb7k"] +[gd_scene load_steps=11 format=3 uid="uid://cbhx1xme0sb7k"] [ext_resource type="Script" path="res://characters/player/player.gd" id="1_ymjub"] [ext_resource type="PackedScene" uid="uid://bcv81ku26xo" path="res://interfaces/hud/hud.tscn" id="2_5qvi2"] [ext_resource type="PackedScene" uid="uid://c8co0qa2omjmh" path="res://weapons/space_gun/space_gun.tscn" id="2_ka38u"] [sub_resource type="PhysicsMaterial" id="PhysicsMaterial_clur0"] +resource_local_to_scene = true bounce = 1.0 absorbent = true [sub_resource type="CapsuleMesh" id="CapsuleMesh_vmqfq"] +radius = 0.3 [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_rm4oh"] +radius = 0.3 [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_3l0v2"] +radius = 0.3 -[sub_resource type="GDScript" id="GDScript_qcxi0"] -script/source = "extends SpringArm3D +[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_rqdp6"] +properties/0/path = NodePath(".:linear_velocity") +properties/0/spawn = true +properties/0/replication_mode = 1 +properties/1/path = NodePath(".:position") +properties/1/spawn = true +properties/1/replication_mode = 1 +properties/2/path = NodePath(".:player_id") +properties/2/spawn = true +properties/2/replication_mode = 2 +[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_5j4ew"] +properties/0/path = NodePath(".:direction") +properties/0/spawn = false +properties/0/replication_mode = 1 +properties/1/path = NodePath(".:jetting") +properties/1/spawn = false +properties/1/replication_mode = 2 +properties/2/path = NodePath(".:camera_rotation") +properties/2/spawn = false +properties/2/replication_mode = 1 +properties/3/path = NodePath(".:skiing") +properties/3/spawn = false +properties/3/replication_mode = 2 + +[sub_resource type="GDScript" id="GDScript_uy24w"] +script/source = "extends MultiplayerSynchronizer + +@export var jumping = false +@export var jetting = false +@export var skiing = false +@export var direction = Vector2.ZERO +@export var camera_rotation : Vector3 @export var MOUSE_SENSITIVITY : float = 0.5 -@export var WHEEL_SENSITIVITY : float = 0.5 var _mouse_position: Vector2 -var _mouse_rotation : Vector3 -# Called when the node enters the scene tree for the first time. func _ready(): - Input.mouse_mode = Input.MOUSE_MODE_CAPTURED - $Camera3D.current = true - + var has_authority = get_multiplayer_authority() == multiplayer.get_unique_id() + set_process(has_authority) + set_process_unhandled_input(has_authority) + if has_authority: + Input.mouse_mode = Input.MOUSE_MODE_CAPTURED func _unhandled_input(event: InputEvent) -> void: var mouse_mode = Input.get_mouse_mode() - + # isolate mouse events if event is InputEventMouseMotion: if mouse_mode == Input.MOUSE_MODE_CAPTURED: # retrieve mouse position relative to last frame _mouse_position = event.relative * MOUSE_SENSITIVITY - -# Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta): + direction = Input.get_vector(\"left\", \"right\", \"forward\", \"backward\") + if Input.is_action_just_pressed(\"jump_and_jet\"): + jump.rpc() + if Input.is_action_just_pressed(\"fire_primary\"): + fire_primary.rpc() + jetting = Input.is_action_pressed(\"jump_and_jet\") + skiing = Input.is_action_pressed(\"ski\") _update_camera(delta) +@rpc(\"call_local\") +func jump(): + jumping = true + +@rpc(\"call_local\") +func fire_primary(): + $\"../SpringArm3D/Inventory/SpaceGun\".fire_primary() + func _update_camera(delta): # clamp vertical rotation (head motion) - _mouse_rotation.y -= _mouse_position.y * delta - _mouse_rotation.y = clamp(_mouse_rotation.y, deg_to_rad(-90.0),deg_to_rad(90.0)) + camera_rotation.y -= _mouse_position.y * delta + camera_rotation.y = clamp(camera_rotation.y, deg_to_rad(-90.0),deg_to_rad(90.0)) # wrap horizontal rotation (to prevent accumulation) - _mouse_rotation.x -= _mouse_position.x * delta - _mouse_rotation.x = wrapf(_mouse_rotation.x, deg_to_rad(0.0),deg_to_rad(360.0)) + camera_rotation.x -= _mouse_position.x * delta + camera_rotation.x = wrapf(camera_rotation.x, deg_to_rad(0.0),deg_to_rad(360.0)) # reset mouse motion until next input event _mouse_position = Vector2.ZERO - # update spring arm global rotation - global_transform.basis = Basis.from_euler(Vector3(_mouse_rotation.y, _mouse_rotation.x, 0.0)) " [node name="Player" type="RigidBody3D"] @@ -66,7 +110,6 @@ continuous_cd = true script = ExtResource("1_ymjub") [node name="MeshInstance3D" type="MeshInstance3D" parent="."] -visible = false mesh = SubResource("CapsuleMesh_vmqfq") [node name="CollisionShape3D" type="CollisionShape3D" parent="."] @@ -84,7 +127,6 @@ collide_with_areas = true unique_name_in_owner = true transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0) spring_length = 0.0 -script = SubResource("GDScript_qcxi0") [node name="Camera3D" type="Camera3D" parent="SpringArm3D"] fov = 100.0 @@ -93,4 +135,12 @@ near = 0.1 [node name="Inventory" type="Node3D" parent="SpringArm3D"] [node name="SpaceGun" parent="SpringArm3D/Inventory" instance=ExtResource("2_ka38u")] -transform = Transform3D(-1, 0, 8.74228e-08, 0, 1, 0, -8.74228e-08, 0, -1, 0.145, -0.095, -0.261784) +transform = Transform3D(-1, 0, 8.74228e-08, 0, 1, 0, -8.74228e-08, 0, -1, 0.281712, -0.095, -0.353461) + +[node name="ServerSynchronizer" type="MultiplayerSynchronizer" parent="."] +replication_config = SubResource("SceneReplicationConfig_rqdp6") + +[node name="PlayerInput" type="MultiplayerSynchronizer" parent="."] +root_path = NodePath(".") +replication_config = SubResource("SceneReplicationConfig_5j4ew") +script = SubResource("GDScript_uy24w") diff --git a/game_modes/demo.tscn b/game_modes/demo.tscn new file mode 100644 index 0000000..77a8cf8 --- /dev/null +++ b/game_modes/demo.tscn @@ -0,0 +1,11 @@ +[gd_scene load_steps=3 format=3 uid="uid://boviiugcnfyrj"] + +[ext_resource type="PackedScene" uid="uid://chbno00ugl6te" path="res://maps/genesis/genesis.tscn" id="2_lsep7"] +[ext_resource type="PackedScene" uid="uid://cbhx1xme0sb7k" path="res://characters/player/player.tscn" id="3_j1li7"] + +[node name="Demo" type="Node"] + +[node name="Map" parent="." instance=ExtResource("2_lsep7")] + +[node name="Player" parent="." instance=ExtResource("3_j1li7")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 92.7007, 0) diff --git a/game_modes/multiplayer.tscn b/game_modes/multiplayer.tscn new file mode 100644 index 0000000..618c2b8 --- /dev/null +++ b/game_modes/multiplayer.tscn @@ -0,0 +1,68 @@ +[gd_scene load_steps=2 format=3 uid="uid://c7ae4jw5d8mue"] + +[sub_resource type="GDScript" id="GDScript_pj58d"] +script/source = "extends Node + +@onready var map = $Map +@onready var players = $Players + +const PLAYER : PackedScene = preload(\"res://characters/player/player.tscn\") + +func start_server(peer): + multiplayer.multiplayer_peer = peer + + print(\"Server started\") + + load_map.call_deferred(load(\"res://maps/genesis/genesis.tscn\")) + + multiplayer.peer_connected.connect(add_player) + multiplayer.peer_disconnected.connect(remove_player) + + for id in multiplayer.get_peers(): + add_player(id) + + add_player(1) + +func join_server(peer): + multiplayer.multiplayer_peer = peer + +func load_map(scene : PackedScene): + map.add_child(scene.instantiate()) + +func add_player(peer_id : int): + var node_name = str(peer_id) + var player_scene_instance = PLAYER.instantiate() + player_scene_instance.name = node_name + player_scene_instance.player_id = peer_id + player_scene_instance.position = Vector3(0.0, 150.0, 0.0) + players.add_child(player_scene_instance) + print(\"Peer `%s` connected\" % node_name) + +func remove_player(peer_id : int): + var node_name = str(peer_id) + players.get_node(node_name).queue_free() + print(\"Peer `%s` disconnected\" % node_name) + +func _exit_tree(): + if not multiplayer.is_server(): + return + + multiplayer.peer_connected.disconnect(add_player) + multiplayer.peer_disconnected.disconnect(remove_player) +" + +[node name="Multiplayer" type="Node"] +script = SubResource("GDScript_pj58d") + +[node name="Map" type="Node" parent="."] + +[node name="MapSpawner" type="MultiplayerSpawner" parent="."] +_spawnable_scenes = PackedStringArray("res://maps/genesis/genesis.tscn") +spawn_path = NodePath("../Map") +spawn_limit = 1 + +[node name="Players" type="Node" parent="."] + +[node name="PlayersSpawner" type="MultiplayerSpawner" parent="."] +_spawnable_scenes = PackedStringArray("res://characters/player/player.tscn") +spawn_path = NodePath("../Players") diff --git a/interfaces/menus/boot/boot.tscn b/interfaces/menus/boot/boot.tscn index c50e588..c6f5833 100644 --- a/interfaces/menus/boot/boot.tscn +++ b/interfaces/menus/boot/boot.tscn @@ -1,8 +1,31 @@ -[gd_scene load_steps=5 format=3 uid="uid://bjctlqvs33nqy"] +[gd_scene load_steps=6 format=3 uid="uid://bjctlqvs33nqy"] [ext_resource type="Texture2D" uid="uid://c1tjamjm8qjog" path="res://interfaces/menus/boot/background.webp" id="1_ph586"] [ext_resource type="PackedScene" uid="uid://1seg8cvss7a7" path="res://interfaces/menus/boot/multiplayer.tscn" id="2_lcb0h"] +[sub_resource type="GDScript" id="GDScript_jd8xf"] +resource_name = "MainMenu" +script/source = "extends CanvasLayer +class_name MainMenu + +signal start_demo +signal start_server(peer) +signal join_server(peer) + +func _ready(): + $Multiplayer.start_server.connect(_on_start_server) + $Multiplayer.join_server.connect(_on_join_server) + +func _on_demo_pressed(): + start_demo.emit() + +func _on_start_server(peer): + start_server.emit(peer) + +func _on_join_server(peer): + join_server.emit(peer) +" + [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_snh7i"] bg_color = Color(0, 0.5, 0.5, 0.25) @@ -11,18 +34,6 @@ script/source = "extends PanelContainer const PLAYER = preload(\"res://characters/player/player.tscn\") const MAP = preload(\"res://maps/genesis/genesis.tscn\") - -func _on_demo_pressed(): - # instantiate scenes - var player = PLAYER.instantiate() - var map = MAP.instantiate() - # set player initial position - player.position = Vector3(0,150,0) - # add as siblings in tree - for o in [map, player]: - owner.add_sibling(o) - # queue main_menu node for deletion - owner.queue_free() func _on_multiplayer_pressed(): hide() @@ -33,6 +44,7 @@ func _on_quit_pressed(): " [node name="MainMenu" type="CanvasLayer"] +script = SubResource("GDScript_jd8xf") [node name="TextureRect" type="TextureRect" parent="."] anchors_preset = 15 @@ -81,6 +93,6 @@ layout_mode = 2 theme_override_font_sizes/font_size = 32 text = "Quit" -[connection signal="pressed" from="Main/MarginContainer/VBoxContainer/Demo" to="Main" method="_on_demo_pressed"] +[connection signal="pressed" from="Main/MarginContainer/VBoxContainer/Demo" to="." method="_on_demo_pressed"] [connection signal="pressed" from="Main/MarginContainer/VBoxContainer/Multiplayer" to="Main" method="_on_multiplayer_pressed"] [connection signal="pressed" from="Main/MarginContainer/VBoxContainer/Quit" to="Main" method="_on_quit_pressed"] diff --git a/interfaces/menus/boot/multiplayer.tscn b/interfaces/menus/boot/multiplayer.tscn index b2573fc..245140a 100644 --- a/interfaces/menus/boot/multiplayer.tscn +++ b/interfaces/menus/boot/multiplayer.tscn @@ -13,6 +13,9 @@ const DEFAULT_HOST : String = \"localhost\" var _join_address = RegEx.new() var _registered_ports = RegEx.new() +signal start_server(peer) +signal join_server(peer) + func _ready(): # see https://datatracker.ietf.org/doc/html/rfc1700 _registered_ports.compile(r'^(?:102[4-9]|10[3-9]\\d|1[1-9]\\d{2}|[2-9]\\d{3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])$') @@ -38,7 +41,8 @@ func _on_join_pressed(): # create client var peer = ENetMultiplayerPeer.new() peer.create_client(addr[0], addr[1]) - multiplayer.multiplayer_peer = peer + + join_server.emit(peer) func _on_host_pressed(): var port = DEFAULT_PORT @@ -52,16 +56,11 @@ func _on_host_pressed(): push_warning(\"A valid port number in the range 1024-65535 is required.\") return - # create server + ## create server var peer = ENetMultiplayerPeer.new() peer.create_server(port, MAX_CLIENTS) - multiplayer.multiplayer_peer = peer - - multiplayer.peer_connected.connect(_on_peer_connected) - -func _on_peer_connected(peer_id): - print(\"Peer `%s` connected\" % peer_id) + start_server.emit(peer) " [node name="Multiplayer" type="PanelContainer"] diff --git a/main.tscn b/main.tscn index 49047f0..a3fddfc 100644 --- a/main.tscn +++ b/main.tscn @@ -3,21 +3,56 @@ [ext_resource type="PackedScene" uid="uid://bjctlqvs33nqy" path="res://interfaces/menus/boot/boot.tscn" id="1_acy5o"] [sub_resource type="GDScript" id="GDScript_e61dq"] -script/source = "extends Node3D +script/source = " +extends Node3D +class_name Main + +@onready var main_menu = $MainMenu +@onready var game_mode = $GameMode + +func _ready(): + $MainMenu.start_demo.connect(_start_demo) + $MainMenu.start_server.connect(_start_server) + $MainMenu.join_server.connect(_join_server) func _unhandled_input(event): # exit the program if event.is_action_pressed(\"exit\"): get_tree().quit() - # switch window mode + # switch window mode if event.is_action_pressed(\"window_mode\"): if DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_FULLSCREEN: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) else: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN) + +func game_mode_cleanup(): + for c in game_mode.get_children(): + game_mode.remove_child(c) + c.queue_free() + +func start_game_mode(game_mode_scene : PackedScene): + main_menu.hide() + game_mode_cleanup() + var scene_instance = game_mode_scene.instantiate() + game_mode.add_child(scene_instance) + return scene_instance + +func _start_demo(): + start_game_mode(load(\"res://game_modes/demo.tscn\")) + +func _start_server(peer): + var server_scene = start_game_mode(load(\"res://game_modes/multiplayer.tscn\")) + server_scene.start_server(peer) + +func _join_server(peer): + var client_scene = start_game_mode(load(\"res://game_modes/multiplayer.tscn\")) + client_scene.join_server(peer) " [node name="Game" type="Node3D"] script = SubResource("GDScript_e61dq") [node name="MainMenu" parent="." instance=ExtResource("1_acy5o")] + +[node name="GameMode" type="Node3D" parent="."] diff --git a/maps/genesis/resources/storage.res b/maps/genesis/resources/storage.res index 322bd92..404d333 100644 Binary files a/maps/genesis/resources/storage.res and b/maps/genesis/resources/storage.res differ diff --git a/weapons/space_gun/assets/material.tres b/weapons/space_gun/assets/material.tres index 323b8c9..2c88076 100644 --- a/weapons/space_gun/assets/material.tres +++ b/weapons/space_gun/assets/material.tres @@ -1,11 +1,3 @@ -[gd_resource type="ShaderMaterial" load_steps=3 format=3 uid="uid://de6t4olk7hrs1"] - -[ext_resource type="Texture2D" uid="uid://cvtqt0k2ewd07" path="res://weapons/space_gun/assets/albedo.png" id="1_36iph"] -[ext_resource type="Shader" path="res://shaders/zclip.gdshader" id="1_c4uko"] +[gd_resource type="ShaderMaterial" format=3 uid="uid://de6t4olk7hrs1"] [resource] -render_priority = 0 -shader = ExtResource("1_c4uko") -shader_parameter/FOV = 70.0 -shader_parameter/albedo = Color(1, 1, 1, 1) -shader_parameter/texture_albedo = ExtResource("1_36iph") diff --git a/weapons/space_gun/space_gun.gd b/weapons/space_gun/space_gun.gd index eacf221..76dca20 100644 --- a/weapons/space_gun/space_gun.gd +++ b/weapons/space_gun/space_gun.gd @@ -21,10 +21,10 @@ func print_node_properties(node): func _ready(): shoot.connect(_on_shoot) -func _input(_event): - if Input.is_action_just_pressed("fire_primary"): - var projectile = PROJECTILE.instantiate() - shoot.emit(projectile, nozzle, inventory.owner) +func fire_primary(): + var projectile = PROJECTILE.instantiate() + shoot.emit(projectile, nozzle, inventory.owner) + func _on_shoot(projectile, origin, player): projectile.position = origin.global_position diff --git a/weapons/space_gun/space_gun.tscn b/weapons/space_gun/space_gun.tscn index 7068a1e..f6205a3 100644 --- a/weapons/space_gun/space_gun.tscn +++ b/weapons/space_gun/space_gun.tscn @@ -1,16 +1,8 @@ -[gd_scene load_steps=7 format=3 uid="uid://c8co0qa2omjmh"] +[gd_scene load_steps=5 format=3 uid="uid://c8co0qa2omjmh"] [ext_resource type="Script" path="res://weapons/space_gun/space_gun.gd" id="1_6sm4s"] -[ext_resource type="Texture2D" uid="uid://cvtqt0k2ewd07" path="res://weapons/space_gun/assets/albedo.png" id="1_51bf5"] [ext_resource type="Material" uid="uid://de6t4olk7hrs1" path="res://weapons/space_gun/assets/material.tres" id="1_uaehs"] -[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ioife"] -resource_name = "dark" -albedo_color = Color(0, 0, 0, 1) -albedo_texture = ExtResource("1_51bf5") -metallic = 0.35 -texture_filter = 1 - [sub_resource type="ArrayMesh" id="ArrayMesh_2hpnh"] _surfaces = [{ "aabb": AABB(-1.5172, -0.57, -4.4, 1.0692, 1.57, 4.6), @@ -55,7 +47,6 @@ _surfaces = [{ "format": 34896613399, "index_count": 60, "index_data": PackedByteArray(30, 4, 28, 4, 29, 4, 29, 4, 31, 4, 30, 4, 34, 4, 32, 4, 33, 4, 33, 4, 35, 4, 34, 4, 38, 4, 36, 4, 37, 4, 37, 4, 39, 4, 38, 4, 42, 4, 40, 4, 41, 4, 41, 4, 43, 4, 42, 4, 43, 4, 44, 4, 42, 4, 43, 4, 45, 4, 44, 4, 48, 4, 46, 4, 47, 4, 47, 4, 49, 4, 48, 4, 234, 3, 233, 3, 50, 4, 53, 4, 51, 4, 52, 4, 52, 4, 54, 4, 53, 4, 57, 4, 55, 4, 56, 4, 60, 4, 58, 4, 59, 4, 59, 4, 61, 4, 60, 4, 64, 4, 62, 4, 63, 4, 65, 4, 238, 3, 222, 3), -"material": SubResource("StandardMaterial3D_ioife"), "name": "dark", "primitive": 3, "uv_scale": Vector4(8.8, 10.8, 0, 0), @@ -75,4 +66,4 @@ mesh = SubResource("ArrayMesh_hudfn") skeleton = NodePath("") [node name="Nozzle" type="Node3D" parent="."] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.225) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.315549)