Compare commits

64 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
3c02ae63f9 organizes card scene, exports tween functionality to its own node and script 2025-04-27 22:49:45 -04:00
ce0bc104ff modifications to the card scene to make it sort of work with its new nodes 2025-04-27 22:02:36 -04:00
7cddb502b4 wip: reworking cards to use sprites and fixing up some stuff related to them 2025-04-27 20:05:28 -04:00
d1ef1d6f4f fixes tapping while dragging issue 2025-04-27 19:02:18 -04:00
fc3a31d144 converts file = null to file.close() 2025-04-27 17:46:06 -04:00
1d9c3fc473 fixes caching not working properly 2025-04-27 17:27:39 -04:00
5937b4db69 cleans up some code 2025-04-27 13:14:56 -04:00
9217834726 fixes tapped cards having a weird drag offset 2025-04-27 13:08:15 -04:00
c463bf0adc another print 2025-04-27 13:03:49 -04:00
98357b5dec removes prints 2025-04-27 13:00:53 -04:00
1beb89ea84 adds tapping/untapping 2025-04-27 12:55:09 -04:00
dd73e1b477 removes print statements 2025-04-27 12:23:40 -04:00
9bb3308487 guts the field code and allows for card dragging 2025-04-27 12:22:39 -04:00
90912c062f deck list importing (mtgo) is now working 2025-04-24 20:55:09 -04:00
b82f5e4c19 adds some documentation 2025-04-24 18:43:26 -04:00
fe38fc49bf completely redoes the caching api 2025-04-24 18:25:28 -04:00
a40ccab3c8 deck input changes 2025-04-24 13:20:57 -04:00
f5edba7402 some small fixes, mtgo is done 2025-04-24 11:45:29 -04:00
b9a07a8c47 adds basic ui template 2025-04-24 01:47:24 -04:00
0f88bd8f7b uses lanczos interpolation to make small cards not look terrible 2025-04-23 22:27:04 -04:00
c75aec06ac Merge remote-tracking branch 'refs/remotes/origin/main' 2025-04-23 22:20:17 -04:00
d726290cf2 adds scryfall required delay 2025-04-23 22:20:06 -04:00
fafaf404ab rudimentary card loading 2025-04-23 22:04:04 -04:00
77da7cf6b2 wip: adding visual representation, starting with player battlefield 2025-04-23 18:05:38 -04:00
21f7d9de04 cleans up more tech debt 2025-04-23 17:08:38 -04:00
6100a50754 cleans up nonsense from card.gd 2025-04-23 17:06:55 -04:00
bf9244c2b7 calls load_card 2025-04-23 17:06:28 -04:00
a3fb627310 separates all of the cacheing functions to its own class, cleans up card 2025-04-23 17:05:10 -04:00
c085a93c49 forgot to add this 2025-04-23 16:43:09 -04:00
29042d5d05 Changes project to fullscreen 1920x1080, formats .gd files 2025-04-23 16:12:00 -04:00
4489eb5f76 adds some library boilerplate 2025-04-23 15:44:17 -04:00
de5e107b86 adds a signal for completion, card should be done 2025-04-23 13:05:47 -04:00
5fc67ab5e5 documents the public facing function: 2025-04-22 19:02:26 -04:00
05cdcf83cc finishes up the base card stuff 2025-04-22 18:59:24 -04:00
37975021cb converts card cache getting to a coroutine function 2025-04-22 18:49:46 -04:00
a3dfa96efd scryfall requests 2025-04-22 17:16:38 -04:00
651b267dd2 changes... 2025-04-22 16:39:59 -04:00
970d7064ed adds some documentation 2025-04-22 14:09:16 -04:00
3808955237 adds quick comment and some code for the images 2025-04-22 13:48:21 -04:00
fcf736af92 wait we can just finish the card cache check rn 2025-04-22 13:45:45 -04:00
a67f2e1096 adds some baseline code for loading cards 2025-04-22 13:41:33 -04:00
c990b68e07 Adds some nodes, notably the root tabletop scene and a scene representing a player along with nodes for the hand, local field, and library 2025-04-22 02:30:17 -04:00
ddd6c15292 Godot initialization 2025-04-22 01:34:16 -04:00
56 changed files with 1146 additions and 13170 deletions

4
.editorconfig Normal file
View File

@ -0,0 +1,4 @@
root = true
[*]
charset = utf-8

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

