@@ -57,7 +57,14 @@ const SECOND_BATTLE_PHASE_DURATIONS := {
" EQ-002 " : [ 1.35 , 1.00 , 1.10 , 1.05 , 1.30 , 1.25 ] ,
" EQ-061 " : [ 1.20 , 0.85 , 0.95 , 0.90 , 1.00 , 1.10 ] ,
}
const THIRD_BATTLE_PHASE_DURATIONS := {
" EQ-002 " : [ 1.20 , 0.80 , 1.00 , 1.00 , 1.10 , 1.10 , 1.40 ] ,
" EQ-061 " : [ 1.10 , 0.70 , 0.90 , 0.90 , 0.90 , 0.90 , 1.20 ] ,
" recruit " : [ 1.10 , 0.80 , 0.90 , 0.80 , 0.80 , 1.10 , 1.50 ] ,
}
const FIRST_VICTORY_COIN := 20
const RETAIN_ITEM_COST := 12
const RECRUIT_REAR_COST := 14
const BATTLE_FEEDBACK_DURATION := 0.68
const BATTLE_FEEDBACK_IMPACT_WINDOW := 0.24
@@ -111,7 +118,7 @@ const CANDIDATES := {
} ,
}
enum AppPage { BOOT , TITLE , YARD , BATTLE , VICTORY , ADAPTATION , SECOND_BATTLE , SECOND_RESULT }
enum AppPage { BOOT , TITLE , YARD , BATTLE , VICTORY , ADAPTATION , SECOND_BATTLE , SECOND_RESULT , GUILD_RETURN , THIRD_BATTLE , SESSION_RESULT }
const COLOR_INK := Color ( " #10182c " )
const COLOR_INDIGO := Color ( " #172745 " )
@@ -159,6 +166,12 @@ var adaptation_choice := ""
var adaptation_committed := false
var second_battle_elapsed := 0.0
var second_result_time := 0.0
var guild_choice := " "
var guild_committed := false
var retained_item := " "
var added_rear := " "
var third_battle_elapsed := 0.0
var third_result_time := 0.0
var animation_library : Dictionary = { }
var victory_clock := 0.0
var active_battle_feedback : Dictionary = { }
@@ -168,6 +181,7 @@ var battle_feedback_history: Array[Dictionary] = []
var display_root : Control
var front_sprite : TextureRect
var rear_sprite : TextureRect
var added_rear_sprite : TextureRect
var enemy_sprite : TextureRect
var battle_feedback_label : Label
var battle_feedback_vfx : TextureRect
@@ -178,6 +192,7 @@ var victory_enemy_sprite: TextureRect
var projectile : Control
var front_base_position := Vector2 . ZERO
var rear_base_position := Vector2 . ZERO
var added_rear_base_position := Vector2 . ZERO
var enemy_base_position := Vector2 . ZERO
var audio_player : AudioStreamPlayer
var sfx_playback : AudioStreamGeneratorPlayback
@@ -275,6 +290,8 @@ func animation_texture(character: String, state: String, elapsed: float) -> Text
func front_animation_state ( ) - > String :
if page == AppPage . THIRD_BATTLE and retained_item == " EQ-061 " and battle_phase == 1 :
return " basic_attack " if front_choice == " Hearthguard " else " evade_counter "
if page == AppPage . SECOND_BATTLE and adaptation_choice == " EQ-061 " and battle_phase == 1 :
return " basic_attack " if front_choice == " Hearthguard " else " evade_counter "
match battle_phase :
@@ -288,6 +305,8 @@ func front_animation_state() -> String:
func rear_animation_state ( ) - > String :
if page == AppPage . THIRD_BATTLE and battle_phase == 3 :
return " draw_release " if rear_choice == " Farstring " else " lantern_heal "
match battle_phase :
0 :
return " advance "
@@ -296,7 +315,29 @@ func rear_animation_state() -> String:
return " idle "
func added_rear_animation_state ( ) - > String :
if page == AppPage . THIRD_BATTLE and battle_phase == 4 :
return " draw_release " if added_rear == " Farstring " else " lantern_heal "
if battle_phase == 0 :
return " advance "
return " idle "
func enemy_animation_state ( ) - > String :
if page == AppPage . THIRD_BATTLE and retained_item == " EQ-061 " and battle_phase == 1 :
return " hit "
if page == AppPage . THIRD_BATTLE :
match battle_phase :
0 :
return " advance "
1 :
return " telegraph "
2 :
return " attack "
3 , 4 , 5 :
return " hit "
6 :
return " death "
if page == AppPage . SECOND_BATTLE and adaptation_choice == " EQ-061 " and battle_phase == 1 :
return " hit "
match battle_phase :
@@ -326,6 +367,7 @@ func render() -> void:
render_pending = false
front_sprite = null
rear_sprite = null
added_rear_sprite = null
enemy_sprite = null
battle_feedback_label = null
battle_feedback_vfx = null
@@ -335,7 +377,7 @@ func render() -> void:
projectile = null
for child in display_root . get_children ( ) :
child . free ( )
var background_texture : Texture2D = FIELD_BACKGROUND if page in [ AppPage . BATTLE , AppPage . VICTORY , AppPage . ADAPTATION , AppPage . SECOND_BATTLE , AppPage . SECOND_RESULT ] else YARD_BACKGROUND
var background_texture : Texture2D = FIELD_BACKGROUND if page in [ AppPage . BATTLE , AppPage . VICTORY , AppPage . ADAPTATION , AppPage . SECOND_BATTLE , AppPage . SECOND_RESULT , AppPage . THIRD_BATTLE , AppPage . SESSION_RESULT ] else YARD_BACKGROUND
add_background ( background_texture )
if portrait_layout ( ) :
render_portrait_notice ( )
@@ -357,6 +399,12 @@ func render() -> void:
render_battle ( )
AppPage . SECOND_RESULT :
render_second_result ( )
AppPage . GUILD_RETURN :
render_guild_return ( )
AppPage . THIRD_BATTLE :
render_battle ( )
AppPage . SESSION_RESULT :
render_session_result ( )
if restart_confirmation :
render_restart_confirmation ( )
@@ -564,20 +612,25 @@ func candidate_button(profession: String) -> Button:
func render_battle ( ) - > void :
add_full_wash ( Color ( 0.015 , 0.025 , 0.055 , 0.1 ) )
var second_cycle := page == AppPage . SECOND_BATTLE
render_hud ( tr_text ( " 第二次远征 · 老兵截道 " , " Second Expedition · Veteran Ambush " ) if second_cycle else tr_text ( " 第一次远征 · 风环遗迹 " , " First Expedition · Windring Ruins " ) )
var third_cycle := page == AppPage . THIRD_BATTLE
var battle_title := tr_text ( " 第三次远征 · 裂隙队长 " , " Third Expedition · Rift Captain " ) if third_cycle else ( tr_text ( " 第二次远征 · 老兵截道 " , " Second Expedition · Veteran Ambush " ) if second_cycle else tr_text ( " 第一次远征 · 风环遗迹 " , " First Expedition · Windring Ruins " ) )
render_hud ( battle_title )
var world_bottom := size . y - 58.0
var actor_height : float = clampf ( size . y * 0.27 , 92.0 , 170.0 )
var actor_width : float = actor_height
var contact_reached := battle_phase > 0 or reduced_motion
front_base_position = Vector2 ( size . x * ( 0.39 if contact_reached else 0.18 ) , world_bottom - actor_height )
rear_base_position = Vector2 ( size . x * ( 0.24 if contact_reached else 0.07 ) , world_bottom - actor_height * 0.92 )
rear_base_position = Vector2 ( size . x * ( 0.26 if contact_reached else 0.08 ) , world_bottom - actor_height * 0.92 )
added_rear_base_position = Vector2 ( size . x * ( 0.12 if contact_reached else - 0.06 ) , world_bottom - actor_height * 0.86 )
enemy_base_position = Vector2 ( size . x * ( 0.56 if contact_reached else 0.77 ) , world_bottom - actor_height * 0.84 )
front_sprite = actor_at ( animation_texture ( front_choice , front_animation_state ( ) , battle_clock ) , front_base_position , Vector2 ( actor_width , actor_height ) , " FrontActor " )
rear_sprite = actor_at ( animation_texture ( rear_choice , rear_animation_state ( ) , battle_clock ) , rear_base_position , Vector2 ( actor_width * 0.9 , actor_height * 0.9 ) , " RearActor " )
if third_cycle and not added_rear . is_empty ( ) :
added_rear_sprite = actor_at ( animation_texture ( added_rear , added_rear_animation_state ( ) , battle_clock ) , added_rear_base_position , Vector2 ( actor_width * 0.86 , actor_height * 0.86 ) , " AddedRearActor " )
enemy_sprite = actor_at ( animation_texture ( " Riftblade " , enemy_animation_state ( ) , battle_clock ) , enemy_base_position , Vector2 ( actor_width * 0.82 , actor_height * 0.82 ) , " EnemyActor " )
add_health_bar ( " FrontHealth " , Vector2 ( 22 , 72 ) , Vector2 ( size . x * 0.29 , 14 ) , front_hp , 100 , candidate_name ( front_choice ) )
add_health_bar ( " EnemyHealth " , Vector2 ( size . x * 0.69 - 22 , 72 ) , Vector2 ( size . x * 0.29 , 14 ) , enemy_hp , 100 , tr_text ( " 裂影刃手 " , " Riftblade " ) )
add_health_bar ( " EnemyHealth " , Vector2 ( size . x * 0.69 - 22 , 72 ) , Vector2 ( size . x * 0.29 , 14 ) , enemy_hp , 130 if third_cycle else 100 , tr_text ( " 裂隙队长 " , " Rift Captain " ) if third_cycle else tr_text ( " 裂影刃手 " , " Riftblade " ) )
var event := battle_event_text ( )
var ribbon := PanelContainer . new ( )
ribbon . name = " BattleEvent "
@@ -586,21 +639,32 @@ func render_battle() -> void:
ribbon . add_theme_stylebox_override ( " panel " , lacquer_style ( Color ( 0.04 , 0.07 , 0.13 , 0.94 ) , COLOR_GOLD , 2 ) )
ribbon . add_child ( centered_label ( event , 12 , COLOR_WHITE ) )
display_root . add_child ( ribbon )
if second_cycle :
var loan := centered_label ( ( " ▣ " if adaptation_choice == " EQ-002 " else " ◷ " ) + adaptation_name ( adaptation_choice ) , 11 , COLOR_GOLD_LIGHT )
if second_cycle or ( third_cycle and not retained_item . is_empty ( ) ) :
var visible_item := retained_item if third_cycle else adaptation_choice
var loan := centered_label ( ( " ▣ " if visible_item == " EQ-002 " else " ◷ " ) + adaptation_name ( visible_item ) , 11 , COLOR_GOLD_LIGHT )
loan . name = " ActiveAdaptation "
loan . position = Vector2 ( size . x * 0.02 , 116 )
loan . size = Vector2 ( size . x * 0.23 , 48 )
loan . add_theme_stylebox_override ( " normal " , lacquer_style ( Color ( 0.03 , 0.055 , 0.10 , 0.92 ) , COLOR_TEAL , 2 ) )
display_root . add_child ( loan )
elif third_cycle :
var company := centered_label ( tr_text ( " ◆ 三人小队 \n 弓手 + 治疗 " , " ◆ THREE-PERSON LINE \n Archer + Healer " ) , 11 , COLOR_GOLD_LIGHT )
company . name = " ActiveCompany "
company . position = Vector2 ( size . x * 0.02 , 116 )
company . size = Vector2 ( size . x * 0.23 , 48 )
company . add_theme_stylebox_override ( " normal " , lacquer_style ( Color ( 0.03 , 0.055 , 0.10 , 0.92 ) , COLOR_TEAL , 2 ) )
display_root . add_child ( company )
if battle_phase == 1 and ( not second_cycle or adaptation_choice = = " EQ-002 " ) :
if third_cycle and battle_phase == 1 and retained_item ! = " EQ-061 " :
add_signal_label ( " ! " , Vector2 ( size . x * 0.61 , size . y * 0.31 ) , COLOR_CRIMSON )
elif battle_phase == 1 and not third_cycle and ( not second_cycle or adaptation_choice == " EQ-002 " ) :
add_signal_label ( " ! " , Vector2 ( size . x * 0.61 , size . y * 0.31 ) , COLOR_CRIMSON )
elif second_cycle and adaptation_choice == " EQ-061 " and battle_phase == 2 :
add_signal_label ( " ! " , Vector2 ( size . x * 0.61 , size . y * 0.31 ) , COLOR_CRIMSON )
elif battle_phase == 3 :
if rear_choice == " Farstring " :
projectile = add_signal_label ( " ➤ " , Vector2 ( size . x * 0.48 , size . y * 0.50 ) , COLOR_GOLD_LIGHT )
elif battle_phase == 3 and rear_choice == " Farstring " :
projectile = add_signal_label ( " ➤ " , Vector2 ( size . x * 0.48 , size . y * 0.50 ) , COLOR_GOLD_LIGHT )
elif third_cycle and battle_phase == 4 and added_rear == " Farstring " :
projectile = add_signal_label ( " ➤ " , Vector2 ( size . x * 0.45 , size . y * 0.52 ) , COLOR_GOLD_LIGHT )
render_battle_feedback ( actor_width , actor_height )
update_battle_feedback_visual ( )
@@ -719,11 +783,106 @@ func render_second_result() -> void:
var facts := centered_label ( tr_text ( " 前排生命 %d / 100 \n 战斗用时 %.2f 秒 \n 金币保留 %d " % [ front_hp , second_result_time , coin ] , " Front HP %d / 100 \n Battle time %.2f s \n Coin kept %d " % [ front_hp , second_result_time , coin ] ) , 13 , Color ( " #6d5523 " ) )
facts . name = " SecondResultFacts "
box . add_child ( facts )
var replay := game_button ( " ReplayExperienceButton " , tr_text ( " 换一种选择重玩 " , " Replay with another choice " ) , request_restart , true )
box . add_child ( replay )
var return_button := game_button ( " OpenGuildReturnButton " , tr_text ( " 带着经验返回公会 " , " Return to the Guild " ) , open_guild_return , true )
box . add_child ( return_button )
box . add_child ( game_button ( " SecondResultHomeButton " , tr_text ( " 回到主页 " , " Home " ) , go_home , false ) )
func render_guild_return ( ) - > void :
add_full_wash ( Color ( 0.012 , 0.028 , 0.06 , 0.16 ) )
render_hud ( tr_text ( " 公会庭院 · 第三次远征准备 " , " Guild Yard · Prepare the third expedition " ) )
var actor_height : float = clampf ( size . y * 0.29 , 98.0 , 178.0 )
actor_at ( animation_texture ( front_choice , " idle " , victory_clock ) , Vector2 ( size . x * 0.055 , size . y - actor_height - 34 ) , Vector2 ( actor_height , actor_height ) , " GuildFront " )
actor_at ( animation_texture ( rear_choice , " idle " , victory_clock ) , Vector2 ( size . x * 0.19 , size . y - actor_height * 0.9 - 34 ) , Vector2 ( actor_height * 0.9 , actor_height * 0.9 ) , " GuildRear " )
if guild_choice == " recruit_rear " :
var preview_rear := missing_rear_profession ( )
actor_at ( animation_texture ( preview_rear , " idle " , victory_clock ) , Vector2 ( size . x * 0.315 , size . y - actor_height * 0.82 - 36 ) , Vector2 ( actor_height * 0.82 , actor_height * 0.82 ) , " GuildRecruitPreview " )
var resources := PanelContainer . new ( )
resources . name = " GuildResources "
resources . position = Vector2 ( 18 , 74 )
resources . size = Vector2 ( size . x * 0.35 , 76 )
resources . add_theme_stylebox_override ( " panel " , lacquer_style ( Color ( 0.035 , 0.06 , 0.12 , 0.94 ) , COLOR_GOLD , 2 ) )
display_root . add_child ( resources )
var resource_copy := VBoxContainer . new ( )
resources . add_child ( resource_copy )
resource_copy . add_child ( game_label ( tr_text ( " 带回公会的远征资源 " , " EXPEDITION RESOURCES RETURNED " ) , 10 , COLOR_TEAL ) )
resource_copy . add_child ( game_label ( tr_text ( " 金币 %d | 小队 2 人 \n 借用装备: %s " % [ coin , adaptation_name ( adaptation_choice ) ] , " Coin %d | Company 2 \n Loaned item: %s " % [ coin , adaptation_name ( adaptation_choice ) ] ) , 12 , COLOR_WHITE ) )
var bench := PanelContainer . new ( )
bench . name = " PreparationBench "
bench . position = Vector2 ( size . x * 0.40 , 72 )
bench . size = Vector2 ( size . x * 0.58 , size . y - 88 )
bench . add_theme_stylebox_override ( " panel " , lacquer_style ( Color ( 0.025 , 0.05 , 0.10 , 0.95 ) , COLOR_GOLD , 2 ) )
display_root . add_child ( bench )
var shell := VBoxContainer . new ( )
shell . add_theme_constant_override ( " separation " , 5 )
bench . add_child ( shell )
shell . add_child ( game_label ( tr_text ( " 队长更强:把金币花在哪? " , " THE CAPTAIN IS STRONGER: where should Coin go? " ) , 16 , COLOR_WHITE ) )
shell . add_child ( game_label ( guild_choice_guidance ( ) , 10 , COLOR_TEAL ) )
var choices := HBoxContainer . new ( )
choices . add_theme_constant_override ( " separation " , 7 )
shell . add_child ( choices )
choices . add_child ( guild_choice_button ( " retain_item " ) )
choices . add_child ( guild_choice_button ( " recruit_rear " ) )
var depart_button := game_button ( " BeginThirdBattle " , tr_text ( " 确认花费并出发 " , " Confirm spend and depart " ) , begin_third_battle , true )
depart_button . disabled = guild_choice . is_empty ( )
depart_button . custom_minimum_size . y = 48
shell . add_child ( depart_button )
func guild_choice_button ( id : String ) - > Button :
var selected := guild_choice == id
var button := Button . new ( )
button . name = " GuildChoice_ %s " % id
if id == " retain_item " :
button . text = tr_text ( " ▣ 保留 %s · 12 金币 \n 小队保持 2 人|剩余 8 金币 \n 继续发挥已验证的装备效果 " % adaptation_name ( adaptation_choice ) , " ▣ RETAIN %s · 12 Coin \n Company stays 2 | 8 Coin left \n Keep the proven item effect " % adaptation_name ( adaptation_choice ) )
else :
button . text = tr_text ( " + 招募%s · 14 金币 \n 小队扩至 3 人|剩余 6 金币 \n 归还借用装备,补齐弓手与治疗 " % candidate_name ( missing_rear_profession ( ) ) , " + RECRUIT %s · 14 Coin \n Company grows to 3 | 6 Coin left \n Return item; gain Archer and Healer " % candidate_name ( missing_rear_profession ( ) ) )
button . focus_mode = Control . FOCUS_ALL
button . size_flags_horizontal = Control . SIZE_EXPAND_FILL
button . custom_minimum_size = Vector2 ( 215 , 112 )
button . add_theme_font_size_override ( " font_size " , scaled ( 10 ) )
button . add_theme_color_override ( " font_color " , COLOR_GOLD_LIGHT if selected else COLOR_WHITE )
button . add_theme_stylebox_override ( " normal " , candidate_style ( selected , false , COLOR_TEAL ) )
button . add_theme_stylebox_override ( " hover " , candidate_style ( selected , true , COLOR_TEAL ) )
button . add_theme_stylebox_override ( " pressed " , candidate_style ( true , true , COLOR_TEAL ) )
button . pressed . connect ( func ( ) : choose_guild_option ( id ) , CONNECT_DEFERRED )
return button
func render_session_result ( ) - > void :
add_full_wash ( Color ( 0.02 , 0.035 , 0.075 , 0.18 ) )
render_hud ( tr_text ( " 首轮远征完成 " , " First Expedition Cycle Complete " ) )
var actor_height : float = clampf ( size . y * 0.29 , 98.0 , 180.0 )
actor_at ( animation_texture ( front_choice , " victory " if front_choice == " Hearthguard " else " idle " , victory_clock ) , Vector2 ( size . x * 0.05 , size . y - actor_height - 38 ) , Vector2 ( actor_height , actor_height ) , " SessionFront " )
actor_at ( animation_texture ( rear_choice , " idle " , victory_clock ) , Vector2 ( size . x * 0.18 , size . y - actor_height * 0.9 - 38 ) , Vector2 ( actor_height * 0.9 , actor_height * 0.9 ) , " SessionRear " )
if not added_rear . is_empty ( ) :
actor_at ( animation_texture ( added_rear , " idle " , victory_clock ) , Vector2 ( size . x * 0.30 , size . y - actor_height * 0.84 - 38 ) , Vector2 ( actor_height * 0.84 , actor_height * 0.84 ) , " SessionAddedRear " )
actor_at ( animation_texture ( " Riftblade " , " death " , 99.0 ) , Vector2 ( size . x * 0.45 , size . y - actor_height * 0.52 - 36 ) , Vector2 ( actor_height * 0.52 , actor_height * 0.52 ) , " SessionEnemy " )
var result := PanelContainer . new ( )
result . name = " SessionResultPanel "
result . position = Vector2 ( size . x * 0.57 , 78 )
result . size = Vector2 ( size . x * 0.40 , size . y - 100 )
result . add_theme_stylebox_override ( " panel " , parchment_style ( ) )
display_root . add_child ( result )
var box := VBoxContainer . new ( )
box . alignment = BoxContainer . ALIGNMENT_CENTER
box . add_theme_constant_override ( " separation " , 5 )
result . add_child ( box )
var headline := tr_text ( " 装备让两人小队守住优势 " , " THE ITEM KEPT A TWO-PERSON EDGE " ) if guild_choice == " retain_item " else tr_text ( " 第三名成员补齐了远程与治疗 " , " A THIRD MEMBER COMPLETED THE REAR LINE " )
box . add_child ( centered_label ( " ◆ " + headline + " ◆ " , 17 , COLOR_INK ) )
var explanation := centered_label ( session_result_explanation ( ) , 11 , Color ( " #4c402d " ) )
explanation . name = " SessionResultExplanation "
box . add_child ( explanation )
var company_size := 3 if not added_rear . is_empty ( ) else 2
var facts := centered_label ( tr_text ( " 金币 %d | 小队 %d 人 \n 前排生命 %d / 100 | 用时 %.2f 秒 " % [ coin , company_size , front_hp , third_result_time ] , " Coin %d | Company %d \n Front HP %d / 100 | Time %.2f s " % [ coin , company_size , front_hp , third_result_time ] ) , 12 , Color ( " #6d5523 " ) )
facts . name = " SessionResultFacts "
box . add_child ( facts )
box . add_child ( game_button ( " ReplayExperienceButton " , tr_text ( " 用另一条路线重新开始 " , " Restart with another route " ) , request_restart , true ) )
box . add_child ( game_button ( " SessionResultHomeButton " , tr_text ( " 回到主页 " , " Home " ) , go_home , false ) )
func render_restart_confirmation ( ) - > void :
var wash := ColorRect . new ( )
wash . name = " RestartWash "
@@ -825,11 +984,19 @@ func depart() -> void:
func advance_battle_phase ( ) - > void :
if page not in [ AppPage . BATTLE , AppPage . SECOND_BATTLE ] or not battle_playing :
if page not in [ AppPage . BATTLE , AppPage . SECOND_BATTLE , AppPage . THIRD_BATTLE ] or not battle_playing :
return
commit_phase_impact ( battle_phase , false )
battle_phase + = 1
battle_clock = 0.0
if page == AppPage . THIRD_BATTLE :
if battle_phase == 7 :
battle_playing = false
victory_events + = 1
third_result_time = third_battle_elapsed
page = AppPage . SESSION_RESULT
render ( )
return
match battle_phase :
6 :
battle_playing = false
@@ -844,6 +1011,18 @@ func advance_battle_phase() -> void:
func phase_impact_time ( phase : int ) - > float :
if page == AppPage . THIRD_BATTLE :
match phase :
1 :
return 3.0 / 12.0 if retained_item == " EQ-061 " else - 1.0
2 :
return 4.0 / 12.0
3 , 4 :
return 2.0 / 7.0
5 :
return 3.0 / 10.0
6 :
return 5.0 / 10.0
if page == AppPage . SECOND_BATTLE and adaptation_choice == " EQ-061 " :
match phase :
1 :
@@ -869,6 +1048,11 @@ func phase_impact_time(phase: int) -> float:
func phase_duration ( phase : int ) - > float :
if page == AppPage . THIRD_BATTLE :
var route := " recruit " if not added_rear . is_empty ( ) else retained_item
if THIRD_BATTLE_PHASE_DURATIONS . has ( route ) :
var third_durations : Array = THIRD_BATTLE_PHASE_DURATIONS [ route ]
return float ( third_durations [ phase ] ) if phase > = 0 and phase < third_durations . size ( ) else 0.0
if page == AppPage . SECOND_BATTLE and SECOND_BATTLE_PHASE_DURATIONS . has ( adaptation_choice ) :
var durations : Array = SECOND_BATTLE_PHASE_DURATIONS [ adaptation_choice ]
return float ( durations [ phase ] ) if phase > = 0 and phase < durations . size ( ) else 0.0
@@ -885,6 +1069,9 @@ func commit_phase_impact(phase: int, refresh_runtime := true) -> void:
var qualifier_zh := " "
var qualifier_en := " "
var tier := " medium "
if page == AppPage . THIRD_BATTLE :
commit_third_phase_impact ( phase , refresh_runtime )
return
if page == AppPage . SECOND_BATTLE :
commit_second_phase_impact ( phase , refresh_runtime )
return
@@ -1010,6 +1197,102 @@ func commit_second_phase_impact(phase: int, refresh_runtime := true) -> void:
render ( )
func commit_third_phase_impact ( phase : int , refresh_runtime := true ) - > void :
if phase_impact_committed . get ( phase , false ) :
return
var target := " "
var delta := 0
var qualifier_zh := " "
var qualifier_en := " "
var tier := " medium "
match phase :
1 :
if retained_item != " EQ-061 " :
return
var previous_enemy_hp := enemy_hp
enemy_hp = 102
target = " enemy "
delta = enemy_hp - previous_enemy_hp
qualifier_zh = " 晨钟先手 "
qualifier_en = " Early Bell "
play_cue ( " hit " )
2 :
var previous_front_hp := front_hp
front_hp = 90 if retained_item == " EQ-002 " else 70
target = " front "
delta = front_hp - previous_front_hp
qualifier_zh = " 装备抗推 " if retained_item == " EQ-002 " else " 队长重击 "
qualifier_en = " item resisted " if retained_item == " EQ-002 " else " captain strike "
guard_events + = 1
play_cue ( " hit " )
3 :
if rear_choice == " Farstring " :
var previous_enemy_hp := enemy_hp
enemy_hp = 66 if added_rear . is_empty ( ) else 94
target = " enemy "
delta = enemy_hp - previous_enemy_hp
qualifier_zh = " 弓手命中 "
qualifier_en = " Archer hit "
else :
var previous_front_hp := front_hp
front_hp = mini ( 100 , front_hp + 12 )
target = " front "
delta = front_hp - previous_front_hp
qualifier_zh = " 治疗 "
qualifier_en = " healing "
rear_events + = 1
play_cue ( " role " )
4 :
if not added_rear . is_empty ( ) :
if added_rear == " Farstring " :
var previous_enemy_hp := enemy_hp
enemy_hp = 94
target = " enemy "
delta = enemy_hp - previous_enemy_hp
qualifier_zh = " 新弓手命中 "
qualifier_en = " new Archer hit "
else :
var previous_front_hp := front_hp
front_hp = mini ( 100 , front_hp + 12 )
target = " front "
delta = front_hp - previous_front_hp
qualifier_zh = " 新治疗支援 "
qualifier_en = " new Healer support "
rear_events + = 1
play_cue ( " role " )
else :
var previous_enemy_hp := enemy_hp
enemy_hp = 44
target = " enemy "
delta = enemy_hp - previous_enemy_hp
qualifier_zh = " 装备连携 "
qualifier_en = " item follow-up "
play_cue ( " hit " )
5 :
var previous_enemy_hp := enemy_hp
enemy_hp = 32 if not added_rear . is_empty ( ) else 26
target = " enemy "
delta = enemy_hp - previous_enemy_hp
qualifier_zh = " 三人连携 " if not added_rear . is_empty ( ) else " 压制 "
qualifier_en = " three-person combo " if not added_rear . is_empty ( ) else " pressure "
play_cue ( " hit " )
6 :
var previous_enemy_hp := enemy_hp
enemy_hp = 0
target = " enemy "
delta = enemy_hp - previous_enemy_hp
qualifier_zh = " 队长击破 "
qualifier_en = " captain defeated "
tier = " large "
play_cue ( " victory " )
_ :
return
phase_impact_committed [ phase ] = true
emit_battle_feedback ( phase , target , delta , qualifier_zh , qualifier_en , tier )
if refresh_runtime and page == AppPage . THIRD_BATTLE :
render ( )
func emit_battle_feedback ( phase : int , target : String , delta : int , qualifier_zh : String , qualifier_en : String , tier : String ) - > void :
active_battle_feedback = {
" phase " : phase ,
@@ -1043,7 +1326,7 @@ func expire_battle_feedback() -> void:
func _process ( delta : float ) - > void :
if page in [ AppPage . VICTORY , AppPage . ADAPTATION , AppPage . SECOND_RESULT ] :
if page in [ AppPage . VICTORY , AppPage . ADAPTATION , AppPage . SECOND_RESULT , AppPage . GUILD_RETURN , AppPage . SESSION_RESULT ] :
if front_choice . is_empty ( ) or rear_choice . is_empty ( ) :
return
victory_clock + = delta
@@ -1051,12 +1334,14 @@ func _process(delta: float) -> void:
victory_front_sprite . texture = animation_texture ( front_choice , " victory " if front_choice == " Hearthguard " else " idle " , victory_clock )
victory_rear_sprite . texture = animation_texture ( rear_choice , " idle " , victory_clock )
return
if page not in [ AppPage . BATTLE , AppPage . SECOND_BATTLE ] or not battle_playing or restart_confirmation :
if page not in [ AppPage . BATTLE , AppPage . SECOND_BATTLE , AppPage . THIRD_BATTLE ] or not battle_playing or restart_confirmation :
return
var feedback_was_active := not active_battle_feedback . is_empty ( )
battle_clock + = delta
if page == AppPage . SECOND_BATTLE :
second_battle_elapsed + = delta
elif page == AppPage . THIRD_BATTLE :
third_battle_elapsed + = delta
var impact_time := phase_impact_time ( battle_phase )
if impact_time > = 0.0 and battle_clock > = impact_time :
commit_phase_impact ( battle_phase )
@@ -1070,13 +1355,18 @@ func _process(delta: float) -> void:
if battle_phase == 0 and not reduced_motion :
front_sprite . position . x = lerp ( size . x * 0.13 , size . x * 0.38 , progress )
rear_sprite . position . x = lerp ( size . x * 0.03 , size . x * 0.20 , progress )
if added_rear_sprite != null and is_instance_valid ( added_rear_sprite ) :
added_rear_sprite . position . x = lerp ( - added_rear_sprite . size . x , size . x * 0.10 , progress )
enemy_sprite . position . x = lerp ( size . x * 0.82 , size . x * 0.57 , progress )
front_sprite . texture = animation_texture ( front_choice , front_animation_state ( ) , battle_clock )
rear_sprite . texture = animation_texture ( rear_choice , rear_animation_state ( ) , battle_clock )
if added_rear_sprite != null and is_instance_valid ( added_rear_sprite ) :
added_rear_sprite . texture = animation_texture ( added_rear , added_rear_animation_state ( ) , battle_clock )
enemy_sprite . texture = animation_texture ( " Riftblade " , enemy_animation_state ( ) , battle_clock )
if projectile ! = null and is_instance_valid ( projectile ) and battle_phase == 3 and rear_choice == " Farstring " and not reduced_motion :
var projectile_actor_is_active : = ( battle_phase == 3 and rear_choice == " Farstring " ) or ( page == AppPage . THIRD_BATTLE and battle_phase == 4 and added_rear == " Farstring " )
if projectile != null and is_instance_valid ( projectile ) and projectile_actor_is_active and not reduced_motion :
var projectile_start := 0.08
var projectile_end := phase_impact_time ( 3 ) + 0.10
var projectile_end := phase_impact_time ( battle_phase ) + 0.10
var projectile_progress := clampf ( ( battle_clock - projectile_start ) / ( projectile_end - projectile_start ) , 0.0 , 1.0 )
projectile . visible = battle_clock > = projectile_start and battle_clock < = projectile_end
projectile . position . x = lerp ( size . x * 0.42 , size . x * 0.64 , projectile_progress )
@@ -1128,6 +1418,59 @@ func begin_second_battle() -> void:
render ( )
func open_guild_return ( ) - > void :
if page != AppPage . SECOND_RESULT or not adaptation_committed :
return
guild_choice = " "
guild_committed = false
retained_item = " "
added_rear = " "
page = AppPage . GUILD_RETURN
play_cue ( " confirm " )
render ( )
func choose_guild_option ( id : String ) - > void :
if page != AppPage . GUILD_RETURN or guild_committed or id not in [ " retain_item " , " recruit_rear " ] :
return
var cost := RETAIN_ITEM_COST if id == " retain_item " else RECRUIT_REAR_COST
if coin < cost :
return
guild_choice = id
play_cue ( " select " )
render ( )
func begin_third_battle ( ) - > void :
if page != AppPage . GUILD_RETURN or guild_committed or guild_choice . is_empty ( ) :
return
var cost := RETAIN_ITEM_COST if guild_choice == " retain_item " else RECRUIT_REAR_COST
if coin < cost :
return
guild_committed = true
coin - = cost
if guild_choice == " retain_item " :
retained_item = adaptation_choice
added_rear = " "
else :
retained_item = " "
added_rear = missing_rear_profession ( )
battle_phase = 0
battle_clock = 0.0
battle_playing = true
front_hp = 100
enemy_hp = 130
guard_events = 0
rear_events = 0
phase_impact_committed . clear ( )
reset_battle_feedback ( )
third_battle_elapsed = 0.0
third_result_time = 0.0
page = AppPage . THIRD_BATTLE
play_cue ( " depart " )
render ( )
func request_restart ( ) - > void :
restart_confirmation = true
render ( )
@@ -1172,6 +1515,12 @@ func clear_experience() -> void:
adaptation_committed = false
second_battle_elapsed = 0.0
second_result_time = 0.0
guild_choice = " "
guild_committed = false
retained_item = " "
added_rear = " "
third_battle_elapsed = 0.0
third_result_time = 0.0
phase_impact_committed . clear ( )
reset_battle_feedback ( )
victory_clock = 0.0
@@ -1201,6 +1550,8 @@ func toggle_mute() -> void:
func battle_event_text ( ) - > String :
if page == AppPage . THIRD_BATTLE :
return third_battle_event_text ( )
if page == AppPage . SECOND_BATTLE :
return second_battle_event_text ( )
match battle_phase :
@@ -1242,6 +1593,27 @@ func second_battle_event_text() -> String:
return " "
func third_battle_event_text ( ) - > String :
match battle_phase :
0 :
return tr_text ( " 小队向裂隙队长推进… " , " The squad advances on the Rift Captain… " )
1 :
return tr_text ( " 晨钟先手:前排抢在队长之前行动 " , " Early Bell: the front acts before the captain " ) if retained_item == " EQ-061 " else tr_text ( " 队长蓄力,准备撕开前排 " , " The captain gathers force against the front " )
2 :
return tr_text ( " 钉阵胫甲守住阵线,重击只造成 -10 " , " Frontpin holds the line; the strike deals only -10 " ) if retained_item == " EQ-002 " else tr_text ( " 队长重击前排;现在需要后排支援 " , " The captain hits the front; rear support is needed " )
3 :
return tr_text ( " 原后排弓手命中队长 " , " The original Archer hits the captain " ) if rear_choice == " Farstring " else tr_text ( " 原后排治疗为前排恢复 +12 " , " The original Healer restores +12 to the front " )
4 :
if not added_rear . is_empty ( ) :
return tr_text ( " 新弓手补上远程伤害 " , " The new Archer adds ranged damage " ) if added_rear == " Farstring " else tr_text ( " 新治疗补上 +12 恢复 " , " The new Healer adds +12 recovery " )
return tr_text ( " 保留装备继续放大两人连携 " , " The retained item amplifies the two-person follow-up " )
5 :
return tr_text ( " 三人阵线合力压制队长 " , " The three-person line overwhelms the captain " ) if not added_rear . is_empty ( ) else tr_text ( " 两人阵线守住装备优势 " , " The two-person line keeps its item advantage " )
6 :
return tr_text ( " 裂隙队长被击败! " , " Rift Captain defeated! " )
return " "
func adaptation_name ( id : String ) - > String :
return tr_text ( " 钉阵胫甲 " , " Frontpin Greaves " ) if id == " EQ-002 " else tr_text ( " 晨钟 " , " Early Bell " )
@@ -1266,6 +1638,26 @@ func second_result_explanation() -> String:
return tr_text ( " 晨钟让前排在敌人之前造成 -28, \n 但前排完整承受了 -30 重击。 " , " Early Bell dealt -28 before the enemy acted, \n but the front took the full -30 strike. " )
func missing_rear_profession ( ) - > String :
return " Kindhand " if rear_choice == " Farstring " else " Farstring "
func guild_choice_guidance ( ) - > String :
if guild_choice . is_empty ( ) :
return tr_text ( " 没有预选。保留已验证的装备,或用更多金币补齐后排。 " , " Nothing is preselected. Keep the proven item, or spend more to complete the rear line. " )
if guild_choice == " retain_item " :
return tr_text ( " 已选择保留装备;剩余 8 金币、小队 2 人。出发前仍可切换。 " , " Retain selected: 8 Coin, Company 2. You can still switch before departure. " )
return tr_text ( " 已选择招募 %s ;剩余 6 金币、小队 3 人。出发前仍可切换。 " % candidate_name ( missing_rear_profession ( ) ) , " Recruit %s selected: 6 Coin, Company 3. You can still switch. " % candidate_name ( missing_rear_profession ( ) ) )
func session_result_explanation ( ) - > String :
if guild_choice == " retain_item " :
if retained_item == " EQ-002 " :
return tr_text ( " 你把金币用于保留钉阵胫甲。 \n 队伍仍是两人,但队长重击从 -30 降为 -10。 " , " You spent Coin to retain Frontpin. \n The line stayed at two, but the captain strike fell from -30 to -10. " )
return tr_text ( " 你把金币用于保留晨钟。 \n 队伍仍是两人,但先手让战斗更快结束。 " , " You spent Coin to retain Early Bell. \n The line stayed at two, but initiative ended the battle faster. " )
return tr_text ( " 你归还借用装备并招募了 %s 。 \n 弓手与治疗都行动:阵线更完整,但花费更多。 " % candidate_name ( added_rear ) , " You returned the loan and recruited %s . \n Archer and Healer both acted: a fuller line at greater cost. " % candidate_name ( added_rear ) )
func candidate_name ( profession : String ) - > String :
if profession . is_empty ( ) or not CANDIDATES . has ( profession ) :
return " "