From fbc4e070da480a3982825c19cc4f22f91de14d45 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Sat, 17 Jan 2026 13:59:18 -0600 Subject: [PATCH] baddle --- battle/BattleScene.gd | 39 ++++++++++++++++++++++ battle/action/BattleAction.gd | 15 +++++++-- battle/action/BattleDecision.gd | 9 ++++-- battle/action/BattleItem.gd | 2 +- battle/action/BattleMove.gd | 28 ++++++++++++---- battle/fighter/BattleFighter.gd | 10 +++++- battle/ui/ActionBox.gd | 43 +++++++++++++++++-------- battle/ui/ActionBox.tscn | 7 ++-- cutscene/battle/BattleCutsceneAction.gd | 42 ++++++++++++------------ party/Party.gd | 2 +- 10 files changed, 145 insertions(+), 52 deletions(-) diff --git a/battle/BattleScene.gd b/battle/BattleScene.gd index 31ae508..c0668bf 100644 --- a/battle/BattleScene.gd +++ b/battle/BattleScene.gd @@ -2,9 +2,48 @@ class_name BattleScene extends Node3D @export var actionBox:ActionBox = null +signal neverEmitted + func _init() ->void: BATTLE.battleScene = self BATTLE.battleStarted.connect(onBattleStarted) func onBattleStarted() -> void: pass + +func getBattleDecisions(fighters:Array[BattleFighter]) -> Array[BattleDecision]: + var decisions:Array[BattleDecision] = [] + + while fighters.size() > decisions.size(): + var fighter = fighters[decisions.size()] + var decision:BattleDecision = null + + # Simulate walking forward animation like in Final Fantasy + await get_tree().create_timer(0.5).timeout + + # Until decision made... + while decision == null: + # Get the selected action + actionBox.visible = true + var action = await self.actionBox.getAction(fighter) + + # Go back to previous fighter + if action == ActionBox.Action.BACK: + decisions.pop_back() + actionBox.visible = false + break + elif action == ActionBox.Action.ATTACK: + var targets = await _getTargets(fighter, fighter.movePrimary) + + actionBox.visible = false + + if decision != null: + decisions.append(decision) + + # Return the made decision + return decisions + +func _getTargets(fighter:BattleFighter, move:BattleAction) -> Array[BattleFighter]: + print("Determining target") + await neverEmitted + return [] \ No newline at end of file diff --git a/battle/action/BattleAction.gd b/battle/action/BattleAction.gd index bcfe5db..abafa42 100644 --- a/battle/action/BattleAction.gd +++ b/battle/action/BattleAction.gd @@ -1,11 +1,22 @@ class_name BattleAction var speedModifier:float +var canTargetMultiple:bool +var canTargetEnemy:bool +var canTargetAlly:bool +var preferAlly:bool func _init(params:Dictionary) -> void: self.speedModifier = params.get("speedModifier", 1.0) + self.canTargetMultiple = params.get("canTargetMultiple", false) + self.canTargetEnemy = params.get("canTargetEnemy", true) + self.canTargetAlly = params.get("canTargetAlly", true) + self.preferAlly = params.get("preferAlly", false) func perform(params:Dictionary) -> void: assert(params.has("user")) - assert(params.has("target")) - pass \ No newline at end of file + assert(params.has("targets")) + pass + +func canFighterUse(fighter:BattleFighter) -> bool: + return true \ No newline at end of file diff --git a/battle/action/BattleDecision.gd b/battle/action/BattleDecision.gd index 885bb60..04bfe6c 100644 --- a/battle/action/BattleDecision.gd +++ b/battle/action/BattleDecision.gd @@ -2,12 +2,15 @@ class_name BattleDecision var action:BattleAction var user:BattleFighter -var target:BattleFighter +var targets:Array[BattleFighter] -func _init(_action:BattleAction, _user:BattleFighter, _target:BattleFighter) -> void: +func _init(_action:BattleAction, _user:BattleFighter, _targets:Array[BattleFighter]) -> void: action = _action user = _user - target = _target + targets = _targets func getPriority() -> float: return 1.0 + +func execute(cutscene:Cutscene) -> void: + pass diff --git a/battle/action/BattleItem.gd b/battle/action/BattleItem.gd index 28e3311..4014965 100644 --- a/battle/action/BattleItem.gd +++ b/battle/action/BattleItem.gd @@ -11,4 +11,4 @@ func perform(params:Dictionary) -> void: super.perform(params) var user:BattleFighter = params.get("user") - var target:BattleFighter = params.get("target") \ No newline at end of file + var targets:Array[BattleFighter] = params.get("targets") \ No newline at end of file diff --git a/battle/action/BattleMove.gd b/battle/action/BattleMove.gd index 6e3141a..7b2a095 100644 --- a/battle/action/BattleMove.gd +++ b/battle/action/BattleMove.gd @@ -32,12 +32,13 @@ func perform(params:Dictionary): super.perform(params) var user:BattleFighter = params.get("user") - var target:BattleFighter = params.get("target") + var targets:Array[BattleFighter] = params.get("targets") # What to do if target is dead? - if target.status == BattleFighter.Status.DEAD: - print("Target is already dead. Move has no effect.") - assert(false) + for target in targets: + if target.status == BattleFighter.Status.DEAD: + print("Target is already dead. Move has no effect.") + assert(false) # TODO: Determine damage var damage:int = 0 @@ -51,9 +52,19 @@ func perform(params:Dictionary): if isCrit: print("CRITICAL HIT!") - target.damage(damage, isCrit) + for target in targets: + target.damage(damage, isCrit) print("DAMAGE DONE") +func canFighterUse(fighter:BattleFighter) -> bool: + if !super.canFighterUse(fighter): + return false + + if fighter.mp < self.mpCost: + return false + + return true + # Moves static var MOVE_PUNCH = BattleMove.new({ "name": "Punch", @@ -67,7 +78,8 @@ static var MOVE_FIRE1 = BattleMove.new({ "power": 25, "mpCost": 5, "accuracy": 0.9, - "moveType": MoveType.MAGICAL + "moveType": MoveType.MAGICAL, + "canTargetMultiple": true }) static var MOVE_HEAL1 = BattleMove.new({ @@ -75,5 +87,7 @@ static var MOVE_HEAL1 = BattleMove.new({ "power": -20, "mpCost": 8, "accuracy": 1.0, - "moveType": MoveType.ABILITY + "moveType": MoveType.ABILITY, + "canTargetMultiple": true, + "preferAlly": true }) \ No newline at end of file diff --git a/battle/fighter/BattleFighter.gd b/battle/fighter/BattleFighter.gd index c53c83e..0e8be42 100644 --- a/battle/fighter/BattleFighter.gd +++ b/battle/fighter/BattleFighter.gd @@ -6,7 +6,7 @@ enum Status { } enum FighterTeam { - PLAYER, + ALLY, ENEMY } @@ -106,4 +106,12 @@ func isPlayerControlled() -> bool: return controller == FighterController.PLAYER func getAIDecision() -> BattleDecision: + if !canMakeDecision(): + return null return null + +func canMakeDecision() -> bool: + return status != Status.DEAD + +func canPlayerMakeDecision() -> bool: + return isPlayerControlled() && canMakeDecision() \ No newline at end of file diff --git a/battle/ui/ActionBox.gd b/battle/ui/ActionBox.gd index 417dadb..8d0de42 100644 --- a/battle/ui/ActionBox.gd +++ b/battle/ui/ActionBox.gd @@ -1,25 +1,40 @@ class_name ActionBox extends GridContainer +enum Action { + ATTACK, + MAGIC, + ITEM, + BACK +} + @export var btnAttack:Button @export var btnMagic:Button @export var btnItem:Button +@export var btnBack:Button -signal decisionMade(move:BattleDecision) +signal action(action:Action) func _ready() -> void: - btnAttack.pressed.connect(onAttackPressed) - btnMagic.pressed.connect(onMagicPressed) - btnItem.pressed.connect(onItemPressed) + btnAttack.pressed.connect(func(): + action.emit(Action.ATTACK) + ) + btnMagic.pressed.connect(func(): + action.emit(Action.MAGIC) + ) + btnItem.pressed.connect(func(): + action.emit(Action.ITEM) + ) + btnBack.pressed.connect(func(): + action.emit(Action.BACK) + ) self.visible = false -func onAttackPressed() -> void: - print("Attack button pressed") - decisionMade.emit(BattleDecision.new(BattleMove.MOVE_PUNCH, null, null)) - -func onMagicPressed() -> void: - print("Magic button pressed") - decisionMade.emit(BattleDecision.new(BattleMove.MOVE_FIRE1, null, null)) +func getAction(fighter:BattleFighter) -> Action: + btnAttack.disabled = fighter.movePrimary == null || !fighter.movePrimary.canFighterUse(fighter) + btnMagic.disabled = fighter.movesMagical.size() == 0 + btnItem.disabled = false # TODO: check if items available? -func onItemPressed() -> void: - print("Item button pressed") - decisionMade.emit(null) \ No newline at end of file + # TODO: Update cursor position to first enabled button + + var act = await action + return act diff --git a/battle/ui/ActionBox.tscn b/battle/ui/ActionBox.tscn index 04ca711..472547f 100644 --- a/battle/ui/ActionBox.tscn +++ b/battle/ui/ActionBox.tscn @@ -2,7 +2,7 @@ [ext_resource type="Script" uid="uid://27274005hbgh" path="res://battle/ui/ActionBox.gd" id="1_w5s71"] -[node name="ActionBox" type="GridContainer" node_paths=PackedStringArray("btnAttack", "btnMagic", "btnItem")] +[node name="ActionBox" type="GridContainer" node_paths=PackedStringArray("btnAttack", "btnMagic", "btnItem", "btnBack")] anchors_preset = 3 anchor_left = 1.0 anchor_top = 1.0 @@ -17,6 +17,7 @@ script = ExtResource("1_w5s71") btnAttack = NodePath("Attack") btnMagic = NodePath("Magic") btnItem = NodePath("Items") +btnBack = NodePath("Back") metadata/_custom_type_script = "uid://27274005hbgh" [node name="Attack" type="Button" parent="."] @@ -31,6 +32,6 @@ text = "Magic" layout_mode = 2 text = "Items" -[node name="idk" type="Button" parent="."] +[node name="Back" type="Button" parent="."] layout_mode = 2 -text = "idk" +text = "Back" diff --git a/cutscene/battle/BattleCutsceneAction.gd b/cutscene/battle/BattleCutsceneAction.gd index e7c0c52..bdf9ce3 100644 --- a/cutscene/battle/BattleCutsceneAction.gd +++ b/cutscene/battle/BattleCutsceneAction.gd @@ -37,29 +37,31 @@ static func playerDecisionCallable(_params:Dictionary) -> int: print("All enemies defeated! Victory!") assert(false) - # OK we are actually fighting. + # Determine fighters and whether they're AI or player controlled + var playerFighters:Array[BattleFighter] = [] + var aiFighters:Array[BattleFighter] = [] + + for fighter:BattleFighter in BATTLE.fighterMap.values(): + if !fighter || !fighter.canPlayerMakeDecision(): + continue + playerFighters.append(fighter) + + for fighter:BattleFighter in BATTLE.fighterMap.values(): + if !fighter || !fighter.canMakeDecision(): + continue + if fighter in playerFighters: + continue + aiFighters.append(fighter) + + # Get decisions var decisions:Array[BattleDecision] = [] + var playerDecisions = await BATTLE.battleScene.getBattleDecisions(playerFighters) + decisions.append_array(playerDecisions) - for fighter:BattleFighter in BATTLE.fighterMap.values(): - if !fighter || !fighter.isPlayerControlled(): - continue - - # Handle player decision - BATTLE.battleScene.actionBox.visible = true - var decision = await BATTLE.battleScene.actionBox.decisionMade - BATTLE.battleScene.actionBox.visible = false - if decision == null: - continue - decisions.append(decision) - - # AI decisions - for fighter:BattleFighter in BATTLE.fighterMap.values(): - if !fighter || fighter.isPlayerControlled(): - continue + for fighter in aiFighters: var decision = fighter.getAIDecision() - if decision == null: - continue - decisions.append(decision) + if decision != null: + decisions.append(decision) # Here I could do something like cutscene specific logic? diff --git a/party/Party.gd b/party/Party.gd index 56aae59..7f9b484 100644 --- a/party/Party.gd +++ b/party/Party.gd @@ -2,7 +2,7 @@ class_name PartySingleton extends Node static var PARTY_JOHN = PartyMember.new({ 'name': "John", - 'team': BattleFighter.FighterTeam.PLAYER, + 'team': BattleFighter.FighterTeam.ALLY, "maxHealth": 9999999999, "movePrimary": BattleMove.MOVE_PUNCH, "movesMagical": [ BattleMove.MOVE_FIRE1 ],