1004 lines
36 KiB
GDScript
1004 lines
36 KiB
GDScript
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()
|