class_name BuildingPanel extends RefCounted # 建筑升级面板: # - 管理房屋升级、土地升级和扩建,消耗判断统一走 callbacks/InventoryFacade。 # - FarmMapper 负责把后端土地列表转成主场景可渲染数据,本面板不直接拼土地节点。 # - 升级成功后只触发资源/土地刷新,具体视觉重绘仍由 game_scene/LandGridView 完成。 const NumberFormatterScript := preload("res://scripts/core/number_formatter.gd") const I18nScript := preload("res://scripts/core/i18n.gd") const LAND_UPGRADE_CONTENT_CENTER_X := 268.5 const LAND_UPGRADE_STAR_COUNT := 12 const LAND_UPGRADE_STAR_SIZE := Vector2(20.0, 19.0) const LAND_UPGRADE_STAR_GAP := 23.6 var _ui var _texture_provider var _game_config var _building_api var _farm_mapper var _get_item_icon_texture: Callable var _context var _callbacks: Dictionary = {} var _panel_screen_size := Vector2(640.0, 960.0) var _max_farm_level := 20 var _land_count := 12 var _land_upgrade_tab_index := 0 var _land_upgrade_content: Control var _land_upgrade_panel: Control func setup(options: Dictionary) -> void: _ui = options.get("ui_builder") _texture_provider = options.get("texture_provider") _game_config = options.get("game_config") _building_api = options.get("building_api") _farm_mapper = options.get("farm_mapper") _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() _panel_screen_size = options.get("panel_screen_size", _panel_screen_size) _max_farm_level = int(options.get("max_farm_level", _max_farm_level)) _land_count = int(options.get("land_count", _land_count)) func open_house_upgrade() -> void: var panel: Control = await create_house_upgrade() if panel != null: _call("open_modal_panel", [panel]) func open_land_upgrade(tab_index := 0) -> void: var panel: Control = await create_land_upgrade(tab_index) if panel != null: _call("open_modal_panel", [panel]) func open_land_extend() -> void: var panel: Control = create_land_extend() if panel != null: _call("open_modal_panel", [panel]) func create_house_upgrade() -> Control: if _is_busy(): return null _call("close_modal_panel", [false]) await _call_async("refresh_store_house") var player := _player_data() var current_level := clampi(int(player.get("farm_level", 1)), 1, _max_farm_level) var next_level := clampi(current_level + 1, 1, _max_farm_level) var is_max := current_level >= _max_farm_level var config: Dictionary = _game_config.get_farm_level_config(next_level) var cost_text := str(config.get("item_id", "")) var level_required := int(config.get("player_level", 0)) var level_enough := int(player.get("level", 1)) >= level_required var cost_enough := _has_costs(cost_text) var panel: Control = _ui.create_common_panel_shell("BuildingPanel", Vector2(640.0, 810.0), "panel_icon_building", _texture_provider.atlas_path("common"), "panel_title_building", _texture_provider.atlas_path("common")) _ui.add_content_panel_background(panel, "InnerPageBack", Vector2(51.0, 204.0), Vector2(538.0, 598.0)) _ui.add_content_panel_background(panel, "InnerPage", Vector2(51.0, 214.0), Vector2(538.0, 579.0)) _ui.create_tab_bar(panel, "BuildingTabs", ["房屋升级"], Vector2(80.0, 157.0), 0, Callable(self, "_on_house_tab_pressed")) _ui.add_texture(panel, "HouseBg", load("res://assets/egret/assets/game/landup/building_housebg.png"), Vector2(77.0, 226.0), Vector2(485.0, 290.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED) _ui.add_texture(panel, "Tuding", _texture_provider.common("tuding"), Vector2(520.0, 225.0), Vector2(52.0, 50.0)) _ui.add_texture(panel, "HouseIcon", _house_texture(next_level), Vector2(190.0, 222.0), Vector2(260.0, 245.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED) _ui.add_texture(panel, "LvBg", _texture_provider.building("building_lvbg"), Vector2(56.0, 218.0), Vector2(122.0, 85.0)) var lv_label: Label = _ui.add_panel_label(panel, "BuildingLv", "LV.%s" % next_level, Vector2(69.0, 250.0), Vector2(98.0, 34.0), 29, Color.html("#fff05a"), 3, Color.html("#8a4b18")) lv_label.rotation_degrees = -15.0 var upgrade_button: Control = _ui.add_texture_button(panel, "UpgradeHouse", _texture_provider.building("btn_building_up"), Vector2(254.0, 234.0), Vector2(159.0, 65.0), "升级", Callable(self, "_upgrade_house").bind(config.duplicate(true)), 38) _style_upgrade_button_label(upgrade_button) _ui.add_texture(upgrade_button, "Arrow", _texture_provider.building("building_up_arrow"), Vector2(115.0, -2.0), Vector2(40.0, 53.0)) var can_upgrade := not is_max and not config.is_empty() and level_enough and cost_enough _set_texture_button_enabled(upgrade_button, can_upgrade, _texture_provider.building("btn_building_up"), _texture_provider.building("btn_building_up_gray")) _ui.add_nine_patch(panel, "Line", _texture_provider.common("line"), Vector2(74.0, 590.0), Vector2(490.0, 4.0), Vector4.ZERO) _ui.add_panel_label(panel, "NeedLevelText", "升级房屋需要农场主等级达到:", Vector2(104.0, 610.0), Vector2(310.0, 32.0), 18, Color.html("#ffcc00"), 2, Color.html("#9c460b"), HORIZONTAL_ALIGNMENT_LEFT) _ui.add_panel_label(panel, "NeedLevel", "LV.%s" % level_required, Vector2(381.0, 606.0), Vector2(80.0, 32.0), 22, Color.html("#73ff8d") if level_enough else Color.html("#ff4b2e"), 2, Color.html("#9c460b"), HORIZONTAL_ALIGNMENT_LEFT) _ui.add_panel_label(panel, "AddOutputText", "收获土地的果实将增加产量百分比:", Vector2(82.0, 641.0), Vector2(336.0, 32.0), 18, Color.html("#ffcc00"), 2, Color.html("#9c460b"), HORIZONTAL_ALIGNMENT_LEFT) _ui.add_panel_label(panel, "AddOutput", "%s%%" % int(config.get("output", 0)), Vector2(381.0, 637.0), Vector2(80.0, 32.0), 22, Color.html("#73ff8d"), 2, Color.html("#9c460b"), HORIZONTAL_ALIGNMENT_LEFT) _ui.add_panel_label(panel, "AddExpText", "升级房屋等级将增加农场主经验百分比:", Vector2(82.0, 673.0), Vector2(346.0, 32.0), 18, Color.html("#ffcc00"), 2, Color.html("#9c460b"), HORIZONTAL_ALIGNMENT_LEFT) _ui.add_panel_label(panel, "AddExp", "%s%%" % int(config.get("add_exp", 0)), Vector2(381.0, 669.0), Vector2(80.0, 32.0), 22, Color.html("#73ff8d"), 2, Color.html("#9c460b"), HORIZONTAL_ALIGNMENT_LEFT) if is_max: var label := upgrade_button.get_node_or_null("Label") as Label if label != null: label.text = I18nScript.t("满级") else: _add_house_cost_slots(panel, _parse_cost_string(cost_text)) return panel func create_land_upgrade(tab_index := 0) -> Control: if _is_busy(): return null _call("close_modal_panel", [false]) await _call_async("refresh_store_house") _land_upgrade_tab_index = clampi(tab_index, 0, 2) var panel: Control = _ui.create_common_panel_shell("LandUpgradePanel", Vector2(640.0, 864.0), "panel_icon_landup", _texture_provider.atlas_path("landup"), "panel_title_landup", _texture_provider.atlas_path("landup")) _ui.add_content_panel_background(panel, "InnerPage", Vector2(51.0, 194.0), Vector2(538.0, 652.0)) _ui.create_tab_bar(panel, "LandUpgradeTabs", ["棕土地", "银土地", "紫土地"], Vector2(80.0, 147.0), _land_upgrade_tab_index, Callable(self, "_on_land_upgrade_tab_pressed")) _land_upgrade_content = Control.new() _land_upgrade_content.name = "LandUpgradeContent" _land_upgrade_content.position = Vector2(51.0, 194.0) _land_upgrade_content.size = Vector2(537.0, 652.0) panel.add_child(_land_upgrade_content) _land_upgrade_panel = panel render_land_upgrade() return panel func create_land_extend() -> Control: if _is_busy(): return null _call("close_modal_panel", [false]) var player := _player_data() var next_count := _lands_by_position().size() + 1 var config: Dictionary = _game_config.get_land_extend_config(next_count) var level_required := int(config.get("player_level", 0)) var gold_required := int(config.get("gold", 0)) var level_enough := int(player.get("level", 1)) >= level_required var gold_enough := int(player.get("gold", 0)) >= gold_required var panel := Control.new() panel.name = "LandOpenPanel" panel.size = _panel_screen_size panel.mouse_filter = Control.MOUSE_FILTER_STOP var mask := Button.new() mask.name = "ModalMask" mask.set_anchors_preset(Control.PRESET_FULL_RECT) mask.text = "" mask.focus_mode = Control.FOCUS_NONE _ui.apply_empty_button_style(mask) mask.pressed.connect(_callback("close_modal_panel")) panel.add_child(mask) _ui.add_nine_patch(panel, "PanelBg", _texture_provider.common("c_panel2"), Vector2(88.0, 365.0), Vector2(464.0, 230.0), Vector4(136.0, 128.0, 4.0, 2.0)) _ui.add_texture(panel, "PanelIcon", _texture_provider.landup("panel_icon_landopen"), Vector2(162.0, 331.0), Vector2(108.0, 78.0)) var title: Texture2D = _texture_provider.landup("panel_title_landopen") _ui.add_texture(panel, "PanelTitle", title, Vector2(342.0 - title.get_width() * 0.5, 329.0), title.get_size()) _ui.add_texture_button(panel, "PanelClose", _texture_provider.common("btn_close"), Vector2(480.0, 341.0), Vector2(80.0, 74.0), "", _callback("close_modal_panel"), 20) _ui.add_panel_label(panel, "NeedText", "扩建这块土地需要:", Vector2(133.0, 430.0), Vector2(250.0, 32.0), 24, Color.html("#ffcc00"), 2, Color.html("#9c460b"), HORIZONTAL_ALIGNMENT_LEFT) _ui.add_panel_label(panel, "LevelText", "角色等级:", Vector2(133.0, 475.0), Vector2(116.0, 32.0), 24, Color.html("#ffcc00"), 2, Color.html("#9c460b"), HORIZONTAL_ALIGNMENT_LEFT) _ui.add_panel_label(panel, "LandNeedLevel", "LV.%s" % level_required, Vector2(249.0, 476.0), Vector2(82.0, 32.0), 24, Color.WHITE if level_enough else Color.html("#ff4b2e"), 2, Color.html("#9c460b"), HORIZONTAL_ALIGNMENT_LEFT) _ui.add_texture(panel, "GoldIcon", _texture_provider.common("sicon_gold"), Vector2(338.0, 469.0), Vector2(35.0, 35.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED) _ui.add_panel_label(panel, "LandNeedGold", NumberFormatterScript.compact(gold_required), Vector2(381.0, 476.0), Vector2(132.0, 32.0), 24, Color.WHITE if gold_enough else Color.html("#ff4b2e"), 2, Color.html("#9c460b"), HORIZONTAL_ALIGNMENT_LEFT) var open_button: Control = _ui.add_texture_button(panel, "OpenLand", _texture_provider.common("c_btn3"), Vector2(237.0, 529.0), Vector2(166.0, 58.0), "扩建", Callable(self, "_extend_land").bind(config.duplicate(true)), 30) _set_texture_button_enabled(open_button, not config.is_empty() and level_enough and gold_enough, _texture_provider.common("c_btn3"), _texture_provider.common("c_btn3_gray")) return panel func render_land_upgrade() -> void: if _land_upgrade_content == null: return _clear_children(_land_upgrade_content) var player := _player_data() var target_level := _land_upgrade_tab_index + 1 var target_name := _land_level_name(target_level) var order := _count_lands_by_level(target_level, true) + 1 var config: Dictionary = _game_config.get_land_upgrade_config_for(target_level, order) var cost_text := str(config.get("item_id", "")) var costs := _parse_cost_string(cost_text) var level_required := int(config.get("player_level", 0)) var level_enough := int(player.get("level", 1)) >= level_required var base_land_available := _count_lands_by_level(target_level - 1, false) > 0 var cost_enough := _has_costs(cost_text) var can_upgrade := not config.is_empty() and level_enough and base_land_available and cost_enough _ui.add_texture(_land_upgrade_content, "HouseBg", load("res://assets/egret/assets/game/landup/building_housebg.png"), Vector2(22.0, 38.0), Vector2(505.0, 290.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED) _ui.add_texture(_land_upgrade_content, "Light", load("res://assets/egret/assets/game/landup/land_up_light.png"), Vector2(36.0, -35.0), Vector2(468.0, 368.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED) _ui.add_texture(_land_upgrade_content, "Icon", load("res://assets/egret/assets/game/landup/land_up_icon%s.png" % target_level), Vector2(158.5, 39.0), Vector2(220.0, 190.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED) _ui.add_nine_patch(_land_upgrade_content, "StarTip", _texture_provider.common("s_tip"), Vector2(LAND_UPGRADE_CONTENT_CENTER_X - 155.0, 335.0), Vector2(310.0, 26.0), Vector4(13.0, 12.0, 1.0, 2.0)) _render_land_upgrade_stars(_land_upgrade_content, target_level) _ui.add_panel_label(_land_upgrade_content, "NeedLevelText", "升级%s需要农场主等级达到:" % target_name, Vector2(18.0, 382.0), Vector2(390.0, 30.0), 22, Color.html("#ffcc00"), 2, Color.html("#9c460b"), HORIZONTAL_ALIGNMENT_LEFT) _ui.add_panel_label(_land_upgrade_content, "NeedLevel", "LV%s" % level_required, Vector2(411.0, 382.0), Vector2(82.0, 30.0), 22, Color.html("#73ff8d") if level_enough else Color.html("#ff4b2e"), 2, Color.html("#9c460b"), HORIZONTAL_ALIGNMENT_LEFT) _ui.add_panel_label(_land_upgrade_content, "ExpText", "收获%s的果实将增加农场主经验点:" % target_name, Vector2(18.0, 413.0), Vector2(390.0, 30.0), 22, Color.html("#ffcc00"), 2, Color.html("#9c460b"), HORIZONTAL_ALIGNMENT_LEFT) _ui.add_panel_label(_land_upgrade_content, "ExpAdd", "%s点" % int(config.get("add_exp", 0)), Vector2(410.0, 413.0), Vector2(100.0, 30.0), 22, Color.html("#73ff8d"), 2, Color.html("#9c460b"), HORIZONTAL_ALIGNMENT_LEFT) _ui.add_panel_label(_land_upgrade_content, "OutputText", "收获%s的果实将增加产量个数:" % target_name, Vector2(18.0, 446.0), Vector2(390.0, 30.0), 22, Color.html("#ffcc00"), 2, Color.html("#9c460b"), HORIZONTAL_ALIGNMENT_LEFT) _ui.add_panel_label(_land_upgrade_content, "OutputAdd", "%s个" % int(config.get("output", 0)), Vector2(410.0, 446.0), Vector2(100.0, 30.0), 22, Color.html("#73ff8d"), 2, Color.html("#9c460b"), HORIZONTAL_ALIGNMENT_LEFT) _add_land_upgrade_cost_slots(_land_upgrade_content, costs) var button: Control = _ui.add_texture_button(_land_upgrade_content, "UpgradeLand", _texture_provider.common("c_btn3"), Vector2(185.0, 500.0), Vector2(166.0, 58.0), "升级", Callable(self, "_upgrade_land").bind(target_level, config.duplicate(true)), 30) _set_texture_button_enabled(button, can_upgrade, _texture_provider.common("c_btn3"), _texture_provider.common("c_btn3_gray")) _ui.add_panel_label(_land_upgrade_content, "Error", _land_upgrade_error_text(config, level_enough, base_land_available, cost_enough), Vector2(23.0, 620.0), Vector2(490.0, 30.0), 22, Color.html("#eb060a"), 1, Color.html("#9c460b")) func _on_house_tab_pressed(_index: int) -> void: pass func _on_land_upgrade_tab_pressed(index: int) -> void: _land_upgrade_tab_index = clampi(index, 0, 2) if _land_upgrade_panel != null: var tabs := _land_upgrade_panel.get_node_or_null("LandUpgradeTabs") as Control _ui.update_tab_bar(tabs, _land_upgrade_tab_index) render_land_upgrade() func _add_house_cost_slots(panel: Control, costs: Array[Dictionary]) -> void: var icon_positions := [Vector2(89.0, 723.0), Vector2(211.0, 723.0), Vector2(336.0, 723.0), Vector2(459.0, 723.0)] var label_positions := [Vector2(127.0, 725.0), Vector2(252.0, 725.0), Vector2(374.0, 725.0), Vector2(499.0, 725.0)] for index in range(mini(costs.size(), 4)): var cost := costs[index] var item_id := int(cost.get("item_id", 0)) var count := int(cost.get("count", 0)) var enough := _owned_amount(item_id) >= count _ui.add_texture(panel, "CostIcon%s" % index, _item_icon_texture(_game_config.get_item_icon_id(item_id)), icon_positions[index], Vector2(35.0, 35.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED) _ui.add_panel_label(panel, "CostLabel%s" % index, NumberFormatterScript.compact(count), label_positions[index], Vector2(74.0, 30.0), 22, Color.html("#73ff8d") if enough else Color.html("#ff4b2e"), 2, Color.html("#9c460b"), HORIZONTAL_ALIGNMENT_LEFT) func _style_upgrade_button_label(button_holder: Control) -> void: var label := button_holder.get_node_or_null("Label") as Label if label == null: return label.position = Vector2(12.0, 0.0) label.size = Vector2(114.0, 65.0) _ui.style_label(label, 38, Color.WHITE, 2, Color.html("#643591")) func _upgrade_house(config: Dictionary) -> void: if _is_busy(): return var token := _token() if token.is_empty(): _show_toast("登录信息失效") return var player := _player_data() var next_level := clampi(int(player.get("farm_level", 1)) + 1, 1, _max_farm_level) if int(player.get("farm_level", 1)) >= _max_farm_level: _show_toast("房屋已满级") return if config.is_empty(): _show_toast("房屋配置缺失") return var level_required := int(config.get("player_level", 0)) if int(player.get("level", 1)) < level_required: _show_toast("农场主等级不足") return var cost_text := str(config.get("item_id", "")) if not _has_costs(cost_text): _show_toast("升级材料不足") return _set_busy(true) var result: Dictionary = await _building_api.upgrade_house(token) _set_busy(false) if not result.get("ok", false): _show_toast("房屋升级失败:%s" % result.get("game_status", result.get("status", 0))) return _apply_cost_deduction(cost_text) player["farm_level"] = next_level player["exp"] = int(player.get("exp", 0)) + int(config.get("add_exp", 0)) _call("render_house_art") _call("render_player") _show_toast("房屋升级") await _call_async("refresh_store_house") var panel: Control = await create_house_upgrade() if panel != null: _call("open_modal_panel", [panel]) func _render_land_upgrade_stars(parent: Control, target_level: int) -> void: var upgraded_count := _count_lands_by_level(target_level, true) var total_width := float(LAND_UPGRADE_STAR_COUNT - 1) * LAND_UPGRADE_STAR_GAP + LAND_UPGRADE_STAR_SIZE.x var start_x := LAND_UPGRADE_CONTENT_CENTER_X - total_width * 0.5 for index in range(LAND_UPGRADE_STAR_COUNT): var texture_name := "building_land_num%s" % target_level if upgraded_count > index else "building_land_num0" _ui.add_texture(parent, "LandStar%s" % index, _texture_provider.landup(texture_name), Vector2(start_x + float(index) * LAND_UPGRADE_STAR_GAP, 339.0), LAND_UPGRADE_STAR_SIZE) func _add_land_upgrade_cost_slots(parent: Control, costs: Array[Dictionary]) -> void: var icon_positions := [Vector2(26.0, 563.0), Vector2(147.0, 563.0), Vector2(282.0, 563.0), Vector2(408.0, 563.0)] var label_positions := [Vector2(62.0, 570.0), Vector2(181.0, 570.0), Vector2(316.0, 570.0), Vector2(444.0, 570.0)] for index in range(mini(costs.size(), 4)): var cost := costs[index] var item_id := int(cost.get("item_id", 0)) var count := int(cost.get("count", 0)) var enough := _owned_amount(item_id) >= count _ui.add_texture(parent, "LandCostIcon%s" % index, _item_icon_texture(_game_config.get_item_icon_id(item_id)), icon_positions[index], Vector2(35.0, 35.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED) _ui.add_panel_label(parent, "LandCostLabel%s" % index, NumberFormatterScript.compact(count), label_positions[index], Vector2(74.0, 30.0), 22, Color.html("#73ff8d") if enough else Color.html("#ff4b2e"), 2, Color.html("#9c460b"), HORIZONTAL_ALIGNMENT_LEFT) func _land_upgrade_error_text(config: Dictionary, level_enough: bool, base_land_available: bool, cost_enough: bool) -> String: if config.is_empty(): return "配置缺失" if not base_land_available: return "没有足够的%s" % _land_level_name(_land_upgrade_tab_index) if not level_enough: return "等级不足" if not cost_enough: return "材料不足" return "" func _upgrade_land(target_level: int, config: Dictionary) -> void: if _is_busy(): return var token := _token() if token.is_empty(): _show_toast("登录信息失效") return if config.is_empty(): _show_toast("土地升级配置缺失") return var base_position := _first_land_position_by_level(target_level - 1) if base_position < 0: _show_toast("没有足够的%s" % _land_level_name(target_level - 1)) return var player := _player_data() var level_required := int(config.get("player_level", 0)) if int(player.get("level", 1)) < level_required: _show_toast("农场主等级不足") return var cost_text := str(config.get("item_id", "")) if not _has_costs(cost_text): _show_toast("材料不足") return _set_busy(true) var result: Dictionary = await _building_api.upgrade_land(token, target_level) _set_busy(false) if not result.get("ok", false): _show_toast(_response_status_text(result, "土地升级失败")) return var lands := _lands_by_position() var land_data: Dictionary = _farm_mapper.land_data_from_result(result, base_position, lands.get(base_position, {})) if land_data.is_empty(): land_data = lands.get(base_position, {}).duplicate(true) land_data["position"] = base_position land_data["level"] = target_level _call("apply_land_data", [land_data]) _apply_cost_deduction(cost_text) _call("render_player") _call("render_lands") await _call_async("refresh_store_house") _show_toast("升级成功") render_land_upgrade() func _extend_land(config: Dictionary) -> void: if _is_busy(): return var token := _token() if token.is_empty(): _show_toast("登录信息失效") return if config.is_empty(): _show_toast("扩建配置缺失") return var player := _player_data() var level_required := int(config.get("player_level", 0)) var gold_required := int(config.get("gold", 0)) if int(player.get("level", 1)) < level_required: _show_toast("角色等级不足") return if int(player.get("gold", 0)) < gold_required: _show_toast("金豆不足,无法扩建") return _set_busy(true) var result: Dictionary = await _building_api.extend_land(token) _set_busy(false) if not result.get("ok", false): _show_toast("扩建土地失败:%s" % result.get("game_status", result.get("status", 0))) return var fallback_position := _first_locked_land_position() var land_data := _land_data_from_extend_result(result, fallback_position) _call("apply_land_data", [land_data]) player["gold"] = maxi(int(player.get("gold", 0)) - gold_required, 0) _call("render_player") _call("render_lands") _call("close_modal_panel") _show_toast("扩建土地成功") func _count_lands_by_level(level: int, include_above: bool) -> int: var count := 0 for land_data in _lands_by_position().values(): if not land_data is Dictionary: continue var current_level := _land_level(land_data) if (include_above and current_level >= level) or ((not include_above) and current_level == level): count += 1 return count func _first_land_position_by_level(level: int) -> int: var lands := _lands_by_position() for position in range(_land_count): var land_data: Dictionary = lands.get(position, {}) if not land_data.is_empty() and _land_level(land_data) == level: return position return -1 func _land_level(land_data: Dictionary) -> int: return clampi(int(land_data.get("level", land_data.get("landLv", 0))), 0, 3) func _land_level_name(level: int) -> String: var names := ["普通土地", "棕土地", "银土地", "紫土地"] return names[clampi(level, 0, names.size() - 1)] func _land_data_from_extend_result(result: Dictionary, fallback_position: int) -> Dictionary: var body = result.get("body", {}) if body is Dictionary: var data = body.get("data", {}) if data is Dictionary and int(data.get("position", -1)) >= 0: var normalized: Dictionary = data.duplicate(true) if not normalized.has("level"): normalized["level"] = 0 if not normalized.has("crop_id"): normalized["crop_id"] = 0 if not normalized.has("crop_phase"): normalized["crop_phase"] = -1 return normalized return { "position": fallback_position, "level": 0, "crop_id": 0, "crop_num": 0, "crop_start_time": 0, "crop_gather_time": 0, "is_ripe": 0, "has_dry": 0, "has_bug": 0, "has_grass": 0, "has_gather": 0, "crop_phase": -1, "is_mystery_seed": 0 } func _first_locked_land_position() -> int: var lands := _lands_by_position() for position in range(_land_count): if not lands.has(position): return position return _land_count - 1 func _clear_children(node: Node) -> void: for child in node.get_children(): child.queue_free() func _parse_cost_string(cost_text: String) -> Array[Dictionary]: var result = _call("parse_cost_string", [cost_text], []) var costs: Array[Dictionary] = [] if result is Array: for cost in result: if cost is Dictionary: costs.append(cost) return costs func _has_costs(cost_text: String) -> bool: return bool(_call("has_costs", [cost_text], false)) func _apply_cost_deduction(cost_text: String) -> void: _call("apply_cost_deduction", [cost_text]) func _owned_amount(item_id: int) -> int: return int(_call("owned_amount", [item_id], 0)) func _set_texture_button_enabled(button_holder: Control, enabled: bool, normal_texture: Texture2D = null, disabled_texture: Texture2D = null) -> void: _call("set_texture_button_enabled", [button_holder, enabled, normal_texture, disabled_texture]) func _house_texture(level: int) -> Texture2D: var result = _call("get_house_texture", [level], null) return result as Texture2D 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 _lands_by_position() -> Dictionary: var result = _call("lands_by_position", [], {}) 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 _response_status_text(result: Dictionary, fallback := "操作失败") -> String: return I18nScript.t(str(_call("response_status_text", [result, fallback], fallback))) func _callback(name: String) -> Callable: if _context != null and _context.has_method("callback"): return _context.callback(name) return _callbacks.get(name, Callable()) as Callable 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 := _callback(name) 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 := _callback(name) if not callable.is_valid(): return null return await callable.callv(args)