Compare commits

3 Commits

18 changed files with 221 additions and 269 deletions

View File

@ -9,12 +9,13 @@ var _emitted_done = 0
signal fetch_start
var _emitted_start = 0
var _consts = preload("res://data/consts.gd")
func _all_downloads_done() -> bool:
return _emitted_done == _emitted_start
func _setup_cache_in_mem():
var file = FileAccess.open("user://bulk.json", FileAccess.READ)
_bulk_data = JSON.parse_string(file.get_as_text())
@ -34,14 +35,16 @@ func setup() -> Error:
_setup_cache_in_mem()
return OK
func _init() -> void:
_req_headers = PackedStringArray(["User-Agent: " + _consts.APP_NAME + "/" + _consts.APP_VERSION, "Accept: */*"])
_req_headers = PackedStringArray(
["User-Agent: " + _consts.APP_NAME + "/" + _consts.APP_VERSION, "Accept: */*"]
)
fetch_done.connect(_on_end_emit)
fetch_start.connect(_on_start_emit)
func _on_start_emit() -> void:
_emitted_start += 1
@ -49,6 +52,7 @@ func _on_start_emit() -> void:
func _on_end_emit() -> void:
_emitted_done += 1
func has_emitted_all() -> bool:
return _emitted_start == _emitted_done
@ -63,12 +67,13 @@ func _get_dict_from_file(filepath: String) -> Dictionary:
return data
## get_card_data_from_name
##
## _name: String [br]
## A wrapper for searching for a card by name. Use **get_card_data_from_id** where possible, as it avoids an expensive search for the new card, if the card has been cached already.
func get_card_data_from_name(_name: String) -> Dictionary:
return _get_card_data_from_bulk(_search_results_name(_name))
return _get_card_data_from_bulk("name", _name)
## get_card_data_from_id
@ -78,50 +83,37 @@ func get_card_data_from_name(_name: String) -> Dictionary:
func get_card_data_from_id(id: String) -> Dictionary:
if FileAccess.file_exists("user://card_cache/" + id + "/card.json"):
return _get_dict_from_file("user://card_cache/" + id + "/card.json")
return _get_card_data_from_bulk("id", id)
return _get_card_data_from_bulk(_search_results_generic("id", id))
func _search_results_name(search_query: String) -> Dictionary:
func _get_card_data_from_bulk(field: String, search_query: String) -> Dictionary:
var selected_entry = null
for entry in _bulk_data:
if entry["layout"] == "art_series":
continue
var entry_name = entry["name"]
if entry_name.contains("//"):
entry_name = entry_name.left(entry_name.find("//") - 1)
if entry_name == search_query:
return entry
push_error("Could not find desired card {" + search_query + "}")
return {}
func _search_results_generic(field: String, search_query: String) -> Dictionary:
var selected_entry = null
for entry in _bulk_data:
if entry["layout"] == "art_series":
continue
if entry[field] == search_query:
return entry[field]
selected_entry = entry
break
continue
push_error("Could not find desired card {" + search_query + "}")
return {}
if selected_entry == null:
return {}
func _get_card_data_from_bulk(dict_entry: Dictionary) -> Dictionary:
if dict_entry["image_status"] != "missing":
_fetch_card_img(dict_entry)
if selected_entry["image_status"] != "missing":
_fetch_card_img(selected_entry)
var dir = DirAccess.open("user://")
dir.make_dir_recursive("user://card_cache/" + dict_entry["id"] + "/")
dir.make_dir_recursive("user://card_cache/" + selected_entry["id"] + "/")
dir = null
var file = FileAccess.open("user://card_cache/" + dict_entry["id"] + "/card.json", FileAccess.WRITE)
file.store_line(JSON.stringify(dict_entry, "\t"))
var file = FileAccess.open(
"user://card_cache/" + selected_entry["id"] + "/card.json", FileAccess.WRITE
)
file.store_line(JSON.stringify(selected_entry, "\t"))
file.close()
print("Card: " + dict_entry["name"] + " (" + dict_entry["id"] + ") found, and cached.")
print("Card: " + selected_entry["name"] + "(" + selected_entry["id"] + ") found, and cached.")
return selected_entry
return dict_entry
func _fetch_card_img(data: Dictionary) -> Error:
fetch_start.emit()
@ -154,13 +146,13 @@ func _fetch_card_img(data: Dictionary) -> Error:
return OK
func get_bulk_data(force: bool) -> Error:
if FileAccess.file_exists("user://bulk.json"):
if force:
DirAccess.remove_absolute("user://bulk.json")
else:
return OK
print("downloading ")
var httpr = HTTPRequest.new()
add_child(httpr)
@ -181,7 +173,6 @@ func get_bulk_data(force: bool) -> Error:
push_error(_cache_error("PARSING") + "Failed to parse the Scryfall card results.")
return FAILED
error = httpr.request(card_content["download_uri"], _req_headers)
if error != OK:
push_error(_cache_error("GET_REQUEST") + "An error occurred in the Scryfall request.")
@ -206,7 +197,10 @@ func get_bulk_data(force: bool) -> Error:
return OK
func _notification(what):
if what == NOTIFICATION_PREDELETE:
if !_all_downloads_done():
push_error("ERR::MEM::CACHE\nCache being deleted before all threads have finished processing!")
push_error(
"ERR::MEM::CACHE\nCache being deleted before all threads have finished processing!"
)

