570 lines
26 KiB
GDScript
570 lines
26 KiB
GDScript
class_name P910ContractState
|
|
extends RefCounted
|
|
|
|
const Content = preload("res://content/p9_10_content.gd")
|
|
const P99State = preload("res://p9_9/run_state.gd")
|
|
|
|
enum Phase { MARKET, ENCOUNTER, BOSS_MUSTER, BOSS_ENCOUNTER, CONTRACT_RESULT, RUN_CLOSED }
|
|
|
|
const SECOND_CLEAR_ID := "CLEAR-RG01-C02"
|
|
const SECOND_RENOWN_RECEIPT_ID := "P910-RENOWN-RG01-C02"
|
|
const SECOND_RECORD_ID := "RECORD-RG01-C02"
|
|
const SECOND_RENOWN_REWARD := 18
|
|
|
|
var state_kind := "p9_10_contract"
|
|
var revision := 0
|
|
var p99_snapshot: Dictionary = {}
|
|
var selected_charter_claim_id := ""
|
|
var content_revision := ""
|
|
var content_hash := ""
|
|
var phase := Phase.MARKET
|
|
var coin := 0
|
|
var cleared_rounds := 3
|
|
var line_capacity := 5
|
|
var current_market := 4
|
|
var company: Array[String] = []
|
|
var deployed_recruit_ids: Array[String] = []
|
|
var recruit_records: Dictionary = {}
|
|
var member_condition: Dictionary = {}
|
|
var inventory: Dictionary = {}
|
|
var equipped_by_recruit: Dictionary = {}
|
|
var market_board: Dictionary = {}
|
|
var available_recruit_offer_ids: Array[String] = []
|
|
var available_equipment_offer_ids: Array[String] = []
|
|
var available_battle_offer_ids: Array[String] = []
|
|
var selected_route := ""
|
|
var route_history: Array[String] = []
|
|
var hire_receipts: Array[Dictionary] = []
|
|
var equipment_receipts: Array[Dictionary] = []
|
|
var formation_receipts: Array[Dictionary] = []
|
|
var reorder_receipts: Array[Dictionary] = []
|
|
var route_receipts: Array[Dictionary] = []
|
|
var battle_settlement_receipts: Array[Dictionary] = []
|
|
var recovery_receipts: Array[Dictionary] = []
|
|
var operation_receipts: Dictionary = {}
|
|
var battle_checkpoint: Dictionary = {}
|
|
var recovery_required := false
|
|
var recovery_kind := ""
|
|
var recovery_cost := 0
|
|
var injured_recruit_ids: Array[String] = []
|
|
var retry_route := ""
|
|
var proposed_final_order: Array[String] = []
|
|
var final_order_confirmed := false
|
|
var boss_muster: Dictionary = {}
|
|
var selected_boss_plan := ""
|
|
var boss_plan_receipts: Array[Dictionary] = []
|
|
var pending_close: Dictionary = {}
|
|
var boss_result_receipts: Array[Dictionary] = []
|
|
var persistent_renown := 0
|
|
var contract_clear_ids: Array[String] = []
|
|
var discovery_ids: Array[String] = []
|
|
var company_records: Array[Dictionary] = []
|
|
var unlocked_region_ids: Array[String] = []
|
|
var renown_receipts: Array[Dictionary] = []
|
|
var close_receipts: Array[Dictionary] = []
|
|
var run_closed := false
|
|
|
|
|
|
func initialize_from_p99(raw: Dictionary) -> bool:
|
|
var prior = P99State.new()
|
|
if not prior.restore_snapshot(raw):
|
|
return false
|
|
if not prior.run_created or prior.phase != P99State.Phase.MARKET or prior.current_market != 4 or prior.cleared_rounds != 3 or not prior.continuation_locked:
|
|
return false
|
|
if prior.company.size() != 4 or prior.deployed_recruit_ids.size() != 4 or prior.recovery_required:
|
|
return false
|
|
var next_content: Dictionary = Content.snapshot(prior.selected_charter_claim_id)
|
|
if not Content.validate(next_content).ok:
|
|
return false
|
|
_reset()
|
|
p99_snapshot = prior.snapshot()
|
|
selected_charter_claim_id = prior.selected_charter_claim_id
|
|
content_revision = String(next_content.revision)
|
|
content_hash = String(next_content.content_hash)
|
|
coin = prior.coin
|
|
company.assign(prior.company)
|
|
deployed_recruit_ids.assign(prior.deployed_recruit_ids)
|
|
recruit_records = prior.recruit_records.duplicate(true)
|
|
member_condition = prior.member_condition.duplicate(true)
|
|
inventory = prior.inventory.duplicate(true)
|
|
equipped_by_recruit = prior.equipped_by_recruit.duplicate(true)
|
|
route_history.assign(prior.route_history)
|
|
persistent_renown = int(prior.profile.persistent_renown)
|
|
contract_clear_ids.assign(prior.profile.contract_clear_ids)
|
|
discovery_ids.assign(prior.profile.discovery_ids)
|
|
company_records.assign(prior.profile.company_records)
|
|
unlocked_region_ids.assign(["RG-01"])
|
|
_open_market(next_content, 4)
|
|
return invariants_hold()
|
|
|
|
|
|
func snapshot() -> Dictionary:
|
|
return {
|
|
"state_kind": state_kind,
|
|
"revision": revision,
|
|
"p99_snapshot": p99_snapshot.duplicate(true),
|
|
"selected_charter_claim_id": selected_charter_claim_id,
|
|
"content_revision": content_revision,
|
|
"content_hash": content_hash,
|
|
"phase": phase,
|
|
"coin": coin,
|
|
"cleared_rounds": cleared_rounds,
|
|
"line_capacity": line_capacity,
|
|
"current_market": current_market,
|
|
"company": company.duplicate(),
|
|
"deployed_recruit_ids": deployed_recruit_ids.duplicate(),
|
|
"recruit_records": recruit_records.duplicate(true),
|
|
"member_condition": member_condition.duplicate(true),
|
|
"inventory": inventory.duplicate(true),
|
|
"equipped_by_recruit": equipped_by_recruit.duplicate(true),
|
|
"market_board": market_board.duplicate(true),
|
|
"available_recruit_offer_ids": available_recruit_offer_ids.duplicate(),
|
|
"available_equipment_offer_ids": available_equipment_offer_ids.duplicate(),
|
|
"available_battle_offer_ids": available_battle_offer_ids.duplicate(),
|
|
"selected_route": selected_route,
|
|
"route_history": route_history.duplicate(),
|
|
"hire_receipts": hire_receipts.duplicate(true),
|
|
"equipment_receipts": equipment_receipts.duplicate(true),
|
|
"formation_receipts": formation_receipts.duplicate(true),
|
|
"reorder_receipts": reorder_receipts.duplicate(true),
|
|
"route_receipts": route_receipts.duplicate(true),
|
|
"battle_settlement_receipts": battle_settlement_receipts.duplicate(true),
|
|
"recovery_receipts": recovery_receipts.duplicate(true),
|
|
"operation_receipts": operation_receipts.duplicate(true),
|
|
"battle_checkpoint": battle_checkpoint.duplicate(true),
|
|
"recovery_required": recovery_required,
|
|
"recovery_kind": recovery_kind,
|
|
"recovery_cost": recovery_cost,
|
|
"injured_recruit_ids": injured_recruit_ids.duplicate(),
|
|
"retry_route": retry_route,
|
|
"proposed_final_order": proposed_final_order.duplicate(),
|
|
"final_order_confirmed": final_order_confirmed,
|
|
"boss_muster": boss_muster.duplicate(true),
|
|
"selected_boss_plan": selected_boss_plan,
|
|
"boss_plan_receipts": boss_plan_receipts.duplicate(true),
|
|
"pending_close": pending_close.duplicate(true),
|
|
"boss_result_receipts": boss_result_receipts.duplicate(true),
|
|
"persistent_renown": persistent_renown,
|
|
"contract_clear_ids": contract_clear_ids.duplicate(),
|
|
"discovery_ids": discovery_ids.duplicate(),
|
|
"company_records": company_records.duplicate(true),
|
|
"unlocked_region_ids": unlocked_region_ids.duplicate(),
|
|
"renown_receipts": renown_receipts.duplicate(true),
|
|
"close_receipts": close_receipts.duplicate(true),
|
|
"run_closed": run_closed,
|
|
}.duplicate(true)
|
|
|
|
|
|
func restore_snapshot(raw: Dictionary) -> bool:
|
|
var normalized_value = JSON.parse_string(JSON.stringify(raw))
|
|
if not normalized_value is Dictionary:
|
|
return false
|
|
var normalized: Dictionary = _normalize_serialized(normalized_value)
|
|
var required := [
|
|
"state_kind", "revision", "p99_snapshot", "selected_charter_claim_id",
|
|
"content_revision", "content_hash", "phase", "coin", "cleared_rounds",
|
|
"line_capacity", "current_market", "company", "deployed_recruit_ids",
|
|
"recruit_records", "member_condition", "inventory", "equipped_by_recruit",
|
|
"market_board", "available_recruit_offer_ids",
|
|
"available_equipment_offer_ids", "available_battle_offer_ids",
|
|
"selected_route", "route_history", "hire_receipts", "equipment_receipts",
|
|
"formation_receipts", "reorder_receipts", "route_receipts",
|
|
"battle_settlement_receipts", "recovery_receipts", "operation_receipts",
|
|
"battle_checkpoint", "recovery_required", "recovery_kind", "recovery_cost",
|
|
"injured_recruit_ids", "retry_route", "proposed_final_order",
|
|
"final_order_confirmed", "boss_muster", "selected_boss_plan",
|
|
"boss_plan_receipts", "pending_close", "boss_result_receipts",
|
|
"persistent_renown", "contract_clear_ids", "discovery_ids",
|
|
"company_records", "unlocked_region_ids", "renown_receipts",
|
|
"close_receipts", "run_closed",
|
|
]
|
|
for key in required:
|
|
if not normalized.has(key):
|
|
return false
|
|
var before := snapshot()
|
|
if not _assign(normalized) or not invariants_hold():
|
|
_assign(before)
|
|
return false
|
|
return true
|
|
|
|
|
|
func invariants_hold() -> bool:
|
|
if state_kind != "p9_10_contract" or revision < 0 or phase < Phase.MARKET or phase > Phase.RUN_CLOSED:
|
|
return false
|
|
var prior = P99State.new()
|
|
if not prior.restore_snapshot(p99_snapshot) or prior.current_market != 4 or not prior.continuation_locked:
|
|
return false
|
|
if prior.selected_charter_claim_id != selected_charter_claim_id:
|
|
return false
|
|
var current_content: Dictionary = Content.snapshot(selected_charter_claim_id)
|
|
if not Content.validate(current_content).ok or content_revision != String(current_content.revision) or content_hash != String(current_content.content_hash):
|
|
return false
|
|
if coin < 0 or cleared_rounds < 3 or cleared_rounds > 12 or line_capacity != capacity_for_clears(cleared_rounds):
|
|
return false
|
|
var base_profile: Dictionary = prior.profile
|
|
if not _persistent_fields_hold(base_profile):
|
|
return false
|
|
if run_closed:
|
|
return _closed_state_holds()
|
|
if company.size() < 4 or company.size() > line_capacity or not _unique(company):
|
|
return false
|
|
if not _same_members(company, deployed_recruit_ids):
|
|
return false
|
|
for recruit_id in company:
|
|
if not recruit_records.has(recruit_id) or not member_condition.has(recruit_id) or not equipped_by_recruit.has(recruit_id):
|
|
return false
|
|
var profession_id := String(recruit_records[recruit_id].get("profession_id", ""))
|
|
var maximum := int(current_content.professions.get(profession_id, {}).get("max_hp", 0))
|
|
if maximum <= 0 or int(member_condition[recruit_id]) < 0 or int(member_condition[recruit_id]) > maximum:
|
|
return false
|
|
for recruit_id in recruit_records:
|
|
if recruit_id not in company:
|
|
return false
|
|
var equipped_instances := {}
|
|
for recruit_id in equipped_by_recruit:
|
|
var instance_id := String(equipped_by_recruit[recruit_id])
|
|
if instance_id.is_empty():
|
|
continue
|
|
if not inventory.has(instance_id) or equipped_instances.has(instance_id):
|
|
return false
|
|
equipped_instances[instance_id] = true
|
|
for instance_id in inventory:
|
|
var row = inventory[instance_id]
|
|
if not row is Dictionary or String(row.get("instance_id", "")) != instance_id or String(row.get("catalog_id", "")) not in current_content.equipment:
|
|
return false
|
|
if phase in [Phase.BOSS_MUSTER, Phase.BOSS_ENCOUNTER, Phase.CONTRACT_RESULT]:
|
|
if cleared_rounds != 12 or current_market != 13 or not market_board.is_empty() or not selected_route.is_empty():
|
|
return false
|
|
if not available_recruit_offer_ids.is_empty() or not available_equipment_offer_ids.is_empty() or not available_battle_offer_ids.is_empty():
|
|
return false
|
|
if String(boss_muster.get("boss_id", "")) != Content.BOSS_ID or String(boss_muster.get("muster_hash", "")).length() != 64:
|
|
return false
|
|
var muster_authority := boss_muster.duplicate(true)
|
|
var muster_hash := String(muster_authority.muster_hash)
|
|
muster_authority.erase("muster_hash")
|
|
if muster_hash != JSON.stringify(muster_authority).sha256_text():
|
|
return false
|
|
if boss_muster.get("company_ids", []) != deployed_recruit_ids or boss_muster.get("route_history", []) != route_history:
|
|
return false
|
|
if boss_muster.get("equipment", {}) != equipped_by_recruit or not boss_muster.get("member_hp", {}) is Dictionary:
|
|
return false
|
|
if not _same_members(Array(boss_muster.member_hp.keys()), company):
|
|
return false
|
|
var plan_ids: Array = Array(boss_muster.get("plans", [])).map(func(row): return String(row.get("id", "")))
|
|
if plan_ids != ["break_spindle", "hold_line"]:
|
|
return false
|
|
if boss_muster.get("enemy_helper_footprints", []) != [1, 2] or int(boss_muster.get("core_timeout_tick", 0)) != 1800:
|
|
return false
|
|
if int(boss_muster.get("half_vitality_threshold", 0)) != 880 or not bool(boss_muster.get("half_vitality_opposed_gusts", false)):
|
|
return false
|
|
if String(boss_muster.get("foremost_recruit_id", "")) != deployed_recruit_ids[0] or String(boss_muster.get("rearmost_recruit_id", "")) != deployed_recruit_ids[-1]:
|
|
return false
|
|
if phase == Phase.BOSS_MUSTER and (not selected_boss_plan.is_empty() or not pending_close.is_empty() or not battle_checkpoint.is_empty()):
|
|
return false
|
|
if phase == Phase.BOSS_ENCOUNTER and (selected_boss_plan not in ["break_spindle", "hold_line"] or pending_close.size() > 0 or battle_checkpoint.is_empty()):
|
|
return false
|
|
if phase == Phase.CONTRACT_RESULT and (selected_boss_plan not in ["break_spindle", "hold_line"] or pending_close.is_empty() or not battle_checkpoint.is_empty()):
|
|
return false
|
|
if phase == Phase.CONTRACT_RESULT:
|
|
if String(pending_close.get("clear_id", "")) != SECOND_CLEAR_ID or String(pending_close.get("boss_id", "")) != Content.BOSS_ID:
|
|
return false
|
|
if String(pending_close.get("muster_hash", "")) != String(boss_muster.muster_hash) or String(pending_close.get("plan", "")) != selected_boss_plan:
|
|
return false
|
|
if String(pending_close.get("renown_receipt_id", "")) != SECOND_RENOWN_RECEIPT_ID or int(pending_close.get("renown", 0)) != SECOND_RENOWN_REWARD:
|
|
return false
|
|
else:
|
|
if current_market != cleared_rounds + 1 or current_market < 4 or current_market > 12 or not boss_muster.is_empty():
|
|
return false
|
|
if not selected_boss_plan.is_empty() or not pending_close.is_empty():
|
|
return false
|
|
if market_board != current_content.boards[current_market]:
|
|
return false
|
|
if not _subset(available_recruit_offer_ids, market_board.recruit_offer_ids) or not _subset(available_equipment_offer_ids, market_board.equipment_offer_ids) or not _subset(available_battle_offer_ids, market_board.battle_offer_ids):
|
|
return false
|
|
if phase == Phase.ENCOUNTER and (selected_route.is_empty() or battle_checkpoint.is_empty() or recovery_required):
|
|
return false
|
|
if phase == Phase.MARKET and (not selected_route.is_empty() or not battle_checkpoint.is_empty()):
|
|
return false
|
|
if not selected_route.is_empty() and selected_route not in market_board.get("battle_offer_ids", []):
|
|
return false
|
|
if recovery_required:
|
|
if recovery_kind == "defeat":
|
|
if phase != Phase.MARKET or recovery_cost != 6 or injured_recruit_ids.is_empty() or retry_route.is_empty():
|
|
return false
|
|
for recruit_id in injured_recruit_ids:
|
|
if recruit_id not in company or int(member_condition.get(recruit_id, 1)) > 0:
|
|
return false
|
|
elif recovery_kind == "boss_defeat":
|
|
if phase != Phase.BOSS_MUSTER or recovery_cost != 16 or not retry_route.is_empty():
|
|
return false
|
|
var checkpoint_hp: Dictionary = boss_muster.get("member_hp", {})
|
|
for recruit_id in injured_recruit_ids:
|
|
if recruit_id not in company or int(checkpoint_hp.get(recruit_id, 0)) <= 0:
|
|
return false
|
|
else:
|
|
return false
|
|
else:
|
|
if not recovery_kind.is_empty() or recovery_cost != 0 or not injured_recruit_ids.is_empty() or not retry_route.is_empty():
|
|
return false
|
|
if not proposed_final_order.is_empty() and (current_market not in [12, 13] or not _same_members(proposed_final_order, company)):
|
|
return false
|
|
if final_order_confirmed and (current_market < 12 or proposed_final_order != deployed_recruit_ids):
|
|
return false
|
|
for receipts in [hire_receipts, equipment_receipts, formation_receipts, reorder_receipts, route_receipts, battle_settlement_receipts, recovery_receipts, boss_plan_receipts, boss_result_receipts, renown_receipts, close_receipts]:
|
|
if not _receipt_indices_hold(receipts):
|
|
return false
|
|
for operation_id in operation_receipts:
|
|
var operation = operation_receipts[operation_id]
|
|
if String(operation_id).is_empty() or not operation is Dictionary or String(operation.get("intent_hash", "")).length() != 64 or not operation.get("receipt", {}) is Dictionary:
|
|
return false
|
|
return true
|
|
|
|
|
|
static func capacity_for_clears(clears: int) -> int:
|
|
return mini(10, 4 + int(floor(float(clears) / 2.0)))
|
|
|
|
|
|
func _open_market(current_content: Dictionary, market: int) -> void:
|
|
phase = Phase.MARKET
|
|
current_market = market
|
|
market_board = Dictionary(current_content.boards[market]).duplicate(true)
|
|
available_recruit_offer_ids.assign(market_board.recruit_offer_ids)
|
|
available_equipment_offer_ids.assign(market_board.equipment_offer_ids)
|
|
available_battle_offer_ids.assign(market_board.battle_offer_ids)
|
|
selected_route = ""
|
|
battle_checkpoint.clear()
|
|
boss_muster.clear()
|
|
|
|
|
|
func _persistent_fields_hold(base_profile: Dictionary) -> bool:
|
|
for key in ["persistent_renown", "contract_clear_ids", "discovery_ids", "company_records"]:
|
|
if not base_profile.has(key):
|
|
return false
|
|
var base_clears: Array = base_profile.contract_clear_ids
|
|
var base_discoveries: Array = base_profile.discovery_ids
|
|
var base_records: Array = base_profile.company_records
|
|
if run_closed:
|
|
var expected_discoveries: Array = base_discoveries.duplicate()
|
|
for discovery_id in [SECOND_RENOWN_RECEIPT_ID, "BOSS-%s" % Content.BOSS_ID, "REGION-RG-02"]:
|
|
if discovery_id not in expected_discoveries:
|
|
expected_discoveries.append(discovery_id)
|
|
if persistent_renown != int(base_profile.persistent_renown) + SECOND_RENOWN_REWARD:
|
|
return false
|
|
if contract_clear_ids != base_clears + [SECOND_CLEAR_ID]:
|
|
return false
|
|
if discovery_ids != expected_discoveries or not _unique(discovery_ids):
|
|
return false
|
|
if company_records.size() != base_records.size() + 1 or company_records.slice(0, base_records.size()) != base_records:
|
|
return false
|
|
var record: Dictionary = company_records[-1]
|
|
if String(record.get("id", "")) != SECOND_RECORD_ID or String(record.get("clear_id", "")) != SECOND_CLEAR_ID:
|
|
return false
|
|
if String(record.get("boss_id", "")) != Content.BOSS_ID or String(record.get("boss_plan", "")) not in ["break_spindle", "hold_line"]:
|
|
return false
|
|
if Array(record.get("round_settlements", [])).size() != 12 or Array(record.get("recorded_route_ids", [])).size() != 12:
|
|
return false
|
|
if String(record.get("carried_charter_claim_id", "")) != selected_charter_claim_id or Array(record.get("company", [])).is_empty():
|
|
return false
|
|
if unlocked_region_ids != ["RG-01", "RG-02"]:
|
|
return false
|
|
if renown_receipts.size() != 1 or String(renown_receipts[0].get("id", "")) != SECOND_RENOWN_RECEIPT_ID or int(renown_receipts[0].get("renown_delta", 0)) != SECOND_RENOWN_REWARD:
|
|
return false
|
|
if close_receipts.size() != 1 or String(close_receipts[0].get("clear_id", "")) != SECOND_CLEAR_ID or String(close_receipts[0].get("unlocked_region_id", "")) != "RG-02":
|
|
return false
|
|
return true
|
|
return persistent_renown == int(base_profile.persistent_renown) \
|
|
and contract_clear_ids == base_clears \
|
|
and discovery_ids == base_discoveries \
|
|
and company_records == base_records \
|
|
and unlocked_region_ids == ["RG-01"] \
|
|
and renown_receipts.is_empty() \
|
|
and close_receipts.is_empty()
|
|
|
|
|
|
func _closed_state_holds() -> bool:
|
|
if phase != Phase.RUN_CLOSED or not run_closed or coin != 0 or cleared_rounds != 12 or line_capacity != 10 or current_market != 13:
|
|
return false
|
|
if not company.is_empty() or not deployed_recruit_ids.is_empty() or not recruit_records.is_empty() or not member_condition.is_empty():
|
|
return false
|
|
if not inventory.is_empty() or not equipped_by_recruit.is_empty() or not market_board.is_empty() or not boss_muster.is_empty():
|
|
return false
|
|
if not available_recruit_offer_ids.is_empty() or not available_equipment_offer_ids.is_empty() or not available_battle_offer_ids.is_empty():
|
|
return false
|
|
if not selected_route.is_empty() or not route_history.is_empty() or not battle_checkpoint.is_empty() or not pending_close.is_empty():
|
|
return false
|
|
if recovery_required or not recovery_kind.is_empty() or recovery_cost != 0 or not injured_recruit_ids.is_empty() or not retry_route.is_empty():
|
|
return false
|
|
if not proposed_final_order.is_empty() or final_order_confirmed or not selected_boss_plan.is_empty():
|
|
return false
|
|
for receipts in [hire_receipts, equipment_receipts, formation_receipts, reorder_receipts, route_receipts, battle_settlement_receipts, recovery_receipts, boss_plan_receipts, boss_result_receipts]:
|
|
if not receipts.is_empty():
|
|
return false
|
|
if operation_receipts.size() != 1:
|
|
return false
|
|
for operation_id in operation_receipts:
|
|
var operation = operation_receipts[operation_id]
|
|
if String(operation_id).is_empty() or not operation is Dictionary or String(operation.get("kind", "")) != "close_contract":
|
|
return false
|
|
if String(operation.get("intent_hash", "")).length() != 64 or not operation.get("receipt", {}) is Dictionary:
|
|
return false
|
|
return true
|
|
|
|
|
|
func _assign(raw: Dictionary) -> bool:
|
|
for key in ["company", "deployed_recruit_ids", "available_recruit_offer_ids", "available_equipment_offer_ids", "available_battle_offer_ids", "route_history", "hire_receipts", "equipment_receipts", "formation_receipts", "reorder_receipts", "route_receipts", "battle_settlement_receipts", "recovery_receipts", "injured_recruit_ids", "proposed_final_order", "boss_plan_receipts", "boss_result_receipts", "contract_clear_ids", "discovery_ids", "company_records", "unlocked_region_ids", "renown_receipts", "close_receipts"]:
|
|
if not raw[key] is Array:
|
|
return false
|
|
for key in ["p99_snapshot", "recruit_records", "member_condition", "inventory", "equipped_by_recruit", "market_board", "operation_receipts", "battle_checkpoint", "boss_muster", "pending_close"]:
|
|
if not raw[key] is Dictionary:
|
|
return false
|
|
state_kind = String(raw.state_kind)
|
|
revision = int(raw.revision)
|
|
p99_snapshot = Dictionary(raw.p99_snapshot).duplicate(true)
|
|
selected_charter_claim_id = String(raw.selected_charter_claim_id)
|
|
content_revision = String(raw.content_revision)
|
|
content_hash = String(raw.content_hash)
|
|
phase = int(raw.phase)
|
|
coin = int(raw.coin)
|
|
cleared_rounds = int(raw.cleared_rounds)
|
|
line_capacity = int(raw.line_capacity)
|
|
current_market = int(raw.current_market)
|
|
company.assign(raw.company)
|
|
deployed_recruit_ids.assign(raw.deployed_recruit_ids)
|
|
recruit_records = Dictionary(raw.recruit_records).duplicate(true)
|
|
member_condition = Dictionary(raw.member_condition).duplicate(true)
|
|
inventory = Dictionary(raw.inventory).duplicate(true)
|
|
equipped_by_recruit = Dictionary(raw.equipped_by_recruit).duplicate(true)
|
|
market_board = Dictionary(raw.market_board).duplicate(true)
|
|
available_recruit_offer_ids.assign(raw.available_recruit_offer_ids)
|
|
available_equipment_offer_ids.assign(raw.available_equipment_offer_ids)
|
|
available_battle_offer_ids.assign(raw.available_battle_offer_ids)
|
|
selected_route = String(raw.selected_route)
|
|
route_history.assign(raw.route_history)
|
|
hire_receipts.assign(raw.hire_receipts)
|
|
equipment_receipts.assign(raw.equipment_receipts)
|
|
formation_receipts.assign(raw.formation_receipts)
|
|
reorder_receipts.assign(raw.reorder_receipts)
|
|
route_receipts.assign(raw.route_receipts)
|
|
battle_settlement_receipts.assign(raw.battle_settlement_receipts)
|
|
recovery_receipts.assign(raw.recovery_receipts)
|
|
operation_receipts = Dictionary(raw.operation_receipts).duplicate(true)
|
|
battle_checkpoint = Dictionary(raw.battle_checkpoint).duplicate(true)
|
|
recovery_required = bool(raw.recovery_required)
|
|
recovery_kind = String(raw.recovery_kind)
|
|
recovery_cost = int(raw.recovery_cost)
|
|
injured_recruit_ids.assign(raw.injured_recruit_ids)
|
|
retry_route = String(raw.retry_route)
|
|
proposed_final_order.assign(raw.proposed_final_order)
|
|
final_order_confirmed = bool(raw.final_order_confirmed)
|
|
boss_muster = Dictionary(raw.boss_muster).duplicate(true)
|
|
selected_boss_plan = String(raw.selected_boss_plan)
|
|
boss_plan_receipts.assign(raw.boss_plan_receipts)
|
|
pending_close = Dictionary(raw.pending_close).duplicate(true)
|
|
boss_result_receipts.assign(raw.boss_result_receipts)
|
|
persistent_renown = int(raw.persistent_renown)
|
|
contract_clear_ids.assign(raw.contract_clear_ids)
|
|
discovery_ids.assign(raw.discovery_ids)
|
|
company_records.assign(raw.company_records)
|
|
unlocked_region_ids.assign(raw.unlocked_region_ids)
|
|
renown_receipts.assign(raw.renown_receipts)
|
|
close_receipts.assign(raw.close_receipts)
|
|
run_closed = bool(raw.run_closed)
|
|
return true
|
|
|
|
|
|
func _reset() -> void:
|
|
state_kind = "p9_10_contract"
|
|
revision = 0
|
|
p99_snapshot.clear()
|
|
selected_charter_claim_id = ""
|
|
content_revision = ""
|
|
content_hash = ""
|
|
phase = Phase.MARKET
|
|
coin = 0
|
|
cleared_rounds = 3
|
|
line_capacity = 5
|
|
current_market = 4
|
|
company.clear()
|
|
deployed_recruit_ids.clear()
|
|
recruit_records.clear()
|
|
member_condition.clear()
|
|
inventory.clear()
|
|
equipped_by_recruit.clear()
|
|
market_board.clear()
|
|
available_recruit_offer_ids.clear()
|
|
available_equipment_offer_ids.clear()
|
|
available_battle_offer_ids.clear()
|
|
selected_route = ""
|
|
route_history.clear()
|
|
hire_receipts.clear()
|
|
equipment_receipts.clear()
|
|
formation_receipts.clear()
|
|
reorder_receipts.clear()
|
|
route_receipts.clear()
|
|
battle_settlement_receipts.clear()
|
|
recovery_receipts.clear()
|
|
operation_receipts.clear()
|
|
battle_checkpoint.clear()
|
|
recovery_required = false
|
|
recovery_kind = ""
|
|
recovery_cost = 0
|
|
injured_recruit_ids.clear()
|
|
retry_route = ""
|
|
proposed_final_order.clear()
|
|
final_order_confirmed = false
|
|
boss_muster.clear()
|
|
selected_boss_plan = ""
|
|
boss_plan_receipts.clear()
|
|
pending_close.clear()
|
|
boss_result_receipts.clear()
|
|
persistent_renown = 0
|
|
contract_clear_ids.clear()
|
|
discovery_ids.clear()
|
|
company_records.clear()
|
|
unlocked_region_ids.clear()
|
|
renown_receipts.clear()
|
|
close_receipts.clear()
|
|
run_closed = false
|
|
|
|
|
|
func _normalize_serialized(value: Variant) -> Variant:
|
|
if value is float:
|
|
return int(value) if is_finite(value) and value == floor(value) else value
|
|
if value is Array:
|
|
var result: Array = []
|
|
for item in value:
|
|
result.append(_normalize_serialized(item))
|
|
return result
|
|
if value is Dictionary:
|
|
var result := {}
|
|
for key in value:
|
|
result[key] = _normalize_serialized(value[key])
|
|
return result
|
|
return value
|
|
|
|
|
|
func _unique(values: Array) -> bool:
|
|
var seen := {}
|
|
for value in values:
|
|
if String(value).is_empty() or seen.has(value):
|
|
return false
|
|
seen[value] = true
|
|
return true
|
|
|
|
|
|
func _subset(values: Array, authority: Array) -> bool:
|
|
return _unique(values) and values.all(func(value): return value in authority)
|
|
|
|
|
|
func _same_members(left: Array, right: Array) -> bool:
|
|
if left.size() != right.size() or not _unique(left) or not _unique(right):
|
|
return false
|
|
return left.all(func(value): return value in right)
|
|
|
|
|
|
func _receipt_indices_hold(receipts: Array) -> bool:
|
|
for index in receipts.size():
|
|
if not receipts[index] is Dictionary or int(receipts[index].get("index", -1)) != index:
|
|
return false
|
|
return true
|