2026-05-29 19:54:56 +08:00

221 lines
8.7 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class_name ShopPanel
extends RefCounted
# 商店面板:
# - 负责普通道具、神秘商店、背景三类商品展示和购买入口。
# - 商品配置来自 GameConfigCatalog购买后通过 callbacks 扣货币/刷新仓库/刷新顶部资源。
# - 该面板只持有自己的列表节点,不直接访问 game_scene 的土地或其它弹窗状态。
const NumberFormatterScript := preload("res://scripts/core/number_formatter.gd")
var _ui
var _texture_provider
var _game_config
var _shop_api
var _get_item_icon_texture: Callable
var _context
var _callbacks: Dictionary = {}
var _tab_index := 0
var _panel: Control
var _list: GridContainer
func setup(options: Dictionary) -> void:
_ui = options.get("ui_builder")
_texture_provider = options.get("texture_provider")
_game_config = options.get("game_config")
_shop_api = options.get("shop_api")
_get_item_icon_texture = options.get("get_item_icon_texture", Callable())
_context = options.get("context")
_callbacks = options.get("callbacks", {})
if _callbacks.is_empty() and _context != null and _context.has_method("legacy_callbacks"):
_callbacks = _context.legacy_callbacks()
func create(tab_index: int) -> Control:
_tab_index = tab_index
_panel = _ui.create_common_panel_shell("ShopPanel", Vector2(620.0, 754.0), "panel_icon_shop", _texture_provider.atlas_path("common"), "panel_title_shop", _texture_provider.atlas_path("common"))
_ui.add_content_panel_background(_panel, "InnerPage", Vector2(67.0, 248.0), Vector2(510.0, 538.0))
_ui.create_tab_bar(_panel, "ShopTabs", ["普通道具", "神秘商店", "背景"], Vector2(89.0, 201.0), _tab_index, Callable(self, "_on_tab_pressed"))
var scroll := ScrollContainer.new()
scroll.name = "ShopScroll"
scroll.position = Vector2(71.0, 251.0)
scroll.size = Vector2(500.0, 531.0)
scroll.clip_contents = true
scroll.follow_focus = true
_ui.configure_vertical_scroll(scroll)
_panel.add_child(scroll)
var holder := MarginContainer.new()
holder.name = "ShopListHolder"
holder.custom_minimum_size = Vector2(500.0, 0.0)
holder.add_theme_constant_override("margin_left", 16)
holder.add_theme_constant_override("margin_top", 8)
holder.add_theme_constant_override("margin_right", 4)
holder.add_theme_constant_override("margin_bottom", 8)
scroll.add_child(holder)
_list = GridContainer.new()
_list.name = "ShopList"
_list.columns = 2
_list.custom_minimum_size = Vector2(464.0, 0.0)
_list.add_theme_constant_override("h_separation", 16)
_list.add_theme_constant_override("v_separation", 12)
holder.add_child(_list)
render()
return _panel
func list() -> GridContainer:
return _list
func render() -> void:
if _list == null:
return
_clear_children(_list)
var shop_type := 1
if _tab_index == 1:
shop_type = 2
elif _tab_index == 2:
shop_type = 3
var items: Array[Dictionary] = _game_config.get_shop_config_by_type(shop_type)
for item in items:
_list.add_child(_create_item_card(item))
func _on_tab_pressed(index: int) -> void:
_tab_index = index
var tab_bar := _panel.get_node_or_null("ShopTabs") if _panel != null else null
_ui.update_tab_bar(tab_bar as Control, _tab_index)
render()
func _create_item_card(shop_item: Dictionary) -> Control:
var holder := Control.new()
holder.name = "ShopItem%s" % int(shop_item.get("id", 0))
holder.custom_minimum_size = Vector2(224.0, 163.0)
holder.size = Vector2(224.0, 163.0)
_ui.add_nine_patch(holder, "Bg", _texture_provider.common("tipbg"), Vector2.ZERO, holder.size, Vector4(45.0, 21.0, 57.0, 69.0))
_ui.add_nine_patch(holder, "PriceBg", _texture_provider.common("blue_rect"), Vector2(116.0, 7.0), Vector2(89.0, 21.0), Vector4(22.0, 10.0, 13.0, 1.0))
_ui.add_panel_label(holder, "PriceTitle", "单价", Vector2(146.0, 10.0), Vector2(48.0, 18.0), 16, Color.WHITE, 0, Color.TRANSPARENT)
var item_config: Dictionary = shop_item.get("item", {})
_ui.add_panel_label(holder, "Name", str(item_config.get("name", "物品")), Vector2(13.0, 121.0), Vector2(193.0, 28.0), 24, Color.html("#753f24"), 0, Color.TRANSPARENT)
var group_icon := Control.new()
group_icon.name = "IconGroup"
group_icon.position = Vector2(14.0, 22.0)
group_icon.size = Vector2(86.0, 91.0)
holder.add_child(group_icon)
_ui.add_texture(group_icon, "ItemBg", _texture_provider.common("item_bg"), Vector2.ZERO, Vector2(90.0, 90.0))
_ui.add_texture(group_icon, "Icon", _item_icon_texture(int(item_config.get("icon", shop_item.get("item_id", 0)))), Vector2(8.0, 8.0), Vector2(74.0, 70.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED)
_ui.add_nine_patch(group_icon, "LvBg", _texture_provider.common("blue_rect"), Vector2(-1.0, -1.0), Vector2(52.0, 21.0), Vector4(0.0, 2.0, 5.0, 17.0))
var level_required := int(shop_item.get("playerLevel", shop_item.get("player_level", 1)))
_ui.add_panel_label(group_icon, "Lv", "Lv.%s" % level_required, Vector2(6.0, 1.0), Vector2(50.0, 20.0), 16, Color.WHITE, 0, Color.TRANSPARENT, HORIZONTAL_ALIGNMENT_LEFT)
var is_locked := int(_player_data().get("level", 1)) < level_required
var lock: TextureRect = _ui.add_texture(group_icon, "Lock", _texture_provider.common("img_lock"), Vector2(53.0, 49.0), Vector2(35.0, 40.0))
lock.visible = is_locked
var price_icon_key := "sicon_gold" if int(shop_item.get("gold", 0)) > 0 else "sicon_diamond"
_ui.add_texture(holder, "PriceIcon", _texture_provider.common(price_icon_key), Vector2(125.0, 42.0), Vector2(35.0, 35.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED)
var price := int(shop_item.get("gold", 0)) if int(shop_item.get("gold", 0)) > 0 else int(shop_item.get("diamond", shop_item.get("gem", 0)))
_ui.add_panel_label(holder, "Price", NumberFormatterScript.compact(price), Vector2(150.0, 43.0), Vector2(68.0, 28.0), 22, Color.html("#fffef7"), 2, Color.html("#685036"), HORIZONTAL_ALIGNMENT_LEFT)
var buy_button: Control = _ui.add_texture_button(holder, "Buy", _texture_provider.common("c_btn2"), Vector2(123.0, 70.0), Vector2(88.0, 44.0), "购买", Callable(self, "_buy_item").bind(shop_item.duplicate(true)), 22)
if is_locked:
buy_button.modulate = Color(0.72, 0.72, 0.72, 1.0)
var hit := buy_button.get_node_or_null("Hit") as Button
if hit != null:
hit.disabled = true
return holder
func _buy_item(shop_item: Dictionary) -> void:
if _is_busy():
return
var token := _token()
if token.is_empty():
_show_toast("登录信息失效")
return
var player_data := _player_data()
var level_required := int(shop_item.get("playerLevel", shop_item.get("player_level", 1)))
if int(player_data.get("level", 1)) < level_required:
_show_toast("购买失败:等级不足")
return
var gold_cost := int(shop_item.get("gold", 0))
var gem_cost := int(shop_item.get("diamond", shop_item.get("gem", 0)))
if gold_cost > int(player_data.get("gold", 0)):
_show_toast("购买失败:金豆不足")
return
if gem_cost > int(player_data.get("gem", 0)):
_show_toast("购买失败:资源不足,请充值!")
return
_set_busy(true)
var count := 100 if int(shop_item.get("id", 0)) == 99 else 1
var result: Dictionary = await _shop_api.buy(token, int(shop_item.get("id", 0)), count)
_set_busy(false)
if not result.get("ok", false):
_show_toast("购买失败:%s" % result.get("game_status", result.get("status", 0)))
return
_call("decrease_player_currency", [gold_cost, gem_cost])
_call("render_player")
_show_toast("购买成功")
await _call_async("refresh_store_house")
render()
func _item_icon_texture(icon_id: int) -> Texture2D:
if _texture_provider != null and _texture_provider.has_method("item_icon"):
return _texture_provider.item_icon(icon_id)
if _get_item_icon_texture.is_valid():
return _get_item_icon_texture.call(icon_id) as Texture2D
return _texture_provider.common("item_bg")
func _player_data() -> Dictionary:
var result = _call("player_data", [], {})
return result if result is Dictionary else {}
func _token() -> String:
return str(_call("token", [], ""))
func _is_busy() -> bool:
return bool(_call("is_operation_in_progress", [], false))
func _set_busy(value: bool) -> void:
_call("set_operation_in_progress", [value])
func _show_toast(message: String) -> void:
_call("show_toast", [message])
func _call(name: String, args: Array = [], fallback: Variant = null) -> Variant:
if _context != null and _context.has_method("call_action"):
return _context.call_action(name, args, fallback)
var callable: Callable = _callbacks.get(name, Callable())
if not callable.is_valid():
return fallback
return callable.callv(args)
func _call_async(name: String, args: Array = []) -> Variant:
if _context != null and _context.has_method("call_action_async"):
return await _context.call_action_async(name, args)
var callable: Callable = _callbacks.get(name, Callable())
if not callable.is_valid():
return null
return await callable.callv(args)
func _clear_children(parent: Node) -> void:
for child in parent.get_children():
parent.remove_child(child)
child.queue_free()