221 lines
8.0 KiB
GDScript
221 lines
8.0 KiB
GDScript
class_name PlayerInventoryFacade
|
||
extends RefCounted
|
||
|
||
# 玩家背包/货币门面:
|
||
# - UI 面板只调用这里的“刷新、检查、扣减、发奖”接口,不直接改 InventoryState。
|
||
# - 后端返回的仓库数据由 InventoryMapper 归一化,金币/钻石/石材/钢材等资源保留在 PlayerState。
|
||
# - 该类仍通过 callbacks 兼容旧 game_scene 字段,后续拆面板时优先依赖它而不是直接读主场景。
|
||
const CostRulesScript := preload("res://scripts/domain/rules/cost_rules.gd")
|
||
|
||
# PlayerState 是玩家快照的唯一权威来源;InventoryState 只管理种子/肥料/仓库列表。
|
||
var _player_state
|
||
var _player_data: Dictionary = {}
|
||
var _inventory_state
|
||
var _inventory_mapper
|
||
var _inventory_api
|
||
var _game_config
|
||
# callbacks 用于通知 UI 局部刷新,避免 Facade 反向依赖具体面板节点。
|
||
var _callbacks: Dictionary = {}
|
||
var _item_id_gem := 105001
|
||
var _item_id_gold := 105002
|
||
var _item_id_stone := 201000
|
||
var _item_id_steel := 201001
|
||
|
||
|
||
func setup(options: Dictionary) -> void:
|
||
# 所有依赖在 bootstrap 阶段注入,保证状态、接口、配置来源一致。
|
||
_player_state = options.get("player_state")
|
||
_player_data = options.get("player_data", {})
|
||
_inventory_state = options.get("inventory_state")
|
||
_inventory_mapper = options.get("inventory_mapper")
|
||
_inventory_api = options.get("inventory_api")
|
||
_game_config = options.get("game_config")
|
||
_callbacks = options.get("callbacks", {})
|
||
_item_id_gem = int(options.get("item_id_gem", _item_id_gem))
|
||
_item_id_gold = int(options.get("item_id_gold", _item_id_gold))
|
||
_item_id_stone = int(options.get("item_id_stone", _item_id_stone))
|
||
_item_id_steel = int(options.get("item_id_steel", _item_id_steel))
|
||
|
||
|
||
func player_data() -> Dictionary:
|
||
if _player_state != null and _player_state.has_method("player_data"):
|
||
return _player_state.player_data()
|
||
return _player_data
|
||
|
||
|
||
func set_player_data(player_data: Dictionary) -> void:
|
||
if _player_state != null and _player_state.has_method("set_data"):
|
||
_player_state.set_data(player_data)
|
||
_player_data = player_data
|
||
|
||
|
||
func seed_items() -> Array[Dictionary]:
|
||
return _inventory_state.seed_items if _inventory_state != null else []
|
||
|
||
|
||
func fertilizer_items() -> Array[Dictionary]:
|
||
return _inventory_state.fertilizer_items if _inventory_state != null else []
|
||
|
||
|
||
func warehouse_items() -> Array[Dictionary]:
|
||
return _inventory_state.warehouse_items if _inventory_state != null else []
|
||
|
||
|
||
func refresh_store_house(token: String) -> void:
|
||
if token.is_empty() or _inventory_api == null or _inventory_mapper == null or _inventory_state == null:
|
||
return
|
||
# 仓库刷新是网络操作;只在打开背包/仓库或关键操作后调用,不放进 process 轮询。
|
||
var result: Dictionary = await _inventory_api.fetch_store_house(token)
|
||
if not result.get("ok", false):
|
||
push_warning("store-house failed: %s" % result.get("game_status", result.get("status", 0)))
|
||
return
|
||
var body = result.get("body", {})
|
||
if not body is Dictionary:
|
||
return
|
||
var store_data = body.get("data", [])
|
||
# 后端一份列表拆成种子、肥料、仓库三份 UI 数据,渲染层无需理解老接口字段。
|
||
_inventory_state.set_items(
|
||
_inventory_mapper.extract_seed_items(store_data),
|
||
_inventory_mapper.extract_fertilizer_items(store_data),
|
||
_inventory_mapper.extract_warehouse_items(store_data)
|
||
)
|
||
_emit_inventory_changed()
|
||
# 只刷新当前可见的面板,避免背包更新导致所有功能面板重建。
|
||
if bool(_call("should_render_bag", [], false)):
|
||
_call("render_bag")
|
||
if bool(_call("should_render_warehouse", [], false)):
|
||
_call("render_warehouse")
|
||
|
||
|
||
func parse_cost_string(cost_text: String) -> Array[Dictionary]:
|
||
return CostRulesScript.parse_cost_string(cost_text)
|
||
|
||
|
||
func aggregate_costs(costs: Array[Dictionary]) -> Dictionary:
|
||
return CostRulesScript.aggregate_costs(costs)
|
||
|
||
|
||
func has_costs(cost_text: String) -> bool:
|
||
# 消耗判断同时覆盖 player_data 货币和仓库道具;各面板不要再各写一份判断。
|
||
return CostRulesScript.has_costs(cost_text, player_data(), warehouse_items())
|
||
|
||
|
||
func owned_amount(item_id: int) -> int:
|
||
return CostRulesScript.get_owned_amount(item_id, player_data(), warehouse_items())
|
||
|
||
|
||
func apply_cost_deduction(cost_text: String) -> void:
|
||
var aggregated := aggregate_costs(parse_cost_string(cost_text))
|
||
var data_ref := player_data()
|
||
# 本地扣减按物品 id 分流:资源写 player_data,普通道具写 InventoryState。
|
||
for item_id in aggregated.keys():
|
||
var count := int(aggregated[item_id])
|
||
match int(item_id):
|
||
_item_id_gem:
|
||
data_ref["gem"] = maxi(int(data_ref.get("gem", 0)) - count, 0)
|
||
_item_id_gold:
|
||
data_ref["gold"] = maxi(int(data_ref.get("gold", 0)) - count, 0)
|
||
_item_id_stone:
|
||
data_ref["stone"] = maxi(int(data_ref.get("stone", 0)) - count, 0)
|
||
_item_id_steel:
|
||
data_ref["steel"] = maxi(int(data_ref.get("steel", 0)) - count, 0)
|
||
_:
|
||
decrease_warehouse_item(int(item_id), count)
|
||
# 扣减后立即刷新顶部资源和相关背包视图,保证操作反馈接近原版 H5。
|
||
_call("render_player")
|
||
_emit_inventory_changed()
|
||
|
||
|
||
func add_reward_items(cost_text: String) -> void:
|
||
# 奖励文本复用 CostRules 格式,活动奖励都能走同一个落账入口。
|
||
var data_ref := player_data()
|
||
for cost in parse_cost_string(cost_text):
|
||
var item_id := int(cost.get("item_id", 0))
|
||
var count := int(cost.get("count", 0))
|
||
match item_id:
|
||
_item_id_gem:
|
||
data_ref["gem"] = int(data_ref.get("gem", 0)) + count
|
||
_item_id_gold:
|
||
data_ref["gold"] = int(data_ref.get("gold", 0)) + count
|
||
_item_id_stone:
|
||
data_ref["stone"] = int(data_ref.get("stone", 0)) + count
|
||
_item_id_steel:
|
||
data_ref["steel"] = int(data_ref.get("steel", 0)) + count
|
||
_:
|
||
add_warehouse_item(item_id, count)
|
||
_call("render_player")
|
||
_emit_inventory_changed()
|
||
|
||
|
||
func add_warehouse_item(item_id: int, count: int) -> void:
|
||
var items := warehouse_items()
|
||
for index in range(items.size()):
|
||
if int(items[index].get("item_id", 0)) != item_id:
|
||
continue
|
||
# 同类道具合并数量,同时写 num/number 两个字段兼容不同旧面板读取习惯。
|
||
var next_num := int(items[index].get("num", items[index].get("number", 0))) + count
|
||
items[index]["num"] = next_num
|
||
items[index]["number"] = next_num
|
||
return
|
||
# 新道具需要补齐 icon/name/config,避免仓库面板再回头查多次配置。
|
||
var config: Dictionary = _game_config.normalize_item_config(_game_config.get_item_config(item_id))
|
||
items.append({
|
||
"item_id": item_id,
|
||
"num": count,
|
||
"number": count,
|
||
"icon_id": int(config.get("icon", _game_config.get_item_icon_id(item_id))),
|
||
"name": str(config.get("name", _game_config.get_item_name(item_id))),
|
||
"config": config
|
||
})
|
||
|
||
|
||
func decrease_warehouse_item(item_id: int, count: int) -> void:
|
||
if _inventory_state == null:
|
||
return
|
||
_inventory_state.decrease_warehouse(item_id, count)
|
||
_emit_inventory_changed()
|
||
|
||
|
||
func decrease_seed_item(item_id: int) -> void:
|
||
if _inventory_state == null:
|
||
return
|
||
_inventory_state.decrease_seed(item_id)
|
||
_emit_inventory_changed()
|
||
|
||
|
||
func decrease_fertilizer_item(item_id: int) -> void:
|
||
if _inventory_state == null:
|
||
return
|
||
_inventory_state.decrease_fertilizer(item_id)
|
||
_emit_inventory_changed()
|
||
|
||
|
||
func decrease_player_currency(gold_cost: int, gem_cost: int) -> void:
|
||
# 旧接口多以 gold_cost/gem_cost 分列返回,因此保留专用货币扣减入口。
|
||
var data_ref := player_data()
|
||
data_ref["gold"] = int(data_ref.get("gold", 0)) - gold_cost
|
||
data_ref["gem"] = int(data_ref.get("gem", 0)) - gem_cost
|
||
_call("render_player")
|
||
|
||
|
||
func add_player_gold(amount: int) -> void:
|
||
var data_ref := player_data()
|
||
data_ref["gold"] = int(data_ref.get("gold", 0)) + amount
|
||
_call("render_player")
|
||
|
||
|
||
func set_dog_hunger_end_time(end_time: int) -> void:
|
||
player_data()["dog_hunger_end_time"] = end_time
|
||
|
||
|
||
func _emit_inventory_changed() -> void:
|
||
# 单点广播给主场景同步旧字段;后续面板可直接订阅 Facade,减少主场景中转。
|
||
_call("inventory_changed")
|
||
|
||
|
||
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)
|