11
.gitignore vendored
View File

@ -1,7 +1,6 @@
# Build artifacts
build/
# Direnv stuff for the flake
.envrc
.direnv
# Godot 4+ specific ignores
.godot/
/android/
# custom ignores
symbol_cache/

View File

@ -1,33 +0,0 @@
CC=g++
CFLAGS= -c -g -Wall
LDLIBS = -lglfw
TARGET := untap
BUILD_DIR := ./build
SRC_DIRS := ./src
SRCS := $(shell find $(SRC_DIRS) -name '*.cpp')
SRCS += $(shell find $(SRC_DIRS) -name '*.c')
OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)
$(BUILD_DIR)/$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $@ $(LDLIBS)
cp -r $(SRC_DIRS)/shaders $(BUILD_DIR)/
cp -r $(SRC_DIRS)/textures $(BUILD_DIR)/
$(BUILD_DIR)/%.cpp.o: %.cpp
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR)/%.c.o: %.c
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -rf $(BUILD_DIR)

294
caching.gd Normal file
View File

@ -0,0 +1,294 @@
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!"
)

1
caching.gd.uid Normal file
View File

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

2
data/consts.gd Normal file
View File

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

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

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

1
data/mana.gd Normal file
View File

@ -0,0 +1 @@
enum ManaCosts { WHITE, BLUE, BLACK, RED, GREEN, COLOURLESS, GENERIC, LIFE }

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

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

73
deck_input.gd Normal file
View File

@ -0,0 +1,73 @@
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
View File

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

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

44
field.gd Normal file
View File

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

1
field.gd.uid Normal file
View File

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

9
field.tscn Normal file
View File

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

61
flake.lock generated
View File

@ -1,61 +0,0 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1744868846,
"narHash": "sha256-5RJTdUHDmj12Qsv7XOhuospjAjATNiTMElplWnJE9Hs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ebe4301cbd8f81c4f8d3244b3632338bbeb6d49c",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@ -1,30 +0,0 @@
{
description = "Template node project";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = {
self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system: let
pkgs = import nixpkgs {
inherit system;
};
in {
devShell = pkgs.mkShell {
buildInputs = with pkgs; [
gnumake
glfw
pkg-config
glib.dev
];
};
}
);
}

15
hand.gd Normal file
View File

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

1
hand.gd.uid Normal file
View File

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

1
icon.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 994 B

37
icon.svg.import Normal file
View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://nrgr3sfkdosx"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

43
library.gd Normal file
View File

@ -0,0 +1,43 @@
extends Node2D
var _card_class = preload("res://scenes/card/card.gd")
# Library cards are represented as an array of card IDs.
var lib_cards: Array[String]
var num_cards: int = 0
func _load_card_callback(card) -> void:
card.load_card()
func _init(_decklist: Dictionary) -> void:
lib_cards = Array()
for card in _decklist:
var _num = _decklist[card]
num_cards += _num
for i in _num:
lib_cards.push_back(card)
func add_cards(cards: Array, top: bool) -> void:
for card in cards:
add_card(card, top)
func add_card(card, top: bool) -> void:
if top:
lib_cards.push_front(card)
else:
lib_cards.push_back(card)
func shuffle() -> void:
lib_cards.shuffle()
func draw_cards(num) -> Array:
var ret = Array()
for i in num:
ret.push_back(lib_cards.pop_front())
return ret

1
library.gd.uid Normal file
View File

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

36
player.gd Normal file
View File

@ -0,0 +1,36 @@
extends Node2D
var _card_class = preload("res://scenes/card/card.tscn")
var field_scene = preload("res://field.tscn")
var fields: Array[Node] = []
var decks: Array[Dictionary]
# 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")
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.
#fields.append(field_scene.instantiate())
#var colors: Array[Color] = [Color(1, 0, 1)]
#fields[0].set_colors(colors)
#add_child(fields[0])
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta: float) -> void:
pass

1
player.gd.uid Normal file
View File

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

21
player.tscn Normal file
View File

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

43
project.godot Normal file
View File

@ -0,0 +1,43 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="mtg-tabletop"
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
window/size/viewport_height=1080
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)
]
}

72
scenes/card/card.gd Normal file
View File

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

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

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

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

