feat: integrate ChaffGames FPS template as local player controller
Replaces the overhead box-mesh view with a full FPS character: - mouse look, WASD movement, jump (Space), crouch (C), sprint (Shift), lean (Q/E) - 6 weapons (LMB shoot, R reload, scroll switch, F melee, G drop) - Crosshair HUD, ammo counter, sprint bar - Client sends position to server at 20Hz for multiplayer visibility - Server broadcasts positions to all peers - Remote players shown as boxes (placeholder) source: https://godotengine.org/asset-library/asset/2652 (MIT license)
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
extends CanvasLayer
|
||||
|
||||
@onready var current_weapon_label = $debug_hud/HBoxContainer/CurrentWeapon
|
||||
@onready var current_ammo_label = $debug_hud/HBoxContainer2/CurrentAmmo
|
||||
@onready var current_weapon_stack = $debug_hud/HBoxContainer3/WeaponStack
|
||||
@onready var hit_sight = $HitSight
|
||||
@onready var hit_sight_timer = $HitSight/HitSightTimer
|
||||
@onready var overLay = $Overlay
|
||||
|
||||
func _on_weapons_manager_update_weapon_stack(WeaponStack):
|
||||
current_weapon_stack.text = ""
|
||||
for i in WeaponStack:
|
||||
current_weapon_stack.text += "\n"+i.weapon.weapon_name
|
||||
|
||||
func _on_weapons_manager_update_ammo(Ammo):
|
||||
current_ammo_label.set_text(str(Ammo[0])+" / "+str(Ammo[1]))
|
||||
|
||||
func _on_weapons_manager_weapon_changed(WeaponName):
|
||||
current_weapon_label.set_text(WeaponName)
|
||||
|
||||
func _on_hit_sight_timer_timeout():
|
||||
hit_sight.set_visible(false)
|
||||
|
||||
func _on_weapons_manager_add_signal_to_hud(_projectile):
|
||||
_projectile.Hit_Successfull.connect(_on_weapons_manager_hit_successfull)
|
||||
|
||||
func _on_weapons_manager_hit_successfull():
|
||||
hit_sight.set_visible(true)
|
||||
hit_sight_timer.start()
|
||||
|
||||
func load_over_lay_texture(Active:bool, txtr: Texture2D = null):
|
||||
overLay.set_texture(txtr)
|
||||
overLay.set_visible(Active)
|
||||
|
||||
func _on_weapons_manager_connect_weapon_to_hud(_weapon_resouce: WeaponResource):
|
||||
_weapon_resouce.update_overlay.connect(load_over_lay_texture)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cc3t6vk7qpwey
|
||||
@@ -0,0 +1,266 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
@onready var camera = %Camera
|
||||
@export var subviewport_camera: Camera3D
|
||||
@export var main_camera:Camera3D
|
||||
@export var animation_tree: AnimationTree
|
||||
|
||||
var camera_rotation: Vector2 = Vector2(0.0,0.0)
|
||||
var mouse_sensitivity = 0.001
|
||||
var crouched: bool = false
|
||||
var crouch_blocked: bool = false
|
||||
|
||||
@export_category("Crouch Parametres")
|
||||
@export var enable_crouch: bool = true
|
||||
@export var crouch_toggle: bool = false
|
||||
@export var crouch_collision: ShapeCast3D
|
||||
@export_range(0.0,3.0) var crouch_speed_reduction = 2.0
|
||||
@export_range(0.0,0.50) var crouch_blend_speed = .2
|
||||
enum {GROUND_CROUCH = -1, STANDING = 0, AIR_CROUCH = 1}
|
||||
|
||||
@export_category("Lean Parametres")
|
||||
@export var enable_lean: bool = true
|
||||
@export_range(0.0,1.0) var lean_speed: float = .2
|
||||
@export var right_lean_collision: ShapeCast3D
|
||||
@export var left_lean_collision: ShapeCast3D
|
||||
var lean_tween
|
||||
enum {LEFT = 1, CENTRE = 0, RIGHT = -1}
|
||||
|
||||
@export_category("speed Parameters")
|
||||
@export var enable_sprint: bool = true
|
||||
@export var sprint_timer: Timer
|
||||
@export var sprint_cooldown_time: float = 3.0
|
||||
@export var sprint_time: float = 1.0
|
||||
@export var sprint_replenish_rate: float = 0.30
|
||||
@export var acceleration: float = 120
|
||||
@export_range(0.01,1.0) var air_acceleration_modifier: float = 0.1
|
||||
var sprint_on_cooldown: bool = false
|
||||
var sprint_time_remaining: float = sprint_time
|
||||
@onready var sprint_bar: Range = $CanvasLayer/SprintBar
|
||||
|
||||
const NORMAL_speed = 1
|
||||
@export_range(1.0,3.0) var sprint_speed: float = 2.0
|
||||
@export_range(0.1,1.0) var walk_speed: float = 0.5
|
||||
var speed_modifier: float = NORMAL_speed
|
||||
|
||||
@export_category("Jump Parameters")
|
||||
@export var coyote_timer: Timer
|
||||
@export var jump_peak_time: float = .5
|
||||
@export var jump_fall_time: float = .5
|
||||
@export var jump_height: float = 2.0
|
||||
@export var jump_distance: float = 4.0
|
||||
@export var coyote_time: float = .1
|
||||
@export var jump_buffer_time: float = .2
|
||||
|
||||
# Get the gravity from the project settings to be synced with RigidBody nodes.
|
||||
var jump_gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
|
||||
var fall_gravity: float
|
||||
var jump_velocity: float
|
||||
var base_speed: float
|
||||
var _speed: float
|
||||
var jump_available: bool = true
|
||||
var jump_buffer: bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
update_camera_rotation()
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
||||
calculate_movement_parameters()
|
||||
|
||||
func update_camera_rotation() -> void:
|
||||
var current_rotation = get_rotation()
|
||||
camera_rotation.x = current_rotation.y
|
||||
camera_rotation.y = current_rotation.x
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("ui_cancel"):
|
||||
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||
else:
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
||||
|
||||
if event is InputEventMouseMotion:
|
||||
var MouseEvent = event.relative * mouse_sensitivity
|
||||
camera_look(MouseEvent)
|
||||
|
||||
if enable_crouch:
|
||||
if event.is_action_pressed("crouch"):
|
||||
crouch()
|
||||
if event.is_action_released("crouch"):
|
||||
if !crouch_toggle and crouched:
|
||||
crouch()
|
||||
|
||||
if enable_lean:
|
||||
if Input.is_action_just_released("lean_left") or Input.is_action_just_released("lean_right"):
|
||||
if !(Input.is_action_pressed("lean_right") or Input.is_action_pressed("lean_left")):
|
||||
lean(CENTRE)
|
||||
if Input.is_action_just_pressed("lean_left"):
|
||||
lean(LEFT)
|
||||
if Input.is_action_just_pressed("lean_right"):
|
||||
lean(RIGHT)
|
||||
|
||||
if enable_sprint:
|
||||
if Input.is_action_just_pressed("sprint") and !crouched:
|
||||
if !sprint_on_cooldown:
|
||||
speed_modifier = sprint_speed
|
||||
sprint_timer.start(sprint_time_remaining)
|
||||
|
||||
if Input.is_action_just_released("sprint") or Input.is_action_just_released("walk"):
|
||||
if !(Input.is_action_pressed("walk") or Input.is_action_pressed("sprint")):
|
||||
speed_modifier = NORMAL_speed
|
||||
exit_sprint()
|
||||
|
||||
if Input.is_action_just_pressed("walk") and !crouched:
|
||||
speed_modifier = walk_speed
|
||||
|
||||
func calculate_movement_parameters() -> void:
|
||||
jump_gravity = (2*jump_height)/pow(jump_peak_time,2)
|
||||
fall_gravity = (2*jump_height)/pow(jump_fall_time,2)
|
||||
jump_velocity = jump_gravity * jump_peak_time
|
||||
base_speed = jump_distance/(jump_peak_time+jump_fall_time)
|
||||
_speed = base_speed
|
||||
|
||||
func lean(blend_amount: int) -> void:
|
||||
if is_on_floor():
|
||||
if lean_tween:
|
||||
lean_tween.kill()
|
||||
|
||||
lean_tween = get_tree().create_tween()
|
||||
lean_tween.tween_property(animation_tree,"parameters/lean_blend/blend_amount", blend_amount, lean_speed)
|
||||
|
||||
func lean_collision() -> void:
|
||||
animation_tree["parameters/left_collision_blend/blend_amount"] = lerp(
|
||||
float(animation_tree["parameters/left_collision_blend/blend_amount"]),float(left_lean_collision.is_colliding()),lean_speed
|
||||
)
|
||||
animation_tree["parameters/right_collision_blend/blend_amount"] = lerp(
|
||||
float(animation_tree["parameters/right_collision_blend/blend_amount"]),float(right_lean_collision.is_colliding()),lean_speed
|
||||
)
|
||||
|
||||
func crouch() -> void:
|
||||
var Blend
|
||||
if !crouch_collision.is_colliding():
|
||||
if crouched:
|
||||
Blend = STANDING
|
||||
else:
|
||||
speed_modifier = NORMAL_speed
|
||||
exit_sprint()
|
||||
|
||||
if is_on_floor():
|
||||
Blend = GROUND_CROUCH
|
||||
else:
|
||||
Blend = AIR_CROUCH
|
||||
var blend_tween = get_tree().create_tween()
|
||||
blend_tween.tween_property(animation_tree,"parameters/Crouch_Blend/blend_amount",Blend,crouch_blend_speed)
|
||||
crouched = !crouched
|
||||
else:
|
||||
crouch_blocked = true
|
||||
|
||||
func camera_look(Movement: Vector2) -> void:
|
||||
camera_rotation += Movement
|
||||
|
||||
transform.basis = Basis()
|
||||
camera.transform.basis = Basis()
|
||||
|
||||
rotate_object_local(Vector3(0,1,0),-camera_rotation.x) # first rotate in Y
|
||||
camera.rotate_object_local(Vector3(1,0,0), -camera_rotation.y) # then rotate in X
|
||||
camera_rotation.y = clamp(camera_rotation.y,-1.5,1.2)
|
||||
|
||||
func exit_sprint() -> void:
|
||||
if !sprint_timer.is_stopped():
|
||||
sprint_time_remaining = sprint_timer.time_left
|
||||
sprint_timer.stop()
|
||||
|
||||
func sprint_replenish(delta) -> void:
|
||||
var sprint_bar_Value
|
||||
|
||||
if !sprint_on_cooldown and (speed_modifier != sprint_speed):
|
||||
|
||||
if is_on_floor():
|
||||
sprint_time_remaining = move_toward(sprint_time_remaining, sprint_time, delta*sprint_replenish_rate)
|
||||
|
||||
sprint_bar_Value= (sprint_time_remaining/sprint_time)*100
|
||||
|
||||
else:
|
||||
sprint_bar_Value = (sprint_timer.time_left/sprint_time)*100
|
||||
|
||||
sprint_bar.value = sprint_bar_Value
|
||||
|
||||
if sprint_bar_Value == 100:
|
||||
sprint_bar.hide()
|
||||
else:
|
||||
sprint_bar.show()
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if subviewport_camera:
|
||||
subviewport_camera.global_transform = main_camera.global_transform
|
||||
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
sprint_replenish(_delta)
|
||||
lean_collision()
|
||||
|
||||
if crouched and crouch_blocked:
|
||||
if !crouch_collision.is_colliding():
|
||||
crouch_blocked = false
|
||||
if !Input.is_action_pressed("crouch") and !crouch_toggle:
|
||||
crouch()
|
||||
|
||||
# Add the gravity.
|
||||
var _acceleration
|
||||
if not is_on_floor():
|
||||
_acceleration = acceleration*air_acceleration_modifier
|
||||
|
||||
if coyote_timer.is_stopped():
|
||||
coyote_timer.start(coyote_time)
|
||||
|
||||
if velocity.y>0:
|
||||
velocity.y -= jump_gravity * _delta
|
||||
else:
|
||||
velocity.y -= fall_gravity * _delta
|
||||
else:
|
||||
_acceleration = acceleration
|
||||
jump_available = true
|
||||
coyote_timer.stop()
|
||||
_speed = (base_speed / max((float(crouched)*crouch_speed_reduction),1)) * speed_modifier
|
||||
if jump_buffer:
|
||||
jump()
|
||||
jump_buffer = false
|
||||
# Handle Jump.
|
||||
if Input.is_action_just_pressed("ui_accept"):
|
||||
if jump_available:
|
||||
if crouched:
|
||||
crouch()
|
||||
else:
|
||||
lean(CENTRE)
|
||||
jump()
|
||||
else:
|
||||
jump_buffer = true
|
||||
get_tree().create_timer(jump_buffer_time).timeout.connect(on_jump_buffer_timeout)
|
||||
|
||||
# Get the input direction and handle the movement/deceleration.
|
||||
# As good practice, you should replace UI actions with custom gameplay actions.
|
||||
var input_dir = Input.get_vector("left", "right", "up", "down")
|
||||
var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
|
||||
velocity.x = move_toward(velocity.x, direction.x * _speed, _acceleration*_delta)
|
||||
velocity.z = move_toward(velocity.z, direction.z * _speed, _acceleration*_delta)
|
||||
|
||||
move_and_slide()
|
||||
|
||||
func jump()->void:
|
||||
velocity.y = jump_velocity
|
||||
jump_available = false
|
||||
|
||||
func _on_sprint_timer_timeout() -> void:
|
||||
sprint_on_cooldown = true
|
||||
get_tree().create_timer(sprint_cooldown_time).timeout.connect(_on_sprint_cooldown_timeout)
|
||||
speed_modifier = NORMAL_speed
|
||||
sprint_time_remaining = 0
|
||||
|
||||
func _on_sprint_cooldown_timeout():
|
||||
sprint_on_cooldown = false
|
||||
|
||||
func _on_coyote_timer_timeout() -> void:
|
||||
jump_available = false
|
||||
|
||||
func on_jump_buffer_timeout()->void:
|
||||
jump_buffer = false
|
||||
@@ -0,0 +1 @@
|
||||
uid://cfdt8t4r8pxel
|
||||
@@ -0,0 +1,137 @@
|
||||
extends Node3D
|
||||
class_name Projectile
|
||||
|
||||
signal Hit_Successfull
|
||||
|
||||
## Can Be Either A Hit Scan or Rigid Body Projectile. If Rigid body is select a Rigid body must be provided.
|
||||
@export_enum ("Hitscan","Rigidbody_Projectile","over_ride") var Projectile_Type: String = "Hitscan"
|
||||
@export var Display_Debug_Decal: bool = true
|
||||
|
||||
@export_category("Rigid Body Projectile Properties")
|
||||
@export var Projectile_Velocity: int
|
||||
@export var Expirey_Time: int = 10
|
||||
@export var Rigid_Body_Projectile: PackedScene
|
||||
@export var pass_through: bool = false
|
||||
|
||||
@onready var Debug_Bullet = preload("res://client/template/Spawnable_Objects/hit_debug.tscn")
|
||||
|
||||
var damage: float = 0
|
||||
var Projectiles_Spawned = []
|
||||
var hit_objects: Array = []
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
get_tree().create_timer(Expirey_Time).timeout.connect(_on_timer_timeout)
|
||||
|
||||
func _Set_Projectile(_damage: int = 0,_spread:Vector2 = Vector2.ZERO, _Range: int = 1000, origin_point: Vector3 = Vector3.ZERO):
|
||||
damage = _damage
|
||||
Fire_Projectile(_spread,_Range,Rigid_Body_Projectile, origin_point)
|
||||
|
||||
func Fire_Projectile(_spread: Vector2 ,_range: int, _proj:PackedScene, origin_point: Vector3):
|
||||
var Camera_Collision = Camera_Ray_Cast(_spread,_range)
|
||||
|
||||
match Projectile_Type:
|
||||
"Hitscan":
|
||||
Hit_Scan_Collision(Camera_Collision, damage,origin_point)
|
||||
"Rigidbody_Projectile":
|
||||
Launch_Rigid_Body_Projectile(Camera_Collision, _proj,origin_point)
|
||||
"over_ride":
|
||||
_over_ride_collision(Camera_Collision, damage)
|
||||
|
||||
func _over_ride_collision(_camera_collision:Array, _damage: float) -> void:
|
||||
pass
|
||||
|
||||
func Camera_Ray_Cast(_spread: Vector2 = Vector2.ZERO, _range: float = 1000):
|
||||
var _Camera = get_viewport().get_camera_3d()
|
||||
var _Viewport = get_viewport().get_size()
|
||||
|
||||
var Ray_Origin = _Camera.project_ray_origin(_Viewport/2)
|
||||
var Ray_End = (Ray_Origin + _Camera.project_ray_normal((_Viewport/2)+Vector2i(_spread)) *_range)
|
||||
var New_Intersection:PhysicsRayQueryParameters3D = PhysicsRayQueryParameters3D.create(Ray_Origin,Ray_End)
|
||||
New_Intersection.set_collision_mask(0b11101101)
|
||||
New_Intersection.set_hit_from_inside(false) # In Jolt this is set to true by defualt
|
||||
|
||||
var Intersection = get_world_3d().direct_space_state.intersect_ray(New_Intersection)
|
||||
|
||||
if not Intersection.is_empty():
|
||||
var Collision = [Intersection.collider,Intersection.position,Intersection.normal]
|
||||
return Collision
|
||||
else:
|
||||
return [null,Ray_End,null]
|
||||
|
||||
func Hit_Scan_Collision(Collision: Array,_damage: float, origin_point: Vector3):
|
||||
var Point = Collision[1]
|
||||
if Collision[0]:
|
||||
Load_Decal(Point, Collision[2])
|
||||
|
||||
if Collision[0].is_in_group("Target"):
|
||||
var Bullet = get_world_3d().direct_space_state
|
||||
|
||||
var Bullet_Direction = (Point - origin_point).normalized()
|
||||
var New_Intersection = PhysicsRayQueryParameters3D.create(origin_point,Point+Bullet_Direction*2)
|
||||
New_Intersection.set_collision_mask(0b11101101)
|
||||
New_Intersection.set_hit_from_inside(false)
|
||||
New_Intersection.set_exclude(hit_objects)
|
||||
var Bullet_Collision = Bullet.intersect_ray(New_Intersection)
|
||||
|
||||
if Bullet_Collision:
|
||||
Hit_Scan_damage(Bullet_Collision.collider, Bullet_Direction,Bullet_Collision.position,_damage)
|
||||
if pass_through and check_pass_through(Bullet_Collision.collider, Bullet_Collision.rid):
|
||||
var pass_through_collision : Array = [Bullet_Collision.collider, Bullet_Collision.position, Bullet_Collision.normal]
|
||||
var pass_through_damage: float = damage/2
|
||||
Hit_Scan_Collision(pass_through_collision,pass_through_damage,Bullet_Collision.position)
|
||||
return
|
||||
queue_free()
|
||||
|
||||
func check_pass_through(collider: Node3D, rid: RID)-> bool:
|
||||
var valid_pass_though: bool = false
|
||||
if collider.is_in_group("Pass Through"):
|
||||
hit_objects.append(rid)
|
||||
valid_pass_though = true
|
||||
return valid_pass_though
|
||||
|
||||
func Hit_Scan_damage(Collider, Direction, Position, _damage):
|
||||
if Collider.is_in_group("Target") and Collider.has_method("Hit_Successful"):
|
||||
Hit_Successfull.emit()
|
||||
Collider.Hit_Successful(_damage, Direction, Position)
|
||||
|
||||
|
||||
func Load_Decal(_pos,_normal):
|
||||
if Display_Debug_Decal:
|
||||
var rd = Debug_Bullet.instantiate()
|
||||
var world = get_tree().get_root()
|
||||
world.add_child(rd)
|
||||
rd.global_translate(_pos+(_normal*.01))
|
||||
|
||||
func Launch_Rigid_Body_Projectile(Collision_Data, _projectile, _origin_point):
|
||||
var _Point = Collision_Data[1]
|
||||
var _Norm = Collision_Data[2]
|
||||
var _proj : RigidBody3D = _projectile.instantiate()
|
||||
_proj.position = _origin_point
|
||||
|
||||
var world = get_tree().get_first_node_in_group("World")
|
||||
world.add_child(_proj)
|
||||
|
||||
_proj.look_at(_Point)
|
||||
Projectiles_Spawned.push_back(_proj)
|
||||
|
||||
_proj.body_entered.connect(_on_body_entered.bind(_proj,_Norm))
|
||||
|
||||
var _Direction = (_Point - _origin_point).normalized()
|
||||
_proj.set_linear_velocity(_Direction*Projectile_Velocity)
|
||||
|
||||
func _on_body_entered(body, _proj, _norm):
|
||||
if body.is_in_group("Target") && body.has_method("Hit_Successful"):
|
||||
body.Hit_Successful(damage)
|
||||
Hit_Successfull.emit()
|
||||
|
||||
Load_Decal(_proj.get_position(),_norm)
|
||||
_proj.queue_free()
|
||||
|
||||
Projectiles_Spawned.erase(_proj)
|
||||
|
||||
if Projectiles_Spawned.is_empty():
|
||||
queue_free()
|
||||
|
||||
func _on_timer_timeout():
|
||||
queue_free()
|
||||
@@ -0,0 +1 @@
|
||||
uid://b26eegme16mfw
|
||||
@@ -0,0 +1,15 @@
|
||||
extends Projectile
|
||||
|
||||
@onready var melee_hitbox: ShapeCast3D = $MeleeHitbox
|
||||
|
||||
func _over_ride_collision(_camera_collision:Array, _damage: float):
|
||||
melee_hitbox.force_shapecast_update()
|
||||
var colliders = melee_hitbox.get_collision_count()
|
||||
for c in colliders:
|
||||
var Target = melee_hitbox.get_collider(c)
|
||||
if Target.is_in_group("Target") and Target.has_method("Hit_Successful"):
|
||||
Hit_Successfull.emit()
|
||||
var Direction = (Target.global_transform.origin - global_transform.origin).normalized()
|
||||
var Position = melee_hitbox.get_collision_point(c)
|
||||
Target.Hit_Successful(_damage, Direction, Position)
|
||||
queue_free()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bfn8eu00l4btq
|
||||
@@ -0,0 +1,23 @@
|
||||
extends Projectile
|
||||
|
||||
@export var explosion: ShapeCast3D
|
||||
|
||||
func _on_body_entered(_body, _proj, _norm):
|
||||
var collision_location: Vector3 = _proj.global_position
|
||||
explosion.global_position = collision_location
|
||||
|
||||
explosion.force_shapecast_update()
|
||||
var targets = explosion.get_collision_count()
|
||||
|
||||
for t in targets:
|
||||
var damage_target = explosion.get_collider(t)
|
||||
var collision_point: Vector3 = explosion.get_collision_point(t)
|
||||
var collision_normal: Vector3 = explosion.get_collision_normal(t)
|
||||
var hit_scan_array = [damage_target,collision_point,collision_normal]
|
||||
Hit_Scan_Collision(hit_scan_array,damage,collision_location)
|
||||
|
||||
_proj.queue_free()
|
||||
Projectiles_Spawned.erase(_proj)
|
||||
|
||||
if Projectiles_Spawned.is_empty():
|
||||
queue_free()
|
||||
@@ -0,0 +1 @@
|
||||
uid://biivm72fp7af8
|
||||
@@ -0,0 +1,23 @@
|
||||
extends Projectile
|
||||
|
||||
@onready var shotgun_pattern: Path2D = $shotgun_pattern
|
||||
@export_range(0.0,20.0) var Randomness = 10.0
|
||||
@export var Split_damage: bool = false
|
||||
var Spray_Vector
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
Spray_Vector = shotgun_pattern.get_curve()
|
||||
return super._ready()
|
||||
|
||||
func _Set_Projectile(_damage: int = 0,_spread:Vector2 = Vector2.ZERO, _Range: int = 1000, origin_point: Vector3 = Vector3.ZERO):
|
||||
randomize()
|
||||
damage = _damage/(max(Spray_Vector.get_point_count()*float(Split_damage),1))
|
||||
|
||||
for point in Spray_Vector.get_point_count():
|
||||
var SprayPoint:Vector2 = Spray_Vector.get_point_position(point)
|
||||
|
||||
SprayPoint.x = SprayPoint.x + randf_range(-Randomness, Randomness)
|
||||
SprayPoint.y = SprayPoint.y + randf_range(-Randomness, Randomness)
|
||||
|
||||
Fire_Projectile(SprayPoint,_Range,Rigid_Body_Projectile, origin_point)
|
||||
@@ -0,0 +1 @@
|
||||
uid://dnh4a4vkl0cl1
|
||||
@@ -0,0 +1,11 @@
|
||||
extends RigidBody3D
|
||||
class_name WeaponPickUp
|
||||
|
||||
@export var weapon: WeaponSlot
|
||||
@export_enum("Weapon","Ammo") var TYPE = "Weapon"
|
||||
|
||||
var Pick_Up_Ready: bool = false
|
||||
|
||||
func _ready():
|
||||
await get_tree().create_timer(2.0).timeout
|
||||
Pick_Up_Ready = true
|
||||
@@ -0,0 +1 @@
|
||||
uid://deaf6cmuofa0
|
||||
@@ -0,0 +1,18 @@
|
||||
extends RigidBody3D
|
||||
|
||||
@export var _weapon_name: String
|
||||
@export var _current_ammo: int
|
||||
@export var _reserve_ammo: int
|
||||
|
||||
const TYPE = "Ammo"
|
||||
|
||||
var Pick_Up_Ready: bool = false
|
||||
|
||||
func _ready():
|
||||
await get_tree().create_timer(2.0).timeout
|
||||
Pick_Up_Ready = true
|
||||
|
||||
func Set_Ammo(w_name: String, c_ammo : int, r_ammo : int):
|
||||
if w_name == _weapon_name:
|
||||
_current_ammo = c_ammo
|
||||
_reserve_ammo = r_ammo
|
||||
@@ -0,0 +1 @@
|
||||
uid://dvxmf6dpx0qdn
|
||||
@@ -0,0 +1,7 @@
|
||||
extends Resource
|
||||
class_name WeaponSlot
|
||||
|
||||
@export var weapon: WeaponResource
|
||||
|
||||
@export var current_ammo: int
|
||||
@export var reserve_ammo: int
|
||||
@@ -0,0 +1 @@
|
||||
uid://bw4j1fbww4k8s
|
||||
@@ -0,0 +1,25 @@
|
||||
[gd_resource type="Resource" script_class="WeaponResource" load_steps=3 format=3 uid="uid://ckwagka0u7opy"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ca7h24lxxrtvx" path="res://client/template/scripts/Weapon_State_Machine/weapon_resource.gd" id="1_2o2yi"]
|
||||
[ext_resource type="PackedScene" uid="uid://6rfh1ejt4m6m" path="res://client/template/Spawnable_Objects/Projectiles_To_Load/melee_projectile.tscn" id="1_pf8fu"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_2o2yi")
|
||||
weapon_name = "MeleeWeapon"
|
||||
pick_up_animation = "blasterP_activate"
|
||||
shoot_animation = "blasterP_strike"
|
||||
reload_animation = ""
|
||||
change_animation = "blasterP_deactivate"
|
||||
drop_animation = ""
|
||||
out_of_ammo_animation = ""
|
||||
melee_animation = "blasterP_strike"
|
||||
has_ammo = false
|
||||
magazine = 0
|
||||
max_ammo = 0
|
||||
damage = 2
|
||||
melee_damage = 1.0
|
||||
auto_fire = true
|
||||
fire_range = 1
|
||||
can_be_dropped = false
|
||||
projectile_to_load = ExtResource("1_pf8fu")
|
||||
incremental_reload = false
|
||||
@@ -0,0 +1,27 @@
|
||||
[gd_resource type="Resource" script_class="WeaponResource" format=3 uid="uid://yc1f2j7pl4vr"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ca7h24lxxrtvx" path="res://client/template/scripts/Weapon_State_Machine/weapon_resource.gd" id="1_jgxyc"]
|
||||
[ext_resource type="PackedScene" uid="uid://dc48o4niy1mok" path="res://client/template/Spawnable_Objects/Projectiles_To_Load/basic_hitscan_projectile.tscn" id="1_l4j8x"]
|
||||
[ext_resource type="PackedScene" uid="uid://cvbk5bxenpxey" path="res://client/template/Spawnable_Objects/Weapons/blaster_I.tscn" id="3_y2nwe"]
|
||||
[ext_resource type="PackedScene" uid="uid://ciu0xkaw0ioef" path="res://client/template/Spawnable_Objects/SprayProfiles/spray_profile_1.tscn" id="4_xc0or"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_jgxyc")
|
||||
weapon_name = "blasterI"
|
||||
pick_up_animation = "Global/blasterI Active"
|
||||
shoot_animation = "Global/blasterI Shoot"
|
||||
reload_animation = "Global/blasterI Reload"
|
||||
change_animation = "Global/blasterI De-Activate"
|
||||
drop_animation = "Global/blasterI Drop"
|
||||
out_of_ammo_animation = "Global/blasterI OOA"
|
||||
melee_animation = "Global/blasterI Melee"
|
||||
magazine = 30
|
||||
max_ammo = 60
|
||||
damage = 10
|
||||
melee_damage = 1.0
|
||||
auto_fire = true
|
||||
fire_range = 100
|
||||
can_be_dropped = true
|
||||
weapon_drop = ExtResource("3_y2nwe")
|
||||
weapon_spray = ExtResource("4_xc0or")
|
||||
projectile_to_load = ExtResource("1_l4j8x")
|
||||
@@ -0,0 +1,29 @@
|
||||
[gd_resource type="Resource" script_class="WeaponResource" load_steps=5 format=3 uid="uid://bwrs8ensewkgc"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://bpxa3je174q43" path="res://client/template/Spawnable_Objects/Projectiles_To_Load/basic_rigid_body_projectile.tscn" id="1_1tllq"]
|
||||
[ext_resource type="Script" uid="uid://ca7h24lxxrtvx" path="res://client/template/scripts/Weapon_State_Machine/weapon_resource.gd" id="2_gpxoe"]
|
||||
[ext_resource type="PackedScene" uid="uid://gtthejj2wmyj" path="res://client/template/Spawnable_Objects/Weapons/blaster_n.tscn" id="3_5a5l8"]
|
||||
[ext_resource type="PackedScene" uid="uid://3mb12hvruajh" path="res://client/template/Spawnable_Objects/SprayProfiles/spray_profile_2.tscn" id="4_p50xh"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("2_gpxoe")
|
||||
weapon_name = "blasterN"
|
||||
pick_up_animation = "Global/blasterL Activate"
|
||||
shoot_animation = "Global/blasterL Shoot"
|
||||
reload_animation = "Global/blasterL Reload"
|
||||
change_animation = "Global/blasterL De-Activate"
|
||||
drop_animation = "Global/blasterL Drop"
|
||||
out_of_ammo_animation = "Global/blasterL OOA"
|
||||
melee_animation = "Global/blasterL Melee"
|
||||
has_ammo = true
|
||||
magazine = 30
|
||||
max_ammo = 60
|
||||
damage = 1
|
||||
melee_damage = 1.0
|
||||
auto_fire = true
|
||||
fire_range = 100
|
||||
can_be_dropped = true
|
||||
weapon_drop = ExtResource("3_5a5l8")
|
||||
weapon_spray = ExtResource("4_p50xh")
|
||||
projectile_to_load = ExtResource("1_1tllq")
|
||||
incremental_reload = false
|
||||
@@ -0,0 +1,29 @@
|
||||
[gd_resource type="Resource" script_class="WeaponResource" load_steps=5 format=3 uid="uid://cd8msvjysh2uv"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ca7h24lxxrtvx" path="res://client/template/scripts/Weapon_State_Machine/weapon_resource.gd" id="1_4ro7h"]
|
||||
[ext_resource type="PackedScene" uid="uid://brvfyahytwd8m" path="res://client/template/Spawnable_Objects/Projectiles_To_Load/shotgun_projectile.tscn" id="1_qi0jo"]
|
||||
[ext_resource type="PackedScene" uid="uid://x8nhs2cer2to" path="res://client/template/Spawnable_Objects/Weapons/blaster_m.tscn" id="3_5hojr"]
|
||||
[ext_resource type="PackedScene" uid="uid://ceuaamuau8pl8" path="res://client/template/Spawnable_Objects/SprayProfiles/spray_profile_3.tscn" id="4_5jfrh"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_4ro7h")
|
||||
weapon_name = "blasterM"
|
||||
pick_up_animation = "blasterM_activate"
|
||||
shoot_animation = "blasterM_Shoot"
|
||||
reload_animation = "blasterM_Reload"
|
||||
change_animation = "blasterM_Deactive"
|
||||
drop_animation = "blasterM_Drop"
|
||||
out_of_ammo_animation = "blasterM_OOA"
|
||||
melee_animation = "blasterM_Melee"
|
||||
has_ammo = true
|
||||
magazine = 9
|
||||
max_ammo = 36
|
||||
damage = 5
|
||||
melee_damage = 1.0
|
||||
auto_fire = true
|
||||
fire_range = 50
|
||||
can_be_dropped = true
|
||||
weapon_drop = ExtResource("3_5hojr")
|
||||
weapon_spray = ExtResource("4_5jfrh")
|
||||
projectile_to_load = ExtResource("1_qi0jo")
|
||||
incremental_reload = true
|
||||
@@ -0,0 +1,26 @@
|
||||
[gd_resource type="Resource" script_class="WeaponResource" format=3 uid="uid://c1jg0ifn7yvve"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ca7h24lxxrtvx" path="res://client/template/scripts/Weapon_State_Machine/weapon_resource.gd" id="1_oxrhl"]
|
||||
[ext_resource type="PackedScene" uid="uid://dc48o4niy1mok" path="res://client/template/Spawnable_Objects/Projectiles_To_Load/basic_hitscan_projectile.tscn" id="1_xr7bm"]
|
||||
[ext_resource type="PackedScene" uid="uid://gtthejj2wmyj" path="res://client/template/Spawnable_Objects/Weapons/blaster_n.tscn" id="3_c38hr"]
|
||||
[ext_resource type="PackedScene" uid="uid://3mb12hvruajh" path="res://client/template/Spawnable_Objects/SprayProfiles/spray_profile_2.tscn" id="4_rk8dx"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_oxrhl")
|
||||
weapon_name = "blaster_n"
|
||||
pick_up_animation = "Global/blasterN Active"
|
||||
shoot_animation = "Global/blasterN Shoot"
|
||||
reload_animation = "Global/blasterN Reload"
|
||||
change_animation = "Global/blasterN De-Activate"
|
||||
drop_animation = "Global/blasterN Drop"
|
||||
out_of_ammo_animation = "Global/blasterN OOA"
|
||||
melee_animation = "Global/blasterN Melee"
|
||||
magazine = 5
|
||||
max_ammo = 15
|
||||
damage = 1
|
||||
melee_damage = 1.0
|
||||
fire_range = 100
|
||||
can_be_dropped = true
|
||||
weapon_drop = ExtResource("3_c38hr")
|
||||
weapon_spray = ExtResource("4_rk8dx")
|
||||
projectile_to_load = ExtResource("1_xr7bm")
|
||||
@@ -0,0 +1,27 @@
|
||||
[gd_resource type="Resource" script_class="WeaponResource" load_steps=4 format=3 uid="uid://dgayjkjx4mckc"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ca7h24lxxrtvx" path="res://client/template/scripts/Weapon_State_Machine/weapon_resource.gd" id="1_321c1"]
|
||||
[ext_resource type="PackedScene" uid="uid://ddfiivxgnakti" path="res://client/template/Spawnable_Objects/Projectiles_To_Load/rocket_launcher_projectile.tscn" id="1_xi5nj"]
|
||||
[ext_resource type="PackedScene" uid="uid://dwgt7gwe3gfyy" path="res://client/template/Spawnable_Objects/Weapons/blasterQ.tscn" id="2_kih88"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_321c1")
|
||||
weapon_name = "blasterQ"
|
||||
pick_up_animation = "Global/blasterQ Active"
|
||||
shoot_animation = "Global/blasterQ Shoot"
|
||||
reload_animation = "Global/blasterQ Reload"
|
||||
change_animation = "Global/blasterQ Deactivate"
|
||||
drop_animation = "Global/blasterQ Drop"
|
||||
out_of_ammo_animation = "Global/blasterQ OOA"
|
||||
melee_animation = "Global/blasterQ Melee"
|
||||
has_ammo = true
|
||||
magazine = 2
|
||||
max_ammo = 6
|
||||
damage = 20
|
||||
melee_damage = 2.0
|
||||
auto_fire = false
|
||||
fire_range = 100
|
||||
can_be_dropped = true
|
||||
weapon_drop = ExtResource("2_kih88")
|
||||
projectile_to_load = ExtResource("1_xi5nj")
|
||||
incremental_reload = false
|
||||
@@ -0,0 +1,263 @@
|
||||
extends Node3D
|
||||
|
||||
signal weapon_changed
|
||||
signal update_ammo
|
||||
signal update_weapon_stack
|
||||
signal hit_successfull
|
||||
signal add_signal_to_hud
|
||||
|
||||
signal connect_weapon_to_hud
|
||||
|
||||
@export var animation_player: AnimationPlayer
|
||||
@export var melee_hitbox: ShapeCast3D
|
||||
@export var max_weapons: int
|
||||
@onready var bullet_point = get_node("%BulletPoint")
|
||||
@onready var debug_bullet = preload("res://client/template/Spawnable_Objects/hit_debug.tscn")
|
||||
|
||||
var next_weapon: WeaponSlot
|
||||
|
||||
#The List of All Available weapons in the game
|
||||
var spray_profiles: Dictionary = {}
|
||||
var _count = 0
|
||||
var shot_tween
|
||||
@export var weapon_stack:Array[WeaponSlot] #An Array of weapons currently in possesion by the player
|
||||
var current_weapon_slot: WeaponSlot = null
|
||||
|
||||
func _ready() -> void:
|
||||
if weapon_stack.is_empty():
|
||||
push_error("Weapon Stack is empty, please populate with weapons")
|
||||
else:
|
||||
animation_player.animation_finished.connect(_on_animation_finished)
|
||||
for i in weapon_stack:
|
||||
initialize(i) #current starts on the first weapon in the stack
|
||||
current_weapon_slot = weapon_stack[0]
|
||||
if check_valid_weapon_slot():
|
||||
enter()
|
||||
update_weapon_stack.emit(weapon_stack)
|
||||
|
||||
func _unhandled_key_input(event: InputEvent) -> void:
|
||||
if not event.is_pressed():
|
||||
return
|
||||
|
||||
if range(KEY_1, KEY_4).has(event.keycode):
|
||||
var _slot_number = (event.keycode - KEY_1)
|
||||
if weapon_stack.size()-1>=_slot_number:
|
||||
exit(weapon_stack[_slot_number])
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("WeaponUp"):
|
||||
if check_valid_weapon_slot():
|
||||
var weapon_index = weapon_stack.find(current_weapon_slot)
|
||||
weapon_index = min(weapon_index+1,weapon_stack.size()-1)
|
||||
exit(weapon_stack[weapon_index])
|
||||
|
||||
if event.is_action_pressed("WeaponDown"):
|
||||
if check_valid_weapon_slot():
|
||||
var weapon_index = weapon_stack.find(current_weapon_slot)
|
||||
weapon_index = max(weapon_index-1,0)
|
||||
exit(weapon_stack[weapon_index])
|
||||
|
||||
if event.is_action_pressed("Shoot"):
|
||||
if check_valid_weapon_slot():
|
||||
shoot()
|
||||
|
||||
if event.is_action_released("Shoot"):
|
||||
if check_valid_weapon_slot():
|
||||
shot_count_update()
|
||||
|
||||
if event.is_action_pressed("Reload"):
|
||||
if check_valid_weapon_slot():
|
||||
reload()
|
||||
|
||||
if event.is_action_pressed("Drop_Weapon"):
|
||||
if check_valid_weapon_slot():
|
||||
drop(current_weapon_slot)
|
||||
|
||||
if event.is_action_pressed("Melee"):
|
||||
if check_valid_weapon_slot():
|
||||
melee()
|
||||
|
||||
func check_valid_weapon_slot()->bool:
|
||||
if current_weapon_slot:
|
||||
if current_weapon_slot.weapon:
|
||||
return true
|
||||
else:
|
||||
push_warning("No Weapon Resource active on the weapon controler.")
|
||||
else:
|
||||
push_warning("No Current Weapon slot active on the weapon controler.")
|
||||
return false
|
||||
|
||||
func initialize(_weapon_slot: WeaponSlot):
|
||||
if !_weapon_slot or !_weapon_slot.weapon:
|
||||
return
|
||||
if _weapon_slot.weapon.weapon_spray:
|
||||
spray_profiles[_weapon_slot.weapon.weapon_name] = _weapon_slot.weapon.weapon_spray.instantiate()
|
||||
connect_weapon_to_hud.emit(_weapon_slot.weapon)
|
||||
|
||||
func enter() -> void:
|
||||
animation_player.queue(current_weapon_slot.weapon.pick_up_animation)
|
||||
weapon_changed.emit(current_weapon_slot.weapon.weapon_name)
|
||||
update_ammo.emit([current_weapon_slot.current_ammo, current_weapon_slot.reserve_ammo])
|
||||
|
||||
func exit(_next_weapon: WeaponSlot) -> void:
|
||||
if _next_weapon != current_weapon_slot:
|
||||
if animation_player.get_current_animation() != current_weapon_slot.weapon.change_animation:
|
||||
animation_player.queue(current_weapon_slot.weapon.change_animation)
|
||||
next_weapon = _next_weapon
|
||||
|
||||
func change_weapon(weapon_slot: WeaponSlot) -> void:
|
||||
current_weapon_slot = weapon_slot
|
||||
next_weapon = null
|
||||
enter()
|
||||
|
||||
func shot_count_update() -> void:
|
||||
shot_tween = get_tree().create_tween()
|
||||
shot_tween.tween_property(self,"_count",0,1)
|
||||
|
||||
func shoot() -> void:
|
||||
if current_weapon_slot.current_ammo != 0 or not current_weapon_slot.weapon.has_ammo:
|
||||
if current_weapon_slot.weapon.incremental_reload and animation_player.current_animation == current_weapon_slot.weapon.reload_animation:
|
||||
animation_player.stop()
|
||||
|
||||
if not animation_player.is_playing():
|
||||
animation_player.play(current_weapon_slot.weapon.shoot_animation)
|
||||
if current_weapon_slot.weapon.has_ammo:
|
||||
current_weapon_slot.current_ammo -= 1
|
||||
|
||||
update_ammo.emit([current_weapon_slot.current_ammo, current_weapon_slot.reserve_ammo])
|
||||
|
||||
if shot_tween:
|
||||
shot_tween.kill()
|
||||
|
||||
var Spread = Vector2.ZERO
|
||||
|
||||
if current_weapon_slot.weapon.weapon_spray:
|
||||
_count = _count + 1
|
||||
Spread = spray_profiles[current_weapon_slot.weapon.weapon_name].Get_Spray(_count, current_weapon_slot.weapon.magazine)
|
||||
|
||||
load_projectile(Spread)
|
||||
else:
|
||||
reload()
|
||||
|
||||
func load_projectile(_spread):
|
||||
var _projectile:Projectile = current_weapon_slot.weapon.projectile_to_load.instantiate()
|
||||
|
||||
_projectile.position = bullet_point.global_position
|
||||
_projectile.rotation = owner.rotation
|
||||
|
||||
bullet_point.add_child(_projectile)
|
||||
add_signal_to_hud.emit(_projectile)
|
||||
var bullet_point_origin = bullet_point.global_position
|
||||
_projectile._Set_Projectile(current_weapon_slot.weapon.damage,_spread,current_weapon_slot.weapon.fire_range, bullet_point_origin)
|
||||
|
||||
func reload() -> void:
|
||||
if current_weapon_slot.current_ammo == current_weapon_slot.weapon.magazine:
|
||||
return
|
||||
elif not animation_player.is_playing():
|
||||
if current_weapon_slot.reserve_ammo != 0:
|
||||
animation_player.queue(current_weapon_slot.weapon.reload_animation)
|
||||
else:
|
||||
animation_player.queue(current_weapon_slot.weapon.out_of_ammo_animation)
|
||||
|
||||
func calculate_reload() -> void:
|
||||
if current_weapon_slot.current_ammo == current_weapon_slot.weapon.magazine:
|
||||
var anim_legnth = animation_player.get_current_animation_length()
|
||||
animation_player.advance(anim_legnth)
|
||||
return
|
||||
|
||||
var Mag_Amount = current_weapon_slot.weapon.magazine
|
||||
|
||||
if current_weapon_slot.weapon.incremental_reload:
|
||||
Mag_Amount = current_weapon_slot.current_ammo+1
|
||||
|
||||
var Reload_Amount = min(Mag_Amount-current_weapon_slot.current_ammo,Mag_Amount,current_weapon_slot.reserve_ammo)
|
||||
|
||||
current_weapon_slot.current_ammo = current_weapon_slot.current_ammo+Reload_Amount
|
||||
current_weapon_slot.reserve_ammo = current_weapon_slot.reserve_ammo-Reload_Amount
|
||||
|
||||
update_ammo.emit([current_weapon_slot.current_ammo, current_weapon_slot.reserve_ammo])
|
||||
shot_count_update()
|
||||
|
||||
func melee() -> void:
|
||||
var Current_Anim = animation_player.get_current_animation()
|
||||
|
||||
if Current_Anim == current_weapon_slot.weapon.shoot_animation:
|
||||
return
|
||||
|
||||
if Current_Anim != current_weapon_slot.weapon.melee_animation:
|
||||
animation_player.play(current_weapon_slot.weapon.melee_animation)
|
||||
if melee_hitbox.is_colliding():
|
||||
var colliders = melee_hitbox.get_collision_count()
|
||||
for c in colliders:
|
||||
var Target = melee_hitbox.get_collider(c)
|
||||
if Target.is_in_group("Target") and Target.has_method("Hit_Successful"):
|
||||
hit_successfull.emit()
|
||||
var Direction = (Target.global_transform.origin - owner.global_transform.origin).normalized()
|
||||
var Position = melee_hitbox.get_collision_point(c)
|
||||
Target.Hit_Successful(current_weapon_slot.weapon.melee_damage, Direction, Position)
|
||||
|
||||
func drop(_slot: WeaponSlot) -> void:
|
||||
if _slot.weapon.can_be_dropped and weapon_stack.size() != 1:
|
||||
var weapon_index = weapon_stack.find(_slot,0)
|
||||
if weapon_index != -1:
|
||||
weapon_stack.pop_at(weapon_index)
|
||||
update_weapon_stack.emit(weapon_stack)
|
||||
|
||||
if _slot.weapon.weapon_drop:
|
||||
var weapon_dropped = _slot.weapon.weapon_drop.instantiate()
|
||||
weapon_dropped.weapon = _slot
|
||||
weapon_dropped.set_global_transform(bullet_point.get_global_transform())
|
||||
get_tree().get_root().add_child(weapon_dropped)
|
||||
|
||||
animation_player.play(current_weapon_slot.weapon.drop_animation)
|
||||
weapon_index = max(weapon_index-1,0)
|
||||
exit(weapon_stack[weapon_index])
|
||||
else:
|
||||
return
|
||||
|
||||
func _on_animation_finished(anim_name):
|
||||
if anim_name == current_weapon_slot.weapon.shoot_animation:
|
||||
if current_weapon_slot.weapon.auto_fire == true:
|
||||
if Input.is_action_pressed("Shoot"):
|
||||
shoot()
|
||||
|
||||
if anim_name == current_weapon_slot.weapon.change_animation:
|
||||
change_weapon(next_weapon)
|
||||
|
||||
if anim_name == current_weapon_slot.weapon.reload_animation:
|
||||
if !current_weapon_slot.weapon.incremental_reload:
|
||||
calculate_reload()
|
||||
|
||||
func _on_pick_up_detection_body_entered(body: RigidBody3D):
|
||||
var weapon_slot = body.weapon
|
||||
for slot in weapon_stack:
|
||||
if slot.weapon == weapon_slot.weapon:
|
||||
var remaining
|
||||
|
||||
remaining = add_ammo(slot, weapon_slot.current_ammo+weapon_slot.reserve_ammo)
|
||||
weapon_slot.current_ammo = min(remaining, slot.weapon.magazine)
|
||||
weapon_slot.reserve_ammo = max(remaining - weapon_slot.current_ammo,0)
|
||||
|
||||
if remaining == 0:
|
||||
body.queue_free()
|
||||
return
|
||||
|
||||
if body.TYPE == "Weapon":
|
||||
if weapon_stack.size() == max_weapons:
|
||||
return
|
||||
|
||||
if body.Pick_Up_Ready == true:
|
||||
var weapon_index = weapon_stack.find(current_weapon_slot)
|
||||
weapon_stack.insert(weapon_index,weapon_slot)
|
||||
update_weapon_stack.emit(weapon_stack)
|
||||
exit(weapon_slot)
|
||||
initialize(weapon_slot)
|
||||
body.queue_free()
|
||||
|
||||
func add_ammo(_weapon_slot: WeaponSlot, ammo: int)->int:
|
||||
var weapon = _weapon_slot.weapon
|
||||
var required = weapon.max_ammo - _weapon_slot.reserve_ammo
|
||||
var remaining = max(ammo - required,0)
|
||||
_weapon_slot.reserve_ammo += min(ammo, required)
|
||||
update_ammo.emit([current_weapon_slot.current_ammo, current_weapon_slot.reserve_ammo])
|
||||
return remaining
|
||||
@@ -0,0 +1 @@
|
||||
uid://cxxpp8cfxb8jp
|
||||
@@ -0,0 +1,15 @@
|
||||
extends RigidBody3D
|
||||
|
||||
signal Hit_Successfull
|
||||
|
||||
var damage: int = 0
|
||||
|
||||
func _on_body_entered(body):
|
||||
if body.is_in_group("Target") && body.has_method("Hit_Successful"):
|
||||
body.Hit_Successful(damage)
|
||||
emit_signal("Hit_Successfull")
|
||||
|
||||
queue_free()
|
||||
|
||||
func _on_timer_timeout():
|
||||
queue_free()
|
||||
@@ -0,0 +1 @@
|
||||
uid://11gya5082xv3
|
||||
@@ -0,0 +1,4 @@
|
||||
extends Sprite3D
|
||||
|
||||
func _on_timer_timeout():
|
||||
queue_free()
|
||||
@@ -0,0 +1 @@
|
||||
uid://dkt6w5wvebi72
|
||||
@@ -0,0 +1,56 @@
|
||||
@icon("res://client/template/scripts/Weapon_State_Machine/weapon_resource_icon.svg")
|
||||
extends Resource
|
||||
|
||||
class_name WeaponResource
|
||||
|
||||
@warning_ignore("unused_signal")
|
||||
signal update_overlay
|
||||
@warning_ignore("unused_signal")
|
||||
signal Zoom
|
||||
|
||||
@export_group("Weapon Animations")
|
||||
##The Reference for the active weapons and pick ups
|
||||
@export var weapon_name: String
|
||||
## The Animation to play when the weapon was picked up
|
||||
@export var pick_up_animation: String
|
||||
## The Animation to play when the weapon is shot
|
||||
@export var shoot_animation: String
|
||||
## The Animation to play when the weapon reload
|
||||
@export var reload_animation: String
|
||||
## The Animation to play when the weapon is Changed
|
||||
@export var change_animation: String
|
||||
## The Animation to play when the weapon is dropped
|
||||
@export var drop_animation: String
|
||||
## The Animation to play when the weapon is out of ammo
|
||||
@export var out_of_ammo_animation: String
|
||||
## The Animation to play when you do the melee strike
|
||||
@export var melee_animation: String
|
||||
|
||||
@export_group("Weapon Stats")
|
||||
## If Uncheck Shoot Function Will skip ammo check
|
||||
@export var has_ammo: bool = true
|
||||
## The Maximum amount that you will reload if zero
|
||||
@export var magazine: int
|
||||
## The Maximum ammo that can be held in reserve.
|
||||
@export var max_ammo: int
|
||||
## The damage that a weapon will do
|
||||
@export var damage: int
|
||||
## The Melee damage that a weapon will do.
|
||||
@export var melee_damage: float
|
||||
## If Auto Fire is set to true the weapon will continuously fire until fire trigger is released
|
||||
@export var auto_fire: bool
|
||||
## The Range that a weapon will fire. Beyong this number no hit will trigger.
|
||||
@export var fire_range: int
|
||||
|
||||
@export_group("Weapon Behaviour")
|
||||
##If Checked the weapon drop scene MUST be provided
|
||||
@export var can_be_dropped: bool
|
||||
## The Rigid body to spawn for the weapon. It should be a Rigid Body of type Weapon_Pick_Up and have a matching weapon_name.
|
||||
@export var weapon_drop: PackedScene
|
||||
## The Spray_Profile to use when firing the weapon. It should be of Type Spray_Profile. This handles the spray calculations and passes back the information to the Projectile to load
|
||||
@export var weapon_spray: PackedScene
|
||||
## The Projectile that will be loaded. Not a Rigid body but class that handles the ray cast processing and can be either hitscan or rigid body. Should be of Type Projectile
|
||||
@export var projectile_to_load: PackedScene
|
||||
## Incremental Reload is for shotgun or sigle item loaded weapons where you can interupt the reload process. If true the Calculate_Reload function on the weapon_state_machine must be called indepently.
|
||||
## For Example: at each step of a shotgun reload the function is called via the animation player.
|
||||
@export var incremental_reload: bool = false
|
||||
@@ -0,0 +1 @@
|
||||
uid://ca7h24lxxrtvx
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="120"
|
||||
height="120"
|
||||
viewBox="0 0 120 120"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
xml:space="preserve"
|
||||
inkscape:export-filename="Secondary/Pistol/Pistol_Icon.png"
|
||||
inkscape:export-xdpi="51.200001"
|
||||
inkscape:export-ydpi="51.200001"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="weapon_resource_icon.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="5.6568543"
|
||||
inkscape:cx="80.786949"
|
||||
inkscape:cy="57.275649"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1027"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer3"
|
||||
showguides="false"><inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid1007" /></sodipodi:namedview><defs
|
||||
id="defs2" /><g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
inkscape:label="Pistol"><path
|
||||
style="fill:#ffffff;stroke:#fdfdfd;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
|
||||
d="M 38.383179,85.989654 H 22.544682 l 0.222115,-1.575826 1.386247,-0.721408 0.05719,-0.847949 -3.175572,-0.262031 -2.445375,-1.472439 -0.110658,-2.665305 2.949314,-7.791327 6.077368,-9.78273 2.91565,-6.907743 0.608662,-4.254202 -0.828252,-2.540647 -1.58005,-1.418767 -2.673229,-0.498449 -1.314259,-0.812357 0.201485,-1.760648 1.061372,-1.444114 v -7.969685 l 3.708858,0.113304 1.021667,-1.839682 h 1.253556 v 1.707812 h 63.130061 l 0.373363,-1.305175 h 1.081786 l 0.566864,1.261097 h 1.459724 v 2.15445 h 1.244851 v 5.163391 h -1.069317 v 6.063658 l -1.496296,1.496296 H 72.741608 l -1.470697,1.683184 -0.364041,1.454612 -0.04419,1.387817 v 2.164182 l 0.423966,1.778557 0.827681,2.201972 H 54.552451 l -2.623079,-0.958804 -2.411172,1.16365 -1.828793,4.83828 0.731728,2.413014 -2.39789,1.427434 -0.93345,2.930928 0.556376,2.135795 c 0,0 -2.498093,1.397347 -2.587302,1.586483 -0.08921,0.189136 -1.241396,3.928682 -1.241396,3.928682 l 0.08274,3.424204 -0.543105,1.129123 -2.746189,0.08859 0.0032,1.06841 h 1.282991 v 2.070652 z"
|
||||
id="path1040"
|
||||
inkscape:label="path1040" /></g></svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
Reference in New Issue
Block a user