Compare commits

13 Commits

16 changed files with 434 additions and 153 deletions

129
caching.gd Normal file
View File

@ -0,0 +1,129 @@
extends Node
var _card_id
var _img_path
var _req_headers
signal cache_done
var _consts = preload("res://data/consts.gd")
func _init() -> void:
_req_headers = PackedStringArray(["User-Agent: " + _consts.APP_NAME + "/" + _consts.APP_VERSION, "Accept: */*"])
func _cache_error(err: String) -> String:
return "CACHE::ERROR::" + err + "\n"
func _check_cache(id: String) -> bool:
if !FileAccess.file_exists("user://card_cache/" + id + "/card.json"):
return false
if !FileAccess.file_exists("user://card_cache/" + id + "/card.png"):
return false
return true
func custom_query_fetch(query: String) -> String:
var error = await _do_custom_http_request_card(query)
if error != OK:
return "NONE"
OS.delay_msec(100)
await _do_http_request_imgs(_card_id)
OS.delay_msec(100)
cache_done.emit()
return _card_id
func _do_custom_http_request_card(query: String) -> Error:
var httpr = HTTPRequest.new()
add_child(httpr)
var error = httpr.request(query, _req_headers)
if error != OK:
push_error(_cache_error("GET_REQUEST") + "An error occurred in the Scryfall request.")
var response = await httpr.request_completed
if response[0] != HTTPRequest.RESULT_SUCCESS:
push_error(_cache_error("GET_REQUEST") + "Failed to fetch card data from Scryfall")
return FAILED
var unprocessed_body = response[3].get_string_from_utf8()
var card_content = JSON.parse_string(unprocessed_body)
if card_content == null:
push_error(_cache_error("PARSING") + "Failed to parse the Scryfall card results.")
_card_id = card_content["id"]
if _check_cache(_card_id):
return FAILED
var dir = DirAccess.open("user://")
dir.make_dir_recursive("user://card_cache/" + _card_id + "/") # lets ensure the path is there
dir = null
var card_cache = FileAccess.open("user://card_cache/" + _card_id + "/card.json", FileAccess.WRITE)
card_cache.store_string(unprocessed_body) # cache the json response
card_cache = null # closes the file
var image_uris = card_content["image_uris"]
_img_path = image_uris["png"]
return OK
func fetch_card(id: String) -> void:
if _check_cache(id):
return
await _do_http_id_request_card(id)
OS.delay_msec(100)
await _do_http_request_imgs(id)
OS.delay_msec(100)
cache_done.emit()
func _do_http_request_imgs(id: String) -> void:
var httpr = HTTPRequest.new()
add_child(httpr)
var error = httpr.request(_img_path, _req_headers)
if error != OK:
push_error(_cache_error("GET_REQUEST") + "An error occurred in the Scryfall request.")
var response = await httpr.request_completed
var img = Image.new()
error = img.load_png_from_buffer(response[3])
if error != OK:
push_error(_cache_error("IMG_LOADING") + "Couldn't load the image.")
img.save_png("user://card_cache/" + id + "/card.png")
img = null
func _do_http_id_request_card(id: String) -> void:
var httpr = HTTPRequest.new()
add_child(httpr)
var error = httpr.request("https://api.scryfall.com/cards/" + id, _req_headers)
if error != OK:
push_error(_cache_error("GET_REQUEST") + "An error occurred in the Scryfall request.")
var response = await httpr.request_completed
if response[0] != HTTPRequest.RESULT_SUCCESS:
push_error(_cache_error("GET_REQUEST") + "Failed to fetch card data from Scryfall")
return
var unprocessed_body = response[3].get_string_from_utf8()
var card_content = JSON.parse_string(unprocessed_body)
if card_content == null:
push_error(_cache_error("PARSING") + "Failed to parse the Scryfall card results.")
var dir = DirAccess.open("user://")
dir.make_dir_recursive("user://card_cache/" + id + "/") # lets ensure the path is there
dir = null
var card_cache = FileAccess.open("user://card_cache/" + id + "/card.json", FileAccess.WRITE)
card_cache.store_string(unprocessed_body) # cache the json response
card_cache = null # closes the file
var image_uris = card_content["image_uris"]
_img_path = image_uris["png"]

