new battle gui
This commit is contained in:
1 parent
86e3de904e
commit
ba2c08b1dc
22 files changed
+1613
-120
No files matched your search
@@ -12,3 +12,49 @@ ff_battle/select_target = Select a target
|
|||||||
ff_battle/escape_fail = Escape failed!
|
ff_battle/escape_fail = Escape failed!
|
||||||
ff_battle/escape_success = You escaped!
|
ff_battle/escape_success = You escaped!
|
||||||
ff_battle/no_actions = No actions
|
ff_battle/no_actions = No actions
|
||||||
|
|
||||||
|
ff_battle/hud_title = Battle
|
||||||
|
ff_battle/round = Round
|
||||||
|
ff_battle/enemies = Enemies
|
||||||
|
ff_battle/allies = Party
|
||||||
|
ff_battle/phase_player = Player turn
|
||||||
|
ff_battle/phase_enemy = Enemy turn
|
||||||
|
ff_battle/banner_player = Player turn
|
||||||
|
ff_battle/banner_enemy = Enemy turn
|
||||||
|
ff_battle/hint_target = Select a target · RMB / Esc — cancel
|
||||||
|
ff_battle/hint_ally = Select an ally · RMB / Esc — cancel
|
||||||
|
ff_battle/menu_caption = {0} · AP {1}/{2}
|
||||||
|
ff_battle/menu_actions = Actions
|
||||||
|
|
||||||
|
ff_battle/log_title = Battle log
|
||||||
|
ff_battle/log_round = round {0}
|
||||||
|
ff_battle/log_battle_start = Battle started
|
||||||
|
ff_battle/log_turn_player = — Player turn · round {0} —
|
||||||
|
ff_battle/log_turn_enemy = — Enemy turn · round {0} —
|
||||||
|
ff_battle/log_action = {0}: {1} → {2}
|
||||||
|
ff_battle/log_action_self = {0}: {1}
|
||||||
|
ff_battle/log_damage = {0}: {1} → {2} · -{3} HP
|
||||||
|
ff_battle/log_shield = {0}'s shield absorbed {1} · left {2}
|
||||||
|
ff_battle/log_defence = Defend: {0} · +{1} DEF (total {2})
|
||||||
|
ff_battle/log_stun = Stunned: {0} · {1} turns
|
||||||
|
ff_battle/log_stun_skip = {0} is stunned — skips the turn
|
||||||
|
ff_battle/log_heal = {0}: {1} restored {2} HP
|
||||||
|
ff_battle/log_death = {0} defeated
|
||||||
|
ff_battle/log_escape_success = Escape succeeded
|
||||||
|
ff_battle/log_escape_failed = Escape failed! {0} loses 1 AP
|
||||||
|
ff_battle/log_no_points = The party is out of AP
|
||||||
|
|
||||||
|
ff_battle/detail_attack = Character action submenu. AP cost is shown next to the name.
|
||||||
|
ff_battle/detail_items = 1 AP per item. Targeted items require an ally.
|
||||||
|
ff_battle/detail_defend = Shortcut: the ally-target action is applied to self. Shield +4 DEF.
|
||||||
|
ff_battle/detail_escape = 70% success chance. On failure the character loses 1 AP.
|
||||||
|
ff_battle/detail_back = Return to the main command menu.
|
||||||
|
ff_battle/detail_action = Special action without numeric parameters.
|
||||||
|
ff_battle/detail_item = An item from the shared inventory.
|
||||||
|
ff_battle/detail_damage = Damage {0} (ATK {1} × {2}) · cost {3} AP
|
||||||
|
ff_battle/detail_stun = Stun for {0} turns · cost {1} AP
|
||||||
|
ff_battle/detail_defence = Target shield +{0} · cost {1} AP
|
||||||
|
|
||||||
|
ff_battle/status_stun = Stunned {0}
|
||||||
|
ff_battle/badge_turns = {0} turns
|
||||||
|
ff_battle/badge_self = Self
|
||||||
@@ -12,3 +12,49 @@ ff_battle/select_target=Выберите цель
|
|||||||
ff_battle/escape_fail=Побег не удался!
|
ff_battle/escape_fail=Побег не удался!
|
||||||
ff_battle/escape_success=Вы сбежали!
|
ff_battle/escape_success=Вы сбежали!
|
||||||
ff_battle/no_actions=Нет действий
|
ff_battle/no_actions=Нет действий
|
||||||
|
|
||||||
|
ff_battle/hud_title=Бой
|
||||||
|
ff_battle/round=Раунд
|
||||||
|
ff_battle/enemies=Врагов
|
||||||
|
ff_battle/allies=Отряд
|
||||||
|
ff_battle/phase_player=Ход игрока
|
||||||
|
ff_battle/phase_enemy=Ход врага
|
||||||
|
ff_battle/banner_player=Ход игрока
|
||||||
|
ff_battle/banner_enemy=Ход врага
|
||||||
|
ff_battle/hint_target=Выберите цель · ПКМ / Esc — отмена
|
||||||
|
ff_battle/hint_ally=Выберите союзника · ПКМ / Esc — отмена
|
||||||
|
ff_battle/menu_caption={0} · ОД {1}/{2}
|
||||||
|
ff_battle/menu_actions=Действия
|
||||||
|
|
||||||
|
ff_battle/log_title=Журнал боя
|
||||||
|
ff_battle/log_round=раунд {0}
|
||||||
|
ff_battle/log_battle_start=Бой начался
|
||||||
|
ff_battle/log_turn_player=— Ход игрока · раунд {0} —
|
||||||
|
ff_battle/log_turn_enemy=— Ход врага · раунд {0} —
|
||||||
|
ff_battle/log_action={0}: {1} → {2}
|
||||||
|
ff_battle/log_action_self={0}: {1}
|
||||||
|
ff_battle/log_damage={0}: {1} → {2} · −{3} HP
|
||||||
|
ff_battle/log_shield=Щит {0} поглотил {1} · осталось {2}
|
||||||
|
ff_battle/log_defence=Защита: {0} · +{1} ЗАЩ (всего {2})
|
||||||
|
ff_battle/log_stun=Ошеломление: {0} · {1} хода
|
||||||
|
ff_battle/log_stun_skip={0} ошеломлён — пропускает ход
|
||||||
|
ff_battle/log_heal={0}: {1} восстановил {2} HP
|
||||||
|
ff_battle/log_death={0} повержен
|
||||||
|
ff_battle/log_escape_success=Побег удался
|
||||||
|
ff_battle/log_escape_failed=Побег не удался! {0} теряет 1 ОД
|
||||||
|
ff_battle/log_no_points=У отряда закончились ОД
|
||||||
|
|
||||||
|
ff_battle/detail_attack=Подменю действий персонажа. Стоимость в ОД указана справа от названия.
|
||||||
|
ff_battle/detail_items=Расход 1 ОД за предмет. Предметы «по цели» требуют выбора союзника.
|
||||||
|
ff_battle/detail_defend=Шорткат: действие «по союзнику» применяется на себя. Щит +4 ЗАЩ.
|
||||||
|
ff_battle/detail_escape=Шанс успеха 70%. При провале персонаж теряет 1 ОД.
|
||||||
|
ff_battle/detail_back=Вернуться в главное меню команд.
|
||||||
|
ff_battle/detail_action=Особое действие без числовых параметров.
|
||||||
|
ff_battle/detail_item=Предмет из общего инвентаря.
|
||||||
|
ff_battle/detail_damage=Урон {0} (АТК {1} × {2}) · расход {3} ОД
|
||||||
|
ff_battle/detail_stun=Ошеломление на {0} хода · расход {1} ОД
|
||||||
|
ff_battle/detail_defence=Щит цели +{0} · расход {1} ОД
|
||||||
|
|
||||||
|
ff_battle/status_stun=Ошеломлён {0}
|
||||||
|
ff_battle/badge_turns={0} хода
|
||||||
|
ff_battle/badge_self=На себя
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="src/UI/FFActionDetailPanel.cs" id="1_script"]
|
||||||
|
|
||||||
|
[node name="FFActionDetailPanel" type="Control" script=ExtResource("1_script")]
|
||||||
|
custom_minimum_size = Vector2(300, 160)
|
||||||
|
layout_mode = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Box" type="VBoxContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/separation = 10
|
||||||
|
|
||||||
|
[node name="Card" type="PanelContainer" parent="Box"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="CardRow" type="HBoxContainer" parent="Box/Card"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/separation = 0
|
||||||
|
|
||||||
|
[node name="Stripe" type="ColorRect" parent="Box/Card/CardRow"]
|
||||||
|
custom_minimum_size = Vector2(4, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="IconMargin" type="MarginContainer" parent="Box/Card/CardRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/margin_left = 8
|
||||||
|
theme_override_constants/margin_top = 8
|
||||||
|
theme_override_constants/margin_right = 12
|
||||||
|
theme_override_constants/margin_bottom = 8
|
||||||
|
|
||||||
|
[node name="CardIcon" type="TextureRect" parent="Box/Card/CardRow/IconMargin"]
|
||||||
|
custom_minimum_size = Vector2(52, 52)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
mouse_filter = 2
|
||||||
|
expand_mode = 1
|
||||||
|
stretch_mode = 5
|
||||||
|
|
||||||
|
[node name="Body" type="VBoxContainer" parent="Box/Card/CardRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 4
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="NameRow" type="HBoxContainer" parent="Box/Card/CardRow/Body"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
|
||||||
|
[node name="NameLabel" type="Label" parent="Box/Card/CardRow/Body/NameRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
clip_text = true
|
||||||
|
text_overrun_behavior = 3
|
||||||
|
|
||||||
|
[node name="IdLabel" type="Label" parent="Box/Card/CardRow/Body/NameRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 9
|
||||||
|
|
||||||
|
[node name="Badges" type="HFlowContainer" parent="Box/Card/CardRow/Body"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/h_separation = 5
|
||||||
|
theme_override_constants/v_separation = 5
|
||||||
|
|
||||||
|
[node name="Props" type="HFlowContainer" parent="Box/Card/CardRow/Body"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/h_separation = 5
|
||||||
|
theme_override_constants/v_separation = 3
|
||||||
|
|
||||||
|
[node name="Note" type="PanelContainer" parent="Box"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="NoteLabel" type="Label" parent="Box/Note"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
autowrap_mode = 3
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="src/UI/FFBattleBanner.cs" id="1_script"]
|
||||||
|
|
||||||
|
[node name="FFBattleBanner" type="Control" script=ExtResource("1_script")]
|
||||||
|
layout_mode = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Center" type="CenterContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Panel" type="PanelContainer" parent="Center"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Center/Panel"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 34
|
||||||
|
horizontal_alignment = 1
|
||||||
|
uppercase = true
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="src/UI/FFBattleHud.cs" id="1_script"]
|
||||||
|
|
||||||
|
[node name="FFBattleHud" type="Control" script=ExtResource("1_script")]
|
||||||
|
custom_minimum_size = Vector2(380, 92)
|
||||||
|
layout_mode = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Panel" type="PanelContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Column" type="VBoxContainer" parent="Panel"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 9
|
||||||
|
|
||||||
|
[node name="TopRow" type="HBoxContainer" parent="Panel/Column"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
|
||||||
|
[node name="TitleLabel" type="Label" parent="Panel/Column/TopRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "ff_battle/hud_title"
|
||||||
|
auto_translate = true
|
||||||
|
uppercase = true
|
||||||
|
|
||||||
|
[node name="PhaseBadge" type="PanelContainer" parent="Panel/Column/TopRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 8
|
||||||
|
size_flags_vertical = 4
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="PhaseLabel" type="Label" parent="Panel/Column/TopRow/PhaseBadge"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 10
|
||||||
|
uppercase = true
|
||||||
|
|
||||||
|
[node name="MetaRow" type="HBoxContainer" parent="Panel/Column"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
|
||||||
|
[node name="RoundCaption" type="Label" parent="Panel/Column/MetaRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "ff_battle/round"
|
||||||
|
auto_translate = true
|
||||||
|
|
||||||
|
[node name="RoundValue" type="Label" parent="Panel/Column/MetaRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "1"
|
||||||
|
|
||||||
|
[node name="EnemiesCaption" type="Label" parent="Panel/Column/MetaRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "ff_battle/enemies"
|
||||||
|
auto_translate = true
|
||||||
|
|
||||||
|
[node name="EnemiesValue" type="Label" parent="Panel/Column/MetaRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "0"
|
||||||
|
|
||||||
|
[node name="AlliesCaption" type="Label" parent="Panel/Column/MetaRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "ff_battle/allies"
|
||||||
|
auto_translate = true
|
||||||
|
|
||||||
|
[node name="AlliesValue" type="Label" parent="Panel/Column/MetaRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "0"
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="src/UI/FFBattleLogPanel.cs" id="1_script"]
|
||||||
|
|
||||||
|
[node name="FFBattleLogPanel" type="Control" script=ExtResource("1_script")]
|
||||||
|
layout_mode = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Window" type="PanelContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Column" type="VBoxContainer" parent="Window"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 0
|
||||||
|
|
||||||
|
[node name="Header" type="MarginContainer" parent="Window/Column"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/margin_left = 16
|
||||||
|
theme_override_constants/margin_top = 13
|
||||||
|
theme_override_constants/margin_right = 16
|
||||||
|
theme_override_constants/margin_bottom = 10
|
||||||
|
|
||||||
|
[node name="HeaderRow" type="HBoxContainer" parent="Window/Column/Header"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="TitleLabel" type="Label" parent="Window/Column/Header/HeaderRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "ff_battle/log_title"
|
||||||
|
auto_translate = true
|
||||||
|
uppercase = true
|
||||||
|
|
||||||
|
[node name="Spacer" type="Control" parent="Window/Column/Header/HeaderRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="RoundLabel" type="Label" parent="Window/Column/Header/HeaderRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
|
||||||
|
[node name="Rule" type="HSeparator" parent="Window/Column"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Scroll" type="ScrollContainer" parent="Window/Column"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
horizontal_scroll_mode = 0
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Rows" type="VBoxContainer" parent="Window/Column/Scroll"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_constants/separation = 0
|
||||||
|
mouse_filter = 2
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="src/UI/FFBattleTargetHint.cs" id="1_script"]
|
||||||
|
|
||||||
|
[node name="FFBattleTargetHint" type="Control" script=ExtResource("1_script")]
|
||||||
|
layout_mode = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Center" type="CenterContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Toast" type="PanelContainer" parent="Center"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Row" type="HBoxContainer" parent="Center/Toast"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
|
||||||
|
[node name="IconLabel" type="Label" parent="Center/Toast/Row"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
text = "◎"
|
||||||
|
|
||||||
|
[node name="TextLabel" type="Label" parent="Center/Toast/Row"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 13
|
||||||
+132
-28
@@ -5,51 +5,155 @@
|
|||||||
[node name="FFCharacterWidget" type="Control" script=ExtResource("1_script")]
|
[node name="FFCharacterWidget" type="Control" script=ExtResource("1_script")]
|
||||||
custom_minimum_size = Vector2(180, 260)
|
custom_minimum_size = Vector2(180, 260)
|
||||||
layout_mode = 3
|
layout_mode = 3
|
||||||
|
mouse_filter = 0
|
||||||
|
|
||||||
[node name="Sprite" type="TextureRect" parent="."]
|
[node name="TeamLine" type="ColorRect" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchor_right = 1.0
|
||||||
|
offset_bottom = 2.0
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Art" type="TextureRect" parent="."]
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
|
offset_left = 3.0
|
||||||
|
offset_top = 3.0
|
||||||
|
offset_right = -3.0
|
||||||
|
offset_bottom = -92.0
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
offset_left = 8.0
|
|
||||||
offset_top = 8.0
|
|
||||||
offset_right = -8.0
|
|
||||||
offset_bottom = -44.0
|
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
expand_mode = 1
|
expand_mode = 1
|
||||||
stretch_mode = 5
|
stretch_mode = 5
|
||||||
|
|
||||||
[node name="HpBg" type="ColorRect" parent="."]
|
[node name="Info" type="PanelContainer" parent="."]
|
||||||
layout_mode = 0
|
layout_mode = 1
|
||||||
offset_left = 4.0
|
anchors_preset = 12
|
||||||
offset_top = 218.0
|
anchor_top = 1.0
|
||||||
offset_right = 176.0
|
anchor_right = 1.0
|
||||||
offset_bottom = 236.0
|
anchor_bottom = 1.0
|
||||||
|
offset_top = -92.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 0
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
color = Color(0.137255, 0.145098, 0.184314, 1)
|
|
||||||
|
|
||||||
[node name="HpFill" type="ColorRect" parent="."]
|
[node name="Column" type="VBoxContainer" parent="Info"]
|
||||||
layout_mode = 0
|
layout_mode = 2
|
||||||
offset_left = 4.0
|
theme_override_constants/separation = 5
|
||||||
offset_top = 218.0
|
|
||||||
offset_right = 176.0
|
[node name="NameRow" type="HBoxContainer" parent="Info/Column"]
|
||||||
offset_bottom = 236.0
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 6
|
||||||
|
|
||||||
|
[node name="TeamDot" type="Panel" parent="Info/Column/NameRow"]
|
||||||
|
custom_minimum_size = Vector2(7, 7)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
color = Color(0.560784, 0.682353, 0.419608, 1)
|
|
||||||
|
|
||||||
[node name="NameLabel" type="Label" parent="."]
|
[node name="NameLabel" type="Label" parent="Info/Column/NameRow"]
|
||||||
layout_mode = 0
|
layout_mode = 2
|
||||||
offset_left = 4.0
|
size_flags_horizontal = 3
|
||||||
offset_top = 238.0
|
size_flags_vertical = 4
|
||||||
offset_right = 176.0
|
theme_override_font_sizes/font_size = 14
|
||||||
offset_bottom = 256.0
|
clip_text = true
|
||||||
|
text_overrun_behavior = 3
|
||||||
|
|
||||||
|
[node name="HpNumLabel" type="Label" parent="Info/Column/NameRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 11
|
||||||
|
|
||||||
|
[node name="HpBar" type="ProgressBar" parent="Info/Column"]
|
||||||
|
custom_minimum_size = Vector2(0, 8)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
horizontal_alignment = 1
|
show_percentage = false
|
||||||
theme_override_font_sizes/font_size = 13
|
|
||||||
|
|
||||||
[node name="Selection" type="TextureRect" parent="."]
|
[node name="Foot" type="HBoxContainer" parent="Info/Column"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 6
|
||||||
|
|
||||||
|
[node name="PipsBox" type="HBoxContainer" parent="Info/Column/Foot"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="AtkLabel" type="Label" parent="Info/Column/Foot"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 10
|
||||||
|
|
||||||
|
[node name="Spacer" type="Control" parent="Info/Column/Foot"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="ShieldChip" type="PanelContainer" parent="Info/Column/Foot"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="ShieldRow" type="HBoxContainer" parent="Info/Column/Foot/ShieldChip"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="ShieldKey" type="Label" parent="Info/Column/Foot/ShieldChip/ShieldRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 10
|
||||||
|
text = "ui/character_panel/stat_def"
|
||||||
|
auto_translate = true
|
||||||
|
uppercase = true
|
||||||
|
|
||||||
|
[node name="ShieldValue" type="Label" parent="Info/Column/Foot/ShieldChip/ShieldRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 11
|
||||||
|
text = "0"
|
||||||
|
|
||||||
|
[node name="StatusBox" type="VBoxContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 1
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
offset_left = -140.0
|
||||||
|
offset_top = 8.0
|
||||||
|
offset_right = -8.0
|
||||||
|
offset_bottom = 40.0
|
||||||
|
grow_horizontal = 0
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="StunBadge" type="PanelContainer" parent="StatusBox"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 8
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="StunRow" type="HBoxContainer" parent="StatusBox/StunBadge"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 5
|
||||||
|
|
||||||
|
[node name="StunIcon" type="TextureRect" parent="StatusBox/StunBadge/StunRow"]
|
||||||
|
custom_minimum_size = Vector2(14, 14)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
mouse_filter = 2
|
||||||
|
expand_mode = 1
|
||||||
|
stretch_mode = 5
|
||||||
|
|
||||||
|
[node name="StunLabel" type="Label" parent="StatusBox/StunBadge/StunRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_font_sizes/font_size = 10
|
||||||
|
uppercase = true
|
||||||
|
|
||||||
|
[node name="Selection" type="Panel" parent="."]
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
|
|||||||
@@ -34,3 +34,13 @@ mouse_filter = 2
|
|||||||
visible = false
|
visible = false
|
||||||
theme_override_font_sizes/font_size = 18
|
theme_override_font_sizes/font_size = 18
|
||||||
theme_override_colors/font_color = Color(0.941176, 0.756863, 0.411765, 1)
|
theme_override_colors/font_color = Color(0.941176, 0.756863, 0.411765, 1)
|
||||||
|
|
||||||
|
[node name="Caption" type="Label" parent="."]
|
||||||
|
offset_left = 2.0
|
||||||
|
offset_top = -25.0
|
||||||
|
offset_right = 218.0
|
||||||
|
offset_bottom = -7.0
|
||||||
|
mouse_filter = 2
|
||||||
|
visible = false
|
||||||
|
theme_override_font_sizes/font_size = 11
|
||||||
|
uppercase = true
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Cthangover.Core.Cards.StatusEffect;
|
||||||
using Cthangover.Core.Characters;
|
using Cthangover.Core.Characters;
|
||||||
using Cthangover.Core.Utils;
|
using Cthangover.Core.Utils;
|
||||||
using Godot;
|
using Godot;
|
||||||
@@ -23,8 +24,8 @@ namespace Cthangover.FFBattle.Actions
|
|||||||
return new ChangedAttributes { Result = false };
|
return new ChangedAttributes { Result = false };
|
||||||
|
|
||||||
var damageDelta = Mathf.RoundToInt(action.GetFloat("Attack", 1f) * user.Attributes.Attack.Value);
|
var damageDelta = Mathf.RoundToInt(action.GetFloat("Attack", 1f) * user.Attributes.Attack.Value);
|
||||||
target.StatusEffectQueue.OnTakeDamage(user, ref damageDelta);
|
StatusEffectService.Of(target).OnTakeDamage(user, ref damageDelta);
|
||||||
user.StatusEffectQueue.OnDealDamage(target, ref damageDelta);
|
StatusEffectService.Of(user).OnDealDamage(target, ref damageDelta);
|
||||||
|
|
||||||
var defenceDelta = 0;
|
var defenceDelta = 0;
|
||||||
var ignoreArmor = user.HasPassiveEffect("IgnoreArmor");
|
var ignoreArmor = user.HasPassiveEffect("IgnoreArmor");
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Cthangover.Core.Cards.StatusEffect;
|
||||||
using Cthangover.Core.Characters;
|
using Cthangover.Core.Characters;
|
||||||
using Cthangover.Core.Utils;
|
using Cthangover.Core.Utils;
|
||||||
|
|
||||||
@@ -23,8 +24,7 @@ namespace Cthangover.FFBattle.Actions
|
|||||||
return new ChangedAttributes { Result = true };
|
return new ChangedAttributes { Result = true };
|
||||||
|
|
||||||
var turns = action.GetInt("Turn", 3);
|
var turns = action.GetInt("Turn", 3);
|
||||||
target.StatusEffectQueue.Add("effect/physics/stun", turns);
|
StatusEffectService.Of(target).Add("effect/physics/stun", turns);
|
||||||
|
|
||||||
GameLogger.Log("FF_BATTLE",
|
GameLogger.Log("FF_BATTLE",
|
||||||
$"{user.Name} stuns {target.Name} for {turns} turns",
|
$"{user.Name} stuns {target.Name} for {turns} turns",
|
||||||
LogLevel.Debug);
|
LogLevel.Debug);
|
||||||
|
|||||||
+287
-10
@@ -4,10 +4,13 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Cthangover.Core.Battle;
|
using Cthangover.Core.Battle;
|
||||||
using Cthangover.Core.Battle.Actions;
|
using Cthangover.Core.Battle.Actions;
|
||||||
|
using Cthangover.Core.Cards.StatusEffect;
|
||||||
using Cthangover.Core.Characters;
|
using Cthangover.Core.Characters;
|
||||||
using Cthangover.Core.Items;
|
using Cthangover.Core.Items;
|
||||||
using Cthangover.Core.Settings;
|
using Cthangover.Core.Settings;
|
||||||
using Cthangover.Core.Scenes;
|
using Cthangover.Core.Scenes;
|
||||||
|
using Cthangover.Core.UI;
|
||||||
|
using Cthangover.Core.UI.CharacterPanel;
|
||||||
using Cthangover.Core.Utils;
|
using Cthangover.Core.Utils;
|
||||||
using Cthangover.FFBattle.UI;
|
using Cthangover.FFBattle.UI;
|
||||||
using Cthangover.Core.UI.Theming;
|
using Cthangover.Core.UI.Theming;
|
||||||
@@ -38,6 +41,11 @@ namespace Cthangover.FFBattle
|
|||||||
private FFPlayerPanel _playerPanel;
|
private FFPlayerPanel _playerPanel;
|
||||||
private FFEnemyPanel _enemyPanel;
|
private FFEnemyPanel _enemyPanel;
|
||||||
private FFMenuPanel _menuPanel;
|
private FFMenuPanel _menuPanel;
|
||||||
|
private FFActionDetailPanel _detailPanel;
|
||||||
|
private FFBattleHud _hud;
|
||||||
|
private FFBattleLogPanel _logPanel;
|
||||||
|
private FFBattleBanner _banner;
|
||||||
|
private FFBattleTargetHint _targetHint;
|
||||||
private FFBattleController _controller;
|
private FFBattleController _controller;
|
||||||
private Control _toolPanel;
|
private Control _toolPanel;
|
||||||
|
|
||||||
@@ -49,6 +57,12 @@ namespace Cthangover.FFBattle
|
|||||||
private bool _targetSelectMode;
|
private bool _targetSelectMode;
|
||||||
private bool _targetAlly;
|
private bool _targetAlly;
|
||||||
private System.Action<FFCharacterWidget> _onTargetSelected;
|
private System.Action<FFCharacterWidget> _onTargetSelected;
|
||||||
|
private int _round;
|
||||||
|
private int _detailKind;
|
||||||
|
|
||||||
|
private const int DetailMain = 0;
|
||||||
|
private const int DetailActions = 1;
|
||||||
|
private const int DetailItems = 2;
|
||||||
|
|
||||||
private const float PLAYER_SCALE = 0.85f;
|
private const float PLAYER_SCALE = 0.85f;
|
||||||
|
|
||||||
@@ -91,6 +105,33 @@ namespace Cthangover.FFBattle
|
|||||||
_menuPanel.HideMenu();
|
_menuPanel.HideMenu();
|
||||||
panel.AddChild(_menuPanel);
|
panel.AddChild(_menuPanel);
|
||||||
|
|
||||||
|
_detailPanel = (FFActionDetailPanel)TscnScenes.LoadAndBuild("scenes/ff_action_detail_panel.tscn");
|
||||||
|
_detailPanel.Name = "FFActionDetailPanel";
|
||||||
|
_detailPanel.EnsureConstructed();
|
||||||
|
_detailPanel.HidePassport();
|
||||||
|
panel.AddChild(_detailPanel);
|
||||||
|
|
||||||
|
_hud = (FFBattleHud)TscnScenes.LoadAndBuild("scenes/ff_battle_hud.tscn");
|
||||||
|
_hud.Name = "FFBattleHud";
|
||||||
|
_hud.EnsureConstructed();
|
||||||
|
panel.AddChild(_hud);
|
||||||
|
|
||||||
|
_logPanel = (FFBattleLogPanel)TscnScenes.LoadAndBuild("scenes/ff_battle_log_panel.tscn");
|
||||||
|
_logPanel.Name = "FFBattleLogPanel";
|
||||||
|
_logPanel.EnsureConstructed();
|
||||||
|
panel.AddChild(_logPanel);
|
||||||
|
|
||||||
|
_banner = (FFBattleBanner)TscnScenes.LoadAndBuild("scenes/ff_battle_banner.tscn");
|
||||||
|
_banner.Name = "FFBattleBanner";
|
||||||
|
_banner.EnsureConstructed();
|
||||||
|
panel.AddChild(_banner);
|
||||||
|
|
||||||
|
_targetHint = (FFBattleTargetHint)TscnScenes.LoadAndBuild("scenes/ff_battle_target_hint.tscn");
|
||||||
|
_targetHint.Name = "FFBattleTargetHint";
|
||||||
|
_targetHint.EnsureConstructed();
|
||||||
|
_targetHint.HideHint();
|
||||||
|
panel.AddChild(_targetHint);
|
||||||
|
|
||||||
_endTurnButton = new Button();
|
_endTurnButton = new Button();
|
||||||
_endTurnButton.Text = TranslationServer.Translate("ff_battle/end_turn");
|
_endTurnButton.Text = TranslationServer.Translate("ff_battle/end_turn");
|
||||||
_endTurnButton.Pressed += OnEndTurnPressed;
|
_endTurnButton.Pressed += OnEndTurnPressed;
|
||||||
@@ -112,6 +153,7 @@ namespace Cthangover.FFBattle
|
|||||||
_enemyPanel.OnEnemyClicked += OnEnemyClicked;
|
_enemyPanel.OnEnemyClicked += OnEnemyClicked;
|
||||||
_enemyPanel.OnEnemyDead += OnEnemyDied;
|
_enemyPanel.OnEnemyDead += OnEnemyDied;
|
||||||
_menuPanel.OnItemSelected += OnMenuItemSelected;
|
_menuPanel.OnItemSelected += OnMenuItemSelected;
|
||||||
|
_menuPanel.OnSelectionChanged += OnMenuSelectionChanged;
|
||||||
_menuPanel.OnCancelled += OnMenuCancel;
|
_menuPanel.OnCancelled += OnMenuCancel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,6 +180,20 @@ namespace Cthangover.FFBattle
|
|||||||
_menuPanel.Position = new Vector2(viewportSize.X * 0.66f, viewportSize.Y * 0.58f);
|
_menuPanel.Position = new Vector2(viewportSize.X * 0.66f, viewportSize.Y * 0.58f);
|
||||||
_menuPanel.Size = new Vector2(viewportSize.X * 0.3f, 160f);
|
_menuPanel.Size = new Vector2(viewportSize.X * 0.3f, 160f);
|
||||||
|
|
||||||
|
_detailPanel.Position = new Vector2(viewportSize.X * 0.66f - 320f, viewportSize.Y * 0.58f);
|
||||||
|
_detailPanel.Size = new Vector2(300f, 190f);
|
||||||
|
|
||||||
|
_hud.Position = new Vector2(26f, 20f);
|
||||||
|
|
||||||
|
_logPanel.Position = new Vector2(viewportSize.X - 466f, 64f);
|
||||||
|
_logPanel.Size = new Vector2(440f, 440f);
|
||||||
|
|
||||||
|
_banner.Position = new Vector2(0f, viewportSize.Y * 0.43f);
|
||||||
|
_banner.Size = new Vector2(viewportSize.X, 70f);
|
||||||
|
|
||||||
|
_targetHint.Position = new Vector2(0f, viewportSize.Y * 0.48f);
|
||||||
|
_targetHint.Size = new Vector2(viewportSize.X, 40f);
|
||||||
|
|
||||||
if (_endTurnButton != null)
|
if (_endTurnButton != null)
|
||||||
_endTurnButton.Position = new Vector2(viewportSize.X - 180f, viewportSize.Y * 0.92f);
|
_endTurnButton.Position = new Vector2(viewportSize.X - 180f, viewportSize.Y * 0.92f);
|
||||||
|
|
||||||
@@ -147,6 +203,9 @@ namespace Cthangover.FFBattle
|
|||||||
BattleSceneContext.Instance.OnBattleCleared += OnBattleCleared;
|
BattleSceneContext.Instance.OnBattleCleared += OnBattleCleared;
|
||||||
|
|
||||||
_isPlayerTurn = true;
|
_isPlayerTurn = true;
|
||||||
|
_round = 0;
|
||||||
|
UpdateHud();
|
||||||
|
_logPanel.AddEntry(Translate("ff_battle/log_battle_start"), FFBattleLogKind.Turn);
|
||||||
StartPlayerTurn();
|
StartPlayerTurn();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,13 +219,15 @@ namespace Cthangover.FFBattle
|
|||||||
_currentCharacter = null;
|
_currentCharacter = null;
|
||||||
_targetSelectMode = false;
|
_targetSelectMode = false;
|
||||||
_menuPanel.HideMenu();
|
_menuPanel.HideMenu();
|
||||||
|
_detailPanel.HidePassport();
|
||||||
|
_targetHint.HideHint();
|
||||||
BattleSceneContext.Instance.ActiveCharacter = null;
|
BattleSceneContext.Instance.ActiveCharacter = null;
|
||||||
|
|
||||||
foreach (var widget in _playerPanel.Widgets)
|
foreach (var widget in _playerPanel.Widgets)
|
||||||
{
|
{
|
||||||
if (!widget.IsDead)
|
if (!widget.IsDead)
|
||||||
{
|
{
|
||||||
widget.Card.StatusEffectQueue.OnTurnStart();
|
StatusEffectService.Of(widget.Card).OnTurnStart();
|
||||||
widget.Card.Attributes.Point.Value = widget.Card.Attributes.Point.BaseValue;
|
widget.Card.Attributes.Point.Value = widget.Card.Attributes.Point.BaseValue;
|
||||||
}
|
}
|
||||||
widget.UpdateInfo();
|
widget.UpdateInfo();
|
||||||
@@ -180,6 +241,13 @@ namespace Cthangover.FFBattle
|
|||||||
|
|
||||||
foreach (var widget in _playerPanel.Widgets)
|
foreach (var widget in _playerPanel.Widgets)
|
||||||
widget.ClearHighlight();
|
widget.ClearHighlight();
|
||||||
|
|
||||||
|
_round += 1;
|
||||||
|
UpdateHud();
|
||||||
|
_hud.SetPhase(true);
|
||||||
|
_banner.Show(Translate("ff_battle/banner_player"));
|
||||||
|
_logPanel.SetRound(_round);
|
||||||
|
_logPanel.AddEntry(string.Format(Translate("ff_battle/log_turn_player"), _round), FFBattleLogKind.Turn);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnWidgetClicked(FFCharacterWidget widget)
|
private void OnWidgetClicked(FFCharacterWidget widget)
|
||||||
@@ -201,6 +269,7 @@ namespace Cthangover.FFBattle
|
|||||||
_targetSelectMode = false;
|
_targetSelectMode = false;
|
||||||
foreach (var w in _enemyPanel.Widgets) w.ClearHighlight();
|
foreach (var w in _enemyPanel.Widgets) w.ClearHighlight();
|
||||||
foreach (var w in _playerPanel.Widgets) w.ClearHighlight();
|
foreach (var w in _playerPanel.Widgets) w.ClearHighlight();
|
||||||
|
_targetHint.HideHint();
|
||||||
|
|
||||||
ExecuteMenuAction(widget);
|
ExecuteMenuAction(widget);
|
||||||
return;
|
return;
|
||||||
@@ -250,12 +319,14 @@ namespace Cthangover.FFBattle
|
|||||||
};
|
};
|
||||||
|
|
||||||
_currentMenuAction = null;
|
_currentMenuAction = null;
|
||||||
_menuPanel.ShowMenu(entries);
|
_detailKind = DetailMain;
|
||||||
|
_menuPanel.ShowMenu(entries, string.Format(Translate("ff_battle/menu_caption"),
|
||||||
|
ActorName(), _currentCharacter.Card.Attributes.Point.Value, _currentCharacter.Card.Attributes.Point.BaseValue));
|
||||||
_controller.State = FFBattleState.MenuOpen;
|
_controller.State = FFBattleState.MenuOpen;
|
||||||
|
|
||||||
foreach (var widget in _playerPanel.Widgets)
|
foreach (var widget in _playerPanel.Widgets)
|
||||||
widget.ClearHighlight();
|
widget.ClearHighlight();
|
||||||
_currentCharacter?.Highlight(UiTokens.Alpha(UiTokens.Info, 0.6f));
|
_currentCharacter?.Highlight(UiTokens.Accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMenuItemSelected(int index)
|
private void OnMenuItemSelected(int index)
|
||||||
@@ -367,7 +438,8 @@ namespace Cthangover.FFBattle
|
|||||||
Enabled = true
|
Enabled = true
|
||||||
});
|
});
|
||||||
|
|
||||||
_menuPanel.ShowMenu(entries);
|
_detailKind = DetailActions;
|
||||||
|
_menuPanel.ShowMenu(entries, ActorName() + " · " + Translate("ff_battle/menu_actions"));
|
||||||
_controller.State = FFBattleState.SubMenuOpen;
|
_controller.State = FFBattleState.SubMenuOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,7 +480,8 @@ namespace Cthangover.FFBattle
|
|||||||
Enabled = true
|
Enabled = true
|
||||||
});
|
});
|
||||||
|
|
||||||
_menuPanel.ShowMenu(entries);
|
_detailKind = DetailItems;
|
||||||
|
_menuPanel.ShowMenu(entries, ActorName() + " · " + Translate("ff_battle/menu_items"));
|
||||||
_controller.State = FFBattleState.SubMenuOpen;
|
_controller.State = FFBattleState.SubMenuOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,6 +491,7 @@ namespace Cthangover.FFBattle
|
|||||||
_targetAlly = targetAlly;
|
_targetAlly = targetAlly;
|
||||||
_menuPanel.HideMenu();
|
_menuPanel.HideMenu();
|
||||||
_controller.State = FFBattleState.TargetSelect;
|
_controller.State = FFBattleState.TargetSelect;
|
||||||
|
_targetHint.ShowHint(Translate(targetAlly ? "ff_battle/hint_ally" : "ff_battle/hint_target"));
|
||||||
|
|
||||||
foreach (var widget in _playerPanel.Widgets)
|
foreach (var widget in _playerPanel.Widgets)
|
||||||
widget.ClearHighlight();
|
widget.ClearHighlight();
|
||||||
@@ -428,13 +502,13 @@ namespace Cthangover.FFBattle
|
|||||||
{
|
{
|
||||||
foreach (var widget in _playerPanel.Widgets)
|
foreach (var widget in _playerPanel.Widgets)
|
||||||
if (!widget.IsDead)
|
if (!widget.IsDead)
|
||||||
widget.Highlight(UiTokens.Alpha(UiTokens.Success, 0.5f));
|
widget.Highlight(UiTokens.Success);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (var widget in _enemyPanel.Widgets)
|
foreach (var widget in _enemyPanel.Widgets)
|
||||||
if (!widget.IsDead)
|
if (!widget.IsDead)
|
||||||
widget.Highlight(UiTokens.Alpha(UiTokens.Danger, 0.5f));
|
widget.Highlight(UiTokens.Danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onTargetSelected = null;
|
_onTargetSelected = null;
|
||||||
@@ -451,6 +525,7 @@ namespace Cthangover.FFBattle
|
|||||||
_targetSelectMode = false;
|
_targetSelectMode = false;
|
||||||
foreach (var w in _enemyPanel.Widgets) w.ClearHighlight();
|
foreach (var w in _enemyPanel.Widgets) w.ClearHighlight();
|
||||||
foreach (var w in _playerPanel.Widgets) w.ClearHighlight();
|
foreach (var w in _playerPanel.Widgets) w.ClearHighlight();
|
||||||
|
_targetHint.HideHint();
|
||||||
|
|
||||||
ExecuteMenuAction(widget);
|
ExecuteMenuAction(widget);
|
||||||
}
|
}
|
||||||
@@ -469,6 +544,7 @@ namespace Cthangover.FFBattle
|
|||||||
_targetSelectMode = false;
|
_targetSelectMode = false;
|
||||||
foreach (var w in _enemyPanel.Widgets) w.ClearHighlight();
|
foreach (var w in _enemyPanel.Widgets) w.ClearHighlight();
|
||||||
foreach (var w in _playerPanel.Widgets) w.ClearHighlight();
|
foreach (var w in _playerPanel.Widgets) w.ClearHighlight();
|
||||||
|
_targetHint.HideHint();
|
||||||
|
|
||||||
ExecuteMenuAction(widget);
|
ExecuteMenuAction(widget);
|
||||||
}
|
}
|
||||||
@@ -480,12 +556,22 @@ namespace Cthangover.FFBattle
|
|||||||
|
|
||||||
_controller.State = FFBattleState.AnimationPlaying;
|
_controller.State = FFBattleState.AnimationPlaying;
|
||||||
_endTurnButton.Visible = false;
|
_endTurnButton.Visible = false;
|
||||||
|
_detailPanel.HidePassport();
|
||||||
|
_targetHint.HideHint();
|
||||||
|
|
||||||
|
var source = _currentCharacter;
|
||||||
|
var actionName = string.Empty;
|
||||||
|
var hpBefore = target.Card.Attributes.Health.Value;
|
||||||
|
var defBefore = target.Card.Attributes.Defence.Value;
|
||||||
|
var stunBefore = CountStunTurns(target.Card);
|
||||||
|
|
||||||
switch (_currentMenuAction.Key)
|
switch (_currentMenuAction.Key)
|
||||||
{
|
{
|
||||||
case "action":
|
case "action":
|
||||||
{
|
{
|
||||||
var action = _currentMenuAction.Data as ActionCharacter;
|
var action = _currentMenuAction.Data as ActionCharacter;
|
||||||
|
actionName = TranslationServer.Translate(action.Name).ToString();
|
||||||
|
LogActionDeclared(source, actionName, target);
|
||||||
IBattleAction anim = null;
|
IBattleAction anim = null;
|
||||||
|
|
||||||
if (action.Type == ActionCharacterType.ToEnemy)
|
if (action.Type == ActionCharacterType.ToEnemy)
|
||||||
@@ -504,6 +590,8 @@ namespace Cthangover.FFBattle
|
|||||||
case "item":
|
case "item":
|
||||||
{
|
{
|
||||||
var item = _currentMenuAction.Data as IItem;
|
var item = _currentMenuAction.Data as IItem;
|
||||||
|
actionName = TranslationServer.Translate(item.Name).ToString();
|
||||||
|
LogActionDeclared(source, actionName, target);
|
||||||
var dummyAction = new ActionCharacter { ID = "ff/item" };
|
var dummyAction = new ActionCharacter { ID = "ff/item" };
|
||||||
var anim = new Actions.FFItemAnimation(_currentCharacter, target, dummyAction, item);
|
var anim = new Actions.FFItemAnimation(_currentCharacter, target, dummyAction, item);
|
||||||
await RunAnimation(anim);
|
await RunAnimation(anim);
|
||||||
@@ -511,6 +599,8 @@ namespace Cthangover.FFBattle
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LogOutcome(source, target, actionName, hpBefore, defBefore, stunBefore);
|
||||||
|
|
||||||
_currentMenuAction = null;
|
_currentMenuAction = null;
|
||||||
_endTurnButton.Visible = true;
|
_endTurnButton.Visible = true;
|
||||||
|
|
||||||
@@ -537,12 +627,15 @@ namespace Cthangover.FFBattle
|
|||||||
private void DoEscapeAction()
|
private void DoEscapeAction()
|
||||||
{
|
{
|
||||||
_menuPanel.HideMenu();
|
_menuPanel.HideMenu();
|
||||||
|
_detailPanel.HidePassport();
|
||||||
_controller.State = FFBattleState.Idle;
|
_controller.State = FFBattleState.Idle;
|
||||||
|
|
||||||
var escapeRoll = (float)GD.RandRange(0, 100);
|
var escapeRoll = (float)GD.RandRange(0, 100);
|
||||||
if (escapeRoll < 70f)
|
if (escapeRoll < 70f)
|
||||||
{
|
{
|
||||||
GameLogger.Log("FF_BATTLE", "Escape succeeded", LogLevel.Debug);
|
GameLogger.Log("FF_BATTLE", "Escape succeeded", LogLevel.Debug);
|
||||||
|
_banner.Show(Translate("ff_battle/escape_success"));
|
||||||
|
_logPanel.AddEntry(Translate("ff_battle/log_escape_success"), FFBattleLogKind.Turn);
|
||||||
_ctx.EndBattle(BattleSide.Player);
|
_ctx.EndBattle(BattleSide.Player);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -550,6 +643,8 @@ namespace Cthangover.FFBattle
|
|||||||
GameLogger.Log("FF_BATTLE", "Escape failed", LogLevel.Debug);
|
GameLogger.Log("FF_BATTLE", "Escape failed", LogLevel.Debug);
|
||||||
_currentCharacter.Card.Attributes.Point.Value -= 1;
|
_currentCharacter.Card.Attributes.Point.Value -= 1;
|
||||||
_currentCharacter.UpdateInfo();
|
_currentCharacter.UpdateInfo();
|
||||||
|
_banner.Show(Translate("ff_battle/escape_fail"));
|
||||||
|
_logPanel.AddEntry(string.Format(Translate("ff_battle/log_escape_failed"), NameOf(_currentCharacter)), FFBattleLogKind.Status);
|
||||||
_currentCharacter = null;
|
_currentCharacter = null;
|
||||||
BattleSceneContext.Instance.ActiveCharacter = null;
|
BattleSceneContext.Instance.ActiveCharacter = null;
|
||||||
TryNextCharacterOrEndTurn();
|
TryNextCharacterOrEndTurn();
|
||||||
@@ -588,6 +683,7 @@ namespace Cthangover.FFBattle
|
|||||||
_targetSelectMode = false;
|
_targetSelectMode = false;
|
||||||
foreach (var w in _enemyPanel.Widgets) w.ClearHighlight();
|
foreach (var w in _enemyPanel.Widgets) w.ClearHighlight();
|
||||||
foreach (var w in _playerPanel.Widgets) w.ClearHighlight();
|
foreach (var w in _playerPanel.Widgets) w.ClearHighlight();
|
||||||
|
_targetHint.HideHint();
|
||||||
ShowMainMenu();
|
ShowMainMenu();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -599,6 +695,7 @@ namespace Cthangover.FFBattle
|
|||||||
}
|
}
|
||||||
|
|
||||||
_menuPanel.HideMenu();
|
_menuPanel.HideMenu();
|
||||||
|
_detailPanel.HidePassport();
|
||||||
_controller.State = FFBattleState.Idle;
|
_controller.State = FFBattleState.Idle;
|
||||||
_currentCharacter = null;
|
_currentCharacter = null;
|
||||||
_currentMenuAction = null;
|
_currentMenuAction = null;
|
||||||
@@ -635,11 +732,12 @@ namespace Cthangover.FFBattle
|
|||||||
if (widget.IsDead || widget.Card?.Attributes?.Point?.Value <= 0)
|
if (widget.IsDead || widget.Card?.Attributes?.Point?.Value <= 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (widget.Card.StatusEffectQueue.HasStun())
|
if (StatusEffectService.Of(widget.Card).HasStun())
|
||||||
{
|
{
|
||||||
GameLogger.Log("FF_BATTLE", $"{widget.Card.Name} is stunned, skipping", LogLevel.Debug);
|
GameLogger.Log("FF_BATTLE", $"{widget.Card.Name} is stunned, skipping", LogLevel.Debug);
|
||||||
widget.Card.Attributes.Point.Value = 0;
|
widget.Card.Attributes.Point.Value = 0;
|
||||||
widget.UpdateInfo();
|
widget.UpdateInfo();
|
||||||
|
_logPanel.AddEntry(string.Format(Translate("ff_battle/log_stun_skip"), NameOf(widget)), FFBattleLogKind.Status);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -649,6 +747,7 @@ namespace Cthangover.FFBattle
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_logPanel.AddEntry(Translate("ff_battle/log_no_points"), FFBattleLogKind.Info);
|
||||||
OnPlayerTurnEnd();
|
OnPlayerTurnEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -668,6 +767,8 @@ namespace Cthangover.FFBattle
|
|||||||
_isPlayerTurn = false;
|
_isPlayerTurn = false;
|
||||||
_endTurnButton.Visible = false;
|
_endTurnButton.Visible = false;
|
||||||
_menuPanel.HideMenu();
|
_menuPanel.HideMenu();
|
||||||
|
_detailPanel.HidePassport();
|
||||||
|
_targetHint.HideHint();
|
||||||
_controller.State = FFBattleState.AnimationPlaying;
|
_controller.State = FFBattleState.AnimationPlaying;
|
||||||
BattleSceneContext.Instance.IsWait = true;
|
BattleSceneContext.Instance.IsWait = true;
|
||||||
|
|
||||||
@@ -676,6 +777,10 @@ namespace Cthangover.FFBattle
|
|||||||
foreach (var widget in _enemyPanel.Widgets)
|
foreach (var widget in _enemyPanel.Widgets)
|
||||||
widget.ClearHighlight();
|
widget.ClearHighlight();
|
||||||
|
|
||||||
|
_hud.SetPhase(false);
|
||||||
|
_banner.Show(Translate("ff_battle/banner_enemy"));
|
||||||
|
_logPanel.AddEntry(string.Format(Translate("ff_battle/log_turn_enemy"), _round), FFBattleLogKind.Turn);
|
||||||
|
|
||||||
var root = (Node)_ctx.RootNode;
|
var root = (Node)_ctx.RootNode;
|
||||||
await root.ToSignal(root.GetTree(), "process_frame");
|
await root.ToSignal(root.GetTree(), "process_frame");
|
||||||
await root.ToSignal(root.GetTree().CreateTimer(0.5f), "timeout");
|
await root.ToSignal(root.GetTree().CreateTimer(0.5f), "timeout");
|
||||||
@@ -694,11 +799,13 @@ namespace Cthangover.FFBattle
|
|||||||
if (enemyWidget.IsDead || enemyWidget.Card?.Actions == null || enemyWidget.Card.Actions.Count == 0)
|
if (enemyWidget.IsDead || enemyWidget.Card?.Actions == null || enemyWidget.Card.Actions.Count == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
enemyWidget.Card.StatusEffectQueue.OnTurnStart();
|
StatusEffectService.Of(enemyWidget.Card).OnTurnStart();
|
||||||
|
enemyWidget.UpdateInfo();
|
||||||
|
|
||||||
if (enemyWidget.Card.StatusEffectQueue.HasStun())
|
if (StatusEffectService.Of(enemyWidget.Card).HasStun())
|
||||||
{
|
{
|
||||||
GameLogger.Log("FF_BATTLE", $"{enemyWidget.Card.Name} is stunned, skipping", LogLevel.Debug);
|
GameLogger.Log("FF_BATTLE", $"{enemyWidget.Card.Name} is stunned, skipping", LogLevel.Debug);
|
||||||
|
_logPanel.AddEntry(string.Format(Translate("ff_battle/log_stun_skip"), NameOf(enemyWidget)), FFBattleLogKind.Status);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,6 +823,12 @@ namespace Cthangover.FFBattle
|
|||||||
$"Enemy: {enemyWidget.Card.Name} → {action.Name} → {target.Card.Name}",
|
$"Enemy: {enemyWidget.Card.Name} → {action.Name} → {target.Card.Name}",
|
||||||
LogLevel.Debug);
|
LogLevel.Debug);
|
||||||
|
|
||||||
|
var actionName = TranslationServer.Translate(action.Name).ToString();
|
||||||
|
LogActionDeclared(enemyWidget, actionName, target);
|
||||||
|
var hpBefore = target.Card.Attributes.Health.Value;
|
||||||
|
var defBefore = target.Card.Attributes.Defence.Value;
|
||||||
|
var stunBefore = CountStunTurns(target.Card);
|
||||||
|
|
||||||
var anim = CreateEnemyAnimation(enemyWidget, target, action);
|
var anim = CreateEnemyAnimation(enemyWidget, target, action);
|
||||||
if (anim != null)
|
if (anim != null)
|
||||||
{
|
{
|
||||||
@@ -725,6 +838,8 @@ namespace Cthangover.FFBattle
|
|||||||
anim.DoEnd();
|
anim.DoEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LogOutcome(enemyWidget, target, actionName, hpBefore, defBefore, stunBefore);
|
||||||
|
|
||||||
await root.ToSignal(root.GetTree().CreateTimer(0.2f), "timeout");
|
await root.ToSignal(root.GetTree().CreateTimer(0.2f), "timeout");
|
||||||
|
|
||||||
if (!_playerPanel.HasAlive())
|
if (!_playerPanel.HasAlive())
|
||||||
@@ -803,6 +918,8 @@ namespace Cthangover.FFBattle
|
|||||||
ctx.NotifyCharacterDied(widget.Card);
|
ctx.NotifyCharacterDied(widget.Card);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateHudCounts();
|
||||||
|
|
||||||
if (!_enemyPanel.HasAlive())
|
if (!_enemyPanel.HasAlive())
|
||||||
{
|
{
|
||||||
GameLogger.Log("FF_BATTLE", "All enemies dead — player wins", LogLevel.Debug);
|
GameLogger.Log("FF_BATTLE", "All enemies dead — player wins", LogLevel.Debug);
|
||||||
@@ -852,10 +969,170 @@ namespace Cthangover.FFBattle
|
|||||||
_playerPanel?.ClearAll();
|
_playerPanel?.ClearAll();
|
||||||
_enemyPanel?.ClearAll();
|
_enemyPanel?.ClearAll();
|
||||||
_menuPanel?.HideMenu();
|
_menuPanel?.HideMenu();
|
||||||
|
_detailPanel?.HidePassport();
|
||||||
|
_targetHint?.HideHint();
|
||||||
|
_logPanel?.Clear();
|
||||||
if (_endTurnButton != null)
|
if (_endTurnButton != null)
|
||||||
_endTurnButton.Visible = false;
|
_endTurnButton.Visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnMenuSelectionChanged(int index)
|
||||||
|
{
|
||||||
|
if (_menuPanel.Entries == null || index < 0 || index >= _menuPanel.Entries.Count)
|
||||||
|
{
|
||||||
|
_detailPanel.HidePassport();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entry = _menuPanel.Entries[index];
|
||||||
|
_detailPanel.ShowPassport();
|
||||||
|
|
||||||
|
if (_detailKind == DetailActions)
|
||||||
|
{
|
||||||
|
if (entry.Data is ActionCharacter action)
|
||||||
|
_detailPanel.ShowAction(action, BuildActionNote(action));
|
||||||
|
else
|
||||||
|
_detailPanel.ShowHint(Translate("ff_battle/detail_back"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_detailKind == DetailItems)
|
||||||
|
{
|
||||||
|
if (entry.Data is IItem item)
|
||||||
|
_detailPanel.ShowItem(item, item.ItemType.HasFlag(ItemType.TargetUsed), BuildItemNote(item));
|
||||||
|
else
|
||||||
|
_detailPanel.ShowHint(Translate("ff_battle/detail_back"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_detailPanel.ShowHint(MainHintFor(entry.Key));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildActionNote(ActionCharacter action)
|
||||||
|
{
|
||||||
|
if (action.Properties == null)
|
||||||
|
return TranslationServer.Translate("ff_battle/detail_action").ToString();
|
||||||
|
|
||||||
|
var cost = action.GetInt("RequiredPoint", 1);
|
||||||
|
var attack = action.GetFloat("Attack", 0f);
|
||||||
|
if (attack > 0f)
|
||||||
|
{
|
||||||
|
var attacker = _currentCharacter?.Card?.Attributes?.Attack?.Value ?? 0;
|
||||||
|
return string.Format(Translate("ff_battle/detail_damage"),
|
||||||
|
Mathf.RoundToInt(attack * attacker), attacker, attack.ToString("0.0"), cost);
|
||||||
|
}
|
||||||
|
|
||||||
|
var turns = action.GetInt("Turn", 0);
|
||||||
|
if (turns > 0)
|
||||||
|
return string.Format(Translate("ff_battle/detail_stun"), turns, cost);
|
||||||
|
|
||||||
|
var defence = action.GetInt("Defence", 0);
|
||||||
|
if (defence > 0)
|
||||||
|
return string.Format(Translate("ff_battle/detail_defence"), defence, cost);
|
||||||
|
|
||||||
|
var description = ActionCharacterUi.StripMarkup(ActionCharacterUi.Translate(action.Description));
|
||||||
|
return !string.IsNullOrEmpty(description) ? description : Translate("ff_battle/detail_action");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildItemNote(IItem item)
|
||||||
|
{
|
||||||
|
var description = ActionCharacterUi.StripMarkup(ActionCharacterUi.Translate(item.Description));
|
||||||
|
return !string.IsNullOrEmpty(description)
|
||||||
|
? description
|
||||||
|
: TranslationServer.Translate("ff_battle/detail_item").ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string MainHintFor(string key)
|
||||||
|
{
|
||||||
|
switch (key)
|
||||||
|
{
|
||||||
|
case "attack": return TranslationServer.Translate("ff_battle/detail_attack").ToString();
|
||||||
|
case "items": return TranslationServer.Translate("ff_battle/detail_items").ToString();
|
||||||
|
case "defend": return TranslationServer.Translate("ff_battle/detail_defend").ToString();
|
||||||
|
case "escape": return TranslationServer.Translate("ff_battle/detail_escape").ToString();
|
||||||
|
default: return TranslationServer.Translate("ff_battle/detail_back").ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogActionDeclared(FFCharacterWidget source, string actionName, FFCharacterWidget target)
|
||||||
|
{
|
||||||
|
var entry = source == target
|
||||||
|
? string.Format(Translate("ff_battle/log_action_self"), NameOf(source), actionName)
|
||||||
|
: string.Format(Translate("ff_battle/log_action"), NameOf(source), actionName, NameOf(target));
|
||||||
|
_logPanel.AddEntry(entry, FFBattleLogKind.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogOutcome(FFCharacterWidget source, FFCharacterWidget target, string actionName,
|
||||||
|
int hpBefore, int defBefore, int stunBefore)
|
||||||
|
{
|
||||||
|
var hpAfter = target.Card.Attributes.Health.Value;
|
||||||
|
var defAfter = target.Card.Attributes.Defence.Value;
|
||||||
|
var stunAfter = CountStunTurns(target.Card);
|
||||||
|
|
||||||
|
if (defAfter > defBefore)
|
||||||
|
_logPanel.AddEntry(string.Format(Translate("ff_battle/log_defence"),
|
||||||
|
NameOf(target), defAfter - defBefore, defAfter), FFBattleLogKind.Buff);
|
||||||
|
|
||||||
|
var shieldLost = defBefore - defAfter;
|
||||||
|
if (shieldLost > 0)
|
||||||
|
_logPanel.AddEntry(string.Format(Translate("ff_battle/log_shield"),
|
||||||
|
NameOf(target), shieldLost, defAfter), FFBattleLogKind.Shield);
|
||||||
|
|
||||||
|
if (stunAfter > stunBefore)
|
||||||
|
_logPanel.AddEntry(string.Format(Translate("ff_battle/log_stun"),
|
||||||
|
NameOf(target), stunAfter), FFBattleLogKind.Status);
|
||||||
|
|
||||||
|
if (hpAfter > hpBefore)
|
||||||
|
_logPanel.AddEntry(string.Format(Translate("ff_battle/log_heal"),
|
||||||
|
actionName, NameOf(target), hpAfter - hpBefore), FFBattleLogKind.Buff);
|
||||||
|
|
||||||
|
if (hpAfter < hpBefore)
|
||||||
|
_logPanel.AddEntry(string.Format(Translate("ff_battle/log_damage"),
|
||||||
|
actionName, NameOf(source), NameOf(target), hpBefore - hpAfter), FFBattleLogKind.Damage);
|
||||||
|
|
||||||
|
if (hpAfter <= 0 && hpBefore > 0)
|
||||||
|
_logPanel.AddEntry(string.Format(Translate("ff_battle/log_death"), NameOf(target)), FFBattleLogKind.Dead);
|
||||||
|
|
||||||
|
UpdateHudCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CountStunTurns(Character character)
|
||||||
|
{
|
||||||
|
var stun = StatusEffectService.Of(character)?.ActiveStatusEffects?
|
||||||
|
.FirstOrDefault(effect => effect.EffectType == StatusEffectType.Stun);
|
||||||
|
return stun?.RemainingTurns ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ActorName()
|
||||||
|
=> _currentCharacter?.Card == null
|
||||||
|
? string.Empty
|
||||||
|
: TranslationServer.Translate(_currentCharacter.Card.Name).ToString();
|
||||||
|
|
||||||
|
private static string NameOf(FFCharacterWidget widget)
|
||||||
|
=> widget?.Card == null
|
||||||
|
? string.Empty
|
||||||
|
: TranslationServer.Translate(widget.Card.Name).ToString();
|
||||||
|
|
||||||
|
private void UpdateHud()
|
||||||
|
{
|
||||||
|
if (_hud == null)
|
||||||
|
return;
|
||||||
|
_hud.SetRound(_round);
|
||||||
|
UpdateHudCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateHudCounts()
|
||||||
|
{
|
||||||
|
if (_hud == null)
|
||||||
|
return;
|
||||||
|
_hud.SetCounts(
|
||||||
|
_enemyPanel.Widgets.Count(w => !w.IsDead),
|
||||||
|
_playerPanel.Widgets.Count(w => !w.IsDead));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Translate(string key)
|
||||||
|
=> TranslationServer.Translate(key).ToString();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <c>true</c> while the core accepts a player turn advance; flips to
|
/// <c>true</c> while the core accepts a player turn advance; flips to
|
||||||
/// <c>false</c> synchronously at the start of <see cref="OnPlayerTurnEnd"/>.
|
/// <c>false</c> synchronously at the start of <see cref="OnPlayerTurnEnd"/>.
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
using Cthangover.Core.Characters;
|
||||||
|
using Cthangover.Core.Items;
|
||||||
|
using Cthangover.Core.UI;
|
||||||
|
using Cthangover.Core.UI.CharacterPanel;
|
||||||
|
using Cthangover.Core.UI.Theming;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Cthangover.FFBattle.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Action/item passport shown to the left of the command menu. It mirrors the
|
||||||
|
/// action-card component of the unified style guide: a type accent stripe, the
|
||||||
|
/// action icon, name and ID, type/cost/cooldown badges, property chips and a
|
||||||
|
/// note line with the computed effect preview. The panel exists so the small
|
||||||
|
/// FF-style menu can stay a compact list while the player still sees what the
|
||||||
|
/// highlighted command actually does. Node structure lives in
|
||||||
|
/// <c>scenes/ff_action_detail_panel.tscn</c>; the class fills it and reuses
|
||||||
|
/// <see cref="ActionCharacterUi"/> for badges and properties so the battle and
|
||||||
|
/// the character panel can never disagree about an action's presentation.
|
||||||
|
/// </summary>
|
||||||
|
public partial class FFActionDetailPanel : ModWidget
|
||||||
|
{
|
||||||
|
private PanelContainer _card;
|
||||||
|
private ColorRect _stripe;
|
||||||
|
private TextureRect _icon;
|
||||||
|
private Label _nameLabel;
|
||||||
|
private Label _idLabel;
|
||||||
|
private HFlowContainer _badges;
|
||||||
|
private HFlowContainer _props;
|
||||||
|
private PanelContainer _note;
|
||||||
|
private Label _noteLabel;
|
||||||
|
|
||||||
|
protected override void Construct()
|
||||||
|
{
|
||||||
|
CthangoverTheme.Apply(this);
|
||||||
|
|
||||||
|
_card = GetNode<PanelContainer>("Box/Card");
|
||||||
|
_stripe = GetNode<ColorRect>("Box/Card/CardRow/Stripe");
|
||||||
|
_icon = GetNode<TextureRect>("Box/Card/CardRow/IconMargin/CardIcon");
|
||||||
|
_nameLabel = GetNode<Label>("Box/Card/CardRow/Body/NameRow/NameLabel");
|
||||||
|
_idLabel = GetNode<Label>("Box/Card/CardRow/Body/NameRow/IdLabel");
|
||||||
|
_badges = GetNode<HFlowContainer>("Box/Card/CardRow/Body/Badges");
|
||||||
|
_props = GetNode<HFlowContainer>("Box/Card/CardRow/Body/Props");
|
||||||
|
_note = GetNode<PanelContainer>("Box/Note");
|
||||||
|
_noteLabel = GetNode<Label>("Box/Note/NoteLabel");
|
||||||
|
|
||||||
|
var cardStyle = UiKit.Frame(UiTokens.RadiusMd, UiTokens.Alpha(UiTokens.Surface1, 0.92f), UiTokens.LineCard);
|
||||||
|
cardStyle.ContentMarginLeft = 0;
|
||||||
|
cardStyle.ContentMarginRight = 0;
|
||||||
|
cardStyle.ContentMarginTop = 0;
|
||||||
|
cardStyle.ContentMarginBottom = 0;
|
||||||
|
_card.AddThemeStyleboxOverride("panel", cardStyle);
|
||||||
|
|
||||||
|
var noteStyle = UiKit.Frame(UiTokens.RadiusMd, UiTokens.Alpha(UiTokens.Surface2, 0.9f), UiTokens.Line);
|
||||||
|
noteStyle.ContentMarginLeft = 12;
|
||||||
|
noteStyle.ContentMarginRight = 12;
|
||||||
|
noteStyle.ContentMarginTop = 9;
|
||||||
|
noteStyle.ContentMarginBottom = 9;
|
||||||
|
_note.AddThemeStyleboxOverride("panel", noteStyle);
|
||||||
|
|
||||||
|
_nameLabel.AddThemeColorOverride("font_color", UiTokens.InkBright);
|
||||||
|
_idLabel.AddThemeColorOverride("font_color", UiTokens.Faint);
|
||||||
|
_noteLabel.AddThemeColorOverride("font_color", UiTokens.Muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fills the card with an action passport. <paramref name="note"/> is a
|
||||||
|
/// ready localised effect preview built by the core, because only the core
|
||||||
|
/// knows the acting character's attack value for the damage estimate.
|
||||||
|
/// </summary>
|
||||||
|
public void ShowAction(ActionCharacter action, string note)
|
||||||
|
{
|
||||||
|
if (action == null)
|
||||||
|
{
|
||||||
|
ShowHint(note);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_stripe.Color = ActionCharacterUi.AccentColor(action.Type);
|
||||||
|
_icon.Texture = action.Image;
|
||||||
|
_nameLabel.Text = ActionCharacterUi.Translate(action.Name);
|
||||||
|
_idLabel.Text = action.ID ?? string.Empty;
|
||||||
|
_card.Visible = true;
|
||||||
|
|
||||||
|
ResetBody();
|
||||||
|
|
||||||
|
AddBadge(ActionCharacterUi.Translate(ActionCharacterUi.TypeLabelKey(action.Type)), ActionCharacterUi.AccentColor(action.Type));
|
||||||
|
|
||||||
|
var costText = ActionCharacterUi.CostText(action);
|
||||||
|
if (!string.IsNullOrEmpty(costText))
|
||||||
|
AddBadge(costText, UiTokens.TypeResource);
|
||||||
|
|
||||||
|
var turns = action.GetInt("Turn", 0);
|
||||||
|
if (turns > 0)
|
||||||
|
AddBadge(string.Format(Translate("ff_battle/badge_turns"), turns), UiTokens.Cooldown);
|
||||||
|
|
||||||
|
foreach (var pair in ActionCharacterUi.SortedProperties(action))
|
||||||
|
{
|
||||||
|
if (pair.Key == "RequiredPoint")
|
||||||
|
continue;
|
||||||
|
_props.AddChild(ActionCharacterUi.MakePropertyChip(pair.Key, pair.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
SetNote(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Fills the card with an inventory item passport for the item sub-menu.</summary>
|
||||||
|
public void ShowItem(IItem item, bool targetAlly, string note)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
ShowHint(note);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_stripe.Color = UiTokens.TypeUsed;
|
||||||
|
_icon.Texture = item.Sprite;
|
||||||
|
_nameLabel.Text = ActionCharacterUi.Translate(item.Name);
|
||||||
|
_idLabel.Text = item.ID ?? string.Empty;
|
||||||
|
_card.Visible = true;
|
||||||
|
|
||||||
|
ResetBody();
|
||||||
|
|
||||||
|
if (item.ItemType.HasFlag(ItemType.Used))
|
||||||
|
AddBadge(ActionCharacterUi.Translate("ui/inventory/type_used"), UiTokens.TypeUsed);
|
||||||
|
AddBadge(ActionCharacterUi.Translate(targetAlly ? "ui/inventory/type_target" : "ff_battle/badge_self"),
|
||||||
|
targetAlly ? UiTokens.TypeTarget : UiTokens.TypeSelf);
|
||||||
|
|
||||||
|
SetNote(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Shows a plain directive note under the card, used for highlighted menu hints.</summary>
|
||||||
|
public void ShowHint(string note)
|
||||||
|
{
|
||||||
|
ResetBody();
|
||||||
|
_card.Visible = false;
|
||||||
|
SetNote(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hides the whole passport; the core calls it when the menu closes. The
|
||||||
|
/// inherited <see cref="CanvasItem.Hide"/> semantics are enough, so the
|
||||||
|
/// method is only a named alias for readability at call sites.
|
||||||
|
/// </summary>
|
||||||
|
public void HidePassport()
|
||||||
|
=> Visible = false;
|
||||||
|
|
||||||
|
/// <summary>Shows the panel; the core calls it before filling any content.</summary>
|
||||||
|
public void ShowPassport()
|
||||||
|
=> Visible = true;
|
||||||
|
|
||||||
|
private void ResetBody()
|
||||||
|
{
|
||||||
|
foreach (var child in _badges.GetChildren())
|
||||||
|
{
|
||||||
|
_badges.RemoveChild(child);
|
||||||
|
child.QueueFree();
|
||||||
|
}
|
||||||
|
foreach (var child in _props.GetChildren())
|
||||||
|
{
|
||||||
|
_props.RemoveChild(child);
|
||||||
|
child.QueueFree();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddBadge(string text, Color color)
|
||||||
|
=> _badges.AddChild(ActionCharacterUi.MakeBadge(text, color));
|
||||||
|
|
||||||
|
private void SetNote(string note)
|
||||||
|
{
|
||||||
|
var hasNote = !string.IsNullOrEmpty(note);
|
||||||
|
_note.Visible = hasNote;
|
||||||
|
_noteLabel.Text = note ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Translate(string key)
|
||||||
|
=> TranslationServer.Translate(key).ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
using Cthangover.Core.UI;
|
||||||
|
using Cthangover.Core.UI.Theming;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Cthangover.FFBattle.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Centered announcement strip ("Player turn", "Enemy turn", escape results).
|
||||||
|
/// The node tree lives in <c>scenes/ff_battle_banner.tscn</c>; this class
|
||||||
|
/// dresses it with tokens and owns the fade in/hold/fade out tween. The banner
|
||||||
|
/// is purely informational, so it ignores the mouse and the core places it on
|
||||||
|
/// its own anchored strip between the enemy grid and the party row.
|
||||||
|
/// </summary>
|
||||||
|
public partial class FFBattleBanner : ModWidget
|
||||||
|
{
|
||||||
|
private Label _label;
|
||||||
|
private Tween _tween;
|
||||||
|
|
||||||
|
protected override void Construct()
|
||||||
|
{
|
||||||
|
CthangoverTheme.Apply(this);
|
||||||
|
|
||||||
|
_label = GetNode<Label>("Center/Panel/Label");
|
||||||
|
_label.AddThemeColorOverride("font_color", UiTokens.InkBright);
|
||||||
|
|
||||||
|
var style = UiKit.Frame(0, UiTokens.Alpha(UiTokens.Page, 0.62f), new Color(0f, 0f, 0f, 0f));
|
||||||
|
style.BorderWidthTop = 1;
|
||||||
|
style.BorderWidthBottom = 1;
|
||||||
|
style.BorderColor = UiTokens.AccentLine;
|
||||||
|
style.ContentMarginLeft = 44;
|
||||||
|
style.ContentMarginRight = 44;
|
||||||
|
style.ContentMarginTop = 12;
|
||||||
|
style.ContentMarginBottom = 12;
|
||||||
|
GetNode<PanelContainer>("Center/Panel").AddThemeStyleboxOverride("panel", style);
|
||||||
|
|
||||||
|
Modulate = UiTokens.Alpha(UiTokens.White, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Shows the given caption and restarts the 1.6s fade cycle.</summary>
|
||||||
|
public void Show(string text)
|
||||||
|
{
|
||||||
|
_label.Text = text;
|
||||||
|
_tween?.Kill();
|
||||||
|
Modulate = UiTokens.Alpha(UiTokens.White, 0f);
|
||||||
|
|
||||||
|
_tween = CreateTween();
|
||||||
|
_tween.TweenProperty(this, "modulate:a", 1f, 0.2f);
|
||||||
|
_tween.TweenInterval(1.1f);
|
||||||
|
_tween.TweenProperty(this, "modulate:a", 0f, 0.3f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
using Cthangover.Core.UI;
|
||||||
|
using Cthangover.Core.UI.Theming;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Cthangover.FFBattle.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Top-left battle HUD: title, phase badge and the round / roster counters.
|
||||||
|
/// The node tree comes from <c>scenes/ff_battle_hud.tscn</c>; this class only
|
||||||
|
/// fetches nodes, dresses them with <see cref="UiTokens"/> styles and exposes
|
||||||
|
/// narrow setters so <see cref="FFBattleCore"/> never assembles controls by
|
||||||
|
/// hand. The phase badge reuses the type-accent palette: blue for the player
|
||||||
|
/// turn, red for the enemy turn.
|
||||||
|
/// </summary>
|
||||||
|
public partial class FFBattleHud : ModWidget
|
||||||
|
{
|
||||||
|
private Label _title;
|
||||||
|
private PanelContainer _phaseBadge;
|
||||||
|
private Label _phaseLabel;
|
||||||
|
private Label _roundValue;
|
||||||
|
private Label _enemiesValue;
|
||||||
|
private Label _alliesValue;
|
||||||
|
|
||||||
|
protected override void Construct()
|
||||||
|
{
|
||||||
|
CthangoverTheme.Apply(this);
|
||||||
|
|
||||||
|
_title = GetNode<Label>("Panel/Column/TopRow/TitleLabel");
|
||||||
|
_phaseBadge = GetNode<PanelContainer>("Panel/Column/TopRow/PhaseBadge");
|
||||||
|
_phaseLabel = GetNode<Label>("Panel/Column/TopRow/PhaseBadge/PhaseLabel");
|
||||||
|
_roundValue = GetNode<Label>("Panel/Column/MetaRow/RoundValue");
|
||||||
|
_enemiesValue = GetNode<Label>("Panel/Column/MetaRow/EnemiesValue");
|
||||||
|
_alliesValue = GetNode<Label>("Panel/Column/MetaRow/AlliesValue");
|
||||||
|
|
||||||
|
var panelStyle = UiKit.Frame(UiTokens.RadiusLg, UiTokens.Alpha(UiTokens.Surface0, 0.86f), UiTokens.Line);
|
||||||
|
panelStyle.ContentMarginLeft = 18;
|
||||||
|
panelStyle.ContentMarginRight = 18;
|
||||||
|
panelStyle.ContentMarginTop = 12;
|
||||||
|
panelStyle.ContentMarginBottom = 13;
|
||||||
|
panelStyle.ShadowColor = new Color(0f, 0f, 0f, 0.6f);
|
||||||
|
panelStyle.ShadowSize = 18;
|
||||||
|
panelStyle.ShadowOffset = new Vector2(0f, 8f);
|
||||||
|
GetNode<PanelContainer>("Panel").AddThemeStyleboxOverride("panel", panelStyle);
|
||||||
|
|
||||||
|
_title.AddThemeColorOverride("font_color", UiTokens.InkBright);
|
||||||
|
|
||||||
|
foreach (var name in new[] { "RoundCaption", "EnemiesCaption", "AlliesCaption" })
|
||||||
|
GetNode<Label>("Panel/Column/MetaRow/" + name).AddThemeColorOverride("font_color", UiTokens.Muted);
|
||||||
|
|
||||||
|
foreach (var name in new[] { "RoundValue", "EnemiesValue", "AlliesValue" })
|
||||||
|
GetNode<Label>("Panel/Column/MetaRow/" + name).AddThemeColorOverride("font_color", UiTokens.AccentBright);
|
||||||
|
|
||||||
|
ApplyPhaseStyle(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Switches the phase badge between the player and enemy turns.</summary>
|
||||||
|
public void SetPhase(bool playerTurn)
|
||||||
|
{
|
||||||
|
_phaseLabel.Text = Translate(playerTurn ? "ff_battle/phase_player" : "ff_battle/phase_enemy");
|
||||||
|
ApplyPhaseStyle(playerTurn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Updates the round counter.</summary>
|
||||||
|
public void SetRound(int round)
|
||||||
|
=> _roundValue.Text = round.ToString();
|
||||||
|
|
||||||
|
/// <summary>Updates living combatant counters.</summary>
|
||||||
|
public void SetCounts(int enemies, int allies)
|
||||||
|
{
|
||||||
|
_enemiesValue.Text = enemies.ToString();
|
||||||
|
_alliesValue.Text = allies.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyPhaseStyle(bool playerTurn)
|
||||||
|
{
|
||||||
|
var accent = playerTurn ? UiTokens.TypeSelf : UiTokens.TypeEnemy;
|
||||||
|
var style = UiKit.Frame(UiTokens.RadiusXs, UiTokens.BadgeFill(accent), UiTokens.BadgeLine(accent));
|
||||||
|
style.ContentMarginLeft = 7;
|
||||||
|
style.ContentMarginRight = 7;
|
||||||
|
style.ContentMarginTop = 2;
|
||||||
|
style.ContentMarginBottom = 2;
|
||||||
|
_phaseBadge.AddThemeStyleboxOverride("panel", style);
|
||||||
|
_phaseLabel.AddThemeColorOverride("font_color", accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Translate(string key)
|
||||||
|
=> TranslationServer.Translate(key).ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
namespace Cthangover.FFBattle.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Semantic categories of <see cref="FFBattleLogPanel"/> entries. The kind
|
||||||
|
/// selects the row colour and the left border accent: turn separators are
|
||||||
|
/// gold, damage red, absorbed shield blue, buffs green, statuses amber and
|
||||||
|
/// deaths grey. <see cref="Info"/> is the neutral narration fallback.
|
||||||
|
/// </summary>
|
||||||
|
public enum FFBattleLogKind
|
||||||
|
{
|
||||||
|
Info,
|
||||||
|
Turn,
|
||||||
|
Damage,
|
||||||
|
Shield,
|
||||||
|
Buff,
|
||||||
|
Status,
|
||||||
|
Dead
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
using Cthangover.Core.UI;
|
||||||
|
using Cthangover.Core.UI.Theming;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Cthangover.FFBattle.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Battle journal docked to the upper right: a window with a header carrying
|
||||||
|
/// the round counter and a scrollable list of colour-coded rows. The panel is
|
||||||
|
/// a proposal on top of the bare battle screen — it gives damage, shield
|
||||||
|
/// absorption, status and death events a readable history that the original
|
||||||
|
/// FF-style UI lacked. Node structure lives in
|
||||||
|
/// <c>scenes/ff_battle_log_panel.tscn</c>; rows are created at runtime because
|
||||||
|
/// they are data, not layout, and are capped to <see cref="MaxRows"/> so a long
|
||||||
|
/// battle cannot grow unbounded.
|
||||||
|
/// </summary>
|
||||||
|
public partial class FFBattleLogPanel : ModWidget
|
||||||
|
{
|
||||||
|
private const int MaxRows = 80;
|
||||||
|
|
||||||
|
private Label _title;
|
||||||
|
private Label _roundLabel;
|
||||||
|
private ScrollContainer _scroll;
|
||||||
|
private VBoxContainer _rows;
|
||||||
|
|
||||||
|
protected override void Construct()
|
||||||
|
{
|
||||||
|
CthangoverTheme.Apply(this);
|
||||||
|
|
||||||
|
_title = GetNode<Label>("Window/Column/Header/HeaderRow/TitleLabel");
|
||||||
|
_roundLabel = GetNode<Label>("Window/Column/Header/HeaderRow/RoundLabel");
|
||||||
|
_scroll = GetNode<ScrollContainer>("Window/Column/Scroll");
|
||||||
|
_rows = GetNode<VBoxContainer>("Window/Column/Scroll/Rows");
|
||||||
|
|
||||||
|
var windowStyle = UiKit.Frame(UiTokens.RadiusLg, UiTokens.Alpha(UiTokens.Surface0, 0.84f), UiTokens.Line);
|
||||||
|
windowStyle.ContentMarginLeft = 0;
|
||||||
|
windowStyle.ContentMarginRight = 0;
|
||||||
|
windowStyle.ContentMarginTop = 0;
|
||||||
|
windowStyle.ContentMarginBottom = 0;
|
||||||
|
windowStyle.ShadowColor = new Color(0f, 0f, 0f, 0.6f);
|
||||||
|
windowStyle.ShadowSize = 18;
|
||||||
|
windowStyle.ShadowOffset = new Vector2(0f, 8f);
|
||||||
|
GetNode<PanelContainer>("Window").AddThemeStyleboxOverride("panel", windowStyle);
|
||||||
|
|
||||||
|
_title.AddThemeColorOverride("font_color", UiTokens.Muted);
|
||||||
|
_roundLabel.AddThemeColorOverride("font_color", UiTokens.Faint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Updates the round caption in the header.</summary>
|
||||||
|
public void SetRound(int round)
|
||||||
|
=> _roundLabel.Text = string.Format(Translate("ff_battle/log_round"), round);
|
||||||
|
|
||||||
|
/// <summary>Drops every row; used when the battle is cleared or restarted.</summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
foreach (var child in _rows.GetChildren())
|
||||||
|
{
|
||||||
|
_rows.RemoveChild(child);
|
||||||
|
child.QueueFree();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends one coloured row and trims the oldest entries past the cap.
|
||||||
|
/// The scroll is pushed to the bottom so the newest event is always visible.
|
||||||
|
/// </summary>
|
||||||
|
public void AddEntry(string text, FFBattleLogKind kind)
|
||||||
|
{
|
||||||
|
var style = UiKit.Frame(0, BackgroundOf(kind), new Color(0f, 0f, 0f, 0f));
|
||||||
|
style.BorderWidthLeft = 2;
|
||||||
|
style.BorderColor = BorderOf(kind);
|
||||||
|
style.ContentMarginLeft = 16;
|
||||||
|
style.ContentMarginRight = 12;
|
||||||
|
style.ContentMarginTop = 7;
|
||||||
|
style.ContentMarginBottom = 7;
|
||||||
|
|
||||||
|
var row = new PanelContainer { MouseFilter = MouseFilterEnum.Ignore };
|
||||||
|
row.AddThemeStyleboxOverride("panel", style);
|
||||||
|
|
||||||
|
var label = UiKit.Label(text, UiTokens.FontBodySmall, TextOf(kind));
|
||||||
|
label.AutowrapMode = TextServer.AutowrapMode.WordSmart;
|
||||||
|
row.AddChild(label);
|
||||||
|
|
||||||
|
_rows.AddChild(row);
|
||||||
|
|
||||||
|
while (_rows.GetChildCount() > MaxRows)
|
||||||
|
{
|
||||||
|
var first = _rows.GetChild(0);
|
||||||
|
_rows.RemoveChild(first);
|
||||||
|
first.QueueFree();
|
||||||
|
}
|
||||||
|
|
||||||
|
_scroll.ScrollVertical = (int)_scroll.GetVScrollBar().MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color BackgroundOf(FFBattleLogKind kind)
|
||||||
|
=> kind == FFBattleLogKind.Turn ? UiTokens.AccentSoft : new Color(0f, 0f, 0f, 0f);
|
||||||
|
|
||||||
|
private static Color BorderOf(FFBattleLogKind kind)
|
||||||
|
{
|
||||||
|
switch (kind)
|
||||||
|
{
|
||||||
|
case FFBattleLogKind.Turn: return UiTokens.Accent;
|
||||||
|
case FFBattleLogKind.Damage: return UiTokens.Danger;
|
||||||
|
case FFBattleLogKind.Shield: return UiTokens.TypeResource;
|
||||||
|
case FFBattleLogKind.Buff: return UiTokens.TypePassive;
|
||||||
|
case FFBattleLogKind.Status: return UiTokens.Cooldown;
|
||||||
|
case FFBattleLogKind.Dead: return UiTokens.LineFaint;
|
||||||
|
default: return UiTokens.Line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color TextOf(FFBattleLogKind kind)
|
||||||
|
{
|
||||||
|
switch (kind)
|
||||||
|
{
|
||||||
|
case FFBattleLogKind.Turn: return UiTokens.AccentBright;
|
||||||
|
case FFBattleLogKind.Damage: return UiTokens.DangerText;
|
||||||
|
case FFBattleLogKind.Shield: return UiTokens.Info;
|
||||||
|
case FFBattleLogKind.Buff: return UiTokens.SuccessText;
|
||||||
|
case FFBattleLogKind.Status: return UiTokens.Cooldown;
|
||||||
|
case FFBattleLogKind.Dead: return UiTokens.Faint;
|
||||||
|
default: return UiTokens.Muted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Translate(string key)
|
||||||
|
=> TranslationServer.Translate(key).ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using Cthangover.Core.UI;
|
||||||
|
using Cthangover.Core.UI.Theming;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Cthangover.FFBattle.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Toast-style directive shown while the player picks a battle target
|
||||||
|
/// ("Select a target · RMB / Esc — cancel"). It is a deliberate addition over
|
||||||
|
/// the bare FF UI: the original mod highlighted widgets without ever telling
|
||||||
|
/// the player what the highlight meant, which is easy to miss on a large enemy
|
||||||
|
/// grid. The node tree lives in <c>scenes/ff_battle_target_hint.tscn</c>; the
|
||||||
|
/// class dresses it with tokens and toggles visibility.
|
||||||
|
/// </summary>
|
||||||
|
public partial class FFBattleTargetHint : ModWidget
|
||||||
|
{
|
||||||
|
private Label _icon;
|
||||||
|
private Label _text;
|
||||||
|
|
||||||
|
protected override void Construct()
|
||||||
|
{
|
||||||
|
CthangoverTheme.Apply(this);
|
||||||
|
|
||||||
|
_icon = GetNode<Label>("Center/Toast/Row/IconLabel");
|
||||||
|
_text = GetNode<Label>("Center/Toast/Row/TextLabel");
|
||||||
|
_icon.AddThemeColorOverride("font_color", UiTokens.AccentBright);
|
||||||
|
_text.AddThemeColorOverride("font_color", UiTokens.Ink);
|
||||||
|
|
||||||
|
var style = UiKit.Frame(UiTokens.RadiusMd, UiTokens.ToastBg, UiTokens.Line);
|
||||||
|
style.ContentMarginLeft = 14;
|
||||||
|
style.ContentMarginRight = 14;
|
||||||
|
style.ContentMarginTop = 10;
|
||||||
|
style.ContentMarginBottom = 10;
|
||||||
|
style.ShadowColor = new Color(0f, 0f, 0f, 0.5f);
|
||||||
|
style.ShadowSize = 10;
|
||||||
|
style.ShadowOffset = new Vector2(0f, 4f);
|
||||||
|
GetNode<PanelContainer>("Center/Toast").AddThemeStyleboxOverride("panel", style);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Shows the hint with the given caption.</summary>
|
||||||
|
public void ShowHint(string text)
|
||||||
|
{
|
||||||
|
_text.Text = text;
|
||||||
|
Visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Hides the hint; called when a target is confirmed or selection is cancelled.</summary>
|
||||||
|
public void HideHint()
|
||||||
|
=> Visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
+230
-75
@@ -1,7 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using Cthangover.Core.Mods;
|
using System.Linq;
|
||||||
using Cthangover.Core.Mods.Resolvers;
|
using Cthangover.Core.Cards.StatusEffect;
|
||||||
using Cthangover.Core.Characters;
|
using Cthangover.Core.Characters;
|
||||||
|
using Cthangover.Core.Mods.Resolvers;
|
||||||
using Cthangover.Core.UI;
|
using Cthangover.Core.UI;
|
||||||
using Cthangover.Core.UI.Theming;
|
using Cthangover.Core.UI.Theming;
|
||||||
using Godot;
|
using Godot;
|
||||||
@@ -9,23 +10,44 @@ using Godot;
|
|||||||
namespace Cthangover.FFBattle.UI
|
namespace Cthangover.FFBattle.UI
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Visual representation of a single character (player or enemy) in the FF battle UI.
|
/// Battle card of a single combatant, shared by the party and enemy panels.
|
||||||
/// Composite widget containing a sprite, HP bar, name label, and selection overlay.
|
/// The node tree lives in <c>scenes/ff_character_widget.tscn</c>; this class
|
||||||
/// Supports highlight, flash, shake, and dissolve-death animations via Godot tweens.
|
/// only fetches the nodes, dresses them with <see cref="UiTokens"/> styles and
|
||||||
/// Created per-character by <see cref="FFPlayerPanel.Init"/> and
|
/// refreshes their content. Visually the card follows the unified style guide:
|
||||||
/// <see cref="FFEnemyPanel.Init"/>. Clickable via <c>GuiInput</c> events;
|
/// a team accent line on top, a sprite well, status badges in the upper right
|
||||||
/// embedding panels wire these to <see cref="FFBattleCore"/> for action flow.
|
/// and an info block holding name, HP numbers, HP bar, action point pips
|
||||||
|
/// (party) or attack caption (enemies) and the defence shield chip.
|
||||||
|
/// <para/>
|
||||||
|
/// The public surface is animation-facing, not layout-facing:
|
||||||
|
/// <see cref="Highlight"/> paints the selection frame, <see cref="Flash"/> and
|
||||||
|
/// <see cref="Shake"/> give impact feedback, <see cref="PlayDeathAnimation"/>
|
||||||
|
/// dissolves the card. Battle code never touches labels directly — it mutates
|
||||||
|
/// the <see cref="Character"/> and calls <see cref="UpdateInfo"/>, which
|
||||||
|
/// re-reads health, defence, points and stun in one place. Panels must assign
|
||||||
|
/// <see cref="IsPlayer"/> before <see cref="Init"/>, because the team colour is
|
||||||
|
/// applied during initialisation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class FFCharacterWidget : ModWidget
|
public partial class FFCharacterWidget : ModWidget
|
||||||
{
|
{
|
||||||
private TextureRect _sprite;
|
/// <summary>Diameter of one action point pip.</summary>
|
||||||
private TextureRect _selection;
|
private const int PipSize = 9;
|
||||||
|
|
||||||
|
private TextureRect _art;
|
||||||
|
private ColorRect _teamLine;
|
||||||
|
private Panel _selection;
|
||||||
|
private Panel _teamDot;
|
||||||
private Label _nameLabel;
|
private Label _nameLabel;
|
||||||
private ColorRect _hpBg;
|
private Label _hpNumLabel;
|
||||||
private ColorRect _hpFill;
|
private ProgressBar _hpBar;
|
||||||
|
private HBoxContainer _pipsBox;
|
||||||
|
private Label _atkLabel;
|
||||||
|
private PanelContainer _shieldChip;
|
||||||
|
private Label _shieldValue;
|
||||||
|
private VBoxContainer _statusBox;
|
||||||
|
private PanelContainer _stunBadge;
|
||||||
|
private TextureRect _stunIcon;
|
||||||
|
private Label _stunLabel;
|
||||||
private Tween _animTween;
|
private Tween _animTween;
|
||||||
private static Texture2D _cachedSelectTex;
|
|
||||||
private static bool _selectTexLoaded;
|
|
||||||
private static Shader _dissolveShader;
|
private static Shader _dissolveShader;
|
||||||
|
|
||||||
/// <summary>Whether this widget belongs to the player party (affects highlight/targeting logic).</summary>
|
/// <summary>Whether this widget belongs to the player party (affects highlight/targeting logic).</summary>
|
||||||
@@ -34,7 +56,7 @@ namespace Cthangover.FFBattle.UI
|
|||||||
public bool IsDead { get; set; }
|
public bool IsDead { get; set; }
|
||||||
/// <summary>The character data model backing this widget: stats, actions, status effects.</summary>
|
/// <summary>The character data model backing this widget: stats, actions, status effects.</summary>
|
||||||
public Character Card { get; set; }
|
public Character Card { get; set; }
|
||||||
/// <summary>Original scale captured at construction; used to restore after death animation.</summary>
|
/// <summary>Original scale captured at initialisation; used to restore after death animation.</summary>
|
||||||
public Vector2 BaseScale { get; private set; }
|
public Vector2 BaseScale { get; private set; }
|
||||||
/// <summary>Exposes this widget as a <see cref="Godot.Control"/> for layout calculations.</summary>
|
/// <summary>Exposes this widget as a <see cref="Godot.Control"/> for layout calculations.</summary>
|
||||||
public Control ControlNode => this;
|
public Control ControlNode => this;
|
||||||
@@ -43,33 +65,29 @@ namespace Cthangover.FFBattle.UI
|
|||||||
{
|
{
|
||||||
CthangoverTheme.Apply(this);
|
CthangoverTheme.Apply(this);
|
||||||
|
|
||||||
_sprite = GetNode<TextureRect>("Sprite");
|
_teamLine = GetNode<ColorRect>("TeamLine");
|
||||||
_hpBg = GetNode<ColorRect>("HpBg");
|
_art = GetNode<TextureRect>("Art");
|
||||||
_hpFill = GetNode<ColorRect>("HpFill");
|
_nameLabel = GetNode<Label>("Info/Column/NameRow/NameLabel");
|
||||||
_nameLabel = GetNode<Label>("NameLabel");
|
_teamDot = GetNode<Panel>("Info/Column/NameRow/TeamDot");
|
||||||
_selection = GetNode<TextureRect>("Selection");
|
_hpNumLabel = GetNode<Label>("Info/Column/NameRow/HpNumLabel");
|
||||||
|
_hpBar = GetNode<ProgressBar>("Info/Column/HpBar");
|
||||||
|
_pipsBox = GetNode<HBoxContainer>("Info/Column/Foot/PipsBox");
|
||||||
|
_atkLabel = GetNode<Label>("Info/Column/Foot/AtkLabel");
|
||||||
|
_shieldChip = GetNode<PanelContainer>("Info/Column/Foot/ShieldChip");
|
||||||
|
_shieldValue = GetNode<Label>("Info/Column/Foot/ShieldChip/ShieldRow/ShieldValue");
|
||||||
|
_statusBox = GetNode<VBoxContainer>("StatusBox");
|
||||||
|
_stunBadge = GetNode<PanelContainer>("StatusBox/StunBadge");
|
||||||
|
_stunIcon = GetNode<TextureRect>("StatusBox/StunBadge/StunRow/StunIcon");
|
||||||
|
_stunLabel = GetNode<Label>("StatusBox/StunBadge/StunRow/StunLabel");
|
||||||
|
_selection = GetNode<Panel>("Selection");
|
||||||
|
|
||||||
Size = CustomMinimumSize;
|
ApplyStyles();
|
||||||
|
|
||||||
if (!_selectTexLoaded)
|
|
||||||
{
|
|
||||||
_cachedSelectTex = Textures.Resolve("select");
|
|
||||||
_selectTexLoaded = true;
|
|
||||||
}
|
|
||||||
if (_cachedSelectTex != null)
|
|
||||||
{
|
|
||||||
_selection.Texture = _cachedSelectTex;
|
|
||||||
_selection.ExpandMode = TextureRect.ExpandModeEnum.IgnoreSize;
|
|
||||||
_selection.StretchMode = TextureRect.StretchModeEnum.Scale;
|
|
||||||
_selection.Modulate = UiTokens.Alpha(UiTokens.White, 0f);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Binds this widget to a <see cref="Character"/> model. Sets the sprite from
|
/// Binds the widget to a <see cref="Character"/>: sprite, localised name,
|
||||||
/// <see cref="Character.Image"/>, localises the name via
|
/// team colour and per-side info layout. The team colour is derived from
|
||||||
/// <see cref="TranslationServer"/>, and calls <see cref="UpdateInfo"/>
|
/// <see cref="IsPlayer"/>, which is why callers set it before calling here.
|
||||||
/// to refresh the HP bar.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Init(Character character)
|
public void Init(Character character)
|
||||||
{
|
{
|
||||||
@@ -77,33 +95,72 @@ namespace Cthangover.FFBattle.UI
|
|||||||
BaseScale = Scale;
|
BaseScale = Scale;
|
||||||
IsDead = false;
|
IsDead = false;
|
||||||
|
|
||||||
if (_sprite != null && character.Image != null)
|
if (_art != null && character?.Image != null)
|
||||||
_sprite.Texture = character.Image;
|
_art.Texture = character.Image;
|
||||||
|
|
||||||
if (_nameLabel != null)
|
if (_nameLabel != null)
|
||||||
_nameLabel.Text = TranslationServer.Translate(character.Name);
|
_nameLabel.Text = TranslationServer.Translate(character?.Name ?? string.Empty).ToString();
|
||||||
|
|
||||||
|
var teamColor = IsPlayer ? UiTokens.Info : UiTokens.Danger;
|
||||||
|
if (_teamLine != null)
|
||||||
|
_teamLine.Color = UiTokens.Alpha(teamColor, 0.55f);
|
||||||
|
if (_teamDot != null)
|
||||||
|
_teamDot.AddThemeStyleboxOverride("panel", UiKit.Frame(4, UiTokens.Alpha(teamColor, 0.9f)));
|
||||||
|
if (_pipsBox != null)
|
||||||
|
_pipsBox.Visible = IsPlayer;
|
||||||
|
if (_atkLabel != null)
|
||||||
|
_atkLabel.Visible = !IsPlayer;
|
||||||
|
|
||||||
UpdateInfo();
|
UpdateInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Refreshes the HP bar fill width and colour gradient based on current health percentage.</summary>
|
/// <summary>
|
||||||
|
/// Single refresh entry point for all mutable character state: HP bar and
|
||||||
|
/// numbers with threshold colours, action point pips, shield chip, attack
|
||||||
|
/// caption and the stun badge with its remaining turns. Called by battle
|
||||||
|
/// code and by animations after each resolved action.
|
||||||
|
/// </summary>
|
||||||
public void UpdateInfo()
|
public void UpdateInfo()
|
||||||
{
|
{
|
||||||
if (_hpFill == null || Card == null)
|
if (Card?.Attributes == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var hpPercent = Card.Attributes.Health.Percent;
|
var health = Card.Attributes.Health;
|
||||||
_hpFill.Size = new Vector2((Size.X - 8) * hpPercent, 18);
|
var percent = Mathf.Clamp(health.Percent, 0f, 1f);
|
||||||
|
var fillColor = percent > 0.5f ? UiTokens.HpOk : percent > 0.25f ? UiTokens.Warning : UiTokens.HpLow;
|
||||||
|
var textColor = percent > 0.5f ? UiTokens.HpText : percent > 0.25f ? UiTokens.Warning : UiTokens.DangerText;
|
||||||
|
|
||||||
if (hpPercent > 0.5f)
|
if (_hpBar != null)
|
||||||
_hpFill.Color = UiTokens.HpOk;
|
{
|
||||||
else if (hpPercent > 0.25f)
|
_hpBar.Value = percent * 100f;
|
||||||
_hpFill.Color = UiTokens.Warning;
|
_hpBar.AddThemeStyleboxOverride("background", UiKit.Frame(UiTokens.RadiusSm, UiTokens.LineDisabled));
|
||||||
else
|
_hpBar.AddThemeStyleboxOverride("fill", UiKit.Frame(UiTokens.RadiusSm, fillColor));
|
||||||
_hpFill.Color = UiTokens.HpLow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Shows a coloured selection overlay with a 0.15s tween fade-in. Used for target highlighting and selection indication.</summary>
|
if (_hpNumLabel != null)
|
||||||
|
{
|
||||||
|
_hpNumLabel.Text = $"{health.Value}/{health.BaseValue}";
|
||||||
|
_hpNumLabel.AddThemeColorOverride("font_color", textColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_pipsBox != null && IsPlayer)
|
||||||
|
RefreshPips();
|
||||||
|
|
||||||
|
if (_atkLabel != null && !IsPlayer)
|
||||||
|
_atkLabel.Text = string.Format("{0} {1}", Translate("ui/character_panel/stat_atk"), Card.Attributes.Attack.Value);
|
||||||
|
|
||||||
|
if (_shieldChip != null)
|
||||||
|
{
|
||||||
|
var defence = Card.Attributes.Defence?.Value ?? 0;
|
||||||
|
_shieldChip.Visible = defence > 0;
|
||||||
|
if (_shieldValue != null)
|
||||||
|
_shieldValue.Text = defence.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshStun();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Shows a coloured selection frame over the card with a 0.15s fade.</summary>
|
||||||
public void Highlight(Color color)
|
public void Highlight(Color color)
|
||||||
{
|
{
|
||||||
if (_selection == null)
|
if (_selection == null)
|
||||||
@@ -115,7 +172,7 @@ namespace Cthangover.FFBattle.UI
|
|||||||
_animTween.TweenProperty(_selection, "modulate", color, 0.15f);
|
_animTween.TweenProperty(_selection, "modulate", color, 0.15f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Fades out the selection overlay to transparent via a 0.15s tween.</summary>
|
/// <summary>Fades the selection frame out via a 0.15s tween.</summary>
|
||||||
public void ClearHighlight()
|
public void ClearHighlight()
|
||||||
{
|
{
|
||||||
if (_selection == null)
|
if (_selection == null)
|
||||||
@@ -127,7 +184,7 @@ namespace Cthangover.FFBattle.UI
|
|||||||
_animTween.TweenProperty(_selection, "modulate", UiTokens.Alpha(UiTokens.White, 0f), 0.15f);
|
_animTween.TweenProperty(_selection, "modulate", UiTokens.Alpha(UiTokens.White, 0f), 0.15f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Rapidly oscillates the widget's position with random offsets (6 iterations) to simulate impact feedback.</summary>
|
/// <summary>Rapidly oscillates the widget with random offsets to simulate impact feedback.</summary>
|
||||||
public void Shake(float intensity, float duration)
|
public void Shake(float intensity, float duration)
|
||||||
{
|
{
|
||||||
var originalPos = Position;
|
var originalPos = Position;
|
||||||
@@ -145,7 +202,7 @@ namespace Cthangover.FFBattle.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Briefly tints the entire widget with a colour pulse (30% rise, 70% fall) for damage/heal feedback.</summary>
|
/// <summary>Briefly tints the whole card with a colour pulse (30% rise, 70% fall).</summary>
|
||||||
public void Flash(Color color, float duration)
|
public void Flash(Color color, float duration)
|
||||||
{
|
{
|
||||||
var originalModulate = Modulate;
|
var originalModulate = Modulate;
|
||||||
@@ -156,11 +213,11 @@ namespace Cthangover.FFBattle.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Plays a dissolve-and-shrink death sequence. If a <c>"scene_transition"</c>
|
/// Plays a dissolve-and-shrink death sequence. The sprite gets the
|
||||||
/// shader is available, applies a dissolve material to the sprite. Fades the
|
/// <c>scene_transition</c> shader with a growing dissolve progress while the
|
||||||
/// widget alpha, scales it to zero, and hides the HP bar and name label.
|
/// card fades and squashes; the info block fades with it. Invokes
|
||||||
/// Invokes <paramref name="onComplete"/> when finished so the parent panel
|
/// <paramref name="onComplete"/> when finished so the parent panel can
|
||||||
/// can remove the widget from the grid.
|
/// remove the widget and re-grid the survivors.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void PlayDeathAnimation(Action onComplete)
|
public void PlayDeathAnimation(Action onComplete)
|
||||||
{
|
{
|
||||||
@@ -180,21 +237,20 @@ namespace Cthangover.FFBattle.UI
|
|||||||
if (_dissolveShader == null)
|
if (_dissolveShader == null)
|
||||||
_dissolveShader = Shaders.Resolve("scene_transition");
|
_dissolveShader = Shaders.Resolve("scene_transition");
|
||||||
|
|
||||||
if (_dissolveShader != null && _sprite?.Texture != null)
|
if (_dissolveShader != null && _art?.Texture != null)
|
||||||
{
|
{
|
||||||
var mat = new ShaderMaterial { Shader = _dissolveShader };
|
var material = new ShaderMaterial { Shader = _dissolveShader };
|
||||||
mat.SetShaderParameter("noise_scale", 5f);
|
material.SetShaderParameter("noise_scale", 5f);
|
||||||
mat.SetShaderParameter("smoothness", 0.25f);
|
material.SetShaderParameter("smoothness", 0.25f);
|
||||||
mat.SetShaderParameter("distortion", 0.092f);
|
material.SetShaderParameter("distortion", 0.092f);
|
||||||
mat.SetShaderParameter("glow_intensity", 0f);
|
material.SetShaderParameter("glow_intensity", 0f);
|
||||||
mat.SetShaderParameter("hue_shift", 0f);
|
material.SetShaderParameter("hue_shift", 0f);
|
||||||
mat.SetShaderParameter("invert_direction", false);
|
material.SetShaderParameter("invert_direction", false);
|
||||||
mat.SetShaderParameter("progress", 0f);
|
material.SetShaderParameter("progress", 0f);
|
||||||
_sprite.Material = mat;
|
_art.Material = material;
|
||||||
|
|
||||||
var matTween = CreateTween();
|
CreateTween().TweenMethod(
|
||||||
matTween.TweenMethod(
|
Callable.From<float>(t => material.SetShaderParameter("progress", t)),
|
||||||
Callable.From<float>(t => mat.SetShaderParameter("progress", t)),
|
|
||||||
0f, 1f, duration);
|
0f, 1f, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,9 +258,13 @@ namespace Cthangover.FFBattle.UI
|
|||||||
tween.SetParallel(true);
|
tween.SetParallel(true);
|
||||||
tween.TweenProperty(this, "modulate:a", 0f, duration * 0.5f);
|
tween.TweenProperty(this, "modulate:a", 0f, duration * 0.5f);
|
||||||
tween.TweenProperty(this, "scale", BaseScale * new Vector2(0.1f, 1.2f), duration);
|
tween.TweenProperty(this, "scale", BaseScale * new Vector2(0.1f, 1.2f), duration);
|
||||||
tween.TweenProperty(_hpBg, "modulate:a", 0f, duration);
|
tween.TweenProperty(_hpBar, "modulate:a", 0f, duration);
|
||||||
tween.TweenProperty(_hpFill, "modulate:a", 0f, duration);
|
tween.TweenProperty(_hpNumLabel, "modulate:a", 0f, duration);
|
||||||
tween.TweenProperty(_nameLabel, "modulate:a", 0f, duration);
|
tween.TweenProperty(_nameLabel, "modulate:a", 0f, duration);
|
||||||
|
tween.TweenProperty(_pipsBox, "modulate:a", 0f, duration);
|
||||||
|
tween.TweenProperty(_atkLabel, "modulate:a", 0f, duration);
|
||||||
|
tween.TweenProperty(_shieldChip, "modulate:a", 0f, duration);
|
||||||
|
tween.TweenProperty(_statusBox, "modulate:a", 0f, duration);
|
||||||
|
|
||||||
tween.Chain().TweenProperty(this, "scale", Vector2.Zero, 0.15f);
|
tween.Chain().TweenProperty(this, "scale", Vector2.Zero, 0.15f);
|
||||||
|
|
||||||
@@ -213,5 +273,100 @@ namespace Cthangover.FFBattle.UI
|
|||||||
onComplete?.Invoke();
|
onComplete?.Invoke();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplyStyles()
|
||||||
|
{
|
||||||
|
_teamLine.Color = UiTokens.Alpha(UiTokens.Danger, 0.55f);
|
||||||
|
|
||||||
|
var infoStyle = UiKit.Frame(UiTokens.RadiusMd, UiTokens.Alpha(UiTokens.Surface2, 0.93f), UiTokens.Line);
|
||||||
|
infoStyle.CornerRadiusTopLeft = 0;
|
||||||
|
infoStyle.CornerRadiusTopRight = 0;
|
||||||
|
infoStyle.ContentMarginLeft = 8;
|
||||||
|
infoStyle.ContentMarginRight = 8;
|
||||||
|
infoStyle.ContentMarginTop = 6;
|
||||||
|
infoStyle.ContentMarginBottom = 8;
|
||||||
|
GetNode<PanelContainer>("Info").AddThemeStyleboxOverride("panel", infoStyle);
|
||||||
|
|
||||||
|
_nameLabel.AddThemeColorOverride("font_color", UiTokens.InkBright);
|
||||||
|
_hpNumLabel.AddThemeColorOverride("font_color", UiTokens.HpText);
|
||||||
|
_atkLabel.AddThemeColorOverride("font_color", UiTokens.Muted);
|
||||||
|
_teamDot.AddThemeStyleboxOverride("panel", UiKit.Frame(4, UiTokens.Danger));
|
||||||
|
|
||||||
|
var hpBackground = UiKit.Frame(UiTokens.RadiusSm, UiTokens.LineDisabled);
|
||||||
|
var hpFill = UiKit.Frame(UiTokens.RadiusSm, UiTokens.HpOk);
|
||||||
|
_hpBar.AddThemeStyleboxOverride("background", hpBackground);
|
||||||
|
_hpBar.AddThemeStyleboxOverride("fill", hpFill);
|
||||||
|
|
||||||
|
var shieldStyle = UiKit.Frame(UiTokens.RadiusXs, UiTokens.StatBg, UiTokens.StatLine);
|
||||||
|
shieldStyle.ContentMarginLeft = 7;
|
||||||
|
shieldStyle.ContentMarginRight = 7;
|
||||||
|
shieldStyle.ContentMarginTop = 2;
|
||||||
|
shieldStyle.ContentMarginBottom = 2;
|
||||||
|
_shieldChip.AddThemeStyleboxOverride("panel", shieldStyle);
|
||||||
|
GetNode<Label>("Info/Column/Foot/ShieldChip/ShieldRow/ShieldKey").AddThemeColorOverride("font_color", UiTokens.Muted);
|
||||||
|
_shieldValue.AddThemeColorOverride("font_color", UiTokens.Info);
|
||||||
|
|
||||||
|
var stunStyle = UiKit.Frame(UiTokens.RadiusXs, UiTokens.CooldownSoft, UiTokens.CooldownLine);
|
||||||
|
stunStyle.ContentMarginLeft = 5;
|
||||||
|
stunStyle.ContentMarginRight = 8;
|
||||||
|
stunStyle.ContentMarginTop = 3;
|
||||||
|
stunStyle.ContentMarginBottom = 3;
|
||||||
|
_stunBadge.AddThemeStyleboxOverride("panel", stunStyle);
|
||||||
|
_stunLabel.AddThemeColorOverride("font_color", UiTokens.Cooldown);
|
||||||
|
|
||||||
|
var selectionStyle = UiKit.Frame(UiTokens.RadiusMd, new Color(1f, 1f, 1f, 0.07f), Colors.White, 2);
|
||||||
|
_selection.AddThemeStyleboxOverride("panel", selectionStyle);
|
||||||
|
_selection.Modulate = UiTokens.Alpha(UiTokens.White, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshPips()
|
||||||
|
{
|
||||||
|
foreach (var child in _pipsBox.GetChildren())
|
||||||
|
{
|
||||||
|
_pipsBox.RemoveChild(child);
|
||||||
|
child.QueueFree();
|
||||||
|
}
|
||||||
|
|
||||||
|
var basePoints = Mathf.Max(0, Card.Attributes.Point.BaseValue);
|
||||||
|
var points = Card.Attributes.Point.Value;
|
||||||
|
for (var i = 0; i < basePoints; i++)
|
||||||
|
{
|
||||||
|
var on = i < points;
|
||||||
|
var pip = new Panel
|
||||||
|
{
|
||||||
|
MouseFilter = MouseFilterEnum.Ignore,
|
||||||
|
CustomMinimumSize = new Vector2(PipSize, PipSize),
|
||||||
|
};
|
||||||
|
pip.AddThemeStyleboxOverride("panel", UiKit.Frame(
|
||||||
|
PipSize / 2,
|
||||||
|
on ? UiTokens.AccentBright : UiTokens.LineDisabled,
|
||||||
|
on ? UiTokens.Accent : UiTokens.LineFaint));
|
||||||
|
_pipsBox.AddChild(pip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshStun()
|
||||||
|
{
|
||||||
|
if (_stunBadge == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var stun = StatusEffectService.Of(Card)?.ActiveStatusEffects?
|
||||||
|
.FirstOrDefault(effect => effect.EffectType == StatusEffectType.Stun);
|
||||||
|
|
||||||
|
if (stun == null)
|
||||||
|
{
|
||||||
|
_stunBadge.Visible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_stunBadge.Visible = true;
|
||||||
|
if (_stunIcon != null)
|
||||||
|
_stunIcon.Texture = stun.Icon;
|
||||||
|
if (_stunLabel != null)
|
||||||
|
_stunLabel.Text = string.Format(Translate("ff_battle/status_stun"), stun.RemainingTurns);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Translate(string key)
|
||||||
|
=> TranslationServer.Translate(key).ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,8 +57,8 @@ namespace Cthangover.FFBattle.UI
|
|||||||
var widget = (FFCharacterWidget)TscnScenes.LoadAndBuild("scenes/ff_character_widget.tscn");
|
var widget = (FFCharacterWidget)TscnScenes.LoadAndBuild("scenes/ff_character_widget.tscn");
|
||||||
widget.EnsureConstructed();
|
widget.EnsureConstructed();
|
||||||
widget.Scale = new Vector2(scale, scale);
|
widget.Scale = new Vector2(scale, scale);
|
||||||
widget.Init(enemies[i]);
|
|
||||||
widget.IsPlayer = false;
|
widget.IsPlayer = false;
|
||||||
|
widget.Init(enemies[i]);
|
||||||
widget.MouseFilter = MouseFilterEnum.Stop;
|
widget.MouseFilter = MouseFilterEnum.Stop;
|
||||||
widget.GuiInput += (evt) =>
|
widget.GuiInput += (evt) =>
|
||||||
{
|
{
|
||||||
|
|||||||
+27
-1
@@ -39,6 +39,9 @@ namespace Cthangover.FFBattle.UI
|
|||||||
private ColorRect _background;
|
private ColorRect _background;
|
||||||
private Panel _border;
|
private Panel _border;
|
||||||
private Label _cursorLabel;
|
private Label _cursorLabel;
|
||||||
|
private Label _caption;
|
||||||
|
private StyleBoxFlat _rowActiveStyle;
|
||||||
|
private StyleBoxFlat _rowIdleStyle;
|
||||||
private List<Label> _itemLabels = new();
|
private List<Label> _itemLabels = new();
|
||||||
private Tween _cursorTween;
|
private Tween _cursorTween;
|
||||||
private int _selectedIndex;
|
private int _selectedIndex;
|
||||||
@@ -70,6 +73,8 @@ namespace Cthangover.FFBattle.UI
|
|||||||
|
|
||||||
/// <summary>Raised when an entry is confirmed (Enter/Space or left click).</summary>
|
/// <summary>Raised when an entry is confirmed (Enter/Space or left click).</summary>
|
||||||
public event System.Action<int> OnItemSelected;
|
public event System.Action<int> OnItemSelected;
|
||||||
|
/// <summary>Raised when the highlighted entry changes (keyboard or mouse hover); carries the new index.</summary>
|
||||||
|
public event System.Action<int> OnSelectionChanged;
|
||||||
/// <summary>Raised when the menu is cancelled (right click, or Escape handled externally).</summary>
|
/// <summary>Raised when the menu is cancelled (right click, or Escape handled externally).</summary>
|
||||||
public event System.Action OnCancelled;
|
public event System.Action OnCancelled;
|
||||||
|
|
||||||
@@ -87,6 +92,12 @@ namespace Cthangover.FFBattle.UI
|
|||||||
_cursorLabel = GetNode<Label>("Cursor");
|
_cursorLabel = GetNode<Label>("Cursor");
|
||||||
_cursorLabel.AddThemeColorOverride("font_color", UiTokens.AccentBright);
|
_cursorLabel.AddThemeColorOverride("font_color", UiTokens.AccentBright);
|
||||||
|
|
||||||
|
_caption = GetNode<Label>("Caption");
|
||||||
|
_caption.AddThemeColorOverride("font_color", UiTokens.Muted);
|
||||||
|
|
||||||
|
_rowActiveStyle = UiKit.Frame(UiTokens.RadiusSm, UiTokens.AccentTrace, UiTokens.Alpha(UiTokens.White, 0f));
|
||||||
|
_rowIdleStyle = UiKit.Frame(UiTokens.RadiusSm, UiTokens.Alpha(UiTokens.White, 0f), UiTokens.Alpha(UiTokens.White, 0f));
|
||||||
|
|
||||||
GuiInput += OnPanelGuiInput;
|
GuiInput += OnPanelGuiInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +123,7 @@ namespace Cthangover.FFBattle.UI
|
|||||||
label.AddThemeColorOverride("font_color", entries[i].Enabled
|
label.AddThemeColorOverride("font_color", entries[i].Enabled
|
||||||
? UiTokens.Ink
|
? UiTokens.Ink
|
||||||
: UiTokens.Faint);
|
: UiTokens.Faint);
|
||||||
|
label.AddThemeStyleboxOverride("normal", _rowIdleStyle);
|
||||||
label.MouseFilter = MouseFilterEnum.Stop;
|
label.MouseFilter = MouseFilterEnum.Stop;
|
||||||
label.Position = new Vector2(PADDING + 24, PADDING + i * ITEM_HEIGHT + CURSOR_OFFSET_Y);
|
label.Position = new Vector2(PADDING + 24, PADDING + i * ITEM_HEIGHT + CURSOR_OFFSET_Y);
|
||||||
label.Size = new Vector2(width - PADDING * 2 - 24, ITEM_HEIGHT);
|
label.Size = new Vector2(width - PADDING * 2 - 24, ITEM_HEIGHT);
|
||||||
@@ -145,16 +157,19 @@ namespace Cthangover.FFBattle.UI
|
|||||||
_selectedIndex = 0;
|
_selectedIndex = 0;
|
||||||
MoveCursor(0);
|
MoveCursor(0);
|
||||||
_cursorLabel.Visible = true;
|
_cursorLabel.Visible = true;
|
||||||
|
_caption.Text = title ?? string.Empty;
|
||||||
|
_caption.Visible = !string.IsNullOrEmpty(title);
|
||||||
_visible = true;
|
_visible = true;
|
||||||
Visible = true;
|
Visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Hides the menu and cursor. Does not clear entries — call <see cref="ShowMenu"/> to repopulate.</summary>
|
/// <summary>Hides the menu, caption and cursor. Does not clear entries — call <see cref="ShowMenu"/> to repopulate.</summary>
|
||||||
public void HideMenu()
|
public void HideMenu()
|
||||||
{
|
{
|
||||||
_visible = false;
|
_visible = false;
|
||||||
Visible = false;
|
Visible = false;
|
||||||
_cursorLabel.Visible = false;
|
_cursorLabel.Visible = false;
|
||||||
|
_caption.Visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Moves selection down, wrapping around and skipping disabled entries.</summary>
|
/// <summary>Moves selection down, wrapping around and skipping disabled entries.</summary>
|
||||||
@@ -208,6 +223,17 @@ namespace Cthangover.FFBattle.UI
|
|||||||
_cursorTween.SetLoops();
|
_cursorTween.SetLoops();
|
||||||
_cursorTween.TweenProperty(_cursorLabel, "position:x", CURSOR_OFFSET_X + 3f, 0.3f);
|
_cursorTween.TweenProperty(_cursorLabel, "position:x", CURSOR_OFFSET_X + 3f, 0.3f);
|
||||||
_cursorTween.TweenProperty(_cursorLabel, "position:x", CURSOR_OFFSET_X, 0.3f);
|
_cursorTween.TweenProperty(_cursorLabel, "position:x", CURSOR_OFFSET_X, 0.3f);
|
||||||
|
|
||||||
|
for (var i = 0; i < _itemLabels.Count; i++)
|
||||||
|
{
|
||||||
|
var enabled = i < Entries.Count && Entries[i].Enabled;
|
||||||
|
var selected = i == index && enabled;
|
||||||
|
_itemLabels[i].AddThemeStyleboxOverride("normal", selected ? _rowActiveStyle : _rowIdleStyle);
|
||||||
|
_itemLabels[i].AddThemeColorOverride("font_color",
|
||||||
|
selected ? UiTokens.AccentBright : enabled ? UiTokens.Ink : UiTokens.Faint);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnSelectionChanged?.Invoke(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPanelGuiInput(InputEvent evt)
|
private void OnPanelGuiInput(InputEvent evt)
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ namespace Cthangover.FFBattle.UI
|
|||||||
var widget = (FFCharacterWidget)TscnScenes.LoadAndBuild("scenes/ff_character_widget.tscn");
|
var widget = (FFCharacterWidget)TscnScenes.LoadAndBuild("scenes/ff_character_widget.tscn");
|
||||||
widget.EnsureConstructed();
|
widget.EnsureConstructed();
|
||||||
widget.Scale = new Vector2(scale, scale);
|
widget.Scale = new Vector2(scale, scale);
|
||||||
widget.Init(players[i]);
|
|
||||||
widget.IsPlayer = true;
|
widget.IsPlayer = true;
|
||||||
|
widget.Init(players[i]);
|
||||||
widget.MouseFilter = MouseFilterEnum.Stop;
|
widget.MouseFilter = MouseFilterEnum.Stop;
|
||||||
widget.GuiInput += (evt) =>
|
widget.GuiInput += (evt) =>
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user