213 lines
8.4 KiB
GDScript
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()
|