Compare commits

..

21 Commits

Author SHA1 Message Date
1a5372f713 makes the tooltip expand to fit text if its long 2025-04-29 12:39:09 -04:00
c125d1b70a fixes missing merge conflict 2025-04-29 07:04:15 -04:00
bd5b734d79 fixes merge conflicts 2025-04-29 07:03:03 -04:00
31175b4ecd adds mana cost to the card tooltip 2025-04-29 07:01:46 -04:00
76a23aab5c formatting 2025-04-29 00:24:42 -04:00
6eb80f768b remove unused var 2025-04-28 20:28:43 -04:00
1fe7500dd2 adds better formatting + adds some TODOs 2025-04-28 20:21:45 -04:00
1c5c4728a3 finishes adding mana symbols into tooltip text 2025-04-28 20:00:26 -04:00
75767e927d adds images in the tooltip.. sort of 2025-04-28 19:23:54 -04:00
c684a00f3a adds a lot holy crap i forgot to commit 2025-04-28 18:14:41 -04:00
a496bb3982 hides useless warnings 2025-04-28 16:31:30 -04:00
a83261cf09 adds globale event bus, and tooltip hover trigger 2025-04-28 11:35:25 -04:00
32bf3be0cd converts card info to a dict 2025-04-28 11:12:35 -04:00
999c7989f3 cleans up some compiler warnings 2025-04-28 10:53:15 -04:00
a7aad7d99f exports card loading handling into its own node and script 2025-04-28 02:48:46 -04:00
2f5185d22c exports card input handling to seperate node and script 2025-04-28 02:34:41 -04:00
933e6b715c refines card movement 2025-04-28 01:54:10 -04:00
45919dc5ae formatting 2025-04-28 00:59:02 -04:00
709859abad fixes some small merge issues 2025-04-28 00:58:04 -04:00
04ae9b856b fixes merge conflicts 2025-04-27 23:51:36 -04:00
86cc3bf8a8 fixes dual cards not properly loading! 2025-04-27 23:36:33 -04:00
20 changed files with 515 additions and 168 deletions

3
.gitignore vendored
View File

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

View File

@ -32,6 +32,8 @@ func setup() -> Error:
push_error("Not done downloading Bulk Data.") push_error("Not done downloading Bulk Data.")
return FAILED return FAILED
_fetch_mana_symbols()
_setup_cache_in_mem() _setup_cache_in_mem()
return OK return OK
@ -73,7 +75,7 @@ func _get_dict_from_file(filepath: String) -> Dictionary:
## _name: String [br] ## _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. ## 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: func get_card_data_from_name(_name: String) -> Dictionary:
return _get_card_data_from_bulk("name", _name) return _get_card_data_from_bulk(_search_results_name(_name))
## get_card_data_from_id ## get_card_data_from_id
@ -83,36 +85,123 @@ func get_card_data_from_name(_name: String) -> Dictionary:
func get_card_data_from_id(id: String) -> Dictionary: func get_card_data_from_id(id: String) -> Dictionary:
if FileAccess.file_exists("user://card_cache/" + id + "/card.json"): if FileAccess.file_exists("user://card_cache/" + id + "/card.json"):
return _get_dict_from_file("user://card_cache/" + id + "/card.json") return _get_dict_from_file("user://card_cache/" + id + "/card.json")
return _get_card_data_from_bulk("id", id)
return _get_card_data_from_bulk(_search_results_generic("id", id))
func _get_card_data_from_bulk(field: String, search_query: String) -> Dictionary: func _search_results_name(search_query: String) -> Dictionary:
var selected_entry = null
for entry in _bulk_data: for entry in _bulk_data:
if entry[field] == search_query: if entry["layout"] == "art_series":
selected_entry = entry
break
continue continue
var entry_name = entry["name"]
if selected_entry == null: 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 {} return {}
if selected_entry["image_status"] != "missing":
_fetch_card_img(selected_entry) 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://") var dir = DirAccess.open("user://")
dir.make_dir_recursive("user://card_cache/" + selected_entry["id"] + "/") dir.make_dir_recursive("user://card_cache/" + dict_entry["id"] + "/")
dir = null dir = null
var file = FileAccess.open( var file = FileAccess.open(
"user://card_cache/" + selected_entry["id"] + "/card.json", FileAccess.WRITE "user://card_cache/" + dict_entry["id"] + "/card.json", FileAccess.WRITE
) )
file.store_line(JSON.stringify(selected_entry, "\t")) file.store_line(JSON.stringify(dict_entry, "\t"))
file.close() file.close()
print("Card: " + selected_entry["name"] + "(" + selected_entry["id"] + ") found, and cached.") print("Card: " + dict_entry["name"] + " (" + dict_entry["id"] + ") found, and cached.")
return selected_entry 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: func _fetch_card_img(data: Dictionary) -> Error:
@ -153,7 +242,6 @@ func get_bulk_data(force: bool) -> Error:
DirAccess.remove_absolute("user://bulk.json") DirAccess.remove_absolute("user://bulk.json")
else: else:
return OK return OK
var httpr = HTTPRequest.new() var httpr = HTTPRequest.new()
add_child(httpr) add_child(httpr)

