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"
|
||||
|
||||
var is_holding_weapon: bool = false
|
||||
var held_weapon_name: String = ""
|
||||
|
||||
func set_weapon(script_path: String) -> void:
|
||||
# Clear existing weapons from root_pivot
|
||||
for child in root_pivot.get_children():
|
||||
for child in lower_arm_r_pivot.get_children():
|
||||
if child.has_meta("is_third_person_weapon"):
|
||||
child.queue_free()
|
||||
|
||||
if script_path == "":
|
||||
is_holding_weapon = false
|
||||
held_weapon_name = ""
|
||||
return
|
||||
|
||||
is_holding_weapon = true
|
||||
@@ -190,6 +191,7 @@ func set_weapon(script_path: String) -> void:
|
||||
var w = script.new()
|
||||
w.name = "ThirdPersonWeapon"
|
||||
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
|
||||
w.ready.connect(func():
|
||||
@@ -198,13 +200,13 @@ func set_weapon(script_path: String) -> void:
|
||||
if shadows_only:
|
||||
_set_shadows_recursive(w)
|
||||
|
||||
# Force position after the weapon's _build_model() sets it for 1st person
|
||||
w.position = Vector3(-0.15, 1.0, 0.4)
|
||||
w.rotation_degrees = Vector3(0, 180, 0)
|
||||
# Attach to hand (which is at y=-0.35 from elbow)
|
||||
w.position = Vector3(0.0, -0.35, 0.0)
|
||||
w.rotation_degrees = Vector3(-90, 0, 0)
|
||||
)
|
||||
|
||||
# Attach to root_pivot so it stays steady and points perfectly forward
|
||||
root_pivot.add_child(w)
|
||||
# Attach to right arm instead of root pivot
|
||||
lower_arm_r_pivot.add_child(w)
|
||||
|
||||
func _set_shadows_recursive(node: Node) -> void:
|
||||
if node is GeometryInstance3D:
|
||||
@@ -265,7 +267,7 @@ func _process(delta: float) -> void:
|
||||
t_calf_r_rot.x = 0.05
|
||||
|
||||
"crouch":
|
||||
t_root_pos.y = -0.5
|
||||
t_root_pos.y = -0.3
|
||||
# Thighs forward, calves back
|
||||
t_thigh_l_rot.x = -1.0
|
||||
t_thigh_r_rot.x = -1.0
|
||||
@@ -279,12 +281,12 @@ func _process(delta: float) -> void:
|
||||
|
||||
"slide":
|
||||
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
|
||||
t_thigh_l_rot.x = deg_to_rad(-60)
|
||||
t_thigh_r_rot.x = deg_to_rad(-30)
|
||||
t_calf_l_rot.x = 0.1
|
||||
t_calf_r_rot.x = 0.1
|
||||
t_thigh_l_rot.x = deg_to_rad(-80)
|
||||
t_thigh_r_rot.x = deg_to_rad(-80)
|
||||
t_calf_l_rot.x = 0.0
|
||||
t_calf_r_rot.x = 0.0
|
||||
# Arms back
|
||||
t_upper_arm_l_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)
|
||||
|
||||
"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_lower_arm_l_rot.x = 0.0
|
||||
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_calf_r_rot.x = 0.0
|
||||
|
||||
# Override right arm (and left) if holding a weapon
|
||||
if is_holding_weapon and current_state != "death":
|
||||
# Pose the right arm to look like it's holding the weapon handle
|
||||
t_upper_arm_r_rot.x = -0.6
|
||||
# Pose the right arm to look like it's holding the weapon handle straight out
|
||||
t_upper_arm_r_rot.x = -1.2
|
||||
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
|
||||
t_upper_arm_l_rot.x = -0.5
|
||||
t_upper_arm_l_rot.z = -0.15
|
||||
t_lower_arm_l_rot.x = -1.1
|
||||
# If it's a two handed weapon, the left arm reaches over
|
||||
if held_weapon_name != "Knife":
|
||||
t_upper_arm_l_rot.x = -1.2
|
||||
t_upper_arm_l_rot.z = -0.15
|
||||
t_lower_arm_l_rot.x = -0.3
|
||||
|
||||
var lerp_speed = 40.0 * delta
|
||||
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()
|
||||
col_shape.name = "CollisionShape3D"
|
||||
var capsule := CapsuleShape3D.new()
|
||||
capsule.radius = 0.4
|
||||
capsule.radius = 0.6
|
||||
capsule.height = 1.8
|
||||
col_shape.shape = capsule
|
||||
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)
|
||||
var humanoid = load("res://characters/humanoid_model.gd").new()
|
||||
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.position = Vector3(0, -0.9, 0) # Offset from center to feet
|
||||
player.add_child(humanoid)
|
||||
@@ -440,6 +444,9 @@ func _spawn_player(pid: int) -> CharacterBody3D:
|
||||
if cv:
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ signal player_disconnected(id: int)
|
||||
signal connection_failed
|
||||
signal connection_succeeded
|
||||
signal match_state_updated
|
||||
signal match_ended
|
||||
signal stats_updated
|
||||
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_time_remaining: float = 0.0
|
||||
var match_duration_setting: float = 300.0
|
||||
var current_gamemode: String = "Deathmatch"
|
||||
var current_scene_path: String = ""
|
||||
|
||||
@@ -94,9 +96,6 @@ func _on_peer_connected(id: int) -> void:
|
||||
print("Player connected: ", id)
|
||||
if not connected_players.has(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:
|
||||
print("Player disconnected: ", id)
|
||||
@@ -130,15 +129,24 @@ func register_player(username: String) -> void:
|
||||
if multiplayer.is_server():
|
||||
var sender_id = multiplayer.get_remote_sender_id()
|
||||
_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 match_active:
|
||||
rpc_id(sender_id, "rpc_load_level", current_scene_path)
|
||||
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")
|
||||
func update_username(new_name: String) -> void:
|
||||
@@ -170,7 +178,7 @@ func rpc_load_level(scene_path: String) -> void:
|
||||
current_scene_path = scene_path
|
||||
if multiplayer.is_server():
|
||||
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)
|
||||
get_tree().change_scene_to_file(scene_path)
|
||||
|
||||
@@ -181,9 +189,16 @@ func _process(delta: float) -> void:
|
||||
if match_time_remaining <= 0:
|
||||
match_time_remaining = 0
|
||||
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
|
||||
match_state_updated.emit()
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func rpc_end_match() -> void:
|
||||
match_active = false
|
||||
match_ended.emit()
|
||||
|
||||
# --- PING SYSTEM ---
|
||||
func _on_ping_timer() -> void:
|
||||
if multiplayer.is_server():
|
||||
|
||||
@@ -163,7 +163,7 @@ func _try_start_grapple() -> void:
|
||||
grapple_length = origin.distance_to(grapple_point)
|
||||
|
||||
# Travel at 45 m/s
|
||||
grapple_travel_time = grapple_length / 45
|
||||
grapple_travel_time = 0.0
|
||||
grapple_shoot_time = 0.0
|
||||
is_grapple_shooting = true
|
||||
movement_event.emit("grapple_shoot", {})
|
||||
|
||||
@@ -712,6 +712,8 @@ func _physics_process(_delta: float) -> void:
|
||||
# Write synced state for remote peers
|
||||
synced_movement_state = sm.current_state
|
||||
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
|
||||
|
||||
|
||||
@@ -887,6 +889,11 @@ func die(impulse: Vector3 = Vector3.ZERO) -> void:
|
||||
if _machine:
|
||||
_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
|
||||
var humanoid = get_node_or_null("HumanoidModel")
|
||||
if humanoid:
|
||||
|
||||
@@ -13,6 +13,11 @@ func _ready() -> void:
|
||||
var nm = get_node_or_null("/root/NetworkManager")
|
||||
if nm:
|
||||
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)
|
||||
@@ -242,6 +247,31 @@ func _ready() -> void:
|
||||
)
|
||||
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()
|
||||
hl_spacer.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
hl_left.add_child(hl_spacer)
|
||||
@@ -410,9 +440,12 @@ func _on_network_player_disconnected(_id: int) -> void:
|
||||
_update_lobby_players()
|
||||
|
||||
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()
|
||||
var nm = get_node("/root/NetworkManager")
|
||||
|
||||
for pid in nm.player_stats.keys():
|
||||
var p_name = nm.player_stats[pid].get("username", "Unknown")
|
||||
@@ -438,6 +471,10 @@ func _on_settings_pressed() -> void:
|
||||
func _on_exit_pressed() -> void:
|
||||
get_tree().quit()
|
||||
|
||||
func _hide_ui_vbox() -> void:
|
||||
if _ui_vbox:
|
||||
_ui_vbox.hide()
|
||||
|
||||
# ==========================================
|
||||
# 3D DIORAMA BUILDER
|
||||
# ==========================================
|
||||
|
||||
@@ -102,9 +102,13 @@ func _ready() -> void:
|
||||
_nm.stats_updated.connect(_update_hud)
|
||||
_nm.killfeed_event.connect(_on_killfeed_event)
|
||||
_nm.match_state_updated.connect(_update_timer)
|
||||
_nm.match_ended.connect(_on_match_ended)
|
||||
_update_hud()
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if _nm and not _nm.match_active:
|
||||
return
|
||||
|
||||
if Input.is_action_just_pressed("scoreboard"):
|
||||
_scoreboard_panel.show()
|
||||
_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_property(l, "modulate:a", 0.0, 1.0)
|
||||
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()
|
||||
user_input.text = SettingsManager.username
|
||||
user_input.max_length = 16
|
||||
user_input.custom_minimum_size = Vector2(200, 0)
|
||||
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 result.collider is StaticBody3D or result.collider is CSGShape3D:
|
||||
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)
|
||||
_explode(result.position)
|
||||
destroy()
|
||||
|
||||
@@ -58,7 +58,7 @@ func _on_hit(result: Dictionary) -> void:
|
||||
final_damage = lerpf(damage, min_damage, falloff_factor)
|
||||
|
||||
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)
|
||||
elif result.collider is StaticBody3D or result.collider is CSGShape3D:
|
||||
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
|
||||
max_ammo = 4 # 4 swarms
|
||||
reload_time = 3.0
|
||||
base_damage = 35.0 # Per mini rocket
|
||||
min_damage = 20.0
|
||||
base_damage = 50.0 # Per mini rocket
|
||||
min_damage = 35.0
|
||||
falloff_start = 0.0
|
||||
max_range = 300.0
|
||||
automatic = false
|
||||
@@ -145,7 +145,7 @@ func _fire() -> void:
|
||||
# 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 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()
|
||||
_spawn_custom_projectile(proj_origin, fire_dir)
|
||||
@@ -179,7 +179,7 @@ func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
|
||||
|
||||
# Logic
|
||||
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.logical_source = origin
|
||||
|
||||
|
||||
@@ -75,6 +75,8 @@ func _build_loadout() -> void:
|
||||
# Clear existing weapons
|
||||
for w in weapons.values():
|
||||
if is_instance_valid(w):
|
||||
if w.has_method("unequip"):
|
||||
w.unequip()
|
||||
w.queue_free()
|
||||
weapons.clear()
|
||||
|
||||
@@ -102,6 +104,8 @@ func _build_loadout() -> void:
|
||||
func _build_remote_loadout(p1: String, p2: String, sp: String, melee: String) -> void:
|
||||
for w in weapons.values():
|
||||
if is_instance_valid(w):
|
||||
if w.has_method("unequip"):
|
||||
w.unequip()
|
||||
w.queue_free()
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
# Right Arm (Main Grip)
|
||||
|
||||
Reference in New Issue
Block a user