feat: implement core movement, combat, weapon management, and networking systems
This commit is contained in:
@@ -170,15 +170,16 @@ func update_state(state: String, speed: float, is_crouching: bool = false) -> vo
|
|||||||
current_state = "crouch"
|
current_state = "crouch"
|
||||||
|
|
||||||
var is_holding_weapon: bool = false
|
var is_holding_weapon: bool = false
|
||||||
|
var held_weapon_name: String = ""
|
||||||
|
|
||||||
func set_weapon(script_path: String) -> void:
|
func set_weapon(script_path: String) -> void:
|
||||||
# Clear existing weapons from root_pivot
|
for child in lower_arm_r_pivot.get_children():
|
||||||
for child in root_pivot.get_children():
|
|
||||||
if child.has_meta("is_third_person_weapon"):
|
if child.has_meta("is_third_person_weapon"):
|
||||||
child.queue_free()
|
child.queue_free()
|
||||||
|
|
||||||
if script_path == "":
|
if script_path == "":
|
||||||
is_holding_weapon = false
|
is_holding_weapon = false
|
||||||
|
held_weapon_name = ""
|
||||||
return
|
return
|
||||||
|
|
||||||
is_holding_weapon = true
|
is_holding_weapon = true
|
||||||
@@ -190,6 +191,7 @@ func set_weapon(script_path: String) -> void:
|
|||||||
var w = script.new()
|
var w = script.new()
|
||||||
w.name = "ThirdPersonWeapon"
|
w.name = "ThirdPersonWeapon"
|
||||||
w.set_meta("is_third_person_weapon", true)
|
w.set_meta("is_third_person_weapon", true)
|
||||||
|
held_weapon_name = w.get("weapon_name") if "weapon_name" in w else ""
|
||||||
|
|
||||||
# Connect to ready so we can override the weapon's own _ready() calls
|
# Connect to ready so we can override the weapon's own _ready() calls
|
||||||
w.ready.connect(func():
|
w.ready.connect(func():
|
||||||
@@ -198,13 +200,13 @@ func set_weapon(script_path: String) -> void:
|
|||||||
if shadows_only:
|
if shadows_only:
|
||||||
_set_shadows_recursive(w)
|
_set_shadows_recursive(w)
|
||||||
|
|
||||||
# Force position after the weapon's _build_model() sets it for 1st person
|
# Attach to hand (which is at y=-0.35 from elbow)
|
||||||
w.position = Vector3(-0.15, 1.0, 0.4)
|
w.position = Vector3(0.0, -0.35, 0.0)
|
||||||
w.rotation_degrees = Vector3(0, 180, 0)
|
w.rotation_degrees = Vector3(-90, 0, 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Attach to root_pivot so it stays steady and points perfectly forward
|
# Attach to right arm instead of root pivot
|
||||||
root_pivot.add_child(w)
|
lower_arm_r_pivot.add_child(w)
|
||||||
|
|
||||||
func _set_shadows_recursive(node: Node) -> void:
|
func _set_shadows_recursive(node: Node) -> void:
|
||||||
if node is GeometryInstance3D:
|
if node is GeometryInstance3D:
|
||||||
@@ -265,7 +267,7 @@ func _process(delta: float) -> void:
|
|||||||
t_calf_r_rot.x = 0.05
|
t_calf_r_rot.x = 0.05
|
||||||
|
|
||||||
"crouch":
|
"crouch":
|
||||||
t_root_pos.y = -0.5
|
t_root_pos.y = -0.3
|
||||||
# Thighs forward, calves back
|
# Thighs forward, calves back
|
||||||
t_thigh_l_rot.x = -1.0
|
t_thigh_l_rot.x = -1.0
|
||||||
t_thigh_r_rot.x = -1.0
|
t_thigh_r_rot.x = -1.0
|
||||||
@@ -279,12 +281,12 @@ func _process(delta: float) -> void:
|
|||||||
|
|
||||||
"slide":
|
"slide":
|
||||||
t_root_pos.y = -0.6
|
t_root_pos.y = -0.6
|
||||||
t_root_rot.x = deg_to_rad(-45) # Lean back
|
t_root_rot.x = deg_to_rad(-10) # Almost upright
|
||||||
# Legs forward
|
# Legs forward
|
||||||
t_thigh_l_rot.x = deg_to_rad(-60)
|
t_thigh_l_rot.x = deg_to_rad(-80)
|
||||||
t_thigh_r_rot.x = deg_to_rad(-30)
|
t_thigh_r_rot.x = deg_to_rad(-80)
|
||||||
t_calf_l_rot.x = 0.1
|
t_calf_l_rot.x = 0.0
|
||||||
t_calf_r_rot.x = 0.1
|
t_calf_r_rot.x = 0.0
|
||||||
# Arms back
|
# Arms back
|
||||||
t_upper_arm_l_rot.x = deg_to_rad(60)
|
t_upper_arm_l_rot.x = deg_to_rad(60)
|
||||||
t_upper_arm_r_rot.x = deg_to_rad(60)
|
t_upper_arm_r_rot.x = deg_to_rad(60)
|
||||||
@@ -342,7 +344,7 @@ func _process(delta: float) -> void:
|
|||||||
t_calf_r_rot.x = deg_to_rad(10)
|
t_calf_r_rot.x = deg_to_rad(10)
|
||||||
|
|
||||||
"dash":
|
"dash":
|
||||||
t_root_rot.x = deg_to_rad(-70)
|
t_root_rot.x = deg_to_rad(45)
|
||||||
t_upper_arm_l_rot.x = deg_to_rad(-170)
|
t_upper_arm_l_rot.x = deg_to_rad(-170)
|
||||||
t_lower_arm_l_rot.x = 0.0
|
t_lower_arm_l_rot.x = 0.0
|
||||||
t_upper_arm_r_rot.x = deg_to_rad(-170)
|
t_upper_arm_r_rot.x = deg_to_rad(-170)
|
||||||
@@ -365,17 +367,17 @@ func _process(delta: float) -> void:
|
|||||||
t_thigh_r_rot.x = deg_to_rad(10)
|
t_thigh_r_rot.x = deg_to_rad(10)
|
||||||
t_calf_r_rot.x = 0.0
|
t_calf_r_rot.x = 0.0
|
||||||
|
|
||||||
# Override right arm (and left) if holding a weapon
|
|
||||||
if is_holding_weapon and current_state != "death":
|
if is_holding_weapon and current_state != "death":
|
||||||
# Pose the right arm to look like it's holding the weapon handle
|
# Pose the right arm to look like it's holding the weapon handle straight out
|
||||||
t_upper_arm_r_rot.x = -0.6
|
t_upper_arm_r_rot.x = -1.2
|
||||||
t_upper_arm_r_rot.z = 0.15
|
t_upper_arm_r_rot.z = 0.15
|
||||||
t_lower_arm_r_rot.x = -1.0
|
t_lower_arm_r_rot.x = -0.2
|
||||||
|
|
||||||
# Pose the left arm to look like it's holding the foregrip
|
# If it's a two handed weapon, the left arm reaches over
|
||||||
t_upper_arm_l_rot.x = -0.5
|
if held_weapon_name != "Knife":
|
||||||
t_upper_arm_l_rot.z = -0.15
|
t_upper_arm_l_rot.x = -1.2
|
||||||
t_lower_arm_l_rot.x = -1.1
|
t_upper_arm_l_rot.z = -0.15
|
||||||
|
t_lower_arm_l_rot.x = -0.3
|
||||||
|
|
||||||
var lerp_speed = 40.0 * delta
|
var lerp_speed = 40.0 * delta
|
||||||
root_pivot.position = root_pivot.position.lerp(t_root_pos, lerp_speed)
|
root_pivot.position = root_pivot.position.lerp(t_root_pos, lerp_speed)
|
||||||
|
|||||||
@@ -332,7 +332,7 @@ func _spawn_player(pid: int) -> CharacterBody3D:
|
|||||||
var col_shape := CollisionShape3D.new()
|
var col_shape := CollisionShape3D.new()
|
||||||
col_shape.name = "CollisionShape3D"
|
col_shape.name = "CollisionShape3D"
|
||||||
var capsule := CapsuleShape3D.new()
|
var capsule := CapsuleShape3D.new()
|
||||||
capsule.radius = 0.4
|
capsule.radius = 0.6
|
||||||
capsule.height = 1.8
|
capsule.height = 1.8
|
||||||
col_shape.shape = capsule
|
col_shape.shape = capsule
|
||||||
player.add_child(col_shape)
|
player.add_child(col_shape)
|
||||||
@@ -345,7 +345,11 @@ func _spawn_player(pid: int) -> CharacterBody3D:
|
|||||||
# Humanoid Model for Player (Shadows only locally, visible to others)
|
# Humanoid Model for Player (Shadows only locally, visible to others)
|
||||||
var humanoid = load("res://characters/humanoid_model.gd").new()
|
var humanoid = load("res://characters/humanoid_model.gd").new()
|
||||||
humanoid.name = "HumanoidModel"
|
humanoid.name = "HumanoidModel"
|
||||||
humanoid.color = Color(0.2, 0.4, 0.8) # Blueish for player
|
var pcolor = Color(0.2, 0.4, 0.8)
|
||||||
|
var nm = get_node_or_null("/root/NetworkManager")
|
||||||
|
if nm and nm.player_stats.has(pid):
|
||||||
|
pcolor = Color(nm.player_stats[pid].get("color", "3366cc"))
|
||||||
|
humanoid.color = pcolor
|
||||||
humanoid.shadows_only = (pid == multiplayer.get_unique_id())
|
humanoid.shadows_only = (pid == multiplayer.get_unique_id())
|
||||||
humanoid.position = Vector3(0, -0.9, 0) # Offset from center to feet
|
humanoid.position = Vector3(0, -0.9, 0) # Offset from center to feet
|
||||||
player.add_child(humanoid)
|
player.add_child(humanoid)
|
||||||
@@ -440,6 +444,9 @@ func _spawn_player(pid: int) -> CharacterBody3D:
|
|||||||
if cv:
|
if cv:
|
||||||
cv.visible = false
|
cv.visible = false
|
||||||
|
|
||||||
|
if is_local and nm and not nm.multiplayer.is_server() and nm.multiplayer.has_multiplayer_peer() and not nm.multiplayer.multiplayer_peer is OfflineMultiplayerPeer:
|
||||||
|
nm.client_ready_for_spawn.rpc_id(1)
|
||||||
|
|
||||||
return player
|
return player
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ signal player_disconnected(id: int)
|
|||||||
signal connection_failed
|
signal connection_failed
|
||||||
signal connection_succeeded
|
signal connection_succeeded
|
||||||
signal match_state_updated
|
signal match_state_updated
|
||||||
|
signal match_ended
|
||||||
signal stats_updated
|
signal stats_updated
|
||||||
signal killfeed_event(victim: String, killer: String, weapon: String, v_color: String, k_color: String)
|
signal killfeed_event(victim: String, killer: String, weapon: String, v_color: String, k_color: String)
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ var player_stats: Dictionary = {}
|
|||||||
|
|
||||||
var match_active: bool = false
|
var match_active: bool = false
|
||||||
var match_time_remaining: float = 0.0
|
var match_time_remaining: float = 0.0
|
||||||
|
var match_duration_setting: float = 300.0
|
||||||
var current_gamemode: String = "Deathmatch"
|
var current_gamemode: String = "Deathmatch"
|
||||||
var current_scene_path: String = ""
|
var current_scene_path: String = ""
|
||||||
|
|
||||||
@@ -94,9 +96,6 @@ func _on_peer_connected(id: int) -> void:
|
|||||||
print("Player connected: ", id)
|
print("Player connected: ", id)
|
||||||
if not connected_players.has(id):
|
if not connected_players.has(id):
|
||||||
connected_players.append(id)
|
connected_players.append(id)
|
||||||
|
|
||||||
# If server, we wait for the client to register themselves via RPC
|
|
||||||
pass
|
|
||||||
|
|
||||||
func _on_peer_disconnected(id: int) -> void:
|
func _on_peer_disconnected(id: int) -> void:
|
||||||
print("Player disconnected: ", id)
|
print("Player disconnected: ", id)
|
||||||
@@ -130,15 +129,24 @@ func register_player(username: String) -> void:
|
|||||||
if multiplayer.is_server():
|
if multiplayer.is_server():
|
||||||
var sender_id = multiplayer.get_remote_sender_id()
|
var sender_id = multiplayer.get_remote_sender_id()
|
||||||
_init_player_stats(sender_id, username)
|
_init_player_stats(sender_id, username)
|
||||||
player_connected.emit(sender_id)
|
|
||||||
|
|
||||||
# Sync everyone's stats
|
|
||||||
rpc("sync_player_stats", player_stats)
|
|
||||||
|
|
||||||
# If a match is active, force the late-joiner to load the level
|
# If a match is active, force the late-joiner to load the level
|
||||||
if match_active:
|
if match_active:
|
||||||
rpc_id(sender_id, "rpc_load_level", current_scene_path)
|
rpc_id(sender_id, "rpc_load_level", current_scene_path)
|
||||||
rpc_id(sender_id, "sync_match_state", match_active, match_time_remaining, current_gamemode)
|
rpc_id(sender_id, "sync_match_state", match_active, match_time_remaining, current_gamemode)
|
||||||
|
else:
|
||||||
|
player_connected.emit(sender_id)
|
||||||
|
|
||||||
|
# Sync everyone's stats
|
||||||
|
rpc("sync_player_stats", player_stats)
|
||||||
|
|
||||||
|
@rpc("any_peer", "call_remote", "reliable")
|
||||||
|
func client_ready_for_spawn() -> void:
|
||||||
|
if multiplayer.is_server():
|
||||||
|
var sender_id = multiplayer.get_remote_sender_id()
|
||||||
|
if sender_id == 0:
|
||||||
|
sender_id = 1
|
||||||
|
player_connected.emit(sender_id)
|
||||||
|
|
||||||
@rpc("any_peer", "call_local", "reliable")
|
@rpc("any_peer", "call_local", "reliable")
|
||||||
func update_username(new_name: String) -> void:
|
func update_username(new_name: String) -> void:
|
||||||
@@ -170,7 +178,7 @@ func rpc_load_level(scene_path: String) -> void:
|
|||||||
current_scene_path = scene_path
|
current_scene_path = scene_path
|
||||||
if multiplayer.is_server():
|
if multiplayer.is_server():
|
||||||
match_active = true
|
match_active = true
|
||||||
match_time_remaining = 300.0 # 5 minutes
|
match_time_remaining = match_duration_setting
|
||||||
rpc("sync_match_state", match_active, match_time_remaining, current_gamemode)
|
rpc("sync_match_state", match_active, match_time_remaining, current_gamemode)
|
||||||
get_tree().change_scene_to_file(scene_path)
|
get_tree().change_scene_to_file(scene_path)
|
||||||
|
|
||||||
@@ -181,9 +189,16 @@ func _process(delta: float) -> void:
|
|||||||
if match_time_remaining <= 0:
|
if match_time_remaining <= 0:
|
||||||
match_time_remaining = 0
|
match_time_remaining = 0
|
||||||
match_active = false
|
match_active = false
|
||||||
|
if multiplayer.is_server() or multiplayer.multiplayer_peer is OfflineMultiplayerPeer:
|
||||||
|
rpc("rpc_end_match")
|
||||||
# We emit this so HUDs can update their timers smoothly
|
# We emit this so HUDs can update their timers smoothly
|
||||||
match_state_updated.emit()
|
match_state_updated.emit()
|
||||||
|
|
||||||
|
@rpc("authority", "call_local", "reliable")
|
||||||
|
func rpc_end_match() -> void:
|
||||||
|
match_active = false
|
||||||
|
match_ended.emit()
|
||||||
|
|
||||||
# --- PING SYSTEM ---
|
# --- PING SYSTEM ---
|
||||||
func _on_ping_timer() -> void:
|
func _on_ping_timer() -> void:
|
||||||
if multiplayer.is_server():
|
if multiplayer.is_server():
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ func _try_start_grapple() -> void:
|
|||||||
grapple_length = origin.distance_to(grapple_point)
|
grapple_length = origin.distance_to(grapple_point)
|
||||||
|
|
||||||
# Travel at 45 m/s
|
# Travel at 45 m/s
|
||||||
grapple_travel_time = grapple_length / 45
|
grapple_travel_time = 0.0
|
||||||
grapple_shoot_time = 0.0
|
grapple_shoot_time = 0.0
|
||||||
is_grapple_shooting = true
|
is_grapple_shooting = true
|
||||||
movement_event.emit("grapple_shoot", {})
|
movement_event.emit("grapple_shoot", {})
|
||||||
|
|||||||
@@ -712,6 +712,8 @@ func _physics_process(_delta: float) -> void:
|
|||||||
# Write synced state for remote peers
|
# Write synced state for remote peers
|
||||||
synced_movement_state = sm.current_state
|
synced_movement_state = sm.current_state
|
||||||
synced_movement_speed = Vector2(velocity.x, velocity.z).length()
|
synced_movement_speed = Vector2(velocity.x, velocity.z).length()
|
||||||
|
if synced_movement_speed < 0.1:
|
||||||
|
synced_movement_speed = 0.0
|
||||||
synced_is_crouching = sm.input_crouch
|
synced_is_crouching = sm.input_crouch
|
||||||
|
|
||||||
|
|
||||||
@@ -887,6 +889,11 @@ func die(impulse: Vector3 = Vector3.ZERO) -> void:
|
|||||||
if _machine:
|
if _machine:
|
||||||
_machine.process_mode = Node.PROCESS_MODE_DISABLED
|
_machine.process_mode = Node.PROCESS_MODE_DISABLED
|
||||||
|
|
||||||
|
if camera:
|
||||||
|
camera.fov = 90.0
|
||||||
|
if head_pivot and head_pivot.has_method("set_weapon_fov"):
|
||||||
|
head_pivot.set_weapon_fov(-1.0)
|
||||||
|
|
||||||
# Hide old animated model
|
# Hide old animated model
|
||||||
var humanoid = get_node_or_null("HumanoidModel")
|
var humanoid = get_node_or_null("HumanoidModel")
|
||||||
if humanoid:
|
if humanoid:
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ func _ready() -> void:
|
|||||||
var nm = get_node_or_null("/root/NetworkManager")
|
var nm = get_node_or_null("/root/NetworkManager")
|
||||||
if nm:
|
if nm:
|
||||||
nm.connection_succeeded.connect(_on_connection_succeeded)
|
nm.connection_succeeded.connect(_on_connection_succeeded)
|
||||||
|
|
||||||
|
# Auto-open lobby if returning to main menu while still connected
|
||||||
|
if nm.multiplayer.has_multiplayer_peer() and not nm.multiplayer.multiplayer_peer is OfflineMultiplayerPeer:
|
||||||
|
call_deferred("_on_connection_succeeded")
|
||||||
|
call_deferred("_hide_ui_vbox")
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# BACKGROUND: 3D DIORAMA (Sliding Player)
|
# BACKGROUND: 3D DIORAMA (Sliding Player)
|
||||||
@@ -242,6 +247,31 @@ func _ready() -> void:
|
|||||||
)
|
)
|
||||||
hl_left.add_child(_gamemode_opt)
|
hl_left.add_child(_gamemode_opt)
|
||||||
|
|
||||||
|
var time_label = Label.new()
|
||||||
|
time_label.text = "Match Duration"
|
||||||
|
time_label.add_theme_font_size_override("font_size", 32)
|
||||||
|
hl_left.add_child(time_label)
|
||||||
|
|
||||||
|
var _time_opt = OptionButton.new()
|
||||||
|
_time_opt.add_theme_font_size_override("font_size", 24)
|
||||||
|
_time_opt.add_item("1 Minute", 0)
|
||||||
|
_time_opt.add_item("3 Minutes", 1)
|
||||||
|
_time_opt.add_item("5 Minutes", 2)
|
||||||
|
_time_opt.add_item("10 Minutes", 3)
|
||||||
|
_time_opt.add_item("15 Minutes", 4)
|
||||||
|
_time_opt.select(2) # Default 5 Minutes
|
||||||
|
_time_opt.add_to_group("host_only_ui")
|
||||||
|
_time_opt.item_selected.connect(func(index: int):
|
||||||
|
var net_man = get_node_or_null("/root/NetworkManager")
|
||||||
|
if net_man:
|
||||||
|
if index == 0: net_man.match_duration_setting = 60.0
|
||||||
|
elif index == 1: net_man.match_duration_setting = 180.0
|
||||||
|
elif index == 2: net_man.match_duration_setting = 300.0
|
||||||
|
elif index == 3: net_man.match_duration_setting = 600.0
|
||||||
|
elif index == 4: net_man.match_duration_setting = 900.0
|
||||||
|
)
|
||||||
|
hl_left.add_child(_time_opt)
|
||||||
|
|
||||||
var hl_spacer = Control.new()
|
var hl_spacer = Control.new()
|
||||||
hl_spacer.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
hl_spacer.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
hl_left.add_child(hl_spacer)
|
hl_left.add_child(hl_spacer)
|
||||||
@@ -410,9 +440,12 @@ func _on_network_player_disconnected(_id: int) -> void:
|
|||||||
_update_lobby_players()
|
_update_lobby_players()
|
||||||
|
|
||||||
func _update_lobby_players() -> void:
|
func _update_lobby_players() -> void:
|
||||||
if not _host_lobby_players_list: return
|
if not is_inside_tree() or not _host_lobby_players_list: return
|
||||||
|
|
||||||
|
var nm = get_node_or_null("/root/NetworkManager")
|
||||||
|
if not nm: return
|
||||||
|
|
||||||
_host_lobby_players_list.clear()
|
_host_lobby_players_list.clear()
|
||||||
var nm = get_node("/root/NetworkManager")
|
|
||||||
|
|
||||||
for pid in nm.player_stats.keys():
|
for pid in nm.player_stats.keys():
|
||||||
var p_name = nm.player_stats[pid].get("username", "Unknown")
|
var p_name = nm.player_stats[pid].get("username", "Unknown")
|
||||||
@@ -438,6 +471,10 @@ func _on_settings_pressed() -> void:
|
|||||||
func _on_exit_pressed() -> void:
|
func _on_exit_pressed() -> void:
|
||||||
get_tree().quit()
|
get_tree().quit()
|
||||||
|
|
||||||
|
func _hide_ui_vbox() -> void:
|
||||||
|
if _ui_vbox:
|
||||||
|
_ui_vbox.hide()
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# 3D DIORAMA BUILDER
|
# 3D DIORAMA BUILDER
|
||||||
# ==========================================
|
# ==========================================
|
||||||
|
|||||||
@@ -102,9 +102,13 @@ func _ready() -> void:
|
|||||||
_nm.stats_updated.connect(_update_hud)
|
_nm.stats_updated.connect(_update_hud)
|
||||||
_nm.killfeed_event.connect(_on_killfeed_event)
|
_nm.killfeed_event.connect(_on_killfeed_event)
|
||||||
_nm.match_state_updated.connect(_update_timer)
|
_nm.match_state_updated.connect(_update_timer)
|
||||||
|
_nm.match_ended.connect(_on_match_ended)
|
||||||
_update_hud()
|
_update_hud()
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
|
if _nm and not _nm.match_active:
|
||||||
|
return
|
||||||
|
|
||||||
if Input.is_action_just_pressed("scoreboard"):
|
if Input.is_action_just_pressed("scoreboard"):
|
||||||
_scoreboard_panel.show()
|
_scoreboard_panel.show()
|
||||||
_update_hud()
|
_update_hud()
|
||||||
@@ -212,3 +216,66 @@ func _on_killfeed_event(victim: String, killer: String, weapon: String, v_color:
|
|||||||
t.tween_interval(4.0)
|
t.tween_interval(4.0)
|
||||||
t.tween_property(l, "modulate:a", 0.0, 1.0)
|
t.tween_property(l, "modulate:a", 0.0, 1.0)
|
||||||
t.tween_callback(l.queue_free)
|
t.tween_callback(l.queue_free)
|
||||||
|
|
||||||
|
func _on_match_ended() -> void:
|
||||||
|
# Hide top HUD and killfeed
|
||||||
|
var top_margin = get_child(0)
|
||||||
|
if top_margin and top_margin is MarginContainer:
|
||||||
|
top_margin.hide()
|
||||||
|
var kf_margin = get_child(1)
|
||||||
|
if kf_margin and kf_margin is MarginContainer:
|
||||||
|
kf_margin.hide()
|
||||||
|
|
||||||
|
_scoreboard_panel.show()
|
||||||
|
_update_hud()
|
||||||
|
|
||||||
|
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||||
|
|
||||||
|
# Disable player controls
|
||||||
|
var player = get_parent()
|
||||||
|
if player:
|
||||||
|
var machine = player.get_node_or_null("MovementStateMachine")
|
||||||
|
if machine:
|
||||||
|
machine.process_mode = Node.PROCESS_MODE_DISABLED
|
||||||
|
var cam = player.get("camera")
|
||||||
|
if cam:
|
||||||
|
var wman = cam.get_node_or_null("WeaponManager")
|
||||||
|
if wman:
|
||||||
|
wman.process_mode = Node.PROCESS_MODE_DISABLED
|
||||||
|
|
||||||
|
var end_vbox = VBoxContainer.new()
|
||||||
|
end_vbox.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||||
|
|
||||||
|
var end_label = Label.new()
|
||||||
|
end_label.text = "MATCH OVER"
|
||||||
|
end_label.add_theme_font_size_override("font_size", 72)
|
||||||
|
end_label.add_theme_color_override("font_color", Color(1.0, 0.2, 0.2))
|
||||||
|
end_label.add_theme_color_override("font_outline_color", Color.BLACK)
|
||||||
|
end_label.add_theme_constant_override("outline_size", 16)
|
||||||
|
end_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
end_vbox.add_child(end_label)
|
||||||
|
|
||||||
|
var spacer = Control.new()
|
||||||
|
spacer.custom_minimum_size = Vector2(0, 650) # 600 for scoreboard + 50 padding
|
||||||
|
end_vbox.add_child(spacer)
|
||||||
|
|
||||||
|
var btn_margin = MarginContainer.new()
|
||||||
|
btn_margin.add_theme_constant_override("margin_left", 100)
|
||||||
|
btn_margin.add_theme_constant_override("margin_right", 100)
|
||||||
|
|
||||||
|
var lobby_btn = Button.new()
|
||||||
|
lobby_btn.text = "Return to Lobby"
|
||||||
|
lobby_btn.add_theme_font_size_override("font_size", 32)
|
||||||
|
lobby_btn.pressed.connect(func():
|
||||||
|
get_tree().change_scene_to_file("res://ui/main_menu/main_menu.tscn")
|
||||||
|
)
|
||||||
|
btn_margin.add_child(lobby_btn)
|
||||||
|
end_vbox.add_child(btn_margin)
|
||||||
|
|
||||||
|
var kf_margin2 = get_node_or_null("MarginContainer2")
|
||||||
|
if not kf_margin2:
|
||||||
|
var center = CenterContainer.new()
|
||||||
|
center.name = "MarginContainer2"
|
||||||
|
center.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
|
center.add_child(end_vbox)
|
||||||
|
add_child(center)
|
||||||
|
|||||||
@@ -262,6 +262,7 @@ func _build_ui() -> void:
|
|||||||
|
|
||||||
var user_input = LineEdit.new()
|
var user_input = LineEdit.new()
|
||||||
user_input.text = SettingsManager.username
|
user_input.text = SettingsManager.username
|
||||||
|
user_input.max_length = 16
|
||||||
user_input.custom_minimum_size = Vector2(200, 0)
|
user_input.custom_minimum_size = Vector2(200, 0)
|
||||||
user_hbox.add_child(user_input)
|
user_hbox.add_child(user_input)
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ func _on_hit(result: Dictionary) -> void:
|
|||||||
if not result.collider.has_method("take_damage"):
|
if not result.collider.has_method("take_damage"):
|
||||||
if result.collider is StaticBody3D or result.collider is CSGShape3D:
|
if result.collider is StaticBody3D or result.collider is CSGShape3D:
|
||||||
ImpactSpawner.spawn(get_tree(), "crater", result.position, result.normal, explosion_radius * 1.5)
|
ImpactSpawner.spawn(get_tree(), "crater", result.position, result.normal, explosion_radius * 1.5)
|
||||||
elif owner_player and owner_player.multiplayer.is_server():
|
elif owner_player and owner_player.is_multiplayer_authority():
|
||||||
result.collider.take_damage(damage, result.position, owner_player)
|
result.collider.take_damage(damage, result.position, owner_player)
|
||||||
_explode(result.position)
|
_explode(result.position)
|
||||||
destroy()
|
destroy()
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func _on_hit(result: Dictionary) -> void:
|
|||||||
final_damage = lerpf(damage, min_damage, falloff_factor)
|
final_damage = lerpf(damage, min_damage, falloff_factor)
|
||||||
|
|
||||||
if result.collider.has_method("take_damage"):
|
if result.collider.has_method("take_damage"):
|
||||||
if owner_player and owner_player.multiplayer.is_server():
|
if owner_player and owner_player.is_multiplayer_authority():
|
||||||
result.collider.take_damage(final_damage, result.position, owner_player)
|
result.collider.take_damage(final_damage, result.position, owner_player)
|
||||||
elif result.collider is StaticBody3D or result.collider is CSGShape3D:
|
elif result.collider is StaticBody3D or result.collider is CSGShape3D:
|
||||||
var size = 0.4 if impact_type == "plasma" else 0.15
|
var size = 0.4 if impact_type == "plasma" else 0.15
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ func _init() -> void:
|
|||||||
fire_rate = 1.0 # 1 click fires all 8
|
fire_rate = 1.0 # 1 click fires all 8
|
||||||
max_ammo = 4 # 4 swarms
|
max_ammo = 4 # 4 swarms
|
||||||
reload_time = 3.0
|
reload_time = 3.0
|
||||||
base_damage = 35.0 # Per mini rocket
|
base_damage = 50.0 # Per mini rocket
|
||||||
min_damage = 20.0
|
min_damage = 35.0
|
||||||
falloff_start = 0.0
|
falloff_start = 0.0
|
||||||
max_range = 300.0
|
max_range = 300.0
|
||||||
automatic = false
|
automatic = false
|
||||||
@@ -145,7 +145,7 @@ func _fire() -> void:
|
|||||||
# They shoot mostly forward, but fan out horizontally, and slightly arch upwards radially
|
# They shoot mostly forward, but fan out horizontally, and slightly arch upwards radially
|
||||||
var angle = lerpf(-PI/3.0, PI/3.0, fraction) # -60 deg to +60 deg fan
|
var angle = lerpf(-PI/3.0, PI/3.0, fraction) # -60 deg to +60 deg fan
|
||||||
var x_dir = sin(angle) * 0.6
|
var x_dir = sin(angle) * 0.6
|
||||||
var y_dir = cos(angle) * 0.8 # Reduced upward angle so it's not mostly straight up
|
var y_dir = cos(angle) * 1.5 # Adjusted upward angle so it launches a little more "up"
|
||||||
|
|
||||||
var fire_dir = (base_dir + right * x_dir + up * y_dir).normalized()
|
var fire_dir = (base_dir + right * x_dir + up * y_dir).normalized()
|
||||||
_spawn_custom_projectile(proj_origin, fire_dir)
|
_spawn_custom_projectile(proj_origin, fire_dir)
|
||||||
@@ -179,7 +179,7 @@ func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
|
|||||||
|
|
||||||
# Logic
|
# Logic
|
||||||
proj.weapon_source = self
|
proj.weapon_source = self
|
||||||
proj.turn_speed = 6.5 # Slower turn radius so they sweep nicely
|
proj.turn_speed = 12.0 # Faster turn radius so they can hit moving targets better
|
||||||
proj.position = origin # Start at origin to spread outward around camera
|
proj.position = origin # Start at origin to spread outward around camera
|
||||||
proj.logical_source = origin
|
proj.logical_source = origin
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ func _build_loadout() -> void:
|
|||||||
# Clear existing weapons
|
# Clear existing weapons
|
||||||
for w in weapons.values():
|
for w in weapons.values():
|
||||||
if is_instance_valid(w):
|
if is_instance_valid(w):
|
||||||
|
if w.has_method("unequip"):
|
||||||
|
w.unequip()
|
||||||
w.queue_free()
|
w.queue_free()
|
||||||
weapons.clear()
|
weapons.clear()
|
||||||
|
|
||||||
@@ -102,6 +104,8 @@ func _build_loadout() -> void:
|
|||||||
func _build_remote_loadout(p1: String, p2: String, sp: String, melee: String) -> void:
|
func _build_remote_loadout(p1: String, p2: String, sp: String, melee: String) -> void:
|
||||||
for w in weapons.values():
|
for w in weapons.values():
|
||||||
if is_instance_valid(w):
|
if is_instance_valid(w):
|
||||||
|
if w.has_method("unequip"):
|
||||||
|
w.unequip()
|
||||||
w.queue_free()
|
w.queue_free()
|
||||||
weapons.clear()
|
weapons.clear()
|
||||||
|
|
||||||
@@ -161,7 +165,16 @@ func _add_procedural_arms(weapon: Node3D) -> void:
|
|||||||
# Attach to weapon instead of model_root to avoid arms spinning on reload
|
# Attach to weapon instead of model_root to avoid arms spinning on reload
|
||||||
|
|
||||||
var mat = StandardMaterial3D.new()
|
var mat = StandardMaterial3D.new()
|
||||||
mat.albedo_color = Color(0.2, 0.4, 0.8) # Player color
|
var pcolor = Color(0.2, 0.4, 0.8)
|
||||||
|
var nm = get_node_or_null("/root/NetworkManager")
|
||||||
|
if nm and nm.multiplayer.has_multiplayer_peer() and not nm.multiplayer.multiplayer_peer is OfflineMultiplayerPeer:
|
||||||
|
var my_id = nm.multiplayer.get_unique_id()
|
||||||
|
if nm.player_stats.has(my_id):
|
||||||
|
pcolor = Color(nm.player_stats[my_id].get("color", "3366cc"))
|
||||||
|
elif nm and nm.player_stats.has(1):
|
||||||
|
pcolor = Color(nm.player_stats[1].get("color", "3366cc"))
|
||||||
|
|
||||||
|
mat.albedo_color = pcolor
|
||||||
mat.roughness = 0.8
|
mat.roughness = 0.8
|
||||||
|
|
||||||
# Right Arm (Main Grip)
|
# Right Arm (Main Grip)
|
||||||
|
|||||||
Reference in New Issue
Block a user