584 lines
23 KiB
GDScript
584 lines
23 KiB
GDScript
class_name ABGGuildContractService
|
|
extends RefCounted
|
|
|
|
const DraftService = preload("res://entry/initial_company_draft_service.gd")
|
|
const ContentRegistry = preload("res://p10_0/content_registry.gd")
|
|
const OfferGenerator = preload("res://p10_0/offer_generator.gd")
|
|
|
|
const DEFAULT_COMPANY_PATH := "user://aetherbound_expedition_company.json"
|
|
const FIRST_CONTRACT_ID := "CTR-R1-01"
|
|
const DIFFICULTIES := ["wayfinder", "standard", "stormbound", "iron_oath"]
|
|
const STARTING_COIN := 52
|
|
const STARTING_LINE_CAPACITY := 4
|
|
const COMPANY_ATTRIBUTE_IDS := ["might", "finesse", "insight", "vigor", "resolve"]
|
|
|
|
var company_path := DEFAULT_COMPANY_PATH
|
|
var _content: Dictionary
|
|
var _generator: RefCounted
|
|
|
|
|
|
func _init() -> void:
|
|
_content = ContentRegistry.snapshot()
|
|
_generator = OfferGenerator.new(_content)
|
|
|
|
|
|
func load_company() -> Dictionary:
|
|
return _store().load_company()
|
|
|
|
|
|
func create_contract(operation_id: String, contract_id: String, difficulty: String) -> Dictionary:
|
|
if not _is_hex(operation_id, 32):
|
|
return {"ok": false, "code": "CONTRACT_OPERATION_ID_INVALID"}
|
|
if contract_id != FIRST_CONTRACT_ID:
|
|
return {"ok": false, "code": "CONTRACT_LOCKED"}
|
|
if difficulty not in DIFFICULTIES:
|
|
return {"ok": false, "code": "CONTRACT_DIFFICULTY_INVALID"}
|
|
var store := _store()
|
|
var loaded: Dictionary = store.load_company()
|
|
if not loaded.ok:
|
|
return loaded
|
|
var company: Dictionary = loaded.company.duplicate(true)
|
|
if not company.active_contract.is_empty():
|
|
var existing_receipt: Dictionary = company.active_contract.creation_receipt
|
|
var same_intent := String(existing_receipt.contract_id) == contract_id and String(existing_receipt.difficulty) == difficulty
|
|
if String(existing_receipt.operation_id) == operation_id and not same_intent:
|
|
return {"ok": false, "code": "CONTRACT_OPERATION_CONFLICT"}
|
|
if same_intent:
|
|
return {
|
|
"ok": true,
|
|
"code": "already_created",
|
|
"company": company,
|
|
"contract": company.active_contract,
|
|
"receipt": existing_receipt,
|
|
}
|
|
return {"ok": false, "code": "CONTRACT_ALREADY_ACTIVE"}
|
|
var contract_ordinal := int(company.next_contract_ordinal)
|
|
var intent := {
|
|
"company_id": String(company.company_id),
|
|
"contract_ordinal": contract_ordinal,
|
|
"contract_id": contract_id,
|
|
"difficulty": difficulty,
|
|
}
|
|
var intent_hash := _hash(intent)
|
|
if contract_ordinal != 0:
|
|
return {"ok": false, "code": "CONTRACT_ORDINAL_INVALID"}
|
|
var run_seed := _low128(_digest(String(company.company_id), "dev3-contract-run", [contract_ordinal]))
|
|
var run_id := _low128(_digest(run_seed, "dev3-contract-id", [contract_id]))
|
|
var generated: Dictionary = _generator.market_board(run_seed, contract_id, 1, 0)
|
|
if not generated.ok:
|
|
return {"ok": false, "code": "CONTRACT_MARKET_GENERATION_FAILED"}
|
|
var board := _price_first_board(generated.board)
|
|
var receipt := {
|
|
"operation_id": operation_id,
|
|
"intent_hash": intent_hash,
|
|
"company_id": String(company.company_id),
|
|
"contract_ordinal": contract_ordinal,
|
|
"contract_id": contract_id,
|
|
"difficulty": difficulty,
|
|
"run_id": run_id,
|
|
"run_seed": run_seed,
|
|
"board_hash": String(board.board_hash),
|
|
"coin": STARTING_COIN,
|
|
"line_capacity": STARTING_LINE_CAPACITY,
|
|
}
|
|
receipt.receipt_hash = _hash(receipt)
|
|
var contract := {
|
|
"contract_version": 1,
|
|
"company_id": String(company.company_id),
|
|
"contract_id": contract_id,
|
|
"run_id": run_id,
|
|
"run_seed": run_seed,
|
|
"difficulty": difficulty,
|
|
"status": "market",
|
|
"market_index": 1,
|
|
"coin": STARTING_COIN,
|
|
"line_capacity": STARTING_LINE_CAPACITY,
|
|
"market_board": board,
|
|
"available_recruit_offer_ids": _offer_ids(board.recruit_offers, "source_offer_id"),
|
|
"available_equipment_offer_ids": _offer_ids(board.equipment_offers, "offer_id"),
|
|
"available_battle_offer_ids": _offer_ids(board.battle_offers, "offer_id"),
|
|
"hire_receipts": [],
|
|
"creation_receipt": receipt,
|
|
}
|
|
contract.contract_hash = _hash(contract)
|
|
company.company_revision = int(company.company_revision) + 1
|
|
company.next_contract_ordinal = contract_ordinal + 1
|
|
company.active_contract = contract
|
|
company.contract_receipts = [receipt]
|
|
company.erase("company_hash")
|
|
company.company_hash = _hash(company)
|
|
var saved: Dictionary = store.save_company(company)
|
|
if not saved.ok:
|
|
return saved
|
|
return {
|
|
"ok": true,
|
|
"code": "created",
|
|
"company": saved.company,
|
|
"contract": saved.company.active_contract,
|
|
"receipt": saved.company.active_contract.creation_receipt,
|
|
}
|
|
|
|
|
|
func hire_recruit(operation_id: String, offer_id: String, expected_price: int) -> Dictionary:
|
|
if not _is_hex(operation_id, 32):
|
|
return {"ok": false, "code": "HIRE_OPERATION_ID_INVALID"}
|
|
if not _is_hex(offer_id, 32):
|
|
return {"ok": false, "code": "HIRE_OFFER_ID_INVALID"}
|
|
var store := _store()
|
|
var loaded: Dictionary = store.load_company()
|
|
if not loaded.ok:
|
|
return loaded
|
|
var company: Dictionary = loaded.company.duplicate(true)
|
|
if company.active_contract.is_empty() or String(company.active_contract.get("status", "")) != "market":
|
|
return {"ok": false, "code": "HIRE_MARKET_REQUIRED"}
|
|
var contract: Dictionary = company.active_contract
|
|
var intent := {
|
|
"company_id": String(company.company_id),
|
|
"run_id": String(contract.run_id),
|
|
"board_hash": String(contract.market_board.board_hash),
|
|
"offer_id": offer_id,
|
|
"expected_price": expected_price,
|
|
}
|
|
var intent_hash := _hash(intent)
|
|
for stored_receipt in contract.hire_receipts:
|
|
var prior_receipt: Dictionary = stored_receipt
|
|
if String(prior_receipt.operation_id) == operation_id:
|
|
if String(prior_receipt.intent_hash) != intent_hash:
|
|
return {"ok": false, "code": "HIRE_OPERATION_CONFLICT"}
|
|
return {"ok": true, "code": "already_hired", "company": company, "receipt": prior_receipt}
|
|
if String(prior_receipt.offer_id) == offer_id:
|
|
if String(prior_receipt.intent_hash) == intent_hash:
|
|
return {"ok": true, "code": "already_hired", "company": company, "receipt": prior_receipt}
|
|
return {"ok": false, "code": "HIRE_OFFER_ALREADY_APPLIED_CONFLICT"}
|
|
if contract.hire_receipts.size() >= 2:
|
|
return {"ok": false, "code": "OPENING_HIRES_COMPLETE"}
|
|
var offer: Dictionary = _find_recruit_offer(contract, offer_id)
|
|
if offer.is_empty() or offer_id not in contract.available_recruit_offer_ids:
|
|
return {"ok": false, "code": "HIRE_OFFER_UNAVAILABLE"}
|
|
var list_price: int = int(offer.get("list_price", -1))
|
|
if expected_price != list_price:
|
|
return {"ok": false, "code": "HIRE_PRICE_MISMATCH"}
|
|
if int(contract.coin) < list_price:
|
|
return {"ok": false, "code": "HIRE_INSUFFICIENT_COIN"}
|
|
var recruit: Dictionary = _materialize_market_recruit(offer)
|
|
var company_before: int = company.owned_recruits.size()
|
|
var coin_before: int = int(contract.coin)
|
|
var receipt: Dictionary = {
|
|
"operation_id": operation_id,
|
|
"intent_hash": intent_hash,
|
|
"company_id": String(company.company_id),
|
|
"run_id": String(contract.run_id),
|
|
"board_hash": String(contract.market_board.board_hash),
|
|
"offer_id": offer_id,
|
|
"recruit_id": String(recruit.recruit_id),
|
|
"expected_price": expected_price,
|
|
"list_price": list_price,
|
|
"coin_before": coin_before,
|
|
"coin_after": coin_before - list_price,
|
|
"company_before": company_before,
|
|
"company_after": company_before + 1,
|
|
"line_capacity": int(contract.line_capacity),
|
|
}
|
|
receipt.receipt_hash = _hash(receipt)
|
|
company.owned_recruits.append(recruit)
|
|
contract.coin = coin_before - list_price
|
|
contract.available_recruit_offer_ids.erase(offer_id)
|
|
contract.hire_receipts.append(receipt)
|
|
contract.erase("contract_hash")
|
|
contract.contract_hash = _hash(contract)
|
|
company.active_contract = contract
|
|
company.company_revision = int(company.company_revision) + 1
|
|
company.erase("company_hash")
|
|
company.company_hash = _hash(company)
|
|
var saved: Dictionary = store.save_company(company)
|
|
if not saved.ok:
|
|
return saved
|
|
return {"ok": true, "code": "hired", "company": saved.company, "receipt": receipt}
|
|
|
|
|
|
func purchase_equipment(operation_id: String, offer_id: String, recruit_id: String, expected_price: int) -> Dictionary:
|
|
if not _is_hex(operation_id, 32):
|
|
return {"ok": false, "code": "EQUIPMENT_OPERATION_ID_INVALID"}
|
|
if not _is_hex(offer_id, 32) or not _is_hex(recruit_id, 32):
|
|
return {"ok": false, "code": "EQUIPMENT_TARGET_ID_INVALID"}
|
|
var store := _store()
|
|
var loaded: Dictionary = store.load_company()
|
|
if not loaded.ok:
|
|
return loaded
|
|
var company: Dictionary = loaded.company.duplicate(true)
|
|
if company.active_contract.is_empty() or String(company.active_contract.get("status", "")) != "market":
|
|
return {"ok": false, "code": "EQUIPMENT_MARKET_REQUIRED"}
|
|
var contract: Dictionary = company.active_contract
|
|
var intent := {
|
|
"company_id": String(company.company_id),
|
|
"run_id": String(contract.run_id),
|
|
"offer_id": offer_id,
|
|
"recruit_id": recruit_id,
|
|
"expected_price": expected_price,
|
|
"board_hash": String(contract.market_board.board_hash),
|
|
}
|
|
var intent_hash := _hash(intent)
|
|
var equipment_receipts: Array = contract.get("equipment_receipts", [])
|
|
for stored_receipt in equipment_receipts:
|
|
var prior: Dictionary = stored_receipt
|
|
if String(prior.operation_id) == operation_id:
|
|
if String(prior.intent_hash) != intent_hash:
|
|
return {"ok": false, "code": "EQUIPMENT_OPERATION_CONFLICT"}
|
|
return {"ok": true, "code": "already_equipped", "company": company, "receipt": prior}
|
|
if String(prior.offer_id) == offer_id:
|
|
if String(prior.intent_hash) == intent_hash:
|
|
return {"ok": true, "code": "already_equipped", "company": company, "receipt": prior}
|
|
return {"ok": false, "code": "EQUIPMENT_OFFER_ALREADY_APPLIED_CONFLICT"}
|
|
var offer: Dictionary = {}
|
|
for candidate in contract.market_board.get("equipment_offers", []):
|
|
if String(candidate.get("offer_id", "")) == offer_id:
|
|
offer = Dictionary(candidate).duplicate(true)
|
|
break
|
|
if offer.is_empty() or offer_id not in contract.available_equipment_offer_ids:
|
|
return {"ok": false, "code": "EQUIPMENT_OFFER_UNAVAILABLE"}
|
|
var list_price := int(offer.get("list_price", -1))
|
|
if expected_price != list_price:
|
|
return {"ok": false, "code": "EQUIPMENT_PRICE_MISMATCH"}
|
|
if int(contract.coin) < list_price:
|
|
return {"ok": false, "code": "EQUIPMENT_INSUFFICIENT_COIN"}
|
|
var target: Dictionary = {}
|
|
for recruit in company.owned_recruits:
|
|
if String(recruit.get("recruit_id", "")) == recruit_id:
|
|
target = recruit
|
|
break
|
|
if target.is_empty():
|
|
return {"ok": false, "code": "EQUIPMENT_RECRUIT_UNAVAILABLE"}
|
|
var previous_item_id := String(target.get("equipped_item_id", ""))
|
|
var previous_item_name := Dictionary(target.get("starting_item_name", {})).duplicate(true)
|
|
var item_record := {
|
|
"catalog_id": String(offer.get("catalog_id", "")),
|
|
"instance_id": String(offer.get("instance_id", offer.get("offer_id", ""))),
|
|
"source_offer_id": offer_id,
|
|
"list_price": list_price,
|
|
"acquired_operation_id": operation_id,
|
|
}
|
|
var inventory: Array = company.get("equipment_inventory", []).duplicate(true)
|
|
if not previous_item_id.is_empty():
|
|
inventory.append({"catalog_id": previous_item_id, "instance_id": ("%s|returned|%s" % [recruit_id, previous_item_id]).sha256_text().substr(0, 32), "source": "replaced_equipment", "name": previous_item_name})
|
|
var coin_before := int(contract.coin)
|
|
var receipt := {
|
|
"operation_id": operation_id,
|
|
"intent_hash": intent_hash,
|
|
"company_id": String(company.company_id),
|
|
"run_id": String(contract.run_id),
|
|
"offer_id": offer_id,
|
|
"catalog_id": String(offer.catalog_id),
|
|
"instance_id": String(item_record.instance_id),
|
|
"recruit_id": recruit_id,
|
|
"previous_equipped_item_id": previous_item_id,
|
|
"new_equipped_item_id": String(offer.catalog_id),
|
|
"coin_before": coin_before,
|
|
"coin_after": coin_before - list_price,
|
|
"list_price": list_price,
|
|
}
|
|
receipt.receipt_hash = _hash(receipt)
|
|
target.equipped_item_id = String(offer.catalog_id)
|
|
target.starting_item_state = "inventory"
|
|
target.recruit_hash = _hash_without(target, "recruit_hash")
|
|
company.equipment_inventory = inventory
|
|
contract.coin = coin_before - list_price
|
|
contract.available_equipment_offer_ids.erase(offer_id)
|
|
equipment_receipts.append(receipt)
|
|
contract.equipment_receipts = equipment_receipts
|
|
contract.erase("contract_hash")
|
|
contract.contract_hash = _hash(contract)
|
|
company.active_contract = contract
|
|
company.company_revision = int(company.company_revision) + 1
|
|
company.erase("company_hash")
|
|
company.company_hash = _hash(company)
|
|
var saved: Dictionary = store.save_company(company)
|
|
if not saved.ok:
|
|
return saved
|
|
return {"ok": true, "code": "equipment_equipped", "company": saved.company, "receipt": receipt, "item": item_record}
|
|
|
|
|
|
func dismiss_recruit(operation_id: String, recruit_id: String) -> Dictionary:
|
|
if not _is_hex(operation_id, 32) or not _is_hex(recruit_id, 32):
|
|
return {"ok": false, "code": "DISMISS_OPERATION_ID_INVALID"}
|
|
var store := _store()
|
|
var loaded: Dictionary = store.load_company()
|
|
if not loaded.ok:
|
|
return loaded
|
|
var company: Dictionary = loaded.company.duplicate(true)
|
|
var contract: Dictionary = company.active_contract
|
|
if contract.is_empty() or String(contract.get("status", "")) != "market":
|
|
return {"ok": false, "code": "DISMISS_MARKET_REQUIRED"}
|
|
if contract.has("formation"):
|
|
return {"ok": false, "code": "DISMISS_FORMATION_LOCKED"}
|
|
var receipts: Array = company.get("dismissal_receipts", [])
|
|
for stored in receipts:
|
|
var prior: Dictionary = stored
|
|
if String(prior.operation_id) == operation_id:
|
|
if String(prior.recruit_id) != recruit_id:
|
|
return {"ok": false, "code": "DISMISS_OPERATION_CONFLICT"}
|
|
return {"ok": true, "code": "already_dismissed", "company": company, "receipt": prior}
|
|
if company.owned_recruits.size() <= 1:
|
|
return {"ok": false, "code": "FINAL_LIVE_RECRUIT"}
|
|
var removed_index := -1
|
|
var removed: Dictionary = {}
|
|
for index in range(company.owned_recruits.size()):
|
|
var recruit: Dictionary = company.owned_recruits[index]
|
|
if String(recruit.recruit_id) == recruit_id:
|
|
removed_index = index
|
|
removed = recruit
|
|
break
|
|
if removed_index < 0:
|
|
return {"ok": false, "code": "DISMISS_RECRUIT_UNAVAILABLE"}
|
|
var inventory: Array = company.get("equipment_inventory", []).duplicate(true)
|
|
if not String(removed.get("equipped_item_id", "")).is_empty():
|
|
inventory.append({
|
|
"catalog_id": String(removed.equipped_item_id),
|
|
"instance_id": ("%s|dismissed|%s" % [recruit_id, removed.equipped_item_id]).sha256_text().substr(0, 32),
|
|
"source": "dismissed_recruit",
|
|
})
|
|
var receipt := {
|
|
"operation_id": operation_id,
|
|
"company_id": String(company.company_id),
|
|
"run_id": String(contract.run_id),
|
|
"recruit_id": recruit_id,
|
|
"removed_index": removed_index,
|
|
"returned_equipped_item_id": String(removed.get("equipped_item_id", "")),
|
|
"coin_delta": 0,
|
|
"company_before": company.owned_recruits.size(),
|
|
"company_after": company.owned_recruits.size() - 1,
|
|
}
|
|
receipt.intent_hash = _hash({"company_id": receipt.company_id, "run_id": receipt.run_id, "recruit_id": recruit_id, "coin_delta": 0})
|
|
receipt.receipt_hash = _hash(receipt)
|
|
company.owned_recruits.remove_at(removed_index)
|
|
company.equipment_inventory = inventory
|
|
receipts.append(receipt)
|
|
company.dismissal_receipts = receipts
|
|
company.company_revision = int(company.company_revision) + 1
|
|
contract.dismissal_receipts = receipts.duplicate(true)
|
|
contract.erase("contract_hash")
|
|
contract.contract_hash = _hash(contract)
|
|
company.active_contract = contract
|
|
company.erase("company_hash")
|
|
company.company_hash = _hash(company)
|
|
var saved: Dictionary = store.save_company(company)
|
|
if not saved.ok:
|
|
return saved
|
|
return {"ok": true, "code": "dismissed", "company": saved.company, "receipt": receipt}
|
|
|
|
|
|
func return_contract(operation_id: String) -> Dictionary:
|
|
if not _is_hex(operation_id, 32):
|
|
return {"ok": false, "code": "CONTRACT_RETURN_OPERATION_ID_INVALID"}
|
|
var store := _store()
|
|
var loaded: Dictionary = store.load_company()
|
|
if not loaded.ok:
|
|
return loaded
|
|
var company: Dictionary = loaded.company.duplicate(true)
|
|
var contract: Dictionary = company.active_contract
|
|
if contract.is_empty():
|
|
return {"ok": false, "code": "CONTRACT_RETURN_MARKET_REQUIRED"}
|
|
var returns: Array = Array(contract.get("contract_return_receipts", [])).duplicate(true)
|
|
var intent_hash := _hash({
|
|
"company_id": String(company.company_id),
|
|
"run_id": String(contract.run_id),
|
|
"contract_id": String(contract.contract_id),
|
|
})
|
|
for stored in returns:
|
|
var prior: Dictionary = stored
|
|
if String(prior.operation_id) == operation_id:
|
|
if String(prior.get("intent_hash", "")) != intent_hash:
|
|
return {"ok": false, "code": "CONTRACT_RETURN_OPERATION_CONFLICT"}
|
|
return {"ok": true, "code": "contract_return_already_applied", "company": company, "receipt": prior}
|
|
if String(contract.get("status", "")) != "market":
|
|
return {"ok": false, "code": "CONTRACT_RETURN_ALREADY_APPLIED" if String(contract.get("status", "")) == "returned" else "CONTRACT_RETURN_MARKET_REQUIRED"}
|
|
if contract.has("formation"):
|
|
return {"ok": false, "code": "CONTRACT_RETURN_FORMATION_LOCKED"}
|
|
var receipt := {
|
|
"operation_id": operation_id,
|
|
"intent_hash": intent_hash,
|
|
"company_id": String(company.company_id),
|
|
"run_id": String(contract.run_id),
|
|
"contract_id": String(contract.contract_id),
|
|
"retained_coin": int(contract.coin),
|
|
"retained_recruit_ids": _owned_recruit_ids(company),
|
|
"destination": "guild",
|
|
}
|
|
receipt.receipt_hash = _hash(receipt)
|
|
returns.append(receipt)
|
|
contract.contract_return_receipts = returns
|
|
contract.status = "returned"
|
|
contract.erase("contract_hash")
|
|
contract.contract_hash = _hash(contract)
|
|
company.active_contract = contract
|
|
company.company_revision = int(company.company_revision) + 1
|
|
company.erase("company_hash")
|
|
company.company_hash = _hash(company)
|
|
var saved: Dictionary = store.save_company(company)
|
|
if not saved.ok:
|
|
return saved
|
|
return {"ok": true, "code": "contract_returned", "company": saved.company, "receipt": receipt}
|
|
|
|
|
|
func _store() -> RefCounted:
|
|
var store := DraftService.new()
|
|
store.company_path = company_path
|
|
return store
|
|
|
|
|
|
func _find_recruit_offer(contract: Dictionary, offer_id: String) -> Dictionary:
|
|
for offer in contract.market_board.recruit_offers:
|
|
if String(offer.source_offer_id) == offer_id:
|
|
return Dictionary(offer).duplicate(true)
|
|
return {}
|
|
|
|
|
|
func _materialize_market_recruit(offer: Dictionary) -> Dictionary:
|
|
var professions := ContentRegistry.rows_by_id(_content, "professions")
|
|
var traits := ContentRegistry.rows_by_id(_content, "traits")
|
|
var equipment := ContentRegistry.rows_by_id(_content, "equipment")
|
|
var profession: Dictionary = professions.get(String(offer.profession_id), {})
|
|
var trait_row: Dictionary = traits.get(String(offer.trait_id), {})
|
|
var item: Dictionary = equipment.get(String(offer.starting_equipment_id), {})
|
|
var name_tokens: Array = Array(offer.display_name_tokens).duplicate()
|
|
var display_name := "%s %s" % [_name_token(name_tokens[0]), _name_token(name_tokens[1])]
|
|
var recruit := {
|
|
"recruit_id": String(offer.recruit_id),
|
|
"generation_seed": String(offer.generation_seed),
|
|
"name_component_ids": name_tokens,
|
|
"rendered_name_snapshot": {"en": display_name, "zh_CN": display_name},
|
|
"appearance_seed": String(offer.appearance_seed),
|
|
"profession_id": String(offer.profession_id),
|
|
"profession_name": {
|
|
"en": String(profession.get("name_en", profession.get("name", ""))),
|
|
"zh_CN": String(profession.get("name_zh", "")),
|
|
},
|
|
"profession_job": String(profession.get("role", "")),
|
|
"attributes": _numeric_company_attributes(String(offer.generation_seed)),
|
|
"trait_id": String(offer.trait_id),
|
|
"trait_name": {
|
|
"en": String(trait_row.get("name_en", trait_row.get("name", ""))),
|
|
"zh_CN": String(trait_row.get("name_zh", "")),
|
|
},
|
|
"trait_benefit": String(trait_row.get("benefit", "")),
|
|
"trait_cost": String(trait_row.get("cost", "")),
|
|
"starting_item_id": String(offer.starting_equipment_id),
|
|
"starting_item_name": {
|
|
"en": String(item.get("name_en", item.get("name", ""))),
|
|
"zh_CN": String(item.get("name_zh", "")),
|
|
},
|
|
"starting_item_state": "equipped",
|
|
"equipped_item_id": String(offer.starting_equipment_id),
|
|
"level": 1,
|
|
"xp": 0,
|
|
"state": "owned",
|
|
"joined_from_market_id": String(offer.market_id),
|
|
"market_offer_id": String(offer.source_offer_id),
|
|
"market_offer_hash": String(offer.canonical_output_hash),
|
|
"battle_history": {"battles_entered": 0, "victories": 0, "knockouts": 0},
|
|
}
|
|
recruit.recruit_hash = _hash(recruit)
|
|
return recruit
|
|
|
|
|
|
func _numeric_company_attributes(seed: String) -> Dictionary:
|
|
var result := {}
|
|
for attribute_id in COMPANY_ATTRIBUTE_IDS:
|
|
result[attribute_id] = 4
|
|
for point in range(15):
|
|
var candidates := COMPANY_ATTRIBUTE_IDS.duplicate()
|
|
candidates.sort_custom(func(left: String, right: String):
|
|
return _digest(seed, "attribute-point", [point, left]) < _digest(seed, "attribute-point", [point, right])
|
|
)
|
|
for attribute_id in candidates:
|
|
if int(result[attribute_id]) < 15:
|
|
result[attribute_id] = int(result[attribute_id]) + 1
|
|
break
|
|
return result
|
|
|
|
|
|
func _name_token(token) -> String:
|
|
var parts := String(token).split(".")
|
|
return String(parts[parts.size() - 1])
|
|
|
|
|
|
func _price_first_board(raw: Dictionary) -> Dictionary:
|
|
var board := raw.duplicate(true)
|
|
for offer in board.recruit_offers:
|
|
offer.offer_level = 1
|
|
offer.profession_tier = 0
|
|
offer.list_price = 14
|
|
for offer in board.equipment_offers:
|
|
var ordinal := int(String(offer.catalog_id).trim_prefix("EQ-"))
|
|
var item_tier := clampi(int((ordinal - 1) / 10), 0, 3)
|
|
offer.item_tier = item_tier
|
|
offer.region_band = 0
|
|
offer.list_price = 12 + 6 * item_tier
|
|
for offer in board.battle_offers:
|
|
offer.entry_cost = 0
|
|
board.erase("board_hash")
|
|
board.board_hash = _hash(board)
|
|
return board
|
|
|
|
|
|
func _offer_ids(offers: Array, id_field: String) -> Array[String]:
|
|
var result: Array[String] = []
|
|
for offer in offers:
|
|
result.append(String(offer.get(id_field, "")))
|
|
return result
|
|
|
|
|
|
func _is_hex(value: String, length: int) -> bool:
|
|
if value.length() != length:
|
|
return false
|
|
for character in value:
|
|
if character not in "0123456789abcdef":
|
|
return false
|
|
return true
|
|
|
|
|
|
func _digest(seed: String, label: String, parts: Array = []) -> String:
|
|
var framed: Array[String] = ["%d:%s" % [seed.length(), seed], "%d:%s" % [label.length(), label]]
|
|
for part in parts:
|
|
var value := str(part)
|
|
framed.append("%d:%s" % [value.length(), value])
|
|
return "|".join(framed).sha256_text()
|
|
|
|
|
|
func _low128(value: String) -> String:
|
|
return value.substr(0, 32)
|
|
|
|
|
|
func _hash(value) -> String:
|
|
return JSON.stringify(_canonical(value)).sha256_text()
|
|
|
|
|
|
func _hash_without(value: Dictionary, field: String) -> String:
|
|
var copy := value.duplicate(true)
|
|
copy.erase(field)
|
|
return _hash(copy)
|
|
|
|
|
|
func _owned_recruit_ids(company: Dictionary) -> Array[String]:
|
|
var ids: Array[String] = []
|
|
for recruit in company.owned_recruits:
|
|
ids.append(String(recruit.recruit_id))
|
|
return ids
|
|
|
|
|
|
func _canonical(value):
|
|
if value is float and is_equal_approx(value, round(value)):
|
|
return int(round(value))
|
|
if value is Dictionary:
|
|
var result := {}
|
|
var keys: Array = value.keys()
|
|
keys.sort_custom(func(left, right): return String(left) < String(right))
|
|
for key in keys:
|
|
result[key] = _canonical(value[key])
|
|
return result
|
|
if value is Array:
|
|
var result: Array = []
|
|
for item in value:
|
|
result.append(_canonical(item))
|
|
return result
|
|
return value
|