Files
Cinderwild/scripts/creatures/crow.gd

116 lines
3.6 KiB
GDScript

extends "res://scripts/creatures/creature.gd"
## A crow (docs/PROGRESSION_DESIGN.md §4): sent by thieves.gd, by day, for
## food left lying on the ground. It flies in, lands on it and pecks it away
## a bit at a time, and takes off again if anyone comes within SCARE. Then
## it flies off over the trees and is gone. A closed chest keeps food safe.
## An arrow brings it down (feathers).
const SCARE := 7.0
const PECK_EVERY := 3.0
const FLY_SPEED := 9.0
const CRUISE := 7.0 ## height above the ground while flying in
const LEAVE_HEIGHT := 14.0
var thieves: Node ## thieves.gd
var food: Node3D = null
var _goal := Vector3.ZERO
var _peck_anim := 0.0
func display_name() -> String:
return "Crow"
func interact_prompt(p: Node) -> String:
if is_dead():
return "Crow [E] Pluck"
return {"peck": "Crow (pecking at your food!)", "fly": "Crow (circling your food)"}.get(state, "Crow")
func _flying() -> bool:
return state in ["fly", "fly_off"]
func _think(dt: float) -> void:
match state:
"fly":
if not thieves.still_there(food) or thieves.watched(food.global_position, SCARE):
_leave()
return
_goal = food.global_position
var ground: float = terrain.sample_height(global_position.x, global_position.z)
if Vector2(_goal.x - global_position.x, _goal.z - global_position.z).length() < 0.35 and global_position.y - ground < 0.15:
state = "peck"
_state_t = PECK_EVERY
thieves.landed(self, food)
"peck":
if not thieves.still_there(food) or thieves.watched(global_position, SCARE):
_leave()
return
_state_t -= dt
if _state_t <= 0.0:
_state_t = PECK_EVERY
_peck_anim = 0.4
if thieves.peck(self, food): # the last of it
_leave()
"fly_off":
if thieves.out_of_sight(global_position):
thieves.gone(self)
_:
_leave()
func _leave() -> void:
state = "fly_off"
food = null
var p: Node3D = world.nearest_player(global_position)
var away := Vector3(1, 0, 0)
if p != null:
away = global_position - p.global_position
away.y = 0.0
if away.length() < 0.1:
away = Vector3(1, 0, 0)
_goal = global_position + away.normalized() * 120.0
## Straight-line flight (no steering round trees: it flies over them).
func _move(dt: float) -> void:
if not _flying():
speed = 0.0
return
var to := _goal - global_position
to.y = 0.0
var flat := to.length()
var spd := FLY_SPEED
if state == "fly" and flat < 6.0:
spd = maxf(flat * 1.5, 1.2) # glide in to land
var p := global_position
if flat > 0.02:
yaw = rotate_toward(yaw, atan2(-to.x, -to.z), 8.0 * dt)
p += to / flat * minf(spd * dt, flat)
var ground: float = terrain.sample_height(p.x, p.z)
var alt := LEAVE_HEIGHT
if state == "fly":
alt = CRUISE * clampf((flat - 0.3) / 8.0, 0.0, 1.0)
p.y = maxf(move_toward(global_position.y, maxf(ground, terrain.water_level) + alt, 5.0 * dt), ground)
global_position = p
rotation.y = yaw
speed = spd
## Wings out and flapping in the air, folded on the ground; a dip of the
## head when it pecks.
func _animate(dt: float) -> void:
var flying := _flying() or (puppet and speed > 0.5)
_phase += dt * 16.0
for l in _legs:
var s := -1.0 if l.position.x < 0.0 else 1.0
l.rotation.z = s * ((PI * 0.5 + sin(_phase) * 0.7) if flying else 0.2)
_peck_anim = maxf(_peck_anim - dt, 0.0)
if is_dead():
return
_visual.rotation.x = 0.5 * sin(_peck_anim / 0.4 * PI) if state == "peck" else 0.0
func _react(_from: Vector3) -> void:
_leave()
## Brought down out of the air: it lands where it fell.
func _die(from: Vector3) -> void:
super._die(from)
var p := global_position
p.y = terrain.sample_height(p.x, p.z)
global_position = p