commit c286271083368ec70234f7a5fbce099009c4814b Author: iisaev Date: Thu Sep 24 11:17:02 2026 +0300 upd diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..7cabeb1 --- /dev/null +++ b/manifest.json @@ -0,0 +1,12 @@ +{ + "id": "turn_battle_base", + "name": "Turn-based battle engine", + "version": "1.0.0", + "author": "ct", + "description": "Turn-based battle GUI: battle scene, victory/defeat screens, EXP and loot reports", + "category": "library, gameplay", + "sources": [ + "src/**/*.cs" + ], + "depends": [ "core>=1", "interface>=1" ] +} diff --git a/scenes/battle/battle.tscn b/scenes/battle/battle.tscn new file mode 100644 index 0000000..3f23a62 --- /dev/null +++ b/scenes/battle/battle.tscn @@ -0,0 +1,69 @@ +[gd_scene format=3 uid="uid://bsrphp6130ptb"] + +[ext_resource type="Script" uid="uid://dq85ubii8cfid" path="res://Core/Battle/Actions/BattleActionMachine.cs" id="BattleActionMachineScript"] +[ext_resource type="Script" uid="uid://c1rwrejr5aoc0" path="res://Core/Battle/BattleSceneContext.cs" id="BattleSceneContextScript"] +[ext_resource type="Script" path="src/BattleSpeedControl.cs" id="BattleSpeedControlScript"] +[ext_resource type="PackedScene" uid="uid://bjhtml8ujba7p" path="res://scenes/battle/deadground.tscn" id="DeadgroundScene"] +[ext_resource type="Script" uid="uid://icsu2hv2c68r" path="res://Core/UI/Lights/UiLightController.cs" id="UiLightControllerScript"] +[ext_resource type="PackedScene" uid="uid://b3yq032kqhtn4" path="res://scenes/battle/winground.tscn" id="WingroundScene"] + +[node name="BattleScene" type="Control" unique_id=760066045] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("BattleSceneContextScript") + +[node name="BattleBg" type="TextureRect" parent="." unique_id=1553515841] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +expand_mode = 1 +stretch_mode = 5 + +[node name="Panel" type="Control" parent="." unique_id=1818202398] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="ToolPanel" type="Control" parent="." unique_id=16130515] +layout_mode = 1 +anchor_top = 0.9 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 0 + +[node name="BattleActionMachine" type="Node" parent="ToolPanel" unique_id=459578957] +script = ExtResource("BattleActionMachineScript") + +[node name="BattleSpeedControl" type="Control" parent="."] +layout_mode = 0 +script = ExtResource("BattleSpeedControlScript") + +[node name="Deadground" parent="." unique_id=2064995106 instance=ExtResource("DeadgroundScene")] +visible = false +layout_mode = 1 + +[node name="Winground" parent="." unique_id=652178152 instance=ExtResource("WingroundScene")] +visible = false +layout_mode = 1 + +[node name="Lights" type="Control" parent="." unique_id=1021519456] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +script = ExtResource("UiLightControllerScript") diff --git a/scenes/battle/deadground.tscn b/scenes/battle/deadground.tscn new file mode 100644 index 0000000..506a077 --- /dev/null +++ b/scenes/battle/deadground.tscn @@ -0,0 +1,77 @@ +[gd_scene format=3 uid="uid://cdln7efykbahy"] + +[ext_resource type="Script" path="src/DeadgroundBehaviour.cs" id="DeadgroundBehaviourController"] + +[node name="Deadground" type="Control" unique_id=1769284696] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("DeadgroundBehaviourController") + +[node name="TransitionBg" type="TextureRect" parent="." unique_id=2003806831] +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="Content" type="Control" parent="." unique_id=516317159] +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="DimBg" type="ColorRect" parent="Content" unique_id=1040388089] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +color = Color(0, 0, 0, 0.7) + +[node name="CenterPanel" type="VBoxContainer" parent="Content" unique_id=695019244] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -200.0 +offset_top = -100.0 +offset_right = 200.0 +offset_bottom = 100.0 +grow_horizontal = 2 +grow_vertical = 2 +alignment = 1 + +[node name="TitleLabel" type="Label" parent="Content/CenterPanel" unique_id=1047371679] +layout_mode = 2 +size_flags_horizontal = 4 +theme_override_colors/font_color = Color(1, 0.3, 0.3, 1) +theme_override_font_sizes/font_size = 48 +text = "ui/battle/defeat" +horizontal_alignment = 1 +auto_translate = true + +[node name="Spacer" type="Control" parent="Content/CenterPanel" unique_id=1145465550] +custom_minimum_size = Vector2(0, 20) +layout_mode = 2 + +[node name="MainMenuButton" type="Button" parent="Content/CenterPanel" unique_id=1265108975] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +size_flags_horizontal = 3 +text = "ui/battle/endgame/to_main_menu" +auto_translate = true + +[connection signal="pressed" from="Content/CenterPanel/MainMenuButton" to="." method="ToMainMenuClick"] diff --git a/scenes/battle/exp_report_card.tscn b/scenes/battle/exp_report_card.tscn new file mode 100644 index 0000000..b6e47cf --- /dev/null +++ b/scenes/battle/exp_report_card.tscn @@ -0,0 +1,44 @@ +[gd_scene format=3 uid="uid://dx40qu1dj80in"] + +[ext_resource type="Script" path="src/ExpReportCard.cs" id="ExpReportCardScript"] + +[node name="ExpReportCard" type="Control" unique_id=223866085] +custom_minimum_size = Vector2(130, 130) +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("ExpReportCardScript") +icon = NodePath("Panel/Icon") +nameLabel = NodePath("Panel/NameLabel") +expLabel = NodePath("Panel/ExpLabel") + +[node name="Panel" type="VBoxContainer" parent="." unique_id=284918240] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 4 +alignment = 1 + +[node name="Icon" type="TextureRect" parent="Panel" unique_id=149388562] +custom_minimum_size = Vector2(100, 100) +layout_mode = 2 +size_flags_horizontal = 4 +expand_mode = 1 +stretch_mode = 5 + +[node name="NameLabel" type="Label" parent="Panel" unique_id=555712225] +layout_mode = 2 +theme_override_colors/font_color = Color(1, 1, 1, 1) +theme_override_font_sizes/font_size = 16 +horizontal_alignment = 1 + +[node name="ExpLabel" type="Label" parent="Panel" unique_id=1019918817] +layout_mode = 2 +theme_override_colors/font_color = Color(0.5, 1, 0.5, 1) +theme_override_font_sizes/font_size = 22 +horizontal_alignment = 1 diff --git a/scenes/battle/loot_report_item.tscn b/scenes/battle/loot_report_item.tscn new file mode 100644 index 0000000..6ac7306 --- /dev/null +++ b/scenes/battle/loot_report_item.tscn @@ -0,0 +1,40 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="src/LootReportItem.cs" id="loot_script"] + +[node name="LootReportItem" type="Control"] +custom_minimum_size = Vector2(80, 100) +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("loot_script") +icon = NodePath("Panel/Icon") +countLabel = NodePath("Panel/CountLabel") + +[node name="Panel" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 2 +alignment = 1 +mouse_filter = 2 + +[node name="Icon" type="TextureRect" parent="Panel"] +custom_minimum_size = Vector2(64, 64) +layout_mode = 2 +size_flags_horizontal = 4 +expand_mode = 1 +stretch_mode = 5 +mouse_filter = 2 + +[node name="CountLabel" type="Label" parent="Panel"] +layout_mode = 2 +theme_override_colors/font_color = Color(1, 1, 1, 1) +theme_override_font_sizes/font_size = 16 +horizontal_alignment = 1 +mouse_filter = 2 diff --git a/scenes/battle/winground.tscn b/scenes/battle/winground.tscn new file mode 100644 index 0000000..8dcab91 --- /dev/null +++ b/scenes/battle/winground.tscn @@ -0,0 +1,124 @@ +[gd_scene format=3 uid="uid://cpfjgflxria5i"] + +[ext_resource type="Script" path="src/WingroundBehaviour.cs" id="1_win"] + +[node name="Winground" type="Control" unique_id=1812297436 node_paths=PackedStringArray("lootTitle")] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_win") +lootTitle = NodePath("Content/LootTitle") + +[node name="TransitionBg" type="TextureRect" parent="." unique_id=1411801503] +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="Content" type="Control" parent="." unique_id=865412688] +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="DimBg" type="ColorRect" parent="Content" unique_id=1956777181] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +color = Color(0, 0, 0, 0.7) + +[node name="CharExpContainer" type="HBoxContainer" parent="Content" unique_id=1416765042] +layout_mode = 1 +anchors_preset = -1 +anchor_left = 0.03 +anchor_top = 0.05 +anchor_right = 0.42100003 +anchor_bottom = 0.344 +offset_right = -0.32006836 +offset_bottom = -0.25601196 +size_flags_horizontal = 4 +size_flags_vertical = 3 +theme_override_constants/separation = 20 +alignment = 1 + +[node name="LootTitle" type="Label" parent="Content" unique_id=2075960163] +layout_mode = 1 +anchors_preset = -1 +anchor_left = 0.65400004 +anchor_top = 0.544 +anchor_right = 0.95100003 +anchor_bottom = 0.58400005 +offset_left = -0.6800537 +offset_top = -0.056030273 +offset_right = -0.92004395 +offset_bottom = -0.016052246 +text = "ui/battle/victory/loot_title" + +[node name="LootScrollContainer" type="ScrollContainer" parent="Content" unique_id=2098696418] +layout_mode = 1 +anchors_preset = -1 +anchor_left = 0.65400004 +anchor_top = 0.544 +anchor_right = 0.96000004 +anchor_bottom = 0.94200003 +offset_left = -0.6800537 +offset_top = -0.056030273 +offset_right = -0.20007324 +offset_bottom = 0.39196777 +grow_horizontal = 2 +grow_vertical = 2 +horizontal_scroll_mode = 3 + +[node name="LootContent" type="Control" parent="Content/LootScrollContainer" unique_id=969489928] +layout_mode = 2 +mouse_filter = 2 + +[node name="CenterPanel" type="VBoxContainer" parent="Content" unique_id=385679072] +layout_mode = 1 +anchors_preset = -1 +anchor_left = 0.31800002 +anchor_top = 0.26000002 +anchor_right = 0.68200004 +anchor_bottom = 0.55200005 +offset_left = -0.5600586 +offset_top = -0.24002075 +offset_right = 0.5599365 +offset_bottom = -0.24804688 +grow_horizontal = 2 +grow_vertical = 2 +alignment = 1 + +[node name="TitleLabel" type="Label" parent="Content/CenterPanel" unique_id=583298800] +auto_translate_mode = 1 +layout_mode = 2 +size_flags_horizontal = 4 +theme_override_colors/font_color = Color(0.3, 1, 0.3, 1) +theme_override_font_sizes/font_size = 48 +text = "ui/battle/victory" +horizontal_alignment = 1 + +[node name="Spacer" type="Control" parent="Content/CenterPanel" unique_id=1854105230] +custom_minimum_size = Vector2(0, 20) +layout_mode = 2 + +[node name="ContinueButton" type="Button" parent="Content/CenterPanel" unique_id=1307713785] +auto_translate_mode = 1 +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +size_flags_horizontal = 3 +text = "ui/battle/victory/return" + +[connection signal="pressed" from="Content/CenterPanel/ContinueButton" to="." method="ToReturnScene"] diff --git a/src/BattleSpeedControl.cs b/src/BattleSpeedControl.cs new file mode 100644 index 0000000..b4e6158 --- /dev/null +++ b/src/BattleSpeedControl.cs @@ -0,0 +1,107 @@ +using System.Collections.Generic; +using Cthangover.Core.Battle; +using Cthangover.Core.Settings; +using Godot; + +namespace Cthangover.TurnBattle +{ + /// + /// In-battle speed toggle bar (x1/x4/x8). Directly sets + /// Engine.TimeScale to globally accelerate the entire game loop — + /// not just animations. Persists the choice to GameData.Settings + /// so it survives scene reloads. restores 1x + /// without saving and is the part the core scene consumes through + /// when battle ends. + /// + public partial class BattleSpeedControl : Control, IBattleSpeedControl + { + private readonly Dictionary _buttons = new(); + private int _activeSpeed; + + /// + /// Builds the speed bar in the constructor. Godot never runs a + /// _Ready override declared in a mod assembly, so the buttons and the + /// saved-speed restore must happen here. Anchors are used instead of a + /// viewport-size lookup because the viewport is unavailable before the node + /// enters the tree. + /// + public BattleSpeedControl() + { + var speeds = new[] { 1, 4, 8 }; + var hbox = new HBoxContainer { Name = "SpeedButtons" }; + hbox.Alignment = BoxContainer.AlignmentMode.Begin; + + foreach (var speed in speeds) + { + var btn = new Button + { + Text = $"x{speed}", + CustomMinimumSize = new Vector2(48, 32) + }; + btn.Pressed += () => OnSpeedSelected(speed); + hbox.AddChild(btn); + _buttons[speed] = btn; + } + + AddChild(hbox); + + AnchorLeft = 1f; + AnchorRight = 1f; + AnchorTop = 0f; + AnchorBottom = 0f; + OffsetLeft = -200f; + OffsetRight = -20f; + OffsetTop = 20f; + OffsetBottom = 52f; + + ApplySavedSpeed(); + } + + /// + /// Restores the speed from . + /// Called on _Ready to reapply the saved preference. + /// + public void ApplySavedSpeed() + { + var speed = GameData.Instance.Settings.BattleSpeed; + if (speed < 1 || speed > 8) + speed = 1; + + _activeSpeed = speed; + Engine.TimeScale = speed; + UpdateHighlight(); + } + + /// + /// Resets to 1.0x without + /// persisting to settings. Used when battle ends so the lobby + /// and menus run at normal speed regardless of the player's + /// in-battle preference. + /// + public void ResetToNormal() + { + Engine.TimeScale = 1.0f; + } + + private void OnSpeedSelected(int speed) + { + _activeSpeed = speed; + Engine.TimeScale = speed; + + var settings = GameData.Instance.Settings; + settings.BattleSpeed = speed; + settings.Save(); + + UpdateHighlight(); + } + + private void UpdateHighlight() + { + foreach (var kv in _buttons) + { + var isActive = kv.Key == _activeSpeed; + kv.Value.Modulate = isActive ? new Color(0.4f, 1f, 0.4f, 1f) : Colors.White; + } + } + } +} diff --git a/src/DeadgroundBehaviour.cs b/src/DeadgroundBehaviour.cs new file mode 100644 index 0000000..d1590c5 --- /dev/null +++ b/src/DeadgroundBehaviour.cs @@ -0,0 +1,58 @@ +using Cthangover.Core.Audio; +using Cthangover.Core.Mods; +using Cthangover.Core.Mods.Resolvers; +using Cthangover.Core.Scenes; +using Cthangover.Core.Settings; +using Cthangover.Core.UI.Base.Lists; +using Cthangover.Core.Utils; +using Godot; + +namespace Cthangover.TurnBattle +{ + /// + /// Defeat screen widget. Minimal compared to WingroundBehaviour — + /// only sets the deadground background texture (resolved from mods) + /// and offers a Main Menu button that clears BattleData and reloads + /// the menu scene. No loot, no EXP. + /// + public partial class DeadgroundBehaviour : TransitionWidget + { + /// + /// Clears battle data and switches to the main menu scene. + /// Bound to the "Main Menu" button on the defeat screen. + /// + public void ToMainMenuClick() + { + var sceneService = GetNode("/root/GodotSceneService"); + sceneService?.SwitchToMenu(); + GameData.Instance.Runtime.BattleData = null; + } + + /// + /// Plays the defeat sound, resolves the deadground background + /// texture from mods, and shows the transition widget. + /// + public override void Show() + { + GameLogger.Log("BATTLE", $"Deadground.Show() ENTER: Visible={Visible}, GodotVisible={base.Visible}", LogLevel.Debug); + + var audioService = GetNode("/root/AudioService"); + audioService?.PlaySound("battle/deadground", SoundType.UI); + + var bg = GetNodeOrNull("TransitionBg"); + + GameLogger.Log("BATTLE", $"Deadground.Show: TransitionBg={(bg != null ? "found" : "NULL")}, texture={(bg?.Texture != null ? "has" : "null")}", LogLevel.Error); + + if (bg != null && bg.Texture == null) + { + var tex = Textures.Resolve("deadground"); + GameLogger.Log("BATTLE", $"Deadground.Show: ResolveTexture('deadground') = {(tex != null ? "loaded" : "NULL")}", LogLevel.Error); + bg.Texture = tex; + } + + base.Show(); + + GameLogger.Log("BATTLE", $"Deadground.Show() EXIT: Visible={Visible}, GodotVisible={base.Visible}", LogLevel.Debug); + } + } +} diff --git a/src/ExpReportCard.cs b/src/ExpReportCard.cs new file mode 100644 index 0000000..8cc3ff0 --- /dev/null +++ b/src/ExpReportCard.cs @@ -0,0 +1,43 @@ +using Godot; + +namespace Cthangover.TurnBattle +{ + /// + /// Single character EXP report in the victory screen. Displays the + /// character icon, localized name, and "+N" EXP value. Uses the same + /// ResolveMissingNodes pattern as LootReportItem for robustness + /// against missing scene links. + /// + public partial class ExpReportCard : Control + { + [Export] private TextureRect icon; + [Export] private Label nameLabel; + [Export] private Label expLabel; + + private void ResolveMissingNodes() + { + icon ??= GetNodeOrNull("Panel/Icon"); + nameLabel ??= GetNodeOrNull