Files
Emberwild/scripts/creatures/fox.gd

96 lines
3.0 KiB
GDScript

extends "res://scripts/creatures/creature.gd"
## A fox (docs/PROGRESSION_DESIGN.md §4): sent by thieves.gd at dusk and
## night for food left lying on the ground. It slinks up, grabs the whole
## stack in its jaws and runs off with it. Come within WARY while it's
## creeping in and it bolts empty-mouthed. Kill it while it's carrying and
## it drops what it took; let it get away and the food's gone.
const WARY := 10.0
const SNEAK_SPEED := 3.4
const GIVE_UP := 30.0 ## seconds running with it before it's gone for good
var thieves: Node
var food: Node3D = null
var carry: Dictionary = {} ## {id, count, extra} once it has the food
var _escape := Vector3.ZERO
var _goal_d := 0.0
func display_name() -> String:
return "Fox"
func interact_prompt(p: Node) -> String:
if is_dead():
return "Fox carcass [E] Butcher"
if not carry.is_empty():
return "Fox (running off with your %s!)" % ItemDB.item_name(String(carry.id)).to_lower()
return "Fox (sneaking up on your food)" if state == "sneak" else "Fox"
func _think(dt: float) -> void:
var pp: Vector3 = player.global_position
_state_t -= dt
match state:
"sneak":
if not thieves.still_there(food):
_bolt(pp)
return
if thieves.watched(global_position, WARY):
_bolt(pp)
return
var fp: Vector3 = food.global_position
_go_to(fp)
if Vector2(fp.x - global_position.x, fp.z - global_position.z).length() < 0.9:
carry = thieves.grab(self, food)
food = null
state = "carry"
_state_t = GIVE_UP
_escape = _away_point(pp, 60.0)
"carry", "flee":
_go_to(_escape)
if thieves.out_of_sight(global_position) or _state_t <= 0.0 or _goal_d < 1.5:
thieves.gone(self)
_:
_bolt(pp)
func _bolt(from: Vector3) -> void:
state = "flee" if carry.is_empty() else "carry"
food = null
_state_t = GIVE_UP
_escape = _away_point(from, 60.0)
## A point `d` away from `from`, through the fox, kept inside the valley.
func _away_point(from: Vector3, d: float) -> Vector3:
var away := global_position - from
away.y = 0.0
if away.length() < 0.1:
away = Vector3(1, 0, 0)
away = away.normalized()
for turn in [0.0, 0.6, -0.6, 1.2, -1.2, PI]:
var p: Vector3 = global_position + away.rotated(Vector3.UP, turn) * d
if terrain.inside_boundary(p.x, p.z, 8.0) and terrain.sample_height(p.x, p.z) > terrain.water_level + 0.3:
return p
return global_position + away * d * 0.3
func _go_to(p: Vector3) -> void:
var to := p - global_position
to.y = 0.0
if to.length() > 0.3:
steer(atan2(-to.x, -to.z))
_goal_d = to.length()
func _desired_speed() -> float:
match state:
"sneak": return 0.0 if _goal_d < 0.5 else SNEAK_SPEED
"carry", "flee": return def.run
return 0.0
func _react(from: Vector3) -> void:
_bolt(from)
## Killed with the food in its jaws: it drops it.
func _die(from: Vector3) -> void:
super._die(from)
if not carry.is_empty() and not puppet:
var at := global_position + Vector3(0, 0.3, 0)
world.drop_item(String(carry.id), int(carry.count), Transform3D(Basis(), at), carry.get("extra", {}))
carry = {}