1
caching.gd.uid Normal file
View File

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

173
card.gd
View File

@ -1,141 +1,86 @@
extends TextureRect extends TextureRect
## The card class [br][br] ## 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. ## Contains helper text for the text, the cards ID, and the image path.
## The goal of this class is to make card management easier.
# we want to use this to convert the mana cost into text # TODO: Implement card utilities such as mana cost, land value, etc using api data.
# in the helper text box, but thats for the future
const ManaCosts = preload("res://data/mana.gd") const ManaCosts = preload("res://data/mana.gd")
signal cache_done var card_id: String
var card_name: String
var card_id = "placedholder_id" var card_type: String
var card_name = "placeholder_name" var oracle_text: String
var card_type = "placeholder_card_type"
var oracle_text = "placeholder_oracle_text"
var _png_path = "placeholder_image_path"
var _jpg_path = "placeholder_image_path"
func _card_error(error_type: String) -> String: func _card_error(error_type: String) -> String:
return "CARD::" + card_id + "::" + error_type + "\n" return "ERROR::CARD::%s::%s::%s::\n" % [card_id, card_name, error_type]
func _init(id) -> void: func init(id: String) -> void:
card_id = id card_id = id
func _ready() -> void: func _ready() -> void:
if _check_cache(card_id): var load_status = _load_card()
return if load_status != OK:
await _do_cache_grab() # TODO: No need to push another error as the failure state of loading does that already,
# if the card is not cached, perhaps a placeholder blank card can be used instead?
# Setting that up can be put here later...
push_error("Failed to load card.")
func _do_cache_grab() -> void: func _load_card() -> Error:
await _do_http_request_card() if _load_data() != OK:
await _do_http_request_imgs(_png_path, true) return FAILED
await _do_http_request_imgs(_jpg_path, false)
cache_done.emit() if _load_image() != OK:
return FAILED
return OK
func _check_cache(id: String) -> bool: func _load_data() -> Error:
if !FileAccess.file_exists("user://card_cache/" + id + "/card.json"): var cached_json = FileAccess.get_file_as_string("user://card_cache/" + card_id + "/card.json")
return false
if !FileAccess.file_exists("user://card_cache/" + id + "/card.png"):
return false
if !FileAccess.file_exists("user://card_cache/" + id + "/card.jpg"):
return false
return true
if cached_json.is_empty():
push_error("%s\nCard json data was not found in cache" % _card_error("CACHE"))
return FAILED
func _do_http_request_imgs(image_path: String, png: bool) -> void: var card_json = JSON.parse_string(cached_json)
var httpr = HTTPRequest.new()
add_child(httpr)
var headers = PackedStringArray(["User-Agent: MTGUntapClone/0.1", "Accept: */*"]) if card_json == null:
var error = httpr.request(image_path, headers) push_error("%s\nCard json data is could not be parsed as valid json" % _card_error("DATA"))
if error != OK: return FAILED
push_error(_card_error("GET_REQUEST") + "An error occurred in the Scryfall request.")
var response = await httpr.request_completed
var img = Image.new()
var imgerr
if png:
imgerr = img.load_png_from_buffer(response[3])
else:
imgerr = img.load_jpg_from_buffer(response[3])
if imgerr != OK:
push_error(_card_error("IMG_LOADING") + "Couldn't load the image.")
img.save_png("user://card_cache/" + card_id + ("/card.png" if png else "/card.jpg"))
img = null
func _do_http_request_card() -> void:
var httpr = HTTPRequest.new()
add_child(httpr)
#httpr.request_completed.connect(_scryfall_card_response)
var headers = PackedStringArray(["User-Agent: MTGUntapClone/0.1", "Accept: */*"])
var error = httpr.request("https://api.scryfall.com/cards/" + card_id, headers)
if error != OK:
push_error(_card_error("GET_REQUEST") + "An error occurred in the Scryfall request.")
var response = await httpr.request_completed
if response[0] != HTTPRequest.RESULT_SUCCESS:
push_error(_card_error("GET_REQUEST") + "Failed to fetch card data from Scryfall")
return
var unprocessed_body = response[3].get_string_from_utf8()
var card_content = JSON.parse_string(unprocessed_body)
if card_content == null:
push_error(_card_error("PARSING") + "Failed to parse the Scryfall card results.")
var dir = DirAccess.open("user://")
dir.make_dir_recursive("user://card_cache/" + card_id + "/") # lets ensure the path is there
dir = null
var card_cache = FileAccess.open(
"user://card_cache/" + card_id + "/card.json", FileAccess.WRITE
)
card_cache.store_string(unprocessed_body) # cache the json response
card_cache = null # closes the file
var image_uris = card_content["image_uris"]
_png_path = image_uris["png"]
_jpg_path = image_uris["normal"]
## load_card
##
## Loads the card, returns false, and triggers
## a cache fetch if the card is not in the cache,
## otherwise sets the cards variables if the cache is present.
func load_card() -> bool:
if !_check_cache(card_id):
await _do_cache_grab()
push_error(
(
_card_error("CACHE_FAIL")
+ "Cache wasn't ready, this card will need to be reinitialized"
)
)
return false
var ondisk_card = FileAccess.open(
"user://card_cache/" + card_id + "/card.json", FileAccess.READ
)
var card_json = JSON.parse_string(ondisk_card.get_as_text())
card_name = card_json["name"] card_name = card_json["name"]
card_type = card_json["type_line"] card_type = card_json["type_line"]
oracle_text = card_json["oracle_text"] oracle_text = card_json["oracle_text"]
var img = Image.new() return OK
img.load("user://card_cache/" + card_id + "/card.jpg")
texture = ImageTexture.create_from_image(img)
ondisk_card = null
return true func _load_image() -> Error:
# NOTE: Assuming we're going with using the .png cards on board.
var cached_img = FileAccess.get_file_as_bytes("user://card_cache/" + card_id + "/card.png")
if cached_img.is_empty():
push_error("%sCard on-board image was not found in cache" % _card_error("CACHE"))
return FAILED
var image = Image.new()
var image_status: Error = image.load_png_from_buffer(cached_img)
if image_status != OK:
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.
image.resize(int(size.x), int(size.y), Image.INTERPOLATE_LANCZOS)
var image_texture = ImageTexture.new()
image_texture.set_image(image)
texture = image_texture
return OK