View File

@ -1,11 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://cah3mvdnom1xg"]
[ext_resource type="Script" uid="uid://b3yqd1qu7dyq" path="res://card.gd" id="1_kikvd"]
[node name="Card" type="TextureRect"]
offset_left = 794.0
offset_top = 79.0
offset_right = 919.0
offset_bottom = 254.0
mouse_default_cursor_shape = 2
script = ExtResource("1_kikvd")

View File

@ -4,26 +4,14 @@ var _caching = preload("res://caching.gd")
var _decklist
func _init() -> void:
var cards: String
func _init(_cards: String) -> void:
cards = _cards
_decklist = Dictionary()
func _write_to_decks(_decks: Array) -> void:
if FileAccess.file_exists("user://decks.json"):
DirAccess.remove_absolute("user://decks.json")
var file = FileAccess.open("user://decks.json", FileAccess.WRITE)
file.store_line(JSON.stringify(_decks))
file.close()
func load_decks() -> Array:
if !FileAccess.file_exists("user://decks.json"):
return []
var file = FileAccess.open("user://decks.json", FileAccess.READ)
var file_text = file.get_as_text()
file.close()
return JSON.parse_string(file_text)
func _convert_mtgo_to_cache_lookup(decklist: String) -> Dictionary:
var _cards = {}
var lines = decklist.split("\n")
@ -42,26 +30,14 @@ func _do_free(cache) -> void:
cache.queue_free()
func add_new_deck(cards: String, name: String, about = "") -> void:
var _queries = _convert_mtgo_to_cache_lookup(cards)
_do_decklist_cache(_queries)
var _decks = load_decks()
_decks.push_back({"name": name, "about": about, "decklist": _queries})
_write_to_decks(_decks)
func _do_decklist_cache(_queries: Dictionary) -> void:
func do_decklist_grab(decklist: String) -> void:
var cache = _caching.new()
add_child(cache)
cache.setup()
for query in _queries:
var queries = _convert_mtgo_to_cache_lookup(decklist)
for query in queries:
var entry = cache.get_card_data_from_name(query)
if entry.size() == 0:
push_error("Failed to find card: " + query)
continue
_decklist[entry["id"]] = _queries[query]
_decklist[entry["id"]] = queries[query]
cache.fetch_done.connect(_do_free.bind(cache))
@ -69,3 +45,8 @@ func _do_decklist_cache(_queries: Dictionary) -> void:
func _show_decklist() -> void:
for card in _decklist:
print(card + " : " + _decklist[card])
func _ready() -> void:
do_decklist_grab(cards)
_show_decklist()

View File

@ -3,7 +3,7 @@ extends TextureRect
var _screen_size: Vector2
var _colors: Array[Color]
var _card_scene = preload("res://card.tscn")
var _card_class = preload("res://scenes/card/card.tscn")
# Called when the node enters the scene tree for the first time.
@ -11,7 +11,7 @@ func _ready() -> void:
# TODO: Calculate this field's scale and position based on which no# field this is.
_screen_size = get_viewport_rect().size
var card = _card_scene.instantiate()
var card = _card_class.instantiate()
# TODO: Currently working with an already-cached card with a known ID to load this.
# Later on, the cards should be pulling the IDs directly from the library's list of IDs.

15
hand.gd Normal file
View File

@ -0,0 +1,15 @@
extends StaticBody2D
var cards: Array[Node] = []
var _card_class = preload("res://scenes/card/card.tscn")
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
pass

1
hand.gd.uid Normal file
View File

@ -0,0 +1 @@
uid://dvu4gdhqjejeo

View File

@ -19,6 +19,7 @@ func _init(_decklist: Dictionary) -> void:
for i in _num:
lib_cards.push_back(card)
func add_cards(cards: Array, top: bool) -> void:
for card in cards:
add_card(card, top)

