Files
aetherbound-guild/runtime/p10_3/runtime_controller.gd
T

1004 lines
36 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
class_name P103RuntimeController
extends "res://p10_2/runtime_controller.gd"
const P103Content = preload("res://p10_3/content_registry.gd")
const P103Board = preload("res://p10_3/market_board_service.gd")
const P103State = preload("res://p10_3/run_state.gd")
const P103Market = preload("res://p10_3/runtime_market_service.gd")
const P103Battle = preload("res://p10_3/deep_channels_battle_service.gd")
const P103Resolution = preload("res://p10_3/deep_channels_resolution_service.gd")
const P103Growth = preload("res://p10_3/growth_service.gd")
const P103Save = preload("res://p10_3/save_v9_service.gd")
const P103Motion = preload("res://p10_3/motion_presenter.gd")
const P103_SAVE_PATH := "user://aetherbound_guild_p10_3.json"
const P103_SCREEN_MARKET := "deep_market"
const P103_SCREEN_ARTIFACT := "deep_artifact"
const P103_SCREEN_PROMOTION := "deep_promotion"
const P103_SCREEN_PLAN := "deep_plan"
const P103_SCREEN_BATTLE := "deep_battle"
const P103_SCREEN_RECOVERY := "deep_recovery"
const P103_SCREEN_MARKET_8 := "deep_market_8"
var p103_mode := false
var p103_catalog: Dictionary = P103Content.snapshot()
var p103_save := P103Save.new()
var p103_board := P103Board.new()
func _bootstrap_market() -> void:
p103_mode = false
var snapshot := _p103_entry_snapshot()
if snapshot.is_empty() and persistence_enabled:
var loaded: Dictionary = p103_save.load_slot(
ProjectSettings.globalize_path(P103_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 = P102Save.new().load_slot(
ProjectSettings.globalize_path(P102_SAVE_PATH)
)
if legacy.ok:
snapshot = _migrate_market_five(Dictionary(legacy.snapshot))
region_save_sequence = int(legacy.get("sequence", 0))
region_save_commit_hash = ""
if snapshot.is_empty():
super._bootstrap_market()
return
_activate_p103(snapshot)
func _p103_entry_snapshot() -> Dictionary:
if String(entry_snapshot.get("state_kind", "")) == P103State.P103_STATE_KIND:
return entry_snapshot.duplicate(true)
if String(entry_snapshot.get("state_kind", "")) == P102State.STATE_KIND:
return _migrate_market_five(entry_snapshot)
return {}
func _migrate_market_five(snapshot: Dictionary) -> Dictionary:
var source := P102State.new()
if not source.restore_snapshot(snapshot):
return {}
var run: Dictionary = source.current_run
if String(run.get("phase", "")) != "market_5" or int(run.get("market_index", -1)) != 5:
return {}
var generated := p103_board.generate(String(run.run_seed), 5, 0)
if not generated.ok:
return {}
var migrated := P103State.new()
return migrated.snapshot() if migrated.initialize_from_p102(source.snapshot(), generated.board) else {}
func _activate_p103(snapshot: Dictionary) -> bool:
var service := P103Market.new(snapshot)
if not service.valid:
return false
region_market = service
region_mode = true
p102_mode = false
p103_mode = true
region_catalog = p103_catalog
region_motion = P103Motion.new()
_prepare_base_content()
_restore_p103_screen()
_reset_region_focus()
p102_focused_company = ""
last_code = "p103_resumed"
_queue_render()
return true
func _restore_region_screen() -> void:
if not p103_mode:
super._restore_region_screen()
return
_restore_p103_screen()
func _restore_p103_screen() -> void:
if region_market == null or region_market.state.current_run.is_empty():
screen = P103_SCREEN_MARKET
return
var run: Dictionary = region_market.state.current_run
match String(run.phase):
"prepared":
screen = P103_SCREEN_PLAN
"recovery":
screen = P103_SCREEN_RECOVERY
"market_8":
screen = P103_SCREEN_MARKET_8
_:
if int(run.market_index) == 7 and String(run.artifact_draft.get("status", "")) == "available":
screen = P103_SCREEN_ARTIFACT
elif int(run.market_index) == 7 and String(run.promotion_draft.get("status", "")) == "available":
screen = P103_SCREEN_PROMOTION
else:
screen = P103_SCREEN_MARKET
func _process(delta: float) -> void:
if not p103_mode:
super._process(delta)
return
if screen == P103_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 != P103_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 p103_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:
P103_SCREEN_ARTIFACT:
_render_p103_artifact()
P103_SCREEN_PROMOTION:
_render_p103_promotion()
P103_SCREEN_PLAN:
_render_p103_plan()
P103_SCREEN_BATTLE:
_render_region_battle()
P103_SCREEN_RECOVERY:
_render_p102_recovery()
P103_SCREEN_MARKET_8:
_render_p103_market_eight()
_:
_render_p103_market()
_add_settings_button()
if settings_open:
_render_settings_band()
func _render_region_battle() -> void:
super._render_region_battle()
if p103_mode:
_animate_region_battle_actors()
func _animate_region_battle_actors() -> void:
if not p103_mode:
super._animate_region_battle_actors()
return
if screen != P103_SCREEN_BATTLE:
return
for actor_id in battle_actors:
var actor: TextureRect = battle_actors[actor_id]
if not is_instance_valid(actor) or not actor.has_meta("base_position"):
continue
var rig := String(actor.get_meta("rig", "Hearthguard"))
var visual: Dictionary
if bool(actor.get_meta("enemy_actor", false)):
visual = region_motion.enemy_visual(String(actor_id), region_motion_clock, reduced_motion, low_power)
else:
visual = region_motion.actor_visual(
String(actor_id),
int(actor.get_meta("slot_index", 0)),
float(actor.get_meta("slot_step", -68.0)),
region_motion_clock,
reduced_motion,
low_power
)
_apply_region_actor_visual(actor, visual, rig)
if battle_hp_bars.has(actor_id):
var bar: ColorRect = battle_hp_bars[actor_id]
bar.position = Vector2(actor.position.x + 4, actor.position.y - 8)
bar.modulate.a = float(visual.alpha)
var enemy_name := display_root.find_child(
"P101BattleEnemyName_%s" % String(actor_id), false, false
) as Label
if enemy_name != null:
enemy_name.position = Vector2(actor.position.x - 12, actor.position.y + actor.size.y - 2)
enemy_name.modulate.a = float(visual.alpha)
_animate_region_persistent_feedback()
_animate_region_motion_effects()
func tap_region_recruit(offer_id: String) -> void:
if not p103_mode:
super.tap_region_recruit(offer_id)
return
if not _p103_market_ready():
return
region_focused_recruit = "" if region_focused_recruit == offer_id else offer_id
region_focused_equipment = ""
region_focused_route = ""
last_code = "deep_recruit_inspected"
_queue_render()
func tap_region_equipment(offer_id: String) -> void:
if not p103_mode:
super.tap_region_equipment(offer_id)
return
if not _p103_market_ready():
return
region_focused_equipment = "" if region_focused_equipment == offer_id else offer_id
region_focused_recruit = ""
region_focused_route = ""
last_code = "deep_equipment_inspected"
_queue_render()
func tap_region_company(recruit_id: String) -> void:
if not p103_mode:
super.tap_region_company(recruit_id)
return
if not _p103_market_ready() or recruit_id not in region_market.state.current_run.company:
return
p102_focused_company = "" if p102_focused_company == recruit_id else recruit_id
region_focused_company = p102_focused_company
last_code = "deep_company_inspected"
_queue_render()
func tap_region_route(offer_id: String) -> void:
if not p103_mode:
super.tap_region_route(offer_id)
return
if not _p103_market_ready():
return
region_focused_route = "" if region_focused_route == offer_id else offer_id
region_focused_recruit = ""
region_focused_equipment = ""
last_code = "deep_route_inspected"
_queue_render()
func commit_region_route() -> void:
if not p103_mode:
super.commit_region_route()
return
if not _p103_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)
)
last_code = String(result.code)
if result.ok:
screen = P103_SCREEN_PLAN
_save_region()
_queue_render()
func choose_p103_artifact(artifact_id: String) -> void:
if not p103_mode or screen != P103_SCREEN_ARTIFACT:
return
var growth := P103Growth.new(region_market.state.snapshot())
var draft_hash := String(growth.state.current_run.artifact_draft.get("draft_hash", ""))
var result := growth.select_artifact(
_region_operation("artifact", artifact_id),
growth.state.revision,
draft_hash,
artifact_id
)
_accept_p103_growth(result)
func defer_p103_artifact() -> void:
if not p103_mode or screen != P103_SCREEN_ARTIFACT:
return
var growth := P103Growth.new(region_market.state.snapshot())
var draft_hash := String(growth.state.current_run.artifact_draft.get("draft_hash", ""))
var result := growth.defer_artifact(
_region_operation("artifact-defer", "draft"),
growth.state.revision,
draft_hash
)
_accept_p103_growth(result)
func _accept_p103_growth(result: Dictionary) -> void:
last_code = String(result.code)
if not result.ok:
_queue_render()
return
var growth := P103Growth.new(result.snapshot)
var opened := growth.open_promotion_draft(
_region_operation("promotion-open", "eligible"),
growth.state.revision
)
if opened.ok:
region_market = P103Market.new(opened.snapshot)
screen = P103_SCREEN_PROMOTION
else:
region_market = P103Market.new(growth.state.snapshot())
screen = P103_SCREEN_MARKET
_save_region()
_queue_render()
func choose_p103_promotion(profession_id: String) -> void:
if not p103_mode or screen != P103_SCREEN_PROMOTION:
return
var growth := P103Growth.new(region_market.state.snapshot())
var draft: Dictionary = growth.state.current_run.promotion_draft
var result := growth.confirm_promotion(
_region_operation("promotion", profession_id),
growth.state.revision,
String(draft.draft_hash),
String(draft.recruit_id),
String(draft.current_profession_id),
profession_id
)
_accept_p103_promotion(result)
func defer_p103_promotion() -> void:
if not p103_mode or screen != P103_SCREEN_PROMOTION:
return
var growth := P103Growth.new(region_market.state.snapshot())
var draft: Dictionary = growth.state.current_run.promotion_draft
var result := growth.defer_promotion(
_region_operation("promotion-defer", String(draft.recruit_id)),
growth.state.revision,
String(draft.draft_hash)
)
_accept_p103_promotion(result)
func _accept_p103_promotion(result: Dictionary) -> void:
last_code = String(result.code)
if result.ok:
region_market = P103Market.new(result.snapshot)
screen = P103_SCREEN_MARKET
_save_region()
_queue_render()
func choose_region_plan(plan_id: String) -> void:
if not p103_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 := P103Battle.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 = P103_SCREEN_BATTLE
_queue_render()
func advance_region_battle_ticks(ticks: int) -> Dictionary:
if not p103_mode:
return super.advance_region_battle_ticks(ticks)
if screen != P103_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 = "deep_channels_playback_terminal"
else:
last_code = "deep_channels_playing"
_queue_render()
return {"ok": true, "code": last_code, "events": emitted, "tick": region_battle_tick}
func toggle_region_battle_pause() -> void:
if not p103_mode:
super.toggle_region_battle_pause()
return
if screen == P103_SCREEN_BATTLE and not region_battle_complete:
region_battle_paused = not region_battle_paused
_queue_render()
func toggle_region_battle_speed() -> void:
if not p103_mode:
super.toggle_region_battle_speed()
return
if screen == P103_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 p103_mode:
super.settle_region_battle()
return
if not region_battle_complete or region_battle_snapshot.is_empty():
return
var resolver := P103Resolution.new(region_market.state.snapshot())
var result := 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 = P103Market.new(result.snapshot)
_reset_region_focus()
p102_focused_company = ""
_save_region()
_restore_p103_screen()
_queue_render()
func recover_region_company() -> void:
if not p103_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 := P103Resolution.new(region_market.state.snapshot())
var result := 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 = P103Market.new(result.snapshot)
pending_line.assign(region_market.state.current_run.deployed_recruit_ids)
_reset_region_focus()
p102_focused_company = ""
_save_region()
screen = P103_SCREEN_MARKET
_queue_render()
func _commit_line_drag(source_id: String, target_id: String) -> void:
if not p103_mode:
super._commit_line_drag(source_id, target_id)
return
if not _p103_market_ready() or source_id == target_id:
return
var order: Array = Array(region_market.state.current_run.deployed_recruit_ids).duplicate()
if source_id not in order or target_id not in order:
return
order.erase(source_id)
order.insert(order.find(target_id), source_id)
var result: Dictionary = region_market.commit_deployment(
_region_operation("deploy", "drag"),
region_market.state.revision,
order
)
last_code = String(result.code)
if result.ok:
pending_line.assign(region_market.state.current_run.deployed_recruit_ids)
_save_region()
_queue_render()
func _consume_region_event(event: Dictionary) -> void:
if not p103_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 == "safe_index_advanced":
region_safe_index = int(payload.get("to", 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 p103_mode:
return super._save_region()
if not persistence_enabled or region_market == null:
return true
var result := p103_save.write_slot(
ProjectSettings.globalize_path(P103_SAVE_PATH),
region_market.state.snapshot(),
region_save_sequence + 1,
region_save_commit_hash
)
if not result.ok:
last_code = "p103_%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 p103_mode:
return super._region_operation(kind, target)
operation_index += 1
return "p103-ui-%s-%d-%d-%s" % [
kind,
region_market.state.revision,
operation_index,
target,
]
func _p103_market_ready() -> bool:
return (
p103_mode
and region_market != null
and not region_market.state.current_run.is_empty()
and String(region_market.state.current_run.phase) == "market"
and int(region_market.state.current_run.market_index) in [5, 6, 7]
and screen == P103_SCREEN_MARKET
)
func _render_p103_market() -> void:
_render_p102_market()
var run: Dictionary = region_market.state.current_run
var status := _outlined_label(_p103_rule_summary(run), 8, CYAN)
status.name = "P103RuleStatus"
status.position = Vector2(size.x * 0.39, 166)
status.size = Vector2(size.x * 0.34, 34)
status.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
status.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
display_root.add_child(status)
func _render_p103_artifact() -> void:
_add_siltwake_world()
var run: Dictionary = region_market.state.current_run
_add_header(
_tr("第6轮结算 · 选择远征法则", "ROUND 6 · CHOOSE AN EXPEDITION LAW"),
_tr("基础容量 %d · 三选一 · 本次远征不可更换" % run.line_capacity, "BASE CAPACITY %d · CHOOSE ONE FOR THIS RUN" % run.line_capacity)
)
var title := _outlined_label(_tr("渡船法则祭坛", "FERRY LAW SHRINE"), 23, GOLD_LIGHT)
title.position = Vector2(size.x * 0.25, 58)
title.size = Vector2(size.x * 0.50, 44)
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
display_root.add_child(title)
var specs := [
["AR-011", _tr("窄锋阵", "NARROW VANGUARD"), _tr("少1容量;前锋获得守护与强制目标", "-1 CAPACITY; FRONT GUARD + FORCED TARGET"), GOLD],
["AR-012", _tr("长队列", "LONG COMPANY"), _tr("多1容量;全队开场无守护", "+1 CAPACITY; NO OPENING GUARD"), CYAN],
["AR-015", _tr("成对职责", "PAIRED DUTIES"), _tr("相邻成对共享;跨组延迟一拍", "PAIR SHARING; CROSS-PAIR DELAY"), VIOLET],
]
var width := size.x * 0.27
for index in range(specs.size()):
var spec: Array = specs[index]
var at := Vector2(size.x * 0.06 + index * size.x * 0.315, 128)
var line := ColorRect.new()
line.name = "P103ArtifactLine_%s" % spec[0]
line.position = Vector2(at.x, at.y + 118)
line.size = Vector2(width, 6)
line.color = Color(spec[3])
display_root.add_child(line)
var icon := _outlined_label(["◆", "◇", "∞"][index], 38, Color(spec[3]))
icon.position = at
icon.size = Vector2(width, 50)
icon.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
display_root.add_child(icon)
var label := _outlined_label("%s\n%s" % [spec[1], spec[2]], 10, WHITE)
label.position = at + Vector2(0, 48)
label.size = Vector2(width, 68)
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
display_root.add_child(label)
display_root.add_child(_actor_target(
"P103ArtifactTarget_%s" % spec[0], at, Vector2(width, 124),
choose_p103_artifact.bind(String(spec[0]))
))
var defer := _secondary_button(
"P103ArtifactDeferButton",
_tr("暂不选择", "DEFER"),
defer_p103_artifact
)
defer.position = Vector2(size.x - 180, size.y - 58)
defer.size = Vector2(164, 48)
display_root.add_child(defer)
func _render_p103_promotion() -> void:
_add_siltwake_world()
var draft: Dictionary = region_market.state.current_run.promotion_draft
var recruit: Dictionary = region_market.state.current_run.recruit_records[draft.recruit_id]
_add_header(
_tr("晋阶铁砧 · %s" % _generated_name(recruit), "PROMOTION ANVIL · %s" % _generated_name(recruit)),
_tr("Lv.%d · 生还%d场 · 从%s选择一个分支" % [recruit.level, recruit.survived_battles, _short_profession(String(draft.current_profession_id))], "LV.%d · %d SURVIVALS · CHOOSE ONE %s BRANCH" % [recruit.level, recruit.survived_battles, _short_profession(String(draft.current_profession_id))])
)
var actor := _add_actor(
_p100_recruit_rig(recruit),
Vector2(size.x * 0.10, size.y * 0.29),
112,
"P103PromotionRecruit",
true,
"victory"
)
actor.self_modulate = _p100_profession_color(String(recruit.profession_id))
var specs := [
["PF-A13", _tr("战地医师", "FIELDMENDER"), _tr("立即治疗后,伤员后移。\n适合追随移动安全位。", "IMMEDIATE CARE, THEN MOVE THE PATIENT REARWARD.\nFAVORS MOVING SAFE GROUND."), CYAN],
["PF-A14", _tr("续护师", "AFTERCARE"), _tr("目标行动后,结算更强治疗。\n适合错开连线反噬。", "STRONGER CARE AFTER THE TARGET ACTS.\nFAVORS DELAYING LINK BACKLASH."), JADE],
]
for index in range(specs.size()):
var spec: Array = specs[index]
var at := Vector2(size.x * (0.34 + index * 0.31), 118)
var label := _outlined_label("%s\n%s" % [spec[1], spec[2]], 11, WHITE)
label.position = at
label.size = Vector2(size.x * 0.27, 122)
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
label.clip_text = true
display_root.add_child(label)
var line := ColorRect.new()
line.position = Vector2(at.x, at.y + 120)
line.size = Vector2(size.x * 0.27, 6)
line.color = Color(spec[3])
display_root.add_child(line)
display_root.add_child(_actor_target(
"P103PromotionTarget_%s" % spec[0], at, Vector2(size.x * 0.27, 128),
choose_p103_promotion.bind(String(spec[0]))
))
var defer := _secondary_button(
"P103PromotionDeferButton",
_tr("保留本职", "KEEP BASE PROFESSION"),
defer_p103_promotion
)
defer.position = Vector2(size.x - 240, size.y - 58)
defer.size = Vector2(224, 48)
display_root.add_child(defer)
func _render_p103_plan() -> void:
_render_p102_plan()
var status := _outlined_label(
_p103_rule_summary(region_market.state.current_run),
8,
CYAN
)
status.name = "P103PlanRuleStatus"
status.position = Vector2(size.x * 0.22, 148)
status.size = Vector2(size.x * 0.56, 32)
status.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
display_root.add_child(status)
func _render_p103_market_eight() -> void:
_add_siltwake_world()
var run: Dictionary = region_market.state.current_run
_add_header(
_tr("淤潮地 · 集市 8", "SILTWAKE · MARKET 8"),
_tr("◆%d · 容量%d · 七轮已保存" % [run.coin, run.line_capacity], "◆%d · CAPACITY %d · SEVEN ROUNDS SAVED" % [run.coin, run.line_capacity])
)
var title := _outlined_label(_tr("深水道法则已写入渡船", "DEEP-CHANNEL LAW BOUND TO THE FERRY"), 24, GOLD_LIGHT)
title.position = Vector2(size.x * 0.36, 82)
title.size = Vector2(size.x * 0.58, 56)
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
display_root.add_child(title)
var body := _outlined_label(
"%s\n%s" % [
_p103_rule_summary(run),
_tr("队员生命、经验、阵亡、神器、转职和七条路线均已保存。", "HP, XP, FALLEN, ARTIFACT, PROMOTION AND SEVEN ROUTES ARE SAVED."),
],
11,
WHITE
)
body.position = Vector2(size.x * 0.39, 142)
body.size = Vector2(size.x * 0.52, 82)
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.06, size.y * 0.46), 66)
var seal := _outlined_label(
_tr("深水道阶段完成 · 第8轮尚未开放", "DEEP CHANNELS COMPLETE · ROUND 8 NOT YET OPEN"),
13,
JADE
)
seal.position = Vector2(size.x * 0.40, size.y - 76)
seal.size = Vector2(size.x * 0.52, 46)
seal.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
display_root.add_child(seal)
func _render_region_persistent_feedback() -> void:
super._render_region_persistent_feedback()
if not p103_mode:
return
var mechanics: Dictionary = region_motion.mechanic_effects(region_motion_clock)
if not String(mechanics.thread.state).is_empty():
var pair: Array = mechanics.thread.pair
if pair.size() == 2:
_add_thread_link(
String(pair[0]),
String(pair[1]),
String(mechanics.thread.state)
)
if not String(mechanics.sluice.state).is_empty():
var safe := _outlined_label("%d" % int(mechanics.sluice.safe_index), 22, CYAN)
safe.name = "P103SafeIndexPulse"
safe.position = Vector2(size.x * 0.49, size.y * 0.36)
safe.size = Vector2(42, 42)
safe.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
safe.set_meta("mechanic_state", String(mechanics.sluice.state))
display_root.add_child(safe)
if not String(mechanics.artifact.state).is_empty():
var artifact := _outlined_label("◆", 24, GOLD_LIGHT)
artifact.name = "P103ArtifactPulse"
artifact.position = Vector2(size.x * 0.45, size.y * 0.69)
artifact.size = Vector2(40, 40)
artifact.set_meta("artifact_id", String(mechanics.artifact.artifact_id))
display_root.add_child(artifact)
if not String(mechanics.promotion.state).is_empty():
var promotion := _motion_sprite(
"P103PromotionPulse",
PROTECT_GAIN_VFX,
Vector2(44, 44),
JADE
)
promotion.set_meta("target_id", String(mechanics.promotion.actor_id))
display_root.add_child(promotion)
func _animate_region_persistent_feedback() -> void:
super._animate_region_persistent_feedback()
if not p103_mode:
return
var promotion := display_root.find_child("P103PromotionPulse", false, false) as Sprite2D
if promotion != null:
promotion.position = _region_actor_center(String(promotion.get_meta("target_id", "")))
promotion.modulate.a = 0.78 if low_power else 0.70 + sin(region_motion_clock * 7.0) * 0.20
for node_name in ["P103SafeIndexPulse", "P103ArtifactPulse", "P103ChannelThread"]:
var node := display_root.find_child(node_name, false, false) as CanvasItem
if node != null:
node.modulate.a = 0.80 if low_power else 0.72 + sin(region_motion_clock * 6.0) * 0.18
func _add_thread_link(first_id: String, second_id: String, state: String) -> void:
var start := _region_actor_center(first_id)
var finish := _region_actor_center(second_id)
var delta := finish - start
var link := ColorRect.new()
link.name = "P103ChannelThread"
link.position = start
link.size = Vector2(maxf(8.0, delta.length()), 5)
link.rotation = delta.angle()
link.pivot_offset = Vector2(0, 2.5)
link.color = CORAL if state == "backlash" else CYAN
link.mouse_filter = Control.MOUSE_FILTER_IGNORE
link.z_index = 25
link.set_meta("mechanic_state", state)
display_root.add_child(link)
func _add_siltwake_world() -> void:
super._add_siltwake_world()
if not p103_mode:
return
var ferry_origin := Vector2(size.x * 0.05, size.y * 0.30)
var ferry_width := size.x * 0.33
var hull := Polygon2D.new()
hull.name = "P103DeepFerry"
hull.polygon = PackedVector2Array([
ferry_origin + Vector2(0, 6),
ferry_origin + Vector2(ferry_width, 0),
ferry_origin + Vector2(ferry_width - 20, 30),
ferry_origin + Vector2(18, 32),
])
hull.color = Color("#ad7636")
hull.z_index = 5
display_root.add_child(hull)
var mast := ColorRect.new()
mast.name = "P103FerryMast"
mast.position = ferry_origin + Vector2(ferry_width * 0.48, -62)
mast.size = Vector2(5, 70)
mast.color = GOLD_LIGHT
mast.z_index = 6
display_root.add_child(mast)
var shrine := _outlined_label("◆", 24, CYAN)
shrine.name = "P103ArtifactShrine"
shrine.position = ferry_origin + Vector2(ferry_width * 0.18, -32)
shrine.size = Vector2(40, 40)
shrine.z_index = 7
display_root.add_child(shrine)
var anvil := _outlined_label("◇", 24, CORAL)
anvil.name = "P103PromotionAnvil"
anvil.position = ferry_origin + Vector2(ferry_width * 0.72, -28)
anvil.size = Vector2(40, 40)
anvil.z_index = 7
display_root.add_child(anvil)
func _p102_context_text() -> String:
if not p103_mode:
return super._p102_context_text()
if not region_focused_recruit.is_empty():
var recruit := _region_offer("recruit", region_focused_recruit)
return _tr(
"%s · Lv.%d · %s;比较其职业、特质与当前战线。" % [_generated_name(recruit), recruit.level, _short_profession(String(recruit.profession_id))],
"%s · LV.%d · %s; COMPARE PROFESSION AND TRAIT WITH THE CURRENT LINE." % [_generated_name(recruit), recruit.level, _short_profession(String(recruit.profession_id))]
)
if not region_focused_equipment.is_empty():
var equipment := _region_offer("equipment", region_focused_equipment)
return _tr(
"%s:%s;代价:%s。" % [_region_name(String(equipment.catalog_id)), equipment.effect, equipment.tradeoff],
"%s: %s; COST: %s" % [_region_name(String(equipment.catalog_id)), equipment.effect, equipment.tradeoff]
)
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():
return _tr("拖动两名战线队员可改变前后顺序。", "DRAG BETWEEN TWO LINE RECRUITS TO CHANGE ORDER.")
return _tr(
"先比较连线、移动安全位与当前法则,再选择路线和应对。",
"COMPARE LINKS, MOVING SAFE GROUND AND THE CURRENT LAW BEFORE COMMITTING."
)
func _p103_rule_summary(run: Dictionary) -> String:
var artifact := (
_tr("尚未选择神器", "NO ARTIFACT YET")
if run.active_artifact_ids.is_empty()
else _region_name(String(run.active_artifact_ids[0]))
)
var promotions: Array[String] = []
for recruit_id in run.company:
var record: Dictionary = run.recruit_records[recruit_id]
if String(record.get("promotion_state", "")) == "regular_advanced":
promotions.append(_short_profession(String(record.profession_id)))
var promotion := _tr("未晋阶", "NO PROMOTION") if promotions.is_empty() else ", ".join(promotions)
return _tr("神器:%s · 晋阶:%s" % [artifact, promotion], "ARTIFACT: %s · PROMOTION: %s" % [artifact, promotion])
func _region_event_text(event: Dictionary) -> String:
if not p103_mode:
return super._region_event_text(event)
var kind := String(event.get("kind", ""))
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", "")))),
"channel_thread_bound": _tr("水道寡妇连结两名队员", "CHANNEL WIDOW LINKS TWO RECRUITS"),
"channel_thread_adjacent": _tr("连结队员相邻,治疗反噬解除", "LINKED RECRUITS BECOME ADJACENT; BACKLASH ENDS"),
"channel_healing_backlash": _tr("治疗沿连线反噬另一名队员", "HEALING BACKLASH STRIKES THE LINKED PARTNER"),
"safe_index_advanced": _tr("闸门长老推进安全位", "SLUICE ELDER ADVANCES THE SAFE INDEX"),
"wet_stack_applied": _tr("湿层达到三层", "WET REACHES THREE STACKS"),
"wet_threshold_avoided": _tr("队伍跟随安全位,避开拉拽", "THE LINE FOLLOWS SAFE GROUND AND AVOIDS THE PULL"),
"wet_pull_resolved": _tr("三层湿气触发拉拽", "THREE WET STACKS TRIGGER A PULL"),
"safe_index_reversed": _tr("长老低于半生命,安全位反向", "BELOW HALF VITALITY, THE SAFE INDEX REVERSES"),
"narrow_vanguard_guard": _tr("窄锋阵守护前锋", "NARROW VANGUARD GUARDS THE FRONT"),
"long_company_opening": _tr("长队列展开且无开场守护", "LONG COMPANY OPENS WITHOUT GUARD"),
"paired_duties_partition": _tr("成对职责划分相邻小组", "PAIRED DUTIES PARTITIONS ADJACENT PAIRS"),
"fieldmender_patient_repositioned": _tr("战地医师治疗并后移伤员", "FIELDMENDER TREATS AND MOVES THE PATIENT REARWARD"),
"aftercare_scheduled": _tr("续护治疗等待目标行动", "AFTERCARE WAITS FOR THE TARGET ACTION"),
"aftercare_resolved": _tr("续护治疗结算", "AFTERCARE RESOLVES"),
"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, super._region_event_text(event))
func _region_event_color(kind: String) -> Color:
if not p103_mode:
return super._region_event_color(kind)
if kind in ["channel_healing_backlash", "wet_pull_resolved", "authoritative_damage", "fallen"]:
return CORAL
if kind in ["channel_thread_adjacent", "wet_threshold_avoided", "fieldmender_patient_repositioned", "aftercare_resolved", "prepared_response_succeeded", "battle_terminal"]:
return JADE
return GOLD_LIGHT
func _route_summary(risk: int) -> String:
if not p103_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, LAW AND CURRENT LINE.")
return String(offer.preview) if locale == "en" else {
"RO-R2-M05-Q0": "先让连结队员相邻,再进行治疗。",
"RO-R2-M05-Q1": "集火治疗源头,避免连线与回复叠加。",
"RO-R2-M05-Q2": "守住中位,同时维持相邻小组。",
"RO-R2-M06-Q0": "等待安全位明确后再处理迟滞。",
"RO-R2-M06-Q1": "跟随安全位,让沉籽落在身后。",
"RO-R2-M06-Q2": "优先切断治疗,再应对反向安全位。",
"RO-R2-M07-Q0": "用相邻守护追随移动安全带。",
"RO-R2-M07-Q1": "集火源头并错开连线治疗。",
"RO-R2-M07-Q2": "保留移动,避免短射程被安全位抛下。",
}.get(String(offer.route_id), "深水道会同时检验站位、目标与时机。")
func _region_name(row_id: String) -> String:
if not p103_mode:
return super._region_name(row_id)
var name := P103Content.localized_name(p103_catalog, row_id, locale)
return super._region_name(row_id) if name.is_empty() else name
func _equipment_symbol(catalog_id: String) -> String:
if catalog_id in ["EQ-057", "EQ-058", "EQ-059", "EQ-060", "EQ-061", "EQ-062", "EQ-063", "EQ-064"]:
return {
"EQ-057": "∿", "EQ-058": "⌁", "EQ-059": "⋈", "EQ-060": "⇆",
"EQ-061": "⌗", "EQ-062": "⊞", "EQ-063": "↯", "EQ-064": "◫",
}[catalog_id]
return super._equipment_symbol(catalog_id)
func reset_progress() -> void:
if not p103_mode:
super.reset_progress()
return
if not reset_confirmation:
reset_confirmation = true
last_code = "reset_confirmation_required"
_queue_render()
return
for save_name in [P103_SAVE_PATH, P102_SAVE_PATH]:
for suffix in ["", ".bak", ".candidate"]:
var path := ProjectSettings.globalize_path(save_name + suffix)
if FileAccess.file_exists(path):
DirAccess.remove_absolute(path)
p103_mode = false
region_mode = false
region_market = null
entry_snapshot = {}
region_save_sequence = 0
region_save_commit_hash = ""
super.reset_progress()