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()