internal static void PlayCardCallback(object[] data)
    {
        string who = (string)data[0];
        PlayerObjectBehaviour forPlayer = LocalPlayer;

        if (who.Equals(EnemyPlayer.PlayerName))
        {
            forPlayer = EnemyPlayer;
        }
        int guid = (int)data[1];
        int mana = (int)data[2];
        CardObjectBehaviour cob = CardObjectBehaviour.GetCOB(guid);

        forPlayer.Hob.RemoveCard(cob.gameObject);
        forPlayer.UpdateMana(mana);
        cob.gameObject.GetComponent <DragHandCard>().CanDrag = false;

        cob.AddDoTweens(() =>
        {
            // Move card to neutral position and play effects
            Vector3 spellEffectPos = NeutralBattlePoint.transform.position;
            cob.AddEffectParticle();
            cob.SetTweening(true);
            Sequence s = DOTween.Sequence();
            s.Insert(0f, cob.gameObject.transform.DOMove(new Vector3(spellEffectPos.x, spellEffectPos.y, -3f), GameConfig.F("SPELL_CARD_FLY_TIME")));
            s.Insert(0f, cob.gameObject.transform.DOScale(GameConfig.F("SPELL_CARD_SCALE"), GameConfig.F("SPELL_CARD_FLY_TIME")));
            s.AppendInterval(GameConfig.F("SPELL_CARD_FLY_TIME") + GameConfig.F("SPELL_CARD_DISPLAY_TIME"));
            s.OnComplete(() =>
            {
                cob.SetTweening(false);
            });
        });
    }
    public override void OnEndDrag()
    {
        DisableAllGlow();

        // find cardslot when end drag
        GameObject endSlot = null;

        RaycastHit[] hits = Physics.RaycastAll(origin: new Vector3(transform.position.x, transform.position.y, -10), direction: new Vector3(0, 0, 1), maxDistance: 30f);
        foreach (RaycastHit h in hits)
        {
            if (h.transform.tag == "CardSlot")
            {
                endSlot = h.transform.gameObject;
            }
        }
        CardObjectBehaviour cob = gameObject.GetComponent <CardObjectBehaviour>();

        if (endSlot != null && BoardBehaviour.IsCurrentPlayerAction())
        {
            //Dragged into a cardslot
            CardSlotBehaviour csb = endSlot.GetComponent <CardSlotBehaviour>();
            NetworkController.Instance.PlayerPlayCardToSlot(cob.Owner.PlayerName, cob.Guid, csb.SlotId);

            // Move card to neutral position and play effects
            Vector3 spellEffectPos = BoardBehaviour.NeutralBattlePoint.transform.position;
            gameObject.transform.DOMove(new Vector3(spellEffectPos.x, spellEffectPos.y, -3f), GameConfig.F("SPELL_CARD_FLY_TIME"));
            gameObject.transform.DOScale(GameConfig.F("SPELL_CARD_SCALE"), GameConfig.F("SPELL_CARD_FLY_TIME"));
            //  - Add effect to the card
            cob.AddEffectParticle();
        }
        else
        {
            // Dragged into somewhere else
            transform.DOMove(cob.OriginPos, 0.5f).SetEase(Ease.OutCubic);
        }
    }
    internal static void BattleResult(object[] data)
    {
        string aname = (string)data[0];
        string dname = (string)data[1];
        List <BattleCardModifier> amods = (List <BattleCardModifier>)data[2];
        List <BattleCardModifier> dmods = (List <BattleCardModifier>)data[3];
        int         aoap     = (int)data[4];
        int         doap     = (int)data[5];
        List <Card> akilled  = (List <Card>)data[6];
        List <Card> atouched = (List <Card>)data[7];
        List <Card> dkilled  = (List <Card>)data[8];
        List <Card> dtouched = (List <Card>)data[9];
        int         ahit     = (int)data[10];
        int         dhit     = (int)data[11];
        List <Card> abattle  = (List <Card>)data[12];
        List <Card> bbattle  = (List <Card>)data[13];
        int         aaap     = (int)data[14];
        int         daap     = (int)data[15];
        int         acs      = (int)data[16];
        int         dcs      = (int)data[17];
        int         ahp      = (int)data[18];
        int         dhp      = (int)data[19];

        Sequence s           = DOTween.Sequence();
        float    endTimeNode = 0f;

        // We are battling
        SetUIState(UIState.BATTLING);
        // Move 2 set of cards to battle position
        int     indexCountA    = 0;
        int     indexCountB    = 0;
        Vector3 attackerAnchor = AttackerBattlePoint.transform.position;

        attackerAnchor.z = GameConfig.F("BATTLE_Z_INDEX");
        Vector3 defenderAnchor = DefenderBattlePoint.transform.position;

        defenderAnchor.z = GameConfig.F("BATTLE_Z_INDEX");
        for (int i = 0; i < abattle.Count; i++)
        {
            CardObjectBehaviour item = CardObjectBehaviour.GetCOB(abattle.ElementAt(i).guid);
            float time = GameConfig.F("BATTLE_CARD_INTERVAL") * i;
            item.TempPos = attackerAnchor;
            s.Insert(time, item.transform.DOMove(attackerAnchor, GameConfig.F("BATTLE_CARD_FLY_TIME")).SetEase(Ease.OutCubic));
            s.Insert(time, item.transform.DOScale(GameConfig.F("BATTLE_CARD_SCALE"), GameConfig.F("BATTLE_CARD_SCALE_TIME")));
            attackerAnchor.x += GameConfig.F("BATTLE_CARD_SPACING");
            attackerAnchor.z -= 0.01f;
            indexCountA++;
        }
        for (int i = 0; i < bbattle.Count; i++)
        {
            CardObjectBehaviour item = CardObjectBehaviour.GetCOB(bbattle.ElementAt(i).guid);
            float time = GameConfig.F("BATTLE_CARD_INTERVAL") * i;
            item.TempPos = defenderAnchor;
            s.Insert(time, item.transform.DOMove(defenderAnchor, GameConfig.F("BATTLE_CARD_FLY_TIME")).SetEase(Ease.OutCubic));
            s.Insert(time, item.transform.DOScale(GameConfig.F("BATTLE_CARD_SCALE"), GameConfig.F("BATTLE_CARD_SCALE_TIME")));
            defenderAnchor.x -= GameConfig.F("BATTLE_CARD_SPACING");
            defenderAnchor.z -= 0.01f;
            indexCountB++;
        }
        endTimeNode = Math.Max(abattle.Count, bbattle.Count) * GameConfig.F("BATTLE_CARD_INTERVAL");

        // Cards do effects
        for (int i = 0; i < amods.Count; i++)
        {
            BattleCardModifier  bcm  = amods.ElementAt(i);
            CardObjectBehaviour item = CardObjectBehaviour.GetCOB(bcm.guid);
            float time = endTimeNode + (GameConfig.F("BATTLE_CARD_INTERVAL") +
                                        GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME") * 2.0f +
                                        GameConfig.F("BATTLE_CARD_EFFECT_HIGHLIGHT_PAUSE") * 2.0f +
                                        GameConfig.F("BATTLE_CARD_EFFECT_LABEL_SCALE_TIME") +
                                        GameConfig.F("BATTLE_CARD_EFFECT_LABEL_PAUSE")) * i;
            // pop out active card
            s.Insert(time, item.transform.DOMove(new Vector3(item.TempPos.x, item.TempPos.y, item.TempPos.z - 1f), GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME")));
            s.Insert(time, item.transform.DOScale(GameConfig.F("BATTLE_CARD_EFFECT_SCALE"), GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME")));
            s.InsertCallback(time, () =>
            {
                item.AddEffectParticle();
            });
            // show effect values
            GameObject buffLabel   = AttackerBattlePoint.transform.Find("Buff").gameObject;
            GameObject debuffLabel = AttackerBattlePoint.transform.Find("Debuff").gameObject;
            GameObject activeLabel = null;
            if (bcm.mod > 0)
            {
                buffLabel.SetActive(false);
                debuffLabel.SetActive(false);
                buffLabel.GetComponent <BuffLabelBehaviour>().Text.text = "+" + bcm.mod.ToString();
                activeLabel = buffLabel;
            }
            else
            {
                buffLabel.SetActive(false);
                debuffLabel.SetActive(false);
                debuffLabel.GetComponent <BuffLabelBehaviour>().Text.text = bcm.mod.ToString();
                activeLabel = debuffLabel;
            }
            s.InsertCallback(time +
                             GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME") +
                             GameConfig.F("BATTLE_CARD_EFFECT_HIGHLIGHT_PAUSE"), () =>
            {
                activeLabel.SetActive(true);
                activeLabel.transform.DOScale(GameConfig.F("BATTLE_CARD_EFFECT_LABEL_SCALE"), GameConfig.F("BATTLE_CARD_EFFECT_LABEL_SCALE_TIME"));
            });
            s.InsertCallback(time +
                             GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME") +
                             GameConfig.F("BATTLE_CARD_EFFECT_HIGHLIGHT_PAUSE") * 2.0f +
                             GameConfig.F("BATTLE_CARD_EFFECT_LABEL_SCALE_TIME") +
                             GameConfig.F("BATTLE_CARD_EFFECT_LABEL_PAUSE"), () =>
            {
                activeLabel.SetActive(false);
                activeLabel.transform.DOScale(1.0f, GameConfig.F("BATTLE_CARD_EFFECT_LABEL_SCALE_TIME"));
            });
            // restore showing card
            s.Insert(time +
                     GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME") +
                     GameConfig.F("BATTLE_CARD_EFFECT_HIGHLIGHT_PAUSE") * 2.0f +
                     GameConfig.F("BATTLE_CARD_EFFECT_LABEL_SCALE_TIME") +
                     GameConfig.F("BATTLE_CARD_EFFECT_LABEL_PAUSE"), item.transform.DOMove(item.TempPos, GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME")));
            s.Insert(time +
                     GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME") +
                     GameConfig.F("BATTLE_CARD_EFFECT_HIGHLIGHT_PAUSE") * 2.0f +
                     GameConfig.F("BATTLE_CARD_EFFECT_LABEL_SCALE_TIME") +
                     GameConfig.F("BATTLE_CARD_EFFECT_LABEL_PAUSE"), item.transform.DOScale(GameConfig.F("BATTLE_CARD_SCALE"), GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME")));
        }
        for (int i = 0; i < dmods.Count; i++)
        {
            BattleCardModifier  bcm  = dmods.ElementAt(i);
            CardObjectBehaviour item = CardObjectBehaviour.GetCOB(bcm.guid);
            float time = endTimeNode + (GameConfig.F("BATTLE_CARD_INTERVAL") +
                                        GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME") * 2.0f +
                                        GameConfig.F("BATTLE_CARD_EFFECT_HIGHLIGHT_PAUSE") * 2.0f +
                                        GameConfig.F("BATTLE_CARD_EFFECT_LABEL_SCALE_TIME") +
                                        GameConfig.F("BATTLE_CARD_EFFECT_LABEL_PAUSE")) * i;
            // pop out active card
            s.Insert(time, item.transform.DOMove(new Vector3(item.TempPos.x, item.TempPos.y, item.TempPos.z - 1f), GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME")));
            s.Insert(time, item.transform.DOScale(GameConfig.F("BATTLE_CARD_EFFECT_SCALE"), GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME")));
            // show effect values
            GameObject buffLabel   = DefenderBattlePoint.transform.Find("Buff").gameObject;
            GameObject debuffLabel = DefenderBattlePoint.transform.Find("Debuff").gameObject;
            GameObject activeLabel = null;
            if (bcm.mod > 0)
            {
                buffLabel.SetActive(false);
                debuffLabel.SetActive(false);
                buffLabel.GetComponent <BuffLabelBehaviour>().Text.text = "+" + bcm.mod.ToString();
                activeLabel = buffLabel;
            }
            else
            {
                buffLabel.SetActive(false);
                debuffLabel.SetActive(false);
                debuffLabel.GetComponent <BuffLabelBehaviour>().Text.text = bcm.mod.ToString();
                activeLabel = debuffLabel;
            }
            s.InsertCallback(time +
                             GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME") +
                             GameConfig.F("BATTLE_CARD_EFFECT_HIGHLIGHT_PAUSE"), () =>
            {
                activeLabel.SetActive(true);
                activeLabel.transform.DOScale(GameConfig.F("BATTLE_CARD_EFFECT_LABEL_SCALE"), GameConfig.F("BATTLE_CARD_EFFECT_LABEL_SCALE_TIME"));
            });
            s.InsertCallback(time +
                             GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME") +
                             GameConfig.F("BATTLE_CARD_EFFECT_HIGHLIGHT_PAUSE") * 2.0f +
                             GameConfig.F("BATTLE_CARD_EFFECT_LABEL_SCALE_TIME") +
                             GameConfig.F("BATTLE_CARD_EFFECT_LABEL_PAUSE"), () =>
            {
                activeLabel.SetActive(false);
                activeLabel.transform.DOScale(1.0f, GameConfig.F("BATTLE_CARD_EFFECT_LABEL_SCALE_TIME"));
            });
            // restore showing card
            s.Insert(time +
                     GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME") +
                     GameConfig.F("BATTLE_CARD_EFFECT_HIGHLIGHT_PAUSE") * 2.0f +
                     GameConfig.F("BATTLE_CARD_EFFECT_LABEL_SCALE_TIME") +
                     GameConfig.F("BATTLE_CARD_EFFECT_LABEL_PAUSE"), item.transform.DOMove(item.TempPos, GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME")));
            s.Insert(time +
                     GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME") +
                     GameConfig.F("BATTLE_CARD_EFFECT_HIGHLIGHT_PAUSE") * 2.0f +
                     GameConfig.F("BATTLE_CARD_EFFECT_LABEL_SCALE_TIME") +
                     GameConfig.F("BATTLE_CARD_EFFECT_LABEL_PAUSE"), item.transform.DOScale(GameConfig.F("BATTLE_CARD_SCALE"), GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME")));
        }
        endTimeNode += (GameConfig.F("BATTLE_CARD_INTERVAL") +
                        GameConfig.F("BATTLE_CARD_EFFECT_SCALE_TIME") * 2.0f +
                        GameConfig.F("BATTLE_CARD_EFFECT_HIGHLIGHT_PAUSE") * 2.0f +
                        GameConfig.F("BATTLE_CARD_EFFECT_LABEL_SCALE_TIME") +
                        GameConfig.F("BATTLE_CARD_EFFECT_LABEL_PAUSE")) * Math.Max(amods.Count, dmods.Count);

        // Do damage
        s.InsertCallback(endTimeNode, () =>
        {
            GameObject defSlashSFX         = Instantiate(Resources.Load("prefabs/SlashEffect")) as GameObject;
            defSlashSFX.transform.position = defenderAnchor;
            Destroy(defSlashSFX, GameConfig.F("BATTLE_SLASH_TIME"));
            GameObject atkSlashSFX         = Instantiate(Resources.Load("prefabs/SlashEffect")) as GameObject;
            atkSlashSFX.transform.position = attackerAnchor;
            Destroy(atkSlashSFX, GameConfig.F("BATTLE_SLASH_TIME"));
        });
        endTimeNode += GameConfig.F("BATTLE_SLASH_TIME");

        endTimeNode += GameConfig.F("BATTLE_AFTER_DAMAGE_INTV");

        // Clean up
        s.InsertCallback(endTimeNode, () =>
        {
            // Move deads to grave
            for (int i = 0; i < akilled.Count; i++)
            {
                CardObjectBehaviour cob = CardObjectBehaviour.GetCOB(akilled.ElementAt(i).guid);
                cob.Owner.CSob[acs].MoveToGrave(s, endTimeNode + GameConfig.F("BATTLE_CARD_INTERVAL") * i, cob.gameObject);
            }
            for (int i = 0; i < dkilled.Count; i++)
            {
                CardObjectBehaviour cob = CardObjectBehaviour.GetCOB(dkilled.ElementAt(i).guid);
                cob.Owner.CSob[dcs].MoveToGrave(s, endTimeNode + GameConfig.F("BATTLE_CARD_INTERVAL") * i, cob.gameObject);
            }
            // Update player health/slot power
            if (aname.Equals(LocalPlayer.PlayerName))
            {
                LocalPlayer.UpdateCardSlotPower(acs, aaap);
                EnemyPlayer.UpdateCardSlotPower(dcs, daap);
                LocalPlayer.UpdateHealth(ahp);
                EnemyPlayer.UpdateHealth(dhp);
            }
            else if (dname.Equals(LocalPlayer.PlayerName))
            {
                LocalPlayer.UpdateCardSlotPower(dcs, daap);
                EnemyPlayer.UpdateCardSlotPower(acs, aaap);
                LocalPlayer.UpdateHealth(dhp);
                EnemyPlayer.UpdateHealth(ahp);
            }
            for (int i = 0; i < atouched.Count; i++)
            {
                Card item = atouched.ElementAt(i);
                CardObjectBehaviour cob = CardObjectBehaviour.GetCOB(item.guid);
                cob.UpdatePower(item.power);
            }
            for (int i = 0; i < dtouched.Count; i++)
            {
                Card item = dtouched.ElementAt(i);
                CardObjectBehaviour cob = CardObjectBehaviour.GetCOB(item.guid);
                cob.UpdatePower(item.power);
            }
        });

        s.OnComplete(() =>
        {
            if (aname.Equals(LocalPlayer.PlayerName))
            {
                LocalPlayer.CSob[acs].RerenderCards();
                EnemyPlayer.CSob[dcs].RerenderCards();
            }
            else if (dname.Equals(LocalPlayer.PlayerName))
            {
                EnemyPlayer.CSob[acs].RerenderCards();
                LocalPlayer.CSob[dcs].RerenderCards();
            }
            SetUIState(UIState.ACTION);
        });
    }