11
card.tscn Normal file
View File

@ -0,0 +1,11 @@
[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")

2
data/consts.gd Normal file
View File

@ -0,0 +1,2 @@
const APP_NAME = "MTG_UNTAP_CLONE"
const APP_VERSION = "0.1"

1
data/consts.gd.uid Normal file
View File

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

108
deck_input.gd Normal file
View File

@ -0,0 +1,108 @@
extends Node
var _caching = preload("res://caching.gd")
var _decklist
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
1 Daybreak Coronet
1 Destiny Spinner
1 Eidolon of Blossoms
1 Eidolon of Countless Battles
1 Ellivere of the Wild Court
1 Enchantress's Presence
1 Envoy of the Ancestors
1 Ethereal Armor
1 Fertile Ground
13 Forest
1 Frantic Strength
1 Generous Gift
1 Gilded Lotus
1 Glittering Frost
1 Grasp of Fate
1 Gylwain, Casting Director
1 Hall of Heliod's Generosity
1 Heliod's Pilgrim
1 Hidden Grotto
1 Horrid Vigor
1 Idyllic Tutor
1 Jukai Naturalist
1 Kenrith's Transformation
1 Kor Spiritdancer
1 Krosan Verge
1 Light-Paws, Emperor's Voice
1 Luminous Broodmoth
1 Mantle of the Ancients
1 Overgrowth
1 Overprotect
1 Pacifism
14 Plains
1 Rancor
1 Retether
1 Rogue's Passage
1 Sage's Reverie
1 Sanctum Weaver
1 Selesnya Guildgate
1 Setessan Champion
1 Shalai, Voice of Plenty
1 Snake Umbra
1 Sol Ring
1 Solemnity
1 Songbirds' Blessing
1 Starfield Mystic
1 Swords to Plowshares
1 Tanglespan Lookout
1 Timber Paladin
1 Timely Ward
1 Tithe Taker
1 Transcendent Envoy
1 Twinblade Blessing
1 Umbra Mystic
1 Unfinished Business
1 Utopia Sprawl
1 Wild Growth
1 Winds of Rath
1 Yenna, Redtooth Regent
1 Sythis, Harvest's Hand"
signal done_fetching
func _init() -> void:
_decklist = Dictionary()
func convert_mtgo_to_http(decklist: String) -> Dictionary:
var links = {}
var lines = decklist.split("\n")
for line in lines:
var words = line.split(" ", false, 1)
if words.size() != 2:
continue
words[1] = words[1].replace(" ", "+")
links["https://api.scryfall.com/cards/named?exact=" + words[1]] = words[0]
return links
func _do_decklist_grab(queries: Dictionary) -> void:
var cache = _caching.new()
add_child(cache)
print("test")
for query in queries:
print("Test2")
var id = await cache.custom_query_fetch(query)
if id == "NONE":
continue
_decklist[id] = queries[query]
print("test3")
done_fetching.emit()
_show_decklist()
func _show_decklist():
for card in _decklist:
print(card + " : " + _decklist[card])
func _ready() -> void:
var queries = convert_mtgo_to_http(cards)
_do_decklist_grab(queries)

1
deck_input.gd.uid Normal file
View File

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

44
field.gd Normal file
View File

@ -0,0 +1,44 @@
extends TextureRect
var _screen_size: Vector2
var _colors: Array[Color]
var _card_scene = preload("res://card.tscn")
# Called when the node enters the scene tree for the first time.
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()
# 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.
card.init("d3f10f07-7cfe-4a6f-8de6-373e367a731b")
add_child(card)
func set_colors(colors: Array[Color]) -> void:
_colors = colors
# TODO: Method to take list of colors, split into this format of dictionary, and apply as gradient.
var gradient_data := {
0.0: Color.MAROON,
1.0: Color.MAROON,
}
var gradient := Gradient.new()
gradient.offsets = gradient_data.keys()
gradient.colors = gradient_data.values()
var gradient_texture = GradientTexture1D.new()
gradient_texture.gradient = gradient
texture = gradient_texture
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta: float) -> void:
pass

