diff --git a/Game/Assets/Characters/Player.gd b/Game/Assets/Characters/Player.gd
index b0ad5ab..0ed566a 100644
--- a/Game/Assets/Characters/Player.gd
+++ b/Game/Assets/Characters/Player.gd
@@ -88,12 +88,35 @@ var jetpack_was_active = false
var velocity := Vector3.ZERO
var gravity_vec := Vector3.ZERO
+var dead = false: # used to workaround Godot crash when destroying player_nodes
+ set(value):
+ match value:
+ true:
+ #input_active = false
+ self.hide()
+ $Body.disabled = true
+ #set_physics_process(false)
+ false:
+ #input_active = true
+ self.show()
+ $Body.disabled = false
+ #set_physics_process(true)
+ dead = value
+
@rpc(auth, nosync,unreliable) func update_movement(player_transform, head_rotation, lin_velocity, jetpack):
global_transform = player_transform
head.set_rotation(head_rotation)
jetpack_active = jetpack
linear_velocity = lin_velocity
+@rpc(any, sync, reliable) func set_dead(is_dead: bool):
+ #print("Recieved RPC call for set_dead ", is_dead)
+ self.dead = is_dead
+
+# if is_multiplayer_authority():
+# print("Rebroadcasting RPC call for set_dead ", dead)
+# rpc(&'set_dead', dead)
+
func _ready() -> void:
#Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
view_zoom_target = 1.0
@@ -124,6 +147,9 @@ func aim(event) -> void:
head.rotation.x = clamp(current_tilt, deg2rad(-90), deg2rad(90))
func _input(event) -> void:
+ if dead:
+ return
+
if not input_active:
return
@@ -161,6 +187,9 @@ func _input(event) -> void:
weapon.trigger(1, false)
func _process(delta):
+ if dead:
+ return
+
$Jetpack/GPUParticles3D.emitting = jetpack_active
if jetpack_active:
@@ -185,12 +214,14 @@ func _process(delta):
elif not view_zoom_direction and view_zoom > view_zoom_target:
view_zoom = max(view_zoom_target, view_zoom - delta * 4)
+ hud.get_node("Stats").get_node("JetpackBar").value = (jetpack_fuel / jetpack_tank) * 100
+
func damage(hp: int):
var target = main.player_list.players[self.get_multiplayer_authority()]
target.health -= hp
-@rpc(any, nosync, reliable) func die(killer_pid: int):
+@rpc(any, sync, reliable) func die(killer_pid: int):
var gibs = gibs_vfx.instantiate()
get_tree().root.add_child(gibs)
gibs.global_transform = self.global_transform
@@ -200,6 +231,7 @@ func damage(hp: int):
# return
#main.rpc(&'destroy_player', self.get_multiplayer_authority())
main.destroy_player(self.get_multiplayer_authority())
+ set_dead(true)
#main.chat.rpc(&'chat_notification', "Player [/i][b][color=" + main.player_list.players[self.get_multiplayer_authority()].color.to_html() + "]" + main.player_list.players[self.get_multiplayer_authority()].name + "[/color][/b][i] was killed by " + main.player_list.players[killer_pid].name )
main.chat.chat_notification("Player [/i][b][color=" + main.player_list.players[self.get_multiplayer_authority()].color.to_html() + "]" + main.player_list.players[self.get_multiplayer_authority()].name + "[/color][/b][i] was killed by " + main.player_list.players[killer_pid].name )
@@ -216,6 +248,10 @@ func update_color(color) -> void: #change player's wolrldmodel color
func _physics_process(delta):
+ if dead: # workaround for Godot player destruction crash
+ linear_velocity = Vector3.ZERO
+ return
+
if not is_multiplayer_authority():
move_and_slide()
return
diff --git a/Game/Assets/HUD/BarOver.svg b/Game/Assets/HUD/BarOver.svg
new file mode 100644
index 0000000..fef00ac
--- /dev/null
+++ b/Game/Assets/HUD/BarOver.svg
@@ -0,0 +1,71 @@
+
+
diff --git a/Game/Assets/HUD/BarOver.svg.import b/Game/Assets/HUD/BarOver.svg.import
new file mode 100644
index 0000000..57aa7e2
--- /dev/null
+++ b/Game/Assets/HUD/BarOver.svg.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture2D"
+uid="uid://dvt25wji1pdyl"
+path="res://.godot/imported/BarOver.svg-3e206b7d7b36feafb9ac8b93edd2c46b.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/HUD/BarOver.svg"
+dest_files=["res://.godot/imported/BarOver.svg-3e206b7d7b36feafb9ac8b93edd2c46b.stex"]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/bptc_ldr=0
+compress/normal_map=0
+compress/channel_pack=0
+compress/streamed=false
+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/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
diff --git a/Game/Assets/HUD/BarProgress.svg b/Game/Assets/HUD/BarProgress.svg
new file mode 100644
index 0000000..5eaba6a
--- /dev/null
+++ b/Game/Assets/HUD/BarProgress.svg
@@ -0,0 +1,71 @@
+
+
diff --git a/Game/Assets/HUD/BarProgress.svg.import b/Game/Assets/HUD/BarProgress.svg.import
new file mode 100644
index 0000000..8ca73be
--- /dev/null
+++ b/Game/Assets/HUD/BarProgress.svg.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture2D"
+uid="uid://0j6rxd7ncmu1"
+path="res://.godot/imported/BarProgress.svg-38f8fe7383fae81cc6603349ccd802d9.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/HUD/BarProgress.svg"
+dest_files=["res://.godot/imported/BarProgress.svg-38f8fe7383fae81cc6603349ccd802d9.stex"]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/bptc_ldr=0
+compress/normal_map=0
+compress/channel_pack=0
+compress/streamed=false
+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/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
diff --git a/Game/Assets/HUD/BarUnder.svg b/Game/Assets/HUD/BarUnder.svg
new file mode 100644
index 0000000..52de9b7
--- /dev/null
+++ b/Game/Assets/HUD/BarUnder.svg
@@ -0,0 +1,71 @@
+
+
diff --git a/Game/Assets/HUD/BarUnder.svg.import b/Game/Assets/HUD/BarUnder.svg.import
new file mode 100644
index 0000000..51685b6
--- /dev/null
+++ b/Game/Assets/HUD/BarUnder.svg.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture2D"
+uid="uid://ck4exwgifde4n"
+path="res://.godot/imported/BarUnder.svg-95ef68273b3476991eb89e9f1df4b93d.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/HUD/BarUnder.svg"
+dest_files=["res://.godot/imported/BarUnder.svg-95ef68273b3476991eb89e9f1df4b93d.stex"]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/bptc_ldr=0
+compress/normal_map=0
+compress/channel_pack=0
+compress/streamed=false
+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/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
diff --git a/Game/Assets/HUD/HUD.tscn b/Game/Assets/HUD/HUD.tscn
index dad234c..68fab7c 100644
--- a/Game/Assets/HUD/HUD.tscn
+++ b/Game/Assets/HUD/HUD.tscn
@@ -1,4 +1,4 @@
-[gd_scene load_steps=13 format=3 uid="uid://bff5uslrxesjx"]
+[gd_scene load_steps=16 format=3 uid="uid://bff5uslrxesjx"]
[ext_resource type="Texture2D" uid="uid://blnjjtjifk22i" path="res://Assets/HUD/Vignette.png" id="1"]
[ext_resource type="Script" path="res://Assets/HUD/HUD.gd" id="1_wc430"]
@@ -8,6 +8,9 @@
[ext_resource type="AudioStream" uid="uid://bllgajqussdi5" path="res://Assets/SFX/HUD_Confirm_Kill.wav" id="5_hh74r"]
[ext_resource type="Script" path="res://Assets/HUD/Chat.gd" id="6"]
[ext_resource type="Theme" uid="uid://ddtu7o1dbp0s8" path="res://Assets/HUD/Theme.tres" id="7"]
+[ext_resource type="Texture2D" uid="uid://ck4exwgifde4n" path="res://Assets/HUD/BarUnder.svg" id="9_q07oy"]
+[ext_resource type="Texture2D" uid="uid://dvt25wji1pdyl" path="res://Assets/HUD/BarOver.svg" id="10_5i332"]
+[ext_resource type="Texture2D" uid="uid://0j6rxd7ncmu1" path="res://Assets/HUD/BarProgress.svg" id="11_tlgqu"]
[sub_resource type="Animation" id="1"]
resource_name = "Default"
@@ -310,4 +313,59 @@ __meta__ = {
"_edit_use_anchors_": false
}
+[node name="Stats" type="VBoxContainer" parent="."]
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -200.0
+offset_top = -68.0
+offset_right = -32.0
+offset_bottom = -32.0
+grow_horizontal = 0
+grow_vertical = 0
+script = null
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="HealthBar" type="TextureProgressBar" parent="Stats"]
+offset_right = 200.0
+offset_bottom = 32.0
+rect_min_size = Vector2(200, 32)
+value = 100.0
+allow_greater = true
+allow_lesser = true
+texture_under = ExtResource( "9_q07oy" )
+texture_over = ExtResource( "10_5i332" )
+texture_progress = ExtResource( "11_tlgqu" )
+tint_under = Color(0, 0, 0, 0.12549)
+tint_over = Color(0, 0.780392, 1, 1)
+tint_progress = Color(0, 0.745098, 1, 1)
+nine_patch_stretch = true
+stretch_margin_left = 16
+stretch_margin_top = 16
+stretch_margin_right = 16
+stretch_margin_bottom = 16
+script = null
+
+[node name="JetpackBar" type="TextureProgressBar" parent="Stats"]
+offset_top = 36.0
+offset_right = 200.0
+offset_bottom = 68.0
+rect_min_size = Vector2(200, 32)
+value = 100.0
+texture_under = ExtResource( "9_q07oy" )
+texture_over = ExtResource( "10_5i332" )
+texture_progress = ExtResource( "11_tlgqu" )
+tint_under = Color(0, 0, 0, 0.12549)
+tint_over = Color(1, 0.764706, 0, 1)
+tint_progress = Color(1, 0.760784, 0, 1)
+nine_patch_stretch = true
+stretch_margin_left = 16
+stretch_margin_top = 16
+stretch_margin_right = 16
+stretch_margin_bottom = 16
+script = null
+
[connection signal="text_submitted" from="Chat/VBoxContainer/Typing/Editor" to="Chat" method="_on_Editor_text_submitted"]
diff --git a/Game/Assets/HUD/Panel.svg b/Game/Assets/HUD/Panel.svg
new file mode 100644
index 0000000..aed61ce
--- /dev/null
+++ b/Game/Assets/HUD/Panel.svg
@@ -0,0 +1,71 @@
+
+
diff --git a/Game/Assets/HUD/Panel.svg.import b/Game/Assets/HUD/Panel.svg.import
new file mode 100644
index 0000000..2ac7fe7
--- /dev/null
+++ b/Game/Assets/HUD/Panel.svg.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture2D"
+uid="uid://duxv0vin55qyy"
+path="res://.godot/imported/Panel.svg-123d44a61e9822e83e9dafcfaffa99a9.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/HUD/Panel.svg"
+dest_files=["res://.godot/imported/Panel.svg-123d44a61e9822e83e9dafcfaffa99a9.stex"]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/bptc_ldr=0
+compress/normal_map=0
+compress/channel_pack=0
+compress/streamed=false
+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/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
diff --git a/Game/Assets/SFX/Weapons_Handgun_Casing_01.wav.import b/Game/Assets/SFX/Weapons_Handgun_Casing_01.wav.import
new file mode 100644
index 0000000..88bd831
--- /dev/null
+++ b/Game/Assets/SFX/Weapons_Handgun_Casing_01.wav.import
@@ -0,0 +1,22 @@
+[remap]
+
+importer="wav"
+type="AudioStreamSample"
+uid="uid://d2siwe07rwepl"
+path="res://.godot/imported/Weapons_Handgun_Casing_01.wav-80f393f0b224823a59ba1d5e69dc8cdd.sample"
+
+[deps]
+
+source_file="res://Assets/SFX/Weapons_Handgun_Casing_01.wav"
+dest_files=["res://.godot/imported/Weapons_Handgun_Casing_01.wav-80f393f0b224823a59ba1d5e69dc8cdd.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=false
+compress/mode=0
diff --git a/Game/Main.gd b/Game/Main.gd
index cb4f2e2..07afad7 100644
--- a/Game/Main.gd
+++ b/Game/Main.gd
@@ -73,6 +73,8 @@ var spawn_queue = {}
var game_score_limit = 15
+const destroy_free_player_crash_workaround = true
+
func _process(delta):
uptime += delta
@@ -88,6 +90,10 @@ func _process(delta):
$Label.text += "\n\nspawn_queue: \n"
$Label.text += str(spawn_queue)
+ if local_player:
+ $Label.text += "\n\nLOCAL PLAYER DEAD: " + str(local_player.dead)
+
+
for i in spawn_queue.keys():
if spawn_queue[i] <= uptime:
var is_local = true if i == get_tree().multiplayer.get_unique_id() else false
@@ -109,25 +115,27 @@ class PlayerList:
@onready var player_list = PlayerList.new()
-var focus = GameFocus.MENU :
+var focus:GameFocus:
set(new_focus):
if local_player != null:
assert(local_player != null, "local_player is not null but it is null, WTF?!")
match new_focus:
- 0:
+ 0: # MENU
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
gui.show()
+ hud.hide()
if local_player:
local_player.input_active = false
- 1:
+ 1: # GAME
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
gui.hide()
+ hud.show()
if local_player:
local_player.input_active = true
- 2:
+ 2: # CHAT
if local_player:
local_player.input_active = false
- 3:
+ 3: #AWAY
if local_player:
local_player.input_active = true
@@ -155,6 +163,16 @@ func check_game_win_condition():
game_over()
func update_hud():
+
+ ### Health
+
+ hud.get_node("Stats").get_node("HealthBar").value = player_list.get( get_tree().multiplayer.get_unique_id() ).health
+
+ if player_list.players.size() <= 1:
+ return # if we're the sole player in the server, stop here
+
+ ### SCORE, RANK, GAP/LEAD
+
hud.get_node("ScoreRank").text = "SCORE: " + str(player_list.get( get_tree().multiplayer.get_unique_id() ).score)
var score = player_list.get( get_tree().multiplayer.get_unique_id() ).score
@@ -199,11 +217,11 @@ func update_hud():
update_hud()
func push_local_player_info(): #
- var id = get_tree().multiplayer.get_unique_id()
+ var pid = get_tree().multiplayer.get_unique_id()
- rpc(&'player_list_update', player_list.get(id).serialize(), get_tree().multiplayer.get_unique_id())
+ rpc(&'player_list_update', player_list.get(pid).serialize(), pid)
-@rpc func destroy_player(pid: int):
+@rpc(sync, any, reliable) func destroy_player(pid: int):
print("DESTROY_PLAYER called on ", get_tree().multiplayer.get_unique_id(), " by ", get_tree().multiplayer.get_remote_sender_id(), " regarding ", pid)
#assert($Players.has_node(str(pid)), "Attempting to destroy a player that does not exist")
@@ -218,13 +236,40 @@ func push_local_player_info(): #
#assert(player_node != null, "Attempting to delete a player node that does not exist")
- player_node.name = str(player_node.name) + "_dead" # avoids name collision when instancing another player scene
- print("before free")
- player_node.queue_free()
- print("after free")
+ # alternative finale to this function that is a workaround to avoid Godot crashing due to an engine bug
+ # https://github.com/godotengine/godot/issues/52853
+ if destroy_free_player_crash_workaround:
+ print("Setting player ", pid, " as DEAD")
+ player_node.set_dead(true)
+ else: # regular method follows
+ player_node.name = str(player_node.name) + "_dead" # avoids name collision when instancing another player scene
+
+ print("before free")
+ player_node.queue_free()
+ print("after free")
+ # respawn queue applies ot both implementations of death
spawn_queue[pid] = uptime + respawn_delay
+
+ if pid == get_tree().multiplayer.get_unique_id():
+ update_hud()
func create_player(pid: int, is_local:= false, respawn:= false) -> void:
+ if destroy_free_player_crash_workaround:
+ var player_node
+ if $Players.has_node(str(pid)): # if this is a respawn
+ player_node = $Players.get_node(str(pid))
+ print("Setting player ", pid, " as ALIVE")
+ player_node.set_dead(false)
+
+ var spawnpoint = $Map/SpawnPoints.get_children()[randi() % len($Map/SpawnPoints.get_children())]
+ player_node.global_transform = spawnpoint.global_transform
+ player_list.players[pid].health = 100
+ push_local_player_info()
+
+ #focus = GameFocus.GAME # make sure the player has no menu displayed when they respawn
+ return # avoid running the rest of this function
+ else:
+ pass # if this is not a respawn, let it carry on
var new_player
new_player = player_scene.instantiate()
@@ -261,6 +306,8 @@ func create_player(pid: int, is_local:= false, respawn:= false) -> void:
# new_player.get_node("Head/Camera").current = false
# local_player.get_node("Head/Camera").current = true
#new_player.rpc(&'set_color', player_list.get(pid).color)
+
+ #update_hud()
func start_dedicated_server(): # start server without creating a local player
role = MultiplayerRole.DEDICATED_SERVER
@@ -326,6 +373,7 @@ func _server_disconnected() -> void:
func _ready() -> void:
+ hud.hide() # start in the menu
#peer.compression_mode = NetworkedMultiplayerENet.COMPRESS_ZSTD
print("Commandline arguments: ", OS.get_cmdline_args())