Files
aetherbound-guild/runtime/p9_8/round_service.gd
T

236 lines
9.1 KiB
GDScript

class_name P98RoundService
extends "res://p9_7/market_service.gd"
const P98Content = preload("res://content/p9_8_content.gd")
const ContractState = preload("res://p9_8/contract_state.gd")
const ContractOperations = preload("res://p9_8/contract_service.gd")
const P97State = preload("res://p9_7/market_state.gd")
func _init(p97_snapshot: Dictionary = {}) -> void:
content = P98Content.snapshot()
state = ContractState.new()
if not p97_snapshot.is_empty():
assert(state.initialize_from_p97(p97_snapshot))
func choose_artifact(operation_id: String, artifact_id: String) -> Dictionary:
var operations = _contract_operations()
var result: Dictionary = operations.choose_artifact(operation_id, artifact_id)
state = operations.state
return result
func open_promotion_draft(recruit_id := "") -> Dictionary:
var operations = _contract_operations()
var result: Dictionary = operations.open_promotion_draft(recruit_id)
state = operations.state
return result
func cancel_promotion_draft() -> Dictionary:
var operations = _contract_operations()
var result: Dictionary = operations.cancel_promotion_draft()
state = operations.state
return result
func confirm_promotion(operation_id: String, recruit_id: String, promotion_id: String) -> Dictionary:
var operations = _contract_operations()
var result: Dictionary = operations.confirm_promotion(operation_id, recruit_id, promotion_id)
state = operations.state
return result
func commit_route(operation_id: String, route_id: String) -> Dictionary:
var intent := {"route_id": route_id, "deployed_recruit_ids": state.deployed_recruit_ids.duplicate()}
var prior := _prior_operation(operation_id, "commit_route", intent)
if prior.known:
return prior.result
if state.contract_phase != ContractState.ContractPhase.MARKET:
return _reject("market_required")
if state.continuation_locked:
return _reject("continuation_locked")
if state.recovery_required:
return _reject("recovery_required")
if state.current_market == 8 and state.selected_artifact_id.is_empty():
return _reject("artifact_decision_required")
if state.promotion_receipts.is_empty() and not state.first_eligible_promotion_recruit().is_empty():
return _reject("promotion_decision_required")
if route_id != state.focused_route_offer or route_id not in state.available_battle_offer_ids:
return _reject("route_focus_required")
if state.deployed_recruit_ids.is_empty():
return _reject("deployment_required")
var checkpoint: Dictionary = state.snapshot()
state.selected_route = route_id
state.phase = P97State.Phase.ENCOUNTER
state.contract_phase = ContractState.ContractPhase.ENCOUNTER
state.battle_checkpoint = checkpoint
state.retry_route = ""
state.route_history.append(route_id)
var route: Dictionary = content.routes[route_id]
var receipt := {
"index": state.route_receipts.size(), "operation_id": operation_id,
"route_id": route_id, "round": int(route.round), "risk": int(route.risk),
"reward": int(route.reward), "enemy_ids": Array(route.enemy_ids).duplicate(),
"deployed_recruit_ids": state.deployed_recruit_ids.duplicate(),
"board_hash": String(state.market_board.board_hash),
}
state.route_receipts.append(receipt)
return _commit_operation(operation_id, "commit_route", intent, "route_committed", receipt)
func settle_battle(operation_id: String, battle_snapshot: Dictionary) -> Dictionary:
var intent := _battle_settlement_intent(battle_snapshot)
var prior := _prior_operation(operation_id, "settle_battle", intent)
if prior.known:
return prior.result
var validation := _validate_terminal_battle(battle_snapshot)
if not validation.ok:
return _reject(String(validation.code))
var route: Dictionary = content.routes[state.selected_route]
var route_id := state.selected_route
var round_index := int(route.round)
var risk := int(route.risk)
var outcome := String(battle_snapshot.outcome)
var coin_before: int = state.coin
var xp_grant := 0
var xp_rows: Array[Dictionary] = []
var injured: Array[String] = []
for recruit_id in state.deployed_recruit_ids:
var hp := int(battle_snapshot.member_hp[recruit_id])
state.member_condition[recruit_id] = hp
if hp <= 0:
injured.append(recruit_id)
if outcome == "victory":
state.coin += int(route.reward)
xp_grant = 10 + 2 * round_index + 4 * risk
for recruit_id in state.deployed_recruit_ids:
if int(state.member_condition[recruit_id]) <= 0:
continue
xp_rows.append(_grant_xp(recruit_id, xp_grant))
state.recruit_records[recruit_id].survival_battles = int(state.recruit_records[recruit_id].get("survival_battles", 0)) + 1
state.cleared_rounds += 1
state.line_capacity = P98Content.capacity_for_clears(state.cleared_rounds)
state.current_market = state.cleared_rounds + 1
state.relay_restored = true
if state.cleared_rounds < 12:
_open_current_market()
state.phase = P97State.Phase.MARKET
state.contract_phase = ContractState.ContractPhase.MARKET
else:
_open_boss_muster()
state.phase = P97State.Phase.MARKET
state.contract_phase = ContractState.ContractPhase.BOSS_MUSTER
_open_promotion_draft_if_eligible()
_set_recovery("victory", injured, injured.size() * VICTORY_FALLEN_RECOVERY_COST, "")
else:
state.phase = P97State.Phase.MARKET
state.contract_phase = ContractState.ContractPhase.MARKET
_set_recovery("defeat", injured, FAILURE_RECOVERY_COST, route_id)
state.selected_route = ""
state.focused_route_offer = ""
state.pending_dismissal.clear()
state.battle_checkpoint.clear()
var receipt := {
"index": state.battle_settlement_receipts.size(),
"operation_id": operation_id,
"route_id": route_id,
"round": round_index,
"risk": risk,
"outcome": outcome,
"tick": int(battle_snapshot.tick),
"coin_reward": int(route.reward) if outcome == "victory" else 0,
"coin_before": coin_before,
"coin_after": state.coin,
"xp_grant": xp_grant,
"xp_members": xp_rows,
"member_hp": Dictionary(battle_snapshot.member_hp).duplicate(true),
"injured_recruit_ids": injured.duplicate(),
"cleared_rounds": state.cleared_rounds,
"line_capacity": state.line_capacity,
"next_market": state.current_market,
"boss_muster_open": state.contract_phase == ContractState.ContractPhase.BOSS_MUSTER,
"boss_hash": String(state.boss_muster.get("boss_hash", "")),
}
state.battle_settlement_receipts.append(receipt)
return _commit_operation(operation_id, "settle_battle", intent, "battle_settled", receipt)
func _open_current_market() -> void:
state.market_board = ContractState.market_board_for(state.current_market, state.selected_artifact_id)
state.available_recruit_offer_ids.assign(state.market_board.recruit_offer_ids)
state.available_equipment_offer_ids.assign(state.market_board.equipment_offer_ids)
state.available_battle_offer_ids.assign(state.market_board.battle_offer_ids)
state.continuation_locked = false
state.focused_recruit_offer = ""
state.focused_equipment_offer = ""
state.focused_route_offer = ""
func _open_boss_muster() -> void:
var boss: Dictionary = content.bosses["BO-R1-01"]
var authority := {
"boss_id": "BO-R1-01",
"cleared_rounds": state.cleared_rounds,
"company": state.company.duplicate(),
"artifact_id": state.selected_artifact_id,
"route_history": state.route_history.duplicate(),
"pane_order": Array(boss.pane_order).duplicate(),
"initial_pane": String(boss.initial_pane),
"plans": Array(boss.plans).duplicate(true),
"recovery_cost": int(boss.recovery_cost),
}
state.boss_muster = authority.duplicate(true)
state.boss_muster["boss_hash"] = JSON.stringify(authority).sha256_text()
state.market_board.clear()
state.available_recruit_offer_ids.clear()
state.available_equipment_offer_ids.clear()
state.available_battle_offer_ids.clear()
state.focused_recruit_offer = ""
state.focused_equipment_offer = ""
state.focused_route_offer = ""
state.continuation_locked = false
func _open_promotion_draft_if_eligible() -> void:
if not state.promotion_receipts.is_empty() or not state.promotion_draft.is_empty():
return
var recruit_id: String = state.first_eligible_promotion_recruit()
if recruit_id.is_empty():
return
var record: Dictionary = state.recruit_records[recruit_id]
state.promotion_draft = {
"id": "P98-PROMOTION-%s" % recruit_id,
"recruit_id": recruit_id,
"parent_profession_id": String(record.profession_id),
"offer_ids": state.promotion_options(recruit_id),
"level": int(record.level),
"survival_battles": int(record.survival_battles),
}
func _member_max_hp(recruit_id: String) -> int:
var profession_id := String(state.recruit_records.get(recruit_id, {}).get("profession_id", ""))
if content.base_professions.has(profession_id):
return int(content.base_professions[profession_id].max_hp)
if content.promotions.has(profession_id):
var parent_id := String(content.promotions[profession_id].parent_id)
return int(content.base_professions[parent_id].max_hp)
return 0
func _profession_role(profession_id: String) -> String:
if content.base_professions.has(profession_id):
return String(content.base_professions[profession_id].role)
if content.promotions.has(profession_id):
return String(content.promotions[profession_id].role)
return ""
func _contract_operations():
var operations = ContractOperations.new()
operations.content = content
operations.state = state
return operations