236 lines
9.1 KiB
GDScript
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
|