Files
aetherbound-guild/runtime/entry/reward_choice_page_controller.gd
T

208 lines
8.6 KiB
GDScript

class_name ABGRewardChoicePageController
extends RefCounted
## UI-independent OUT-003 Reward Choice view-data assembly.
##
## BattleService remains responsible for claim/decline transactions and
## Recovery. This controller only validates/copies the open reward draft and
## exposes selection/Claim/Skip eligibility for the existing View.
func snapshot(
company: Dictionary,
battle: Dictionary,
draft: Dictionary = {},
selected_reward_id := "",
notice := "",
locale := "zh_CN"
) -> Dictionary:
if company.is_empty():
return {"ok": false, "code": "COMPANY_REQUIRED"}
var raw_recruits = company.get("owned_recruits", [])
if not raw_recruits is Array:
return {"ok": false, "code": "REWARD_CHOICE_COMPANY_INVALID"}
var raw_contract = company.get("active_contract", {})
if not raw_contract is Dictionary or Dictionary(raw_contract).is_empty():
return {"ok": false, "code": "REWARD_CHOICE_CONTRACT_REQUIRED"}
if battle.is_empty():
return {"ok": false, "code": "REWARD_CHOICE_BATTLE_REQUIRED"}
if String(battle.get("status", "")) != "result":
return {"ok": false, "code": "REWARD_CHOICE_BATTLE_NOT_SETTLED"}
var operation_id := String(battle.get("operation_id", ""))
if operation_id.is_empty():
return {"ok": false, "code": "REWARD_CHOICE_OPERATION_REQUIRED"}
var members := _members_by_id(raw_recruits)
if members.is_empty():
return {"ok": false, "code": "REWARD_CHOICE_COMPANY_EMPTY"}
var contract: Dictionary = Dictionary(raw_contract).duplicate(true)
var result: Dictionary = Dictionary(battle.get("result", {})).duplicate(true)
if result.is_empty():
return {"ok": false, "code": "REWARD_CHOICE_RESULT_REQUIRED"}
var source_draft := draft
if source_draft.is_empty():
source_draft = battle.get("reward_draft", {})
if not source_draft is Dictionary or Dictionary(source_draft).is_empty():
return {"ok": false, "code": "REWARD_CHOICE_DRAFT_REQUIRED"}
var copied_draft: Dictionary = Dictionary(source_draft).duplicate(true)
var persisted_draft = battle.get("reward_draft", {})
if persisted_draft is Dictionary and not Dictionary(persisted_draft).is_empty():
var persisted_hash := String(Dictionary(persisted_draft).get("draft_hash", ""))
var supplied_hash := String(copied_draft.get("draft_hash", ""))
if not persisted_hash.is_empty() and not supplied_hash.is_empty() and persisted_hash != supplied_hash:
return {"ok": false, "code": "REWARD_CHOICE_DRAFT_IDENTITY_MISMATCH"}
var status := String(copied_draft.get("status", ""))
if status != "open":
return {"ok": false, "code": "REWARD_CHOICE_DRAFT_NOT_OPEN"}
var draft_operation_id := String(copied_draft.get("operation_id", ""))
if not draft_operation_id.is_empty() and draft_operation_id != operation_id:
return {"ok": false, "code": "REWARD_CHOICE_DRAFT_OPERATION_MISMATCH"}
var result_draft_id := String(result.get("reward_draft_id", ""))
var draft_id := String(copied_draft.get("draft_id", ""))
if not result_draft_id.is_empty() and not draft_id.is_empty() and result_draft_id != draft_id:
return {"ok": false, "code": "REWARD_CHOICE_DRAFT_ID_MISMATCH"}
var raw_options = copied_draft.get("options", [])
if not raw_options is Array or raw_options.size() < 2:
return {"ok": false, "code": "REWARD_CHOICE_OPTIONS_REQUIRED"}
var option_ids := {}
var options: Array = []
for raw_option in raw_options:
if not raw_option is Dictionary:
return {"ok": false, "code": "REWARD_CHOICE_OPTION_INVALID"}
var option: Dictionary = Dictionary(raw_option).duplicate(true)
var reward_id := String(option.get("reward_id", ""))
if reward_id.is_empty():
return {"ok": false, "code": "REWARD_CHOICE_REWARD_ID_INVALID"}
if option_ids.has(reward_id):
return {"ok": false, "code": "REWARD_CHOICE_REWARD_ID_DUPLICATE"}
option_ids[reward_id] = true
options.append(option)
var selected_id := String(selected_reward_id)
if not selected_id.is_empty() and not option_ids.has(selected_id):
return {"ok": false, "code": "REWARD_CHOICE_SELECTION_INVALID"}
var selected_option: Dictionary = {}
for option in options:
if String(option.get("reward_id", "")) == selected_id:
selected_option = Dictionary(option).duplicate(true)
break
var receipt_state := _settlement_receipt(contract, battle, operation_id)
if not bool(receipt_state.get("ok", false)):
return receipt_state
var receipt: Dictionary = Dictionary(receipt_state.receipt).duplicate(true)
var copied_battle: Dictionary = battle.duplicate(true)
var copied_contract: Dictionary = {
"contract_id": String(contract.get("contract_id", "")),
"run_id": String(contract.get("run_id", "")),
"status": String(contract.get("status", "result")),
"difficulty": String(contract.get("difficulty", "standard")),
"coin": int(contract.get("coin", result.get("coin_after", 0))),
"line_capacity": int(contract.get("line_capacity", Array(battle.get("front_to_rear_ids", [])).size())),
"contract_hash": String(contract.get("contract_hash", "")),
}
return {
"ok": true,
"page_id": "reward_choice",
"locale": String(locale),
"company": {
"company_id": String(company.get("company_id", "")),
"owned_count": members.size(),
"line_capacity": int(contract.get("line_capacity", Array(battle.get("front_to_rear_ids", [])).size())),
"recruits": _copy_array(raw_recruits),
},
"contract": copied_contract,
"battle": {
"operation_id": operation_id,
"battle_hash": String(battle.get("battle_hash", "")),
"status": String(battle.get("status", "")),
"snapshot_id": String(battle.get("snapshot_id", "")),
"snapshot_hash": String(battle.get("snapshot_hash", "")),
"phase_index": int(battle.get("phase_index", 0)),
"total_phases": int(battle.get("total_phases", Array(battle.get("phase_events", [])).size())),
"front_to_rear_ids": _copy_array(battle.get("front_to_rear_ids", [])),
"enemy_ids": _copy_array(battle.get("enemy_ids", [])),
"copy": copied_battle,
},
"result": result,
"paid": {
"coin_before": int(result.get("coin_before", 0)),
"coin_after": int(result.get("coin_after", 0)),
"battle_reward_coin": int(result.get("reward_coin", 0)),
"xp_award": int(result.get("xp_award", 0)),
},
"reward": {
"draft": copied_draft,
"draft_id": draft_id,
"draft_hash": String(copied_draft.get("draft_hash", "")),
"status": status,
"options": options,
"options_count": options.size(),
"selected_reward_id": selected_id,
"selected_option": selected_option,
"selected": not selected_option.is_empty(),
},
"receipt": {
"settlement": receipt,
"operation_id": operation_id,
"receipt_hash": String(receipt.get("receipt_hash", "")),
"duplicate_safe": bool(receipt_state.duplicate_safe),
},
"notice": String(notice),
"continuation": {
"after_claim": "battle_recovery",
"after_skip": "battle_recovery",
"recovery_ready": true,
},
"commands": {
"back_enabled": true,
"select_enabled": true,
"claim_enabled": not selected_option.is_empty(),
"skip_enabled": true,
"recovery_enabled": false,
},
}
func _settlement_receipt(contract: Dictionary, battle: Dictionary, operation_id: String) -> Dictionary:
var matches: Array = []
var raw_receipts = contract.get("battle_settlement_receipts", [])
if raw_receipts is Array:
for raw_receipt in raw_receipts:
if raw_receipt is Dictionary and String(raw_receipt.get("operation_id", "")) == operation_id:
matches.append(Dictionary(raw_receipt).duplicate(true))
if matches.is_empty():
var battle_receipt = battle.get("settlement_receipt", {})
if battle_receipt is Dictionary and not Dictionary(battle_receipt).is_empty() and String(battle_receipt.get("operation_id", operation_id)) == operation_id:
matches.append(Dictionary(battle_receipt).duplicate(true))
if matches.is_empty():
return {"ok": false, "code": "REWARD_CHOICE_SETTLEMENT_RECEIPT_REQUIRED"}
if matches.size() > 1:
return {"ok": false, "code": "REWARD_CHOICE_SETTLEMENT_RECEIPT_DUPLICATE"}
var receipt: Dictionary = matches[0]
var battle_id := String(receipt.get("battle_operation_id", operation_id))
if not battle_id.is_empty() and battle_id != operation_id:
return {"ok": false, "code": "REWARD_CHOICE_RECEIPT_OPERATION_MISMATCH"}
return {"ok": true, "receipt": receipt, "duplicate_safe": true}
func _members_by_id(values) -> Dictionary:
var result := {}
if not values is Array:
return result
for value in values:
if value is Dictionary:
var copied: Dictionary = Dictionary(value).duplicate(true)
var recruit_id := String(copied.get("recruit_id", ""))
if not recruit_id.is_empty():
result[recruit_id] = copied
return result
func _copy_array(values) -> Array:
var result: Array = []
if not values is Array:
return result
for value in values:
result.append(value.duplicate(true) if value is Dictionary or value is Array else value)
return result