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 = {}