diff --git a/scripts/ui/rz_slot_machine_screen.gd b/scripts/ui/rz_slot_machine_screen.gd index 918479e..d485609 100644 --- a/scripts/ui/rz_slot_machine_screen.gd +++ b/scripts/ui/rz_slot_machine_screen.gd @@ -28,7 +28,8 @@ var main: Control var subtitle_label: Label var machine_root: Control -var reel_columns: Array = [] +var lever_button: TextureButton +var reel_states: Array = [] var win_up_to_card: PanelContainer var win_up_to_label: Label var cost_card: PanelContainer @@ -134,32 +135,53 @@ func _build_machine(parent: Control) -> void: reels_grid.add_theme_constant_override("separation", 16) center.add_child(reels_grid) - reel_columns.clear() + var cell := 66.0 + reel_states.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 clip := Control.new() + clip.clip_contents = true + clip.custom_minimum_size = Vector2(cell, cell * 3) + reels_grid.add_child(clip) + + var strip := VBoxContainer.new() + strip.add_theme_constant_override("separation", 0) + clip.add_child(strip) + + var icons: Array = [] + for _row_index in range(4): var icon := TextureRect.new() icon.texture = REEL_SYMBOLS[randi() % REEL_SYMBOLS.size()] - icon.custom_minimum_size = Vector2(66, 66) + icon.custom_minimum_size = Vector2(cell, cell) 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) + strip.add_child(icon) + icons.append(icon) - var lever := TextureRect.new() - lever.texture = LEVER_TEXTURE - lever.anchor_left = 0.80 - lever.anchor_right = 1.06 - lever.anchor_top = 0.24 - lever.anchor_bottom = 0.88 - lever.expand_mode = TextureRect.EXPAND_IGNORE_SIZE - lever.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT - lever.mouse_filter = Control.MOUSE_FILTER_IGNORE - machine_root.add_child(lever) + reel_states.append({ + "strip": strip, + "icons": icons, + "step": cell, + "speed": 620.0, + "spinning": false, + "stop_requested": false, + "target_texture": null, + "recycles_after_target": -1, + "done": true, + }) + + lever_button = TextureButton.new() + lever_button.texture_normal = LEVER_TEXTURE + lever_button.ignore_texture_size = true + lever_button.stretch_mode = TextureButton.STRETCH_KEEP_ASPECT_CENTERED + lever_button.anchor_left = 0.80 + lever_button.anchor_right = 1.06 + lever_button.anchor_top = 0.24 + lever_button.anchor_bottom = 0.88 + lever_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND + lever_button.tooltip_text = "Pull to spin" + lever_button.resized.connect(func(): lever_button.pivot_offset = Vector2(lever_button.size.x * 0.5, lever_button.size.y * 0.92)) + lever_button.pressed.connect(_on_lever_pulled) + machine_root.add_child(lever_button) func _build_controls(parent: Control) -> void: @@ -431,7 +453,20 @@ func _build_reward_row(dark: bool, reward: Dictionary) -> HBoxContainer: func _refresh_spin_button() -> void: - spin_button.disabled = _spinning or CreditsService.credits < CreditsService.spin_cost + var disabled := _spinning or CreditsService.credits < CreditsService.spin_cost + spin_button.disabled = disabled + lever_button.disabled = disabled + + +func _on_lever_pulled() -> void: + _play_lever_pull_animation() + _on_spin_pressed() + + +func _play_lever_pull_animation() -> void: + var tween := create_tween() + tween.tween_property(lever_button, "rotation_degrees", -20.0, 0.1).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) + tween.tween_property(lever_button, "rotation_degrees", 0.0, 0.3).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT) func _on_spin_pressed() -> void: @@ -451,13 +486,33 @@ func _on_spin_pressed() -> void: func _do_one_spin() -> bool: - _animate_reels() + for state in reel_states: + state["stop_requested"] = false + state["target_texture"] = null + state["recycles_after_target"] = -1 + state["done"] = false + _spin_reel(state) + var result := await _await_spin_result() + if not result.get("ok", false): + for state in reel_states: + state["target_texture"] = REEL_SYMBOLS[randi() % REEL_SYMBOLS.size()] + state["stop_requested"] = true + await _await_reels_done() 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_key := str(result.get("reward_key", "")) + var reward_texture := _icon_for_reward(reward_key) + for index in range(reel_states.size()): + var state: Dictionary = reel_states[index] + get_tree().create_timer(index * 0.25).timeout.connect(func(): + state["target_texture"] = reward_texture + state["stop_requested"] = true) + await _await_reels_done() + 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 @@ -471,6 +526,24 @@ func _do_one_spin() -> bool: return true +func _icon_for_reward(key: String) -> Texture2D: + var spec: Dictionary = REWARD_ICONS.get(key, {}) + var texture: Texture2D = spec.get("texture", SLOT_COIN) + return texture if texture != null else SLOT_COIN + + +func _await_reels_done() -> void: + while true: + var all_done := true + for state in reel_states: + if not state.get("done", false): + all_done = false + break + if all_done: + return + await get_tree().process_frame + + func _await_spin_result() -> Dictionary: var result := {} var on_resolved := func(reward_key: String, reward_label: String, coins_won: int): @@ -492,23 +565,33 @@ func _await_spin_result() -> Dictionary: 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 _spin_reel(state: Dictionary) -> void: + state["spinning"] = true + var strip: VBoxContainer = state["strip"] + var icons: Array = state["icons"] + var step: float = state["step"] + var speed: float = state["speed"] + while true: + var current_speed: float = speed if state["recycles_after_target"] == -1 else speed * 0.45 + strip.position.y -= current_speed * get_process_delta_time() + if strip.position.y <= -step: + strip.position.y += step + var front: TextureRect = icons.pop_front() + strip.move_child(front, strip.get_child_count() - 1) + icons.append(front) + if state["stop_requested"] and state["recycles_after_target"] == -1: + front.texture = state["target_texture"] + state["recycles_after_target"] = 0 + else: + front.texture = REEL_SYMBOLS[randi() % REEL_SYMBOLS.size()] + if state["recycles_after_target"] >= 0: + state["recycles_after_target"] += 1 + if state["recycles_after_target"] == 2: + strip.position.y = 0.0 + break + await get_tree().process_frame + state["spinning"] = false + state["done"] = true func _play_coin_burst(coins_won: int) -> void: