This commit is contained in:
yaoyanwei
2025-08-04 16:45:48 +08:00
parent 565aa16389
commit 2f2a601227
2296 changed files with 522745 additions and 93 deletions

View File

@@ -0,0 +1,132 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace TcgEngine.Client
{
/// <summary>
/// Base class for BoardSlot, BoardSlotPlayer and BoardSlotGroup
/// </summary>
public class BSlot : MonoBehaviour
{
protected SpriteRenderer render;
protected Collider collide;
protected Bounds bounds;
protected float start_alpha = 0f;
protected float current_alpha = 0f;
protected float target_alpha = 0f;
private static List<BSlot> slot_list = new List<BSlot>();
protected virtual void Awake()
{
slot_list.Add(this);
render = GetComponent<SpriteRenderer>();
collide = GetComponent<Collider>();
start_alpha = render.color.a;
render.color = new Color(render.color.r, render.color.g, render.color.b, 0f);
bounds = collide.bounds;
}
protected virtual void OnDestroy()
{
slot_list.Remove(this);
}
protected virtual void Update()
{
current_alpha = Mathf.MoveTowards(current_alpha, target_alpha * start_alpha, 2f * Time.deltaTime);
render.color = new Color(render.color.r, render.color.g, render.color.b, current_alpha);
}
public virtual Slot GetSlot()
{
return Slot.None;
}
public virtual Slot GetSlot(Vector3 wpos)
{
return GetSlot();
}
public virtual Slot GetEmptySlot(Vector3 wpos)
{
return GetSlot();
}
public virtual Card GetSlotCard(Vector3 wpos)
{
Game gdata = GameClient.Get().GetGameData();
Slot slot = GetSlot(wpos);
return gdata.GetSlotCard(slot);
}
public virtual Vector3 GetPosition(Slot slot)
{
return transform.position;
}
public virtual Player GetPlayer()
{
return null;
}
public virtual bool HasSlot(Slot slot)
{
Slot aslot = GetSlot();
return aslot == slot;
}
public virtual bool IsPlayer()
{
Slot slot = GetSlot();
return slot.x == 0 && slot.y == 0;
}
public virtual bool IsInside(Vector3 wpos)
{
return bounds.Contains(wpos);
}
public static BSlot GetNearest(Vector3 pos)
{
BSlot nearest = null;
float min_dist = 999f;
foreach (BSlot slot in GetAll())
{
float dist = (slot.transform.position - pos).magnitude;
if (slot.IsInside(pos) && dist < min_dist)
{
min_dist = dist;
nearest = slot;
}
}
return nearest;
}
public static BSlot Get(Slot slot)
{
foreach (BSlot bslot in GetAll())
{
if (bslot.HasSlot(slot))
return bslot;
}
return null;
}
public static List<BSlot> GetAll()
{
return slot_list;
}
}
public enum BoardSlotType
{
Fixed = 0, //x,y,p = slot
PlayerSelf = 5, //p = client player id
PlayerOpponent = 7, //p = client's opponent player id
FlipX = 10, //p=0, x=unchanged for first player, x=reversed for second player
FlipY = 11, //p=0, y=unchanged for first player, y=reversed for second player
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f24fd6723cd31954591a68e4398ebe8a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,541 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TcgEngine.Client;
using UnityEngine.Events;
using TcgEngine.UI;
using TcgEngine.FX;
namespace TcgEngine.Client
{
/// <summary>
/// Represents the visual aspect of a card on the board.
/// Will take the data from Card.cs and display it
/// </summary>
public class BoardCard : MonoBehaviour
{
public SpriteRenderer card_sprite;
public SpriteRenderer card_glow;
public SpriteRenderer card_shadow;
public Image armor_icon;
public Text armor;
public CanvasGroup status_group;
public Text status_text;
public BoardCardEquip equipment;
public AbilityButton[] buttons;
public Color glow_ally;
public Color glow_enemy;
public UnityAction onKill;
private CardUI card_ui;
private BoardCardFX card_fx;
private Canvas canvas;
private string card_uid = "";
private bool destroyed = false;
private bool focus = false;
private float timer = 0f;
private float status_alpha_target = 0f;
private float delayed_damage_timer = 0f;
private int prev_hp = 0;
private bool back_to_hand;
private Vector3 back_to_hand_target;
private static List<BoardCard> card_list = new List<BoardCard>();
void Awake()
{
card_list.Add(this);
card_ui = GetComponent<CardUI>();
card_fx = GetComponent<BoardCardFX>();
canvas = GetComponentInChildren<Canvas>();
card_glow.color = new Color(card_glow.color.r, card_glow.color.g, card_glow.color.b, 0f);
canvas.gameObject.SetActive(false);
status_alpha_target = 0f;
if (equipment != null)
equipment.Hide();
if (status_group != null)
status_group.alpha = 0f;
}
void OnDestroy()
{
card_list.Remove(this);
}
private void Start()
{
//Random slight rotation
Vector3 board_rot = GameBoard.Get().GetAngles();
transform.rotation = Quaternion.Euler(board_rot.x, board_rot.y, board_rot.z + Random.Range(-1f, 1f));
}
void Update()
{
if (!GameClient.Get().IsReady())
return;
delayed_damage_timer -= Time.deltaTime;
timer += Time.deltaTime;
if (timer > 0.15f && !destroyed && !canvas.gameObject.activeSelf)
canvas.gameObject.SetActive(true);
PlayerControls controls = PlayerControls.Get();
Game data = GameClient.Get().GetGameData();
Player player = GameClient.Get().GetPlayer();
Card card = data.GetCard(card_uid);
if (!destroyed)
{
card_ui.SetCard(card);
card_ui.SetHP(prev_hp);
}
//Save Previous HP
if (!IsDamagedDelayed())
prev_hp = card.GetHP();
bool selected = controls.GetSelected() == this;
Vector3 targ_pos = GetTargetPos();
float speed = 12f;
transform.position = Vector3.MoveTowards(transform.position, targ_pos, speed * Time.deltaTime);
float target_alpha = IsFocus() || selected ? 1f : 0f;
if (destroyed || timer < 1f)
target_alpha = 0f;
if (equipment != null && equipment.IsFocus())
target_alpha = 0f;
Color ccolor = player.player_id == card.player_id ? glow_ally : glow_enemy;
float calpha = Mathf.MoveTowards(card_glow.color.a, target_alpha * ccolor.a, 4f * Time.deltaTime);
card_glow.color = new Color(ccolor.r, ccolor.g, ccolor.b, calpha);
card_shadow.enabled = !destroyed && timer > 0.4f;
card_sprite.color = card.HasStatus(StatusType.Stealth) ? Color.gray : Color.white;
card_ui.hp.color = (destroyed || card.damage > 0) ? Color.yellow : Color.white;
//armor
int armor_val = card.GetStatusValue(StatusType.Armor);
armor.text = armor_val.ToString();
armor.enabled = armor_val > 0;
armor_icon.enabled = armor_val > 0;
//Update card image
Sprite sprite = card.CardData.GetBoardArt(card.VariantData);
if (sprite != card_sprite.sprite)
card_sprite.sprite = sprite;
//Update frame image
Sprite frame = card.VariantData.frame_board;
if (frame != null && card_ui.frame_image != null)
card_ui.frame_image.sprite = frame;
//Equipment
if (equipment != null)
{
Card equip = data.GetEquipCard(card.equipped_uid);
equipment.SetEquip(equip);
}
//Ability buttons
foreach (AbilityButton button in buttons)
button.Hide();
if (selected && card.player_id == player.player_id)
{
int index = 0;
List<AbilityData> abilities = card.GetAbilities();
foreach (AbilityData iability in abilities)
{
if (iability != null && iability.trigger == AbilityTrigger.Activate)
{
if (index < buttons.Length)
{
AbilityButton button = buttons[index];
button.SetAbility(card, iability);
button.SetInteractable(data.CanCastAbility(card, iability));
}
index++;
}
}
Card equip = data.GetEquipCard(card.equipped_uid);
if (equip != null)
{
List<AbilityData> equip_abilities = equip.GetAbilities();
foreach (AbilityData iability in equip_abilities)
{
if (iability != null && iability.trigger == AbilityTrigger.Activate)
{
if (index < buttons.Length)
{
AbilityButton button = buttons[index];
button.SetAbility(equip, iability);
button.SetInteractable(data.CanCastAbility(equip, iability));
}
index++;
}
}
}
}
//Status bar
if (status_group != null)
status_group.alpha = Mathf.MoveTowards(status_group.alpha, status_alpha_target, 5f * Time.deltaTime);
}
private Vector3 GetTargetPos()
{
Game data = GameClient.Get().GetGameData();
Card card = data.GetCard(card_uid);
if (destroyed && back_to_hand && timer > 0.5f)
return back_to_hand_target;
BSlot slot = BSlot.Get(card.slot);
if (slot != null)
{
Vector3 targ_pos = slot.GetPosition(card.slot);
return targ_pos;
}
return transform.position;
}
public void SetCard(Card card)
{
this.card_uid = card.uid;
transform.position = GetTargetPos();
prev_hp = card.GetHP();
CardData icard = CardData.Get(card.card_id);
if (icard)
{
card_ui.SetCard(card);
card_sprite.sprite = icard.GetBoardArt(card.VariantData);
armor.enabled = false;
armor_icon.enabled = false;
status_alpha_target = 0f;
}
}
public void SetOrder(int order)
{
card_sprite.sortingOrder = order;
canvas.sortingOrder = order + 1;
}
public void Destroy()
{
if (!destroyed)
{
Game data = GameClient.Get().GetGameData();
Card card = data.GetCard(card_uid);
Player player = data.GetPlayer(card.player_id);
destroyed = true;
timer = 0f;
status_alpha_target = 0f;
card_glow.enabled = false;
card_shadow.enabled = false;
SetOrder(card_sprite.sortingOrder - 2);
Destroy(gameObject, 1.3f);
TimeTool.WaitFor(0.8f, () =>
{
canvas.gameObject.SetActive(false);
});
GameBoard board = GameBoard.Get();
if (player.HasCard(player.cards_hand, card) || player.HasCard(player.cards_deck, card))
{
back_to_hand = true;
back_to_hand_target = player.player_id == GameClient.Get().GetPlayerID() ? -board.transform.up : board.transform.up;
back_to_hand_target = back_to_hand_target * 10f;
}
if (!back_to_hand)
{
card.hp = 0;
card_ui.SetCard(card);
}
if (onKill != null)
onKill.Invoke();
}
}
//Offset the HP visuals by a value so the HP dont go down before end of animation (like a projectile)
public void DelayDamage(int damage, float duration = 1f)
{
if (damage != 0)
{
delayed_damage_timer = duration;
}
}
public bool IsDamagedDelayed()
{
return delayed_damage_timer > 0f;
}
private void ShowStatusBar()
{
Card card = GetCard();
if (card != null && status_text != null && !destroyed)
{
string stxt = GetStatusText();
string ttxt = GetTraitText();
if (stxt.Length > 0 && ttxt.Length > 0)
status_text.text = ttxt + ", " + stxt;
else
status_text.text = ttxt + stxt;
}
bool show_status = status_text != null && status_text.text.Length > 0;
status_alpha_target = show_status ? 1f : 0f;
}
public string GetStatusText()
{
Card card = GetCard();
string txt = "";
foreach (CardStatus astatus in card.GetAllStatus())
{
StatusData istats = StatusData.Get(astatus.type);
if (istats != null && !string.IsNullOrEmpty(istats.title))
{
int ival = Mathf.Max(astatus.value, Mathf.CeilToInt(astatus.duration / 2f));
string sval = ival > 1 ? " " + ival : "";
txt += istats.GetTitle() + sval + ", ";
}
}
if (txt.Length > 2)
txt = txt.Substring(0, txt.Length - 2);
return txt;
}
public string GetTraitText()
{
Card card = GetCard();
string txt = "";
foreach (CardTrait atrait in card.GetAllTraits())
{
TraitData itrait = TraitData.Get(atrait.id);
if (itrait != null && !string.IsNullOrEmpty(itrait.title))
{
int ival = atrait.value;
string sval = ival > 1 ? " " + ival : "";
txt += itrait.GetTitle() + sval + ", ";
}
}
if (txt.Length > 2)
txt = txt.Substring(0, txt.Length - 2);
return txt;
}
public bool IsDead()
{
return destroyed;
}
public bool IsFocus()
{
return focus;
}
public bool IsEquipFocus()
{
return equipment != null && equipment.IsFocus();
}
public void OnMouseEnter()
{
if (GameUI.IsUIOpened())
return;
if (GameTool.IsMobile())
return;
focus = true;
ShowStatusBar();
}
public void OnMouseExit()
{
focus = false;
status_alpha_target = 0f;
}
public void OnMouseDown()
{
if (GameUI.IsOverUILayer("UI"))
return;
PlayerControls.Get().SelectCard(this);
if (GameTool.IsMobile())
{
focus = true;
ShowStatusBar();
}
}
public void OnMouseUp()
{
}
public void OnMouseOver()
{
if (Input.GetMouseButtonDown(1))
{
PlayerControls.Get().SelectCardRight(this);
}
}
public string GetCardUID()
{
return card_uid;
}
//Return main card (not equip)
public Card GetCard()
{
Game data = GameClient.Get().GetGameData();
Card card = data.GetCard(card_uid);
return card;
}
//Return equip card
public Card GetEquipCard()
{
Game data = GameClient.Get().GetGameData();
Card card = GetCard();
Card equip = data?.GetEquipCard(card.equipped_uid);
return equip;
}
//Return either main or equip card based on which one is focused
public Card GetFocusCard()
{
if (IsEquipFocus())
return GetEquipCard();
return GetCard();
}
public CardData GetCardData()
{
Card card = GetCard();
if (card != null)
return CardData.Get(card.card_id);
return null;
}
public Slot GetSlot()
{
return GetCard().slot;
}
public BoardCardFX GetCardFX()
{
return card_fx;
}
public CardData CardData { get { return GetCardData(); } }
public static int GetNbCardsBoardPlayer(int player_id)
{
int nb = 0;
foreach (BoardCard acard in card_list)
{
if (acard != null && acard.GetCard().player_id == player_id)
nb++;
}
return nb;
}
public static BoardCard GetNearestPlayer(Vector3 pos, int skip_player_id, BoardCard skip, float range = 2f)
{
BoardCard nearest = null;
float min_dist = range;
foreach (BoardCard card in card_list)
{
float dist = (card.transform.position - pos).magnitude;
if (dist < min_dist && card != skip && skip_player_id != card.GetCard().player_id)
{
min_dist = dist;
nearest = card;
}
}
return nearest;
}
public static BoardCard GetNearest(Vector3 pos, BoardCard skip, float range = 2f)
{
BoardCard nearest = null;
float min_dist = range;
foreach (BoardCard card in card_list)
{
float dist = (card.transform.position - pos).magnitude;
if (dist < min_dist && card != skip)
{
min_dist = dist;
nearest = card;
}
}
return nearest;
}
public static BoardCard GetFocus()
{
if (GameUI.IsOverUI())
return null;
foreach (BoardCard card in card_list)
{
if (card.IsFocus() || card.IsEquipFocus())
return card;
}
return null;
}
public static void UnfocusAll()
{
foreach (BoardCard card in card_list)
{
card.focus = false;
card.status_alpha_target = 0f;
}
}
public static BoardCard Get(string uid)
{
foreach (BoardCard card in card_list)
{
if (card.card_uid == uid)
return card;
}
return null;
}
public static List<BoardCard> GetAll()
{
return card_list;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0667b5470f3948f498f8dbd5e0af2433
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,105 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using TcgEngine.UI;
namespace TcgEngine.Client
{
public class BoardCardEquip : MonoBehaviour
{
public Image equip_sprite;
public Image equip_glow;
public Text equip_hp;
public Color glow_ally;
public Color glow_enemy;
private Canvas canvas;
private RectTransform rect;
private Card equip;
private bool focus;
private float target_alpha = 0f;
void Awake()
{
canvas = GetComponentInParent<Canvas>();
rect = GetComponent<RectTransform>();
}
private void Update()
{
if (equip != null)
{
target_alpha = focus ? 1f : 0f;
focus = GameUI.IsOverRectTransform(canvas, rect);
}
else
{
target_alpha = 0f;
focus = false;
}
if (equip_glow != null)
{
int player_id = GameClient.Get().GetPlayerID();
Color ccolor = player_id == equip.player_id ? glow_ally : glow_enemy;
float calpha = Mathf.MoveTowards(equip_glow.color.a, target_alpha * ccolor.a, 4f * Time.deltaTime);
equip_glow.color = new Color(ccolor.r, ccolor.g, ccolor.b, calpha);
}
}
public void SetEquip(Card equip)
{
if (equip != null)
{
this.equip = equip;
equip_sprite.sprite = equip.CardData.GetBoardArt(equip.VariantData);
equip_hp.text = equip.GetHP().ToString();
if (!gameObject.activeSelf)
gameObject.SetActive(true);
}
else
{
Hide();
}
}
public void Hide()
{
this.equip = null;
focus = false;
if (gameObject.activeSelf)
gameObject.SetActive(false);
}
public bool IsFocus()
{
return equip != null && focus;
}
public Card GetCard()
{
return equip;
}
public void OnPointerEnter(PointerEventData eventData)
{
focus = true;
}
public void OnPointerExit(PointerEventData eventData)
{
focus = false;
}
void OnDisable()
{
focus = false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 490b587793e30f64290ebc73ec4dc3bd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,101 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TcgEngine.UI;
namespace TcgEngine.Client
{
/// <summary>
/// Represents the visual deck on the board
/// Will show number of cards in deck/discard when hovering
/// </summary>
public class BoardDeck : MonoBehaviour
{
public bool opponent;
public UIPanel hover_panel;
public SpriteRenderer deck_render;
public Text deck_value;
public Text discard_value;
private bool hover = false;
void Start()
{
if (GameTool.IsMobile())
{
hover_panel?.SetVisible(true);
}
}
void Update()
{
Refresh();
}
private void Refresh()
{
if (!GameClient.Get().IsReady())
return;
Player player = opponent ? GameClient.Get().GetOpponentPlayer() : GameClient.Get().GetPlayer();
if (player == null)
return;
CardbackData cb = CardbackData.Get(player.cardback);
if (deck_render != null && cb != null)
deck_render.sprite = cb.deck;
if (deck_value != null)
deck_value.text = player.cards_deck.Count.ToString();
if (discard_value != null)
discard_value.text = player.cards_discard.Count.ToString();
}
public void ShowDeckCards()
{
Player player = GameClient.Get().GetPlayer();
CardSelector.Get().Show(player.cards_deck, "DECK");
}
public void ShowDiscardCards()
{
Player player = opponent ? GameClient.Get().GetOpponentPlayer() : GameClient.Get().GetPlayer();
CardSelector.Get().Show(player.cards_discard, "DISCARD");
}
private void ShowHover(bool hover)
{
if(!GameTool.IsMobile())
hover_panel?.SetVisible(hover);
}
private void OnMouseEnter()
{
hover = true;
ShowHover(hover);
Refresh();
}
private void OnMouseExit()
{
hover = false;
ShowHover(hover);
}
private void OnDisable()
{
hover = false;
ShowHover(hover);
}
private void OnMouseOver()
{
if (!opponent && Input.GetMouseButtonDown(0))
ShowDeckCards(); //Cannot see opponent deck
else if(Input.GetMouseButtonDown(1))
ShowDiscardCards(); //Cant see both player discard
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 30b49cb9ea26dff4fb1c815bacdf2cbf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,57 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace TcgEngine.Client
{
public enum BoardRefType
{
None = 0,
PackCard = 4,
}
/// <summary>
/// A reference to a position on the board,
/// It is currently being used by the PackOpen board
/// </summary>
public class BoardRef : MonoBehaviour
{
public BoardRefType type;
public int index;
public bool opponent;
private static List<BoardRef> ref_list = new List<BoardRef>();
void Awake()
{
ref_list.Add(this);
}
void OnDestroy()
{
ref_list.Remove(this);
}
public static BoardRef Get(BoardRefType type, bool opponent)
{
foreach (BoardRef bref in ref_list)
{
if (bref.type == type && bref.opponent == opponent)
return bref;
}
return null;
}
public static BoardRef Get(BoardRefType type, int index)
{
foreach (BoardRef bref in ref_list)
{
if (bref.type == type && bref.index == index)
return bref;
}
return null;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a77230af9aa79be4799f4927e744d232
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,142 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TcgEngine.Client;
using TcgEngine.UI;
namespace TcgEngine.Client
{
/// <summary>
/// Visual representation of a Slot.cs
/// Will highlight when can be interacted with
/// </summary>
public class BoardSlot : BSlot
{
public BoardSlotType type;
public int x;
public int y;
private static List<BoardSlot> slot_list = new List<BoardSlot>();
protected override void Awake()
{
base.Awake();
slot_list.Add(this);
}
protected override void OnDestroy()
{
base.OnDestroy();
slot_list.Remove(this);
}
private void Start()
{
if (x < Slot.x_min || x > Slot.x_max || y < Slot.y_min || y > Slot.y_max)
Debug.LogError("Board Slot X and Y value must be within the min and max set for those values, check Slot.cs script to change those min/max.");
}
protected override void Update()
{
base.Update();
if (!GameClient.Get().IsReady())
return;
BoardCard bcard_selected = PlayerControls.Get().GetSelected();
HandCard drag_card = HandCard.GetDrag();
Game gdata = GameClient.Get().GetGameData();
Player player = GameClient.Get().GetPlayer();
Slot slot = GetSlot();
Card dcard = drag_card?.GetCard();
Card slot_card = gdata.GetSlotCard(GetSlot());
bool your_turn = GameClient.Get().IsYourTurn();
collide.enabled = slot_card == null; //Disable collider when a card is here
//Find target opacity value
target_alpha = 0f;
if (your_turn && dcard != null && dcard.CardData.IsBoardCard() && gdata.CanPlayCard(dcard, slot))
{
target_alpha = 1f; //hightlight when dragging a character or artifact
}
if (your_turn && dcard != null && dcard.CardData.IsRequireTarget() && gdata.CanPlayCard(dcard, slot))
{
target_alpha = 1f; //Highlight when dragin a spell with target
}
if (gdata.selector == SelectorType.SelectTarget && player.player_id == gdata.selector_player_id)
{
Card caster = gdata.GetCard(gdata.selector_caster_uid);
AbilityData ability = AbilityData.Get(gdata.selector_ability_id);
if(ability != null && slot_card == null && ability.CanTarget(gdata, caster, slot))
target_alpha = 1f; //Highlight when selecting a target and slot are valid
if (ability != null && slot_card != null && ability.CanTarget(gdata, caster, slot_card))
target_alpha = 1f; //Highlight when selecting a target and cards are valid
}
Card select_card = bcard_selected?.GetCard();
bool can_do_move = your_turn && select_card != null && slot_card == null && gdata.CanMoveCard(select_card, slot);
bool can_do_attack = your_turn && select_card != null && slot_card != null && gdata.CanAttackTarget(select_card, slot_card);
if (can_do_attack || can_do_move)
{
target_alpha = 1f;
}
}
//Find the actual slot coordinates of this board slot
public override Slot GetSlot()
{
int p = 0;
if (type == BoardSlotType.FlipX)
{
int pid = GameClient.Get().GetPlayerID();
int px = x;
if ((pid % 2) == 1)
px = Slot.x_max - x + Slot.x_min; //Flip X coordinate if not the first player
return new Slot(px, y, p);
}
if (type == BoardSlotType.FlipY)
{
int pid = GameClient.Get().GetPlayerID();
int py = y;
if ((pid % 2) == 1)
py = Slot.y_max - y + Slot.y_min; //Flip Y coordinate if not the first player
return new Slot(x, py, p);
}
if (type == BoardSlotType.PlayerSelf)
p = GameClient.Get().GetPlayerID();
if(type == BoardSlotType.PlayerOpponent)
p = GameClient.Get().GetOpponentPlayerID();
return new Slot(x, y, p);
}
//When clicking on the slot
public void OnMouseDown()
{
if (GameUI.IsOverUI())
return;
Game gdata = GameClient.Get().GetGameData();
int player_id = GameClient.Get().GetPlayerID();
if (gdata.selector == SelectorType.SelectTarget && player_id == gdata.selector_player_id)
{
Slot slot = GetSlot();
Card slot_card = gdata.GetSlotCard(slot);
if (slot_card == null)
{
GameClient.Get().SelectSlot(slot);
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d054438193103c8408c5eedfc485482d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,278 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TcgEngine.Client;
using TcgEngine.UI;
namespace TcgEngine.Client
{
/// <summary>
/// Visual representation of a Slot.cs that position cards automatically
/// </summary>
public class BoardSlotGroup : BSlot
{
public BoardSlotType type;
public int min_x = 1;
public int max_x = 5;
public int y = 1;
public float spacing = 2.5f;
public float reduce_delay = 1f;
private int nb_occupied = 0;
private List<GroupSlot> group_slots = new List<GroupSlot>();
protected override void Awake()
{
base.Awake();
}
protected override void OnDestroy()
{
base.OnDestroy();
}
private void Start()
{
if (min_x < Slot.x_min || max_x > Slot.x_max || y < Slot.y_min || y > Slot.y_max)
Debug.LogError("Board Slot X and Y value must be within the min and max set for those values, check Slot.cs script to change those min/max.");
GameClient.Get().onConnectGame += OnConnect;
nb_occupied = 0;
collide.enabled = false;
}
private void OnConnect()
{
foreach (Slot slot in Slot.GetAll())
{
if (IsInGroup(slot))
{
GroupSlot pos = new GroupSlot();
pos.slot = slot;
pos.pos = transform.position;
group_slots.Add(pos);
}
}
}
protected override void Update()
{
base.Update();
if (!GameClient.Get().IsReady())
return;
Game gdata = GameClient.Get().GetGameData();
HandCard drag_card = HandCard.GetDrag();
bool your_turn = GameClient.Get().IsYourTurn();
Card dcard = drag_card?.GetCard();
//Find target opacity value
target_alpha = 0f;
if (your_turn && dcard != null && dcard.CardData.IsBoardCard())
{
foreach (GroupSlot slot in group_slots)
{
if(gdata.CanPlayCard(dcard, slot.slot))
target_alpha = 1f; //hightlight when dragging a character or artifact
}
}
UpdateOccupied();
UpdatePositions();
}
public void UpdateOccupied()
{
int count = 0;
Game gdata = GameClient.Get().GetGameData();
foreach (GroupSlot slot in group_slots)
{
Card card = gdata.GetSlotCard(slot.slot);
slot.timer += (card != null ? 1f : -1f) * Time.deltaTime / reduce_delay;
slot.timer = Mathf.Clamp01(slot.timer);
if (slot.IsOccupied)
count += 1;
}
nb_occupied = count;
}
public void UpdatePositions()
{
bool even = nb_occupied % 2 == 0;
float offset = (nb_occupied / 2) * -spacing;
if (even)
offset += spacing * 0.5f;
int index = 0;
foreach (GroupSlot slot in group_slots)
{
if (slot.IsOccupied)
{
slot.pos = transform.position + Vector3.right * (index * spacing + offset);
index++;
}
else
{
slot.pos = transform.position + Vector3.right * (nb_occupied * spacing + offset);
}
}
}
public bool IsInGroup(Slot slot)
{
return IsInGroup(slot.x, slot.y, slot.p);
}
public bool IsInGroup(int x, int y)
{
Slot min = GetSlotMin();
Slot max = GetSlotMax();
return x >= min.x && x <= max.x && y >= min.y && y <= max.y;
}
public bool IsInGroup(int x, int y, int p)
{
Slot min = GetSlotMin();
Slot max = GetSlotMax();
return x >= min.x && x <= max.x && y >= min.y && y <= max.y && p >= min.p && p <= max.p;
}
public Slot GetSlotMin()
{
return GetSlot(min_x, y);
}
public Slot GetSlotMax()
{
return GetSlot(max_x, y);
}
//Find the actual slot coordinates of this board slot
public Slot GetSlot(int x, int y)
{
int p = 0;
if (type == BoardSlotType.FlipX)
{
int pid = GameClient.Get().GetPlayerID();
int px = x;
if ((pid % 2) == 1)
px = Slot.x_max - x + Slot.x_min; //Flip X coordinate if not the first player
return new Slot(px, y, p);
}
if (type == BoardSlotType.FlipY)
{
int pid = GameClient.Get().GetPlayerID();
int py = y;
if ((pid % 2) == 1)
py = Slot.y_max - y + Slot.y_min; //Flip Y coordinate if not the first player
return new Slot(x, py, p);
}
if (type == BoardSlotType.PlayerSelf)
p = GameClient.Get().GetPlayerID();
if(type == BoardSlotType.PlayerOpponent)
p = GameClient.Get().GetOpponentPlayerID();
return new Slot(x, y, p);
}
public override Slot GetSlot(Vector3 wpos)
{
GroupSlot nearest = null;
float min_dist = 99f;
foreach (GroupSlot spos in group_slots)
{
float dist = (spos.pos - wpos).magnitude;
if (dist < min_dist)
{
min_dist = dist;
nearest = spos;
}
}
if (nearest != null)
return nearest.slot;
return Slot.None;
}
public virtual Slot GetSlotOccupied(Vector3 wpos)
{
GroupSlot nearest = null;
float min_dist = 99f;
foreach (GroupSlot spos in group_slots)
{
float dist = (spos.pos - wpos).magnitude;
if (spos.IsOccupied && dist < min_dist)
{
min_dist = dist;
nearest = spos;
}
}
if (nearest != null)
return nearest.slot;
return Slot.None;
}
public override Card GetSlotCard(Vector3 wpos)
{
Game gdata = GameClient.Get().GetGameData();
Slot slot = GetSlotOccupied(wpos);
if (slot != Slot.None)
return gdata.GetSlotCard(slot);
return null;
}
public override bool HasSlot(Slot slot)
{
foreach (GroupSlot spos in group_slots)
{
if (spos.slot == slot)
return true;
}
return false;
}
public override Vector3 GetPosition(Slot slot)
{
foreach (GroupSlot spos in group_slots)
{
if (spos.slot == slot)
return spos.pos;
}
return transform.position;
}
public override Slot GetEmptySlot(Vector3 wpos)
{
foreach (GroupSlot slot in group_slots)
{
if (!slot.IsOccupied)
return slot.slot;
}
return Slot.None;
}
}
public class GroupSlot
{
public Slot slot;
public Vector3 pos;
public float timer;
public bool IsOccupied { get { return timer > 0.01f; } }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 81e500df2cf1ead4b997a8f5b582063d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,198 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TcgEngine.UI;
using TcgEngine.FX;
namespace TcgEngine.Client
{
/// <summary>
/// Visual zone that can be attacked by opponent's card to damage the player HP
/// </summary>
public class BoardSlotPlayer : BSlot
{
public bool opponent;
public float range_x = 3f;
public float range_y = 1f;
private static BoardSlotPlayer instance_self;
private static BoardSlotPlayer instance_other;
private static List<BoardSlotPlayer> zone_list = new List<BoardSlotPlayer>();
protected override void Awake()
{
base.Awake();
zone_list.Add(this);
if (opponent)
instance_other = this;
else
instance_self = this;
}
protected override void OnDestroy()
{
base.OnDestroy();
zone_list.Remove(this);
}
private void Start()
{
GameClient.Get().onPlayerDamaged += OnPlayerDamaged;
GameClient.Get().onAbilityStart += OnAbilityStart;
GameClient.Get().onAbilityTargetPlayer += OnAbilityEffect;
}
protected override void Update()
{
base.Update();
if (!GameClient.Get().IsReady())
return;
if (!opponent)
return;
//int player_id = opponent ? GameClient.Get().GetOpponentPlayerID() : GameClient.Get().GetPlayerID();
BoardCard bcard_selected = PlayerControls.Get().GetSelected();
HandCard drag_card = HandCard.GetDrag();
bool your_turn = GameClient.Get().IsYourTurn();
Game gdata = GameClient.Get().GetGameData();
Player player = GameClient.Get().GetPlayer();
Player oplayer = GameClient.Get().GetOpponentPlayer();
target_alpha = 0f;
Card select_card = bcard_selected?.GetCard();
if (select_card != null)
{
bool can_do_attack = gdata.IsPlayerActionTurn(player) && select_card.CanAttack();
bool can_be_attacked = gdata.CanAttackTarget(select_card, oplayer);
if (can_do_attack && can_be_attacked)
{
target_alpha = 1f;
}
}
if (your_turn && drag_card != null && drag_card.CardData.IsRequireTargetSpell() && gdata.IsPlayTargetValid(drag_card.GetCard(), GetPlayer()))
{
target_alpha = 1f; //Highlight when dragin a spell with target
}
if (gdata.selector == SelectorType.SelectTarget && player.player_id == gdata.selector_player_id)
{
Card caster = gdata.GetCard(gdata.selector_caster_uid);
AbilityData ability = AbilityData.Get(gdata.selector_ability_id);
if (ability != null && ability.AreTargetConditionsMet(gdata, caster, GetPlayer()))
target_alpha = 1f; //Highlight when selecting a target and empty slots are valid
}
}
private void OnAbilityStart(AbilityData iability, Card caster)
{
if (iability != null && caster != null)
{
int player_id = opponent ? GameClient.Get().GetOpponentPlayerID() : GameClient.Get().GetPlayerID();
if (caster.CardData.type == CardType.Spell && caster.player_id == player_id)
{
FXTool.DoFX(iability.caster_fx, transform.position);
AudioTool.Get().PlaySFX("fx", iability.cast_audio);
}
}
}
private void OnAbilityEffect(AbilityData iability, Card caster, Player target)
{
if (iability != null && caster != null && target != null)
{
int player_id = opponent ? GameClient.Get().GetOpponentPlayerID() : GameClient.Get().GetPlayerID();
if (target.player_id == player_id)
{
FXTool.DoFX(iability.target_fx, transform.position);
FXTool.DoProjectileFX(iability.projectile_fx, GetFXSource(caster), transform, iability.GetDamage());
AudioTool.Get().PlaySFX("fx", iability.target_audio);
}
}
}
private void OnPlayerDamaged(Player target, int damage)
{
if (GetPlayerID() == target.player_id && damage > 0)
{
DamageFX(transform, damage);
}
}
private void DamageFX(Transform target, int value, float delay = 0.5f)
{
TimeTool.WaitFor(delay, () =>
{
GameObject fx = FXTool.DoFX(AssetData.Get().damage_fx, target.position);
fx.GetComponent<DamageFX>().SetValue(value);
});
}
private Transform GetFXSource(Card caster)
{
if (caster.CardData.IsBoardCard())
{
BoardCard bcard = BoardCard.Get(caster.uid);
if (bcard != null)
return bcard.transform;
}
else
{
BoardSlotPlayer slot = BoardSlotPlayer.Get(caster.player_id);
if (slot != null)
return slot.transform;
}
return null;
}
public void OnMouseDown()
{
if (GameUI.IsUIOpened() || GameUI.IsOverUILayer("UI"))
return;
Game gdata = GameClient.Get().GetGameData();
int player_id = GameClient.Get().GetPlayerID();
if (gdata.selector == SelectorType.SelectTarget && player_id == gdata.selector_player_id)
{
GameClient.Get().SelectPlayer(GetPlayer());
}
}
public int GetPlayerID()
{
return opponent ? GameClient.Get().GetOpponentPlayerID() : GameClient.Get().GetPlayerID();
}
public override Player GetPlayer()
{
return opponent ? GameClient.Get().GetOpponentPlayer() : GameClient.Get().GetPlayer();
}
public override Slot GetSlot()
{
return new Slot(GetPlayerID());
}
public static BoardSlotPlayer Get(bool opponent)
{
if(opponent)
return instance_other;
return instance_self;
}
public static BoardSlotPlayer Get(int player_id)
{
bool opponent = player_id != GameClient.Get().GetPlayerID();
return Get(opponent);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4c1429011d3d53046b30acf29b7dbb24
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,165 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TcgEngine.Client;
using UnityEngine.Events;
using TcgEngine.UI;
namespace TcgEngine.Client
{
/// <summary>
/// GameBoard takes care of spawning and despawning BoardCards, based on the refreshed data received from the server
/// It also ends the game when the server sends a endgame
/// </summary>
public class GameBoard : MonoBehaviour
{
public GameObject card_prefab;
public UnityAction<Card> onCardSpawned;
public UnityAction<Card> onCardKilled;
private bool game_ended = false;
private static GameBoard _instance;
void Awake()
{
_instance = this;
}
private void Start()
{
}
void Update()
{
if (!GameClient.Get().IsReady())
return;
Game data = GameClient.Get().GetGameData();
//--- Board cards --------
List<BoardCard> cards = BoardCard.GetAll();
//Add new cards
foreach (Player p in data.players)
{
foreach (Card card in p.cards_board)
{
BoardCard bcard = BoardCard.Get(card.uid);
if (card != null && bcard == null)
{
SpawnNewCard(card);
}
}
}
//Remove cards
for (int i = cards.Count - 1; i >= 0; i--)
{
BoardCard bcard = cards[i];
if (bcard != null && HasBoardCard(bcard) &&!bcard.IsDamagedDelayed())
{
KillCard(bcard);
}
}
//--- End Game ----
if (!game_ended && data.state == GameState.GameEnded)
{
game_ended = true;
EndGame();
}
}
private void SpawnNewCard(Card card)
{
GameObject card_obj = Instantiate(card_prefab, Vector3.zero, Quaternion.identity);
card_obj.SetActive(true);
card_obj.GetComponent<BoardCard>().SetCard(card);
onCardSpawned?.Invoke(card);
}
private void KillCard(BoardCard card)
{
card.Destroy();
onCardKilled?.Invoke(card.GetCard());
}
private bool HasBoardCard(BoardCard bcard)
{
Game data = GameClient.Get().GetGameData();
Card card = data.GetBoardCard(bcard.GetCardUID());
return card == null && !bcard.IsDead();
}
public void EndGame()
{
StartCoroutine(EndGameRun());
}
private IEnumerator EndGameRun()
{
Game data = GameClient.Get().GetGameData();
Player pwinner = data.GetPlayer(data.current_player);
Player player = GameClient.Get().GetPlayer();
bool win = pwinner != null && player.player_id == pwinner.player_id;
bool tied = pwinner == null;
AudioTool.Get().FadeOutMusic("music");
yield return new WaitForSeconds(1f);
if (win)
PlayerUI.Get(true).Kill();
if (!win && !tied)
PlayerUI.Get(false).Kill();
if (win && AssetData.Get().win_fx != null)
Instantiate(AssetData.Get().win_fx, Vector3.zero, Quaternion.identity);
else if (tied && AssetData.Get().tied_fx != null)
Instantiate(AssetData.Get().tied_fx, Vector3.zero, Quaternion.identity);
else if (tied && AssetData.Get().lose_fx != null)
Instantiate(AssetData.Get().lose_fx, Vector3.zero, Quaternion.identity);
if (win)
AudioTool.Get().PlaySFX("ending_sfx", AssetData.Get().win_audio);
else
AudioTool.Get().PlaySFX("ending_sfx", AssetData.Get().defeat_audio);
if (win)
AudioTool.Get().PlayMusic("music", AssetData.Get().win_music, 0.4f, false);
else
AudioTool.Get().PlayMusic("music", AssetData.Get().defeat_music, 0.4f, false);
yield return new WaitForSeconds(2f);
EndGamePanel.Get().ShowEnd(data.current_player);
}
//Raycast mouse position to board position
public Vector3 RaycastMouseBoard()
{
Ray ray = GameCamera.Get().MouseToRay(Input.mousePosition);
Plane plane = new Plane(transform.forward, 0f);
bool success = plane.Raycast(ray, out float dist);
if (success)
return ray.GetPoint(dist);
return Vector3.zero;
}
public Vector3 GetAngles()
{
return transform.rotation.eulerAngles;
}
public static GameBoard Get()
{
return _instance;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0af8ce7f0f0acf4408922e1c9fe6d764
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,81 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace TcgEngine.Client
{
/// <summary>
/// Game Camera has some useful features such as shake
/// Or raycasting the mouse to a world/screen position
/// </summary>
public class GameCamera : MonoBehaviour
{
private float shake_timer = 0f;
private float shake_intensity = 1f;
private Camera cam;
private Vector3 shake_vector = Vector3.zero;
private Vector3 start_pos;
private static GameCamera instance;
void Awake()
{
instance = this;
start_pos = transform.position;
cam = GetComponent<Camera>();
}
void Update()
{
//Shake FX
if (shake_timer > 0f)
{
shake_timer -= Time.deltaTime;
shake_vector = new Vector3(Mathf.Cos(shake_timer * Mathf.PI * 16f) * 0.02f, Mathf.Sin(shake_timer * Mathf.PI * 12f) * 0.01f, 0f);
transform.position = start_pos + shake_vector * shake_intensity;
}
else
{
transform.position = start_pos;
}
}
public void Shake(float intensity = 1f, float duration = 1f)
{
shake_intensity = intensity;
shake_timer = duration;
}
public Vector2 MouseToPercent(Vector3 mouse_pos)
{
float x = mouse_pos.x / Screen.width;
float y = mouse_pos.y / Screen.height;
return new Vector2(x, y);
}
public Ray MouseToRay(Vector3 mouse_pos)
{
return cam.ScreenPointToRay(mouse_pos);
}
public Vector3 MouseToWorld(Vector2 mouse_pos, float distance = 10f)
{
Vector3 wpos = cam.ScreenToWorldPoint(new Vector3(mouse_pos.x, mouse_pos.y, distance));
return wpos;
}
public static Camera GetCamera()
{
if (instance != null)
return instance.cam;
return null;
}
public static GameCamera Get()
{
return instance;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3a9b8c2d7c7896346a760f2d221a1d59
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,747 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using Unity.Netcode;
using System.Threading.Tasks;
namespace TcgEngine.Client
{
/// <summary>
/// Main script for the client-side of the game, should be in game scene only
/// Will connect to server, then connect to the game on that server (with uid) and then will send game settings
/// During the game, will send all actions performed by the player and receive game refreshes
/// </summary>
public class GameClient : MonoBehaviour
{
//--- These settings are set in the menu scene and when the game start will be sent to server
public static GameSettings game_settings = GameSettings.Default;
public static PlayerSettings player_settings = PlayerSettings.Default;
public static PlayerSettings ai_settings = PlayerSettings.DefaultAI;
public static string observe_user = null; //Which user should it observe, null if not an obs
//-----
public UnityAction onConnectServer;
public UnityAction onConnectGame;
public UnityAction<int> onPlayerReady;
public UnityAction onGameStart;
public UnityAction<int> onGameEnd; //winner player_id
public UnityAction<int> onNewTurn; //current player_id
public UnityAction<Card, Slot> onCardPlayed;
public UnityAction<Card, Slot> onCardMoved;
public UnityAction<Slot> onCardSummoned;
public UnityAction<Card> onCardTransformed;
public UnityAction<Card> onCardDiscarded;
public UnityAction<int> onCardDraw;
public UnityAction<int> onValueRolled;
public UnityAction<AbilityData, Card> onAbilityStart;
public UnityAction<AbilityData, Card, Card> onAbilityTargetCard; //Ability, Caster, Target
public UnityAction<AbilityData, Card, Player> onAbilityTargetPlayer;
public UnityAction<AbilityData, Card, Slot> onAbilityTargetSlot;
public UnityAction<AbilityData, Card> onAbilityEnd;
public UnityAction<Card, Card> onSecretTrigger; //Secret, Triggerer
public UnityAction<Card, Card> onSecretResolve; //Secret, Triggerer
public UnityAction<Card, Card> onAttackStart; //Attacker, Defender
public UnityAction<Card, Card> onAttackEnd; //Attacker, Defender
public UnityAction<Card, Player> onAttackPlayerStart;
public UnityAction<Card, Player> onAttackPlayerEnd;
public UnityAction<Card, int> onCardDamaged;
public UnityAction<Player, int> onPlayerDamaged;
public UnityAction<Card, int> onCardHealed;
public UnityAction<Player, int> onPlayerHealed;
public UnityAction<int, string> onChatMsg; //player_id, msg
public UnityAction< string> onServerMsg; //msg
public UnityAction onRefreshAll;
private int player_id = 0; //Player playing on this device;
private Game game_data;
private bool observe_mode = false;
private int observe_player_id = 0;
private float timer = 0f;
private Dictionary<ushort, RefreshEvent> registered_commands = new Dictionary<ushort, RefreshEvent>();
private static GameClient instance;
protected virtual void Awake()
{
instance = this;
Application.targetFrameRate = 120;
}
protected virtual void Start()
{
RegisterRefresh(GameAction.Connected, OnConnectedToGame);
RegisterRefresh(GameAction.PlayerReady, OnPlayerReady);
RegisterRefresh(GameAction.GameStart, OnGameStart);
RegisterRefresh(GameAction.GameEnd, OnGameEnd);
RegisterRefresh(GameAction.NewTurn, OnNewTurn);
RegisterRefresh(GameAction.CardPlayed, OnCardPlayed);
RegisterRefresh(GameAction.CardMoved, OnCardMoved);
RegisterRefresh(GameAction.CardSummoned, OnCardSummoned);
RegisterRefresh(GameAction.CardTransformed, OnCardTransformed);
RegisterRefresh(GameAction.CardDiscarded, OnCardDiscarded);
RegisterRefresh(GameAction.CardDrawn, OnCardDraw);
RegisterRefresh(GameAction.ValueRolled, OnValueRolled);
RegisterRefresh(GameAction.AttackStart, OnAttackStart);
RegisterRefresh(GameAction.AttackEnd, OnAttackEnd);
RegisterRefresh(GameAction.AttackPlayerStart, OnAttackPlayerStart);
RegisterRefresh(GameAction.AttackPlayerEnd, OnAttackPlayerEnd);
RegisterRefresh(GameAction.CardDamaged, OnCardDamaged);
RegisterRefresh(GameAction.PlayerDamaged, OnPlayerDamaged);
RegisterRefresh(GameAction.CardHealed, OnCardHealed);
RegisterRefresh(GameAction.PlayerHealed, OnPlayerHealed);
RegisterRefresh(GameAction.AbilityTrigger, OnAbilityTrigger);
RegisterRefresh(GameAction.AbilityTargetCard, OnAbilityTargetCard);
RegisterRefresh(GameAction.AbilityTargetPlayer, OnAbilityTargetPlayer);
RegisterRefresh(GameAction.AbilityTargetSlot, OnAbilityTargetSlot);
RegisterRefresh(GameAction.AbilityEnd, OnAbilityAfter);
RegisterRefresh(GameAction.SecretTriggered, OnSecretTrigger);
RegisterRefresh(GameAction.SecretResolved, OnSecretResolve);
RegisterRefresh(GameAction.ChatMessage, OnChat);
RegisterRefresh(GameAction.ServerMessage, OnServerMsg);
RegisterRefresh(GameAction.RefreshAll, OnRefreshAll);
TcgNetwork.Get().onConnect += OnConnectedServer;
TcgNetwork.Get().Messaging.ListenMsg("refresh", OnReceiveRefresh);
ConnectToAPI();
ConnectToServer();
}
protected virtual void OnDestroy()
{
TcgNetwork.Get().onConnect -= OnConnectedServer;
TcgNetwork.Get().Messaging.UnListenMsg("refresh");
}
protected virtual void Update()
{
bool is_starting = game_data == null || game_data.state == GameState.Connecting;
bool is_client = !game_settings.IsHost();
bool is_connecting = TcgNetwork.Get().IsConnecting();
bool is_connected = TcgNetwork.Get().IsConnected();
//Exit game scene if cannot connect after a while
if (is_starting && is_client)
{
timer += Time.deltaTime;
if (timer > 10f)
{
SceneNav.GoTo("Menu");
}
}
//Reconnect to server
if (!is_starting && !is_connecting && is_client && !is_connected)
{
timer += Time.deltaTime;
if (timer > 5f)
{
timer = 0f;
ConnectToServer();
}
}
}
//--------------------
public virtual void ConnectToAPI()
{
//Should already be logged in from the menu
//If not connected, start in test mode (this means game scene was launched directly from Unity)
if (!Authenticator.Get().IsSignedIn())
{
Authenticator.Get().LoginTest("Player");
if (!player_settings.HasDeck())
{
player_settings.deck = new UserDeckData(GameplayData.Get().test_deck);
}
if (!ai_settings.HasDeck())
{
ai_settings.deck = new UserDeckData(GameplayData.Get().test_deck_ai);
ai_settings.ai_level = GameplayData.Get().ai_level;
}
}
//Set avatar, cardback based on your api data
UserData udata = Authenticator.Get().UserData;
if (udata != null)
{
player_settings.avatar = udata.GetAvatar();
player_settings.cardback = udata.GetCardback();
}
}
public virtual async void ConnectToServer()
{
await TimeTool.Delay(100); //Wait for initialization to finish
if (TcgNetwork.Get().IsActive())
return; // Already connected
if (game_settings.IsHost() && NetworkData.Get().solo_type == SoloType.Offline)
{
TcgNetwork.Get().StartHostOffline(); //WebGL dont support hosting a game, must join a dedicated server, in solo it starts a offline mode that doesn't use netcode at all
}
else if (game_settings.IsHost())
{
TcgNetwork.Get().StartHost(NetworkData.Get().port); //Host a game, either solo or for P2P, still using netcode in solo to have consistant behavior when testing solo vs multi
}
else
{
TcgNetwork.Get().StartClient(game_settings.GetUrl(), NetworkData.Get().port); //Join server
}
}
public virtual async void ConnectToGame(string uid)
{
await TimeTool.Delay(100); //Wait for initialization to finish
if (!TcgNetwork.Get().IsActive())
return; //Not connected to server
Debug.Log("Connect to Game: " + uid);
MsgPlayerConnect nplayer = new MsgPlayerConnect();
nplayer.user_id = Authenticator.Get().UserID;
nplayer.username = Authenticator.Get().Username;
nplayer.game_uid = uid;
nplayer.nb_players = game_settings.nb_players;
nplayer.observer = game_settings.game_type == GameType.Observer;
Messaging.SendObject("connect", ServerID, nplayer, NetworkDelivery.Reliable);
}
public virtual void SendGameSettings()
{
if (game_settings.IsOffline())
{
//Solo mode, send both your settings and AI settings
SendGameplaySettings(game_settings);
SendPlayerSettingsAI(ai_settings);
SendPlayerSettings(player_settings);
}
else
{
//Online mode, only send your own settings
SendGameplaySettings(game_settings);
SendPlayerSettings(player_settings);
}
}
public virtual void Disconnect()
{
TcgNetwork.Get().Disconnect();
}
private void RegisterRefresh(ushort tag, UnityAction<SerializedData> callback)
{
RefreshEvent cmdevt = new RefreshEvent();
cmdevt.tag = tag;
cmdevt.callback = callback;
registered_commands.Add(tag, cmdevt);
}
public void OnReceiveRefresh(ulong client_id, FastBufferReader reader)
{
reader.ReadValueSafe(out ushort type);
bool found = registered_commands.TryGetValue(type, out RefreshEvent command);
if (found)
{
command.callback.Invoke(new SerializedData(reader));
}
}
//--------------------------
public void SendPlayerSettings(PlayerSettings psettings)
{
SendAction(GameAction.PlayerSettings, psettings, NetworkDelivery.ReliableFragmentedSequenced);
}
public void SendPlayerSettingsAI(PlayerSettings psettings)
{
SendAction(GameAction.PlayerSettingsAI, psettings, NetworkDelivery.ReliableFragmentedSequenced);
}
public void SendGameplaySettings(GameSettings settings)
{
SendAction(GameAction.GameSettings, settings, NetworkDelivery.ReliableFragmentedSequenced);
}
public void PlayCard(Card card, Slot slot)
{
MsgPlayCard mdata = new MsgPlayCard();
mdata.card_uid = card.uid;
mdata.slot = slot;
SendAction(GameAction.PlayCard, mdata);
}
public void AttackTarget(Card card, Card target)
{
MsgAttack mdata = new MsgAttack();
mdata.attacker_uid = card.uid;
mdata.target_uid = target.uid;
SendAction(GameAction.Attack, mdata);
}
public void AttackPlayer(Card card, Player target)
{
MsgAttackPlayer mdata = new MsgAttackPlayer();
mdata.attacker_uid = card.uid;
mdata.target_id = target.player_id;
SendAction(GameAction.AttackPlayer, mdata);
}
public void Move(Card card, Slot slot)
{
MsgPlayCard mdata = new MsgPlayCard();
mdata.card_uid = card.uid;
mdata.slot = slot;
SendAction(GameAction.Move, mdata);
}
public void CastAbility(Card card, AbilityData ability)
{
MsgCastAbility mdata = new MsgCastAbility();
mdata.caster_uid = card.uid;
mdata.ability_id = ability.id;
mdata.target_uid = "";
SendAction(GameAction.CastAbility, mdata);
}
public void SelectCard(Card card)
{
MsgCard mdata = new MsgCard();
mdata.card_uid = card.uid;
SendAction(GameAction.SelectCard, mdata);
}
public void SelectPlayer(Player player)
{
MsgPlayer mdata = new MsgPlayer();
mdata.player_id = player.player_id;
SendAction(GameAction.SelectPlayer, mdata);
}
public void SelectSlot(Slot slot)
{
SendAction(GameAction.SelectSlot, slot);
}
public void SelectChoice(int c)
{
MsgInt choice = new MsgInt();
choice.value = c;
SendAction(GameAction.SelectChoice, choice);
}
public void SelectCost(int c)
{
MsgInt choice = new MsgInt();
choice.value = c;
SendAction(GameAction.SelectCost, choice);
}
public void Mulligan(string[] cards)
{
MsgMulligan mdata = new MsgMulligan();
mdata.cards = cards;
SendAction(GameAction.SelectMulligan, mdata);
}
public void CancelSelection()
{
SendAction(GameAction.CancelSelect);
}
public void SendChatMsg(string msg)
{
MsgChat chat = new MsgChat();
chat.msg = msg;
chat.player_id = player_id;
SendAction(GameAction.ChatMessage, chat);
}
public void EndTurn()
{
SendAction(GameAction.EndTurn);
}
public void Resign()
{
SendAction(GameAction.Resign);
}
public void SetObserverMode(int player_id)
{
observe_mode = true;
observe_player_id = player_id;
}
public void SetObserverMode(string username)
{
observe_player_id = 0; //Default value of observe_user not found
Game data = GetGameData();
foreach (Player player in data.players)
{
if (player.username == username)
{
observe_player_id = player.player_id;
}
}
}
public void SendAction<T>(ushort type, T data, NetworkDelivery delivery = NetworkDelivery.Reliable) where T : INetworkSerializable
{
FastBufferWriter writer = new FastBufferWriter(128, Unity.Collections.Allocator.Temp, TcgNetwork.MsgSizeMax);
writer.WriteValueSafe(type);
writer.WriteNetworkSerializable(data);
Messaging.Send("action", ServerID, writer, delivery);
writer.Dispose();
}
public void SendAction(ushort type, int data)
{
FastBufferWriter writer = new FastBufferWriter(128, Unity.Collections.Allocator.Temp, TcgNetwork.MsgSizeMax);
writer.WriteValueSafe(type);
writer.WriteValueSafe(data);
Messaging.Send("action", ServerID, writer, NetworkDelivery.Reliable);
writer.Dispose();
}
public void SendAction(ushort type)
{
FastBufferWriter writer = new FastBufferWriter(128, Unity.Collections.Allocator.Temp, TcgNetwork.MsgSizeMax);
writer.WriteValueSafe(type);
Messaging.Send("action", ServerID, writer, NetworkDelivery.Reliable);
writer.Dispose();
}
//--- Receive Refresh ----------------------
protected virtual void OnConnectedServer()
{
ConnectToGame(game_settings.game_uid);
onConnectServer?.Invoke();
}
protected virtual void OnConnectedToGame(SerializedData sdata)
{
MsgAfterConnected msg = sdata.Get<MsgAfterConnected>();
player_id = msg.player_id;
game_data = msg.game_data;
observe_mode = player_id < 0; //Will usually return -1 if its an observer
if (observe_mode)
SetObserverMode(observe_user);
if (onConnectGame != null)
onConnectGame.Invoke();
SendGameSettings();
}
protected virtual void OnPlayerReady(SerializedData sdata)
{
MsgInt msg = sdata.Get<MsgInt>();
int pid = msg.value;
if (onPlayerReady != null)
onPlayerReady.Invoke(pid);
}
private void OnGameStart(SerializedData sdata)
{
onGameStart?.Invoke();
}
private void OnGameEnd(SerializedData sdata)
{
MsgPlayer msg = sdata.Get<MsgPlayer>();
onGameEnd?.Invoke(msg.player_id);
}
private void OnNewTurn(SerializedData sdata)
{
MsgPlayer msg = sdata.Get<MsgPlayer>();
onNewTurn?.Invoke(msg.player_id);
}
private void OnCardPlayed(SerializedData sdata)
{
MsgPlayCard msg = sdata.Get<MsgPlayCard>();
Card card = game_data.GetCard(msg.card_uid);
onCardPlayed?.Invoke(card, msg.slot);
}
private void OnCardSummoned(SerializedData sdata)
{
MsgPlayCard msg = sdata.Get<MsgPlayCard>();
onCardSummoned?.Invoke(msg.slot);
}
private void OnCardMoved(SerializedData sdata)
{
MsgPlayCard msg = sdata.Get<MsgPlayCard>();
Card card = game_data.GetCard(msg.card_uid);
onCardMoved?.Invoke(card, msg.slot);
}
private void OnCardTransformed(SerializedData sdata)
{
MsgCard msg = sdata.Get<MsgCard>();
Card card = game_data.GetCard(msg.card_uid);
onCardTransformed?.Invoke(card);
}
private void OnCardDiscarded(SerializedData sdata)
{
MsgCard msg = sdata.Get<MsgCard>();
Card card = game_data.GetCard(msg.card_uid);
onCardDiscarded?.Invoke(card);
}
private void OnCardDraw(SerializedData sdata)
{
MsgInt msg = sdata.Get<MsgInt>();
onCardDraw?.Invoke(msg.value);
}
private void OnValueRolled(SerializedData sdata)
{
MsgInt msg = sdata.Get<MsgInt>();
onValueRolled?.Invoke(msg.value);
}
private void OnAttackStart(SerializedData sdata)
{
MsgAttack msg = sdata.Get<MsgAttack>();
Card attacker = game_data.GetCard(msg.attacker_uid);
Card target = game_data.GetCard(msg.target_uid);
onAttackStart?.Invoke(attacker, target);
}
private void OnAttackEnd(SerializedData sdata)
{
MsgAttack msg = sdata.Get<MsgAttack>();
Card attacker = game_data.GetCard(msg.attacker_uid);
Card target = game_data.GetCard(msg.target_uid);
onAttackEnd?.Invoke(attacker, target);
}
private void OnAttackPlayerStart(SerializedData sdata)
{
MsgAttackPlayer msg = sdata.Get<MsgAttackPlayer>();
Card attacker = game_data.GetCard(msg.attacker_uid);
Player target = game_data.GetPlayer(msg.target_id);
onAttackPlayerStart?.Invoke(attacker, target);
}
private void OnAttackPlayerEnd(SerializedData sdata)
{
MsgAttackPlayer msg = sdata.Get<MsgAttackPlayer>();
Card attacker = game_data.GetCard(msg.attacker_uid);
Player target = game_data.GetPlayer(msg.target_id);
onAttackPlayerEnd?.Invoke(attacker, target);
}
private void OnCardDamaged(SerializedData sdata)
{
MsgCardValue msg = sdata.Get<MsgCardValue>();
Card card = game_data.GetCard(msg.card_uid);
onCardDamaged?.Invoke(card, msg.value);
}
private void OnPlayerDamaged(SerializedData sdata)
{
MsgPlayerValue msg = sdata.Get<MsgPlayerValue>();
Player player = game_data.GetPlayer(msg.player_id);
onPlayerDamaged?.Invoke(player, msg.value);
}
private void OnCardHealed(SerializedData sdata)
{
MsgCardValue msg = sdata.Get<MsgCardValue>();
Card card = game_data.GetCard(msg.card_uid);
onCardHealed?.Invoke(card, msg.value);
}
private void OnPlayerHealed(SerializedData sdata)
{
MsgPlayerValue msg = sdata.Get<MsgPlayerValue>();
Player player = game_data.GetPlayer(msg.player_id);
onPlayerHealed?.Invoke(player, msg.value);
}
private void OnAbilityTrigger(SerializedData sdata)
{
MsgCastAbility msg = sdata.Get<MsgCastAbility>();
AbilityData ability = AbilityData.Get(msg.ability_id);
Card caster = game_data.GetCard(msg.caster_uid);
onAbilityStart?.Invoke(ability, caster);
}
private void OnAbilityTargetCard(SerializedData sdata)
{
MsgCastAbility msg = sdata.Get<MsgCastAbility>();
AbilityData ability = AbilityData.Get(msg.ability_id);
Card caster = game_data.GetCard(msg.caster_uid);
Card target = game_data.GetCard(msg.target_uid);
onAbilityTargetCard?.Invoke(ability, caster, target);
}
private void OnAbilityTargetPlayer(SerializedData sdata)
{
MsgCastAbilityPlayer msg = sdata.Get<MsgCastAbilityPlayer>();
AbilityData ability = AbilityData.Get(msg.ability_id);
Card caster = game_data.GetCard(msg.caster_uid);
Player target = game_data.GetPlayer(msg.target_id);
onAbilityTargetPlayer?.Invoke(ability, caster, target);
}
private void OnAbilityTargetSlot(SerializedData sdata)
{
MsgCastAbilitySlot msg = sdata.Get<MsgCastAbilitySlot>();
AbilityData ability = AbilityData.Get(msg.ability_id);
Card caster = game_data.GetCard(msg.caster_uid);
onAbilityTargetSlot?.Invoke(ability, caster, msg.slot);
}
private void OnAbilityAfter(SerializedData sdata)
{
MsgCastAbility msg = sdata.Get<MsgCastAbility>();
AbilityData ability = AbilityData.Get(msg.ability_id);
Card caster = game_data.GetCard(msg.caster_uid);
onAbilityEnd?.Invoke(ability, caster);
}
private void OnSecretTrigger(SerializedData sdata)
{
MsgSecret msg = sdata.Get<MsgSecret>();
Card secret = game_data.GetCard(msg.secret_uid);
Card triggerer = game_data.GetCard(msg.triggerer_uid);
onSecretTrigger?.Invoke(secret, triggerer);
}
private void OnSecretResolve(SerializedData sdata)
{
MsgSecret msg = sdata.Get<MsgSecret>();
Card secret = game_data.GetCard(msg.secret_uid);
Card triggerer = game_data.GetCard(msg.triggerer_uid);
onSecretResolve?.Invoke(secret, triggerer);
}
private void OnChat(SerializedData sdata)
{
MsgChat msg = sdata.Get<MsgChat>();
onChatMsg?.Invoke(msg.player_id, msg.msg);
}
private void OnServerMsg(SerializedData sdata)
{
string msg = sdata.GetString();
onServerMsg?.Invoke(msg);
}
private void OnRefreshAll(SerializedData sdata)
{
MsgRefreshAll msg = sdata.Get<MsgRefreshAll>();
game_data = msg.game_data;
onRefreshAll?.Invoke();
}
//--------------------------
public virtual bool IsReady()
{
return game_data != null && TcgNetwork.Get().IsConnected();
}
public Player GetPlayer()
{
Game gdata = GetGameData();
return gdata.GetPlayer(GetPlayerID());
}
public Player GetOpponentPlayer()
{
Game gdata = GetGameData();
return gdata.GetPlayer(GetOpponentPlayerID());
}
public int GetPlayerID()
{
if (observe_mode)
return observe_player_id;
return player_id;
}
public int GetOpponentPlayerID()
{
return GetPlayerID() == 0 ? 1 : 0;
}
public virtual bool IsYourTurn()
{
Game game_data = GetGameData();
Player player = GetPlayer();
return IsReady() && game_data.IsPlayerTurn(player);
}
public bool IsObserveMode()
{
return observe_mode;
}
public Game GetGameData()
{
return game_data;
}
public bool HasEnded()
{
return game_data != null && game_data.HasEnded();
}
private void OnApplicationQuit()
{
Resign(); //Auto Resign before closing the app. NOTE: doesn't seem to work since the msg dont have time to be sent before it closes
}
public bool IsHost { get { return TcgNetwork.Get().IsHost; } }
public ulong ServerID { get { return TcgNetwork.Get().ServerID; } }
public NetworkMessaging Messaging { get { return TcgNetwork.Get().Messaging; } }
public static GameClient Get()
{
return instance;
}
}
public class RefreshEvent
{
public ushort tag;
public UnityAction<SerializedData> callback;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0ad56e078bd4f1041bf0e76b3aa1b462
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,265 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;
using UnityEngine.Events;
using TcgEngine.UI;
namespace TcgEngine.Client
{
/// <summary>
/// Main client script for the matchmaker
/// Will send requests to server and receive a response when a matchmaking succeed or fail
/// </summary>
public class GameClientMatchmaker : MonoBehaviour
{
public UnityAction<MatchmakingResult> onMatchmaking;
public UnityAction<MatchmakingList> onMatchmakingList;
public UnityAction<MatchList> onMatchList;
private bool matchmaking = false;
private float timer = 0f;
private float match_timer = 0f;
private string matchmaking_group;
private int matchmaking_players;
private UnityAction<bool> connect_callback;
private static GameClientMatchmaker _instance;
void Awake()
{
_instance = this;
}
private void Start()
{
TcgNetwork.Get().onConnect += OnConnect;
TcgNetwork.Get().onDisconnect += OnDisconnect;
Messaging.ListenMsg("matchmaking", ReceiveMatchmaking);
Messaging.ListenMsg("matchmaking_list", ReceiveMatchmakingList);
Messaging.ListenMsg("match_list", ReceiveMatchList);
}
private void OnDestroy()
{
Disconnect(); //Disconnect when switching scene
if (TcgNetwork.Get() != null)
{
TcgNetwork.Get().onConnect -= OnConnect;
TcgNetwork.Get().onDisconnect -= OnDisconnect;
Messaging.UnListenMsg("matchmaking");
Messaging.UnListenMsg("matchmaking_list");
Messaging.UnListenMsg("match_list");
}
}
void Update()
{
if (matchmaking)
{
timer += Time.deltaTime;
match_timer += Time.deltaTime;
//Send periodic request
if (IsConnected() && timer > 2f)
{
timer = 0f;
SendMatchRequest(true, matchmaking_group, matchmaking_players);
}
//Disconnected, stop
if (!IsConnected() && !IsConnecting() && timer > 5f)
{
StopMatchmaking();
}
}
}
public void StartMatchmaking(string group, int nb_players)
{
if (matchmaking)
StopMatchmaking();
Debug.Log("Start Matchmaking!");
matchmaking_group = group;
matchmaking_players = nb_players;
matchmaking = true;
match_timer = 0f;
timer = 0f;
Connect(NetworkData.Get().url, NetworkData.Get().port, (bool success) =>
{
if (success)
{
SendMatchRequest(false, group, nb_players);
}
else
{
StopMatchmaking();
}
});
}
public void StopMatchmaking()
{
if (matchmaking)
{
Debug.Log("Stop Matchmaking!");
onMatchmaking?.Invoke(null);
matchmaking_group = "";
matchmaking_players = 0;
matchmaking = false;
}
}
public void RefreshMatchmakingList()
{
Connect(NetworkData.Get().url, NetworkData.Get().port, (bool success) =>
{
if(success)
SendMatchmakingListRequest();
});
}
public void RefreshMatchList(string username)
{
Connect(NetworkData.Get().url, NetworkData.Get().port, (bool success) =>
{
if (success)
SendMatchListRequest(username);
});
}
public void Connect(string url, ushort port, UnityAction<bool> callback=null)
{
//Must be logged in to API to connect
if(!Authenticator.Get().IsSignedIn())
{
callback?.Invoke(false);
return;
}
//Check if already connected
if (IsConnected() || IsConnecting())
{
callback?.Invoke(IsConnected());
return;
}
connect_callback = callback;
TcgNetwork.Get().StartClient(url, port);
}
public void Disconnect()
{
TcgNetwork.Get()?.Disconnect();
}
private void OnConnect()
{
Debug.Log("Connected to server!");
connect_callback?.Invoke(true);
connect_callback = null;
}
private void OnDisconnect()
{
StopMatchmaking(); //Stop if currently running
connect_callback?.Invoke(false);
connect_callback = null;
matchmaking = false;
}
private void SendMatchRequest(bool refresh, string group, int nb_players)
{
MsgMatchmaking msg_match = new MsgMatchmaking();
UserData udata = Authenticator.Get().GetUserData();
msg_match.user_id = Authenticator.Get().GetUserId();
msg_match.username = Authenticator.Get().GetUsername();
msg_match.group = group;
msg_match.players = nb_players;
msg_match.elo = udata.elo;
msg_match.time = match_timer;
msg_match.refresh = refresh;
Messaging.SendObject("matchmaking", ServerID, msg_match, NetworkDelivery.Reliable);
}
private void SendMatchmakingListRequest()
{
MsgMatchmakingList msg_match = new MsgMatchmakingList();
msg_match.username = ""; //Return all users
Messaging.SendObject("matchmaking_list", ServerID, msg_match, NetworkDelivery.Reliable);
}
private void SendMatchListRequest(string username)
{
MsgMatchmakingList msg_match = new MsgMatchmakingList();
msg_match.username = username;
Messaging.SendObject("match_list", ServerID, msg_match, NetworkDelivery.Reliable);
}
private void ReceiveMatchmaking(ulong client_id, FastBufferReader reader)
{
reader.ReadNetworkSerializable(out MatchmakingResult msg);
if (IsConnected() && matchmaking && matchmaking_group == msg.group)
{
matchmaking = !msg.success; //Stop matchmaking if success
onMatchmaking?.Invoke(msg);
}
}
private void ReceiveMatchmakingList(ulong client_id, FastBufferReader reader)
{
reader.ReadNetworkSerializable(out MatchmakingList list);
onMatchmakingList?.Invoke(list);
}
private void ReceiveMatchList(ulong client_id, FastBufferReader reader)
{
reader.ReadNetworkSerializable(out MatchList list);
onMatchList?.Invoke(list);
}
public bool IsMatchmaking()
{
return matchmaking;
}
public string GetGroup()
{
return matchmaking_group;
}
public int GetNbPlayers()
{
return matchmaking_players;
}
public float GetTimer()
{
return match_timer;
}
public bool IsConnected()
{
return TcgNetwork.Get().IsConnected();
}
public bool IsConnecting()
{
return TcgNetwork.Get().IsConnecting();
}
public ulong ServerID { get { return TcgNetwork.Get().ServerID; } }
public NetworkMessaging Messaging { get { return TcgNetwork.Get().Messaging; } }
public static GameClientMatchmaker Get()
{
return _instance;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: edceb3bb736f47a4aab07ab20b0d947a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,298 @@
using TcgEngine.Client;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TcgEngine.UI;
namespace TcgEngine.Client
{
/// <summary>
/// Represents the visual aspect of a card in hand.
/// Will take the data from Card.cs and display it
/// </summary>
public class HandCard : MonoBehaviour
{
public Image card_glow;
public float move_speed = 10f;
public float move_rotate_speed = 4f;
public float move_max_rotate = 10f;
[HideInInspector]
public Vector2 deck_position;
[HideInInspector]
public float deck_angle;
private string card_uid = "";
private CardUI card_ui;
private RectTransform hand_transform;
private RectTransform card_transform;
private Vector3 start_scale;
private Vector3 current_rotate;
private Vector3 target_rotate;
private Vector3 prev_pos;
private bool destroyed = false;
private float focus_timer = 0f;
private bool focus = false;
private bool drag = false;
private bool selected = false;
private static List<HandCard> card_list = new List<HandCard>();
void Awake()
{
card_list.Add(this);
card_ui = GetComponent<CardUI>();
card_transform = transform.GetComponent<RectTransform>();
hand_transform = transform.parent.GetComponent<RectTransform>();
start_scale = transform.localScale;
}
private void Start()
{
}
private void OnDestroy()
{
card_list.Remove(this);
}
void Update()
{
if (!GameClient.Get().IsReady())
return;
Card card = GetCard();
Vector2 target_position = deck_position;
Vector3 target_size = start_scale;
focus_timer += Time.deltaTime;
if (IsFocus())
{
target_position = deck_position + Vector2.up * 40f;
}
if (IsDrag())
{
target_position = GetTargetPosition();
target_size = start_scale * 0.75f;
Vector3 dir = card_transform.position - prev_pos;
Vector3 addrot = new Vector3(dir.y * 90f, -dir.x * 90f, 0f);
target_rotate += addrot * move_rotate_speed * Time.deltaTime;
target_rotate = new Vector3(Mathf.Clamp(target_rotate.x, -move_max_rotate, move_max_rotate), Mathf.Clamp(target_rotate.y, -move_max_rotate, move_max_rotate), 0f);
current_rotate = Vector3.Lerp(current_rotate, target_rotate, move_rotate_speed * Time.deltaTime);
}
else
{
target_rotate = new Vector3(0f, 0f, deck_angle);
current_rotate = new Vector3(0f, 0f, deck_angle);
}
card_transform.anchoredPosition = Vector2.Lerp(card_transform.anchoredPosition, target_position, Time.deltaTime * move_speed);
card_transform.localRotation = Quaternion.Slerp(card_transform.localRotation, Quaternion.Euler(current_rotate), Time.deltaTime * move_speed);
card_transform.localScale = Vector3.Lerp(card_transform.localScale, target_size, 5f * Time.deltaTime);
card_ui.SetCard(card);
card_glow.enabled = IsFocus() || IsDrag();
prev_pos = Vector3.Lerp(prev_pos, card_transform.position, 1f * Time.deltaTime);
//Unselect
if (!drag && selected && Input.GetMouseButtonDown(0))
selected = false;
}
private Vector2 GetTargetPosition()
{
Card card = GetCard();
RectTransformUtility.ScreenPointToLocalPointInRectangle(hand_transform, Input.mousePosition, Camera.main, out Vector2 tpos);
if (card.CardData.IsRequireTarget())
{
tpos = deck_position + Vector2.up * 150f + Vector2.right * tpos.x / 10f;
}
return tpos;
}
public void SetCard(Card card)
{
this.card_uid = card.uid;
card_ui.SetCard(card);
}
public void Kill()
{
if (!destroyed)
{
destroyed = true;
Destroy(gameObject);
}
}
public bool IsFocus()
{
if (GameTool.IsMobile())
return selected && !drag;
return focus && !drag && focus_timer > 0f;
}
public bool IsDrag()
{
return drag;
}
public Card GetCard()
{
Game gdata = GameClient.Get().GetGameData();
return gdata.GetCard(card_uid);
}
public CardData GetCardData()
{
Card card = GetCard();
if (card != null)
return CardData.Get(card.card_id);
return null;
}
public string GetCardUID()
{
return card_uid;
}
public void OnMouseEnterCard()
{
if (GameUI.IsUIOpened())
return;
focus = true;
}
public void OnMouseExitCard()
{
focus = false;
focus_timer = -0.2f;
}
public void OnMouseDownCard()
{
if (GameUI.IsOverUILayer("UI"))
return;
UnselectAll();
drag = true;
selected = true;
PlayerControls.Get().UnselectAll();
AudioTool.Get().PlaySFX("hand_card", AssetData.Get().hand_card_click_audio);
}
public void OnMouseUpCard()
{
Vector2 mpos = GameCamera.Get().MouseToPercent(Input.mousePosition);
Vector3 board_pos = GameBoard.Get().RaycastMouseBoard();
if (drag && mpos.y > 0.25f)
TryPlayCard(board_pos);
else if (!GameTool.IsMobile())
HandCardArea.Get().SortCards();
drag = false;
}
public void TryPlayCard(Vector3 board_pos)
{
if (!GameClient.Get().IsYourTurn())
{
WarningText.ShowNotYourTurn();
return;
}
BSlot bslot = BSlot.GetNearest(board_pos);
int player_id = GameClient.Get().GetPlayerID();
Game gdata = GameClient.Get().GetGameData();
Player player = gdata.GetPlayer(player_id);
Card card = GetCard();
Slot slot = Slot.None;
if (bslot != null)
slot = bslot.GetEmptySlot(board_pos);
if(bslot != null && card.CardData.IsRequireTarget())
slot = bslot.GetSlot(board_pos);
if (!Tutorial.Get().CanDo(TutoEndTrigger.PlayCard, card))
return;
Card slot_card = bslot?.GetSlotCard(board_pos);
if (bslot != null && card.CardData.IsRequireTargetSpell() && slot_card != null && slot_card.HasStatus(StatusType.SpellImmunity))
{
WarningText.ShowSpellImmune();
return;
}
if (!player.CanPayMana(card))
{
WarningText.ShowNoMana();
return;
}
if (gdata.CanPlayCard(card, slot, true))
{
PlayCard(slot);
}
}
public void PlayCard(Slot slot)
{
GameClient.Get().PlayCard(GetCard(), slot);
HandCardArea.Get().DelayRefresh(GetCard());
Destroy(gameObject);
if (GameTool.IsMobile())
BoardCard.UnfocusAll();
}
public CardData CardData { get { return GetCardData(); } }
public static HandCard GetDrag()
{
foreach (HandCard card in card_list)
{
if (card.IsDrag())
return card;
}
return null;
}
public static HandCard GetFocus()
{
foreach (HandCard card in card_list)
{
if (card.IsFocus())
return card;
}
return null;
}
public static HandCard Get(string uid)
{
foreach (HandCard card in card_list)
{
if (card && card.GetCardUID() == uid)
return card;
}
return null;
}
public static void UnselectAll()
{
foreach (HandCard card in card_list)
card.selected = false;
}
public static List<HandCard> GetAll()
{
return card_list;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d2357af860d3cda49906d9a355df329e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,131 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TcgEngine.Client;
using TcgEngine.UI;
namespace TcgEngine.Client
{
/// <summary>
/// Area where all the hand cards are
/// Will take card of spawning/despawning hand cards based on the refresh data received from server
/// </summary>
public class HandCardArea : MonoBehaviour
{
public GameObject card_prefab;
public RectTransform card_area;
public float card_spacing = 100f;
public float card_angle = 10f;
public float card_offset_y = 10f;
private List<HandCard> cards = new List<HandCard>();
private bool is_dragging;
private string last_destroyed;
private float last_destroyed_timer = 0f;
private static HandCardArea _instance;
void Awake()
{
_instance = this;
}
void Update()
{
if (!GameClient.Get().IsReady())
return;
int player_id = GameClient.Get().GetPlayerID();
Game data = GameClient.Get().GetGameData();
Player player = data.GetPlayer(player_id);
last_destroyed_timer += Time.deltaTime;
//Add new cards
foreach (Card card in player.cards_hand)
{
if (!HasCard(card.uid))
SpawnNewCard(card);
}
//Remove destroyed cards
for (int i = cards.Count - 1; i >= 0; i--)
{
HandCard card = cards[i];
if (card == null || player.GetHandCard(card.GetCard().uid) == null)
{
cards.RemoveAt(i);
if(card != null)
card.Kill();
}
}
//Set card index
int index = 0;
float count_half = cards.Count / 2f;
foreach (HandCard card in cards)
{
card.deck_position = new Vector2((index - count_half) * card_spacing, (index - count_half) * (index - count_half) * -card_offset_y);
card.deck_angle = (index - count_half) * -card_angle;
index++;
}
//Set target forcus
HandCard drag_card = HandCard.GetDrag();
is_dragging = drag_card != null;
}
public void SpawnNewCard(Card card)
{
GameObject card_obj = Instantiate(card_prefab, card_area.transform);
card_obj.GetComponent<HandCard>().SetCard(card);
card_obj.GetComponent<RectTransform>().anchoredPosition = new Vector2(0f, -100f);
cards.Add(card_obj.GetComponent<HandCard>());
}
public void DelayRefresh(Card card)
{
last_destroyed_timer = 0f;
last_destroyed = card.uid;
}
public void SortCards()
{
cards.Sort(SortFunc);
int i = 0;
foreach (HandCard acard in cards)
{
acard.transform.SetSiblingIndex(i);
i++;
}
}
private int SortFunc(HandCard a, HandCard b)
{
return a.transform.position.x.CompareTo(b.transform.position.x);
}
public bool HasCard(string card_uid)
{
HandCard card = HandCard.Get(card_uid);
bool just_destroyed = card_uid == last_destroyed && last_destroyed_timer < 0.7f;
return card != null || just_destroyed;
}
public bool IsDragging()
{
return is_dragging;
}
public static HandCardArea Get()
{
return _instance;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8fde1028e457564468c157e3c4b1bd3e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,46 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace TcgEngine.Client
{
/// <summary>
/// Same as HandCard, but simpler version for the opponent's cards
/// </summary>
public class HandCardBack : MonoBehaviour
{
public Image card_sprite;
private RectTransform rect;
private static List<HandCardBack> card_list = new List<HandCardBack>();
void Awake()
{
card_list.Add(this);
rect = GetComponent<RectTransform>();
SetCardback(null);
}
private void OnDestroy()
{
card_list.Remove(this);
}
public void SetCardback(CardbackData cb)
{
if (cb != null && cb.cardback != null)
card_sprite.sprite = cb.cardback;
}
public RectTransform GetRect()
{
if (rect == null)
return GetComponent<RectTransform>();
return rect;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f2f4f7030abb7e74abf11024a293fde4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,261 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TcgEngine.FX;
using TcgEngine.Client;
namespace TcgEngine.Client
{
/// <summary>
/// Visual representation of a booster pack in hand, for the OpenPack scene
/// </summary>
public class HandPack : MonoBehaviour
{
public Image pack_sprite;
public Image pack_glow;
public Text pack_quantity;
public float move_speed = 10f;
public float move_rotate_speed = 4f;
public float move_max_rotate = 10f;
[HideInInspector]
public Vector2 deck_position;
[HideInInspector]
public float deck_angle;
[Header("FX")]
public GameObject pack_open_fx;
public AudioClip pack_open_audio;
private string pack_tid = "";
private int quantity = 0;
private RectTransform hand_transform;
private RectTransform card_transform;
private Vector3 start_scale;
private float current_alpha = 0f;
private Vector3 current_rotate;
private Vector3 target_rotate;
private Vector3 prev_pos;
private bool destroyed = false;
private float focus_timer = 0f;
private bool focus = false;
private bool drag = false;
private static List<HandPack> pack_list = new List<HandPack>();
void Awake()
{
pack_list.Add(this);
card_transform = transform.GetComponent<RectTransform>();
hand_transform = transform.parent.GetComponent<RectTransform>();
start_scale = transform.localScale;
}
private void Start()
{
}
private void OnDestroy()
{
pack_list.Remove(this);
}
void Update()
{
focus_timer += Time.deltaTime;
Vector2 target_position = deck_position;
Vector3 target_size = start_scale;
float target_alpha = 1f;
bool player_dragging = HandPackArea.Get().IsDragging();
if (focus && focus_timer > 0.5f)
{
target_position = deck_position + Vector2.up * 40f;
}
if (drag)
{
target_position = GetTargetPosition();
target_size = start_scale * 0.8f;
Vector3 dir = card_transform.position - prev_pos;
Vector3 addrot = new Vector3(dir.y * 90f, -dir.x * 90f, 0f);
target_rotate += addrot * move_rotate_speed * Time.deltaTime;
target_rotate = new Vector3(Mathf.Clamp(target_rotate.x, -move_max_rotate, move_max_rotate), Mathf.Clamp(target_rotate.y, -move_max_rotate, move_max_rotate), 0f);
current_rotate = Vector3.Lerp(current_rotate, target_rotate, move_rotate_speed * Time.deltaTime);
move_speed = 9f;
target_alpha = 0.8f;
}
else
{
target_rotate = new Vector3(0f, 0f, deck_angle);
current_rotate = new Vector3(0f, 0f, deck_angle);
}
card_transform.anchoredPosition = Vector2.Lerp(card_transform.anchoredPosition, target_position, Time.deltaTime * move_speed);
card_transform.rotation = Quaternion.Slerp(card_transform.rotation, Quaternion.Euler(current_rotate), Time.deltaTime * move_speed);
card_transform.localScale = Vector3.Lerp(card_transform.localScale, target_size, 4f * Time.deltaTime);
pack_glow.enabled = (focus && !player_dragging) || drag;
current_alpha = Mathf.MoveTowards(current_alpha, target_alpha, 2f * Time.deltaTime);
pack_sprite.color = new Color(1f, 1f, 1f, current_alpha);
pack_glow.color = new Color(pack_glow.color.r, pack_glow.color.g, pack_glow.color.b, current_alpha * 0.8f);
pack_quantity.text = quantity.ToString();
prev_pos = Vector3.Lerp(prev_pos, card_transform.position, 1f * Time.deltaTime);
}
private Vector2 GetTargetPosition()
{
RectTransformUtility.ScreenPointToLocalPointInRectangle(hand_transform, Input.mousePosition, Camera.main, out Vector2 tpos);
return tpos;
}
public void SetPack(UserCardData pack)
{
this.pack_tid = pack.tid;
this.quantity = pack.quantity;
PackData ipack = PackData.Get(pack.tid);
if (ipack)
{
pack_sprite.sprite = ipack.pack_img;
}
}
public void OpenPack()
{
FXTool.DoFX(pack_open_fx, transform.position);
AudioTool.Get().PlaySFX("pack_open", pack_open_audio);
Destroy(gameObject);
OpenPackMenu.Get().OpenPack(pack_tid);
}
public void Remove()
{
quantity--;
if (quantity <= 0)
Kill();
}
public void Kill()
{
if (!destroyed)
{
destroyed = true;
Destroy(gameObject);
}
}
public bool IsFocus()
{
return focus && !drag;
}
public bool IsDrag()
{
return drag;
}
public PackData GetPackData()
{
return PackData.Get(pack_tid);
}
public string GetPackTid()
{
return pack_tid;
}
public int GetPackQuantity()
{
UserData udata = Authenticator.Get().UserData;
return udata.GetPackQuantity(pack_tid);
}
public void OnMouseEnterCard()
{
if (HandPackArea.Get().IsLocked())
return;
focus = true;
}
public void OnMouseExitCard()
{
focus = false;
focus_timer = 0f;
}
public void OnMouseDownCard()
{
if (HandPackArea.Get().IsLocked())
return;
drag = true;
AudioTool.Get().PlaySFX("hand_card", AssetData.Get().hand_card_click_audio);
}
public void OnMouseUpCard()
{
Vector3 world_pos = MouseToWorld(Input.mousePosition);
if (drag && world_pos.y > -2.5f)
OpenPack();
else
HandPackArea.Get().SortCards();
drag = false;
}
public Vector3 MouseToWorld(Vector3 mouse_pos)
{
Vector3 wpos = Camera.main.ScreenToWorldPoint(mouse_pos);
wpos.z = 0f;
return wpos;
}
public PackData PackData { get { return GetPackData(); } }
public static HandPack GetDrag()
{
foreach (HandPack card in pack_list)
{
if (card.IsDrag())
return card;
}
return null;
}
public static HandPack GetFocus()
{
foreach (HandPack card in pack_list)
{
if (card.IsFocus())
return card;
}
return null;
}
public static HandPack Get(string uid)
{
foreach (HandPack card in pack_list)
{
if (card && card.GetPackTid() == uid)
return card;
}
return null;
}
public static List<HandPack> GetAll()
{
return pack_list;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d4cb6ec85335c724eae85b838b0b3f89
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,172 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TcgEngine.Client;
using TcgEngine.UI;
namespace TcgEngine.Client
{
/// <summary>
/// Area of the hand of packs, will spawn/despawns visual packs based on what player has in the data
/// </summary>
public class HandPackArea : MonoBehaviour
{
public RectTransform hand_area;
public GameObject pack_template;
public float card_spacing = 100f;
public float card_angle = 10f;
public float card_offset_y = 10f;
private List<HandPack> packs = new List<HandPack>();
private Vector3 start_pos;
private bool is_dragging;
private bool is_locked;
private string last_destroyed;
private float last_destroyed_timer = 0f;
private static HandPackArea _instance;
void Awake()
{
_instance = this;
}
private void Start()
{
pack_template.SetActive(false);
start_pos = hand_area.anchoredPosition;
if (Authenticator.Get().IsConnected())
LoadPacks();
else
RefreshLogin();
}
private async void RefreshLogin()
{
bool success = await Authenticator.Get().RefreshLogin();
if (success)
LoadPacks();
else
SceneNav.GoTo("LoginMenu");
}
public async void LoadPacks()
{
UserData udata = await Authenticator.Get().LoadUserData();
if (udata != null)
{
RefreshPacks();
}
}
public void RefreshPacks()
{
UserData udata = Authenticator.Get().UserData;
foreach (UserCardData pack in udata.packs)
{
PackData dpack = PackData.Get(pack.tid);
if (dpack != null && !HasPack(pack.tid))
SpawnNewPack(pack);
}
//Remove removed cards
for (int i = packs.Count - 1; i >= 0; i--)
{
HandPack pack = packs[i];
if (pack == null || !udata.HasPack(pack.GetPackTid()))
{
packs.RemoveAt(i);
if (pack)
pack.Remove();
}
}
}
void Update()
{
last_destroyed_timer += Time.deltaTime;
//Position
Vector3 tpos = is_locked ? (start_pos + Vector3.down * 200f) : start_pos;
hand_area.anchoredPosition = Vector3.MoveTowards(hand_area.anchoredPosition, tpos, 200f * Time.deltaTime);
//Set card index
int index = 0;
float count_half = packs.Count / 2f;
foreach (HandPack card in packs)
{
card.deck_position = new Vector2((index - count_half) * card_spacing, (index - count_half) * (index - count_half) * -card_offset_y);
card.deck_angle = (index - count_half) * -card_angle;
index++;
}
//Set target forcus
HandPack drag_pack = HandPack.GetDrag();
is_dragging = drag_pack != null;
}
public void SpawnNewPack(UserCardData pack)
{
GameObject card_obj = Instantiate(pack_template, hand_area.transform);
card_obj.SetActive(true);
card_obj.GetComponent<HandPack>().SetPack(pack);
card_obj.GetComponent<RectTransform>().anchoredPosition = new Vector2(0f, -100f);
packs.Add(card_obj.GetComponent<HandPack>());
}
public void DelayRefresh(Card card)
{
last_destroyed_timer = 0f;
last_destroyed = card.uid;
}
public void Lock(bool locked)
{
is_locked = locked;
}
public void SortCards()
{
packs.Sort(SortFunc);
int i = 0;
foreach (HandPack acard in packs)
{
acard.transform.SetSiblingIndex(i);
i++;
}
}
private int SortFunc(HandPack a, HandPack b)
{
return a.transform.position.x.CompareTo(b.transform.position.x);
}
public bool HasPack(string pack_tid)
{
HandPack card = HandPack.Get(pack_tid);
bool just_destroyed = pack_tid == last_destroyed && last_destroyed_timer < 0.5f;
return card != null || just_destroyed;
}
public bool IsDragging()
{
return is_dragging;
}
public bool IsLocked()
{
return is_locked;
}
public static HandPackArea Get()
{
return _instance;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ca2948006648e4e4db48fa099d16fef5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,69 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TcgEngine.Client;
namespace TcgEngine.Client
{
/// <summary>
/// Same as HandCardArea but for the opponents hand
/// Simpler version with display only (no draging of card)
/// </summary>
public class OpponentHand : MonoBehaviour
{
public GameObject card_prefab;
public RectTransform card_area;
public float card_spacing = 100f;
public float card_angle = 10f;
public float card_offset_y = 10f;
private List<HandCardBack> cards = new List<HandCardBack>();
void Start()
{
}
void Update()
{
if (!GameClient.Get().IsReady())
return;
Game gdata = GameClient.Get().GetGameData();
Player player = gdata.GetPlayer(GameClient.Get().GetOpponentPlayerID());
if (cards.Count < player.cards_hand.Count)
{
GameObject new_card = Instantiate(card_prefab, card_area);
HandCardBack hand_card = new_card.GetComponent<HandCardBack>();
CardbackData cbdata = CardbackData.Get(player.cardback);
hand_card.SetCardback(cbdata);
RectTransform card_rect = new_card.GetComponent<RectTransform>();
card_rect.anchoredPosition = new Vector2(0f, 100f);
cards.Add(hand_card);
}
if (cards.Count > player.cards_hand.Count)
{
HandCardBack card = cards[cards.Count - 1];
cards.RemoveAt(cards.Count - 1);
Destroy(card.gameObject);
}
int nb_cards = Mathf.Min(cards.Count, player.cards_hand.Count);
for (int i = 0; i < nb_cards; i++)
{
HandCardBack card = cards[i];
RectTransform crect = card.GetRect();
float half = nb_cards / 2f;
Vector3 tpos = new Vector3((i - half) * card_spacing, (i - half) * (i - half) * card_offset_y);
float tangle = (i - half) * card_angle;
crect.anchoredPosition = Vector3.Lerp(crect.anchoredPosition, tpos, 4f * Time.deltaTime);
card.transform.localRotation = Quaternion.Slerp(card.transform.localRotation, Quaternion.Euler(0f, 0f, tangle), 4f * Time.deltaTime);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4ee08f17e9a14a14fa9ceb86cd38989a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,138 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TcgEngine.UI;
using TcgEngine.FX;
namespace TcgEngine.Client
{
/// <summary>
/// Visual representation of a card found in a pack (once opened)
/// The card can be flipped by clicking on it
/// </summary>
public class PackCard : MonoBehaviour
{
public float move_speed = 5f;
public float flip_speed = 10f;
public SpriteRenderer cardback;
public CardUI card_ui;
public GameObject new_card;
[Header("FX")]
public GameObject card_flip_fx;
public GameObject card_rare_flip_fx;
public AudioClip card_flip_audio;
public AudioClip card_rare_flip_audio;
private CardData icard;
private VariantData variant;
private Vector3 target;
private Quaternion rtarget;
private bool revealed = false;
private bool removed = false;
private bool is_new = false;
private float timer = 0f;
private static List<PackCard> card_list = new List<PackCard>();
void Awake()
{
card_list.Add(this);
}
private void OnDestroy()
{
card_list.Remove(this);
}
void Update()
{
transform.position = Vector3.MoveTowards(transform.position, target, move_speed * Time.deltaTime);
if (revealed)
{
timer += Time.deltaTime;
transform.rotation = Quaternion.Slerp(transform.rotation, rtarget, flip_speed * Time.deltaTime);
}
if (removed && timer > 4f)
Destroy(gameObject);
}
public void SetCard(PackData pack, CardData card, VariantData variant)
{
this.icard = card;
this.variant = variant;
if (cardback != null)
cardback.sprite = pack.cardback_img;
card_ui.SetCard(card, variant);
new_card?.SetActive(false);
UserData udata = Authenticator.Get().GetUserData();
is_new = !udata.HasCard(icard.id, variant.id);
}
public void SetTarget(Vector3 pos)
{
target = pos;
rtarget = Quaternion.Euler(0f, 180f, 0f);
transform.rotation = rtarget;
}
public void Reveal()
{
if (revealed)
return;
revealed = true;
rtarget = Quaternion.Euler(0f, 0f, 0f);
new_card?.SetActive(is_new);
if (icard != null && icard.rarity.rank >= 3)
{
FXTool.DoFX(card_rare_flip_fx, transform.position);
AudioTool.Get().PlaySFX("pack_open", card_rare_flip_audio);
}
else
{
FXTool.DoFX(card_flip_fx, transform.position);
AudioTool.Get().PlaySFX("pack_open", card_flip_audio);
}
}
public void Remove()
{
if (removed)
return;
removed = true;
timer = 0f;
target = Vector3.up * 10f;
}
public void OnMouseDown()
{
if (!GameUI.IsOverUILayer("UI"))
{
Reveal();
}
}
public bool IsRevealed()
{
return revealed && timer > 0.5f;
}
public static List<PackCard> GetAll()
{
return card_list;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e17031c28aa430e4ab297ac6ff325bfa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,12 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TcgEngine.UI;
namespace TcgEngine.Client
{
public class PlayerAttackZone1 : MonoBehaviour
{
//Renamed to BoardSlotPlayer, kept file to override file in update
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9581722b67f7afa49a527eefff1a0084
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,137 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TcgEngine.Client;
using UnityEngine.Events;
using TcgEngine.UI;
namespace TcgEngine.Client
{
/// <summary>
/// Script that contain main controls for clicking on cards, attacking, activating abilities
/// Holds the currently selected card and will send action to GameClient on click release
/// </summary>
public class PlayerControls : MonoBehaviour
{
private BoardCard selected_card = null;
private static PlayerControls instance;
void Awake()
{
instance = this;
}
void Update()
{
if (!GameClient.Get().IsReady())
return;
if (Input.GetMouseButtonDown(1))
UnselectAll();
if (selected_card != null)
{
if (Input.GetMouseButtonUp(0))
{
ReleaseClick();
UnselectAll();
}
}
}
public void SelectCard(BoardCard bcard)
{
Game gdata = GameClient.Get().GetGameData();
Player player = GameClient.Get().GetPlayer();
Card card = bcard.GetFocusCard();
if (gdata.IsPlayerSelectorTurn(player) && gdata.selector == SelectorType.SelectTarget)
{
if (!Tutorial.Get().CanDo(TutoEndTrigger.SelectTarget, card))
return;
//Target selector, select this card
GameClient.Get().SelectCard(card);
}
else if (gdata.IsPlayerActionTurn(player) && card.player_id == player.player_id)
{
//Start dragging card
selected_card = bcard;
}
}
public void SelectCardRight(BoardCard card)
{
if (!Input.GetMouseButton(0))
{
//Nothing on right-click
}
}
private void ReleaseClick()
{
bool yourturn = GameClient.Get().IsYourTurn();
if (yourturn && selected_card != null)
{
Card card = selected_card.GetCard();
Vector3 wpos = GameBoard.Get().RaycastMouseBoard();
BSlot tslot = BSlot.GetNearest(wpos);
Card target = tslot?.GetSlotCard(wpos);
AbilityButton ability = AbilityButton.GetFocus(wpos, 1f);
if (ability != null && ability.IsInteractable())
{
if (!Tutorial.Get().CanDo(TutoEndTrigger.CastAbility, card))
return;
GameClient.Get().CastAbility(card, ability.GetAbility());
}
else if (tslot is BoardSlotPlayer)
{
if (!Tutorial.Get().CanDo(TutoEndTrigger.AttackPlayer, card))
return;
if (card.exhausted)
WarningText.ShowExhausted();
else
GameClient.Get().AttackPlayer(card, tslot.GetPlayer());
}
else if (target != null && target.uid != card.uid && target.player_id != card.player_id)
{
if (!Tutorial.Get().CanDo(TutoEndTrigger.Attack, card) && !Tutorial.Get().CanDo(TutoEndTrigger.Attack, target))
return;
if (card.exhausted)
WarningText.ShowExhausted();
else
GameClient.Get().AttackTarget(card, target);
}
else if (tslot != null && tslot is BoardSlot)
{
if (!Tutorial.Get().CanDo(TutoEndTrigger.Move, tslot.GetSlot()))
return;
GameClient.Get().Move(card, tslot.GetSlot());
}
}
}
public void UnselectAll()
{
selected_card = null;
}
public BoardCard GetSelected()
{
return selected_card;
}
public static PlayerControls Get()
{
return instance;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9b1bad97b4da5664eb3a2254097ac000
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,93 @@
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
namespace TcgEngine.Client
{
//Grants rewards for adventure solo mode
public class RewardManager : MonoBehaviour
{
private bool reward_gained = false;
private static RewardManager instance;
void Awake()
{
instance = this;
}
private void Start()
{
GameClient.Get().onGameEnd += OnGameEnd;
}
void OnGameEnd(int winner)
{
int player_id = GameClient.Get().GetPlayerID();
if (GameClient.game_settings.game_type == GameType.Adventure && winner == player_id)
{
UserData udata = Authenticator.Get().UserData;
LevelData level = LevelData.Get(GameClient.game_settings.level);
if (level != null && !udata.HasReward(level.id) && !reward_gained)
{
if (Authenticator.Get().IsTest())
GainRewardTest(level);
if (Authenticator.Get().IsApi())
GainRewardAPI(level);
}
}
}
private async void GainRewardTest(LevelData level)
{
VariantData variant = VariantData.GetDefault();
UserData udata = Authenticator.Get().UserData;
udata.coins += level.reward_coins;
udata.xp += level.reward_xp;
udata.AddReward(level.id);
foreach (CardData card in level.reward_cards)
{
udata.AddCard(card.id, variant.id, 1);
}
foreach (PackData pack in level.reward_packs)
{
udata.AddPack(pack.id, 1);
}
reward_gained = true;
await Authenticator.Get().SaveUserData();
}
private async void GainRewardAPI(LevelData level)
{
bool success = await GainRewardAPI(level.id);
reward_gained = success;
}
public async Task<bool> GainRewardAPI(string reward_id)
{
RewardGainRequest req = new RewardGainRequest();
req.reward = reward_id;
string url = ApiClient.ServerURL + "/users/rewards/gain/" + ApiClient.Get().UserID;
string json = ApiTool.ToJson(req);
WebResponse res = await ApiClient.Get().SendPostRequest(url, json);
Debug.Log("Gain Reward: " + reward_id + " " + res.success);
return res.success;
}
public bool IsRewardGained()
{
return reward_gained;
}
public static RewardManager Get()
{
return instance;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b096db1450456404a90d190fb5e7f3ee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,43 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace TcgEngine.Client
{
/// <summary>
/// Component added to a scene to add some generic sfx/music to the arena
/// </summary>
public class SceneSettings : MonoBehaviour
{
public AudioClip start_audio;
public AudioClip[] game_music;
public AudioClip[] game_ambience;
private static SceneSettings instance;
private void Awake()
{
instance = this;
}
void Start()
{
AudioTool.Get().PlaySFX("game_sfx", start_audio);
if (game_music.Length > 0)
AudioTool.Get().PlayMusic("music", game_music[Random.Range(0, game_music.Length)]);
if (game_ambience.Length > 0)
AudioTool.Get().PlaySFX("ambience", game_ambience[Random.Range(0, game_ambience.Length)], 0.5f, true);
}
void Update()
{
}
public static SceneSettings Get()
{
return instance;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c4bf35dcd0851ff49b983f15c2831e9f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,70 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TcgEngine.UI;
namespace TcgEngine.Client
{
/// <summary>
/// TutoStep inside a group will all be triggered sequentially, game proceed to next step when end_trigger is met, or when another group is triggered
/// </summary>
public class TutoStep : UIPanel
{
[Header("Tuto Step")]
public TutoEndTrigger end_trigger;
public CardData trigger_target;
public bool forced; //Player MUST do the end_trigger action to proceed
private TutoStepGroup group;
private int step;
private TutoBox tuto_box;
private static List<TutoStep> steps = new List<TutoStep>();
protected override void Awake()
{
base.Awake();
step = transform.GetSiblingIndex();
group = GetComponentInParent<TutoStepGroup>();
tuto_box = GetComponentInChildren<TutoBox>();
steps.Add(this);
}
protected override void Start()
{
base.Start();
tuto_box.SetNextButton(end_trigger == TutoEndTrigger.Click);
}
protected virtual void OnDestroy()
{
steps.Remove(this);
}
public int GetStepIndex()
{
return step;
}
public static TutoStep Get(TutoStepGroup group, int step)
{
foreach (TutoStep s in steps)
{
if (s.group == group && s.step == step)
return s;
}
return null;
}
public static void HideAll()
{
foreach (TutoStep s in steps)
{
s.Hide();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 58b5377c4c00f3e47bb3c3619c915d9f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,69 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace TcgEngine.Client
{
/// <summary>
/// TutoStep groups do NOT need to be triggered in order, group will be triggered on start_trigger condition
/// and then all TutoStep inside group will be executed in order.
/// </summary>
public class TutoStepGroup : MonoBehaviour
{
public int turn_min = 0;
public int turn_max = 99;
public TutoStartTrigger start_trigger;
public CardData start_target;
public bool forced; //Must finish all TutoStep inside group before triggering another group
private bool triggered = false;
private static List<TutoStepGroup> groups = new List<TutoStepGroup>();
protected virtual void Awake()
{
groups.Add(this);
}
protected virtual void OnDestroy()
{
groups.Remove(this);
}
public void SetTriggered()
{
triggered = true;
}
public static TutoStepGroup Get(TutoStartTrigger trigger, int turn)
{
foreach (TutoStepGroup s in groups)
{
if (s.start_trigger == trigger && !s.triggered)
{
if(turn >= s.turn_min&& turn <= s.turn_max)
return s;
}
}
return null;
}
public static TutoStepGroup Get(TutoStartTrigger trigger, CardData target, int turn)
{
foreach (TutoStepGroup s in groups)
{
if (s.start_trigger == trigger && !s.triggered)
{
if (turn >= s.turn_min && turn <= s.turn_max)
{
if (s.start_target == null || s.start_target == target)
return s;
}
}
}
return null;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dc92c978bda7c8d43ada9d0487c31d1e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,295 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace TcgEngine.Client
{
public class Tutorial : MonoBehaviour
{
private bool is_tuto = false;
private TutoStepGroup current_group;
private TutoStep current_step;
private bool locked = false;
private static Tutorial instance;
void Awake()
{
instance = this;
}
void Start()
{
if (GameClient.game_settings.game_type == GameType.Adventure)
{
LevelData level = GameClient.game_settings.GetLevel();
if (level.tuto_prefab != null)
{
is_tuto = true;
GameObject tuto_obj = Instantiate(level.tuto_prefab);
tuto_obj.GetComponent<Canvas>().worldCamera = GameCamera.GetCamera();
GameClient.Get().onNewTurn += OnNewTurn;
GameClient.Get().onCardPlayed += OnCardPlayed;
GameClient.Get().onAttackStart += OnAttack;
GameClient.Get().onAttackPlayerStart += OnAttackPlayer;
GameClient.Get().onAbilityStart += OnCastAbility;
GameClient.Get().onAbilityTargetCard += OnTargetCard;
GameClient.Get().onAbilityTargetPlayer += OnTargetPlayer;
}
HideAll();
}
}
private void OnNewTurn(int player_id)
{
Game data = GameClient.Get().GetGameData();
if (data == null)
return;
EndGroup();
if (player_id != GameClient.Get().GetPlayerID())
return;
TutoStepGroup group = TutoStepGroup.Get(TutoStartTrigger.StartTurn, data.turn_count);
ShowGroup(group);
}
private void OnCardPlayed(Card card, Slot slot)
{
Game data = GameClient.Get().GetGameData();
if (card.player_id == GameClient.Get().GetPlayerID())
{
TriggerEndStep(TutoEndTrigger.PlayCard);
TriggerStartGroup(TutoStartTrigger.PlayCard, card);
}
}
private void OnAttack(Card card, Card target)
{
Game data = GameClient.Get().GetGameData();
if (card.player_id == GameClient.Get().GetPlayerID())
{
TriggerEndStep(TutoEndTrigger.Attack, 2f);
TriggerStartGroup(TutoStartTrigger.Attack, card);
TriggerStartGroup(TutoStartTrigger.Attack, target);
}
}
private void OnAttackPlayer(Card card, Player target)
{
Game data = GameClient.Get().GetGameData();
if (card.player_id == GameClient.Get().GetPlayerID())
{
TriggerEndStep(TutoEndTrigger.AttackPlayer, 2f);
TriggerStartGroup(TutoStartTrigger.Attack, card);
}
}
private void OnCastAbility(AbilityData ability, Card card)
{
Game data = GameClient.Get().GetGameData();
if (card.player_id == GameClient.Get().GetPlayerID())
{
TriggerEndStep(TutoEndTrigger.CastAbility);
TriggerStartGroup(TutoStartTrigger.CastAbility, card);
}
}
private void OnTargetCard(AbilityData ability, Card card, Card target)
{
Game data = GameClient.Get().GetGameData();
if (card.player_id == GameClient.Get().GetPlayerID())
{
TriggerEndStep(TutoEndTrigger.SelectTarget);
}
}
private void OnTargetPlayer(AbilityData ability, Card card, Player target)
{
Game data = GameClient.Get().GetGameData();
if (card.player_id == GameClient.Get().GetPlayerID())
{
TriggerEndStep(TutoEndTrigger.SelectTarget);
}
}
public void TriggerEndStep(TutoEndTrigger trigger, float time = 1f)
{
if (current_step != null && current_step.end_trigger == trigger)
{
Hide();
TutoStepGroup group = current_group;
locked = true;
TimeTool.WaitFor(time, () =>
{
locked = false;
if (group == current_group)
{
ShowNext();
}
});
}
}
public void TriggerStartGroup(TutoStartTrigger trigger, Card card)
{
if (current_group == null || !current_group.forced)
{
if (current_step == null || !current_step.forced)
{
CardData target = card != null ? card.CardData : null;
ShowGroup(trigger, target);
}
}
}
public void ShowGroup(TutoStartTrigger trigger, CardData target)
{
Game data = GameClient.Get().GetGameData();
TutoStepGroup group = TutoStepGroup.Get(trigger, target, data.turn_count);
ShowGroup(group);
}
public void ShowGroup(TutoStepGroup group)
{
if (group != null)
{
current_group = group;
group.SetTriggered();
TutoStep step = TutoStep.Get(group, 0);
Show(step);
}
}
public void ShowNext()
{
if (current_group != null)
{
int index = GetNextIndex();
TutoStep step = TutoStep.Get(current_group, index);
if (step != null)
Show(step);
else
EndGroup();
}
}
public void Show(TutoStep step)
{
HideAll();
current_step = step;
if (step != null)
step.Show();
}
public void EndGroup()
{
HideAll();
current_group = null;
current_step = null;
}
public void Hide(TutoStep step)
{
if (step != null)
step.Hide();
}
public void Hide()
{
Hide(current_step);
}
public bool CanDo(TutoEndTrigger trigger)
{
return CanDo(trigger, null);
}
public bool CanDo(TutoEndTrigger trigger, Slot slot)
{
Game data = GameClient.Get().GetGameData();
Card card = data.GetSlotCard(slot);
return CanDo(trigger, card);
}
public bool CanDo(TutoEndTrigger trigger, Card target)
{
if (!is_tuto)
return true; //Not a tutorial
if (locked)
return false;
if (current_step != null && current_step.forced)
{
if (trigger == TutoEndTrigger.CastAbility && current_step.end_trigger == TutoEndTrigger.SelectTarget)
return true; //Dont get locked into select target if ability was canceled
if (current_step.end_trigger != trigger)
return false; //Wrong trigger
CardData target_data = target != null ? target.CardData : null;
if (current_step.trigger_target != null && current_step.trigger_target != target_data)
return false; //Wrong target
}
return true;
}
public int GetNextIndex()
{
if (current_step != null)
return current_step.GetStepIndex() + 1;
return 0;
}
public bool IsTuto()
{
return is_tuto;
}
public TutoEndTrigger GetEndTrigger()
{
if (current_step != null)
return current_step.end_trigger;
return TutoEndTrigger.Click;
}
public void HideAll()
{
TutoStep.HideAll();
}
public static Tutorial Get()
{
return instance;
}
}
public enum TutoStartTrigger
{
StartTurn = 0,
PlayCard = 10,
Attack = 20,
CastAbility = 30,
}
public enum TutoEndTrigger
{
Click = 0,
EndTurn = 5,
PlayCard = 10,
Move = 15,
Attack = 20,
AttackPlayer = 25,
CastAbility = 30,
SelectTarget = 35,
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b8660c53fc51f6b469daa03c25f99f99
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: