Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- class_name Weapon extends Node3D
- @export_category("Basic")
- @export var weapon_name : String = "fsp45"
- ##Rate of fire per minute
- @export var fire_rate = 800
- ##Rate of burst mode fire per minute
- @export var burst_fire_rate = 800
- ##Default fire mode
- @export_enum("semi", "auto", "burst") var fire_mode : int = 1
- @export_flags("semi", "auto", "burst") var available_shooting_modes = 3
- ##Maximum engagement distance
- @export var fire_distance : float = 50
- ##Lenght maps to fire distance
- @export var fire_range_damage : Curve = preload("res://player/weapon_logic/damage_curve.tres")
- ##Zoom X while aiming
- @export var aim_camera_zoom : float = 2
- @export_group("Bullets")
- @export var clip_size : int = 30
- @export var magazine_size : int = 300
- ##Additional bullet in chamber when tactical reloaded
- @export var bullet_in_chamber : bool = true
- @export var burst_size : int = 3
- @export var buckshot_size : int = 0
- @export var buckshot_spread_max_angle : int = 5
- @export_group("Damage")
- ##Base damage.
- ##Damage depends on the body part and is calculated using the formula:
- ##result_damage = (damage * part_mult) * fire_range_distance.sample(distance to enemy / fire_distance)
- @export var damage : float = 27
- @export var head_mult : float = 2
- @export var torso_mult : float = 1
- @export var limbs_mult : float = 0.7
- @export_group("Recoil")
- @export var max_hip_camera_kick : Vector3
- @export var min_hip_camera_kick : Vector3
- @export var max_aim_camera_kick : Vector3
- @export var min_aim_camera_kick : Vector3
- #@export var max_camera_kick : Vector3
- ##Does nothing
- @export var random_mult : float = 1
- ##How fast camera snaps to kick
- @export var snappinnes = 6
- ##How fast camera returns to normal rotation
- @export var return_speed = 2
- @export_subgroup("NonStop paremetres")
- ##If this option is activated, the camera's recoil accumulates
- @export var non_stop_mult_enabled : bool = true
- @export var max_non_stop_mult : float = 5
- @export var min_non_stop_mult : float = 1
- ##How much accumulates per shoot
- @export var non_stop_increase : float = 0.05
- ##Waiting for the last shot to reset the recoil
- @export var non_stop_reset_threshold : float = 0.5
- @export_group("Position & Procedural animation")
- ##Procedural animations for aiming
- @export var procedural_animation_enabled : bool = true
- @export_subgroup("Hip")
- @export var standart_position : Vector3
- @export var standart_rotation : Vector3
- @export_subgroup("Aim")
- @export var aim_position : Vector3
- @export var aim_rotation : Vector3
- ##How long does the animation last in seconds
- @export var aim_speed : float = 1.0
- @export_group("Shell")
- @export var manual_shell_eject : bool = false
- @export_enum("9mm:0","7mm:1","5mm:2") var shell_type : int = 0
- @export var min_impulse : Vector3
- @export var max_impulse : Vector3
- @export_subgroup("Custom shell model")
- @export var custom_model : PackedScene
- @export_group("Sounds & Animations")
- ## if single loading is true, _animations_reload[0] = reload_start; _animations_reload[1] = reload_cycle; _animations_reload[2] = reload_end.
- ##Reload cycle animation should be looped for properly work.
- @export var single_loading : bool = false
- @export_placeholder("You can write several animations separated by commas") var idle_animation : String = ""
- @export_placeholder("You can write several animations separated by commas") var aim_idle_animation : String = ""
- @export_placeholder("You can write several animations separated by commas") var take_out_animation : String = ""
- @export_subgroup("Fire Animations")
- @export_placeholder("You can write several animations separated by commas") var fire_animation : String = ""
- @export_placeholder("You can write several animations separated by commas") var aim_fire_animation : String = ""
- @export_placeholder("You can write several animations separated by commas") var last_fire_animation : String = ""
- @export_placeholder("You can write several animations separated by commas") var last_aim_fire_animation : String = ""
- #@export_placeholder("You can write several animations separated by commas") var aim_reload_animation : String = ""
- @export_subgroup("Reload Animations")
- @export_placeholder("You can write several animations separated by commas") var tactical_reload_animation : String = ""
- @export_placeholder("You can write several animations separated by commas") var reload_animation : String = ""
- @export_group("UI")
- @export var icon : CompressedTexture2D = preload("res://icon.svg")
- @export var hit_marker_standart_color : Color = Color(1, 1, 1)
- @export var hit_marker_critical_color : Color = Color(0.72428458929062, 0.14479520916939, 0.22202762961388)
- @export_group("Nodes")
- ## Weapon`s animation node
- @export_node_path("AnimationPlayer") var animation_player
- ## Weapon`s shot sound
- @export_node_path("AudioStreamPlayer3D") var audio_stream_player
- ## Weapon`s change mode click
- @export_node_path("AudioStreamPlayer3D") var audio_fire_mode_switch_stream_player
- ## Weapon`s flash
- @export_node_path("OmniLight3D") var omni_light
- ## Weapon`s shells spawn position
- @export_node_path("Marker3D") var shell_position
- ## Weapon`s sparkles and smoke
- @export_node_path("GPUParticles3D") var gpu_particles
- @export var camera_animation : Node3D
- @export var bullet_pos : Marker3D
- var scene_root
- #NODES
- var _animation_player : AnimationPlayer
- var _audio_stream_player : AudioStreamPlayer3D
- var _omni_light : OmniLight3D
- var _gpu_particles : GPUParticles3D
- var _shell_position : Marker3D
- var _audio_fire_mode_switch_stream_player : AudioStreamPlayer3D
- var player
- var timer : Timer = Timer.new()
- var non_stop_timer : Timer = Timer.new()
- #Shells
- var shell_9mm : PackedScene = preload("res://player/weapons/shells/models/9mm/9_mm_shell.tscn")
- var shell_7mm : PackedScene
- var shell_5mm : PackedScene
- var bullet_hole_decal : PackedScene = preload("res://player/weapons/bullet_hole/bullet_hole.tscn")
- var fire_mode_str : Array = ["SEMI","AUTO","BURST"]
- #Internal variables
- var is_aiming : bool = false
- var previous_aim_state : bool = is_aiming
- var timer_wait : float = 60.0 / fire_rate
- var non_stop : float = 1.0
- var weapon_ray_cast_3d : RayCast3D
- var ui_element
- var is_tactical_reload : bool = false
- var is_reloading : bool = false :
- set(value):
- is_reloading = value
- EventBus.emit_signal("weapon_reload", is_reloading)
- var is_take_out : bool = false
- var is_haste : bool = false
- var clip : int
- var magazine : int = magazine_size :
- set(value):
- magazine = value
- self._update_ui()
- var _animations_fire : AnimationsPack = AnimationsPack.new()
- var _animations_aim_fire : AnimationsPack = AnimationsPack.new()
- var _animations_last_fire : AnimationsPack = AnimationsPack.new()
- var _animations_last_aim_fire : AnimationsPack = AnimationsPack.new()
- var _animations_idle : AnimationsPack = AnimationsPack.new()
- var _animations_aim_idle : AnimationsPack = AnimationsPack.new()
- var _animations_take_out : AnimationsPack = AnimationsPack.new()
- var _animations_tactical_reload : AnimationsPack = AnimationsPack.new()
- var _animations_reload : AnimationsPack = AnimationsPack.new()
- class AnimationsPack:
- var _animations_pack : PackedStringArray
- var _pack_size : int = -1
- func parse(data : String, splitter : String = ",") -> void:
- _animations_pack = data.split(splitter, false)
- _pack_size = _animations_pack.size() - 1
- func get_size() -> int:
- return _pack_size
- func get_pack() -> PackedStringArray:
- return _animations_pack
- func get_rand_animation() -> String:
- if _pack_size > -1:
- return _animations_pack[randi_range(0,_pack_size)]
- else:
- return "null"
- var bullet_tracer = preload("res://player/weapons/in_game/bullet_tracer.tscn")
- var tracer_instance
- var shell_instance
- var bullet_shotted : int = 0
- #Signals
- #Weapon_recoil in Event Bus
- #Weapon_aim in Event Bus
- func _ready() -> void:
- add_child(timer)
- add_child(non_stop_timer)
- timer.one_shot = true; non_stop_timer.one_shot = true
- timer.autostart = false; non_stop_timer.autostart = false
- non_stop = min_non_stop_mult
- if animation_player != null: _animation_player = get_node_or_null(animation_player)
- if audio_stream_player != null: _audio_stream_player = get_node_or_null(audio_stream_player)
- if audio_fire_mode_switch_stream_player != null: _audio_fire_mode_switch_stream_player = get_node_or_null(audio_fire_mode_switch_stream_player)
- if omni_light != null: _omni_light = get_node_or_null(omni_light)
- if gpu_particles != null: _gpu_particles = get_node_or_null(gpu_particles)
- if shell_position != null: _shell_position = get_node_or_null(shell_position)
- if camera_animation:
- camera_animation.rotation_degrees = Vector3.ZERO
- _animations_last_fire.parse(last_fire_animation)
- _animations_last_aim_fire.parse(last_aim_fire_animation)
- _animations_fire.parse(fire_animation)
- _animations_aim_fire.parse(aim_fire_animation)
- _animations_idle.parse(idle_animation)
- _animations_aim_idle.parse(aim_idle_animation)
- _animations_reload.parse(reload_animation)
- _animations_tactical_reload.parse(tactical_reload_animation)
- _animations_take_out.parse(take_out_animation)
- scene_root = get_tree().root
- magazine = magazine_size
- clip = clip_size
- if _animation_player:
- _animation_player.animation_finished.connect(_animation_handler)
- _change_shooting_mode(true)
- _update_ui()
- func _check(_delta : float):
- if !visible:
- return
- is_aiming = Input.is_action_pressed("mouse_2")
- if Global.player.camera_weapon_animation and camera_animation:
- #Global.player.camera_weapon_animation.position = camera_animation.position
- Global.player.camera_weapon_animation.rotation = camera_animation.rotation
- if non_stop_timer.is_stopped():
- non_stop = min_non_stop_mult
- if not single_loading:
- if !timer.is_stopped() or is_reloading or is_take_out:
- return
- elif !timer.is_stopped() or is_take_out:
- return
- if Input.is_action_just_pressed("mouse_1") and clip == 0:
- _reload()
- match fire_mode:
- 0: # Semi
- if Input.is_action_just_pressed("mouse_1"):
- timer_wait = 60.0 / fire_rate
- _fire()
- 1: #Auto
- if Input.is_action_pressed("mouse_1"):
- timer_wait = 60.0 / fire_rate
- _fire()
- 2: #Burst
- if Input.is_action_just_pressed("mouse_1"):
- timer_wait = 60.0 / burst_fire_rate
- for bullet in burst_size:
- _fire()
- timer.start(timer_wait)
- await timer.timeout
- func _physics_process(delta):
- _check(delta)
- if !visible:
- return
- if Input.is_action_just_pressed("chande_fire_mode"):
- _change_shooting_mode(false)
- if Input.is_action_just_pressed("r"):
- _reload()
- func _process(delta: float) -> void:
- _check(delta)
- _procedural_animation(delta)
- func _fire():
- if !clip > 0:
- return
- is_reloading = false
- if not manual_shell_eject:
- _eject_shell()
- clip -= 1
- if buckshot_size > 0:
- var rot_to = manual_spread()
- for bullet in buckshot_size:
- weapon_ray_cast_3d.rotation_degrees = rot_to
- weapon_ray_cast_3d.force_raycast_update()
- rot_to = manual_spread()
- _hit_reg()
- _play_vfx()
- else:
- _hit_reg()
- _play_vfx()
- _update_ui()
- random_recoil()
- timer.start(timer_wait)
- non_stop_timer.start(non_stop_reset_threshold)
- if _animation_player != null and fire_animation != "":
- _animation_player.stop()
- if(aim_fire_animation != "") and is_aiming:
- _play_animation(_animations_aim_fire.get_rand_animation())
- else:
- _play_animation(_animations_fire.get_rand_animation())
- if _audio_stream_player != null:
- _audio_stream_player.pitch_scale = randf_range(0.9,1.1)
- _audio_stream_player.play()
- non_stop += non_stop_increase
- non_stop = clamp(non_stop, min_non_stop_mult, max_non_stop_mult)
- func _hit_reg():
- if weapon_ray_cast_3d:
- bullet_shotted += 1
- var collider = weapon_ray_cast_3d.get_collider()
- var hit_position : Vector3
- var normal : Vector3
- if collider:
- hit_position = weapon_ray_cast_3d.get_collision_point()
- normal = weapon_ray_cast_3d.get_collision_normal()
- if collider and collider.has_method("get_hit"):
- var final_damage = damage
- if collider is Hitbox:
- match collider.type:
- 0:
- final_damage *= head_mult
- EventBus.emit_signal("weapon_hitted", hit_marker_critical_color)
- 1:
- final_damage *= torso_mult
- EventBus.emit_signal("weapon_hitted", hit_marker_standart_color)
- 2:
- final_damage *= limbs_mult
- EventBus.emit_signal("weapon_hitted", hit_marker_standart_color)
- final_damage *= fire_range_damage.sample(Global.player.global_position.distance_to(hit_position) / fire_distance)
- EventBus.emit_signal("ui_message","%d Bullet Calculated Damage : %1.1f" %[bullet_shotted,final_damage])
- collider.call("get_hit", final_damage, hit_position)
- if collider is RigidBody3D:
- collider.apply_impulse(normal * -2, hit_position - collider.global_position)
- func _reload():
- #While shooting, the player cannot start reloading
- if (_animation_player.current_animation in _animations_fire.get_pack() or _animation_player.current_animation in _animations_reload.get_pack()):
- return
- if (is_reloading || is_take_out):
- return
- if single_loading:
- if magazine > 0 and clip != clip_size:
- _play_animation(_animations_reload.get_pack()[0])
- is_tactical_reload = clip < 1
- is_reloading = true
- return
- var wasted_ammo_size = clip_size - (clip - int(bullet_in_chamber and clip > 0))
- if wasted_ammo_size <= 0 || magazine <= 0:
- return
- if clip > 0 and _animations_tactical_reload.get_size() > -1:
- _play_animation(_animations_tactical_reload.get_rand_animation())
- is_tactical_reload = true
- is_reloading = true
- elif _animations_reload.get_size() > -1:
- _play_animation(_animations_reload.get_rand_animation())
- is_tactical_reload = false
- is_reloading = true
- await EventBus.weapon_reload
- if visible:
- if (wasted_ammo_size <= magazine):
- magazine -= wasted_ammo_size
- clip += wasted_ammo_size
- else:
- clip += magazine
- magazine = 0
- _update_ui()
- func _change_shooting_mode(ready_update : bool = false):
- # fire_mode = semi : 0, auto : 1, burst : 2
- # Single modes are not described, because they don't need logic
- if(ready_update):
- match available_shooting_modes:
- 1: #Semi
- fire_mode = 0
- 2: # Auto
- fire_mode = 1
- 4: # Burst
- fire_mode = 2
- else:
- match available_shooting_modes:
- 1: #Semi
- fire_mode = 0
- 2: # Auto
- fire_mode = 1
- 4: # Burst
- fire_mode = 2
- 3: #Semi : 1 + Auto : 2
- if(fire_mode == 0): fire_mode = 1
- else : fire_mode = 0
- 5: #Burst : 4 + Semi : 1
- if(fire_mode == 0): fire_mode = 2
- else : fire_mode = 0
- 6: #Auto : 2 + burst : 4
- if(fire_mode == 1): fire_mode = 2
- else : fire_mode = 1
- 7: #Semi : 1 + Auto : 2 + Burst : 4
- if(fire_mode == 0): fire_mode = 1
- elif(fire_mode == 1) : fire_mode = 2
- else : fire_mode = 0
- if _audio_fire_mode_switch_stream_player:
- _audio_fire_mode_switch_stream_player.pitch_scale = randf_range(0.9,1.1)
- _audio_fire_mode_switch_stream_player.play()
- if(available_shooting_modes == 0):
- DebugOutput.print_warning("Please select at least one shooting mode! : " + str(self))
- _update_ui()
- func random_recoil():
- var camera_kick : Vector3
- if is_aiming:
- camera_kick = Vector3(
- randf_range(min_aim_camera_kick.x,max_aim_camera_kick.x),
- randf_range(-min_aim_camera_kick.y,max_aim_camera_kick.y),
- randf_range(-min_aim_camera_kick.z,max_aim_camera_kick.z)
- ) * non_stop
- else:
- camera_kick = Vector3(
- randf_range(min_hip_camera_kick.x,max_hip_camera_kick.x),
- randf_range(-min_hip_camera_kick.y,max_hip_camera_kick.y),
- randf_range(-min_hip_camera_kick.z,max_hip_camera_kick.z)
- ) * non_stop
- EventBus.emit_signal("weapon_recoil", camera_kick, snappinnes, return_speed)
- func _update_ui():
- if ui_element != null:
- ui_element.ammos.text = str(clip) + "/" + str(magazine)
- ui_element.fire_mode.text = fire_mode_str[fire_mode]
- func _get_random_impulse():
- return Vector3(
- randf_range(min_impulse.x,max_impulse.x),
- randf_range(min_impulse.y,max_impulse.y),
- randf_range(min_impulse.z,max_impulse.z)
- )
- func _procedural_animation(_delta : float):
- if !procedural_animation_enabled:
- return
- if is_aiming == previous_aim_state:
- return
- previous_aim_state = is_aiming
- var new_tween = get_tree().create_tween()
- if (is_aiming):
- EventBus.emit_signal("weapon_aim", aim_camera_zoom, aim_speed, is_aiming)
- else:
- EventBus.emit_signal("weapon_aim", 1, aim_speed, is_aiming)
- if is_aiming:
- new_tween.tween_property(self, "position",aim_position, aim_speed).set_trans(Tween.TRANS_SINE)
- new_tween.parallel()
- new_tween.tween_property(self, "rotation_degrees",aim_rotation, aim_speed).set_trans(Tween.TRANS_SINE)
- new_tween.play()
- #position = lerp(position, aim_position, aim_speed * delta)
- #rotation_degrees = lerp(rotation_degrees, aim_rotation, aim_speed * delta)
- else:
- new_tween.tween_property(self, "position",standart_position, aim_speed).set_trans(Tween.TRANS_SINE)
- new_tween.parallel()
- new_tween.tween_property(self, "rotation_degrees",standart_rotation, aim_speed).set_trans(Tween.TRANS_SINE)
- new_tween.play()
- #position = lerp(position, standart_position, aim_speed * delta)
- #rotation_degrees = lerp(rotation_degrees, standart_rotation, aim_speed * delta)
- func _animation_handler(animation_name : String):
- if (animation_name in _animations_take_out.get_pack() + _animations_fire.get_pack() + _animations_reload.get_pack() + _animations_tactical_reload.get_pack() + _animations_aim_idle.get_pack() + _animations_idle.get_pack()):
- var func_get_idle = func():
- if not is_aiming or _animations_aim_idle.get_size() < 0:
- return _animations_idle.get_rand_animation()
- else:
- return _animations_aim_idle.get_rand_animation()
- var rand_animation : String = func_get_idle.call()
- if(animation_name in _animations_reload.get_pack() + _animations_tactical_reload.get_pack()):
- is_reloading = false
- if(animation_name in _animations_take_out.get_pack()):
- is_take_out = false
- if (animation_name in _animations_idle.get_pack()):
- rand_animation = func_get_idle.call()
- if (animation_name in _animations_fire.get_pack() + _animations_aim_fire.get_pack()):
- rand_animation = func_get_idle.call()
- _play_animation(rand_animation)
- func _play_vfx():
- if !Global.decals_enabled:
- return
- if bullet_pos:
- tracer_instance = bullet_tracer.instantiate()
- bullet_pos.add_child(tracer_instance)
- tracer_instance.top_level = true
- if weapon_ray_cast_3d.is_colliding():
- tracer_instance.look_at(weapon_ray_cast_3d.get_collision_point(), Vector3.UP, true)
- if weapon_ray_cast_3d.is_colliding():
- var collider = weapon_ray_cast_3d.get_collider()
- if collider:
- if not collider.is_in_group("dynamic_object"):
- var decal = bullet_hole_decal.instantiate()
- decal.collider = collider
- collider.add_child(decal)
- decal.global_transform.origin = weapon_ray_cast_3d.get_collision_point()
- decal.look_at(Global.player.neck.global_rotation, weapon_ray_cast_3d.get_collision_normal())
- if _gpu_particles:_gpu_particles.emitting = true
- if _omni_light:_omni_light.visible = true
- await get_tree().create_timer(0.1).timeout
- if _gpu_particles:_gpu_particles.emitting = false
- if _omni_light:_omni_light.visible = false
- func _play_animation(anim_name : String):
- if is_haste and anim_name in (_animations_reload.get_pack() + _animations_tactical_reload.get_pack()):
- _animation_player.speed_scale = 2
- else:
- _animation_player.speed_scale = 1
- if _animation_player:
- _animation_player.play(anim_name)
- func change_visible(visible_weapon : bool = false):
- if (visible_weapon == visible):
- return
- else:
- visible = visible_weapon
- is_reloading = false
- if(visible_weapon and _animations_take_out.get_size() > -1):
- _animation_player.stop()
- _play_animation(_animations_take_out.get_rand_animation())
- is_take_out = true
- #Function for track in AnimationPlayer
- func end_reloading():
- is_reloading = false
- func end_take_out():
- is_take_out = false
- func break_reloading():
- #is_reloading = false
- #if _animation_player:
- #_animation_player.stop()
- #dummy
- pass
- func manual_spread():
- var raycast_rot : Vector3 = Vector3.ZERO
- if is_aiming:
- raycast_rot = Vector3(
- randf_range(-buckshot_spread_max_angle,buckshot_spread_max_angle),
- randf_range(-buckshot_spread_max_angle,buckshot_spread_max_angle),
- 0
- ) * non_stop
- else:
- raycast_rot = Vector3(
- randf_range(-buckshot_spread_max_angle,buckshot_spread_max_angle),
- randf_range(-buckshot_spread_max_angle,buckshot_spread_max_angle),
- 0
- ) * non_stop
- return raycast_rot
- func reload_by_one():
- if not visible:
- return
- if magazine > 0:
- if clip + 1 < clip_size:
- EventBus.emit_signal("ui_message", "Reload Cycle")
- clip += 1; magazine -= 1
- _play_animation(_animations_reload.get_pack()[1])
- else:
- EventBus.emit_signal("ui_message", "Reload End")
- clip += 1; magazine -= 1
- if not is_tactical_reload:
- _play_animation(_animations_reload.get_pack()[2])
- else:
- _play_animation(_animations_tactical_reload.get_pack()[2])
- is_reloading = false
- func _eject_shell():
- if Global.decals_enabled and shell_position:
- var instance
- if not custom_model:
- match shell_type:
- 0:
- instance = shell_9mm.instantiate()
- 1:
- instance = shell_9mm.instantiate()
- 2:
- instance = shell_9mm.instantiate()
- else:
- instance = custom_model.instantiate()
- scene_root.add_child(instance)
- instance.global_transform = _shell_position.global_transform
- instance.top_level = true
- instance.apply_central_impulse(global_transform.basis * _get_random_impulse())
- instance.apply_torque_impulse(_get_random_impulse())
- instance = null
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement