Multiplayer Weapon Buy System

Godot Version

4.2.2.stable.mono

Question

` Hey everyone, I tried to create a buy system for my multiplayer game. I created a buy_screen and instantiate it to Player scene. buy_screen has this script ;

extends Control

var weapon_cost
var weapon_id
var player

func _enter_tree():
	player = get_parent()

func _on_glock_button_pressed() -> void:
	weapon_cost = 800
	weapon_id = 1
	rpc("purchase",weapon_id,weapon_cost)
		

@rpc("any_peer","call_local")
func purchase(weapon_id,weapon_cost):
	player.request_purchase(weapon_id,weapon_cost)

and my Player scene has this script ;

extends CharacterBody3D
class_name Player

@onready var camera: Camera3D = $Camera3D
@onready var animation_player: AnimationPlayer = $AnimationPlayer
@onready var ray_cast_3d: RayCast3D = $Camera3D/RayCast3D
@onready var weapon_handler: Node3D = $Camera3D/WeaponHandler
@onready var buy_screen: Control = $BuyScreen


@export var health = 100
@export var team = 1
@export var money = 80000

var is_alive = true
var is_buy_screen_open = false
var primary_weapon: PackedScene


const SPEED = 10.0
const JUMP_VELOCITY = 10.0

var gravity: float = 20.0

func _enter_tree():
	set_multiplayer_authority(str(name).to_int())

func _ready() -> void:
	if not is_multiplayer_authority(): return
	
	Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
	camera.current = true

func _input(event: InputEvent) -> void:
		pass
		

func _unhandled_input(event: InputEvent) -> void:
	if not is_multiplayer_authority(): return
	
	if event is InputEventMouseMotion:
		rotate_y(-event.relative.x * .005)
		camera.rotate_x(-event.relative.y * .005)
		camera.rotation.x = clamp(camera.rotation.x, -PI/2, PI/2)
		
	if event.is_action_pressed("Buy"):
			toggle_buy_screen()
		
		
func _physics_process(delta: float) -> void:
	if not is_multiplayer_authority(): return
	
	# Add the gravity.
	if not is_on_floor():
		velocity.y -= gravity * delta

	# Handle jump.
	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
		velocity.y = JUMP_VELOCITY

	var input_dir := Input.get_vector("Left", "Right", "Forward", "Backward")
	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()
	
@rpc("any_peer")
func request_purchase(weapon_id, weapon_cost):
	if multiplayer.is_server():
		server_purchase_weapon(weapon_id, weapon_cost)
	else:
		rpc_id(1, "server_purchase_weapon", weapon_id, weapon_cost)
	
@rpc("any_peer") 
func take_damage(damage: int) -> void:
	health -= damage
	
	if health <= 0:
		position = Vector3(-1.195,0,0)

@rpc("any_peer", "reliable")
func server_purchase_weapon(weapon_id, weapon_cost):
	
	if money >= weapon_cost:
		money -= weapon_cost
		weapon_id = weapon_id
		rpc("client_update_weapon", get_multiplayer_authority(),weapon_id)

@rpc("any_peer", "reliable")
func client_update_weapon(player_id,weapon_id):
	if get_multiplayer_authority() == player_id:
		update_weapon(weapon_id)
	else:
		pass
	
func update_weapon(weapon_id):
	#if not is_multiplayer_authority(): return
	var weapon_scene: PackedScene
	
	match weapon_id:
		1:
			weapon_scene = preload("res://Weapons/glock.tscn")
	if weapon_scene:
		var weapon = weapon_scene.instantiate()
		weapon_handler.add_child(weapon)

func toggle_buy_screen():
	is_buy_screen_open = !is_buy_screen_open
	buy_screen.visible = is_buy_screen_open
	set_process_input(not is_buy_screen_open)
	set_physics_process(not is_buy_screen_open)
	
	if is_buy_screen_open:
		buy_screen.set_focus_mode(Control.FOCUS_ALL)
		Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
	else:
		get_viewport().set_input_as_handled()
		Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

Problem is when host tries to buy weapon, visually there is no weapon. Client can buy weapon and its visible. But if host press shoot button both client and host shoots. Client can’t shoot. This is my weapon script ;

extends Node3D

@export var fire_rate := 14.0
@export var recoil := 0.05
@export var weapon_mesh : Node3D
@export var weapon_damage := 15
@export var muzzle_flash: GPUParticles3D
@export var sparks: PackedScene
@export var automatic: bool

@onready var cooldown_timer: Timer = $CooldownTimer
@onready var weapon_position: Vector3 = weapon_mesh.position
@onready var ray_cast_3d: RayCast3D = $RayCast3D
		
func _process(delta: float) -> void:
	if not is_multiplayer_authority(): return
			
	if automatic:
		if Input.is_action_pressed("Shoot"):
			if cooldown_timer.is_stopped():
				shoot()
	else:
		if Input.is_action_just_pressed("Shoot"):
			if cooldown_timer.is_stopped():
				shoot()
	
	weapon_mesh.position = weapon_mesh.position.lerp(weapon_position, delta * 10.0)
	
func shoot() -> void:
	cooldown_timer.start(1.0 / fire_rate)
	weapon_mesh.position.z += recoil
	
	play_visual_effects(ray_cast_3d.get_collision_point())
	
	if ray_cast_3d.is_colliding():
		var collider = ray_cast_3d.get_collider()
		
		if collider is Player and collider != null:
			var authority = collider.get_multiplayer_authority()
			collider.rpc_id(authority, "take_damage", weapon_damage)
		else:
			print("invalid")
				
	rpc("play_visual_effects", ray_cast_3d.get_collision_point())
	

@rpc("call_remote")
func play_visual_effects(collison_point: Vector3) -> void:
	muzzle_flash.restart()
	
	var spark = sparks.instantiate()
	add_child(spark)
	spark.global_position = collison_point

Can anyone help?

`

not marked “call_local” so the server never calls it locally.

Use 4.x style rpc calls by the way

rpc_id(1, "server_purchase_weapon", weapon_id, weapon_cost)
# to 4.x
server_purchase_weapon.rpc_id(1, weapon_id, weapon_cost)

rpc("client_update_weapon", get_multiplayer_authority(),weapon_id)
# to 4.x
client_update_weapon.rpc(get_multiplayer_authority(),weapon_id)
2 Likes

Thank you for your answer. I updated my script. Now client can’t see hosts weapon and host still shoots for client also. I have this error when host tries to shoot ;