96 lines
3.0 KiB
GDScript
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 = {}
|