extends Node3D #Hand Camera Head Player @onready var main = get_tree().root.get_node("Main") @onready var hud = main.get_node("HUD") @onready var score_rank = hud.get_node("ScoreRank") @onready var player = get_parent().get_parent().get_parent().get_parent() @onready var camera = get_parent().get_parent() @onready var ejector = find_node("Ejector") @onready var muzzle = find_node("Muzzle") @onready var animation_player = find_node("AnimationPlayer") @export var animation_speed : float var casing = preload("res://Assets/Weapons/Handgun/Casing.tscn") var flash = preload("res://Assets/Effects/MuzzleFlash.tscn") var tracer = preload("res://Assets/Effects/BulletTracer.tscn") var impact_bullet = preload("res://Assets/Effects/ImpactSparks.tscn") var impact_explosion = preload("res://Assets/Effects/ImpactExplosion.tscn") var flyby_sound = preload("res://Assets/Audio/BulletFlyBySoundPlayer.tscn") enum WeaponType {HITSCAN, PROJECTILE, MELEE} @export var weapon_type : WeaponType @export var projectile_or_tracer_scene : PackedScene @export var weapon_damage : int # accuracy var spread_min = 1 var spread_max = 64 var spread_lerp = 0.01 var spread_boost = 16 var spread = spread_min # Declare member variables here. Examples: # var a = 2 # var b = "text" #enum Trigger {TRIGGER_PRIMARY, TRIGGER_SECONDARY} @rpc(any_peer, call_local, reliable) func shoot(spread_offset:=Vector3.ZERO): $"SFX/Shoot A".play() $"SFX/Shoot B".play() $"SFX/Shoot C".play() var flash_effect = flash.instantiate() get_parent().add_child(flash_effect) flash_effect.global_transform = muzzle.global_transform animation_player.play("Shoot", 0, animation_speed) var space_state = get_world_3d().direct_space_state var from = camera.get_global_transform().origin var aim = - camera.get_global_transform().basis[2] var to = from + (aim * 1000) + spread_offset var physics_ray_query_parameters_3d = PhysicsRayQueryParameters3D.new() physics_ray_query_parameters_3d.from = from physics_ray_query_parameters_3d.to = to physics_ray_query_parameters_3d.exclude = [player] var ray = space_state.intersect_ray(physics_ray_query_parameters_3d) if weapon_type == WeaponType.HITSCAN: var tracer_instance = projectile_or_tracer_scene.instantiate() get_tree().root.add_child(tracer_instance) tracer_instance.global_transform = muzzle.global_transform.looking_at(to) var casing_instance = casing.instantiate() get_tree().root.add_child(casing_instance) casing_instance.global_transform = ejector.global_transform#approximating delta casing_instance.linear_velocity = ejector.global_transform.basis[1] * randf_range(3.2, 4.5) + (player.motion_velocity / 2) casing_instance.angular_velocity.y += randf_range(-10, 10) casing_instance.angular_velocity.x += randf_range(-10, 10) if ray: if is_multiplayer_authority(): # only do this on the attacker's local instance of the game give_damage(ray['collider'], ray['position'], ray['normal'], 20, self.global_transform.origin, Globals.DamageType.BULLET, 1.0) ### bullet flyby sounds if ray: # if we hit something - use that to evaluate the flyby sound to = ray['position'] var flyby_camera = get_tree().get_root().get_camera_3d() if flyby_camera == camera: # don't spawn flyby sound for the shooter return var x := Vector3.ZERO var A = from var B = to var C = flyby_camera.global_transform.origin var d0 = (B - A).dot(A - C) var d1 = (B - A).dot(B - C) if d0 < 0 and d1 < 0: print("Firing away from the camera") elif d0 > 0 and d1 > 0: print("Bullet hit before passing by") else: var X = d0/(d0-d1) var flyby = flyby_sound.instantiate() get_tree().root.add_child(flyby) flyby.global_transform.origin = A + X * (B - A) #print("===") #print(X) #print("===") #flyby.global_transform.origin = A + x * (B - A) # TODO - spawn elif weapon_type == WeaponType.PROJECTILE: var projectile_instance = projectile_or_tracer_scene.instantiate() projectile_instance.global_transform = muzzle.global_transform.looking_at(to) projectile_instance.source_position = player.global_transform.origin projectile_instance.player = player projectile_instance.damage = weapon_damage #projectile_instance.get_node("RayCast3D").add_exception(player) #projectile_instance.rotate_x(PI/2) get_tree().root.add_child(projectile_instance) return # skip the rest - it's deprecated code ####################### DEPRACATED ↓↓↓↓↓↓↓↓ # # var impact_vfx # # if ray: # did we hit anything? # if ray['collider'].has_method(&'receive_damage') && is_multiplayer_authority(): # ray['collider'].rpc(&'damage', 20, get_multiplayer_authority(), global_transform.origin) # apply damage # # return # # if ray['collider'].has_method(&'damage'): # if is_multiplayer_authority(): #get_tree().multiplayer.get_multiplayer_unique_id() == 1: # make sure this can only run on the server # #print("SHOT HIT ", ray['collider']) # ray['collider'].rpc(&'damage', 20) # apply damage # if main.player_list.get(ray['collider'].get_multiplayer_authority()).health <= 0: # if he ded # ray['collider'].rpc(&'die', self.get_multiplayer_authority()) # # main.player_list.players[player.get_multiplayer_authority()].score += 1 # give the killer a point # main.rpc(&'player_list_update', main.player_list.get(player.get_multiplayer_authority()).serialize(), player.get_multiplayer_authority()) # hud.get_node("Crosshair").kill() # main.check_game_win_condition() # # # check for firstblood # if main.player_list.players[player.get_multiplayer_authority()].score == 1: # var firstblood = true # for i in main.player_list.players.keys(): # if i != player.get_multiplayer_authority() and main.player_list.players[i].score > 0: # firstblood = false # if firstblood: # main.get_node("Announcer").speak(main.get_node("Announcer").firstblood) # # # check for revenge (payback) - don't play if this is a duel, it'd be silly # if main.player_list.players.size() > 2 and ray['collider'].get_multiplayer_authority() == player.revenge_pid: # main.get_node("Announcer").speak(main.get_node("Announcer").payback) # player.revenge_pid = -1 # reset revenge # else: # hud.get_node("Crosshair").hit() # ray['collider'].rpc(&'moan') # # # boardcast the new health value to all peers # main.rpc(&'player_list_update', main.player_list.get(ray['collider'].get_multiplayer_authority()).serialize(), ray['collider'].get_multiplayer_authority()) # main.update_hud() # ## impact_vfx = impact_player.instantiate() # else: ## impact_vfx = impact_wall.instantiate() # pass # ## if impact_vfx != null: ## impact_vfx.global_transform = impact_vfx.global_transform.looking_at(ray['normal']) ## impact_vfx.global_transform.origin = ray['position'] ## get_tree().root.add_child(impact_vfx) ## # #print(ray) ####################### DEPRACATED ↑↑↑↑↑↑↑↑ func give_damage(target: Node, hit_position: Vector3, hit_normal: Vector3, damage: int, source_position: Vector3, type: Globals.DamageType, push: float): if target.has_method(&'take_damage'): # we've hit a player or something else - the ywill handle everything like effects etc. target.rpc(&'take_damage', get_multiplayer_authority(), hit_position, hit_normal, damage, source_position, type, push) else: # TODO take data from the material of the target and spawn an appropriate hit effect var impact_vfx : Node = impact_bullet.instantiate() get_tree().root.add_child(impact_vfx) impact_vfx.global_transform.origin = hit_position #print(impact_vfx.global_transform) var result = impact_vfx.look_at(hit_position + hit_normal) if not result: # if the look_at failed (as it will on floors and ceilings) try another approach: impact_vfx.look_at(hit_position + hit_normal, Vector3.LEFT) impact_vfx.rotate(hit_normal, randf_range(0, PI * 2)) #print(impact_vfx.global_transform) func trigger(index: int, active: bool) -> void: #print("Weapon " + str(name) + ", Trigger " + str(index) + ", active: " + str(active)) if index == 0 and active and animation_player.is_playing() == false: spread = min(spread + spread_boost, spread_max) var spread_offset = Vector3.ZERO spread_offset.x = randf_range(-1,1) spread_offset.y = randf_range(-1,1) spread_offset.z = randf_range(-1,1) spread_offset = spread_offset.normalized() * randf_range(spread_min, spread) rpc(&'shoot', spread_offset) # Called when the node enters the scene tree for the first time. func _ready(): # align the sound source with the head to produce balanced stereo pass #$SFX/Shoot.global_transform = camera.global_transform