class_name FriendVisitController extends RefCounted # 好友访问控制器。 # # 参考原 H5: # - 好友列表点击房屋/手指后触发 interaction/get-other-user-info。 # - 访问他人农场后的土地操作统一走 interaction/interaction。 # - interaction type 来自 common/utils/EnumUtil.php: # 0 偷菜,1 放虫,2 放草,3 浇水,4 除虫,5 除草。 # # 控制器只处理接口请求和业务回写,不持有场景节点。 const StaminaRulesScript := preload("res://scripts/domain/rules/stamina_rules.gd") const ACTION_STEAL := "steal" const ACTION_PUT_BUG := "put_bug" const ACTION_PUT_GRASS := "put_grass" const ACTION_WATER := "water" const ACTION_DIE_BUG := "die_bug" const ACTION_DIE_GRASS := "die_grass" const INTERACTION_TYPE_STEAL := 0 const INTERACTION_TYPE_BUG := 1 const INTERACTION_TYPE_GRASS := 2 const INTERACTION_TYPE_WATERING := 3 const INTERACTION_TYPE_DISINSECTION := 4 const INTERACTION_TYPE_WEEDING := 5 var _interaction_api var _game_config var _crop_phase_rules var _callbacks: Dictionary = {} func setup(options: Dictionary) -> void: _interaction_api = options.get("interaction_api") _game_config = options.get("game_config") _crop_phase_rules = options.get("crop_phase_rules") _callbacks = options.get("callbacks", {}) func visit_friend_home(row: Dictionary) -> void: var target_id := target_user_id(row) if target_id <= 0: _show_toast("好友数据异常") return if target_id == _player_id(): _show_toast("这是你的农场,不用去啦!") return if _is_busy() or _interaction_api == null: return var token := _token() if token.is_empty(): _show_toast("登录已失效") return _set_busy(true) var result: Dictionary = await _interaction_api.fetch_other_user_info(token, target_id) _set_busy(false) if not result.get("ok", false): _show_toast(_response_status_text(result, "访问好友失败")) return var data = _response_data(result, {}) if not data is Dictionary: _show_toast("好友数据异常") return _call("enter_friend_farm", [data]) func interact_land(action: String) -> void: if not _is_visiting_friend() or _interaction_api == null: return var position := _selected_position() if position < 0 or _is_busy(): return var interaction_type := _interaction_type(action) if interaction_type < 0: return var token := _token() var visited_friend_id := _visited_friend_id() if token.is_empty() or visited_friend_id <= 0: _show_toast("登录已失效") return if interaction_type == INTERACTION_TYPE_STEAL and not StaminaRulesScript.has_steal_cost(_own_player_data()): _show_toast("Not enough stamina") return var old_land := _land_data(position) _set_busy(true) var result: Dictionary = await _interaction_api.interact(token, visited_friend_id, position, interaction_type) _set_busy(false) if not result.get("ok", false): var fail_data = _response_data(result, {}) if fail_data is Dictionary: _sync_stamina(fail_data) _show_toast(_response_status_text(result, "好友互动失败")) return var data = _response_data(result, {}) var response_data: Dictionary = data if data is Dictionary else {} _sync_stamina(response_data) var pet_battle_data := _pet_battle_data(response_data) if not pet_battle_data.is_empty(): _sync_pet_battle(pet_battle_data) if _pet_battle_triggered(pet_battle_data): await _call_async("play_pet_battle_vs") if interaction_type == INTERACTION_TYPE_STEAL and not _pet_battle_allows_steal(pet_battle_data): _show_pet_battle_result(pet_battle_data) return var land_data: Dictionary = old_land.duplicate(true) var returned_land = response_data.get("land", {}) if returned_land is Dictionary: land_data = returned_land.duplicate(true) if not land_data.has("position"): land_data["position"] = position if not land_data.has("crop_phase") and _crop_phase_rules != null: land_data["crop_phase"] = _crop_phase_rules.get_phase(land_data) land_data["interaction_type_list"] = _remove_interaction_type(old_land.get("interaction_type_list", []), interaction_type) _call("apply_land_data", [land_data]) _apply_interaction_reward(interaction_type, response_data, old_land) _call("hide_selection_ui") _call("render_lands") 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 _interaction_type(action: String) -> int: match action: ACTION_STEAL: return INTERACTION_TYPE_STEAL ACTION_PUT_BUG: return INTERACTION_TYPE_BUG ACTION_PUT_GRASS: return INTERACTION_TYPE_GRASS ACTION_WATER: return INTERACTION_TYPE_WATERING ACTION_DIE_BUG: return INTERACTION_TYPE_DISINSECTION ACTION_DIE_GRASS: return INTERACTION_TYPE_WEEDING _: return -1 func _remove_interaction_type(raw_types: Variant, interaction_type: int) -> Array[int]: var next_types: Array[int] = [] if not raw_types is Array: return next_types for raw_type in raw_types: var type_id := int(raw_type) if type_id != interaction_type and not next_types.has(type_id): next_types.append(type_id) return next_types func _apply_interaction_reward(interaction_type: int, response_data: Dictionary, old_land: Dictionary) -> void: match interaction_type: INTERACTION_TYPE_STEAL: _apply_steal_reward(response_data, old_land) INTERACTION_TYPE_BUG: _show_toast("放虫成功") INTERACTION_TYPE_GRASS: _show_toast("放草成功") INTERACTION_TYPE_WATERING: _show_toast("浇水成功") INTERACTION_TYPE_DISINSECTION: _show_toast("除虫成功") INTERACTION_TYPE_WEEDING: _show_toast("除草成功") func _apply_steal_reward(response_data: Dictionary, old_land: Dictionary) -> void: var steal_count := int(response_data.get("steal_count", 0)) var crop_id := int(old_land.get("crop_id", 0)) if crop_id <= 0: var returned_land = response_data.get("land", {}) if returned_land is Dictionary: crop_id = int(returned_land.get("crop_id", 0)) var crop_name := "作物" var item_id := 0 if _game_config != null: crop_name = _game_config.get_crop_name(crop_id) if _game_config.has_method("get_crop_output_item_id"): item_id = int(_game_config.get_crop_output_item_id(crop_id)) if item_id > 0 and steal_count > 0: _call("add_reward_items", ["%s:%s" % [item_id, steal_count]]) _show_toast("偷取%sx%s" % [crop_name, steal_count]) func _sync_stamina(response_data: Dictionary) -> void: var stamina_data = response_data.get("stamina", {}) if stamina_data is Dictionary: _call("apply_player_stamina", [stamina_data]) func _sync_pet_battle(pet_battle_data: Dictionary) -> void: _call("apply_pet_battle_data", [pet_battle_data]) func _pet_battle_data(response_data: Dictionary) -> Dictionary: var data = response_data.get("pet_battle", {}) return data if data is Dictionary else {} func _pet_battle_triggered(pet_battle_data: Dictionary) -> bool: return int(pet_battle_data.get("triggered", 0)) == 1 func _pet_battle_allows_steal(pet_battle_data: Dictionary) -> bool: return int(pet_battle_data.get("allow_steal", 1)) == 1 func _show_pet_battle_result(pet_battle_data: Dictionary) -> void: var damage := int(pet_battle_data.get("damage", 0)) var result := str(pet_battle_data.get("result", "")) if result == "damage": _show_toast("Pet duel dealt %s damage" % damage) elif result == "weak_opened": _show_toast("Pet defeated. Farm weakened.") else: _show_toast("Pet duel failed") func _selected_position() -> int: return int(_call("selected_position", [], -1)) func _token() -> String: return str(_call("token", [], "")) func _player_id() -> int: return int(_call("player_id", [], 0)) func _own_player_data() -> Dictionary: var data = _call("own_player_data", [], {}) return data if data is Dictionary else {} func _visited_friend_id() -> int: return int(_call("visited_friend_id", [], 0)) func _land_data(position: int) -> Dictionary: var result = _call("land_data", [position], {}) return result if result is Dictionary else {} func _is_visiting_friend() -> bool: return bool(_call("is_visiting_friend", [], false)) 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 _response_data(result: Dictionary, fallback: Variant = {}) -> Variant: return _call("response_data", [result, fallback], fallback) func _response_status_text(result: Dictionary, fallback := "操作失败") -> String: return str(_call("response_status_text", [result, fallback], fallback)) func _show_toast(message: String) -> void: _call("show_toast", [message]) func _call(name: String, args: Array = [], fallback: Variant = null) -> Variant: 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 = [], fallback: Variant = null) -> Variant: var callable: Callable = _callbacks.get(name, Callable()) if not callable.is_valid(): return fallback return await callable.callv(args)