114 lines
3.7 KiB
GDScript
114 lines
3.7 KiB
GDScript
extends "res://scripts/creatures/creature.gd"
|
|
## A crab (0.6.0, the Cove only): shore.gd brings them up out of the sea
|
|
## onto the beach at dusk. They scuttle about the sand sideways and keep to
|
|
## it. Walk within NIP of one and it comes at you, claws up, and pinches
|
|
## (not much, but it adds up with three of them about). Leave it NIP_GIVE_UP
|
|
## behind and it forgets you. Hit one hard enough and it runs for it. At
|
|
## dawn they all go back into the sea. A dead one gives crab meat.
|
|
|
|
const NIP := 2.2 ## this close and it comes for you
|
|
const NIP_GIVE_UP := 6.0 ## this far and it lets you go
|
|
const HOME_FOR := 40.0 ## at dawn: this long to reach the water before it simply goes
|
|
|
|
func interact_prompt(p: Node) -> String:
|
|
if is_dead():
|
|
return "Crab [E] Crack it open"
|
|
match state:
|
|
"charge": return "Crab (claws up!)"
|
|
"home": return "Crab (heading back to the sea)"
|
|
return "Crab (keep your distance, or it'll pinch)"
|
|
|
|
## Daytime: back to the sea.
|
|
func _daytime() -> bool:
|
|
var h: float = world.day_night.hours()
|
|
return h >= 6.5 and h < 17.5
|
|
|
|
## Which way the sea is: straight out from the middle of the bay.
|
|
func _seaward() -> Vector3:
|
|
var a := deg_to_rad(float(terrain.beach_angle_deg))
|
|
return Vector3(cos(a), 0.0, sin(a))
|
|
|
|
func _think(dt: float) -> void:
|
|
if state != "home" and state != "flee" and _daytime():
|
|
state = "home"
|
|
_state_t = HOME_FOR
|
|
var pp: Vector3 = player.global_position
|
|
var d := global_position.distance_to(pp)
|
|
match state:
|
|
"home":
|
|
var to := _seaward()
|
|
steer(atan2(-to.x, -to.z))
|
|
_state_t -= dt
|
|
if _state_t <= 0.0 or global_position.y < terrain.water_level - 0.25:
|
|
queue_free() # (into the waves)
|
|
return
|
|
"idle", "wander":
|
|
if d < NIP:
|
|
state = "charge"
|
|
_aggro = def.aggro
|
|
_attack_cd = 0.5 # (a moment to see it coming)
|
|
return
|
|
"charge":
|
|
if d < NIP_GIVE_UP:
|
|
_aggro = def.aggro # (it keeps on at you while you're close)
|
|
else:
|
|
_aggro = 0.0
|
|
super._think(dt)
|
|
|
|
func _desired_speed() -> float:
|
|
if state == "home":
|
|
return def.walk * 1.6
|
|
return super._desired_speed()
|
|
|
|
## Keeps to the sand (and out of the water) until it's going home.
|
|
func _blocked(dir: Vector3) -> bool:
|
|
var probe := global_position + dir * (0.8 + speed * 0.15)
|
|
if not terrain.inside_boundary(probe.x, probe.z, 4.0):
|
|
return true
|
|
if state == "home":
|
|
return false # (straight down the beach and into the sea)
|
|
if not terrain.on_sand(probe.x, probe.z):
|
|
return true
|
|
return super._blocked(dir)
|
|
|
|
## Hit: it fights back while it can, then runs.
|
|
func _react(from: Vector3) -> void:
|
|
if hp > def.hp * 0.35:
|
|
state = "charge"
|
|
_aggro = def.aggro
|
|
_attack_cd = 0.3
|
|
else:
|
|
_start_flee(from)
|
|
_state_t = 5.0
|
|
|
|
## Sideways, legs a-flicker; the claws come up when it's after you.
|
|
func _animate(dt: float) -> void:
|
|
_visual.rotation.y = PI * 0.5 # (it walks sideways)
|
|
if is_dead():
|
|
return
|
|
var k := clampf(speed / maxf(def.run, 0.01), 0.0, 1.0)
|
|
_phase += dt * (6.0 + speed * 14.0)
|
|
for i in range(_legs.size()):
|
|
var l: Node3D = _legs[i]
|
|
var s := -1.0 if l.position.x < 0.0 else 1.0
|
|
l.rotation.z = s * (1.0 + sin(_phase + i * 1.9) * (0.08 + 0.3 * k))
|
|
l.rotation.x = cos(_phase + i * 1.9) * 0.25 * k
|
|
var up := 0.12 if state == "charge" or (puppet and speed > 1.5) else 0.0
|
|
_visual.rotation.x = lerpf(_visual.rotation.x, -up, minf(dt * 6.0, 1.0))
|
|
_visual.position.y = absf(sin(_phase * 0.5)) * 0.01 * k
|
|
|
|
## A crab doesn't topple onto its side: it ends up on its back.
|
|
func _die(_from: Vector3) -> void:
|
|
state = "dead"
|
|
speed = 0.0
|
|
hp = 0.0
|
|
_flash = 0.0
|
|
_set_material(_mat)
|
|
remove_from_group("creature")
|
|
add_to_group("carcass")
|
|
collision_layer = 2
|
|
_visual.rotation = Vector3(0.0, PI * 0.5, PI)
|
|
_visual.position.y = 0.1
|
|
if not puppet:
|
|
died.emit(self)
|