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" # (A barrow guard, or a drowned sailor at a treasure dig: treasure.gd.) var s: String = def.name match state: "rest": s += " (still... for now)" "charge": s += " (coming for you!)" "home": s += " (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 %s" % String(def.name).to_lower()) "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