class_name GoldExchangePanel extends PanelModule # 兑换金豆面板: # - 负责钻石/道具兑换金豆的配置展示和兑换请求。 # - 消耗判断走 PanelModule,兑换成功后只增加金豆并刷新顶部资源。 # - 该入口来自左侧“兑换金豆”按钮,UI 尺寸需保持原版面板比例。 const NumberFormatterScript := preload("res://scripts/core/number_formatter.gd") var _mode := "gemToGold" var _amount := 1 var _history_list: VBoxContainer var _status_label: Label var _amount_label: Label var _result_label: Label func open() -> void: _close_modal(false) _mode = "gemToGold" _amount = _step() var panel: Control = _ui.create_modal_root("GoldExchangePanel") var player := _player_data() _ui.add_scale9_patch(panel, "PanelBg", _texture_provider.common("c_panel"), Vector2(20.0, 76.0), Vector2(600.0, 820.0), Rect2(122.0, 174.0, 21.0, 10.0)) _ui.add_texture(panel, "TitleBg", _texture_provider.frame(_texture_provider.atlas_path("common2"), "common2_diban_png"), Vector2(168.0, 70.0), Vector2(304.0, 70.0)) _ui.add_panel_label(panel, "TitleText", "兑换金豆", Vector2(170.0, 84.0), Vector2(300.0, 48.0), 34, Color.html("#fffbf2"), 2, Color.html("#724f2e")) _status_label = _ui.add_panel_label(panel, "Status", "", Vector2(60.0, 142.0), Vector2(520.0, 34.0), 20, Color.html("#5b3616"), 0, Color.TRANSPARENT) _ui.add_panel_label(panel, "GoldBalance", "金豆:%s" % NumberFormatterScript.compact(int(player.get("gold", 0))), Vector2(92.0, 184.0), Vector2(210.0, 30.0), 22, Color.html("#5b3616"), 0, Color.TRANSPARENT, HORIZONTAL_ALIGNMENT_LEFT) _ui.add_panel_label(panel, "GemBalance", "钻石:%s" % NumberFormatterScript.compact(int(player.get("gem", player.get("diamond", 0)))), Vector2(332.0, 184.0), Vector2(210.0, 30.0), 22, Color.html("#5b3616"), 0, Color.TRANSPARENT, HORIZONTAL_ALIGNMENT_LEFT) _ui.add_texture_button(panel, "GemToGold", _texture_provider.common("c_btn3"), Vector2(80.0, 228.0), Vector2(220.0, 54.0), "钻石兑金豆", Callable(self, "_set_mode").bind("gemToGold"), 24) _ui.add_texture_button(panel, "GoldToGem", _texture_provider.common("c_btn3"), Vector2(340.0, 228.0), Vector2(220.0, 54.0), "金豆兑钻石", Callable(self, "_set_mode").bind("goldToGem"), 24) _ui.add_panel_label(panel, "ModeTitle", "", Vector2(80.0, 306.0), Vector2(480.0, 40.0), 28, Color.html("#fffbf2"), 2, Color.html("#724f2e")) _ui.add_panel_label(panel, "Rate", "", Vector2(80.0, 348.0), Vector2(480.0, 30.0), 20, Color.html("#7d1c1c"), 0, Color.TRANSPARENT) _ui.add_texture_button(panel, "Minus", _texture_provider.common("btn_sub"), Vector2(112.0, 394.0), Vector2(54.0, 44.0), "", Callable(self, "_change_amount").bind(-1), 20) _ui.add_scale9_patch(panel, "InputBg", _texture_provider.frame(_texture_provider.atlas_path("common2"), "common2_inputbg_png"), Vector2(184.0, 396.0), Vector2(270.0, 42.0), Rect2(13.0, 4.0, 84.0, 30.0)) _amount_label = _ui.add_panel_label(panel, "Amount", "", Vector2(196.0, 400.0), Vector2(246.0, 34.0), 24, Color.html("#5b3616"), 0, Color.TRANSPARENT) _ui.add_texture_button(panel, "Plus", _texture_provider.common("btn_add"), Vector2(474.0, 394.0), Vector2(54.0, 44.0), "", Callable(self, "_change_amount").bind(1), 20) _result_label = _ui.add_panel_label(panel, "Result", "", Vector2(80.0, 448.0), Vector2(480.0, 30.0), 20, Color.html("#5b3616"), 0, Color.TRANSPARENT) _ui.add_texture_button(panel, "Exchange", _texture_provider.common("c_btn3"), Vector2(245.0, 486.0), Vector2(150.0, 58.0), "兑换", Callable(self, "_exchange"), 28) _ui.add_panel_label(panel, "HistoryTitle", "兑换记录", Vector2(70.0, 566.0), Vector2(500.0, 32.0), 24, Color.html("#fffbf2"), 2, Color.html("#724f2e")) _ui.add_scale9_patch(panel, "HistoryBg", _texture_provider.common("inner_page"), Vector2(70.0, 602.0), Vector2(500.0, 214.0), Rect2(31.0, 31.0, 8.0, 9.0)) var scroll := ScrollContainer.new() scroll.position = Vector2(70.0, 607.0) scroll.size = Vector2(500.0, 204.0) scroll.clip_contents = true _ui.configure_vertical_scroll(scroll) panel.add_child(scroll) _history_list = VBoxContainer.new() _history_list.custom_minimum_size = Vector2(490.0, 0.0) scroll.add_child(_history_list) _ui.add_texture_button(panel, "PanelClose", _texture_provider.common("c_btn3"), Vector2(240.0, 820.0), Vector2(160.0, 58.0), "离开", Callable(self, "_close_modal").bind(true), 30) _open_modal(panel) await render() func _set_mode(mode: String) -> void: _mode = mode _amount = _step() await render() func _change_amount(direction: int) -> void: _amount = maxi(_step(), _amount + direction * _step()) _update_labels() func render() -> void: _update_labels() var history_list := _history_list if not _clear_children_if_live(history_list): return _ui.add_panel_label(history_list, "Loading", "正在读取兑换记录...", Vector2.ZERO, Vector2(490.0, 40.0), 18, Color.html("#5b3616"), 0, Color.TRANSPARENT) var result: Dictionary = await _activity_api.fetch_exchange_history(_token(), 0 if _mode == "gemToGold" else 1) if not _is_current_node(history_list, _history_list): return _clear_children(history_list) if not result.get("ok", false): _ui.add_panel_label(history_list, "Error", _response_status_text(result, "兑换记录读取失败"), Vector2.ZERO, Vector2(490.0, 40.0), 18, Color.html("#ce5f5f"), 0, Color.TRANSPARENT) return var data = _response_data(result, {}) var rows: Array = data.get("list", []) if data is Dictionary else [] if _is_live_node(_status_label): _status_label.text = I18nScript.t("兑换记录已加载" if not rows.is_empty() else "暂无兑换记录") for index in range(mini(rows.size(), 12)): if rows[index] is Dictionary: _add_history_row(history_list, rows[index]) func _update_labels() -> void: var panel := _history_list.get_parent().get_parent() as Control if _is_live_node(_history_list) and _history_list.get_parent() != null else null var mode_title := panel.get_node_or_null("ModeTitle") as Label if panel != null else null var rate_label := panel.get_node_or_null("Rate") as Label if panel != null else null var is_gem := _mode == "gemToGold" var rate := _rate() if mode_title != null: mode_title.text = I18nScript.t("钻石兑换金豆" if is_gem else "金豆兑换钻石") if rate_label != null: rate_label.text = I18nScript.t("兑换比例 1 钻石 = %s 金豆" % rate if is_gem else "兑换比例 %s 金豆 = 1 钻石" % rate) if _is_live_node(_amount_label): _amount_label.text = str(_amount) if _is_live_node(_result_label): _result_label.text = I18nScript.t("预计获得 %s 金豆" % _result() if is_gem else "预计获得 %s 钻石" % _result()) func _exchange() -> void: if _is_busy(): return var player := _player_data() var owned := int(player.get("gem", player.get("diamond", 0))) if _mode == "gemToGold" else int(player.get("gold", 0)) if _amount <= 0 or _amount > owned: _show_toast("钻石不足" if _mode == "gemToGold" else "金豆不足") return _set_busy(true) var result: Dictionary if _mode == "gemToGold": result = await _activity_api.exchange_gem_to_gold(_token(), _amount) else: result = await _activity_api.exchange_gold_to_gem(_token(), _amount) _set_busy(false) if not result.get("ok", false): _show_toast(_response_status_text(result, "兑换失败")) return var data = _response_data(result, {}) if data is Dictionary: for key in data.keys(): player[key] = data[key] else: if _mode == "gemToGold": player["gem"] = maxi(int(player.get("gem", player.get("diamond", 0))) - _amount, 0) player["gold"] = int(player.get("gold", 0)) + _result() else: player["gold"] = maxi(int(player.get("gold", 0)) - _amount, 0) player["gem"] = int(player.get("gem", player.get("diamond", 0))) + _result() _render_player() _show_toast("兑换成功") await render() func _add_history_row(parent: Control, row: Dictionary) -> void: var holder := Control.new() holder.custom_minimum_size = Vector2(490.0, 62.0) holder.size = Vector2(490.0, 62.0) parent.add_child(holder) _ui.add_panel_label(holder, "Time", _format_timestamp(int(row.get("created_at", 0))), Vector2(12.0, 8.0), Vector2(190.0, 24.0), 18, Color.html("#f4991d"), 0, Color.TRANSPARENT, HORIZONTAL_ALIGNMENT_LEFT) _ui.add_panel_label(holder, "Desc", "兑换获得金豆" if _mode == "gemToGold" else "兑换获得钻石", Vector2(12.0, 34.0), Vector2(370.0, 24.0), 18, Color.html("#7d1c1c"), 0, Color.TRANSPARENT, HORIZONTAL_ALIGNMENT_LEFT) _ui.add_panel_label(holder, "Num", "+%s" % int(row.get("num", row.get("count", 0))), Vector2(390.0, 34.0), Vector2(92.0, 24.0), 18, Color.html("#2f9c11"), 0, Color.TRANSPARENT, HORIZONTAL_ALIGNMENT_RIGHT) _ui.add_nine_patch(holder, "Line", _texture_provider.common("line"), Vector2(8.0, 60.0), Vector2(482.0, 2.0), Vector4.ZERO) func _rate() -> int: return maxi(int(_game_config.get_common_config(24 if _mode == "gemToGold" else 17).get("min", 10)), 1) func _step() -> int: return 1 if _mode == "gemToGold" else _rate() func _result() -> int: var rate := _rate() return _amount * rate if _mode == "gemToGold" else int(floor(float(_amount) / float(rate)))