Files
FFCardGame/scripts/autoload/CardDatabase.gd
2026-01-26 16:14:57 -05:00

297 lines
8.4 KiB
GDScript

extends Node
## CardDatabase - Singleton for managing card data
## Loads card definitions from JSON and provides lookup methods
const CARDS_PATH = "res://data/cards.json"
# Loaded card data
var _cards: Dictionary = {} # id -> CardData
var _cards_by_element: Dictionary = {} # Element -> Array[CardData]
var _cards_by_type: Dictionary = {} # CardType -> Array[CardData]
var _card_textures: Dictionary = {} # id -> Texture2D
# Signals
signal database_loaded
signal load_error(message: String)
func _ready() -> void:
_load_database()
func _load_database() -> void:
var file = FileAccess.open(CARDS_PATH, FileAccess.READ)
if not file:
push_error("Failed to open cards database: " + CARDS_PATH)
load_error.emit("Failed to open cards database")
return
var json_text = file.get_as_text()
file.close()
var json = JSON.new()
var error = json.parse(json_text)
if error != OK:
push_error("Failed to parse cards JSON: " + json.get_error_message())
load_error.emit("Failed to parse cards JSON")
return
var data = json.get_data()
if not data.has("cards"):
push_error("Cards database missing 'cards' array")
load_error.emit("Invalid database format")
return
# Initialize element and type dictionaries
for element in Enums.Element.values():
_cards_by_element[element] = []
for card_type in Enums.CardType.values():
_cards_by_type[card_type] = []
# Parse cards
for card_data in data["cards"]:
var card = _parse_card(card_data)
if card:
_cards[card.id] = card
# Index by element(s)
for element in card.elements:
_cards_by_element[element].append(card)
# Index by type
_cards_by_type[card.type].append(card)
print("CardDatabase: Loaded ", _cards.size(), " cards")
database_loaded.emit()
func _parse_card(data: Dictionary) -> CardData:
var card = CardData.new()
# Required fields
if not data.has("id") or not data.has("name") or not data.has("type") or not data.has("element") or not data.has("cost"):
push_error("Card missing required fields: " + str(data))
return null
card.id = data["id"]
card.name = data["name"]
card.type = Enums.card_type_from_string(data["type"])
card.cost = data["cost"]
# Parse element (can be string or array)
if data["element"] is Array:
for elem_str in data["element"]:
card.elements.append(Enums.element_from_string(elem_str))
else:
card.elements.append(Enums.element_from_string(data["element"]))
# Optional fields
card.power = data.get("power", 0) if data.get("power") != null else 0
card.job = data.get("job", "") if data.get("job") != null else ""
card.category = data.get("category", "") if data.get("category") != null else ""
card.is_generic = data.get("is_generic", false) if data.get("is_generic") != null else false
card.has_ex_burst = data.get("has_ex_burst", false) if data.get("has_ex_burst") != null else false
card.image_path = data.get("image", "") if data.get("image") != null else ""
# Parse abilities
if data.has("abilities"):
for ability_data in data["abilities"]:
var ability = _parse_ability(ability_data)
if ability:
card.abilities.append(ability)
return card
func _parse_ability(data: Dictionary) -> AbilityData:
var ability = AbilityData.new()
if not data.has("type") or not data.has("effect"):
push_error("Ability missing required fields")
return null
match data["type"].to_lower():
"field": ability.type = Enums.AbilityType.FIELD
"auto": ability.type = Enums.AbilityType.AUTO
"action": ability.type = Enums.AbilityType.ACTION
"special": ability.type = Enums.AbilityType.SPECIAL
ability.effect = data["effect"]
ability.name = data.get("name", "")
ability.trigger = data.get("trigger", "")
ability.is_ex_burst = data.get("is_ex_burst", false)
# Parse cost if present
if data.has("cost"):
ability.cost = _parse_cost(data["cost"])
return ability
func _parse_cost(data: Dictionary) -> CostData:
var cost = CostData.new()
cost.generic = data.get("generic", 0)
cost.fire = data.get("fire", 0)
cost.ice = data.get("ice", 0)
cost.wind = data.get("wind", 0)
cost.lightning = data.get("lightning", 0)
cost.water = data.get("water", 0)
cost.earth = data.get("earth", 0)
cost.light = data.get("light", 0)
cost.dark = data.get("dark", 0)
cost.requires_dull = data.get("dull", false)
cost.discard_count = data.get("discard", 0)
cost.specific_discard = data.get("specific_discard", "")
return cost
## Get a card by ID
func get_card(id: String) -> CardData:
return _cards.get(id)
## Get all cards
func get_all_cards() -> Array:
return _cards.values()
## Get cards by element
func get_cards_by_element(element: Enums.Element) -> Array:
return _cards_by_element.get(element, [])
## Get cards by type
func get_cards_by_type(card_type: Enums.CardType) -> Array:
return _cards_by_type.get(card_type, [])
## Get or load a card texture
func get_card_texture(card: CardData) -> Texture2D:
if card.id in _card_textures:
return _card_textures[card.id]
if card.image_path.is_empty():
return null
# Try source-cards directory first (primary location for card images)
var texture_path = "res://source-cards/" + card.image_path
if ResourceLoader.exists(texture_path):
var texture = load(texture_path)
_card_textures[card.id] = texture
return texture
# Fallback to assets/cards directory
texture_path = "res://assets/cards/" + card.image_path
if ResourceLoader.exists(texture_path):
var texture = load(texture_path)
_card_textures[card.id] = texture
return texture
return null
## Create a list of card IDs for a deck (for testing)
## Player 0 gets Fire/Ice deck, Player 1 gets Wind/Lightning deck
func create_test_deck(player_index: int) -> Array[String]:
var deck: Array[String] = []
# Define element pairs for each player
var primary_element: Enums.Element
var secondary_element: Enums.Element
if player_index == 0:
primary_element = Enums.Element.FIRE
secondary_element = Enums.Element.ICE
else:
primary_element = Enums.Element.WIND
secondary_element = Enums.Element.LIGHTNING
# Get cards by element
var primary_cards = get_cards_by_element(primary_element)
var secondary_cards = get_cards_by_element(secondary_element)
# Add 3 copies of each primary element card
for card in primary_cards:
for i in range(3):
if deck.size() < 50:
deck.append(card.id)
# Add 3 copies of each secondary element card
for card in secondary_cards:
for i in range(3):
if deck.size() < 50:
deck.append(card.id)
# If we still need cards, add from Earth/Water
if deck.size() < 50:
var filler_element = Enums.Element.EARTH if player_index == 0 else Enums.Element.WATER
var filler_cards = get_cards_by_element(filler_element)
for card in filler_cards:
for i in range(3):
if deck.size() < 50:
deck.append(card.id)
# Final fallback: add any cards
if deck.size() < 50:
var all_cards = get_all_cards()
for card in all_cards:
for i in range(3):
if deck.size() < 50:
deck.append(card.id)
return deck
## Data Classes
class CardData:
var id: String = ""
var name: String = ""
var type: Enums.CardType = Enums.CardType.FORWARD
var elements: Array[Enums.Element] = []
var cost: int = 0
var power: int = 0
var job: String = ""
var category: String = ""
var is_generic: bool = false
var has_ex_burst: bool = false
var image_path: String = ""
var abilities: Array[AbilityData] = []
func get_primary_element() -> Enums.Element:
if elements.size() > 0:
return elements[0]
return Enums.Element.FIRE
func is_multi_element() -> bool:
return elements.size() > 1
class AbilityData:
var type: Enums.AbilityType = Enums.AbilityType.FIELD
var name: String = ""
var effect: String = ""
var trigger: String = ""
var is_ex_burst: bool = false
var cost: CostData = null
class CostData:
var generic: int = 0
var fire: int = 0
var ice: int = 0
var wind: int = 0
var lightning: int = 0
var water: int = 0
var earth: int = 0
var light: int = 0
var dark: int = 0
var requires_dull: bool = false
var discard_count: int = 0
var specific_discard: String = ""
func get_total_cp() -> int:
return generic + fire + ice + wind + lightning + water + earth + light + dark
func get_element_requirements() -> Dictionary:
var reqs = {}
if fire > 0: reqs[Enums.Element.FIRE] = fire
if ice > 0: reqs[Enums.Element.ICE] = ice
if wind > 0: reqs[Enums.Element.WIND] = wind
if lightning > 0: reqs[Enums.Element.LIGHTNING] = lightning
if water > 0: reqs[Enums.Element.WATER] = water
if earth > 0: reqs[Enums.Element.EARTH] = earth
if light > 0: reqs[Enums.Element.LIGHT] = light
if dark > 0: reqs[Enums.Element.DARK] = dark
return reqs