P3 alpha: wolf moon every 7th night, the alpha breaks wooden doors, iron-banded door, broken-door repair, alpha pelt and cloak (protocol 7)

This commit is contained in:
Paul Bailey
2026-11-05 01:40:52 +00:00
parent 411bfef34a
commit fc58c8d0e1
18 changed files with 742 additions and 40 deletions

View File

@@ -15,8 +15,13 @@ extends Node3D
## While hunting the pack circles you and lets one wolf at a time dart in
## (every few seconds, slower if you hold a torch). Near a lit campfire it
## circles wide around the fire and never closes in.
##
## Every 7th night is a wolf moon (day_night.gd turns the moon red): an
## alpha comes down from the hills with the pack. It leaves at dawn whether
## or not you killed it, and comes back the next wolf moon (wolf.gd).
const WolfScript = preload("res://scripts/creatures/wolf.gd")
const DayNight = preload("res://scripts/world/day_night.gd")
const DEN_DAY := 3 ## wolves arrive in the den
const HUNT_DAY := 4 ## first night they hunt
@@ -42,6 +47,14 @@ var _warned := {} ## day -> true once that night's toast was shown
var _howl_left := 0.0
var _audio: AudioStreamPlayer3D
static var _howl: AudioStreamWAV
var alpha: Node = null ## tonight's alpha (wolf moon)
var _alpha_day := -1 ## the night it came
var _told := {} ## "door id:outcome" -> true: door toasts already shown
## Night `day` is a wolf moon (once the pack hunts at all). The same rule as
## day_night.gd's red moon.
func wolf_moon(day: int) -> bool:
return day >= hunt_day and DayNight.wolf_moon_day(day)
func setup(w: Node3D, d: Node3D) -> void:
world = w
@@ -139,6 +152,10 @@ func step(dt: float) -> void:
func _enter(m: String, day: int) -> void:
mode = m
if m == "hunt" and wolf_moon(day) and _alpha_day != day:
spawn_alpha(day)
elif m != "hunt":
_alpha_leaves()
if m == "none":
for w in members:
if is_instance_valid(w):
@@ -160,6 +177,8 @@ func _warn(day: int) -> void:
Actions.tell_all("Wolves are howling up in the hills. They'll come hunting soon.")
elif day == hunt_day:
Actions.tell_all("The wolves are hunting tonight. Stay near a fire or behind closed walls.")
elif wolf_moon(day):
Actions.tell_all("The moon is red: a wolf moon. Something big is hunting with the pack tonight.")
_howl_left = _rng.randf_range(2.0, 6.0)
## Brings the pack up to size, wolves lying in the den. `force` ignores
@@ -189,6 +208,65 @@ func fill_den(force: bool) -> void:
need -= 1
i += 1
## The alpha comes out of the den's mouth with the pack.
func spawn_alpha(day: int) -> Node:
_alpha_leaves()
_alpha_day = day
var w: AnimatableBody3D = WolfScript.new()
w.setup("alpha", world, den.mouth_pos(), _rng.randi())
w.pack = self
w.slot = 4
w.state = "stalk"
w.yaw = den_yaw
w.rotation.y = w.yaw
add_child(w)
members.append(w)
alpha = w
return w
## At dawn it goes back into the hills (a carcass stays for butchering).
func _alpha_leaves() -> void:
if alpha != null and is_instance_valid(alpha) and not alpha.is_dead():
members.erase(alpha)
alpha.queue_free()
alpha = null
func alpha_alive() -> bool:
return alpha != null and is_instance_valid(alpha) and not alpha.is_dead()
## The door the alpha `w` should go for to get at `target`: the closed door
## nearest `target` (within `r`), a little biased toward ones nearer `w`.
## `open_ones`: the nearest open or broken door instead (a way in).
func door_for(w: Node3D, target: Node3D, r: float, open_ones: bool = false) -> Node3D:
var best: Node3D = null
var best_s := INF
var tp: Vector3 = target.global_position
for p in world.build.build_pieces():
if not p.has_method("bash") or p.blocks() == open_ones:
continue
var d: float = p.global_position.distance_to(tp)
if d > r:
continue
var score: float = d + 0.3 * p.global_position.distance_to(w.global_position)
if score < best_s:
best_s = score
best = p
return best
## The alpha hits `door`. Tells the player it's after (once per door and
## outcome); on a server, every game hears the door broke.
func bash_door(w: Node, door: Node3D, damage: float) -> void:
var res: String = door.bash(damage)
var key := "%d:%s" % [door.get_instance_id(), res]
if not _told.has(key):
_told[key] = true
match res:
"hit": Actions.tell(w.player, "Something is battering at your door!")
"held": Actions.tell(w.player, "Something is battering at your door, but the iron bands hold.")
"broke": Actions.tell(w.player, "The alpha has smashed your door in!")
if res == "broke" and world.server_mode:
WorldNet.piece_state(door)
func home_slot(w: Node) -> Vector3:
return den.slot_pos(w.slot)
@@ -320,8 +398,9 @@ func howl() -> void:
return
if _audio == null:
return
_audio.global_position = (live[0] as Node3D).global_position + Vector3(0, 1, 0)
_audio.pitch_scale = _rng.randf_range(0.9, 1.1)
var from: Node3D = alpha if alpha_alive() else live[0] # (the alpha's howl is deeper)
_audio.global_position = from.global_position + Vector3(0, 1, 0)
_audio.pitch_scale = _rng.randf_range(0.68, 0.76) if alpha_alive() else _rng.randf_range(0.9, 1.1)
_audio.play()
## A synthesised wolf howl: a rising then falling tone with vibrato and a