View File

@ -4,14 +4,29 @@ var _caching = preload("res://caching.gd")
var _decklist var _decklist
var cards: String
func _init() -> void:
func _init(_cards: String) -> void:
cards = _cards
_decklist = Dictionary() _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: func _convert_mtgo_to_cache_lookup(decklist: String) -> Dictionary:
var _cards = {} var _cards = {}
var lines = decklist.split("\n") var lines = decklist.split("\n")
@ -30,23 +45,29 @@ func _do_free(cache) -> void:
cache.queue_free() cache.queue_free()
func do_decklist_grab(decklist: String) -> void: 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() var cache = _caching.new()
add_child(cache) add_child(cache)
cache.setup()
var queries = _convert_mtgo_to_cache_lookup(decklist) for query in _queries:
for query in queries:
var entry = cache.get_card_data_from_name(query) var entry = cache.get_card_data_from_name(query)
_decklist[entry["id"]] = queries[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)) cache.fetch_done.connect(_do_free.bind(cache))
func _show_decklist() -> void: func _show_decklist() -> void:
for card in _decklist: for card in _decklist:
print(card + " : " + _decklist[card]) print(card + " : " + _decklist[card])
func _ready() -> void:
do_decklist_grab(cards)
_show_decklist()

6
event_bus.gd Normal file
View File

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

1
event_bus.gd.uid Normal file
View File

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

View File

@ -1,6 +1,6 @@
extends Node2D extends Node2D
var _card_class = preload("res://card.gd") var _card_class = preload("res://scenes/card/card.gd")
# Library cards are represented as an array of card IDs. # Library cards are represented as an array of card IDs.
var lib_cards: Array[String] var lib_cards: Array[String]

View File

