Houses ~8.5 m; chimps capped ~0.85 m with safer axis fitting. Large GLB/PBR packs stay local until Git LFS (Forgejo HTTP 413). Co-authored-by: Cursor <cursoragent@cursor.com>
515 lines
18 KiB
GDScript
515 lines
18 KiB
GDScript
extends CanvasLayer
|
|
## Global UI: pause, inventory, shop, settings, toast, VN-dialog, burger-cham.
|
|
|
|
@onready var pause_panel: PanelContainer = $PausePanel
|
|
@onready var dialog_layer: Control = $DialogLayer
|
|
@onready var dialog_dim: ColorRect = $DialogLayer/Dim
|
|
@onready var dialog_panel: PanelContainer = $DialogLayer/DialogPanel
|
|
@onready var dialog_label: Label = $DialogLayer/DialogPanel/Margin/VBox/DialogLabel
|
|
@onready var speaker_label: Label = $DialogLayer/DialogPanel/Margin/VBox/SpeakerLabel
|
|
@onready var continue_hint: Label = $DialogLayer/DialogPanel/Margin/VBox/Footer/ContinueHint
|
|
@onready var advance_btn: Button = $DialogLayer/DialogPanel/Margin/VBox/Footer/AdvanceButton
|
|
@onready var bark_panel: PanelContainer = $BarkPanel
|
|
@onready var bark_label: Label = $BarkPanel/BarkLabel
|
|
@onready var toast_label: Label = $ToastLabel
|
|
@onready var burger_overlay: ColorRect = $BurgerOverlay
|
|
@onready var burger_label: Label = $BurgerOverlay/BurgerLabel
|
|
@onready var points_label: Label = $PausePanel/Margin/VBox/PointsLabel
|
|
@onready var inventory_list: ItemList = $PausePanel/Margin/VBox/InventoryList
|
|
@onready var sfx_slider: HSlider = $PausePanel/Margin/VBox/SfxSlider
|
|
@onready var music_slider: HSlider = $PausePanel/Margin/VBox/MusicSlider
|
|
@onready var lang_option: OptionButton = $PausePanel/Margin/VBox/LangOption
|
|
@onready var hp_hearts: HBoxContainer = $HUD/HpHearts
|
|
@onready var status_label: Label = $HUD/StatusLabel
|
|
@onready var fat_stim: ColorRect = $FatStim
|
|
@onready var fat_label: Label = $FatStim/FatLabel
|
|
@onready var interact_prompt: Label = $HUD/InteractPrompt
|
|
@onready var attack_flash: ColorRect = $HUD/AttackFlash
|
|
|
|
const TYPEWRITER_CPS := 42.0
|
|
const PANEL_SLIDE := 36.0
|
|
|
|
var _fat_blink: float = 0.0
|
|
var _toast_brief_timer: float = 0.0
|
|
var _heart_full: Texture2D
|
|
var _heart_empty: Texture2D
|
|
|
|
## VN dialog state
|
|
var _dialog_open: bool = false
|
|
var _dialog_blocking: bool = false
|
|
var _dialog_paused_game: bool = false
|
|
var _dialog_full_text: String = ""
|
|
var _dialog_visible_chars: float = 0.0
|
|
var _dialog_typing: bool = false
|
|
var _dialog_queue: Array[Dictionary] = []
|
|
var _dialog_panel_rest_top: float = -210.0
|
|
var _bark_timer: float = 0.0
|
|
var _continue_blink: float = 0.0
|
|
var _voice_cast: Dictionary = {}
|
|
var _open_tween: Tween
|
|
|
|
func _ready() -> void:
|
|
process_mode = Node.PROCESS_MODE_ALWAYS
|
|
layer = 100
|
|
_heart_full = load("res://assets/sprites/placeholders/heart.png")
|
|
_heart_empty = load("res://assets/sprites/placeholders/heart_empty.png")
|
|
_load_voice_cast()
|
|
_style_dialog_panel()
|
|
pause_panel.visible = false
|
|
dialog_layer.visible = false
|
|
bark_panel.visible = false
|
|
burger_overlay.visible = false
|
|
toast_label.visible = false
|
|
_dialog_panel_rest_top = dialog_panel.offset_top
|
|
if advance_btn:
|
|
advance_btn.pressed.connect(_on_dialog_advance_pressed)
|
|
_setup_lang_option()
|
|
_refresh_texts()
|
|
sfx_slider.value = Settings.sfx_volume
|
|
music_slider.value = 1.0
|
|
music_slider.editable = false
|
|
Achievements.unlocked.connect(_on_achievement)
|
|
AudioDirector.burger_cham_triggered.connect(_on_burger_cham)
|
|
Settings.settings_changed.connect(_refresh_texts)
|
|
Locale.locale_changed.connect(_refresh_texts)
|
|
Inventory.inventory_changed.connect(_refresh_inventory)
|
|
Shop.points_changed.connect(_refresh_points)
|
|
GameState.hp_changed.connect(_on_hp)
|
|
GameState.status_changed.connect(func(): _on_hp(GameState.hp, GameState.max_hp))
|
|
_on_hp(GameState.hp, GameState.max_hp)
|
|
_refresh_inventory()
|
|
_refresh_points(Shop.dressler_points)
|
|
if interact_prompt:
|
|
interact_prompt.visible = false
|
|
if attack_flash:
|
|
attack_flash.visible = false
|
|
|
|
func _load_voice_cast() -> void:
|
|
var f := FileAccess.open("res://data/voice_cast.json", FileAccess.READ)
|
|
if f == null:
|
|
return
|
|
var parsed: Variant = JSON.parse_string(f.get_as_text())
|
|
if typeof(parsed) == TYPE_DICTIONARY:
|
|
_voice_cast = parsed
|
|
|
|
func _style_dialog_panel() -> void:
|
|
var box := StyleBoxFlat.new()
|
|
box.bg_color = Color(0.07, 0.08, 0.12, 0.94)
|
|
box.border_color = Color(0.85, 0.72, 0.4, 0.85)
|
|
box.set_border_width_all(2)
|
|
box.set_corner_radius_all(10)
|
|
box.content_margin_left = 4
|
|
box.content_margin_right = 4
|
|
box.content_margin_top = 4
|
|
box.content_margin_bottom = 4
|
|
dialog_panel.add_theme_stylebox_override("panel", box)
|
|
var bark_box := box.duplicate() as StyleBoxFlat
|
|
bark_box.bg_color = Color(0.1, 0.08, 0.06, 0.88)
|
|
bark_panel.add_theme_stylebox_override("panel", bark_box)
|
|
|
|
func _process(delta: float) -> void:
|
|
if _dialog_open and _dialog_typing:
|
|
_dialog_visible_chars += TYPEWRITER_CPS * delta
|
|
var shown := mini(int(_dialog_visible_chars), _dialog_full_text.length())
|
|
dialog_label.text = _dialog_full_text.substr(0, shown)
|
|
if shown >= _dialog_full_text.length():
|
|
_dialog_typing = false
|
|
_dialog_visible_chars = float(_dialog_full_text.length())
|
|
if _dialog_open and not _dialog_typing:
|
|
_continue_blink += delta
|
|
var blink_a: float = 0.45 + 0.4 * abs(sin(_continue_blink * 4.0))
|
|
continue_hint.modulate.a = blink_a
|
|
advance_btn.modulate.a = 0.85 + 0.15 * abs(sin(_continue_blink * 4.0))
|
|
if _bark_timer > 0.0:
|
|
_bark_timer -= delta
|
|
if _bark_timer <= 0.0:
|
|
bark_panel.visible = false
|
|
if _toast_brief_timer > 0.0:
|
|
_toast_brief_timer -= delta
|
|
_update_fat_stim(delta)
|
|
|
|
func _input(event: InputEvent) -> void:
|
|
if not _dialog_open or not _dialog_blocking:
|
|
return
|
|
# Advance first: Space is both ui_accept and interact — don't let interact steal it.
|
|
if event.is_action_pressed("dialog_advance") or event.is_action_pressed("ui_accept"):
|
|
_advance_dialog()
|
|
get_viewport().set_input_as_handled()
|
|
return
|
|
# Gameplay-only keys must NOT dismiss / advance dialog (E / attack click)
|
|
if event.is_action_pressed("interact") or event.is_action_pressed("attack"):
|
|
get_viewport().set_input_as_handled()
|
|
return
|
|
|
|
func set_interact_prompt(text: String) -> void:
|
|
if interact_prompt == null:
|
|
return
|
|
if _dialog_open or text == "" or GameState.current_scene_id in ["title_screen", "death_screen", "intro_cutscene", "catscene"]:
|
|
interact_prompt.visible = false
|
|
return
|
|
interact_prompt.text = text
|
|
interact_prompt.visible = true
|
|
|
|
func _wait(sec: float) -> void:
|
|
## Safe timer: works when paused; never call create_timer on a null tree.
|
|
var tree := get_tree()
|
|
if tree == null:
|
|
tree = Engine.get_main_loop() as SceneTree
|
|
if tree == null:
|
|
return
|
|
await tree.create_timer(sec, true).timeout
|
|
|
|
func force_close_dialog() -> void:
|
|
## Called on scene change so pause/dialog state cannot leak to title.
|
|
_dialog_queue.clear()
|
|
_dialog_open = false
|
|
_dialog_typing = false
|
|
_dialog_blocking = false
|
|
if dialog_layer:
|
|
dialog_layer.visible = false
|
|
if bark_panel:
|
|
bark_panel.visible = false
|
|
_bark_timer = 0.0
|
|
_dialog_paused_game = false
|
|
_sync_player_focus(true)
|
|
|
|
func pulse_attack() -> void:
|
|
if attack_flash == null:
|
|
return
|
|
attack_flash.color = Color(1.0, 0.85, 0.3, 0.18)
|
|
attack_flash.visible = true
|
|
await _wait(0.08)
|
|
if is_instance_valid(attack_flash):
|
|
attack_flash.visible = false
|
|
|
|
func pulse_interact() -> void:
|
|
if attack_flash == null:
|
|
return
|
|
attack_flash.color = Color(0.5, 1.0, 0.7, 0.14)
|
|
attack_flash.visible = true
|
|
await _wait(0.1)
|
|
if is_instance_valid(attack_flash):
|
|
attack_flash.visible = false
|
|
|
|
func show_toast_brief(text: String, duration: float = 0.5) -> void:
|
|
toast_label.text = text
|
|
toast_label.visible = true
|
|
_toast_brief_timer = duration
|
|
await _wait(duration)
|
|
if _toast_brief_timer <= 0.0:
|
|
toast_label.visible = false
|
|
|
|
func _update_fat_stim(delta: float) -> void:
|
|
if fat_stim == null:
|
|
return
|
|
var fat := GameState.is_fat()
|
|
fat_stim.visible = fat and GameState.current_scene_id not in ["title_screen", "death_screen", "intro_cutscene", "catscene"]
|
|
if fat:
|
|
_fat_blink += delta
|
|
fat_label.text = Locale.tr_key("dialog_tiramisu")
|
|
fat_stim.color = Color(0.05, 0.05, 0.08, 0.35 + 0.25 * abs(sin(_fat_blink * 3.0)))
|
|
var stim := fat_stim.get_node_or_null("StimImage") as TextureRect
|
|
if stim == null:
|
|
stim = TextureRect.new()
|
|
stim.name = "StimImage"
|
|
fat_stim.add_child(stim)
|
|
stim.set_anchors_preset(Control.PRESET_CENTER_BOTTOM)
|
|
stim.offset_left = -80.0
|
|
stim.offset_top = -200.0
|
|
stim.offset_right = 80.0
|
|
stim.offset_bottom = -40.0
|
|
stim.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
|
stim.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
|
stim.texture = load("res://assets/sprites/placeholders/shorts_stim.png")
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if _dialog_open and _dialog_blocking:
|
|
return
|
|
if event.is_action_pressed("pause_menu"):
|
|
toggle_pause()
|
|
get_viewport().set_input_as_handled()
|
|
|
|
func toggle_pause() -> void:
|
|
if GameState.current_scene_id in ["title_screen", "death_screen", "intro_cutscene", "catscene"]:
|
|
return
|
|
if _dialog_open and _dialog_blocking:
|
|
return
|
|
pause_panel.visible = not pause_panel.visible
|
|
get_tree().paused = pause_panel.visible
|
|
_sync_player_focus(not pause_panel.visible)
|
|
if pause_panel.visible:
|
|
_refresh_texts()
|
|
_refresh_inventory()
|
|
|
|
func _sync_player_focus(focused: bool) -> void:
|
|
for node in get_tree().get_nodes_in_group("player"):
|
|
if node.has_method("set_gameplay_focused"):
|
|
node.set_gameplay_focused(focused)
|
|
|
|
## Blocking VN dialog (pauses world). `duration` ignored for dismiss — user advances with Enter/C.
|
|
func show_dialog(text: String, _duration: float = 3.5, voice_key: String = "", blocking: bool = true) -> void:
|
|
if not blocking:
|
|
_show_bark(text, maxf(_duration, 2.0), voice_key)
|
|
return
|
|
_dialog_queue.append({"text": text, "voice_key": voice_key})
|
|
if not _dialog_open:
|
|
_present_next_dialog()
|
|
|
|
func show_dialog_key(key: String, duration: float = 3.5, blocking: bool = true) -> void:
|
|
show_dialog(Locale.tr_key(key), duration, key, blocking)
|
|
|
|
func is_dialog_open() -> bool:
|
|
return _dialog_open and _dialog_blocking
|
|
|
|
func _show_bark(text: String, duration: float, voice_key: String) -> void:
|
|
bark_label.text = text
|
|
bark_panel.visible = true
|
|
_bark_timer = duration
|
|
if voice_key != "":
|
|
AudioDirector.play_dialog_line(voice_key)
|
|
|
|
func _present_next_dialog() -> void:
|
|
if _dialog_queue.is_empty():
|
|
_close_dialog()
|
|
return
|
|
var entry: Dictionary = _dialog_queue.pop_front()
|
|
var text: String = str(entry.get("text", ""))
|
|
var voice_key: String = str(entry.get("voice_key", ""))
|
|
_dialog_full_text = text
|
|
_dialog_visible_chars = 0.0
|
|
_dialog_typing = text.length() > 0
|
|
_dialog_blocking = true
|
|
_dialog_open = true
|
|
dialog_label.text = ""
|
|
speaker_label.text = _speaker_for_key(voice_key)
|
|
continue_hint.text = Locale.tr_key("dialog_continue")
|
|
advance_btn.text = Locale.tr_key("dialog_advance_btn")
|
|
dialog_layer.visible = true
|
|
if interact_prompt:
|
|
interact_prompt.visible = false
|
|
_begin_dialog_pause()
|
|
# Player _ready may run after this and steal mouse — re-assert after scene settles.
|
|
call_deferred("_ensure_dialog_input_ready")
|
|
_play_open_anim()
|
|
if voice_key != "":
|
|
AudioDirector.play_dialog_line(voice_key)
|
|
|
|
func _speaker_for_key(voice_key: String) -> String:
|
|
if voice_key == "":
|
|
return Locale.tr_key("speaker_system")
|
|
var lines: Variant = _voice_cast.get("lines", {})
|
|
if typeof(lines) == TYPE_DICTIONARY and lines.has(voice_key):
|
|
var npc := str(lines[voice_key])
|
|
var sk := "speaker_%s" % npc
|
|
var named := Locale.tr_key(sk)
|
|
if named != sk:
|
|
return named
|
|
return npc.capitalize()
|
|
return Locale.tr_key("speaker_system")
|
|
|
|
func _play_open_anim() -> void:
|
|
if _open_tween and _open_tween.is_running():
|
|
_open_tween.kill()
|
|
dialog_dim.color.a = 0.0
|
|
dialog_panel.modulate.a = 0.0
|
|
dialog_panel.offset_top = _dialog_panel_rest_top + PANEL_SLIDE
|
|
dialog_panel.offset_bottom = -28.0 + PANEL_SLIDE
|
|
_open_tween = create_tween()
|
|
_open_tween.set_parallel(true)
|
|
_open_tween.tween_property(dialog_dim, "color:a", 0.55, 0.28).set_ease(Tween.EASE_OUT)
|
|
_open_tween.tween_property(dialog_panel, "modulate:a", 1.0, 0.32).set_ease(Tween.EASE_OUT)
|
|
_open_tween.tween_property(dialog_panel, "offset_top", _dialog_panel_rest_top, 0.35).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT)
|
|
_open_tween.tween_property(dialog_panel, "offset_bottom", -28.0, 0.35).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT)
|
|
|
|
func _advance_dialog() -> void:
|
|
if not _dialog_open:
|
|
return
|
|
if _dialog_typing:
|
|
# First confirm: finish typewriter
|
|
_dialog_typing = false
|
|
_dialog_visible_chars = float(_dialog_full_text.length())
|
|
dialog_label.text = _dialog_full_text
|
|
return
|
|
# Second confirm: next line or close
|
|
if not _dialog_queue.is_empty():
|
|
_present_next_dialog()
|
|
else:
|
|
_close_dialog()
|
|
|
|
func _on_dialog_advance_pressed() -> void:
|
|
_advance_dialog()
|
|
|
|
func _begin_dialog_pause() -> void:
|
|
if pause_panel.visible:
|
|
_dialog_paused_game = false
|
|
return
|
|
if not get_tree().paused:
|
|
get_tree().paused = true
|
|
_dialog_paused_game = true
|
|
else:
|
|
_dialog_paused_game = true
|
|
_sync_player_focus(false)
|
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
|
|
|
func _ensure_dialog_input_ready() -> void:
|
|
if not _dialog_open or not _dialog_blocking:
|
|
return
|
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
|
_sync_player_focus(false)
|
|
if advance_btn:
|
|
advance_btn.disabled = false
|
|
advance_btn.grab_focus()
|
|
|
|
func _end_dialog_pause() -> void:
|
|
if _dialog_paused_game and not pause_panel.visible:
|
|
get_tree().paused = false
|
|
_sync_player_focus(true)
|
|
_dialog_paused_game = false
|
|
|
|
func _close_dialog() -> void:
|
|
_dialog_open = false
|
|
_dialog_blocking = false
|
|
_dialog_typing = false
|
|
_dialog_queue.clear()
|
|
dialog_layer.visible = false
|
|
AudioDirector.stop_voice()
|
|
_end_dialog_pause()
|
|
|
|
func _setup_lang_option() -> void:
|
|
lang_option.clear()
|
|
lang_option.add_item("Deutsch", 0)
|
|
lang_option.add_item("English", 1)
|
|
lang_option.add_item("Bayerisch", 2)
|
|
match Settings.language:
|
|
"en":
|
|
lang_option.select(1)
|
|
"by":
|
|
lang_option.select(2)
|
|
_:
|
|
lang_option.select(0)
|
|
|
|
func _refresh_texts() -> void:
|
|
$PausePanel/Margin/VBox/TitleLabel.text = Locale.tr_key("menu_settings")
|
|
$PausePanel/Margin/VBox/ResumeButton.text = Locale.tr_key("menu_resume")
|
|
$PausePanel/Margin/VBox/ShopTitle.text = Locale.tr_key("menu_shop")
|
|
$PausePanel/Margin/VBox/BuyPointButton.text = Locale.tr_key("menu_buy_point")
|
|
$PausePanel/Margin/VBox/SfxLabel.text = Locale.tr_key("menu_sfx")
|
|
$PausePanel/Margin/VBox/MusicLabel.text = Locale.tr_key("menu_music")
|
|
$PausePanel/Margin/VBox/LangLabel.text = Locale.tr_key("menu_language")
|
|
$PausePanel/Margin/VBox/InventoryLabel.text = Locale.tr_key("menu_inventory")
|
|
if continue_hint:
|
|
continue_hint.text = Locale.tr_key("dialog_continue")
|
|
if advance_btn:
|
|
advance_btn.text = Locale.tr_key("dialog_advance_btn")
|
|
_refresh_points(Shop.dressler_points)
|
|
|
|
func _refresh_points(points: int) -> void:
|
|
points_label.text = Locale.tr_key("menu_points", [str(points)])
|
|
|
|
func _refresh_inventory() -> void:
|
|
inventory_list.clear()
|
|
inventory_list.fixed_icon_size = Vector2i(int(SpriteFit.UI_ICON_PX), int(SpriteFit.UI_ICON_PX))
|
|
var items := Inventory.get_all()
|
|
for id in items.keys():
|
|
var count: int = int(items[id])
|
|
var item_name: String = Locale.tr_key("item_" + str(id))
|
|
var idx := inventory_list.item_count
|
|
inventory_list.add_item("%s x%d" % [item_name, count])
|
|
inventory_list.set_item_metadata(idx, id)
|
|
var icon_path := Inventory.get_icon_path(str(id))
|
|
if icon_path != "":
|
|
var tex: Texture2D = load(icon_path)
|
|
if tex:
|
|
inventory_list.set_item_icon(idx, tex)
|
|
|
|
func _on_hp(hp: int, max_hp: int) -> void:
|
|
_rebuild_hearts(hp, max_hp)
|
|
var extra := ""
|
|
if GameState.is_fat():
|
|
extra += Locale.tr_key("status_fat", [str(int(GameState.fat_time_left))])
|
|
if GameState.is_shocked():
|
|
if extra != "":
|
|
extra += " · "
|
|
extra += Locale.tr_key("status_shock")
|
|
if GameState.is_glitched_slow():
|
|
if extra != "":
|
|
extra += " · "
|
|
extra += Locale.tr_key("status_glitch")
|
|
if status_label:
|
|
status_label.text = extra
|
|
status_label.visible = extra != ""
|
|
|
|
func _rebuild_hearts(hp: int, max_hp: int) -> void:
|
|
if hp_hearts == null:
|
|
return
|
|
while hp_hearts.get_child_count() > 0:
|
|
var c := hp_hearts.get_child(0)
|
|
hp_hearts.remove_child(c)
|
|
c.queue_free()
|
|
var n := maxi(max_hp, 1)
|
|
for i in range(n):
|
|
var tex_rect := TextureRect.new()
|
|
tex_rect.custom_minimum_size = Vector2(28, 28)
|
|
tex_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
|
tex_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
|
tex_rect.texture = _heart_full if i < hp else _heart_empty
|
|
tex_rect.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
|
hp_hearts.add_child(tex_rect)
|
|
|
|
func _on_achievement(_id: String, title: String) -> void:
|
|
toast_label.text = Locale.tr_key("toast_achievement", [title])
|
|
toast_label.visible = true
|
|
await _wait(3.0)
|
|
if is_instance_valid(toast_label):
|
|
toast_label.visible = false
|
|
|
|
func _on_burger_cham() -> void:
|
|
burger_label.text = Locale.tr_key("burger_cham")
|
|
burger_overlay.visible = true
|
|
var img := burger_overlay.get_node_or_null("BurgerImage") as TextureRect
|
|
if img == null:
|
|
img = TextureRect.new()
|
|
img.name = "BurgerImage"
|
|
burger_overlay.add_child(img)
|
|
img.set_anchors_preset(Control.PRESET_CENTER)
|
|
img.offset_left = -120.0
|
|
img.offset_top = -200.0
|
|
img.offset_right = 120.0
|
|
img.offset_bottom = -40.0
|
|
img.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
|
img.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
|
img.texture = load("res://assets/sprites/placeholders/burger_cham.png")
|
|
SpriteFit.apply_texture_rect_budget(img, 240.0)
|
|
await _wait(2.5)
|
|
if is_instance_valid(burger_overlay):
|
|
burger_overlay.visible = false
|
|
|
|
func _on_resume_pressed() -> void:
|
|
pause_panel.visible = false
|
|
get_tree().paused = false
|
|
_sync_player_focus(true)
|
|
|
|
func _on_buy_point() -> void:
|
|
Shop.purchase("buy_point")
|
|
|
|
func _on_sfx_changed(value: float) -> void:
|
|
Settings.set_sfx_volume(value)
|
|
|
|
func _on_music_changed(_value: float) -> void:
|
|
Settings.set_music_volume(1.0)
|
|
music_slider.value = 1.0
|
|
|
|
func _on_lang_selected(index: int) -> void:
|
|
match index:
|
|
1:
|
|
Settings.set_language("en")
|
|
2:
|
|
Settings.set_language("by")
|
|
_:
|
|
Settings.set_language("de")
|
|
|
|
func _on_inventory_activated(index: int) -> void:
|
|
var id: Variant = inventory_list.get_item_metadata(index)
|
|
if typeof(id) == TYPE_STRING:
|
|
Inventory.use_item(str(id))
|
|
_refresh_inventory()
|