From f0803c56ffc880e35d893819fc418b4a43e46978 Mon Sep 17 00:00:00 2001 From: cheesefrycook <151570126+cheesefrycook@users.noreply.github.com> Date: Wed, 27 Dec 2023 13:32:58 -0600 Subject: [PATCH] Deck animation and layout (#25) * Add CardMovementComponent -Add states, but they don't do anything yet. Just lerp to desired position -Replace LerpComponent on Card scene * Change CardContainer and CardWorld to Control Change from Node2D to Control, this way we can anchor the CardContainer to the bottom of the screen * Add card draw/discard queue system Cards now draw and discard in a queue. To remove or draw a card, it is first added to a queue. Queued cards get removed/added from the queue on a looping timer -Cards are removed from draw pile immediately when a draw is requested. They are added to the queue so they can be spawned async -Cards are added to the discard pile immediately when a discard is requested. They are added to the queue to be destroyed async. This means that cards technically exist in the draw/discard immediately when the request is given, but the world versions of the cards may spawn/destroy at a later time. * Add CardMovementState resource -Add CardMovementState as a Resource. So each state will be a separate file -Added child states InHand, MovingToHand, and Discarding -Added CardState properties which gives values for the card states -Added EasingConstants which is string constants used for easing * Add better card separation Cards now separate based on a max and min value. Cards will squish closer together if there are a lot of cards in your hand. If you only have a small amount of cards in your hand, they will be centered * Fix z-ordering of cards Cards are moved to the top of the hierarchy if they are hovered. This makes it to where cards below this card can't be hovered. * Other cards scoot out of the way when you hover one When you hover a card, other cards in your hand scoot out of the way to give room for the hovered card * Add y-offset and rotation curve to cards in hand * Add HOVERED and QUEUED states * Remove state switch in CardContainer * Smooth rotation Hover and Queued states * Organize state values. Fix MOVING_TO_HAND state MOVING_TO_HAND state was not easing to the correct position * Fix offset in QUEUED * Move discard and draw timers Moved timers to the end of the draw queue functions so the cards aren't discarded and drawn with a starting delay * Naming cleanup. Fix weird movement with discarding state * Fix movement issue with focus card Focus card not getting set to null when played * Ease rotation on discarding state * Bind input only when the card is finished moving * Restrict end turn button press Only allow when cards are finished dealing and you have no card queued * Static typing and cleanup * Fix tests by adding on_cards_finished_dealing Wait for cards to finish dealing before checking cards in test_cards Removed PhaseManager.on_game_start check in test_health because the tests would never start. Since test_health isn't the first test anymore it never recieved the signal. Wasn't needed anyways * Change CardStateProperties to Resource instead of Node * Further commenting and cleanup * Issue-25: Update naming for _state_not_null(), clean up readability for pulling card to start discard animation * Issue-25: Rename LERP_SPEED to MOVE_SPEED for readability, added active_cards array to determine if all active cards are done doing their action * Issue-12: Update active_cards from an array to just a single CardWorld object --------- Co-authored-by: tynutsathitya --- #Scenes/TestingScene.tscn | 37 ++- Cards/Card.tscn | 15 +- Cards/CardBase.gd | 72 ++++- Cards/CardContainer.gd | 271 ++++++++++++++---- Cards/CardContainer.tscn | 4 +- Cards/CardMovementComponent.gd | 84 ++++++ Cards/CardWorld.gd | 6 +- Cards/Movement State/CardMovementState.gd | 37 +++ Cards/Movement State/CardStateProperties.gd | 8 + Cards/Movement State/MoveState_Discarding.gd | 42 +++ Cards/Movement State/MoveState_Hovered.gd | 32 +++ Cards/Movement State/MoveState_InHand.gd | 27 ++ .../Movement State/MoveState_MovingToHand.gd | 35 +++ Cards/Movement State/MoveState_Queued.gd | 32 +++ Cards/Resource/Card_Damage.tres | 8 + Cards/Resource/Card_DamageHealth.tres | 8 + Cards/Resource/Card_DrawCards.tres | 8 + Cards/Resource/Card_Poison.tres | 8 + Cards/Resource/Card_damage_and_poison.tres | 8 + Core/Battler.gd | 5 +- Core/Enums.gd | 10 + Helpers/EasingConstants.gd | 6 + Helpers/StaticHelpers.gd | 13 + Managers/CardManager.gd | 1 + Tests/test_cards.gd | 17 +- Tests/test_health.gd | 3 - UI/EndTurnButton.gd | 11 +- 27 files changed, 715 insertions(+), 93 deletions(-) create mode 100644 Cards/CardMovementComponent.gd create mode 100644 Cards/Movement State/CardMovementState.gd create mode 100644 Cards/Movement State/CardStateProperties.gd create mode 100644 Cards/Movement State/MoveState_Discarding.gd create mode 100644 Cards/Movement State/MoveState_Hovered.gd create mode 100644 Cards/Movement State/MoveState_InHand.gd create mode 100644 Cards/Movement State/MoveState_MovingToHand.gd create mode 100644 Cards/Movement State/MoveState_Queued.gd create mode 100644 Helpers/EasingConstants.gd diff --git a/#Scenes/TestingScene.tscn b/#Scenes/TestingScene.tscn index 6f0d1e18..d8c97bc4 100644 --- a/#Scenes/TestingScene.tscn +++ b/#Scenes/TestingScene.tscn @@ -1,15 +1,18 @@ -[gd_scene load_steps=14 format=3 uid="uid://87fedoq257xg"] +[gd_scene load_steps=17 format=3 uid="uid://b60uabg68ra1l"] [ext_resource type="PackedScene" uid="uid://clmg3l3n28x38" path="res://Entity/Player/Player.tscn" id="3_4psp7"] [ext_resource type="PackedScene" uid="uid://dpjfy4pv0vxst" path="res://Cards/CardContainer.tscn" id="3_e7sws"] -[ext_resource type="Resource" uid="uid://dxgoopi1roxu4" path="res://Cards/Resource/Card_Damage.tres" id="4_uiqnj"] -[ext_resource type="Resource" uid="uid://5yn4t13kwwoo" path="res://Cards/Resource/Card_DamageHealth.tres" id="5_j13b3"] -[ext_resource type="Resource" uid="uid://d4lugn62mmlep" path="res://Cards/Resource/Card_DrawCards.tres" id="6_4i4gj"] -[ext_resource type="Resource" uid="uid://ctx8jsvac84so" path="res://Cards/Resource/Card_Poison.tres" id="8_cs735"] +[ext_resource type="Resource" uid="uid://dxgoopi1roxu4" path="res://Cards/Resource/Card_Damage.tres" id="4_wvn3v"] +[ext_resource type="Resource" uid="uid://0x385c3nuq8f" path="res://Cards/Resource/Card_DamageAll.tres" id="5_j1lqt"] +[ext_resource type="Resource" uid="uid://5yn4t13kwwoo" path="res://Cards/Resource/Card_DamageHealth.tres" id="6_4124l"] +[ext_resource type="Resource" uid="uid://d4lugn62mmlep" path="res://Cards/Resource/Card_DrawCards.tres" id="7_smkw8"] [ext_resource type="PackedScene" uid="uid://bcpmrmofcilbn" path="res://Core/Battler.tscn" id="8_qtw1k"] -[ext_resource type="Resource" uid="uid://3s4aet1ciesh" path="res://Cards/Resource/Card_damage_and_poison.tres" id="8_ruc8h"] [ext_resource type="Script" path="res://UI/DrawPileUISetter.gd" id="8_voref"] +[ext_resource type="Resource" uid="uid://ctx8jsvac84so" path="res://Cards/Resource/Card_Poison.tres" id="8_x6t2k"] +[ext_resource type="Resource" uid="uid://d12g33rc6c3u5" path="res://Cards/Resource/Card_Heal.tres" id="9_ojxic"] [ext_resource type="Script" path="res://UI/DiscardPileUISetter.gd" id="10_pqly7"] +[ext_resource type="Resource" uid="uid://3s4aet1ciesh" path="res://Cards/Resource/Card_damage_and_poison.tres" id="10_w0xgm"] +[ext_resource type="Resource" uid="uid://bsrdu33ukb1ym" path="res://Cards/Resource/Card_EnemyAttack.tres" id="11_3k5t2"] [ext_resource type="Texture2D" uid="uid://caemucaya30wh" path="res://Cards/draw_pile.png" id="11_pw70x"] [ext_resource type="Texture2D" uid="uid://d4muqvs3etnr8" path="res://Cards/discard_pile.png" id="12_kxw48"] [ext_resource type="Script" path="res://UI/EndTurnButton.gd" id="14_dpe64"] @@ -21,9 +24,6 @@ [node name="Player" parent="." instance=ExtResource("3_4psp7")] position = Vector2(595, 284) -[node name="CardContainer" parent="." instance=ExtResource("3_e7sws")] -default_deck = Array[Resource("res://Cards/CardBase.gd")]([ExtResource("4_uiqnj"), ExtResource("5_j13b3"), ExtResource("6_4i4gj"), ExtResource("8_cs735"), ExtResource("8_ruc8h")]) - [node name="CanvasLayer" type="CanvasLayer" parent="."] [node name="UIControl" type="Control" parent="CanvasLayer"] @@ -35,6 +35,25 @@ grow_horizontal = 2 grow_vertical = 2 mouse_filter = 2 +[node name="CardContainer" parent="CanvasLayer/UIControl" node_paths=PackedStringArray("draw_pile_ui", "discard_pile_ui") instance=ExtResource("3_e7sws")] +layout_mode = 1 +anchors_preset = 7 +anchor_left = 0.5 +anchor_top = 1.0 +anchor_right = 0.5 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 0 +default_deck = Array[Resource("res://Cards/CardBase.gd")]([ExtResource("4_wvn3v"), ExtResource("5_j1lqt"), ExtResource("6_4124l"), ExtResource("7_smkw8"), ExtResource("9_ojxic"), ExtResource("8_x6t2k"), ExtResource("10_w0xgm"), ExtResource("11_3k5t2")]) +starting_hand_size = 10 +max_hand_width = 900.0 +min_card_separation = 90.0 +max_card_separation = 120.0 +hover_offset_max = 100.0 +max_rotation = 40.0 +draw_pile_ui = NodePath("../DrawPile") +discard_pile_ui = NodePath("../DiscardPile") + [node name="EndTurnButton" type="Button" parent="CanvasLayer/UIControl"] layout_mode = 1 anchors_preset = 3 diff --git a/Cards/Card.tscn b/Cards/Card.tscn index a40d39a5..728eab5a 100644 --- a/Cards/Card.tscn +++ b/Cards/Card.tscn @@ -3,25 +3,26 @@ [ext_resource type="Script" path="res://Cards/CardWorld.gd" id="1_4el5m"] [ext_resource type="Texture2D" uid="uid://nheutko3fha3" path="res://Cards/card.png" id="2_k6rq5"] [ext_resource type="Script" path="res://Input/ClickHandler.gd" id="3_du4nn"] -[ext_resource type="Script" path="res://Helpers/LerpComponent.gd" id="4_ujyje"] [ext_resource type="Script" path="res://UI/CardUISetter.gd" id="5_hkyxd"] [ext_resource type="Texture2D" uid="uid://bdc5r0w6lyh34" path="res://Cards/card_image.png" id="6_p357x"] +[ext_resource type="Script" path="res://Cards/CardMovementComponent.gd" id="6_qvt2l"] [sub_resource type="LabelSettings" id="LabelSettings_ooxvf"] font_color = Color(0, 0, 0, 1) -[node name="Card" type="Node2D"] +[node name="Card" type="Control"] +layout_mode = 3 +anchors_preset = 0 script = ExtResource("1_4el5m") -[node name="ClickHandler" type="Node2D" parent="."] +[node name="ClickHandler" type="Node" parent="."] script = ExtResource("3_du4nn") -[node name="LerpComponent" type="Node2D" parent="."] -script = ExtResource("4_ujyje") -lerp_speed = 7.0 +[node name="CardMovementComponent" type="Node" parent="."] +script = ExtResource("6_qvt2l") [node name="CardUI" type="Control" parent="." node_paths=PackedStringArray("title_label", "description_label", "key_art")] -layout_mode = 3 +layout_mode = 1 anchors_preset = 8 anchor_left = 0.5 anchor_top = 0.5 diff --git a/Cards/CardBase.gd b/Cards/CardBase.gd index 1fd5abc1..e392f8d0 100644 --- a/Cards/CardBase.gd +++ b/Cards/CardBase.gd @@ -13,9 +13,14 @@ class_name CardBase ## wish to take damage in some contexts. ## For example, consider the card: "Deal 10 damage to all enemies, but take 3 damage" - -# @export var status_to_apply_to_target: Array[StatusBase] -# @export var status_to_apply_to_caster: Array[StatusBase] +@export var damage_to_apply_to_target: float = 0.0 +@export var damage_to_apply_to_caster: float = 0.0 +@export var status_to_apply_to_target: Array[StatusBase] +@export var status_to_apply_to_caster: Array[StatusBase] +@export var affect_all_targets: bool = false +@export var affect_all_casters: bool = false +@export var amount_of_cards_to_draw: int = 0 +@export var amount_of_cards_to_discard: int = 0 @export var application_type: Enums.ApplicationType = Enums.ApplicationType.ENEMY_ONLY @export var card_title: String = "NULL" @export var card_key_art: ImageTexture = null @@ -43,5 +48,64 @@ func can_play_card(caster: Entity, target: Entity) -> bool: return caster.get_party_component().can_play_on_entity(application_type, target) -func on_card_play(target: Entity) -> void: +func on_card_play(caster: Entity, target: Entity) -> void: _apply_all_effects(target) + _deal_damage(caster, target) + _apply_status(caster, target) + _draw_cards() + _discard_random_cards() + CardManager.on_card_action_finished.emit(self) + # TODO add other functionality that lots of cards may share (eg: restore AP) + + +# override in child cards if you want to deal damage in a unique way +func _deal_damage(caster: Entity, target: Entity) -> void: + # damage target + if damage_to_apply_to_target != 0.0: + _damage_entity(caster, target, damage_to_apply_to_target, affect_all_targets) + + #damage caster + if damage_to_apply_to_caster != 0.0: + _damage_entity(caster, caster, damage_to_apply_to_caster, affect_all_casters) + + +func _damage_entity(caster: Entity, target: Entity, damage_amount: float, damage_all: bool) -> void: + var target_damage_data: DealDamageData = DealDamageData.new() + target_damage_data.damage = damage_amount + target_damage_data.caster = caster + + # If damage_all is set, try to damage all the party members set in the party component + if damage_all: + var party: Array[Entity] = target.get_party_component().party + assert(party.size() > 0, "Entity has an empty party. Make sure you added party members.") + + for party_member: Entity in party: + party_member.get_health_component().deal_damage(target_damage_data) + else: + target.get_health_component().deal_damage(target_damage_data) + + +func _apply_status(caster: Entity, target: Entity) -> void: + # apply status to caster + for status: StatusBase in status_to_apply_to_caster: + if affect_all_casters: + for party_member: Entity in caster.get_party_component().party: + party_member.get_status_component().add_status(status, caster) + else: + caster.get_status_component().add_status(status, caster) + + # apply status to target + for status: StatusBase in status_to_apply_to_target: + if affect_all_targets: + for party_member: Entity in target.get_party_component().party: + party_member.get_status_component().add_status(status, caster) + else: + target.get_status_component().add_status(status, caster) + + +func _draw_cards() -> void: + CardManager.card_container.draw_cards(amount_of_cards_to_draw) + + +func _discard_random_cards() -> void: + CardManager.card_container.discard_random_card(amount_of_cards_to_discard) diff --git a/Cards/CardContainer.gd b/Cards/CardContainer.gd index bae33aae..679e4732 100644 --- a/Cards/CardContainer.gd +++ b/Cards/CardContainer.gd @@ -1,4 +1,4 @@ -extends Node2D +extends Control class_name CardContainer ## Lays out cards in the player's hand and spawns new cards. ## @@ -9,6 +9,7 @@ class_name CardContainer signal on_card_counts_updated +signal on_cards_finished_dealing @export var card_scene: PackedScene @export var total_hand_width: float = 100 @@ -17,16 +18,36 @@ signal on_card_counts_updated @export var default_deck: Array[CardBase] @export var starting_hand_size: int = 5 @export var max_hand_size: int = 10 +@export var card_draw_time: float = 0.2 +@export var card_discard_time: float = 0.1 +@export var max_hand_width: float = 100 +@export var min_card_separation: float = 100 +@export var max_card_separation: float = 100 +# Amount that other cards will scoot out of the way when you hover a card +@export var hover_offset_max: float = 50.0 +@export var max_rotation: float = 30 +# Max amount of visual offset for cards. Increasing this will make cards more "curved" +@export var max_hand_offset_y: float = 100 +@export var draw_pile_ui: DrawPileUISetter = null +@export var discard_pile_ui: DiscardPileUISetter = null var cards_in_hand: Array[CardWorld] = [] var draw_pile: Array[CardBase] = [] var discard_pile: Array[CardBase] = [] var queued_card: CardWorld = null +var _active_card: CardBase = null +var _focused_card: CardWorld = null +var _cards_queued_for_add: Array[CardBase] = [] +var _draw_timer: SceneTreeTimer = null +var _cards_queued_for_discard: Array[CardWorld] = [] +var _discard_timer: SceneTreeTimer = null + func _ready() -> void: PhaseManager.on_phase_changed.connect(_on_phase_changed) CardManager.set_card_container(self) + CardManager.on_card_action_finished.connect(remove_active_card) _init_default_draw_pile() @@ -40,6 +61,7 @@ func set_queued_card(card: CardWorld) -> void: func remove_queued_card() -> void: + _focused_card = null discard_card(queued_card) set_queued_card(null) @@ -48,6 +70,21 @@ func is_card_queued() -> bool: return queued_card != null +func set_active_card(card: CardBase) -> void: + _active_card = card + + +func remove_active_card(card: CardBase) -> void: + _active_card = null + + +func are_cards_active() -> bool: + return _active_card != null + +func are_cards_dealing() -> bool: + return _cards_queued_for_add.size() > 0 + + func deal_to_starting_hand_size() -> void: # deal to our starting hand size, ignoring amount of cards we already have var amount_to_draw: int = maxi(0, starting_hand_size - cards_in_hand.size()) @@ -57,7 +94,7 @@ func deal_to_starting_hand_size() -> void: func draw_cards(amount: int) -> void: for card_index: int in amount: # limit amount of cards to a maximum - if cards_in_hand.size() < max_hand_size: + if get_total_queued_hand_size() < max_hand_size: _draw_card() @@ -81,6 +118,10 @@ func discard_card(card: CardWorld) -> void: _discard_card_at_index(card_index) +func get_total_queued_hand_size() -> int: + return _cards_queued_for_add.size() + cards_in_hand.size() + + func get_draw_pile_size() -> int: return draw_pile.size() @@ -94,7 +135,7 @@ func _init_default_draw_pile() -> void: draw_pile.shuffle() -# Final place where a card is created and added to the world +# This is where cards are removed from the draw pile. func _draw_card() -> void: # if draw pile is empty, shuffle discard pile into draw pile. Clear discard pile. if draw_pile.size() <= 0: @@ -109,22 +150,91 @@ func _draw_card() -> void: var drawn_card: CardBase = draw_pile[0] draw_pile.remove_at(0) - _create_card_in_world(drawn_card) + # add to queue for creation at a later time + _add_to_card_draw_queue(drawn_card) on_card_counts_updated.emit() +# This is where a card is removed from the hand and added to the discard pile. +func _discard_card_at_index(card_index: int) -> void: + var card: CardWorld = cards_in_hand[card_index] + + # add to discard pile + discard_pile.append(card.card_data) + + # remove from hand and add to discard queue + cards_in_hand.remove_at(card_index) + _add_to_discard_queue(card) + + on_card_counts_updated.emit() + + +func _discard_last_card() -> void: + if cards_in_hand.size() > 0: + _discard_card_at_index(cards_in_hand.size() - 1) + + +func _add_to_card_draw_queue(card: CardBase) -> void: + _cards_queued_for_add.append(card) + _handle_card_draw_queue() + + +# Final place where a card is created and added to the world. +# NOTE: at this point, cards have already been removed from the draw pile. +# Cards are removed from the draw pile in _draw_card. +# _create_card_in_world spawns the cards from the queue and adds them to your hand. +func _handle_card_draw_queue() -> void: + if _draw_timer != null: + return + + var card_data: CardBase = _cards_queued_for_add[0] + _cards_queued_for_add.remove_at(0) + + _create_card_in_world(card_data) + + # Wait for a timer to expire before adding another card + _draw_timer = get_tree().create_timer(card_draw_time) + await _draw_timer.timeout + _draw_timer = null + + if _cards_queued_for_add.size() > 0: + _handle_card_draw_queue() + else: + on_cards_finished_dealing.emit() + + func _create_card_in_world(card_data: CardBase) -> void: - var card_instance: CardWorld = card_scene.instantiate() - add_child(card_instance) - cards_in_hand.append(card_instance) + var card: CardWorld = card_scene.instantiate() + add_child(card) + cards_in_hand.append(card) - card_instance.init_card(card_data) + card.init_card(card_data) - _bind_card_input(card_instance) + # set starting position of the card to the draw pile, so it visually shows it dealing from there + if draw_pile_ui: + card.global_position = draw_pile_ui.global_position + # force an update of the card positions so all card positions are up to date with this new card + _update_card_positions() + + var card_movement: CardMovementComponent = card.get_card_movement_component() + card_movement.set_movement_state(Enums.CardMovementState.MOVING_TO_HAND) + + # Wait for card to finish moving, then bind input + card_movement.on_movement_state_update.connect(_on_card_change_state.bind(card)) + + +func _on_card_change_state(new_state: Enums.CardMovementState, card: CardWorld) -> void: + # once we enter IN_HAND state, bind input + if new_state == Enums.CardMovementState.IN_HAND: + _bind_card_input(card) + + # unbind movement signal so it doesn't keep firing for every state + card.get_card_movement_component().on_movement_state_update.disconnect(_on_card_change_state) -func _bind_card_input(card: CardWorld): + +func _bind_card_input(card: CardWorld) -> void: # bind mouse events var card_click_handler: ClickHandler = card.get_click_handler() card_click_handler.on_click.connect(_on_card_clicked.bind(card)) @@ -132,23 +242,37 @@ func _bind_card_input(card: CardWorld): card_click_handler.on_unhover.connect(_on_card_unhovered.bind(card)) -# Final place where a card is discarded and removed from the world -func _discard_card_at_index(card_index: int) -> void: - var card: CardWorld = cards_in_hand[card_index] +func _add_to_discard_queue(card: CardWorld) -> void: + _cards_queued_for_discard.append(card) + _handle_discard_queue() + +const FIRST_CARD_TO_DISCARD_INDEX = 0; +# Looping queue that starts the discarding animation for cards. +# NOTE: a card is destroyed when the DISCARDING state is finished. See MoveState_Discarding +# NOTE: when you discard cards, they are removed from your hand and added to the discard pile +# immediately (see _discard_card_at_index). +# This function starts movement states on those cards that were queued and eventually +# destroys them. +func _handle_discard_queue() -> void: + if _discard_timer != null: + return - # add to discard pile - discard_pile.append(card.card_data) + var card: CardWorld = _cards_queued_for_discard[FIRST_CARD_TO_DISCARD_INDEX] + _cards_queued_for_discard.remove_at(FIRST_CARD_TO_DISCARD_INDEX) - # remove from world - cards_in_hand.remove_at(card_index) - card.queue_free() + # Set desired position to the discard pile UI and then set state + var movement: CardMovementComponent = card.get_card_movement_component() + if discard_pile_ui: + movement.state_properties.desired_position = discard_pile_ui.global_position + movement.set_movement_state(Enums.CardMovementState.DISCARDING) - on_card_counts_updated.emit() - - -func _discard_last_card() -> void: - if cards_in_hand.size() > 0: - _discard_card_at_index(cards_in_hand.size() - 1) + # Wait for timer to expire before discarding another card + _discard_timer = get_tree().create_timer(card_discard_time) + await _discard_timer.timeout + _discard_timer = null + + if _cards_queued_for_discard.size() > 0: + _handle_discard_queue() func _on_phase_changed(new_phase: Enums.Phase, _old_phase: Enums.Phase) -> void: @@ -176,53 +300,96 @@ func _on_card_clicked(card: CardWorld) -> void: else: # If we click a card with no card queued, queue it set_queued_card(card) - _focus_card(card, card_queued_offset) - - # Unfocus all other cards - for other_card in cards_in_hand: - if other_card == card: - continue - _unfocus_card(other_card) + + card.get_card_movement_component().set_movement_state(Enums.CardMovementState.QUEUED) + _focus_card(card) func _on_card_hovering(card: CardWorld) -> void: if !is_card_queued(): - _focus_card(card, card_hovered_offset) + card.get_card_movement_component().set_movement_state(Enums.CardMovementState.HOVERED) + _focus_card(card) func _on_card_unhovered(card: CardWorld) -> void: if !is_card_queued(): + card.get_card_movement_component().set_movement_state(Enums.CardMovementState.IN_HAND) _unfocus_card(card) -func _focus_card(card: CardWorld, offset: float) -> void: - card.get_lerp_component().desired_position.y = -offset - card.z_index = 1 +func _focus_card(card: CardWorld) -> void: + _focused_card = card + + # children at the top of the hierarchy will render in front + move_child(card, get_child_count()) func _unfocus_card(card: CardWorld) -> void: - card.get_lerp_component().desired_position.y = 0 - card.z_index = 0 + _focused_card = null + + # move back to original place in the hierarchy + var card_index: int = cards_in_hand.find(card) + move_child(card, card_index) func _update_card_positions() -> void: - if cards_in_hand.size() <= 0: + var amount_of_cards: int = cards_in_hand.size() + + if amount_of_cards <= 0: return - var viewport_width: float = get_viewport_rect().size.x - var viewport_height: float = get_viewport_rect().size.y + # set hand width to the max of all our card separations + var per_card_separation: float = max_card_separation + var current_hand_width: float = 0 + if amount_of_cards > 1: + current_hand_width = per_card_separation * (amount_of_cards - 1) - # set container to bottom center of screen (doing every frame encase the viewport size changes) - position.x = viewport_width / 2 - position.x -= total_hand_width / 2 - position.y = viewport_height + # if our hand width is over the set maximum, reduce down the width between cards + # only reduce down to the min_card_separation + if current_hand_width > max_hand_width: + var amount_hand_over_max: float = current_hand_width - max_hand_width + var new_per_card_separation_delta: float = amount_hand_over_max / amount_of_cards + + per_card_separation -= new_per_card_separation_delta + per_card_separation = maxf(per_card_separation, min_card_separation) + + current_hand_width = per_card_separation * (amount_of_cards - 1) - # set spacing of each card - var per_card_width: float = 0 - if cards_in_hand.size() > 1: - per_card_width = total_hand_width / (cards_in_hand.size() - 1) - for card_index: int in cards_in_hand.size(): + # start setting positions for each card + for card_index: int in amount_of_cards: var card: CardWorld = cards_in_hand[card_index] - var card_x: float = per_card_width * card_index - - card.get_lerp_component().desired_position.x = card_x + var movement_component: CardMovementComponent = card.get_card_movement_component() + var move_state: Enums.CardMovementState = movement_component.current_move_state + + var card_x: float = per_card_separation * card_index + var card_y: float = 0.0 + + # center cards in the hand + card_x -= current_hand_width / 2.0 + + # if we are focusing a card, scoot other cards out of the way + # we move closer cards further away, which results in a sort of "accordion" effect + if _focused_card != null: + var focused_card_index: int = cards_in_hand.find(_focused_card) + if card_index != focused_card_index: + var scaled_hover_offset: float = hover_offset_max + var index_delta: float = card_index - focused_card_index + scaled_hover_offset = scaled_hover_offset / index_delta + card_x += scaled_hover_offset + + # scale card's x-value to a range of [-1, 1] from range of [0, viewport width] + # this is so we can use the x-value more easily for our positioning equations below + var viewport_width: float = get_viewport_rect().size.x + var card_x_scaled: float = Helpers.convert_from_range(card.global_position.x, 0.0, viewport_width, -1.0, 1.0) + + # curve the cards in hand by finding the y value on a parabola + # y = ax^2 - a where a = max_hand_offset_y and x = x position scaled to a range of -1 to 1 + card_y = (pow(card_x_scaled, 2.0) * max_hand_offset_y) - max_hand_offset_y + + # rotate cards on a cubic curve + # y = ax^3 where a = max_rotation and x = x position scaled to a range of -1 to 1 + var rotation_amount: float = pow(card_x_scaled, 3) * max_rotation + + # set position and rotation + movement_component.state_properties.desired_position = Vector2(card_x, card_y) + movement_component.state_properties.desired_rotation = rotation_amount diff --git a/Cards/CardContainer.tscn b/Cards/CardContainer.tscn index b91620cb..15a24e75 100644 --- a/Cards/CardContainer.tscn +++ b/Cards/CardContainer.tscn @@ -9,7 +9,9 @@ [ext_resource type="Resource" uid="uid://d12g33rc6c3u5" path="res://Cards/Resource/Card_Heal.tres" id="7_6wjqi"] [ext_resource type="Resource" uid="uid://ctx8jsvac84so" path="res://Cards/Resource/Card_Poison.tres" id="8_gbk7f"] -[node name="CardContainer" type="Node2D"] +[node name="CardContainer" type="Control"] +layout_mode = 3 +anchors_preset = 0 script = ExtResource("1_8wctf") card_scene = ExtResource("2_ytmtr") total_hand_width = 500.0 diff --git a/Cards/CardMovementComponent.gd b/Cards/CardMovementComponent.gd new file mode 100644 index 00000000..6baef907 --- /dev/null +++ b/Cards/CardMovementComponent.gd @@ -0,0 +1,84 @@ +extends Node +class_name CardMovementComponent +## Holds a mapping of CardMovementState and dispatches state events to them. +## +## To make a new state, derive from CardMovementState. Then add it to the mapping via _state_mapping + + +signal on_movement_state_update(new_state: Enums.CardMovementState) + +var current_move_state: Enums.CardMovementState = Enums.CardMovementState.NONE +# This is meant to be updated by CardContainer. This property Resource is sent to the state when +# it is initialized, so it will update in the state whenever it is updated in here. +var state_properties: CardStateProperties = CardStateProperties.new() + +# Map each enum to a CardMovementState Resource +var _state_mapping: Dictionary = { + Enums.CardMovementState.NONE: null, + Enums.CardMovementState.MOVING_TO_HAND: MoveState_MovingToHand.new(), + Enums.CardMovementState.IN_HAND: MoveState_InHand.new(), + Enums.CardMovementState.DISCARDING: MoveState_Discarding.new(), + Enums.CardMovementState.HOVERED: MoveState_Hovered.new(), + Enums.CardMovementState.QUEUED: MoveState_Queued.new(), +} + + +func _ready() -> void: + # init state with the parent. Parent should always be a CardWorld + assert(get_parent() is CardWorld, "Please attach this to a CardWorld!") + state_properties.card = get_parent() + + # bind exit state events + for state: Enums.CardMovementState in _state_mapping: + if _state_not_null(state): + _state_mapping[state].trigger_exit_state.connect(_on_state_triggered_exit) + + set_movement_state(current_move_state) + + +func _process(delta: float) -> void: + _on_state_process(current_move_state) + + +func set_movement_state(new_state: Enums.CardMovementState) -> void: + if new_state == current_move_state: + return + + # Check if we can transition from the current state. Exit if not. + if _state_not_null(current_move_state): + if not _state_mapping[current_move_state].can_transition_from(new_state): + return + + _on_state_exit(current_move_state) + + current_move_state = new_state + + _on_state_enter(current_move_state) + + on_movement_state_update.emit(current_move_state) + + +func _on_state_enter(state: Enums.CardMovementState) -> void: + if _state_not_null(state): + # Send the properties to the state and start it + _state_mapping[state].init_state(state_properties) + _state_mapping[state].on_state_enter() + + +func _on_state_process(state: Enums.CardMovementState) -> void: + if _state_not_null(state): + var delta: float = get_process_delta_time() + _state_mapping[state].on_state_process(delta) + + +func _on_state_exit(state: Enums.CardMovementState) -> void: + if _state_not_null(state): + _state_mapping[state].on_state_exit() + + +func _state_not_null(state: Enums.CardMovementState) -> bool: + return _state_mapping[state] != null + + +func _on_state_triggered_exit(state: Enums.CardMovementState) -> void: + set_movement_state(state) diff --git a/Cards/CardWorld.gd b/Cards/CardWorld.gd index ef92a23b..e2b3ec9f 100644 --- a/Cards/CardWorld.gd +++ b/Cards/CardWorld.gd @@ -1,4 +1,4 @@ -extends Node2D +extends Control class_name CardWorld ## Card object that exists in the world. Not to be confused with CardBase, ## which holds a card's data. @@ -23,8 +23,8 @@ func _on_phase_changed(new_phase: Enums.Phase, _old_phase: Enums.Phase) -> void: get_click_handler().set_interactable(new_phase == Enums.Phase.PLAYER_ATTACKING) -func get_lerp_component() -> LerpComponent: - return Helpers.get_first_child_node_of_type(self, LerpComponent) as LerpComponent +func get_card_movement_component() -> CardMovementComponent: + return Helpers.get_first_child_node_of_type(self, CardMovementComponent) as CardMovementComponent func get_click_handler() -> ClickHandler: diff --git a/Cards/Movement State/CardMovementState.gd b/Cards/Movement State/CardMovementState.gd new file mode 100644 index 00000000..8efab0c3 --- /dev/null +++ b/Cards/Movement State/CardMovementState.gd @@ -0,0 +1,37 @@ +extends Resource +class_name CardMovementState +## Base movement state for a card. Override to create a new movement state. +## +## Each movement state has on_state_enter, on_state_process, and on_state_exit +## on_state_enter - fired when the state first starts. Good for triggering a one time tween. +## on_state_process - fired every frame that the state is active. Good for lerping. +## on_state_exit - fired when the state is finished. +## can_transition_from - If false, then this state will not allow other states +## to trigger from set_movement_state in CardMovementComponent. + + +# This is meant to be called from child states if you wish to enter another state from within +# that state. +signal trigger_exit_state(next_state: Enums.CardMovementState) + +var _state: CardStateProperties + + +func init_state(in_state_properties: CardStateProperties) -> void: + _state = in_state_properties + + +func on_state_enter() -> void: + pass + + +func on_state_process(delta: float) -> void: + pass + + +func on_state_exit() -> void: + pass + + +func can_transition_from(new_state: Enums.CardMovementState) -> bool: + return true diff --git a/Cards/Movement State/CardStateProperties.gd b/Cards/Movement State/CardStateProperties.gd new file mode 100644 index 00000000..da7dd2fa --- /dev/null +++ b/Cards/Movement State/CardStateProperties.gd @@ -0,0 +1,8 @@ +extends Resource +class_name CardStateProperties +## Properties used by CardMovementStates + + +var card: CardWorld = null +var desired_position: Vector2 = Vector2.ZERO +var desired_rotation: float = 0.0 diff --git a/Cards/Movement State/MoveState_Discarding.gd b/Cards/Movement State/MoveState_Discarding.gd new file mode 100644 index 00000000..c8a3aec1 --- /dev/null +++ b/Cards/Movement State/MoveState_Discarding.gd @@ -0,0 +1,42 @@ +extends CardMovementState +class_name MoveState_Discarding +## Discarding state. Moves to the discard pile and scales down to 0 + + +const MOVE_SPEED: float = 10.0 +const EASE_TIME: float = 0.3 +const EASE_TYPE: Tween.EaseType = Tween.EASE_OUT +const TRANS_TYPE: Tween.TransitionType = Tween.TRANS_CUBIC + + +# @Override +func on_state_enter() -> void: + # Move to discard pile + # Destroy when move is finished + _state.card.create_tween()\ + .tween_property(_state.card, EasingConstants.GLOBAL_POSITION_PROPERTY, _state.desired_position, EASE_TIME)\ + .set_ease(EASE_TYPE)\ + .set_trans(TRANS_TYPE)\ + .finished.connect(_on_easing_finished) + + # Scale down to zero + _state.card.create_tween()\ + .tween_property(_state.card, EasingConstants.SCALE_PROPERTY, Vector2.ZERO, EASE_TIME)\ + .set_ease(EASE_TYPE)\ + .set_trans(TRANS_TYPE) + + +# @Override +func on_state_process(delta: float) -> void: + # Ease rotation + _state.card.rotation_degrees = lerpf(_state.card.rotation_degrees, 0.0, delta * MOVE_SPEED) + + +# @Override +func can_transition_from(new_state: Enums.CardMovementState) -> bool: + # Once you discard, you can't exit this state + return false + + +func _on_easing_finished() -> void: + _state.card.queue_free() diff --git a/Cards/Movement State/MoveState_Hovered.gd b/Cards/Movement State/MoveState_Hovered.gd new file mode 100644 index 00000000..3fdb6890 --- /dev/null +++ b/Cards/Movement State/MoveState_Hovered.gd @@ -0,0 +1,32 @@ +extends CardMovementState +class_name MoveState_Hovered +## Hovering state. Moves to desired position with a y-offset + + +const MOVE_SPEED: float = 10.0 +const EASE_TIME: float = 0.5 +const EASE_TYPE: Tween.EaseType = Tween.EASE_OUT +const TRANS_TYPE: Tween.TransitionType = Tween.TRANS_CUBIC +const HOVER_OFFSET: float = -100 +const SCALE_AMOUNT: Vector2 = Vector2(1.3, 1.3) + + +# @Override +func on_state_enter() -> void: + # Ease to SCALE_AMOUNT + _state.card.create_tween()\ + .tween_property(_state.card, EasingConstants.SCALE_PROPERTY, SCALE_AMOUNT, EASE_TIME)\ + .set_ease(EASE_TYPE)\ + .set_trans(TRANS_TYPE) + + +# @Override +func on_state_process(delta: float) -> void: + # Ease to desired position with offset + var offset_desired_position: Vector2 = _state.desired_position + offset_desired_position.y += HOVER_OFFSET + + _state.card.position = _state.card.position.lerp(offset_desired_position, delta * MOVE_SPEED) + + # Ease rotation + _state.card.rotation_degrees = lerpf(_state.card.rotation_degrees, 0.0, delta * MOVE_SPEED) diff --git a/Cards/Movement State/MoveState_InHand.gd b/Cards/Movement State/MoveState_InHand.gd new file mode 100644 index 00000000..c5d1752a --- /dev/null +++ b/Cards/Movement State/MoveState_InHand.gd @@ -0,0 +1,27 @@ +extends CardMovementState +class_name MoveState_InHand +## InHand state. Moves to desired position + + +const MOVE_SPEED: float = 10.0 +const EASE_TIME: float = 0.5 +const EASE_TYPE: Tween.EaseType = Tween.EASE_OUT +const TRANS_TYPE: Tween.TransitionType = Tween.TRANS_CUBIC + + +# @Override +func on_state_enter() -> void: + # Ease scale to one. Resets any scale changes that happened in other states (HOVERED, QUEUED) + _state.card.create_tween()\ + .tween_property(_state.card, EasingConstants.SCALE_PROPERTY, Vector2.ONE, EASE_TIME)\ + .set_ease(EASE_TYPE)\ + .set_trans(TRANS_TYPE) + + +# @Override +func on_state_process(delta: float) -> void: + # Ease to desired position in the hand + _state.card.position = _state.card.position.lerp(_state.desired_position, delta * MOVE_SPEED) + + # Ease rotation + _state.card.rotation_degrees = lerpf(_state.card.rotation_degrees, _state.desired_rotation, delta * MOVE_SPEED) diff --git a/Cards/Movement State/MoveState_MovingToHand.gd b/Cards/Movement State/MoveState_MovingToHand.gd new file mode 100644 index 00000000..876d8af9 --- /dev/null +++ b/Cards/Movement State/MoveState_MovingToHand.gd @@ -0,0 +1,35 @@ +extends CardMovementState +class_name MoveState_MovingToHand + ## MovingToHand state. Scales to 1 from 0 and sets to desired position + + +const MOVE_SPEED: float = 15.0 +const EASE_TIME: float = 0.5 +const EASE_TYPE: Tween.EaseType = Tween.EASE_OUT +const TRANS_TYPE: Tween.TransitionType = Tween.TRANS_CUBIC + + +# @Override +func on_state_enter() -> void: + # Scale up from zero + # When finished, set state to IN_HAND + _state.card.create_tween()\ + .tween_property(_state.card, EasingConstants.SCALE_PROPERTY, Vector2.ONE, EASE_TIME)\ + .set_ease(EASE_TYPE)\ + .set_trans(TRANS_TYPE)\ + .from(Vector2.ZERO)\ + .finished.connect(_on_easing_finished) + + +# @Override +func on_state_process(delta: float) -> void: + # Ease to desired position in the hand + _state.card.position = _state.card.position.lerp(_state.desired_position, delta * MOVE_SPEED) + + # Ease rotation + _state.card.rotation_degrees = lerpf(_state.card.rotation_degrees, _state.desired_rotation, delta * MOVE_SPEED) + + +func _on_easing_finished() -> void: + # Fire off this signal, which should exit this state and enter the IN_HAND state + trigger_exit_state.emit(Enums.CardMovementState.IN_HAND) diff --git a/Cards/Movement State/MoveState_Queued.gd b/Cards/Movement State/MoveState_Queued.gd new file mode 100644 index 00000000..f63dc130 --- /dev/null +++ b/Cards/Movement State/MoveState_Queued.gd @@ -0,0 +1,32 @@ +extends CardMovementState +class_name MoveState_Queued +## Queued state. Moves to desired position with a y-offset + + +const MOVE_SPEED: float = 10.0 +const EASE_TIME: float = 0.5 +const EASE_TYPE: Tween.EaseType = Tween.EASE_OUT +const TRANS_TYPE: Tween.TransitionType = Tween.TRANS_CUBIC +const QUEUE_OFFSET: float = -150 +const SCALE_AMOUNT: Vector2 = Vector2(1.3, 1.3) + + +# @Override +func on_state_enter() -> void: + # Scale to SCALE_AMOUNT + _state.card.create_tween()\ + .tween_property(_state.card, EasingConstants.SCALE_PROPERTY, SCALE_AMOUNT, EASE_TIME)\ + .set_ease(EASE_TYPE)\ + .set_trans(TRANS_TYPE) + + +# @Override +func on_state_process(delta: float) -> void: + # Ease position with an offset + var offset_desired_position: Vector2 = _state.desired_position + offset_desired_position.y += QUEUE_OFFSET + + _state.card.position = _state.card.position.lerp(offset_desired_position, delta * MOVE_SPEED) + + # Ease rotation + _state.card.rotation_degrees = lerpf(_state.card.rotation_degrees, 0.0, delta * MOVE_SPEED) diff --git a/Cards/Resource/Card_Damage.tres b/Cards/Resource/Card_Damage.tres index b34bf796..2930342e 100644 --- a/Cards/Resource/Card_Damage.tres +++ b/Cards/Resource/Card_Damage.tres @@ -14,6 +14,14 @@ value = 3 [resource] script = ExtResource("1_44g0o") +damage_to_apply_to_target = 0.0 +damage_to_apply_to_caster = 0.0 +status_to_apply_to_target = Array[Resource("res://Status/StatusBase.gd")]([]) +status_to_apply_to_caster = Array[Resource("res://Status/StatusBase.gd")]([]) +affect_all_targets = false +affect_all_casters = false +amount_of_cards_to_draw = 0 +amount_of_cards_to_discard = 0 application_type = 1 card_title = "Damage" card_description = "Deal 3 damage" diff --git a/Cards/Resource/Card_DamageHealth.tres b/Cards/Resource/Card_DamageHealth.tres index 22a3068c..eb7e601f 100644 --- a/Cards/Resource/Card_DamageHealth.tres +++ b/Cards/Resource/Card_DamageHealth.tres @@ -14,6 +14,14 @@ value = 0 [resource] script = ExtResource("1_ighm7") +damage_to_apply_to_target = 0.0 +damage_to_apply_to_caster = 0.0 +status_to_apply_to_target = Array[Resource("res://Status/StatusBase.gd")]([]) +status_to_apply_to_caster = Array[Resource("res://Status/StatusBase.gd")]([]) +affect_all_targets = false +affect_all_casters = false +amount_of_cards_to_draw = 0 +amount_of_cards_to_discard = 0 application_type = 1 card_title = "Damage Health" card_description = "Deal damage equal to health lost." diff --git a/Cards/Resource/Card_DrawCards.tres b/Cards/Resource/Card_DrawCards.tres index 79df45bb..ea874bc6 100644 --- a/Cards/Resource/Card_DrawCards.tres +++ b/Cards/Resource/Card_DrawCards.tres @@ -14,6 +14,14 @@ value = 2 [resource] script = ExtResource("1_bboy2") +damage_to_apply_to_target = 0.0 +damage_to_apply_to_caster = 0.0 +status_to_apply_to_target = Array[Resource("res://Status/StatusBase.gd")]([]) +status_to_apply_to_caster = Array[Resource("res://Status/StatusBase.gd")]([]) +affect_all_targets = false +affect_all_casters = false +amount_of_cards_to_draw = 0 +amount_of_cards_to_discard = 0 application_type = 0 card_title = "Draw 2" card_description = "Draw 2 cards" diff --git a/Cards/Resource/Card_Poison.tres b/Cards/Resource/Card_Poison.tres index fd6bc9e0..c09dc36f 100644 --- a/Cards/Resource/Card_Poison.tres +++ b/Cards/Resource/Card_Poison.tres @@ -14,6 +14,14 @@ value = 3 [resource] script = ExtResource("1_u6k7h") +damage_to_apply_to_target = 0.0 +damage_to_apply_to_caster = 0.0 +status_to_apply_to_target = Array[Resource("res://Status/StatusBase.gd")]([]) +status_to_apply_to_caster = Array[Resource("res://Status/StatusBase.gd")]([]) +affect_all_targets = false +affect_all_casters = false +amount_of_cards_to_draw = 0 +amount_of_cards_to_discard = 0 application_type = 1 card_title = "Poison" card_description = "Apply 3 poison" diff --git a/Cards/Resource/Card_damage_and_poison.tres b/Cards/Resource/Card_damage_and_poison.tres index 3de8c4c7..f147920d 100644 --- a/Cards/Resource/Card_damage_and_poison.tres +++ b/Cards/Resource/Card_damage_and_poison.tres @@ -23,6 +23,14 @@ value = 2 [resource] script = ExtResource("1_h8l2w") +damage_to_apply_to_target = 0.0 +damage_to_apply_to_caster = 0.0 +status_to_apply_to_target = Array[Resource("res://Status/StatusBase.gd")]([]) +status_to_apply_to_caster = Array[Resource("res://Status/StatusBase.gd")]([]) +affect_all_targets = false +affect_all_casters = false +amount_of_cards_to_draw = 0 +amount_of_cards_to_discard = 0 application_type = 1 card_title = "Damage and poison" card_description = "Deals 1 damage and applies 2 poison" diff --git a/Core/Battler.gd b/Core/Battler.gd index 2877d676..54778850 100644 --- a/Core/Battler.gd +++ b/Core/Battler.gd @@ -72,7 +72,7 @@ func _on_enemy_start_turn() -> void: assert(can_attack == true, "Enemy failed to attack.") if can_attack: - enemy_attack.on_card_play(PlayerManager.player) + enemy_attack.on_card_play(enemy, PlayerManager.player) # TODO: temporary delay so we can see the draw pile and discard pile working await get_tree().create_timer(enemy_attack_time).timeout @@ -99,4 +99,5 @@ func _try_player_play_card_on_entity(entity: Entity) -> void: # remove queued card, then play the card # This is so the queued card doesn't have any influence over our hand count CardManager.card_container.remove_queued_card() - queued_card_data.on_card_play(entity) + CardManager.card_container.set_active_card(queued_card_data) + queued_card_data.on_card_play(PlayerManager.player, entity) diff --git a/Core/Enums.gd b/Core/Enums.gd index 36a6e48d..51994bf6 100644 --- a/Core/Enums.gd +++ b/Core/Enums.gd @@ -23,3 +23,13 @@ enum ApplicationType ENEMY_ONLY, ALL, } + +enum CardMovementState +{ + NONE, + MOVING_TO_HAND, + IN_HAND, + DISCARDING, + HOVERED, + QUEUED, +} diff --git a/Helpers/EasingConstants.gd b/Helpers/EasingConstants.gd new file mode 100644 index 00000000..1feb885f --- /dev/null +++ b/Helpers/EasingConstants.gd @@ -0,0 +1,6 @@ +extends Node +class_name EasingConstants + +static var POSITION_PROPERTY: String = "position" +static var GLOBAL_POSITION_PROPERTY: String = "global_position" +static var SCALE_PROPERTY: String = "scale" diff --git a/Helpers/StaticHelpers.gd b/Helpers/StaticHelpers.gd index 6a5d3501..eeee6518 100644 --- a/Helpers/StaticHelpers.gd +++ b/Helpers/StaticHelpers.gd @@ -19,3 +19,16 @@ static func find_first_from_array_by_type(array: Array[Variant], type: Variant) static func get_random_array_index(array: Array[Variant]) -> int: return randi_range(0, array.size() - 1) + + +static func convert_from_range(value: float, from_min: float, from_max: float, to_min: float, to_max: float) -> float: + var from_range: float = from_max - from_min + var to_value: float = 0.0 + + if from_range == 0.0: + return to_min + else: + var to_range: float = to_max - to_min + to_value = (((value - from_min) * to_range) / from_range) + to_min + + return to_value diff --git a/Managers/CardManager.gd b/Managers/CardManager.gd index f52eedee..82462a2b 100644 --- a/Managers/CardManager.gd +++ b/Managers/CardManager.gd @@ -3,6 +3,7 @@ extends Node signal on_card_container_initialized +signal on_card_action_finished(card: CardWorld) var card_container: CardContainer = null diff --git a/Tests/test_cards.gd b/Tests/test_cards.gd index ff000b73..00adae40 100644 --- a/Tests/test_cards.gd +++ b/Tests/test_cards.gd @@ -2,27 +2,24 @@ extends GutTest ## Tests for cards -var _battler_scene: PackedScene = load("res://Core/Battler.tscn") var _card_container_scene: PackedScene = load("res://Cards/CardContainer.tscn") -var _battler: Battler = null var _card_container: CardContainer = null func before_each(): - _battler = _battler_scene.instantiate() _card_container = _card_container_scene.instantiate() # fill deck with 50 default cards _card_container.default_deck.resize(50) _card_container.max_hand_size = 10 - _card_container.starting_hand_size = 5 + _card_container.starting_hand_size = 0 + _card_container.card_draw_time = .05 # assign cards names of Card1-Card50 for card_index: int in _card_container.default_deck.size(): _card_container.default_deck[card_index] = CardBase.new() _card_container.default_deck[card_index].card_title = "Card" + str(card_index + 1) - get_tree().root.add_child(_battler) get_tree().root.add_child(_card_container) # set draw pile to default deck. This removes any randomness from shuffling on _ready() @@ -31,12 +28,12 @@ func before_each(): func after_each(): - _battler.queue_free() _card_container.queue_free() func test_draw_cards(): _card_container.draw_cards(5) + await _card_container.on_cards_finished_dealing assert_eq(_card_container.cards_in_hand.size(), 5) assert_eq(_card_container.draw_pile.size(), 45) @@ -46,6 +43,7 @@ func test_draw_cards(): func test_draw_cards_2(): _card_container.draw_cards(5) _card_container.draw_cards(5) + await _card_container.on_cards_finished_dealing assert_eq(_card_container.cards_in_hand.size(), 10) assert_eq(_card_container.draw_pile.size(), 40) @@ -54,6 +52,7 @@ func test_draw_cards_2(): func test_discard_cards(): _card_container.draw_cards(5) + await _card_container.on_cards_finished_dealing _card_container.discard_random_card(1) assert_eq(_card_container.cards_in_hand.size(), 4) @@ -63,6 +62,7 @@ func test_discard_cards(): func test_discard_cards_2(): _card_container.draw_cards(5) + await _card_container.on_cards_finished_dealing _card_container.discard_random_card(1) _card_container.discard_random_card(1) @@ -72,6 +72,7 @@ func test_discard_cards_2(): func test_discard_all(): _card_container.draw_cards(5) + await _card_container.on_cards_finished_dealing _card_container.discard_all_cards() assert_eq(_card_container.cards_in_hand.size(), 0) @@ -80,7 +81,9 @@ func test_discard_all(): func test_deal_starting_hand(): + _card_container.starting_hand_size = 5 _card_container.deal_to_starting_hand_size() + await _card_container.on_cards_finished_dealing assert_eq(_card_container.cards_in_hand.size(), 5) assert_eq(_card_container.draw_pile.size(), 45) @@ -89,6 +92,7 @@ func test_deal_starting_hand(): func test_draw_to_max(): _card_container.draw_cards(50) + await _card_container.on_cards_finished_dealing assert_eq(_card_container.cards_in_hand.size(), 10) assert_eq(_card_container.draw_pile.size(), 40) @@ -97,6 +101,7 @@ func test_draw_to_max(): func test_discard_specific(): _card_container.draw_cards(5) + await _card_container.on_cards_finished_dealing var discard_card: CardWorld = _card_container.cards_in_hand[2] diff --git a/Tests/test_health.gd b/Tests/test_health.gd index 3ad1d80e..e72e85eb 100644 --- a/Tests/test_health.gd +++ b/Tests/test_health.gd @@ -11,9 +11,6 @@ var _battler: Battler = null var _player_health_component: HealthComponent = null var _enemy_health_component: HealthComponent = null -func before_all(): - await PhaseManager.on_game_start - func before_each(): _player = _player_scene.instantiate() diff --git a/UI/EndTurnButton.gd b/UI/EndTurnButton.gd index 898e4085..ebe7ef3c 100644 --- a/UI/EndTurnButton.gd +++ b/UI/EndTurnButton.gd @@ -2,13 +2,12 @@ extends Button ## Ends the player's turn when clicked. -func _ready() -> void: - PhaseManager.on_phase_changed.connect(_on_phase_changed) - - -func _on_phase_changed(new_phase: Enums.Phase, _old_phase: Enums.Phase) -> void: +func _process(delta: float) -> void: # disable button during enemy attack phase - disabled = new_phase == Enums.Phase.ENEMY_ATTACKING + disabled = Enums.Phase.ENEMY_ATTACKING == PhaseManager.current_phase\ + or (Enums.Phase.PLAYER_ATTACKING and (CardManager.card_container.are_cards_dealing()\ + or CardManager.card_container.is_card_queued()\ + or CardManager.card_container.are_cards_active())) func _on_pressed() -> void: