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)