diff --git a/CHANGELOG.md b/CHANGELOG.md index c8ac2610..d2c9e2ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,25 @@ # Changelog -## 1.13 (Ongoing) +## 1.13 -## Tweaks +### Tweaks * Right click in the Card Viewer filter buttons, will not press that button and unpress all the others -* Can now filter also on properties not mention in CardConfig -* Added button which presses all filter buttons. +* Can now filter also on properties not defined in CardConfig * Improved performance when showing/hiding the viewport focus * Improved performance when loading the card viewer in grid mode. +### New Features + +* In Cardviewer, added button which presses all filter buttons. + + +#### ScriptingEngine + +* Added new `nested_script` task allows to execute more tasks recursively. It allows for infinite amount of nests, which can create pretty complex combinations of scripts. +* Added new selection window functionality which creates an dialogue window to the player to select an amount of cards from those selected as subjects. Typically should be combined with boardseek, tutor or index subjects. + Controlled by the following new keys: `KEY_NEEDS_SELECTION`, `KEY_SELECTION_COUNT`, `KEY_SELECTION_TYPE`, `KEY_SELECTION_OPTIONAL`, `KEY_SELECTION_IGNORE_SELF` + ## 1.12 ### Important for Upgrades diff --git a/README.md b/README.md index fd9b8c2e..c26ad13d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# Godot Card Game Framework [1.12](CHANGELOG.md) +# Godot Card Game Framework [1.13](CHANGELOG.md) ![Godot Card Game Framework preview image](preview.png "Godot Card Game Framework preview image") diff --git a/project.godot b/project.godot index 8ed1ccec..52e6b0b6 100644 --- a/project.godot +++ b/project.godot @@ -269,6 +269,11 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://src/core/ScriptingEngine.gd" }, { +"base": "AcceptDialog", +"class": "SelectionWindow", +"language": "GDScript", +"path": "res://src/core/SelectionWindow.gd" +}, { "base": "Line2D", "class": "TargetingArrow", "language": "GDScript", @@ -352,6 +357,7 @@ _global_script_class_icons={ "ScriptProperties": "", "ScriptTask": "", "ScriptingEngine": "", +"SelectionWindow": "", "TargetingArrow": "", "Token": "", "TokenDrawer": "", diff --git a/src/core/CardTemplate.gd b/src/core/CardTemplate.gd index cde10877..bf02b814 100644 --- a/src/core/CardTemplate.gd +++ b/src/core/CardTemplate.gd @@ -1576,10 +1576,10 @@ func get_focus() -> bool: # Returns the Card's index position among other card objects func get_my_card_index() -> int: - if "CardPopUpSlot" in get_parent().name: - return(0) + if get_parent().has_method('get_card_index'): + return(get_parent().get_card_index(self)) else: - return get_parent().get_card_index(self) + return(0) # Changes the hosted Control nodes filters diff --git a/src/core/CardViewer/CVGridCardObject.gd b/src/core/CardViewer/CVGridCardObject.gd index 07689e46..c307ae09 100644 --- a/src/core/CardViewer/CVGridCardObject.gd +++ b/src/core/CardViewer/CVGridCardObject.gd @@ -12,8 +12,13 @@ func _ready() -> void: rect_min_size = CFConst.CARD_SIZE -func setup(card_name) -> Card: - display_card = cfc.instance_card(card_name) +func setup(card) -> Card: + if typeof(card) == TYPE_STRING: + display_card = cfc.instance_card(card) + else: + display_card = card + display_card.position = Vector2(0,0) + display_card.scale = Vector2(1,1) add_child(display_card) display_card.resize_recursively(display_card._control, CFConst.THUMBNAIL_SCALE) display_card.card_front.scale_to(CFConst.THUMBNAIL_SCALE) diff --git a/src/core/MousePointer.gd b/src/core/MousePointer.gd index 55d4af7d..f07b2470 100644 --- a/src/core/MousePointer.gd +++ b/src/core/MousePointer.gd @@ -23,7 +23,7 @@ var current_focused_card : Card = null # Instead we populate according to signals,which are more immediate var overlaps := [] # When set to false, prevents the player from disable interacting with the game. -var is_disabled := false +var is_disabled := false setget set_disabled # Called when the node enters the scene tree for the first time. @@ -122,6 +122,11 @@ func enable() -> void: func forget_focus() -> void: current_focused_card = null +func set_disabled(value) -> void: + forget_focus() + overlaps.clear() + is_disabled = value + # Parses all collided objects and figures out which card, if any, to focus on and # also while a card is being dragged, figures out which potential area to highlight # for the drop effect. diff --git a/src/core/OverridableUtils.gd b/src/core/OverridableUtils.gd index 95527bd4..3bf9ea92 100644 --- a/src/core/OverridableUtils.gd +++ b/src/core/OverridableUtils.gd @@ -6,6 +6,8 @@ class_name OVUtils extends Reference +const _CARD_SELECT_SCENE_FILE = CFConst.PATH_CORE + "SelectionWindow.tscn" +const _CARD_SELECT_SCENE = preload(_CARD_SELECT_SCENE_FILE) # Populates the info panels under the card, when it is shown in the # viewport focus or deckbuilder @@ -27,3 +29,24 @@ func populate_info_panels(card: Card, focus_info: DetailPanels) -> void: func get_subjects(_subject_request, _stored_integer : int = 0) -> Array: return([]) + +func select_card( + card_list: Array, + selection_count: int, + selection_type: String, + selection_optional: bool): + cfc.game_paused = true + var selected_cards + var selection = _CARD_SELECT_SCENE.instance() + cfc.NMAP.board.add_child(selection) + selection.initiate_selection(card_list,selection_count,selection_type,selection_optional) + # We have to wait until the player has finished selecting their cards + yield(selection,"confirmed") + if selection.is_cancelled: + selected_cards = false + else: + selected_cards = selection.selected_cards + # Garbage cleanup + selection.queue_free() + cfc.game_paused = false + return(selected_cards) diff --git a/src/core/ScriptObject.gd b/src/core/ScriptObject.gd index b6ec4d5a..17b63263 100644 --- a/src/core/ScriptObject.gd +++ b/src/core/ScriptObject.gd @@ -101,6 +101,35 @@ func _find_subjects(prev_subjects := [], stored_integer := 0) -> Array: _: subjects_array = cfc.ov_utils.get_subjects( get_property(SP.KEY_SUBJECT), stored_integer) + if get_property(SP.KEY_NEEDS_SELECTION): + var selection_count = get_property(SP.KEY_SELECTION_COUNT) + var selection_type = get_property(SP.KEY_SELECTION_TYPE) + var selection_optional = get_property(SP.KEY_SELECTION_OPTIONAL) + if get_property(SP.KEY_SELECTION_IGNORE_SELF): + subjects_array.erase(owner) + var select_return = cfc.ov_utils.select_card( + subjects_array, selection_count, selection_type, selection_optional) + # In case the owner card is still focused (say because script was triggered + # on double-click and card was not moved + # Then we need to ensure it's unfocused + # Otherwise its z-index will make it draw on top of the popup. + if owner as Card: + if owner.state in [Card.CardState.FOCUSED_IN_HAND]: + # We also reorganize the whole hand to avoid it getting + # stuck like this. + for c in owner.get_parent().get_all_cards(): + c.interruptTweening() + c.reorganize_self() + if select_return is GDScriptFunctionState: # Still working. + select_return = yield(select_return, "completed") + # If the return is not an array, it means that the selection + # was cancelled (either because there were not enough cards + # or because the player pressed cancel + # in which case we consider the task invalid + if typeof(select_return) == TYPE_ARRAY: + subjects_array = select_return + else: + is_valid = false subjects = subjects_array return(subjects_array) diff --git a/src/core/ScriptProperties.gd b/src/core/ScriptProperties.gd index 92b79f26..28b53669 100644 --- a/src/core/ScriptProperties.gd +++ b/src/core/ScriptProperties.gd @@ -109,6 +109,15 @@ const KEY_SUBJECT_COUNT_V_ALL := "all" const KEY_IS_COST := "is_cost" # Value Type: bool (Default = false). # +# This key is used on a task marked with KEY_IS_COST +# It means that its cost effects will not evn be evaluated if previous costs +# have already failed. +# This is useful when there's more than 1 interactive cost, +# such as targeting or selection boxes +# To prevnent them from popping up even when previous costs have already failed. +const KEY_ABORT_ON_COST_FAILURE := "abort_on_cost_failure" +# Value Type: bool (Default = false). +# # This key is used to mark a task to be executed only if the card costs # cannot be paid. As such, they will never fire,unless the card also has # an "is_cost" task. @@ -440,6 +449,12 @@ const KEY_ALTERATION := "alteration" # Note using a minus-sign '-' in place of a plus-sign will not work as expected. # Use [KEY_IS_INVERTED](#KEY_IS_INVERTED) instead const VALUE_PER := "per_" +# Value Type: Float/Int +# +# Used to multiply per results. +# This allows us to craft scripts like +# "Gain 2 Health per card on the table" or "Gain 1 Health per two cards on the table" +const KEY_MULTIPLIER := "multiplier" # Value Type: String # # This key is typically needed in combination with @@ -674,6 +689,44 @@ const VALUE_COMPARE_WITH_TRIGGER := "compare_with_trigger" # # At the script level, the whole script if cancelled. const KEY_IS_OPTIONAL := "is_optional_" +# Value Type: Bool (default: False) +# +# If true, the script will popup a card selection window, among all the +# valid subjects detected for this script. +const KEY_NEEDS_SELECTION := "needs_selection" +# Value Type: Int (default: 0) +# +# How many cards need to be selected from the selection window +const KEY_SELECTION_COUNT := "selection_count" +# Value Type: String (default: 'min') +# How to evaluate [SELECTION_COUNT](#SELECTION_COUNT) +# before the player is allowed to proceed +# +# * 'min': The minimum amount of cards that need to be selected +# * 'equal': The exact amount of cards that need to be selected +# * 'max': The maximum amount of cards that need to be selected +const KEY_SELECTION_TYPE := "selection_type" +# Value Type: Bool (default: False) +# +# Marks a selection window as optional. This means the player can opt to +# select none of the possible choices. +# In which case, the underlying task will be considered invalid +# and if it is a cost, it will also abort further execution. +const KEY_SELECTION_OPTIONAL := "selection_optional" +# Value Type: Bool (default: False) +# +# Ignores the card executing the script from the selection window +# This is necessary in some instances where the selection encompases the +# scripting card, but this is unwanted. For example because the card +# is supposed to already be in a different pile but this will only +# technically happen as the last task. +const KEY_SELECTION_IGNORE_SELF := "selection_ignore_self" +# Value Type: Array +# +# Initiates a new instance of the scripting engine +# Which runs through the specified task list +# using its own cost calculations +const KEY_NESTED_TASKS := "nested_tasks" #--------------------------------------------------------------------- # Filter Definition Keys # @@ -1051,7 +1104,8 @@ const TRIGGER_V_COUNT_INCREASED := "increased" const TRIGGER_V_COUNT_DECREASED := "decreased" -# Returns the default value any script definition key should have +# For any script key defined in this reference, +# returns the default it should have static func get_default(property: String): var default # for property details, see const definitionts @@ -1061,13 +1115,18 @@ static func get_default(property: String): KEY_IS_INVERTED,\ KEY_SET_TO_MOD,\ KEY_IS_OPTIONAL,\ + KEY_NEEDS_SELECTION,\ + KEY_SELECTION_OPTIONAL,\ KEY_SORT_DESCENDING,\ + KEY_ABORT_ON_COST_FAILURE,\ KEY_STORE_INTEGER: default = false KEY_TRIGGER: default = "any" - KEY_SUBJECT_INDEX: + KEY_SUBJECT_INDEX,KEY_SELECTION_COUNT: default = 0 + KEY_SELECTION_TYPE: + default = "min" KEY_DEST_INDEX: default = -1 KEY_BOARD_POSITION: @@ -1078,7 +1137,8 @@ static func get_default(property: String): default = {} KEY_SUBJECT_COUNT,\ KEY_OBJECT_COUNT,\ - KEY_MODIFICATION: + KEY_MODIFICATION,\ + KEY_MULTIPLIER: default = 1 KEY_GRID_NAME, KEY_SUBJECT: default = "" diff --git a/src/core/ScriptTask.gd b/src/core/ScriptTask.gd index fb27f811..80d8a0be 100644 --- a/src/core/ScriptTask.gd +++ b/src/core/ScriptTask.gd @@ -7,7 +7,7 @@ class_name ScriptTask extends ScriptObject # Stores the details arg passed the signal to use for filtering -var signal_details : Dictionary +var trigger_details : Dictionary # If true if this task has been confirmed to run by the player # Only relevant for optional tasks (see [SP].KEY_IS_OPTIONAL) var is_accepted := true @@ -20,9 +20,10 @@ var is_else := false func _init(owner, script: Dictionary, _trigger_object, - trigger_details).(owner, script, _trigger_object) -> void: + _trigger_details).(owner, script, _trigger_object) -> void: # The function name to be called gets its own var script_name = get_property("name") + trigger_details = _trigger_details is_cost = get_property(SP.KEY_IS_COST) is_else = get_property(SP.KEY_IS_ELSE) if not SP.filter_trigger( diff --git a/src/core/ScriptingEngine.gd b/src/core/ScriptingEngine.gd index 4f50cc9b..e89c4d83 100644 --- a/src/core/ScriptingEngine.gd +++ b/src/core/ScriptingEngine.gd @@ -82,6 +82,12 @@ func execute(_run_type := CFInt.RunType.NORMAL) -> void: "modifier": _retrieve_temp_modifiers(script, "properties") } if not script.is_primed: + # Since the costs are processed serially, we can mark some costs + # to only be paid, if all previous costs have been paid as well. + # This allows us to avoid opening popup windows for cost selection + # when previous costs have not been achieved (e.g. targeting) + if script.get_property(SP.KEY_ABORT_ON_COST_FAILURE) and not can_all_costs_be_paid: + continue # If we have requested to use the previous target, # but the subject_array is empty, we check if # subject available in the next task and try to use that instead. @@ -690,6 +696,46 @@ func execute_scripts(script: ScriptTask) -> int: return(retcode) +# Task for executing nested tasks +# This task will execute internal non-cost cripts accordin to its own +# nested cost instructions. +# Therefore if you set this task as a cost, +# it will modify the board, even if other costs of this script +# could not be paid. +# You can use [SP.KEY_ABORT_ON_COST_FAILURE](SP#KEY_ABORT_ON_COST_FAILURE) +# to control this behaviour better +func nested_script(script: ScriptTask) -> int: + var retcode : int = CFConst.ReturnCode.CHANGED + var nested_task_list: Array = script.get_property(SP.KEY_NESTED_TASKS) + var sceng = cfc.scripting_engine.new( + nested_task_list, + script.owner, + script.trigger_object, + script.trigger_details) + # In case the script involves targetting, we need to wait on further + # execution until targetting has completed + sceng.execute(CFInt.RunType.COST_CHECK) + if not sceng.all_tasks_completed: + yield(sceng,"tasks_completed") + # If the dry-run of the ScriptingEngine returns that all + # costs can be paid, then we proceed with the actual run + if sceng.can_all_costs_be_paid: + sceng.execute() + if not sceng.all_tasks_completed: + yield(sceng,"tasks_completed") + # This will only trigger when costs could not be paid, and will + # execute the "is_else" tasks + elif not sceng.can_all_costs_be_paid: + sceng.execute(CFInt.RunType.ELSE) + # If the nested task had a cost which could not be paid + # we return a failed result. This means that if the nested_script task + # was also marked as a cost itself, then it will block execution of + # further non-cost tasks. + if not sceng.can_all_costs_be_paid: + retcode = CFConst.ReturnCode.FAILED + return(retcode) + + # Initiates a seek through the table to see if there's any cards # which have scripts which modify the intensity of the current task. func _check_for_alterants(script: ScriptTask, value: int) -> int: diff --git a/src/core/SelectionWindow.gd b/src/core/SelectionWindow.gd new file mode 100644 index 00000000..df5856b7 --- /dev/null +++ b/src/core/SelectionWindow.gd @@ -0,0 +1,170 @@ +class_name SelectionWindow +extends AcceptDialog + + +# The path to the GridCardObject scene. +const _GRID_CARD_OBJECT_SCENE_FILE = CFConst.PATH_CORE\ + + "CardViewer/CVGridCardObject.tscn" +const _GRID_CARD_OBJECT_SCENE = preload(_GRID_CARD_OBJECT_SCENE_FILE) +const _INFO_PANEL_SCENE_FILE = CFConst.PATH_CORE\ + + "CardViewer/CVInfoPanel.tscn" +const _INFO_PANEL_SCENE = preload(_INFO_PANEL_SCENE_FILE) + +export(PackedScene) var grid_card_object_scene := _GRID_CARD_OBJECT_SCENE +export(PackedScene) var info_panel_scene := _INFO_PANEL_SCENE + +var selected_cards := [] +var selection_count : int +var selection_type: String +var is_selection_optional: bool +var is_cancelled := false +var _card_dupe_map := {} + +onready var _card_grid = $GridContainer +onready var _tween = $Tween + +#func _ready(): +# var c = cfc.instance_card("Test Card 1") +# var c2 = cfc.instance_card("Test Card 2") +# initiate_selection([c, c2]) + +func _process(delta): + var current_count = selected_cards.size() + # We disable the OK button, if the amount of cards to be + # chosen do not match our expectations + match selection_type: + "min": + if current_count < selection_count: + get_ok().disabled = true + else: + get_ok().disabled = false + "equal": + if current_count != selection_count: + get_ok().disabled = true + else: + get_ok().disabled = false + "max": + if current_count > selection_count: + get_ok().disabled = true + else: + get_ok().disabled = false + + +# Populates the selection window with duplicates of the possible cards +# Then displays them in a popup for the player to select them. +func initiate_selection( + card_array: Array, + _selection_count := 0, + _selection_type := 'min', + _selection_optional := false) -> void: + # We don't allow the player to close the popup with the close button + # as that will not send the mandatory signal to unpause the game + get_close_button().visible = false + selection_count = _selection_count + selection_type = _selection_type + is_selection_optional = _selection_optional + # If the selection is optional, we allow the player to cancel out + # of the popup + if is_selection_optional: + var cancel_button := add_cancel("Cancel") + cancel_button.connect("pressed",self, "_on_cancel_pressed") + # If the amount of cards available for the choice are below the requirements + # We return that the selection was canceled + elif card_array.size() < selection_count\ + and selection_type in ["equal", "min"]: + selected_cards = [] + is_cancelled = true + emit_signal("confirmed") + return + # If the amount of cards available for the choice are exactly the requirements + # And we're looking for equal or minimum amount + # We immediately return what is there. + elif card_array.size() == selection_count\ + and selection_type in ["equal", "min"]: + selected_cards = card_array + emit_signal("confirmed") + return + # We change the window title to be descriptive + match selection_type: + "min": + window_title = "Select at least " + str(selection_count) + " cards." + "max": + window_title = "Select at most " + str(selection_count) + " cards." + "equal": + window_title = "Select exactly " + str(selection_count) + " cards." + for c in _card_grid.get_children(): + c.queue_free() + # for each card that the player needs to select amonst + # we create a duplicate card inside a Card Grid object + for card in card_array: + var dupe_selection: Card + dupe_selection = card.duplicate(DUPLICATE_USE_INSTANCING) + # This prevents the card from being scripted with the + # signal propagator and other things going via groups + dupe_selection.remove_from_group("cards") + dupe_selection.canonical_name = card.canonical_name + dupe_selection.properties = card.properties.duplicate() + dupe_selection.is_faceup = true + var card_grid_obj = grid_card_object_scene.instance() + _card_grid.add_child(card_grid_obj) + # This is necessary setup for the card grid container + card_grid_obj.preview_popup.focus_info.info_panel_scene = info_panel_scene + card_grid_obj.preview_popup.focus_info.setup() + card_grid_obj.setup(dupe_selection) + _extra_dupe_ready(dupe_selection, card) + _card_dupe_map[card] = dupe_selection + # We connect each card grid's gui input into a call which will handle + # The selections + card_grid_obj.connect("gui_input", self, "on_selection_gui_input", [dupe_selection, card]) + # We don't want to show a popup longer than the cards. So the width is based on the lowest + # between the grid columns or the amount of cards + var shown_columns = min(_card_grid.columns, card_array.size()) + var popup_size_x = (CFConst.CARD_SIZE.x * CFConst.THUMBNAIL_SCALE * shown_columns)\ + + _card_grid.get("custom_constants/vseparation") * shown_columns + # The height will be automatically adjusted based on the amount of cards + rect_size = Vector2(popup_size_x,0) + popup_centered_minsize() + # Spawning all the duplicates is a bit heavy + # So we delay showing the tween to avoid having it look choppy + yield(get_tree().create_timer(0.2), "timeout") + _tween.remove_all() + # We do a nice alpha-modulate tween + _tween.interpolate_property(self,'modulate:a', + 0, 1, 0.5, + Tween.TRANS_SINE, Tween.EASE_IN) + _tween.start() + + +# Overridable function for games to extend processing of dupe card +# after adding it to the scene +func _extra_dupe_ready(dupe_selection: Card, _card: Card) -> void: + dupe_selection.targeting_arrow.visible = false + +# The player can select the cards using a simple left-click. +func on_selection_gui_input(event: InputEvent, dupe_selection: Card, origin_card: Card) -> void: + if event is InputEventMouseButton\ + and event.is_pressed()\ + and event.get_button_index() == 1: + # Each time a card is clicked, it's selected/unselected + if origin_card in selected_cards: + selected_cards.erase(origin_card) + dupe_selection.highlight.set_highlight(false) + else: + selected_cards.append(origin_card) + dupe_selection.highlight.set_highlight(true) + # We want to avoid the player being able to select more cards than + # the max, even if the OK button is disabled + # So whenever they exceed the max, we unselect the first card in the array. + if selection_type in ["equal", "max"] and selected_cards.size() > selection_count: + _card_dupe_map[selected_cards[0]].highlight.set_highlight(false) + selected_cards.remove(0) + + +# Cancels out of the selection window +func _on_cancel_pressed() -> void: + selected_cards.clear() + is_cancelled = true + # The signal is the same, but the calling method, should be checking the + # is_cancelled bool. + # This is to be able to yield to only one specific signal. + emit_signal("confirmed") diff --git a/src/core/SelectionWindow.tscn b/src/core/SelectionWindow.tscn new file mode 100644 index 00000000..21b7311e --- /dev/null +++ b/src/core/SelectionWindow.tscn @@ -0,0 +1,28 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://src/core/SelectionWindow.gd" type="Script" id=1] + +[node name="SelectionWindow" type="AcceptDialog"] +modulate = Color( 1, 1, 1, 0 ) +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_right = -1178.0 +margin_bottom = -662.0 +popup_exclusive = true +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="GridContainer" type="GridContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 8.0 +margin_top = 8.0 +margin_right = -8.0 +margin_bottom = -36.0 +custom_constants/vseparation = 5 +custom_constants/hseparation = 5 +columns = 8 + +[node name="Tween" type="Tween" parent="."] diff --git a/src/core/perMessage.gd b/src/core/perMessage.gd index 7890b87c..9fa317e9 100644 --- a/src/core/perMessage.gd +++ b/src/core/perMessage.gd @@ -25,9 +25,10 @@ func _init( # Returns the amount of things the calling script is trying to count. func count_found_things() -> int: var found_count := 0 + var multiplier = per_definitions.get("multiplier", 1) var per_discovery = cfc.script_per.new(self) # if not per_discovery.has_init_completed: # yield(per_discovery,"primed") found_count = per_discovery.return_per_count() - return(found_count) + return(found_count * multiplier) diff --git a/src/custom/CGFBoard.tscn b/src/custom/CGFBoard.tscn index 3dd5fbe6..7f7f33f0 100644 --- a/src/custom/CGFBoard.tscn +++ b/src/custom/CGFBoard.tscn @@ -275,8 +275,6 @@ margin_bottom = 600.0 rect_min_size = Vector2( 800, 0 ) [node name="DeckBuilder" parent="DeckBuilderPopup" instance=ExtResource( 10 )] -anchor_right = 1.0 -anchor_bottom = 1.0 margin_left = 2.0 margin_top = 2.0 margin_right = -2.0 diff --git a/src/custom/MainMenu.tscn b/src/custom/MainMenu.tscn index ae5324a6..4e987db1 100644 --- a/src/custom/MainMenu.tscn +++ b/src/custom/MainMenu.tscn @@ -23,13 +23,9 @@ __meta__ = { } [node name="DeckBuilder" parent="." instance=ExtResource( 4 )] -anchor_right = 1.0 -anchor_bottom = 1.0 custom_styles/panel = SubResource( 1 ) [node name="CardLibrary" parent="." instance=ExtResource( 5 )] -anchor_right = 1.0 -anchor_bottom = 1.0 custom_styles/panel = SubResource( 1 ) property_width_exceptions = { "Cost": 30, diff --git a/src/custom/cards/sets/SetDefinition_Demo1.gd b/src/custom/cards/sets/SetDefinition_Demo1.gd index 8a2328fa..d5c933ea 100644 --- a/src/custom/cards/sets/SetDefinition_Demo1.gd +++ b/src/custom/cards/sets/SetDefinition_Demo1.gd @@ -16,7 +16,7 @@ const CARDS := { "Type": "Purple", "Tags": ["Rich","Text"], "Requirements": "", - "Abilities": "We can really have [color=green][shake rate=5 level=10]some fun[/shake][/color] now!\n", + "Abilities": "Gain 5 Research [color=green][shake rate=5 level=10]You may discard two cards[/shake][/color] to gain 3 more research.", "Cost": 0, "Power": 0, diff --git a/src/custom/cards/sets/SetScripts_Demo1.gd b/src/custom/cards/sets/SetScripts_Demo1.gd index 100ba6e8..1360795c 100644 --- a/src/custom/cards/sets/SetScripts_Demo1.gd +++ b/src/custom/cards/sets/SetScripts_Demo1.gd @@ -45,6 +45,46 @@ func get_scripts(card_name: String) -> Dictionary: ] }, }, + "Shaking Card": { + "manual": { + "hand": [ + { + "name": "mod_counter", + "modification": 5, + "counter_name": "research" + }, +# { +# "name": "move_card_to_container", +# "subject": "self", +# "dest_container": cfc.NMAP.discard, +# }, + { + "name": "nested_script", + "nested_tasks": [ + { + "name": "move_card_to_container", + "is_cost": true, + "subject": "index", + "subject_count": "all", + "subject_index": "top", + SP.KEY_NEEDS_SELECTION: true, + SP.KEY_SELECTION_COUNT: 2, + SP.KEY_SELECTION_TYPE: "equal", + SP.KEY_SELECTION_OPTIONAL: true, + SP.KEY_SELECTION_IGNORE_SELF: true, + "src_container": cfc.NMAP.hand, + "dest_container": cfc.NMAP.discard, + }, + { + "name": "mod_counter", + "modification": 3, + "counter_name": "research" + }, + ] + }, + ], + }, + }, } # We return only the scripts that match the card name and trigger return(scripts.get(card_name,{})) diff --git a/tests/integration/test_scripting_engine_nested.gd b/tests/integration/test_scripting_engine_nested.gd new file mode 100644 index 00000000..7455b45e --- /dev/null +++ b/tests/integration/test_scripting_engine_nested.gd @@ -0,0 +1,27 @@ +extends "res://tests/UTcommon.gd" + +var cards := [] +var card: Card +var target: Card + +func before_all(): + cfc.game_settings.fancy_movement = false + +func after_all(): + cfc.game_settings.fancy_movement = true + +func before_each(): + var confirm_return = setup_board() + if confirm_return is GDScriptFunctionState: # Still working. + confirm_return = yield(confirm_return, "completed") + cards = draw_test_cards(5) + yield(yield_for(0.1), YIELD) + card = cards[0] + target = cards[2] + + + +func test_draw_more_cards_than_pile_max(): + target = deck.get_last_card() + pending("Test good execution of nested tasks") + pending("Test failing cost in nested cost task aborts parent script")