116 lines
3.6 KiB
GDScript
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
|