194 lines
7.6 KiB
GDScript
194 lines
7.6 KiB
GDScript
class_name ABGBattleOpeningPageController
|
|
extends RefCounted
|
|
|
|
## UI-independent BAT-001 Battle Opening view-data assembly.
|
|
##
|
|
## BattleService remains responsible for loading the active battle and starting
|
|
## observation. This controller only validates/copies the phase-zero facts and
|
|
## exposes opening-state command eligibility.
|
|
|
|
const VALID_STATES: Array[String] = ["ready_to_observe", "interrupted_resume", "opening_handoff", "snapshot_mismatch"]
|
|
|
|
|
|
func snapshot(
|
|
company: Dictionary,
|
|
battle: Dictionary,
|
|
opening_state := "ready_to_observe",
|
|
notice := "",
|
|
handoff_receipt: Dictionary = {},
|
|
commitment_receipt: Dictionary = {},
|
|
locale := "zh_CN"
|
|
) -> Dictionary:
|
|
if company.is_empty():
|
|
return {"ok": false, "code": "COMPANY_REQUIRED"}
|
|
var raw_contract = company.get("active_contract", {})
|
|
if not raw_contract is Dictionary or Dictionary(raw_contract).is_empty():
|
|
return {"ok": false, "code": "OPENING_CONTRACT_REQUIRED"}
|
|
if battle.is_empty():
|
|
return {"ok": false, "code": "OPENING_BATTLE_REQUIRED"}
|
|
if String(battle.get("status", "")) != "active":
|
|
return {"ok": false, "code": "OPENING_BATTLE_INVALID"}
|
|
if int(battle.get("phase_index", -1)) != 0 or not Array(battle.get("phase_events", [])).is_empty():
|
|
return {"ok": false, "code": "OPENING_PHASE_NOT_ZERO"}
|
|
if opening_state not in VALID_STATES:
|
|
return {"ok": false, "code": "OPENING_STATE_INVALID"}
|
|
|
|
var contract: Dictionary = Dictionary(raw_contract).duplicate(true)
|
|
var formation: Dictionary = Dictionary(contract.get("formation", {})).duplicate(true)
|
|
var raw_snapshot = formation.get("accepted_party_snapshot", {})
|
|
if not raw_snapshot is Dictionary or Dictionary(raw_snapshot).is_empty():
|
|
return {"ok": false, "code": "OPENING_PARTY_SNAPSHOT_REQUIRED"}
|
|
var party_snapshot: Dictionary = Dictionary(raw_snapshot).duplicate(true)
|
|
var members := _members_by_id(company.get("owned_recruits", []))
|
|
var party_ids = battle.get("front_to_rear_ids", party_snapshot.get("front_to_rear_ids", []))
|
|
if not party_ids is Array or party_ids.size() != int(contract.get("line_capacity", 4)):
|
|
return {"ok": false, "code": "OPENING_PARTY_ORDER_INVALID"}
|
|
var order_validation := _validate_ids(party_ids, members)
|
|
if not order_validation.ok:
|
|
return order_validation
|
|
var enemy_ids = battle.get("enemy_ids", [])
|
|
if not enemy_ids is Array or enemy_ids.is_empty():
|
|
return {"ok": false, "code": "OPENING_ENEMY_LINE_INVALID"}
|
|
|
|
var operation_id := String(battle.get("operation_id", ""))
|
|
if operation_id.is_empty():
|
|
return {"ok": false, "code": "OPENING_OPERATION_REQUIRED"}
|
|
var copied_relations: Array = _copy_array(party_snapshot.get("relations", formation.get("relations", [])))
|
|
var relation_by_id := _relations_by_id(copied_relations)
|
|
var ordered_recruits: Array[Dictionary] = []
|
|
for position in range(order_validation.ids.size()):
|
|
var recruit_id := String(order_validation.ids[position])
|
|
ordered_recruits.append({
|
|
"position": position,
|
|
"recruit_id": recruit_id,
|
|
"recruit": Dictionary(members[recruit_id]).duplicate(true),
|
|
"relation": Dictionary(relation_by_id.get(recruit_id, {})).duplicate(true),
|
|
})
|
|
|
|
var resolved_receipt: Dictionary = Dictionary(commitment_receipt).duplicate(true)
|
|
if resolved_receipt.is_empty():
|
|
resolved_receipt = _receipt_for_operation(contract.get("battle_receipts", []), operation_id)
|
|
var copied_battle: Dictionary = battle.duplicate(true)
|
|
var copied_handoff: Dictionary = Dictionary(handoff_receipt).duplicate(true)
|
|
var copied_enemy_ids: Array = _copy_array(enemy_ids)
|
|
var handoff := opening_state == "opening_handoff"
|
|
var mismatch := opening_state == "snapshot_mismatch"
|
|
var interrupted := opening_state == "interrupted_resume"
|
|
var can_start := not handoff and not mismatch and not resolved_receipt.is_empty()
|
|
return {
|
|
"ok": true,
|
|
"page_id": "battle_opening",
|
|
"locale": String(locale),
|
|
"company": {
|
|
"company_id": String(company.get("company_id", "")),
|
|
"owned_count": members.size(),
|
|
"line_capacity": int(contract.get("line_capacity", order_validation.ids.size())),
|
|
},
|
|
"contract": {
|
|
"contract_id": String(contract.get("contract_id", "")),
|
|
"run_id": String(contract.get("run_id", "")),
|
|
"contract_hash": String(contract.get("contract_hash", "")),
|
|
},
|
|
"battle": {
|
|
"operation_id": operation_id,
|
|
"battle_hash": String(battle.get("battle_hash", "")),
|
|
"status": String(battle.get("status", "active")),
|
|
"risk_option_id": String(battle.get("risk_option_id", "")),
|
|
"risk": int(battle.get("risk", 0)),
|
|
"snapshot_id": String(battle.get("snapshot_id", party_snapshot.get("snapshot_id", ""))),
|
|
"snapshot_hash": String(battle.get("snapshot_hash", party_snapshot.get("snapshot_hash", ""))),
|
|
"phase_index": int(battle.get("phase_index", 0)),
|
|
"total_phases": int(battle.get("total_phases", 0)),
|
|
"front_to_rear_ids": order_validation.ids.duplicate(true),
|
|
"enemy_ids": copied_enemy_ids,
|
|
"phase_events": _copy_array(battle.get("phase_events", [])),
|
|
"copy": copied_battle,
|
|
},
|
|
"receipt": {
|
|
"present": not resolved_receipt.is_empty(),
|
|
"battle": resolved_receipt,
|
|
"handoff": copied_handoff,
|
|
},
|
|
"party": {
|
|
"snapshot_id": String(party_snapshot.get("snapshot_id", "")),
|
|
"snapshot_hash": String(party_snapshot.get("snapshot_hash", "")),
|
|
"front_to_rear_ids": order_validation.ids.duplicate(true),
|
|
"ordered_recruits": ordered_recruits,
|
|
"orientation": String(party_snapshot.get("orientation", formation.get("orientation", "rear_left_front_right"))),
|
|
"front_position": int(party_snapshot.get("front_position", formation.get("front_position", 0))),
|
|
"relations": copied_relations,
|
|
"coverage": Dictionary(party_snapshot.get("coverage", formation.get("coverage", {}))).duplicate(true),
|
|
"first_threat": Dictionary(formation.get("first_threat", {})).duplicate(true),
|
|
},
|
|
"enemies": {
|
|
"ids": copied_enemy_ids,
|
|
"count": copied_enemy_ids.size(),
|
|
},
|
|
"state": {
|
|
"name": String(opening_state),
|
|
"notice": String(notice),
|
|
"phase_zero": true,
|
|
"interrupted_resume": interrupted,
|
|
"handoff": handoff,
|
|
"snapshot_mismatch": mismatch,
|
|
},
|
|
"commands": {
|
|
"pause_enabled": true,
|
|
"speed_enabled": true,
|
|
"inspect_enabled": true,
|
|
"settings_enabled": true,
|
|
"start_observation_enabled": can_start,
|
|
},
|
|
}
|
|
|
|
|
|
func _validate_ids(values: Array, members: Dictionary) -> Dictionary:
|
|
var ids: Array = []
|
|
for value in values:
|
|
var recruit_id := String(value)
|
|
if not members.has(recruit_id):
|
|
return {"ok": false, "code": "OPENING_RECRUIT_UNKNOWN"}
|
|
if recruit_id in ids:
|
|
return {"ok": false, "code": "OPENING_RECRUIT_DUPLICATE"}
|
|
ids.append(recruit_id)
|
|
return {"ok": true, "code": "OPENING_PARTY_VALID", "ids": ids}
|
|
|
|
|
|
func _receipt_for_operation(values, operation_id: String) -> Dictionary:
|
|
if not values is Array:
|
|
return {}
|
|
for value in values:
|
|
if value is Dictionary and String(value.get("operation_id", "")) == operation_id:
|
|
return Dictionary(value).duplicate(true)
|
|
return {}
|
|
|
|
|
|
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 _relations_by_id(values: Array) -> Dictionary:
|
|
var result := {}
|
|
for value in values:
|
|
if value is Dictionary:
|
|
result[String(value.get("recruit_id", ""))] = Dictionary(value).duplicate(true)
|
|
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
|