extends Control ## GachaPanel — Two-banner gacha interface. ## Banners: Star (✦) and Gold (▤) ## Pull results shown in animated card reveal. signal closed # ─── Node refs ─────────────────────────────────────────────────────────────── @onready var back_btn := %BackBtn as Button @onready var star_tab_btn := %StarTabBtn as Button @onready var gold_tab_btn := %GoldTabBtn as Button @onready var banner_label := %BannerLabel as Label @onready var balance_label := %BalanceLabel as Label @onready var pity_label := %PityLabel as Label @onready var pull_1_btn := %Pull1Btn as Button @onready var pull_10_btn := %Pull10Btn as Button @onready var cost_1_label := %Cost1Label as Label @onready var cost_10_label := %Cost10Label as Label @onready var rates_label := %RatesLabel as Label @onready var result_panel := %ResultPanel as PanelContainer @onready var result_grid := %ResultGrid as GridContainer @onready var close_result_btn := %CloseResultBtn as Button @onready var craft_btn := %CraftBtn as Button @onready var status_label := %StatusLabel as Label # ─── State ─────────────────────────────────────────────────────────────────── var _current_banner: String = "star" var _pulling: bool = false const RARITY_COLORS := { "common": Color(0.80, 0.80, 0.80), "uncommon": Color(0.30, 0.85, 0.35), "rare": Color(0.20, 0.55, 1.00), "real_prize": Color(1.00, 0.75, 0.10) } const RARITY_LABELS := { "common": "Common", "uncommon": "Uncommon", "rare": "Rare", "real_prize": "✨ REAL PRIZE ✨" } # ─── Lifecycle ──────────────────────────────────────────────────────────────── func _ready() -> void: back_btn.pressed.connect(_on_close) star_tab_btn.pressed.connect(func(): _switch_banner("star")) gold_tab_btn.pressed.connect(func(): _switch_banner("gold")) pull_1_btn.pressed.connect(func(): _do_pull(1)) pull_10_btn.pressed.connect(func(): _do_pull(10)) close_result_btn.pressed.connect(func(): result_panel.hide()) craft_btn.pressed.connect(_on_open_craft) result_panel.hide() _switch_banner("star") func show_panel() -> void: show() _refresh_ui() # ─── Banner switching ───────────────────────────────────────────────────────── func _switch_banner(id: String) -> void: _current_banner = id star_tab_btn.modulate = Color(1.3, 1.1, 0.3) if id == "star" else Color.WHITE gold_tab_btn.modulate = Color(1.3, 1.1, 0.3) if id == "gold" else Color.WHITE _refresh_ui() func _refresh_ui() -> void: var gd: Dictionary = GachaManager.data var banner: Dictionary = gd.get("banners", {}).get(_current_banner, {}) if banner.is_empty(): return var currency: String = banner.get("currency", "star") var icon: String = "✦" if currency == "star" else "▤" var bal: int = GachaManager.get_balance(_current_banner) var pity: int = GachaManager.get_pity(_current_banner) var pity_at: int = banner.get("pity_at", 90) var c1: int = banner.get("pull_1_cost", 0) var c10: int = banner.get("pull_10_cost", 0) var rates: Dictionary = banner.get("rates", {}) banner_label.text = banner.get("name", "Banner") balance_label.text = "%s %d" % [icon, bal] pity_label.text = "Pity: %d / %d" % [pity, pity_at] cost_1_label.text = "%s %d" % [icon, c1] cost_10_label.text = "%s %d" % [icon, c10] pull_1_btn.disabled = (bal < c1) or _pulling pull_10_btn.disabled = (bal < c10) or _pulling var real_pool: Array = gd.get("pools", {}).get("real_prize", []) var real_names: Array = [] for rid in real_pool: var rd = gd.get("real_prize_catalog", {}).get(rid, {}) real_names.append(rd.get("name", rid)) rates_label.text = ( "Common %.0f%% Uncommon %.0f%% Rare %.0f%% ✨Real Prize %.1f%%\n" + "Guaranteed Real Prize every %d pulls (current pity: %d)\n\n" + "✨ Exclusive Real Prizes:\n" + "\n".join(real_names.map(func(n): return " • " + n)) ) % [ rates.get("common", 0) * 100, rates.get("uncommon", 0) * 100, rates.get("rare", 0) * 100, rates.get("real_prize", 0) * 100, pity_at, pity ] # ─── Pull ───────────────────────────────────────────────────────────────────── func _do_pull(count: int) -> void: if _pulling: return _pulling = true status_label.text = "Rolling..." var results: Array = await _run_pull(count) _pulling = false if results.is_empty(): status_label.text = "❌ Not enough currency!" _refresh_ui() return status_label.text = "" _refresh_ui() _show_results(results) func _run_pull(count: int) -> Array: # Yield one frame so UI updates first await get_tree().process_frame return GachaManager.pull(_current_banner, count) # ─── Result display ─────────────────────────────────────────────────────────── func _show_results(results: Array) -> void: # Clear old cards for c in result_grid.get_children(): c.queue_free() await get_tree().process_frame result_panel.show() for res in results: var card := _make_result_card(res) result_grid.add_child(card) # Staggered reveal await get_tree().create_timer(0.08).timeout func _make_result_card(res: Dictionary) -> PanelContainer: var rarity: String = res.get("rarity", "common") var col: Color = RARITY_COLORS.get(rarity, Color.WHITE) var label_txt: String = RARITY_LABELS.get(rarity, rarity.capitalize()) var panel := PanelContainer.new() panel.custom_minimum_size = Vector2(110, 130) var margin := MarginContainer.new() margin.add_theme_constant_override("margin_left", 8) margin.add_theme_constant_override("margin_top", 8) margin.add_theme_constant_override("margin_right", 8) margin.add_theme_constant_override("margin_bottom", 8) panel.add_child(margin) var vbox := VBoxContainer.new() vbox.alignment = BoxContainer.ALIGNMENT_CENTER margin.add_child(vbox) # Rarity icon var icon_lbl := Label.new() icon_lbl.text = _rarity_icon(rarity) icon_lbl.add_theme_font_size_override("font_size", 36) icon_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER icon_lbl.add_theme_color_override("font_color", col) vbox.add_child(icon_lbl) # Item name var name_lbl := Label.new() name_lbl.text = res.get("name", "?") name_lbl.add_theme_font_size_override("font_size", 11) name_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER name_lbl.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART name_lbl.add_theme_color_override("font_color", col) vbox.add_child(name_lbl) # Rarity label var rar_lbl := Label.new() rar_lbl.text = label_txt rar_lbl.add_theme_font_size_override("font_size", 9) rar_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER rar_lbl.add_theme_color_override("font_color", col.lerp(Color.WHITE, 0.3)) vbox.add_child(rar_lbl) # Flash animation panel.modulate.a = 0.0 var tween := create_tween() tween.tween_property(panel, "modulate:a", 1.0, 0.25) if rarity == "real_prize": tween.tween_property(panel, "modulate", Color(1.4, 1.3, 0.5, 1.0), 0.2) tween.tween_property(panel, "modulate", Color.WHITE, 0.3) return panel func _rarity_icon(rarity: String) -> String: match rarity: "common": return "⬜" "uncommon": return "🟩" "rare": return "🟦" "real_prize": return "✨" return "❓" # ─── Craft link ─────────────────────────────────────────────────────────────── func _on_open_craft() -> void: hide() # Find or load the fragment craft panel in the scene tree var main = get_tree().current_scene var fcp = main.get_node_or_null("FragmentCraftPanel") if fcp: fcp.show_panel() func _on_close() -> void: hide() closed.emit()