1251 lines
44 KiB
GDScript
1251 lines
44 KiB
GDScript
extends Control
|
||
|
||
# GameScene 是当前 Godot 农场主场景的装配层。
|
||
#
|
||
# 经过模块化拆分后,它仍保留以下职责:
|
||
# - 调用 Bootstrap/RuntimeAssembler 完成服务、状态、规则、资源和 UI 模块装配。
|
||
# - 连接场景节点和独立模块,例如 HUD、菜单、土地网格、面板路由。
|
||
# - 维护少量跨模块共享状态,例如库存、当前选中土地。
|
||
# - 通过 GameSceneContext 桥接独立模块,避免主场景继续维护大 callback 字典。
|
||
#
|
||
# 不应再把新功能的大块 UI/业务写回本文件;新功能应新增独立模块并在 Bootstrap/RuntimeAssembler 中装配。
|
||
|
||
# 依赖按层分组:
|
||
# 1. core/state/rules/mappers/services 是非 UI 逻辑;
|
||
# 2. ui/common/ui/farm/ui/panels 是可视化和交互模块;
|
||
# 3. assets 常量是旧 Egret 资源路径兼容层。
|
||
const NumberFormatterScript := preload("res://scripts/core/number_formatter.gd")
|
||
const I18nScript := preload("res://scripts/core/i18n.gd")
|
||
const CostRulesScript := preload("res://scripts/domain/rules/cost_rules.gd")
|
||
const GameSceneBootstrapScript := preload("res://scripts/ui/game_scene_bootstrap.gd")
|
||
const GameSceneRuntimeAssemblerScript := preload("res://scripts/ui/game_scene_runtime_assembler.gd")
|
||
const NewbieSeedGuideScript := preload("res://scripts/ui/newbie/newbie_seed_guide.gd")
|
||
const TitleFont := preload("res://assets/fonts/google/LilitaOne-Regular.ttf")
|
||
const ButtonFont := preload("res://assets/fonts/google/LilitaOne-Regular.ttf")
|
||
const BodyFont := preload("res://assets/fonts/google/LilitaOne-Regular.ttf")
|
||
const LogItemBgTexture := preload("res://assets/egret/common/log_itembg_png.tres")
|
||
const CommonAtlasPath := "res://assets/egret/common/common.png"
|
||
const CommonJsonAtlasPath := "res://assets/egret/assets/common.png"
|
||
const FriendAtlasPath := "res://assets/egret/assets/game/friend/friend.png"
|
||
const RankAtlasPath := "res://assets/egret/assets/game/rank/rank.png"
|
||
const BuildingAtlasPath := "res://assets/egret/assets/game/building/building.png"
|
||
const LandupAtlasPath := "res://assets/egret/assets/game/landup/landup.png"
|
||
const SignAtlasPath := "res://assets/egret/assets/game/sign/sign.png"
|
||
const RoleInfoAtlasPath := "res://assets/egret/assets/game/roleinfo/roleinfo.png"
|
||
const CreateRoleAtlasPath := "res://assets/egret/assets/game/createrole/createrole.png"
|
||
const Common2AtlasPath := "res://assets/egret/assets/common2.png"
|
||
const LotteryAtlasPath := "res://assets/egret/assets/game/lottery/lottery.png"
|
||
const GiftAtlasPath := "res://assets/egret/assets/game/gift/onLineGift.png"
|
||
const MofaHechengAtlasPath := "res://assets/egret/assets/game/mofahecheng/mofahecheng.png"
|
||
const BattlePetAtlasPath := "res://assets/egret/assets/game/battlePet/battle_pet.png"
|
||
const Menu2AtlasPath := "res://assets/egret/assets/game/menu2/menu3.png"
|
||
const PayComAtlasPath := "res://assets/egret/assets/game/pay/pay_com.png"
|
||
const PayBgPath := "res://assets/egret/assets/game/pay/pay_bg.png"
|
||
|
||
const LAND_COUNT := 12
|
||
const LAND_TEXTURE_SIZE := Vector2(172.0, 102.0)
|
||
const MENU_BUTTON_SIZE := Vector2(92.0, 88.0)
|
||
const BAG_MODE_SEED := "seed"
|
||
const BAG_MODE_FERTILIZER := "fertilizer"
|
||
const ACTION_PLANT := "plant"
|
||
const ACTION_GAIN := "gain"
|
||
const ACTION_UPROOT := "uproot"
|
||
const ACTION_MANURE := "manure"
|
||
const ACTION_WATER := "water"
|
||
const ACTION_DIE_BUG := "die_bug"
|
||
const ACTION_DIE_GRASS := "die_grass"
|
||
const ACTION_STEAL := "steal"
|
||
const ACTION_PUT_BUG := "put_bug"
|
||
const ACTION_PUT_GRASS := "put_grass"
|
||
const DISEASE_TYPE_BUG := 1
|
||
const DISEASE_TYPE_GRASS := 2
|
||
const DISEASE_TYPE_WATER := 0
|
||
const PHASE_NAMES := ["种子期", "发芽期", "生长期", "成熟期", "枯萎"]
|
||
const PANEL_SCREEN_SIZE := Vector2(640.0, 960.0)
|
||
const SCENE_ZOOM_MIN := 1.0 / 1.5
|
||
const SCENE_ZOOM_MAX := 1.3
|
||
const WARE_CAPACITY := 32
|
||
const FRIEND_PAGE_SIZE := 7
|
||
const ITEM_TYPE_SEED := 1
|
||
const ITEM_TYPE_FRUIT := 2
|
||
const ITEM_TYPE_PROPS := 3
|
||
const ITEM_ID_GEM := 105001
|
||
const ITEM_ID_GOLD := 105002
|
||
const ITEM_ID_STONE := 201000
|
||
const ITEM_ID_STEEL := 201001
|
||
const REMOVED_BOX_ITEM_TYPE := 400
|
||
const SERVER_STATUS_TEXT := {
|
||
1008: "农田上已经有农作物",
|
||
1009: "当前等级不足",
|
||
1011: "你没有这个物品",
|
||
1012: "物品数量不够",
|
||
1014: "物品类型错误",
|
||
1015: "农田上没有种植种子",
|
||
1016: "尚未成熟",
|
||
1017: "你已经收割过该农作物",
|
||
1018: "农田不需要清理",
|
||
1019: "农作物已经成熟",
|
||
1020: "操作错误",
|
||
1044: "施肥已经到达上限",
|
||
1045: "施肥时间错误",
|
||
1047: "土地等级不够,无法种植该植物",
|
||
1073: "Not enough stamina",
|
||
1074: "You did not participate in the pet duel and cannot steal.",
|
||
}
|
||
const REMOVED_BOX_ITEM_IDS := [201012, 201013, 201014, 201015, 201016]
|
||
const ITEM_ID_BUG_KILLER := 108001
|
||
const ITEM_ID_GRASS_KILLER := 109001
|
||
const MAX_FARM_LEVEL := 12
|
||
const DOG_FOOD_ITEM_IDS := [103005, 103006, 103007]
|
||
const PLANT_PHASE_REFRESH_SECONDS := 1.0
|
||
const STAMINA_KEYS := [
|
||
"stamina",
|
||
"stamina_max",
|
||
"stamina_next_recover_time",
|
||
"stamina_recover_amount",
|
||
"stamina_recover_interval",
|
||
"stamina_cost_steal",
|
||
]
|
||
|
||
# 场景节点引用全部集中在 @onready 区域,避免业务方法里散落 get_node 路径。
|
||
# 这些节点来自 scenes/ui/game_scene.tscn,修改场景结构时优先同步这里。
|
||
@onready var label_name: Label = %LabelName
|
||
@onready var label_level: Label = %LabelLevel
|
||
@onready var label_gold: Label = %LabelGold
|
||
@onready var label_diamond: Label = %LabelDiamond
|
||
@onready var label_score: Label = %LabelScore
|
||
@onready var label_exp: Label = %LabelExp
|
||
@onready var label_stamina: Label = %LabelStamina
|
||
@onready var exp_fill: NinePatchRect = %ExpFill
|
||
@onready var stamina_fill: NinePatchRect = %StaminaFill
|
||
@onready var avatar: TextureRect = %Avatar
|
||
@onready var game_view: Control = $GameView
|
||
@onready var top_layer: Control = $TopLayer
|
||
@onready var top_button_group: Control = $TopLayer/TopButtonGroup
|
||
@onready var top_hide: TextureRect = $TopLayer/TopHide
|
||
@onready var main_menu: Control = $MainMenu
|
||
@onready var home_button_art: TextureRect = $MainMenu/HomeButtonArt
|
||
@onready var left_menu: Control = $LeftMenu
|
||
@onready var left_middle_menu: Control = $LeftMiddleMenu
|
||
@onready var market_button_art: TextureRect = $LeftMenu/MarketButtonArt
|
||
@onready var exchange_gold_art: TextureRect = $ExchangeGold
|
||
@onready var ground_art: TextureRect = $Ground
|
||
@onready var fence_back_art: TextureRect = $FenceBack
|
||
@onready var fence_front_art: TextureRect = $FenceFront
|
||
@onready var house_art: TextureRect = $House
|
||
@onready var dog_house_art: TextureRect = $DogHouse
|
||
@onready var add_land_art: TextureRect = $GameView/AddLand
|
||
@onready var back_home_art: TextureRect = $BackHome
|
||
|
||
var _player_data: Dictionary = {}
|
||
|
||
# 架构层对象:负责接口、配置、状态、规则、资源缓存。
|
||
# 这些对象只在 _setup_architecture_modules() 创建一次,其他模块通过引用复用。
|
||
var _api_client: ApiClient
|
||
var _game_config
|
||
var _atlas_cache: AtlasTextureCache
|
||
var _player_state: PlayerState
|
||
var _farm_state: FarmState
|
||
var _inventory_state: InventoryState
|
||
var _player_inventory_facade
|
||
var _crop_phase_rules: CropPhaseRules
|
||
var _land_action_rules: LandActionRules
|
||
var _farm_mapper: FarmMapper
|
||
var _inventory_mapper: InventoryMapper
|
||
var _farm_api: FarmApi
|
||
var _inventory_api: InventoryApi
|
||
var _building_api
|
||
var _shop_api
|
||
var _warehouse_api
|
||
var _friend_api
|
||
var _activity_api
|
||
var _pet_api
|
||
var _pay_api
|
||
var _texture_provider
|
||
var _ui_builder
|
||
var _context
|
||
var _game_scene_bootstrap
|
||
var _runtime_assembler
|
||
var _panel_router
|
||
var _scene_hit_router
|
||
|
||
# 农场运行时状态。
|
||
# _lands_by_position 是渲染和交互的土地快照;真正归一化逻辑在 FarmState。
|
||
var _lands_by_position: Dictionary = {}
|
||
var _seed_items: Array[Dictionary] = []
|
||
var _fertilizer_items: Array[Dictionary] = []
|
||
|
||
var _operation_in_progress := false
|
||
var _visiting_friend := false
|
||
var _visited_friend_id := 0
|
||
var _visited_friend_data: Dictionary = {}
|
||
|
||
# 主场景临时 UI 节点。
|
||
# 这些是土地菜单、背包、提示、弹窗层等可复用节点,不是业务面板本体。
|
||
var _bag_panel: Control
|
||
var _long_press_timer: Timer
|
||
var _plant_phase_timer: Timer
|
||
var _last_land_visual_signature := ""
|
||
var _farm_scene_root: Control
|
||
var _modal_layer: Control
|
||
var _warehouse_items: Array[Dictionary] = []
|
||
|
||
var _farm_viewport_controller
|
||
var _modal_controller
|
||
var _main_menu_controller
|
||
var _top_bar_view
|
||
var _newbie_seed_guide
|
||
var _farm_controller
|
||
var _friend_visit_controller
|
||
var _land_grid_view
|
||
var _land_action_menu
|
||
var _seed_bag_view
|
||
var _farm_interaction_coordinator
|
||
var _add_land_hit: Button
|
||
var _pet_layer: Control
|
||
var _pet_display: Control
|
||
var _pet_battle_vs_overlay
|
||
|
||
|
||
func setup(player_data: Dictionary) -> void:
|
||
# Bootstrap 自动登录成功后把后端玩家数据注入主场景。
|
||
_player_data = player_data.duplicate(true)
|
||
|
||
|
||
func _ready() -> void:
|
||
# 启动顺序很重要:
|
||
# 先装配服务/状态,再装配 UI 模块,最后渲染场景并发起异步库存/宠物刷新。
|
||
_setup_architecture_modules()
|
||
_setup_runtime_views()
|
||
_setup_newbie_seed_guide()
|
||
_collect_land_data()
|
||
_render_scene_skin()
|
||
_render_house_art()
|
||
_render_player()
|
||
_render_lands()
|
||
_apply_visit_mode_visuals()
|
||
_refresh_store_house()
|
||
if _panel_router != null:
|
||
_panel_router.refresh_pet_display()
|
||
_play_scene_enter_animation()
|
||
|
||
|
||
func _input(event: InputEvent) -> void:
|
||
# 弹窗打开时屏蔽主场景缩放和底层点击,避免弹窗按钮和土地同时响应。
|
||
if _modal_layer != null and _modal_layer.visible:
|
||
return
|
||
var mouse_event := event as InputEventMouseButton
|
||
if mouse_event != null and mouse_event.button_index == MOUSE_BUTTON_LEFT and mouse_event.pressed and _handle_main_menu_direct_hit(mouse_event.position):
|
||
get_viewport().set_input_as_handled()
|
||
return
|
||
var touch_event := event as InputEventScreenTouch
|
||
if touch_event != null and touch_event.pressed and _handle_main_menu_direct_hit(touch_event.position):
|
||
get_viewport().set_input_as_handled()
|
||
return
|
||
if mouse_event != null and mouse_event.button_index == MOUSE_BUTTON_LEFT and mouse_event.pressed and _handle_land_action_menu_direct_hit(mouse_event.position):
|
||
get_viewport().set_input_as_handled()
|
||
return
|
||
if touch_event != null and touch_event.pressed and _handle_land_action_menu_direct_hit(touch_event.position):
|
||
get_viewport().set_input_as_handled()
|
||
return
|
||
if _handle_farm_viewport_input(event):
|
||
get_viewport().set_input_as_handled()
|
||
return
|
||
if mouse_event != null and mouse_event.button_index == MOUSE_BUTTON_LEFT and mouse_event.pressed and _handle_direct_scene_hit(mouse_event.position):
|
||
get_viewport().set_input_as_handled()
|
||
return
|
||
if mouse_event != null and mouse_event.button_index == MOUSE_BUTTON_LEFT and _handle_direct_land_input(mouse_event.position, mouse_event.pressed):
|
||
get_viewport().set_input_as_handled()
|
||
return
|
||
if touch_event != null and touch_event.pressed and _handle_direct_scene_hit(touch_event.position):
|
||
get_viewport().set_input_as_handled()
|
||
return
|
||
if touch_event != null and _handle_direct_land_input(touch_event.position, touch_event.pressed):
|
||
get_viewport().set_input_as_handled()
|
||
|
||
|
||
func _handle_direct_scene_hit(position: Vector2) -> bool:
|
||
if _scene_hit_router == null:
|
||
return false
|
||
return _scene_hit_router.handle_direct_scene_hit(position)
|
||
|
||
|
||
func _handle_main_menu_direct_hit(position: Vector2) -> bool:
|
||
if _main_menu_controller == null:
|
||
return false
|
||
if not _main_menu_controller.has_method("handle_direct_hit"):
|
||
return false
|
||
return _main_menu_controller.handle_direct_hit(position)
|
||
|
||
|
||
func _handle_land_action_menu_direct_hit(position: Vector2) -> bool:
|
||
if _land_action_menu == null:
|
||
return false
|
||
if not _land_action_menu.has_method("handle_direct_hit"):
|
||
return false
|
||
return _land_action_menu.handle_direct_hit(position)
|
||
|
||
|
||
func _handle_direct_land_input(position: Vector2, pressed: bool) -> bool:
|
||
if _farm_interaction_coordinator == null:
|
||
return false
|
||
if not _farm_interaction_coordinator.has_method("handle_direct_land_pointer"):
|
||
return false
|
||
return _farm_interaction_coordinator.handle_direct_land_pointer(position, pressed)
|
||
|
||
|
||
func _handle_farm_viewport_input(event: InputEvent) -> bool:
|
||
if _farm_viewport_controller == null:
|
||
return false
|
||
return _farm_viewport_controller.handle_input(event)
|
||
|
||
|
||
func _setup_architecture_modules() -> void:
|
||
_game_scene_bootstrap = GameSceneBootstrapScript.new()
|
||
_apply_bootstrap_modules(_game_scene_bootstrap.setup({
|
||
"owner": self,
|
||
"player_data": _player_data,
|
||
"panel_screen_size": PANEL_SCREEN_SIZE,
|
||
"font": BodyFont,
|
||
"fonts": {
|
||
"title": TitleFont,
|
||
"button": ButtonFont,
|
||
"body": BodyFont,
|
||
},
|
||
"atlas_paths": {
|
||
"common": CommonAtlasPath,
|
||
"common_json": CommonJsonAtlasPath,
|
||
"friend": FriendAtlasPath,
|
||
"rank": RankAtlasPath,
|
||
"building": BuildingAtlasPath,
|
||
"landup": LandupAtlasPath,
|
||
"sign": SignAtlasPath,
|
||
"roleinfo": RoleInfoAtlasPath,
|
||
"createrole": CreateRoleAtlasPath,
|
||
"common2": Common2AtlasPath,
|
||
"gift": GiftAtlasPath,
|
||
"lottery": LotteryAtlasPath,
|
||
"mofahecheng": MofaHechengAtlasPath,
|
||
"battle_pet": BattlePetAtlasPath,
|
||
"menu2": Menu2AtlasPath,
|
||
"pay_com": PayComAtlasPath,
|
||
},
|
||
"paths": {
|
||
"pay_bg": PayBgPath,
|
||
},
|
||
"textures": {
|
||
"log_item_bg": LogItemBgTexture,
|
||
},
|
||
"constants": {
|
||
"land_count": LAND_COUNT,
|
||
"ware_capacity": WARE_CAPACITY,
|
||
"friend_page_size": FRIEND_PAGE_SIZE,
|
||
"common_atlas_path": CommonAtlasPath,
|
||
"friend_atlas_path": FriendAtlasPath,
|
||
"landup_atlas_path": LandupAtlasPath,
|
||
"item_type_seed": ITEM_TYPE_SEED,
|
||
"item_type_fruit": ITEM_TYPE_FRUIT,
|
||
"item_type_props": ITEM_TYPE_PROPS,
|
||
"item_id_gem": ITEM_ID_GEM,
|
||
"item_id_gold": ITEM_ID_GOLD,
|
||
"item_id_stone": ITEM_ID_STONE,
|
||
"item_id_steel": ITEM_ID_STEEL,
|
||
"item_id_bug_killer": ITEM_ID_BUG_KILLER,
|
||
"item_id_grass_killer": ITEM_ID_GRASS_KILLER,
|
||
"disease_type_bug": DISEASE_TYPE_BUG,
|
||
"disease_type_grass": DISEASE_TYPE_GRASS,
|
||
"disease_type_water": DISEASE_TYPE_WATER,
|
||
"max_farm_level": MAX_FARM_LEVEL,
|
||
"dog_food_item_ids": DOG_FOOD_ITEM_IDS,
|
||
},
|
||
}))
|
||
|
||
|
||
func _apply_bootstrap_modules(modules: Dictionary) -> void:
|
||
_game_config = modules.get("game_config")
|
||
_api_client = modules.get("api_client")
|
||
_atlas_cache = modules.get("atlas_cache")
|
||
_texture_provider = modules.get("texture_provider")
|
||
_ui_builder = modules.get("ui_builder")
|
||
_context = modules.get("context")
|
||
_panel_router = modules.get("panel_router")
|
||
_player_state = modules.get("player_state")
|
||
if _player_state != null and _player_state.has_method("player_data"):
|
||
_player_data = _player_state.player_data()
|
||
_farm_state = modules.get("farm_state")
|
||
_inventory_state = modules.get("inventory_state")
|
||
_player_inventory_facade = modules.get("player_inventory_facade")
|
||
_crop_phase_rules = modules.get("crop_phase_rules")
|
||
_land_action_rules = modules.get("land_action_rules")
|
||
_farm_mapper = modules.get("farm_mapper")
|
||
_inventory_mapper = modules.get("inventory_mapper")
|
||
_farm_api = modules.get("farm_api")
|
||
_inventory_api = modules.get("inventory_api")
|
||
_building_api = modules.get("building_api")
|
||
_shop_api = modules.get("shop_api")
|
||
_warehouse_api = modules.get("warehouse_api")
|
||
_friend_api = modules.get("friend_api")
|
||
_activity_api = modules.get("activity_api")
|
||
_pet_api = modules.get("pet_api")
|
||
_pay_api = modules.get("pay_api")
|
||
_farm_controller = modules.get("farm_controller")
|
||
_friend_visit_controller = modules.get("friend_visit_controller")
|
||
|
||
|
||
func _setup_runtime_views() -> void:
|
||
_runtime_assembler = GameSceneRuntimeAssemblerScript.new()
|
||
_apply_runtime_modules(_runtime_assembler.setup({
|
||
"owner": self,
|
||
"texture_provider": _texture_provider,
|
||
"nodes": {
|
||
"label_name": label_name,
|
||
"label_level": label_level,
|
||
"label_gold": label_gold,
|
||
"label_diamond": label_diamond,
|
||
"label_score": label_score,
|
||
"label_exp": label_exp,
|
||
"label_stamina": label_stamina,
|
||
"exp_fill": exp_fill,
|
||
"stamina_fill": stamina_fill,
|
||
"avatar": avatar,
|
||
"game_view": game_view,
|
||
"top_layer": top_layer,
|
||
"top_button_group": top_button_group,
|
||
"top_hide": top_hide,
|
||
"main_menu": main_menu,
|
||
"left_middle_menu": left_middle_menu,
|
||
"home_button_art": home_button_art,
|
||
"left_menu": left_menu,
|
||
"market_button_art": market_button_art,
|
||
"exchange_gold_art": exchange_gold_art,
|
||
"house_art": house_art,
|
||
"dog_house_art": dog_house_art,
|
||
"add_land_art": add_land_art,
|
||
"back_home_art": back_home_art,
|
||
},
|
||
"constants": {
|
||
"panel_screen_size": PANEL_SCREEN_SIZE,
|
||
"scene_zoom_min": SCENE_ZOOM_MIN,
|
||
"scene_zoom_max": SCENE_ZOOM_MAX,
|
||
"land_count": LAND_COUNT,
|
||
"land_texture_size": LAND_TEXTURE_SIZE,
|
||
"menu_button_size": MENU_BUTTON_SIZE,
|
||
"bag_mode_seed": BAG_MODE_SEED,
|
||
"bag_mode_fertilizer": BAG_MODE_FERTILIZER,
|
||
"action_plant": ACTION_PLANT,
|
||
"action_gain": ACTION_GAIN,
|
||
"action_uproot": ACTION_UPROOT,
|
||
"action_manure": ACTION_MANURE,
|
||
"action_water": ACTION_WATER,
|
||
"action_die_bug": ACTION_DIE_BUG,
|
||
"action_die_grass": ACTION_DIE_GRASS,
|
||
"action_steal": ACTION_STEAL,
|
||
"action_put_bug": ACTION_PUT_BUG,
|
||
"action_put_grass": ACTION_PUT_GRASS,
|
||
"disease_type_bug": DISEASE_TYPE_BUG,
|
||
"disease_type_grass": DISEASE_TYPE_GRASS,
|
||
"disease_type_water": DISEASE_TYPE_WATER,
|
||
"phase_names": PHASE_NAMES,
|
||
"plant_phase_refresh_seconds": PLANT_PHASE_REFRESH_SECONDS,
|
||
"menu2_atlas_path": Menu2AtlasPath,
|
||
"game_config": _game_config,
|
||
"crop_phase_rules": _crop_phase_rules,
|
||
},
|
||
"callbacks": {
|
||
"style_label": Callable(_ui_builder, "style_label"),
|
||
"hide_selection_ui": Callable(self, "_hide_selection_ui"),
|
||
"open_shop_panel": Callable(_panel_router, "open_shop_panel"),
|
||
"open_warehouse_panel": Callable(_panel_router, "open_warehouse_panel"),
|
||
"open_friend_panel": Callable(_panel_router, "open_friend_panel"),
|
||
"open_pet_panel": Callable(_panel_router, "open_pet_panel"),
|
||
"open_player_info_panel": Callable(_panel_router, "open_player_info_panel"),
|
||
"open_market_panel": Callable(_panel_router, "open_market_panel"),
|
||
"pet_panel_view": Callable(_panel_router, "pet_panel_view"),
|
||
"get_land_node": Callable(self, "_get_land_node"),
|
||
"render_add_land_indicator": Callable(self, "_render_add_land_indicator"),
|
||
"cool_menu_pressed": Callable(self, "_on_cool_menu_pressed"),
|
||
"menu_dismiss_pressed": Callable(self, "_on_menu_dismiss_pressed"),
|
||
"get_item_icon_texture": Callable(_texture_provider, "item_icon"),
|
||
"seed_icon_id": Callable(self, "_seed_icon_id"),
|
||
"bag_item_pressed": Callable(self, "_on_bag_item_pressed"),
|
||
"format_duration": Callable(self, "_format_duration"),
|
||
"plant_phase_timer_timeout": Callable(self, "_on_plant_phase_timer_timeout"),
|
||
"land_data": Callable(self, "_get_land_data"),
|
||
"land_actions": Callable(self, "_get_land_actions"),
|
||
"is_visiting_friend": Callable(self, "_is_visiting_friend"),
|
||
"is_operation_in_progress": Callable(self, "_is_operation_in_progress"),
|
||
"open_land_extend_panel": Callable(_panel_router, "open_land_extend_panel"),
|
||
"seed_items": Callable(self, "_seed_items_ref"),
|
||
"fertilizer_items": Callable(self, "_fertilizer_items_ref"),
|
||
"refresh_store_house": Callable(self, "_refresh_store_house"),
|
||
"sow_seed": Callable(self, "_sow_seed"),
|
||
"use_fertilizer": Callable(self, "_use_fertilizer"),
|
||
"gather_crop": Callable(self, "_gather_crop"),
|
||
"clear_land": Callable(self, "_clear_land"),
|
||
"kick_disease": Callable(self, "_kick_disease"),
|
||
"interact_friend_land": Callable(self, "_interact_friend_land"),
|
||
"keep_modal_layers_on_top": Callable(self, "_keep_modal_layers_on_top"),
|
||
"open_log_panel": Callable(_panel_router, "open_log_panel"),
|
||
"open_land_upgrade_panel": Callable(_panel_router, "open_land_upgrade_panel"),
|
||
"open_pay_panel": Callable(_panel_router, "open_pay_panel"),
|
||
"open_level_gift_panel": Callable(_panel_router, "open_level_gift_panel"),
|
||
"open_lottery_panel": Callable(_panel_router, "open_lottery_panel"),
|
||
"toggle_top_button_group": Callable(self, "_toggle_top_button_group"),
|
||
"open_notice_panel": Callable(_panel_router, "open_notice_panel"),
|
||
"open_sign_panel": Callable(_panel_router, "open_sign_panel"),
|
||
"open_online_gift_panel": Callable(_panel_router, "open_online_gift_panel"),
|
||
"open_gold_exchange_panel": Callable(_panel_router, "open_gold_exchange_panel"),
|
||
"open_house_upgrade_panel": Callable(_panel_router, "open_house_upgrade_panel"),
|
||
"back_to_own_farm": Callable(self, "_back_to_own_farm"),
|
||
},
|
||
}))
|
||
|
||
|
||
func _apply_runtime_modules(modules: Dictionary) -> void:
|
||
_top_bar_view = modules.get("top_bar_view")
|
||
_main_menu_controller = modules.get("main_menu_controller")
|
||
_modal_controller = modules.get("modal_controller")
|
||
_modal_layer = modules.get("modal_layer")
|
||
_pet_layer = modules.get("pet_layer")
|
||
_pet_display = modules.get("pet_display")
|
||
_pet_battle_vs_overlay = modules.get("pet_battle_vs_overlay")
|
||
_farm_viewport_controller = modules.get("farm_viewport_controller")
|
||
_farm_scene_root = modules.get("farm_scene_root")
|
||
_land_grid_view = modules.get("land_grid_view")
|
||
_land_action_menu = modules.get("land_action_menu")
|
||
_long_press_timer = modules.get("long_press_timer")
|
||
_seed_bag_view = modules.get("seed_bag_view")
|
||
_bag_panel = modules.get("bag_panel")
|
||
_plant_phase_timer = modules.get("plant_phase_timer")
|
||
_farm_interaction_coordinator = modules.get("farm_interaction_coordinator")
|
||
_scene_hit_router = modules.get("scene_hit_router")
|
||
_add_land_hit = modules.get("add_land_hit")
|
||
if _panel_router != null:
|
||
_panel_router.set_modal_controller(_modal_controller)
|
||
|
||
|
||
func _setup_newbie_seed_guide() -> void:
|
||
_newbie_seed_guide = NewbieSeedGuideScript.new()
|
||
_newbie_seed_guide.setup({
|
||
"show_toast": Callable(self, "_show_toast"),
|
||
})
|
||
|
||
|
||
func _render_player() -> void:
|
||
if _top_bar_view != null:
|
||
var own_player_data := _player_state.player_data() if _player_state != null else {}
|
||
_top_bar_view.render(_player_data, own_player_data)
|
||
|
||
|
||
func _toggle_top_button_group() -> void:
|
||
if _top_bar_view == null:
|
||
return
|
||
var expanded := bool(_top_bar_view.toggle_top_button_group())
|
||
if _scene_hit_router != null:
|
||
_scene_hit_router.set_top_button_group_hits_enabled(expanded)
|
||
|
||
|
||
func _set_scene_hit_layers_enabled(enabled: bool) -> void:
|
||
if _scene_hit_router != null:
|
||
_scene_hit_router.set_hit_layers_enabled(enabled)
|
||
|
||
|
||
func _play_scene_enter_animation() -> void:
|
||
if top_layer == null:
|
||
return
|
||
top_layer.position.y = -220.0
|
||
var tween := create_tween()
|
||
tween.tween_property(top_layer, "position:y", 0.0, 0.25).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||
|
||
|
||
func _show_toast(message: String) -> void:
|
||
if _modal_controller != null:
|
||
_modal_controller.show_toast(message)
|
||
|
||
|
||
func _collect_land_data() -> void:
|
||
_farm_state.setup_from_player_data(_player_data, LAND_COUNT)
|
||
_lands_by_position = _farm_state.snapshot()
|
||
|
||
|
||
func _render_house_art() -> void:
|
||
if house_art == null:
|
||
return
|
||
var farm_level := clampi(int(_player_data.get("farm_level", 1)), 1, MAX_FARM_LEVEL)
|
||
house_art.texture = _texture_provider.house(farm_level, MAX_FARM_LEVEL) if _texture_provider != null else null
|
||
|
||
|
||
func _render_scene_skin() -> void:
|
||
var graphic_id := _current_skin_graphic_id()
|
||
var ground_texture := _load_skin_texture(graphic_id, ".jpg")
|
||
if ground_art != null:
|
||
ground_art.texture = ground_texture
|
||
var fence_back_texture := _load_skin_texture(graphic_id, "_0.png")
|
||
if fence_back_art != null:
|
||
fence_back_art.texture = fence_back_texture
|
||
var fence_front_texture := _load_skin_texture(graphic_id, "_1.png")
|
||
if fence_front_art != null:
|
||
fence_front_art.texture = fence_front_texture
|
||
if _farm_viewport_controller != null:
|
||
var backdrop: TextureRect = _farm_viewport_controller.zoom_backdrop()
|
||
if backdrop != null:
|
||
backdrop.texture = ground_texture
|
||
|
||
|
||
func _render_add_land_indicator() -> void:
|
||
if add_land_art == null:
|
||
return
|
||
if _visiting_friend:
|
||
add_land_art.visible = false
|
||
if _add_land_hit != null:
|
||
_add_land_hit.visible = false
|
||
return
|
||
var next_position := -1
|
||
for position in range(LAND_COUNT):
|
||
if not _lands_by_position.has(position):
|
||
next_position = position
|
||
break
|
||
if next_position < 0:
|
||
add_land_art.visible = false
|
||
if _add_land_hit != null:
|
||
_add_land_hit.visible = false
|
||
return
|
||
|
||
var land_node := _get_land_node(next_position)
|
||
if land_node == null:
|
||
add_land_art.visible = false
|
||
if _add_land_hit != null:
|
||
_add_land_hit.visible = false
|
||
return
|
||
|
||
add_land_art.visible = true
|
||
add_land_art.position = land_node.position + Vector2(40.0, -36.0)
|
||
if _add_land_hit != null:
|
||
_add_land_hit.visible = true
|
||
var hit_global_position := add_land_art.get_global_rect().position
|
||
_add_land_hit.position = _farm_scene_root.get_global_transform().affine_inverse() * hit_global_position if _farm_scene_root != null else add_land_art.position
|
||
_add_land_hit.size = add_land_art.size
|
||
|
||
|
||
func _keep_modal_layers_on_top() -> void:
|
||
if _modal_controller != null:
|
||
_modal_controller.bring_to_front()
|
||
|
||
|
||
func _render_lands() -> void:
|
||
if _land_grid_view == null:
|
||
return
|
||
# 土地渲染集中交给 LandGridView;主场景只维护后端归一化后的 position -> land_data。
|
||
_land_grid_view.render(_lands_by_position)
|
||
# 记录视觉签名,计时器只在成熟阶段变化时重绘,避免每秒全量创建土地节点。
|
||
_last_land_visual_signature = _land_grid_view.build_signature(_lands_by_position)
|
||
if _newbie_seed_guide != null:
|
||
_newbie_seed_guide.on_lands_changed(_lands_by_position)
|
||
|
||
|
||
func _on_plant_phase_timer_timeout() -> void:
|
||
if _land_grid_view == null:
|
||
return
|
||
# 成熟倒计时会影响纹理阶段和倒计时文本;签名没变化时不刷新 UI。
|
||
var signature: String = _land_grid_view.build_signature(_lands_by_position)
|
||
if signature == _last_land_visual_signature:
|
||
return
|
||
_render_lands()
|
||
|
||
|
||
func _get_land_actions(land_data: Dictionary) -> Array[String]:
|
||
if _visiting_friend and _land_action_rules.has_method("get_friend_actions"):
|
||
return _land_action_rules.get_friend_actions(land_data)
|
||
return _land_action_rules.get_actions(land_data)
|
||
|
||
|
||
func _hide_land_light() -> void:
|
||
if _farm_interaction_coordinator != null:
|
||
_farm_interaction_coordinator.hide_land_light()
|
||
|
||
|
||
func _on_cool_menu_pressed(action: String) -> void:
|
||
if _farm_interaction_coordinator != null:
|
||
_farm_interaction_coordinator.on_cool_menu_pressed(action)
|
||
|
||
|
||
func _on_menu_dismiss_pressed() -> void:
|
||
if _farm_interaction_coordinator != null:
|
||
_farm_interaction_coordinator.on_menu_dismiss_pressed()
|
||
|
||
|
||
func _process(_delta: float) -> void:
|
||
if _farm_interaction_coordinator != null:
|
||
_farm_interaction_coordinator.tick(_lands_by_position)
|
||
|
||
|
||
func _show_plant_tip(position: int, land_data: Dictionary) -> void:
|
||
if _farm_interaction_coordinator != null:
|
||
_farm_interaction_coordinator.show_plant_tip(position, land_data)
|
||
|
||
|
||
func _format_duration(seconds: int) -> String:
|
||
return I18nScript.format_duration(seconds)
|
||
|
||
|
||
func _refresh_store_house() -> void:
|
||
if _player_inventory_facade == null:
|
||
return
|
||
# 背包统一从 Facade 刷新:它会拉后端、归一化为三类列表,并按当前可见面板触发局部重绘。
|
||
await _player_inventory_facade.refresh_store_house(_token())
|
||
if _newbie_seed_guide != null:
|
||
_newbie_seed_guide.on_inventory_changed(_seed_items)
|
||
|
||
|
||
func _decrease_warehouse_item(item_id: int, count: int) -> void:
|
||
if _player_inventory_facade == null:
|
||
return
|
||
_player_inventory_facade.decrease_warehouse_item(item_id, count)
|
||
|
||
|
||
func _parse_cost_string(cost_text: String) -> Array[Dictionary]:
|
||
# 配置表里的消耗/奖励通常是 "item_id:count,item_id:count",优先走 Facade 保持规则入口一致。
|
||
var costs: Array[Dictionary] = []
|
||
if _player_inventory_facade != null:
|
||
costs = _player_inventory_facade.parse_cost_string(cost_text)
|
||
else:
|
||
costs = CostRulesScript.parse_cost_string(cost_text)
|
||
return _filter_removed_item_costs(costs)
|
||
|
||
|
||
func _has_costs(cost_text: String) -> bool:
|
||
# 所有购买、升级前置检查都走同一套库存 + 货币判断,避免面板各自实现导致数值漂移。
|
||
var filtered_cost_text := _costs_to_text(_parse_cost_string(cost_text))
|
||
if _player_inventory_facade != null:
|
||
return _player_inventory_facade.has_costs(filtered_cost_text)
|
||
return CostRulesScript.has_costs(filtered_cost_text, _player_data, _warehouse_items)
|
||
|
||
|
||
func _apply_cost_deduction(cost_text: String) -> void:
|
||
if _player_inventory_facade == null:
|
||
return
|
||
# 本地先扣减用于立即反馈;后续接口失败的路径必须自行回滚或重新刷新仓库。
|
||
_player_inventory_facade.apply_cost_deduction(_costs_to_text(_parse_cost_string(cost_text)))
|
||
|
||
|
||
func _get_owned_amount(item_id: int) -> int:
|
||
if _player_inventory_facade != null:
|
||
return _player_inventory_facade.owned_amount(item_id)
|
||
return CostRulesScript.get_owned_amount(item_id, _player_data, _warehouse_items)
|
||
|
||
|
||
func _player_data_ref() -> Dictionary:
|
||
# 面板模块读取的是引用,不复制;这样等级、金豆、宠物饥饿时间等 UI 能即时看到最新值。
|
||
return _player_data
|
||
|
||
|
||
func _warehouse_items_ref() -> Array[Dictionary]:
|
||
# 仓库数据由 InventoryState 持有,这里只提供只读式引用给旧面板兼容层。
|
||
return _warehouse_items
|
||
|
||
|
||
func _seed_items_ref() -> Array[Dictionary]:
|
||
return _seed_items
|
||
|
||
|
||
func _fertilizer_items_ref() -> Array[Dictionary]:
|
||
return _fertilizer_items
|
||
|
||
|
||
func _lands_by_position_ref() -> Dictionary:
|
||
return _lands_by_position
|
||
|
||
|
||
func _decrease_player_currency(gold_cost: int, gem_cost: int) -> void:
|
||
if _player_inventory_facade == null:
|
||
return
|
||
_player_inventory_facade.decrease_player_currency(gold_cost, gem_cost)
|
||
|
||
|
||
func _add_player_gold(amount: int) -> void:
|
||
if _player_inventory_facade == null:
|
||
return
|
||
_player_inventory_facade.add_player_gold(amount)
|
||
|
||
|
||
func _set_dog_hunger_end_time(end_time: int) -> void:
|
||
if _player_inventory_facade == null:
|
||
if _player_state != null:
|
||
_player_state.set_value("dog_hunger_end_time", end_time)
|
||
else:
|
||
_player_inventory_facade.set_dog_hunger_end_time(end_time)
|
||
if _panel_router != null:
|
||
_panel_router.render_pet_display()
|
||
|
||
|
||
func _set_use_dog_item_id(item_id: int) -> void:
|
||
if _player_state != null:
|
||
_player_state.set_value("use_dog_item_id", item_id)
|
||
_player_data = _player_state.player_data()
|
||
else:
|
||
_player_data["use_dog_item_id"] = item_id
|
||
if _player_inventory_facade != null:
|
||
_player_inventory_facade.set_player_data(_player_data)
|
||
_render_player()
|
||
if _panel_router != null:
|
||
_panel_router.render_pet_display()
|
||
|
||
|
||
func _get_selected_position() -> int:
|
||
if _farm_interaction_coordinator != null:
|
||
return _farm_interaction_coordinator.selected_position()
|
||
return -1
|
||
|
||
|
||
func _get_land_data(position: int) -> Dictionary:
|
||
return _lands_by_position.get(position, {})
|
||
|
||
|
||
func _is_operation_in_progress() -> bool:
|
||
return _operation_in_progress
|
||
|
||
|
||
func _set_operation_in_progress(value: bool) -> void:
|
||
_operation_in_progress = value
|
||
|
||
|
||
func _token() -> String:
|
||
if _player_state == null:
|
||
return ""
|
||
return _player_state.token()
|
||
|
||
|
||
func _player_id() -> int:
|
||
if _player_state == null:
|
||
return 0
|
||
var id: int = _player_state.int_value("id", 0)
|
||
if id <= 0:
|
||
id = _player_state.int_value("user_id", 0)
|
||
return id
|
||
|
||
|
||
func _own_player_data() -> Dictionary:
|
||
if _player_state == null:
|
||
return _player_data
|
||
return _player_state.player_data()
|
||
|
||
|
||
func _apply_player_stamina(stamina_data: Dictionary) -> void:
|
||
if stamina_data.is_empty():
|
||
return
|
||
var next_data := _own_player_data().duplicate(true)
|
||
for key in STAMINA_KEYS:
|
||
if stamina_data.has(key):
|
||
next_data[key] = stamina_data.get(key)
|
||
if _player_state != null:
|
||
_player_state.set_data(next_data)
|
||
_player_data = next_data if not _visiting_friend else _player_data
|
||
else:
|
||
_player_data = next_data
|
||
if _player_inventory_facade != null:
|
||
_player_inventory_facade.set_player_data(next_data)
|
||
_render_player()
|
||
|
||
|
||
func _apply_pet_battle_data(battle_data: Dictionary) -> void:
|
||
if battle_data.is_empty():
|
||
return
|
||
var attacker = battle_data.get("attacker_pet", {})
|
||
if attacker is Dictionary:
|
||
_apply_pet_snapshot_to_player_data(_own_player_data(), attacker, true)
|
||
var defender = battle_data.get("defender_pet", {})
|
||
if defender is Dictionary:
|
||
_apply_pet_snapshot_to_player_data(_player_data, defender, false)
|
||
if battle_data.has("weak_until"):
|
||
var weak_until := int(battle_data.get("weak_until", 0))
|
||
if _visiting_friend and weak_until > 0:
|
||
_player_data["pet_weak_until"] = weak_until
|
||
_player_data["pet_weak"] = 1
|
||
if _panel_router != null:
|
||
_panel_router.render_pet_display()
|
||
|
||
|
||
func _play_pet_battle_vs() -> void:
|
||
if _pet_battle_vs_overlay == null:
|
||
return
|
||
await _pet_battle_vs_overlay.play()
|
||
|
||
|
||
func _apply_pet_snapshot_to_player_data(target_data: Dictionary, pet_data: Dictionary, own_player := false) -> void:
|
||
var target_pet = target_data.get("pet", {})
|
||
if not target_pet is Dictionary:
|
||
return
|
||
if int(target_pet.get("id", 0)) != int(pet_data.get("id", 0)):
|
||
return
|
||
target_pet["hp"] = int(pet_data.get("hp", target_pet.get("hp", 0)))
|
||
target_pet["max_hp"] = int(pet_data.get("max_hp", target_pet.get("max_hp", 1)))
|
||
target_pet["fighting_capacity"] = int(pet_data.get("fighting_capacity", target_pet.get("fighting_capacity", 0)))
|
||
target_data["pet"] = target_pet
|
||
if own_player and _player_state != null:
|
||
_player_state.set_data(target_data)
|
||
if _player_inventory_facade != null:
|
||
_player_inventory_facade.set_player_data(target_data)
|
||
|
||
|
||
func _is_visiting_friend() -> bool:
|
||
return _visiting_friend
|
||
|
||
|
||
func _current_visited_friend_id() -> int:
|
||
return _visited_friend_id
|
||
|
||
|
||
func _visit_friend_home(row: Dictionary) -> void:
|
||
if _friend_visit_controller == null:
|
||
return
|
||
await _friend_visit_controller.visit_friend_home(row)
|
||
|
||
|
||
func _back_to_own_farm() -> void:
|
||
if not _visiting_friend:
|
||
return
|
||
_visiting_friend = false
|
||
_visited_friend_id = 0
|
||
_visited_friend_data.clear()
|
||
_player_data = _player_state.player_data() if _player_state != null else {}
|
||
_farm_state.setup_from_player_data(_player_data, LAND_COUNT)
|
||
_lands_by_position = _farm_state.snapshot()
|
||
_hide_selection_ui()
|
||
_render_scene_skin()
|
||
_render_house_art()
|
||
_render_player()
|
||
_render_lands()
|
||
_apply_visit_mode_visuals()
|
||
_refresh_scene_pet_display()
|
||
|
||
|
||
func _enter_friend_farm(friend_data: Dictionary) -> void:
|
||
var scene_data := friend_data.duplicate(true)
|
||
var target_id := 0
|
||
if _friend_visit_controller != null and _friend_visit_controller.has_method("target_user_id"):
|
||
target_id = int(_friend_visit_controller.target_user_id(scene_data))
|
||
if target_id <= 0:
|
||
for key in ["uid", "user_id", "id"]:
|
||
target_id = int(scene_data.get(key, 0))
|
||
if target_id > 0:
|
||
break
|
||
if target_id <= 0:
|
||
_show_toast("好友数据异常")
|
||
return
|
||
scene_data["id"] = target_id
|
||
scene_data["user_id"] = target_id
|
||
_visiting_friend = true
|
||
_visited_friend_id = target_id
|
||
_visited_friend_data = scene_data
|
||
_player_data = _visited_friend_data
|
||
if _panel_router != null:
|
||
_panel_router.close_modal_panel(false)
|
||
_farm_state.setup_from_player_data(_player_data, LAND_COUNT)
|
||
_lands_by_position = _farm_state.snapshot()
|
||
_hide_selection_ui()
|
||
_render_scene_skin()
|
||
_render_house_art()
|
||
_render_player()
|
||
_render_lands()
|
||
_apply_visit_mode_visuals()
|
||
_refresh_scene_pet_display()
|
||
_show_toast("正在访问:%s" % str(_player_data.get("nickname", _player_data.get("username", ""))).left(8))
|
||
|
||
|
||
func _apply_visit_mode_visuals() -> void:
|
||
if back_home_art != null:
|
||
back_home_art.visible = _visiting_friend
|
||
if main_menu != null:
|
||
main_menu.visible = not _visiting_friend
|
||
if left_menu != null:
|
||
left_menu.visible = not _visiting_friend
|
||
if left_middle_menu != null:
|
||
left_middle_menu.visible = not _visiting_friend
|
||
if exchange_gold_art != null:
|
||
exchange_gold_art.visible = false
|
||
if add_land_art != null and _visiting_friend:
|
||
add_land_art.visible = false
|
||
if _add_land_hit != null and _visiting_friend:
|
||
_add_land_hit.visible = false
|
||
if _top_bar_view != null and _top_bar_view.has_method("set_visitor_mode"):
|
||
_top_bar_view.set_visitor_mode(_visiting_friend)
|
||
if _scene_hit_router != null and _scene_hit_router.has_method("set_visitor_mode"):
|
||
_scene_hit_router.set_visitor_mode(_visiting_friend)
|
||
|
||
|
||
func _refresh_scene_pet_display() -> void:
|
||
if _panel_router != null:
|
||
_panel_router.render_pet_display()
|
||
if _visiting_friend and _pet_display != null:
|
||
_pet_display.visible = false
|
||
|
||
|
||
func _response_data(result: Dictionary, fallback: Variant = {}) -> Variant:
|
||
# 服务层保留完整 HTTP 结果,UI 只关心业务 body.data;缺失时返回调用方指定兜底值。
|
||
var body = result.get("body", {})
|
||
if body is Dictionary and body.has("data"):
|
||
return body.get("data")
|
||
return fallback
|
||
|
||
|
||
func _response_status_text(result: Dictionary, fallback := "操作失败") -> String:
|
||
var status = result.get("game_status", result.get("status", 0))
|
||
if status == null:
|
||
return I18nScript.t(fallback)
|
||
var status_code := _status_code(status)
|
||
if SERVER_STATUS_TEXT.has(status_code):
|
||
return I18nScript.t(str(SERVER_STATUS_TEXT.get(status_code)))
|
||
var body = result.get("body", {})
|
||
if body is Dictionary:
|
||
var info := str(body.get("info", "")).strip_edges()
|
||
if not info.is_empty():
|
||
return I18nScript.t(info)
|
||
return "%s:%s" % [I18nScript.t(fallback), status]
|
||
|
||
|
||
func _status_code(status: Variant) -> int:
|
||
if status is int:
|
||
return int(status)
|
||
if status is float:
|
||
return int(status)
|
||
var text := str(status)
|
||
return int(text) if text.is_valid_int() else -1
|
||
|
||
|
||
func _reward_fields_to_text(row: Dictionary, first_field: int, last_field: int, multiplier := 1) -> String:
|
||
var parts: Array[String] = []
|
||
# 很多老配置把奖励拆成 fruit_id1...fruit_idN,这里统一拼回 CostRules 可解析的文本。
|
||
for index in range(first_field, last_field + 1):
|
||
var raw := str(row.get("fruit_id%s" % index, "")).strip_edges()
|
||
if raw.is_empty():
|
||
continue
|
||
var pair := raw.split(":", false)
|
||
if pair.size() != 2:
|
||
continue
|
||
var item_id := int(pair[0])
|
||
var count := int(pair[1]) * multiplier
|
||
if item_id > 0 and count > 0:
|
||
parts.append("%s:%s" % [item_id, count])
|
||
return ",".join(parts)
|
||
|
||
|
||
func _format_reward_text(cost_text: String) -> String:
|
||
var labels: Array[String] = []
|
||
for cost in _parse_cost_string(cost_text):
|
||
var item_id := int(cost.get("item_id", 0))
|
||
var name: String = _game_config.get_item_name(item_id)
|
||
labels.append("%s x%s" % [name, NumberFormatterScript.compact(int(cost.get("count", 0)))])
|
||
return I18nScript.list_separator().join(labels)
|
||
|
||
|
||
func _add_reward_items(cost_text: String) -> void:
|
||
if _player_inventory_facade == null:
|
||
return
|
||
# 活动和任务奖励统一进 Facade,货币和仓库道具会在同一个地方落账。
|
||
_player_inventory_facade.add_reward_items(_costs_to_text(_parse_cost_string(cost_text)))
|
||
|
||
|
||
func _filter_removed_item_costs(costs: Array[Dictionary]) -> Array[Dictionary]:
|
||
var result: Array[Dictionary] = []
|
||
for cost in costs:
|
||
var item_id := int(cost.get("item_id", 0))
|
||
if _is_removed_item(item_id):
|
||
continue
|
||
result.append(cost)
|
||
return result
|
||
|
||
|
||
func _is_removed_item(item_id: int) -> bool:
|
||
if item_id in REMOVED_BOX_ITEM_IDS:
|
||
return true
|
||
if _game_config == null:
|
||
return false
|
||
return _game_config.get_item_type2(item_id) == REMOVED_BOX_ITEM_TYPE
|
||
|
||
|
||
func _costs_to_text(costs: Array[Dictionary]) -> String:
|
||
var parts: Array[String] = []
|
||
for cost in costs:
|
||
var item_id := int(cost.get("item_id", 0))
|
||
var count := int(cost.get("count", 0))
|
||
if item_id > 0 and count > 0:
|
||
parts.append("%s:%s" % [item_id, count])
|
||
return ",".join(parts)
|
||
|
||
|
||
func _set_texture_button_enabled(button_holder: Control, enabled: bool, normal_texture: Texture2D = null, disabled_texture: Texture2D = null) -> void:
|
||
if button_holder == null:
|
||
return
|
||
button_holder.modulate = Color.WHITE if enabled else Color(0.78, 0.78, 0.78, 1.0)
|
||
var art := button_holder.get_node_or_null("Art") as TextureRect
|
||
if art != null:
|
||
if enabled and normal_texture != null:
|
||
art.texture = normal_texture
|
||
elif not enabled and disabled_texture != null:
|
||
art.texture = disabled_texture
|
||
var hit := button_holder.get_node_or_null("Hit") as Button
|
||
if hit != null:
|
||
hit.disabled = not enabled
|
||
|
||
|
||
func _sync_inventory_from_facade() -> void:
|
||
if _player_inventory_facade == null:
|
||
return
|
||
# Facade 内部状态变化后回写主场景字段,保留旧面板仍然依赖的成员变量接口。
|
||
if not _visiting_friend:
|
||
_player_data = _player_inventory_facade.player_data()
|
||
_seed_items = _player_inventory_facade.seed_items()
|
||
_fertilizer_items = _player_inventory_facade.fertilizer_items()
|
||
_warehouse_items = _player_inventory_facade.warehouse_items()
|
||
|
||
|
||
func _should_render_bag() -> bool:
|
||
return (_seed_bag_view != null and _seed_bag_view.is_visible()) or (_bag_panel != null and _bag_panel.visible)
|
||
|
||
|
||
func _render_bag() -> void:
|
||
if _farm_interaction_coordinator != null:
|
||
_farm_interaction_coordinator.render_bag(_seed_items, _fertilizer_items)
|
||
|
||
|
||
func _on_bag_item_pressed(item: Dictionary, mode := "") -> void:
|
||
if _farm_interaction_coordinator != null:
|
||
_farm_interaction_coordinator.on_bag_item_pressed(item, mode)
|
||
|
||
|
||
func _sow_seed(item: Dictionary) -> void:
|
||
if _farm_controller == null:
|
||
return
|
||
await _farm_controller.sow_seed(item)
|
||
|
||
|
||
func _gather_crop() -> void:
|
||
if _farm_controller == null:
|
||
return
|
||
await _farm_controller.gather_crop()
|
||
|
||
|
||
func _clear_land() -> void:
|
||
if _farm_controller == null:
|
||
return
|
||
await _farm_controller.clear_land()
|
||
|
||
|
||
func _use_fertilizer(item: Dictionary) -> void:
|
||
if _farm_controller == null:
|
||
return
|
||
await _farm_controller.use_fertilizer(item)
|
||
|
||
|
||
func _kick_disease(type: int) -> void:
|
||
if _farm_controller == null:
|
||
return
|
||
await _farm_controller.kick_disease(type)
|
||
|
||
|
||
func _interact_friend_land(action: String) -> void:
|
||
if _friend_visit_controller == null:
|
||
return
|
||
await _friend_visit_controller.interact_land(action)
|
||
|
||
|
||
func _apply_land_data(land_data: Dictionary) -> void:
|
||
if land_data.is_empty():
|
||
return
|
||
var position := int(land_data.get("position", -1))
|
||
if position < 0:
|
||
return
|
||
_farm_state.set_land(land_data)
|
||
_lands_by_position = _farm_state.snapshot()
|
||
if _visiting_friend:
|
||
_replace_current_scene_land(land_data)
|
||
else:
|
||
_replace_player_land(land_data)
|
||
|
||
|
||
func _add_player_exp(add_exp: int) -> void:
|
||
if add_exp <= 0:
|
||
return
|
||
_player_state.add_exp(add_exp)
|
||
_player_data = _player_state.player_data()
|
||
if _player_inventory_facade != null:
|
||
_player_inventory_facade.set_player_data(_player_data)
|
||
_render_player()
|
||
|
||
|
||
func _replace_player_land(land_data: Dictionary) -> void:
|
||
_player_state.replace_land(land_data)
|
||
_player_data = _player_state.player_data()
|
||
if _player_inventory_facade != null:
|
||
_player_inventory_facade.set_player_data(_player_data)
|
||
|
||
|
||
func _replace_current_scene_land(land_data: Dictionary) -> void:
|
||
var land_list = _player_data.get("landList", [])
|
||
if not land_list is Array:
|
||
_player_data["landList"] = [land_data.duplicate(true)]
|
||
return
|
||
var target_position := int(land_data.get("position", -1))
|
||
var replaced := false
|
||
for index in range(land_list.size()):
|
||
if land_list[index] is Dictionary and int(land_list[index].get("position", -1)) == target_position:
|
||
land_list[index] = land_data.duplicate(true)
|
||
replaced = true
|
||
break
|
||
if not replaced:
|
||
land_list.append(land_data.duplicate(true))
|
||
_player_data["landList"] = land_list
|
||
|
||
|
||
func _decrease_seed_item(item_id: int) -> void:
|
||
if _player_inventory_facade == null:
|
||
return
|
||
_player_inventory_facade.decrease_seed_item(item_id)
|
||
|
||
|
||
func _decrease_fertilizer_item(item_id: int) -> void:
|
||
if _player_inventory_facade == null:
|
||
return
|
||
_player_inventory_facade.decrease_fertilizer_item(item_id)
|
||
|
||
|
||
func _hide_selection_ui() -> void:
|
||
if _main_menu_controller != null and _main_menu_controller.is_open():
|
||
_main_menu_controller.hide(true)
|
||
if _farm_interaction_coordinator != null:
|
||
_farm_interaction_coordinator.hide_selection_ui()
|
||
|
||
|
||
func _hide_bag_panel() -> void:
|
||
if _farm_interaction_coordinator != null:
|
||
_farm_interaction_coordinator.hide_bag_panel()
|
||
|
||
|
||
func _current_skin_graphic_id() -> int:
|
||
var skin_item_id: int = int(_player_data.get("bg_image_id", 104004))
|
||
var config: Dictionary = _game_config.get_item_config(skin_item_id) if _game_config != null else {}
|
||
if not config.is_empty():
|
||
return int(config.get("graphical_id", 5004))
|
||
return skin_item_id if skin_item_id > 0 else 5004
|
||
|
||
|
||
func _load_skin_texture(graphic_id: int, suffix: String) -> Texture2D:
|
||
var path := "res://assets/egret/skin/%s%s" % [graphic_id, suffix]
|
||
if not ResourceLoader.exists(path):
|
||
path = "res://assets/egret/skin/5004%s" % suffix
|
||
if _texture_provider != null and _texture_provider.has_method("standalone"):
|
||
return _texture_provider.standalone(path)
|
||
return load(path)
|
||
|
||
|
||
func _seed_icon_id(item_id: int) -> int:
|
||
return _game_config.get_item_icon_id(item_id)
|
||
|
||
|
||
func _item_icon_id(item_id: int) -> int:
|
||
return _game_config.get_item_icon_id(item_id)
|
||
|
||
|
||
func _get_land_node(position: int) -> TextureRect:
|
||
return game_view.get_node_or_null("Land%s" % position) as TextureRect
|