Skip to content

Commit

Permalink
Merge pull request #151 from db0/selection_window
Browse files Browse the repository at this point in the history
Selection Window / Nested Script
  • Loading branch information
db0 authored Jul 30, 2021
2 parents f86090f + ac8ab96 commit de510a2
Show file tree
Hide file tree
Showing 19 changed files with 469 additions and 24 deletions.
18 changes: 14 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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")

Expand Down
6 changes: 6 additions & 0 deletions project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -352,6 +357,7 @@ _global_script_class_icons={
"ScriptProperties": "",
"ScriptTask": "",
"ScriptingEngine": "",
"SelectionWindow": "",
"TargetingArrow": "",
"Token": "",
"TokenDrawer": "",
Expand Down
6 changes: 3 additions & 3 deletions src/core/CardTemplate.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions src/core/CardViewer/CVGridCardObject.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 6 additions & 1 deletion src/core/MousePointer.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
23 changes: 23 additions & 0 deletions src/core/OverridableUtils.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
29 changes: 29 additions & 0 deletions src/core/ScriptObject.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
66 changes: 63 additions & 3 deletions src/core/ScriptProperties.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
#
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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 = ""
Expand Down
5 changes: 3 additions & 2 deletions src/core/ScriptTask.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand Down
46 changes: 46 additions & 0 deletions src/core/ScriptingEngine.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down
Loading

0 comments on commit de510a2

Please sign in to comment.