1
field.gd.uid Normal file
View File

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

9
field.tscn Normal file
View File

@ -0,0 +1,9 @@
[gd_scene load_steps=2 format=3 uid="uid://clnevm4xcexrs"]
[ext_resource type="Script" uid="uid://cqutu8u3qenu0" path="res://field.gd" id="1_6e7u2"]
[node name="Field" type="TextureRect"]
offset_top = 540.0
offset_right = 1520.0
offset_bottom = 1080.0
script = ExtResource("1_6e7u2")

View File

@ -2,8 +2,9 @@ extends Node2D
var _card_class = preload("res://card.gd") var _card_class = preload("res://card.gd")
var lib_cards # Library cards are represented as an array of card IDs.
var num_cards var lib_cards: Array[String] = []
var num_cards: int = 0
func _load_card_callback(card) -> void: func _load_card_callback(card) -> void:
@ -14,12 +15,17 @@ func _init(card_ids: Array) -> void:
lib_cards = Array() lib_cards = Array()
var temp_card var temp_card
for id in card_ids: for id in card_ids:
temp_card = _card_class.new(id) temp_card = _card_class.new()
temp_card.cache_done.connect(_load_card_callback.bind(temp_card)) temp_card.cache_done.connect(_load_card_callback.bind(temp_card))
lib_cards.push_back(temp_card) lib_cards.push_back(temp_card)
num_cards += 1 num_cards += 1
func init(card_ids: Array[String]) -> void:
for id in card_ids:
pass
func add_cards(cards: Array, top: bool) -> void: func add_cards(cards: Array, top: bool) -> void:
for card in cards: for card in cards:
add_card(card, top) add_card(card, top)

View File

@ -1,34 +1,21 @@
extends Node2D extends Node2D
var _card_class = preload("res://card.gd") # var _card_class = preload("res://card.gd")
var card var field_scene = preload("res://field.tscn")
var fields: Array[Node] = []
func _on_request_completed(result, response_code, headers, body):
var json = JSON.parse_string(body.get_string_from_utf8())
print(json["name"])
func _test_func():
card.load_card()
print(card.card_id)
print(card.card_name)
print(card.card_type)
print(card.oracle_text)
# Called when the node enters the scene tree for the first time. # Called when the node enters the scene tree for the first time.
func _ready() -> void: func _ready() -> void:
# TODO: Create 2-4 player instances as children of this tabletop node. # The first field in the array will be the player's own field.
card = _card_class.new("d3f10f07-7cfe-4a6f-8de6-373e367a731b") # Might be a better idea to have that in a seperate variable? idk
add_child(card) fields.append(field_scene.instantiate())
var colors: Array[Color] = [Color(1, 0, 1)]
card.cache_done.connect(_test_func) fields[0].set_colors(colors)
add_child(fields[0])
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame. # Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void: func _process(_delta: float) -> void:
pass pass

