Compare commits

..

No commits in common. "main" and "godot" have entirely different histories.
main ... godot

34 changed files with 184 additions and 988 deletions

3
.gitignore vendored
View File

@ -1,6 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/
# custom ignores
symbol_cache/

View File

@ -1,294 +0,0 @@
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
_fetch_mana_symbols()
_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:
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:
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 _get_mana_img(symbol: String, img_url: String) -> Error:
fetch_start.emit()
if FileAccess.file_exists("res://symbol_cache/" + symbol + ".svg"):
return OK
var httpr = HTTPRequest.new()
add_child(httpr)
var err = httpr.request(img_url, _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_svg_from_buffer(resp[3])
if err != OK:
push_error(_cache_error("IMG_LOADING") + "Couldn't load the image.")
return FAILED
if img.get_size() == Vector2i(100, 100):
print("resizing")
img.resize(20, 20, Image.INTERPOLATE_LANCZOS)
img.save_png(
"res://symbol_cache/" + symbol.replace("/", "-").replace("{", "").replace("}", "") + ".png"
)
img = null
fetch_done.emit()
return OK
func _fetch_mana_symbols() -> Error:
var mana_symbols: Dictionary = Dictionary()
if DirAccess.dir_exists_absolute("res://symbol_cache"):
return OK
else:
DirAccess.make_dir_absolute("res://symbol_cache")
var httpr = HTTPRequest.new()
add_child(httpr)
var err = httpr.request("https://api.scryfall.com/symbology", _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 unprocessed_body = resp[3].get_string_from_utf8()
var json_body = JSON.parse_string(unprocessed_body)
for icon in json_body["data"]:
err = await _get_mana_img(icon["symbol"], icon["svg_uri"])
if err != OK:
push_error("Couldn't fetch mana symbol " + icon["symbol"])
mana_symbols[icon["symbol"]] = (
"res://symbol_cache/"
+ icon["symbol"].replace("/", "-").replace("{", "").replace("}", "")
+ ".png"
)
print(icon["symbol"] + " image cached.")
var file = FileAccess.open("res://symbol_cache/symbols.json", FileAccess.WRITE)
file.store_line(JSON.stringify(mana_symbols))
file.close()
print("Done caching mana symbols.")
return OK
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
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!"
)

View File

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

141
card.gd Normal file
View File

@ -0,0 +1,141 @@
extends TextureRect
## The card class [br][br]
##
##
## 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
# in the helper text box, but thats for the future
const ManaCosts = preload("res://data/mana.gd")
signal cache_done
var card_id = "placedholder_id"
var card_name = "placeholder_name"
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:
return "CARD::" + card_id + "::" + error_type + "\n"
func _init(id) -> void:
card_id = id
func _ready() -> void:
if _check_cache(card_id):
return
await _do_cache_grab()
func _do_cache_grab() -> void:
await _do_http_request_card()
await _do_http_request_imgs(_png_path, true)
await _do_http_request_imgs(_jpg_path, false)
cache_done.emit()
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
if !FileAccess.file_exists("user://card_cache/" + id + "/card.jpg"):
return false
return true
func _do_http_request_imgs(image_path: String, png: bool) -> void:
var httpr = HTTPRequest.new()
add_child(httpr)
var headers = PackedStringArray(["User-Agent: MTGUntapClone/0.1", "Accept: */*"])
var error = httpr.request(image_path, headers)
if error != OK:
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_type = card_json["type_line"]
oracle_text = card_json["oracle_text"]
var img = Image.new()
img.load("user://card_cache/" + card_id + "/card.jpg")
texture = ImageTexture.create_from_image(img)
ondisk_card = null
return true

View File

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

View File

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

View File

@ -1,73 +0,0 @@
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])

View File

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

View File

@ -1,6 +0,0 @@
extends Node
@warning_ignore("unused_signal")
signal card_on_hover(card_info, card_image)
@warning_ignore("unused_signal")
signal card_on_unhover

View File

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

View File

