feat(client): save/load client UI — F5/F6 quicksave/quickload (#554)
Wire SaveGame/LoadGame player actions through the full client stack: protocol v15 decode, InputMapper F5/F6 bindings, SimBridge wire mapping with one-shot carry-forward, GameState save_result field, and HUD notification via monologue display. Quit-to-menu triggers quicksave before scene change. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,8 @@ const _LATTICE_COLORS: Dictionary = {
|
||||
}
|
||||
const _FALLBACK_STANDARD: Color = Color("#c8d0e0")
|
||||
const _FALLBACK_URGENT: Color = Color("#e0e8f8")
|
||||
const _NOTIFICATION_COLOR: Color = Color("#8890a0") # #554: neutral system notification
|
||||
const _NOTIFICATION_DURATION: float = 2.5
|
||||
|
||||
@onready var _vbox: VBoxContainer = $VBoxContainer
|
||||
|
||||
@@ -71,6 +73,18 @@ func _process(delta: float) -> void:
|
||||
# Empty text is silently ignored — no slot created, no queue entry.
|
||||
# lattice_profile is read from GameState here and passed down — renderer stays
|
||||
# decoupled from the autoload (D-020 renderer contract).
|
||||
# #554: Show a brief system notification (save/load result, connection status).
|
||||
# Uses neutral color, short duration, bypasses lattice_profile styling.
|
||||
func show_notification(text: String) -> void:
|
||||
if text.is_empty():
|
||||
return
|
||||
var now := float(Time.get_ticks_msec())
|
||||
if _visible.size() < MAX_VISIBLE and now >= _next_fade_in_msec:
|
||||
_show_notification_line(text)
|
||||
else:
|
||||
_enqueue(text, _NOTIFICATION_DURATION, 1, false, "")
|
||||
|
||||
|
||||
func show_monologue(text: String, duration: float, priority: int = 2, is_urgent: bool = false) -> void:
|
||||
if text.is_empty():
|
||||
return
|
||||
@@ -86,6 +100,35 @@ func show_monologue(text: String, duration: float, priority: int = 2, is_urgent:
|
||||
# Internal
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func _show_notification_line(text: String) -> void:
|
||||
var container := MarginContainer.new()
|
||||
container.add_theme_constant_override("margin_left", 4)
|
||||
container.add_theme_constant_override("margin_right", 4)
|
||||
container.add_theme_constant_override("margin_top", 2)
|
||||
container.add_theme_constant_override("margin_bottom", 2)
|
||||
var label := RichTextLabel.new()
|
||||
label.bbcode_enabled = true
|
||||
label.fit_content = true
|
||||
label.scroll_active = false
|
||||
label.add_theme_font_size_override("normal_font_size", 13)
|
||||
var safe_text := text.replace("[", "[lb]")
|
||||
label.text = "[color=#%s]%s[/color]" % [_NOTIFICATION_COLOR.to_html(false), safe_text]
|
||||
container.add_child(label)
|
||||
_vbox.add_child(container)
|
||||
var slot := {
|
||||
node = container,
|
||||
expire_timer = _NOTIFICATION_DURATION,
|
||||
priority = 1,
|
||||
tween = null,
|
||||
}
|
||||
_visible.append(slot)
|
||||
_next_fade_in_msec = float(Time.get_ticks_msec()) + STAGGER_SEC * 1000.0
|
||||
container.modulate.a = 0.0
|
||||
var tween := create_tween()
|
||||
slot.tween = tween
|
||||
tween.tween_property(container, "modulate:a", 0.85, FADE_IN_SEC)
|
||||
|
||||
|
||||
func _show_line(text: String, duration: float, priority: int, is_urgent: bool, lattice_profile: String) -> void:
|
||||
var line_node := _build_line_node(text, is_urgent, lattice_profile)
|
||||
_vbox.add_child(line_node)
|
||||
|
||||
Reference in New Issue
Block a user