Phase 17/18 closure: stockpile filter UI + day summary + atmospheric audio
Three-agent fan-out (gdscript-refactor x3) closing deferred polish: - Stockpile chip filter UI: new StockpilePanel (layer 18, right-anchored, mirrors WorkbenchPanel). 5-priority segmented control + 21-chip 4-col filter grid using Item.ALL_TYPES; wildcard (empty accepted_types) shows all chips checked with 'All' hint, first explicit pick switches to explicit-list mode. Selection chain extended to pawn → workbench → stockpile with mutual exclusion. 12 ui.stockpile.* + 13 item.* keys. - DaySummaryCard: layer-19 modal auto-opens at dusk→night via day_ended, auto-pauses sim, shows day+season header, weather row, stats grid with green/yellow/red tension bar, Continue dismiss + backdrop tap. Settings 'Show end-of-day summary' toggle persists via GameState. - Atmospheric audio: rain ambient loop (Cozy Melodies Pack 6) on weather_changed rain/storm with 0.5s fade-out on clear; thunder sting (Magic and Spells 6) on rain→storm transition; raid warning sting (Sword Pack 1, 'blades drawn') on EventBus.wolf_spawned. All on SFX bus — inherits existing slider + suspend mute. Contracts pre-written before fan-out: EventBus.stockpile_selected / stockpile_deselected / wolf_spawned signals; WolfSpawner._trigger_raid + _on_request_wolf_spawn now emit wolf_spawned with the spawned array. MCP runtime verified: StockpilePanel opens with 21 chips, DaySummaryCard renders weather row + tension bar + auto-pause, rain_player.playing=true on weather_changed(rain), all three new SFX keys in Audio.SFX_FILES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
88e3fa9364
commit
bba1ce4334
18 changed files with 935 additions and 18 deletions
|
|
@ -1,5 +1,5 @@
|
|||
extends Node
|
||||
## AudioManager — Phase 18.
|
||||
## AudioManager — Phase 18 + atmospheric audio (Phase 18 follow-up).
|
||||
##
|
||||
## Owns three AudioServer buses (Master, Music, SFX), a small catalog of music
|
||||
## loops + one-shot SFX, and a single AudioStreamPlayer for the music director.
|
||||
|
|
@ -9,6 +9,13 @@ extends Node
|
|||
## EventBus signals (combat, storyteller stings) and direct calls from Tree /
|
||||
## Rock entities for chop / mine completion sounds.
|
||||
##
|
||||
## Atmospheric audio (Phase 18 follow-up):
|
||||
## - Rain ambient loop: persistent AudioStreamPlayer on SFX bus, fades in/out
|
||||
## on weather_changed. Routed to SFX bus (no new bus) so it respects the
|
||||
## existing SFX slider and application-pause muting via Master bus mute.
|
||||
## - Storm thunder sting: one-shot on WEATHER_RAIN → WEATHER_STORM transition.
|
||||
## - Raid warning sting: one-shot on wolf_spawned.
|
||||
##
|
||||
## Volume sliders in SettingsMenu drive set_*_db() at the Master/Music/SFX bus
|
||||
## level. App-pause mutes everything (NOTIFICATION_APPLICATION_PAUSED / focus
|
||||
## loss) to match the existing "no background simulation" rule.
|
||||
|
|
@ -33,6 +40,14 @@ const SFX_FILES: Dictionary = {
|
|||
&"tree_fell": "res://audio/sfx/tree_fell.ogg",
|
||||
&"mine_tick": "res://audio/sfx/mine_tick.ogg",
|
||||
&"combat_hit": "res://audio/sfx/combat_hit.ogg",
|
||||
# Atmospheric — Phase 18 follow-up.
|
||||
# rain_ambient: Cozy Melodies Pack 6 (ElvGames Ultimate Farming RPG Tier 3)
|
||||
&"rain_ambient": "res://audio/sfx/rain_ambient.ogg",
|
||||
# thunder_sting: Magic and Spells 6 (ElvGames Ultimate Farming RPG Tier 3)
|
||||
&"thunder_sting": "res://audio/sfx/thunder_sting.ogg",
|
||||
# raid_warning: Sword Sound Effects Pack 1 (ElvGames Ultimate Farming RPG Tier 3)
|
||||
# Sword unsheathe sting used as danger signal; no dedicated alarm/horn in bundle.
|
||||
&"raid_warning": "res://audio/sfx/raid_warning.ogg",
|
||||
}
|
||||
|
||||
const MUSIC_FILES: Dictionary = {
|
||||
|
|
@ -57,6 +72,14 @@ var master_linear: float = 1.0
|
|||
var music_linear: float = 1.0
|
||||
var sfx_linear: float = 1.0
|
||||
|
||||
# Atmospheric — rain ambient loop player. Persistent; stream stays loaded.
|
||||
# Routed to SFX bus so it inherits the SFX slider and app-pause muting.
|
||||
var _rain_player: AudioStreamPlayer = null
|
||||
# Tween used for the rain fade-out (clear/cold_snap). Cancelled on re-rain.
|
||||
var _rain_tween: Tween = null
|
||||
# Track previous weather to detect RAIN → STORM transition for thunder sting.
|
||||
var _prev_weather: StringName = &""
|
||||
|
||||
|
||||
# ── lifecycle ───────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -68,6 +91,15 @@ func _ready() -> void:
|
|||
_music_player.name = "MusicPlayer"
|
||||
add_child(_music_player)
|
||||
|
||||
# Rain ambient: persistent looping player on SFX bus. Starts silent; weather
|
||||
# signal activates it. Routing to SFX bus means app-pause muting (Master bus)
|
||||
# covers it automatically — no extra NOTIFICATION handling needed.
|
||||
_rain_player = AudioStreamPlayer.new()
|
||||
_rain_player.bus = BUS_SFX
|
||||
_rain_player.name = "RainAmbientPlayer"
|
||||
_rain_player.volume_db = 0.0
|
||||
add_child(_rain_player)
|
||||
|
||||
# SFX → world event wiring. Keep this list short and obvious.
|
||||
if EventBus.has_signal("pawn_took_damage"):
|
||||
EventBus.pawn_took_damage.connect(_on_pawn_took_damage)
|
||||
|
|
@ -75,6 +107,10 @@ func _ready() -> void:
|
|||
EventBus.storyteller_event_fired.connect(_on_storyteller_event_fired)
|
||||
if EventBus.has_signal("alert_added"):
|
||||
EventBus.alert_added.connect(_on_alert_added)
|
||||
if EventBus.has_signal("weather_changed"):
|
||||
EventBus.weather_changed.connect(_on_weather_changed)
|
||||
if EventBus.has_signal("wolf_spawned"):
|
||||
EventBus.wolf_spawned.connect(_on_wolf_spawned)
|
||||
|
||||
# Music director: swap day/night loops as Clock.phase_changed fires.
|
||||
if Clock.has_signal("phase_changed"):
|
||||
|
|
@ -86,7 +122,14 @@ func _ready() -> void:
|
|||
initial_phase = Clock.current_phase()
|
||||
_apply_phase_to_music(initial_phase)
|
||||
|
||||
Audit.log("audio", "AudioManager ready (master/music/sfx buses online)")
|
||||
# Sync rain loop to current weather in case we loaded mid-rain (e.g. after
|
||||
# save/load). Weather autoload is ready before Audio in the autoload list;
|
||||
# safe to query here.
|
||||
if Weather.has_method("is_raining") and Weather.is_raining():
|
||||
_prev_weather = Weather.current_weather
|
||||
_start_rain_loop()
|
||||
|
||||
Audit.log("audio", "AudioManager ready (master/music/sfx buses online + atmospheric)")
|
||||
|
||||
|
||||
## NOTIFICATION_APPLICATION_PAUSED fires when the app is suspended (mobile
|
||||
|
|
@ -232,3 +275,54 @@ func _on_alert_added(_severity: StringName, _text: String, _focus_tile: Vector2i
|
|||
|
||||
func _on_clock_phase_changed(new_phase: StringName) -> void:
|
||||
_apply_phase_to_music(new_phase)
|
||||
|
||||
|
||||
func _on_weather_changed(weather: StringName) -> void:
|
||||
var prev: StringName = _prev_weather
|
||||
_prev_weather = weather
|
||||
|
||||
match weather:
|
||||
&"rain", &"storm":
|
||||
# Cancel any pending fade-out tween.
|
||||
if _rain_tween != null and _rain_tween.is_valid():
|
||||
_rain_tween.kill()
|
||||
_rain_tween = null
|
||||
_start_rain_loop()
|
||||
# Thunder sting only on a direct transition into storm.
|
||||
if weather == &"storm" and prev != &"storm":
|
||||
play_sfx(&"thunder_sting")
|
||||
_:
|
||||
# WEATHER_CLEAR or WEATHER_COLD_SNAP — fade out rain loop.
|
||||
_stop_rain_loop_fade()
|
||||
|
||||
|
||||
func _on_wolf_spawned(wolves: Array) -> void:
|
||||
if wolves.size() > 0:
|
||||
play_sfx(&"raid_warning")
|
||||
|
||||
|
||||
# ── rain ambient helpers ─────────────────────────────────────────────────────
|
||||
|
||||
func _start_rain_loop() -> void:
|
||||
# Load the stream once and reuse across transitions.
|
||||
var stream: AudioStream = _load_sfx(&"rain_ambient")
|
||||
if stream == null:
|
||||
return
|
||||
if _rain_player.stream != stream:
|
||||
_rain_player.stream = stream
|
||||
if stream is AudioStreamOggVorbis:
|
||||
stream.loop = true
|
||||
_rain_player.volume_db = 0.0
|
||||
if not _rain_player.playing:
|
||||
_rain_player.play()
|
||||
|
||||
|
||||
func _stop_rain_loop_fade() -> void:
|
||||
if not _rain_player.playing:
|
||||
return
|
||||
# 0.5 s linear fade from current volume to SILENT_DB, then stop.
|
||||
if _rain_tween != null and _rain_tween.is_valid():
|
||||
_rain_tween.kill()
|
||||
_rain_tween = create_tween()
|
||||
_rain_tween.tween_property(_rain_player, "volume_db", SILENT_DB, 0.5)
|
||||
_rain_tween.tween_callback(_rain_player.stop)
|
||||
|
|
|
|||
|
|
@ -57,9 +57,12 @@ signal pawn_selected(pawn) ## Emitted when Selection pic
|
|||
signal pawn_deselected ## Emitted when Selection clears — closes PawnDetailPanel.
|
||||
signal workbench_selected(workbench)
|
||||
signal workbench_deselected
|
||||
signal stockpile_selected(zone) ## Emitted when Selection picks a StockpileZone — opens StockpilePanel for filter editing.
|
||||
signal stockpile_deselected ## Emitted when Selection clears the active stockpile.
|
||||
signal pawn_priority_changed(pawn, category: StringName, level: int) ## Emitted when priority matrix updates a cell.
|
||||
signal alert_added(severity: StringName, text: String, focus_tile: Vector2i) ## Emitted by gameplay subsystems to surface a player notice. severity = info | warn | danger.
|
||||
signal request_wolf_spawn(count: int) ## Phase 15 EventCatalog → WolfSpawner. Decouples threat-event effects from spawner.
|
||||
signal wolf_spawned(wolves: Array) ## Emitted by WolfSpawner AFTER a raid wave has been instantiated; carries the spawned Wolf nodes. Audio uses this for raid-warning sting.
|
||||
signal day_ended(summary: Dictionary) ## Emitted by Clock at dusk→night boundary; carries the end-of-day recap dict.
|
||||
|
||||
# Phase 18 — Alert wiring (dangling signals surfaced to AlertsLog).
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ var settings: Dictionary = {
|
|||
"pause_on_wanderer": true,
|
||||
"pause_on_pawn_down": true,
|
||||
"pause_on_modal": true,
|
||||
"show_day_summary": true,
|
||||
"audio_master": 1.0,
|
||||
"audio_music": 1.0,
|
||||
"audio_sfx": 1.0,
|
||||
|
|
|
|||
|
|
@ -218,6 +218,49 @@ const TABLE: Dictionary = {
|
|||
&"ui.bill.no_bills_hint": "No bills. Add one to start crafting.",
|
||||
&"ui.workbench.current_bill": "Current",
|
||||
&"ui.workbench.idle": "Idle",
|
||||
# Phase 17 — DaySummaryCard end-of-day modal.
|
||||
&"ui.day_summary.title": "Day {day} — {season}",
|
||||
&"ui.day_summary.continue": "Continue",
|
||||
&"ui.day_summary.pawns_alive": "Pawns alive",
|
||||
&"ui.day_summary.wolves_on_map": "Wolves on map",
|
||||
&"ui.day_summary.tension": "Tension",
|
||||
&"ui.day_summary.tension_fmt": "{t} / 100",
|
||||
# Weather labels (weather.<name>)
|
||||
&"weather.clear": "Clear",
|
||||
&"weather.rain": "Rain",
|
||||
&"weather.storm": "Storm",
|
||||
&"weather.cold_snap": "Cold Snap",
|
||||
&"weather.unknown": "Unknown",
|
||||
# Settings checkbox label
|
||||
&"ui.settings.show_day_summary": "Show end-of-day summary",
|
||||
# Phase 17 — Stockpile filter chips (StockpilePanel).
|
||||
&"ui.stockpile.title": "Stockpile",
|
||||
&"ui.stockpile.priority": "Priority:",
|
||||
&"ui.stockpile.prio.critical": "Crit",
|
||||
&"ui.stockpile.prio.high": "High",
|
||||
&"ui.stockpile.prio.normal": "Norm",
|
||||
&"ui.stockpile.prio.low": "Low",
|
||||
&"ui.stockpile.prio.off": "Off",
|
||||
&"ui.stockpile.accepts": "Accepts:",
|
||||
&"ui.stockpile.accepts_all_hint": "All",
|
||||
&"ui.stockpile.select_all": "All",
|
||||
&"ui.stockpile.clear_all": "None",
|
||||
# Item type chip labels missing from existing table (copper_ore through ash).
|
||||
# (item.wood, .stone, .iron_ore, .plank, .stone_block, .flour, .bread, .meal
|
||||
# are already defined above — only the ones below are new.)
|
||||
&"item.copper_ore": "Copper",
|
||||
&"item.silver": "Silver",
|
||||
&"item.gold": "Gold",
|
||||
&"item.cloth": "Cloth",
|
||||
&"item.vegetable": "Vegetable",
|
||||
&"item.meat": "Meat",
|
||||
&"item.grain": "Grain",
|
||||
&"item.medicine": "Medicine",
|
||||
&"item.tool": "Tool",
|
||||
&"item.weapon": "Weapon",
|
||||
&"item.armor": "Armor",
|
||||
&"item.corpse": "Corpse",
|
||||
&"item.ash": "Ash",
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue