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)