hy-farm/godot/FarmGodot/scripts/farm/farm_controller.gd
2026-05-29 19:54:56 +08:00

236 lines
8.0 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)