<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Transfiguration/Spells/VanishingStep.cs<|end_filename|>
﻿using System.Collections.Generic;

namespace HarryPotterUnity.Cards.Transfiguration.Spells
{
    public class VanishingStep : BaseSpell
    {
        protected override void SpellAction(List<BaseCard> targets)
        {
            this.Player.OppositePlayer.OnNextTurnStartEvent += () => this.Player.OppositePlayer.AddActions(-1);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Charms/Locations/WandShop.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Utils;

namespace HarryPotterUnity.Cards.Charms.Locations
{
    public class WandShop : BaseLocation
    {

        private IEnumerable<BaseLesson> AllLessons
        {
            get
            {
                return this.Player.InPlay.LessonsOfType(LessonTypes.Charms)
                    .Concat(this.Player.OppositePlayer.InPlay.LessonsOfType(LessonTypes.Charms))
                    .Cast<BaseLesson>();
            }
        }
        public override void OnEnterInPlayAction()
        {
            base.OnEnterInPlayAction();

            foreach (var lesson in this.AllLessons)
            {
                lesson.AmountLessonsProvided = 2;
            }

            this.Player.OnCardPlayedEvent += this.DoubleLessonsProvided;
            this.Player.OppositePlayer.OnCardPlayedEvent += this.DoubleLessonsProvided;
        }
        
        public override void OnExitInPlayAction()
        {
            foreach (var lesson in this.AllLessons)
            {
                lesson.AmountLessonsProvided = 1;
            }

            this.Player.OnCardPlayedEvent -= this.DoubleLessonsProvided;
            this.Player.OppositePlayer.OnCardPlayedEvent -= this.DoubleLessonsProvided;
        }

        private void DoubleLessonsProvided(BaseCard card, List<BaseCard> targets)
        {
            var lesson = card as BaseLesson;

            if (lesson != null && lesson.LessonType == LessonTypes.Charms)
            {
                lesson.AmountLessonsProvided = 2;
            }
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/BaseAdventure.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards
{
    public abstract class BaseAdventure : BaseCard, IPersistentCard
    {
        public virtual void OnInPlayBeforeTurnAction() { }
        public virtual void OnInPlayAfterTurnAction() { }

        protected abstract bool CanOpponentSolve();
        protected abstract void Solve();
        protected abstract void Reward();

        public virtual void OnEnterInPlayAction() { }
        public virtual void OnExitInPlayAction() { }

        public bool CanPerformInPlayAction()
        {
            bool isOppositePlayer = this.Player.IsLocalPlayer == false;

            return isOppositePlayer 
                && this.Player.OppositePlayer.CanUseActions() 
                && this.CanOpponentSolve();
        }

        public void OnInPlayAction(List<BaseCard> targets = null)
        {
            this.Player.Discard.Add(this);
            this.Solve();
            this.Reward();
        }
        
        protected override Type GetCardType()
        {
            return Type.Adventure;
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/CareOfMagicalCreatures/Items/GuideToHouseholdPests.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.BasicBehavior;
using HarryPotterUnity.Cards.PlayRequirements;
using UnityEngine;

namespace HarryPotterUnity.Cards.CareOfMagicalCreatures.Items
{
    [RequireComponent(typeof(InputRequirement))]
    public class GuideToHouseholdPests : ItemLessonProvider
    {
        public override List<BaseCard> GetInPlayActionTargets()
        {
            return this.Player.OppositePlayer.InPlay.Creatures.Concat(this.Player.InPlay.Creatures).ToList();
        }

        public override bool CanPerformInPlayAction()
        {
            return this.Player.IsLocalPlayer
                   && this.Player.CanUseActions();
        }

        public override void OnInPlayAction(List<BaseCard> targets = null)
        {
            if (targets != null)
            {
                this.Player.Discard.Add(this);

                var target = targets.First();
                ((BaseCreature)target).TakeDamage(4);

                this.Player.UseActions();
            }
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Game/Player.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Cards.PlayerConstraints;
using HarryPotterUnity.DeckGeneration;
using HarryPotterUnity.Enums;
using JetBrains.Annotations;
using UnityEngine;
using Type = HarryPotterUnity.Enums.Type;

namespace HarryPotterUnity.Game
{
    [UsedImplicitly]
    public class Player : MonoBehaviour
    {
        public Player OppositePlayer { get; set; }
        public Hand Hand { get; private set; }
        public Deck Deck { get; private set; }
        public InPlay InPlay { get; private set; }
        public Discard Discard { get; private set; }

        public List<BaseCard> AllCards
        {
            get { return this.Hand.Cards.Concat(this.Deck.Cards).Concat(this.InPlay.Cards).Concat(this.Discard.Cards).ToList(); }
        }

        private readonly HashSet<LessonTypes> _lessonTypesInPlay = new HashSet<LessonTypes>(); 
        public HashSet<LessonTypes> LessonTypesInPlay
        {
            get
            {
                this._lessonTypesInPlay.Clear();
                foreach (var providers in this.InPlay.Cards.OfType<ILessonProvider>())
                {
                    this._lessonTypesInPlay.Add(providers.LessonType);
                }
                return this._lessonTypesInPlay;
            }
        }
 
        public int AmountLessonsInPlay
        {
            get
            {
                return this.InPlay.Cards.OfType<ILessonProvider>().Sum(card => card.AmountLessonsProvided);
            }
        }

        public HashSet<Type> TypeImmunity { get; private set; }

        public int ActionsAvailable { get; private set; }

        public bool IsLocalPlayer { get; set; }
        public byte NetworkId { get; set; }
        public List<IPlayerConstraint> Constraints { get; private set; }

        public delegate void TurnEvent();
        public delegate void CardPlayedEvent(BaseCard card, List<BaseCard> targets = null);
        public delegate void DamageTakenEvent(BaseCard source, int amount);

        public event TurnEvent OnNextTurnStartEvent;
        public event TurnEvent OnStartTurnEvent;
        public event TurnEvent OnEndTurnEvent;
        public event CardPlayedEvent OnCardPlayedEvent;
        public event DamageTakenEvent OnDamageTakenEvent;

        public void OnDestroy()
        {
            this.OnNextTurnStartEvent = null;
            this.OnStartTurnEvent = null;
            this.OnEndTurnEvent = null;
            this.OnCardPlayedEvent = null;
            this.OnDamageTakenEvent = null;
        }

        public void Awake()
        {
            this.ActionsAvailable = 0;

            this.Hand = this.transform.GetComponentInChildren<Hand>();
            this.Deck = this.transform.GetComponentInChildren<Deck>();
            this.InPlay = this.transform.GetComponentInChildren<InPlay>();
            this.Discard = this.transform.GetComponentInChildren<Discard>();

            this.TypeImmunity = new HashSet<Type>();
            this.Constraints = new List<IPlayerConstraint>();
        }

        public void InitDeck(List<LessonTypes> selectedLessons)
        {
            List<BaseCard> cards;
            BaseCard startingCharacter;

            if (GameManager.DebugModeEnabled)
            {
                var prebuiltCards = GameManager.GetPlayerTestDeck(this.NetworkId);
                cards = DeckGenerator.GenerateDeck(prebuiltCards, selectedLessons);
                startingCharacter = GameManager.GetPlayerTestCharacter(this.NetworkId);
            }
            else
            {
                cards = DeckGenerator.GenerateDeck(selectedLessons);
                startingCharacter = DeckGenerator.GetRandomCharacter();
            }

            this.Deck.Initialize(cards, startingCharacter);
        }

        /// <summary>
        /// Remember to always call this function after the card's effect,
        /// otherwise the action will be used before the card activates.
        /// possibly beginning the next turn and causing a chain of actions to occur
        /// such as drawing the opponent's card and dealing his creature's damage before your card's effect activates.
        /// </summary>
        /// <param name="amount"></param>
        public void UseActions(int amount = 1)
        {
            this.ActionsAvailable -= amount;
            if (this.ActionsAvailable <= 0) this.EndTurn(); 
        }
        
        public void AddActions(int amount)
        {
            this.ActionsAvailable += amount;
        }

        public void InvokeCardPlayedEvent(BaseCard card, List<BaseCard> targets = null)
        {
            if (this.OnCardPlayedEvent != null) this.OnCardPlayedEvent(card, targets);
        }

        public void BeginTurn()
        {
            if (this.OnNextTurnStartEvent != null)
            {
                this.OnNextTurnStartEvent();
                this.OnNextTurnStartEvent = null;
            }

            if (this.OnStartTurnEvent != null)
            {
                this.OnStartTurnEvent();
            }

            foreach (var card in this.InPlay.Cards.Cast<IPersistentCard>())
            {
                card.OnInPlayBeforeTurnAction();
            }

            
             //TODO: some adventures prevent this step from happening, need a check of some kind.
            this.Deck.DrawCard();

            this.AddActions(2);
            if (this.ActionsAvailable < 1)
            {
                this.ActionsAvailable = 1;
            }

            foreach (var creature in this.InPlay.Creatures.Cast<BaseCreature>())
            {
                this.OppositePlayer.TakeDamage(creature, creature.DamagePerTurn);
            }
        }

        private void EndTurn()
        {
            if (this.OnEndTurnEvent != null)
            {
                this.OnEndTurnEvent();
            }
            this.ActionsAvailable = 0;

            foreach (var card in this.InPlay.Cards.Cast<IPersistentCard>())
            {
                card.OnInPlayAfterTurnAction();
            }

            this.Hand.AdjustHandSpacing();
            this.OppositePlayer.BeginTurn();
        }

        public bool CanUseActions(int amount = 1)
        {
            return this.ActionsAvailable >= amount;
        }

        public void DrawInitialHand()
        {
            for (int i = 0; i < 7; i++)
            {
                var card = this.Deck.TakeTopCard();
                this.Hand.Add(card, preview: false, adjustSpacing: false);
            }       
        }
        
        public void TakeDamage(BaseCard sourceCard, int amount)
        {
            if (amount <= 0) return;
            
            var cards = new List<BaseCard>();

            for (int i = 0; i < amount; i++)
            {
                if (this.TypeImmunity.Contains(sourceCard.Type))
                {
                    continue;
                }

                var card = this.Deck.TakeTopCard();

                if (card == null)
                {
                    break;
                }
                cards.Add(card);
            }

            this.Discard.AddAll(cards);

            if (this.OnDamageTakenEvent != null && sourceCard != null)
            {
                this.OnDamageTakenEvent(sourceCard, cards.Count); 
            }
        }
        
        public void DisableAllCards()
        {
            this.Deck.gameObject.layer = GameManager.IGNORE_RAYCAST_LAYER;
            GameManager.DisableCards(this.Hand.Cards);
            GameManager.DisableCards(this.InPlay.Cards);
        }

        public void EnableAllCards()
        {
            this.Deck.gameObject.layer = GameManager.DECK_LAYER;
            GameManager.EnableCards(this.Hand.Cards);
            GameManager.EnableCards(this.InPlay.Cards);
        }

        public void ClearHighlightComponent()
        {
            foreach (var card in this.Hand.Cards)
            {
                card.RemoveHighlight();
            }

            foreach (var card in this.InPlay.Cards)
            {
                card.RemoveHighlight();
            }

            foreach (var card in this.Discard.Cards)
            {
                card.RemoveHighlight();
            }
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Transfiguration/Spells/Alchemy.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;

namespace HarryPotterUnity.Cards.Transfiguration.Spells
{
    public class Alchemy : BaseSpell
    {
        protected override void SpellAction(List<BaseCard> targets)
        {
            var lessons = this.Player.Deck.Lessons.Take(2);

            this.Player.Hand.AddAll(lessons);
            this.Player.Deck.Shuffle();
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Transfiguration/Spells/PetrificusTotalus.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.PlayRequirements;
using HarryPotterUnity.Enums;
using UnityEngine;

namespace HarryPotterUnity.Cards.Transfiguration.Spells
{
    [RequireComponent(typeof(InputRequirement))]
    public class PetrificusTotalus : BaseSpell
    {
        protected override bool MeetsAdditionalPlayRequirements()
        {
            return this.Player.OppositePlayer.
                Discard.Cards.Any(c => c.Type == Type.Lesson);
        }

        public override List<BaseCard> GetFromHandActionTargets()
        {
            return this.Player.OppositePlayer.InPlay.Creatures;
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            if (targets.Count != 1)
            {
                Debug.LogError("More than 1 input sent to Petrificus Totalus");
                return;
            }

            var target = targets.First();
            var player = target.Player;

            var lesson = player.Discard.Cards.First(c => c.Type == Type.Lesson);
            
            player.Discard.Add(target);
            
            player.InPlay.Add(lesson);
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Charms/Spells/EndlessSandwiches.cs<|end_filename|>
﻿using System.Collections.Generic;

namespace HarryPotterUnity.Cards.Charms.Spells
{
    public class EndlessSandwiches : BaseSpell
    {
        protected override void SpellAction(List<BaseCard> targets)
        {
            int amountCardsToDraw = 7 - this.Player.Hand.Cards.Count;
            var cardsToDraw = new List<BaseCard>();

            while (amountCardsToDraw-- > 0)
            {
                var card = this.Player.Deck.TakeTopCard();
                if (card == null) break;

                cardsToDraw.Add(this.Player.Deck.TakeTopCard() );
            }

            this.Player.Hand.AddAll(cardsToDraw);
        }

        protected override bool MeetsAdditionalPlayRequirements()
        {
            return this.Player.Hand.Cards.Count <= 7;
        }
    }
}


<|start_filename|>Assets/Editor/HarryPotterExtensions/CardProcessing.cs<|end_filename|>
﻿using System.Linq;
using HarryPotterUnity.Cards.PlayRequirements;
using HarryPotterUnity.DeckGeneration.Requirements;
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;

namespace HarryPotterExtensions
{
    public static class CardProcessing
    {    
        /// <summary>
        /// Template for applying changes to all card objects in the library
        /// </summary>
        [UsedImplicitly]
        //[MenuItem("Harry Potter TCG/Find Requirement")]
        public static void DoWork()
        {

            //var assetFolderPaths = AssetDatabase.GetAllAssetPaths().Where(path => path.EndsWith(".prefab") && path.Contains("/Cards/"));

            //foreach (string path in assetFolderPaths)
            //{
            //    var obj = (GameObject)AssetDatabase.LoadAssetAtPath(path, typeof(GameObject));

            //    if (obj.GetComponent<DeckDoesNotContainCardWithNameRequirement>() != null)
            //    {
            //        Debug.Log(obj.name);
            //    }
            //}
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Transfiguration/Locations/DumbledoresOffice.cs<|end_filename|>
﻿using System.Linq;
using HarryPotterUnity.Cards.PlayRequirements;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Game;

namespace HarryPotterUnity.Cards.Transfiguration.Locations
{
    public class DumbledoresOffice : BaseLocation
    {
        public override void OnEnterInPlayAction()
        {
            base.OnEnterInPlayAction();

            var itemLessonRequirements =
                GameManager.AllCards.Where(card => card.Type == Type.Item)
                .Select(card => card.GetComponent<LessonRequirement>())
                .ToList();

            foreach (var requirement in itemLessonRequirements)
            {
                requirement.AmountRequired -= 3;

                if (requirement.AmountRequired < 1)
                {
                    requirement.AmountRequired = 1;
                }
            }
        }

        public override void OnExitInPlayAction()
        {
            base.OnExitInPlayAction();

            var itemLessonRequirements =
                GameManager.AllCards.Where(card => card.Type == Type.Item)
                .Select(card => card.GetComponent<LessonRequirement>())
                .ToList();

            foreach (var req in itemLessonRequirements)
            {
                req.ResetRequirement();
            }
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/BasicBehavior/ItemLessonBook.cs<|end_filename|>
﻿using System.Collections.Generic;

namespace HarryPotterUnity.Cards.BasicBehavior
{
    public class ItemLessonBook : ItemLessonProvider
    {
        public override bool CanPerformInPlayAction()
        {
            return this.Player.CanUseActions() && this.Player.IsLocalPlayer;
        }

        public override void OnInPlayAction(List<BaseCard> targets = null)
        {
            this.Player.Discard.Add(this);

            this.Player.Deck.DrawCard();
            this.Player.Deck.DrawCard();
            this.Player.Deck.DrawCard();

            this.Player.UseActions();
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Items/NimbusTwoThousand.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Cards.BasicBehavior;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Utils;

namespace HarryPotterUnity.Cards.Quidditch.Items
{
    public class NimbusTwoThousand : ItemLessonProvider
    {
        private bool _hasEffectedTriggered;
        protected int _damageAmount;

        protected override void Start()
        {
            base.Start();
            this._damageAmount = 2;
        }
        public override void OnInPlayBeforeTurnAction()
        {
            this._hasEffectedTriggered = false;
        }

        public override void OnEnterInPlayAction()
        {
            this.Player.OnCardPlayedEvent += this.AddDamageToQuidditchCards;
        }

        private void AddDamageToQuidditchCards(BaseCard cardPlayed, List<BaseCard> targets)
        {
            if (cardPlayed.IsQuidditchDamageCard() == false) return;
            if (this._hasEffectedTriggered) return;

            this._hasEffectedTriggered = true;

            ((IDamageSpell) cardPlayed).DamageAmount += this._damageAmount;
        }
        
        public override void OnExitInPlayAction()
        {
            this.Player.OnCardPlayedEvent -= this.AddDamageToQuidditchCards;
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Characters/MadamPomfrey.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;

namespace HarryPotterUnity.Cards.Characters
{
    public class MadamPomfrey : BaseCharacter
    {
        private bool HasUsedAbility { get; set; }

        public override bool CanPerformInPlayAction()
        {
            return this.HasUsedAbility == false 
                && this.Player.CanUseActions()
                && this.Player.IsLocalPlayer;
        }

        public override void OnInPlayAction(List<BaseCard> targets = null)
        {
            this.HasUsedAbility = true;

            var cards = this.Player.Discard.NonHealingCards.Take(12);

            this.Player.Deck.AddAll(cards);

            this.Player.Deck.Shuffle();
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/CareOfMagicalCreatures/Creatures/VenomousTentacula.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Utils;

namespace HarryPotterUnity.Cards.CareOfMagicalCreatures.Creatures
{
    public class VenomousTentacula : BaseCreature
    {
        public override bool CanPerformInPlayAction()
        {
            return this.Player.CanUseActions() 
                && this.Player.IsLocalPlayer
                && this.Player.Discard.LessonsOfType(LessonTypes.Potions).Any();
        }

        public override void OnInPlayAction(List<BaseCard> targets = null)
        {
            this.Player.Discard.Add(this);

            var lesson = this.Player.Discard.LessonsOfType(LessonTypes.Potions).First();

            this.Player.InPlay.Add(lesson);

            this.Player.UseActions();
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Tween/AsyncMoveTween.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using HarryPotterUnity.Cards;
using UnityEngine;

namespace HarryPotterUnity.Tween
{
    public class AsyncMoveTween : ITweenObject
    {

        public List<BaseCard> Targets { get; set; }
        public Func<BaseCard, Vector3> GetPosition { get; set; }
        public float TimeUntilNextTween { get; set; }
        
        public float CompletionTime
        {
            get { return 0.2f; }
        }
        
        public void ExecuteTween()
        {
            foreach (var target in this.Targets)
            {
                iTween.MoveTo(target.gameObject, iTween.Hash(
                "time", 0.2f,
                "position", this.GetPosition(target),
                "easetype", iTween.EaseType.EaseInOutSine,
                "islocal", true
                ));    
            }
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Interfaces/IPersistentCard.cs<|end_filename|>
﻿using System.Collections.Generic;

namespace HarryPotterUnity.Cards.Interfaces
{
    public interface IPersistentCard
    {
        void OnInPlayBeforeTurnAction();
        void OnInPlayAfterTurnAction();

        bool CanPerformInPlayAction();
        //TODO: Default behavior for OnSelectedAction to preview the card before activating effect.
        void OnInPlayAction(List<BaseCard> targets = null); 

        void OnEnterInPlayAction();
        void OnExitInPlayAction();
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/DeckGeneration/Requirements/DeckCardLimitRequirement.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.DeckGeneration.Requirements
{
    public class DeckCardLimitRequirement : MonoBehaviour, IDeckGenerationRequirement
    {
        [SerializeField] private int _maximumAmountAllowed;

        private BaseCard _cardInfo;

        public int MaximumAmountAllowed
        {
            private get { return this._maximumAmountAllowed; }
            set { this._maximumAmountAllowed = value; }
        }

        [UsedImplicitly]
        private void Start()
        {
            this._cardInfo = this.GetComponent<BaseCard>();
        }

        public bool MeetsRequirement(List<BaseCard> currentDeck)
        {
            return currentDeck.Count(c => c == this._cardInfo) < this.MaximumAmountAllowed;
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Charms/Items/BrokenWand.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Cards.BasicBehavior;

namespace HarryPotterUnity.Cards.Charms.Items
{
    public class BrokenWand : ItemLessonProvider
    {
        public override void OnInPlayAction(List<BaseCard> targets = null)
        {
            var card = this.Player.Deck.TakeTopCard();

            if (card is BaseLesson)
            {
                this.Player.InPlay.Add(card);
            }
            else
            {
                this.Player.Discard.Add(card);
            }

            this.Player.UseActions();
        }

        public override bool CanPerformInPlayAction()
        {
            return this.Player.CanUseActions() && this.Player.IsLocalPlayer;
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/CareOfMagicalCreatures/Creatures/Unicorn.cs<|end_filename|>
﻿namespace HarryPotterUnity.Cards.CareOfMagicalCreatures.Creatures
{
    public class Unicorn : BaseCreature {

        public override void OnInPlayBeforeTurnAction()
        {
            this.Player.AddActions(1);
        }

        public override void OnEnterInPlayAction()
        {
            base.OnEnterInPlayAction();
            this.Player.AddActions(1);
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/CareOfMagicalCreatures/Creatures/CobraLily.cs<|end_filename|>
﻿namespace HarryPotterUnity.Cards.CareOfMagicalCreatures.Creatures
{
    public class CobraLily : BaseCreature
    {
        public override void OnInPlayAfterTurnAction()
        {
            this.Heal(this.MaxHealth);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Charms/Spells/WizardCrackers.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards.Charms.Spells
{
    public class WizardCrackers : BaseSpell
    {
        protected override void SpellAction(List<BaseCard> targets)
        {
            var card = this.Player.Deck.TakeTopCard();

            if (card.Type == Type.Lesson)
            {
                this.Player.InPlay.Add(card);
            }
            else
            {
                this.Player.Hand.Add(card);
            }
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Transfiguration/Spells/RavenToWritingDesk.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.PlayRequirements;
using UnityEngine;

namespace HarryPotterUnity.Cards.Transfiguration.Spells
{
    [RequireComponent(typeof(InputRequirement))]
    public class RavenToWritingDesk : BaseSpell
    {
        public override List<BaseCard> GetFromHandActionTargets()
        {
            return this.Player.OppositePlayer.InPlay.Creatures;
        }

        protected override bool MeetsAdditionalPlayRequirements()
        {
            return this.Player.OppositePlayer.InPlay.Creatures.Count >= 2;
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            var target = targets.First();

            this.Player.OppositePlayer.Discard.Add(target);
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Adventures/RidingTheCentaur.cs<|end_filename|>
﻿using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards.Adventures
{
    public class RidingTheCentaur : BaseAdventure
    {
        public override void OnInPlayAfterTurnAction()
        {
            this.Player.TypeImmunity.Add(Type.Creature);
            this.Player.OnNextTurnStartEvent += () => this.Player.TypeImmunity.Remove(Type.Creature);
        }

        protected override bool CanOpponentSolve()
        {
            return this.Player.OppositePlayer.InPlay.Cards.Count >= 4;

        }

        protected override void Solve()
        {
            int amountToReturn = 4;

            for (int i = amountToReturn; i > 0; i--)
            {
                var card = this.Player.OppositePlayer.InPlay.GetRandomCard();

                this.Player.OppositePlayer.Hand.Add(card);
            }
        }

        protected override void Reward()
        {
            this.Player.OppositePlayer.Deck.DrawCard();
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/BaseCreature.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Enums;
using JetBrains.Annotations;
using UnityEngine;
using UnityEngine.UI;

namespace HarryPotterUnity.Cards
{
    public class BaseCreature : BaseCard, IPersistentCard {

        [Header("Creature Settings")]
        [SerializeField, UsedImplicitly]
        protected int _damagePerTurn;

        [SerializeField, UsedImplicitly]
        protected int _health;

        private GameObject _uiCanvas;
        private Text _healthLabel;
        protected Text _attackLabel;
        
        public int DamagePerTurn { get { return this._damagePerTurn; } }

        public int MaxHealth { get; private set; }

        protected override void Start()
        {
            base.Start();
            this.LoadUiOverlay();

            this.MaxHealth = this._health;
        }

        private void LoadUiOverlay()
        {
            var resource = Resources.Load("CreatureUIOverlay");
            this._uiCanvas = (GameObject) Instantiate(resource);

            this._uiCanvas.transform.position = this.transform.position - Vector3.back;
            this._uiCanvas.transform.SetParent(this.transform, true);
            this._uiCanvas.transform.localRotation = this.Player.IsLocalPlayer ? Quaternion.identity : Quaternion.Euler(0f,0f,180f);

            this._healthLabel = this._uiCanvas.transform.FindChild("HealthLabel").gameObject.GetComponent<Text>();
            this._attackLabel = this._uiCanvas.transform.FindChild("AttackLabel").gameObject.GetComponent<Text>();

            this._healthLabel.text = this._health.ToString();
            this._attackLabel.text = this._damagePerTurn.ToString();

            this._uiCanvas.SetActive(false);
        }
        
        public virtual void OnEnterInPlayAction()
        {
            this._uiCanvas.SetActive(true);
        }

        public virtual void OnExitInPlayAction()
        {
            this._uiCanvas.SetActive(false);
        }

        public void TakeDamage(int amount)
        {
            this._health -= amount;
            this._healthLabel.text = Mathf.Clamp(this._health, 0, int.MaxValue).ToString();

            if (this._health <= 0)
            {
                this.Player.Discard.Add(this);
            }
        }

        public void Heal(int amount)
        {
            this._health = Mathf.Clamp(this._health + amount, 0, this.MaxHealth);

            this._healthLabel.text = this._health.ToString();
        }

        public virtual bool CanPerformInPlayAction() { return false; }
        public virtual void OnInPlayBeforeTurnAction() { }
        public virtual void OnInPlayAfterTurnAction() { }
        public virtual void OnInPlayAction(List<BaseCard> targets = null) { }

        protected sealed override Type GetCardType()
        {
            return Type.Creature;
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/BasicBehavior/HealingSpell.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.Cards.BasicBehavior
{
    public class HealingSpell : BaseSpell
    {
        [Header("Healing Spell Settings")]
        [SerializeField, UsedImplicitly]
        private int _healingAmount;

        [SerializeField, UsedImplicitly]
        private bool _shuffleDeckAfterHeal;

        protected override void SpellAction(List<BaseCard> targets)
        {
            var cards = this.Player.Discard.NonHealingCards.Take(this._healingAmount);

            this.Player.Deck.AddAll(cards);
            if (this._shuffleDeckAfterHeal)
            {
                this.Player.Deck.Shuffle();
            }
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Utils/StaticCoroutine.cs<|end_filename|>
﻿using System.Collections;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.Utils
{
    public class StaticCoroutine : MonoBehaviour
    {

        private static StaticCoroutine _instance;
        private static StaticCoroutine Instance
        {
            get
            {
                if (_instance != null) return _instance;
                
                _instance = FindObjectOfType(typeof(StaticCoroutine)) as StaticCoroutine ??
                             new GameObject("TweenQueueManager").AddComponent<StaticCoroutine>();

                return _instance;
            }
        }

        private void Awake()
        {
            if (_instance == null)
            {
                _instance = this;
            }
        }

        private IEnumerator Perform(IEnumerator coroutine)
        {
            yield return this.StartCoroutine(coroutine);
            Die();
        }

        /// <summary>
        /// Place your lovely static IEnumerator in here and witness magic!
        /// </summary>
        /// <param name="coroutine">Static IEnumerator</param>
        public static void DoCoroutine(IEnumerator coroutine)
        {
            Instance.StartCoroutine(Instance.Perform(coroutine)); //this will launch the coroutine on our instance
        }

        public static void Die()
        {
            if (_instance != null)
            {
                _instance.StopAllCoroutines();
                Destroy(_instance.gameObject);
            }
            _instance = null;
        }

        [UsedImplicitly]
        private void OnApplicationQuit()
        {
            _instance = null;
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Game/InputGatherer.cs<|end_filename|>
﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards;
using HarryPotterUnity.Cards.PlayRequirements;
using HarryPotterUnity.Enums;
using UnityEngine;

namespace HarryPotterUnity.Game
{
    [RequireComponent(typeof(BaseCard))]
    [RequireComponent(typeof(InputRequirement))]
    public class InputGatherer : MonoBehaviour
    {
        private BaseCard _cardInfo;

        private int _fromHandInputRequired;
        private int _inPlayInputRequired;

        private static int LayerMask
        {
            get { return 1 << GameManager.VALID_CHOICE_LAYER; }
        }

        private InputGatherMode GatherMode { get; set; }

        private void Start()
        {
            this._cardInfo = this.GetComponent<BaseCard>();
            var requirement = this.GetComponent<InputRequirement>();

            if (requirement == null)
            {
                Debug.LogError("No Input Requirement Attached to this card");
                return;
            }

            this._fromHandInputRequired = requirement.FromHandActionInputRequired;
            this._inPlayInputRequired = requirement.InPlayActionInputRequired;

        }

        public void GatherInput(InputGatherMode mode)
        {
            this.GatherMode = mode;
            this._cardInfo.Player.DisableAllCards();
            this._cardInfo.Player.OppositePlayer.DisableAllCards();

            var validCards = this.GetValidTargets();

            foreach (var card in validCards)
            {
                card.SetHighlight();
                card.Enable();
                card.gameObject.layer = GameManager.VALID_CHOICE_LAYER;
            }

            GameManager.IsInputGathererActive = true;

            this.StartCoroutine(this.WaitForPlayerInput());
        }

        private IEnumerator WaitForPlayerInput()
        {
            int inputRequired = this.GetInputRequired();

            if (inputRequired <= 0) throw new Exception("Input required field is not set or set to a negative value!");

            var selectedCards = new List<BaseCard>();

            while (selectedCards.Count < inputRequired)
            {
                if (Input.GetKeyUp(KeyCode.Mouse0) && this._cardInfo.Player.IsLocalPlayer)
                {
                    var ray = Camera.main.ScreenPointToRay(Input.mousePosition);

                    RaycastHit hit;
                    if (Physics.Raycast(ray, out hit, 1000f, LayerMask))
                    {
                        var target = hit.transform.gameObject.GetComponent<BaseCard>();
                        selectedCards.Add(target);

                        target.SetSelected();

                        if (selectedCards.Count == inputRequired)
                        {
                            foreach (var card in selectedCards)
                            {
                                card.RemoveHighlight();
                            }

                            this.ExecuteAction(selectedCards);
                        }
                    }
                }
                yield return null;
            }
        }

        private int GetInputRequired()
        {
            switch (this.GatherMode)
            {
                case InputGatherMode.FromHandAction:
                    return this._fromHandInputRequired;
                case InputGatherMode.InPlayAction:
                    return this._inPlayInputRequired;
            }
            return 0;
        }

        private List<BaseCard> GetValidTargets()
        {
            switch (this.GatherMode)
            {
                case InputGatherMode.FromHandAction:
                    return this._cardInfo.GetFromHandActionTargets();
                case InputGatherMode.InPlayAction:
                    return this._cardInfo.GetInPlayActionTargets();
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        private void ExecuteAction(List<BaseCard> selectedCards)
        {
            var selectedCardIds = selectedCards.Select(c => c.NetworkId).ToArray();

            switch (this.GatherMode)
            {
                case InputGatherMode.FromHandAction:
                    GameManager.Network.RPC("ExecuteInputCardById", PhotonTargets.All, this._cardInfo.NetworkId, selectedCardIds);
                    break;
                case InputGatherMode.InPlayAction:
                    GameManager.Network.RPC("ExecuteInPlayInputCardById", PhotonTargets.All, this._cardInfo.NetworkId, selectedCardIds);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            GameManager.IsInputGathererActive = false;
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Utils/Extensions.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Game;
using UnityEngine;

namespace HarryPotterUnity.Utils
{
    public static class Extensions
    {
        public static IEnumerable<BaseCard> LessonsOfType(this CardCollection collection, LessonTypes type)
        {
            return collection.Lessons.Where(c => ((BaseLesson)c).LessonType == type);
        }

        public static bool IsQuidditchDamageCard(this BaseCard card)
        {
            return card is IDamageSpell &&
                   card.Classification == ClassificationTypes.Quidditch;
        }

        public static Color WithAlpha(this Color color, float target)
        {
            color.a = target;
            return color;
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Spells/PenaltyShot.cs<|end_filename|>
﻿using System.Collections.Generic;

namespace HarryPotterUnity.Cards.Quidditch.Spells
{
    public class PenaltyShot : BaseSpell 
    {
        protected override void SpellAction(List<BaseCard> targets)
        {
            this.Player.Deck.DrawCard();
            this.Player.Deck.DrawCard();
            this.Player.Deck.DrawCard();
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/BasicBehavior/DirectDamageSpell.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Cards.Interfaces;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.Cards.BasicBehavior
{
    public class DirectDamageSpell : BaseSpell, IDamageSpell
    {
        
        [Header("Direct Damage Spell Settings"), Space(10)]
        [UsedImplicitly, SerializeField]
        private int _damageAmount;

        public int DamageAmount { get; set; }

        protected override void Start()
        {
            base.Start();
            this.DamageAmount = this._damageAmount;
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            this.Player.OppositePlayer.TakeDamage(this, this.DamageAmount);

            this.DamageAmount = this._damageAmount;
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/CareOfMagicalCreatures/Locations/HagridsHouse.cs<|end_filename|>
﻿using System.Linq;
using System.Net.Cache;
using HarryPotterUnity.Cards.PlayRequirements;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Game;

namespace HarryPotterUnity.Cards.CareOfMagicalCreatures.Locations
{
    public class HagridsHouse : BaseLocation
    {
        public override void OnEnterInPlayAction()
        {
            base.OnEnterInPlayAction();

            var removeLessonFromPlayRequirements = GameManager.AllCards
                .Select(c => c.GetComponent<RemoveLessonFromPlayRequirement>())
                .Where(req => req != null && req.LessonType == LessonTypes.Creatures);

            foreach (var requirement in removeLessonFromPlayRequirements)
            {
                requirement.ReturnToHand = true;
            }
        }

        public override void OnExitInPlayAction()
        {
            base.OnExitInPlayAction();

            var removeLessonFromPlayRequirements = GameManager.AllCards
                .Select(c => c.GetComponent<RemoveLessonFromPlayRequirement>())
                .Where(req => req != null && req.LessonType == LessonTypes.Creatures);

            foreach (var requirement in removeLessonFromPlayRequirements)
            {
                requirement.Reset();
            }
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Game/Deck.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Tween;
using UnityEngine;
using UnityLogWrapper;
using Random = UnityEngine.Random;

namespace HarryPotterUnity.Game
{
    public class Deck : CardCollection
    {
        private Player _player;

        private readonly Vector2 _deckPositionOffset = new Vector2(-355f, -124f);
        
        public BaseCard StartingCharacter { get; private set; }

        public event Action<Player> OnDeckIsOutOfCardsEvent;

        private void OnDestroy()
        {
            this.OnDeckIsOutOfCardsEvent = null;
        }

        private void Awake()
        {
            this._player = this.transform.GetComponentInParent<Player>();

            var col = this.gameObject.AddComponent<BoxCollider>();
            col.isTrigger = true;
            col.size = new Vector3(50f, 70f, 1f);
            col.center = new Vector3(this._deckPositionOffset.x, this._deckPositionOffset.y, 0f);
        }

        public void Initialize (IEnumerable<BaseCard> cardList, BaseCard startingCharacter)
        {
            this.Cards = new List<BaseCard>(cardList);
            this.StartingCharacter = startingCharacter;
            
            var cardPos = new Vector3(this._deckPositionOffset.x, this._deckPositionOffset.y);
            
            for (int i = 0; i < this.Cards.Count; i++)
            {
                this.Cards[i] = Instantiate(this.Cards[i] );

                this.Cards[i].transform.parent = this.transform;
                this.Cards[i].transform.localPosition = cardPos + Vector3.back * -16f;
                this.Cards[i].transform.rotation = Quaternion.Euler(new Vector3(0f, 180f, this._player.transform.rotation.eulerAngles.z));
                this.Cards[i].transform.position += i * Vector3.back * 0.2f;

                this.Cards[i].Player = this._player;

                this.Cards[i].NetworkId = GameManager.NetworkIdCounter++;
                
                GameManager.AllCards.Add(this.Cards[i]);

                this.Cards[i].CurrentCollection = this;
            }
        }

        public void SpawnStartingCharacter()
        {
            this.StartingCharacter = Instantiate(this.StartingCharacter);
            this.StartingCharacter.CurrentCollection = this;

            this.StartingCharacter.transform.parent = this.transform;
            this.StartingCharacter.Player = this._player;

            this.StartingCharacter.transform.rotation = Quaternion.Euler(new Vector3(0f, 180f, this._player.transform.rotation.eulerAngles.z));

            this.StartingCharacter.NetworkId = GameManager.NetworkIdCounter++;
            
            GameManager.AllCards.Add(this.StartingCharacter);

            this._player.InPlay.Add(this.StartingCharacter);
        }

        public BaseCard TakeTopCard()
        {
            BaseCard card = null;

            if (this.Cards.Count > 0)
            {
                card = this.Cards[this.Cards.Count - 1];
                this.Cards.RemoveAt(this.Cards.Count - 1);
                card.RemoveHighlight();
            }

            if (this.Cards.Count <= 0)
            {
                this.GameOver(); //TODO: call OnDeckIsOutOfCards event here
            }
            
            return card;
        }
        
        private void GameOver()
        {
            //TODO: Refactor this logic to occur on player class by subscribing to event
            this._player.DisableAllCards();
            this._player.OppositePlayer.DisableAllCards();

            if (this.OnDeckIsOutOfCardsEvent != null)
            {
                this.OnDeckIsOutOfCardsEvent(this._player);
            }
                
        }

        private void OnMouseUp()
        {
            if (!this.CanDrawCard()) return;

            GameManager.Network.RPC("ExecuteDrawActionOnPlayer", PhotonTargets.All, this._player.NetworkId);
        }

        private void OnMouseOver()
        {
            if (this.CanDrawCard())
            {
                this.Cards[this.Cards.Count - 1].SetHighlight();
            }
        }

        private void OnMouseExit()
        {
            if(this.Cards.Any()) this.Cards[this.Cards.Count - 1].RemoveHighlight();
        }

        private bool CanDrawCard()
        {
            if (!this._player.IsLocalPlayer) return false;

            return this.Cards.Count > 0 && this._player.CanUseActions();
        }

        public void DrawCard()
        {
            var card = this.TakeTopCard();
            if (card != null)
            {
                this._player.Hand.Add(card);
            }
        }

        public void Shuffle()
        {
            for (int i = this.Cards.Count-1; i >= 0; i--)
            {
                int random = Random.Range(0, i);

                var temp = this.Cards[i];
                this.Cards[i] = this.Cards[random];
                this.Cards[random] = temp;
            }

            GameManager.TweenQueue.AddTweenToQueue(new ShuffleDeckTween(this.Cards));
            
        }
        
        protected override void Remove(BaseCard card)
        {
            this.Cards.Remove(card);
        }

        /// <summary>
        /// Adds a card to the bottom of the deck
        /// </summary>
        public override void Add(BaseCard card)
        {
            this.MoveToThisCollection(card);

            this.Cards.Insert(0, card);

            card.transform.parent = this.transform;

            var cardPos = new Vector3(this._deckPositionOffset.x, this._deckPositionOffset.y, 16f);
            cardPos.z -= this.Cards.IndexOf(card) * 0.2f;

            var tween = new MoveTween
            {
                Target = card.gameObject,
                Position = cardPos,
                Time = 0.25f,
                Flip = FlipState.FaceDown,
                Rotate = TweenRotationType.NoRotate,
                OnCompleteCallback = () => card.State = State.InDeck
            };

            GameManager.TweenQueue.AddTweenToQueue(tween);
        }

        /// <summary>
        /// Last card in the list with be at the bottom of the deck.
        /// </summary>
        public override void AddAll(IEnumerable<BaseCard> cards)
        {
            foreach (var card in cards)
            {
                this.Add(card);
            }

            this.AdjustCardSpacing();
        }

        protected override void RemoveAll(IEnumerable<BaseCard> cards)
        {
            foreach (var card in cards)
            {
                this.Remove(card);
            }

            this.AdjustCardSpacing();
        }
        
        private void AdjustCardSpacing()
        {
            var tween = new AsyncMoveTween
            {
                Targets = this.Cards,
                GetPosition = this.GetTargetPositionForCard
            };
            GameManager.TweenQueue.AddTweenToQueue(tween);
        }

        private Vector3 GetTargetPositionForCard(BaseCard card)
        {
            if (!this.Cards.Contains(card))
            {
                Log.Write("Card not found in CardCollection");
                return card.transform.localPosition;
            }

            int index = this.Cards.IndexOf(card);
            var result = new Vector3(this._deckPositionOffset.x, this._deckPositionOffset.y, 16f);
            result += index * Vector3.back * 0.2f;

            return result;
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Game/InPlay.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Tween;
using UnityLogWrapper;
using UnityEngine;
using Type = HarryPotterUnity.Enums.Type;

namespace HarryPotterUnity.Game
{
    public class InPlay : CardCollection
    {
        private static readonly Vector3 _lessonPositionOffset = new Vector3(-255f, -60f, 15f);
        private static readonly Vector3 _topRowPositionOffset = new Vector3(-255f, 0f, 15f);
        private static readonly Vector3 _creaturePositionOffset = new Vector3(5f, -60f, 15f);
        private static readonly Vector3 _characterPositionOffset = new Vector3(-356f, -207, 15f);

        private static readonly Vector2 _lessonSpacing = new Vector2(80f, 15f);
        private static readonly Vector2 _topRowSpacing = new Vector2(80f, 0f);
        private static readonly Vector2 _creatureSpacing = new Vector2(80f, 55f);
        private static readonly Vector2 _characterSpacing = new Vector2(80f, 27f);

        public delegate void CardEnteredPlayEvent(BaseCard card);
        public delegate void CardExitedPlayEvent(BaseCard card);

        public event CardEnteredPlayEvent OnCardEnteredPlay;
        public event CardExitedPlayEvent  OnCardExitedPlay;

        private void OnDestroy()
        {
            this.OnCardEnteredPlay = null;
            this.OnCardExitedPlay = null;
        }

        public override void Add(BaseCard card)
        {
            this.Cards.Add(card);
            
            card.transform.parent = this.transform;

            this.TweenCardToPosition(card);

            this.MoveToThisCollection(card);

            ((IPersistentCard) card).OnEnterInPlayAction();

            if (this.OnCardEnteredPlay != null) this.OnCardEnteredPlay(card);
        }

        protected override void Remove(BaseCard card)
        {
            this.Cards.Remove(card);

            this.RearrangeCardsOfType(card.Type);

            ((IPersistentCard) card).OnExitInPlayAction();

            if (this.OnCardExitedPlay != null) this.OnCardExitedPlay(card);
        }

        public override void AddAll(IEnumerable<BaseCard> cards)
        {
            var cardList = cards as IList<BaseCard> ?? cards.ToList();

            foreach (var card in cardList)
            {
                this.Add(card);
            }
        }
        
        protected override void RemoveAll(IEnumerable<BaseCard> cards)
        {
            var cardList = cards as IList<BaseCard> ?? cards.ToList();

            foreach (var card in cardList)
            {
                this.Cards.Remove(card);

                ((IPersistentCard)card).OnExitInPlayAction();

                if (this.OnCardExitedPlay != null) this.OnCardExitedPlay(card);
            }

            foreach (var type in cardList.GroupBy(c => c.Type))
            {
                this.RearrangeCardsOfType(type.Key);
            }
        }

        public override BaseCard GetRandomCard()
        {
            return this.CardsExceptStartingCharacter.Skip(Random.Range(0, this.CardsExceptStartingCharacter.Count)).First();
        }

        private void TweenCardToPosition(BaseCard card)
        {
            var tween = new MoveTween
            {
                Target = card.gameObject,
                Position = this.GetTargetPositionForCard(card),
                Time = 0.3f,
                Flip = FlipState.FaceUp,
                Rotate = TweenRotationType.Rotate90,
                OnCompleteCallback = () => card.State = State.InPlay
            };
            GameManager.TweenQueue.AddTweenToQueue( tween );
        }

        private void RearrangeCardsOfType(Type type)
        {
            var targets = 
                type.IsTopRow() ? this.Cards.FindAll(c => c.Type.IsTopRow()) : this.Cards.FindAll(c => c.Type == type);

            var tween = new AsyncMoveTween
            {
                Targets = targets,
                GetPosition = this.GetTargetPositionForCard
            };
            GameManager.TweenQueue.AddTweenToQueue(tween);
        }

        private Vector3 GetTargetPositionForCard(BaseCard card)
        {
            if (!this.Cards.Contains(card)) return card.transform.localPosition;

            int position = this.Cards.FindAll(c => c.Type == card.Type).IndexOf(card);
            
            var cardPosition = new Vector3();

            switch (card.Type)
            {
                case Type.Lesson:
                    cardPosition = _lessonPositionOffset;
                    cardPosition.x += (position % 3) * _lessonSpacing.x;
                    cardPosition.y -= (int)(position / 3) * _lessonSpacing.y;
                    cardPosition.z -= (int)(position / 3);
                    break;
                case Type.Creature:
                    cardPosition = _creaturePositionOffset;
                    cardPosition.x += (position % 4) * _creatureSpacing.x;
                    cardPosition.y -= (int)(position / 4) * _creatureSpacing.y;
                    cardPosition.z -= (int)(position / 4);
                    break;
                case Type.Character:
                    cardPosition = _characterPositionOffset;
                    cardPosition.x += (position % 2) * _characterSpacing.x * 0.5f;
                    cardPosition.y -= (int) (position/2)*_characterSpacing.y;

                    int zPosOffset = 2*(position/2);
                    if (position%2 == 1)
                    {
                        zPosOffset -= 2;
                    }

                    cardPosition.z -= zPosOffset;
                    break;
                case Type.Item:
                case Type.Location:
                case Type.Adventure:
                case Type.Match:
                    int topRowIndex = this.Cards.FindAll(c => c.Type.IsTopRow()).IndexOf(card);
                    float shrinkFactor = 0.5f;
                    cardPosition = _topRowPositionOffset;
                    cardPosition.x += topRowIndex * _topRowSpacing.x * shrinkFactor;
                    cardPosition.z -= topRowIndex;
                    break;
                default:
                    Log.Error("GetTargetPositionForCard could not identify cardType");
                    break;
            }

            

            return cardPosition;
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/CareOfMagicalCreatures/Creatures/DevilsSnare.cs<|end_filename|>
﻿namespace HarryPotterUnity.Cards.CareOfMagicalCreatures.Creatures
{
    public class DevilsSnare : BaseCreature
    {
        public override void OnInPlayAfterTurnAction()
        {
            this.Heal(this.MaxHealth);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Transfiguration/Items/ColourChangingInk.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;

namespace HarryPotterUnity.Cards.Transfiguration.Items
{
    public class ColourChangingInk : BaseItem
    { 
        public override bool CanPerformInPlayAction()
        {
            return this.Player.CanUseActions() 
                && this.Player.Hand.Cards.Count > 0 
                && this.Player.IsLocalPlayer;
        }

        public override void OnInPlayAction(List<BaseCard> targets = null)
        {
            var cardsInHand = this.Player.Hand.Cards.ToList();
            int cardCount = cardsInHand.Count;

            this.Player.Deck.AddAll( cardsInHand );

            while (cardCount-- > 0)
            {
                this.Player.Deck.DrawCard();
            }

            this.Player.UseActions();
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Tween/ITweenObject.cs<|end_filename|>
﻿namespace HarryPotterUnity.Tween
{
    public interface ITweenObject
    {
        float CompletionTime { get; }
        float TimeUntilNextTween { get; }

        void ExecuteTween();
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Tween/MoveTween.cs<|end_filename|>
﻿using System;
using System.Collections;
using HarryPotterUnity.Cards;
using HarryPotterUnity.Enums;
using UnityEngine;

namespace HarryPotterUnity.Tween
{
    public class MoveTween : ITweenObject
    {
        
        public GameObject Target { get; set; }
        public Vector3 Position { get; set; }
        public float Time { get; set; }
        public float Delay { get; set; }
        public FlipState Flip { get; set; }
        public TweenRotationType Rotate { get; set; }

        public float TimeUntilNextTween { get; set; }

        public Action OnCompleteCallback { private get; set; }

        public float CompletionTime
        {
            get { return this.Time + this.Delay; }
        }
        
        public void ExecuteTween()
        {
            var args = iTween.Hash(
                "time", this.Time,
                "delay", this.Delay,
                "position", this.Position,
                "easetype", iTween.EaseType.EaseInOutSine,
                "islocal", true);

            if (this.OnCompleteCallback != null)
            {
                args["oncomplete"] = this.OnCompleteCallback;
            }

            iTween.MoveTo(this.Target, args);

            this.RotateAndFlipCard();
        }

        private void RotateAndFlipCard()
        {
            float targetRotate = 0f;
            switch (this.Rotate)
            {
                case TweenRotationType.Rotate90:
                    targetRotate = 270f;
                    break;
                case TweenRotationType.Rotate180:
                    targetRotate = 180f;
                    break;
            }

            float targetFlip = 0f;
            switch (this.Flip)
            {
                    case FlipState.FaceUp:
                        targetFlip = 0f;
                        break;
                    case FlipState.FaceDown:
                        targetFlip = 180f;
                        break;
            }

            this.Target.GetComponent<BaseCard>().FlipState = this.Flip;

            iTween.RotateTo(this.Target, iTween.Hash("time", this.Time,
                "y", targetFlip,
                "z", targetRotate,
                "easetype", iTween.EaseType.EaseInOutSine,
                "islocal", true
                ));
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/BaseItem.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards
{
    public abstract class BaseItem : BaseCard, IPersistentCard
    {
        protected sealed override Type GetCardType()
        {
            return Type.Item;
        }

        public virtual bool CanPerformInPlayAction()
        {
            return false;
        }

        public virtual void OnInPlayBeforeTurnAction() { }
        public virtual void OnInPlayAfterTurnAction() { }
        public virtual void OnInPlayAction(List<BaseCard> targets = null) { }
        public virtual void OnEnterInPlayAction() { }
        public virtual void OnExitInPlayAction() { }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/UI/Camera/PreviewCamera.cs<|end_filename|>
﻿using HarryPotterUnity.Cards;
using HarryPotterUnity.Cards.Interfaces;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.UI.Camera
{
    public class PreviewCamera : MonoBehaviour
    {
        [SerializeField, UsedImplicitly]
        private RectTransform _renderTexture;

        private static readonly Vector3 _defaultPreviewCameraPosition = new Vector3(-400, 255, -70);

        private static readonly Vector2 _horizontalPreviewPosition = new Vector2(350f, 0f);
        private static readonly Vector2 _verticalPreviewPosition = Vector2.zero;
        
        private void RotateHorizontal()
        {
            this._renderTexture.anchoredPosition = _horizontalPreviewPosition;
            this._renderTexture.localRotation = Quaternion.Euler(0f,0f,270f);
        }

        private void RotateVertical()
        {
            this._renderTexture.anchoredPosition = _verticalPreviewPosition;
            this._renderTexture.localRotation = Quaternion.Euler(0f, 0f, 0f);
        }

        public void ShowPreview(BaseCard card)
        {
            if (card is IPersistentCard) this.RotateHorizontal();
            else this.RotateVertical();

            this.transform.rotation = card.transform.rotation;
            this.transform.position = card.transform.position + 2 * Vector3.back;

            this._renderTexture.gameObject.SetActive(true);
        }

        public void HidePreview()
        {
            this._renderTexture.gameObject.SetActive(false);
            this.RotateVertical();
            this.transform.position = _defaultPreviewCameraPosition;
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/PlayerConstraints/CannotPlayTypeConstraint.cs<|end_filename|>
﻿using HarryPotterUnity.Enums;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.Cards.PlayerConstraints
{
    public class CannotPlayTypeConstraint : IPlayerConstraint
    {
        private readonly Type _disallowedType;

        public CannotPlayTypeConstraint(Type disallowedType)
        {
            this._disallowedType = disallowedType;
        }
        public bool MeetsConstraint(BaseCard cardToPlay)
        {
            return cardToPlay.Type != this._disallowedType;
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Spells/SearchForTheSnitch.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Enums;
using UnityEngine;

namespace HarryPotterUnity.Cards.Quidditch.Spells
{
    public class SearchForTheSnitch : BaseSpell
    {
        protected override void SpellAction(List<BaseCard> targets)
        {
            var cards = this.Player.Deck.Cards.Where(
                    c =>
                        c.Classification == ClassificationTypes.Quidditch ||
                        c is BaseLesson && ((BaseLesson) c).LessonType == LessonTypes.Quidditch).ToList();

            if (cards.Count > 0)
            {
                var selectedCard = cards.Skip(Random.Range(0, cards.Count)).First();
                this.Player.Hand.Add(selectedCard);
            }

            this.Player.Deck.Shuffle();
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Game/CardCollection.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards;
using HarryPotterUnity.Enums;
using UnityEngine;

namespace HarryPotterUnity.Game
{
    public abstract class CardCollection : MonoBehaviour
    {
        public List<BaseCard> Cards { get; protected set; }

        #region CardType Shortcuts
        public List<BaseCard> Lessons
        {
            get { return this.Cards.Where(c => c.Type == Type.Lesson).ToList(); }
        }

        public List<BaseCard> Items
        {
            get { return this.Cards.Where(c => c.Type == Type.Item).ToList(); }
        }

        public List<BaseCard> Creatures
        {
            get { return this.Cards.Where(c => c.Type == Type.Creature).ToList(); }
        }

        public List<BaseCard> Spells
        {
            get { return this.Cards.Where(c => c.Type == Type.Spell).ToList(); }
        }

        public List<BaseCard> Locations
        {
            get { return this.Cards.Where(c => c.Type == Type.Location).ToList(); }
        }

        public List<BaseCard> Matches
        {
            get { return this.Cards.Where(c => c.Type == Type.Match).ToList(); }
        }

        public List<BaseCard> Adventures
        {
            get { return this.Cards.Where(c => c.Type == Type.Adventure).ToList(); }
        }

        public List<BaseCard> Characters
        {
            get { return this.Cards.Where(c => c.Type == Type.Character).ToList(); }
        }

        public List<BaseCard> NonHealingCards
        {
            get { return this.Cards.Where(c => c.HasTag(Tag.Healing) == false).ToList(); }
        }

        public List<BaseCard> CardsExceptStartingCharacter
        {
            get { return this.Cards.Where(c => c != c.Player.Deck.StartingCharacter).ToList(); }
        }
        #endregion

        public abstract void Add(BaseCard card);
        public abstract void AddAll(IEnumerable<BaseCard> cards);

        protected abstract void Remove(BaseCard card);
        protected abstract void RemoveAll(IEnumerable<BaseCard> cards);
        
        protected CardCollection()
        {
            this.Cards = new List<BaseCard>();
        }

        protected void MoveToThisCollection(BaseCard card)
        {
            if (card.CurrentCollection.Contains(card))
            {
                card.CurrentCollection.Remove(card);
            }
            else if (card.PreviousCollection != null && card.PreviousCollection.Contains(card))
            {
                card.PreviousCollection.Remove(card);
            }

            card.CurrentCollection = this;
        }

        protected void MoveToThisCollection(IEnumerable<BaseCard> cards)
        {
            var cardList = cards as IList<BaseCard> ?? cards.ToList();

            var cardCollection = cardList.First().CurrentCollection;
            var prevCollection = cardList.First().PreviousCollection;

            if (cardList.All(c => c.CurrentCollection.Contains(c) && c.CurrentCollection == cardCollection))
            {
                cardCollection.RemoveAll(cardList);
            }
            else if (cardList.All(c => c.PreviousCollection != null && c.PreviousCollection.Contains(c)))
            {
                prevCollection.RemoveAll(cardList);
            }

            foreach (var card in cardList)
            {
                card.CurrentCollection = this;
            }
        }

        public virtual BaseCard GetRandomCard()
        {
            return this.Cards.Skip(Random.Range(0, this.Cards.Count)).First();
        }

        public BaseCard GetRandomCard(Type ofType)
        {
            var cardsOfType = this.Cards.Where(c => c.Type == ofType).ToList();
            return cardsOfType.Skip(Random.Range(0, cardsOfType.Count)).First();
        }

        private bool Contains(BaseCard card)
        {
            return this.Cards.Contains(card);
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Transfiguration/Items/Remembrall.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;

namespace HarryPotterUnity.Cards.Transfiguration.Items
{
    public class Remembrall : BaseItem
    { 
        public override bool CanPerformInPlayAction()
        {
            return this.Player.CanUseActions() 
                && this.Player.Discard.Lessons.Count > 0
                && this.Player.IsLocalPlayer;
        }

        public override void OnInPlayAction(List<BaseCard> targets = null)
        {
            var lesson = this.Player.Discard.Lessons.First();

            this.Player.InPlay.Add(lesson);

            this.Player.UseActions();
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/PlayRequirements/InputRequirement.cs<|end_filename|>
﻿using HarryPotterUnity.Enums;
using HarryPotterUnity.Game;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.Cards.PlayRequirements
{
    public class InputRequirement : MonoBehaviour, ICardPlayRequirement
    {
        private BaseCard _cardInfo;

        [SerializeField, UsedImplicitly]
        private int _fromHandActionInputRequired;

        [SerializeField, UsedImplicitly]
        private int _inPlayActionInputRequired;

        public int FromHandActionInputRequired { get { return this._fromHandActionInputRequired; } }
        public int InPlayActionInputRequired { get { return this._inPlayActionInputRequired; } }

        private void Awake()
        {
            this._cardInfo = this.GetComponent<BaseCard>();
            if (this.GetComponent<InputGatherer>() == null)
            {
                this.gameObject.AddComponent<InputGatherer>();   
            }
        }

        public bool MeetsRequirement()
        {
            switch (this._cardInfo.State)
            {
                case State.InHand:
                    return this._cardInfo.GetFromHandActionTargets().Count >= this._fromHandActionInputRequired;
                case State.InPlay:
                    return this._cardInfo.GetInPlayActionTargets().Count >= this._inPlayActionInputRequired;
            }

            return false;

        }

        public void OnRequirementMet() { }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Transfiguration/Locations/FlourishAndBlotts.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;

namespace HarryPotterUnity.Cards.Transfiguration.Locations
{
    public class FlourishAndBlotts : BaseLocation
    {
        public override bool CanPerformInPlayAction()
        {
            var player = this.Player.IsLocalPlayer ? this.Player : this.Player.OppositePlayer;

            return player.CanUseActions() &&
                   player.InPlay.Lessons.Count >= 2;
        }

        public override void OnInPlayAction(List<BaseCard> targets = null)
        {
            var player = this.Player.CanUseActions() ? this.Player : this.Player.OppositePlayer;

            var lessons = player.InPlay.Lessons.Take(2);

            player.Discard.AddAll(lessons);

            for (int i = 0; i < 5; i++)
            {
                player.Deck.DrawCard();
            }

            player.UseActions();
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Potions/Spells/PotionsHomework.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Cards.Interfaces;

namespace HarryPotterUnity.Cards.Potions.Spells
{
    public class PotionsHomework : BaseSpell, IDamageSpell
    {
        public int DamageAmount { get; set; }

        private int LessonsFound { get; set; }

        protected int LessonTargetCount { private get; set; }

        protected override void Start()
        {
            base.Start();
            this.LessonTargetCount = 1;
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            this.LessonsFound = 0;

            this.DamageAmount = 0;

            while (this.LessonsFound < this.LessonTargetCount)
            {
                int cardIndex = (this.Player.OppositePlayer.Deck.Cards.Count - 1) - this.DamageAmount;

                if (cardIndex < 0)
                {
                    break;
                }

                var card = this.Player.OppositePlayer.Deck.Cards[cardIndex];

                if (card is BaseLesson)
                {
                    this.LessonsFound++;
                }

                this.DamageAmount++;
            }

            this.Player.OppositePlayer.TakeDamage(this, this.DamageAmount);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Matches/PracticeMatch.cs<|end_filename|>
﻿using HarryPotterUnity.Game;

namespace HarryPotterUnity.Cards.Quidditch.Matches
{
    public class PracticeMatch : BaseMatch
    {
        public override void OnPlayerHasWonMatch(Player winner, Player loser)
        {
            winner.Deck.DrawCard();
            winner.Deck.DrawCard();
            winner.Deck.DrawCard();
            winner.Deck.DrawCard();
        }
    }
}


<|start_filename|>Assets/Photon Unity Networking/Plugins/PhotonNetwork/Views/PhotonTransformViewPositionControl.cs<|end_filename|>
﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class PhotonTransformViewPositionControl 
{
    PhotonTransformViewPositionModel m_Model;
    float m_CurrentSpeed;
    double m_LastSerializeTime;
    Vector3 m_SynchronizedSpeed = Vector3.zero;
    float m_SynchronizedTurnSpeed = 0;

    Vector3 m_NetworkPosition;
    Queue<Vector3> m_OldNetworkPositions = new Queue<Vector3>();

    bool m_UpdatedPositionAfterOnSerialize = true;

    public PhotonTransformViewPositionControl( PhotonTransformViewPositionModel model )
    {
        this.m_Model = model;
    }

    Vector3 GetOldestStoredNetworkPosition()
    {
        Vector3 oldPosition = this.m_NetworkPosition;

        if(this.m_OldNetworkPositions.Count > 0 )
        {
            oldPosition = this.m_OldNetworkPositions.Peek();
        }

        return oldPosition;
    }

    /// <summary>
    /// These values are synchronized to the remote objects if the interpolation mode
    /// or the extrapolation mode SynchronizeValues is used. Your movement script should pass on
    /// the current speed (in units/second) and turning speed (in angles/second) so the remote
    /// object can use them to predict the objects movement.
    /// </summary>
    /// <param name="speed">The current movement vector of the object in units/second.</param>
    /// <param name="turnSpeed">The current turn speed of the object in angles/second.</param>
    public void SetSynchronizedValues( Vector3 speed, float turnSpeed )
    {
        this.m_SynchronizedSpeed = speed;
        this.m_SynchronizedTurnSpeed = turnSpeed;
    }

    /// <summary>
    /// Calculates the new position based on the values setup in the inspector
    /// </summary>
    /// <param name="currentPosition">The current position.</param>
    /// <returns>The new position.</returns>
    public Vector3 UpdatePosition( Vector3 currentPosition )
    {
        Vector3 targetPosition = this.GetNetworkPosition() + this.GetExtrapolatedPositionOffset();

        switch(this.m_Model.InterpolateOption )
        {
        case PhotonTransformViewPositionModel.InterpolateOptions.Disabled:
            if(this.m_UpdatedPositionAfterOnSerialize == false )
            {
                currentPosition = targetPosition;
                this.m_UpdatedPositionAfterOnSerialize = true;
            }
            break;
        case PhotonTransformViewPositionModel.InterpolateOptions.FixedSpeed:
            currentPosition = Vector3.MoveTowards( currentPosition, targetPosition, Time.deltaTime *this.m_Model.InterpolateMoveTowardsSpeed );
            break;
        case PhotonTransformViewPositionModel.InterpolateOptions.EstimatedSpeed:
            int positionsCount = Mathf.Min( 1, this.m_OldNetworkPositions.Count );
            float estimatedSpeed = Vector3.Distance(this.m_NetworkPosition, this.GetOldestStoredNetworkPosition() ) / positionsCount;
            currentPosition = Vector3.MoveTowards( currentPosition, targetPosition, Time.deltaTime * estimatedSpeed );
            break;
        case PhotonTransformViewPositionModel.InterpolateOptions.SynchronizeValues:
            if(this.m_SynchronizedSpeed.magnitude == 0 )
            {
                currentPosition = targetPosition;
            }
            else
            {
                currentPosition = Vector3.MoveTowards( currentPosition, targetPosition, Time.deltaTime *this.m_SynchronizedSpeed.magnitude );
            }
            break;
        case PhotonTransformViewPositionModel.InterpolateOptions.Lerp:
            currentPosition = Vector3.Lerp( currentPosition, targetPosition, Time.deltaTime *this.m_Model.InterpolateLerpSpeed );
            break;
        /*case PhotonTransformViewPositionModel.InterpolateOptions.MoveTowardsComplex:
            float distanceToTarget = Vector3.Distance( currentPosition, targetPosition );
            float targetSpeed = m_Model.InterpolateSpeedCurve.Evaluate( distanceToTarget ) * m_Model.InterpolateMoveTowardsSpeed;

            if( targetSpeed > m_CurrentSpeed )
            {
                m_CurrentSpeed = Mathf.MoveTowards( m_CurrentSpeed, targetSpeed, Time.deltaTime * m_Model.InterpolateMoveTowardsAcceleration );
            }
            else
            {
                m_CurrentSpeed = Mathf.MoveTowards( m_CurrentSpeed, targetSpeed, Time.deltaTime * m_Model.InterpolateMoveTowardsDeceleration );
            }

            //Debug.Log( m_CurrentSpeed + " - " + targetSpeed + " - " + transform.localPosition + " - " + targetPosition );

            currentPosition = Vector3.MoveTowards( currentPosition, targetPosition, Time.deltaTime * m_CurrentSpeed );
            break;*/
        }

        if(this.m_Model.TeleportEnabled == true )
        {
            if( Vector3.Distance( currentPosition, this.GetNetworkPosition() ) > this.m_Model.TeleportIfDistanceGreaterThan )
            {
                currentPosition = this.GetNetworkPosition();
            }
        }

        return currentPosition;
    }

    /// <summary>
    /// Gets the last position that was received through the network
    /// </summary>
    /// <returns></returns>
    public Vector3 GetNetworkPosition()
    {
        return this.m_NetworkPosition;
    }

    /// <summary>
    /// Calculates an estimated position based on the last synchronized position,
    /// the time when the last position was received and the movement speed of the object
    /// </summary>
    /// <returns>Estimated position of the remote object</returns>
    public Vector3 GetExtrapolatedPositionOffset()
    {
        float timePassed = (float)( PhotonNetwork.time - this.m_LastSerializeTime );

        if(this.m_Model.ExtrapolateIncludingRoundTripTime == true )
        {
            timePassed += (float)PhotonNetwork.GetPing() / 1000f;
        }

        Vector3 extrapolatePosition = Vector3.zero;

        switch(this.m_Model.ExtrapolateOption )
        {
        case PhotonTransformViewPositionModel.ExtrapolateOptions.SynchronizeValues:
            Quaternion turnRotation = Quaternion.Euler( 0, this.m_SynchronizedTurnSpeed * timePassed, 0 );
            extrapolatePosition = turnRotation * (this.m_SynchronizedSpeed * timePassed );
            break;
        case PhotonTransformViewPositionModel.ExtrapolateOptions.FixedSpeed:
            Vector3 moveDirection = (this.m_NetworkPosition - this.GetOldestStoredNetworkPosition() ).normalized;

            extrapolatePosition = moveDirection *this.m_Model.ExtrapolateSpeed * timePassed;
            break;
        case PhotonTransformViewPositionModel.ExtrapolateOptions.EstimateSpeedAndTurn:
            Vector3 moveDelta = (this.m_NetworkPosition - this.GetOldestStoredNetworkPosition() ) * PhotonNetwork.sendRateOnSerialize;
            extrapolatePosition = moveDelta * timePassed;
            break;
        }

        return extrapolatePosition;
    }

    public void OnPhotonSerializeView( Vector3 currentPosition, PhotonStream stream, PhotonMessageInfo info )
    {
        if(this.m_Model.SynchronizeEnabled == false )
        {
            return;
        }

        if( stream.isWriting == true )
        {
            this.SerializeData( currentPosition, stream, info );
        }
        else
        {
            this.DeserializeData( stream, info );
        }

        this.m_LastSerializeTime = PhotonNetwork.time;
        this.m_UpdatedPositionAfterOnSerialize = false;
    }

    void SerializeData( Vector3 currentPosition, PhotonStream stream, PhotonMessageInfo info )
    {
        stream.SendNext( currentPosition );
        this.m_NetworkPosition = currentPosition;

        if(this.m_Model.ExtrapolateOption == PhotonTransformViewPositionModel.ExtrapolateOptions.SynchronizeValues || this.m_Model.InterpolateOption == PhotonTransformViewPositionModel.InterpolateOptions.SynchronizeValues )
        {
            stream.SendNext(this.m_SynchronizedSpeed );
            stream.SendNext(this.m_SynchronizedTurnSpeed );
        }
    }

    void DeserializeData( PhotonStream stream, PhotonMessageInfo info )
    {
        this.m_OldNetworkPositions.Enqueue(this.m_NetworkPosition );

        while(this.m_OldNetworkPositions.Count > this.m_Model.ExtrapolateNumberOfStoredPositions )
        {
            this.m_OldNetworkPositions.Dequeue();
        }

        this.m_NetworkPosition = (Vector3)stream.ReceiveNext();

        if(this.m_Model.ExtrapolateOption == PhotonTransformViewPositionModel.ExtrapolateOptions.SynchronizeValues || this.m_Model.InterpolateOption == PhotonTransformViewPositionModel.InterpolateOptions.SynchronizeValues )
        {
            this.m_SynchronizedSpeed = (Vector3)stream.ReceiveNext();
            this.m_SynchronizedTurnSpeed = (float)stream.ReceiveNext();
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Adventures/TrollInTheBathroom.cs<|end_filename|>
﻿namespace HarryPotterUnity.Cards.Adventures
{
    public class TrollInTheBathroom : BaseAdventure
    {
        public override void OnInPlayBeforeTurnAction()
        {
            this.Player.OppositePlayer.TakeDamage(this, 2);
        }

        protected override bool CanOpponentSolve()
        {
            return this.Player.OppositePlayer.Hand.Cards.Count >= 7;
        }

        protected override void Solve()
        {
            int amountToDiscard = 7;

            for (int i = amountToDiscard; i > 0; i--)
            {
                var card = this.Player.OppositePlayer.Hand.GetRandomCard();

                this.Player.OppositePlayer.Discard.Add(card);
            }
        }

        protected override void Reward()
        {
            this.Player.TakeDamage(this, 4);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/PlayRequirements/ICardPlayRequirement.cs<|end_filename|>
﻿namespace HarryPotterUnity.Cards.PlayRequirements
{
    public interface ICardPlayRequirement
    {
        bool MeetsRequirement();

        void OnRequirementMet();
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Charms/Spells/Fumos.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.Interfaces;

namespace HarryPotterUnity.Cards.Charms.Spells
{
    public class Fumos : BaseSpell, IDamageSpell
    {
        public int DamageAmount { get; set; }

        protected override void Start()
        {
            base.Start();
            this.DamageAmount = 2;
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            var allCreatures = this.Player.InPlay.Creatures.Concat(this.Player.OppositePlayer.InPlay.Creatures);

            foreach (var creature in allCreatures)
            {
                ((BaseCreature)creature).TakeDamage(this.DamageAmount);
            }
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/PlayRequirements/SpecificLessonTypeRequirement.cs<|end_filename|>
﻿using System.Linq;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Game;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.Cards.PlayRequirements
{
    public class SpecificLessonTypeRequirement : MonoBehaviour, ICardPlayRequirement
    {
        private Player _player;

        [UsedImplicitly, SerializeField]
        private LessonTypes _lessonType;

        [UsedImplicitly, SerializeField]
        private int _amountProvided;
    
        private void Start()
        {
            this._player = this.GetComponent<BaseCard>().Player;
        }

        public bool MeetsRequirement()
        {
            return this._player.InPlay.Cards
                .OfType<ILessonProvider>()
                .Count(c => c.LessonType == this._lessonType) >= this._amountProvided;
        }

        public void OnRequirementMet() { }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/DeckGeneration/Requirements/DeckContainsCardsOfTypeRequirement.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards;
using HarryPotterUnity.Enums;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.DeckGeneration.Requirements
{
    public class DeckContainsCardsOfTypeRequirement : MonoBehaviour, IDeckGenerationRequirement
    {
        [SerializeField, UsedImplicitly] private Type _cardType;

        public bool MeetsRequirement(List<BaseCard> currentDeck)
        {
            return currentDeck.Any(c => c.Type == this._cardType);
            
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Transfiguration/Spells/MiceToSnuffboxes.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.PlayRequirements;
using UnityEngine;

namespace HarryPotterUnity.Cards.Transfiguration.Spells
{
    [RequireComponent(typeof(InputRequirement))]
    public class MiceToSnuffboxes : BaseSpell
    {
        public override List<BaseCard> GetFromHandActionTargets()
        {
            var validCards = this.Player.InPlay.Creatures
                .Concat(this.Player.OppositePlayer.InPlay.Creatures)
                .ToList();

            return validCards;
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            //Check if the cards belong to the same player and do and AddAll to prevent animation bugs
            bool localPlayer = targets.First().Player.IsLocalPlayer;
            if (targets.All(c => c.Player.IsLocalPlayer == localPlayer))
            {
                targets.First().Player.Hand.AddAll(targets);

            }
            else
            {
                foreach (var card in targets)
                {
                    card.Player.Hand.Add(card, preview: false, adjustSpacing: true);
                }
            }
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Game/NetworkManager.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.DeckGeneration;
using HarryPotterUnity.Enums;
using HarryPotterUnity.UI;
using HarryPotterUnity.UI.Menu;
using JetBrains.Annotations;
using UnityLogWrapper;
using UnityEngine;
using Random = UnityEngine.Random;

namespace HarryPotterUnity.Game
{
    public class NetworkManager : Photon.MonoBehaviour
    {
        private Player Player1 { get; set; }
        private Player Player2 { get; set; }

        private MenuManager MenuManager { get; set; }
        private List<BaseMenu> MenuScreens { get; set; }

        private const string LOBBY_VERSION = "v0.2-dev";

        private static readonly TypedLobby _defaultLobby = new TypedLobby(LOBBY_VERSION, LobbyType.Default);

        public void Awake()
        {
            Log.Write("Initialize Log");

            this.MenuManager = FindObjectOfType<MenuManager>();
            this.MenuScreens = FindObjectsOfType<BaseMenu>().ToList();

            GameManager.Network = this.photonView;

            PhotonNetwork.ConnectUsingSettings(LOBBY_VERSION);
        }

        [UsedImplicitly]
        public void OnConnectedToMaster()
        {
            Log.Write("Connected to Photon Master Server");
            ConnectToPhotonLobby();
        }

        public static void ConnectToPhotonLobby()
        {
            PhotonNetwork.JoinLobby(_defaultLobby );
        }

        [UsedImplicitly]
        public void OnJoinedLobby()
        {
            Log.Write("Joined {0} Lobby", LOBBY_VERSION);
        }

        [UsedImplicitly]
        public void OnJoinedRoom()
        {
            Log.Write("Joined Photon Room, Waiting for Players...");

            if (PhotonNetwork.room.playerCount == 2)
            {
                var p2Rotation = Quaternion.Euler(0f, 0f, 180f);

                Camera.main.transform.rotation = p2Rotation;
                Camera.main.transform.localPosition = new Vector3
                {
                    x = Camera.main.transform.localPosition.x,
                    y = 132f,
                    z = Camera.main.transform.localPosition.z
                };

                GameManager.PreviewCamera.transform.rotation = p2Rotation;
            }
            else
            {
                Camera.main.transform.rotation = Quaternion.identity;
                GameManager.PreviewCamera.transform.rotation = Quaternion.identity;
            }
        }

        [UsedImplicitly]
        public void OnPhotonPlayerConnected()
        {
            int seed = Random.Range(int.MinValue, int.MaxValue);

            Log.Write("New Player has Connected, Starting Game...");
            this.photonView.RPC("StartGameRpc", PhotonTargets.All, seed);
        }
        
        [UsedImplicitly]
        public void OnPhotonRandomJoinFailed()
        {
            string roomName = "Room " + PhotonNetwork.GetRoomList().Length;
            PhotonNetwork.JoinOrCreateRoom(roomName, new RoomOptions { maxPlayers = 2 }, null);
        }

        [UsedImplicitly]
        public void OnPhotonPlayerDisconnected()
        {
            Log.Write("Opponent Disconnected, Back to Main Menu...");
            if (PhotonNetwork.inRoom)
            {
                PhotonNetwork.LeaveRoom();
            }
        }

        [UsedImplicitly]
        public void OnLeftRoom()
        {
            Log.Write("Player Disconnected, Back to Main Menu");
            
            GameManager.TweenQueue.Reset();
            this.DestroyPlayerObjects();

            this.MenuManager.ShowMenu(this.MenuScreens.First(m => m.name.Contains("MainMenuContainer")));
        }

        private void DestroyPlayerObjects()
        {
            if (this.Player1 != null) Destroy(this.Player1.gameObject);
            if (this.Player2 != null) Destroy(this.Player2.gameObject);
        }

        [PunRPC, UsedImplicitly]
        public void StartGameRpc(int rngSeed)
        {
            //Synchronize the Random Number Generator for both clients with the given seed
            Random.InitState(rngSeed);

            this.SpawnPlayers();
            this.SetPlayerProperties();
            this.SetUpGameplayHud();
            this.InitPlayerDecks();
            this.BeginGame();
        }
        
        private void SpawnPlayers()
        {
            var playerObject = Resources.Load("Player");
            this.Player1 = ( (GameObject) Instantiate(playerObject) ).GetComponent<Player>();
            this.Player2 = ( (GameObject) Instantiate(playerObject) ).GetComponent<Player>();

            if (!this.Player1 || !this.Player2)
            {
                Log.Error("One of the players was not properly instantiated, Report this error!");
            }
        }

        private void SetPlayerProperties()
        {
            this.Player1.IsLocalPlayer = PhotonNetwork.player.isMasterClient;
            this.Player2.IsLocalPlayer = !this.Player1.IsLocalPlayer;

            this.Player1.OppositePlayer = this.Player2;
            this.Player2.OppositePlayer = this.Player1;

            this.Player1.transform.localRotation = Quaternion.identity;
            this.Player2.transform.localRotation = Quaternion.Euler(0f, 0f, 180f);

            this.Player1.NetworkId = 0;
            this.Player2.NetworkId = 1;
        }

        private void SetUpGameplayHud()
        {
            var gameplayMenu = this.MenuScreens.FirstOrDefault(m => m.name.Contains("GameplayMenuContainer")) as GameplayMenu;

            if (gameplayMenu != null)
            {
                gameplayMenu.LocalPlayer  = this.Player1.IsLocalPlayer ? this.Player1 : this.Player2;
                gameplayMenu.RemotePlayer = this.Player1.IsLocalPlayer ? this.Player2 : this.Player1;

                this.MenuManager.ShowMenu(gameplayMenu);
            }
            else
            {
                Log.Error("SetUpGameplayHud() Failed, could not find GameplayMenuContainer, Report this error!");
            }
        }
            
        private void InitPlayerDecks()
        {
            int p1Id = PhotonNetwork.isMasterClient ? 1 : 0;
            int p2Id = p1Id == 0 ? 1 : 0;

            var p1LessonsBytes = PhotonNetwork.playerList[p1Id].customProperties["lessons"] as byte[];
            var p2LessonsBytes = PhotonNetwork.playerList[p2Id].customProperties["lessons"] as byte[];

            if (p1LessonsBytes == null || p2LessonsBytes == null)
            {
                Log.Error("p1 or p2 selected lessons are null, report this error!");
                return;
            }

            var p1SelectedLessons = p1LessonsBytes.Select(n => (LessonTypes) n).ToList();
            var p2SelectedLessons = p2LessonsBytes.Select(n => (LessonTypes) n).ToList();

            GameManager.NetworkIdCounter = 0;
            GameManager.AllCards.Clear();

            DeckGenerator.ResetStartingCharacterPool();

            Log.Write("Generating Player Decks");
            this.Player1.InitDeck(p1SelectedLessons);
            this.Player2.InitDeck(p2SelectedLessons);
        }

        private void BeginGame()
        {
            Log.Write("Game setup complete, starting match");
            this.Player1.Deck.SpawnStartingCharacter();
            this.Player2.Deck.SpawnStartingCharacter();

            //Shuffle after drawing the initial hand if debug mode is enabled
            if (GameManager.DebugModeEnabled == false)
            {
                this.Player1.Deck.Shuffle();
                this.Player2.Deck.Shuffle();
                this.Player1.DrawInitialHand();
                this.Player2.DrawInitialHand();
            }
            else
            {
                this.Player1.DrawInitialHand();
                this.Player2.DrawInitialHand();
                this.Player1.Deck.Shuffle();
                this.Player2.Deck.Shuffle();
            }

            this.Player1.BeginTurn();
        }
        
        [PunRPC, UsedImplicitly]
        public void ExecutePlayActionById(byte id)
        {
            var card = GameManager.AllCards.Find(c => c.NetworkId == id);

            if (card == null)
            {   
                Log.Error("ExecutePlayActionById could not find card with Id: " + id);
                return;
            }

            Log.Write("Player {0} Plays {1} from hand", card.Player.NetworkId + 1, card.CardName);
            card.Player.InvokeCardPlayedEvent(card);
            card.PlayFromHand();
        }

        [PunRPC, UsedImplicitly]
        public void ExecuteInPlayActionById(byte id)
        {
            BaseCard card = GameManager.AllCards.Find(c => c.NetworkId == id);

            if (card == null)
            {
                Log.Error("ExecuteInPlayActionById could not find card with Id: " + id);
                return;
            }

            var persistentCard = card as IPersistentCard;
            if (persistentCard == null)
            {
                Log.Error("ExecuteInPlayActionById did not receive a PersistentCard!");
                return;
            }
            
            Log.Write("Player {0} Activates {1}'s effect", card.Player.NetworkId + 1, card.CardName);
            persistentCard.OnInPlayAction();
        }

        [PunRPC, UsedImplicitly]
        public void ExecuteDrawActionOnPlayer(byte id)
        {
            var player = id == 0 ? this.Player1 : this.Player2;

            Log.Write("Player {0} Draws a Card", player.NetworkId + 1);
            player.Deck.DrawCard();
            player.UseActions();
        }

        [PunRPC, UsedImplicitly]
        public void ExecuteInputCardById(byte id, params byte[] selectedCardIds)
        {
            var card = GameManager.AllCards.Find(c => c.NetworkId == id);
            
            var targetedCards = selectedCardIds.Select(cardId => GameManager.AllCards.Find(c => c.NetworkId == cardId)).ToList();
            
            Log.Write("Player {0} plays card {1} targeting {2}", 
                card.Player.NetworkId + 1, 
                card.CardName, 
                string.Join(",", targetedCards.Select(c => c.CardName).ToArray()));


            card.Player.InvokeCardPlayedEvent(card, targetedCards);

            card.PlayFromHand(targetedCards);

            card.Player.EnableAllCards();
            card.Player.ClearHighlightComponent();

            card.Player.OppositePlayer.EnableAllCards();
            card.Player.OppositePlayer.ClearHighlightComponent();
        }

        [PunRPC, UsedImplicitly]
        public void ExecuteInPlayInputCardById(byte id, params byte[] selectedCardIds)
        {
            var card = GameManager.AllCards.Find(c => c.NetworkId == id);

            var selectedCards = selectedCardIds.Select(cardId => GameManager.AllCards.Find(c => c.NetworkId == cardId)).ToList();

            Log.Write("Player {0} activates card {1} targeting {2}",
                card.Player.NetworkId + 1,
                card.CardName,
                string.Join(",", selectedCards.Select(c => c.CardName).ToArray()));

            var persistentCard = card as IPersistentCard;
            if (persistentCard == null)
            {
                Log.Error("ExecuteInPlayInputCardById did not receive a PersistentCard!");
                return;
            }

            persistentCard.OnInPlayAction(selectedCards);

            card.Player.EnableAllCards();
            card.Player.ClearHighlightComponent();

            card.Player.OppositePlayer.EnableAllCards();
            card.Player.OppositePlayer.ClearHighlightComponent();
        }

        [PunRPC, UsedImplicitly]
        public void ExecuteSkipAction()
        {
            if (this.Player1.CanUseActions())
            {
                Log.Write("Player 1 skipped an action");
                this.Player1.UseActions();
            }
            else if (this.Player2.CanUseActions())
            {
                Log.Write("Player 2 skipped an action");
                this.Player2.UseActions();
            }
            else
            {
                Log.Error("ExecuteSkipAction() failed to identify which player wants to skip their Action!");
            }
        }

        private void OnApplicationQuit()
        {
            Log.SaveToFile("HP-TCG", "Harry Potter TCG Log");
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/CareOfMagicalCreatures/Locations/GreenhouseThree.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards.CareOfMagicalCreatures.Locations
{
    public class GreenhouseThree : BaseLocation
    {
        private bool HasEffectActivated { get; set; }

        public override void OnEnterInPlayAction()
        {
            base.OnEnterInPlayAction();

            this.Player.OnCardPlayedEvent += this.AddActionOnPlantPlayedEvent;
            this.Player.OppositePlayer.OnCardPlayedEvent += this.AddActionOnPlantPlayedEvent;
        }

        private void AddActionOnPlantPlayedEvent(BaseCard card, List<BaseCard> targets)
        {
            if (this.HasEffectActivated == false && card.HasTag(Tag.Plant))
            {
                this.HasEffectActivated = true;

                var player = this.Player.CanUseActions() ? this.Player : this.Player.OppositePlayer;

                player.AddActions(1);

                player.OppositePlayer.OnNextTurnStartEvent += () => this.HasEffectActivated = false;
            }
        }

        public override void OnExitInPlayAction()
        {
            base.OnExitInPlayAction();

            this.HasEffectActivated = false;

            this.Player.OnCardPlayedEvent -= this.AddActionOnPlantPlayedEvent;
            this.Player.OppositePlayer.OnCardPlayedEvent -= this.AddActionOnPlantPlayedEvent;
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Items/SchoolBroom.cs<|end_filename|>
﻿using HarryPotterUnity.Cards.BasicBehavior;

namespace HarryPotterUnity.Cards.Quidditch.Items
{
    public class SchoolBroom : ItemLessonProvider
    {
        public override void OnEnterInPlayAction()
        {
            this.Player.Deck.DrawCard();
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Potions/Spells/PotionsTest.cs<|end_filename|>
﻿namespace HarryPotterUnity.Cards.Potions.Spells
{
    public class PotionsTest : PotionsProject
    {
        protected override void Start()
        {
            base.Start();
            this.LessonTargetCount = 3;
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Transfiguration/Items/PhilosophersStone.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards.Transfiguration.Items
{
    public class PhilosophersStone : BaseItem
    {
        public override bool CanPerformInPlayAction()
        {
            return this.Player.CanUseActions(2) && this.Player.IsLocalPlayer;
        }

        public override void OnInPlayAction(List<BaseCard> targets = null)
        {
            this.Player.Discard.Add(this);

            var allCards = this.Player.Discard.NonHealingCards;

            var lessons = allCards.Where(c => c.Type == Type.Lesson);
            var other = allCards.Where(c => c.Type != Type.Lesson);

            this.Player.InPlay.AddAll(lessons);
            this.Player.Deck.AddAll(other);

            this.Player.Deck.Shuffle();

            this.Player.UseActions(2);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/PlayRequirements/UniqueTagRequirement.cs<|end_filename|>
﻿using HarryPotterUnity.Enums;
using HarryPotterUnity.Game;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.Cards.PlayRequirements
{
    public class UniqueTagRequirement : MonoBehaviour, ICardPlayRequirement
    {
        private Player _player;

        [SerializeField, UsedImplicitly]
        private Tag _tag;

        private void Start()
        {
            this._player = this.GetComponent<BaseCard>().Player;
        }
        public bool MeetsRequirement()
        {
            return true;
        }

        public void OnRequirementMet()
        {
            if (!this._player.InPlay.Cards.Exists(c => c.HasTag(this._tag))) return;

            var card = this._player.InPlay.Cards.Find(c => c.HasTag(this._tag));

            this._player.Discard.Add(card);
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/DeckGeneration/IDeckGenerationRequirement.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Cards;

namespace HarryPotterUnity.DeckGeneration
{
    public interface IDeckGenerationRequirement
    {
        bool MeetsRequirement(List<BaseCard> currentDeck);
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Transfiguration/Spells/InvisibleInk.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.PlayRequirements;
using UnityEngine;

namespace HarryPotterUnity.Cards.Transfiguration.Spells
{
    [RequireComponent(typeof(InputRequirement))]
    public class InvisibleInk : BaseSpell
    {
        public override List<BaseCard> GetFromHandActionTargets()
        {
            return this.Player.InPlay.Lessons.Concat(this.Player.OppositePlayer.InPlay.Lessons).ToList();
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            bool localPlayer = targets.First().Player.IsLocalPlayer;
            if (targets.All(c => c.Player.IsLocalPlayer == localPlayer))
            {
                targets.First().Player.Discard.AddAll(targets);
            }
            else
            {
                foreach (var card in targets)
                {
                    card.Player.Discard.Add(card);
                }
            }
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/CareOfMagicalCreatures/Creatures/Mandrake.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Enums;
using UnityEngine;

namespace HarryPotterUnity.Cards.CareOfMagicalCreatures.Creatures
{
    public class Mandrake : BaseCreature
    {
        public override bool CanPerformInPlayAction()
        {
            return this.Player.Discard.Characters.Any(c => c.HasTag(Tag.Healing) == false) && this.Player.IsLocalPlayer;
        }

        public override void OnInPlayAction(List<BaseCard> targets = null)
        {
            this.Player.Discard.Add(this);

            var possibleCharacters = this.Player.Discard.Characters
                .Where(c => c.HasTag(Tag.Healing) == false).ToList();

            var character = possibleCharacters
                .Skip(Random.Range(0, possibleCharacters.Count))
                .First();

            this.Player.Hand.Add(character);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Locations/QuidditchPitch.cs<|end_filename|>
﻿using System.Linq;
using HarryPotterUnity.Cards.PlayRequirements;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Game;

namespace HarryPotterUnity.Cards.Quidditch.Locations
{
    public class QuidditchPitch : BaseLocation
    {
        public override void OnEnterInPlayAction()
        {
            base.OnEnterInPlayAction();

            var quidditchSpellsLessonRequirements =
                GameManager.AllCards.Where(c => c.Type == Type.Spell && c.Classification == ClassificationTypes.Quidditch)
                .Select(spell => spell.GetComponent<LessonRequirement>())
                .ToList();

            foreach (var requirement in quidditchSpellsLessonRequirements)
            {
                requirement.AmountRequired -= 2;

                if (requirement.AmountRequired < 1)
                {
                    requirement.AmountRequired = 1;
                }
            }
        }

        public override void OnExitInPlayAction()
        {
            base.OnExitInPlayAction();

            var lessonRequirements =
                GameManager.AllCards.Where(c => c.Type == Type.Spell && c.Classification == ClassificationTypes.Quidditch)
                .Select(spell => spell.GetComponent<LessonRequirement>())
                .ToList();

            foreach (var req in lessonRequirements)
            {
                req.ResetRequirement();
            }
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Game/Hand.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Tween;
using UnityEngine;

namespace HarryPotterUnity.Game
{
    public class Hand : CardCollection
    {   
        private Player _player;

        private static readonly Vector3 _handPreviewPosition = new Vector3(-80f, -13f, -336f);

        private static readonly Vector3 _handCardsOffset = new Vector3(-240f, -200f, 0f);

        private const float SPACING = 55f;

        private void Awake()
        {
            this._player = this.transform.GetComponentInParent<Player>();
        }

        public override void Add(BaseCard card)
        {
            this.Add(card, preview: true, adjustSpacing: true);
        }

        public void Add(BaseCard card, bool preview, bool adjustSpacing)
        {
            if (adjustSpacing) this.AdjustHandSpacing();

            card.transform.parent = this.transform;

            this.Cards.Add(card);

            //TODO: Add option to show the card to both players before putting it into the player's hand
            var flipState = this._player.IsLocalPlayer ? FlipState.FaceUp : FlipState.FaceDown;

            this.AnimateCardToHand(card, flipState, preview);

            this.MoveToThisCollection(card);
        }
        
        public override void AddAll(IEnumerable<BaseCard> cards)
        {
            this.AdjustHandSpacing();

            var cardList = cards as IList<BaseCard> ?? cards.ToList();

            foreach (var card in cardList)
            {
                card.transform.parent = this.transform;

                this.Cards.Add(card);
                var flipState = this._player.IsLocalPlayer ? FlipState.FaceUp : FlipState.FaceDown;
                this.AnimateCardToHand(card, flipState);
            }

            this.MoveToThisCollection(cardList);

            this.AdjustHandSpacing();
        }

        protected override void RemoveAll(IEnumerable<BaseCard> cardsToRemove)
        {
            foreach (var card in cardsToRemove)
            {
                this.Cards.Remove(card);
            }

            this.AdjustHandSpacing();
        }

        protected override void Remove(BaseCard card)
        {
            this.RemoveAll(new[] { card });
        }

        public void AdjustHandSpacing()
        {
            var tween = new AsyncMoveTween
            {
                Targets = this.Cards.ToList(),
                GetPosition = this.GetTargetPositionForCard
            };
            GameManager.TweenQueue.AddTweenToQueue(tween);
        }

        private Vector3 GetTargetPositionForCard(BaseCard card)
        {
            if (!this.Cards.Contains(card)) return card.transform.localPosition;

            var cardPosition = _handCardsOffset;

            float shrinkFactor = this.Cards.Count >= 12 ? 0.5f : 1f;
            
            int index = this.Cards.IndexOf(card);
            cardPosition.x += index * SPACING * shrinkFactor;
            cardPosition.z -= index;

            return cardPosition;
        }

        private void AnimateCardToHand(BaseCard card, FlipState flipState, bool preview = true)
        {
            var cardPosition = this.GetTargetPositionForCard(card);

            if (preview)
            {
                var previewTween = new MoveTween
                {
                    Target = card.gameObject,
                    Position = _handPreviewPosition,
                    Time = 0.5f,
                    Flip = flipState,
                    Rotate = TweenRotationType.NoRotate,
                    OnCompleteCallback = () => card.State = State.InHand
                };
                GameManager.TweenQueue.AddTweenToQueue( previewTween );
            }

            var finalTween = new MoveTween
            {
                Target = card.gameObject,
                Position = cardPosition,
                Time = 0.3f,
                Delay = 0.1f,
                Flip = flipState,
                Rotate = TweenRotationType.NoRotate,
                OnCompleteCallback = () => card.State = State.InHand
            };
            GameManager.TweenQueue.AddTweenToQueue( finalTween );
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Potions/Spells/WeaknessPotion.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards.Potions.Spells
{
    public class WeaknessPotion : BaseSpell, IDamageSpell
    {
        public int DamageAmount { get; set; }

        protected override void Start()
        {
            base.Start();
            this.DamageAmount = 5;
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            this.Player.OppositePlayer.TakeDamage(this, this.DamageAmount);

            this.Player.TypeImmunity.Add(Type.Creature);
            this.Player.OnNextTurnStartEvent += () => this.Player.TypeImmunity.Remove(Type.Creature);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Locations/QualityQuidditchSupplies.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Enums;
using UnityEngine;

namespace HarryPotterUnity.Cards.Quidditch.Locations
{
    public class QualityQuidditchSupplies : BaseLocation
    {
        public override bool CanPerformInPlayAction()
        {
            var player = this.Player.IsLocalPlayer ? this.Player : this.Player.OppositePlayer;

            return player.CanUseActions();
        }

        public override void OnInPlayAction(List<BaseCard> targets = null)
        {
            var player = this.Player.CanUseActions() ? this.Player : this.Player.OppositePlayer;

            var items = player.Deck.Items.Where(c => c.Classification == ClassificationTypes.Quidditch).ToList();

            if (items.Any())
            {
                var selectedItem = items.Skip(Random.Range(0, items.Count)).First();
                player.Hand.Add(selectedItem);
            }

            player.Deck.Shuffle();
            player.UseActions();
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Charms/Spells/HalloweenFeast.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;

namespace HarryPotterUnity.Cards.Charms.Spells
{
    public class HalloweenFeast : BaseSpell
    {
        protected override void SpellAction(List<BaseCard> targets)
        {
            var creatures = this.Player.Discard.Creatures.Take(4).ToList();

            this.Player.Hand.AddAll(creatures);
        }

        protected override bool MeetsAdditionalPlayRequirements()
        {
            return this.Player.Discard.Creatures.Count >= 1;
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/DeckGeneration/DeckGenerator.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Enums;
using JetBrains.Annotations;
using UnityEngine;
using UnityLogWrapper;
using Random = UnityEngine.Random;
using Type = HarryPotterUnity.Enums.Type;

namespace HarryPotterUnity.DeckGeneration
{
    [UsedImplicitly]
    public static class DeckGenerator
    {
        private static List<BaseCard> _allStartingCharacters;
        private static List<BaseCard> _cardLibrary;
        private static List<BaseCard> _availableStartingCharacters;

        private static List<BaseCard> CardLibrary
        {
            get
            {
                if (_cardLibrary == null)
                {
                    var cards = Resources.LoadAll("Cards/")
                        .Cast<GameObject>()
                        .Select( o => o.GetComponent<BaseCard>() );

                    _cardLibrary = new List<BaseCard>(cards);
                }

                return _cardLibrary;
            }
        }

        private static List<BaseCard> AllStartingCharacters
        {
            get
            {
                if (_allStartingCharacters == null)
                {
                    _allStartingCharacters = new List<BaseCard>();
                    _allStartingCharacters.AddRange(CardLibrary.Where(c => c.Type == Type.Character));
                }

                return _allStartingCharacters;
            }
        }
        
        private static List<BaseCard> AvailableStartingCharacters
        {
            get
            {
                if (_availableStartingCharacters == null)
                {
                    _availableStartingCharacters = new List<BaseCard>();
                    _availableStartingCharacters.AddRange( AllStartingCharacters );
                }

                return _availableStartingCharacters;
            }
        }
        
        public static BaseCard GetRandomCharacter()
        {
            var character = AvailableStartingCharacters.Skip(Random.Range(0, AvailableStartingCharacters.Count)).First();

            AvailableStartingCharacters.Remove(character);

            return character;
        }

        public static List<BaseCard> GenerateDeck(List<LessonTypes> types)
        {
            var deck = new List<BaseCard>();

            BuildDeck(ref deck, types);

            if (deck.Count != 60)
            {
                Log.Error("Deck did not initialize with the correct amount of cards, deck.count = {0}", deck.Count);
            }
            return deck;
        }

        public static List<BaseCard> GenerateDeck(List<BaseCard> prebuiltDeck, List<LessonTypes> types)
        {
            var deck = new List<BaseCard>();

            BuildDeck(ref deck, types);

            if (deck.Count != 60)
            {
                Log.Warning("Generate Deck with Prebuilt Deck finished with CardCount = {0}", deck.Count);
            }

            deck.AddRange(prebuiltDeck);

            return deck;
        }

        private static void BuildDeck(ref List<BaseCard> deck, List<LessonTypes> types)
        {
            switch (types.Count)
            {
                case 2:
                    AddLessonsToDeck(ref deck, types[0], 13);
                    AddLessonsToDeck(ref deck, types[1], 10);

                    AddSupportCharacters(ref deck, 1);
                    AddAdventures(ref deck, 3);
                    AddCardsToDeck(ref deck, types[0].ToClassification(), 17);
                    AddCardsToDeck(ref deck, types[1].ToClassification(), 16);
                    break;
                case 3:
                    AddLessonsToDeck(ref deck, types[0], 12);
                    AddLessonsToDeck(ref deck, types[1], 6);
                    AddLessonsToDeck(ref deck, types[2], 5);

                    AddSupportCharacters(ref deck, 1);
                    AddAdventures(ref deck, 3);
                    AddCardsToDeck(ref deck, types[0].ToClassification(), 11);
                    AddCardsToDeck(ref deck, types[1].ToClassification(), 11);
                    AddCardsToDeck(ref deck, types[2].ToClassification(), 11);
                    break;
                default:
                    throw new Exception(types.Count + " type(s) sent to GenerateDeck, unsupported");
            }
        }

        private static void AddAdventures(ref List<BaseCard> deck, int amount)
        {
            for (int i = 0; i < amount; i++)
            {
                deck.Add(GetRandomAdventure());
            }
        }

        private static BaseCard GetRandomAdventure()
        {
            var adventures = CardLibrary.Where(c => c.Classification == ClassificationTypes.Adventure).ToList();
            var card = adventures.Skip(Random.Range(0, adventures.Count)).First();

            return card;
        }

        private static void AddSupportCharacters(ref List<BaseCard> deck, int amount)
        {
            for (int i = 0; i < amount; i++)
            {
                deck.Add( GetRandomCharacter() );
            }
        }

        private static void AddLessonsToDeck(ref List<BaseCard> deck, LessonTypes lessonType, int amount)
        {
            var card = CardLibrary
                .Where(c => c.Classification == ClassificationTypes.Lesson)
                .First(l => ((ILessonProvider) l).LessonType == lessonType);

            for (int i = 0; i < amount; i++)
            {
                deck.Add(card);
            }
        }

        private static void AddCardsToDeck(ref List<BaseCard> deck, ClassificationTypes classification, int amount)
        {
            var potentialCards = CardLibrary.Where(c => c.Classification == classification).ToList();

            int cardsAdded = 0;

            while (cardsAdded < amount)
            {
                int selected = Random.Range(0, potentialCards.Count);
                var card = potentialCards[selected];

                var deckCopy = deck.ToList();

                bool canBeAdded = (card.DeckGenerationRequirements.Count == 0 || 
                                   card.DeckGenerationRequirements.All(req => req.MeetsRequirement(deckCopy))) && 
                                   card.MeetsRarityRequirements() &&
                                   deck.Count(c => c.CardName.Equals(card.CardName)) < 4;
                
                if (canBeAdded)
                {
                    deck.Add(card);
                    cardsAdded++;
                }
            }
        }

        private static bool MeetsRarityRequirements(this BaseCard card)
        {
            float chanceToAdd;

            float rng = Random.Range(0f, 1f);

            switch (card.Rarity)
            {
                case Rarity.Common:
                    chanceToAdd = 1f;
                    break;
                case Rarity.Uncommon:
                    chanceToAdd = 0.7f;
                    break;
                case Rarity.Rare:
                    chanceToAdd = 0.5f;
                    break;
                case Rarity.UltraRare:
                    chanceToAdd = 0.3f;
                    break;
                default:
                    chanceToAdd = 1f;
                    break;
            }

            return rng <= chanceToAdd;
        }

        public static void ResetStartingCharacterPool()
        {
            _availableStartingCharacters = new List<BaseCard>();
            _availableStartingCharacters.AddRange(AllStartingCharacters);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/BaseLocation.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards
{
    public class BaseLocation : BaseCard, IPersistentCard
    {
        protected sealed override Type GetCardType()
        {
            return Type.Location;
        }
        
        public virtual void OnInPlayBeforeTurnAction() { }
        public virtual void OnInPlayAfterTurnAction() { }

        public virtual bool CanPerformInPlayAction() { return false; }
        public virtual void OnInPlayAction(List<BaseCard> targets = null) { }

        public virtual void OnEnterInPlayAction()
        {
            BaseCard existingLocation = this.Player.InPlay.Cards.Concat(this.Player.OppositePlayer.InPlay.Cards)
                    .SingleOrDefault(c => c.Type == Type.Location && c != this);

            if (existingLocation != null)
            {
                existingLocation.Player.Discard.Add(existingLocation);
            }
        }
        public virtual void OnExitInPlayAction() { }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Adventures/VoldemortRevealed.cs<|end_filename|>
﻿using System.Linq;
using HarryPotterUnity.Cards.BasicBehavior;
using HarryPotterUnity.Cards.PlayerConstraints;
using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards.Adventures
{
    public class VoldemortRevealed : AdventureWithConstraints
    {
        protected override void AddConstraints()
        {
            this._constraints.Add(new CannotPlayTypeConstraint(Type.Spell));
        }

        protected override bool CanOpponentSolve()
        {
            return true;
        }

        protected override void Solve()
        {
            this.Player.OppositePlayer.TakeDamage(this, 7);
        }

        protected override void Reward()
        {
            var cards = this.Player.OppositePlayer.Discard.NonHealingCards.Take(2);

            this.Player.OppositePlayer.Deck.AddAll(cards);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/PlayRequirements/RemoveLessonFromPlayRequirement.cs<|end_filename|>
﻿using System.Linq;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Game;
using HarryPotterUnity.Utils;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.Cards.PlayRequirements
{
    public class RemoveLessonFromPlayRequirement : MonoBehaviour, ICardPlayRequirement
    {

        [SerializeField, UsedImplicitly]
        private int _amountRequired;

        [SerializeField, UsedImplicitly]
        private LessonTypes _lessonType;

        [SerializeField, UsedImplicitly]
        private bool _returnToHand;

        private Player _player;

        public bool ReturnToHand { get; set; }

        public LessonTypes LessonType { get { return this._lessonType; } }

        private void Start()
        {
            this._player = this.GetComponent<BaseCard>().Player;
            this.ReturnToHand = this._returnToHand;
        }
        public bool MeetsRequirement()
        {
            return this._player.InPlay
                .LessonsOfType(this._lessonType).Count() >= this._amountRequired;
        }

        public void OnRequirementMet()
        {
            var lessons = this._player.InPlay.LessonsOfType(this._lessonType)
                .Take(this._amountRequired).ToList();

            if (this.ReturnToHand)
            {
                this._player.Hand.AddAll(lessons);
            }
            else
            {
                this._player.Discard.AddAll(lessons);
            }
        }

        public void Reset()
        {
            this.ReturnToHand = this._returnToHand;
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Potions/Locations/MoaningMyrtlesBathroom.cs<|end_filename|>
﻿using HarryPotterUnity.Game;

namespace HarryPotterUnity.Cards.Potions.Locations
{
    public class MoaningMyrtlesBathroom : BaseLocation
    {
        public override void OnInPlayBeforeTurnAction()
        {
            this.CheckCharacter(this.Player);
        }

        public override void OnInPlayAfterTurnAction()
        {
            this.CheckCharacter(this.Player.OppositePlayer);
        }

        private void CheckCharacter(Player p)
        {
            if (p.InPlay.Characters.Count >= 2)
            {
                p.AddActions(1);
            }
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/UI/SubMenuManager.cs<|end_filename|>
﻿using HarryPotterUnity.UI.Menu;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.UI
{
    public class SubMenuManager : MonoBehaviour
    {
        private BaseMenu _currentMenu;
        
        public void ShowMenu(BaseMenu menu)
        {
            if (menu.IsOpen)
            {
                return;
            }

            this._currentMenu = menu;
            this._currentMenu.IsOpen = true;
        }

        public void HideMenu()
        {
            if (this._currentMenu == null) return;

            this._currentMenu.IsOpen = false;
            this._currentMenu.OnHideMenu();
            this._currentMenu = null;
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Potions/Items/CollapsibleCauldron.cs<|end_filename|>
﻿using HarryPotterUnity.Cards.BasicBehavior;

namespace HarryPotterUnity.Cards.Potions.Items
{
    public class CollapsibleCauldron : ItemLessonProvider
    {
        public override void OnExitInPlayAction()
        {
            this.Player.Hand.Add(this);
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Transfiguration/Spells/SwitchingSpell.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.PlayRequirements;
using UnityEngine;

namespace HarryPotterUnity.Cards.Transfiguration.Spells
{
    [RequireComponent(typeof(InputRequirement))]
    public class SwitchingSpell : BaseSpell
    {
        public override List<BaseCard> GetFromHandActionTargets()
        {
            return this.Player.InPlay.Items.Concat(this.Player.OppositePlayer.InPlay.Items).ToList();
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            var item = targets.Single();

            var newItem = this.Player.Deck.Items.Skip(Random.Range(0, this.Player.Deck.Items.Count)).First();

            item.Player.Discard.Add(item);

            this.Player.Hand.Add(newItem);

            this.Player.Deck.Shuffle();
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Charms/Spells/Obliviate.cs<|end_filename|>
﻿using System.Collections.Generic;

namespace HarryPotterUnity.Cards.Charms.Spells
{
    public class Obliviate : BaseSpell
    {
        protected override void SpellAction(List<BaseCard> targets)
        {
            int handCount = this.Player.OppositePlayer.Hand.Cards.Count;
            
            for (int i = handCount - 1; i >= 0; i--)
            {
                var card = this.Player.OppositePlayer.Hand.Cards[i];

                this.Player.OppositePlayer.Discard.Add(card);
            }
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/BaseLesson.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Enums;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.Cards {

    public sealed class BaseLesson : BaseCard, IPersistentCard, ILessonProvider
    {
        #region Inspector Settings
        [Header("Lesson Settings")]
        [SerializeField, UsedImplicitly]
        private LessonTypes _lessonType;
        #endregion
        
        public LessonTypes LessonType { get { return this._lessonType; } }
        public int AmountLessonsProvided { get; set; }

        protected override void Start()
        {
            base.Start();
            this.AmountLessonsProvided = 1;
        }

        protected override Type GetCardType() { return Type.Lesson; }

        public bool CanPerformInPlayAction() { return false; }
        public void OnInPlayAction(List<BaseCard> targets = null) { }

        public void OnEnterInPlayAction() { }

        public void OnExitInPlayAction()
        {
            //Reset Amount Provided, cards like wand shop may alter this value during play
            this.AmountLessonsProvided = 1;
        }

        public void OnInPlayBeforeTurnAction() { }
        public void OnInPlayAfterTurnAction() { }
        
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/PlayRequirements/DiscardPileContainsRequirement.cs<|end_filename|>
﻿using System.Linq;
using HarryPotterUnity.Enums;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.Cards.PlayRequirements
{
    class DiscardPileContainsRequirement : MonoBehaviour, ICardPlayRequirement
    {
        [SerializeField, UsedImplicitly]
        private Type _type;

        [SerializeField, UsedImplicitly]
        private int _minimumAmount;

        public bool MeetsRequirement()
        {
            var player = this.GetComponent<BaseCard>().Player;

            return player.Discard.Cards.Count(card => card.Type == this._type) >= this._minimumAmount;
        }

        public void OnRequirementMet() { }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Characters/AlbusDumbledore.cs<|end_filename|>
﻿using System.Linq;
using HarryPotterUnity.Cards.PlayRequirements;
using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards.Characters
{
    public class AlbusDumbledore : BaseCharacter
    {
        public override void OnEnterInPlayAction()
        {
            var lessonRequirements = this.Player.AllCards.Where(c => c.Type == Type.Spell)
                .Select(spell => spell.GetComponent<LessonRequirement>())
                .Where(req => req.AmountRequired >= 6);

            foreach (var requirement in lessonRequirements)
            {
                requirement.AmountRequired -= 2;
            }
        }

        public override void OnExitInPlayAction()
        {
            var lessonRequirements = this.Player.AllCards.Where(c => c.Type == Type.Spell)
                .Select(spell => spell.GetComponent<LessonRequirement>())
                .Where(req => req.AmountRequired >= 6);

            foreach (var requirement in lessonRequirements)
            {
                requirement.ResetRequirement();
            }
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/UI/MenuManager.cs<|end_filename|>
﻿using System.Linq;
using HarryPotterUnity.UI.Menu;
using JetBrains.Annotations;
using UnityEngine;
using UnityEngine.UI;

namespace HarryPotterUnity.UI
{
    public class MenuManager : MonoBehaviour
    {
        [SerializeField]
        private BaseMenu _currentMenu;

        [SerializeField]
        private SubMenuManager _subMenuManager;

        private Text _networkStatus;
        private Text _playersOnline;

        private void Awake()
        {
            this._subMenuManager = this.GetComponent<SubMenuManager>();

            var allTextObjects = FindObjectsOfType<Text>();

            this._networkStatus = allTextObjects.SingleOrDefault(t => t.name.Contains("NetworkStatusDetailed"));
            this._playersOnline = allTextObjects.SingleOrDefault(t => t.name.Contains("PlayersOnlineLabel"));
        }

        [UsedImplicitly]
        public void OnJoinedLobby()
        {
            this.ShowMenu(this._currentMenu);
        }

        public void ShowMenu(BaseMenu menu)
        {
            if (this._currentMenu != null)
            {
                this._currentMenu.IsOpen = false;
            }

            this._currentMenu = menu;
            this._currentMenu.IsOpen = true;

            this._subMenuManager.HideMenu();
        }

        [UsedImplicitly]
        public void HideMenu()
        {
            if (this._currentMenu != null)
            {
                this._currentMenu.IsOpen = false;
            }

            this._currentMenu = null;
        }

        private void Update()
        {
            this._playersOnline.text = string.Format("Players Online: {0}", PhotonNetwork.countOfPlayers);
            this._networkStatus.text = string.Format("Network Status: {0}", PhotonNetwork.connectionStateDetailed);
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/DeckGeneration/Requirements/DeckContainsLessonTypeRequirement.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Cards;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Enums;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.DeckGeneration.Requirements
{
    public class DeckContainsLessonTypeRequirement : MonoBehaviour, IDeckGenerationRequirement
    {
        [SerializeField, UsedImplicitly] private LessonTypes _type;

        public bool MeetsRequirement(List<BaseCard> currentDeck)
        {
            return currentDeck.Exists(c => c is ILessonProvider && ((ILessonProvider)c).LessonType == this._type);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Adventures/UnusualPets.cs<|end_filename|>
﻿using System.Linq;
using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards.Adventures
{
    public class UnusualPets : BaseAdventure
    {
        protected override bool MeetsAdditionalPlayRequirements()
        {
            return this.Player.OppositePlayer.InPlay.Creatures.Any();
        }

        public override void OnInPlayBeforeTurnAction()
        {
            this.Player.OppositePlayer.TakeDamage(this, 4);
        }

        protected override bool CanOpponentSolve()
        {
            return this.Player.OppositePlayer.InPlay.Creatures.Count >= 2;
        }

        protected override void Solve()
        {
            int amountToDiscard = 2;

            for (int i = 0; i < amountToDiscard; i++)
            {
                var card = this.Player.OppositePlayer.InPlay.GetRandomCard(ofType:Type.Creature);

                this.Player.Discard.Add(card);
            }
        }

        protected override void Reward()
        {
            this.Player.OppositePlayer.Deck.DrawCard();
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Spells/Defence.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards.Quidditch.Spells
{
    public class Defence : BaseSpell
    {
        protected override void SpellAction(List<BaseCard> targets)
        {
            //Creature, Spell, Item, Location, Match, Adventure, Character
            this.Player.TypeImmunity.Add(Type.Creature);
            this.Player.TypeImmunity.Add(Type.Spell);
            this.Player.TypeImmunity.Add(Type.Item);
            this.Player.TypeImmunity.Add(Type.Location);
            this.Player.TypeImmunity.Add(Type.Match);
            this.Player.TypeImmunity.Add(Type.Adventure);
            this.Player.TypeImmunity.Add(Type.Character);

            this.Player.OnNextTurnStartEvent += () =>
            {
                this.Player.TypeImmunity.Remove(Type.Creature);
                this.Player.TypeImmunity.Remove(Type.Spell);
                this.Player.TypeImmunity.Remove(Type.Item);
                this.Player.TypeImmunity.Remove(Type.Location);
                this.Player.TypeImmunity.Remove(Type.Match);
                this.Player.TypeImmunity.Remove(Type.Adventure);
                this.Player.TypeImmunity.Remove(Type.Character);
            };
        }
    }
}

<|start_filename|>Assets/Editor/HarryPotterExtensions/CreateCardWindow.cs<|end_filename|>
﻿using System;
using System.IO;
using System.Linq;
using HarryPotterUnity.Cards;
using HarryPotterUnity.Cards.PlayRequirements;
using HarryPotterUnity.DeckGeneration.Requirements;
using HarryPotterUnity.Enums;
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
using Type = HarryPotterUnity.Enums.Type;

namespace HarryPotterExtensions
{
    public class CreateCardWindow : EditorWindow 
    {
        private struct CreateCardRequest
        {
            public string CardName { get; set; }
            public Texture2D CardGraphic { get; set; }

            public Type CardType { get; set; }
            public ClassificationTypes Classification { get; set; }
            public Rarity Rarity { get; set; }
            
            public bool AddLessonRequirement { get; set; }
            public LessonTypes LessonType { get; set; }
            public int LessonAmtRequired { get; set; }
            
            public bool AddCardLimit { get; set; }
            public int MaxAllowedInDeck { get; set; }
            
            public bool AddExistingScript { get; set; }
            public MonoScript AttachedScript { get; set; }

            public bool IsValid
            {
                get
                {
                    bool validScript = false;

                    if (AddExistingScript == false)
                    {
                        validScript = true;
                    }
                    else if (AttachedScript != null)
                    {
                        validScript = AttachedScript.GetClass().IsSubclassOf(typeof(BaseCard));
                    }

                    return CardGraphic != null && !string.IsNullOrEmpty(CardName)
                        && validScript;
                }
            }
        }

        private CreateCardRequest _cardRequest;


        [MenuItem("Harry Potter TCG/Add New Card"), UsedImplicitly]
        public static void AddCard()
        {
            GetWindow(typeof(CreateCardWindow), true, "New Card");
        }
        
        private static void CreateCard(CreateCardRequest request)
        {
            GameObject card = InstantiateCardTemplate();

            try
            {
                card.transform.name = request.CardName;

                var newMaterial = CreateNewMaterial(request);
                card.transform.FindChild("Front").gameObject.GetComponent<Renderer>().material = newMaterial;

                AddChosenComponents(request, card);

                TryCreatePrefab(request, card);

                DestroyImmediate(card);
            }
            catch (Exception e)
            {
                Debug.Log(string.Format("Error: {0}", e.Message));
                Debug.Log("Card was not created");
                Destroy(card);
            }
        }

        private static GameObject InstantiateCardTemplate()
        {
            string templatePath =
                AssetDatabase.GetAllAssetPaths().Single(path => path.Contains("CardTemplate.prefab"));

            var template = AssetDatabase.LoadAssetAtPath(templatePath, typeof (GameObject)) as GameObject;

            return (GameObject) Instantiate(template, Vector3.zero, Quaternion.identity);
        }

        private static Material CreateNewMaterial(CreateCardRequest request)
        {
            string newMaterialAssetPath = string.Format("Assets/Materials/{0}/{1}/{2}Mat.mat",
                request.CardType == Type.Match ? "Matches" : request.CardType + "s",
                request.Classification,
                request.CardName);

            if (request.Classification == ClassificationTypes.Character || request.Classification == ClassificationTypes.Adventure)
            {
                var stringToRemove = request.Classification + "/";
                newMaterialAssetPath = newMaterialAssetPath.Replace(stringToRemove, "");
            }

            var material = new Material(Shader.Find("Diffuse"))
            {
                mainTexture = request.CardGraphic
            };
            
            if (AssetDatabase.GetAllAssetPaths().Contains(newMaterialAssetPath) == false)
            {
                var path = Path.GetDirectoryName(newMaterialAssetPath);
                if (path != null && !Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }
                AssetDatabase.CreateAsset(material, newMaterialAssetPath);
            }
            else
            {
                Debug.LogError(string.Format("Asset at {0} already exists!", newMaterialAssetPath));
                return null;
            }
            
            return material;
        }

        private static void AddChosenComponents(CreateCardRequest request, GameObject card)
        {
            if (request.AddLessonRequirement)
            {
                if (request.CardType == Type.Adventure)
                {
                    var newComponent = card.AddComponent<GenericLessonRequirement>();
                    newComponent.AmountRequired = request.LessonAmtRequired;
                }
                else
                {
                    var newComponent = card.AddComponent<LessonRequirement>();
                    newComponent.AmountRequired = request.LessonAmtRequired;
                    newComponent.LessonType = request.LessonType;
                }
            }

            if (request.AddCardLimit)
            {
                var newComponent = card.AddComponent<DeckCardLimitRequirement>();
                newComponent.MaximumAmountAllowed = request.MaxAllowedInDeck;
            }

            if (request.AddExistingScript)
            {
                var scriptType = request.AttachedScript.GetClass();

                var newComponent = (BaseCard) card.AddComponent(scriptType);
                newComponent.Classification = request.Classification;
                newComponent.Rarity = request.Rarity;
            }
        }

        private static void TryCreatePrefab(CreateCardRequest request, GameObject card)
        {
            string newPrefabAssetPath = string.Format("Assets/Prefabs/Resources/Cards/{0}/{1}/{2}.prefab",
                request.Classification,
                request.CardType == Type.Match ? "Matches" : request.CardType + "s",
                request.CardName);

            if (request.Classification == ClassificationTypes.Character || request.Classification == ClassificationTypes.Adventure)
            {
                var stringToRemove = request.Classification + "/";
                newPrefabAssetPath = newPrefabAssetPath.Replace(stringToRemove, "");
            }

            if (AssetDatabase.LoadAssetAtPath(newPrefabAssetPath, typeof (GameObject)))
            {
                string message = string.Format("A prefab already exists at\n{0}\nReplace?", newPrefabAssetPath);

                if (EditorUtility.DisplayDialog("Prefab already exists", message, "Yes", "No"))
                {
                    CreatePrefab(newPrefabAssetPath, card);
                }
            }
            else
            {
                CreatePrefab(newPrefabAssetPath, card);
            }
        }

        private static void CreatePrefab(string newPrefabAssetPath, GameObject card)
        {
            var path = Path.GetDirectoryName(newPrefabAssetPath);
            if (path != null && !Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }

            Object empty = PrefabUtility.CreateEmptyPrefab(newPrefabAssetPath);
            var newPrefab = PrefabUtility.ReplacePrefab(card, empty);

            Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(newPrefabAssetPath);
            EditorGUIUtility.PingObject(newPrefab);
            EditorUtility.FocusProjectWindow();
        }

        [UsedImplicitly]
        // ReSharper disable once InconsistentNaming
        public void OnGUI()
        {
            GUILayout.BeginVertical();
            GUILayout.Label("Create New Card", EditorStyles.boldLabel);

            ChooseCardTexture();
            ChooseCardProperties();
            ChooseAddLessonRequirement();
            ChooseAddCardLimitRequirement();
            ChooseScript();
            ValidateRequest();

            GUILayout.EndVertical();
        }

        private void ValidateRequest()
        {
            GUILayout.Space(10);
            if (!_cardRequest.IsValid) return;

            if (GUILayout.Button("Create Card"))
            {
                EditorApplication.delayCall += () => CreateCard(_cardRequest);
                Close();
            }
        }

        private void ChooseAddCardLimitRequirement()
        {
            GUILayout.Space(10);

            _cardRequest.AddCardLimit = EditorGUILayout.BeginToggleGroup("Add Card Limit", _cardRequest.AddCardLimit);

            GUILayout.BeginHorizontal();
            GUILayout.Label("Maximum Allowed In Deck: ");
            _cardRequest.MaxAllowedInDeck = EditorGUILayout.IntField(_cardRequest.MaxAllowedInDeck);
            GUILayout.EndHorizontal();

            EditorGUILayout.EndToggleGroup();
        }

        private void ChooseAddLessonRequirement()
        {
            GUILayout.Space(10);

            _cardRequest.AddLessonRequirement = EditorGUILayout.BeginToggleGroup("Add Lesson Requirement", _cardRequest.AddLessonRequirement);

            if (_cardRequest.CardType != Type.Adventure)
            {
                _cardRequest.LessonType = (LessonTypes)EditorGUILayout.EnumPopup("Lesson Type: ", _cardRequest.LessonType);
            }

            GUILayout.BeginHorizontal();
            GUILayout.Label("Amount Required: ");
            _cardRequest.LessonAmtRequired = EditorGUILayout.IntField(_cardRequest.LessonAmtRequired);
            GUILayout.EndHorizontal();

            EditorGUILayout.EndToggleGroup();
        }

        private void ChooseCardProperties()
        {
            GUILayout.Space(10);

            _cardRequest.CardType = (Type) EditorGUILayout.EnumPopup("Card Type: ", _cardRequest.CardType);
            _cardRequest.Classification = (ClassificationTypes) EditorGUILayout.EnumPopup("Classification: ", _cardRequest.Classification);
            _cardRequest.Rarity = (Rarity)EditorGUILayout.EnumPopup("Rarity: ", _cardRequest.Rarity);
        }

        private void ChooseCardTexture()
        {
            GUILayout.Space(10);

            _cardRequest.CardGraphic =
                (Texture2D)
                    EditorGUILayout.ObjectField("Card Image:", _cardRequest.CardGraphic, typeof (Texture2D), false);
            if (_cardRequest.CardGraphic != null)
            {
                _cardRequest.CardName = _cardRequest.CardGraphic.name.Replace("_", "").Replace("'", "");
            }
        }

        private void ChooseScript()
        {
            GUILayout.Space(20);

            _cardRequest.AddExistingScript = EditorGUILayout.BeginToggleGroup("Add Existing BaseCard Script", _cardRequest.AddExistingScript);

            _cardRequest.AttachedScript =
                (MonoScript) EditorGUILayout.ObjectField("Script:", _cardRequest.AttachedScript, typeof (MonoScript), false);

            EditorGUILayout.EndToggleGroup();
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Charms/Spells/Incendio.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.BasicBehavior;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Utils;
using UnityEngine;

namespace HarryPotterUnity.Cards.Charms.Spells
{
    public class Incendio : TargetedDamageSpell
    {
        protected override void SpellAction(List<BaseCard> targets)
        {
            this.DamageAmount = this.Player.InPlay.LessonsOfType(LessonTypes.Charms).Count();

            base.SpellAction(targets);
        }

    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Game/GameManager.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards;
using HarryPotterUnity.Tween;
using HarryPotterUnity.UI.Camera;
using UnityEngine;

namespace HarryPotterUnity.Game
{
    public class DebugModeOptions
    {
        public List<GameObject> Player1Deck { get; set; }
        public List<GameObject> Player2Deck { get; set; }
        public GameObject Player1StartingCharacter { get; set; }
        public GameObject Player2StartingCharacter { get; set; }
    }

    public static class GameManager
    {
        public const int PREVIEW_LAYER = 9;
        public const int CARD_LAYER = 10;
        public const int VALID_CHOICE_LAYER = 11;
        public const int IGNORE_RAYCAST_LAYER = 2;
        public const int DECK_LAYER = 12;

        public static bool IsInputGathererActive { get; set; }

        public static byte NetworkIdCounter { get; set; }

        public static readonly List<BaseCard> AllCards = new List<BaseCard>();

        public static readonly PreviewCamera PreviewCamera = GameObject.Find("Preview Camera").GetComponent<PreviewCamera>();

        public static readonly TweenQueue TweenQueue = new TweenQueue();

        public static PhotonView Network { get; set; }

        public static void DisableCards(IEnumerable<BaseCard> cards)
        {
            foreach (var card in cards) card.Disable();
        }

        public static bool DebugModeEnabled { get; set; }

        public static DebugModeOptions DebugModeOptions { get; set; }

        public static void EnableCards(IEnumerable<BaseCard> cards)
        {
            foreach (var card in cards) card.Enable();
        }

        public static List<BaseCard> GetPlayerTestDeck(int playerId)
        {
            var prefabList = playerId == 0
                ? DebugModeOptions.Player1Deck
                : DebugModeOptions.Player2Deck;

            return prefabList.Select(o => o.GetComponent<BaseCard>()).ToList();
        }

        public static BaseCard GetPlayerTestCharacter(int playerId)
        {
            var obj = playerId == 0
                ? DebugModeOptions.Player1StartingCharacter
                : DebugModeOptions.Player2StartingCharacter;

            return obj.GetComponent<BaseCard>();
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Spells/BiasedCommentary.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Utils;

namespace HarryPotterUnity.Cards.Quidditch.Spells
{
    public class BiasedCommentary : BaseSpell
    {
        private bool _hasEffectedTriggered;

        protected override void SpellAction(List<BaseCard> targets)
        {
            this._hasEffectedTriggered = false;
            this.Player.OnCardPlayedEvent += this.AddDamageToQuidditchCards;

            this.Player.OnNextTurnStartEvent += () => this.Player.OnCardPlayedEvent -= this.AddDamageToQuidditchCards;

        }

        private void AddDamageToQuidditchCards(BaseCard card, List<BaseCard> targets)
        {
            if (card.IsQuidditchDamageCard() == false) return;
            if (this._hasEffectedTriggered) return;

            this._hasEffectedTriggered = true;

            ((IDamageSpell)card).DamageAmount += 5;
        }

        
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Potions/Items/SelfStirringCauldron.cs<|end_filename|>
﻿using HarryPotterUnity.Cards.BasicBehavior;

namespace HarryPotterUnity.Cards.Potions.Items
{
    public class SelfStirringCauldron : ItemLessonProvider
    {
        public override void OnEnterInPlayAction()
        {
            this.Player.AddActions(1);
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/BasicBehavior/AdventureWithConstraints.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Cards.PlayerConstraints;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.Cards.BasicBehavior
{
    public abstract class AdventureWithConstraints : BaseAdventure
    {
        [Header("Adventure Settings")]
        [SerializeField, UsedImplicitly]
        protected readonly List<IPlayerConstraint> _constraints = new List<IPlayerConstraint>();

        protected abstract void AddConstraints();

        protected override void Start()
        {
            base.Start();
            this.AddConstraints();
        }

        public override void OnEnterInPlayAction()
        {
            foreach (var constraint in this._constraints)
            {
                this.Player.OppositePlayer.Constraints.Add(constraint);
            }
        }

        public override void OnExitInPlayAction()
        {
            foreach (var constraint in this._constraints)
            {
                this.Player.OppositePlayer.Constraints.Remove(constraint);
            }
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/CareOfMagicalCreatures/Creatures/WhompingWillow.cs<|end_filename|>
﻿using System.Linq;
using UnityEngine;

namespace HarryPotterUnity.Cards.CareOfMagicalCreatures.Creatures
{
    public class WhompingWillow : BaseCreature
    {
        public override void OnInPlayBeforeTurnAction()
        {
            if (this.Player.OppositePlayer.InPlay.Items.Any())
            {
                var item = this.Player.OppositePlayer.InPlay.Items.Skip(Random.Range(0, this.Player.OppositePlayer.InPlay.Items.Count))
                        .First();

                this.Player.OppositePlayer.Discard.Add(item);
            }
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Transfiguration/Spells/TransfigurationExam.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;

namespace HarryPotterUnity.Cards.Transfiguration.Spells
{
    public class TransfigurationExam : BaseSpell
    {
        protected override bool MeetsAdditionalPlayRequirements()
        {
            return this.Player.InPlay.Creatures
                                .Concat(this.Player.OppositePlayer.InPlay.Creatures)
                                .Any();
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            var playerCreatures = this.Player.InPlay.Creatures;
            var enemyCreatures = this.Player.OppositePlayer.InPlay.Creatures;

            this.Player.OppositePlayer.Discard.AddAll(enemyCreatures);
            this.Player.Discard.AddAll(playerCreatures);
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Charms/Spells/HoverCharm.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.PlayRequirements;
using UnityEngine;

namespace HarryPotterUnity.Cards.Charms.Spells
{
    [RequireComponent(typeof(InputRequirement))]
    public class HoverCharm : BaseSpell
    {
        public override List<BaseCard> GetFromHandActionTargets()
        {
            return this.Player.OppositePlayer.InPlay.CardsExceptStartingCharacter;
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            var target = targets.Single();

            this.Player.OppositePlayer.Hand.Add(target);
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/BasicBehavior/ItemLessonProvider.cs<|end_filename|>
﻿using System;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Enums;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.Cards.BasicBehavior
{
    public class ItemLessonProvider : BaseItem, ILessonProvider
    {
        #region Inspector Settings
        [Header("Lesson Provider Settings")]
        [SerializeField, UsedImplicitly]
        private LessonTypes _lessonType;
        [SerializeField, UsedImplicitly]
        private int _amountLessonsProvided;
        #endregion

        public LessonTypes LessonType { get {return this._lessonType; } }

        public int AmountLessonsProvided
        {
            get
            {
                return this._amountLessonsProvided;
            }
            set
            {
                throw new InvalidOperationException("Cannot change the amount of lessons provided by ItemLessonProvider");
            }
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Items/BluebottleBroom.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.BasicBehavior;

namespace HarryPotterUnity.Cards.Quidditch.Items
{
    public class BluebottleBroom : ItemLessonProvider
    {
        public override void OnInPlayAction(List<BaseCard> targets = null)
        {
            var card = this.Player.Discard.NonHealingCards.FirstOrDefault();

            if (card != null)
            {
                this.Player.Deck.Add(card);
            }

            this.Player.UseActions();
        }

        public override bool CanPerformInPlayAction()
        {
            return this.Player.CanUseActions() && this.Player.IsLocalPlayer;
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Charms/Spells/Accio.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;

namespace HarryPotterUnity.Cards.Charms.Spells
{
    public class Accio : BaseSpell
    {
        protected override void SpellAction(List<BaseCard> targets)
        {
            var lessons = this.Player.Discard.Lessons.Take(2);

            this.Player.Hand.AddAll(lessons);
        }

        protected override bool MeetsAdditionalPlayRequirements()
        {
            return this.Player.Discard.Lessons.Count >= 2;
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/BaseMatch.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Game;
using JetBrains.Annotations;
using UnityEngine;
using UnityEngine.UI;

namespace HarryPotterUnity.Cards
{
    public abstract class BaseMatch : BaseCard, IPersistentCard
    {
        private Player _player1;
        private Player _player2;

        private int _p1Progress;
        private int _p2Progress;
        
        private GameObject _uiCanvas;
        private Text _p1ProgressLabel;
        private Text _p2ProgressLabel;

        [Header("Match Settings")]
        [SerializeField, UsedImplicitly]
        private int _goal;

        public abstract void OnPlayerHasWonMatch(Player winner, Player loser);

        protected override void Start()
        {
            base.Start();

            this._player1 = this.Player;
            this._player2 = this.Player.OppositePlayer;
            this.LoadUiOverlay();
        }

        private void LoadUiOverlay()
        {
            //Reuse the CreatureUIOverlay for now
            var resource = Resources.Load("CreatureUIOverlay");
            this._uiCanvas = (GameObject)Instantiate(resource);

            this._uiCanvas.transform.position = this.transform.position - Vector3.back;
            this._uiCanvas.transform.SetParent(this.transform, true);
            this._uiCanvas.transform.localRotation = this.Player.IsLocalPlayer ? Quaternion.identity : Quaternion.Euler(0f, 0f, 180f);

            //TODO: Change Match progress label colors based on local/remote value
            this._p1ProgressLabel = this._uiCanvas.transform.FindChild("HealthLabel").gameObject.GetComponent<Text>();
            this._p2ProgressLabel = this._uiCanvas.transform.FindChild("AttackLabel").gameObject.GetComponent<Text>();

            this.UpdateProgressLabels();

            this._uiCanvas.SetActive(false);
        }

        private void UpdateProgressLabels()
        {
            this._p1ProgressLabel.text = string.Format("{0}/{1}", this._p1Progress, this._goal);
            this._p2ProgressLabel.text = string.Format("{0}/{1}", this._p2Progress, this._goal);
        }

        //Ensure no other matches are in play
        protected override bool MeetsAdditionalPlayRequirements()
        {
            return this.Player.InPlay.Cards
                .Concat(this.Player.OppositePlayer.InPlay.Cards)
                .Count(c => c.Type == Type.Match) == 0;
        }

        public void OnEnterInPlayAction()
        {
            this._uiCanvas.SetActive(true);
            this.SubscribeToMatchProgressEvents();
        }

        public void OnExitInPlayAction()
        {
            this._uiCanvas.SetActive(false);
            this.UnsubscribeToMatchProgressEvents();
        }

        private void SubscribeToMatchProgressEvents()
        {
            this._player1.OnDamageTakenEvent += this.OnDamageTakenEvent;
            this._player2.OnDamageTakenEvent += this.OnDamageTakenEvent;
        }

        private void UnsubscribeToMatchProgressEvents()
        {
            this._player1.OnDamageTakenEvent -= this.OnDamageTakenEvent;
            this._player2.OnDamageTakenEvent -= this.OnDamageTakenEvent;
        }

        private void OnDamageTakenEvent(BaseCard sourceCard, int amount)
        {
            //BUG: Causes damage to count towards the enemy player when a player plays a card that causes himself to take damage.
            var cardOwner = sourceCard.Player;
            
            if (cardOwner == this._player1)
            {
                this.IncreasePlayer1Progress(amount);
            }
            else if (cardOwner == this._player2)
            {
                this.IncreasePlayer2Progress(amount);
            }
        }

        private void IncreasePlayer1Progress(int amount)
        {
            this._p1Progress += amount;
            this.UpdateProgressLabels();

            if (this._p1Progress >= this._goal)
            {
                this.Player.Discard.Add(this);
                this.OnPlayerHasWonMatch(winner: this._player1, loser: this._player2);
            }
        }

        private void IncreasePlayer2Progress(int amount)
        {
            this._p2Progress += amount;
            this.UpdateProgressLabels();

            if (this._p2Progress >= this._goal)
            {
                this.Player.Discard.Add(this);
                this.OnPlayerHasWonMatch(winner: this._player2, loser: this._player1);
            }
        }
        
        protected sealed override Type GetCardType()
        {
            return Type.Match;
        }

        public virtual void OnInPlayBeforeTurnAction() { }
        public virtual void OnInPlayAfterTurnAction() { }
        public virtual void OnInPlayAction(List<BaseCard> targets = null) { }
        public virtual bool CanPerformInPlayAction() { return false; }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Characters/TheFamousHarryPotter.cs<|end_filename|>
﻿namespace HarryPotterUnity.Cards.Characters
{
    public class TheFamousHarryPotter : BaseCharacter
    {
        public override void OnInPlayBeforeTurnAction()
        {
            if (this.Player.Hand.Cards.Count <= 4)
            {
                this.Player.Deck.DrawCard();
            }
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Spells/BludgerInTheStands.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.Interfaces;
using UnityEngine;

namespace HarryPotterUnity.Cards.Quidditch.Spells
{
    public class BludgerInTheStands : BaseSpell, IDamageSpell
    {
        public int DamageAmount { get; set; }

        protected override void Start()
        {
            base.Start();
            this.DamageAmount = 4;
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            this.Player.OppositePlayer.TakeDamage(this, this.DamageAmount);

            var item = this.Player.OppositePlayer.InPlay.Items.Skip(Random.Range(0, this.Player.OppositePlayer.InPlay.Items.Count))
                    .First();

            this.Player.OppositePlayer.Discard.Add(item);

            this.DamageAmount = 4;
        }
        
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/BasicBehavior/DiscardLessonFromPlaySpell.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Utils;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.Cards.BasicBehavior
{
    public class DiscardLessonFromPlaySpell : BaseSpell
    {
        [Header("Lesson Discard Settings")]
        [SerializeField, UsedImplicitly] private LessonTypes _lessonType;
        [SerializeField, UsedImplicitly] private int _amount;


        protected override bool MeetsAdditionalPlayRequirements()
        {
            return this.Player.OppositePlayer.InPlay.LessonsOfType(this._lessonType).Any();
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            var lessonsToDiscard = this.Player.OppositePlayer.InPlay.LessonsOfType(this._lessonType).Take(this._amount);

            this.Player.OppositePlayer.Discard.AddAll(lessonsToDiscard);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Transfiguration/Spells/Transmogrify.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.PlayRequirements;
using UnityEngine;

namespace HarryPotterUnity.Cards.Transfiguration.Spells
{
    [RequireComponent(typeof(InputRequirement))]
    public class Transmogrify : BaseSpell
    {
        public override List<BaseCard> GetFromHandActionTargets()
        {
            return this.Player.InPlay.Creatures;
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            var creature = targets.Single();

            var newCreature = this.Player.Deck.Creatures.Skip(Random.Range(0, this.Player.Deck.Creatures.Count)).First();

            this.Player.Discard.Add(creature);

            this.Player.Hand.Add(newCreature);

            this.Player.Deck.Shuffle();
        }

        
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Tween/TweenQueue.cs<|end_filename|>
﻿using System.Collections;
using System.Collections.Generic;
using HarryPotterUnity.Utils;
using UnityEngine;

namespace HarryPotterUnity.Tween
{
    public class TweenQueue
    {
        private Queue<ITweenObject> _queue;

        private bool _tweenQueueRunning;

        private float _timeUntilNextTween;

        public bool TweenQueueIsEmpty { get; private set; }

        public TweenQueue()
        {
            this._queue = new Queue<ITweenObject>();
            this._tweenQueueRunning = false;
            this._timeUntilNextTween = 0f;
            this.TweenQueueIsEmpty = true;
        }

        
        public void AddTweenToQueue(ITweenObject tweenObject)
        {
            this._queue.Enqueue(tweenObject);
            this.TweenQueueIsEmpty = false;

            if (this._tweenQueueRunning) return;

            this._tweenQueueRunning = true;
            StaticCoroutine.DoCoroutine(this.RunTweenQueue());
        }

        private IEnumerator RunTweenQueue()
        {
            while (this._tweenQueueRunning)
            {
                if (this._queue.Count == 0)
                {
                    this.TweenQueueIsEmpty = true;
                    yield return null;
                }
                else
                {
                    yield return new WaitForSeconds(this._timeUntilNextTween);
                    ITweenObject tween = this._queue.Dequeue();

                    tween.ExecuteTween();
                    this._timeUntilNextTween = tween.TimeUntilNextTween;

                    yield return new WaitForSeconds(tween.CompletionTime);
                }
            }
        }

        public void Reset()
        {
            this._queue = new Queue<ITweenObject>();
            this._tweenQueueRunning = false;
            this.TweenQueueIsEmpty = true;

            iTween.Stop();

            StaticCoroutine.Die();
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Transfiguration/Items/PorcupineRobe.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards.Transfiguration.Items
{
    public class PorcupineRobe : BaseItem
    {
        private readonly List<BaseCard> _enemyCreatures = new List<BaseCard>();

        public override void OnInPlayBeforeTurnAction()
        {
            foreach (var creature in this._enemyCreatures.Cast<BaseCreature>())
            {
                creature.TakeDamage(1);
            }

            this._enemyCreatures.Clear();
        }

        public override void OnEnterInPlayAction()
        {
            this.Player.OnDamageTakenEvent += this.CountCreatureDamage;
        }

        public override void OnExitInPlayAction()
        {
            this.Player.OnDamageTakenEvent -= this.CountCreatureDamage;
        }

        private void CountCreatureDamage(BaseCard source, int amount)
        {
            if (source.Type == Type.Creature)
            {
                this._enemyCreatures.Add(source);
            }
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Game/Discard.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Tween;
using UnityEngine;

namespace HarryPotterUnity.Game
{
    public class Discard : CardCollection
    {
        private static readonly Vector2 _discardPositionOffset = new Vector2(-355f, -30f);

        private void Start ()
        {
            this.Cards = new List<BaseCard>();

            var col = this.gameObject.AddComponent<BoxCollider>();
            col.isTrigger = true;
            col.size = new Vector3(50f, 70f, 1f);
            col.center = new Vector3(_discardPositionOffset.x, _discardPositionOffset.y, 0f);
        }

        public override void Add(BaseCard card) 
        {
            this.Cards.Add(card);
            card.Enable();
            
            card.transform.parent = this.transform;

            var cardPos = this.GetTargetPositionForCard(card);

            Vector3 cardPreviewPos = cardPos;
            cardPreviewPos.z -= 20f;
            
            var previewTween = new MoveTween
            {
                Target = card.gameObject,
                Position = cardPreviewPos,
                Time = 0.35f,
                Flip = FlipState.FaceUp,
                Rotate = TweenRotationType.NoRotate,
                OnCompleteCallback = () => card.State = State.Discarded
            };

            var finalTween = new MoveTween
            {
                Target = card.gameObject,
                Position = cardPos,
                Time = 0.25f,
                Flip = FlipState.FaceUp,
                Rotate = TweenRotationType.NoRotate,
                OnCompleteCallback = () => card.State = State.Discarded
            };

            GameManager.TweenQueue.AddTweenToQueue( previewTween );
            GameManager.TweenQueue.AddTweenToQueue( finalTween );

            this.MoveToThisCollection(card);
        }

        protected override void Remove(BaseCard card)
        {
            this.Cards.Remove(card);
        }

        public override void AddAll(IEnumerable<BaseCard> cards)
        {

            var cardList = cards as List<BaseCard> ?? cards.ToList();

            foreach (var card in cardList)
            {
                this.Add(card);
            }

            this.AdjustCardSpacing();
        }

        protected override void RemoveAll(IEnumerable<BaseCard> cards)
        {
            foreach (var card in cards)
            {
                this.Cards.Remove(card);
            }

            this.AdjustCardSpacing();
        }
        
        private void AdjustCardSpacing()
        {
            ITweenObject tween = new AsyncMoveTween
            {
                Targets = this.Cards.ToList(),
                GetPosition = this.GetTargetPositionForCard
            };
            GameManager.TweenQueue.AddTweenToQueue(tween);
        }

        private Vector3 GetTargetPositionForCard(BaseCard card)
        {
            if (!this.Cards.Contains(card)) return card.transform.localPosition;

            int position = this.Cards.IndexOf(card);

            var cardPos = new Vector3(_discardPositionOffset.x, _discardPositionOffset.y, 16f);
            cardPos.z -= position * 0.2f;
            
            return cardPos;
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Tween/FlipCardsTween.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Cards;
using HarryPotterUnity.Enums;
using UnityEngine;

namespace HarryPotterUnity.Tween
{
    public class FlipCardsTween : ITweenObject
    {
        
        public List<GameObject> Targets { get; set; }
        public float TimeUntilNextTween { get; set; }
        public FlipState Flip { get; set; }
        
        public float CompletionTime
        {
            get { return 0.3f; }
        }
        
        public void ExecuteTween()
        {
            float targetFlip = 0f;
            switch (this.Flip)
            {
                case FlipState.FaceUp:
                    targetFlip = 0f;
                    break;
                case FlipState.FaceDown:
                    targetFlip = 180f;
                    break;
            }

            foreach (var target in this.Targets)
            {
                target.GetComponent<BaseCard>().FlipState = this.Flip;

                iTween.RotateTo(target, iTween.Hash("time", this.CompletionTime,
                    "y", targetFlip,
                    "easetype", iTween.EaseType.EaseInOutSine,
                    "islocal", true
                    ));
            }
            
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Spells/PullingUp.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Cards.Interfaces;

namespace HarryPotterUnity.Cards.Quidditch.Spells
{
    public class PullingUp : BaseSpell, IDamageSpell
    {
        public int DamageAmount { get; set; }

        protected override void Start()
        {
            base.Start();
            this.DamageAmount = 2;
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            this.Player.OppositePlayer.TakeDamage(this, this.DamageAmount);

            this.Player.Deck.DrawCard();
            this.Player.Deck.DrawCard();

            this.DamageAmount = 2;
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Charms/Locations/Gringotts.cs<|end_filename|>
﻿namespace HarryPotterUnity.Cards.Charms.Locations
{
    public class Gringotts : BaseLocation
    {
        public override void OnInPlayBeforeTurnAction()
        {
            this.Player.AddActions(1);
        }

        public override void OnEnterInPlayAction()
        {
            base.OnEnterInPlayAction();
            this.Player.AddActions(1);
        }

        public override void OnInPlayAfterTurnAction()
        {
            base.OnEnterInPlayAction();
            this.Player.OppositePlayer.AddActions(1);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Transfiguration/Spells/Incarcifors.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.PlayRequirements;
using UnityEngine;

namespace HarryPotterUnity.Cards.Transfiguration.Spells
{
    [RequireComponent(typeof(InputRequirement))]
    public class Incarcifors : BaseSpell
    {
        public override List<BaseCard> GetFromHandActionTargets()
        {
            return this.Player.OppositePlayer.InPlay.Creatures;
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            var target = targets.Single();

            this.Player.OppositePlayer.Discard.Add(target);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Utils/FlagsPropertyDrawer.cs<|end_filename|>
﻿using JetBrains.Annotations;
using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
#endif


namespace HarryPotterUnity.Utils
{
    public class EnumFlagsAttribute : PropertyAttribute { }

#if UNITY_EDITOR
    [CustomPropertyDrawer(typeof(EnumFlagsAttribute)), UsedImplicitly]
    public class EnumFlagsAttributeDrawer : PropertyDrawer
    {

        private const float SPACING = 1.4f;
        private const float BUTTON_WIDTH = 95f;

        public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
        {
            return base.GetPropertyHeight(property, label) + EditorGUIUtility.singleLineHeight * SPACING * property.enumNames.Length * 0.5f;
        }

        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            int buttonsIntValue = 0;
            int enumLength = property.enumNames.Length;
            var buttonPressed = new bool[enumLength];

            EditorGUI.LabelField(new Rect(position.x, position.y, EditorGUIUtility.labelWidth, position.height), label);

            EditorGUI.BeginChangeCheck();

            for (int i = 0; i < enumLength; i++)
            {
                // Check if the button is/was pressed 
                if ((property.intValue & (1 << i)) == 1 << i)
                {
                    buttonPressed[i] = true;
                }

                var buttonPos = new Rect
                {
                    x = i % 2 == 1 ? position.width - BUTTON_WIDTH : position.width - BUTTON_WIDTH*2 - 2*SPACING,
                    y = i % 2 == 0 ? position.y + (EditorGUIUtility.singleLineHeight*i*SPACING*0.5f) : position.y + EditorGUIUtility.singleLineHeight*(i - 1)*SPACING*0.5f,
                    width = BUTTON_WIDTH,
                    height = EditorGUIUtility.singleLineHeight
                };

                buttonPressed[i] = GUI.Toggle(buttonPos, buttonPressed[i], property.enumNames[i], "Button");

                if (buttonPressed[i])
                    buttonsIntValue += 1 << i;
            }

            if (EditorGUI.EndChangeCheck())
            {
                property.intValue = buttonsIntValue;
            }
        }
    }
#endif
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/UI/LessonDescriptions.cs<|end_filename|>
﻿using System;
using HarryPotterUnity.Enums;
using UnityEngine;
using UnityEngine.UI;

namespace HarryPotterUnity.UI
{
    public class LessonDescriptions : MonoBehaviour
    {
        private struct LessonDescription
        {
            public string Title { get; set; }
            public string Style { get; set; }
            public string Description { get; set; }
            public Color32 TextColor { get; set; }
        }

        private readonly LessonDescription _creatures = new LessonDescription
        {
            TextColor = new Color32(0xFF, 0x83, 0x00, 0xFF),
            Title = "<b>Care of Magical Creatures</b>",
            Style = "Style: <b>High & Consistent Damage</b>",
            Description = "Crucial for almost every deck, Care of Magical Creatures allows you to play Creature cards, which provide a consistent damage source against your opponent once per turn."
        };

        private readonly LessonDescription _charms = new LessonDescription
        {
            TextColor = new Color32(0x30, 0x98, 0xFF, 0xFF),
            Title = "<b>Charms</b>",
            Style = "Style: <b>Low But Versatile Damage</b>",
            Description = "Charms are spells with low damage that make up for it in versatility, charms have the ability to target creatures as well as your opponent, so they can be used for removal when you are in a pinch."
        };

        private readonly LessonDescription _transfiguration = new LessonDescription
        {
            TextColor = new Color32(0xF3, 0x20, 0x2D, 0xFF),
            Title = "<b>Transfiguration</b>",
            Style = "Style: <b>Card Removal / Defensive</b>",
            Description = "Transfiguration spells provide various ways of card removal that help you control the board, however, transfiguration spells cannot damage your opponent directly, so they are purely defensive."
        };

        private readonly LessonDescription _potions = new LessonDescription
        {
            TextColor = new Color32(0x00, 0xC6, 0x1A, 0xFF),
            Title = "<b>Potions</b>",
            Style = "Style: <b>Cheap & Powerful, But At A Cost</b>",
            Description = "Potions contain very powerful spells that are cheap, versatile, and deal high damage, but they often require a lesson sacrifice."
        };

        private readonly LessonDescription _quidditch = new LessonDescription
        {
            TextColor = new Color32(0xE8, 0xE8, 0x2E, 0xFF),
            Title = "<b>Quidditch</b>",
            Style = "Style: <b>Dual Effects</b>",
            Description = "Quidditch cards have a relatively high cost but often contain multiple effects, such as dealing damage to your opponent AND crippling their ability to play certain cards on their next turn."
        };

        private Text _lessonDescription;

        private void Start()
        {
            this._lessonDescription = this.GetComponent<Text>();
        }

        public void SwitchText(string lesson)
        {
            LessonDescription newDescription;
            var type = (LessonTypes) Enum.Parse(typeof(LessonTypes), lesson);
            switch (type)
            {
                case LessonTypes.Creatures:
                    newDescription = this._creatures;
                    break;
                case LessonTypes.Charms:
                    newDescription = this._charms;
                    break;
                case LessonTypes.Transfiguration:
                    newDescription = this._transfiguration;
                    break;
                case LessonTypes.Potions:
                    newDescription = this._potions;
                    break;
                case LessonTypes.Quidditch:
                    newDescription = this._quidditch;
                    break;
                default:
                    throw new ArgumentException("Bad Parameter to LessonDescription.SwitchText");
            }

            this._lessonDescription.color = newDescription.TextColor;
            this._lessonDescription.text = string.Format("{0}\n{1}\n\n{2}", 
                newDescription.Title, 
                newDescription.Style,
                newDescription.Description);
        }

        public void ClearText()
        {
            this._lessonDescription.color = Color.clear;
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Characters/DracoMalfoySlytherin.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards.Characters
{
    public class DracoMalfoySlytherin : BaseCharacter
    {
        private bool HasEffectActivated { get; set; }

        public override void OnEnterInPlayAction()
        {
            this.Player.OnCardPlayedEvent += this.AddActionOnItemPlayedEvent;
        }

        private void AddActionOnItemPlayedEvent(BaseCard card, List<BaseCard> targets)
        {
            if (this.HasEffectActivated == false && card.Type == Type.Item)
            {
                this.HasEffectActivated = true;

                this.Player.AddActions(1);
            }
        }

        public override void OnExitInPlayAction()
        {
            this.HasEffectActivated = false;

            this.Player.OnCardPlayedEvent -= this.AddActionOnItemPlayedEvent;
        }

        public override void OnInPlayAfterTurnAction()
        {
            this.HasEffectActivated = false;
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Potions/Spells/PotionsProject.cs<|end_filename|>
﻿namespace HarryPotterUnity.Cards.Potions.Spells
{
    public class PotionsProject : PotionsHomework
    {
        protected override void Start()
        {
            base.Start();
            this.LessonTargetCount = 2;
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Potions/Locations/PotionsDungeon.cs<|end_filename|>
﻿using System.Linq;
using HarryPotterUnity.Cards.PlayRequirements;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Game;

namespace HarryPotterUnity.Cards.Potions.Locations
{
    public class PotionsDungeon : BaseLocation
    {
        public override void OnEnterInPlayAction()
        {
            base.OnEnterInPlayAction();

            var removeLessonFromPlayRequirements = GameManager.AllCards
                .Select(c => c.GetComponent<RemoveLessonFromPlayRequirement>())
                .Where(req => req != null && req.LessonType == LessonTypes.Potions);

            foreach (var requirement in removeLessonFromPlayRequirements)
            {
                requirement.ReturnToHand = true;
            }


        }

        public override void OnExitInPlayAction()
        {
            base.OnExitInPlayAction();

            var removeLessonFromPlayRequirements = GameManager.AllCards
                .Select(c => c.GetComponent<RemoveLessonFromPlayRequirement>())
                .Where(req => req != null && req.LessonType == LessonTypes.Potions);

            foreach (var requirement in removeLessonFromPlayRequirements)
            {
                requirement.Reset();
            }
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Potions/Spells/ManegroPotion.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Utils;

namespace HarryPotterUnity.Cards.Potions.Spells
{
    public class ManegroPotion : BaseSpell
    {
        protected override void SpellAction(List<BaseCard> targets)
        {
            int damage = this.Player.InPlay.LessonsOfType(LessonTypes.Potions).Count();
            var lesson = this.Player.InPlay.LessonsOfType(LessonTypes.Potions).First();

            this.Player.Discard.Add(lesson);

            this.Player.OppositePlayer.TakeDamage(this, damage);
        }

        protected override bool MeetsAdditionalPlayRequirements()
        {
            return this.Player.InPlay.LessonsOfType(LessonTypes.Potions).Any();
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/CareOfMagicalCreatures/Creatures/CreatureDamageBattlecry.cs<|end_filename|>
﻿using HarryPotterUnity.Cards.Interfaces;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.Cards.CareOfMagicalCreatures.Creatures
{
    public class CreatureDamageBattlecry : BaseCreature
    {
        [UsedImplicitly, SerializeField, Space(10)]
        private int _battlecryDamage;

        public override void OnEnterInPlayAction()
        {
            base.OnEnterInPlayAction();

            this.Player.OppositePlayer.TakeDamage(this, this._battlecryDamage);
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Enums/Enums.cs<|end_filename|>
﻿using System;

namespace HarryPotterUnity.Enums
{
    public enum State
    {
        InDeck, InHand, InPlay, Discarded
    }

    public enum Type
    {
        Lesson, Creature, Spell, Item, Location, Match, Adventure, Character
    }

    [Flags]
    public enum Tag
    {
        Unique      = 1 << 0,
        Healing     = 1 << 1,
        Wand        = 1 << 2,
        Cauldron    = 1 << 3,
        Broom       = 1 << 4,
        Plant       = 1 << 5,
        Owl         = 1 << 6
    }

    public enum Rarity
    {
        Common, Uncommon, Rare, UltraRare
    }

    public enum LessonTypes
    {
        Creatures, Charms, Transfiguration, Potions, Quidditch
    }


    public enum ClassificationTypes
    {
        CareOfMagicalCreatures, Charms, Transfiguration, Potions, Quidditch,
        Lesson,
        Character,
        Adventure
    }

    public enum FlipState
    {
        FaceUp, FaceDown
    }

    public enum TweenRotationType
    {
        NoRotate, Rotate90, Rotate180
    }

    public enum InputGatherMode
    {
        FromHandAction,
        InPlayAction
    }

    public static class EnumExtensions
    {
        public static ClassificationTypes ToClassification(this LessonTypes type)
        {
            switch (type)
            {
                case LessonTypes.Creatures: return ClassificationTypes.CareOfMagicalCreatures;
                case LessonTypes.Charms: return ClassificationTypes.Charms;
                case LessonTypes.Transfiguration: return ClassificationTypes.Transfiguration;
                case LessonTypes.Quidditch: return ClassificationTypes.Quidditch;
                case LessonTypes.Potions: return ClassificationTypes.Potions;
                default:
                    throw new ArgumentException("Unable to map lesson type");
            }
        }

        public static bool IsTopRow(this Type type)
        {
            return type == Type.Item
                || type == Type.Location
                || type == Type.Adventure
                || type == Type.Match;
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/UI/Menu/FindMatchMenu.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using ExitGames.Client.Photon;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Game;
using JetBrains.Annotations;
using UnityEngine;
using UnityEngine.UI;

namespace HarryPotterUnity.UI.Menu
{
    public class FindMatchMenu : BaseMenu
    {
        private List<Toggle> _lessonSelectButtons;
        private SubMenuManager _subMenuManager;
        private ErrorPanel _errorPanel;

        private Button _startMatchmakingButton;
        private Button _cancelMatchmakingButton;

        private Text _findMatchStatus;

        protected override void Awake()
        {
            base.Awake();

            this._lessonSelectButtons = FindObjectsOfType<Toggle>().ToList();

            this._subMenuManager = FindObjectOfType<SubMenuManager>();
            this._errorPanel = FindObjectOfType<ErrorPanel>();

            var allButtons = FindObjectsOfType<Button>().ToList();
            this._startMatchmakingButton = allButtons.Find(b => b.name.Contains("StartMatchmakingButton"));
            this._cancelMatchmakingButton = allButtons.Find(b => b.name.Contains("BackToMainMenuButton"));

            this._findMatchStatus = FindObjectsOfType<Text>().First(t => t.name.Contains("FindMatchStatus"));
        }

        [UsedImplicitly]
        public void StartMatchmaking()
        {
            var selectedLessons = this.GetSelectedLessons();
            
            if (selectedLessons.Count == 2 || selectedLessons.Count == 3)
            {
                var lessonBytes = selectedLessons.Select(n => (byte)n).ToArray();

                var selected = new Hashtable { { "lessons", lessonBytes } };
                PhotonNetwork.player.SetCustomProperties(selected);

                PhotonNetwork.JoinRandomRoom();

                this._startMatchmakingButton.interactable = false;
                this._cancelMatchmakingButton.interactable = false;

                this._lessonSelectButtons.ForEach(t => t.interactable = false);
            }
            else
            {
                this._errorPanel.Title = "Error!";
                this._errorPanel.Message = "You must choose two or three lesson types to enter matchmaking!";
                this._subMenuManager.ShowMenu(this._errorPanel);
            }
        }

        [UsedImplicitly]
        public void OnJoinedRoom()
        {
            this._cancelMatchmakingButton.interactable = true;
            this._findMatchStatus.GetComponent<Animator>().SetBool("IsFindingMatch", true);
        }

        [UsedImplicitly]
        public void OnLeftRoom()
        {
            this._findMatchStatus.GetComponent<Animator>().SetBool("IsFindingMatch", false);
        }
        

        private List<LessonTypes> GetSelectedLessons()
        {
            var selectedLessons = this._lessonSelectButtons
                .Where(t => t.isOn)
                .Select( t => (LessonTypes) Enum.Parse( typeof(LessonTypes), t.name) )
                .ToList();
            
            return selectedLessons;
        }

        [UsedImplicitly]
        public void CancelMatchmaking()
        {
            if (PhotonNetwork.inRoom)
            {
                PhotonNetwork.LeaveRoom();
                NetworkManager.ConnectToPhotonLobby();
            }
        }

        public override void OnShowMenu()
        {
            this._startMatchmakingButton.interactable = true;
            this._lessonSelectButtons.ForEach(t => t.interactable = true);
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Charms/Spells/AntiCheatingSpell.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Game;
using HarryPotterUnity.Tween;
using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards.Charms.Spells
{
    public class AntiCheatingSpell : BaseSpell
    {
        protected override void SpellAction(List<BaseCard> targets)
        {
            var enemyHand = this.Player.OppositePlayer.Hand.Cards.Select(card => card.gameObject).ToList();

            if (this.Player.IsLocalPlayer)
            {
                var tween = new FlipCardsTween
                {
                    Targets = enemyHand.ToList(),
                    Flip = FlipState.FaceUp,
                    TimeUntilNextTween = 1f
                };
                GameManager.TweenQueue.AddTweenToQueue(tween);
            }

            var lesson = this.Player.OppositePlayer.Hand.Lessons.FirstOrDefault();

            if (lesson != null)
            {
                this.Player.OppositePlayer.Discard.Add(lesson);
                enemyHand.Remove(lesson.gameObject);
            }

            if (this.Player.IsLocalPlayer)
            {
                var tween = new FlipCardsTween
                {
                    Targets = enemyHand,
                    Flip = FlipState.FaceDown
                };
                GameManager.TweenQueue.AddTweenToQueue(tween);
            }
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Adventures/Detention.cs<|end_filename|>
﻿using System.Linq;
using HarryPotterUnity.Cards.BasicBehavior;
using HarryPotterUnity.Cards.PlayerConstraints;
using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards.Adventures
{
    public class Detention : AdventureWithConstraints
    {
        protected override void AddConstraints()
        {
            this._constraints.Add(new CannotPlayTypeConstraint(Type.Adventure));
        }

        protected override bool CanOpponentSolve()
        {
            return this.Player.OppositePlayer.Hand.Adventures.Count >= 2;
        }

        protected override void Solve()
        {
            var adventures = this.Player.OppositePlayer.Hand.Adventures.Take(2);

            this.Player.OppositePlayer.Discard.AddAll(adventures);
        }

        protected override void Reward()
        {
            this.Player.OppositePlayer.Deck.DrawCard();
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Characters/RonYoungestBrother.cs<|end_filename|>
﻿using System.Collections.Generic;

namespace HarryPotterUnity.Cards.Characters
{
    public class RonYoungestBrother : BaseCharacter
    {
        public override bool CanPerformInPlayAction()
        {
            return this.Player.Hand.Cards.Count == 0
                && this.Player.IsLocalPlayer
                && this.Player.CanUseActions();
        }

        public override void OnInPlayAction(List<BaseCard> targets = null)
        {
            for (int i = 0; i < 5; i++)
            {
                this.Player.Deck.DrawCard();
            }

            this.Player.UseActions();
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Characters/HermioneGranger.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;

namespace HarryPotterUnity.Cards.Characters
{
    public class HermioneGranger : BaseCharacter
    {
        public override bool CanPerformInPlayAction()
        {
            return this.Player.CanUseActions() && this.Player.Hand.Lessons.Count  >= 2 && this.Player.AmountLessonsInPlay >= 2 && this.Player.IsLocalPlayer;
        }

        public override void OnInPlayAction(List<BaseCard> targets = null)
        {
            var firstLesson = this.Player.Hand.Lessons.First();
            var secondLesson = this.Player.Hand.Lessons.Last();

            this.Player.InPlay.AddAll(new [] {firstLesson, secondLesson });
            this.Player.UseActions();
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/UI/ExitButton.cs<|end_filename|>
﻿using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.UI
{
    public class ExitButton : MonoBehaviour
    {
        [UsedImplicitly]
        public void ExitGame()
        {
            //Save settings here (when we have some)
            Application.Quit();
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/CareOfMagicalCreatures/Creatures/SandstoneGargoyle.cs<|end_filename|>
﻿namespace HarryPotterUnity.Cards.CareOfMagicalCreatures.Creatures
{
    public class SandstoneGargoyle : BaseCreature
    {
        private bool _hasAddedDamage;

        public override void OnInPlayBeforeTurnAction()
        {
            if (this.Player.OppositePlayer.InPlay.Creatures.Count != 0) return;

            this._damagePerTurn += 2;
            this._attackLabel.text = (this._damagePerTurn).ToString();
            this._hasAddedDamage = true;
        }

        public override void OnInPlayAfterTurnAction()
        {
            if (!this._hasAddedDamage) return;

            this._damagePerTurn -= 2;
            this._attackLabel.text = (this._damagePerTurn).ToString();
            this._hasAddedDamage = false;
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/UI/Menu/GameplayMenu.cs<|end_filename|>
﻿using System.Linq;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Game;
using JetBrains.Annotations;
using UnityEngine;
using UnityEngine.UI;
using UnityLogWrapper;

namespace HarryPotterUnity.UI.Menu
{
    public class GameplayMenu : BaseMenu
    {
        private Player _localPlayer;
        private Player _remotePlayer;

        public Player LocalPlayer
        {
            private get { return this._localPlayer; }
            set
            {
                this._localPlayer = value;

                this._localPlayer.OnStartTurnEvent += () =>
                {
                    this._actionsLeftLabelLocal.GetComponent<Animator>().SetBool("IsOpen", true);
                    this._skipActionButton.interactable = true;
                };

                this._localPlayer.OnEndTurnEvent += () =>
                {
                    this._actionsLeftLabelLocal.GetComponent<Animator>().SetBool("IsOpen", false);
                    this._skipActionButton.interactable = false;
                };

                this._localPlayer.InPlay.OnCardEnteredPlay += card =>
                {
                        this.UpdateLessonPanel();
                };

                this._localPlayer.InPlay.OnCardExitedPlay += card =>
                {
                        this.UpdateLessonPanel();
                };

                this._localPlayer.Deck.OnDeckIsOutOfCardsEvent += this.ShowGameOverMessage;

                this.UpdateLessonPanel(forceUpdate: true);
            }
        }

        public Player RemotePlayer
        {
            private get { return this._remotePlayer; }
            set
            {
                this._remotePlayer = value;
                this._remotePlayer.OnStartTurnEvent +=
                    () => this._actionsLeftLabelRemote.GetComponent<Animator>().SetBool("IsOpen", true);
                this._remotePlayer.OnEndTurnEvent +=
                    () => this._actionsLeftLabelRemote.GetComponent<Animator>().SetBool("IsOpen", false);

                this._remotePlayer.Deck.OnDeckIsOutOfCardsEvent += this.ShowGameOverMessage;
            }
        }

        private Text _actionsLeftLabelLocal;
        private Text _actionsLeftLabelRemote;

        private Text _cardsLeftLabelLocal;
        private Text _cardsLeftLabelRemote;

        private Button _skipActionButton;

        private Text _mainMenuTitle;
        private Image _mainMenuBackground;

        private Text _lessonCountLabel;

        private Image _lessonIndicatorCreatures;
        private Image _lessonIndicatorCharms;
        private Image _lessonIndicatorTransfiguration;
        private Image _lessonIndicatorPotions;
        private Image _lessonIndicatorQuidditch;

        protected override void Awake()
        {
            base.Awake();

            var allText   = FindObjectsOfType<Text>();
            var allImages = FindObjectsOfType<Image>();

            this._mainMenuBackground = allImages.FirstOrDefault(i => i.name.Contains("MainMenuBackground"));
            this._mainMenuTitle      = allText.FirstOrDefault(i => i.name.Contains("MainMenuTitle"));

            this._actionsLeftLabelLocal  = allText.FirstOrDefault(t => t.name.Contains("ActionsLeftLabel_Local"));
            this._actionsLeftLabelRemote = allText.FirstOrDefault(t => t.name.Contains("ActionsLeftLabel_Remote"));

            this._cardsLeftLabelLocal  = allText.FirstOrDefault(t => t.name.Contains("CardsLeftLabel_Local"));
            this._cardsLeftLabelRemote = allText.FirstOrDefault(t => t.name.Contains("CardsLeftLabel_Remote"));

            this._skipActionButton = FindObjectsOfType<Button>().FirstOrDefault(b => b.name.Contains("SkipActionButton"));

            this._lessonCountLabel               = allText.FirstOrDefault(t => t.name.Contains("LessonCountLabel"));
            this._lessonIndicatorCreatures       = allImages.FirstOrDefault(i => i.name.Contains("LessonIndicator_Creatures"));
            this._lessonIndicatorCharms          = allImages.FirstOrDefault(i => i.name.Contains("LessonIndicator_Charms"));
            this._lessonIndicatorTransfiguration = allImages.FirstOrDefault(i => i.name.Contains("LessonIndicator_Transfiguration"));
            this._lessonIndicatorPotions         = allImages.FirstOrDefault(i => i.name.Contains("LessonIndicator_Potions"));
            this._lessonIndicatorQuidditch       = allImages.FirstOrDefault(i => i.name.Contains("LessonIndicator_Quidditch"));

            if (this._actionsLeftLabelLocal == null || 
                this._actionsLeftLabelRemote == null || 
                this._cardsLeftLabelLocal == null || 
                this._cardsLeftLabelRemote == null || 
                this._mainMenuTitle == null || 
                this._mainMenuBackground == null || 
                this._skipActionButton == null || 
                this._lessonCountLabel == null || 
                this._lessonIndicatorCreatures == null || 
                this._lessonIndicatorCharms == null || 
                this._lessonIndicatorTransfiguration == null || 
                this._lessonIndicatorPotions == null || 
                this._lessonIndicatorQuidditch == null)
            {
                Log.Error("Could not find all needed HUD elements in gameplay menu, report this error!");
            }
        }

        private void Start()
        {
            this._skipActionButton.interactable = false;
        }

        protected override void Update()
        {
            base.Update();

            if (this.LocalPlayer != null)
            {
                //Update Local Player Properties
                this._actionsLeftLabelLocal.text = string.Format("Actions Left: {0}", this.LocalPlayer.ActionsAvailable);
                this._cardsLeftLabelLocal.text = string.Format("Cards Left: {0}", this.LocalPlayer.Deck.Cards.Count);
            }

            if (this.RemotePlayer != null)
            {
                //Update Remote Plater Properties
                this._actionsLeftLabelRemote.text = string.Format("Actions Left: {0}", this.RemotePlayer.ActionsAvailable);
                this._cardsLeftLabelRemote.text = string.Format("Cards Left: {0}", this.RemotePlayer.Deck.Cards.Count);
            }
        }

        private void ShowGameOverMessage(Player loser)
        {
            //TODO: Test how this function behaves, show pop up here or use NetworkManager to RPC the event to both players
            //Unsub from the other game over message here, only show the win/lose based on who died first
            Log.Write("Player {0} has lost the game.", loser.NetworkId+1);
        }

        private void UpdateLessonPanel(bool forceUpdate = false)
        {
            int currentAmount;
            bool parsed = int.TryParse(this._lessonCountLabel.text, out currentAmount);

            if (!parsed) forceUpdate = true;

            if (currentAmount == this.LocalPlayer.AmountLessonsInPlay && !forceUpdate) return;

            this._lessonCountLabel.text = this.LocalPlayer.AmountLessonsInPlay <= 0 ? string.Empty : this.LocalPlayer.AmountLessonsInPlay.ToString();
            this._lessonIndicatorCreatures.gameObject.SetActive(this.LocalPlayer.LessonTypesInPlay.Contains(LessonTypes.Creatures));
            this._lessonIndicatorCharms.gameObject.SetActive(this.LocalPlayer.LessonTypesInPlay.Contains(LessonTypes.Charms));
            this._lessonIndicatorTransfiguration.gameObject.SetActive(this.LocalPlayer.LessonTypesInPlay.Contains(LessonTypes.Transfiguration));
            this._lessonIndicatorPotions.gameObject.SetActive(this.LocalPlayer.LessonTypesInPlay.Contains(LessonTypes.Potions));
            this._lessonIndicatorQuidditch.gameObject.SetActive(this.LocalPlayer.LessonTypesInPlay.Contains(LessonTypes.Quidditch));
        }
        
        [UsedImplicitly]
        public void SkipAction()
        {
            var player = this.LocalPlayer.CanUseActions() ? this.LocalPlayer : this.RemotePlayer;
            
            if (player.ActionsAvailable == 1)
            {
                this._skipActionButton.interactable = false;
            }

            GameManager.Network.RPC("ExecuteSkipAction", PhotonTargets.All);
        }

        public override void OnShowMenu()
        {
            this._mainMenuBackground.GetComponent<Animator>().SetBool("IsVisible", false);
            this._mainMenuTitle.GetComponent<Animator>().SetBool("IsVisible", false);
        }

        public override void OnHideMenu()
        {
            this._mainMenuBackground.GetComponent<Animator>().SetBool("IsVisible", true);
            this._mainMenuTitle.GetComponent<Animator>().SetBool("IsVisible", true);
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/BaseCard.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Cards.PlayRequirements;
using HarryPotterUnity.DeckGeneration;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Game;
using HarryPotterUnity.Utils;
using JetBrains.Annotations;
using UnityEngine;
using UnityLogWrapper;
using Type = HarryPotterUnity.Enums.Type;

namespace HarryPotterUnity.Cards
{
    [SelectionBase]
    public abstract class BaseCard : MonoBehaviour
    {
        [Header("Deck Generation Options")]
        [SerializeField, UsedImplicitly] private ClassificationTypes _classification;
        [SerializeField, UsedImplicitly] private Rarity _rarity;

        [Header("Card Settings")]
        [SerializeField, EnumFlags]
        [UsedImplicitly] private Tag _tags; 
        
        public State State { get; set; }
        public ClassificationTypes Classification { get { return this._classification; } set { this._classification = value; } }

        public Type Type { get { return this.GetCardType(); } }
        protected abstract Type GetCardType();

        public FlipState FlipState { private get; set; }
        public Rarity Rarity { get { return this._rarity; }  set { this._rarity = value; } }

        public Player Player { get; set; }

        private List<IDeckGenerationRequirement> _deckGenerationRequirements; 
        public List<IDeckGenerationRequirement> DeckGenerationRequirements
        {
            get
            {
                return this._deckGenerationRequirements ??
                       (this._deckGenerationRequirements = this.GetComponents<MonoBehaviour>().OfType<IDeckGenerationRequirement>().ToList());
            }
        }

        public string CardName { get { return string.Format("{0}: {1}", this.Type, this.transform.name.Replace("(Clone)", "")); } }
        public byte NetworkId { get; set; }

        private InputGatherer _inputGatherer;
        private int _fromHandActionInputRequired;
        private int _inPlayActionInputRequired;

        private static readonly Vector2 _colliderSize = new Vector2(50f, 70f);

        private GameObject _cardFace;
        private GameObject _outline;
        private GameObject _highlight;

        private List<ICardPlayRequirement> PlayRequirements { get; set; }

        private CardCollection _collection;
        public CardCollection PreviousCollection { get; private set; }
        public CardCollection CurrentCollection
        {
            get
            {
                if (this._collection == null)
                {
                    throw new Exception("collection is null for card: " + this.CardName);
                }
                return this._collection;
            }
            set
            {
                this.PreviousCollection = this._collection;
                this._collection = value;
            }

        }

        private int ActionCost
        {
            get
            {
                switch (this.GetCardType())
                {
                    case Type.Adventure:
                    case Type.Character:
                        return 2;
                    default:
                        return 1;
                }
            }
        }

        protected virtual void Start()
        {
            this.FlipState = FlipState.FaceDown;

            this.gameObject.layer = GameManager.CARD_LAYER;
            this._cardFace = this.transform.FindChild("Front").gameObject;

            this.AddCollider();

            this.LoadPlayRequirements();

            this.AddOutlineComponent();
            this.AddHighlightComponent();
        }

        private void AddOutlineComponent()
        {
            var tmp = Resources.Load("Outline");

            this._outline = (GameObject) Instantiate(tmp);
            this._outline.transform.position = this.transform.position + Vector3.back*0.3f;
            this._outline.transform.parent = this.transform;

            this._outline.SetActive(false);
        }

        private void AddHighlightComponent()
        {
            var tmp = Resources.Load("Highlight");

            this._highlight = (GameObject)Instantiate(tmp);
            this._highlight.transform.position = this.transform.position + Vector3.back * 0.2f;
            this._highlight.transform.parent = this.transform;

            this._highlight.SetActive(false);
        }

        private void LoadPlayRequirements()
        {
            this.PlayRequirements = this.GetComponents<MonoBehaviour>().OfType<ICardPlayRequirement>().ToList();

            var inputRequirement = this.PlayRequirements.OfType<InputRequirement>().SingleOrDefault();

            if (inputRequirement != null)
            {
                this._inputGatherer = this.GetComponent<InputGatherer>();
                this._fromHandActionInputRequired = inputRequirement.FromHandActionInputRequired;
                this._inPlayActionInputRequired = inputRequirement.InPlayActionInputRequired;
            }
        }

        private void AddCollider()
        {
            if (this.gameObject.GetComponent<Collider>() == null)
            {
                var col = this.gameObject.AddComponent<BoxCollider>();
                col.isTrigger = true;
                col.size = new Vector3(_colliderSize.x, _colliderSize.y, 0.2f);
            }
        }
        
        public void OnMouseOver()
        {
            this.ShowPreview();

            if ( (this.IsPlayableFromHand() || this.IsActivatable()) && GameManager.IsInputGathererActive == false)
            {
                this._outline.SetActive(true);
            }

        }

        public void OnMouseExit()
        {
            this.HidePreview();
            this._outline.SetActive(false);
        }

        public void OnMouseUp()
        {
            if (this._outline.activeSelf == false) return; //Do not call OnMouseDown if cursor has left the object

            if(GameManager.IsInputGathererActive) return; //Player clicked on this card as a target, not to activate its effect.

            if(this.IsActivatable())
            {
                if (this._inPlayActionInputRequired > 0)
                {
                    this._inputGatherer.GatherInput(InputGatherMode.InPlayAction);
                }
                else
                {
                    GameManager.Network.RPC("ExecuteInPlayActionById", PhotonTargets.All, this.NetworkId);
                }
            }
            else if (this.IsPlayableFromHand())
            {
                if (this._fromHandActionInputRequired > 0)
                {
                    this._inputGatherer.GatherInput(InputGatherMode.FromHandAction);
                }
                else
                {
                    GameManager.Network.RPC("ExecutePlayActionById", PhotonTargets.All, this.NetworkId);
                }
            }

            this._outline.SetActive(false);
        }

        private bool IsActivatable()
        {
            return this.State == State.InPlay 
                   && ((IPersistentCard) this).CanPerformInPlayAction()
                   && this.GetInPlayActionTargets().Count >= this._fromHandActionInputRequired;
        }

        private bool IsPlayableFromHand()
        {            
            bool meetsPlayRequirements = this.PlayRequirements.Count == 0 || this.PlayRequirements.All(req => req.MeetsRequirement());

            bool meetsPlayerConstraints = this.Player.Constraints.Count == 0 || this.Player.Constraints.All(req => req.MeetsConstraint(this));

            return this.Player.IsLocalPlayer && this.State == State.InHand && this.Player.CanUseActions(this.ActionCost) &&
                   meetsPlayRequirements &&
                   meetsPlayerConstraints && this.IsUnique() && this.MeetsAdditionalPlayRequirements();
        }

        public bool HasTag(Tag t)
        {
            return (this._tags & t) == t;    
        }

        private bool IsUnique()
        {
            if (this.HasTag(Tag.Unique) == false) return true;

            var allInPlayCards = this.Player.InPlay.Cards.Concat(this.Player.OppositePlayer.InPlay.Cards)
                .Select(c => c.CardName);

            return allInPlayCards.Contains(this.CardName) == false;
        }

        public void PlayFromHand(List<BaseCard> targets = null)
        {       
            foreach (var requirement in this.PlayRequirements)
            {
                requirement.OnRequirementMet();
            }

            this.OnPlayFromHandAction(targets);

            this.Player.UseActions(this.ActionCost);
            
        }

        protected virtual void OnPlayFromHandAction(List<BaseCard> targets)
        {
            if (this is IPersistentCard)
            {
                this.Player.InPlay.Add(this);
            }
            else
            {
                throw new Exception("OnPlayFromHandAction must be overriden in cards that do not implement IPersistentCard!");
            }
        }
        
        private void ShowPreview()
        {
            this._cardFace.layer = GameManager.PREVIEW_LAYER;
            
            if (this.FlipState == FlipState.FaceUp && iTween.Count(this.gameObject) == 0)
                GameManager.PreviewCamera.ShowPreview(this);
            else this.HidePreview();
        }
    
        private void HidePreview()
        {
            this._cardFace.layer = GameManager.CARD_LAYER;
            GameManager.PreviewCamera.HidePreview();
        }

        public void Disable()
        {
            this.gameObject.layer = GameManager.IGNORE_RAYCAST_LAYER;
            this._cardFace.GetComponent<Renderer>().material.color = new Color(0.35f, 0.35f, 0.35f);
        }

        public void Enable()
        {
            this.gameObject.layer = GameManager.CARD_LAYER;
            this._cardFace.GetComponent<Renderer>().material.color = Color.white;
        }

        public void SetSelected()
        {
            this.gameObject.layer = GameManager.CARD_LAYER;
            this._cardFace.GetComponent<Renderer>().material.color = Color.yellow;
        }

        public virtual List<BaseCard> GetFromHandActionTargets()
        {
            if (this._fromHandActionInputRequired == 0)
            {
                return new List<BaseCard>();
            }

            throw new NotSupportedException("Card with from hand input did not define valid targets");
        }

        public virtual List<BaseCard> GetInPlayActionTargets()
        {
            if (this._inPlayActionInputRequired == 0)
            {
                return new List<BaseCard>();
            }

            throw new NotSupportedException("Card with in play input did not define valid targets.");
        }

        protected virtual bool MeetsAdditionalPlayRequirements()
        {
            return true;
        }

        public void SetHighlight()
        {
            if (this._highlight) this._highlight.SetActive(true);
            else Log.Error("Highlight component has not yet been added.");
        }

        public void RemoveHighlight()
        {
            if(this._highlight) this._highlight.SetActive(false);
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Charms/Spells/StreamOfFlames.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Cards.PlayRequirements;
using UnityEngine;

namespace HarryPotterUnity.Cards.Charms.Spells
{
    [RequireComponent(typeof(InputRequirement))]
    public class StreamOfFlames : BaseSpell, IDamageSpell
    {
        public int DamageAmount { get; set; }

        protected override void Start()
        {
            base.Start();
            this.DamageAmount = 3;
        }

        public override List<BaseCard> GetFromHandActionTargets()
        {
            return this.Player.OppositePlayer.InPlay.Creatures;
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            var creature = targets.First() as BaseCreature;

            if (creature != null)
            {
                creature.TakeDamage(this.DamageAmount);
            }

            this.Player.OppositePlayer.TakeDamage(this, this.DamageAmount);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Matches/GryffindorMatch.cs<|end_filename|>
﻿using HarryPotterUnity.Game;

namespace HarryPotterUnity.Cards.Quidditch.Matches
{
    public class GryffindorMatch : BaseMatch
    {
        public override void OnPlayerHasWonMatch(Player winner, Player loser)
        {
            winner.OnNextTurnStartEvent += () => winner.AddActions(2);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/PlayerConstraints/IPlayerConstraint.cs<|end_filename|>
﻿namespace HarryPotterUnity.Cards.PlayerConstraints
{
    public interface IPlayerConstraint
    {
        bool MeetsConstraint(BaseCard cardToPlay);
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Spells/CatchTheSnitch.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;

namespace HarryPotterUnity.Cards.Quidditch.Spells
{
    public class CatchTheSnitch : BaseSpell
    {
        protected override bool MeetsAdditionalPlayRequirements()
        {
            return this.Player.InPlay.Matches.Any() || this.Player.OppositePlayer.InPlay.Matches.Any();
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            var match = (BaseMatch) this.Player.InPlay.Matches.Concat(this.Player.OppositePlayer.InPlay.Matches).Single();

            this.Player.Discard.Add(match);
            match.OnPlayerHasWonMatch(this.Player, this.Player.OppositePlayer);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Transfiguration/Spells/FlooPowder.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace HarryPotterUnity.Cards.Transfiguration.Spells
{
    public class FlooPowder : BaseSpell
    {
        protected override void SpellAction(List<BaseCard> targets)
        {
            var location = this.Player.Deck.Locations.Skip(Random.Range(0, this.Player.Deck.Locations.Count)).FirstOrDefault();

            if (location != null)
            {
                this.Player.Hand.Add(location);
                this.Player.Deck.Shuffle();
            }
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Spells/PowerPlay.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.Interfaces;

namespace HarryPotterUnity.Cards.Quidditch.Spells
{
    public class PowerPlay : BaseSpell, IDamageSpell
    {
        public int DamageAmount { get; set; }

        protected override void Start()
        {
            base.Start();
            this.DamageAmount = 7;
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            this.Player.OppositePlayer.TakeDamage(this, this.DamageAmount);

            this.DamageAmount = 7;
        }

        protected override bool MeetsAdditionalPlayRequirements()
        {
            return this.Player.InPlay.Matches.Any() || this.Player.OppositePlayer.InPlay.Matches.Any();
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Adventures/RunningFromFilch.cs<|end_filename|>
﻿using HarryPotterUnity.Cards.BasicBehavior;
using HarryPotterUnity.Cards.PlayerConstraints;
using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards.Adventures
{
    public class RunningFromFilch : AdventureWithConstraints
    {
        protected override void AddConstraints()
        {
            this._constraints.Add(new CannotPlayTypeConstraint(Type.Lesson));
        }

        protected override bool CanOpponentSolve()
        {
            return true;
        }

        protected override void Solve()
        {
            int handCount = this.Player.OppositePlayer.Hand.Cards.Count;

            for (int i = handCount - 1; i >= 0; i--)
            {
                var card = this.Player.OppositePlayer.Hand.Cards[i];

                this.Player.OppositePlayer.Discard.Add(card);
            }
        }

        protected override void Reward()
        {
            this.Player.OppositePlayer.Deck.DrawCard();
            this.Player.OppositePlayer.Deck.DrawCard();
            this.Player.OppositePlayer.Deck.DrawCard();
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Matches/SlytherinMatch.cs<|end_filename|>
﻿using System.Linq;
using HarryPotterUnity.Game;

namespace HarryPotterUnity.Cards.Quidditch.Matches
{
    public class SlytherinMatch : BaseMatch
    {
        public override void OnPlayerHasWonMatch(Player winner, Player loser)
        {
            var cards = winner.Discard.NonHealingCards.Take(15);

            winner.Deck.AddAll(cards);
            winner.Deck.Shuffle();
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Spells/NoHands.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;

namespace HarryPotterUnity.Cards.Quidditch.Spells
{
    public class NoHands : BaseSpell
    {
        protected override bool MeetsAdditionalPlayRequirements()
        {
            return this.Player.InPlay.Matches.Any() || this.Player.OppositePlayer.InPlay.Matches.Any();
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            var topCard = this.Player.Deck.TakeTopCard();

            this.Player.Discard.Add(topCard);

            if (topCard is BaseLesson)
            {
                var match = (BaseMatch) this.Player.InPlay.Matches.Concat(this.Player.OppositePlayer.InPlay.Matches).Single();

                this.Player.Discard.Add(match);
                match.OnPlayerHasWonMatch(this.Player, this.Player.OppositePlayer);
            }
            else
            {
                this.Player.TakeDamage(this, 3);
            }
            
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/UI/Menu/BaseMenu.cs<|end_filename|>
﻿using UnityEngine;

namespace HarryPotterUnity.UI.Menu
{
    [RequireComponent(typeof(Animator), typeof(CanvasGroup))]
    public class BaseMenu : MonoBehaviour
    {
        private Animator _animator;
        private CanvasGroup _canvasGroup;

        public bool IsOpen
        {
            get { return this._animator.GetBool("IsOpen"); }
            set
            {
                if (value == false)
                {
                    this.OnHideMenu();
                }
                else
                {
                    this.OnShowMenu();
                }
                this._animator.SetBool("IsOpen", value);
            }
        }

        protected virtual void Awake()
        {
            this._animator = this.GetComponent<Animator>();
            this._canvasGroup = this.GetComponent<CanvasGroup>();

            var rect = this.GetComponent<RectTransform>();
            rect.offsetMax = rect.offsetMin = Vector2.zero;
        }

        protected virtual void Update()
        {
            if (this._animator.GetCurrentAnimatorStateInfo(0).IsName("Open") == false)
            {
                this._canvasGroup.blocksRaycasts = this._canvasGroup.interactable = false;
            }
            else
            {
                this._canvasGroup.blocksRaycasts = this._canvasGroup.interactable = true;
            }
        }

        public virtual void OnShowMenu() { }
        public virtual void OnHideMenu() { }
    }
}

	


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Interfaces/ILessonProvider.cs<|end_filename|>
﻿using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards.Interfaces
{
    public interface ILessonProvider
    {
        LessonTypes LessonType { get; }

        int AmountLessonsProvided { get; set; }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Matches/HufflepuffMatch.cs<|end_filename|>
﻿using HarryPotterUnity.Game;

namespace HarryPotterUnity.Cards.Quidditch.Matches
{
    public class HufflepuffMatch : BaseMatch
    {
        public override void OnPlayerHasWonMatch(Player winner, Player loser)
        {
            for (int i = 0; i < 5; i++)
            {
                winner.Deck.DrawCard();
            }

            loser.TakeDamage(this, 5);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/BasicBehavior/TargetedDamageSpell.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Cards.PlayRequirements;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.Cards.BasicBehavior
{
    [RequireComponent(typeof(InputRequirement))]
    public class TargetedDamageSpell : BaseSpell, IDamageSpell
    {
        [Header("Targeted Damage Spell Settings"), Space(10)]
        [UsedImplicitly, SerializeField]
        private int _damageAmount;

        [UsedImplicitly, SerializeField]
        protected bool _canTargetPlayer;

        public int DamageAmount
        {
            get { return this._damageAmount; }
            set { this._damageAmount = value; }
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            var target = targets.Single();

            if (target is BaseCreature)
            {
                (target as BaseCreature).TakeDamage(this._damageAmount);
            }
            else
            {
                this.Player.OppositePlayer.TakeDamage(this, this._damageAmount);
            }
                
        }

        public override List<BaseCard> GetFromHandActionTargets()
        {
            var targets = new List<BaseCard>();

            if (this._canTargetPlayer) targets.Add(this.Player.OppositePlayer.Deck.StartingCharacter);

            targets.AddRange(this.Player.OppositePlayer.InPlay.Creatures.Concat(this.Player.InPlay.Creatures));

            return targets;
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/BaseSpell.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Game;
using HarryPotterUnity.Tween;
using UnityEngine;

namespace HarryPotterUnity.Cards
{
    public abstract class BaseSpell : BaseCard
    {
        private static readonly Vector3 _spellOffset = new Vector3(0f, 0f, -400f);

        protected sealed override void OnPlayFromHandAction(List<BaseCard> targets)
        {
            this.Enable();
            this.PreviewSpell();

            this.Player.Discard.Add(this);

            this.SpellAction(targets);
        }

        protected abstract void SpellAction(List<BaseCard> targets);

        private void PreviewSpell()
        {
            this.State = State.Discarded;
            var rotateType = this.Player.OppositePlayer.IsLocalPlayer ? TweenRotationType.Rotate180 : TweenRotationType.NoRotate;

            var tween = new MoveTween
            {
                Target = this.gameObject,
                Position = _spellOffset,
                Time = 0.5f,
                Flip = FlipState.FaceUp,
                Rotate = rotateType,
                TimeUntilNextTween = 0.6f
            };
            GameManager.TweenQueue.AddTweenToQueue(tween);
        }

        protected sealed override Type GetCardType()
        {
            return Type.Spell;
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Items/QuidditchCup.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards.Quidditch.Items
{
    public class QuidditchCup : BaseItem
    {

        private bool HasEffectActivated { get; set; }

        public override void OnEnterInPlayAction()
        {
            this.Player.OnCardPlayedEvent += this.AddActionOnQuidditchCardPlayedEvent;
        }

        private void AddActionOnQuidditchCardPlayedEvent(BaseCard card, List<BaseCard> targets)
        {
            if (this.HasEffectActivated == false && card.Classification == ClassificationTypes.Quidditch)
            {
                this.HasEffectActivated = true;

                this.Player.AddActions(1);
            }
        }

        public override void OnExitInPlayAction()
        {
            this.HasEffectActivated = false;

            this.Player.OnCardPlayedEvent -= this.AddActionOnQuidditchCardPlayedEvent;
        }

        public override void OnInPlayAfterTurnAction()
        {
            this.HasEffectActivated = false;
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Matches/RavenclawMatch.cs<|end_filename|>
﻿using System.Linq;
using HarryPotterUnity.Game;

namespace HarryPotterUnity.Cards.Quidditch.Matches
{
    public class RavenclawMatch : BaseMatch
    {
        public override void OnPlayerHasWonMatch(Player winner, Player loser)
        {
            var lessons = winner.Deck.Lessons.Take(2);

            winner.InPlay.AddAll(lessons);
            winner.Deck.Shuffle();
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Interfaces/IDamageSpell.cs<|end_filename|>
﻿namespace HarryPotterUnity.Cards.Interfaces
{
    public interface IDamageSpell
    {
        int DamageAmount { get; set; }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/DeckGeneration/Requirements/DeckDoesNotContainCardWithNameRequirement.cs<|end_filename|>
﻿using System.Collections.Generic;
using HarryPotterUnity.Cards;
using JetBrains.Annotations;
using UnityEngine;

namespace HarryPotterUnity.DeckGeneration.Requirements
{
    [UsedImplicitly]
    public class DeckDoesNotContainCardWithNameRequirement : MonoBehaviour, IDeckGenerationRequirement
    {
        [SerializeField, UsedImplicitly]
        private string _cardName;

        public bool MeetsRequirement(List<BaseCard> currentDeck)
        {
            return !currentDeck.Exists(card => card.CardName.Contains(this._cardName));
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/UI/Menu/ErrorPanel.cs<|end_filename|>
﻿using System.Linq;
using UnityEngine.UI;

namespace HarryPotterUnity.UI.Menu
{
    public class ErrorPanel : BaseMenu
    {
        private Text _title;
        private Text _message;

        public string Title
        {
            get { return this._title.text; }
            set { this._title.text = value; }
        }

        public string Message
        {
            get { return this._message.text; }
            set { this._message.text = value; }
        }

        protected override void Awake()
        {
            base.Awake();
            var textComponents = this.GetComponentsInChildren<Text>();
            this._title = textComponents.First(t => t.name.Contains("Title"));
            this._message = textComponents.First(t => t.name.Contains("Message"));
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Adventures/FivePointsFromGryffindor.cs<|end_filename|>
﻿namespace HarryPotterUnity.Cards.Adventures
{
    public class FivePointsFromGryffindor : BaseAdventure
    {
        public override void OnInPlayAfterTurnAction()
        {
            this.Player.OppositePlayer.OnNextTurnStartEvent += () => this.Player.OppositePlayer.AddActions(-1);
        }

        protected override bool CanOpponentSolve()
        {
            return this.Player.OppositePlayer.Hand.Cards.Count >= 5;
        }

        protected override void Solve()
        {
            int amountToDiscard = 5;

            for (int i = amountToDiscard; i > 0; i--)
            {
                var card = this.Player.OppositePlayer.Hand.GetRandomCard();

                this.Player.OppositePlayer.Discard.Add(card);
            }
        }

        protected override void Reward()
        {
            this.Player.TakeDamage(this, 5);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Spells/QuickThinking.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards.Interfaces;
using HarryPotterUnity.Cards.PlayRequirements;
using UnityEngine;

namespace HarryPotterUnity.Cards.Quidditch.Spells
{
    [RequireComponent(typeof(InputRequirement))]
    public class QuickThinking : BaseSpell, IDamageSpell
    {
        public int DamageAmount { get; set; }

        protected override void Start()
        {
            base.Start();
            this.DamageAmount = 3;
        }

        public override List<BaseCard> GetFromHandActionTargets()
        {
            return new List<BaseCard> {this.Player.Deck.StartingCharacter, this.Player.OppositePlayer.Deck.StartingCharacter };
        }

        protected override void SpellAction(List<BaseCard> targets)
        {
            var target = targets.Single();

            if (target.Player == this.Player)
            {
                this.Player.Deck.DrawCard();
                this.Player.Deck.DrawCard();
                this.Player.Deck.DrawCard();
            }
            else
            {
                this.Player.OppositePlayer.TakeDamage(this, this.DamageAmount);
            }

            this.DamageAmount = 3;
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Charms/Spells/OutOfTheWoods.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Game;
using HarryPotterUnity.Tween;

namespace HarryPotterUnity.Cards.Charms.Spells
{
    public class OutOfTheWoods : BaseSpell
    {
        protected override void SpellAction(List<BaseCard> targets)
        {
            int handCount = this.Player.OppositePlayer.Hand.Cards.Count;

            var enemyHand = this.Player.OppositePlayer.Hand.Cards.Select(card => card.gameObject).ToList();

            if(this.Player.IsLocalPlayer)
            {
                var tween = new FlipCardsTween
                {
                    Targets = enemyHand.ToList(),
                    Flip = FlipState.FaceUp,
                    TimeUntilNextTween = 1f
                };
                GameManager.TweenQueue.AddTweenToQueue( tween );
            }

            for (int i = handCount - 1; i >= 0; i--)
            {
                var card = this.Player.OppositePlayer.Hand.Cards[i];

                if (card.Type != Type.Creature) continue;

                this.Player.OppositePlayer.Discard.Add(card);

                enemyHand.Remove(card.gameObject);
            }

            if (this.Player.IsLocalPlayer)
            {
                var tween = new FlipCardsTween
                {
                    Targets = enemyHand,
                    Flip = FlipState.FaceDown
                };
                GameManager.TweenQueue.AddTweenToQueue(tween);
            }
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Game/GameConfiguration.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Cards;
using JetBrains.Annotations;
using UnityEngine;
using UnityLogWrapper;

namespace HarryPotterUnity.Game
{
    public class GameConfiguration : MonoBehaviour
    {
        [Header("Game Options")] [SerializeField, UsedImplicitly]
        private bool _enableDebugMode;

        [Header("Add pre-determined cards to each player's deck")]
        [SerializeField, UsedImplicitly]
        private List<GameObject> _player1Deck = new List<GameObject>();

        [SerializeField, UsedImplicitly]
        private List<GameObject> _player2Deck = new List<GameObject>();

        [Header("Select Default Starting Characters")] [SerializeField, UsedImplicitly]
        private GameObject _player1StartingCharacter;

        [SerializeField, UsedImplicitly]
        private GameObject _player2StartingCharacter;

        private void Awake()
        {
            GameManager.DebugModeEnabled = this._enableDebugMode;
            if (this._enableDebugMode)
            {
                this.SetDebugModeConfiguration();
            }
        }

        private void SetDebugModeConfiguration()
        {
            if (this._player1Deck.Any(o => o.GetComponent<BaseCard>() == null)
                || this._player2Deck.Any(o => o.GetComponent<BaseCard>() == null))
            {
                Log.Error(
                    "Incorrect Configuration for Player Test Decks in Game Manager (one or more objects is not a Card).");
            }

            if (this._player1StartingCharacter.GetComponent<BaseCard>() is BaseCharacter == false
                || this._player2StartingCharacter.GetComponent<BaseCard>() is BaseCharacter == false)
            {
                Log.Error(
                    "Incorrect Configuration for Player Test Decks in Game Manager (The assigned starting characters do not derive from BaseCharacter)");
            }

            GameManager.DebugModeOptions = new DebugModeOptions
            {
                Player1Deck = this._player1Deck,
                Player2Deck = this._player2Deck,
                Player1StartingCharacter = this._player1StartingCharacter,
                Player2StartingCharacter = this._player2StartingCharacter
            };
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Transfiguration/Spells/Aparecium.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Enums;
using HarryPotterUnity.Game;
using HarryPotterUnity.Tween;

namespace HarryPotterUnity.Cards.Transfiguration.Spells
{
    public class Aparecium : BaseSpell
    {
        protected override void SpellAction(List<BaseCard> targets)
        {
            int handCount = this.Player.Hand.Cards.Count;

            var hand = this.Player.Hand.Cards.Select(card => card.gameObject).ToList();

            if (!this.Player.IsLocalPlayer)
            {
                var tween = new FlipCardsTween
                {
                    Targets = hand.ToList(),
                    Flip = FlipState.FaceUp,
                    TimeUntilNextTween = 1f
                };
                GameManager.TweenQueue.AddTweenToQueue(tween);
            }
              
            for (int i = handCount - 1; i >= 0; i--)
            {
                var card = this.Player.Hand.Cards[i];

                if (card.Type != Type.Lesson) continue;

                this.Player.InPlay.Add(card);

                hand.Remove(card.gameObject);
            }

            if (!this.Player.IsLocalPlayer)
            {
                var tween = new FlipCardsTween
                {
                    Targets = hand,
                    Flip = FlipState.FaceDown
                };
                GameManager.TweenQueue.AddTweenToQueue(tween);
            }
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Items/NimbusTwoThousandOne.cs<|end_filename|>
﻿namespace HarryPotterUnity.Cards.Quidditch.Items
{
    public class NimbusTwoThousandOne : NimbusTwoThousand
    {
       
        protected override void Start()
        {
            base.Start();
            this._damageAmount = 3;
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Charms/Spells/EngorgementCharm.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;

namespace HarryPotterUnity.Cards.Charms.Spells
{
    public class EngorgementCharm : BaseSpell
    {
        protected override void SpellAction(List<BaseCard> targets)
        {
            foreach (var creature in this.Player.InPlay.Creatures.Cast<BaseCreature>())
            {
                creature.Heal(creature.MaxHealth);
            }
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Tween/ShuffleDeckTween.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using HarryPotterUnity.Cards;
using UnityEngine;
using Random = UnityEngine.Random;

namespace HarryPotterUnity.Tween
{
    public class ShuffleDeckTween : ITweenObject
    {
        private readonly IEnumerable<BaseCard> _cards;
        
        public float CompletionTime { get { return 2f; } }
        public float TimeUntilNextTween { get { return 0f; } }

        public ShuffleDeckTween(IEnumerable<BaseCard> cards)
        {
            this._cards = cards;
        }

        public void ExecuteTween()
        {
            foreach (var card in this._cards)
            {
                int cardIndex = ((IList<BaseCard>) this._cards).IndexOf(card);
                var targetPosition = new Vector3( card.transform.position.x, 
                                                  card.transform.position.y, 
                                                 (card.transform.parent.position.z + 16f) - cardIndex * 0.2f);

                var midPoint = Vector3.MoveTowards(card.transform.position, Camera.main.transform.position, 80f);
                midPoint.z = card.transform.position.z;
                
                iTween.MoveTo(card.gameObject, iTween.Hash("time", 0.5f,
                                                           "path", new[] {midPoint, targetPosition},
                                                           "easetype", iTween.EaseType.EaseInOutSine,
                                                           "delay", Random.Range(0f, 1.5f)
                                                           ));
            }
        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Quidditch/Matches/ThreeMonthLongMatch.cs<|end_filename|>
﻿using HarryPotterUnity.Game;

namespace HarryPotterUnity.Cards.Quidditch.Matches
{
    public class ThreeMonthLongMatch : BaseMatch
    {
        public override void OnPlayerHasWonMatch(Player winner, Player loser)
        {
            loser.TakeDamage(this, 15);
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/Charms/Spells/WingardiumLeviosa.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using HarryPotterUnity.Enums;

namespace HarryPotterUnity.Cards.Charms.Spells
{
    public class WingardiumLeviosa : BaseSpell
    {
        protected override void SpellAction(List<BaseCard> targets)
        {
            this.Player.TypeImmunity.Add(Type.Creature);

            this.Player.OnNextTurnStartEvent += () => this.Player.TypeImmunity.Remove(Type.Creature);

        }
    }
}


<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/PlayRequirements/GenericLessonRequirement.cs<|end_filename|>
﻿using HarryPotterUnity.Game;
using UnityEngine;

namespace HarryPotterUnity.Cards.PlayRequirements
{
    public class GenericLessonRequirement : MonoBehaviour, ICardPlayRequirement
    {
        [SerializeField]
        private int _amountRequired;

        private Player _player;

        public int AmountRequired
        {
            get { return this._amountRequired; }
            set { this._amountRequired = value; }
        }

        private void Start()
        {
            this._player = this.GetComponent<BaseCard>().Player;
        }

        public bool MeetsRequirement()
        {
            return this._player.AmountLessonsInPlay >= this._amountRequired;
        }

        public void OnRequirementMet()
        {
            
        }
    }
}

<|start_filename|>Assets/Scripts/HarryPotterUnity/Cards/PlayRequirements/LessonRequirement.cs<|end_filename|>
﻿using HarryPotterUnity.Enums;
using HarryPotterUnity.Game;
using UnityEngine;

namespace HarryPotterUnity.Cards.PlayRequirements
{
    public class LessonRequirement : MonoBehaviour, ICardPlayRequirement
    {

        [SerializeField]
        private int _amountRequired;

        [SerializeField]
        private LessonTypes _lessonType;

        private Player _player;

        public int AmountRequired
        {
            get { return this._amountRequired; }
            set { this._amountRequired = value; }
        }

        public LessonTypes LessonType
        {
            get { return this._lessonType; }
            set { this._lessonType = value; }
        }

        private int _originalAmountRequired; //Some cards may change how many lessons are required to play a card, this variable holds the "printed" amount.

        private void Start()
        {
            this._player = this.GetComponent<BaseCard>().Player;
            this._originalAmountRequired = this._amountRequired;
        }
        public bool MeetsRequirement()
        {
            return this._player.AmountLessonsInPlay >= this._amountRequired && this._player.LessonTypesInPlay.Contains(this.LessonType);
        }

        public void OnRequirementMet() { }

        public void ResetRequirement()
        {
            this.AmountRequired = this._originalAmountRequired;
        }
    }
}
