Files
Emberwild/scripts/creatures/guard.gd

111 lines
4.0 KiB
GDScript

extends "res://scripts/creatures/creature.gd"
## A barrow guard (docs/PROGRESSION_DESIGN.md §4): a skeleton standing at its
## post in the barrow (barrow.gd) until something wakes it: someone inside
## the stone ring at night (or any time after day 10), someone opening the
## coffin, or a blow. Then it comes for you. It's slow, so you can walk away
## from it, but it hits hard and it isn't afraid of fire or torches. It
## won't follow you far from the barrow; it goes back to its post and stands
## there again.
var barrow: Node ## barrow.gd
var post := Vector3.ZERO
var post_yaw := 0.0
var _goal_d := 0.0
func display_name() -> String:
return def.name
func interact_prompt(p: Node) -> String:
if is_dead():
return "Bones [E] Search"
var s := "Barrow guard"
match state:
"rest": s = "Barrow guard (still... for now)"
"charge": s = "Barrow guard (coming for you!)"
"home": s = "Barrow guard (going back to its post)"
if not Actions.melee_stats(p).armed and not ItemDB.get_item(p.held_item_id()).has("bow"):
s += " (you need a weapon: a club cracks bone)"
return s
func _think(dt: float) -> void:
var pp: Vector3 = player.global_position
var d := global_position.distance_to(pp)
_state_t -= dt
match state:
"rest":
target_yaw = post_yaw
if barrow.wakes_for(player):
state = "charge"
_attack_cd = 0.8
"charge":
if player.survival.dead or barrow.center().distance_to(pp) > barrow.LEASH or not barrow.alarmed:
state = "home"
return
var to := pp - global_position
steer(atan2(-to.x, -to.z))
_goal_d = d
if d < def.attack_range and _attack_cd <= 0.0 and _can_reach(player):
_attack_cd = def.attack_cd
player.survival.damage(def.damage * world.difficulty_mult("damage"), global_position, "a barrow guard")
"home":
if barrow.wakes_for(player) and barrow.center().distance_to(pp) < barrow.LEASH:
state = "charge"
return
var to := post - global_position
to.y = 0.0
_goal_d = to.length()
if _goal_d < 0.4:
state = "rest"
else:
steer(atan2(-to.x, -to.z))
_:
state = "home"
func _desired_speed() -> float:
match state:
"charge": return 0.0 if _goal_d < def.attack_range * 0.8 else def.run
"home": return 0.0 if _goal_d < 0.4 else def.walk * 1.5
return 0.0
## Nothing solid between its sword and you.
func _can_reach(p: Node3D) -> bool:
var from := global_position + Vector3(0, 1.2, 0)
var to: Vector3 = p.global_position + Vector3(0, 1.0, 0)
var q := PhysicsRayQueryParameters3D.create(from, to, 1)
q.exclude = [get_rid(), p.get_rid(), terrain.get_node("TerrainBody").get_rid()]
return get_world_3d().direct_space_state.intersect_ray(q).is_empty()
## In the barrow, what's ahead is measured from the stone floor (the ground
## under it is lower, and a probe aimed at that would hit the floor slab).
func _blocked(dir: Vector3) -> bool:
var probe := global_position + dir * (1.4 + speed * 0.15)
var f: float = barrow.floor_y_at(probe)
if f == -INF and barrow.floor_y_at(global_position) == -INF:
return super._blocked(dir)
if not terrain.inside_boundary(probe.x, probe.z, 6.0):
return true
var h: float = f if f > -INF else terrain.sample_height(probe.x, probe.z)
if absf(h - global_position.y) > 1.1:
return true
var q := PhysicsRayQueryParameters3D.create(global_position + Vector3(0, 0.45, 0), Vector3(probe.x, h + 0.45, probe.z), 1)
q.exclude = [get_rid(), terrain.get_node("TerrainBody").get_rid()]
return not get_world_3d().direct_space_state.intersect_ray(q).is_empty()
## Inside the barrow it walks on the stone floor, not the ground under it.
func _move(dt: float) -> void:
super._move(dt)
var f: float = barrow.floor_y_at(global_position)
if f > global_position.y:
global_position.y = f
func _react(_from: Vector3) -> void:
barrow.alarm()
state = "charge"
_attack_cd = maxf(_attack_cd, 0.6)
## Standing upright, a blow knocks it to pieces (no toppling sideways).
func _die(from: Vector3) -> void:
super._die(from)
_visual.rotation = Vector3(-PI * 0.5, 0, 0)
_visual.position.y = 0.15