293 lines
8.9 KiB
GDScript
293 lines
8.9 KiB
GDScript
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)
|