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

275 lines
9.7 KiB
GDScript

class_name FriendPanel
extends RefCounted
# 好友/排行面板:
# - 负责全国排行、好友排行、翻页和好友行渲染。
# - 排行接口返回的数据只在本面板分页状态中保存,不参与全局玩家状态。
# - 行按钮后续扩展访问好友时,应继续通过 callbacks 发回上层路由。
const NumberFormatterScript := preload("res://scripts/core/number_formatter.gd")
const I18nScript := preload("res://scripts/core/i18n.gd")
var _ui
var _texture_provider
var _friend_api
var _context
var _callbacks: Dictionary = {}
var _page_size := 7
var _rank_type := 0
var _show_type := 1
var _page_index := 1
var _page_max := 1
var _panel: Control
var _list: Control
var _page_label: Label
var _title_lv_label: Label
func setup(options: Dictionary) -> void:
_ui = options.get("ui_builder")
_texture_provider = options.get("texture_provider")
_friend_api = options.get("friend_api")
_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()
_page_size = int(options.get("page_size", _page_size))
func create(rank_type: int) -> Control:
_rank_type = rank_type
_show_type = 1
_page_index = 1
_page_max = 1
_panel = _ui.create_common_panel_shell("FriendPanel", Vector2(594.0, 862.0), "panel_icon_friend", _texture_provider.atlas_path("friend"), "panel_title_friend", _texture_provider.atlas_path("friend"))
_ui.add_content_panel_background(_panel, "InnerPage", Vector2(88.0, 206.0), Vector2(464.0, 638.0))
_ui.create_tab_bar(_panel, "FriendTabs", ["全国排名", "好友排名"], Vector2(88.0, 159.0), _rank_type, Callable(self, "_on_tab_pressed"), 50.0)
_ui.add_panel_label(_panel, "TitleRank", "排名", Vector2(120.0, 233.0), Vector2(60.0, 32.0), 26, Color.html("#ffcc00"), 3, Color.html("#9c460b"))
_ui.add_panel_label(_panel, "TitleName", "游戏昵称", Vector2(201.0, 233.0), Vector2(108.0, 32.0), 26, Color.html("#ffcc00"), 3, Color.html("#9c460b"))
_title_lv_label = _ui.add_panel_label(_panel, "TitleLv", "等级", Vector2(335.0, 233.0), Vector2(72.0, 32.0), 26, Color.html("#ffcc00"), 3, Color.html("#9c460b"))
_ui.add_panel_label(_panel, "TitleHome", "家园", Vector2(413.0, 233.0), Vector2(64.0, 32.0), 26, Color.html("#ffcc00"), 3, Color.html("#9c460b"))
var scroll := ScrollContainer.new()
scroll.name = "FriendScroll"
scroll.position = Vector2(102.0, 278.0)
scroll.size = Vector2(446.0, 487.0)
scroll.clip_contents = true
_ui.configure_vertical_scroll(scroll)
_panel.add_child(scroll)
_list = Control.new()
_list.name = "FriendList"
_list.custom_minimum_size = Vector2(450.0, float(_page_size) * 70.0)
_list.size = Vector2(450.0, float(_page_size) * 70.0)
scroll.add_child(_list)
_ui.add_texture_button(_panel, "FriendPrev", _texture_provider.common("btn_page_prev"), Vector2(364.0, 791.0), Vector2(46.0, 41.0), "", Callable(self, "_on_prev_pressed"), 20)
_ui.add_texture_button(_panel, "FriendNext", _texture_provider.common("btn_page_next"), Vector2(481.0, 791.0), Vector2(46.0, 41.0), "", Callable(self, "_on_next_pressed"), 20)
_page_label = _ui.add_panel_label(_panel, "FriendPage", "1/1", Vector2(409.0, 800.0), Vector2(72.0, 28.0), 24, Color.html("#753f24"), 0, Color.TRANSPARENT)
_ui.add_texture_button(_panel, "FriendGold", _texture_provider.friend("friend_btn_caifu"), Vector2(94.0, 773.0), Vector2(72.0, 65.0), "", Callable(self, "_on_gold_pressed"), 20)
_ui.add_texture_button(_panel, "FriendLv", _texture_provider.friend("friend_btn_lv"), Vector2(173.0, 760.0), Vector2(70.0, 80.0), "", Callable(self, "_on_lv_pressed"), 20)
_ui.add_texture_button(_panel, "FriendPower", _texture_provider.friend("friend_btn_zhanli"), Vector2(253.0, 773.0), Vector2(66.0, 67.0), "", Callable(self, "_on_power_pressed"), 20)
return _panel
func list() -> Control:
return _list
func page_label() -> Label:
return _page_label
func title_lv_label() -> Label:
return _title_lv_label
func update_page() -> void:
var list := _list
if _friend_api == null or not _is_live_node(list):
return
_render_empty_rows(list)
_update_page_label()
var token := _token()
if token.is_empty():
return
var result: Dictionary = await _friend_api.fetch_list(token, _rank_type, _show_type, _page_index, _page_size)
if not _is_current_node(list, _list):
return
if not result.get("ok", false):
_show_toast("排行榜加载失败:%s" % result.get("game_status", result.get("status", 0)))
return
var body = result.get("body", {})
var data = body.get("data", {}) if body is Dictionary else {}
if not data is Dictionary:
return
_page_max = maxi(int(ceil(float(int(data.get("total_count", 0))) / float(_page_size))), 1)
_update_page_label()
_clear_children(list)
var rows = data.get("list", [])
var row_index := 0
if rows is Array:
for raw_row in rows:
if raw_row is Dictionary:
var row: Dictionary = raw_row.duplicate(true)
if not row.has("ranking"):
row["ranking"] = (_page_index - 1) * _page_size + row_index + 1
_add_row_to_list(list, _create_row(row), row_index)
row_index += 1
for index in range(row_index, _page_size):
_add_row_to_list(list, _create_row({}), index)
func _on_tab_pressed(index: int) -> void:
_rank_type = index
_page_index = 1
var tab_bar := _panel.get_node_or_null("FriendTabs") if _panel != null else null
_ui.update_tab_bar(tab_bar as Control, _rank_type)
update_page()
func _on_prev_pressed() -> void:
if _page_index <= 1:
return
_page_index -= 1
update_page()
func _on_next_pressed() -> void:
if _page_index >= _page_max:
return
_page_index += 1
update_page()
func _on_gold_pressed() -> void:
_show_type = 0
if _title_lv_label != null:
_title_lv_label.text = I18nScript.t("财富")
_page_index = 1
update_page()
func _on_lv_pressed() -> void:
_show_type = 1
if _title_lv_label != null:
_title_lv_label.text = I18nScript.t("等级")
_page_index = 1
update_page()
func _on_power_pressed() -> void:
_show_toast("战力排行暂未开放")
func _render_empty_rows(parent: Control) -> void:
if not _clear_children_if_live(parent):
return
for index in range(_page_size):
_add_row_to_list(parent, _create_row({}), index)
func _update_page_label() -> void:
if _is_live_node(_page_label):
_page_label.text = "%s/%s" % [_page_index, _page_max]
func _add_row_to_list(parent: Control, row: Control, index: int) -> void:
if not _is_live_node(parent):
return
row.position = Vector2(0.0, float(index) * 70.0)
parent.add_child(row)
func _create_row(row: Dictionary) -> Control:
var holder := Control.new()
holder.name = "FriendRow%s" % int(row.get("user_id", 0))
holder.custom_minimum_size = Vector2(450.0, 70.0)
holder.size = Vector2(450.0, 70.0)
_ui.add_texture(holder, "Bg", _texture_provider.rank("friend_item"), Vector2(0.0, 8.0), Vector2(430.0, 56.0))
if row.is_empty():
return holder
var rank := int(row.get("ranking", row.get("rank", 0)))
if rank >= 1 and rank <= 3:
_ui.add_texture(holder, "RankIcon", _texture_provider.rank("rank%s" % rank), Vector2(24.0, 15.0), Vector2(31.0, 40.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED)
else:
_ui.add_panel_label(holder, "Rank", str(rank), Vector2(19.0, 27.0), Vector2(39.0, 24.0), 24, Color.html("#753f24"), 0, Color.TRANSPARENT)
_ui.add_panel_label(holder, "Name", str(row.get("nickname", row.get("name", ""))).left(8), Vector2(46.0, 27.0), Vector2(193.0, 24.0), 20, Color.html("#753f24"), 0, Color.TRANSPARENT)
var value := int(row.get("level", row.get("lv", 0))) if _show_type == 1 else int(row.get("gold", 0))
_ui.add_panel_label(holder, "Value", NumberFormatterScript.compact(value), Vector2(200.0, 27.0), Vector2(113.0, 24.0), 20, Color.html("#753f24"), 0, Color.TRANSPARENT)
_ui.add_texture_button(holder, "Home", _texture_provider.rank("btn_friend_home"), Vector2(308.0, 6.0), Vector2(63.0, 59.0), "", Callable(self, "_on_home_pressed").bind(row.duplicate(true)), 20)
return holder
func _on_home_pressed(row: Dictionary) -> void:
await _visit_friend(row)
func _token() -> String:
return str(_call("token", [], ""))
func _show_toast(message: String) -> void:
_call("show_toast", [message])
func _visit_friend(row: Dictionary) -> void:
var target_id := _target_user_id(row)
if target_id <= 0:
_show_toast("好友数据异常")
return
var own_id := int(_call("player_id", [], 0))
if own_id > 0 and own_id == target_id:
_show_toast("这是你的农场,不用去啦!")
return
await _call_async("visit_friend_home", [row])
func _target_user_id(row: Dictionary) -> int:
for key in ["uid", "user_id", "id"]:
var value := int(row.get(key, 0))
if value > 0:
return value
return 0
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()
func _is_live_node(node: Variant) -> bool:
return node != null and is_instance_valid(node) and node is Node
func _is_current_node(candidate: Variant, current: Variant) -> bool:
return _is_live_node(candidate) and _is_live_node(current) and candidate == current
func _clear_children_if_live(parent: Variant) -> bool:
if not _is_live_node(parent):
return false
_clear_children(parent as Node)
return true