Hand-authored 6 gradient-shaded SVG icons (gold coin, purple gem, green potion, blue RZ shield, coin stack, gift box) to replace the flat monochrome placeholder icons in the reels and reward list - no image-generation tool is available here, so this is genuine vector art with radial/linear gradients and highlight layers rather than a literal asset match to the mockup's renders. Combined with real Godot techniques: a layered-label glow effect on the "RZ" marquee, chase-lit marquee bulbs down both sides of the reel screen (looping Tweens), a much more pronounced arch silhouette and neon border on the cabinet, and an overall scale-up (bigger reels, marquee, lever) so the machine reads as the dominant element instead of a cramped placeholder. Verified with an actual rendered screenshot (not just structural checks) at each iteration.
703 lines
26 KiB
GDScript
703 lines
26 KiB
GDScript
extends VBoxContainer
|
|
|
|
const Brand := preload("res://scripts/ui/medmind_style.gd")
|
|
const STAR_ICON := preload("res://assets/icons/lucide/star.svg")
|
|
const CLOCK_ICON := preload("res://assets/icons/lucide/clock-3.svg")
|
|
const SLOT_COIN := preload("res://assets/icons/slots/slot_coin.svg")
|
|
const SLOT_GEM := preload("res://assets/icons/slots/slot_gem.svg")
|
|
const SLOT_POTION := preload("res://assets/icons/slots/slot_potion.svg")
|
|
const SLOT_SHIELD := preload("res://assets/icons/slots/slot_shield.svg")
|
|
const SLOT_COIN_STACK := preload("res://assets/icons/slots/slot_coin_stack.svg")
|
|
const SLOT_GIFT := preload("res://assets/icons/slots/slot_gift.svg")
|
|
const REEL_SYMBOLS := [SLOT_COIN, SLOT_GEM, SLOT_POTION, SLOT_SHIELD, SLOT_COIN_STACK, SLOT_GIFT]
|
|
const REWARD_ICONS := {
|
|
"jackpot": {"texture": SLOT_COIN, "modulate": false},
|
|
"epic": {"texture": SLOT_GEM, "modulate": false},
|
|
"great": {"texture": SLOT_SHIELD, "modulate": false},
|
|
"exam_token": {"texture": CLOCK_ICON, "modulate": true},
|
|
"qbank_reset": {"texture": SLOT_GIFT, "modulate": false},
|
|
"nice": {"texture": SLOT_COIN_STACK, "modulate": false},
|
|
"good_luck": {"texture": SLOT_COIN, "modulate": false, "tint": true},
|
|
}
|
|
|
|
var main: Control
|
|
|
|
var subtitle_label: Label
|
|
var machine_panel: PanelContainer
|
|
var reel_columns: Array = []
|
|
var win_up_to_card: PanelContainer
|
|
var win_up_to_label: Label
|
|
var cost_card: PanelContainer
|
|
var cost_value_label: Label
|
|
var auto_card: PanelContainer
|
|
var spin_button: Button
|
|
var auto_spin_toggle: CheckButton
|
|
var status_label: Label
|
|
var rewards_list: VBoxContainer
|
|
var footer_card: PanelContainer
|
|
var footer_icon_panel: PanelContainer
|
|
var footer_icon: TextureRect
|
|
var footer_heading: Label
|
|
var footer_description: Label
|
|
|
|
var _spinning := false
|
|
|
|
|
|
func _ready() -> void:
|
|
size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
add_theme_constant_override("separation", 18)
|
|
|
|
_build_header()
|
|
|
|
var body := HBoxContainer.new()
|
|
body.add_theme_constant_override("separation", 18)
|
|
body.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
add_child(body)
|
|
|
|
_build_machine(body)
|
|
_build_rewards_panel(body)
|
|
|
|
status_label = Label.new()
|
|
status_label.add_theme_font_override("font", Brand.font)
|
|
status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
add_child(status_label)
|
|
|
|
_build_footer()
|
|
|
|
CreditsService.credits_changed.connect(_refresh_spin_button)
|
|
CreditsService.spin_config_loaded.connect(_refresh_rewards)
|
|
UIThemeService.theme_changed.connect(func(_dark: bool): _refresh_theme_controls())
|
|
_refresh_rewards()
|
|
_refresh_spin_button()
|
|
|
|
|
|
func on_show(_payload: Variant = null) -> void:
|
|
CreditsService.refresh()
|
|
CreditsService.fetch_spin_config()
|
|
_refresh_rewards()
|
|
_refresh_spin_button()
|
|
|
|
|
|
func _build_header() -> void:
|
|
var title := Label.new()
|
|
title.text = "RZ Coin Slot Machine"
|
|
title.add_theme_font_override("font", Brand.font)
|
|
title.add_theme_font_size_override("font_size", 30)
|
|
add_child(title)
|
|
subtitle_label = Label.new()
|
|
subtitle_label.text = "Test your luck and win exciting rewards!"
|
|
subtitle_label.add_theme_font_override("font", Brand.font)
|
|
subtitle_label.add_theme_font_size_override("font_size", 14)
|
|
subtitle_label.add_theme_color_override("font_color", Brand.muted_color(UIThemeService.is_dark_mode()))
|
|
add_child(subtitle_label)
|
|
|
|
|
|
func _build_machine(parent: Control) -> void:
|
|
var dark := UIThemeService.is_dark_mode()
|
|
machine_panel = PanelContainer.new()
|
|
machine_panel.custom_minimum_size = Vector2(560, 0)
|
|
machine_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
machine_panel.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
|
|
machine_panel.add_theme_stylebox_override("panel", _cabinet_box())
|
|
parent.add_child(machine_panel)
|
|
|
|
var margin := MarginContainer.new()
|
|
margin.add_theme_constant_override("margin_left", 32)
|
|
margin.add_theme_constant_override("margin_right", 32)
|
|
margin.add_theme_constant_override("margin_top", 30)
|
|
margin.add_theme_constant_override("margin_bottom", 28)
|
|
machine_panel.add_child(margin)
|
|
|
|
var column := VBoxContainer.new()
|
|
column.add_theme_constant_override("separation", 20)
|
|
margin.add_child(column)
|
|
|
|
_build_marquee(column)
|
|
|
|
var reel_row := HBoxContainer.new()
|
|
reel_row.add_theme_constant_override("separation", 20)
|
|
reel_row.alignment = BoxContainer.ALIGNMENT_CENTER
|
|
column.add_child(reel_row)
|
|
|
|
reel_row.add_child(_build_light_strip(7))
|
|
|
|
var reel_screen := PanelContainer.new()
|
|
reel_screen.add_theme_stylebox_override("panel", _reel_screen_box())
|
|
reel_row.add_child(reel_screen)
|
|
|
|
var reel_margin := MarginContainer.new()
|
|
reel_margin.add_theme_constant_override("margin_left", 22)
|
|
reel_margin.add_theme_constant_override("margin_right", 22)
|
|
reel_margin.add_theme_constant_override("margin_top", 18)
|
|
reel_margin.add_theme_constant_override("margin_bottom", 18)
|
|
reel_screen.add_child(reel_margin)
|
|
|
|
var reels_grid := HBoxContainer.new()
|
|
reels_grid.add_theme_constant_override("separation", 18)
|
|
reel_margin.add_child(reels_grid)
|
|
|
|
reel_columns.clear()
|
|
for _column_index in range(3):
|
|
var reel_col := VBoxContainer.new()
|
|
reel_col.add_theme_constant_override("separation", 10)
|
|
reels_grid.add_child(reel_col)
|
|
var cells: Array = []
|
|
for _row_index in range(3):
|
|
var icon := TextureRect.new()
|
|
icon.texture = REEL_SYMBOLS[randi() % REEL_SYMBOLS.size()]
|
|
icon.custom_minimum_size = Vector2(58, 58)
|
|
icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
|
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
|
reel_col.add_child(icon)
|
|
cells.append(icon)
|
|
reel_columns.append(cells)
|
|
|
|
reel_row.add_child(_build_light_strip(7))
|
|
reel_row.add_child(_build_lever())
|
|
|
|
win_up_to_card = PanelContainer.new()
|
|
win_up_to_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
|
|
column.add_child(win_up_to_card)
|
|
var win_margin := MarginContainer.new()
|
|
win_margin.add_theme_constant_override("margin_left", 14)
|
|
win_margin.add_theme_constant_override("margin_right", 14)
|
|
win_margin.add_theme_constant_override("margin_top", 10)
|
|
win_margin.add_theme_constant_override("margin_bottom", 10)
|
|
win_up_to_card.add_child(win_margin)
|
|
win_up_to_label = Label.new()
|
|
win_up_to_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
win_up_to_label.add_theme_font_override("font", Brand.font)
|
|
win_up_to_label.add_theme_font_size_override("font_size", 15)
|
|
win_up_to_label.add_theme_color_override("font_color", Brand.text_color(dark))
|
|
win_margin.add_child(win_up_to_label)
|
|
|
|
var controls_row := HBoxContainer.new()
|
|
controls_row.add_theme_constant_override("separation", 12)
|
|
column.add_child(controls_row)
|
|
|
|
cost_card = PanelContainer.new()
|
|
cost_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
|
|
controls_row.add_child(cost_card)
|
|
var cost_margin := MarginContainer.new()
|
|
cost_margin.add_theme_constant_override("margin_left", 14)
|
|
cost_margin.add_theme_constant_override("margin_right", 14)
|
|
cost_margin.add_theme_constant_override("margin_top", 8)
|
|
cost_margin.add_theme_constant_override("margin_bottom", 8)
|
|
cost_card.add_child(cost_margin)
|
|
var cost_box := VBoxContainer.new()
|
|
cost_box.add_theme_constant_override("separation", 0)
|
|
cost_margin.add_child(cost_box)
|
|
var cost_caption := Label.new()
|
|
cost_caption.text = "Cost per spin"
|
|
cost_caption.add_theme_font_override("font", Brand.font)
|
|
cost_caption.add_theme_font_size_override("font_size", 11)
|
|
cost_caption.add_theme_color_override("font_color", Brand.muted_color(dark))
|
|
cost_box.add_child(cost_caption)
|
|
cost_value_label = Label.new()
|
|
cost_value_label.add_theme_font_override("font", Brand.font)
|
|
cost_value_label.add_theme_font_size_override("font_size", 15)
|
|
cost_value_label.add_theme_color_override("font_color", Brand.text_color(dark))
|
|
cost_box.add_child(cost_value_label)
|
|
|
|
spin_button = Button.new()
|
|
spin_button.text = "Spin"
|
|
spin_button.custom_minimum_size = Vector2(140, 58)
|
|
spin_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
spin_button.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
|
spin_button.add_theme_font_override("font", Brand.font)
|
|
spin_button.add_theme_font_size_override("font_size", 17)
|
|
_style_spin_button()
|
|
spin_button.pressed.connect(_on_spin_pressed)
|
|
controls_row.add_child(spin_button)
|
|
|
|
auto_card = PanelContainer.new()
|
|
auto_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
|
|
controls_row.add_child(auto_card)
|
|
var auto_margin := MarginContainer.new()
|
|
auto_margin.add_theme_constant_override("margin_left", 14)
|
|
auto_margin.add_theme_constant_override("margin_right", 14)
|
|
auto_margin.add_theme_constant_override("margin_top", 8)
|
|
auto_margin.add_theme_constant_override("margin_bottom", 8)
|
|
auto_card.add_child(auto_margin)
|
|
var auto_box := VBoxContainer.new()
|
|
auto_box.add_theme_constant_override("separation", 0)
|
|
auto_margin.add_child(auto_box)
|
|
auto_spin_toggle = CheckButton.new()
|
|
auto_spin_toggle.text = "Auto Spin"
|
|
auto_spin_toggle.add_theme_font_override("font", Brand.font)
|
|
auto_box.add_child(auto_spin_toggle)
|
|
var auto_caption := Label.new()
|
|
auto_caption.text = "10 spins"
|
|
auto_caption.add_theme_font_override("font", Brand.font)
|
|
auto_caption.add_theme_font_size_override("font_size", 11)
|
|
auto_caption.add_theme_color_override("font_color", Brand.muted_color(dark))
|
|
auto_box.add_child(auto_caption)
|
|
|
|
var info_label := Label.new()
|
|
info_label.text = "All rewards are instant and sent to your account."
|
|
info_label.add_theme_font_override("font", Brand.font)
|
|
info_label.add_theme_font_size_override("font_size", 11)
|
|
info_label.add_theme_color_override("font_color", Brand.muted_color(dark))
|
|
info_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
column.add_child(info_label)
|
|
|
|
|
|
func _build_marquee(column: VBoxContainer) -> void:
|
|
var row := HBoxContainer.new()
|
|
row.alignment = BoxContainer.ALIGNMENT_CENTER
|
|
row.add_theme_constant_override("separation", 14)
|
|
column.add_child(row)
|
|
row.add_child(_marquee_star())
|
|
row.add_child(_marquee_glow_label())
|
|
row.add_child(_marquee_star())
|
|
|
|
|
|
func _marquee_star() -> TextureRect:
|
|
var star := TextureRect.new()
|
|
star.texture = STAR_ICON
|
|
star.modulate = Brand.SKY_BLUE
|
|
star.custom_minimum_size = Vector2(16, 16)
|
|
star.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
|
star.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
|
return star
|
|
|
|
|
|
func _marquee_glow_label() -> Control:
|
|
var wrap := Control.new()
|
|
wrap.custom_minimum_size = Vector2(150, 70)
|
|
var halo_specs := [
|
|
{"size": 74, "alpha": 0.22, "offset": Vector2(-4, -4)},
|
|
{"size": 74, "alpha": 0.22, "offset": Vector2(4, 4)},
|
|
{"size": 64, "alpha": 0.4, "offset": Vector2.ZERO},
|
|
]
|
|
for spec in halo_specs:
|
|
var glow := Label.new()
|
|
glow.text = "RZ"
|
|
glow.add_theme_font_override("font", Brand.font)
|
|
glow.add_theme_font_size_override("font_size", spec["size"])
|
|
glow.add_theme_color_override("font_color", Color(Brand.SKY_BLUE, spec["alpha"]))
|
|
glow.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
glow.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
glow.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
glow.position += spec["offset"]
|
|
wrap.add_child(glow)
|
|
var mark := Label.new()
|
|
mark.text = "RZ"
|
|
mark.add_theme_font_override("font", Brand.font)
|
|
mark.add_theme_font_size_override("font_size", 46)
|
|
mark.add_theme_color_override("font_color", Brand.SKY_BLUE)
|
|
mark.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
mark.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
mark.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
wrap.add_child(mark)
|
|
return wrap
|
|
|
|
|
|
func _build_light_strip(count: int) -> VBoxContainer:
|
|
var strip := VBoxContainer.new()
|
|
strip.add_theme_constant_override("separation", 14)
|
|
strip.alignment = BoxContainer.ALIGNMENT_CENTER
|
|
for index in range(count):
|
|
var bulb := PanelContainer.new()
|
|
bulb.custom_minimum_size = Vector2(11, 11)
|
|
var bulb_box := StyleBoxFlat.new()
|
|
bulb_box.bg_color = Color("fef3c7")
|
|
bulb_box.set_corner_radius_all(6)
|
|
bulb_box.corner_detail = 12
|
|
bulb_box.anti_aliasing = false
|
|
bulb.add_theme_stylebox_override("panel", bulb_box)
|
|
strip.add_child(bulb)
|
|
_animate_bulb(bulb, index)
|
|
return strip
|
|
|
|
|
|
func _animate_bulb(bulb: Control, index: int) -> void:
|
|
bulb.modulate.a = 0.3
|
|
var tween := create_tween()
|
|
tween.set_loops()
|
|
tween.tween_interval(index * 0.12)
|
|
tween.tween_property(bulb, "modulate:a", 1.0, 0.4).set_trans(Tween.TRANS_SINE)
|
|
tween.tween_property(bulb, "modulate:a", 0.3, 0.4).set_trans(Tween.TRANS_SINE)
|
|
tween.tween_interval(1.4)
|
|
|
|
|
|
func _build_lever() -> Control:
|
|
var wrap := VBoxContainer.new()
|
|
wrap.alignment = BoxContainer.ALIGNMENT_BEGIN
|
|
wrap.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
wrap.add_theme_constant_override("separation", 0)
|
|
|
|
var knob := PanelContainer.new()
|
|
knob.custom_minimum_size = Vector2(30, 30)
|
|
knob.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
|
var knob_box := StyleBoxFlat.new()
|
|
knob_box.bg_color = Brand.ROYAL_BLUE
|
|
knob_box.set_corner_radius_all(15)
|
|
knob_box.corner_detail = 20
|
|
knob_box.anti_aliasing = false
|
|
knob.add_theme_stylebox_override("panel", knob_box)
|
|
wrap.add_child(knob)
|
|
|
|
var rod := ColorRect.new()
|
|
rod.color = Color("64748b")
|
|
rod.custom_minimum_size = Vector2(6, 140)
|
|
rod.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
|
wrap.add_child(rod)
|
|
|
|
return wrap
|
|
|
|
|
|
func _cabinet_box() -> StyleBoxFlat:
|
|
var box := StyleBoxFlat.new()
|
|
box.bg_color = Color("0b1220")
|
|
box.border_color = Brand.SKY_BLUE
|
|
box.set_border_width_all(4)
|
|
box.corner_radius_top_left = 90
|
|
box.corner_radius_top_right = 90
|
|
box.corner_radius_bottom_left = 20
|
|
box.corner_radius_bottom_right = 20
|
|
box.shadow_color = Color(Brand.SKY_BLUE, 0.45)
|
|
box.shadow_size = 18
|
|
return box
|
|
|
|
|
|
func _reel_screen_box() -> StyleBoxFlat:
|
|
var box := StyleBoxFlat.new()
|
|
box.bg_color = Color("e2e8f0")
|
|
box.border_color = Color("94a3b8")
|
|
box.set_border_width_all(2)
|
|
box.set_corner_radius_all(10)
|
|
return box
|
|
|
|
|
|
func _build_rewards_panel(parent: Control) -> void:
|
|
var dark := UIThemeService.is_dark_mode()
|
|
var panel := PanelContainer.new()
|
|
panel.custom_minimum_size = Vector2(280, 0)
|
|
panel.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
|
|
parent.add_child(panel)
|
|
|
|
var margin := MarginContainer.new()
|
|
margin.add_theme_constant_override("margin_left", 16)
|
|
margin.add_theme_constant_override("margin_right", 16)
|
|
margin.add_theme_constant_override("margin_top", 14)
|
|
margin.add_theme_constant_override("margin_bottom", 14)
|
|
panel.add_child(margin)
|
|
|
|
var column := VBoxContainer.new()
|
|
column.add_theme_constant_override("separation", 10)
|
|
margin.add_child(column)
|
|
|
|
var heading := Label.new()
|
|
heading.text = "Possible Rewards"
|
|
heading.add_theme_font_override("font", Brand.font)
|
|
heading.add_theme_font_size_override("font_size", 16)
|
|
heading.add_theme_color_override("font_color", Brand.text_color(dark))
|
|
column.add_child(heading)
|
|
|
|
rewards_list = VBoxContainer.new()
|
|
rewards_list.add_theme_constant_override("separation", 10)
|
|
column.add_child(rewards_list)
|
|
|
|
|
|
func _build_footer() -> void:
|
|
var dark := UIThemeService.is_dark_mode()
|
|
footer_card = PanelContainer.new()
|
|
footer_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
|
|
add_child(footer_card)
|
|
|
|
var margin := MarginContainer.new()
|
|
margin.add_theme_constant_override("margin_left", 16)
|
|
margin.add_theme_constant_override("margin_right", 16)
|
|
margin.add_theme_constant_override("margin_top", 12)
|
|
margin.add_theme_constant_override("margin_bottom", 12)
|
|
footer_card.add_child(margin)
|
|
|
|
var row := HBoxContainer.new()
|
|
row.add_theme_constant_override("separation", 14)
|
|
margin.add_child(row)
|
|
|
|
footer_icon_panel = PanelContainer.new()
|
|
footer_icon_panel.custom_minimum_size = Vector2(36, 36)
|
|
footer_icon_panel.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
|
footer_icon_panel.add_theme_stylebox_override("panel", Brand.icon_circle_box(dark, Brand.ROYAL_BLUE, 18))
|
|
footer_icon = TextureRect.new()
|
|
footer_icon.texture = STAR_ICON
|
|
footer_icon.custom_minimum_size = Vector2(18, 18)
|
|
footer_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
|
footer_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
|
footer_icon.modulate = Brand.ROYAL_BLUE
|
|
footer_icon_panel.add_child(footer_icon)
|
|
row.add_child(footer_icon_panel)
|
|
|
|
var copy := VBoxContainer.new()
|
|
copy.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
copy.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
|
copy.add_theme_constant_override("separation", 2)
|
|
footer_heading = Label.new()
|
|
footer_heading.text = "How it works"
|
|
footer_heading.add_theme_font_override("font", Brand.font)
|
|
footer_heading.add_theme_font_size_override("font_size", 15)
|
|
footer_heading.add_theme_color_override("font_color", Brand.text_color(dark))
|
|
copy.add_child(footer_heading)
|
|
footer_description = Label.new()
|
|
footer_description.text = "Spend RZ Coins to spin the slot machine. Win coins, boosts, tokens, and more!"
|
|
footer_description.add_theme_font_override("font", Brand.font)
|
|
footer_description.add_theme_font_size_override("font_size", 12)
|
|
footer_description.add_theme_color_override("font_color", Brand.muted_color(dark))
|
|
footer_description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
copy.add_child(footer_description)
|
|
row.add_child(copy)
|
|
|
|
|
|
func _refresh_theme_controls() -> void:
|
|
var dark := UIThemeService.is_dark_mode()
|
|
subtitle_label.add_theme_color_override("font_color", Brand.muted_color(dark))
|
|
win_up_to_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
|
|
win_up_to_label.add_theme_color_override("font_color", Brand.text_color(dark))
|
|
cost_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
|
|
cost_value_label.add_theme_color_override("font_color", Brand.text_color(dark))
|
|
auto_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
|
|
footer_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
|
|
footer_icon_panel.add_theme_stylebox_override("panel", Brand.icon_circle_box(dark, Brand.ROYAL_BLUE, 18))
|
|
footer_heading.add_theme_color_override("font_color", Brand.text_color(dark))
|
|
footer_description.add_theme_color_override("font_color", Brand.muted_color(dark))
|
|
_refresh_rewards()
|
|
|
|
|
|
func _reward_color(key: String) -> Color:
|
|
match key:
|
|
"jackpot": return Brand.AMBER
|
|
"epic": return Brand.PURPLE
|
|
"great": return Brand.ROYAL_BLUE
|
|
"exam_token": return Brand.ROYAL_BLUE
|
|
"qbank_reset": return Brand.SUCCESS
|
|
"nice": return Brand.SKY_BLUE
|
|
"good_luck": return Brand.SLATE
|
|
_: return Brand.ROYAL_BLUE
|
|
|
|
|
|
func _refresh_rewards() -> void:
|
|
var dark := UIThemeService.is_dark_mode()
|
|
cost_value_label.text = "%d RZ Coins" % CreditsService.spin_cost
|
|
|
|
var max_coins := 0
|
|
for reward_variant in CreditsService.spin_rewards:
|
|
if typeof(reward_variant) != TYPE_DICTIONARY:
|
|
continue
|
|
max_coins = maxi(max_coins, int((reward_variant as Dictionary).get("coinAmount", 0)))
|
|
win_up_to_label.text = "Win up to %s RZ Coins!" % _format_thousands(max_coins) if max_coins > 0 else ""
|
|
|
|
for child in rewards_list.get_children():
|
|
child.queue_free()
|
|
for reward_variant in CreditsService.spin_rewards:
|
|
if typeof(reward_variant) != TYPE_DICTIONARY:
|
|
continue
|
|
var reward: Dictionary = reward_variant
|
|
rewards_list.add_child(_build_reward_row(dark, reward))
|
|
|
|
|
|
func _build_reward_row(dark: bool, reward: Dictionary) -> HBoxContainer:
|
|
var key := str(reward.get("key", ""))
|
|
var accent := _reward_color(key)
|
|
var row := HBoxContainer.new()
|
|
row.add_theme_constant_override("separation", 10)
|
|
|
|
var icon_panel := PanelContainer.new()
|
|
icon_panel.custom_minimum_size = Vector2(36, 36)
|
|
icon_panel.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
|
icon_panel.add_theme_stylebox_override("panel", Brand.icon_circle_box(dark, accent, 18))
|
|
var icon := TextureRect.new()
|
|
var spec: Dictionary = REWARD_ICONS.get(key, {"texture": SLOT_COIN, "modulate": false})
|
|
icon.texture = spec.get("texture")
|
|
icon.custom_minimum_size = Vector2(22, 22)
|
|
icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
|
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
|
if spec.get("modulate", false):
|
|
icon.modulate = accent
|
|
elif spec.get("tint", false):
|
|
icon.modulate = Color("94a3b8")
|
|
icon_panel.add_child(icon)
|
|
row.add_child(icon_panel)
|
|
|
|
var copy := VBoxContainer.new()
|
|
copy.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
copy.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
|
copy.add_theme_constant_override("separation", 0)
|
|
var label := Label.new()
|
|
label.text = str(reward.get("label", ""))
|
|
label.add_theme_font_override("font", Brand.font)
|
|
label.add_theme_font_size_override("font_size", 13)
|
|
label.add_theme_color_override("font_color", Brand.text_color(dark))
|
|
copy.add_child(label)
|
|
var description := Label.new()
|
|
description.text = str(reward.get("description", ""))
|
|
description.add_theme_font_override("font", Brand.font)
|
|
description.add_theme_font_size_override("font_size", 11)
|
|
description.add_theme_color_override("font_color", Brand.muted_color(dark))
|
|
copy.add_child(description)
|
|
row.add_child(copy)
|
|
|
|
var percent_label := Label.new()
|
|
percent_label.text = "%s%%" % _format_percent(float(reward.get("probability", 0.0)) * 100.0)
|
|
percent_label.add_theme_font_override("font", Brand.font)
|
|
percent_label.add_theme_font_size_override("font_size", 13)
|
|
percent_label.add_theme_color_override("font_color", accent)
|
|
row.add_child(percent_label)
|
|
|
|
return row
|
|
|
|
|
|
func _refresh_spin_button() -> void:
|
|
spin_button.disabled = _spinning or CreditsService.credits < CreditsService.spin_cost
|
|
|
|
|
|
func _on_spin_pressed() -> void:
|
|
if _spinning:
|
|
return
|
|
_spinning = true
|
|
_refresh_spin_button()
|
|
var spin_count := 10 if auto_spin_toggle.button_pressed else 1
|
|
for _i in range(spin_count):
|
|
if CreditsService.credits < CreditsService.spin_cost:
|
|
break
|
|
var should_continue := await _do_one_spin()
|
|
if not should_continue:
|
|
break
|
|
_spinning = false
|
|
_refresh_spin_button()
|
|
|
|
|
|
func _do_one_spin() -> bool:
|
|
_animate_reels()
|
|
var result := await _await_spin_result()
|
|
if not result.get("ok", false):
|
|
status_label.text = str(result.get("message", "The spin could not be completed."))
|
|
status_label.add_theme_color_override("font_color", Brand.MAGENTA)
|
|
return false
|
|
|
|
var reward_label := str(result.get("reward_label", ""))
|
|
var coins_won := int(result.get("coins_won", 0))
|
|
status_label.text = "You won: %s" % reward_label
|
|
status_label.add_theme_color_override("font_color", Brand.SUCCESS)
|
|
if coins_won > 0:
|
|
_play_coin_burst(coins_won)
|
|
elif main != null:
|
|
var credits_badge: Object = main.get("credits_badge")
|
|
if credits_badge != null:
|
|
credits_badge.bump()
|
|
return true
|
|
|
|
|
|
func _await_spin_result() -> Dictionary:
|
|
var result := {}
|
|
var on_resolved := func(reward_key: String, reward_label: String, coins_won: int):
|
|
result.merge({"ok": true, "reward_key": reward_key, "reward_label": reward_label, "coins_won": coins_won}, true)
|
|
var on_failed := func(message: String):
|
|
result.merge({"ok": false, "message": message}, true)
|
|
CreditsService.spin_resolved.connect(on_resolved, CONNECT_ONE_SHOT)
|
|
CreditsService.credits_failed.connect(on_failed, CONNECT_ONE_SHOT)
|
|
CreditsService.spin()
|
|
var deadline := Time.get_ticks_msec() + 20000
|
|
while result.is_empty() and Time.get_ticks_msec() < deadline:
|
|
await get_tree().process_frame
|
|
if result.is_empty():
|
|
result = {"ok": false, "message": "The spin timed out. Check your connection and try again."}
|
|
if CreditsService.spin_resolved.is_connected(on_resolved):
|
|
CreditsService.spin_resolved.disconnect(on_resolved)
|
|
if CreditsService.credits_failed.is_connected(on_failed):
|
|
CreditsService.credits_failed.disconnect(on_failed)
|
|
return result
|
|
|
|
|
|
func _animate_reels() -> void:
|
|
for index in range(reel_columns.size()):
|
|
_spin_one_reel(reel_columns[index], 0.9 + index * 0.25)
|
|
|
|
|
|
func _spin_one_reel(cells: Array, duration: float) -> void:
|
|
var elapsed := 0.0
|
|
var interval := 0.06
|
|
while elapsed < duration:
|
|
var symbol: Texture2D = REEL_SYMBOLS[randi() % REEL_SYMBOLS.size()]
|
|
for cell in cells:
|
|
cell.texture = symbol
|
|
await get_tree().create_timer(interval).timeout
|
|
elapsed += interval
|
|
interval = minf(interval * 1.18, 0.22)
|
|
for cell in cells:
|
|
cell.texture = SLOT_COIN
|
|
|
|
|
|
func _play_coin_burst(coins_won: int) -> void:
|
|
if main == null:
|
|
return
|
|
var credits_badge: Control = main.get("credits_badge")
|
|
if credits_badge == null or not is_instance_valid(credits_badge):
|
|
return
|
|
var origin := machine_panel.get_global_rect().get_center()
|
|
var target := credits_badge.get_global_rect().get_center()
|
|
var coin_count := clampi(3 + coins_won / 500, 4, 10)
|
|
var flight_time := 0.55
|
|
var stagger := 0.035
|
|
for index in range(coin_count):
|
|
var coin := TextureRect.new()
|
|
coin.texture = SLOT_COIN
|
|
coin.custom_minimum_size = Vector2(16, 16)
|
|
coin.size = Vector2(16, 16)
|
|
coin.top_level = true
|
|
coin.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
coin.pivot_offset = Vector2(8, 8)
|
|
var jitter := Vector2(randf_range(-24.0, 24.0), randf_range(-16.0, 16.0))
|
|
coin.global_position = origin + jitter - coin.size * 0.5
|
|
add_child(coin)
|
|
var tween := create_tween()
|
|
tween.tween_interval(index * stagger)
|
|
tween.set_parallel(true)
|
|
tween.tween_property(coin, "global_position", target - coin.size * 0.5, flight_time).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN)
|
|
tween.tween_property(coin, "scale", Vector2(0.6, 0.6), flight_time).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN)
|
|
tween.set_parallel(false)
|
|
tween.tween_callback(coin.queue_free)
|
|
var settle_time := (coin_count - 1) * stagger + flight_time
|
|
get_tree().create_timer(settle_time).timeout.connect(func():
|
|
if is_instance_valid(credits_badge):
|
|
credits_badge.bump())
|
|
|
|
|
|
func _style_spin_button() -> void:
|
|
var normal := StyleBoxFlat.new()
|
|
normal.bg_color = Brand.ROYAL_BLUE
|
|
normal.set_corner_radius_all(8)
|
|
var hover := StyleBoxFlat.new()
|
|
hover.bg_color = Brand.SKY_BLUE
|
|
hover.set_corner_radius_all(8)
|
|
var disabled := StyleBoxFlat.new()
|
|
disabled.bg_color = Color(Brand.ROYAL_BLUE, 0.35)
|
|
disabled.set_corner_radius_all(8)
|
|
spin_button.add_theme_stylebox_override("normal", normal)
|
|
spin_button.add_theme_stylebox_override("hover", hover)
|
|
spin_button.add_theme_stylebox_override("pressed", normal)
|
|
spin_button.add_theme_stylebox_override("disabled", disabled)
|
|
spin_button.add_theme_color_override("font_color", Brand.DARK_TEXT)
|
|
spin_button.add_theme_color_override("font_hover_color", Brand.DARK_TEXT)
|
|
spin_button.add_theme_color_override("font_disabled_color", Color(Brand.DARK_TEXT, 0.7))
|
|
|
|
|
|
func _format_thousands(value: int) -> String:
|
|
var digits := str(absi(value))
|
|
var grouped := ""
|
|
var count := 0
|
|
for index in range(digits.length() - 1, -1, -1):
|
|
grouped = digits[index] + grouped
|
|
count += 1
|
|
if count % 3 == 0 and index != 0:
|
|
grouped = "," + grouped
|
|
return ("-" if value < 0 else "") + grouped
|
|
|
|
|
|
func _format_percent(value: float) -> String:
|
|
if absf(value - roundf(value)) < 0.001:
|
|
return str(int(roundf(value)))
|
|
return "%.1f" % value
|