1036 lines
45 KiB
GDScript
1036 lines
45 KiB
GDScript
class_name P102RuntimeController
|
|
extends "res://p10_1/runtime_controller.gd"
|
|
|
|
const P101State = preload("res://p10_1/region_run_state.gd")
|
|
const P101Save = preload("res://p10_1/save_v7_service.gd")
|
|
const P102Content = preload("res://p10_2/content_registry.gd")
|
|
const P102BoardService = preload("res://p10_2/market_board_service.gd")
|
|
const P102State = preload("res://p10_2/run_state.gd")
|
|
const P102MarketService = preload("res://p10_2/market_service.gd")
|
|
const P102BattleService = preload("res://p10_2/mid_contract_battle_service.gd")
|
|
const P102ResolutionService = preload("res://p10_2/mid_contract_resolution_service.gd")
|
|
const P102Save = preload("res://p10_2/save_v8_service.gd")
|
|
const P102Motion = preload("res://p10_2/motion_presenter.gd")
|
|
|
|
const P102_SAVE_PATH := "user://aetherbound_guild_p10_2.json"
|
|
const P101_SAVE_PATH := "user://aetherbound_guild_p10_1.json"
|
|
const P102_SCREEN_MARKET := "siltwake_market"
|
|
const P102_SCREEN_PLAN := "siltwake_plan"
|
|
const P102_SCREEN_BATTLE := "siltwake_battle"
|
|
const P102_SCREEN_RECOVERY := "siltwake_recovery"
|
|
const P102_SCREEN_MARKET_5 := "siltwake_market_5"
|
|
|
|
var p102_mode := false
|
|
var p102_catalog: Dictionary = P102Content.snapshot()
|
|
var p102_save = P102Save.new()
|
|
var p102_board = P102BoardService.new()
|
|
var p102_focused_company := ""
|
|
|
|
|
|
func _bootstrap_market() -> void:
|
|
p102_mode = false
|
|
var snapshot := _p102_entry_snapshot()
|
|
if snapshot.is_empty() and persistence_enabled:
|
|
var loaded: Dictionary = p102_save.load_slot(ProjectSettings.globalize_path(P102_SAVE_PATH))
|
|
if loaded.ok:
|
|
snapshot = Dictionary(loaded.snapshot).duplicate(true)
|
|
region_save_sequence = int(loaded.sequence)
|
|
region_save_commit_hash = String(loaded.commit_hash)
|
|
if snapshot.is_empty() and persistence_enabled:
|
|
var legacy: Dictionary = P101Save.new().load_slot(ProjectSettings.globalize_path(P101_SAVE_PATH))
|
|
if legacy.ok:
|
|
snapshot = _migrate_market_two(Dictionary(legacy.snapshot))
|
|
region_save_sequence = int(legacy.get("sequence", 0))
|
|
region_save_commit_hash = ""
|
|
if snapshot.is_empty():
|
|
super._bootstrap_market()
|
|
return
|
|
_activate_p102(snapshot)
|
|
|
|
|
|
func _p102_entry_snapshot() -> Dictionary:
|
|
if String(entry_snapshot.get("state_kind", "")) == P102State.STATE_KIND:
|
|
return entry_snapshot.duplicate(true)
|
|
if String(entry_snapshot.get("state_kind", "")) == P101State.STATE_KIND:
|
|
return _migrate_market_two(entry_snapshot)
|
|
return {}
|
|
|
|
|
|
func _migrate_market_two(snapshot: Dictionary) -> Dictionary:
|
|
var source := P101State.new()
|
|
if not source.restore_snapshot(snapshot):
|
|
return {}
|
|
var run: Dictionary = source.current_run
|
|
if String(run.get("phase", "")) != "market_2" or int(run.get("market_index", -1)) != 2:
|
|
return {}
|
|
var generated: Dictionary = p102_board.generate(String(run.run_seed), 2, 0)
|
|
if not generated.ok:
|
|
return {}
|
|
var migrated := P102State.new()
|
|
return migrated.snapshot() if migrated.initialize_from_p101(source.snapshot(), generated.board) else {}
|
|
|
|
|
|
func _activate_p102(snapshot: Dictionary) -> bool:
|
|
var service = P102MarketService.new(snapshot)
|
|
if not service.valid:
|
|
return false
|
|
region_market = service
|
|
region_mode = true
|
|
p102_mode = true
|
|
region_catalog = p102_catalog
|
|
region_motion = P102Motion.new()
|
|
_prepare_base_content()
|
|
_restore_p102_screen()
|
|
_reset_region_focus()
|
|
p102_focused_company = ""
|
|
last_code = "p102_resumed"
|
|
_queue_render()
|
|
return true
|
|
|
|
|
|
func _restore_region_screen() -> void:
|
|
if not p102_mode:
|
|
super._restore_region_screen()
|
|
return
|
|
_restore_p102_screen()
|
|
|
|
|
|
func _restore_p102_screen() -> void:
|
|
if region_market == null or region_market.state.current_run.is_empty():
|
|
screen = P102_SCREEN_MARKET
|
|
return
|
|
match String(region_market.state.current_run.phase):
|
|
"prepared":
|
|
screen = P102_SCREEN_PLAN
|
|
"recovery":
|
|
screen = P102_SCREEN_RECOVERY
|
|
"market_5":
|
|
screen = P102_SCREEN_MARKET_5
|
|
_:
|
|
screen = P102_SCREEN_MARKET
|
|
|
|
|
|
func _process(delta: float) -> void:
|
|
if not p102_mode:
|
|
super._process(delta)
|
|
return
|
|
if screen == P102_SCREEN_BATTLE and not region_battle_paused:
|
|
region_motion_clock += maxf(0.0, delta)
|
|
if not low_power:
|
|
world_clock += maxf(0.0, delta)
|
|
_update_ambient_actors()
|
|
_animate_region_battle_actors()
|
|
if (
|
|
screen != P102_SCREEN_BATTLE
|
|
or region_battle_complete
|
|
or region_battle_paused
|
|
or not battle_auto_advance
|
|
):
|
|
return
|
|
if region_motion.playback_held(region_motion_clock, reduced_motion):
|
|
return
|
|
region_battle_accumulator += maxf(0.0, delta) * float(region_battle_speed)
|
|
var ticks := int(floor(region_battle_accumulator / REGION_TICK_SECONDS))
|
|
if ticks > 0:
|
|
region_battle_accumulator -= float(ticks) * REGION_TICK_SECONDS
|
|
advance_region_battle_ticks(ticks)
|
|
|
|
|
|
func _render_current() -> void:
|
|
if not p102_mode:
|
|
super._render_current()
|
|
return
|
|
if display_root == null:
|
|
return
|
|
_clear_display()
|
|
line_touch_rects.clear()
|
|
set_meta("visible_world_ratio", 0.88)
|
|
if size.y > size.x:
|
|
_render_rotate_notice()
|
|
else:
|
|
match screen:
|
|
P102_SCREEN_PLAN:
|
|
_render_p102_plan()
|
|
P102_SCREEN_BATTLE:
|
|
_render_region_battle()
|
|
P102_SCREEN_RECOVERY:
|
|
_render_p102_recovery()
|
|
P102_SCREEN_MARKET_5:
|
|
_render_p102_market_five()
|
|
_:
|
|
_render_p102_market()
|
|
_add_settings_button()
|
|
if settings_open:
|
|
_render_settings_band()
|
|
|
|
|
|
func tap_region_recruit(offer_id: String) -> void:
|
|
if not p102_mode:
|
|
super.tap_region_recruit(offer_id)
|
|
return
|
|
if not _p102_market_ready():
|
|
return
|
|
if region_focused_recruit != offer_id:
|
|
region_focused_recruit = offer_id
|
|
region_focused_equipment = ""
|
|
region_focused_route = ""
|
|
last_code = "recruit_inspected"
|
|
_queue_render()
|
|
return
|
|
var offer := _region_offer("recruit", offer_id)
|
|
if offer.is_empty():
|
|
return
|
|
var result: Dictionary = region_market.hire(
|
|
_region_operation("hire", offer_id),
|
|
region_market.state.revision,
|
|
offer_id,
|
|
int(offer.list_price)
|
|
)
|
|
_accept_region_result(result)
|
|
if result.ok:
|
|
region_focused_recruit = ""
|
|
pending_line.assign(region_market.state.current_run.deployed_recruit_ids)
|
|
|
|
|
|
func tap_region_equipment(offer_id: String) -> void:
|
|
if not p102_mode:
|
|
super.tap_region_equipment(offer_id)
|
|
return
|
|
if not _p102_market_ready():
|
|
return
|
|
if region_focused_equipment != offer_id:
|
|
region_focused_equipment = offer_id
|
|
region_focused_recruit = ""
|
|
region_focused_route = ""
|
|
last_code = "equipment_inspected"
|
|
_queue_render()
|
|
return
|
|
var offer := _region_offer("equipment", offer_id)
|
|
if offer.is_empty():
|
|
return
|
|
var result: Dictionary = region_market.buy_equipment(
|
|
_region_operation("buy", offer_id),
|
|
region_market.state.revision,
|
|
offer_id,
|
|
int(offer.list_price)
|
|
)
|
|
_accept_region_result(result)
|
|
if result.ok:
|
|
region_selected_instance = String(result.receipt.instance_id)
|
|
region_focused_equipment = ""
|
|
|
|
|
|
func tap_region_company(recruit_id: String) -> void:
|
|
if not p102_mode:
|
|
super.tap_region_company(recruit_id)
|
|
return
|
|
if not _p102_market_ready() or recruit_id not in region_market.state.current_run.company:
|
|
return
|
|
if not region_selected_instance.is_empty():
|
|
var equipped: Dictionary = region_market.equip(
|
|
_region_operation("equip", recruit_id),
|
|
region_market.state.revision,
|
|
recruit_id,
|
|
region_selected_instance
|
|
)
|
|
_accept_region_result(equipped)
|
|
if equipped.ok:
|
|
region_selected_instance = ""
|
|
p102_focused_company = recruit_id
|
|
return
|
|
var run: Dictionary = region_market.state.current_run
|
|
if p102_focused_company == recruit_id:
|
|
var deployment: Array = Array(run.deployed_recruit_ids).duplicate()
|
|
if recruit_id in deployment and deployment.size() > 1:
|
|
deployment.erase(recruit_id)
|
|
_commit_p102_deployment(deployment, "to-reserve")
|
|
return
|
|
if recruit_id in run.reserve_recruit_ids and deployment.size() < int(run.line_capacity):
|
|
deployment.append(recruit_id)
|
|
_commit_p102_deployment(deployment, "to-line")
|
|
return
|
|
if not p102_focused_company.is_empty() and p102_focused_company != recruit_id:
|
|
var one_deployed: bool = p102_focused_company in run.deployed_recruit_ids
|
|
var other_deployed: bool = recruit_id in run.deployed_recruit_ids
|
|
if one_deployed != other_deployed:
|
|
_swap_deployed_and_reserve(p102_focused_company, recruit_id)
|
|
return
|
|
p102_focused_company = recruit_id
|
|
region_focused_company = recruit_id
|
|
last_code = "company_inspected"
|
|
_queue_render()
|
|
|
|
|
|
func tap_region_route(offer_id: String) -> void:
|
|
if not p102_mode:
|
|
super.tap_region_route(offer_id)
|
|
return
|
|
if not _p102_market_ready():
|
|
return
|
|
region_focused_route = offer_id
|
|
region_focused_recruit = ""
|
|
region_focused_equipment = ""
|
|
last_code = "route_inspected"
|
|
_queue_render()
|
|
|
|
|
|
func commit_region_route() -> void:
|
|
if not p102_mode:
|
|
super.commit_region_route()
|
|
return
|
|
if not _p102_market_ready() or region_focused_route.is_empty():
|
|
return
|
|
var result: Dictionary = region_market.commit_route(
|
|
_region_operation("route", region_focused_route),
|
|
region_market.state.revision,
|
|
region_focused_route,
|
|
String(region_market.state.current_run.market_board.board_hash)
|
|
)
|
|
_accept_region_result(result)
|
|
if result.ok:
|
|
screen = P102_SCREEN_PLAN
|
|
_save_region()
|
|
_queue_render()
|
|
|
|
|
|
func choose_region_plan(plan_id: String) -> void:
|
|
if not p102_mode:
|
|
super.choose_region_plan(plan_id)
|
|
return
|
|
if region_market == null or String(region_market.state.current_run.get("phase", "")) != "prepared":
|
|
return
|
|
var result: Dictionary = P102BattleService.new(region_market.state.snapshot()).run_to_terminal(plan_id)
|
|
last_code = String(result.code)
|
|
if not result.ok:
|
|
_queue_render()
|
|
return
|
|
region_plan_id = plan_id
|
|
region_battle_snapshot = Dictionary(result.snapshot).duplicate(true)
|
|
region_battle_tick = 0
|
|
region_battle_terminal_tick = int(region_battle_snapshot.tick)
|
|
region_battle_accumulator = 0.0
|
|
region_battle_complete = false
|
|
region_battle_paused = false
|
|
region_battle_speed = 1
|
|
region_event_cursor = 0
|
|
region_recent_events.clear()
|
|
region_event_counts.clear()
|
|
region_actor_order = Array(region_market.state.current_run.deployed_recruit_ids).duplicate()
|
|
region_display_hp.clear()
|
|
for recruit_id in region_actor_order:
|
|
region_display_hp[recruit_id] = int(region_market.state.current_run.recruit_records[recruit_id].current_hp)
|
|
region_safe_index = int(region_market.state.current_run.pending_encounter.tide_disclosure.current_safe_index)
|
|
region_motion_clock = 0.0
|
|
region_motion.reset(region_actor_order, region_safe_index)
|
|
screen = P102_SCREEN_BATTLE
|
|
_queue_render()
|
|
|
|
|
|
func advance_region_battle_ticks(ticks: int) -> Dictionary:
|
|
if not p102_mode:
|
|
return super.advance_region_battle_ticks(ticks)
|
|
if screen != P102_SCREEN_BATTLE or region_battle_complete or ticks <= 0:
|
|
return {"ok": false, "code": "battle_inactive", "events": []}
|
|
region_battle_tick = mini(region_battle_terminal_tick, region_battle_tick + ticks)
|
|
var emitted: Array[Dictionary] = []
|
|
var events: Array = region_battle_snapshot.get("event_log", [])
|
|
while region_event_cursor < events.size() and int(events[region_event_cursor].tick) <= region_battle_tick:
|
|
var event: Dictionary = Dictionary(events[region_event_cursor]).duplicate(true)
|
|
region_event_cursor += 1
|
|
emitted.append(event)
|
|
_consume_region_event(event)
|
|
if region_battle_tick >= region_battle_terminal_tick:
|
|
region_battle_complete = true
|
|
last_code = "mid_contract_playback_terminal"
|
|
else:
|
|
last_code = "mid_contract_playing"
|
|
_queue_render()
|
|
return {"ok": true, "code": last_code, "events": emitted, "tick": region_battle_tick}
|
|
|
|
|
|
func toggle_region_battle_pause() -> void:
|
|
if not p102_mode:
|
|
super.toggle_region_battle_pause()
|
|
return
|
|
if screen == P102_SCREEN_BATTLE and not region_battle_complete:
|
|
region_battle_paused = not region_battle_paused
|
|
_queue_render()
|
|
|
|
|
|
func toggle_region_battle_speed() -> void:
|
|
if not p102_mode:
|
|
super.toggle_region_battle_speed()
|
|
return
|
|
if screen == P102_SCREEN_BATTLE and not region_battle_complete:
|
|
region_battle_speed = 2 if region_battle_speed == 1 else 1
|
|
_queue_render()
|
|
|
|
|
|
func settle_region_battle() -> void:
|
|
if not p102_mode:
|
|
super.settle_region_battle()
|
|
return
|
|
if not region_battle_complete or region_battle_snapshot.is_empty():
|
|
return
|
|
var resolver = P102ResolutionService.new(region_market.state.snapshot())
|
|
var result: Dictionary = resolver.settle(
|
|
_region_operation("settle", String(region_battle_snapshot.snapshot_hash).left(8)),
|
|
resolver.state.revision,
|
|
region_battle_snapshot
|
|
)
|
|
last_code = String(result.code)
|
|
if not result.ok:
|
|
_queue_render()
|
|
return
|
|
region_market = P102MarketService.new(result.snapshot)
|
|
_reset_region_focus()
|
|
p102_focused_company = ""
|
|
_save_region()
|
|
_restore_p102_screen()
|
|
_queue_render()
|
|
|
|
|
|
func recover_region_company() -> void:
|
|
if not p102_mode:
|
|
super.recover_region_company()
|
|
return
|
|
if region_market == null or String(region_market.state.current_run.get("phase", "")) != "recovery":
|
|
return
|
|
var run: Dictionary = region_market.state.current_run
|
|
var report: Dictionary = run.last_battle_report
|
|
var resolver = P102ResolutionService.new(region_market.state.snapshot())
|
|
var result: Dictionary = resolver.recover(
|
|
_region_operation("recover", String(report.get("battle_hash", "")).left(8)),
|
|
resolver.state.revision,
|
|
String(report.get("battle_hash", "")),
|
|
int(run.recovery_cost)
|
|
)
|
|
last_code = String(result.code)
|
|
if result.ok:
|
|
region_market = P102MarketService.new(result.snapshot)
|
|
pending_line.assign(region_market.state.current_run.deployed_recruit_ids)
|
|
_reset_region_focus()
|
|
p102_focused_company = ""
|
|
_save_region()
|
|
screen = P102_SCREEN_MARKET
|
|
_queue_render()
|
|
|
|
|
|
func _commit_line_drag(source_id: String, target_id: String) -> void:
|
|
if not p102_mode:
|
|
super._commit_line_drag(source_id, target_id)
|
|
return
|
|
if not _p102_market_ready() or source_id.is_empty() or target_id.is_empty() or source_id == target_id:
|
|
return
|
|
var run: Dictionary = region_market.state.current_run
|
|
var source_deployed: bool = source_id in run.deployed_recruit_ids
|
|
var target_deployed: bool = target_id in run.deployed_recruit_ids
|
|
if source_deployed != target_deployed:
|
|
_swap_deployed_and_reserve(source_id, target_id)
|
|
return
|
|
if not source_deployed:
|
|
return
|
|
var order: Array = Array(run.deployed_recruit_ids).duplicate()
|
|
order.erase(source_id)
|
|
order.insert(order.find(target_id), source_id)
|
|
_commit_p102_deployment(order, "drag")
|
|
|
|
|
|
func _swap_deployed_and_reserve(first_id: String, second_id: String) -> void:
|
|
var run: Dictionary = region_market.state.current_run
|
|
var deployed_id := first_id if first_id in run.deployed_recruit_ids else second_id
|
|
var reserve_id := second_id if deployed_id == first_id else first_id
|
|
if reserve_id not in run.reserve_recruit_ids:
|
|
return
|
|
var order: Array = Array(run.deployed_recruit_ids).duplicate()
|
|
var index := order.find(deployed_id)
|
|
if index < 0:
|
|
return
|
|
order[index] = reserve_id
|
|
_commit_p102_deployment(order, "reserve-swap")
|
|
|
|
|
|
func _commit_p102_deployment(order: Array, suffix: String) -> void:
|
|
var result: Dictionary = region_market.commit_deployment(
|
|
_region_operation("deploy", suffix),
|
|
region_market.state.revision,
|
|
order
|
|
)
|
|
_accept_region_result(result)
|
|
if result.ok:
|
|
pending_line.assign(region_market.state.current_run.deployed_recruit_ids)
|
|
p102_focused_company = ""
|
|
|
|
|
|
func _consume_region_event(event: Dictionary) -> void:
|
|
if not p102_mode:
|
|
super._consume_region_event(event)
|
|
return
|
|
var kind := String(event.get("kind", ""))
|
|
var payload: Dictionary = Dictionary(event.get("payload", {}))
|
|
region_motion.consume(event, region_motion_clock)
|
|
region_actor_order = Array(region_motion.actor_order).duplicate()
|
|
region_event_counts[kind] = int(region_event_counts.get(kind, 0)) + 1
|
|
region_recent_events.append(event.duplicate(true))
|
|
if region_recent_events.size() > 4:
|
|
region_recent_events.pop_front()
|
|
if kind == "tide_shifted":
|
|
region_safe_index = int(payload.get("safe_index", region_safe_index))
|
|
elif kind == "authoritative_damage":
|
|
region_display_hp[String(event.get("target_id", ""))] = int(payload.get("hp_after", 0))
|
|
elif kind == "fallen":
|
|
region_display_hp[String(event.get("target_id", ""))] = 0
|
|
|
|
|
|
func _save_region() -> bool:
|
|
if not p102_mode:
|
|
return super._save_region()
|
|
if not persistence_enabled or region_market == null:
|
|
return true
|
|
var result: Dictionary = p102_save.write_slot(
|
|
ProjectSettings.globalize_path(P102_SAVE_PATH),
|
|
region_market.state.snapshot(),
|
|
region_save_sequence + 1,
|
|
region_save_commit_hash
|
|
)
|
|
if not result.ok:
|
|
last_code = "p102_%s" % String(result.code)
|
|
return false
|
|
region_save_sequence = int(result.sequence)
|
|
region_save_commit_hash = String(result.commit_hash)
|
|
return true
|
|
|
|
|
|
func _region_operation(kind: String, target: String) -> String:
|
|
if not p102_mode:
|
|
return super._region_operation(kind, target)
|
|
operation_index += 1
|
|
return "p102-ui-%s-%d-%d-%s" % [kind, region_market.state.revision, operation_index, target]
|
|
|
|
|
|
func _region_offer(kind: String, offer_id: String) -> Dictionary:
|
|
if not p102_mode:
|
|
return super._region_offer(kind, offer_id)
|
|
if region_market == null or region_market.state.current_run.is_empty():
|
|
return {}
|
|
var field := String({"recruit": "recruit_offers", "equipment": "equipment_offers", "route": "battle_offers"}.get(kind, ""))
|
|
var id_field := "source_offer_id" if kind == "recruit" else "offer_id"
|
|
for offer in region_market.state.current_run.market_board.get(field, []):
|
|
if String(offer.get(id_field, "")) == offer_id:
|
|
return Dictionary(offer).duplicate(true)
|
|
return {}
|
|
|
|
|
|
func _p102_market_ready() -> bool:
|
|
return (
|
|
p102_mode
|
|
and region_market != null
|
|
and not region_market.state.current_run.is_empty()
|
|
and String(region_market.state.current_run.phase) == "market"
|
|
)
|
|
|
|
|
|
func _render_p102_market() -> void:
|
|
_add_siltwake_world()
|
|
var run: Dictionary = region_market.state.current_run
|
|
var tide: Dictionary = run.market_board.tide_disclosure
|
|
_add_header(
|
|
_tr("淤潮地 · 集市 %d" % run.market_index, "SILTWAKE · MARKET %d" % run.market_index),
|
|
_tr("◆%d 战线 %d/%d 预备 %d 安全位 %d→%d" % [run.coin, run.deployed_recruit_ids.size(), run.line_capacity, run.reserve_recruit_ids.size(), tide.current_safe_index, tide.next_safe_index], "◆%d LINE %d/%d RESERVE %d SAFE %d→%d" % [run.coin, run.deployed_recruit_ids.size(), run.line_capacity, run.reserve_recruit_ids.size(), tide.current_safe_index, tide.next_safe_index])
|
|
)
|
|
_render_tide_notches(tide, 0)
|
|
_render_p102_recruit_pier()
|
|
_render_p102_equipment_bank()
|
|
_render_p102_route_posts()
|
|
_render_p102_company()
|
|
_render_p102_context()
|
|
|
|
|
|
func _render_p102_recruit_pier() -> void:
|
|
var run: Dictionary = region_market.state.current_run
|
|
var offers: Array = run.market_board.recruit_offers
|
|
var actor_size := clampf(size.y * 0.18, 58.0, 72.0)
|
|
var caption := _outlined_label(_tr("渡口候选", "FERRY RECRUITS"), 9, GOLD_LIGHT)
|
|
caption.position = Vector2(18, 56)
|
|
caption.size = Vector2(size.x * 0.34, 26)
|
|
display_root.add_child(caption)
|
|
for index in range(offers.size()):
|
|
var offer: Dictionary = offers[index]
|
|
var offer_id := String(offer.source_offer_id)
|
|
var available: bool = offer_id in run.available_recruit_offer_ids
|
|
var x := 18.0 + index * (actor_size + 8.0)
|
|
var y := 82.0
|
|
_add_ground_ring(Vector2(x + actor_size * 0.5, y + actor_size - 3), actor_size * 0.72, GOLD if region_focused_recruit == offer_id else CYAN, "P102RecruitRing_%s" % offer_id)
|
|
var actor := _add_actor(_p100_recruit_rig(offer), Vector2(x, y), actor_size, "P102RecruitActor_%s" % offer_id, true)
|
|
actor.self_modulate = _p100_profession_color(String(offer.profession_id))
|
|
actor.modulate.a = 1.0 if available else 0.24
|
|
if available:
|
|
display_root.add_child(_actor_target("P102RecruitTarget_%s" % offer_id, Vector2(x - 3, y - 4), Vector2(actor_size + 6, actor_size + 38), tap_region_recruit.bind(offer_id)))
|
|
var label := _outlined_label("%s\nLv.%d ◆%d" % [_generated_name(offer).left(10), int(offer.level), int(offer.list_price)], 7, GOLD_LIGHT if region_focused_recruit == offer_id else WHITE)
|
|
label.position = Vector2(x - 7, y + actor_size - 2)
|
|
label.size = Vector2(actor_size + 14, 38)
|
|
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
display_root.add_child(label)
|
|
|
|
|
|
func _render_p102_equipment_bank() -> void:
|
|
var run: Dictionary = region_market.state.current_run
|
|
var offers: Array = run.market_board.equipment_offers
|
|
var x0 := size.x * 0.35
|
|
var width := size.x * 0.36
|
|
var spacing := width / 4.0
|
|
var caption := _outlined_label(_tr("防潮装备", "TIDE GEAR"), 9, GOLD_LIGHT)
|
|
caption.position = Vector2(x0, 56)
|
|
caption.size = Vector2(width, 26)
|
|
display_root.add_child(caption)
|
|
for index in range(offers.size()):
|
|
var offer: Dictionary = offers[index]
|
|
var offer_id := String(offer.offer_id)
|
|
var available: bool = offer_id in run.available_equipment_offer_ids
|
|
var x := x0 + index * spacing
|
|
var color := _equipment_color(String(offer.catalog_id))
|
|
var icon := _outlined_label(_equipment_symbol(String(offer.catalog_id)), 22, color)
|
|
icon.position = Vector2(x, 82)
|
|
icon.size = Vector2(spacing - 4, 48)
|
|
icon.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
display_root.add_child(icon)
|
|
if available:
|
|
display_root.add_child(_actor_target("P102EquipmentTarget_%s" % offer_id, Vector2(x, 80), Vector2(spacing - 4, 86), tap_region_equipment.bind(offer_id)))
|
|
var label := _outlined_label("%s\n◆%d" % [_region_name(String(offer.catalog_id)).left(8), int(offer.list_price)], 7, GOLD_LIGHT if region_focused_equipment == offer_id else WHITE)
|
|
label.position = Vector2(x - 4, 126)
|
|
label.size = Vector2(spacing + 4, 40)
|
|
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
label.modulate.a = 1.0 if available else 0.3
|
|
display_root.add_child(label)
|
|
|
|
|
|
func _render_p102_route_posts() -> void:
|
|
var offers: Array = region_market.state.current_run.market_board.battle_offers
|
|
var x := size.x - 202.0
|
|
var caption := _outlined_label(_tr("三条水路", "THREE ROUTES"), 9, GOLD_LIGHT)
|
|
caption.position = Vector2(x, 56)
|
|
caption.size = Vector2(184, 26)
|
|
display_root.add_child(caption)
|
|
for index in range(offers.size()):
|
|
var offer: Dictionary = offers[index]
|
|
var offer_id := String(offer.offer_id)
|
|
var y := 82.0 + index * 54.0
|
|
var selected := region_focused_route == offer_id
|
|
var line := ColorRect.new()
|
|
line.name = "P102RouteLine_%s" % offer_id
|
|
line.position = Vector2(x, y + 38)
|
|
line.size = Vector2(174, 4)
|
|
line.color = GOLD if selected else [JADE, CYAN, CORAL][int(offer.risk)]
|
|
line.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
display_root.add_child(line)
|
|
var label := _outlined_label(_tr("风险%d %s" % [offer.risk, _region_name(String(offer.route_id))], "RISK %d %s" % [offer.risk, _region_name(String(offer.route_id))]), 8, GOLD_LIGHT if selected else WHITE)
|
|
label.position = Vector2(x, y)
|
|
label.size = Vector2(174, 38)
|
|
label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
|
display_root.add_child(label)
|
|
display_root.add_child(_actor_target("P102RouteTarget_%s" % offer_id, Vector2(x - 4, y), Vector2(182, 48), tap_region_route.bind(offer_id)))
|
|
|
|
|
|
func _render_p102_company() -> void:
|
|
var run: Dictionary = region_market.state.current_run
|
|
var actor_size := clampf(size.y * 0.15, 52.0, 64.0)
|
|
var y := size.y - 150.0
|
|
var x0 := size.x * 0.20
|
|
var available_width := size.x * 0.52
|
|
var spacing := minf(actor_size + 8.0, available_width / maxf(1.0, float(run.line_capacity)))
|
|
var caption := _outlined_label(_tr("战线 %d/%d · 预备 %d" % [run.deployed_recruit_ids.size(), run.line_capacity, run.reserve_recruit_ids.size()], "LINE %d/%d · RESERVE %d" % [run.deployed_recruit_ids.size(), run.line_capacity, run.reserve_recruit_ids.size()]), 9, GOLD_LIGHT)
|
|
caption.position = Vector2(x0, y - 24)
|
|
caption.size = Vector2(available_width, 22)
|
|
display_root.add_child(caption)
|
|
for slot in range(int(run.line_capacity)):
|
|
var x := x0 + slot * spacing
|
|
_add_ground_ring(Vector2(x + actor_size * 0.5, y + actor_size - 3), actor_size * 0.68, Color(0.36, 0.82, 0.64, 0.36), "P102CapacitySlot_%d" % slot)
|
|
if slot >= run.deployed_recruit_ids.size():
|
|
continue
|
|
var recruit_id := String(run.deployed_recruit_ids[slot])
|
|
_render_p102_company_actor(recruit_id, Vector2(x, y), actor_size, false)
|
|
var reserve_x := size.x * 0.73
|
|
for index in range(run.reserve_recruit_ids.size()):
|
|
var recruit_id := String(run.reserve_recruit_ids[index])
|
|
_render_p102_company_actor(recruit_id, Vector2(reserve_x + index * (actor_size * 0.72), y + 10), actor_size * 0.76, true)
|
|
|
|
|
|
func _render_p102_company_actor(recruit_id: String, position_value: Vector2, actor_size: float, reserve: bool) -> void:
|
|
var run: Dictionary = region_market.state.current_run
|
|
var record: Dictionary = run.recruit_records[recruit_id]
|
|
var prefix := "P102Reserve" if reserve else "P102Company"
|
|
var ring_color := GOLD if p102_focused_company == recruit_id else VIOLET if reserve else JADE
|
|
_add_ground_ring(position_value + Vector2(actor_size * 0.5, actor_size - 3), actor_size * 0.70, ring_color, "%sRing_%s" % [prefix, recruit_id])
|
|
var actor := _add_actor(_p100_recruit_rig(record), position_value, actor_size, "%sActor_%s" % [prefix, recruit_id], true)
|
|
actor.self_modulate = _p100_profession_color(String(record.profession_id))
|
|
var rect := Rect2(position_value - Vector2(3, 4), Vector2(actor_size + 6, actor_size + 34))
|
|
line_touch_rects[recruit_id] = rect
|
|
display_root.add_child(_actor_target("%sTarget_%s" % [prefix, recruit_id], rect.position, rect.size, tap_region_company.bind(recruit_id)))
|
|
var item_id := String(run.equipped_by_recruit.get(recruit_id, ""))
|
|
var catalog_id := String(run.inventory.get(item_id, {}).get("catalog_id", ""))
|
|
var label := _outlined_label("Lv.%d %dHP\n%s" % [record.level, record.current_hp, _region_name(catalog_id).left(7) if catalog_id.begins_with("EQ-") else _short_profession(String(record.profession_id))], 7, WHITE)
|
|
label.position = position_value + Vector2(-6, actor_size - 2)
|
|
label.size = Vector2(actor_size + 12, 36)
|
|
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
display_root.add_child(label)
|
|
|
|
|
|
func _render_p102_context() -> void:
|
|
var band := ColorRect.new()
|
|
band.name = "P102MarketContextBand"
|
|
band.position = Vector2(0, size.y - 58)
|
|
band.size = Vector2(size.x, 58)
|
|
band.color = Color(0.015, 0.035, 0.075, 0.95)
|
|
band.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
display_root.add_child(band)
|
|
var context := _outlined_label(_p102_context_text(), 9, WHITE)
|
|
context.position = Vector2(14, size.y - 54)
|
|
context.size = Vector2(size.x - 242, 48)
|
|
context.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
display_root.add_child(context)
|
|
var depart := _action_button("P102PrepareRouteButton", _tr("准备第%d轮" % region_market.state.current_run.round_index, "PREPARE ROUND %d" % region_market.state.current_run.round_index), commit_region_route)
|
|
depart.position = Vector2(size.x - 220, size.y - 54)
|
|
depart.size = Vector2(204, 48)
|
|
depart.disabled = region_focused_route.is_empty()
|
|
display_root.add_child(depart)
|
|
|
|
|
|
func _p102_context_text() -> String:
|
|
if not region_selected_instance.is_empty():
|
|
var item: Dictionary = region_market.state.current_run.inventory.get(region_selected_instance, {})
|
|
return _tr("已购买 %s;点战线或预备队员替换其唯一装备。" % _region_name(String(item.get("catalog_id", ""))), "BOUGHT %s; TAP A LINE OR RESERVE RECRUIT TO REPLACE THEIR ONE ITEM." % _region_name(String(item.get("catalog_id", ""))))
|
|
if not region_focused_recruit.is_empty():
|
|
var recruit := _region_offer("recruit", region_focused_recruit)
|
|
return _tr("%s · Lv.%d · ◆%d;再点一次雇佣,超出容量会进入预备队。" % [_generated_name(recruit), recruit.level, recruit.list_price], "%s · LV.%d · ◆%d; TAP AGAIN TO HIRE. OVER CAPACITY ENTERS RESERVE." % [_generated_name(recruit), recruit.level, recruit.list_price])
|
|
if not region_focused_equipment.is_empty():
|
|
var equipment := _region_offer("equipment", region_focused_equipment)
|
|
return _tr("%s · ◆%d;再点购买,然后点队员装备。" % [_region_name(String(equipment.catalog_id)), equipment.list_price], "%s · ◆%d; TAP AGAIN TO BUY, THEN TAP A RECRUIT." % [_region_name(String(equipment.catalog_id)), equipment.list_price])
|
|
if not region_focused_route.is_empty():
|
|
return _route_summary(int(_region_offer("route", region_focused_route).risk))
|
|
if not p102_focused_company.is_empty():
|
|
var run: Dictionary = region_market.state.current_run
|
|
return _tr("已选队员:再点本人切换战线/预备,或点另一侧队员交换。", "RECRUIT SELECTED: TAP AGAIN TO MOVE LINE/RESERVE, OR TAP THE OTHER SIDE TO SWAP.") if p102_focused_company in run.company else ""
|
|
return _tr("先看路线压力,再调整战线、预备队与装备;容量会在第2、4轮后增长。", "READ ROUTE PRESSURE, THEN ADAPT LINE, RESERVE AND GEAR. CAPACITY GROWS AFTER ROUNDS 2 AND 4.")
|
|
|
|
|
|
func _render_p102_plan() -> void:
|
|
_add_siltwake_world()
|
|
var encounter: Dictionary = region_market.state.current_run.pending_encounter
|
|
var tide: Dictionary = encounter.tide_disclosure
|
|
_add_header(_tr("第%d轮 · 选择应对" % encounter.round_index, "ROUND %d · CHOOSE RESPONSE" % encounter.round_index), _tr("%s · 风险%d · 安全位 %d→%d" % [_region_name(String(encounter.route_id)), encounter.risk, tide.current_safe_index, tide.next_safe_index], "%s · RISK %d · SAFE %d→%d" % [_region_name(String(encounter.route_id)), encounter.risk, tide.current_safe_index, tide.next_safe_index]))
|
|
_render_tide_notches(tide, 0)
|
|
_render_plan_company()
|
|
var hint := _outlined_label(_route_summary(int(encounter.risk)), 10, WHITE)
|
|
hint.position = Vector2(size.x * 0.13, 62)
|
|
hint.size = Vector2(size.x * 0.74, 48)
|
|
hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
display_root.add_child(hint)
|
|
var specs := [
|
|
["hold_line", _tr("稳住战线", "HOLD LINE"), _tr("保持顺序,抵抗拉拽", "KEEP ORDER; RESIST PULL"), GOLD],
|
|
["follow_tide", _tr("顺潮移动", "FOLLOW TIDE"), _tr("保留移动,避开标记", "SAVE MOVEMENT; EVADE MARKS"), CYAN],
|
|
["focus_source", _tr("集火源头", "FOCUS SOURCE"), _tr("先打断治疗或控制", "INTERRUPT HEAL OR CONTROL"), CORAL],
|
|
["guard_center", _tr("守护中位", "GUARD CENTER"), _tr("诱导攻击,抓住显形", "BAIT ATTACK; USE REVEAL"), VIOLET],
|
|
]
|
|
var w := size.x * 0.38
|
|
var h := 62.0
|
|
for index in range(specs.size()):
|
|
var spec: Array = specs[index]
|
|
var at := Vector2(size.x * (0.08 if index % 2 == 0 else 0.54), 188 + int(index / 2) * 72)
|
|
_render_p102_plan_target(String(spec[0]), String(spec[1]), String(spec[2]), at, Vector2(w, h), Color(spec[3]))
|
|
|
|
|
|
func _render_p102_plan_target(plan_id: String, title: String, consequence: String, at: Vector2, target_size: Vector2, color: Color) -> void:
|
|
var line := ColorRect.new()
|
|
line.name = "P102PlanLine_%s" % plan_id
|
|
line.position = Vector2(at.x, at.y + target_size.y - 5)
|
|
line.size = Vector2(target_size.x, 5)
|
|
line.color = color
|
|
line.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
display_root.add_child(line)
|
|
var label := _outlined_label("%s\n%s" % [title, consequence], 10, WHITE)
|
|
label.position = at
|
|
label.size = target_size
|
|
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
display_root.add_child(label)
|
|
display_root.add_child(_actor_target("P102PlanTarget_%s" % plan_id, at, target_size, choose_region_plan.bind(plan_id)))
|
|
|
|
|
|
func _render_region_battle() -> void:
|
|
super._render_region_battle()
|
|
var header := display_root.find_child("P100Header", false, false)
|
|
if header != null:
|
|
var title := display_root.get_child(header.get_index() + 1) as Label
|
|
if title != null:
|
|
title.text = _tr("第%d轮 · %s" % [region_market.state.current_run.round_index, _region_name(String(region_market.state.current_run.pending_encounter.route_id))], "ROUND %d · %s" % [region_market.state.current_run.round_index, _region_name(String(region_market.state.current_run.pending_encounter.route_id))])
|
|
|
|
|
|
func _render_region_persistent_feedback() -> void:
|
|
super._render_region_persistent_feedback()
|
|
if not p102_mode:
|
|
return
|
|
var mechanics: Dictionary = region_motion.mechanic_effects(region_motion_clock)
|
|
if not String(mechanics.drownseed.state).is_empty():
|
|
var seed := _motion_sprite("P102DrownseedMarker", PROTECT_ABSORB_VFX, Vector2(38, 38), CORAL if mechanics.drownseed.state == "burst" else GOLD)
|
|
seed.set_meta("target_id", String(mechanics.drownseed.target_id))
|
|
seed.set_meta("mechanic_state", String(mechanics.drownseed.state))
|
|
display_root.add_child(seed)
|
|
if not String(mechanics.medic.state).is_empty():
|
|
var heal := _motion_sprite("P102MedicPulse", PROTECT_GAIN_VFX, Vector2(42, 42), CORAL if mechanics.medic.state == "interrupted" else JADE)
|
|
heal.set_meta("target_id", String(mechanics.medic.target_id))
|
|
heal.set_meta("mechanic_state", String(mechanics.medic.state))
|
|
display_root.add_child(heal)
|
|
if String(mechanics.fogjaw.state) == "revealed":
|
|
var reveal := _motion_sprite("P102FogjawReveal", DAMAGE_VFX, Vector2(48, 48), CYAN)
|
|
reveal.set_meta("target_id", "EN-R2-10")
|
|
display_root.add_child(reveal)
|
|
|
|
|
|
func _animate_region_persistent_feedback() -> void:
|
|
super._animate_region_persistent_feedback()
|
|
for node_name in ["P102DrownseedMarker", "P102MedicPulse", "P102FogjawReveal"]:
|
|
var sprite := display_root.find_child(node_name, false, false) as Sprite2D
|
|
if sprite == null:
|
|
continue
|
|
sprite.position = _region_actor_center(String(sprite.get_meta("target_id", "")))
|
|
var pulse := 0.72 if low_power else 0.68 + sin(region_motion_clock * 8.0) * 0.22
|
|
sprite.modulate.a = pulse
|
|
|
|
|
|
func _render_p102_recovery() -> void:
|
|
_add_siltwake_world()
|
|
var run: Dictionary = region_market.state.current_run
|
|
var report: Dictionary = run.last_battle_report
|
|
_add_header(_tr("第%d轮 · 撤回渡口" % report.round_index, "ROUND %d · FERRY RECOVERY" % report.round_index), _tr("◆%d 救援 ◆%d 信用 %d" % [run.coin, run.recovery_cost, run.replacement_credit], "◆%d RECOVERY ◆%d CREDIT %d" % [run.coin, run.recovery_cost, run.replacement_credit]))
|
|
var title := _outlined_label(_tr("阵亡永久保留,幸存者可恢复", "FALLEN STAY LOST; SURVIVORS CAN RECOVER"), 23, CORAL)
|
|
title.position = Vector2(size.x * 0.10, 76)
|
|
title.size = Vector2(size.x * 0.80, 48)
|
|
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
display_root.add_child(title)
|
|
var body := _outlined_label(_tr("失败原因:%s\n支付救援费后回到同一个集市,路线和商品不会重抽。" % _battle_cause_text(report), "CAUSE: %s\nPAY RECOVERY TO RETURN TO THE SAME MARKET. ROUTES AND OFFERS DO NOT REROLL." % _battle_cause_text(report)), 11, WHITE)
|
|
body.position = Vector2(size.x * 0.16, 128)
|
|
body.size = Vector2(size.x * 0.68, 72)
|
|
body.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
body.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
display_root.add_child(body)
|
|
_render_p102_survivors(Vector2(size.x * 0.24, size.y * 0.54), 68)
|
|
var recover := _action_button("P102RecoverButton", _tr("支付 ◆%d 救援" % run.recovery_cost, "PAY ◆%d TO RECOVER" % run.recovery_cost), recover_region_company)
|
|
recover.position = Vector2(size.x - 236, size.y - 62)
|
|
recover.size = Vector2(220, 52)
|
|
recover.disabled = int(run.coin) < int(run.recovery_cost)
|
|
display_root.add_child(recover)
|
|
|
|
|
|
func _render_p102_market_five() -> void:
|
|
_add_siltwake_world()
|
|
var run: Dictionary = region_market.state.current_run
|
|
_add_header(_tr("淤潮地 · 集市 5", "SILTWAKE · MARKET 5"), _tr("◆%d 战线容量 %d 已保存" % [run.coin, run.line_capacity], "◆%d LINE CAPACITY %d SAVED" % [run.coin, run.line_capacity]))
|
|
var title := _outlined_label(_tr("芦苇渡船已修复", "REED FERRY RESTORED"), 28, GOLD_LIGHT)
|
|
title.position = Vector2(size.x * 0.43, 82)
|
|
title.size = Vector2(size.x * 0.50, 56)
|
|
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
display_root.add_child(title)
|
|
var body := _outlined_label(_tr("四轮已清除 · 战线容量 6 · 队员生命、等级、经验、装备与路线历史均已写入 Save v8。", "FOUR ROUNDS CLEARED · LINE CAPACITY 6 · HP, LEVELS, XP, GEAR AND ROUTE HISTORY ARE SAVED IN V8."), 12, WHITE)
|
|
body.position = Vector2(size.x * 0.45, 142)
|
|
body.size = Vector2(size.x * 0.46, 80)
|
|
body.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
body.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
display_root.add_child(body)
|
|
_render_p102_survivors(Vector2(size.x * 0.07, size.y * 0.46), clampf(size.y * 0.13, 62.0, 92.0))
|
|
var seal := _outlined_label(_tr("中段远征完成 · 后续集市尚未开放", "MID-CONTRACT COMPLETE · LATER MARKETS NOT YET OPEN"), 13, JADE)
|
|
seal.position = Vector2(size.x * 0.44, size.y - 78)
|
|
seal.size = Vector2(size.x * 0.48, 48)
|
|
seal.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
display_root.add_child(seal)
|
|
|
|
|
|
func _render_p102_survivors(origin: Vector2, actor_size: float) -> void:
|
|
var run: Dictionary = region_market.state.current_run
|
|
for index in range(run.company.size()):
|
|
var recruit_id := String(run.company[index])
|
|
var record: Dictionary = run.recruit_records[recruit_id]
|
|
var actor := _add_actor(_p100_recruit_rig(record), origin + Vector2(index * (actor_size * 0.78), 0), actor_size, "P102Survivor_%s" % recruit_id, true, "victory" if screen == P102_SCREEN_MARKET_5 else "idle")
|
|
actor.self_modulate = _p100_profession_color(String(record.profession_id))
|
|
|
|
|
|
func _add_siltwake_world() -> void:
|
|
super._add_siltwake_world()
|
|
if not p102_mode:
|
|
return
|
|
var restored := region_market != null and bool(region_market.state.current_run.get("reed_ferry_restored", false))
|
|
var ferry_width := size.x * (0.32 if restored else 0.20)
|
|
var ferry_origin := Vector2(size.x * 0.05, size.y * 0.31)
|
|
var hull := Polygon2D.new()
|
|
hull.name = "P102ReedFerryRestored" if restored else "P102ReedFerryScaffold"
|
|
hull.polygon = PackedVector2Array([
|
|
ferry_origin + Vector2(0, 4),
|
|
ferry_origin + Vector2(ferry_width, 0),
|
|
ferry_origin + Vector2(ferry_width - 18, 24),
|
|
ferry_origin + Vector2(18, 28),
|
|
])
|
|
hull.color = Color("#bd873d") if restored else Color(0.34, 0.25, 0.17, 0.62)
|
|
hull.z_index = 5
|
|
display_root.add_child(hull)
|
|
var deck := ColorRect.new()
|
|
deck.name = "P102FerryDeck"
|
|
deck.position = ferry_origin + Vector2(8, -2)
|
|
deck.size = Vector2(ferry_width - 16, 8)
|
|
deck.color = GOLD if restored else Color(0.44, 0.36, 0.24, 0.58)
|
|
deck.rotation = -0.025
|
|
deck.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
deck.z_index = 6
|
|
display_root.add_child(deck)
|
|
for index in range(6 if restored else 2):
|
|
var post := ColorRect.new()
|
|
post.name = "P102FerryPost_%d" % index
|
|
post.position = ferry_origin + Vector2(14 + index * (ferry_width - 28) / maxf(1.0, float(5 if restored else 1)), -8)
|
|
post.size = Vector2(4, 24)
|
|
post.color = deck.color
|
|
post.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
post.z_index = 7
|
|
display_root.add_child(post)
|
|
if restored:
|
|
var mast := ColorRect.new()
|
|
mast.name = "P102FerryMast"
|
|
mast.position = ferry_origin + Vector2(ferry_width * 0.52, -54)
|
|
mast.size = Vector2(5, 62)
|
|
mast.color = Color("#efd17a")
|
|
mast.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
mast.z_index = 7
|
|
display_root.add_child(mast)
|
|
var flag := Polygon2D.new()
|
|
flag.name = "P102FerryFlag"
|
|
flag.polygon = PackedVector2Array([
|
|
mast.position + Vector2(5, 3),
|
|
mast.position + Vector2(42, 12),
|
|
mast.position + Vector2(5, 23),
|
|
])
|
|
flag.color = CYAN
|
|
flag.z_index = 7
|
|
display_root.add_child(flag)
|
|
var lantern := _outlined_label("◆", 18, GOLD_LIGHT)
|
|
lantern.name = "P102FerryLantern"
|
|
lantern.position = mast.position + Vector2(-11, -20)
|
|
lantern.size = Vector2(28, 28)
|
|
lantern.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
lantern.z_index = 8
|
|
display_root.add_child(lantern)
|
|
|
|
|
|
func _region_event_text(event: Dictionary) -> String:
|
|
if not p102_mode:
|
|
return super._region_event_text(event)
|
|
var kind := String(event.get("kind", "battle_started"))
|
|
var payload: Dictionary = Dictionary(event.get("payload", {}))
|
|
return {
|
|
"battle_started": _tr("第%d轮开始:%s" % [payload.get("round_index", 0), _plan_name(String(payload.get("plan_id", "")))], "ROUND %d: %s" % [payload.get("round_index", 0), _plan_name(String(payload.get("plan_id", "")))]),
|
|
"wave_entered": _tr("%s进入战线" % _region_name(String(event.get("source_id", ""))), "%s ENTERS THE LINE" % _region_name(String(event.get("source_id", "")))),
|
|
"drownseed_placed": _tr("沉籽落地:两拍后爆裂", "DROWNSEED PLACED: BURST IN TWO CADENCES"),
|
|
"drownseed_evaded": _tr("队员移出沉籽标记", "RECRUIT CLEARS THE DROWNSEED MARK"),
|
|
"drownseed_burst": _tr("沉籽爆裂并冲击战线", "DROWNSEED BURSTS INTO THE LINE"),
|
|
"mire_medic_heal": _tr("泥沼医师治疗并缩短迟滞", "MIRE MEDIC HEALS AND SHORTENS HINDRANCE"),
|
|
"source_interrupted": _tr("源头被集火打断", "SOURCE INTERRUPTED BY FOCUS FIRE"),
|
|
"enemy_sustain_resolved": _tr("敌方治疗完整生效", "ENEMY SUSTAIN RESOLVES"),
|
|
"fogjaw_hidden": _tr("雾颚兽藏入雾中,远程无法锁定", "FOGJAW HIDES; LONG REACH CANNOT TARGET"),
|
|
"fogjaw_center_attack": _tr("雾颚兽扑向中位并短暂显形", "FOGJAW STRIKES CENTER AND REVEALS"),
|
|
"center_guarded": _tr("中位守护接住突袭", "CENTER GUARD CATCHES THE STRIKE"),
|
|
"center_exposed": _tr("中位暴露,突袭命中", "CENTER EXPOSED; STRIKE CONNECTS"),
|
|
"rear_pull": _tr("芦钩尝试拉动后排", "REED HOOK PULLS THE REAR"),
|
|
"healing_denial_attached": _tr("泥灯水蛭封锁最低生命队员", "MUDLAMP LEECH BLOCKS THE LOWEST-HP RECRUIT"),
|
|
"indexed_mud_marked": _tr("当前安全位被淤泥标记", "CURRENT SAFE INDEX IS MARKED"),
|
|
"enemy_order_changed": _tr("敌方前后顺序改变", "ENEMY ORDER CHANGES"),
|
|
"tide_shift_warning": _tr("换潮警告:下一安全位已亮起", "TIDE WARNING: NEXT SAFE INDEX LIT"),
|
|
"tide_shifted": _tr("队伍随潮移动", "THE LINE FOLLOWS THE TIDE") if bool(payload.get("followed", false)) else _tr("战线留在原位承受换潮", "THE LINE HOLDS THROUGH THE TIDE"),
|
|
"allied_basic": _tr("队员攻击产生接触", "RECRUIT ATTACK CONNECTS"),
|
|
"authoritative_damage": _tr("伤害 -%d" % int(payload.get("amount", 0)), "DAMAGE -%d" % int(payload.get("amount", 0))),
|
|
"fallen": _tr("一名队员永久阵亡", "A RECRUIT IS PERMANENTLY FALLEN"),
|
|
"prepared_response_succeeded": _tr("战术应对命中关键窗口", "THE RESPONSE HITS THE DECISIVE WINDOW"),
|
|
"battle_terminal": _tr("本轮结果已确定", "ROUND RESULT LOCKED"),
|
|
}.get(kind, kind.replace("_", " ").to_upper())
|
|
|
|
|
|
func _region_event_color(kind: String) -> Color:
|
|
if not p102_mode:
|
|
return super._region_event_color(kind)
|
|
if kind in ["drownseed_burst", "enemy_sustain_resolved", "center_exposed", "authoritative_damage", "fallen"]:
|
|
return CORAL
|
|
if kind in ["drownseed_evaded", "source_interrupted", "center_guarded", "prepared_response_succeeded", "battle_terminal"]:
|
|
return JADE
|
|
return GOLD_LIGHT
|
|
|
|
|
|
func _route_summary(risk: int) -> String:
|
|
if not p102_mode:
|
|
return super._route_summary(risk)
|
|
var offer := _region_offer("route", region_focused_route)
|
|
if offer.is_empty() and region_market != null:
|
|
offer = Dictionary(region_market.state.current_run.get("pending_encounter", {}))
|
|
if offer.is_empty():
|
|
return _tr("比较路线压力与当前战线,再选择应对。", "COMPARE ROUTE PRESSURE WITH THE CURRENT LINE, THEN CHOOSE A RESPONSE.")
|
|
return String(offer.preview) if locale == "en" else _p102_route_hint(String(offer.route_id))
|
|
|
|
|
|
func _p102_route_hint(route_id: String) -> String:
|
|
return {
|
|
"RO-R2-M02-Q0": "稳住后排,让镜像目标保持可预测。",
|
|
"RO-R2-M02-Q1": "集火打断笛手,避免敌方换位后牵引战线。",
|
|
"RO-R2-M02-Q2": "保留一次移动,在沉籽爆裂前离开标记。",
|
|
"RO-R2-M03-Q0": "等待迟滞过半,或先处理泥沼医师。",
|
|
"RO-R2-M03-Q1": "优先打断水蛭或医师,别让治疗反复生效。",
|
|
"RO-R2-M03-Q2": "守住中位,等雾颚兽攻击显形后反击。",
|
|
"RO-R2-M04-Q0": "抵抗拉拽,把移动留给沉籽倒计时。",
|
|
"RO-R2-M04-Q1": "先喂给镜像安全目标,再守住中位显形窗口。",
|
|
"RO-R2-M04-Q2": "先集火医师,再带着可用移动击破沉籽。",
|
|
}.get(route_id, "路线会检验当前战线的站位、目标和时机。")
|
|
|
|
|
|
func _region_name(row_id: String) -> String:
|
|
if not p102_mode:
|
|
return super._region_name(row_id)
|
|
var p102_name := P102Content.localized_name(p102_catalog, row_id, locale)
|
|
return super._region_name(row_id) if p102_name.is_empty() else p102_name
|
|
|
|
|
|
func _equipment_symbol(catalog_id: String) -> String:
|
|
if catalog_id in ["EQ-049", "EQ-050", "EQ-051", "EQ-052", "EQ-053", "EQ-054", "EQ-055", "EQ-056"]:
|
|
return {"EQ-049": "◇", "EQ-050": "▽", "EQ-051": "⊘", "EQ-052": "♨", "EQ-053": "□", "EQ-054": "Ⅲ", "EQ-055": "↔", "EQ-056": "⇥"}[catalog_id]
|
|
return super._equipment_symbol(catalog_id)
|
|
|
|
|
|
func _plan_name(plan_id: String) -> String:
|
|
return {
|
|
"hold_line": _tr("稳住战线", "HOLD LINE"),
|
|
"follow_tide": _tr("顺潮移动", "FOLLOW TIDE"),
|
|
"focus_source": _tr("集火源头", "FOCUS SOURCE"),
|
|
"guard_center": _tr("守护中位", "GUARD CENTER"),
|
|
}.get(plan_id, plan_id)
|
|
|
|
|
|
func _battle_cause_text(report: Dictionary) -> String:
|
|
var event: Dictionary = Dictionary(report.get("first_decisive_event", {}))
|
|
return _region_event_text(event)
|
|
|
|
|
|
func reset_progress() -> void:
|
|
if not p102_mode:
|
|
super.reset_progress()
|
|
return
|
|
if not reset_confirmation:
|
|
reset_confirmation = true
|
|
last_code = "reset_confirmation_required"
|
|
_queue_render()
|
|
return
|
|
for save_name in [P102_SAVE_PATH, P101_SAVE_PATH]:
|
|
for suffix in ["", ".bak", ".candidate"]:
|
|
var path := ProjectSettings.globalize_path(save_name + suffix)
|
|
if FileAccess.file_exists(path):
|
|
DirAccess.remove_absolute(path)
|
|
p102_mode = false
|
|
region_mode = false
|
|
region_market = null
|
|
entry_snapshot = {}
|
|
region_save_sequence = 0
|
|
region_save_commit_hash = ""
|
|
super.reset_progress()
|