public void MoveEnemies(MinionAnimationSequence animation)
        {
            List<MinionMove> minionMoves = new List<MinionMove>();
            HashSet<Minion> minionsStillMoving = new HashSet<Minion>();

            MinionAnimationBatch stepBatch = animation.AddBatch(this, Game1.WALK_ANIM_DURATION);
            // enemies walk forward
            Point levelSize = levelScript.levelSize;
            for (int x = 0; x < levelSize.X; x++) //NB processing these in ascending X order - this matters
            {
                for (int y = levelSize.Y - 1; y >= 0; y--)
                {
                    Minion m = getMinionAt(new Point(x, y));

                    if(m == null)
                        continue;

                    if(m.isEnemy && m.stats.move > 0)
                    {
                        HandleTriggers(new TriggerEvent(TriggerType.beforeMove, m));

                        Vector2 oldDrawPos = m.drawPos;
                        if (m.stats.move == 1 && m.stats.hasKeyword(Keyword.slow) && !m.slow_movedHalfWay)
                        {
                            m.slow_movedHalfWay = true;
                            stepBatch.AddAnimation(m, oldDrawPos, m.drawPos);
                        }
                        else
                        {
                            MinionMove mmove = new MinionMove(m);
                            minionMoves.Add(mmove);
                            minionsStillMoving.Add(m);
                        }
                    }
                }
            }

            CleanUp(animation);

            bool hasMoreMoves = true;
            bool firstPass = true;
            while(hasMoreMoves)
            {
                foreach (MinionMove mmove in minionMoves)
                {
                    if (mmove.movesLeft <= 0)
                        continue;

                    if (levelScript.Blocks(mmove.moveTo, TargetType.empty))
                    {
                        mmove.movesLeft = 0;
                        mmove.moveSpent = true;
                        minionsStillMoving.Remove(mmove.minion);
                    }
                    else if (minions.ContainsKey(mmove.moveTo) && !minionsStillMoving.Contains(getMinionAt(mmove.moveTo)))
                    {
                        // If I'm blocked:
                        if (mmove.minion.stats.hasKeyword(Keyword.unstoppable) || getMinionAt(mmove.moveTo).stats.hasKeyword(Keyword.intangible))
                        {
                            Destroyed(mmove.moveTo, mmove.minion);
                        }
                        mmove.moveSpent = true;
                    }
                }
                CleanUp(animation);

                hasMoreMoves = false;
                foreach (MinionMove mmove in minionMoves)
                {
                    if (mmove.movesLeft > 0)
                    {
                        if (TryMove(mmove, mmove.moveTo))
                        {
                            mmove.moveSpent = true;
                            mmove.minion.slow_movedHalfWay = false;
                            stepBatch.AddAnimation(mmove.minion, mmove.currentAnimPos, mmove.minion.drawPos);
                            mmove.currentAnimPos = mmove.minion.drawPos;
                        }

                        if (mmove.moveSpent)
                        {
                            mmove.movesLeft--;
                            mmove.moveSpent = false;
                        }

                        if (mmove.movesLeft > 0)
                        {
                            hasMoreMoves = true;
                        }
                        else
                        {
                            minionsStillMoving.Remove(mmove.minion);
                        }
                    }
                }

                if (firstPass)
                {
                    levelScript.Apply(this, stepBatch);
                    firstPass = false;
                }
                stepBatch.SetInitialGameState(new GameState(this));

                stepBatch = animation.AddBatch(this, Game1.WALK_ANIM_DURATION);
            }
        }
        public void ResolveWaitingTriggers(MinionAnimationSequence animation)
        {
            int loopCount = 0;
            while(triggersWaiting.Count > 0)
            {
                loopCount++;
                List<WaitingTrigger> triggersResolving = triggersWaiting;
                triggersWaiting = new List<WaitingTrigger>();

                bool hasAttackTriggers = false;
                foreach(WaitingTrigger trigger in triggersResolving)
                {
                    if (trigger.ability.isAttackTrigger)
                        hasAttackTriggers = true;
                    else
                        trigger.ability.Apply(new EffectContext(this, null, trigger.permanent, TriggerItem.create(trigger.permanent), trigger.evt, animation));
                }

                if (hasAttackTriggers)
                {
                    MinionAnimationBatch attackAnim = animation.AddBatch(new GameState(this), Game1.ATTACK_ANIM_DURATION);
                    MinionAnimationBatch recoverAnim = animation.AddBatch(this, Game1.RECOVER_ANIM_DURATION);
                    foreach (WaitingTrigger trigger in triggersResolving)
                    {
                        if (trigger.ability.isAttackTrigger)
                            trigger.ability.Apply(new EffectContext(this, trigger.permanent, trigger.position, trigger.evt, attackAnim, recoverAnim, animation));
                    }
                    recoverAnim.SetInitialGameState(new GameState(this));
                }

                DeadMinionsDie();

                if (loopCount > 100)
                {
                    triggersWaiting.Clear();
                }
            }
        }
        public void MinionsAttack(MinionAnimationSequence animation)
        {
            HandleTriggers(new TriggerEvent(TriggerType.beforeCombat));
            CleanUp(animation);

            MinionAnimationBatch attack = null;
            MinionAnimationBatch recover = null;
            if (animation != null)
            {
                attack = animation.AddBatch(new GameState(this), Game1.ATTACK_ANIM_DURATION);
                recover = animation.AddBatch(this, Game1.RECOVER_ANIM_DURATION);
            }

            for (int y = levelScript.levelSize.Y - 1; y >= 0; y--)
            {
                for (int x = 0; x < levelScript.levelSize.X; x++)
                {
                    Minion p = getMinionAt(new Point(x, y));
                    if (p != null)
                    {
                        p.CheckAttacks(this, 0, attack, recover, animation);
                    }
                }
            }

            recover.SetInitialGameState(new GameState(this));

            HandleTriggers(new TriggerEvent(TriggerType.afterCombat));
            CleanUp(animation);
        }