236 lines
8.0 KiB
GDScript
236 lines
8.0 KiB
GDScript
class_name FarmController
|
||
extends RefCounted
|
||
|
||
# 土地玩法控制器。
|
||
#
|
||
# 这个类只处理“用户对某块土地执行动作”的业务闭环:
|
||
# 1. 读取当前选中土地和 token。
|
||
# 2. 调后端接口。
|
||
# 3. 用 FarmMapper 把后端返回归一化成土地数据。
|
||
# 4. 通过 callbacks 通知 GameScene 更新状态、库存、UI。
|
||
#
|
||
# 它不直接持有节点,也不直接画 UI;这样土地玩法可以独立于具体场景布局维护。
|
||
|
||
var _farm_api
|
||
var _farm_mapper
|
||
|
||
# callbacks 是控制器和 UI/状态层的边界。
|
||
# 目前由 GameScene 提供,后续可以替换成专门的 FarmInteractionCoordinator。
|
||
var _callbacks: Dictionary = {}
|
||
var _disease_type_bug := 1
|
||
var _disease_type_grass := 2
|
||
var _disease_type_water := 0
|
||
var _item_id_bug_killer := 108001
|
||
var _item_id_grass_killer := 109001
|
||
|
||
|
||
func setup(options: Dictionary) -> void:
|
||
# 疾病类型和道具 id 由宿主注入,避免在控制器里散落硬编码。
|
||
_farm_api = options.get("farm_api")
|
||
_farm_mapper = options.get("farm_mapper")
|
||
_callbacks = options.get("callbacks", {})
|
||
_disease_type_bug = int(options.get("disease_type_bug", _disease_type_bug))
|
||
_disease_type_grass = int(options.get("disease_type_grass", _disease_type_grass))
|
||
_disease_type_water = int(options.get("disease_type_water", _disease_type_water))
|
||
_item_id_bug_killer = int(options.get("item_id_bug_killer", _item_id_bug_killer))
|
||
_item_id_grass_killer = int(options.get("item_id_grass_killer", _item_id_grass_killer))
|
||
|
||
|
||
func sow_seed(item: Dictionary) -> void:
|
||
# 播种成功后,后端可能只返回部分字段;FarmMapper 会用旧土地数据补齐位置/等级等信息。
|
||
var position := _selected_position()
|
||
if position < 0 or _is_busy():
|
||
return
|
||
var token := _token()
|
||
var item_id := int(item.get("item_id", 0))
|
||
if token.is_empty() or item_id <= 0 or _farm_api == null:
|
||
return
|
||
|
||
_set_busy(true)
|
||
var result: Dictionary = await _farm_api.sow_seed(token, position, item_id)
|
||
_set_busy(false)
|
||
if not result.get("ok", false):
|
||
_warn_and_show("farm/sow-seeds", result, "播种失败")
|
||
return
|
||
|
||
# UI 先用接口结果立即刷新,再异步刷新仓库,保证播种体验不被库存接口阻塞太久。
|
||
var land_data: Dictionary = _farm_mapper.land_data_from_sow_result(result, position, item_id, _land_data(position))
|
||
_call("apply_land_data", [land_data])
|
||
_call("decrease_seed_item", [item_id])
|
||
_call("hide_bag_panel")
|
||
_call("hide_land_light")
|
||
_call("render_lands")
|
||
_show_toast("播种成功")
|
||
await _call_async("refresh_store_house")
|
||
|
||
|
||
func gather_crop() -> void:
|
||
# 收获是土地状态和玩家经验同时变化的动作,因此成功后要刷新土地、经验和仓库。
|
||
var position := _selected_position()
|
||
if position < 0 or _is_busy():
|
||
return
|
||
var token := _token()
|
||
if token.is_empty() or _farm_api == null:
|
||
return
|
||
|
||
_set_busy(true)
|
||
var result: Dictionary = await _farm_api.gather_crop(token, position)
|
||
_set_busy(false)
|
||
if not result.get("ok", false):
|
||
_warn_and_show("farm/gather-crop", result, "收获失败")
|
||
return
|
||
|
||
var land_data: Dictionary = _farm_mapper.land_data_from_result(result, position, _land_data(position))
|
||
_call("apply_land_data", [land_data])
|
||
_call("add_player_exp", [int(land_data.get("add_exp", 0))])
|
||
var reward_text := str(land_data.get("reward_text", ""))
|
||
if reward_text.is_empty():
|
||
var item_id := int(land_data.get("item_id", 0))
|
||
var item_count := int(land_data.get("item_add_num", 0))
|
||
if item_id > 0 and item_count > 0:
|
||
reward_text = "%s:%s" % [item_id, item_count]
|
||
if not reward_text.is_empty():
|
||
_call("add_reward_items", [reward_text])
|
||
_call("hide_selection_ui")
|
||
_call("render_lands")
|
||
await _call_async("refresh_store_house")
|
||
|
||
|
||
func clear_land() -> void:
|
||
# 铲除只改变土地本身,不产出奖励;后端成功后关闭选择态并重绘土地。
|
||
var position := _selected_position()
|
||
if position < 0 or _is_busy():
|
||
return
|
||
var token := _token()
|
||
if token.is_empty() or _farm_api == null:
|
||
return
|
||
|
||
_set_busy(true)
|
||
var result: Dictionary = await _farm_api.clear_land(token, position)
|
||
_set_busy(false)
|
||
if not result.get("ok", false):
|
||
_warn_and_show("farm/clear-land", result, "铲除失败")
|
||
return
|
||
|
||
var land_data: Dictionary = _farm_mapper.land_data_from_result(result, position, _land_data(position))
|
||
_call("apply_land_data", [land_data])
|
||
_call("hide_selection_ui")
|
||
_call("render_lands")
|
||
|
||
|
||
func use_fertilizer(item: Dictionary) -> void:
|
||
# 施肥后需要重新打开植物提示,因为成熟时间/阶段可能立即变化。
|
||
var position := _selected_position()
|
||
if position < 0 or _is_busy():
|
||
return
|
||
var token := _token()
|
||
var item_id := int(item.get("item_id", 0))
|
||
if token.is_empty() or item_id <= 0 or _farm_api == null:
|
||
return
|
||
|
||
_set_busy(true)
|
||
var result: Dictionary = await _farm_api.fertilize(token, position, item_id)
|
||
_set_busy(false)
|
||
if not result.get("ok", false):
|
||
_warn_and_show("farm/fertilize", result, "施肥失败")
|
||
return
|
||
|
||
var land_data: Dictionary = _farm_mapper.land_data_from_result(result, position, _land_data(position))
|
||
_call("apply_land_data", [land_data])
|
||
_call("decrease_fertilizer_item", [item_id])
|
||
_call("hide_bag_panel")
|
||
_call("render_lands")
|
||
await _call_async("refresh_store_house")
|
||
_call("show_plant_tip", [position, land_data])
|
||
|
||
|
||
func kick_disease(type: int) -> void:
|
||
# 原版把浇水、除虫、除草都走 farm/disease,通过 type 区分。
|
||
# 只有除虫/除草消耗仓库道具;浇水不扣除道具。
|
||
var position := _selected_position()
|
||
if position < 0 or _is_busy():
|
||
return
|
||
var token := _token()
|
||
if token.is_empty() or _farm_api == null:
|
||
return
|
||
|
||
_set_busy(true)
|
||
var result: Dictionary = await _farm_api.kick_disease(token, position, type)
|
||
_set_busy(false)
|
||
if not result.get("ok", false):
|
||
_warn_and_show("farm/disease", result, _disease_failure_text(type))
|
||
return
|
||
|
||
var land_data: Dictionary = _farm_mapper.land_data_from_result(result, position, _land_data(position))
|
||
_call("apply_land_data", [land_data])
|
||
if type == _disease_type_bug:
|
||
_call("decrease_warehouse_item", [_item_id_bug_killer, 1])
|
||
elif type == _disease_type_grass:
|
||
_call("decrease_warehouse_item", [_item_id_grass_killer, 1])
|
||
_call("hide_selection_ui")
|
||
_call("render_lands")
|
||
await _call_async("refresh_store_house")
|
||
_call("show_toast", ["浇水成功" if type == _disease_type_water else "操作成功"])
|
||
|
||
|
||
func _selected_position() -> int:
|
||
return int(_call("selected_position", [], -1))
|
||
|
||
|
||
func _token() -> String:
|
||
return str(_call("token", [], ""))
|
||
|
||
|
||
func _land_data(position: int) -> Dictionary:
|
||
var result = _call("land_data", [position], {})
|
||
return result if result is Dictionary else {}
|
||
|
||
|
||
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_status_text(result: Dictionary, fallback := "操作失败") -> String:
|
||
return str(_call("response_status_text", [result, fallback], fallback))
|
||
|
||
|
||
func _show_toast(message: String) -> void:
|
||
if not message.is_empty():
|
||
_call("show_toast", [message])
|
||
|
||
|
||
func _warn_and_show(endpoint: String, result: Dictionary, fallback: String) -> void:
|
||
var status = result.get("game_status", result.get("status", 0))
|
||
print_verbose("%s failed: %s" % [endpoint, status])
|
||
_show_toast(_response_status_text(result, fallback))
|
||
|
||
|
||
func _disease_failure_text(type: int) -> String:
|
||
if type == _disease_type_water:
|
||
return "浇水失败"
|
||
if type == _disease_type_bug:
|
||
return "除虫失败"
|
||
if type == _disease_type_grass:
|
||
return "除草失败"
|
||
return "操作失败"
|
||
|
||
|
||
func _call(name: String, args: Array = [], fallback: Variant = null) -> Variant:
|
||
# 控制器可以独立 check-only;缺失 callback 时返回 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:
|
||
# 异步 callback 统一 await,避免调用方混用 call/callv 产生不可见时序问题。
|
||
var callable: Callable = _callbacks.get(name, Callable())
|
||
if not callable.is_valid():
|
||
return null
|
||
return await callable.callv(args)
|