giants-godot/entities/character.gd
2021-10-19 00:20:29 +02:00

288 lines
7.0 KiB
GDScript

extends KinematicBody
class_name Character
enum States {
Alive,
Dead
}
enum GroundType {
DIRT,
WATER,
WOOD,
METAL,
SAND,
GRASS
}
signal health_changed
signal died
signal changed_weapon
signal fired(projectile)
signal pickup_weapon
signal jumped
export var max_weapons: int = 5
var weapons: Array = []
var current_weapon: Weapon
var is_in_water: bool = false
export var weapon_refpt_right: NodePath
export var weapon_refpt_left: NodePath
export var water_splash_sound: AudioStreamSample
var animation_blend_vector = Vector2.ZERO
export var animation_tree_path: NodePath
var animation_tree: AnimationTree
export var back_camera_path: NodePath
onready var back_camera = get_node(back_camera_path)
export var head_path: NodePath
onready var head: Spatial = get_node(head_path)
var marker: MeshInstance
# Move
var velocity := Vector3()
var move_axis: Vector3 = Vector3.ZERO
var can_sprint := true
var sprinting := false
const FLOOR_NORMAL := Vector3.UP
export var sprint_speed := 16.0
export var acceleration := 8
export var deacceleration := 6
export(float, 0.0, 1.0, 0.00001) var air_control := 0.001
export var can_jump = true
export var jump_height := 5
export var floor_max_angle := 90.0
export var gravity: float = 40.0
export var walk_speed: float = 2.0
export var ground_friction: float = 3.0
export var animation_blend_speed: float = 10
var ground_snap = Vector3.DOWN
export var has_marker: bool = false
var ground_type = GroundType.DIRT
var footstep_sounds = {}
export var max_health = 100
export var health = 100
var state = States.Alive
var shoot_delay: float = 0
func has_weapon(weapon: Weapon) -> bool:
if get_similar_weapon(weapon):
return true
else:
return false
func get_similar_weapon(weapon: Weapon) -> Weapon:
for weap in weapons:
if weap.name == weapon.name:
return weap
return null
func can_pickup_weapon(weapon: Weapon) -> bool:
return has_weapon(weapon) or len(weapons) < max_weapons
func can_see(other_character: Character) -> bool:
var ray = RayCast.new()
ray.translation = head.translation
ray.cast_to(other_character.head)
var collider = ray.get_collider()
return collider == other_character.head
func can_shoot_delay() -> bool:
# returns true if the character has not shot a bullet recently
return shoot_delay <= 0
func pickup_weapon(weapon: Weapon) -> Weapon:
var similar = get_similar_weapon(weapon)
if similar:
similar.current_ammo = clamp(similar.current_ammo + weapon.current_ammo, 0, weapon.max_ammo)
return similar
else:
# add new weapon
weapons.append(weapon)
weapon.holder = self
weapon.visible = false
if weapon.held_hand == Weapon.WeaponHeld.Left:
get_node(weapon_refpt_left).add_child(weapon)
else:
get_node(weapon_refpt_right).add_child(weapon)
return weapon
func take_damage(hitpts: float):
if state == States.Alive:
health -= hitpts
emit_signal("health_changed")
if health <= 0:
die()
func get_current_weapon():
return current_weapon
func die():
state = States.Dead
emit_signal("died")
print("Me ded")
func set_weapon_slot(slot: int):
if current_weapon:
current_weapon.visible = false
current_weapon.disconnect("fired", self, "weapon_fired")
var newweapon = weapons[slot]
current_weapon = newweapon
current_weapon.visible = true
current_weapon.play_switch_sound()
current_weapon.connect("fired", self, "weapon_fired")
func play_footstep():
if ground_type in footstep_sounds:
var sound = footstep_sounds[ground_type][randi() % len(footstep_sounds[ground_type])]
Globals.play_sound(sound, Globals.Bus.Sounds, self)
func get_current_weapon_slot():
return get_weapon_slot(current_weapon)
func get_weapon_slot(weapon: Weapon):
var i = 0
for weap in weapons:
if weap == weapon:
return i
i+=1
return i
func create_weapon(weapon_scene: PackedScene):
var weapon = weapon_scene.instance()
if weapon is Weapon:
return weapon
else:
weapon.queue_free()
return null
func get_next_weapon_slot():
var current = get_current_weapon_slot()
if current + 1 < len(weapons):
return current + 1
else:
return 0
func switch_next_weapon_slot():
var next_slot = get_next_weapon_slot()
if next_slot == get_current_weapon_slot():
return
set_weapon_slot(next_slot)
func enter_water(force: float):
is_in_water = true
ground_type = GroundType.WATER
if abs(force) >= 50:
Globals.play_sound(water_splash_sound, Globals.Bus.Sounds, self)
func exit_water(force: float):
is_in_water = false
ground_type = GroundType.GRASS
func _ready():
if animation_tree_path:
animation_tree = get_node(animation_tree_path)
if has_marker:
marker = preload("res://ui/player_marker.tscn").instance()
head.add_child(marker)
marker.transform.origin = Vector3(0, 0.337, 0)
marker.mesh.size = Vector2.ONE * 5
func _process(delta):
if shoot_delay > 0:
shoot_delay -= delta
func _physics_process(delta):
if Engine.editor_hint:
return
walk(delta)
if not animation_tree:
return
# Blend nicely between animations
var anim_vector = Vector2(move_axis.y, move_axis.x)
var res = animation_blend_vector.linear_interpolate(anim_vector, delta*animation_blend_speed)
animation_tree.set("parameters/movement/blend_position", res)
animation_blend_vector = res
func fire_pressed():
if current_weapon and can_shoot_delay():
current_weapon.activate(Globals.target_position["position"])
func fire_released():
if current_weapon:
current_weapon.deactivate(Globals.target_position["position"])
func jump():
if can_jump and is_on_floor():
velocity.y = jump_height
emit_signal("jumped")
func set_sprint(sprint_state: bool):
if can_sprint:
sprinting = sprint_state
func weapon_fired():
shoot_delay = current_weapon.repeat_time
func walk(delta: float) -> void:
# Input
var direction = Vector3()
var aim: Basis = get_global_transform().basis
if move_axis.x >= 0.5:
direction -= aim.z
if move_axis.x <= -0.5:
direction += aim.z
if move_axis.y <= -0.5:
direction -= aim.x
if move_axis.y >= 0.5:
direction += aim.x
direction.y = 0
direction = direction.normalized()
# Apply Gravity
velocity.y -= gravity * delta
# if is_in_water:
# velocity.y = 0
# Sprint
var _speed: float
if sprinting:
# if can_sprint and move_axis.x == 1:
_speed = sprint_speed
else:
_speed = walk_speed
# Acceleration and Deacceleration
# where would the player go
var _temp_vel: Vector3 = velocity
_temp_vel.y = 0
var _target: Vector3 = direction * _speed
var _temp_accel: float
if direction.dot(_temp_vel) > 0:
_temp_accel = acceleration
else:
_temp_accel = deacceleration
if not is_on_floor():
_temp_accel *= air_control
# interpolation
_temp_vel = _temp_vel.linear_interpolate(_target, _temp_accel * delta)
velocity.x = _temp_vel.x
velocity.z = _temp_vel.z
# clamping (to stop on slopes)
if direction.dot(velocity) == 0:
var _vel_clamp := 1
if velocity.x < _vel_clamp and velocity.x > -_vel_clamp:
velocity.x = 0
if velocity.z < _vel_clamp and velocity.z > -_vel_clamp:
velocity.z = 0
# Move
velocity = move_and_slide(velocity, FLOOR_NORMAL, true, 4, deg2rad(floor_max_angle))
# velocity.y = move_and_slide_with_snap(velocity, _snap, FLOOR_NORMAL, true, 4, deg2rad(floor_max_angle)).y