init game files
This commit is contained in:
178
scripts/game/CPPool.gd
Normal file
178
scripts/game/CPPool.gd
Normal file
@@ -0,0 +1,178 @@
|
||||
class_name CPPool
|
||||
extends RefCounted
|
||||
|
||||
## CPPool - Tracks Crystal Points generated during a turn
|
||||
|
||||
# CP stored by element
|
||||
var _cp: Dictionary = {}
|
||||
|
||||
func _init() -> void:
|
||||
clear()
|
||||
|
||||
## Clear all CP (called at end of each action)
|
||||
func clear() -> void:
|
||||
for element in Enums.Element.values():
|
||||
_cp[element] = 0
|
||||
|
||||
## Add CP of a specific element
|
||||
func add_cp(element: Enums.Element, amount: int) -> void:
|
||||
_cp[element] = _cp.get(element, 0) + amount
|
||||
|
||||
## Get CP of a specific element
|
||||
func get_cp(element: Enums.Element) -> int:
|
||||
return _cp.get(element, 0)
|
||||
|
||||
## Get total CP available
|
||||
func get_total_cp() -> int:
|
||||
var total = 0
|
||||
for element in _cp:
|
||||
total += _cp[element]
|
||||
return total
|
||||
|
||||
## Check if we can afford a card cost
|
||||
func can_afford_card(card_data: CardDatabase.CardData) -> bool:
|
||||
if not card_data:
|
||||
return false
|
||||
|
||||
var cost = card_data.cost
|
||||
var elements = card_data.elements
|
||||
|
||||
# For Light/Dark cards, just need total CP
|
||||
var is_light_dark = false
|
||||
for element in elements:
|
||||
if Enums.is_light_or_dark(element):
|
||||
is_light_dark = true
|
||||
break
|
||||
|
||||
if is_light_dark:
|
||||
return get_total_cp() >= cost
|
||||
|
||||
# For multi-element cards, need at least 1 CP of each element
|
||||
if elements.size() > 1:
|
||||
for element in elements:
|
||||
if get_cp(element) < 1:
|
||||
return false
|
||||
# Total must be at least the cost
|
||||
return get_total_cp() >= cost
|
||||
|
||||
# For single element cards, need at least 1 CP of that element
|
||||
var primary_element = elements[0] if elements.size() > 0 else Enums.Element.FIRE
|
||||
if get_cp(primary_element) < 1:
|
||||
return false
|
||||
|
||||
return get_total_cp() >= cost
|
||||
|
||||
## Spend CP to pay a card cost
|
||||
## Returns true if successful, false if cannot afford
|
||||
func spend_for_card(card_data: CardDatabase.CardData) -> bool:
|
||||
if not can_afford_card(card_data):
|
||||
return false
|
||||
|
||||
var cost = card_data.cost
|
||||
var elements = card_data.elements
|
||||
var remaining = cost
|
||||
|
||||
# For multi-element, spend 1 of each required element first
|
||||
if elements.size() > 1:
|
||||
for element in elements:
|
||||
_cp[element] -= 1
|
||||
remaining -= 1
|
||||
|
||||
# For single element (non-Light/Dark), spend at least 1 of that element
|
||||
elif elements.size() == 1:
|
||||
var element = elements[0]
|
||||
if not Enums.is_light_or_dark(element):
|
||||
_cp[element] -= 1
|
||||
remaining -= 1
|
||||
|
||||
# Spend remaining from any element
|
||||
while remaining > 0:
|
||||
var spent = false
|
||||
for element in _cp:
|
||||
if _cp[element] > 0:
|
||||
_cp[element] -= 1
|
||||
remaining -= 1
|
||||
spent = true
|
||||
break
|
||||
if not spent:
|
||||
push_error("Failed to spend remaining CP")
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
## Check if we can afford an ability cost
|
||||
func can_afford_ability(cost: CardDatabase.CostData) -> bool:
|
||||
if not cost:
|
||||
return true # No cost means free
|
||||
|
||||
# Check element-specific requirements
|
||||
if cost.fire > 0 and get_cp(Enums.Element.FIRE) < cost.fire:
|
||||
return false
|
||||
if cost.ice > 0 and get_cp(Enums.Element.ICE) < cost.ice:
|
||||
return false
|
||||
if cost.wind > 0 and get_cp(Enums.Element.WIND) < cost.wind:
|
||||
return false
|
||||
if cost.lightning > 0 and get_cp(Enums.Element.LIGHTNING) < cost.lightning:
|
||||
return false
|
||||
if cost.water > 0 and get_cp(Enums.Element.WATER) < cost.water:
|
||||
return false
|
||||
if cost.earth > 0 and get_cp(Enums.Element.EARTH) < cost.earth:
|
||||
return false
|
||||
if cost.light > 0 and get_cp(Enums.Element.LIGHT) < cost.light:
|
||||
return false
|
||||
if cost.dark > 0 and get_cp(Enums.Element.DARK) < cost.dark:
|
||||
return false
|
||||
|
||||
# Check total
|
||||
return get_total_cp() >= cost.get_total_cp()
|
||||
|
||||
## Spend CP to pay an ability cost
|
||||
func spend_for_ability(cost: CardDatabase.CostData) -> bool:
|
||||
if not can_afford_ability(cost):
|
||||
return false
|
||||
|
||||
# Spend element-specific CP
|
||||
if cost.fire > 0:
|
||||
_cp[Enums.Element.FIRE] -= cost.fire
|
||||
if cost.ice > 0:
|
||||
_cp[Enums.Element.ICE] -= cost.ice
|
||||
if cost.wind > 0:
|
||||
_cp[Enums.Element.WIND] -= cost.wind
|
||||
if cost.lightning > 0:
|
||||
_cp[Enums.Element.LIGHTNING] -= cost.lightning
|
||||
if cost.water > 0:
|
||||
_cp[Enums.Element.WATER] -= cost.water
|
||||
if cost.earth > 0:
|
||||
_cp[Enums.Element.EARTH] -= cost.earth
|
||||
if cost.light > 0:
|
||||
_cp[Enums.Element.LIGHT] -= cost.light
|
||||
if cost.dark > 0:
|
||||
_cp[Enums.Element.DARK] -= cost.dark
|
||||
|
||||
# Spend generic from any element
|
||||
var generic_remaining = cost.generic
|
||||
while generic_remaining > 0:
|
||||
for element in _cp:
|
||||
if _cp[element] > 0:
|
||||
_cp[element] -= 1
|
||||
generic_remaining -= 1
|
||||
break
|
||||
|
||||
return true
|
||||
|
||||
## Get a display-friendly dictionary of current CP
|
||||
func get_display_data() -> Dictionary:
|
||||
var data = {}
|
||||
for element in _cp:
|
||||
if _cp[element] > 0:
|
||||
data[Enums.element_to_string(element)] = _cp[element]
|
||||
return data
|
||||
|
||||
func _to_string() -> String:
|
||||
var parts = []
|
||||
for element in _cp:
|
||||
if _cp[element] > 0:
|
||||
parts.append("%s: %d" % [Enums.element_to_string(element), _cp[element]])
|
||||
if parts.size() == 0:
|
||||
return "[CPPool: empty]"
|
||||
return "[CPPool: " + ", ".join(parts) + "]"
|
||||
193
scripts/game/CardInstance.gd
Normal file
193
scripts/game/CardInstance.gd
Normal file
@@ -0,0 +1,193 @@
|
||||
class_name CardInstance
|
||||
extends RefCounted
|
||||
|
||||
## CardInstance - Runtime instance of a card in the game
|
||||
## Represents a specific card in play with its current state
|
||||
|
||||
# Reference to card definition
|
||||
var card_data: CardDatabase.CardData
|
||||
|
||||
# Unique instance ID
|
||||
var instance_id: int = 0
|
||||
|
||||
# Current state
|
||||
var state: Enums.CardState = Enums.CardState.ACTIVE
|
||||
var current_power: int = 0
|
||||
var damage_received: int = 0
|
||||
|
||||
# Owner and controller
|
||||
var owner_index: int = 0 # Player who owns this card (0 or 1)
|
||||
var controller_index: int = 0 # Player who currently controls this card
|
||||
|
||||
# Current zone
|
||||
var zone_type: Enums.ZoneType = Enums.ZoneType.DECK
|
||||
|
||||
# Temporary effects (cleared at end of turn)
|
||||
var power_modifiers: Array[int] = []
|
||||
var temporary_abilities: Array = []
|
||||
|
||||
# Turn tracking
|
||||
var turns_on_field: int = 0
|
||||
var attacked_this_turn: bool = false
|
||||
|
||||
# Static counter for unique IDs
|
||||
static var _next_id: int = 1
|
||||
|
||||
func _init(data: CardDatabase.CardData = null, owner: int = 0) -> void:
|
||||
card_data = data
|
||||
owner_index = owner
|
||||
controller_index = owner
|
||||
instance_id = _next_id
|
||||
_next_id += 1
|
||||
|
||||
if data:
|
||||
current_power = data.power
|
||||
|
||||
## Get the card's current power (base + modifiers)
|
||||
func get_power() -> int:
|
||||
var total = current_power
|
||||
for mod in power_modifiers:
|
||||
total += mod
|
||||
return max(0, total)
|
||||
|
||||
## Check if this is a Forward
|
||||
func is_forward() -> bool:
|
||||
return card_data and card_data.type == Enums.CardType.FORWARD
|
||||
|
||||
## Check if this is a Backup
|
||||
func is_backup() -> bool:
|
||||
return card_data and card_data.type == Enums.CardType.BACKUP
|
||||
|
||||
## Check if this is a Summon
|
||||
func is_summon() -> bool:
|
||||
return card_data and card_data.type == Enums.CardType.SUMMON
|
||||
|
||||
## Check if the card is active (not dull)
|
||||
func is_active() -> bool:
|
||||
return state == Enums.CardState.ACTIVE
|
||||
|
||||
## Check if the card is dull
|
||||
func is_dull() -> bool:
|
||||
return state == Enums.CardState.DULL
|
||||
|
||||
## Dull this card
|
||||
func dull() -> void:
|
||||
state = Enums.CardState.DULL
|
||||
|
||||
## Activate this card
|
||||
func activate() -> void:
|
||||
state = Enums.CardState.ACTIVE
|
||||
|
||||
## Check if this card can attack
|
||||
func can_attack() -> bool:
|
||||
if not is_forward():
|
||||
return false
|
||||
if is_dull():
|
||||
return false
|
||||
if attacked_this_turn:
|
||||
return false
|
||||
# Must have been on field since start of turn (or have Haste)
|
||||
if turns_on_field < 1 and not has_haste():
|
||||
return false
|
||||
return true
|
||||
|
||||
## Check if this card can block
|
||||
func can_block() -> bool:
|
||||
if not is_forward():
|
||||
return false
|
||||
if is_dull():
|
||||
return false
|
||||
return true
|
||||
|
||||
## Check if this card can use dull abilities
|
||||
func can_use_dull_ability() -> bool:
|
||||
# Must have been on field since start of turn (or have Haste)
|
||||
# Monsters are exception
|
||||
if card_data.type == Enums.CardType.MONSTER:
|
||||
return true
|
||||
if turns_on_field < 1 and not has_haste():
|
||||
return false
|
||||
return true
|
||||
|
||||
## Check if card has Haste (from abilities)
|
||||
func has_haste() -> bool:
|
||||
if not card_data:
|
||||
return false
|
||||
for ability in card_data.abilities:
|
||||
if ability.type == Enums.AbilityType.FIELD:
|
||||
if "haste" in ability.effect.to_lower():
|
||||
return true
|
||||
return false
|
||||
|
||||
## Check if card has Brave (from abilities)
|
||||
func has_brave() -> bool:
|
||||
if not card_data:
|
||||
return false
|
||||
for ability in card_data.abilities:
|
||||
if ability.type == Enums.AbilityType.FIELD:
|
||||
if "brave" in ability.effect.to_lower():
|
||||
return true
|
||||
return false
|
||||
|
||||
## Check if card has First Strike
|
||||
func has_first_strike() -> bool:
|
||||
if not card_data:
|
||||
return false
|
||||
for ability in card_data.abilities:
|
||||
if ability.type == Enums.AbilityType.FIELD:
|
||||
if "first strike" in ability.effect.to_lower():
|
||||
return true
|
||||
return false
|
||||
|
||||
## Get primary element
|
||||
func get_element() -> Enums.Element:
|
||||
if card_data:
|
||||
return card_data.get_primary_element()
|
||||
return Enums.Element.FIRE
|
||||
|
||||
## Get all elements
|
||||
func get_elements() -> Array[Enums.Element]:
|
||||
if card_data:
|
||||
return card_data.elements
|
||||
return []
|
||||
|
||||
## Check if card is Light or Dark element
|
||||
func is_light_or_dark() -> bool:
|
||||
for element in get_elements():
|
||||
if Enums.is_light_or_dark(element):
|
||||
return true
|
||||
return false
|
||||
|
||||
## Apply damage to this Forward
|
||||
func apply_damage(amount: int) -> bool:
|
||||
if not is_forward():
|
||||
return false
|
||||
damage_received += amount
|
||||
# Check if broken (damage >= power)
|
||||
return damage_received >= get_power()
|
||||
|
||||
## Reset temporary effects at end of turn
|
||||
func end_turn_cleanup() -> void:
|
||||
power_modifiers.clear()
|
||||
temporary_abilities.clear()
|
||||
damage_received = 0
|
||||
attacked_this_turn = false
|
||||
|
||||
## Called when card enters field
|
||||
func entered_field() -> void:
|
||||
turns_on_field = 0
|
||||
attacked_this_turn = false
|
||||
damage_received = 0
|
||||
|
||||
## Called at start of each turn
|
||||
func start_turn() -> void:
|
||||
turns_on_field += 1
|
||||
|
||||
## Get a display string for this card
|
||||
func get_display_name() -> String:
|
||||
if card_data:
|
||||
return card_data.name
|
||||
return "Unknown Card"
|
||||
|
||||
func _to_string() -> String:
|
||||
return "[CardInstance: %s (%s)]" % [get_display_name(), instance_id]
|
||||
141
scripts/game/Enums.gd
Normal file
141
scripts/game/Enums.gd
Normal file
@@ -0,0 +1,141 @@
|
||||
class_name Enums
|
||||
extends RefCounted
|
||||
|
||||
## Card Elements
|
||||
enum Element {
|
||||
FIRE,
|
||||
ICE,
|
||||
WIND,
|
||||
LIGHTNING,
|
||||
WATER,
|
||||
EARTH,
|
||||
LIGHT,
|
||||
DARK
|
||||
}
|
||||
|
||||
## Card Types
|
||||
enum CardType {
|
||||
FORWARD,
|
||||
BACKUP,
|
||||
SUMMON,
|
||||
MONSTER
|
||||
}
|
||||
|
||||
## Ability Types
|
||||
enum AbilityType {
|
||||
FIELD,
|
||||
AUTO,
|
||||
ACTION,
|
||||
SPECIAL
|
||||
}
|
||||
|
||||
## Game Phases
|
||||
enum TurnPhase {
|
||||
ACTIVE,
|
||||
DRAW,
|
||||
MAIN_1,
|
||||
ATTACK,
|
||||
MAIN_2,
|
||||
END
|
||||
}
|
||||
|
||||
## Attack Phase Steps
|
||||
enum AttackStep {
|
||||
PREPARATION,
|
||||
DECLARATION,
|
||||
BLOCK_DECLARATION,
|
||||
DAMAGE_RESOLUTION
|
||||
}
|
||||
|
||||
## Zone Types
|
||||
enum ZoneType {
|
||||
DECK,
|
||||
HAND,
|
||||
FIELD_FORWARDS,
|
||||
FIELD_BACKUPS,
|
||||
DAMAGE,
|
||||
BREAK,
|
||||
STACK,
|
||||
REMOVED
|
||||
}
|
||||
|
||||
## Card States
|
||||
enum CardState {
|
||||
ACTIVE,
|
||||
DULL
|
||||
}
|
||||
|
||||
## Helper functions for Element
|
||||
static func element_from_string(s: String) -> Element:
|
||||
match s.to_lower():
|
||||
"fire": return Element.FIRE
|
||||
"ice": return Element.ICE
|
||||
"wind": return Element.WIND
|
||||
"lightning": return Element.LIGHTNING
|
||||
"water": return Element.WATER
|
||||
"earth": return Element.EARTH
|
||||
"light": return Element.LIGHT
|
||||
"dark": return Element.DARK
|
||||
push_error("Unknown element: " + s)
|
||||
return Element.FIRE
|
||||
|
||||
static func element_to_string(e: Element) -> String:
|
||||
match e:
|
||||
Element.FIRE: return "Fire"
|
||||
Element.ICE: return "Ice"
|
||||
Element.WIND: return "Wind"
|
||||
Element.LIGHTNING: return "Lightning"
|
||||
Element.WATER: return "Water"
|
||||
Element.EARTH: return "Earth"
|
||||
Element.LIGHT: return "Light"
|
||||
Element.DARK: return "Dark"
|
||||
return "Unknown"
|
||||
|
||||
static func element_to_color(e: Element) -> Color:
|
||||
match e:
|
||||
Element.FIRE: return Color(0.9, 0.2, 0.2) # Red
|
||||
Element.ICE: return Color(0.4, 0.8, 0.9) # Cyan
|
||||
Element.WIND: return Color(0.3, 0.8, 0.3) # Green
|
||||
Element.LIGHTNING: return Color(0.6, 0.3, 0.8) # Purple
|
||||
Element.WATER: return Color(0.2, 0.4, 0.9) # Blue
|
||||
Element.EARTH: return Color(0.8, 0.7, 0.3) # Yellow
|
||||
Element.LIGHT: return Color(1.0, 1.0, 0.9) # White
|
||||
Element.DARK: return Color(0.2, 0.1, 0.3) # Dark Purple
|
||||
return Color.WHITE
|
||||
|
||||
## Helper functions for CardType
|
||||
static func card_type_from_string(s: String) -> CardType:
|
||||
match s.to_lower():
|
||||
"forward": return CardType.FORWARD
|
||||
"backup": return CardType.BACKUP
|
||||
"summon": return CardType.SUMMON
|
||||
"monster": return CardType.MONSTER
|
||||
push_error("Unknown card type: " + s)
|
||||
return CardType.FORWARD
|
||||
|
||||
static func card_type_to_string(t: CardType) -> String:
|
||||
match t:
|
||||
CardType.FORWARD: return "Forward"
|
||||
CardType.BACKUP: return "Backup"
|
||||
CardType.SUMMON: return "Summon"
|
||||
CardType.MONSTER: return "Monster"
|
||||
return "Unknown"
|
||||
|
||||
## Helper functions for TurnPhase
|
||||
static func phase_to_string(p: TurnPhase) -> String:
|
||||
match p:
|
||||
TurnPhase.ACTIVE: return "Active Phase"
|
||||
TurnPhase.DRAW: return "Draw Phase"
|
||||
TurnPhase.MAIN_1: return "Main Phase 1"
|
||||
TurnPhase.ATTACK: return "Attack Phase"
|
||||
TurnPhase.MAIN_2: return "Main Phase 2"
|
||||
TurnPhase.END: return "End Phase"
|
||||
return "Unknown Phase"
|
||||
|
||||
## Check if element is Light or Dark (special rules apply)
|
||||
static func is_light_or_dark(e: Element) -> bool:
|
||||
return e == Element.LIGHT or e == Element.DARK
|
||||
|
||||
## Alias for element_to_color
|
||||
static func get_element_color(e: Element) -> Color:
|
||||
return element_to_color(e)
|
||||
332
scripts/game/GameState.gd
Normal file
332
scripts/game/GameState.gd
Normal file
@@ -0,0 +1,332 @@
|
||||
class_name GameState
|
||||
extends RefCounted
|
||||
|
||||
## GameState - Central game state container and rules engine
|
||||
|
||||
signal game_started
|
||||
signal game_ended(winner_index: int)
|
||||
signal card_played(card: CardInstance, player_index: int)
|
||||
signal card_moved(card: CardInstance, from_zone: Enums.ZoneType, to_zone: Enums.ZoneType)
|
||||
signal damage_dealt(player_index: int, amount: int, cards: Array[CardInstance])
|
||||
signal forward_broken(card: CardInstance)
|
||||
signal attack_declared(attacker: CardInstance)
|
||||
signal block_declared(blocker: CardInstance)
|
||||
signal combat_resolved(attacker: CardInstance, blocker: CardInstance)
|
||||
signal cp_generated(player_index: int, element: Enums.Element, amount: int)
|
||||
|
||||
# Players
|
||||
var players: Array[Player] = []
|
||||
|
||||
# Turn management
|
||||
var turn_manager: TurnManager
|
||||
|
||||
# Shared stack for abilities/summons
|
||||
var stack: Zone
|
||||
|
||||
# Game state flags
|
||||
var game_active: bool = false
|
||||
var winner_index: int = -1
|
||||
|
||||
func _init() -> void:
|
||||
turn_manager = TurnManager.new()
|
||||
stack = Zone.new(Enums.ZoneType.STACK, -1)
|
||||
|
||||
# Connect turn manager signals
|
||||
turn_manager.phase_changed.connect(_on_phase_changed)
|
||||
turn_manager.turn_started.connect(_on_turn_started)
|
||||
turn_manager.turn_ended.connect(_on_turn_ended)
|
||||
|
||||
## Initialize a new game with two players
|
||||
func setup_game(deck1: Array[String], deck2: Array[String]) -> void:
|
||||
# Create players
|
||||
players.clear()
|
||||
players.append(Player.new(0, "Player 1"))
|
||||
players.append(Player.new(1, "Player 2"))
|
||||
|
||||
# Setup decks
|
||||
players[0].setup_deck(deck1)
|
||||
players[1].setup_deck(deck2)
|
||||
|
||||
game_active = false
|
||||
winner_index = -1
|
||||
|
||||
## Start the game
|
||||
func start_game(first_player: int = -1) -> void:
|
||||
if first_player < 0:
|
||||
first_player = randi() % 2
|
||||
|
||||
players[first_player].is_first_player = true
|
||||
|
||||
# Draw initial hands (5 cards each)
|
||||
players[0].draw_cards(5)
|
||||
players[1].draw_cards(5)
|
||||
|
||||
game_active = true
|
||||
turn_manager.start_game(first_player)
|
||||
|
||||
game_started.emit()
|
||||
|
||||
## Get current player
|
||||
func get_current_player() -> Player:
|
||||
return players[turn_manager.current_player_index]
|
||||
|
||||
## Get opponent of current player
|
||||
func get_opponent() -> Player:
|
||||
return players[1 - turn_manager.current_player_index]
|
||||
|
||||
## Get player by index
|
||||
func get_player(index: int) -> Player:
|
||||
if index >= 0 and index < players.size():
|
||||
return players[index]
|
||||
return null
|
||||
|
||||
## Execute Active Phase
|
||||
func execute_active_phase() -> void:
|
||||
var player = get_current_player()
|
||||
player.activate_all()
|
||||
turn_manager.advance_phase()
|
||||
|
||||
## Execute Draw Phase
|
||||
func execute_draw_phase() -> void:
|
||||
var player = get_current_player()
|
||||
var draw_count = turn_manager.get_draw_count()
|
||||
|
||||
var drawn = player.draw_cards(draw_count)
|
||||
|
||||
# Check for loss condition (can't draw)
|
||||
if drawn.size() < draw_count and player.deck.is_empty():
|
||||
_check_loss_conditions()
|
||||
|
||||
turn_manager.advance_phase()
|
||||
|
||||
## End Main Phase
|
||||
func end_main_phase() -> void:
|
||||
# Clear any unused CP
|
||||
get_current_player().cp_pool.clear()
|
||||
turn_manager.advance_phase()
|
||||
|
||||
## Play a card from hand
|
||||
func play_card(player_index: int, card: CardInstance) -> bool:
|
||||
if not game_active:
|
||||
return false
|
||||
|
||||
if player_index != turn_manager.current_player_index:
|
||||
return false
|
||||
|
||||
if not turn_manager.is_main_phase():
|
||||
return false
|
||||
|
||||
var player = players[player_index]
|
||||
|
||||
# Validate and play
|
||||
if player.play_card(card):
|
||||
card_played.emit(card, player_index)
|
||||
card_moved.emit(card, Enums.ZoneType.HAND,
|
||||
Enums.ZoneType.FIELD_FORWARDS if card.is_forward() else Enums.ZoneType.FIELD_BACKUPS)
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
## Discard a card for CP
|
||||
func discard_for_cp(player_index: int, card: CardInstance) -> bool:
|
||||
if not game_active:
|
||||
return false
|
||||
|
||||
var player = players[player_index]
|
||||
var element = card.get_element()
|
||||
|
||||
if player.discard_for_cp(card):
|
||||
cp_generated.emit(player_index, element, 2)
|
||||
card_moved.emit(card, Enums.ZoneType.HAND, Enums.ZoneType.BREAK)
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
## Dull a backup for CP
|
||||
func dull_backup_for_cp(player_index: int, card: CardInstance) -> bool:
|
||||
if not game_active:
|
||||
return false
|
||||
|
||||
var player = players[player_index]
|
||||
var element = card.get_element()
|
||||
|
||||
if player.dull_backup_for_cp(card):
|
||||
cp_generated.emit(player_index, element, 1)
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
## Start Attack Phase
|
||||
func start_attack_phase() -> void:
|
||||
turn_manager.start_attack_declaration()
|
||||
|
||||
## Declare an attack
|
||||
func declare_attack(attacker: CardInstance) -> bool:
|
||||
if not game_active:
|
||||
return false
|
||||
|
||||
if not turn_manager.is_attack_phase():
|
||||
return false
|
||||
|
||||
var player = get_current_player()
|
||||
|
||||
if not player.field_forwards.has_card(attacker):
|
||||
return false
|
||||
|
||||
if not attacker.can_attack():
|
||||
return false
|
||||
|
||||
# Dull the attacker (unless Brave)
|
||||
if not attacker.has_brave():
|
||||
attacker.dull()
|
||||
|
||||
attacker.attacked_this_turn = true
|
||||
turn_manager.set_attacker(attacker)
|
||||
|
||||
attack_declared.emit(attacker)
|
||||
return true
|
||||
|
||||
## Declare a block
|
||||
func declare_block(blocker: CardInstance) -> bool:
|
||||
if not game_active:
|
||||
return false
|
||||
|
||||
if turn_manager.attack_step != Enums.AttackStep.BLOCK_DECLARATION:
|
||||
return false
|
||||
|
||||
var opponent = get_opponent()
|
||||
|
||||
if blocker != null:
|
||||
if not opponent.field_forwards.has_card(blocker):
|
||||
return false
|
||||
|
||||
if not blocker.can_block():
|
||||
return false
|
||||
|
||||
turn_manager.set_blocker(blocker)
|
||||
if blocker:
|
||||
block_declared.emit(blocker)
|
||||
|
||||
return true
|
||||
|
||||
## Skip blocking
|
||||
func skip_block() -> bool:
|
||||
return declare_block(null)
|
||||
|
||||
## Resolve combat damage
|
||||
func resolve_combat() -> void:
|
||||
if turn_manager.attack_step != Enums.AttackStep.DAMAGE_RESOLUTION:
|
||||
return
|
||||
|
||||
var attacker = turn_manager.current_attacker
|
||||
var blocker = turn_manager.current_blocker
|
||||
|
||||
if blocker == null:
|
||||
# Unblocked - deal 1 damage to opponent
|
||||
_deal_damage_to_player(1 - turn_manager.current_player_index, 1)
|
||||
else:
|
||||
# Blocked - exchange damage
|
||||
var attacker_power = attacker.get_power()
|
||||
var blocker_power = blocker.get_power()
|
||||
|
||||
# Apply damage
|
||||
var attacker_broken = attacker.apply_damage(blocker_power)
|
||||
var blocker_broken = blocker.apply_damage(attacker_power)
|
||||
|
||||
# Break destroyed forwards
|
||||
if attacker_broken:
|
||||
_break_forward(attacker, turn_manager.current_player_index)
|
||||
|
||||
if blocker_broken:
|
||||
_break_forward(blocker, 1 - turn_manager.current_player_index)
|
||||
|
||||
combat_resolved.emit(attacker, blocker)
|
||||
turn_manager.complete_attack()
|
||||
|
||||
## End Attack Phase (no more attacks)
|
||||
func end_attack_phase() -> void:
|
||||
turn_manager.end_attack_phase()
|
||||
|
||||
## Execute End Phase
|
||||
func execute_end_phase() -> void:
|
||||
var player = get_current_player()
|
||||
|
||||
# Discard to hand limit
|
||||
var discarded = player.discard_to_hand_limit()
|
||||
for card in discarded:
|
||||
card_moved.emit(card, Enums.ZoneType.HAND, Enums.ZoneType.BREAK)
|
||||
|
||||
# Cleanup
|
||||
player.end_turn_cleanup()
|
||||
|
||||
turn_manager.advance_phase()
|
||||
|
||||
## Deal damage to a player
|
||||
func _deal_damage_to_player(player_index: int, amount: int) -> void:
|
||||
var player = players[player_index]
|
||||
var damage_cards = player.take_damage(amount)
|
||||
|
||||
damage_dealt.emit(player_index, amount, damage_cards)
|
||||
|
||||
# Check for EX Bursts (simplified - would need full implementation)
|
||||
for card in damage_cards:
|
||||
if card.card_data and card.card_data.has_ex_burst:
|
||||
# TODO: Offer EX Burst choice to player
|
||||
pass
|
||||
|
||||
_check_loss_conditions()
|
||||
|
||||
## Break a forward
|
||||
func _break_forward(card: CardInstance, player_index: int) -> void:
|
||||
var player = players[player_index]
|
||||
if player.break_card(card):
|
||||
forward_broken.emit(card)
|
||||
card_moved.emit(card, Enums.ZoneType.FIELD_FORWARDS, Enums.ZoneType.BREAK)
|
||||
|
||||
## Check for game-ending conditions
|
||||
func _check_loss_conditions() -> void:
|
||||
for i in range(players.size()):
|
||||
var player = players[i]
|
||||
|
||||
# Check damage
|
||||
if player.has_lost():
|
||||
_end_game(1 - i) # Other player wins
|
||||
return
|
||||
|
||||
# Check deck out (can't draw when needed)
|
||||
# This is checked during draw phase
|
||||
|
||||
## End the game
|
||||
func _end_game(winner: int) -> void:
|
||||
game_active = false
|
||||
winner_index = winner
|
||||
game_ended.emit(winner)
|
||||
|
||||
## Phase change handler
|
||||
func _on_phase_changed(phase: Enums.TurnPhase) -> void:
|
||||
match phase:
|
||||
Enums.TurnPhase.ACTIVE:
|
||||
execute_active_phase()
|
||||
Enums.TurnPhase.DRAW:
|
||||
execute_draw_phase()
|
||||
Enums.TurnPhase.ATTACK:
|
||||
start_attack_phase()
|
||||
Enums.TurnPhase.END:
|
||||
execute_end_phase()
|
||||
|
||||
## Turn started handler
|
||||
func _on_turn_started(player_index: int, _turn_number: int) -> void:
|
||||
players[player_index].start_turn()
|
||||
|
||||
## Turn ended handler
|
||||
func _on_turn_ended(_player_index: int) -> void:
|
||||
pass
|
||||
|
||||
func _to_string() -> String:
|
||||
if not game_active:
|
||||
return "[GameState: Inactive]"
|
||||
return "[GameState: Turn %d, Phase: %s, Player %d]" % [
|
||||
turn_manager.turn_number,
|
||||
turn_manager.get_phase_string(),
|
||||
turn_manager.current_player_index + 1
|
||||
]
|
||||
263
scripts/game/Player.gd
Normal file
263
scripts/game/Player.gd
Normal file
@@ -0,0 +1,263 @@
|
||||
class_name Player
|
||||
extends RefCounted
|
||||
|
||||
## Player - Represents a player's game state
|
||||
|
||||
var player_index: int = 0
|
||||
var player_name: String = "Player"
|
||||
|
||||
# Zones
|
||||
var deck: Zone
|
||||
var hand: Zone
|
||||
var field_forwards: Zone
|
||||
var field_backups: Zone
|
||||
var damage_zone: Zone
|
||||
var break_zone: Zone
|
||||
|
||||
# Resources
|
||||
var cp_pool: CPPool
|
||||
|
||||
# Game state
|
||||
var is_first_player: bool = false
|
||||
var has_mulliganed: bool = false
|
||||
|
||||
# Constants
|
||||
const MAX_HAND_SIZE: int = 5
|
||||
const MAX_BACKUPS: int = 5
|
||||
const DAMAGE_TO_LOSE: int = 7
|
||||
|
||||
func _init(index: int, name: String = "") -> void:
|
||||
player_index = index
|
||||
player_name = name if name != "" else "Player %d" % (index + 1)
|
||||
|
||||
# Initialize zones
|
||||
deck = Zone.new(Enums.ZoneType.DECK, index)
|
||||
hand = Zone.new(Enums.ZoneType.HAND, index)
|
||||
field_forwards = Zone.new(Enums.ZoneType.FIELD_FORWARDS, index)
|
||||
field_backups = Zone.new(Enums.ZoneType.FIELD_BACKUPS, index)
|
||||
damage_zone = Zone.new(Enums.ZoneType.DAMAGE, index)
|
||||
break_zone = Zone.new(Enums.ZoneType.BREAK, index)
|
||||
|
||||
# Initialize CP pool
|
||||
cp_pool = CPPool.new()
|
||||
|
||||
## Setup the player's deck from a list of card IDs
|
||||
func setup_deck(card_ids: Array[String]) -> void:
|
||||
for card_id in card_ids:
|
||||
var card_data = CardDatabase.get_card(card_id)
|
||||
if card_data:
|
||||
var card = CardInstance.new(card_data, player_index)
|
||||
deck.add_card(card)
|
||||
|
||||
# Shuffle the deck
|
||||
deck.shuffle()
|
||||
|
||||
## Draw cards from deck to hand
|
||||
func draw_cards(count: int) -> Array[CardInstance]:
|
||||
var drawn: Array[CardInstance] = []
|
||||
|
||||
for i in range(count):
|
||||
if deck.is_empty():
|
||||
break # Can't draw from empty deck
|
||||
var card = deck.pop_top_card()
|
||||
if card:
|
||||
hand.add_card(card)
|
||||
drawn.append(card)
|
||||
|
||||
return drawn
|
||||
|
||||
## Check if player can draw (deck not empty)
|
||||
func can_draw() -> bool:
|
||||
return not deck.is_empty()
|
||||
|
||||
## Get current damage count
|
||||
func get_damage_count() -> int:
|
||||
return damage_zone.get_count()
|
||||
|
||||
## Check if player has lost (7+ damage)
|
||||
func has_lost() -> bool:
|
||||
return get_damage_count() >= DAMAGE_TO_LOSE
|
||||
|
||||
## Take damage (move cards from deck to damage zone)
|
||||
func take_damage(amount: int) -> Array[CardInstance]:
|
||||
var damage_cards: Array[CardInstance] = []
|
||||
|
||||
for i in range(amount):
|
||||
if deck.is_empty():
|
||||
break
|
||||
var card = deck.pop_top_card()
|
||||
if card:
|
||||
damage_zone.add_card(card)
|
||||
damage_cards.append(card)
|
||||
|
||||
return damage_cards
|
||||
|
||||
## Discard a card from hand to generate CP
|
||||
func discard_for_cp(card: CardInstance) -> bool:
|
||||
if not hand.has_card(card):
|
||||
return false
|
||||
|
||||
# Light/Dark cards cannot be discarded for CP
|
||||
if card.is_light_or_dark():
|
||||
return false
|
||||
|
||||
hand.remove_card(card)
|
||||
break_zone.add_card(card)
|
||||
|
||||
# Generate 2 CP of the card's element
|
||||
var element = card.get_element()
|
||||
cp_pool.add_cp(element, 2)
|
||||
|
||||
return true
|
||||
|
||||
## Dull a backup to generate CP
|
||||
func dull_backup_for_cp(card: CardInstance) -> bool:
|
||||
if not field_backups.has_card(card):
|
||||
return false
|
||||
|
||||
if not card.is_backup():
|
||||
return false
|
||||
|
||||
if card.is_dull():
|
||||
return false
|
||||
|
||||
card.dull()
|
||||
|
||||
# Generate 1 CP of the backup's element
|
||||
var element = card.get_element()
|
||||
cp_pool.add_cp(element, 1)
|
||||
|
||||
return true
|
||||
|
||||
## Play a card from hand to field
|
||||
func play_card(card: CardInstance) -> bool:
|
||||
if not hand.has_card(card):
|
||||
return false
|
||||
|
||||
# Check if we can afford it
|
||||
if not cp_pool.can_afford_card(card.card_data):
|
||||
return false
|
||||
|
||||
# Check field restrictions
|
||||
if card.is_backup():
|
||||
if field_backups.get_count() >= MAX_BACKUPS:
|
||||
return false
|
||||
|
||||
# Check unique name restriction
|
||||
if not card.card_data.is_generic:
|
||||
if card.is_forward() and field_forwards.has_card_with_name(card.card_data.name):
|
||||
return false
|
||||
if card.is_backup() and field_backups.has_card_with_name(card.card_data.name):
|
||||
return false
|
||||
|
||||
# Check Light/Dark restriction
|
||||
if card.is_light_or_dark():
|
||||
if field_forwards.has_light_or_dark() or field_backups.has_light_or_dark():
|
||||
return false
|
||||
|
||||
# Pay the cost
|
||||
if not cp_pool.spend_for_card(card.card_data):
|
||||
return false
|
||||
|
||||
# Move to field
|
||||
hand.remove_card(card)
|
||||
|
||||
if card.is_forward():
|
||||
field_forwards.add_card(card)
|
||||
card.state = Enums.CardState.ACTIVE
|
||||
elif card.is_backup():
|
||||
field_backups.add_card(card)
|
||||
card.state = Enums.CardState.DULL # Backups enter dull
|
||||
|
||||
card.entered_field()
|
||||
|
||||
return true
|
||||
|
||||
## Break a card (move from field to break zone)
|
||||
func break_card(card: CardInstance) -> bool:
|
||||
var removed = false
|
||||
|
||||
if field_forwards.has_card(card):
|
||||
field_forwards.remove_card(card)
|
||||
removed = true
|
||||
elif field_backups.has_card(card):
|
||||
field_backups.remove_card(card)
|
||||
removed = true
|
||||
|
||||
if removed:
|
||||
break_zone.add_card(card)
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
## Activate all dull cards (Active Phase)
|
||||
func activate_all() -> void:
|
||||
for card in field_forwards.get_dull_cards():
|
||||
card.activate()
|
||||
for card in field_backups.get_dull_cards():
|
||||
card.activate()
|
||||
|
||||
## Discard down to hand size (End Phase)
|
||||
func discard_to_hand_limit() -> Array[CardInstance]:
|
||||
var discarded: Array[CardInstance] = []
|
||||
|
||||
while hand.get_count() > MAX_HAND_SIZE:
|
||||
# For now, just discard the last card
|
||||
# In actual game, player would choose
|
||||
var card = hand.pop_top_card()
|
||||
if card:
|
||||
break_zone.add_card(card)
|
||||
discarded.append(card)
|
||||
|
||||
return discarded
|
||||
|
||||
## End of turn cleanup
|
||||
func end_turn_cleanup() -> void:
|
||||
# Clear CP pool
|
||||
cp_pool.clear()
|
||||
|
||||
# Reset temporary effects on field cards
|
||||
for card in field_forwards.get_cards():
|
||||
card.end_turn_cleanup()
|
||||
for card in field_backups.get_cards():
|
||||
card.end_turn_cleanup()
|
||||
|
||||
## Start of turn setup
|
||||
func start_turn() -> void:
|
||||
# Increment turn counter for field cards
|
||||
for card in field_forwards.get_cards():
|
||||
card.start_turn()
|
||||
for card in field_backups.get_cards():
|
||||
card.start_turn()
|
||||
|
||||
## Get all cards on field
|
||||
func get_all_field_cards() -> Array[CardInstance]:
|
||||
var cards: Array[CardInstance] = []
|
||||
cards.append_array(field_forwards.get_cards())
|
||||
cards.append_array(field_backups.get_cards())
|
||||
return cards
|
||||
|
||||
## Get forwards that can attack
|
||||
func get_attackable_forwards() -> Array[CardInstance]:
|
||||
var attackers: Array[CardInstance] = []
|
||||
for card in field_forwards.get_cards():
|
||||
if card.can_attack():
|
||||
attackers.append(card)
|
||||
return attackers
|
||||
|
||||
## Get forwards that can block
|
||||
func get_blockable_forwards() -> Array[CardInstance]:
|
||||
var blockers: Array[CardInstance] = []
|
||||
for card in field_forwards.get_cards():
|
||||
if card.can_block():
|
||||
blockers.append(card)
|
||||
return blockers
|
||||
|
||||
func _to_string() -> String:
|
||||
return "[Player: %s, Hand: %d, Forwards: %d, Backups: %d, Damage: %d]" % [
|
||||
player_name,
|
||||
hand.get_count(),
|
||||
field_forwards.get_count(),
|
||||
field_backups.get_count(),
|
||||
damage_zone.get_count()
|
||||
]
|
||||
150
scripts/game/TurnManager.gd
Normal file
150
scripts/game/TurnManager.gd
Normal file
@@ -0,0 +1,150 @@
|
||||
class_name TurnManager
|
||||
extends RefCounted
|
||||
|
||||
## TurnManager - Handles turn and phase progression
|
||||
|
||||
signal phase_changed(phase: Enums.TurnPhase)
|
||||
signal turn_changed(player_index: int)
|
||||
signal turn_started(player_index: int, turn_number: int)
|
||||
signal turn_ended(player_index: int)
|
||||
|
||||
var current_phase: Enums.TurnPhase = Enums.TurnPhase.ACTIVE
|
||||
var current_player_index: int = 0
|
||||
var turn_number: int = 0
|
||||
var is_first_turn: bool = true
|
||||
|
||||
# Attack phase tracking
|
||||
var attack_step: Enums.AttackStep = Enums.AttackStep.PREPARATION
|
||||
var current_attacker: CardInstance = null
|
||||
var current_blocker: CardInstance = null
|
||||
|
||||
func _init() -> void:
|
||||
pass
|
||||
|
||||
## Start a new game
|
||||
func start_game(first_player: int) -> void:
|
||||
current_player_index = first_player
|
||||
turn_number = 1
|
||||
is_first_turn = true
|
||||
current_phase = Enums.TurnPhase.ACTIVE
|
||||
turn_started.emit(current_player_index, turn_number)
|
||||
|
||||
## Advance to the next phase
|
||||
func advance_phase() -> Enums.TurnPhase:
|
||||
var next_phase = _get_next_phase(current_phase)
|
||||
|
||||
if next_phase == Enums.TurnPhase.ACTIVE:
|
||||
# Starting a new turn
|
||||
_end_current_turn()
|
||||
_start_new_turn()
|
||||
else:
|
||||
current_phase = next_phase
|
||||
phase_changed.emit(current_phase)
|
||||
|
||||
return current_phase
|
||||
|
||||
## Get the next phase in sequence
|
||||
func _get_next_phase(phase: Enums.TurnPhase) -> Enums.TurnPhase:
|
||||
match phase:
|
||||
Enums.TurnPhase.ACTIVE:
|
||||
return Enums.TurnPhase.DRAW
|
||||
Enums.TurnPhase.DRAW:
|
||||
return Enums.TurnPhase.MAIN_1
|
||||
Enums.TurnPhase.MAIN_1:
|
||||
return Enums.TurnPhase.ATTACK
|
||||
Enums.TurnPhase.ATTACK:
|
||||
return Enums.TurnPhase.MAIN_2
|
||||
Enums.TurnPhase.MAIN_2:
|
||||
return Enums.TurnPhase.END
|
||||
Enums.TurnPhase.END:
|
||||
return Enums.TurnPhase.ACTIVE # Loop back to start new turn
|
||||
return Enums.TurnPhase.ACTIVE
|
||||
|
||||
## End the current turn
|
||||
func _end_current_turn() -> void:
|
||||
turn_ended.emit(current_player_index)
|
||||
|
||||
## Start a new turn
|
||||
func _start_new_turn() -> void:
|
||||
# Switch to other player
|
||||
current_player_index = 1 - current_player_index
|
||||
turn_number += 1
|
||||
is_first_turn = false
|
||||
|
||||
current_phase = Enums.TurnPhase.ACTIVE
|
||||
_reset_attack_state()
|
||||
|
||||
turn_changed.emit(current_player_index)
|
||||
turn_started.emit(current_player_index, turn_number)
|
||||
phase_changed.emit(current_phase)
|
||||
|
||||
## Reset attack phase state
|
||||
func _reset_attack_state() -> void:
|
||||
attack_step = Enums.AttackStep.PREPARATION
|
||||
current_attacker = null
|
||||
current_blocker = null
|
||||
|
||||
## Check if we're in a main phase (can play cards)
|
||||
func is_main_phase() -> bool:
|
||||
return current_phase == Enums.TurnPhase.MAIN_1 or current_phase == Enums.TurnPhase.MAIN_2
|
||||
|
||||
## Check if we're in attack phase
|
||||
func is_attack_phase() -> bool:
|
||||
return current_phase == Enums.TurnPhase.ATTACK
|
||||
|
||||
## Get cards drawn this turn (2 normally, 1 on first turn for first player)
|
||||
func get_draw_count() -> int:
|
||||
if is_first_turn and current_player_index == 0:
|
||||
return 1
|
||||
return 2
|
||||
|
||||
## Attack phase state management
|
||||
|
||||
## Start attack declaration
|
||||
func start_attack_declaration() -> void:
|
||||
attack_step = Enums.AttackStep.DECLARATION
|
||||
|
||||
## Set the attacking forward
|
||||
func set_attacker(attacker: CardInstance) -> void:
|
||||
current_attacker = attacker
|
||||
attack_step = Enums.AttackStep.BLOCK_DECLARATION
|
||||
|
||||
## Set the blocking forward (or null for no block)
|
||||
func set_blocker(blocker: CardInstance) -> void:
|
||||
current_blocker = blocker
|
||||
attack_step = Enums.AttackStep.DAMAGE_RESOLUTION
|
||||
|
||||
## Complete the current attack
|
||||
func complete_attack() -> void:
|
||||
current_attacker = null
|
||||
current_blocker = null
|
||||
attack_step = Enums.AttackStep.DECLARATION
|
||||
|
||||
## End the attack phase (no more attacks)
|
||||
func end_attack_phase() -> void:
|
||||
_reset_attack_state()
|
||||
advance_phase()
|
||||
|
||||
## Get current phase as string
|
||||
func get_phase_string() -> String:
|
||||
return Enums.phase_to_string(current_phase)
|
||||
|
||||
## Get current attack step as string
|
||||
func get_attack_step_string() -> String:
|
||||
match attack_step:
|
||||
Enums.AttackStep.PREPARATION:
|
||||
return "Preparation"
|
||||
Enums.AttackStep.DECLARATION:
|
||||
return "Declare Attacker"
|
||||
Enums.AttackStep.BLOCK_DECLARATION:
|
||||
return "Declare Blocker"
|
||||
Enums.AttackStep.DAMAGE_RESOLUTION:
|
||||
return "Damage Resolution"
|
||||
return "Unknown"
|
||||
|
||||
func _to_string() -> String:
|
||||
return "[TurnManager: Turn %d, Player %d, Phase: %s]" % [
|
||||
turn_number,
|
||||
current_player_index + 1,
|
||||
get_phase_string()
|
||||
]
|
||||
158
scripts/game/Zone.gd
Normal file
158
scripts/game/Zone.gd
Normal file
@@ -0,0 +1,158 @@
|
||||
class_name Zone
|
||||
extends RefCounted
|
||||
|
||||
## Zone - Container for cards in a specific game area
|
||||
|
||||
var zone_type: Enums.ZoneType
|
||||
var owner_index: int # Player who owns this zone (-1 for shared zones like stack)
|
||||
var _cards: Array[CardInstance] = []
|
||||
|
||||
# Signals (these would need to be connected via the parent)
|
||||
# In GDScript, RefCounted can't have signals, so we'll use callbacks
|
||||
var on_card_added: Callable = func(_card): pass
|
||||
var on_card_removed: Callable = func(_card): pass
|
||||
|
||||
func _init(type: Enums.ZoneType, owner: int = -1) -> void:
|
||||
zone_type = type
|
||||
owner_index = owner
|
||||
|
||||
## Add a card to this zone
|
||||
func add_card(card: CardInstance, at_top: bool = true) -> void:
|
||||
if card in _cards:
|
||||
push_warning("Card already in zone: " + str(card))
|
||||
return
|
||||
|
||||
card.zone_type = zone_type
|
||||
|
||||
if at_top:
|
||||
_cards.append(card)
|
||||
else:
|
||||
_cards.insert(0, card)
|
||||
|
||||
on_card_added.call(card)
|
||||
|
||||
## Remove a card from this zone
|
||||
func remove_card(card: CardInstance) -> bool:
|
||||
var index = _cards.find(card)
|
||||
if index >= 0:
|
||||
_cards.remove_at(index)
|
||||
on_card_removed.call(card)
|
||||
return true
|
||||
return false
|
||||
|
||||
## Get all cards in this zone
|
||||
func get_cards() -> Array[CardInstance]:
|
||||
return _cards.duplicate()
|
||||
|
||||
## Get card count
|
||||
func get_count() -> int:
|
||||
return _cards.size()
|
||||
|
||||
## Check if zone contains a card
|
||||
func has_card(card: CardInstance) -> bool:
|
||||
return card in _cards
|
||||
|
||||
## Get card at index
|
||||
func get_card_at(index: int) -> CardInstance:
|
||||
if index >= 0 and index < _cards.size():
|
||||
return _cards[index]
|
||||
return null
|
||||
|
||||
## Get top card (last added)
|
||||
func get_top_card() -> CardInstance:
|
||||
if _cards.size() > 0:
|
||||
return _cards[_cards.size() - 1]
|
||||
return null
|
||||
|
||||
## Remove and return top card
|
||||
func pop_top_card() -> CardInstance:
|
||||
if _cards.size() > 0:
|
||||
var card = _cards.pop_back()
|
||||
on_card_removed.call(card)
|
||||
return card
|
||||
return null
|
||||
|
||||
## Get bottom card (first added)
|
||||
func get_bottom_card() -> CardInstance:
|
||||
if _cards.size() > 0:
|
||||
return _cards[0]
|
||||
return null
|
||||
|
||||
## Check if zone is empty
|
||||
func is_empty() -> bool:
|
||||
return _cards.size() == 0
|
||||
|
||||
## Shuffle the zone (for decks)
|
||||
func shuffle() -> void:
|
||||
_cards.shuffle()
|
||||
|
||||
## Clear all cards from zone
|
||||
func clear() -> Array[CardInstance]:
|
||||
var removed = _cards.duplicate()
|
||||
_cards.clear()
|
||||
for card in removed:
|
||||
on_card_removed.call(card)
|
||||
return removed
|
||||
|
||||
## Get all Forwards in zone
|
||||
func get_forwards() -> Array[CardInstance]:
|
||||
var forwards: Array[CardInstance] = []
|
||||
for card in _cards:
|
||||
if card.is_forward():
|
||||
forwards.append(card)
|
||||
return forwards
|
||||
|
||||
## Get all Backups in zone
|
||||
func get_backups() -> Array[CardInstance]:
|
||||
var backups: Array[CardInstance] = []
|
||||
for card in _cards:
|
||||
if card.is_backup():
|
||||
backups.append(card)
|
||||
return backups
|
||||
|
||||
## Get all active cards
|
||||
func get_active_cards() -> Array[CardInstance]:
|
||||
var active: Array[CardInstance] = []
|
||||
for card in _cards:
|
||||
if card.is_active():
|
||||
active.append(card)
|
||||
return active
|
||||
|
||||
## Get all dull cards
|
||||
func get_dull_cards() -> Array[CardInstance]:
|
||||
var dull: Array[CardInstance] = []
|
||||
for card in _cards:
|
||||
if card.is_dull():
|
||||
dull.append(card)
|
||||
return dull
|
||||
|
||||
## Find cards by name
|
||||
func find_cards_by_name(card_name: String) -> Array[CardInstance]:
|
||||
var found: Array[CardInstance] = []
|
||||
for card in _cards:
|
||||
if card.card_data and card.card_data.name == card_name:
|
||||
found.append(card)
|
||||
return found
|
||||
|
||||
## Find cards by element
|
||||
func find_cards_by_element(element: Enums.Element) -> Array[CardInstance]:
|
||||
var found: Array[CardInstance] = []
|
||||
for card in _cards:
|
||||
if element in card.get_elements():
|
||||
found.append(card)
|
||||
return found
|
||||
|
||||
## Check if zone has a card with specific name (non-generic check)
|
||||
func has_card_with_name(card_name: String) -> bool:
|
||||
for card in _cards:
|
||||
if card.card_data and card.card_data.name == card_name:
|
||||
if not card.card_data.is_generic:
|
||||
return true
|
||||
return false
|
||||
|
||||
## Check if zone has any Light or Dark card
|
||||
func has_light_or_dark() -> bool:
|
||||
for card in _cards:
|
||||
if card.is_light_or_dark():
|
||||
return true
|
||||
return false
|
||||
Reference in New Issue
Block a user