197 lines
9.5 KiB
GDScript
197 lines
9.5 KiB
GDScript
extends SceneTree
|
|
|
|
## M79-B gate: Contracts advance by ordinal, so the second region becomes
|
|
## reachable. Contract 1 and 2 are region one; contract 3 opens Siltwake Fens
|
|
## with its own equipment band, enemy pool and boss anchor.
|
|
|
|
const DraftService = preload("res://entry/initial_company_draft_service.gd")
|
|
const ContractService = preload("res://entry/guild_contract_service.gd")
|
|
const ContentRegistry = preload("res://p10_0/content_registry.gd")
|
|
|
|
const COMPANY_PATH := "user://aetherbound_region_advance_company.json"
|
|
const DRAFT_PATH := "user://aetherbound_region_advance_draft.json"
|
|
const OP_CREATE_1 := "11111111111111111111111111111111"
|
|
const OP_RETURN_1 := "22222222222222222222222222222222"
|
|
const OP_CREATE_2 := "33333333333333333333333333333333"
|
|
const OP_RETURN_2 := "44444444444444444444444444444444"
|
|
const OP_CREATE_3 := "55555555555555555555555555555555"
|
|
const OP_RETURN_3 := "66666666666666666666666666666666"
|
|
const OP_CREATE_4 := "77777777777777777777777777777777"
|
|
const OP_RETURN_4 := "88888888888888888888888888888888"
|
|
const OP_CREATE_5 := "99999999999999999999999999999999"
|
|
const OP_RETURN_5 := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
const OP_CREATE_6 := "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
|
const OP_RETURN_6 := "cccccccccccccccccccccccccccccccc"
|
|
const OP_CREATE_7 := "dddddddddddddddddddddddddddddddd"
|
|
const OP_RETURN_7 := "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
|
|
const OP_CREATE_8 := "ffffffffffffffffffffffffffffffff"
|
|
const OP_RETURN_8 := "00000000000000000000000000000000"
|
|
const OP_CREATE_9 := "12121212121212121212121212121212"
|
|
const OP_RETURN_9 := "34343434343434343434343434343434"
|
|
const OP_CREATE_10 := "56565656565656565656565656565656"
|
|
const OP_RETURN_10 := "78787878787878787878787878787878"
|
|
const OP_CREATE_11 := "9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a"
|
|
const OP_RETURN_11 := "b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7"
|
|
const OP_CREATE_12 := "c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8"
|
|
const OP_RETURN_12 := "d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9"
|
|
const OP_CREATE_13 := "e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0"
|
|
|
|
var failures: Array[String] = []
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
|
|
func _run() -> void:
|
|
_cleanup()
|
|
var company := _create_company()
|
|
if company.is_empty():
|
|
push_error("region advance: company creation failed")
|
|
quit(1)
|
|
return
|
|
expect(String(company.active_contract.region_id) == "RG-01", "first contract opens region one")
|
|
expect(_equipment_band(company) == "EQ-001..EQ-040", "region one market draws EQ-001..EQ-040")
|
|
expect(int(company.active_contract.region_index) == 1, "region index starts at one")
|
|
|
|
# M79-H: drive every contract in order and assert the region band it opens.
|
|
# Contract ids follow the catalogue order: two per region, so contract 15 is
|
|
# the first Crown of Morn contract.
|
|
var expected_region := 1
|
|
var equipment_low := 1
|
|
var checked := 1
|
|
var last_region_id := "RG-01"
|
|
var last_boss := ""
|
|
var last_enemies := 0
|
|
for ordinal in range(1, 16):
|
|
var returned: Dictionary = _service().return_contract(_operation_id("return", ordinal))
|
|
if not returned.ok:
|
|
push_error("region advance: return %d failed: %s" % [ordinal, String(returned.get("code", ""))])
|
|
quit(1)
|
|
return
|
|
var contract_rows: Array = Array(ContentRegistry.snapshot().get("contracts", []))
|
|
contract_rows.sort_custom(func(a, b): return String(Dictionary(a).get("id", "")) < String(Dictionary(b).get("id", "")))
|
|
var row: Dictionary = Dictionary(contract_rows[ordinal])
|
|
var created: Dictionary = _service().create_contract(_operation_id("create", ordinal), String(row.get("id", "")), "standard")
|
|
if not created.ok:
|
|
push_error("region advance: create %d (%s) failed: %s" % [ordinal, row.get("id", ""), String(created.get("code", ""))])
|
|
quit(1)
|
|
return
|
|
var contract: Dictionary = created.company.active_contract
|
|
checked += 1
|
|
# Loop step `ordinal` creates contract number `ordinal + 1`; a region's
|
|
# first contract is an odd ordinal, so a new region opens on even steps.
|
|
if ordinal % 2 == 0:
|
|
expected_region = (ordinal + 2) / 2
|
|
equipment_low = 1 + (expected_region - 1) * 40
|
|
expect(int(contract.region_index) == expected_region, "contract %d opens region %d, got %s" % [ordinal + 1, expected_region, String(contract.region_id)])
|
|
expect(String(contract.region_id) == "RG-%02d" % expected_region, "contract %d region id, got %s" % [ordinal, String(contract.region_id)])
|
|
expect(String(contract.boss_id).begins_with("BO-R%d-" % expected_region), "contract %d anchors a region %d boss, got %s" % [ordinal, expected_region, String(contract.boss_id)])
|
|
else:
|
|
expect(int(contract.region_index) == expected_region, "contract %d stays in region %d" % [ordinal + 1, expected_region])
|
|
expect(_equipment_band(created.company) == "EQ-%03d..EQ-%03d" % [equipment_low, equipment_low + 39],
|
|
"contract %d market draws EQ-%03d..EQ-%03d" % [ordinal, equipment_low, equipment_low + 39])
|
|
var region_enemies: Array[String] = []
|
|
for offer in Array(contract.market_board.battle_offers):
|
|
for enemy_id in Array(Dictionary(offer).get("enemy_ids", [])):
|
|
region_enemies.append(String(enemy_id))
|
|
var leaked: Array[String] = []
|
|
for enemy_id in region_enemies:
|
|
if not enemy_id.begins_with("EN-R%d-" % expected_region) and not enemy_id.begins_with("BO-R%d-" % expected_region):
|
|
leaked.append(enemy_id)
|
|
expect(leaked.is_empty(), "contract %d fields only region %d identities, leaked %s" % [ordinal, expected_region, str(leaked)])
|
|
if ordinal == 15:
|
|
last_region_id = String(contract.region_id)
|
|
last_boss = String(contract.boss_id)
|
|
last_enemies = region_enemies.size()
|
|
|
|
# Close the final contract, then the Guild reports a completed route.
|
|
# Voluntary withdrawals grant no clear facts or renown.
|
|
var final_return: Dictionary = _service().return_contract(_operation_id("return", 16))
|
|
expect(final_return.ok, "the final contract returns cleanly: %s" % String(final_return.get("code", "")))
|
|
var final_company: Dictionary = Dictionary(final_return.get("company", {}))
|
|
expect(Array(final_company.get("clear_facts", [])).is_empty(), "voluntary withdrawals grant no clear facts")
|
|
expect(int(final_company.get("renown", 0)) == 0, "voluntary withdrawals grant no renown")
|
|
var guild_controller := preload("res://entry/guild_page_controller.gd").new()
|
|
var guild_snapshot: Dictionary = guild_controller.snapshot(final_company, "zh_CN")
|
|
expect(bool(guild_snapshot.get("route_complete", false)), "the guild reports a completed route after sixteen contracts")
|
|
var contract_destination: Dictionary = guild_controller.destination_state(guild_snapshot, "contract")
|
|
expect(String(contract_destination.get("reason", "")) == "route_complete", "the board closes with the completion reason, got %s" % String(contract_destination.get("reason", "")))
|
|
expect(not bool(contract_destination.get("enabled", true)), "the board is closed once the route is complete")
|
|
|
|
var content: Dictionary = ContentRegistry.snapshot()
|
|
expect(ContentRegistry.localized_name(content, "RG-02", "zh_CN") == "淤醒沼泽", "region two zh authority resolves")
|
|
expect(ContentRegistry.localized_name(content, "RG-08", "zh_CN") == "晨冠", "region eight zh authority resolves")
|
|
expect(ContentRegistry.localized_name(content, "RG-08", "en") == "Crown of Morn", "region eight en authority resolves")
|
|
|
|
_cleanup()
|
|
if failures.is_empty():
|
|
print("ABG_M79_REGION_ADVANCE_OK contracts=%d region=%s equipment=EQ-281..EQ-320 enemies=%d boss=%s route_complete=true" % [checked, last_region_id, last_enemies, last_boss])
|
|
quit(0)
|
|
return
|
|
for failure in failures:
|
|
push_error("M79 region advance: %s" % failure)
|
|
quit(1)
|
|
|
|
|
|
func _equipment_band(company: Dictionary) -> String:
|
|
# The market draws four identities from the region band; the gate checks that
|
|
# every drawn identity stays inside the band rather than the band's extremes.
|
|
var ids: Array[String] = []
|
|
for offer in Array(Dictionary(company.get("active_contract", {})).get("market_board", {}).get("equipment_offers", [])):
|
|
ids.append(String(Dictionary(offer).get("catalog_id", "")))
|
|
if ids.is_empty():
|
|
return "none"
|
|
var low := 9999
|
|
var high := 0
|
|
for catalog_id in ids:
|
|
var ordinal := int(catalog_id.split("-")[1])
|
|
low = mini(low, ordinal)
|
|
high = maxi(high, ordinal)
|
|
var expected_low := 1 + (int(Dictionary(company.get("active_contract", {})).get("region_index", 1)) - 1) * 40
|
|
var expected_high := expected_low + 39
|
|
if low >= expected_low and high <= expected_high:
|
|
return "EQ-%03d..EQ-%03d" % [expected_low, expected_high]
|
|
return "out_of_band:%d..%d" % [low, high]
|
|
|
|
|
|
func _create_company() -> Dictionary:
|
|
var draft: Dictionary = _draft_service().begin_or_load("abcdef0123456789abcdef0123456789")
|
|
if not draft.ok:
|
|
return {}
|
|
var selected := [String(draft.draft.offers[0].offer_id), String(draft.draft.offers[1].offer_id)]
|
|
if not _draft_service().set_selection(selected).ok:
|
|
return {}
|
|
if not _draft_service().commit_company().ok:
|
|
return {}
|
|
var created: Dictionary = _service().create_contract(OP_CREATE_1, "CTR-R1-01", "standard")
|
|
return created.company if created.ok else {}
|
|
|
|
|
|
func _operation_id(kind: String, ordinal: int) -> String:
|
|
return ("%s-%08d" % [kind, ordinal]).sha256_text().substr(0, 32)
|
|
|
|
|
|
func _service() -> RefCounted:
|
|
var service := ContractService.new()
|
|
service.company_path = COMPANY_PATH
|
|
return service
|
|
|
|
|
|
func _draft_service() -> RefCounted:
|
|
var service := DraftService.new()
|
|
service.path = DRAFT_PATH
|
|
service.company_path = COMPANY_PATH
|
|
return service
|
|
|
|
|
|
func _cleanup() -> void:
|
|
for path in [COMPANY_PATH, DRAFT_PATH, "%s.bak" % COMPANY_PATH, "%s.candidate" % COMPANY_PATH]:
|
|
if FileAccess.file_exists(path):
|
|
DirAccess.remove_absolute(ProjectSettings.globalize_path(path))
|
|
|
|
|
|
func expect(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
failures.append(message)
|