feat: implement core movement, combat, weapon management, and networking systems

This commit is contained in:
DottsGit
2026-06-07 00:22:21 -04:00
parent 064bc90f53
commit 7f54792ad9
12 changed files with 191 additions and 42 deletions
+24 -22
View File
@@ -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)
+9 -2
View File
@@ -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
+23 -8
View File
@@ -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():
+1 -1
View File
@@ -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", {})
+7
View File
@@ -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:
+39 -2
View File
@@ -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
# ========================================== # ==========================================
+67
View File
@@ -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)
+1
View File
@@ -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)
+1 -1
View File
@@ -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()
+1 -1
View File
@@ -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
+4 -4
View File
@@ -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
+14 -1
View File
@@ -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)