Audio: mood music, ambience, sound effects and creature voices (Kenney CC0 + synthesised); protocol 13
This commit is contained in:
@@ -217,11 +217,14 @@ func step(dt: float) -> void:
|
||||
_aggro = maxf(_aggro - dt, 0.0)
|
||||
# Who it's reacting to: the player (or on a server, the nearest one).
|
||||
var t := _pick_target()
|
||||
var was := state
|
||||
var cd0 := _attack_cd
|
||||
if t != null:
|
||||
player = t
|
||||
_think(dt)
|
||||
elif state in ["flee", "charge"]:
|
||||
state = "idle" # nobody about
|
||||
_voice(dt, was, cd0)
|
||||
_move(dt)
|
||||
_animate(dt)
|
||||
|
||||
@@ -410,6 +413,13 @@ func take_hit(damage: float, from: Vector3, embed_id: String = "") -> void:
|
||||
hp -= damage
|
||||
if not embed_id.is_empty():
|
||||
embedded[embed_id] = embedded.get(embed_id, 0) + 1
|
||||
Sfx.at("hit_bone" if bool(def.get("bone", false)) else "hit_flesh", global_position + Vector3(0, 0.6, 0))
|
||||
var cry := String(VOICES.get(kind, {}).get("hurt", ""))
|
||||
if not cry.is_empty() and _hurt_voice_cd <= 0.0:
|
||||
Sfx.at(cry, global_position + Vector3(0, 0.8, 0))
|
||||
_hurt_voice_cd = 0.6 # (a flurry of arrows: one cry, not five)
|
||||
if hp <= 0.0:
|
||||
Sfx.at("creature_die", global_position)
|
||||
_flash = 0.15
|
||||
_set_material(_hit_mat)
|
||||
_burst()
|
||||
@@ -491,3 +501,60 @@ func _burst() -> void:
|
||||
p.global_position = global_position + Vector3(0, Meshes.build(kind).height * 0.6, 0)
|
||||
p.emitting = true
|
||||
p.finished.connect(p.queue_free)
|
||||
|
||||
# --- voices (sfx.gd) ---------------------------------------------------------------------
|
||||
|
||||
## What each creature sounds like: "alert" when it turns on someone, "angry"
|
||||
## now and then while it's after them, "attack" as it strikes, "hurt" when hit,
|
||||
## and "idle" now and then while it's minding its own business. Played by
|
||||
## whoever runs the creature (single-player, or the server, which sends them on).
|
||||
const VOICES := {
|
||||
"wolf": {"alert": "wolf_growl", "angry": "wolf_growl", "attack": "wolf_snarl", "hurt": "wolf_yelp"},
|
||||
"alpha": {"alert": "alpha_growl", "angry": "alpha_growl", "attack": "alpha_snarl", "hurt": "alpha_yelp"},
|
||||
"spirit_wolf": {"alert": "spirit_growl", "angry": "spirit_growl", "attack": "spirit_snarl", "hurt": "spirit_growl"},
|
||||
"bear": {"alert": "bear_roar", "angry": "bear_grunt", "attack": "bear_grunt", "hurt": "bear_hurt", "idle": "bear_grunt"},
|
||||
"boar": {"alert": "boar_grunt", "angry": "boar_grunt", "attack": "boar_grunt", "hurt": "boar_squeal", "idle": "boar_grunt"},
|
||||
"old_stag": {"alert": "stag_bellow", "angry": "stag_bellow", "attack": "stag_snort", "hurt": "stag_snort"},
|
||||
"guard": {"alert": "guard_rattle", "angry": "guard_rattle", "attack": "guard_rattle"},
|
||||
"crow": {"alert": "crow_caw", "hurt": "crow_caw", "idle": "crow_caw"},
|
||||
"fox": {"alert": "fox_yip", "hurt": "fox_yip"},
|
||||
}
|
||||
## States a creature is in when it's after someone (music.gd's list too).
|
||||
const ANGRY := ["charge", "stalk", "lunge", "bash", "stomp", "telegraph", "warn", "raid", "summon"]
|
||||
const ANGRY_EVERY := [4.0, 8.0]
|
||||
const IDLE_EVERY := [18.0, 45.0]
|
||||
var _voice_left := -1.0
|
||||
var _hurt_voice_cd := 0.0
|
||||
## Its own dice, so voices never change what the creature does (the
|
||||
## behaviour tests replay the same choices from the same seed).
|
||||
var _voice_rng := RandomNumberGenerator.new()
|
||||
|
||||
func _voice(dt: float, was: String, cd0: float) -> void:
|
||||
_hurt_voice_cd = maxf(_hurt_voice_cd - dt, 0.0)
|
||||
var v: Dictionary = VOICES.get(kind, {})
|
||||
if v.is_empty():
|
||||
return
|
||||
var mouth := global_position + Vector3(0, 0.8, 0)
|
||||
if _attack_cd > cd0 + 0.05 and v.has("attack"):
|
||||
Sfx.at(String(v.attack), mouth)
|
||||
_voice_left = maxf(_voice_left, 1.5)
|
||||
return
|
||||
var angry := state in ANGRY
|
||||
if angry and not (was in ANGRY) and v.has("alert"):
|
||||
Sfx.at(String(v.alert), mouth)
|
||||
_voice_left = _voice_rng.randf_range(ANGRY_EVERY[0], ANGRY_EVERY[1])
|
||||
return
|
||||
if _voice_left < 0.0:
|
||||
_voice_rng.seed = get_instance_id()
|
||||
_voice_left = _voice_rng.randf_range(2.0, IDLE_EVERY[1]) # (a new creature doesn't call out at once)
|
||||
_voice_left -= dt
|
||||
if _voice_left > 0.0:
|
||||
return
|
||||
if angry and v.has("angry"):
|
||||
Sfx.at(String(v.angry), mouth)
|
||||
_voice_left = _voice_rng.randf_range(ANGRY_EVERY[0], ANGRY_EVERY[1])
|
||||
elif not angry and v.has("idle") and state != "rest" and state != "flee":
|
||||
Sfx.at(String(v.idle), mouth)
|
||||
_voice_left = _voice_rng.randf_range(IDLE_EVERY[0], IDLE_EVERY[1])
|
||||
else:
|
||||
_voice_left = _voice_rng.randf_range(ANGRY_EVERY[0], ANGRY_EVERY[1])
|
||||
|
||||
@@ -126,6 +126,7 @@ func _think(dt: float) -> void:
|
||||
p.survival.damage(def.damage * world.difficulty_mult("damage"), global_position, "the Old Stag")
|
||||
_hit_this_charge = true
|
||||
charge_hits += 1
|
||||
Sfx.at("stag_snort", global_position + Vector3(0, 1.2, 0))
|
||||
var left := Vector2(_target.x - global_position.x, _target.z - global_position.z)
|
||||
if left.dot(Vector2(_dir.x, _dir.z)) <= 0.0 or _state_t <= 0.0:
|
||||
state = "stunned"
|
||||
@@ -140,6 +141,7 @@ func _think(dt: float) -> void:
|
||||
for p in world.players():
|
||||
if p.global_position.distance_to(global_position) < STOMP_R and not p.survival.dead:
|
||||
p.survival.damage(STOMP_DAMAGE * world.difficulty_mult("damage"), global_position, "the Old Stag")
|
||||
Sfx.at("rock_break", global_position)
|
||||
stones.stomped(self)
|
||||
_start_circle()
|
||||
_:
|
||||
|
||||
Reference in New Issue
Block a user