working updates
@@ -8,7 +8,9 @@
|
|||||||
"Bash(snap list:*)",
|
"Bash(snap list:*)",
|
||||||
"Bash(timeout 10 godot4:*)",
|
"Bash(timeout 10 godot4:*)",
|
||||||
"Bash(ls:*)",
|
"Bash(ls:*)",
|
||||||
"Bash(timeout 5 godot4:*)"
|
"Bash(timeout 5 godot4:*)",
|
||||||
|
"Bash(curl:*)",
|
||||||
|
"Bash(timeout 3 godot4:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/.godot/imported
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[folding]
|
||||||
|
|
||||||
|
sections_unfolded=PackedStringArray()
|
||||||
@@ -19,8 +19,8 @@ dock_filesystem_split=0
|
|||||||
dock_filesystem_display_mode=0
|
dock_filesystem_display_mode=0
|
||||||
dock_filesystem_file_sort=0
|
dock_filesystem_file_sort=0
|
||||||
dock_filesystem_file_list_display_mode=1
|
dock_filesystem_file_list_display_mode=1
|
||||||
dock_filesystem_selected_paths=PackedStringArray("res://")
|
dock_filesystem_selected_paths=PackedStringArray("res://scripts/visual/TableCamera.gd")
|
||||||
dock_filesystem_uncollapsed_paths=PackedStringArray("Favorites", "res://")
|
dock_filesystem_uncollapsed_paths=PackedStringArray("Favorites", "res://", "res://scripts/", "res://scripts/visual/")
|
||||||
dock_3="Scene,Import"
|
dock_3="Scene,Import"
|
||||||
dock_4="FileSystem"
|
dock_4="FileSystem"
|
||||||
dock_5="Inspector,Node,History"
|
dock_5="Inspector,Node,History"
|
||||||
@@ -36,8 +36,8 @@ selected_bottom_panel_item=0
|
|||||||
|
|
||||||
[ScriptEditor]
|
[ScriptEditor]
|
||||||
|
|
||||||
open_scripts=["res://scripts/ui/ActionLog.gd", "res://scripts/autoload/CardDatabase.gd", "res://scripts/ui/HandDisplay.gd", "res://scripts/Main.gd", "res://scripts/ui/PauseMenu.gd", "res://scripts/visual/TableSetup.gd", "res://scripts/game/UndoSystem.gd"]
|
open_scripts=["res://scripts/ui/ActionLog.gd", "res://scripts/autoload/CardDatabase.gd", "res://scripts/ui/HandDisplay.gd", "res://scripts/Main.gd", "res://scripts/ui/PauseMenu.gd", "res://scripts/visual/TableCamera.gd", "res://scripts/visual/TableSetup.gd", "res://scripts/game/UndoSystem.gd"]
|
||||||
selected_script="res://scripts/ui/ActionLog.gd"
|
selected_script="res://scripts/visual/TableCamera.gd"
|
||||||
open_help=[]
|
open_help=[]
|
||||||
script_split_offset=140
|
script_split_offset=140
|
||||||
list_split_offset=0
|
list_split_offset=0
|
||||||
@@ -47,3 +47,13 @@ list_split_offset=0
|
|||||||
open_shaders=[]
|
open_shaders=[]
|
||||||
split_offset=0
|
split_offset=0
|
||||||
selected_shader=""
|
selected_shader=""
|
||||||
|
|
||||||
|
[editor_log]
|
||||||
|
|
||||||
|
log_filter_0=true
|
||||||
|
log_filter_2=true
|
||||||
|
log_filter_1=false
|
||||||
|
log_filter_3=true
|
||||||
|
log_filter_4=true
|
||||||
|
collapse=false
|
||||||
|
show_search=true
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
res://scenes/main.tscn
|
res://scenes/main.tscn
|
||||||
|
res://scripts/visual/TableCamera.gd
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ run_reload_scripts=true
|
|||||||
[recent_files]
|
[recent_files]
|
||||||
|
|
||||||
scenes=["res://scenes/main.tscn"]
|
scenes=["res://scenes/main.tscn"]
|
||||||
scripts=["res://scripts/ui/ActionLog.gd", "res://scripts/game/UndoSystem.gd", "res://scripts/ui/HandDisplay.gd", "res://scripts/ui/PauseMenu.gd", "res://scripts/visual/TableSetup.gd", "res://scripts/autoload/CardDatabase.gd", "res://scripts/Main.gd"]
|
scripts=["res://scripts/visual/TableCamera.gd", "res://scripts/ui/ActionLog.gd", "res://scripts/game/UndoSystem.gd", "res://scripts/ui/HandDisplay.gd", "res://scripts/ui/PauseMenu.gd", "res://scripts/visual/TableSetup.gd", "res://scripts/autoload/CardDatabase.gd", "res://scripts/Main.gd"]
|
||||||
|
|
||||||
[linked_properties]
|
[linked_properties]
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
state={
|
state={
|
||||||
"bookmarks": PackedInt32Array(),
|
"bookmarks": PackedInt32Array(),
|
||||||
"breakpoints": PackedInt32Array(),
|
"breakpoints": PackedInt32Array(),
|
||||||
"column": 0,
|
"column": 29,
|
||||||
"folded_lines": Array[int]([]),
|
"folded_lines": Array[int]([]),
|
||||||
"h_scroll_position": 0,
|
"h_scroll_position": 0,
|
||||||
"row": 75,
|
"row": 49,
|
||||||
"scroll_position": 64.0,
|
"scroll_position": 45.0,
|
||||||
"selection": false,
|
"selection": false,
|
||||||
"syntax_highlighter": "GDScript"
|
"syntax_highlighter": "GDScript"
|
||||||
}
|
}
|
||||||
@@ -95,3 +95,17 @@ state={
|
|||||||
"selection": false,
|
"selection": false,
|
||||||
"syntax_highlighter": "GDScript"
|
"syntax_highlighter": "GDScript"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[res://scripts/visual/TableCamera.gd]
|
||||||
|
|
||||||
|
state={
|
||||||
|
"bookmarks": PackedInt32Array(),
|
||||||
|
"breakpoints": PackedInt32Array(),
|
||||||
|
"column": 44,
|
||||||
|
"folded_lines": Array[int]([]),
|
||||||
|
"h_scroll_position": 0,
|
||||||
|
"row": 8,
|
||||||
|
"scroll_position": 3.0,
|
||||||
|
"selection": false,
|
||||||
|
"syntax_highlighter": "GDScript"
|
||||||
|
}
|
||||||
|
|||||||
@@ -71,6 +71,12 @@ list=Array[Dictionary]([{
|
|||||||
"language": &"GDScript",
|
"language": &"GDScript",
|
||||||
"path": "res://scripts/game/Player.gd"
|
"path": "res://scripts/game/Player.gd"
|
||||||
}, {
|
}, {
|
||||||
|
"base": &"Node",
|
||||||
|
"class": &"PlaymatRenderer",
|
||||||
|
"icon": "",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://scripts/visual/PlaymatRenderer.gd"
|
||||||
|
}, {
|
||||||
"base": &"Camera3D",
|
"base": &"Camera3D",
|
||||||
"class": &"TableCamera",
|
"class": &"TableCamera",
|
||||||
"icon": "",
|
"icon": "",
|
||||||
|
|||||||
BIN
FF_mat_option_1.png
Normal file
|
After Width: | Height: | Size: 20 MiB |
34
FF_mat_option_1.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://b3d41k7p505h3"
|
||||||
|
path="res://.godot/imported/FF_mat_option_1.png-4c587e83f2d49b5ff6f5af6fbfec88c4.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://FF_mat_option_1.png"
|
||||||
|
dest_files=["res://.godot/imported/FF_mat_option_1.png-4c587e83f2d49b5ff6f5af6fbfec88c4.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
|
||||||
BIN
background_1.jpeg
Normal file
|
After Width: | Height: | Size: 589 KiB |
1291
data/cards.json
BIN
mat_part_2.pdf
Normal file
@@ -78,9 +78,10 @@ func _setup_ui() -> void:
|
|||||||
# We'll update position in _process or use a deferred call after container is sized
|
# We'll update position in _process or use a deferred call after container is sized
|
||||||
call_deferred("_position_hand_display")
|
call_deferred("_position_hand_display")
|
||||||
|
|
||||||
hand_display.card_selected.connect(_on_hand_card_selected)
|
hand_display.card_action_requested.connect(_on_hand_card_action)
|
||||||
hand_display.card_hovered.connect(_on_hand_card_hovered)
|
hand_display.card_hovered.connect(_on_hand_card_hovered)
|
||||||
hand_display.card_unhovered.connect(_on_hand_card_unhovered)
|
hand_display.card_unhovered.connect(_on_hand_card_unhovered)
|
||||||
|
hand_display.card_selected.connect(_on_hand_card_selected)
|
||||||
|
|
||||||
# Create damage displays (positioned by 3D camera overlay later)
|
# Create damage displays (positioned by 3D camera overlay later)
|
||||||
for i in range(2):
|
for i in range(2):
|
||||||
@@ -92,20 +93,27 @@ func _position_hand_display() -> void:
|
|||||||
var viewport = get_viewport()
|
var viewport = get_viewport()
|
||||||
if viewport:
|
if viewport:
|
||||||
var vp_size = viewport.get_visible_rect().size
|
var vp_size = viewport.get_visible_rect().size
|
||||||
hand_display.position = Vector2(50, vp_size.y - 180)
|
# Hand cards are 195x273px (triple original), positioned at y=0 in container
|
||||||
hand_display.size = Vector2(vp_size.x - 100, 170)
|
var card_height = 273.0
|
||||||
|
var hand_height = card_height + 25.0 # Extra space for hover lift
|
||||||
|
|
||||||
|
# Position so card bottom is 100px above viewport bottom
|
||||||
|
var bottom_offset = 100.0
|
||||||
|
hand_display.position = Vector2(10, vp_size.y - bottom_offset - card_height)
|
||||||
|
hand_display.size = Vector2(vp_size.x - 20, hand_height)
|
||||||
|
|
||||||
# Connect to viewport size changed signal if not already connected
|
|
||||||
if not viewport.size_changed.is_connected(_on_viewport_resized):
|
if not viewport.size_changed.is_connected(_on_viewport_resized):
|
||||||
viewport.size_changed.connect(_on_viewport_resized)
|
viewport.size_changed.connect(_on_viewport_resized)
|
||||||
|
|
||||||
func _on_viewport_resized() -> void:
|
func _on_viewport_resized() -> void:
|
||||||
# Reposition hand display when window resizes
|
|
||||||
var viewport = get_viewport()
|
var viewport = get_viewport()
|
||||||
if viewport and hand_display:
|
if viewport and hand_display:
|
||||||
var vp_size = viewport.get_visible_rect().size
|
var vp_size = viewport.get_visible_rect().size
|
||||||
hand_display.position = Vector2(50, vp_size.y - 180)
|
var card_height = 273.0 # Triple size hand cards
|
||||||
hand_display.size = Vector2(vp_size.x - 100, 170)
|
var hand_height = card_height + 25.0
|
||||||
|
var bottom_offset = 100.0
|
||||||
|
hand_display.position = Vector2(10, vp_size.y - bottom_offset - card_height)
|
||||||
|
hand_display.size = Vector2(vp_size.x - 20, hand_height)
|
||||||
|
|
||||||
func _connect_signals() -> void:
|
func _connect_signals() -> void:
|
||||||
# GameManager signals
|
# GameManager signals
|
||||||
@@ -192,28 +200,38 @@ func _update_playable_highlights() -> void:
|
|||||||
_:
|
_:
|
||||||
hand_display.clear_highlights()
|
hand_display.clear_highlights()
|
||||||
|
|
||||||
func _on_hand_card_selected(card: CardInstance) -> void:
|
func _on_hand_card_action(card: CardInstance, action: String) -> void:
|
||||||
var input_mode = GameManager.input_mode
|
match action:
|
||||||
|
"play":
|
||||||
match input_mode:
|
# Try to play the card
|
||||||
GameManager.InputMode.SELECT_CARD_TO_PLAY:
|
|
||||||
GameManager.try_play_card(card)
|
GameManager.try_play_card(card)
|
||||||
_sync_visuals()
|
_sync_visuals()
|
||||||
_update_hand_display()
|
_update_hand_display()
|
||||||
_update_cp_display()
|
_update_cp_display()
|
||||||
|
|
||||||
GameManager.InputMode.SELECT_CP_SOURCE:
|
"discard_cp":
|
||||||
# Discard for CP
|
# Discard for CP
|
||||||
GameManager.discard_card_for_cp(card)
|
GameManager.discard_card_for_cp(card)
|
||||||
|
_sync_visuals()
|
||||||
_update_hand_display()
|
_update_hand_display()
|
||||||
_update_cp_display()
|
_update_cp_display()
|
||||||
|
|
||||||
func _on_hand_card_hovered(card: CardInstance) -> void:
|
"view":
|
||||||
|
# Show detailed card view
|
||||||
game_ui.show_card_detail(card)
|
game_ui.show_card_detail(card)
|
||||||
|
|
||||||
|
func _on_hand_card_hovered(_card: CardInstance) -> void:
|
||||||
|
# Hand cards use the selection panel for detail view, not the GameUI hover preview
|
||||||
|
# So we intentionally do nothing here - no hover preview for hand cards
|
||||||
|
pass
|
||||||
|
|
||||||
func _on_hand_card_unhovered() -> void:
|
func _on_hand_card_unhovered() -> void:
|
||||||
game_ui.hide_card_detail()
|
game_ui.hide_card_detail()
|
||||||
|
|
||||||
|
func _on_hand_card_selected(_card: CardInstance) -> void:
|
||||||
|
# Selection panel is now visible - ensure any stale hover preview is hidden
|
||||||
|
game_ui.hide_card_detail()
|
||||||
|
|
||||||
func _on_undo_requested() -> void:
|
func _on_undo_requested() -> void:
|
||||||
if GameManager.undo_last_action():
|
if GameManager.undo_last_action():
|
||||||
_sync_visuals()
|
_sync_visuals()
|
||||||
|
|||||||
@@ -166,7 +166,15 @@ func get_card_texture(card: CardData) -> Texture2D:
|
|||||||
if card.image_path.is_empty():
|
if card.image_path.is_empty():
|
||||||
return null
|
return null
|
||||||
|
|
||||||
var texture_path = "res://assets/cards/" + card.image_path
|
# 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):
|
if ResourceLoader.exists(texture_path):
|
||||||
var texture = load(texture_path)
|
var texture = load(texture_path)
|
||||||
_card_textures[card.id] = texture
|
_card_textures[card.id] = texture
|
||||||
@@ -175,25 +183,53 @@ func get_card_texture(card: CardData) -> Texture2D:
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
## Create a list of card IDs for a deck (for testing)
|
## Create a list of card IDs for a deck (for testing)
|
||||||
func create_test_deck(_player_index: int) -> Array[String]:
|
## 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] = []
|
var deck: Array[String] = []
|
||||||
|
|
||||||
# Get all cards and create a 50-card deck
|
# Define element pairs for each player
|
||||||
var all_cards = get_all_cards()
|
var primary_element: Enums.Element
|
||||||
|
var secondary_element: Enums.Element
|
||||||
|
|
||||||
# Add 3 copies of each card until we have 50
|
if player_index == 0:
|
||||||
for card in all_cards:
|
primary_element = Enums.Element.FIRE
|
||||||
if deck.size() >= 50:
|
secondary_element = Enums.Element.ICE
|
||||||
break
|
else:
|
||||||
# Add up to 3 copies
|
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):
|
for i in range(3):
|
||||||
if deck.size() >= 50:
|
if deck.size() < 50:
|
||||||
break
|
|
||||||
deck.append(card.id)
|
deck.append(card.id)
|
||||||
|
|
||||||
# Fill remaining slots if needed
|
# Add 3 copies of each secondary element card
|
||||||
while deck.size() < 50 and all_cards.size() > 0:
|
for card in secondary_cards:
|
||||||
deck.append(all_cards[0].id)
|
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
|
return deck
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ func start_new_game() -> void:
|
|||||||
func _connect_game_signals() -> void:
|
func _connect_game_signals() -> void:
|
||||||
game_state.game_ended.connect(_on_game_ended)
|
game_state.game_ended.connect(_on_game_ended)
|
||||||
game_state.card_played.connect(_on_card_played)
|
game_state.card_played.connect(_on_card_played)
|
||||||
|
game_state.summon_cast.connect(_on_summon_cast)
|
||||||
game_state.damage_dealt.connect(_on_damage_dealt)
|
game_state.damage_dealt.connect(_on_damage_dealt)
|
||||||
game_state.forward_broken.connect(_on_forward_broken)
|
game_state.forward_broken.connect(_on_forward_broken)
|
||||||
game_state.attack_declared.connect(_on_attack_declared)
|
game_state.attack_declared.connect(_on_attack_declared)
|
||||||
@@ -147,14 +148,19 @@ func try_play_card(card: CardInstance) -> bool:
|
|||||||
message.emit(play_error)
|
message.emit(play_error)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# Determine target zone
|
# Handle summons differently
|
||||||
|
if card.is_summon():
|
||||||
|
return _try_cast_summon(player_index, card)
|
||||||
|
|
||||||
|
# Determine target zone for Forwards/Backups
|
||||||
var to_zone = Enums.ZoneType.FIELD_FORWARDS if card.is_forward() else Enums.ZoneType.FIELD_BACKUPS
|
var to_zone = Enums.ZoneType.FIELD_FORWARDS if card.is_forward() else Enums.ZoneType.FIELD_BACKUPS
|
||||||
|
|
||||||
# Try to play
|
# Try to play (returns CP spent dict)
|
||||||
if game_state.play_card(player_index, card):
|
var cp_spent = game_state.play_card(player_index, card)
|
||||||
# Record for undo
|
if not cp_spent.is_empty():
|
||||||
|
# Record for undo with actual CP spent
|
||||||
if undo_system:
|
if undo_system:
|
||||||
undo_system.record_play_card(player_index, card, to_zone, {})
|
undo_system.record_play_card(player_index, card, to_zone, cp_spent)
|
||||||
|
|
||||||
message.emit("Played " + card.get_display_name())
|
message.emit("Played " + card.get_display_name())
|
||||||
return true
|
return true
|
||||||
@@ -164,6 +170,18 @@ func try_play_card(card: CardInstance) -> bool:
|
|||||||
message.emit("Failed to play " + card.get_display_name() + " (internal error)")
|
message.emit("Failed to play " + card.get_display_name() + " (internal error)")
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
## Try to cast a summon
|
||||||
|
func _try_cast_summon(player_index: int, card: CardInstance) -> bool:
|
||||||
|
if game_state.cast_summon(player_index, card):
|
||||||
|
# Note: Summons cannot be undone as they have immediate effects
|
||||||
|
message.emit("Cast " + card.get_display_name() + "!")
|
||||||
|
# TODO: Implement effect resolution system
|
||||||
|
# For now, summons just go to break zone with no effect
|
||||||
|
return true
|
||||||
|
else:
|
||||||
|
message.emit("Failed to cast " + card.get_display_name() + " (internal error)")
|
||||||
|
return false
|
||||||
|
|
||||||
## Check play restrictions and return error message, or empty string if playable
|
## Check play restrictions and return error message, or empty string if playable
|
||||||
func _check_play_restrictions(player: Player, card: CardInstance) -> String:
|
func _check_play_restrictions(player: Player, card: CardInstance) -> String:
|
||||||
# Check if card is in hand
|
# Check if card is in hand
|
||||||
@@ -175,11 +193,11 @@ func _check_play_restrictions(player: Player, card: CardInstance) -> String:
|
|||||||
if player.field_backups.get_count() >= Player.MAX_BACKUPS:
|
if player.field_backups.get_count() >= Player.MAX_BACKUPS:
|
||||||
return "Cannot play: Maximum 5 Backups allowed!"
|
return "Cannot play: Maximum 5 Backups allowed!"
|
||||||
|
|
||||||
# Check unique name restriction (non-generic cards)
|
# Check unique name restriction (non-generic cards can't share names across entire field)
|
||||||
if not card.card_data.is_generic:
|
if not card.card_data.is_generic:
|
||||||
if card.is_forward() and player.field_forwards.has_card_with_name(card.card_data.name):
|
if player.field_forwards.has_card_with_name(card.card_data.name):
|
||||||
return "Cannot play: You already have " + card.card_data.name + " on the field!"
|
return "Cannot play: You already have " + card.card_data.name + " on the field!"
|
||||||
if card.is_backup() and player.field_backups.has_card_with_name(card.card_data.name):
|
if player.field_backups.has_card_with_name(card.card_data.name):
|
||||||
return "Cannot play: You already have " + card.card_data.name + " on the field!"
|
return "Cannot play: You already have " + card.card_data.name + " on the field!"
|
||||||
|
|
||||||
# Check Light/Dark restriction
|
# Check Light/Dark restriction
|
||||||
@@ -194,6 +212,11 @@ func discard_card_for_cp(card: CardInstance) -> bool:
|
|||||||
if not game_state or not is_game_active:
|
if not game_state or not is_game_active:
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
# Check Light/Dark restriction before attempting
|
||||||
|
if card.is_light_or_dark():
|
||||||
|
message.emit("Cannot discard Light/Dark cards for CP!")
|
||||||
|
return false
|
||||||
|
|
||||||
var player_index = card.owner_index
|
var player_index = card.owner_index
|
||||||
var element = card.get_element()
|
var element = card.get_element()
|
||||||
|
|
||||||
@@ -239,9 +262,11 @@ func _check_pending_action() -> void:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if player.cp_pool.can_afford_card(selected_card.card_data):
|
if player.cp_pool.can_afford_card(selected_card.card_data):
|
||||||
# Can now afford - try to play
|
# Per FF-TCG rules: CP is generated to pay for a specific card
|
||||||
try_play_card(selected_card)
|
# Auto-play when we have enough CP
|
||||||
clear_selection()
|
var card_to_play = selected_card
|
||||||
|
clear_selection() # Clear first to avoid re-entry
|
||||||
|
try_play_card(card_to_play)
|
||||||
|
|
||||||
## Declare an attack
|
## Declare an attack
|
||||||
func declare_attack(card: CardInstance) -> bool:
|
func declare_attack(card: CardInstance) -> bool:
|
||||||
@@ -356,6 +381,12 @@ func _on_card_played(card: CardInstance, _player_index: int) -> void:
|
|||||||
"type": Enums.card_type_to_string(card.card_data.type)
|
"type": Enums.card_type_to_string(card.card_data.type)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
func _on_summon_cast(card: CardInstance, _player_index: int) -> void:
|
||||||
|
card_played.emit({
|
||||||
|
"name": card.get_display_name(),
|
||||||
|
"type": "Summon"
|
||||||
|
})
|
||||||
|
|
||||||
func _on_damage_dealt(player_index: int, amount: int, _cards: Array) -> void:
|
func _on_damage_dealt(player_index: int, amount: int, _cards: Array) -> void:
|
||||||
var player_name = game_state.get_player(player_index).player_name
|
var player_name = game_state.get_player(player_index).player_name
|
||||||
damage_dealt.emit(player_name, amount)
|
damage_dealt.emit(player_name, amount)
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ func clear() -> void:
|
|||||||
for element in Enums.Element.values():
|
for element in Enums.Element.values():
|
||||||
_cp[element] = 0
|
_cp[element] = 0
|
||||||
|
|
||||||
## Add CP of a specific element
|
## Add CP of a specific element (floor at 0 to prevent negative)
|
||||||
func add_cp(element: Enums.Element, amount: int) -> void:
|
func add_cp(element: Enums.Element, amount: int) -> void:
|
||||||
_cp[element] = _cp.get(element, 0) + amount
|
_cp[element] = max(0, _cp.get(element, 0) + amount)
|
||||||
|
|
||||||
## Get CP of a specific element
|
## Get CP of a specific element
|
||||||
func get_cp(element: Enums.Element) -> int:
|
func get_cp(element: Enums.Element) -> int:
|
||||||
@@ -29,6 +29,14 @@ func get_total_cp() -> int:
|
|||||||
total += _cp[element]
|
total += _cp[element]
|
||||||
return total
|
return total
|
||||||
|
|
||||||
|
## Get total CP of non-Light/Dark elements
|
||||||
|
func get_non_light_dark_cp() -> int:
|
||||||
|
var total = 0
|
||||||
|
for element in _cp:
|
||||||
|
if not Enums.is_light_or_dark(element):
|
||||||
|
total += _cp[element]
|
||||||
|
return total
|
||||||
|
|
||||||
## Check if we can afford a card cost
|
## Check if we can afford a card cost
|
||||||
func can_afford_card(card_data: CardDatabase.CardData) -> bool:
|
func can_afford_card(card_data: CardDatabase.CardData) -> bool:
|
||||||
if not card_data:
|
if not card_data:
|
||||||
@@ -37,14 +45,19 @@ func can_afford_card(card_data: CardDatabase.CardData) -> bool:
|
|||||||
var cost = card_data.cost
|
var cost = card_data.cost
|
||||||
var elements = card_data.elements
|
var elements = card_data.elements
|
||||||
|
|
||||||
# For Light/Dark cards, just need total CP
|
# Check if this is a Light/Dark card
|
||||||
var is_light_dark = false
|
var is_light_dark = false
|
||||||
for element in elements:
|
for element in elements:
|
||||||
if Enums.is_light_or_dark(element):
|
if Enums.is_light_or_dark(element):
|
||||||
is_light_dark = true
|
is_light_dark = true
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# For Light/Dark cards: need at least 1 CP of any non-Light/Dark element
|
||||||
|
# (Light/Dark cards can be paid with any combination of CP, but you need
|
||||||
|
# at least 1 CP from a regular element - can't pay entirely with L/D CP)
|
||||||
if is_light_dark:
|
if is_light_dark:
|
||||||
|
if get_non_light_dark_cp() < 1:
|
||||||
|
return false
|
||||||
return get_total_cp() >= cost
|
return get_total_cp() >= cost
|
||||||
|
|
||||||
# For multi-element cards, need at least 1 CP of each element
|
# For multi-element cards, need at least 1 CP of each element
|
||||||
@@ -63,19 +76,39 @@ func can_afford_card(card_data: CardDatabase.CardData) -> bool:
|
|||||||
return get_total_cp() >= cost
|
return get_total_cp() >= cost
|
||||||
|
|
||||||
## Spend CP to pay a card cost
|
## Spend CP to pay a card cost
|
||||||
## Returns true if successful, false if cannot afford
|
## Returns dictionary of CP spent by element, or empty dict if cannot afford
|
||||||
func spend_for_card(card_data: CardDatabase.CardData) -> bool:
|
func spend_for_card(card_data: CardDatabase.CardData) -> Dictionary:
|
||||||
if not can_afford_card(card_data):
|
if not can_afford_card(card_data):
|
||||||
return false
|
return {}
|
||||||
|
|
||||||
var cost = card_data.cost
|
var cost = card_data.cost
|
||||||
var elements = card_data.elements
|
var elements = card_data.elements
|
||||||
var remaining = cost
|
var remaining = cost
|
||||||
|
var spent_cp: Dictionary = {} # Track what we spent
|
||||||
|
var max_iterations = cost + 10 # Safety limit to prevent infinite loops
|
||||||
|
var iterations = 0
|
||||||
|
|
||||||
|
# Check if this is a Light/Dark card
|
||||||
|
var is_light_dark = false
|
||||||
|
for element in elements:
|
||||||
|
if Enums.is_light_or_dark(element):
|
||||||
|
is_light_dark = true
|
||||||
|
break
|
||||||
|
|
||||||
|
# For Light/Dark cards: spend 1 non-Light/Dark CP first (required)
|
||||||
|
if is_light_dark:
|
||||||
|
for element in _cp:
|
||||||
|
if not Enums.is_light_or_dark(element) and _cp[element] > 0:
|
||||||
|
_cp[element] -= 1
|
||||||
|
spent_cp[element] = spent_cp.get(element, 0) + 1
|
||||||
|
remaining -= 1
|
||||||
|
break
|
||||||
|
|
||||||
# For multi-element, spend 1 of each required element first
|
# For multi-element, spend 1 of each required element first
|
||||||
if elements.size() > 1:
|
elif elements.size() > 1:
|
||||||
for element in elements:
|
for element in elements:
|
||||||
_cp[element] -= 1
|
_cp[element] -= 1
|
||||||
|
spent_cp[element] = spent_cp.get(element, 0) + 1
|
||||||
remaining -= 1
|
remaining -= 1
|
||||||
|
|
||||||
# For single element (non-Light/Dark), spend at least 1 of that element
|
# For single element (non-Light/Dark), spend at least 1 of that element
|
||||||
@@ -83,22 +116,42 @@ func spend_for_card(card_data: CardDatabase.CardData) -> bool:
|
|||||||
var element = elements[0]
|
var element = elements[0]
|
||||||
if not Enums.is_light_or_dark(element):
|
if not Enums.is_light_or_dark(element):
|
||||||
_cp[element] -= 1
|
_cp[element] -= 1
|
||||||
|
spent_cp[element] = spent_cp.get(element, 0) + 1
|
||||||
remaining -= 1
|
remaining -= 1
|
||||||
|
|
||||||
# Spend remaining from any element
|
# Spend remaining from any element (prefer non-Light/Dark first)
|
||||||
while remaining > 0:
|
while remaining > 0 and iterations < max_iterations:
|
||||||
var spent = false
|
iterations += 1
|
||||||
|
var did_spend = false
|
||||||
|
|
||||||
|
# First try non-Light/Dark elements
|
||||||
|
for element in _cp:
|
||||||
|
if not Enums.is_light_or_dark(element) and _cp[element] > 0:
|
||||||
|
_cp[element] -= 1
|
||||||
|
spent_cp[element] = spent_cp.get(element, 0) + 1
|
||||||
|
remaining -= 1
|
||||||
|
did_spend = true
|
||||||
|
break
|
||||||
|
|
||||||
|
# Then try Light/Dark elements if needed
|
||||||
|
if not did_spend:
|
||||||
for element in _cp:
|
for element in _cp:
|
||||||
if _cp[element] > 0:
|
if _cp[element] > 0:
|
||||||
_cp[element] -= 1
|
_cp[element] -= 1
|
||||||
|
spent_cp[element] = spent_cp.get(element, 0) + 1
|
||||||
remaining -= 1
|
remaining -= 1
|
||||||
spent = true
|
did_spend = true
|
||||||
break
|
break
|
||||||
if not spent:
|
|
||||||
push_error("Failed to spend remaining CP")
|
|
||||||
return false
|
|
||||||
|
|
||||||
return true
|
if not did_spend:
|
||||||
|
push_error("Failed to spend remaining CP")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if remaining > 0:
|
||||||
|
push_error("CP spending exceeded maximum iterations")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return spent_cp
|
||||||
|
|
||||||
## Check if we can afford an ability cost
|
## Check if we can afford an ability cost
|
||||||
func can_afford_ability(cost: CardDatabase.CostData) -> bool:
|
func can_afford_ability(cost: CardDatabase.CostData) -> bool:
|
||||||
@@ -131,30 +184,30 @@ func spend_for_ability(cost: CardDatabase.CostData) -> bool:
|
|||||||
if not can_afford_ability(cost):
|
if not can_afford_ability(cost):
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# Spend element-specific CP
|
# Spend element-specific CP (use add_cp with negative to ensure floor at 0)
|
||||||
if cost.fire > 0:
|
if cost.fire > 0:
|
||||||
_cp[Enums.Element.FIRE] -= cost.fire
|
add_cp(Enums.Element.FIRE, -cost.fire)
|
||||||
if cost.ice > 0:
|
if cost.ice > 0:
|
||||||
_cp[Enums.Element.ICE] -= cost.ice
|
add_cp(Enums.Element.ICE, -cost.ice)
|
||||||
if cost.wind > 0:
|
if cost.wind > 0:
|
||||||
_cp[Enums.Element.WIND] -= cost.wind
|
add_cp(Enums.Element.WIND, -cost.wind)
|
||||||
if cost.lightning > 0:
|
if cost.lightning > 0:
|
||||||
_cp[Enums.Element.LIGHTNING] -= cost.lightning
|
add_cp(Enums.Element.LIGHTNING, -cost.lightning)
|
||||||
if cost.water > 0:
|
if cost.water > 0:
|
||||||
_cp[Enums.Element.WATER] -= cost.water
|
add_cp(Enums.Element.WATER, -cost.water)
|
||||||
if cost.earth > 0:
|
if cost.earth > 0:
|
||||||
_cp[Enums.Element.EARTH] -= cost.earth
|
add_cp(Enums.Element.EARTH, -cost.earth)
|
||||||
if cost.light > 0:
|
if cost.light > 0:
|
||||||
_cp[Enums.Element.LIGHT] -= cost.light
|
add_cp(Enums.Element.LIGHT, -cost.light)
|
||||||
if cost.dark > 0:
|
if cost.dark > 0:
|
||||||
_cp[Enums.Element.DARK] -= cost.dark
|
add_cp(Enums.Element.DARK, -cost.dark)
|
||||||
|
|
||||||
# Spend generic from any element
|
# Spend generic from any element
|
||||||
var generic_remaining = cost.generic
|
var generic_remaining = cost.generic
|
||||||
while generic_remaining > 0:
|
while generic_remaining > 0:
|
||||||
for element in _cp:
|
for element in _cp:
|
||||||
if _cp[element] > 0:
|
if _cp[element] > 0:
|
||||||
_cp[element] -= 1
|
add_cp(element, -1)
|
||||||
generic_remaining -= 1
|
generic_remaining -= 1
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ extends RefCounted
|
|||||||
signal game_started
|
signal game_started
|
||||||
signal game_ended(winner_index: int)
|
signal game_ended(winner_index: int)
|
||||||
signal card_played(card: CardInstance, player_index: int)
|
signal card_played(card: CardInstance, player_index: int)
|
||||||
|
signal summon_cast(card: CardInstance, player_index: int)
|
||||||
signal card_moved(card: CardInstance, from_zone: Enums.ZoneType, to_zone: Enums.ZoneType)
|
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 damage_dealt(player_index: int, amount: int, cards: Array[CardInstance])
|
||||||
signal forward_broken(card: CardInstance)
|
signal forward_broken(card: CardInstance)
|
||||||
@@ -106,7 +107,31 @@ func end_main_phase() -> void:
|
|||||||
turn_manager.advance_phase()
|
turn_manager.advance_phase()
|
||||||
|
|
||||||
## Play a card from hand
|
## Play a card from hand
|
||||||
func play_card(player_index: int, card: CardInstance) -> bool:
|
## Returns dictionary of CP spent, or empty dict on failure
|
||||||
|
func play_card(player_index: int, card: CardInstance) -> Dictionary:
|
||||||
|
if not game_active:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if player_index != turn_manager.current_player_index:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if not turn_manager.is_main_phase():
|
||||||
|
return {}
|
||||||
|
|
||||||
|
var player = players[player_index]
|
||||||
|
|
||||||
|
# Validate and play (returns CP spent dict)
|
||||||
|
var cp_spent = player.play_card(card)
|
||||||
|
if not cp_spent.is_empty():
|
||||||
|
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 cp_spent
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
## Cast a summon from hand
|
||||||
|
func cast_summon(player_index: int, card: CardInstance) -> bool:
|
||||||
if not game_active:
|
if not game_active:
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@@ -118,11 +143,15 @@ func play_card(player_index: int, card: CardInstance) -> bool:
|
|||||||
|
|
||||||
var player = players[player_index]
|
var player = players[player_index]
|
||||||
|
|
||||||
# Validate and play
|
# Cast the summon (pays cost, removes from hand)
|
||||||
if player.play_card(card):
|
var cast_card = player.cast_summon(card)
|
||||||
card_played.emit(card, player_index)
|
if cast_card:
|
||||||
card_moved.emit(card, Enums.ZoneType.HAND,
|
# Emit signal for effect resolution (TODO: implement effect system)
|
||||||
Enums.ZoneType.FIELD_FORWARDS if card.is_forward() else Enums.ZoneType.FIELD_BACKUPS)
|
summon_cast.emit(cast_card, player_index)
|
||||||
|
|
||||||
|
# Move to break zone after casting
|
||||||
|
player.break_zone.add_card(cast_card)
|
||||||
|
card_moved.emit(cast_card, Enums.ZoneType.HAND, Enums.ZoneType.BREAK)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@@ -168,6 +197,10 @@ func declare_attack(attacker: CardInstance) -> bool:
|
|||||||
if not turn_manager.is_attack_phase():
|
if not turn_manager.is_attack_phase():
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
# Must be in DECLARATION step to declare attack
|
||||||
|
if turn_manager.attack_step != Enums.AttackStep.DECLARATION:
|
||||||
|
return false
|
||||||
|
|
||||||
var player = get_current_player()
|
var player = get_current_player()
|
||||||
|
|
||||||
if not player.field_forwards.has_card(attacker):
|
if not player.field_forwards.has_card(attacker):
|
||||||
@@ -181,7 +214,14 @@ func declare_attack(attacker: CardInstance) -> bool:
|
|||||||
attacker.dull()
|
attacker.dull()
|
||||||
|
|
||||||
attacker.attacked_this_turn = true
|
attacker.attacked_this_turn = true
|
||||||
turn_manager.set_attacker(attacker)
|
|
||||||
|
# Update attack step (now returns bool for validation)
|
||||||
|
if not turn_manager.set_attacker(attacker):
|
||||||
|
# Rollback dull if state transition failed
|
||||||
|
if not attacker.has_brave():
|
||||||
|
attacker.activate()
|
||||||
|
attacker.attacked_this_turn = false
|
||||||
|
return false
|
||||||
|
|
||||||
attack_declared.emit(attacker)
|
attack_declared.emit(attacker)
|
||||||
return true
|
return true
|
||||||
@@ -203,7 +243,10 @@ func declare_block(blocker: CardInstance) -> bool:
|
|||||||
if not blocker.can_block():
|
if not blocker.can_block():
|
||||||
return false
|
return false
|
||||||
|
|
||||||
turn_manager.set_blocker(blocker)
|
# Update attack step (now returns bool for validation)
|
||||||
|
if not turn_manager.set_blocker(blocker):
|
||||||
|
return false
|
||||||
|
|
||||||
if blocker:
|
if blocker:
|
||||||
block_declared.emit(blocker)
|
block_declared.emit(blocker)
|
||||||
|
|
||||||
@@ -220,10 +263,22 @@ func resolve_combat() -> void:
|
|||||||
|
|
||||||
var attacker = turn_manager.current_attacker
|
var attacker = turn_manager.current_attacker
|
||||||
var blocker = turn_manager.current_blocker
|
var blocker = turn_manager.current_blocker
|
||||||
|
var player = get_current_player()
|
||||||
|
|
||||||
|
# Validate attacker is still on field (could have been broken by ability)
|
||||||
|
if not attacker or not player.field_forwards.has_card(attacker):
|
||||||
|
turn_manager.complete_attack()
|
||||||
|
return
|
||||||
|
|
||||||
if blocker == null:
|
if blocker == null:
|
||||||
# Unblocked - deal 1 damage to opponent
|
# Unblocked - deal 1 damage to opponent
|
||||||
_deal_damage_to_player(1 - turn_manager.current_player_index, 1)
|
_deal_damage_to_player(1 - turn_manager.current_player_index, 1)
|
||||||
|
else:
|
||||||
|
# Validate blocker is still on field
|
||||||
|
var opponent = get_opponent()
|
||||||
|
if not opponent.field_forwards.has_card(blocker):
|
||||||
|
# Blocker was removed - attack goes through as unblocked
|
||||||
|
_deal_damage_to_player(1 - turn_manager.current_player_index, 1)
|
||||||
else:
|
else:
|
||||||
# Blocked - exchange damage
|
# Blocked - exchange damage
|
||||||
var attacker_power = attacker.get_power()
|
var attacker_power = attacker.get_power()
|
||||||
|
|||||||
@@ -130,34 +130,36 @@ func dull_backup_for_cp(card: CardInstance) -> bool:
|
|||||||
return true
|
return true
|
||||||
|
|
||||||
## Play a card from hand to field
|
## Play a card from hand to field
|
||||||
func play_card(card: CardInstance) -> bool:
|
## Returns dictionary of CP spent, or empty dict on failure
|
||||||
|
func play_card(card: CardInstance) -> Dictionary:
|
||||||
if not hand.has_card(card):
|
if not hand.has_card(card):
|
||||||
return false
|
return {}
|
||||||
|
|
||||||
# Check if we can afford it
|
# Check if we can afford it
|
||||||
if not cp_pool.can_afford_card(card.card_data):
|
if not cp_pool.can_afford_card(card.card_data):
|
||||||
return false
|
return {}
|
||||||
|
|
||||||
# Check field restrictions
|
# Check field restrictions
|
||||||
if card.is_backup():
|
if card.is_backup():
|
||||||
if field_backups.get_count() >= MAX_BACKUPS:
|
if field_backups.get_count() >= MAX_BACKUPS:
|
||||||
return false
|
return {}
|
||||||
|
|
||||||
# Check unique name restriction
|
# Check unique name restriction (non-generic cards can't share names across entire field)
|
||||||
if not card.card_data.is_generic:
|
if not card.card_data.is_generic:
|
||||||
if card.is_forward() and field_forwards.has_card_with_name(card.card_data.name):
|
if field_forwards.has_card_with_name(card.card_data.name):
|
||||||
return false
|
return {}
|
||||||
if card.is_backup() and field_backups.has_card_with_name(card.card_data.name):
|
if field_backups.has_card_with_name(card.card_data.name):
|
||||||
return false
|
return {}
|
||||||
|
|
||||||
# Check Light/Dark restriction
|
# Check Light/Dark restriction
|
||||||
if card.is_light_or_dark():
|
if card.is_light_or_dark():
|
||||||
if field_forwards.has_light_or_dark() or field_backups.has_light_or_dark():
|
if field_forwards.has_light_or_dark() or field_backups.has_light_or_dark():
|
||||||
return false
|
return {}
|
||||||
|
|
||||||
# Pay the cost
|
# Pay the cost (returns dict of spent CP, empty if failed)
|
||||||
if not cp_pool.spend_for_card(card.card_data):
|
var spent = cp_pool.spend_for_card(card.card_data)
|
||||||
return false
|
if spent.is_empty():
|
||||||
|
return {}
|
||||||
|
|
||||||
# Move to field
|
# Move to field
|
||||||
hand.remove_card(card)
|
hand.remove_card(card)
|
||||||
@@ -171,7 +173,29 @@ func play_card(card: CardInstance) -> bool:
|
|||||||
|
|
||||||
card.entered_field()
|
card.entered_field()
|
||||||
|
|
||||||
return true
|
return spent
|
||||||
|
|
||||||
|
## Cast a summon from hand (pays cost, removes from hand, returns card for effect resolution)
|
||||||
|
func cast_summon(card: CardInstance) -> CardInstance:
|
||||||
|
if not hand.has_card(card):
|
||||||
|
return null
|
||||||
|
|
||||||
|
if not card.is_summon():
|
||||||
|
return null
|
||||||
|
|
||||||
|
# Check if we can afford it
|
||||||
|
if not cp_pool.can_afford_card(card.card_data):
|
||||||
|
return null
|
||||||
|
|
||||||
|
# Pay the cost (returns dict of spent CP, empty if failed)
|
||||||
|
var spent = cp_pool.spend_for_card(card.card_data)
|
||||||
|
if spent.is_empty():
|
||||||
|
return null
|
||||||
|
|
||||||
|
# Remove from hand - card will be moved to break zone after effect resolves
|
||||||
|
hand.remove_card(card)
|
||||||
|
|
||||||
|
return card
|
||||||
|
|
||||||
## Break a card (move from field to break zone)
|
## Break a card (move from field to break zone)
|
||||||
func break_card(card: CardInstance) -> bool:
|
func break_card(card: CardInstance) -> bool:
|
||||||
|
|||||||
@@ -96,36 +96,53 @@ func is_attack_phase() -> bool:
|
|||||||
|
|
||||||
## Get cards drawn this turn (2 normally, 1 on first turn for first player)
|
## Get cards drawn this turn (2 normally, 1 on first turn for first player)
|
||||||
func get_draw_count() -> int:
|
func get_draw_count() -> int:
|
||||||
if is_first_turn and current_player_index == 0:
|
if is_first_turn:
|
||||||
return 1
|
return 1
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
## Attack phase state management
|
## Attack phase state management
|
||||||
|
|
||||||
## Start attack declaration
|
## Start attack declaration (only valid from PREPARATION)
|
||||||
func start_attack_declaration() -> void:
|
func start_attack_declaration() -> bool:
|
||||||
|
if attack_step != Enums.AttackStep.PREPARATION:
|
||||||
|
return false
|
||||||
attack_step = Enums.AttackStep.DECLARATION
|
attack_step = Enums.AttackStep.DECLARATION
|
||||||
|
return true
|
||||||
|
|
||||||
## Set the attacking forward
|
## Set the attacking forward (only valid from DECLARATION)
|
||||||
func set_attacker(attacker: CardInstance) -> void:
|
func set_attacker(attacker: CardInstance) -> bool:
|
||||||
|
if attack_step != Enums.AttackStep.DECLARATION:
|
||||||
|
return false
|
||||||
|
if attacker == null:
|
||||||
|
return false
|
||||||
current_attacker = attacker
|
current_attacker = attacker
|
||||||
attack_step = Enums.AttackStep.BLOCK_DECLARATION
|
attack_step = Enums.AttackStep.BLOCK_DECLARATION
|
||||||
|
return true
|
||||||
|
|
||||||
## Set the blocking forward (or null for no block)
|
## Set the blocking forward (or null for no block) - only valid from BLOCK_DECLARATION
|
||||||
func set_blocker(blocker: CardInstance) -> void:
|
func set_blocker(blocker: CardInstance) -> bool:
|
||||||
current_blocker = blocker
|
if attack_step != Enums.AttackStep.BLOCK_DECLARATION:
|
||||||
|
return false
|
||||||
|
current_blocker = blocker # null is valid (no block)
|
||||||
attack_step = Enums.AttackStep.DAMAGE_RESOLUTION
|
attack_step = Enums.AttackStep.DAMAGE_RESOLUTION
|
||||||
|
return true
|
||||||
|
|
||||||
## Complete the current attack
|
## Complete the current attack (only valid from DAMAGE_RESOLUTION)
|
||||||
func complete_attack() -> void:
|
func complete_attack() -> bool:
|
||||||
|
if attack_step != Enums.AttackStep.DAMAGE_RESOLUTION:
|
||||||
|
return false
|
||||||
current_attacker = null
|
current_attacker = null
|
||||||
current_blocker = null
|
current_blocker = null
|
||||||
attack_step = Enums.AttackStep.DECLARATION
|
attack_step = Enums.AttackStep.DECLARATION
|
||||||
|
return true
|
||||||
|
|
||||||
## End the attack phase (no more attacks)
|
## End the attack phase (no more attacks) - only valid from DECLARATION
|
||||||
func end_attack_phase() -> void:
|
func end_attack_phase() -> bool:
|
||||||
|
if attack_step != Enums.AttackStep.DECLARATION and attack_step != Enums.AttackStep.PREPARATION:
|
||||||
|
return false
|
||||||
_reset_attack_state()
|
_reset_attack_state()
|
||||||
advance_phase()
|
advance_phase()
|
||||||
|
return true
|
||||||
|
|
||||||
## Get current phase as string
|
## Get current phase as string
|
||||||
func get_phase_string() -> String:
|
func get_phase_string() -> String:
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class UndoAction:
|
|||||||
# Additional data depending on action type
|
# Additional data depending on action type
|
||||||
var cp_element: Enums.Element = Enums.Element.FIRE
|
var cp_element: Enums.Element = Enums.Element.FIRE
|
||||||
var cp_amount: int = 0
|
var cp_amount: int = 0
|
||||||
|
var cp_spent: Dictionary = {} # Tracks CP spent by element for proper refund
|
||||||
var previous_card_state: Enums.CardState = Enums.CardState.ACTIVE
|
var previous_card_state: Enums.CardState = Enums.CardState.ACTIVE
|
||||||
var from_zone: Enums.ZoneType = Enums.ZoneType.HAND
|
var from_zone: Enums.ZoneType = Enums.ZoneType.HAND
|
||||||
var to_zone: Enums.ZoneType = Enums.ZoneType.FIELD_FORWARDS
|
var to_zone: Enums.ZoneType = Enums.ZoneType.FIELD_FORWARDS
|
||||||
@@ -69,13 +70,14 @@ func record_dull_backup_for_cp(player_index: int, card: CardInstance, element: E
|
|||||||
undo_available_changed.emit(true)
|
undo_available_changed.emit(true)
|
||||||
|
|
||||||
## Record playing a card
|
## Record playing a card
|
||||||
func record_play_card(player_index: int, card: CardInstance, to_zone: Enums.ZoneType, _cp_spent: Dictionary) -> void:
|
func record_play_card(player_index: int, card: CardInstance, to_zone: Enums.ZoneType, cp_spent: Dictionary) -> void:
|
||||||
var action = UndoAction.new()
|
var action = UndoAction.new()
|
||||||
action.type = ActionType.PLAY_CARD
|
action.type = ActionType.PLAY_CARD
|
||||||
action.player_index = player_index
|
action.player_index = player_index
|
||||||
action.card = card
|
action.card = card
|
||||||
action.from_zone = Enums.ZoneType.HAND
|
action.from_zone = Enums.ZoneType.HAND
|
||||||
action.to_zone = to_zone
|
action.to_zone = to_zone
|
||||||
|
action.cp_spent = cp_spent # Store actual CP spent for proper refund
|
||||||
action.description = "Play " + card.get_display_name()
|
action.description = "Play " + card.get_display_name()
|
||||||
|
|
||||||
last_action = action
|
last_action = action
|
||||||
@@ -177,12 +179,16 @@ func _undo_play_card(action: UndoAction) -> bool:
|
|||||||
card.state = Enums.CardState.ACTIVE
|
card.state = Enums.CardState.ACTIVE
|
||||||
card.turns_on_field = 0
|
card.turns_on_field = 0
|
||||||
|
|
||||||
# Refund the CP (we don't track exact CP spent, so this is simplified)
|
# Refund the actual CP spent (by element)
|
||||||
# In a full implementation, we'd track what CP was spent
|
if action.cp_spent.is_empty():
|
||||||
# For now, we'll add back CP equal to the card cost
|
# Fallback: refund card cost as primary element (legacy behavior)
|
||||||
var cost = card.card_data.cost
|
var cost = card.card_data.cost
|
||||||
var element = card.get_element()
|
var element = card.get_element()
|
||||||
player.cp_pool.add_cp(element, cost)
|
player.cp_pool.add_cp(element, cost)
|
||||||
|
else:
|
||||||
|
# Refund each element's CP properly
|
||||||
|
for element in action.cp_spent:
|
||||||
|
player.cp_pool.add_cp(element, action.cp_spent[element])
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,11 @@ func _init(type: Enums.ZoneType, owner: int = -1) -> void:
|
|||||||
owner_index = owner
|
owner_index = owner
|
||||||
|
|
||||||
## Add a card to this zone
|
## Add a card to this zone
|
||||||
func add_card(card: CardInstance, at_top: bool = true) -> void:
|
## Returns true if successful, false if card was already in zone
|
||||||
|
func add_card(card: CardInstance, at_top: bool = true) -> bool:
|
||||||
if card in _cards:
|
if card in _cards:
|
||||||
push_warning("Card already in zone: " + str(card))
|
push_warning("Card already in zone: " + str(card))
|
||||||
return
|
return false
|
||||||
|
|
||||||
card.zone_type = zone_type
|
card.zone_type = zone_type
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@ func add_card(card: CardInstance, at_top: bool = true) -> void:
|
|||||||
_cards.insert(0, card)
|
_cards.insert(0, card)
|
||||||
|
|
||||||
on_card_added.call(card)
|
on_card_added.call(card)
|
||||||
|
return true
|
||||||
|
|
||||||
## Remove a card from this zone
|
## Remove a card from this zone
|
||||||
func remove_card(card: CardInstance) -> bool:
|
func remove_card(card: CardInstance) -> bool:
|
||||||
@@ -142,11 +144,11 @@ func find_cards_by_element(element: Enums.Element) -> Array[CardInstance]:
|
|||||||
found.append(card)
|
found.append(card)
|
||||||
return found
|
return found
|
||||||
|
|
||||||
## Check if zone has a card with specific name (non-generic check)
|
## Check if zone has a card with specific name
|
||||||
|
## Note: The non-generic check should be on the card being played, not the field card
|
||||||
func has_card_with_name(card_name: String) -> bool:
|
func has_card_with_name(card_name: String) -> bool:
|
||||||
for card in _cards:
|
for card in _cards:
|
||||||
if card.card_data and card.card_data.name == card_name:
|
if card.card_data and card.card_data.name == card_name:
|
||||||
if not card.card_data.is_generic:
|
|
||||||
return true
|
return true
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|||||||
@@ -340,6 +340,7 @@ func show_card_detail(card: CardInstance) -> void:
|
|||||||
|
|
||||||
## Hide card detail panel
|
## Hide card detail panel
|
||||||
func hide_card_detail() -> void:
|
func hide_card_detail() -> void:
|
||||||
|
print("DEBUG: GameUI.hide_card_detail() called")
|
||||||
card_detail_panel.visible = false
|
card_detail_panel.visible = false
|
||||||
|
|
||||||
## Update button states based on game phase
|
## Update button states based on game phase
|
||||||
|
|||||||
@@ -2,32 +2,146 @@ class_name HandDisplay
|
|||||||
extends Control
|
extends Control
|
||||||
|
|
||||||
## HandDisplay - Shows the current player's hand in a fan layout
|
## HandDisplay - Shows the current player's hand in a fan layout
|
||||||
|
## Click a card to select it and show action menu
|
||||||
|
|
||||||
signal card_selected(card_instance: CardInstance)
|
signal card_action_requested(card_instance: CardInstance, action: String)
|
||||||
signal card_hovered(card_instance: CardInstance)
|
signal card_hovered(card_instance: CardInstance)
|
||||||
signal card_unhovered
|
signal card_unhovered
|
||||||
|
signal card_selected(card_instance: CardInstance) # Emitted when selection panel opens
|
||||||
|
|
||||||
# Hand card visuals
|
# Hand card visuals
|
||||||
var hand_cards: Array[Control] = []
|
var hand_cards: Array[Control] = []
|
||||||
var card_instances: Array[CardInstance] = []
|
var card_instances: Array[CardInstance] = []
|
||||||
|
|
||||||
# Layout settings
|
# Layout settings - FF-TCG card ratio is approximately 63:88
|
||||||
const CARD_WIDTH: float = 100.0
|
const CARD_WIDTH: float = 195.0 # Hand cards (triple the original 65)
|
||||||
const CARD_HEIGHT: float = 140.0
|
const CARD_HEIGHT: float = 273.0 # Matches ~63:88 ratio (triple the original 91)
|
||||||
const CARD_OVERLAP: float = 70.0
|
const CARD_OVERLAP: float = 100.0 # Increased overlap for larger cards
|
||||||
const FAN_ANGLE: float = 2.0 # Degrees per card from center
|
const FAN_ANGLE: float = 1.0 # Degrees per card from center
|
||||||
const HOVER_LIFT: float = 25.0
|
const HOVER_LIFT: float = 15.0
|
||||||
|
|
||||||
# Current hover state
|
# Selected card display settings (double the original 180x252)
|
||||||
|
const SELECTED_CARD_WIDTH: float = 405.0
|
||||||
|
const SELECTED_CARD_HEIGHT: float = 567.0
|
||||||
|
const MENU_WIDTH: float = 180.0
|
||||||
|
|
||||||
|
# Current state
|
||||||
var hovered_card_index: int = -1
|
var hovered_card_index: int = -1
|
||||||
|
var selected_card_index: int = -1
|
||||||
|
var selected_card_panel: Panel = null
|
||||||
|
var card_image_container: Control = null
|
||||||
|
var action_menu: VBoxContainer = null
|
||||||
|
|
||||||
|
# Debug visualization (set to true to see positioning markers)
|
||||||
|
const DEBUG_MARKERS: bool = false
|
||||||
|
var debug_container_outline: ColorRect = null
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
# Connect to resized signal to re-layout when size changes
|
clip_contents = false # Allow cards to extend beyond container bounds
|
||||||
resized.connect(_on_resized)
|
resized.connect(_on_resized)
|
||||||
|
_create_selection_ui()
|
||||||
|
if DEBUG_MARKERS:
|
||||||
|
_create_debug_markers()
|
||||||
|
|
||||||
|
func _create_selection_ui() -> void:
|
||||||
|
# Create the selected card display panel using HBoxContainer for reliable layout
|
||||||
|
selected_card_panel = Panel.new()
|
||||||
|
selected_card_panel.visible = false
|
||||||
|
selected_card_panel.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||||
|
selected_card_panel.z_index = 200
|
||||||
|
|
||||||
|
var style = StyleBoxFlat.new()
|
||||||
|
style.bg_color = Color(0.08, 0.08, 0.12, 0.95)
|
||||||
|
style.border_color = Color(0.5, 0.4, 0.2)
|
||||||
|
style.set_border_width_all(2)
|
||||||
|
style.set_corner_radius_all(6)
|
||||||
|
# Add padding via content margins
|
||||||
|
style.content_margin_left = 15
|
||||||
|
style.content_margin_right = 15
|
||||||
|
style.content_margin_top = 15
|
||||||
|
style.content_margin_bottom = 15
|
||||||
|
selected_card_panel.add_theme_stylebox_override("panel", style)
|
||||||
|
|
||||||
|
add_child(selected_card_panel)
|
||||||
|
|
||||||
|
# Use HBoxContainer inside the panel for horizontal layout
|
||||||
|
var hbox = HBoxContainer.new()
|
||||||
|
hbox.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
|
hbox.add_theme_constant_override("separation", 20) # Gap between card and menu
|
||||||
|
selected_card_panel.add_child(hbox)
|
||||||
|
|
||||||
|
# Card image container with fixed size
|
||||||
|
card_image_container = Control.new()
|
||||||
|
card_image_container.custom_minimum_size = Vector2(SELECTED_CARD_WIDTH, SELECTED_CARD_HEIGHT)
|
||||||
|
card_image_container.name = "CardContainer"
|
||||||
|
card_image_container.clip_contents = false
|
||||||
|
hbox.add_child(card_image_container)
|
||||||
|
|
||||||
|
# Action menu - VBoxContainer for vertical button layout
|
||||||
|
action_menu = VBoxContainer.new()
|
||||||
|
action_menu.custom_minimum_size = Vector2(MENU_WIDTH, SELECTED_CARD_HEIGHT)
|
||||||
|
action_menu.add_theme_constant_override("separation", 10)
|
||||||
|
hbox.add_child(action_menu)
|
||||||
|
|
||||||
|
# Set panel size based on contents (padding + card + gap + menu + padding)
|
||||||
|
var panel_width = 15 + SELECTED_CARD_WIDTH + 20 + MENU_WIDTH + 15
|
||||||
|
var panel_height = 15 + SELECTED_CARD_HEIGHT + 15
|
||||||
|
selected_card_panel.custom_minimum_size = Vector2(panel_width, panel_height)
|
||||||
|
selected_card_panel.size = Vector2(panel_width, panel_height)
|
||||||
|
|
||||||
|
|
||||||
|
func _create_debug_markers() -> void:
|
||||||
|
# Create a visible outline around the hand container bounds
|
||||||
|
# RED = hand container area
|
||||||
|
debug_container_outline = ColorRect.new()
|
||||||
|
debug_container_outline.color = Color(1, 0, 0, 0.3) # Semi-transparent red
|
||||||
|
debug_container_outline.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
debug_container_outline.z_index = -1 # Behind cards
|
||||||
|
add_child(debug_container_outline)
|
||||||
|
|
||||||
|
# GREEN line = where card bottoms should be (base_y + CARD_HEIGHT = 0 + 91 = 91)
|
||||||
|
var card_bottom_line = ColorRect.new()
|
||||||
|
card_bottom_line.color = Color(0, 1, 0, 0.8) # Bright green
|
||||||
|
card_bottom_line.position = Vector2(0, CARD_HEIGHT) # y = 91
|
||||||
|
card_bottom_line.size = Vector2(2000, 3) # Thin horizontal line
|
||||||
|
card_bottom_line.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
card_bottom_line.z_index = 300
|
||||||
|
add_child(card_bottom_line)
|
||||||
|
|
||||||
|
# BLUE line = where card tops should be (base_y = 0)
|
||||||
|
var card_top_line = ColorRect.new()
|
||||||
|
card_top_line.color = Color(0, 0, 1, 0.8) # Bright blue
|
||||||
|
card_top_line.position = Vector2(0, 0)
|
||||||
|
card_top_line.size = Vector2(2000, 3)
|
||||||
|
card_top_line.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
card_top_line.z_index = 300
|
||||||
|
add_child(card_top_line)
|
||||||
|
|
||||||
|
# Print positioning info
|
||||||
|
call_deferred("_print_debug_info")
|
||||||
|
|
||||||
|
func _print_debug_info() -> void:
|
||||||
|
var viewport = get_viewport()
|
||||||
|
var vp_size = viewport.get_visible_rect().size if viewport else Vector2.ZERO
|
||||||
|
print("=== HAND DISPLAY DEBUG ===")
|
||||||
|
print("Viewport size: ", vp_size)
|
||||||
|
print("HandDisplay global_position: ", global_position)
|
||||||
|
print("HandDisplay size: ", size)
|
||||||
|
print("Card base_y in container: 0")
|
||||||
|
print("Card height: ", CARD_HEIGHT)
|
||||||
|
print("Card global top: ", global_position.y)
|
||||||
|
print("Card global bottom: ", global_position.y + CARD_HEIGHT)
|
||||||
|
print("Distance from viewport bottom: ", vp_size.y - (global_position.y + CARD_HEIGHT))
|
||||||
|
print("=== END DEBUG ===")
|
||||||
|
|
||||||
func _on_resized() -> void:
|
func _on_resized() -> void:
|
||||||
_layout_cards()
|
_layout_cards()
|
||||||
|
if DEBUG_MARKERS and debug_container_outline:
|
||||||
|
# Update debug outline to match container size
|
||||||
|
debug_container_outline.position = Vector2.ZERO
|
||||||
|
debug_container_outline.size = size
|
||||||
|
call_deferred("_print_debug_info")
|
||||||
|
|
||||||
## Update hand display with cards
|
## Update hand display with cards
|
||||||
func update_hand(cards: Array) -> void:
|
func update_hand(cards: Array) -> void:
|
||||||
@@ -37,6 +151,9 @@ func update_hand(cards: Array) -> void:
|
|||||||
hand_cards.clear()
|
hand_cards.clear()
|
||||||
card_instances.clear()
|
card_instances.clear()
|
||||||
|
|
||||||
|
# Deselect any selected card
|
||||||
|
_deselect_card()
|
||||||
|
|
||||||
# Store card instances
|
# Store card instances
|
||||||
for card in cards:
|
for card in cards:
|
||||||
if card is CardInstance:
|
if card is CardInstance:
|
||||||
@@ -56,85 +173,56 @@ func _create_card_visuals() -> void:
|
|||||||
hand_cards.append(card_ui)
|
hand_cards.append(card_ui)
|
||||||
|
|
||||||
func _create_card_ui(card: CardInstance, index: int) -> Control:
|
func _create_card_ui(card: CardInstance, index: int) -> Control:
|
||||||
# Use Panel for better input handling
|
|
||||||
var container = Panel.new()
|
var container = Panel.new()
|
||||||
container.custom_minimum_size = Vector2(CARD_WIDTH, CARD_HEIGHT)
|
container.custom_minimum_size = Vector2(CARD_WIDTH, CARD_HEIGHT)
|
||||||
container.size = Vector2(CARD_WIDTH, CARD_HEIGHT)
|
container.size = Vector2(CARD_WIDTH, CARD_HEIGHT)
|
||||||
container.mouse_filter = Control.MOUSE_FILTER_STOP
|
container.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||||
|
|
||||||
# Style the panel with element color
|
|
||||||
var style = StyleBoxFlat.new()
|
var style = StyleBoxFlat.new()
|
||||||
style.bg_color = _get_element_color(card.card_data.get_primary_element())
|
style.bg_color = Color.TRANSPARENT
|
||||||
style.border_color = Color.BLACK
|
style.border_color = Color(0.2, 0.2, 0.2)
|
||||||
style.set_border_width_all(2)
|
style.set_border_width_all(1)
|
||||||
|
style.set_corner_radius_all(4)
|
||||||
container.add_theme_stylebox_override("panel", style)
|
container.add_theme_stylebox_override("panel", style)
|
||||||
|
|
||||||
# Card background (keep for highlighting)
|
var texture = CardDatabase.get_card_texture(card.card_data)
|
||||||
|
|
||||||
|
if texture:
|
||||||
|
var tex_rect = TextureRect.new()
|
||||||
|
tex_rect.texture = texture
|
||||||
|
tex_rect.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
|
tex_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||||
|
tex_rect.stretch_mode = TextureRect.STRETCH_SCALE
|
||||||
|
tex_rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
container.add_child(tex_rect)
|
||||||
|
else:
|
||||||
|
# Fallback: colored background with card info
|
||||||
var bg = ColorRect.new()
|
var bg = ColorRect.new()
|
||||||
bg.size = Vector2(CARD_WIDTH, CARD_HEIGHT)
|
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
bg.color = _get_element_color(card.card_data.get_primary_element())
|
bg.color = _get_element_color(card.card_data.get_primary_element())
|
||||||
bg.mouse_filter = Control.MOUSE_FILTER_IGNORE # Let parent handle input
|
bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
container.add_child(bg)
|
container.add_child(bg)
|
||||||
|
|
||||||
# Card name
|
|
||||||
var name_label = Label.new()
|
var name_label = Label.new()
|
||||||
name_label.text = card.card_data.name
|
name_label.text = card.card_data.name
|
||||||
name_label.position = Vector2(5, 5)
|
name_label.position = Vector2(5, 5)
|
||||||
name_label.size = Vector2(CARD_WIDTH - 10, 50)
|
name_label.size = Vector2(CARD_WIDTH - 10, 40)
|
||||||
name_label.add_theme_font_size_override("font_size", 11)
|
name_label.add_theme_font_size_override("font_size", 10)
|
||||||
name_label.add_theme_color_override("font_color", Color.BLACK)
|
name_label.add_theme_color_override("font_color", Color.BLACK)
|
||||||
name_label.autowrap_mode = TextServer.AUTOWRAP_WORD
|
name_label.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||||
name_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
name_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
container.add_child(name_label)
|
container.add_child(name_label)
|
||||||
|
|
||||||
# Cost indicator (top right)
|
|
||||||
var cost_bg = ColorRect.new()
|
|
||||||
cost_bg.size = Vector2(28, 28)
|
|
||||||
cost_bg.position = Vector2(CARD_WIDTH - 32, 4)
|
|
||||||
cost_bg.color = Color(0.2, 0.2, 0.2)
|
|
||||||
cost_bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
||||||
container.add_child(cost_bg)
|
|
||||||
|
|
||||||
var cost_label = Label.new()
|
var cost_label = Label.new()
|
||||||
cost_label.text = str(card.card_data.cost)
|
cost_label.text = str(card.card_data.cost)
|
||||||
cost_label.position = Vector2(CARD_WIDTH - 32, 4)
|
cost_label.position = Vector2(CARD_WIDTH - 25, 5)
|
||||||
cost_label.size = Vector2(28, 28)
|
cost_label.size = Vector2(20, 20)
|
||||||
cost_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
cost_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
cost_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
cost_label.add_theme_font_size_override("font_size", 14)
|
||||||
cost_label.add_theme_font_size_override("font_size", 16)
|
|
||||||
cost_label.add_theme_color_override("font_color", Color.WHITE)
|
cost_label.add_theme_color_override("font_color", Color.WHITE)
|
||||||
cost_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
cost_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
container.add_child(cost_label)
|
container.add_child(cost_label)
|
||||||
|
|
||||||
# Type indicator (bottom left)
|
|
||||||
var type_label = Label.new()
|
|
||||||
type_label.text = Enums.card_type_to_string(card.card_data.type)
|
|
||||||
type_label.position = Vector2(5, CARD_HEIGHT - 22)
|
|
||||||
type_label.add_theme_font_size_override("font_size", 10)
|
|
||||||
type_label.add_theme_color_override("font_color", Color.BLACK)
|
|
||||||
type_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
||||||
container.add_child(type_label)
|
|
||||||
|
|
||||||
# Power for forwards (bottom right)
|
|
||||||
if card.card_data.type == Enums.CardType.FORWARD:
|
|
||||||
var power_bg = ColorRect.new()
|
|
||||||
power_bg.size = Vector2(45, 20)
|
|
||||||
power_bg.position = Vector2(CARD_WIDTH - 50, CARD_HEIGHT - 24)
|
|
||||||
power_bg.color = Color(0.3, 0.1, 0.1, 0.8)
|
|
||||||
power_bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
||||||
container.add_child(power_bg)
|
|
||||||
|
|
||||||
var power_label = Label.new()
|
|
||||||
power_label.text = str(card.card_data.power)
|
|
||||||
power_label.position = Vector2(CARD_WIDTH - 50, CARD_HEIGHT - 24)
|
|
||||||
power_label.size = Vector2(45, 20)
|
|
||||||
power_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
||||||
power_label.add_theme_font_size_override("font_size", 12)
|
|
||||||
power_label.add_theme_color_override("font_color", Color.WHITE)
|
|
||||||
power_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
||||||
container.add_child(power_label)
|
|
||||||
|
|
||||||
# Connect signals
|
|
||||||
container.gui_input.connect(_on_card_gui_input.bind(index))
|
container.gui_input.connect(_on_card_gui_input.bind(index))
|
||||||
container.mouse_entered.connect(_on_card_mouse_entered.bind(index))
|
container.mouse_entered.connect(_on_card_mouse_entered.bind(index))
|
||||||
container.mouse_exited.connect(_on_card_mouse_exited.bind(index))
|
container.mouse_exited.connect(_on_card_mouse_exited.bind(index))
|
||||||
@@ -143,7 +231,6 @@ func _create_card_ui(card: CardInstance, index: int) -> Control:
|
|||||||
|
|
||||||
func _get_element_color(element: Enums.Element) -> Color:
|
func _get_element_color(element: Enums.Element) -> Color:
|
||||||
var color = Enums.get_element_color(element)
|
var color = Enums.get_element_color(element)
|
||||||
# Lighten for card background
|
|
||||||
return color.lightened(0.4)
|
return color.lightened(0.4)
|
||||||
|
|
||||||
func _layout_cards() -> void:
|
func _layout_cards() -> void:
|
||||||
@@ -151,34 +238,36 @@ func _layout_cards() -> void:
|
|||||||
if card_count == 0:
|
if card_count == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get actual display width - use size if available, otherwise viewport
|
|
||||||
var display_width = size.x
|
var display_width = size.x
|
||||||
|
|
||||||
if display_width <= 0:
|
if display_width <= 0:
|
||||||
var viewport = get_viewport()
|
var viewport = get_viewport()
|
||||||
if viewport:
|
if viewport:
|
||||||
display_width = viewport.get_visible_rect().size.x - 100 # Leave margins
|
display_width = viewport.get_visible_rect().size.x - 100
|
||||||
|
|
||||||
if display_width <= 0:
|
if display_width <= 0:
|
||||||
display_width = 1820 # Fallback
|
display_width = 1820
|
||||||
|
|
||||||
# Calculate total width needed
|
|
||||||
var total_width = CARD_WIDTH + (card_count - 1) * CARD_OVERLAP
|
var total_width = CARD_WIDTH + (card_count - 1) * CARD_OVERLAP
|
||||||
var start_x = (display_width - total_width) / 2.0
|
var start_x = (display_width - total_width) / 2.0
|
||||||
|
|
||||||
# Position each card
|
# Base Y position - cards at y=0 in container, hover lifts them up (negative y)
|
||||||
|
var base_y = 0.0
|
||||||
|
|
||||||
for i in range(card_count):
|
for i in range(card_count):
|
||||||
var card_ui = hand_cards[i]
|
var card_ui = hand_cards[i]
|
||||||
|
|
||||||
# X position with overlap
|
|
||||||
var x = start_x + i * CARD_OVERLAP
|
var x = start_x + i * CARD_OVERLAP
|
||||||
|
var y = base_y
|
||||||
|
|
||||||
# Y position (near top of container, with hover lift)
|
# Lift hovered card slightly
|
||||||
var y = 10.0
|
if i == hovered_card_index and selected_card_index == -1:
|
||||||
if i == hovered_card_index:
|
y -= HOVER_LIFT # Lift by 15px
|
||||||
y -= HOVER_LIFT
|
|
||||||
|
# Dim selected card in hand
|
||||||
|
if i == selected_card_index:
|
||||||
|
card_ui.modulate = Color(0.5, 0.5, 0.5, 0.7)
|
||||||
|
else:
|
||||||
|
card_ui.modulate = Color.WHITE
|
||||||
|
|
||||||
# Rotation based on position in fan
|
|
||||||
var center_offset = i - (card_count - 1) / 2.0
|
var center_offset = i - (card_count - 1) / 2.0
|
||||||
var angle = center_offset * FAN_ANGLE
|
var angle = center_offset * FAN_ANGLE
|
||||||
|
|
||||||
@@ -192,13 +281,17 @@ func _on_card_gui_input(event: InputEvent, index: int) -> void:
|
|||||||
if event is InputEventMouseButton:
|
if event is InputEventMouseButton:
|
||||||
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
||||||
if index >= 0 and index < card_instances.size():
|
if index >= 0 and index < card_instances.size():
|
||||||
card_selected.emit(card_instances[index])
|
if selected_card_index == index:
|
||||||
|
_deselect_card()
|
||||||
|
else:
|
||||||
|
_select_card(index)
|
||||||
|
|
||||||
func _on_card_mouse_entered(index: int) -> void:
|
func _on_card_mouse_entered(index: int) -> void:
|
||||||
hovered_card_index = index
|
hovered_card_index = index
|
||||||
_layout_cards()
|
_layout_cards()
|
||||||
if index >= 0 and index < card_instances.size():
|
# Hover signal is no longer used for GameUI preview (selection panel shows card instead)
|
||||||
card_hovered.emit(card_instances[index])
|
# Keep the signal for potential future use but don't emit it
|
||||||
|
pass
|
||||||
|
|
||||||
func _on_card_mouse_exited(index: int) -> void:
|
func _on_card_mouse_exited(index: int) -> void:
|
||||||
if hovered_card_index == index:
|
if hovered_card_index == index:
|
||||||
@@ -206,31 +299,186 @@ func _on_card_mouse_exited(index: int) -> void:
|
|||||||
_layout_cards()
|
_layout_cards()
|
||||||
card_unhovered.emit()
|
card_unhovered.emit()
|
||||||
|
|
||||||
|
func _select_card(index: int) -> void:
|
||||||
|
if index < 0 or index >= card_instances.size():
|
||||||
|
return
|
||||||
|
|
||||||
|
selected_card_index = index
|
||||||
|
var card = card_instances[index]
|
||||||
|
|
||||||
|
# Emit selection signal first (so Main.gd can hide hover preview)
|
||||||
|
card_selected.emit(card)
|
||||||
|
# Also emit unhovered to ensure hover state is cleared
|
||||||
|
card_unhovered.emit()
|
||||||
|
|
||||||
|
# Clear previous card image from the card container
|
||||||
|
for child in card_image_container.get_children():
|
||||||
|
child.queue_free()
|
||||||
|
|
||||||
|
# Clear action menu buttons
|
||||||
|
for child in action_menu.get_children():
|
||||||
|
child.queue_free()
|
||||||
|
|
||||||
|
# Add card image to the card container
|
||||||
|
var texture = CardDatabase.get_card_texture(card.card_data)
|
||||||
|
if texture:
|
||||||
|
var tex_rect = TextureRect.new()
|
||||||
|
tex_rect.texture = texture
|
||||||
|
tex_rect.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
|
tex_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||||
|
tex_rect.stretch_mode = TextureRect.STRETCH_SCALE
|
||||||
|
tex_rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
card_image_container.add_child(tex_rect)
|
||||||
|
else:
|
||||||
|
# Fallback colored rect for cards without images
|
||||||
|
var color_rect = ColorRect.new()
|
||||||
|
color_rect.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
|
color_rect.color = _get_element_color(card.card_data.get_primary_element())
|
||||||
|
card_image_container.add_child(color_rect)
|
||||||
|
|
||||||
|
var name_label = Label.new()
|
||||||
|
name_label.text = card.card_data.name
|
||||||
|
name_label.position = Vector2(5, 10)
|
||||||
|
name_label.size = Vector2(SELECTED_CARD_WIDTH - 10, 50)
|
||||||
|
name_label.add_theme_font_size_override("font_size", 16)
|
||||||
|
name_label.add_theme_color_override("font_color", Color.BLACK)
|
||||||
|
name_label.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||||
|
name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
card_image_container.add_child(name_label)
|
||||||
|
|
||||||
|
# Add action buttons (right side via action_menu VBoxContainer)
|
||||||
|
_add_action_button("Play Card", "play")
|
||||||
|
_add_action_button("Discard for CP", "discard_cp")
|
||||||
|
_add_action_button("View Details", "view")
|
||||||
|
_add_action_button("Cancel", "cancel")
|
||||||
|
|
||||||
|
# Position the panel centered above the hand
|
||||||
|
var viewport = get_viewport()
|
||||||
|
if viewport:
|
||||||
|
var vp_size = viewport.get_visible_rect().size
|
||||||
|
|
||||||
|
# Center horizontally relative to screen
|
||||||
|
var panel_x = (vp_size.x - selected_card_panel.size.x) / 2.0 - global_position.x
|
||||||
|
|
||||||
|
# Position above hand container
|
||||||
|
var panel_y = -selected_card_panel.size.y - 20
|
||||||
|
|
||||||
|
# Clamp to stay on screen
|
||||||
|
var global_y = global_position.y + panel_y
|
||||||
|
if global_y < 90:
|
||||||
|
panel_y = 90 - global_position.y
|
||||||
|
|
||||||
|
var global_x = global_position.x + panel_x
|
||||||
|
if global_x < 10:
|
||||||
|
panel_x = 10 - global_position.x
|
||||||
|
if global_x + selected_card_panel.size.x > vp_size.x - 10:
|
||||||
|
panel_x = vp_size.x - 10 - selected_card_panel.size.x - global_position.x
|
||||||
|
|
||||||
|
selected_card_panel.position = Vector2(panel_x, panel_y)
|
||||||
|
|
||||||
|
selected_card_panel.visible = true
|
||||||
|
_layout_cards()
|
||||||
|
|
||||||
|
func _add_action_button(text: String, action: String) -> void:
|
||||||
|
var button = Button.new()
|
||||||
|
button.text = text
|
||||||
|
button.custom_minimum_size = Vector2(MENU_WIDTH - 10, 40)
|
||||||
|
|
||||||
|
var style_normal = StyleBoxFlat.new()
|
||||||
|
style_normal.bg_color = Color(0.25, 0.25, 0.3)
|
||||||
|
style_normal.border_color = Color(0.5, 0.5, 0.6)
|
||||||
|
style_normal.set_border_width_all(1)
|
||||||
|
style_normal.set_corner_radius_all(5)
|
||||||
|
style_normal.content_margin_top = 8
|
||||||
|
style_normal.content_margin_bottom = 8
|
||||||
|
button.add_theme_stylebox_override("normal", style_normal)
|
||||||
|
|
||||||
|
var style_hover = StyleBoxFlat.new()
|
||||||
|
style_hover.bg_color = Color(0.35, 0.35, 0.45)
|
||||||
|
style_hover.border_color = Color(0.7, 0.6, 0.3)
|
||||||
|
style_hover.set_border_width_all(2)
|
||||||
|
style_hover.set_corner_radius_all(5)
|
||||||
|
style_hover.content_margin_top = 8
|
||||||
|
style_hover.content_margin_bottom = 8
|
||||||
|
button.add_theme_stylebox_override("hover", style_hover)
|
||||||
|
|
||||||
|
var style_pressed = StyleBoxFlat.new()
|
||||||
|
style_pressed.bg_color = Color(0.2, 0.2, 0.25)
|
||||||
|
style_pressed.border_color = Color(0.7, 0.6, 0.3)
|
||||||
|
style_pressed.set_border_width_all(2)
|
||||||
|
style_pressed.set_corner_radius_all(5)
|
||||||
|
style_pressed.content_margin_top = 8
|
||||||
|
style_pressed.content_margin_bottom = 8
|
||||||
|
button.add_theme_stylebox_override("pressed", style_pressed)
|
||||||
|
|
||||||
|
button.add_theme_font_size_override("font_size", 14)
|
||||||
|
button.pressed.connect(_on_action_pressed.bind(action))
|
||||||
|
action_menu.add_child(button)
|
||||||
|
|
||||||
|
func _on_action_pressed(action: String) -> void:
|
||||||
|
if selected_card_index < 0 or selected_card_index >= card_instances.size():
|
||||||
|
_deselect_card()
|
||||||
|
return
|
||||||
|
|
||||||
|
var card = card_instances[selected_card_index]
|
||||||
|
|
||||||
|
if action == "cancel":
|
||||||
|
_deselect_card()
|
||||||
|
else:
|
||||||
|
card_action_requested.emit(card, action)
|
||||||
|
if action != "view":
|
||||||
|
_deselect_card()
|
||||||
|
|
||||||
|
func _deselect_card() -> void:
|
||||||
|
selected_card_index = -1
|
||||||
|
selected_card_panel.visible = false
|
||||||
|
_layout_cards()
|
||||||
|
|
||||||
|
## Public method to deselect (called from Main when action completes)
|
||||||
|
func deselect() -> void:
|
||||||
|
_deselect_card()
|
||||||
|
|
||||||
|
## Check if a card is currently selected
|
||||||
|
func has_selection() -> bool:
|
||||||
|
return selected_card_index >= 0
|
||||||
|
|
||||||
## Highlight playable cards
|
## Highlight playable cards
|
||||||
func highlight_playable(predicate: Callable) -> void:
|
func highlight_playable(predicate: Callable) -> void:
|
||||||
for i in range(hand_cards.size()):
|
for i in range(hand_cards.size()):
|
||||||
var card_ui = hand_cards[i]
|
var card_ui = hand_cards[i]
|
||||||
var card = card_instances[i]
|
var card = card_instances[i]
|
||||||
|
var first_child = card_ui.get_child(0) if card_ui.get_child_count() > 0 else null
|
||||||
|
|
||||||
var bg = card_ui.get_child(0) as ColorRect
|
if first_child is TextureRect:
|
||||||
if bg:
|
|
||||||
if predicate.call(card):
|
if predicate.call(card):
|
||||||
# Add glow effect - brighter
|
first_child.modulate = Color(1.2, 1.2, 1.2)
|
||||||
bg.color = _get_element_color(card.card_data.get_primary_element()).lightened(0.2)
|
|
||||||
else:
|
else:
|
||||||
# Dim non-playable
|
first_child.modulate = Color(0.5, 0.5, 0.5)
|
||||||
bg.color = _get_element_color(card.card_data.get_primary_element()).darkened(0.4)
|
elif first_child is ColorRect:
|
||||||
|
if predicate.call(card):
|
||||||
|
first_child.color = _get_element_color(card.card_data.get_primary_element()).lightened(0.2)
|
||||||
|
else:
|
||||||
|
first_child.color = _get_element_color(card.card_data.get_primary_element()).darkened(0.4)
|
||||||
|
|
||||||
## Clear all highlights
|
## Clear all highlights
|
||||||
func clear_highlights() -> void:
|
func clear_highlights() -> void:
|
||||||
for i in range(hand_cards.size()):
|
for i in range(hand_cards.size()):
|
||||||
var card_ui = hand_cards[i]
|
var card_ui = hand_cards[i]
|
||||||
var card = card_instances[i]
|
var card = card_instances[i]
|
||||||
|
var first_child = card_ui.get_child(0) if card_ui.get_child_count() > 0 else null
|
||||||
|
|
||||||
var bg = card_ui.get_child(0) as ColorRect
|
if first_child is TextureRect:
|
||||||
if bg:
|
first_child.modulate = Color.WHITE
|
||||||
bg.color = _get_element_color(card.card_data.get_primary_element())
|
elif first_child is ColorRect:
|
||||||
|
first_child.color = _get_element_color(card.card_data.get_primary_element())
|
||||||
|
|
||||||
func _notification(what: int) -> void:
|
func _notification(what: int) -> void:
|
||||||
if what == NOTIFICATION_RESIZED:
|
if what == NOTIFICATION_RESIZED:
|
||||||
_layout_cards()
|
_layout_cards()
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
# Close selection on Escape
|
||||||
|
if event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
|
||||||
|
if selected_card_index >= 0:
|
||||||
|
_deselect_card()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ signal clicked(card_visual: CardVisual)
|
|||||||
signal hovered(card_visual: CardVisual)
|
signal hovered(card_visual: CardVisual)
|
||||||
signal unhovered(card_visual: CardVisual)
|
signal unhovered(card_visual: CardVisual)
|
||||||
|
|
||||||
# Card dimensions (standard card ratio ~2.5:3.5)
|
# Card dimensions (doubled for readability, standard card ratio ~2.5:3.5)
|
||||||
const CARD_WIDTH: float = 0.63
|
const CARD_WIDTH: float = 1.26
|
||||||
const CARD_HEIGHT: float = 0.88
|
const CARD_HEIGHT: float = 1.76
|
||||||
const CARD_THICKNESS: float = 0.02
|
const CARD_THICKNESS: float = 0.02
|
||||||
|
|
||||||
# Associated card instance
|
# Associated card instance
|
||||||
@@ -23,8 +23,10 @@ var is_hovered: bool = false
|
|||||||
# Animation
|
# Animation
|
||||||
var target_position: Vector3 = Vector3.ZERO
|
var target_position: Vector3 = Vector3.ZERO
|
||||||
var target_rotation: Vector3 = Vector3.ZERO
|
var target_rotation: Vector3 = Vector3.ZERO
|
||||||
|
var base_position: Vector3 = Vector3.ZERO # Position set by zone arrangement (before hover offset)
|
||||||
var move_speed: float = 10.0
|
var move_speed: float = 10.0
|
||||||
var is_animating: bool = false
|
var is_animating: bool = false
|
||||||
|
const HOVER_LIFT: float = 0.15
|
||||||
|
|
||||||
# Components
|
# Components
|
||||||
var mesh_instance: MeshInstance3D
|
var mesh_instance: MeshInstance3D
|
||||||
@@ -46,14 +48,21 @@ func _create_card_mesh() -> void:
|
|||||||
mesh_instance = MeshInstance3D.new()
|
mesh_instance = MeshInstance3D.new()
|
||||||
add_child(mesh_instance)
|
add_child(mesh_instance)
|
||||||
|
|
||||||
# Create box mesh for card
|
# Create a quad mesh for the card face (more appropriate for card textures)
|
||||||
var box = BoxMesh.new()
|
var quad = QuadMesh.new()
|
||||||
box.size = Vector3(CARD_WIDTH, CARD_THICKNESS, CARD_HEIGHT)
|
quad.size = Vector2(CARD_WIDTH, CARD_HEIGHT)
|
||||||
mesh_instance.mesh = box
|
mesh_instance.mesh = quad
|
||||||
|
|
||||||
|
# Rotate to lay flat on table (facing up)
|
||||||
|
mesh_instance.rotation_degrees.x = -90
|
||||||
|
|
||||||
|
# Slight offset up so it's above the table
|
||||||
|
mesh_instance.position.y = CARD_THICKNESS / 2
|
||||||
|
|
||||||
# Create material
|
# Create material
|
||||||
material = StandardMaterial3D.new()
|
material = StandardMaterial3D.new()
|
||||||
material.albedo_color = Color.WHITE
|
material.albedo_color = Color.WHITE
|
||||||
|
material.cull_mode = BaseMaterial3D.CULL_DISABLED # Show both sides
|
||||||
mesh_instance.material_override = material
|
mesh_instance.material_override = material
|
||||||
|
|
||||||
func _setup_collision() -> void:
|
func _setup_collision() -> void:
|
||||||
@@ -99,8 +108,10 @@ func _load_card_texture() -> void:
|
|||||||
var texture = CardDatabase.get_card_texture(card_instance.card_data)
|
var texture = CardDatabase.get_card_texture(card_instance.card_data)
|
||||||
if texture:
|
if texture:
|
||||||
material.albedo_texture = texture
|
material.albedo_texture = texture
|
||||||
|
material.albedo_color = Color.WHITE # Reset to white so texture shows properly
|
||||||
else:
|
else:
|
||||||
# Use element color as fallback
|
# Use element color as fallback when no texture
|
||||||
|
material.albedo_texture = null
|
||||||
var element_color = Enums.element_to_color(card_instance.get_element())
|
var element_color = Enums.element_to_color(card_instance.get_element())
|
||||||
material.albedo_color = element_color
|
material.albedo_color = element_color
|
||||||
|
|
||||||
@@ -109,18 +120,22 @@ func _update_visual_state() -> void:
|
|||||||
if not material:
|
if not material:
|
||||||
return
|
return
|
||||||
|
|
||||||
var color = normal_color
|
# Only apply color tints if we don't have a texture, or for highlight/dull effects
|
||||||
|
var has_texture = material.albedo_texture != null
|
||||||
|
var base_color = Color.WHITE if has_texture else normal_color
|
||||||
|
|
||||||
if is_selected:
|
if is_selected:
|
||||||
color = selected_color
|
# Slight green tint for selection
|
||||||
|
base_color = base_color * Color(0.9, 1.1, 0.9)
|
||||||
elif is_highlighted:
|
elif is_highlighted:
|
||||||
color = highlight_color
|
# Slight yellow tint for highlight
|
||||||
|
base_color = base_color * Color(1.1, 1.1, 0.9)
|
||||||
|
|
||||||
# Apply dull tint
|
# Apply dull tint (darkening)
|
||||||
if card_instance and card_instance.is_dull():
|
if card_instance and card_instance.is_dull():
|
||||||
color = color * dull_tint
|
base_color = base_color * dull_tint
|
||||||
|
|
||||||
material.albedo_color = color
|
material.albedo_color = base_color
|
||||||
|
|
||||||
## Set card as highlighted
|
## Set card as highlighted
|
||||||
func set_highlighted(highlighted: bool) -> void:
|
func set_highlighted(highlighted: bool) -> void:
|
||||||
@@ -134,12 +149,16 @@ func set_selected(selected: bool) -> void:
|
|||||||
|
|
||||||
## Move card to position with animation
|
## Move card to position with animation
|
||||||
func move_to(pos: Vector3, rot: Vector3 = Vector3.ZERO) -> void:
|
func move_to(pos: Vector3, rot: Vector3 = Vector3.ZERO) -> void:
|
||||||
|
base_position = pos
|
||||||
target_position = pos
|
target_position = pos
|
||||||
|
if is_hovered:
|
||||||
|
target_position.y = pos.y + HOVER_LIFT
|
||||||
target_rotation = rot
|
target_rotation = rot
|
||||||
is_animating = true
|
is_animating = true
|
||||||
|
|
||||||
## Move card instantly
|
## Move card instantly
|
||||||
func set_position_instant(pos: Vector3, rot: Vector3 = Vector3.ZERO) -> void:
|
func set_position_instant(pos: Vector3, rot: Vector3 = Vector3.ZERO) -> void:
|
||||||
|
base_position = pos
|
||||||
position = pos
|
position = pos
|
||||||
rotation = rot
|
rotation = rot
|
||||||
target_position = pos
|
target_position = pos
|
||||||
@@ -163,17 +182,15 @@ func _on_input_event(_camera: Node, event: InputEvent, _position: Vector3, _norm
|
|||||||
func _on_mouse_entered() -> void:
|
func _on_mouse_entered() -> void:
|
||||||
is_hovered = true
|
is_hovered = true
|
||||||
hovered.emit(self)
|
hovered.emit(self)
|
||||||
# Slight raise on hover
|
# Raise card above base position on hover
|
||||||
if not is_animating:
|
target_position.y = base_position.y + HOVER_LIFT
|
||||||
target_position.y = position.y + 0.1
|
|
||||||
is_animating = true
|
is_animating = true
|
||||||
|
|
||||||
func _on_mouse_exited() -> void:
|
func _on_mouse_exited() -> void:
|
||||||
is_hovered = false
|
is_hovered = false
|
||||||
unhovered.emit(self)
|
unhovered.emit(self)
|
||||||
# Return to normal height
|
# Return to base height
|
||||||
if not is_animating and card_instance:
|
target_position.y = base_position.y
|
||||||
target_position.y = position.y - 0.1
|
|
||||||
is_animating = true
|
is_animating = true
|
||||||
|
|
||||||
## Get display info for UI
|
## Get display info for UI
|
||||||
|
|||||||
189
scripts/visual/PlaymatRenderer.gd
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
class_name PlaymatRenderer
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
## PlaymatRenderer - Generates a clean programmatic play mat texture using SubViewport
|
||||||
|
## Layout matches FF-TCG official play mat zones:
|
||||||
|
## Left: Damage Zone (top) / Turn Structure (bottom)
|
||||||
|
## Center: Field (Forwards top, Backups bottom)
|
||||||
|
## Right: Deck (top) / Break Zone (bottom)
|
||||||
|
|
||||||
|
const MAT_TEXTURE_WIDTH: int = 2048
|
||||||
|
const MAT_TEXTURE_HEIGHT: int = 1024
|
||||||
|
|
||||||
|
# Color scheme - dark green felt table look
|
||||||
|
const BG_COLOR := Color(0.12, 0.15, 0.12)
|
||||||
|
const ZONE_BORDER_COLOR := Color(0.35, 0.40, 0.30)
|
||||||
|
const ZONE_FILL_COLOR := Color(0.14, 0.18, 0.14)
|
||||||
|
const LABEL_COLOR := Color(0.45, 0.50, 0.40)
|
||||||
|
const FIELD_LINE_COLOR := Color(0.20, 0.25, 0.20)
|
||||||
|
const TURN_TEXT_COLOR := Color(0.35, 0.40, 0.30)
|
||||||
|
|
||||||
|
# Zone regions as pixel rects in the texture
|
||||||
|
# Left column: ~17% width
|
||||||
|
const DAMAGE_RECT := Rect2(30, 30, 320, 460)
|
||||||
|
const TURN_REF_RECT := Rect2(30, 520, 320, 474)
|
||||||
|
# Center: ~66% width
|
||||||
|
const FIELD_RECT := Rect2(380, 30, 1290, 964)
|
||||||
|
# Right column: ~17% width
|
||||||
|
const DECK_RECT := Rect2(1700, 30, 318, 440)
|
||||||
|
const BREAK_RECT := Rect2(1700, 500, 318, 494)
|
||||||
|
|
||||||
|
# Border thickness
|
||||||
|
const BORDER_WIDTH: int = 3
|
||||||
|
|
||||||
|
func render() -> ImageTexture:
|
||||||
|
var viewport = SubViewport.new()
|
||||||
|
viewport.size = Vector2i(MAT_TEXTURE_WIDTH, MAT_TEXTURE_HEIGHT)
|
||||||
|
viewport.render_target_update_mode = SubViewport.UPDATE_ONCE
|
||||||
|
viewport.transparent_bg = false
|
||||||
|
|
||||||
|
# Background
|
||||||
|
var bg = ColorRect.new()
|
||||||
|
bg.color = BG_COLOR
|
||||||
|
bg.position = Vector2.ZERO
|
||||||
|
bg.size = Vector2(MAT_TEXTURE_WIDTH, MAT_TEXTURE_HEIGHT)
|
||||||
|
viewport.add_child(bg)
|
||||||
|
|
||||||
|
# Draw zone rectangles with borders and labels
|
||||||
|
_draw_zone(viewport, DAMAGE_RECT, "DAMAGE ZONE")
|
||||||
|
_draw_zone(viewport, DECK_RECT, "DECK")
|
||||||
|
_draw_zone(viewport, BREAK_RECT, "BREAK ZONE")
|
||||||
|
_draw_zone(viewport, FIELD_RECT, "FIELD")
|
||||||
|
|
||||||
|
# Divider line in Field (separating Forwards from Backups)
|
||||||
|
var divider_y = FIELD_RECT.position.y + FIELD_RECT.size.y * 0.48
|
||||||
|
_draw_dashed_line(viewport, Vector2(FIELD_RECT.position.x + 20, divider_y),
|
||||||
|
Vector2(FIELD_RECT.position.x + FIELD_RECT.size.x - 20, divider_y))
|
||||||
|
|
||||||
|
# Forwards / Backups sub-labels
|
||||||
|
_draw_label(viewport, "FORWARDS",
|
||||||
|
Vector2(FIELD_RECT.position.x + FIELD_RECT.size.x / 2, FIELD_RECT.position.y + FIELD_RECT.size.y * 0.22), 32)
|
||||||
|
_draw_label(viewport, "BACKUPS",
|
||||||
|
Vector2(FIELD_RECT.position.x + FIELD_RECT.size.x / 2, FIELD_RECT.position.y + FIELD_RECT.size.y * 0.74), 32)
|
||||||
|
|
||||||
|
# Turn structure reference (decorative)
|
||||||
|
_draw_turn_structure(viewport)
|
||||||
|
|
||||||
|
# Add outer border around entire mat
|
||||||
|
_draw_border_rect(viewport, Rect2(5, 5, MAT_TEXTURE_WIDTH - 10, MAT_TEXTURE_HEIGHT - 10), ZONE_BORDER_COLOR, 2)
|
||||||
|
|
||||||
|
add_child(viewport)
|
||||||
|
|
||||||
|
# Wait for the viewport to render
|
||||||
|
await RenderingServer.frame_post_draw
|
||||||
|
|
||||||
|
# Grab the rendered image
|
||||||
|
var image = viewport.get_texture().get_image()
|
||||||
|
var texture = ImageTexture.create_from_image(image)
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
viewport.queue_free()
|
||||||
|
|
||||||
|
return texture
|
||||||
|
|
||||||
|
func _draw_zone(viewport: SubViewport, rect: Rect2, label_text: String) -> void:
|
||||||
|
# Zone fill
|
||||||
|
var fill = ColorRect.new()
|
||||||
|
fill.color = ZONE_FILL_COLOR
|
||||||
|
fill.position = rect.position
|
||||||
|
fill.size = rect.size
|
||||||
|
viewport.add_child(fill)
|
||||||
|
|
||||||
|
# Zone border (4 thin ColorRects forming a rectangle)
|
||||||
|
_draw_border_rect(viewport, rect, ZONE_BORDER_COLOR, BORDER_WIDTH)
|
||||||
|
|
||||||
|
# Zone label centered at top of zone
|
||||||
|
_draw_label(viewport, label_text,
|
||||||
|
Vector2(rect.position.x + rect.size.x / 2, rect.position.y + 30), 28)
|
||||||
|
|
||||||
|
func _draw_border_rect(viewport: SubViewport, rect: Rect2, color: Color, width: int) -> void:
|
||||||
|
# Top edge
|
||||||
|
var top = ColorRect.new()
|
||||||
|
top.color = color
|
||||||
|
top.position = rect.position
|
||||||
|
top.size = Vector2(rect.size.x, width)
|
||||||
|
viewport.add_child(top)
|
||||||
|
|
||||||
|
# Bottom edge
|
||||||
|
var bottom = ColorRect.new()
|
||||||
|
bottom.color = color
|
||||||
|
bottom.position = Vector2(rect.position.x, rect.position.y + rect.size.y - width)
|
||||||
|
bottom.size = Vector2(rect.size.x, width)
|
||||||
|
viewport.add_child(bottom)
|
||||||
|
|
||||||
|
# Left edge
|
||||||
|
var left = ColorRect.new()
|
||||||
|
left.color = color
|
||||||
|
left.position = rect.position
|
||||||
|
left.size = Vector2(width, rect.size.y)
|
||||||
|
viewport.add_child(left)
|
||||||
|
|
||||||
|
# Right edge
|
||||||
|
var right = ColorRect.new()
|
||||||
|
right.color = color
|
||||||
|
right.position = Vector2(rect.position.x + rect.size.x - width, rect.position.y)
|
||||||
|
right.size = Vector2(width, rect.size.y)
|
||||||
|
viewport.add_child(right)
|
||||||
|
|
||||||
|
func _draw_label(viewport: SubViewport, text: String, center_pos: Vector2, font_size: int) -> void:
|
||||||
|
var label = Label.new()
|
||||||
|
label.text = text
|
||||||
|
label.add_theme_font_size_override("font_size", font_size)
|
||||||
|
label.add_theme_color_override("font_color", LABEL_COLOR)
|
||||||
|
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||||
|
# Size the label wide enough and position so center_pos is the center
|
||||||
|
var label_width = text.length() * font_size * 0.7
|
||||||
|
var label_height = font_size * 1.5
|
||||||
|
label.position = Vector2(center_pos.x - label_width / 2, center_pos.y - label_height / 2)
|
||||||
|
label.size = Vector2(label_width, label_height)
|
||||||
|
viewport.add_child(label)
|
||||||
|
|
||||||
|
func _draw_dashed_line(viewport: SubViewport, from: Vector2, to: Vector2) -> void:
|
||||||
|
var dash_length = 20.0
|
||||||
|
var gap_length = 15.0
|
||||||
|
var direction = (to - from).normalized()
|
||||||
|
var total_length = from.distance_to(to)
|
||||||
|
var current = 0.0
|
||||||
|
|
||||||
|
while current < total_length:
|
||||||
|
var dash_end = min(current + dash_length, total_length)
|
||||||
|
var start_pos = from + direction * current
|
||||||
|
var end_pos = from + direction * dash_end
|
||||||
|
|
||||||
|
var dash = ColorRect.new()
|
||||||
|
dash.color = FIELD_LINE_COLOR
|
||||||
|
dash.position = start_pos
|
||||||
|
dash.size = Vector2(dash_end - current, 2)
|
||||||
|
viewport.add_child(dash)
|
||||||
|
|
||||||
|
current = dash_end + gap_length
|
||||||
|
|
||||||
|
func _draw_turn_structure(viewport: SubViewport) -> void:
|
||||||
|
var phases = [
|
||||||
|
"Turn Structure",
|
||||||
|
"",
|
||||||
|
"1. Active Phase",
|
||||||
|
"2. Draw Phase",
|
||||||
|
"3. Main Phase 1",
|
||||||
|
"4. Attack Phase",
|
||||||
|
"5. Main Phase 2",
|
||||||
|
"6. End Phase",
|
||||||
|
]
|
||||||
|
|
||||||
|
var start_y = TURN_REF_RECT.position.y + 20
|
||||||
|
var x = TURN_REF_RECT.position.x + 15
|
||||||
|
|
||||||
|
for i in range(phases.size()):
|
||||||
|
var text = phases[i]
|
||||||
|
if text == "":
|
||||||
|
continue
|
||||||
|
|
||||||
|
var label = Label.new()
|
||||||
|
label.text = text
|
||||||
|
var font_size = 20 if i == 0 else 16
|
||||||
|
label.add_theme_font_size_override("font_size", font_size)
|
||||||
|
label.add_theme_color_override("font_color", TURN_TEXT_COLOR)
|
||||||
|
label.position = Vector2(x, start_y + i * 28)
|
||||||
|
label.size = Vector2(TURN_REF_RECT.size.x - 30, 28)
|
||||||
|
viewport.add_child(label)
|
||||||
@@ -4,9 +4,9 @@ extends Camera3D
|
|||||||
## TableCamera - Isometric camera for the game table
|
## TableCamera - Isometric camera for the game table
|
||||||
|
|
||||||
# Camera settings
|
# Camera settings
|
||||||
@export var camera_distance: float = 18.0
|
@export var camera_distance: float = 17.0
|
||||||
@export var camera_angle: float = 55.0 # Degrees from horizontal (higher = more top-down)
|
@export var camera_angle: float = 55.0 # Degrees from horizontal (higher = more top-down)
|
||||||
@export var camera_height_offset: float = 0.0 # Offset to look slightly above center
|
@export var camera_height_offset: float = -4.0 # Offset to look slightly above center
|
||||||
|
|
||||||
# Smooth movement
|
# Smooth movement
|
||||||
@export var smooth_speed: float = 5.0
|
@export var smooth_speed: float = 5.0
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
class_name TableSetup
|
class_name TableSetup
|
||||||
extends Node3D
|
extends Node3D
|
||||||
|
|
||||||
## TableSetup - Sets up the 3D game table with all zones
|
## TableSetup - Sets up the 3D game table with two play mats and all zones
|
||||||
|
|
||||||
signal card_clicked(card_instance: CardInstance, zone_type: Enums.ZoneType, player_index: int)
|
signal card_clicked(card_instance: CardInstance, zone_type: Enums.ZoneType, player_index: int)
|
||||||
|
|
||||||
@@ -12,32 +12,42 @@ var player_zones: Array[Dictionary] = [{}, {}]
|
|||||||
var deck_indicators: Array[MeshInstance3D] = [null, null]
|
var deck_indicators: Array[MeshInstance3D] = [null, null]
|
||||||
var deck_count_labels: Array[Label3D] = [null, null]
|
var deck_count_labels: Array[Label3D] = [null, null]
|
||||||
|
|
||||||
# Table dimensions
|
# Table dimensions (scaled up for card readability)
|
||||||
const TABLE_WIDTH: float = 16.0
|
const TABLE_WIDTH: float = 20.0
|
||||||
const TABLE_DEPTH: float = 12.0
|
const TABLE_DEPTH: float = 16.0
|
||||||
|
|
||||||
# Zone positions (relative to table center)
|
# Play mat dimensions (each player gets one)
|
||||||
|
const MAT_WIDTH: float = 18.0
|
||||||
|
const MAT_DEPTH: float = 7.0
|
||||||
|
const MAT_MARGIN: float = 0.3 # Gap between mats and from table center
|
||||||
|
|
||||||
|
# Zone positions (relative to table center, for Player 1)
|
||||||
|
# Player 1 sits at +Z looking toward -Z
|
||||||
|
# Player's left = +X, Player's right = -X
|
||||||
const ZONE_POSITIONS = {
|
const ZONE_POSITIONS = {
|
||||||
"deck": Vector3(5.5, 0.1, 3.5),
|
"damage": Vector3(7.0, 0.1, 1.5), # Front-left (player's left)
|
||||||
"damage": Vector3(3.5, 0.1, 3.5),
|
"deck": Vector3(-7.0, 0.1, 1.5), # Front-right (player's right)
|
||||||
"backups": Vector3(0.0, 0.1, 3.5),
|
"break": Vector3(-7.0, 0.1, 4.0), # Right side, behind deck
|
||||||
"break": Vector3(-3.5, 0.1, 3.5),
|
"forwards": Vector3(0.0, 0.1, 2.0), # Center, front row
|
||||||
"forwards": Vector3(0.0, 0.1, 1.5),
|
"backups": Vector3(0.0, 0.1, 5.0), # Center, back row
|
||||||
"hand": Vector3(0.0, 0.5, 5.5)
|
"hand": Vector3(0.0, 0.5, 7.5) # Not used on table (2D overlay)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Components
|
# Components
|
||||||
var table_mesh: MeshInstance3D
|
var table_mesh: MeshInstance3D
|
||||||
|
var player_mat_meshes: Array[MeshInstance3D] = [null, null]
|
||||||
var camera: TableCamera
|
var camera: TableCamera
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
_create_table()
|
_create_table_base()
|
||||||
_create_camera()
|
_create_camera()
|
||||||
_create_lighting()
|
_create_lighting()
|
||||||
_create_zones()
|
_create_zones()
|
||||||
_create_deck_indicators()
|
_create_deck_indicators()
|
||||||
|
_generate_mats()
|
||||||
|
|
||||||
func _create_table() -> void:
|
func _create_table_base() -> void:
|
||||||
|
# Base table surface (dark, slightly larger than mats)
|
||||||
table_mesh = MeshInstance3D.new()
|
table_mesh = MeshInstance3D.new()
|
||||||
add_child(table_mesh)
|
add_child(table_mesh)
|
||||||
|
|
||||||
@@ -45,18 +55,41 @@ func _create_table() -> void:
|
|||||||
plane.size = Vector2(TABLE_WIDTH, TABLE_DEPTH)
|
plane.size = Vector2(TABLE_WIDTH, TABLE_DEPTH)
|
||||||
table_mesh.mesh = plane
|
table_mesh.mesh = plane
|
||||||
|
|
||||||
# Create table material
|
|
||||||
var mat = StandardMaterial3D.new()
|
var mat = StandardMaterial3D.new()
|
||||||
mat.albedo_color = Color(0.15, 0.2, 0.15) # Dark green felt
|
mat.albedo_color = Color(0.08, 0.10, 0.08) # Very dark green base
|
||||||
|
|
||||||
# Try to load playmat texture
|
|
||||||
if ResourceLoader.exists("res://assets/table/playmat.webp"):
|
|
||||||
var texture = load("res://assets/table/playmat.webp")
|
|
||||||
if texture:
|
|
||||||
mat.albedo_texture = texture
|
|
||||||
|
|
||||||
table_mesh.material_override = mat
|
table_mesh.material_override = mat
|
||||||
# PlaneMesh in Godot 4 is already horizontal (facing up), no rotation needed
|
|
||||||
|
func _create_player_mat(player_idx: int, texture: ImageTexture) -> void:
|
||||||
|
var mat_mesh = MeshInstance3D.new()
|
||||||
|
add_child(mat_mesh)
|
||||||
|
|
||||||
|
var plane = PlaneMesh.new()
|
||||||
|
plane.size = Vector2(MAT_WIDTH, MAT_DEPTH)
|
||||||
|
mat_mesh.mesh = plane
|
||||||
|
|
||||||
|
var mat = StandardMaterial3D.new()
|
||||||
|
mat.albedo_texture = texture
|
||||||
|
mat_mesh.material_override = mat
|
||||||
|
|
||||||
|
# Position each mat on its half of the table
|
||||||
|
var mat_center_z = MAT_DEPTH / 2.0 + MAT_MARGIN
|
||||||
|
if player_idx == 0:
|
||||||
|
mat_mesh.position = Vector3(0, 0.01, mat_center_z) # Player 1: positive Z
|
||||||
|
else:
|
||||||
|
mat_mesh.position = Vector3(0, 0.01, -mat_center_z) # Player 2: negative Z
|
||||||
|
mat_mesh.rotation.y = deg_to_rad(180) # Rotate 180 so labels face Player 2
|
||||||
|
|
||||||
|
player_mat_meshes[player_idx] = mat_mesh
|
||||||
|
|
||||||
|
func _generate_mats() -> void:
|
||||||
|
var renderer = PlaymatRenderer.new()
|
||||||
|
add_child(renderer)
|
||||||
|
var texture = await renderer.render()
|
||||||
|
renderer.queue_free()
|
||||||
|
|
||||||
|
# Apply texture to both player mats
|
||||||
|
_create_player_mat(0, texture)
|
||||||
|
_create_player_mat(1, texture)
|
||||||
|
|
||||||
func _create_camera() -> void:
|
func _create_camera() -> void:
|
||||||
camera = TableCamera.new()
|
camera = TableCamera.new()
|
||||||
@@ -128,7 +161,7 @@ func _create_deck_indicators() -> void:
|
|||||||
add_child(deck_mesh)
|
add_child(deck_mesh)
|
||||||
|
|
||||||
var box = BoxMesh.new()
|
var box = BoxMesh.new()
|
||||||
box.size = Vector3(0.63, 0.3, 0.88) # Card size with thickness for deck
|
box.size = Vector3(1.26, 0.3, 1.76) # Card size with thickness for deck
|
||||||
deck_mesh.mesh = box
|
deck_mesh.mesh = box
|
||||||
|
|
||||||
var mat = StandardMaterial3D.new()
|
var mat = StandardMaterial3D.new()
|
||||||
@@ -162,7 +195,7 @@ func _create_zone(zone_type: Enums.ZoneType, player_idx: int, pos: Vector3, rot:
|
|||||||
# Configure based on zone type
|
# Configure based on zone type
|
||||||
match zone_type:
|
match zone_type:
|
||||||
Enums.ZoneType.FIELD_FORWARDS, Enums.ZoneType.FIELD_BACKUPS:
|
Enums.ZoneType.FIELD_FORWARDS, Enums.ZoneType.FIELD_BACKUPS:
|
||||||
zone.card_spacing = 0.8
|
zone.card_spacing = 1.5
|
||||||
Enums.ZoneType.DAMAGE, Enums.ZoneType.BREAK:
|
Enums.ZoneType.DAMAGE, Enums.ZoneType.BREAK:
|
||||||
zone.stack_offset = 0.02
|
zone.stack_offset = 0.02
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func _create_zone_indicator() -> void:
|
|||||||
zone_indicator.mesh = plane
|
zone_indicator.mesh = plane
|
||||||
|
|
||||||
var mat = StandardMaterial3D.new()
|
var mat = StandardMaterial3D.new()
|
||||||
mat.albedo_color = Color(0.2, 0.2, 0.3, 0.3)
|
mat.albedo_color = Color(0.2, 0.2, 0.3, 0.1) # Nearly transparent (mat texture shows zones)
|
||||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||||
zone_indicator.material_override = mat
|
zone_indicator.material_override = mat
|
||||||
|
|
||||||
@@ -88,7 +88,9 @@ func _arrange_cards() -> void:
|
|||||||
match zone_type:
|
match zone_type:
|
||||||
Enums.ZoneType.HAND:
|
Enums.ZoneType.HAND:
|
||||||
_arrange_hand()
|
_arrange_hand()
|
||||||
Enums.ZoneType.DECK, Enums.ZoneType.DAMAGE, Enums.ZoneType.BREAK:
|
Enums.ZoneType.DAMAGE:
|
||||||
|
_arrange_damage()
|
||||||
|
Enums.ZoneType.DECK, Enums.ZoneType.BREAK:
|
||||||
_arrange_stack()
|
_arrange_stack()
|
||||||
Enums.ZoneType.FIELD_FORWARDS, Enums.ZoneType.FIELD_BACKUPS:
|
Enums.ZoneType.FIELD_FORWARDS, Enums.ZoneType.FIELD_BACKUPS:
|
||||||
_arrange_field()
|
_arrange_field()
|
||||||
@@ -117,12 +119,25 @@ func _arrange_hand() -> void:
|
|||||||
Vector3(0, 0, deg_to_rad(-angle))
|
Vector3(0, 0, deg_to_rad(-angle))
|
||||||
)
|
)
|
||||||
|
|
||||||
## Arrange as a stack (for deck, damage, break zone)
|
## Arrange as a stack (for deck, break zone)
|
||||||
func _arrange_stack() -> void:
|
func _arrange_stack() -> void:
|
||||||
for i in range(card_visuals.size()):
|
for i in range(card_visuals.size()):
|
||||||
var card = card_visuals[i]
|
var card = card_visuals[i]
|
||||||
card.move_to(Vector3(0, i * stack_offset, 0))
|
card.move_to(Vector3(0, i * stack_offset, 0))
|
||||||
|
|
||||||
|
## Arrange damage cards spread along Z axis (vertical column on the mat)
|
||||||
|
func _arrange_damage() -> void:
|
||||||
|
var count = card_visuals.size()
|
||||||
|
if count == 0:
|
||||||
|
return
|
||||||
|
# Spread damage cards along Z axis so they fan out visually
|
||||||
|
var spacing = min(0.5, 3.0 / max(count, 1))
|
||||||
|
var total_depth = (count - 1) * spacing
|
||||||
|
var start_z = -total_depth / 2.0
|
||||||
|
for i in range(count):
|
||||||
|
var card = card_visuals[i]
|
||||||
|
card.move_to(Vector3(0, i * stack_offset, start_z + i * spacing))
|
||||||
|
|
||||||
## Arrange in a row (for field zones)
|
## Arrange in a row (for field zones)
|
||||||
func _arrange_field() -> void:
|
func _arrange_field() -> void:
|
||||||
var count = card_visuals.size()
|
var count = card_visuals.size()
|
||||||
@@ -135,13 +150,14 @@ func _arrange_field() -> void:
|
|||||||
for i in range(count):
|
for i in range(count):
|
||||||
var card = card_visuals[i]
|
var card = card_visuals[i]
|
||||||
var x = start_x + i * card_spacing
|
var x = start_x + i * card_spacing
|
||||||
|
var y = i * 0.01 # Slight Y offset so cards layer properly
|
||||||
|
|
||||||
# Apply dull rotation if card is dull
|
# Apply dull rotation if card is dull
|
||||||
var rot_y = 0.0
|
var rot_y = 0.0
|
||||||
if card.card_instance and card.card_instance.is_dull():
|
if card.card_instance and card.card_instance.is_dull():
|
||||||
rot_y = deg_to_rad(90)
|
rot_y = deg_to_rad(90)
|
||||||
|
|
||||||
card.move_to(Vector3(x, 0, 0), Vector3(0, rot_y, 0))
|
card.move_to(Vector3(x, y, 0), Vector3(0, rot_y, 0))
|
||||||
|
|
||||||
## Arrange in a simple row
|
## Arrange in a simple row
|
||||||
func _arrange_row() -> void:
|
func _arrange_row() -> void:
|
||||||
|
|||||||
BIN
source-cards/1-001H.jpg
Normal file
|
After Width: | Height: | Size: 96 KiB |
34
source-cards/1-001H.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://4kj8jdh4u5nl"
|
||||||
|
path="res://.godot/imported/1-001H.jpg-a23ed67aa9e36e1d266918fe07a1d620.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-001H.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-001H.jpg-a23ed67aa9e36e1d266918fe07a1d620.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
|
||||||
BIN
source-cards/1-002R.jpg
Normal file
|
After Width: | Height: | Size: 84 KiB |
34
source-cards/1-002R.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dyvb3sdeanrq2"
|
||||||
|
path="res://.godot/imported/1-002R.jpg-a88107fd1684dbbad7f220f1570f22d4.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-002R.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-002R.jpg-a88107fd1684dbbad7f220f1570f22d4.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
|
||||||
BIN
source-cards/1-003C.jpg
Normal file
|
After Width: | Height: | Size: 74 KiB |
34
source-cards/1-003C.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://h7aph48764bu"
|
||||||
|
path="res://.godot/imported/1-003C.jpg-33398dc301d59d6fb00611e50d4e9d31.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-003C.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-003C.jpg-33398dc301d59d6fb00611e50d4e9d31.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
|
||||||
BIN
source-cards/1-004C.jpg
Normal file
|
After Width: | Height: | Size: 88 KiB |
34
source-cards/1-004C.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://btmd560tgrr8n"
|
||||||
|
path="res://.godot/imported/1-004C.jpg-7c10f8b7f34d8cf305d3bc5df9adf0ca.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-004C.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-004C.jpg-7c10f8b7f34d8cf305d3bc5df9adf0ca.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
|
||||||
BIN
source-cards/1-005R.jpg
Normal file
|
After Width: | Height: | Size: 78 KiB |
34
source-cards/1-005R.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bbx8ma4rqdoi3"
|
||||||
|
path="res://.godot/imported/1-005R.jpg-8f86adbe7b815e791bae549e418bfd11.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-005R.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-005R.jpg-8f86adbe7b815e791bae549e418bfd11.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
|
||||||
BIN
source-cards/1-006H.jpg
Normal file
|
After Width: | Height: | Size: 91 KiB |
34
source-cards/1-006H.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cksy5fejyx5f4"
|
||||||
|
path="res://.godot/imported/1-006H.jpg-ff40e8d353f1ff4ba4838c56ccd674b3.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-006H.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-006H.jpg-ff40e8d353f1ff4ba4838c56ccd674b3.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
|
||||||
BIN
source-cards/1-007R.jpg
Normal file
|
After Width: | Height: | Size: 98 KiB |
34
source-cards/1-007R.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dwlg1r2jjm8op"
|
||||||
|
path="res://.godot/imported/1-007R.jpg-f5c6ba3184327e97cf4de2d2fe085161.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-007R.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-007R.jpg-f5c6ba3184327e97cf4de2d2fe085161.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
|
||||||
BIN
source-cards/1-008C.jpg
Normal file
|
After Width: | Height: | Size: 75 KiB |
34
source-cards/1-008C.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bg37jr5nupxy"
|
||||||
|
path="res://.godot/imported/1-008C.jpg-349164be93741ca8a689775c7ad90a2e.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-008C.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-008C.jpg-349164be93741ca8a689775c7ad90a2e.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
|
||||||
BIN
source-cards/1-009C.jpg
Normal file
|
After Width: | Height: | Size: 90 KiB |
34
source-cards/1-009C.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://e62go5oijrms"
|
||||||
|
path="res://.godot/imported/1-009C.jpg-5dc94fd64aa991517527ce83bc1ec8e5.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-009C.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-009C.jpg-5dc94fd64aa991517527ce83bc1ec8e5.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
|
||||||
BIN
source-cards/1-010C.jpg
Normal file
|
After Width: | Height: | Size: 75 KiB |
34
source-cards/1-010C.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://q23hlfnvsdqh"
|
||||||
|
path="res://.godot/imported/1-010C.jpg-68107c4efa105b8d795124f4149f3c40.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-010C.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-010C.jpg-68107c4efa105b8d795124f4149f3c40.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
|
||||||
BIN
source-cards/1-011C.jpg
Normal file
|
After Width: | Height: | Size: 69 KiB |
34
source-cards/1-011C.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://br2nb358sj2kh"
|
||||||
|
path="res://.godot/imported/1-011C.jpg-08de4c3fe4a784af4c92e897c84b5ef8.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-011C.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-011C.jpg-08de4c3fe4a784af4c92e897c84b5ef8.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
|
||||||
BIN
source-cards/1-012R.jpg
Normal file
|
After Width: | Height: | Size: 77 KiB |
34
source-cards/1-012R.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://ch625n6rmhb3t"
|
||||||
|
path="res://.godot/imported/1-012R.jpg-a9aa6f993aff94b0049f249ab7eb1ca0.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-012R.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-012R.jpg-a9aa6f993aff94b0049f249ab7eb1ca0.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
|
||||||
BIN
source-cards/1-013H.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
34
source-cards/1-013H.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dg27ayrsjomif"
|
||||||
|
path="res://.godot/imported/1-013H.jpg-b2defd407ec0c615026035a125f831b9.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-013H.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-013H.jpg-b2defd407ec0c615026035a125f831b9.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
|
||||||
BIN
source-cards/1-014C.jpg
Normal file
|
After Width: | Height: | Size: 89 KiB |
34
source-cards/1-014C.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cth4c2segbj4f"
|
||||||
|
path="res://.godot/imported/1-014C.jpg-55be4998b74cfc62f628b34aed967ed7.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-014C.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-014C.jpg-55be4998b74cfc62f628b34aed967ed7.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
|
||||||
BIN
source-cards/1-015L.jpg
Normal file
|
After Width: | Height: | Size: 100 KiB |
34
source-cards/1-015L.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://da5uuy3necurr"
|
||||||
|
path="res://.godot/imported/1-015L.jpg-420424558d279dbc40818136eafeef22.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-015L.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-015L.jpg-420424558d279dbc40818136eafeef22.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
|
||||||
BIN
source-cards/1-016C.jpg
Normal file
|
After Width: | Height: | Size: 82 KiB |
34
source-cards/1-016C.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://c6w4mhyyqr32j"
|
||||||
|
path="res://.godot/imported/1-016C.jpg-ab766085330f8082041ee0c94420e1a4.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-016C.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-016C.jpg-ab766085330f8082041ee0c94420e1a4.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
|
||||||
BIN
source-cards/1-017R.jpg
Normal file
|
After Width: | Height: | Size: 272 KiB |
34
source-cards/1-017R.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://df02fr0yg2v7m"
|
||||||
|
path="res://.godot/imported/1-017R.jpg-9a5663ec5f05794ed78cfa5f1b9224e7.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-017R.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-017R.jpg-9a5663ec5f05794ed78cfa5f1b9224e7.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
|
||||||
BIN
source-cards/1-018L.jpg
Normal file
|
After Width: | Height: | Size: 303 KiB |
34
source-cards/1-018L.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://ch7oansw6372q"
|
||||||
|
path="res://.godot/imported/1-018L.jpg-5057db4d8273f9a06111aafba76ed31f.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-018L.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-018L.jpg-5057db4d8273f9a06111aafba76ed31f.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
|
||||||
BIN
source-cards/1-019C.jpg
Normal file
|
After Width: | Height: | Size: 263 KiB |
34
source-cards/1-019C.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://82108usubwkx"
|
||||||
|
path="res://.godot/imported/1-019C.jpg-600aeeb3d4974f7169b36c8f19c81cd1.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-019C.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-019C.jpg-600aeeb3d4974f7169b36c8f19c81cd1.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
|
||||||
BIN
source-cards/1-020R.jpg
Normal file
|
After Width: | Height: | Size: 91 KiB |
34
source-cards/1-020R.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://ckdhaklrsri7g"
|
||||||
|
path="res://.godot/imported/1-020R.jpg-d1827446cb811dae7d975df9c79e0268.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-020R.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-020R.jpg-d1827446cb811dae7d975df9c79e0268.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
|
||||||
BIN
source-cards/1-021H.jpg
Normal file
|
After Width: | Height: | Size: 82 KiB |
34
source-cards/1-021H.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://b4x2jis6i3rcp"
|
||||||
|
path="res://.godot/imported/1-021H.jpg-be9f681d213929cdc846329998cfdb49.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-021H.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-021H.jpg-be9f681d213929cdc846329998cfdb49.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
|
||||||
BIN
source-cards/1-022R.jpg
Normal file
|
After Width: | Height: | Size: 107 KiB |
34
source-cards/1-022R.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cvmhmfo4se3qt"
|
||||||
|
path="res://.godot/imported/1-022R.jpg-ba1f26977fbc7e033244c6192a6ef709.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-022R.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-022R.jpg-ba1f26977fbc7e033244c6192a6ef709.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
|
||||||
BIN
source-cards/1-023R.jpg
Normal file
|
After Width: | Height: | Size: 85 KiB |
34
source-cards/1-023R.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://ywwbqpiem6mr"
|
||||||
|
path="res://.godot/imported/1-023R.jpg-e66ecc6c675064962eaf3adcde54163e.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-023R.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-023R.jpg-e66ecc6c675064962eaf3adcde54163e.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
|
||||||
BIN
source-cards/1-024C.jpg
Normal file
|
After Width: | Height: | Size: 76 KiB |
34
source-cards/1-024C.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dxjkygvy3hj3i"
|
||||||
|
path="res://.godot/imported/1-024C.jpg-010ea02ae48e15a99e3a85437dcffefc.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-024C.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-024C.jpg-010ea02ae48e15a99e3a85437dcffefc.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
|
||||||
BIN
source-cards/1-025C.jpg
Normal file
|
After Width: | Height: | Size: 84 KiB |
34
source-cards/1-025C.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bv1ibte5oislr"
|
||||||
|
path="res://.godot/imported/1-025C.jpg-cfbda8146831ade494bd66a705dd3a4e.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-025C.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-025C.jpg-cfbda8146831ade494bd66a705dd3a4e.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
|
||||||
BIN
source-cards/1-026C.jpg
Normal file
|
After Width: | Height: | Size: 82 KiB |
34
source-cards/1-026C.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dblbjr62di2um"
|
||||||
|
path="res://.godot/imported/1-026C.jpg-73c25e2c2082a343f2211be2fc9cec1a.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-026C.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-026C.jpg-73c25e2c2082a343f2211be2fc9cec1a.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
|
||||||
BIN
source-cards/1-027H.jpg
Normal file
|
After Width: | Height: | Size: 80 KiB |
34
source-cards/1-027H.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bey14dovkfbd4"
|
||||||
|
path="res://.godot/imported/1-027H.jpg-bf6725ad20d9cdf6f3bb5464e173f12c.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-027H.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-027H.jpg-bf6725ad20d9cdf6f3bb5464e173f12c.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
|
||||||
BIN
source-cards/1-028R.jpg
Normal file
|
After Width: | Height: | Size: 80 KiB |
34
source-cards/1-028R.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://1mciuyikp0ui"
|
||||||
|
path="res://.godot/imported/1-028R.jpg-8ba2185774c8c711620f123f7695db3a.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-028R.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-028R.jpg-8ba2185774c8c711620f123f7695db3a.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
|
||||||
BIN
source-cards/1-029C.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
34
source-cards/1-029C.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bhppsqhwnc4cy"
|
||||||
|
path="res://.godot/imported/1-029C.jpg-1a58ad2b04d7f3d30a623d2544f67f1d.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-029C.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-029C.jpg-1a58ad2b04d7f3d30a623d2544f67f1d.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
|
||||||
BIN
source-cards/1-030R.jpg
Normal file
|
After Width: | Height: | Size: 81 KiB |
34
source-cards/1-030R.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://c6arvqj4j0quj"
|
||||||
|
path="res://.godot/imported/1-030R.jpg-58ef590a369cd417cdc3479f149f9faf.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-030R.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-030R.jpg-58ef590a369cd417cdc3479f149f9faf.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
|
||||||
BIN
source-cards/1-031C.jpg
Normal file
|
After Width: | Height: | Size: 82 KiB |
34
source-cards/1-031C.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bu5e4sv1pr6gv"
|
||||||
|
path="res://.godot/imported/1-031C.jpg-8cc8b47189ffea102486d93809731dae.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-031C.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-031C.jpg-8cc8b47189ffea102486d93809731dae.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
|
||||||
BIN
source-cards/1-032C.jpg
Normal file
|
After Width: | Height: | Size: 79 KiB |
34
source-cards/1-032C.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dvf8tiwoy0jav"
|
||||||
|
path="res://.godot/imported/1-032C.jpg-2bbeaee67f09f72b9fe904c64a712672.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-032C.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-032C.jpg-2bbeaee67f09f72b9fe904c64a712672.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
|
||||||
BIN
source-cards/1-033C.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
34
source-cards/1-033C.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dcs4na1hrs8du"
|
||||||
|
path="res://.godot/imported/1-033C.jpg-2d992a66dbb4eec538a117ac96a04817.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-033C.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-033C.jpg-2d992a66dbb4eec538a117ac96a04817.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
|
||||||
BIN
source-cards/1-034R.jpg
Normal file
|
After Width: | Height: | Size: 88 KiB |
34
source-cards/1-034R.jpg.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://d2uvywsbi61ra"
|
||||||
|
path="res://.godot/imported/1-034R.jpg-3f589d353bd3e0149992194b3d19b046.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://source-cards/1-034R.jpg"
|
||||||
|
dest_files=["res://.godot/imported/1-034R.jpg-3f589d353bd3e0149992194b3d19b046.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
|
||||||
BIN
source-cards/1-035C.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |