extends Spatial class_name Board signal loaded() onready var CardTemplate := preload("res://Scenes/Components/Card.tscn") onready var camera := $Camera onready var hand := $Camera/PlayerHand onready var oppHand := $Camera/OppHand onready var ui := $BoardUI onready var cards := $Cards export var mouseHandThreshold = 0.90 var holdingCard: Card = null var focusedCard: Card = null var currentZone: Stack = null var mouseOrigin: Vector2 var lastCameraTransform: Transform var cardsToLoad: int = 0 onready var zones: Dictionary = { "player1.deck": $Cards/P1Deck, "player1.banished": $Cards/P1Banished, "player1.graveyard": $Cards/P1Graveyard, "player1.problem": $Cards/P1Problem, "player2.deck": $Cards/P2Deck, "player2.banished": $Cards/P2Banished, "player2.graveyard": $Cards/P2Graveyard, "player2.problem": $Cards/P2Problem } func _ready(): # Load bgm BGM.load_music("ingame1") # Sample deck var deck = [ "pr10","pr11","pr12","pr13","pr14","pr15","pr16","pr12","pr13","pr14", "pr10","pr11","pr12","pr13","pr14","pr15","pr16","pr12","pr13","pr14", "pr10","pr11","pr12","pr13","pr14","pr15","pr16","pr12","pr13","pr14", "pr10","pr11","pr12","pr13","pr14","pr15","pr16","pr12","pr13","pr14", "pr10","pr11","pr12","pr13","pr14","pr15","pr16","pr12","pr13","pr14" ] cardsToLoad = len(deck) # Fill deck with cards for card in deck: add_card(card, 0, "player1.deck") #TODO Shuffle deck #TODO Draw cards from deck for i in range(0,6): draw_card(0, "player1.deck") for i in range(0,6): draw_card(1, "player1.deck") func _input(event: InputEvent): # Camera zoom if event.is_action("zoom_in"): camera.zoom(true) elif event.is_action("zoom_out"): camera.zoom(false) # Save original camera and mouse position before panning if event.is_action_pressed("pan"): mouseOrigin = get_viewport().get_mouse_position() lastCameraTransform = camera.transform var baseViewport := Vector2(1280, 720) func _process(delta: float): # If mouse is under a certain area then we're managing our hand var absMousePos := get_viewport().get_mouse_position() # If panning, translate mouse delta to camera delta if Input.is_action_pressed("pan"): var mouseDelta := absMousePos - mouseOrigin var mousePos: Vector2 = mouseDelta * 0.0096 * (1-camera.get_zoom()/5) # Magic numbers everywhere camera.transform.origin = lastCameraTransform.origin - Vector3(mousePos.x, 0, mousePos.y) # If holding a card, move it between board/hand if holdingCard != null: # Check if we're selecting our hand while holding a card var relPos := get_viewport().get_mouse_position() / baseViewport var xMargin = 1.0-HAND_SCREEN_PERC var selectingHand: bool = relPos.y > mouseHandThreshold and (relPos.x > xMargin/2 and relPos.x < (1.0-xMargin/2)) # Move card in/out hand if selectingHand and not holdingCard.inHand: holdingCard.inHand = true call_deferred("reparent", holdingCard, cards, hand) elif not selectingHand and holdingCard.inHand: holdingCard.inHand = false call_deferred("reparent", holdingCard, hand, cards) # Move card to/from zone if currentZone != null: if holdingCard.inZone: if holdingCard.zoneName != currentZone.zoneName: # Move from old zone to new call_deferred("reparent", holdingCard, zones[holdingCard.zoneName], currentZone) holdingCard.zoneName = currentZone.zoneName else: if holdingCard.inHand: call_deferred("reparent", holdingCard, hand, currentZone) else: call_deferred("reparent", holdingCard, cards, currentZone) holdingCard.inZone = true holdingCard.zoneName = currentZone.zoneName elif holdingCard.inZone: holdingCard.inZone = false # Move from zone to hand/field if holdingCard.inHand: call_deferred("reparent", holdingCard, zones[holdingCard.zoneName], hand) else: call_deferred("reparent", holdingCard, zones[holdingCard.zoneName], cards) holdingCard.zoneName = "" holdingCard.flipped = false func _physics_process(delta): raycast_zone() func _card_picked(card: Card): # Call pop if applicable if card.inZone: zones[card.zoneName].pop_card() holdingCard = card holdingCard.animation.queue("lift") func _card_dropped(card: Card): if card.inHand: card.animation.queue("drop-hand") reorder_hand(0) elif currentZone != null: card.inZone = true currentZone.push_card(card) card.animation.queue("drop-board") holdingCard = null func _card_selected(card: Card): if card.inHand and focusedCard == null: focusedCard = card card.handanim.play("focus") func _card_unselected(card: Card): if focusedCard == card: focusedCard = null if card.inHand: card.handanim.play("blur") func _card_moved(origin: Vector3, direction: Vector3, t: float, card: Card): if card.flipped: card.border.translation = Vector3(0, 0.01, 0) card.border.rotation = Vector3(0, 0, PI) else: card.border.translation = Vector3.ZERO card.border.rotation = Vector3.ZERO if card.inHand: card.translation = Vector3(direction.x * 3.0, 0.25, -0.25) # Fix rotation if coming from hand card.rotation = Vector3.ZERO elif card.inZone: card.translation = Vector3.UP * zones[card.zoneName].cards.size() * zones[card.zoneName].CARD_DISTANCE card.rotation = Vector3.ZERO else: card.tween_move_to(origin + direction * t, 0.016) # Rotate depending on what side of the board it is var baseRot = card.rotation - Vector3.UP * card.rotation.y if card.translation.z < 0: card.tween_rotate(baseRot + Vector3.UP * PI, 0.04) else: card.tween_rotate(baseRot, 0.04) card.tween() func _card_clicked(card: Card): if card.exhausted: card.animation.queue("tap-back") card.exhausted = false else: card.animation.queue("tap") card.exhausted = true func _card_menu_action(id: int, card: Card): match id: 0: # Flip if card.flipped: card.animation.queue("flip-back") card.flipped = false else: card.animation.queue("flip") card.flipped = true func _card_loaded(): cardsToLoad -= 1 if cardsToLoad <= 0: emit_signal("loaded") func reparent(object: Node, from: Node, to: Node): from.remove_child(object) to.add_child(object) object.set_owner(to) if to == hand or from == hand: reorder_hand(0) if to == oppHand or from == oppHand: reorder_hand(1) func add_card(cardID: String, playerID: int, zone: String): var card := CardTemplate.instance() card.cardID = cardID card.playerID = playerID card.inHand = zone == "hand" card.connect("card_dropped", self, "_card_dropped", [card]) card.connect("card_picked", self, "_card_picked", [card]) card.connect("card_selected", ui, "_card_selected", [card]) card.connect("card_unselected", ui, "_card_unselected", [card]) card.connect("card_selected", self, "_card_selected", [card]) card.connect("card_unselected", self, "_card_unselected", [card]) card.connect("card_dropped_anim", self, "check_hand_drop", [card]) card.connect("card_moved", self, "_card_moved", [card]) card.connect("card_menu", ui, "show_card_menu", [card, self, "_card_menu_action"]) card.connect("card_clicked", self, "_card_clicked", [card]) card.connect("loaded", self, "_card_loaded") if card.inHand: # TODO support for >2 players if playerID == 0: hand.add_child(card) else: oppHand.add_child(card) elif zones.has(zone): card.inZone = true card.zoneName = zone zones[zone].add_child(card) zones[zone].push_card(card) else: cards.add_child(card) func draw_card(playerID: int, from: String): var card := (zones[from] as Stack).pop_card() card.inHand = true card.inZone = false card.flipped = false card.animation.queue("flip-back-fast") var target := hand if playerID != 0: target = oppHand reparent(card, zones[from], target) func check_hand_drop(card: Card): # Re-order hand as soon as the animation is over if card.inHand: card.reset_transform() reorder_hand(0) const MAX_CARD_DISTANCE := 0.5 const HAND_SCREEN_PERC := 0.6 const CARD_ROTATION := 0.03 const UNITSPERPX := 0.003 func reorder_hand(playerID: int): var cardsInHand: Array = [] if playerID == 0: cardsInHand = hand.get_children() else: cardsInHand = oppHand.get_children() cardsInHand.sort_custom(TransformSorter, "sort") var size := cardsInHand.size() # Check if we have nothing to order if size < 1: return # Calculate total width of the player's hand and other things # This is done in two ways, for small hands, MAX_CARD_DISTANCE is usually used # as constant distance between cards, however, as the hand gets larger we don't # want them to go offscreen, therefore a "maximum screen %" is used to determine # how to fit all the cards within a % of the viewport's width var distancePerc := get_viewport().size.x * HAND_SCREEN_PERC * UNITSPERPX / size var distance := min(distancePerc, MAX_CARD_DISTANCE) var totalWidth := distance * (size-1) var minX := -totalWidth/2 # Iterate over all items, keep track of the index var i := 0 for child in cardsInHand: child.tween_move_to(Vector3(minX + distance * i, 0.02*i, -0.005*i)) child.tween_rotate(Vector3(0, CARD_ROTATION * (size/2.0 - i), 0)) i += 1 child.reset_transform() child.tween() func raycast_zone(): # Do some raycast magic that normal Godot events cannot get (like ignoring items) var space_state := get_world().direct_space_state var absMousePos := get_viewport().get_mouse_position() var from: Vector3 = camera.project_ray_origin(absMousePos) var to: Vector3 = from + camera.project_ray_normal(absMousePos) * 1000 var result := space_state.intersect_ray(from, to, [self, holdingCard], 0x7FFFFFFF, true, true) if result: if result.collider is Stack and result.collider.editable: currentZone = result.collider else: currentZone = null class TransformSorter: static func sort(a, b): if a.transform.origin.x < b.transform.origin.x: return true return false func _card_picked_zone(card, zone): _card_picked(card)