View File

@ -1,22 +1,12 @@
[gd_scene load_steps=4 format=3 uid="uid://cx0vga81xwckh"] [gd_scene load_steps=3 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://w2rqm1u7p7im" path="res://player.gd" id="1_4flbx"]
[ext_resource type="Script" uid="uid://bc51go8t8uvts" path="res://library.gd" id="2_onrkg"] [ext_resource type="Script" uid="uid://bc51go8t8uvts" path="res://library.gd" id="2_onrkg"]
[ext_resource type="Script" uid="uid://b3yqd1qu7dyq" path="res://card.gd" id="3_i3pqv"]
[node name="Player" type="Node2D"] [node name="Player" type="Node2D"]
script = ExtResource("1_4flbx") script = ExtResource("1_4flbx")
[node name="Field" type="Node2D" parent="."]
[node name="Hand" type="Node2D" parent="."] [node name="Hand" type="Node2D" parent="."]
[node name="Library" type="Node2D" parent="."] [node name="Library" type="Node2D" parent="."]
script = ExtResource("2_onrkg") script = ExtResource("2_onrkg")
[node name="TextureRect" type="TextureRect" parent="."]
offset_left = -1.0
offset_top = 1.0
offset_right = 39.0
offset_bottom = 41.0
script = ExtResource("3_i3pqv")

View File

@ -1,6 +1,7 @@
extends Node2D extends Node2D
var player_class = preload("res://player.gd") var player_class = preload("res://player.gd")
var deck_input = preload("res://deck_input.gd")
# Called when the node enters the scene tree for the first time. # Called when the node enters the scene tree for the first time.
@ -9,10 +10,15 @@ func _ready() -> void:
var player = player_class.new() var player = player_class.new()
add_child(player) add_child(player)
move_child(player, 0)
var deck = deck_input.new()
add_child(deck)
pass # Replace with function body. pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame. # Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void: func _process(_delta: float) -> void:
pass pass

View File

@ -1,6 +1,46 @@
[gd_scene load_steps=2 format=3 uid="uid://b4ldtb3gw0jlu"] [gd_scene load_steps=5 format=3 uid="uid://b4ldtb3gw0jlu"]
[ext_resource type="Script" uid="uid://cfkew150yl1y3" path="res://tabletop.gd" id="1_3we3x"] [ext_resource type="Script" uid="uid://cfkew150yl1y3" path="res://tabletop.gd" id="1_3we3x"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3we3x"]
bg_color = Color(0, 0, 0, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_d43bn"]
bg_color = Color(0.6, 0.6, 0.6, 0.709804)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pqag1"]
bg_color = Color(0.6, 0.6, 0.6, 0)
[node name="Tabletop" type="Node2D"] [node name="Tabletop" type="Node2D"]
script = ExtResource("1_3we3x") script = ExtResource("1_3we3x")
[node name="UI" type="Control" parent="."]
layout_mode = 3
anchors_preset = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="BigBar" type="PanelContainer" parent="UI"]
layout_mode = 0
offset_left = 1520.0
offset_right = 1920.0
offset_bottom = 1080.0
theme_override_styles/panel = SubResource("StyleBoxFlat_3we3x")
[node name="Items" type="VBoxContainer" parent="UI/BigBar"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="MenuArea" type="PanelContainer" parent="UI/BigBar/Items"]
custom_minimum_size = Vector2(0, 540)
layout_mode = 2
[node name="ChatArea" type="PanelContainer" parent="UI/BigBar/Items"]
custom_minimum_size = Vector2(0, 270)
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_d43bn")
[node name="LibraryArea" type="PanelContainer" parent="UI/BigBar/Items"]
custom_minimum_size = Vector2(0, 270)
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_pqag1")