146 lines
4.4 KiB
GDScript
146 lines
4.4 KiB
GDScript
extends Node
|
|
|
|
## Persistent player-facing settings (AnchorV1.0 §9.5 difficulty windows,
|
|
## §20.5 music/sfx volumes). Sole writer of the Music / SFX buses and of the
|
|
## RhythmManager judgement windows; every menu goes through this node.
|
|
|
|
signal difficulty_changed(difficulty: StringName)
|
|
signal volumes_changed(music_volume: int, sfx_volume: int)
|
|
|
|
const SETTINGS_PATH := "user://settings.cfg"
|
|
|
|
const MUSIC_BUS_NAME := "Music"
|
|
const SFX_BUS_NAME := "SFX"
|
|
|
|
const DIFFICULTY_ORDER: Array[StringName] = [&"easy", &"normal", &"hard"]
|
|
|
|
## Windows in seconds per AnchorV1.0 §21.8.
|
|
const DIFFICULTY_WINDOWS := {
|
|
&"easy": Vector3(0.090, 0.160, 0.260),
|
|
&"normal": Vector3(0.060, 0.115, 0.215),
|
|
&"hard": Vector3(0.030, 0.070, 0.170),
|
|
}
|
|
|
|
## 小怪血量难度倍率:普通 3 倍、困难 5 倍,简单保持基准值。
|
|
const DIFFICULTY_MINION_HEALTH_MULTIPLIER := {
|
|
&"easy": 1.0,
|
|
&"normal": 3.0,
|
|
&"hard": 5.0,
|
|
}
|
|
|
|
const DEFAULT_DIFFICULTY: StringName = &"normal"
|
|
const DEFAULT_MUSIC_VOLUME := 85
|
|
const DEFAULT_SFX_VOLUME := 80
|
|
|
|
var difficulty: StringName = DEFAULT_DIFFICULTY
|
|
var music_volume := DEFAULT_MUSIC_VOLUME
|
|
var sfx_volume := DEFAULT_SFX_VOLUME
|
|
|
|
|
|
func _ready() -> void:
|
|
_ensure_audio_buses()
|
|
_load_settings()
|
|
apply_difficulty_windows()
|
|
_apply_bus_volumes()
|
|
|
|
|
|
func set_difficulty(next: StringName) -> void:
|
|
if not DIFFICULTY_WINDOWS.has(next):
|
|
return
|
|
if next == difficulty:
|
|
return
|
|
difficulty = next
|
|
apply_difficulty_windows()
|
|
_save_settings()
|
|
difficulty_changed.emit(difficulty)
|
|
|
|
|
|
func set_music_volume(value: int) -> void:
|
|
music_volume = clampi(value, 0, 100)
|
|
_apply_bus_volumes()
|
|
_save_settings()
|
|
volumes_changed.emit(music_volume, sfx_volume)
|
|
|
|
|
|
func set_sfx_volume(value: int) -> void:
|
|
sfx_volume = clampi(value, 0, 100)
|
|
_apply_bus_volumes()
|
|
_save_settings()
|
|
volumes_changed.emit(music_volume, sfx_volume)
|
|
|
|
|
|
func difficulty_windows(for_difficulty: StringName = &"") -> Vector3:
|
|
var key := for_difficulty if DIFFICULTY_WINDOWS.has(for_difficulty) else difficulty
|
|
return DIFFICULTY_WINDOWS.get(key, DIFFICULTY_WINDOWS[DEFAULT_DIFFICULTY])
|
|
|
|
|
|
func minion_health_multiplier(for_difficulty: StringName = &"") -> float:
|
|
var key := for_difficulty if DIFFICULTY_MINION_HEALTH_MULTIPLIER.has(for_difficulty) else difficulty
|
|
return float(DIFFICULTY_MINION_HEALTH_MULTIPLIER.get(key, 1.0))
|
|
|
|
|
|
func difficulty_display_name(value: StringName = &"") -> String:
|
|
match (value if not value.is_empty() else difficulty):
|
|
&"easy":
|
|
return "简单"
|
|
&"hard":
|
|
return "困难"
|
|
return "普通"
|
|
|
|
|
|
func apply_difficulty_windows() -> void:
|
|
var rhythm := _rhythm_manager_or_null()
|
|
if rhythm == null or not rhythm.has_method("apply_judgement_windows"):
|
|
return
|
|
var windows := difficulty_windows()
|
|
rhythm.call("apply_judgement_windows", windows.x, windows.y, windows.z)
|
|
|
|
|
|
func _ensure_audio_buses() -> void:
|
|
for bus_name: String in [MUSIC_BUS_NAME, SFX_BUS_NAME]:
|
|
if AudioServer.get_bus_index(bus_name) != -1:
|
|
continue
|
|
AudioServer.add_bus()
|
|
var bus_index := AudioServer.bus_count - 1
|
|
AudioServer.set_bus_name(bus_index, bus_name)
|
|
AudioServer.set_bus_send(bus_index, "Master")
|
|
|
|
|
|
func _apply_bus_volumes() -> void:
|
|
_set_bus_volume(MUSIC_BUS_NAME, music_volume)
|
|
_set_bus_volume(SFX_BUS_NAME, sfx_volume)
|
|
|
|
|
|
func _set_bus_volume(bus_name: String, value: int) -> void:
|
|
var bus_index := AudioServer.get_bus_index(bus_name)
|
|
if bus_index == -1:
|
|
return
|
|
var linear := clampf(float(value) / 100.0, 0.0, 1.0)
|
|
AudioServer.set_bus_mute(bus_index, linear <= 0.0)
|
|
AudioServer.set_bus_volume_db(bus_index, linear_to_db(maxf(0.0001, linear)))
|
|
|
|
|
|
func _load_settings() -> void:
|
|
var config := ConfigFile.new()
|
|
if config.load(SETTINGS_PATH) != OK:
|
|
return
|
|
var stored_difficulty := StringName(str(config.get_value("gameplay", "difficulty", str(DEFAULT_DIFFICULTY))))
|
|
if DIFFICULTY_WINDOWS.has(stored_difficulty):
|
|
difficulty = stored_difficulty
|
|
music_volume = clampi(int(config.get_value("audio", "music_volume", DEFAULT_MUSIC_VOLUME)), 0, 100)
|
|
sfx_volume = clampi(int(config.get_value("audio", "sfx_volume", DEFAULT_SFX_VOLUME)), 0, 100)
|
|
|
|
|
|
func _save_settings() -> void:
|
|
var config := ConfigFile.new()
|
|
config.set_value("gameplay", "difficulty", str(difficulty))
|
|
config.set_value("audio", "music_volume", music_volume)
|
|
config.set_value("audio", "sfx_volume", sfx_volume)
|
|
config.save(SETTINGS_PATH)
|
|
|
|
|
|
func _rhythm_manager_or_null() -> Node:
|
|
if not is_inside_tree():
|
|
return null
|
|
return get_tree().root.get_node_or_null("RhythmManager")
|