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