@ -1,44 +0,0 @@
extends TextureRect
var _screen_size: Vector2
var _colors: Array[Color]
var _card_class = preload("res://scenes/card/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_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", _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

View File

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

View File

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

15
hand.gd
View File

@ -1,15 +0,0 @@
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

View File

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

View File

@ -1,23 +1,23 @@
extends Node2D
var _card_class = preload("res://scenes/card/card.gd")
var _card_class = preload("res://card.gd")
# Library cards are represented as an array of card IDs.
var lib_cards: Array[String]
var num_cards: int = 0
var lib_cards
var num_cards
func _load_card_callback(card) -> void:
card.load_card()
func _init(_decklist: Dictionary) -> void:
func _init(card_ids: Array) -> void:
lib_cards = Array()
for card in _decklist:
var _num = _decklist[card]
num_cards += _num
for i in _num:
lib_cards.push_back(card)
var temp_card
for id in card_ids:
temp_card = _card_class.new(id)
temp_card.cache_done.connect(_load_card_callback.bind(temp_card))
lib_cards.push_back(temp_card)
num_cards += 1
func add_cards(cards: Array, top: bool) -> void:

View File

@ -1,36 +1,34 @@
extends Node2D
var _card_class = preload("res://scenes/card/card.tscn")
var _card_class = preload("res://card.gd")
var field_scene = preload("res://field.tscn")
var fields: Array[Node] = []
var card
var decks: Array[Dictionary]
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.
func _ready() -> void:
# The first field in the array will be the player's own field.
# Might be a better idea to have that in a seperate variable? idk
var card = _card_class.instantiate()
card.init("d3f10f07-7cfe-4a6f-8de6-373e367a731b")
# TODO: Create 2-4 player instances as children of this tabletop node.
card = _card_class.new("d3f10f07-7cfe-4a6f-8de6-373e367a731b")
add_child(card)
card.position = Vector2(100, 100)
# 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.cache_done.connect(_test_func)
#fields.append(field_scene.instantiate())
#var colors: Array[Color] = [Color(1, 0, 1)]
#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.
func _process(_delta: float) -> void:
func _process(delta: float) -> void:
pass

View File

@ -1,21 +1,22 @@
[gd_scene load_steps=5 format=3 uid="uid://cx0vga81xwckh"]
[gd_scene load_steps=4 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)
[ext_resource type="Script" uid="uid://b3yqd1qu7dyq" path="res://card.gd" id="3_i3pqv"]
[node name="Player" type="Node2D"]
script = ExtResource("1_4flbx")
[node name="Hand" type="StaticBody2D" parent="."]
script = ExtResource("2_i3pqv")
[node name="Field" type="Node2D" parent="."]
[node name="CollisionArea" type="CollisionShape2D" parent="Hand"]
position = Vector2(960, 980)
shape = SubResource("RectangleShape2D_onrkg")
[node name="Hand" type="Node2D" parent="."]
[node name="Library" type="Node2D" parent="."]
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

@ -15,10 +15,6 @@ run/main_scene="uid://b4ldtb3gw0jlu"
config/features=PackedStringArray("4.4", "Forward Plus")
config/icon="res://icon.svg"
[autoload]
EventBus="*res://event_bus.gd"
[display]
window/size/viewport_width=1920
@ -28,16 +24,3 @@ window/size/mode=3
[editor_plugins]
enabled=PackedStringArray()
[input]
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)
]
}
SELECT={
"deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}

View File

@ -1,72 +0,0 @@
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.
# Card information.
var card_info: Dictionary
var cached_image: Image
# Card properties.
var tapped: bool
# Card input state.
var hovered: bool # Is the mouse currently on this card?
var dragging: bool # Is the card currently being dragged?
var focused: bool # Is this card currently a focus?
var mouse_offset: Vector2
func init(id: String) -> void:
card_info["id"] = id
# This is called when we want to apply the behaviour of the mouse being
# inside/outside the card when we can't trigger the enter/exit triggers.
func check_hover() -> void:
if hovered:
_on_mouse_entered()
else:
_on_mouse_exited()
func error(error_type: String) -> String:
return "ERROR::CARD::%s::%s::%s::\n" % [card_info["id"], card_info["name"], error_type]
func colission_size() -> Vector2:
return $Area2D/CollisionShape2D.shape.size
func _physics_process(delta: float) -> void:
focused = hovered or dragging
$InputHandler.handle_inputs(delta)
$TweenController.handle_constant_tweens(delta)
func _on_mouse_entered() -> void:
hovered = true
# Do not apply any more effects if we're dragging the card.
if dragging:
return
Input.set_default_cursor_shape(Input.CURSOR_POINTING_HAND)
$TweenController.scale(1.05)
EventBus.emit_signal("card_on_hover", card_info, cached_image)
func _on_mouse_exited() -> void:
hovered = false
# Do not apply any more effects if we're dragging the card.
if dragging:
return
Input.set_default_cursor_shape(Input.CURSOR_ARROW)
$TweenController.scale(1.0)
EventBus.emit_signal("card_on_unhover")

View File

@ -1,31 +0,0 @@
[gd_scene load_steps=6 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"]
[ext_resource type="Script" uid="uid://dhgk6fhw8oua0" path="res://scenes/card/input.gd" id="3_vtcvk"]
[ext_resource type="Script" uid="uid://vckbno504iay" path="res://scenes/card/load.gd" id="4_g65cd"]
[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="Node" parent="."]
script = ExtResource("2_imta7")
[node name="InputHandler" type="Node" parent="."]
script = ExtResource("3_vtcvk")
[node name="DataLoader" type="Node" parent="."]
script = ExtResource("4_g65cd")
[connection signal="mouse_entered" from="Area2D" to="." method="_on_mouse_entered"]
[connection signal="mouse_exited" from="Area2D" to="." method="_on_mouse_exited"]

View File

@ -1,27 +0,0 @@
extends Node
var card: Node
var tween_controller: Node
func _ready() -> void:
card = get_parent()
tween_controller = card.get_node("TweenController")
func handle_inputs(delta: float) -> void:
if not card.focused:
# TODO: Global card actions, e.g. untapping everything.
return
if Input.is_action_just_pressed("MAIN"):
card.tapped = not card.tapped
tween_controller.tap(card.tapped, delta)
if Input.is_action_just_pressed("SELECT"):
card.dragging = true
Input.set_default_cursor_shape(Input.CURSOR_DRAG)
card.mouse_offset = card.get_global_mouse_position() - card.global_position
if Input.is_action_just_released("SELECT"):
card.dragging = false
card.check_hover()

View File

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

View File

@ -1,83 +0,0 @@
extends Node
var card: Node
func _ready() -> void:
card = get_parent()
if _load_card() != OK:
# 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 _load_card() -> Error:
if _load_data() != OK:
return FAILED
if _load_image() != OK:
return FAILED
return OK
func _load_data() -> Error:
var cached_json = FileAccess.get_file_as_string(
"user://card_cache/" + card.card_info["id"] + "/card.json"
)
if cached_json.is_empty():
push_error("%s\nCard json data was not found in cache" % card.error("CACHE"))
return FAILED
var card_json = JSON.parse_string(cached_json)
if card_json == null:
push_error("%s\nCard json data is could not be parsed as valid json" % card.error("DATA"))
return FAILED
card.card_info["name"] = card_json["name"]
card.card_info["type"] = card_json["type_line"]
card.card_info["desc"] = card_json["oracle_text"]
card.card_info["cost"] = card_json["mana_cost"]
return OK
func _load_image() -> Error:
var cached_img = FileAccess.get_file_as_bytes(
"user://card_cache/" + card.card_info["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 cache_image = Image.new()
var image_status: Error = cache_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
card.cached_image = cache_image
var image = Image.new()
image_status = 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
var size = card.colission_size()
image.resize(int(size.x), int(size.y), Image.INTERPOLATE_LANCZOS)
var image_texture = ImageTexture.new()
image_texture.set_image(image)
var card_sprite = card.get_node("Sprite2D")
card_sprite.texture = image_texture
return OK

View File

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

View File

@ -1,35 +0,0 @@
extends Node
@export var move_speed = 1.0
@export var tap_speed = 5.0
@export var scale_speed = 0.1
var card: Node
# TODO: Figure out elastic tween transitions for bounciness.
func handle_constant_tweens(delta: float) -> void:
if card.dragging:
move_to(card.get_global_mouse_position() - card.mouse_offset, delta)
func move_to(location: Vector2, delta: float) -> void:
var tween = create_tween()
tween.tween_property(card, "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(card, "rotation_degrees", rotation, delta * tap_speed)
func scale(scalar: float) -> void:
var tween = create_tween()
var new_scale = Vector2.ONE * scalar
tween.tween_property(card, "scale", new_scale, scale_speed)
func _ready() -> void:
card = get_parent()

View File

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

View File

@ -1,17 +0,0 @@
extends TextureRect
func _set_tip_image(_card_info: Dictionary, card_image: Image) -> void:
card_image.resize(int(size.x / 1.75), int(size.y), Image.INTERPOLATE_LANCZOS)
var tex = ImageTexture.new()
tex.set_image(card_image)
texture = tex
func _clear_tip_image() -> void:
texture = null
func _ready() -> void:
EventBus.connect("card_on_hover", _set_tip_image)
EventBus.connect("card_on_unhover", _clear_tip_image)

View File

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

View File

@ -1,39 +0,0 @@
extends RichTextLabel
var mana_symbols: Dictionary
func _convert_text_to_symbol(_text: String):
var last_idx = 0
for symbol in mana_symbols:
last_idx = 0
while _text.find(symbol, last_idx) != -1:
_text = _text.replace(symbol, "[img]" + mana_symbols[symbol] + "[/img]")
last_idx = _text.find(symbol, last_idx) + symbol.length()
return _text
func _set_tip_text(card_info: Dictionary, _card_image: Image) -> void:
# TODO: add more card formatting, check all of the logos, very niche icons will be affected i believe since they're
# different sizes
# shrink text if we use too much space for it, etc
text = "[b]" + card_info["name"] + "[/b]\t"
text += _convert_text_to_symbol(card_info["cost"]) + "\n"
text += "[i]" + card_info["type"] + "[/i]\n"
text += _convert_text_to_symbol(card_info["desc"])
func _clear_tip_text() -> void:
text = ""
func _ready() -> void:
if !FileAccess.file_exists("res://symbol_cache/symbols.json"):
push_error("Symbols haven't been cached yet!")
return
var file = FileAccess.open("res://symbol_cache/symbols.json", FileAccess.READ)
mana_symbols = JSON.parse_string(file.get_as_text())
file.close()
EventBus.connect("card_on_hover", _set_tip_text)
EventBus.connect("card_on_unhover", _clear_tip_text)

View File

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

View File

@ -1,125 +1,18 @@
extends Node2D
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.
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.
var player = player_class.new()
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.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta: float) -> void:
func _process(delta: float) -> void:
pass

View File

@ -1,65 +1,6 @@
[gd_scene load_steps=7 format=3 uid="uid://b4ldtb3gw0jlu"]
[gd_scene load_steps=2 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://b8tioen4n1rip" path="res://scenes/tooltip/card_text.gd" id="2_d43bn"]
[ext_resource type="Script" uid="uid://cpvbftm0swoa6" path="res://scenes/tooltip/card_image.gd" id="2_pqag1"]
[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"]
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="VBoxContainer" type="VBoxContainer" parent="UI/BigBar/Items/MenuArea"]
layout_mode = 2
[node name="TextureRect" type="TextureRect" parent="UI/BigBar/Items/MenuArea/VBoxContainer"]
custom_minimum_size = Vector2(400, 300)
layout_direction = 2
layout_mode = 2
stretch_mode = 5
script = ExtResource("2_pqag1")
[node name="RichTextLabel" type="RichTextLabel" parent="UI/BigBar/Items/MenuArea/VBoxContainer"]
custom_minimum_size = Vector2(0, 230)
layout_mode = 2
bbcode_enabled = true
fit_content = true
script = ExtResource("2_d43bn")
[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")