@ -0,0 +1,31 @@
[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"]

27
scenes/card/input.gd Normal file
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()

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

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

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

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

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

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

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

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

1140
src/glad.c

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,114 +0,0 @@
#ifndef SHADER_H
#define SHADER_H
#include "glad.h"
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
class Shader
{
public:
// program ID
unsigned int ID;
// constructor reads and builds the shader
Shader(const char* vertexPath, const char* fragmentPath)
{
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
// setup exceptions for bad files
vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try {
// open files
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
// read file buffers into streams
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
// close file handlers
vShaderFile.close();
fShaderFile.close();
// convert stream into string
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
} catch (std::ifstream::failure& e) {
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ:" << e.what() << "\n";
}
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
unsigned int vertex, fragment;
int success;
char infoLog[512];
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(vertex, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << "\n";
}
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(fragment, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << "\n";
}
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
glLinkProgram(ID);
glGetProgramiv(ID, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(ID, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << "\n";
}
// delete shaders, no longer needed
glDeleteShader(vertex);
glDeleteShader(fragment);
}
// use/activate the shader
void use()
{
glUseProgram(ID);
}
// utility uniform functions
void setBool(const std::string &name, bool value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), (int) value);
}
void setInt(const std::string &name, int value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}
void setFloat(const std::string &name, float value) const
{
glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}
};
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,157 +0,0 @@
#include "include/glad.h"
#include "include/shader.h"
#include "include/stb_image.h"
#include <GLFW/glfw3.h>
#include <iostream>
#include <math.h>
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
void setupGLFW(GLFWwindow** window);
typedef struct {
int width, height, nrChannels;
unsigned char* data;
} texture_t;
float vertices[] = {
// positions colours texture coords
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top right
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // bottom left
};
unsigned int indices[] = {
0, 1, 3, 1, 2, 3,
};
bool wireframe = false;
int main()
{
GLFWwindow* window;
setupGLFW(&window);
Shader myShader("shaders/shader.vs", "shaders/shader.fs");
texture_t tex;
stbi_set_flip_vertically_on_load(true);
tex.data = stbi_load("textures/wg.jpg", &tex.width, &tex.height, &tex.nrChannels, 0);
unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
if (tex.data) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tex.width, tex.height, 0, GL_RGB, GL_UNSIGNED_BYTE, tex.data);
glGenerateMipmap(GL_TEXTURE_2D);
} else {
std::cout << "Failed to load texture\n";
}
stbi_image_free(tex.data);
unsigned int VAO;
glGenVertexArrays(1, &VAO);
unsigned int VBO;
glGenBuffers(1, &VBO);
unsigned int EBO;
glGenBuffers(1, &EBO);
// bind Vertex Array Object
glBindVertexArray(VAO);
// copy our vertices into a buffer for opengl to use
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// copy our indices
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// set the vertex attributes pointers
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
myShader.use();
myShader.setInt("tex", 0);
while (!glfwWindowShouldClose(window)) {
// input
processInput(window);
// rendering
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
myShader.use();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0); // unbinds the VAO so its ready to be bound again for the next render
// check and call events and swap buffers
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}
void setupGLFW(GLFWwindow** window)
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
*window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL) {
std::cout << "Failed to create GLFW window\n";
glfwTerminate();
exit(-1);
}
glfwMakeContextCurrent(*window);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "Failed to init GLAD\n";
exit(-1);
}
glViewport(0, 0, 800, 600);
glfwSetFramebufferSizeCallback(*window, framebuffer_size_callback);
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
if (glfwGetKey(window, GLFW_KEY_ENTER) == GLFW_PRESS) // triggers wireframe mode
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
if (glfwGetKey(window, GLFW_KEY_BACKSPACE) == GLFW_PRESS) // disables wireframe mode
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}

View File

@ -1,13 +0,0 @@
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
uniform sampler2D tex;
void main()
{
FragColor = texture(tex, TexCoord);
}

View File

@ -1,15 +0,0 @@
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;
out vec3 ourColor;
out vec2 TexCoord;
void main()
{
gl_Position = vec4(aPos, 1.0);
ourColor = aColor;
TexCoord = aTexCoord;
}

View File

@ -1,2 +0,0 @@
#define STB_IMAGE_IMPLEMENTATION
#include "include/stb_image.h"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

125
tabletop.gd Normal file
View File

@ -0,0 +1,125 @@
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")
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta: float) -> void:
pass

1
tabletop.gd.uid Normal file
View File

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

65
tabletop.tscn Normal file
View File

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