@ -8,27 +8,22 @@ var fields: Array[Node] = []
var decks: Array[Dictionary] var decks: Array[Dictionary]
func _load_decks():
if !FileAccess.file_exists("user://decks.json"):
return # no loaded decks
var file = FileAccess.open("user://decks.json", FileAccess.READ)
decks = JSON.parse_string(file.get_as_text())
file.close()
# Called when the node enters the scene tree for the first time. # Called when the node enters the scene tree for the first time.
func _ready() -> void: func _ready() -> void:
# The first field in the array will be the player's own field. # 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 # Might be a better idea to have that in a seperate variable? idk
var card = _card_class.instantiate() var card = _card_class.instantiate()
card.init("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. # 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. # 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)
#fields.append(field_scene.instantiate()) #fields.append(field_scene.instantiate())
#var colors: Array[Color] = [Color(1, 0, 1)] #var colors: Array[Color] = [Color(1, 0, 1)]

View File

@ -15,6 +15,10 @@ run/main_scene="uid://b4ldtb3gw0jlu"
config/features=PackedStringArray("4.4", "Forward Plus") config/features=PackedStringArray("4.4", "Forward Plus")
config/icon="res://icon.svg" config/icon="res://icon.svg"
[autoload]
EventBus="*res://event_bus.gd"
[display] [display]
window/size/viewport_width=1920 window/size/viewport_width=1920
@ -32,3 +36,8 @@ MAIN={
"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) "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

@ -5,140 +5,68 @@ extends Node2D
## 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.
# Card information. # Card information.
var card_id: String var card_info: Dictionary
var card_name: String var cached_image: Image
var card_type: String
var oracle_text: String
# Card properties. # Card properties.
var tapped: bool var tapped: bool
# Card input state. # Card input state.
var is_focused: bool # Is the card a focus? var hovered: bool # Is the mouse currently on this card?
var is_dragging: bool # Is the card currently being dragged? var dragging: bool # Is the card currently being dragged?
var focused: bool # Is this card currently a focus?
var mouse_offset: Vector2 var mouse_offset: Vector2
func _physics_process(delta: float) -> void: func init(id: String) -> void:
if is_focused: card_info["id"] = id
# TODO: Export handling keypresses to its own area.
if Input.is_action_just_pressed("MAIN"):
tapped = not tapped
$TweenController.tap(tapped, delta)
if is_dragging:
$TweenController.move_to(get_global_mouse_position() - mouse_offset, delta)
func _on_input_event(viewport: Node, event: InputEvent, shape_idx: int) -> void: # This is called when we want to apply the behaviour of the mouse being
if event is not InputEventMouseButton: # inside/outside the card when we can't trigger the enter/exit triggers.
return func check_hover() -> void:
if hovered:
match event.button_index: _on_mouse_entered()
# MOUSE BUTTONS
MOUSE_BUTTON_LEFT:
if event.pressed:
Input.set_default_cursor_shape(Input.CURSOR_DRAG)
mouse_offset = get_global_mouse_position() - global_position
is_dragging = true
else: else:
Input.set_default_cursor_shape(Input.CURSOR_POINTING_HAND) _on_mouse_exited()
is_dragging = false
MOUSE_BUTTON_RIGHT:
# TODO: Tooltip menu for right-button clicking on cards. func error(error_type: String) -> String:
pass 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: func _on_mouse_entered() -> void:
# Do not care about mouse entering if we're dragging the card. hovered = true
if is_dragging:
# Do not apply any more effects if we're dragging the card.
if dragging:
return return
Input.set_default_cursor_shape(Input.CURSOR_POINTING_HAND) Input.set_default_cursor_shape(Input.CURSOR_POINTING_HAND)
$TweenController.scale(1.05) $TweenController.scale(1.05)
is_focused = true EventBus.emit_signal("card_on_hover", card_info, cached_image)
func _on_mouse_exited() -> void: func _on_mouse_exited() -> void:
# Do not care about mouse exiting if we're dragging the card. hovered = false
if is_dragging:
# Do not apply any more effects if we're dragging the card.
if dragging:
return return
Input.set_default_cursor_shape(Input.CURSOR_ARROW) Input.set_default_cursor_shape(Input.CURSOR_ARROW)
$TweenController.scale(1.0) $TweenController.scale(1.0)
is_focused = false EventBus.emit_signal("card_on_unhover")
func _card_error(error_type: String) -> String:
return "ERROR::CARD::%s::%s::%s::\n" % [card_id, card_name, error_type]
func init(id: String) -> void:
card_id = id
func _ready() -> void:
var load_status = _load_card()
if load_status != 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_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_name = card_json["name"]
card_type = card_json["type_line"]
oracle_text = card_json["oracle_text"]
return OK
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
var size = $Area2D/CollisionShape2D.shape.size
image.resize(int(size.x), int(size.y), Image.INTERPOLATE_LANCZOS)
var image_texture = ImageTexture.new()
image_texture.set_image(image)
$Sprite2D.texture = image_texture
return OK

View File

@ -1,7 +1,9 @@
[gd_scene load_steps=4 format=3 uid="uid://cah3mvdnom1xg"] [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://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://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"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_kikvd"]
size = Vector2(125, 175) size = Vector2(125, 175)
@ -16,9 +18,14 @@ script = ExtResource("1_kikvd")
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] [node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"]
shape = SubResource("RectangleShape2D_kikvd") shape = SubResource("RectangleShape2D_kikvd")
[node name="TweenController" type="Node2D" parent="."] [node name="TweenController" type="Node" parent="."]
script = ExtResource("2_imta7") script = ExtResource("2_imta7")
[connection signal="input_event" from="Area2D" to="." method="_on_input_event"] [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_entered" from="Area2D" to="." method="_on_mouse_entered"]
[connection signal="mouse_exited" from="Area2D" to="." method="_on_mouse_exited"] [connection signal="mouse_exited" from="Area2D" to="." method="_on_mouse_exited"]

View File

@ -0,0 +1,27 @@
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()

83
scenes/card/load.gd Normal file
View File

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

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

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

View File

@ -4,19 +4,32 @@ extends Node
@export var tap_speed = 5.0 @export var tap_speed = 5.0
@export var scale_speed = 0.1 @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: func move_to(location: Vector2, delta: float) -> void:
var tween = create_tween() var tween = create_tween()
tween.tween_property(get_parent(), "position", location, delta * move_speed) tween.tween_property(card, "position", location, delta * move_speed)
func tap(tapped: bool, delta: float) -> void: func tap(tapped: bool, delta: float) -> void:
var tween = create_tween() var tween = create_tween()
var rotation = 90 if tapped else 0 var rotation = 90 if tapped else 0
tween.tween_property(get_parent(), "rotation_degrees", rotation, delta * tap_speed) tween.tween_property(card, "rotation_degrees", rotation, delta * tap_speed)
func scale(scalar: float) -> void: func scale(scalar: float) -> void:
var tween = create_tween() var tween = create_tween()
var new_scale = Vector2.ONE * scalar var new_scale = Vector2.ONE * scalar
tween.tween_property(get_parent(), "scale", new_scale, scale_speed) tween.tween_property(card, "scale", new_scale, scale_speed)
func _ready() -> void:
card = get_parent()

View File

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

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

View File

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

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

View File

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

View File

@ -1,6 +1,8 @@
[gd_scene load_steps=5 format=3 uid="uid://b4ldtb3gw0jlu"] [gd_scene load_steps=7 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"]
[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"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3we3x"]
bg_color = Color(0, 0, 0, 1) bg_color = Color(0, 0, 0, 1)
@ -35,6 +37,23 @@ theme_override_constants/separation = 0
custom_minimum_size = Vector2(0, 540) custom_minimum_size = Vector2(0, 540)
layout_mode = 2 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"] [node name="ChatArea" type="PanelContainer" parent="UI/BigBar/Items"]
custom_minimum_size = Vector2(0, 270) custom_minimum_size = Vector2(0, 270)
layout_mode = 2 layout_mode = 2