commit 07577d3fd3f907730533546c54566a62418a9092 Author: wanp Date: Tue Feb 11 08:00:26 2025 -0300 awesome!!! diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0035ad6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Godot 4+ specific ignores +.godot/ + +# Godot-specific ignores +.import/ +export.cfg +export_presets.cfg + +# Imported translations (automatically generated from CSV files) +*.translation + +# Mono-specific ignores +.mono/ +data_*/ +mono_crash.*.json \ No newline at end of file diff --git a/assets/drop.png b/assets/drop.png new file mode 100644 index 0000000..0b84458 Binary files /dev/null and b/assets/drop.png differ diff --git a/assets/drop.png.import b/assets/drop.png.import new file mode 100644 index 0000000..e7ff05e --- /dev/null +++ b/assets/drop.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cwbl0r1e26eja" +path="res://.godot/imported/drop.png-46e76e0a1a49ec3b9e6c8f19494b9486.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/drop.png" +dest_files=["res://.godot/imported/drop.png-46e76e0a1a49ec3b9e6c8f19494b9486.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/assets/sfx/boom.wav b/assets/sfx/boom.wav new file mode 100644 index 0000000..980b5db Binary files /dev/null and b/assets/sfx/boom.wav differ diff --git a/assets/sfx/boom.wav.import b/assets/sfx/boom.wav.import new file mode 100644 index 0000000..e3bae8d --- /dev/null +++ b/assets/sfx/boom.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://3dlhs18w1fa2" +path="res://.godot/imported/boom.wav-b7dc2436738b092118bc6a9ac139ed1b.sample" + +[deps] + +source_file="res://assets/sfx/boom.wav" +dest_files=["res://.godot/imported/boom.wav-b7dc2436738b092118bc6a9ac139ed1b.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=0 diff --git a/assets/sfx/splash-small-quiet.wav b/assets/sfx/splash-small-quiet.wav new file mode 100644 index 0000000..bb3cb7a Binary files /dev/null and b/assets/sfx/splash-small-quiet.wav differ diff --git a/assets/sfx/splash-small-quiet.wav.import b/assets/sfx/splash-small-quiet.wav.import new file mode 100644 index 0000000..4e19ee7 --- /dev/null +++ b/assets/sfx/splash-small-quiet.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://blgrl2wl05feq" +path="res://.godot/imported/splash-small-quiet.wav-5d79804a4ce6d654b376652fbb9b2033.sample" + +[deps] + +source_file="res://assets/sfx/splash-small-quiet.wav" +dest_files=["res://.godot/imported/splash-small-quiet.wav-5d79804a4ce6d654b376652fbb9b2033.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=0 diff --git a/assets/sfx/splash-small.wav b/assets/sfx/splash-small.wav new file mode 100644 index 0000000..d1b8f46 Binary files /dev/null and b/assets/sfx/splash-small.wav differ diff --git a/assets/sfx/splash-small.wav.import b/assets/sfx/splash-small.wav.import new file mode 100644 index 0000000..20f76eb --- /dev/null +++ b/assets/sfx/splash-small.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://dtjpv2b74g24m" +path="res://.godot/imported/splash-small.wav-395e6cb2278f19594f3004dfba45fa36.sample" + +[deps] + +source_file="res://assets/sfx/splash-small.wav" +dest_files=["res://.godot/imported/splash-small.wav-395e6cb2278f19594f3004dfba45fa36.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=0 diff --git a/assets/sprout.png b/assets/sprout.png new file mode 100644 index 0000000..3637fad Binary files /dev/null and b/assets/sprout.png differ diff --git a/assets/sprout.png.import b/assets/sprout.png.import new file mode 100644 index 0000000..233f2ef --- /dev/null +++ b/assets/sprout.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d35y5ckne72qe" +path="res://.godot/imported/sprout.png-98a4ab515d3bfdbd6f6db7d14b52ead7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/sprout.png" +dest_files=["res://.godot/imported/sprout.png-98a4ab515d3bfdbd6f6db7d14b52ead7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/default_bus_layout.tres b/default_bus_layout.tres new file mode 100644 index 0000000..10dd5e1 --- /dev/null +++ b/default_bus_layout.tres @@ -0,0 +1,9 @@ +[gd_resource type="AudioBusLayout" format=3 uid="uid://dcxpomrfumov"] + +[resource] +bus/1/name = &"SoundEffects" +bus/1/solo = false +bus/1/mute = false +bus/1/bypass_fx = false +bus/1/volume_db = 0.0 +bus/1/send = &"Master" diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..9d8b7fa --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..0dc5b26 --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cb6qv3c0iojfl" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..b196c6b --- /dev/null +++ b/project.godot @@ -0,0 +1,82 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Soil Tower" +run/main_scene="res://src/main/main.tscn" +config/features=PackedStringArray("4.3", "GL Compatibility") +config/icon="res://icon.svg" + +[display] + +window/size/viewport_width=640 +window/size/viewport_height=360 +window/size/window_width_override=1280 +window/size/window_height_override=720 +window/stretch/mode="canvas_items" +window/stretch/aspect="expand" + +[global_group] + +spawn_points="" +voids="" + +[input] + +move_forward={ +"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":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) +] +} +move_backward={ +"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":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) +] +} +strafe_left={ +"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":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) +] +} +strafe_right={ +"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":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null) +] +} +jump={ +"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":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null) +] +} +chat={ +"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":84,"key_label":0,"unicode":116,"location":0,"echo":false,"script":null) +, 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":89,"key_label":0,"unicode":121,"location":0,"echo":false,"script":null) +] +} +fire={ +"deadzone": 0.5, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null) +] +} + +[layer_names] + +3d_physics/layer_1="solid" +3d_physics/layer_2="players" +3d_physics/layer_3="projectiles" +3d_physics/layer_4="plants" +3d_physics/layer_5="pickups" + +[rendering] + +renderer/rendering_method="gl_compatibility" +renderer/rendering_method.mobile="gl_compatibility" diff --git a/src/ingame/chlorophyll.gd b/src/ingame/chlorophyll.gd new file mode 100644 index 0000000..712e782 --- /dev/null +++ b/src/ingame/chlorophyll.gd @@ -0,0 +1,10 @@ +extends Node3D + + +func _ready() -> void: + set_multiplayer_authority(1) + + +@rpc("any_peer", "call_local", "reliable") +func pick_up() -> void: + queue_free() diff --git a/src/ingame/chlorophyll.tscn b/src/ingame/chlorophyll.tscn new file mode 100644 index 0000000..cb73b5c --- /dev/null +++ b/src/ingame/chlorophyll.tscn @@ -0,0 +1,25 @@ +[gd_scene load_steps=4 format=3 uid="uid://byfbrru6pl1ue"] + +[ext_resource type="Script" path="res://src/ingame/chlorophyll.gd" id="1_t6bqw"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_l1bir"] +albedo_color = Color(0, 0.894118, 0.211765, 1) +emission_enabled = true +emission = Color(0, 0.894118, 0.211765, 1) +emission_energy_multiplier = 2.0 + +[sub_resource type="SphereShape3D" id="SphereShape3D_so6gf"] +radius = 0.3 + +[node name="Chlorophyll" type="Node3D"] +script = ExtResource("1_t6bqw") + +[node name="CSGSphere3D" type="CSGSphere3D" parent="."] +material_override = SubResource("StandardMaterial3D_l1bir") +radius = 0.1 + +[node name="PickupArea" type="Area3D" parent="."] +collision_layer = 16 + +[node name="CollisionShape3D" type="CollisionShape3D" parent="PickupArea"] +shape = SubResource("SphereShape3D_so6gf") diff --git a/src/ingame/ingame.gd b/src/ingame/ingame.gd new file mode 100644 index 0000000..ca829cb --- /dev/null +++ b/src/ingame/ingame.gd @@ -0,0 +1,133 @@ +extends Node3D + +@export var _player_scene: PackedScene + +@export var _players: Node3D +@export var _chat_panel: Panel +@export var _chat_input: LineEdit +@export var _chat_history_scroll: ScrollContainer +@export var _chat_history: VBoxContainer +@export var _chat_panel_inactive: Panel +@export var _chat_history_inactive: VBoxContainer +@export var _chat_history_scroll_inactive: ScrollContainer + + +func _ready() -> void: + print("ingame ready") + + _chat_history_scroll.get_v_scroll_bar().changed.connect( + _on_chat_history_scroll_changed.bind(_chat_history_scroll) + ) + _chat_history_scroll_inactive.get_v_scroll_bar().changed.connect( + _on_chat_history_scroll_changed.bind(_chat_history_scroll_inactive) + ) + + Input.mouse_mode = Input.MOUSE_MODE_CAPTURED + + if not multiplayer.is_server(): + return + + multiplayer.peer_connected.connect(_add_player) + multiplayer.peer_disconnected.connect(_remove_player) + + for id in multiplayer.get_peers(): + _add_player.call_deferred(id) + + if not OS.has_feature("dedicated_server"): + _add_player.call_deferred(1) + + +func _exit_tree() -> void: + if not multiplayer.is_server(): + return + + multiplayer.peer_connected.disconnect(_add_player) + multiplayer.peer_disconnected.disconnect(_remove_player) + + +func _unhandled_input(event: InputEvent) -> void: + if event.is_action_pressed("chat"): + get_viewport().set_input_as_handled() + _activate_chat() + + +func _input(event: InputEvent) -> void: + if event.is_action_pressed("ui_cancel"): + get_viewport().set_input_as_handled() + _deactivate_chat() + + +func _activate_chat() -> void: + _chat_panel.show() + _chat_panel_inactive.hide() + _chat_input.grab_focus.call_deferred() + _players.get_node(str(multiplayer.get_unique_id())).controls_disabled = true + Input.mouse_mode = Input.MOUSE_MODE_VISIBLE + + +func _deactivate_chat() -> void: + _chat_panel.hide() + _chat_panel_inactive.show() + _players.get_node(str(multiplayer.get_unique_id())).controls_disabled = false + Input.mouse_mode = Input.MOUSE_MODE_CAPTURED + + +func _add_player(id: int) -> void: + print("add player %d" % id) + var character: CharacterBody3D = _player_scene.instantiate() + character.id = id + character.name = str(id) + _players.add_child(character, true) + _set_character_authority.rpc(id) + + +func _remove_player(id: int) -> void: + if not _players.has_node(str(id)): + return + + print("remove player %d" % id) + _players.get_node(str(id)).queue_free() + + +@rpc("authority", "call_local", "reliable") +func _set_character_authority(id: int) -> void: + if not _players.has_node(str(id)): + return + + _players.get_node(str(id)).set_multiplayer_authority(id) + + +@rpc("any_peer", "call_local", "reliable", 1) +func _submit_chat_message(text: String) -> void: + var id := multiplayer.get_remote_sender_id() + var username: String = GameState.fetch().player_data[id].username + _add_chat_message.rpc(username, text) + + +@rpc("authority", "call_local", "reliable", 1) +func _add_chat_message(username: String, text: String) -> void: + _chat_history.add_child(_make_chat_message(username, text)) + _chat_history_inactive.add_child(_make_chat_message(username, text)) + + +func _on_chat_history_scroll_changed(history: ScrollContainer) -> void: + # keep history scrolled to the bottom + history.scroll_vertical = history.get_v_scroll_bar().max_value + + +func _make_chat_message(username: String, text: String) -> RichTextLabel: + var label := RichTextLabel.new() + label.bbcode_enabled = true + label.fit_content = true + label.append_text("[color=red]%s[/color] %s" % [username.replace("[", "[lb]"), text.replace("[", "[lb]")]) + return label + + +func _on_chat_message_submitted(new_text := "") -> void: + if _chat_input.text.is_empty(): + _deactivate_chat() + return + + _submit_chat_message.rpc_id(1, _chat_input.text) + _chat_input.clear() + _deactivate_chat() diff --git a/src/ingame/ingame.tscn b/src/ingame/ingame.tscn new file mode 100644 index 0000000..25eb2a0 --- /dev/null +++ b/src/ingame/ingame.tscn @@ -0,0 +1,261 @@ +[gd_scene load_steps=11 format=3 uid="uid://oyvhcwq60v2"] + +[ext_resource type="Script" path="res://src/ingame/ingame.gd" id="1_akuuj"] +[ext_resource type="PackedScene" uid="uid://cs8c570bxh6u" path="res://src/ingame/player.tscn" id="2_w1gjc"] +[ext_resource type="PackedScene" uid="uid://bysgtksvovyur" path="res://src/ingame/sprout.tscn" id="3_2xvqq"] + +[sub_resource type="BoxShape3D" id="BoxShape3D_l0cfx"] +size = Vector3(8, 0.2, 17) + +[sub_resource type="BoxShape3D" id="BoxShape3D_8v8cv"] +size = Vector3(4, 0.6, 17) + +[sub_resource type="BoxShape3D" id="BoxShape3D_0k58u"] +size = Vector3(2.2, 0.6, 3) + +[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_eu7fm"] + +[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_e4ynx"] +ground_bottom_color = Color(0.647059, 0.654902, 0.670588, 1) +sun_angle_max = 8.0 +sun_curve = 8.81744 +energy_multiplier = 1.5 + +[sub_resource type="Sky" id="Sky_ygvd3"] +sky_material = SubResource("ProceduralSkyMaterial_e4ynx") + +[sub_resource type="Environment" id="Environment_ijk14"] +background_mode = 2 +sky = SubResource("Sky_ygvd3") +ambient_light_color = Color(0, 0.164706, 0.278431, 1) +ambient_light_energy = 2.0 + +[node name="Ingame" type="Node3D" node_paths=PackedStringArray("_players", "_chat_panel", "_chat_input", "_chat_history_scroll", "_chat_history", "_chat_panel_inactive", "_chat_history_inactive", "_chat_history_scroll_inactive")] +script = ExtResource("1_akuuj") +_player_scene = ExtResource("2_w1gjc") +_players = NodePath("Players") +_chat_panel = NodePath("UI/ChatPanel") +_chat_input = NodePath("UI/ChatPanel/ChatInput") +_chat_history_scroll = NodePath("UI/ChatPanel/ChatHistoryScroll") +_chat_history = NodePath("UI/ChatPanel/ChatHistoryScroll/ChatHistory") +_chat_panel_inactive = NodePath("UI/ChatPanelInactive") +_chat_history_inactive = NodePath("UI/ChatPanelInactive/ChatHistoryScroll/ChatHistory") +_chat_history_scroll_inactive = NodePath("UI/ChatPanelInactive/ChatHistoryScroll") + +[node name="UI" type="CanvasLayer" parent="."] + +[node name="ChatPanel" type="Panel" parent="UI"] +visible = false +self_modulate = Color(1, 1, 1, 0.498039) +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -312.0 +offset_top = 12.0 +offset_bottom = 172.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="ChatHistoryScroll" type="ScrollContainer" parent="UI/ChatPanel"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 8.0 +offset_top = 8.0 +offset_right = -8.0 +offset_bottom = -48.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="ChatHistory" type="VBoxContainer" parent="UI/ChatPanel/ChatHistoryScroll"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="ChatInput" type="LineEdit" parent="UI/ChatPanel"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 8.0 +offset_top = 120.0 +offset_right = -72.0 +offset_bottom = -9.0 +grow_horizontal = 2 +grow_vertical = 2 +placeholder_text = "say something" +max_length = 300 +context_menu_enabled = false +clear_button_enabled = true +middle_mouse_paste_enabled = false + +[node name="ChatSendButton" type="Button" parent="UI/ChatPanel"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 248.0 +offset_top = 120.0 +offset_right = -8.0 +offset_bottom = -9.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "send" + +[node name="ChatPanelInactive" type="Panel" parent="UI"] +self_modulate = Color(1, 1, 1, 0) +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -312.0 +offset_top = 12.0 +offset_bottom = 172.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="ChatHistoryScroll" type="ScrollContainer" parent="UI/ChatPanelInactive"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 8.0 +offset_top = 8.0 +offset_right = -8.0 +offset_bottom = -9.0 +grow_horizontal = 2 +grow_vertical = 2 +vertical_scroll_mode = 3 + +[node name="ChatHistory" type="VBoxContainer" parent="UI/ChatPanelInactive/ChatHistoryScroll"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="CSGBox3D" type="CSGBox3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 7.45058e-09, 0) +collision_mask = 0 +size = Vector3(8, 0.2, 17) + +[node name="StaticBody3D" type="StaticBody3D" parent="CSGBox3D"] +collision_mask = 0 + +[node name="CollisionShape3D" type="CollisionShape3D" parent="CSGBox3D/StaticBody3D"] +shape = SubResource("BoxShape3D_l0cfx") + +[node name="GPUParticlesCollisionBox3D" type="GPUParticlesCollisionBox3D" parent="CSGBox3D"] +size = Vector3(8, 0.2, 17) + +[node name="CSGBox3D2" type="CSGBox3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.03442, 0.2, 0) +collision_mask = 0 +size = Vector3(4, 0.6, 17) + +[node name="StaticBody3D" type="StaticBody3D" parent="CSGBox3D2"] +collision_mask = 0 + +[node name="CollisionShape3D" type="CollisionShape3D" parent="CSGBox3D2/StaticBody3D"] +shape = SubResource("BoxShape3D_8v8cv") + +[node name="GPUParticlesCollisionBox3D" type="GPUParticlesCollisionBox3D" parent="CSGBox3D2"] +size = Vector3(4, 0.6, 17) + +[node name="CSGBox3D3" type="CSGBox3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.23442, 0.8, -2.4) +collision_mask = 0 +size = Vector3(2.2, 0.6, 3) + +[node name="StaticBody3D" type="StaticBody3D" parent="CSGBox3D3"] +collision_mask = 0 + +[node name="CollisionShape3D" type="CollisionShape3D" parent="CSGBox3D3/StaticBody3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.76837e-07, 0, 0) +shape = SubResource("BoxShape3D_0k58u") + +[node name="GPUParticlesCollisionBox3D" type="GPUParticlesCollisionBox3D" parent="CSGBox3D3"] +size = Vector3(2.2, 0.6, 3) + +[node name="Void" type="StaticBody3D" parent="." groups=["voids"]] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -8, 0) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="Void"] +shape = SubResource("WorldBoundaryShape3D_eu7fm") + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource("Environment_ijk14") + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] +transform = Transform3D(0.943957, 0.3178, 0.0891561, 0, -0.270113, 0.962829, 0.33007, -0.908868, -0.254975, 0, 0, 0) +light_energy = 2.0 + +[node name="SpawnPoint" type="Marker3D" parent="." groups=["spawn_points"]] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) + +[node name="PlayerSpawner" type="MultiplayerSpawner" parent="."] +_spawnable_scenes = PackedStringArray("res://src/ingame/player.tscn") +spawn_path = NodePath("../Players") + +[node name="Players" type="Node3D" parent="."] + +[node name="Plants" type="Node3D" parent="."] + +[node name="Sprout" parent="Plants" instance=ExtResource("3_2xvqq")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00401986, 0.396167, -3.22022) + +[node name="Sprout2" parent="Plants" instance=ExtResource("3_2xvqq")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.60402, 0.396167, -3.22022) + +[node name="Sprout3" parent="Plants" instance=ExtResource("3_2xvqq")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.59598, 0.396167, -3.22022) + +[node name="Sprout4" parent="Plants" instance=ExtResource("3_2xvqq")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00401986, 0.396167, 2.77978) + +[node name="Sprout5" parent="Plants" instance=ExtResource("3_2xvqq")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.60402, 0.396167, 2.77978) + +[node name="Sprout6" parent="Plants" instance=ExtResource("3_2xvqq")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.59598, 0.396167, 2.77978) + +[node name="Sprout7" parent="Plants" instance=ExtResource("3_2xvqq")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00401986, 0.396167, 3.37978) + +[node name="Sprout8" parent="Plants" instance=ExtResource("3_2xvqq")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.60402, 0.396167, 3.37978) + +[node name="Sprout9" parent="Plants" instance=ExtResource("3_2xvqq")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.59598, 0.396167, 3.37978) + +[node name="Sprout10" parent="Plants" instance=ExtResource("3_2xvqq")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.99598, 1.39617, -2.22022) + +[node name="Sprout11" parent="Plants" instance=ExtResource("3_2xvqq")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.39598, 1.39617, -2.22022) + +[node name="Sprout12" parent="Plants" instance=ExtResource("3_2xvqq")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.59598, 1.39617, -2.22022) + +[node name="Sprout16" parent="Plants" instance=ExtResource("3_2xvqq")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.99598, 1.39617, -2.82022) + +[node name="Sprout17" parent="Plants" instance=ExtResource("3_2xvqq")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.39598, 1.39617, -2.82022) + +[node name="Sprout18" parent="Plants" instance=ExtResource("3_2xvqq")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.59598, 1.39617, -2.82022) + +[node name="Sprout13" parent="Plants" instance=ExtResource("3_2xvqq")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.99598, 1.39617, -1.62022) + +[node name="Sprout14" parent="Plants" instance=ExtResource("3_2xvqq")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.39598, 1.39617, -1.62022) + +[node name="Sprout15" parent="Plants" instance=ExtResource("3_2xvqq")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.59598, 1.39617, -1.62022) + +[connection signal="text_submitted" from="UI/ChatPanel/ChatInput" to="." method="_on_chat_message_submitted"] +[connection signal="pressed" from="UI/ChatPanel/ChatSendButton" to="." method="_on_chat_message_submitted"] diff --git a/src/ingame/player.gd b/src/ingame/player.gd new file mode 100644 index 0000000..460bb7c --- /dev/null +++ b/src/ingame/player.gd @@ -0,0 +1,105 @@ +extends CharacterBody3D + + +const SPEED = 5.0 +const JUMP_VELOCITY = 4.5 + +@export var _projectile_scene: PackedScene + +@export var _projectile_holder: Node +@export var _projectile_point: Marker3D +@export var _camera_pivot: Node3D +@export var _camera: Camera3D +@export var _shot_sound: AudioStreamPlayer3D + +@export var id: int +@export var input_dir := Vector2() +@export var input_jumped := false + +var _max_speed := 12 +var _mouse_sensitivity := 0.008 # radians/pixel + +var _projectile_speed := 12.0 + +var controls_disabled := false + + +func _ready() -> void: + if id != multiplayer.get_unique_id(): + return + + $Model.hide() + _camera.make_current() + + var spawn_point: Marker3D = get_tree().get_nodes_in_group("spawn_points").pick_random() + global_position = spawn_point.global_position + reset_physics_interpolation() + + +func _process(_delta: float) -> void: + if id != multiplayer.get_unique_id(): + return + + _process_input() + + +func _physics_process(delta: float) -> void: + # Add the gravity. + if not is_on_floor(): + velocity += get_gravity() * delta + + # Handle jump. + if input_jumped and is_on_floor(): + velocity.y = JUMP_VELOCITY + + # Get the input direction and handle the movement/deceleration. + # As good practice, you should replace UI actions with custom gameplay actions. + var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() + if direction: + velocity.x = direction.x * SPEED + velocity.z = direction.z * SPEED + else: + velocity.x = move_toward(velocity.x, 0, SPEED) + velocity.z = move_toward(velocity.z, 0, SPEED) + + move_and_slide() + + +func _unhandled_input(event: InputEvent) -> void: + if controls_disabled or id != multiplayer.get_unique_id(): + return + + if event is InputEventMouseMotion: + rotate_y(-event.relative.x * _mouse_sensitivity) + _camera_pivot.rotate_x(-event.relative.y * _mouse_sensitivity) + _camera_pivot.rotation.x = clamp(_camera_pivot.rotation.x, -1.2, 1.2) + return + + if event.is_action_pressed("fire"): + var new_projectile: Node3D = _projectile_scene.instantiate() + _projectile_holder.add_child(new_projectile, true) + _set_projectile_authority.rpc(new_projectile.get_path(), id) + new_projectile.set_global_pos.rpc(_projectile_point.global_position) + + new_projectile.body.process_mode = Node.PROCESS_MODE_INHERIT + new_projectile.body.linear_velocity = -_camera.global_basis.z * _projectile_speed + + _shot_sound.play() + + +@rpc("authority", "call_local", "reliable") +func _set_projectile_authority(path: NodePath, authority_id: int) -> void: + if not _projectile_holder.has_node(path): + return + + _projectile_holder.get_node(path).set_multiplayer_authority(authority_id) + + +func _process_input() -> void: + if controls_disabled: + input_dir = Vector2.ZERO + input_jumped = false + return + + input_dir = Input.get_vector("strafe_left", "strafe_right", "move_forward", "move_backward") + input_jumped = Input.is_action_just_pressed("jump") diff --git a/src/ingame/player.tscn b/src/ingame/player.tscn new file mode 100644 index 0000000..c277cb3 --- /dev/null +++ b/src/ingame/player.tscn @@ -0,0 +1,71 @@ +[gd_scene load_steps=7 format=3 uid="uid://cs8c570bxh6u"] + +[ext_resource type="Script" path="res://src/ingame/player.gd" id="1_isrmf"] +[ext_resource type="PackedScene" uid="uid://tdsbo3e5ic86" path="res://src/ingame/water_bomb.tscn" id="2_naek4"] +[ext_resource type="AudioStream" uid="uid://3dlhs18w1fa2" path="res://assets/sfx/boom.wav" id="3_u2hxa"] + +[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_2xotl"] +properties/0/path = NodePath(".:position") +properties/0/spawn = true +properties/0/replication_mode = 1 +properties/1/path = NodePath(".:id") +properties/1/spawn = true +properties/1/replication_mode = 0 +properties/2/path = NodePath(".:rotation") +properties/2/spawn = true +properties/2/replication_mode = 1 +properties/3/path = NodePath(".:input_dir") +properties/3/spawn = true +properties/3/replication_mode = 1 +properties/4/path = NodePath(".:input_jumped") +properties/4/spawn = true +properties/4/replication_mode = 2 +properties/5/path = NodePath("ShotSound:playing") +properties/5/spawn = true +properties/5/replication_mode = 2 + +[sub_resource type="CapsuleMesh" id="CapsuleMesh_hi8jw"] + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_f4jjl"] + +[node name="Player" type="CharacterBody3D" node_paths=PackedStringArray("_projectile_holder", "_projectile_point", "_camera_pivot", "_camera", "_shot_sound")] +collision_layer = 2 +script = ExtResource("1_isrmf") +_projectile_scene = ExtResource("2_naek4") +_projectile_holder = NodePath("ProjectileHolder") +_projectile_point = NodePath("ProjectilePoint") +_camera_pivot = NodePath("CameraPivot") +_camera = NodePath("CameraPivot/Camera3D") +_shot_sound = NodePath("ShotSound") + +[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."] +replication_config = SubResource("SceneReplicationConfig_2xotl") + +[node name="ProjectileSpawner" type="MultiplayerSpawner" parent="."] +_spawnable_scenes = PackedStringArray("res://src/ingame/water_bomb.tscn") +spawn_path = NodePath("../ProjectileHolder") + +[node name="ProjectileHolder" type="Node" parent="."] + +[node name="ProjectilePoint" type="Marker3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.3, -0.095) + +[node name="Model" type="MeshInstance3D" parent="."] +transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0, 0) +mesh = SubResource("CapsuleMesh_hi8jw") + +[node name="CSGBox3D" type="CSGBox3D" parent="Model"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.5) +size = Vector3(0.8, 0.8, 0.8) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +shape = SubResource("CapsuleShape3D_f4jjl") + +[node name="CameraPivot" type="Node3D" parent="."] + +[node name="Camera3D" type="Camera3D" parent="CameraPivot"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0) + +[node name="ShotSound" type="AudioStreamPlayer3D" parent="."] +stream = ExtResource("3_u2hxa") +bus = &"SoundEffects" diff --git a/src/ingame/sprout.gd b/src/ingame/sprout.gd new file mode 100644 index 0000000..7a67309 --- /dev/null +++ b/src/ingame/sprout.gd @@ -0,0 +1,25 @@ +extends Node3D + +@export var _need_water_drop: Sprite3D +@export var _production_timer: Timer + +var needs_water := true + + +func _ready() -> void: + set_multiplayer_authority(1) + _need_water_drop.show() + + +func _on_production_timer_timeout() -> void: + needs_water = true + _need_water_drop.show() + + +@rpc("any_peer", "call_local", "reliable") +func water() -> void: + if needs_water: + needs_water = false + _need_water_drop.hide() + + _production_timer.start() diff --git a/src/ingame/sprout.tscn b/src/ingame/sprout.tscn new file mode 100644 index 0000000..63a81da --- /dev/null +++ b/src/ingame/sprout.tscn @@ -0,0 +1,53 @@ +[gd_scene load_steps=6 format=3 uid="uid://bysgtksvovyur"] + +[ext_resource type="Script" path="res://src/ingame/sprout.gd" id="1_snma1"] +[ext_resource type="Texture2D" uid="uid://d35y5ckne72qe" path="res://assets/sprout.png" id="2_ipgad"] +[ext_resource type="Texture2D" uid="uid://cwbl0r1e26eja" path="res://assets/drop.png" id="3_kghdv"] + +[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_rs2qp"] +properties/0/path = NodePath(".:needs_water") +properties/0/spawn = true +properties/0/replication_mode = 2 +properties/1/path = NodePath("NeedWaterDrop:visible") +properties/1/spawn = true +properties/1/replication_mode = 2 + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_cwbye"] +radius = 0.3 +height = 0.8 + +[node name="Sprout" type="Node3D" node_paths=PackedStringArray("_need_water_drop", "_production_timer")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00401986, -0.00383317, 0.00119042) +script = ExtResource("1_snma1") +_need_water_drop = NodePath("NeedWaterDrop") +_production_timer = NodePath("ProductionTimer") + +[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."] +replication_config = SubResource("SceneReplicationConfig_rs2qp") + +[node name="Sprite3D" type="Sprite3D" parent="."] +pixel_size = 0.02 +billboard = 2 +shaded = true +texture_filter = 2 +texture = ExtResource("2_ipgad") + +[node name="NeedWaterDrop" type="Sprite3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.3, 0) +visible = false +pixel_size = 0.02 +billboard = 1 +texture_filter = 2 +texture = ExtResource("3_kghdv") + +[node name="Area3D" type="Area3D" parent="."] +collision_layer = 8 +collision_mask = 0 + +[node name="CollisionShape3D" type="CollisionShape3D" parent="Area3D"] +shape = SubResource("CapsuleShape3D_cwbye") + +[node name="ProductionTimer" type="Timer" parent="."] +one_shot = true + +[connection signal="timeout" from="ProductionTimer" to="." method="_on_production_timer_timeout"] diff --git a/src/ingame/water_bomb.gd b/src/ingame/water_bomb.gd new file mode 100644 index 0000000..6b2c93d --- /dev/null +++ b/src/ingame/water_bomb.gd @@ -0,0 +1,66 @@ +extends Node3D + +@export var _splash_small_sound: AudioStreamPlayer3D +@export var _splash_small_quiet_sound: AudioStreamPlayer3D +@export var _model: Node3D +@export var _splash_particles: GPUParticles3D +@export var body: RigidBody3D +## no longer exists and shouldn't be considered, but not ready to be freed yet +@export var is_dead := false + +var _in_splash_range := {} + + +func _ready() -> void: + body.process_mode = Node.PROCESS_MODE_DISABLED + + +func _process(delta: float) -> void: + if not is_multiplayer_authority(): + return + + global_position = body.global_position + + +@rpc("authority", "call_local", "reliable") +func _disable_body() -> void: + # not using synchronizer for this because it ends up enabling processing + # on other clients + (func() -> void: body.process_mode = Node.PROCESS_MODE_DISABLED).call_deferred() + + +func _on_body_entered(p_body: Node3D) -> void: + if p_body.is_in_group("voids"): + queue_free() + return + + for area: Area3D in _in_splash_range: + area.get_parent_node_3d().water.rpc_id(1) + + _splash_small_sound.play() + _splash_small_quiet_sound.play() + _splash_particles.emitting = true + + is_dead = true + _model.hide() + _disable_body.rpc() + + await _splash_small_sound.finished + if _splash_small_quiet_sound.playing: + await _splash_small_quiet_sound.finished + queue_free() + + +func _on_splash_area_entered(area: Area3D) -> void: + _in_splash_range[area] = true + + +func _on_splash_area_exited(area: Area3D) -> void: + _in_splash_range.erase(area) + + +@rpc("authority", "call_local", "reliable") +func set_global_pos(new: Vector3) -> void: + global_position = new + body.global_position = new + body.reset_physics_interpolation() diff --git a/src/ingame/water_bomb.tscn b/src/ingame/water_bomb.tscn new file mode 100644 index 0000000..2875885 --- /dev/null +++ b/src/ingame/water_bomb.tscn @@ -0,0 +1,110 @@ +[gd_scene load_steps=11 format=3 uid="uid://tdsbo3e5ic86"] + +[ext_resource type="Script" path="res://src/ingame/water_bomb.gd" id="1_lk5fq"] +[ext_resource type="AudioStream" uid="uid://dtjpv2b74g24m" path="res://assets/sfx/splash-small.wav" id="2_0wk8g"] +[ext_resource type="AudioStream" uid="uid://blgrl2wl05feq" path="res://assets/sfx/splash-small-quiet.wav" id="3_hgy7l"] + +[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_0ebrr"] +properties/0/path = NodePath(".:position") +properties/0/spawn = true +properties/0/replication_mode = 1 +properties/1/path = NodePath("SplashSmallSound:playing") +properties/1/spawn = true +properties/1/replication_mode = 2 +properties/2/path = NodePath("SplashSmallQuietSound:playing") +properties/2/spawn = true +properties/2/replication_mode = 2 +properties/3/path = NodePath(".:is_dead") +properties/3/spawn = true +properties/3/replication_mode = 2 +properties/4/path = NodePath("Model:visible") +properties/4/spawn = true +properties/4/replication_mode = 2 +properties/5/path = NodePath("SplashParticles:emitting") +properties/5/spawn = true +properties/5/replication_mode = 2 + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ptaep"] +albedo_color = Color(0.0936238, 0.825356, 1, 1) +metallic = 0.8 +roughness = 0.4 + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_v7dnr"] +albedo_color = Color(0, 1, 1, 1) +roughness = 0.2 + +[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_rjbgm"] +direction = Vector3(0, 1, 0) +initial_velocity_min = 5.0 +initial_velocity_max = 5.0 +gravity = Vector3(0, -8, 0) +collision_mode = 2 + +[sub_resource type="SphereMesh" id="SphereMesh_bhdh4"] +radius = 0.1 +height = 0.2 + +[sub_resource type="SphereShape3D" id="SphereShape3D_6c830"] +radius = 0.2 + +[sub_resource type="SphereShape3D" id="SphereShape3D_y6453"] +radius = 1.5 + +[node name="WaterBomb" type="Node3D" node_paths=PackedStringArray("_splash_small_sound", "_splash_small_quiet_sound", "_model", "_splash_particles", "body")] +script = ExtResource("1_lk5fq") +_splash_small_sound = NodePath("SplashSmallSound") +_splash_small_quiet_sound = NodePath("SplashSmallQuietSound") +_model = NodePath("Model") +_splash_particles = NodePath("SplashParticles") +body = NodePath("RigidBody3D") + +[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."] +replication_config = SubResource("SceneReplicationConfig_0ebrr") + +[node name="Model" type="CSGSphere3D" parent="."] +material_override = SubResource("StandardMaterial3D_ptaep") +radius = 0.2 +radial_segments = 24 +rings = 12 + +[node name="SplashParticles" type="GPUParticles3D" parent="."] +material_override = SubResource("StandardMaterial3D_v7dnr") +emitting = false +amount = 32 +lifetime = 2.0 +one_shot = true +explosiveness = 1.0 +randomness = 1.0 +process_material = SubResource("ParticleProcessMaterial_rjbgm") +draw_pass_1 = SubResource("SphereMesh_bhdh4") + +[node name="RigidBody3D" type="RigidBody3D" parent="."] +top_level = true +collision_layer = 4 +contact_monitor = true +max_contacts_reported = 1 +linear_damp_mode = 1 +linear_damp = 0.3 + +[node name="CollisionShape3D" type="CollisionShape3D" parent="RigidBody3D"] +shape = SubResource("SphereShape3D_6c830") + +[node name="SplashArea" type="Area3D" parent="RigidBody3D"] +collision_layer = 0 +collision_mask = 8 +monitorable = false + +[node name="CollisionShape3D" type="CollisionShape3D" parent="RigidBody3D/SplashArea"] +shape = SubResource("SphereShape3D_y6453") + +[node name="SplashSmallSound" type="AudioStreamPlayer3D" parent="."] +stream = ExtResource("2_0wk8g") +bus = &"SoundEffects" + +[node name="SplashSmallQuietSound" type="AudioStreamPlayer3D" parent="."] +stream = ExtResource("3_hgy7l") +bus = &"SoundEffects" + +[connection signal="body_entered" from="RigidBody3D" to="." method="_on_body_entered"] +[connection signal="area_entered" from="RigidBody3D/SplashArea" to="." method="_on_splash_area_entered"] +[connection signal="area_exited" from="RigidBody3D/SplashArea" to="." method="_on_splash_area_exited"] diff --git a/src/lib/game_state.gd b/src/lib/game_state.gd new file mode 100644 index 0000000..0529cab --- /dev/null +++ b/src/lib/game_state.gd @@ -0,0 +1,14 @@ +class_name GameState +extends Resource + +@export var player_data := {} + +static var _instance := GameState.new() + + +static func save() -> void: + ResourceSaver.save(_instance, "user://game_state.tres") + + +static func fetch() -> GameState: + return _instance diff --git a/src/lib/player_data.gd b/src/lib/player_data.gd new file mode 100644 index 0000000..559accf --- /dev/null +++ b/src/lib/player_data.gd @@ -0,0 +1,4 @@ +class_name PlayerData +extends Resource + +@export var username: String diff --git a/src/main/main.gd b/src/main/main.gd new file mode 100644 index 0000000..68309a2 --- /dev/null +++ b/src/main/main.gd @@ -0,0 +1,164 @@ +extends Node + +@export var _start_menu: Control +@export var _username_input: LineEdit +@export var _ip_input: LineEdit + +@export var _player_list: VBoxContainer +@export var _chat_menu: Control +@export var _chat_input: LineEdit +@export var _chat_history_scroll: ScrollContainer +@export var _chat_history: VBoxContainer +@export var _start_button: Button + +@export var _ingame_scene: PackedScene + +var _game_started := false + + +func _ready() -> void: + _start_menu.show() + _chat_menu.hide() + + +func _on_player_joined(id: int) -> void: + for player_id in GameState.fetch().player_data.keys(): + var username = GameState.fetch().player_data[player_id].username + _register_player.rpc_id(id, username) + + if _game_started: + _start_game.rpc_id(id) + + +func _on_player_left(id: int) -> void: + _unregister_player.rpc(id) + + +@rpc("any_peer", "call_local", "reliable") +func _register_player(username: String) -> void: + var id = multiplayer.get_remote_sender_id() + GameState.fetch().player_data[id] = PlayerData.new() + GameState.fetch().player_data[id].username = username + + var list_label := RichTextLabel.new() + list_label.bbcode_enabled = false + list_label.fit_content = true + list_label.text = username + _player_list.add_child(list_label) + + +@rpc("authority", "call_local", "reliable") +func _unregister_player(id: int) -> void: + var labels := _player_list.get_children() + for label: RichTextLabel in labels: + if label.text == GameState.fetch().player_data[id].username: + label.queue_free() + break + + GameState.fetch().player_data.erase(id) + + +@rpc("any_peer", "call_local", "reliable", 1) +func _submit_chat_message(text: String) -> void: + var id := multiplayer.get_remote_sender_id() + var username: String = GameState.fetch().player_data[id].username + _add_chat_message.rpc(username, text) + + +@rpc("authority", "call_local", "reliable", 1) +func _add_chat_message(username: String, text: String) -> void: + var label := RichTextLabel.new() + label.bbcode_enabled = true + label.fit_content = true + label.append_text("[color=red]%s[/color] %s" % [username.replace("[", "[lb]"), text.replace("[", "[lb]")]) + _chat_history.add_child(label) + + # keep chat scrolled to the bottom + await _chat_history_scroll.get_v_scroll_bar().changed + _chat_history_scroll.scroll_vertical = _chat_history_scroll.get_v_scroll_bar().max_value + + +@rpc("authority", "call_local", "reliable") +func _start_game() -> void: + _start_menu.hide() + _chat_menu.hide() + _game_started = true + + +# START MENU # +func _on_host_button_pressed() -> void: + var peer := ENetMultiplayerPeer.new() + var error = peer.create_server(20106, 8, 2) + if error != OK: + match error: + ERR_ALREADY_IN_USE: + push_error("server ERR_ALREADY_IN_USE") + ERR_CANT_CREATE: + push_error("server ERR_CANT_CREATE") + return + + multiplayer.multiplayer_peer = peer + multiplayer.multiplayer_peer.peer_connected.connect(_on_player_joined) + multiplayer.multiplayer_peer.peer_disconnected.connect(_on_player_left) + + var username := _username_input.text + if username.is_empty(): + username = "schlorpy" + _register_player.rpc_id(1, username) + + _start_button.disabled = false + _start_menu.hide() + _chat_menu.show() + + print("hosted!") + + +func _on_join_button_pressed() -> void: + var peer := ENetMultiplayerPeer.new() + + var ip := "" + if _ip_input.text.is_empty(): + ip = "127.0.0.1" + else: + ip = _ip_input.text + + var error = peer.create_client(ip, 20106, 2) + + + if error != OK: + match error: + ERR_ALREADY_IN_USE: + push_error("client ERR_ALREADY_IN_USE") + ERR_CANT_CREATE: + push_error("client ERR_CANT_CREATE") + return + + multiplayer.multiplayer_peer = peer + + multiplayer.connected_to_server.connect( + func() -> void: + var username := _username_input.text + if username.is_empty(): + username = "schlorpy" + _register_player.rpc(username) + + _start_button.disabled = true + _start_menu.hide() + _chat_menu.show() + + print("joined!") + ) + + +# CHAT MENU # +func _on_start_button_pressed() -> void: + _start_game.rpc() + $IngameContainer.add_child.call_deferred(_ingame_scene.instantiate()) + + +func _on_chat_message_submitted(text := "") -> void: + if _chat_input.text.is_empty(): + return + + _submit_chat_message.rpc_id(1, _chat_input.text) + _chat_input.clear() diff --git a/src/main/main.tscn b/src/main/main.tscn new file mode 100644 index 0000000..ad513f8 --- /dev/null +++ b/src/main/main.tscn @@ -0,0 +1,213 @@ +[gd_scene load_steps=3 format=3 uid="uid://dk6pok1gk6ick"] + +[ext_resource type="Script" path="res://src/main/main.gd" id="1_fqmow"] +[ext_resource type="PackedScene" uid="uid://oyvhcwq60v2" path="res://src/ingame/ingame.tscn" id="2_5ajgq"] + +[node name="Main" type="Node" node_paths=PackedStringArray("_start_menu", "_username_input", "_ip_input", "_player_list", "_chat_menu", "_chat_input", "_chat_history_scroll", "_chat_history", "_start_button")] +script = ExtResource("1_fqmow") +_start_menu = NodePath("StartMenu") +_username_input = NodePath("StartMenu/UsernameInput") +_ip_input = NodePath("StartMenu/IPInput") +_player_list = NodePath("ChatMenu/PlayersPanel/ScrollContainer/PlayerList") +_chat_menu = NodePath("ChatMenu") +_chat_input = NodePath("ChatMenu/ChatPanel/ChatInput") +_chat_history_scroll = NodePath("ChatMenu/ChatPanel/ChatHistoryScroll") +_chat_history = NodePath("ChatMenu/ChatPanel/ChatHistoryScroll/ChatHistory") +_start_button = NodePath("ChatMenu/StartButton") +_ingame_scene = ExtResource("2_5ajgq") + +[node name="IngameSpawner" type="MultiplayerSpawner" parent="."] +_spawnable_scenes = PackedStringArray("res://src/ingame/ingame.tscn") +spawn_path = NodePath("../IngameContainer") +spawn_limit = 1 + +[node name="IngameContainer" type="Node" parent="."] + +[node name="StartMenu" type="Control" parent="."] +layout_mode = 3 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -20.0 +offset_top = -20.0 +offset_right = 20.0 +offset_bottom = 20.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="UsernameInput" type="LineEdit" parent="StartMenu"] +layout_mode = 0 +offset_left = -44.0 +offset_top = -128.0 +offset_right = 84.0 +offset_bottom = -97.0 +placeholder_text = "username" +alignment = 1 +max_length = 12 +context_menu_enabled = false +middle_mouse_paste_enabled = false + +[node name="IPInput" type="LineEdit" parent="StartMenu"] +layout_mode = 0 +offset_left = -180.0 +offset_top = 112.0 +offset_right = 220.0 +offset_bottom = 143.0 +placeholder_text = "127.0.0.1" +alignment = 1 +max_length = 39 +context_menu_enabled = false +middle_mouse_paste_enabled = false + +[node name="HostButton" type="Button" parent="StartMenu"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -64.0 +offset_top = -100.0 +offset_right = 64.0 +offset_bottom = -36.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "host" + +[node name="JoinButton" type="Button" parent="StartMenu"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -64.0 +offset_top = 12.0 +offset_right = 64.0 +offset_bottom = 76.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "join" + +[node name="ChatMenu" type="Control" parent="."] +visible = false +layout_mode = 3 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -20.0 +offset_top = -20.0 +offset_right = 20.0 +offset_bottom = 20.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="PlayersPanel" type="Panel" parent="ChatMenu"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -312.0 +offset_top = -172.0 +offset_right = -192.0 +offset_bottom = 4.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="ScrollContainer" type="ScrollContainer" parent="ChatMenu/PlayersPanel"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 8.0 +offset_top = 8.0 +offset_right = -8.0 +offset_bottom = -8.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="PlayerList" type="VBoxContainer" parent="ChatMenu/PlayersPanel/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="ChatPanel" type="Panel" parent="ChatMenu"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -312.0 +offset_top = 12.0 +offset_right = 312.0 +offset_bottom = 172.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="ChatHistoryScroll" type="ScrollContainer" parent="ChatMenu/ChatPanel"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 8.0 +offset_top = 8.0 +offset_right = -8.0 +offset_bottom = -48.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="ChatHistory" type="VBoxContainer" parent="ChatMenu/ChatPanel/ChatHistoryScroll"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="ChatInput" type="LineEdit" parent="ChatMenu/ChatPanel"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 8.0 +offset_top = 120.0 +offset_right = -72.0 +offset_bottom = -9.0 +grow_horizontal = 2 +grow_vertical = 2 +placeholder_text = "say something" +max_length = 300 +context_menu_enabled = false +clear_button_enabled = true +middle_mouse_paste_enabled = false + +[node name="ChatSendButton" type="Button" parent="ChatMenu/ChatPanel"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 560.0 +offset_top = 120.0 +offset_right = -8.0 +offset_bottom = -9.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "send" + +[node name="StartButton" type="Button" parent="ChatMenu"] +layout_mode = 0 +offset_left = -164.0 +offset_top = -32.0 +offset_right = -100.0 +offset_bottom = 23.0 +text = "Start!" + +[connection signal="pressed" from="StartMenu/HostButton" to="." method="_on_host_button_pressed"] +[connection signal="pressed" from="StartMenu/JoinButton" to="." method="_on_join_button_pressed"] +[connection signal="text_submitted" from="ChatMenu/ChatPanel/ChatInput" to="." method="_on_chat_message_submitted"] +[connection signal="pressed" from="ChatMenu/ChatPanel/ChatSendButton" to="." method="_on_chat_message_submitted"] +[connection signal="pressed" from="ChatMenu/StartButton" to="." method="_on_start_button_pressed"]