Compare commits
27 Commits
godot
...
86cc3bf8a8
Author | SHA1 | Date | |
---|---|---|---|
86cc3bf8a8 | |||
d1ef1d6f4f | |||
fc3a31d144 | |||
1d9c3fc473 | |||
5937b4db69 | |||
9217834726 | |||
c463bf0adc | |||
98357b5dec | |||
1beb89ea84 | |||
dd73e1b477 | |||
9bb3308487 | |||
90912c062f | |||
b82f5e4c19 | |||
fe38fc49bf | |||
a40ccab3c8 | |||
f5edba7402 | |||
b9a07a8c47 | |||
0f88bd8f7b | |||
c75aec06ac | |||
d726290cf2 | |||
fafaf404ab | |||
77da7cf6b2 | |||
21f7d9de04 | |||
6100a50754 | |||
bf9244c2b7 | |||
a3fb627310 | |||
c085a93c49 |
212
caching.gd
Normal file
212
caching.gd
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
var _req_headers: PackedStringArray
|
||||||
|
|
||||||
|
var _bulk_data: Array
|
||||||
|
|
||||||
|
signal fetch_done
|
||||||
|
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())
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
|
||||||
|
func setup() -> Error:
|
||||||
|
if !FileAccess.file_exists("user://bulk.json"):
|
||||||
|
get_bulk_data(false)
|
||||||
|
push_error("Bulk Data was not downloaded! Downloading now!")
|
||||||
|
return FAILED
|
||||||
|
|
||||||
|
if !_all_downloads_done():
|
||||||
|
push_error("Not done downloading Bulk Data.")
|
||||||
|
return FAILED
|
||||||
|
|
||||||
|
_setup_cache_in_mem()
|
||||||
|
return OK
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
_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
|
||||||
|
|
||||||
|
|
||||||
|
func _on_end_emit() -> void:
|
||||||
|
_emitted_done += 1
|
||||||
|
|
||||||
|
func has_emitted_all() -> bool:
|
||||||
|
return _emitted_start == _emitted_done
|
||||||
|
|
||||||
|
|
||||||
|
func _cache_error(err: String) -> String:
|
||||||
|
return "CACHE::ERROR::" + err + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
func _get_dict_from_file(filepath: String) -> Dictionary:
|
||||||
|
var file = FileAccess.open(filepath, FileAccess.READ)
|
||||||
|
var data = JSON.parse_string(file.get_as_text())
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
|
||||||
|
## get_card_data_from_id
|
||||||
|
##
|
||||||
|
## id: String [br]
|
||||||
|
## This is the preferred wrapper to use when fetching card data, it checks the cache for preexisting data and uses that if it's available. Otherwise, it will search the bulk json for the data.
|
||||||
|
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(_search_results_generic("id", id))
|
||||||
|
|
||||||
|
func _search_results_name(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]
|
||||||
|
|
||||||
|
push_error("Could not find desired card {" + search_query + "}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
func _get_card_data_from_bulk(dict_entry: Dictionary) -> Dictionary:
|
||||||
|
if dict_entry["image_status"] != "missing":
|
||||||
|
_fetch_card_img(dict_entry)
|
||||||
|
|
||||||
|
var dir = DirAccess.open("user://")
|
||||||
|
dir.make_dir_recursive("user://card_cache/" + dict_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"))
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
print("Card: " + dict_entry["name"] + " (" + dict_entry["id"] + ") found, and cached.")
|
||||||
|
|
||||||
|
return dict_entry
|
||||||
|
|
||||||
|
func _fetch_card_img(data: Dictionary) -> Error:
|
||||||
|
fetch_start.emit()
|
||||||
|
if FileAccess.file_exists("user://card_cache/" + data["id"] + "card.png"):
|
||||||
|
return OK
|
||||||
|
|
||||||
|
var httpr = HTTPRequest.new()
|
||||||
|
add_child(httpr)
|
||||||
|
|
||||||
|
var err = httpr.request((data["image_uris"])["png"], _req_headers)
|
||||||
|
if err != OK:
|
||||||
|
push_error(_cache_error("GET_REQUEST") + "An error occured in the Scryfall request.")
|
||||||
|
return FAILED
|
||||||
|
var resp = await httpr.request_completed
|
||||||
|
|
||||||
|
var img = Image.new()
|
||||||
|
err = img.load_png_from_buffer(resp[3])
|
||||||
|
if err != OK:
|
||||||
|
push_error(_cache_error("IMG_LOADING") + "Couldn't load the image.")
|
||||||
|
return FAILED
|
||||||
|
|
||||||
|
var dir = DirAccess.open("user://")
|
||||||
|
dir.make_dir_recursive("user://card_cache/" + data["id"] + "/")
|
||||||
|
dir = null
|
||||||
|
|
||||||
|
img.save_png("user://card_cache/" + data["id"] + "/card.png")
|
||||||
|
img = null
|
||||||
|
|
||||||
|
fetch_done.emit()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
var error = httpr.request("https://api.scryfall.com/bulk-data/unique-artwork", _req_headers)
|
||||||
|
if error != OK:
|
||||||
|
push_error(_cache_error("GET_REQUEST") + "An error occurred in the Scryfall request.")
|
||||||
|
return FAILED
|
||||||
|
|
||||||
|
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.")
|
||||||
|
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.")
|
||||||
|
return FAILED
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
unprocessed_body = response[3].get_string_from_utf8()
|
||||||
|
card_content = JSON.parse_string(unprocessed_body)
|
||||||
|
if card_content == null:
|
||||||
|
push_error(_cache_error("PARSING") + "Failed to parse the Scryfall card results.")
|
||||||
|
return FAILED
|
||||||
|
|
||||||
|
var data_cache = FileAccess.open("user://bulk.json", FileAccess.WRITE)
|
||||||
|
data_cache.store_string(unprocessed_body)
|
||||||
|
data_cache.close()
|
||||||
|
|
||||||
|
fetch_done.emit()
|
||||||
|
|
||||||
|
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!")
|
1
caching.gd.uid
Normal file
1
caching.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://cml6c3fdbyy75
|
242
card.gd
242
card.gd
@ -1,141 +1,155 @@
|
|||||||
extends TextureRect
|
extends TextureRect
|
||||||
## The card class [br][br]
|
#extends Sprite2D
|
||||||
##
|
## 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
|
enum pivot {
|
||||||
# in the helper text box, but thats for the future
|
ROTATE_0,
|
||||||
const ManaCosts = preload("res://data/mana.gd")
|
ROTATE_90,
|
||||||
|
ROTATE_180,
|
||||||
|
ROTATE_270
|
||||||
|
}
|
||||||
|
|
||||||
signal cache_done
|
var current_pivot = pivot.ROTATE_0
|
||||||
|
|
||||||
var card_id = "placedholder_id"
|
var card_id: String
|
||||||
var card_name = "placeholder_name"
|
var card_name: String
|
||||||
var card_type = "placeholder_card_type"
|
var card_type: String
|
||||||
var oracle_text = "placeholder_oracle_text"
|
var oracle_text: String
|
||||||
var _png_path = "placeholder_image_path"
|
|
||||||
var _jpg_path = "placeholder_image_path"
|
|
||||||
|
|
||||||
|
var is_dragging = false
|
||||||
|
var is_pivot = false
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
func _gui_input(event: InputEvent) -> void:
|
||||||
|
if event is not InputEventMouseButton:
|
||||||
|
return
|
||||||
|
match event.button_index:
|
||||||
|
MOUSE_BUTTON_LEFT:
|
||||||
|
if event.pressed:
|
||||||
|
is_dragging = true
|
||||||
|
mouse_offset = get_global_mouse_position() - global_position
|
||||||
|
else:
|
||||||
|
is_dragging = false
|
||||||
|
MOUSE_BUTTON_RIGHT:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func _unhandled_key_input(event: InputEvent) -> void:
|
||||||
|
if not event.is_action_pressed("default_action"):
|
||||||
|
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)
|
||||||
|
|
||||||
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.")
|
||||||
|
|
||||||
|
set_pivot_offset(size / 2)
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
#expand_mode = TextureRect.EXPAND_FIT_WIDTH
|
||||||
|
texture = image_texture
|
||||||
|
|
||||||
|
return OK
|
||||||
|
11
card.tscn
Normal file
11
card.tscn
Normal 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
2
data/consts.gd
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
const APP_NAME = "MTG_UNTAP_CLONE"
|
||||||
|
const APP_VERSION = "0.1"
|
1
data/consts.gd.uid
Normal file
1
data/consts.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://bo0256822a2ss
|
71
deck_input.gd
Normal file
71
deck_input.gd
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
var _caching = preload("res://caching.gd")
|
||||||
|
|
||||||
|
var _decklist
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
_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")
|
||||||
|
for line in lines:
|
||||||
|
var words = line.split(" ", false, 1)
|
||||||
|
if words.size() != 2:
|
||||||
|
continue
|
||||||
|
|
||||||
|
_cards[words[1]] = words[0]
|
||||||
|
return _cards
|
||||||
|
|
||||||
|
|
||||||
|
func _do_free(cache) -> void:
|
||||||
|
if !cache.has_emitted_all():
|
||||||
|
return
|
||||||
|
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:
|
||||||
|
var cache = _caching.new()
|
||||||
|
add_child(cache)
|
||||||
|
cache.setup()
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
cache.fetch_done.connect(_do_free.bind(cache))
|
||||||
|
|
||||||
|
|
||||||
|
func _show_decklist() -> void:
|
||||||
|
for card in _decklist:
|
||||||
|
print(card + " : " + _decklist[card])
|
1
deck_input.gd.uid
Normal file
1
deck_input.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://c0vxigp5y302o
|
44
field.gd
Normal file
44
field.gd
Normal 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", _screen_size)
|
||||||
|
|
||||||
|
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
1
field.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://cqutu8u3qenu0
|
9
field.tscn
Normal file
9
field.tscn
Normal 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")
|
19
library.gd
19
library.gd
@ -2,23 +2,22 @@ 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:
|
||||||
card.load_card()
|
card.load_card()
|
||||||
|
|
||||||
|
|
||||||
func _init(card_ids: Array) -> void:
|
func _init(_decklist: Dictionary) -> void:
|
||||||
lib_cards = Array()
|
lib_cards = Array()
|
||||||
var temp_card
|
for card in _decklist:
|
||||||
for id in card_ids:
|
var _num = _decklist[card]
|
||||||
temp_card = _card_class.new(id)
|
num_cards += _num
|
||||||
temp_card.cache_done.connect(_load_card_callback.bind(temp_card))
|
for i in _num:
|
||||||
lib_cards.push_back(temp_card)
|
lib_cards.push_back(card)
|
||||||
num_cards += 1
|
|
||||||
|
|
||||||
|
|
||||||
func add_cards(cards: Array, top: bool) -> void:
|
func add_cards(cards: Array, top: bool) -> void:
|
||||||
for card in cards:
|
for card in cards:
|
||||||
|
42
player.gd
42
player.gd
@ -1,34 +1,32 @@
|
|||||||
extends Node2D
|
extends Node2D
|
||||||
|
|
||||||
var _card_class = preload("res://card.gd")
|
var _card_class = preload("res://card.tscn")
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
var decks: Array[Dictionary]
|
||||||
|
|
||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
|
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.
|
||||||
|
card.init("d3f10f07-7cfe-4a6f-8de6-373e367a731b")
|
||||||
|
|
||||||
add_child(card)
|
add_child(card)
|
||||||
|
|
||||||
card.cache_done.connect(_test_func)
|
#fields.append(field_scene.instantiate())
|
||||||
|
#var colors: Array[Color] = [Color(1, 0, 1)]
|
||||||
pass # Replace with function body.
|
#fields[0].set_colors(colors)
|
||||||
|
#add_child(fields[0])
|
||||||
|
|
||||||
|
|
||||||
# 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
|
||||||
|
12
player.tscn
12
player.tscn
@ -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")
|
|
||||||
|
@ -24,3 +24,11 @@ window/size/mode=3
|
|||||||
[editor_plugins]
|
[editor_plugins]
|
||||||
|
|
||||||
enabled=PackedStringArray()
|
enabled=PackedStringArray()
|
||||||
|
|
||||||
|
[input]
|
||||||
|
|
||||||
|
default_action={
|
||||||
|
"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)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
105
tabletop.gd
105
tabletop.gd
@ -1,18 +1,121 @@
|
|||||||
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")
|
||||||
|
var _caching = preload("res://caching.gd")
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
# Called when the node enters the scene tree for the first time.
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
var cache = _caching.new()
|
||||||
|
add_child(cache)
|
||||||
|
|
||||||
|
if cache.setup() != OK:
|
||||||
|
cache.fetch_done.connect(_bulk_callback.bind(cache))
|
||||||
# TODO: Create 2-4 player instances as children of this tabletop node.
|
# TODO: Create 2-4 player instances as children of this tabletop node.
|
||||||
|
|
||||||
var player = player_class.new()
|
var player = player_class.new()
|
||||||
add_child(player)
|
add_child(player)
|
||||||
|
move_child(player, 0)
|
||||||
|
|
||||||
|
cache.get_card_data_from_name("1996 World Champion")
|
||||||
|
|
||||||
|
var deck = deck_input.new()
|
||||||
|
add_child(deck)
|
||||||
|
deck.add_new_deck(cards, "Blood rites")
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -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")
|
||||||
|
Reference in New Issue
Block a user