View File

@ -1,6 +1,6 @@
extends Node2D
var _card_class = preload("res://card.tscn")
var _card_class = preload("res://scenes/card/card.tscn")
var field_scene = preload("res://field.tscn")
var fields: Array[Node] = []
@ -8,6 +8,15 @@ var fields: Array[Node] = []
var decks: Array[Dictionary]
func _load_decks():
if !FileAccess.file_exists("user://decks.json"):
return # no loaded decks
var file = FileAccess.open("user://decks.json", FileAccess.READ)
decks = JSON.parse_string(file.get_as_text())
file.close()
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
# The first field in the array will be the player's own field.

View File

@ -1,12 +1,21 @@
[gd_scene load_steps=3 format=3 uid="uid://cx0vga81xwckh"]
[gd_scene load_steps=5 format=3 uid="uid://cx0vga81xwckh"]
[ext_resource type="Script" uid="uid://w2rqm1u7p7im" path="res://player.gd" id="1_4flbx"]
[ext_resource type="Script" uid="uid://dvu4gdhqjejeo" path="res://hand.gd" id="2_i3pqv"]
[ext_resource type="Script" uid="uid://bc51go8t8uvts" path="res://library.gd" id="2_onrkg"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_onrkg"]
size = Vector2(1920, 200)
[node name="Player" type="Node2D"]
script = ExtResource("1_4flbx")
[node name="Hand" type="Node2D" parent="."]
[node name="Hand" type="StaticBody2D" parent="."]
script = ExtResource("2_i3pqv")
[node name="CollisionArea" type="CollisionShape2D" parent="Hand"]
position = Vector2(960, 980)
shape = SubResource("RectangleShape2D_onrkg")
[node name="Library" type="Node2D" parent="."]
script = ExtResource("2_onrkg")

View File

@ -27,7 +27,7 @@ enabled=PackedStringArray()
[input]
default_action={
MAIN={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
]

View File

@ -1,80 +1,75 @@
extends TextureRect
#extends Sprite2D
extends Node2D
## The card class
##
## Represents an instance of a card to be displayed on the tabletop.
## Contains helper text for the text, the cards ID, and the image path.
enum pivot {
ROTATE_0,
ROTATE_90,
ROTATE_180,
ROTATE_270
}
var current_pivot = pivot.ROTATE_0
# Card information.
var card_id: String
var card_name: String
var card_type: String
var oracle_text: String
var is_dragging = false
var is_pivot = false
# Card properties.
var tapped: bool
# Card input state.
var is_focused: bool # Is the card a focus?
var is_dragging: bool # Is the card currently being dragged?
var delay = 5.0
var mouse_offset: Vector2
func _pivot() -> int:
var deg: int
match current_pivot:
pivot.ROTATE_0:
deg = 0
pivot.ROTATE_90:
deg = 90
pivot.ROTATE_180:
deg = 180
pivot.ROTATE_270:
deg = 270
return deg
func _physics_process(delta: float) -> void:
if is_dragging == true:
var tween = get_tree().create_tween()
tween.tween_property(self, "position", get_global_mouse_position() - mouse_offset, delay * delta)
if is_pivot == true:
var tween = get_tree().create_tween()
tween.tween_property(self, "rotation_degrees", _pivot(), delta * delay)
is_pivot = false
if is_focused:
# TODO: Export handling keypresses to its own area.
if Input.is_action_just_pressed("MAIN"):
tapped = not tapped
$TweenController.tap(tapped, delta)
if is_dragging:
$TweenController.move_to(get_global_mouse_position() - mouse_offset, delta)
func _gui_input(event: InputEvent) -> void:
func _on_input_event(viewport: Node, event: InputEvent, shape_idx: int) -> void:
if event is not InputEventMouseButton:
return
match event.button_index:
# MOUSE BUTTONS
MOUSE_BUTTON_LEFT:
if event.pressed:
is_dragging = true
Input.set_default_cursor_shape(Input.CURSOR_DRAG)
mouse_offset = get_global_mouse_position() - global_position
is_dragging = true
else:
Input.set_default_cursor_shape(Input.CURSOR_POINTING_HAND)
is_dragging = false
MOUSE_BUTTON_RIGHT:
# TODO: Tooltip menu for right-button clicking on cards.
pass
func _unhandled_key_input(event: InputEvent) -> void:
if not event.is_action_pressed("default_action"):
func _on_mouse_entered() -> void:
# Do not care about mouse entering if we're dragging the card.
if is_dragging:
return
if current_pivot == pivot.ROTATE_0:
current_pivot = pivot.ROTATE_90
is_pivot = true
else:
current_pivot = pivot.ROTATE_0
is_pivot = true
set_pivot_offset(size / 2)
Input.set_default_cursor_shape(Input.CURSOR_POINTING_HAND)
$TweenController.scale(1.05)
is_focused = true
func _on_mouse_exited() -> void:
# Do not care about mouse exiting if we're dragging the card.
if is_dragging:
return
Input.set_default_cursor_shape(Input.CURSOR_ARROW)
$TweenController.scale(1.0)
is_focused = false
func _card_error(error_type: String) -> String:
return "ERROR::CARD::%s::%s::%s::\n" % [card_id, card_name, error_type]
@ -92,8 +87,6 @@ func _ready() -> void:
# Setting that up can be put here later...
push_error("Failed to load card.")
set_pivot_offset(size / 2)
func _load_card() -> Error:
if _load_data() != OK:
@ -140,16 +133,12 @@ func _load_image() -> Error:
push_error("%sCard on-board image failed to load correctly" % _card_error("IMAGE"))
return FAILED
# TODO: Get the size from the node or some constant variable.
var size = $Area2D/CollisionShape2D.shape.size
image.resize(int(size.x), int(size.y), Image.INTERPOLATE_LANCZOS)
var image_texture = ImageTexture.new()
image_texture.set_image(image)
#expand_mode = TextureRect.EXPAND_FIT_WIDTH
texture = image_texture
$Sprite2D.texture = image_texture
return OK

24
scenes/card/card.tscn Normal file
View File

@ -0,0 +1,24 @@
[gd_scene load_steps=4 format=3 uid="uid://cah3mvdnom1xg"]
[ext_resource type="Script" uid="uid://b3yqd1qu7dyq" path="res://scenes/card/card.gd" id="1_kikvd"]
[ext_resource type="Script" uid="uid://bkk0pyypi1id7" path="res://scenes/card/tween.gd" id="2_imta7"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_kikvd"]
size = Vector2(125, 175)
[node name="Card" type="Node2D"]
script = ExtResource("1_kikvd")
[node name="Sprite2D" type="Sprite2D" parent="."]
[node name="Area2D" type="Area2D" parent="."]
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"]
shape = SubResource("RectangleShape2D_kikvd")
[node name="TweenController" type="Node2D" parent="."]
script = ExtResource("2_imta7")
[connection signal="input_event" from="Area2D" to="." method="_on_input_event"]
[connection signal="mouse_entered" from="Area2D" to="." method="_on_mouse_entered"]
[connection signal="mouse_exited" from="Area2D" to="." method="_on_mouse_exited"]

0
scenes/card/input.gd Normal file
View File

1
scenes/card/input.gd.uid Normal file
View File

@ -0,0 +1 @@
uid://dhgk6fhw8oua0

22
scenes/card/tween.gd Normal file
View File

@ -0,0 +1,22 @@
extends Node
@export var move_speed = 1.0
@export var tap_speed = 5.0
@export var scale_speed = 0.1
func move_to(location: Vector2, delta: float) -> void:
var tween = create_tween()
tween.tween_property(get_parent(), "position", location, delta * move_speed)
func tap(tapped: bool, delta: float) -> void:
var tween = create_tween()
var rotation = 90 if tapped else 0
tween.tween_property(get_parent(), "rotation_degrees", rotation, delta * tap_speed)
func scale(scalar: float) -> void:
var tween = create_tween()
var new_scale = Vector2.ONE * scalar
tween.tween_property(get_parent(), "scale", new_scale, scale_speed)

1
scenes/card/tween.gd.uid Normal file
View File

@ -0,0 +1 @@
uid://bkk0pyypi1id7

View File

@ -4,95 +4,13 @@ var player_class = preload("res://player.gd")
var deck_input = preload("res://deck_input.gd")
var _caching = preload("res://caching.gd")
var cards = "1 All That Glitters\n1 Ancestral Mask\n1 Angelic Destiny\n1 Arcane Signet\n1 Archon of Sun's Grace\n1 Austere Command\n1 Banishing Light\n1 Bear Umbra\n1 Blossoming Sands\n1 Canopy Vista\n1 Celestial Mantle\n1 Collective Resistance\n1 Command Tower\n1 Danitha Capashen, Paragon\n1 Danitha, New Benalia's Light\n1 Darksteel Mutation\n1 Daybreak Coronet\n1 Destiny Spinner\n1 Eidolon of Blossoms\n1 Eidolon of Countless Battles\n1 Ellivere of the Wild Court\n1 Enchantress's Presence\n1 Envoy of the Ancestors\n1 Ethereal Armor\n1 Fertile Ground\n13 Forest\n1 Frantic Strength\n1 Generous Gift\n1 Gilded Lotus\n1 Glittering Frost\n1 Grasp of Fate\n1 Gylwain, Casting Director\n1 Hall of Heliod's Generosity\n1 Heliod's Pilgrim\n1 Hidden Grotto\n1 Horrid Vigor\n1 Idyllic Tutor\n1 Jukai Naturalist\n1 Kenrith's Transformation\n1 Kor Spiritdancer\n1 Krosan Verge\n1 Light-Paws, Emperor's Voice\n1 Luminous Broodmoth\n1 Mantle of the Ancients\n1 Overgrowth\n1 Overprotect\n1 Pacifism\n14 Plains\n1 Rancor\n1 Retether\n1 Rogue's Passage\n1 Sage's Reverie\n1 Sanctum Weaver\n1 Selesnya Guildgate\n1 Setessan Champion\n1 Shalai, Voice of Plenty\n1 Snake Umbra\n1 Sol Ring\n1 Solemnity\n1 Songbirds' Blessing\n1 Starfield Mystic\n1 Swords to Plowshares\n1 Tanglespan Lookout\n1 Timber Paladin\n1 Timely Ward\n1 Tithe Takern1 Transcendent Envoy\n1 Twinblade Blessing\n1 Umbra Mystic\n1 Unfinished Business\n1 Utopia Sprawl\n1 Wild Growth\n1 Winds of Rath\n1 Yenna, Redtooth Regent\n1 Sythis, Harvest's Hand"
var cards = "1 Arcane Signet
1 Austere Command
1 Bartolomé del Presidio
1 Blade of the Bloodchief
1 Blood Artist
1 Bloodghast
1 Bloodline Necromancer
1 Bloodtracker
1 Bojuka Bog
1 Butcher of Malakir
1 Carmen, Cruel Skymarcher
1 Champion of Dusk
1 Charismatic Conqueror
1 Command Tower
1 Commander's Sphere
1 Cordial Vampire
1 Crossway Troublemakers
1 Cruel Celebrant
1 Damn
1 Drana, Liberator of Malakir
1 Dusk Legion Sergeant
1 Dusk Legion Zealot
1 Elenda, the Dusk Rose
1 Elenda's Hierophant
1 Etchings of the Chosen
1 Exquisite Blood
1 Falkenrath Noble
1 Glass-Cast Heart
1 Heirloom Blade
1 Indulgent Aristocrat
1 Isolated Chapel
1 Kindred Boon
1 Legion Lieutenant
1 March of the Canonized
1 Martyr of Dusk
1 Master of Dark Rites
1 Mavren Fein, Dusk Apostle
1 Mind Stone
1 Myriad Landscape
1 New Blood
1 Nighthawk Scavenger
1 Oathsworn Vampire
1 Olivia's Wrath
1 Order of Sacred Dusk
1 Orzhov Basilica
1 Orzhov Signet
1 Pact of the Serpent
1 Path of Ancestry
1 Patron of the Vein
4 Plains
4 Plains
1 Promise of Aclazotz
1 Radiant Destiny
1 Redemption Choir
1 Return to Dust
1 Rogue's Passage
1 Sanctum Seeker
1 Secluded Courtyard
1 Shineshadow Snarl
1 Sol Ring
1 Sorin, Lord of Innistrad
7 Swamp
6 Swamp
1 Swiftfoot Boots
1 Swords to Plowshares
1 Tainted Field
1 Talisman of Hierarchy
1 Temple of Silence
1 Temple of the False God
1 Timothar, Baron of Bats
1 Twilight Prophet
1 Unclaimed Territory
1 Utter End
1 Vault of the Archangel
1 Village Rites
1 Viscera Seer
1 Voldaren Estate
1 Vona, Butcher of Magan
1 Wayfarer's Bauble
1 Welcoming Vampire
1 Windbrisk Heights
1 Yahenni, Undying Partisan
1 Clavileño, First of the Blessed"
func _bulk_callback(cache) -> void:
cache.setup()
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
var cache = _caching.new()
@ -108,10 +26,8 @@ func _ready() -> void:
cache.get_card_data_from_name("1996 World Champion")
var deck = deck_input.new()
add_child(deck)
deck.add_new_deck(cards, "Blood rites")
# var deck = deck_input.new(cards)
# add_child(deck)
pass # Replace with function body.