Files
tekton/scripts/ui/gacha_panel.gd
T

213 lines
8.4 KiB
GDScript

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()