public CastRoutine SpellSavingThrow(SpellAttackEffector failure,SpellAttackEffector success,AbilityType abilityType)
    {
        return((AT.Battle.Actor caster,AT.Battle.Action cast,List <AT.ATTile> targets) => {
            AT.Battle.Cast c = cast as AT.Battle.Cast;
            foreach (AT.ATTile target in targets)
            {
                SaveSituation sit = new SaveSituation(target.FirstOccupant.ActorComponent.CharSheet,
                                                      cast.actor.CharSheet,
                                                      abilityType,
                                                      SaveContext.SPELL,
                                                      c.Spell);
                List <AT.Character.Effect.GenericEffect> resultingEffects = null;

                ResultType result = sit.GetResult();
                switch (result)
                {
                case ResultType.SUCCESS:
                    resultingEffects = success.Invoke(cast,target);
                    break;

                case ResultType.FAILURE:
                    resultingEffects = failure.Invoke(cast,target);
                    break;
                }


                CharacterCastingAnimation castingAnims = caster.GetComponent <CharacterCastingAnimation>();
                if (castingAnims != null)
                {
                    castingAnims.SetupAndDoCastAnimation(cast);
                    castingAnims.OneShotSpellRelease((animationInst) => {
                        if (resultingEffects != null)
                        {
                            foreach (AT.Character.Effect.GenericEffect effect in resultingEffects)
                            {
                                effect.ApplyTo(target.FirstOccupant.CharSheet,cast);
                            }
                        }
                    });

                    castingAnims.OneShotAnimEnded((animationInst) => {
                        cast.CallOnFinished();
                    });
                }
                else
                {
                    if (resultingEffects != null)
                    {
                        foreach (AT.Character.Effect.GenericEffect effect in resultingEffects)
                        {
                            effect.ApplyTo(target.FirstOccupant.CharSheet,cast);
                        }
                    }
                    cast.CallOnFinished();
                }
            }
        });
    }
    public CastRoutine SpellRangedAttack(SpellAttackEffector hit,
                                         MissileScript.MissileAnimationName missileName,
                                         SpellAttackEffector crit = null,
                                         SpellAttackEffector miss = null)
    {
        return((AT.Battle.Actor caster,AT.Battle.Action cast,List <AT.ATTile> targets) => {
            GameObject prefab = MissileAnimationPrefabDispenser.instance.GetAnimationPrefabByName(missileName);

            foreach (AT.ATTile target in targets)
            {
                AttackSituation sit = new AttackSituation(caster.CharSheet,target.FirstOccupant.CharSheet,cast);

                List <AT.Character.Effect.GenericEffect> resultingEffects = null;

                ResultType result = sit.GetResult();
                switch (result)
                {
                case ResultType.HIT:
                    resultingEffects = hit.Invoke(cast,target);
                    break;

                case ResultType.CRITICAL_HIT:
                    if (crit != null)
                    {
                        resultingEffects = crit.Invoke(cast,target);
                    }
                    break;

                case ResultType.MISS:
                    if (miss != null)
                    {
                        resultingEffects = miss.Invoke(cast,target);
                    }
                    break;

                case ResultType.CRITICAL_MISS:
                    //nothing
                    break;
                }


                CharacterCastingAnimation castingAnims = caster.GetComponent <CharacterCastingAnimation>();
                if (castingAnims != null)
                {
                    castingAnims.SetupAndDoCastAnimation(cast);
                    castingAnims.OneShotSpellRelease((animationInst) => {
                        GameObject copy = Instantiate(prefab);
                        copy.transform.position = caster.transform.position;
                        MissileScript missile = copy.GetComponent <MissileScript>();

                        missile.LaunchAt(target,true);
                        //Safe to not worry about unsubbing, thanks to garbage cleanup
                        missile.OnConnectedWithTarget += (MissileScript self) => {
                            if (resultingEffects != null)
                            {
                                foreach (AT.Character.Effect.GenericEffect effect in resultingEffects)
                                {
                                    if (target.FirstOccupant != null)
                                    {
                                        effect.ApplyTo(target.FirstOccupant.CharSheet,cast);
                                    }
                                }
                            }
                            //This assumes only one missile.   Needs a "Checkin of sorts, to conditionally check on finished"
                            cast.CallOnFinished();
                        };
                    });                     //ToBe ATOCIJL
                }
                else
                {
                    GameObject copy = Instantiate(prefab);
                    copy.transform.position = caster.transform.position;
                    MissileScript missile = copy.GetComponent <MissileScript>();
                    missile.LaunchAt(target.transform);
                    //Safe to not worry about unsubbing, thanks to garbage cleanup
                    missile.OnConnectedWithTarget += (MissileScript self) => {
                        if (resultingEffects != null)
                        {
                            foreach (AT.Character.Effect.GenericEffect effect in resultingEffects)
                            {
                                effect.ApplyTo(target.FirstOccupant.CharSheet,cast);
                            }
                        }
                        //This assumes only one missile.   Needs a "Checkin of sorts, to conditionally check on finished"
                        cast.CallOnFinished();
                    };
                }
            }
        });
    }
    public CastRoutine SpellTouchAttack(SpellAttackEffector hit,SpellAttackEffector crit = null,SpellAttackEffector miss = null)
    {
        return((AT.Battle.Actor caster,AT.Battle.Action cast,List <AT.ATTile> targets) => {
            foreach (AT.ATTile target in targets)
            {
                AttackSituation sit = new AttackSituation(caster.CharSheet,target.FirstOccupant.CharSheet,cast);

                List <AT.Character.Effect.GenericEffect> resultingEffects = null;

                ResultType result = sit.GetResult();
                switch (result)
                {
                case ResultType.HIT:
                    resultingEffects = hit.Invoke(cast,target);
                    break;

                case ResultType.CRITICAL_HIT:
                    if (crit != null)
                    {
                        resultingEffects = crit.Invoke(cast,target);
                    }
                    break;

                case ResultType.MISS:
                    if (miss != null)
                    {
                        resultingEffects = miss.Invoke(cast,target);
                    }
                    break;

                case ResultType.CRITICAL_MISS:
                    //nothing
                    break;
                }


                CharacterCastingAnimation castingAnims = caster.GetComponent <CharacterCastingAnimation>();
                if (castingAnims != null)
                {
                    castingAnims.SetupAndDoCastAnimation(cast);
                    castingAnims.OneShotSpellRelease((animationInst) => {
                        if (resultingEffects != null)
                        {
                            foreach (AT.Character.Effect.GenericEffect effect in resultingEffects)
                            {
                                if (target.FirstOccupant != null)
                                {
                                    effect.ApplyTo(target.FirstOccupant.CharSheet,cast);
                                }
                            }
                        }
                    });

                    castingAnims.OneShotAnimEnded((animationInst) => {
                        cast.CallOnFinished();
                    });
                }
                else
                {
                    if (resultingEffects != null)
                    {
                        foreach (AT.Character.Effect.GenericEffect effect in resultingEffects)
                        {
                            effect.ApplyTo(target.FirstOccupant.CharSheet,cast);
                        }
                    }
                    cast.CallOnFinished();
                }
            }
        });
    }
    void Start()
    {
        Spell s = new Spell();

        s.cast = SpellRangedAttack((Action cast, AT.ATTile target) => {
            AT.Character.Effect.Damage dmg = new AT.Character.Effect.Damage(AT.Character.Effect.DamageType.NECROTIC);

            int numDice = NumDiceFromScaleThresholds(new List <int> {
                5, 11, 17
            }, (cast as Cast).Spell.scaler);
            GaugeDamageRollAndTally(dmg.gauge, numDice.ToString() + "d8");

            AT.Character.Condition.ICondition condition  = new AT.Character.Condition.Necrosis(1, AT.Character.Condition.TickType.TURN_BEGIN, cast.actor.CharSheet);
            AT.Character.Effect.ConditionEffect necrosis = new AT.Character.Effect.ConditionEffect(condition);

            return(new List <AT.Character.Effect.GenericEffect> {
                dmg, necrosis
            });
        }, MissileScript.MissileAnimationName.PLACEHOLDER,
                                   (Action cast, AT.ATTile target) => {
            AT.Character.Effect.Damage dmg = new AT.Character.Effect.Damage(AT.Character.Effect.DamageType.NECROTIC);

            int numDice = NumDiceFromScaleThresholds(new List <int> {
                5, 11, 17
            }, (cast as Cast).Spell.scaler);
            GaugeDamageRollAndTally(dmg.gauge, numDice.ToString() + "d8", true);
            AT.Character.Condition.ICondition condition  = new AT.Character.Condition.Necrosis(1, AT.Character.Condition.TickType.TURN_BEGIN, cast.actor.CharSheet);
            AT.Character.Effect.ConditionEffect necrosis = new AT.Character.Effect.ConditionEffect(condition);

            return(new List <AT.Character.Effect.GenericEffect> {
                dmg, necrosis
            });
        });

        s.school         = School.NECROMANCY;
        s.name           = SpellName.CHILL_TOUCH;
        s.classType      = ClassType.WIZARD;
        s.targetingType  = TargetingType.SINGLE_ENEMY;
        s.rangeInSquares = 20;
        s.isCantrip      = true;

        s.description = "<i>Chill Touch\nMake a ranged spell attack against an enemy within 120ft.  The enemy suffers 1d8 necrotic damage, and cannot be healed until the start of the caster's next turn.</i>";
        RegisterSpell(s);


        CastRoutine sgCast = SpellTouchAttack((Action cast, AT.ATTile target) => {
            AT.Character.Effect.Damage dmg = new AT.Character.Effect.Damage(AT.Character.Effect.DamageType.LIGHTNING);

            int numDice = NumDiceFromScaleThresholds(new List <int> {
                5, 11, 17
            }, (cast as Cast).Spell.scaler);
            GaugeDamageRollAndTally(dmg.gauge, numDice.ToString() + "d8");

            AT.Character.Effect.DelegateEffect shocked = new AT.Character.Effect.DelegateEffect((character, spellCast) => {
                target.FirstOccupant.ActorComponent.PreventReactions(spellCast);
            });

            return(new List <AT.Character.Effect.GenericEffect> {
                dmg, shocked
            });
        }, (Action cast, AT.ATTile target) => {
            AT.Character.Effect.Damage dmg = new AT.Character.Effect.Damage(AT.Character.Effect.DamageType.LIGHTNING);

            int numDice = NumDiceFromScaleThresholds(new List <int> {
                5, 11, 17
            }, (cast as Cast).Spell.scaler);
            GaugeDamageRollAndTally(dmg.gauge, numDice.ToString() + "d8", true);

            AT.Character.Effect.DelegateEffect shocked = new AT.Character.Effect.DelegateEffect((character, spellCast) => {
                target.FirstOccupant.ActorComponent.PreventReactions(spellCast);
            });
            return(new List <AT.Character.Effect.GenericEffect> {
                dmg, shocked
            });
        });
        Spell shockingGrasp = new Spell();

        shockingGrasp.cast           = sgCast;
        shockingGrasp.school         = School.EVOCATION;
        shockingGrasp.name           = SpellName.SHOCKING_GRASP;
        shockingGrasp.classType      = ClassType.WIZARD;
        shockingGrasp.targetingType  = TargetingType.SINGLE_ENEMY;
        shockingGrasp.rangeInSquares = 1;
        shockingGrasp.isCantrip      = true;
        RegisterSpell(shockingGrasp);

        CastRoutine fbCast = SpellRangedAttack((Action cast, AT.ATTile target) => {
            AT.Character.Effect.Damage dmg = new AT.Character.Effect.Damage(AT.Character.Effect.DamageType.FIRE);


            int numDice = NumDiceFromScaleThresholds(new List <int> {
                5, 11, 17
            }, (cast as Cast).Spell.scaler);
            GaugeDamageRollAndTally(dmg.gauge, numDice.ToString() + "d10");

            return(new List <AT.Character.Effect.GenericEffect> {
                dmg
            });
        }, MissileScript.MissileAnimationName.PLACEHOLDER,
                                               (Action cast, AT.ATTile target) => {
            AT.Character.Effect.Damage dmg = new AT.Character.Effect.Damage(AT.Character.Effect.DamageType.FIRE);

            int numDice = NumDiceFromScaleThresholds(new List <int> {
                5, 11, 17
            }, (cast as Cast).Spell.scaler);
            GaugeDamageRollAndTally(dmg.gauge, numDice.ToString() + "d10", true);

            return(new List <AT.Character.Effect.GenericEffect> {
                dmg
            });
        });
        Spell firebolt = new Spell();

        firebolt.cast           = fbCast;
        firebolt.school         = School.EVOCATION;
        firebolt.name           = SpellName.FIREBOLT;
        firebolt.classType      = ClassType.WIZARD;
        firebolt.targetingType  = TargetingType.SINGLE_ENEMY;
        firebolt.rangeInSquares = 12;
        firebolt.isCantrip      = true;
        RegisterSpell(firebolt);

        CastRoutine poisonSprayCast = SpellSavingThrow((Action cast, AT.ATTile target) => {
            AT.Character.Effect.Damage dmg = new AT.Character.Effect.Damage(AT.Character.Effect.DamageType.POISON);
            dmg.Gauge.ChangeCurrentAndMax(Sheet.DiceRoll(12));

            return(new List <AT.Character.Effect.GenericEffect> {
                dmg
            });
        },
                                                       (Action cast, AT.ATTile target) => {
            //miss effect does nothing
            return(new List <AT.Character.Effect.GenericEffect>());
        }, AbilityType.CONSTITUTION);

        Spell poisonSpray = new Spell();

        poisonSpray.cast           = poisonSprayCast;
        poisonSpray.school         = School.CONJURATION;
        poisonSpray.name           = SpellName.POISON_SPRAY;
        poisonSpray.classType      = ClassType.WIZARD;
        poisonSpray.targetingType  = TargetingType.SINGLE_ENEMY;
        poisonSpray.rangeInSquares = 2;
        poisonSpray.isCantrip      = true;
        RegisterSpell(poisonSpray);

        CastRoutine magicMissileCast = ((caster, cast, targets) => {
            float delay = 0f;             //this doesn't work...  All missils have the same delay
            foreach (AT.ATTile target in targets)
            {
                CharacterCastingAnimation castingAnims = caster.GetComponent <CharacterCastingAnimation>();
                GameObject prefab = MissileAnimationPrefabDispenser.instance.GetAnimationPrefabByName(MissileScript.MissileAnimationName.PLACEHOLDER);
                if (castingAnims != null)
                {
                    castingAnims.SetupAndDoCastAnimation(cast);
                    castingAnims.OneShotSpellRelease((animationInst) => {
                        GameObject copy = Instantiate(prefab);
                        copy.transform.position = caster.transform.position;
                        MissileScript missile = copy.GetComponent <MissileScript>();

                        missile.DelayLaunchAt(target, delay, true);
                        //Safe to not worry about unsubbing, thanks to garbage cleanup
                        missile.OnConnectedWithTarget += (MissileScript self) => {
                            AT.Character.Effect.Damage d = new AT.Character.Effect.Damage(AT.Character.Effect.DamageType.FORCE);
                            d.Gauge.ChangeCurrentAndMax(Sheet.DiceRoll(4) + 1);
                            if (target.FirstOccupant != null)
                            {
                                d.ApplyTo(target.FirstOccupant.ActorComponent.CharSheet);
                            }
                            //This assumes only one missile.   Needs a "Checkin of sorts, to conditionally check on finished"
                            cast.CallOnFinished();
                        };
                    });                     //ToBe ATOCIJL
                }
                else
                {
                    GameObject copy = Instantiate(prefab);
                    copy.transform.position = caster.transform.position;
                    MissileScript missile = copy.GetComponent <MissileScript>();
                    missile.DelayLaunchAt(target, delay, true);
                    //Safe to not worry about unsubbing, thanks to garbage cleanup
                    missile.OnConnectedWithTarget += (MissileScript self) => {
                        AT.Character.Effect.Damage d = new AT.Character.Effect.Damage(AT.Character.Effect.DamageType.FORCE);
                        d.Gauge.ChangeCurrentAndMax(Sheet.DiceRoll(4) + 1);
                        if (target.FirstOccupant != null)
                        {
                            d.ApplyTo(target.FirstOccupant.ActorComponent.CharSheet);
                        }
                        //This assumes only one missile.   Needs a "Checkin of sorts, to conditionally check on finished"
                        cast.CallOnFinished();
                    };
                }
                delay += .2f;
            }
        });

        Spell magicMissile = new Spell();

        magicMissile.cast                 = magicMissileCast;
        magicMissile.level                = 1;
        magicMissile.school               = School.EVOCATION;
        magicMissile.name                 = SpellName.MAGIC_MISSILE;
        magicMissile.classType            = ClassType.WIZARD;
        magicMissile.targetingType        = TargetingType.MULTIPLE_ENEMY;
        magicMissile.alterCastOnSetParams = (cast) => {
            cast.NumTargets  = 3;
            cast.NumTargets += magicMissile.scaler.ScaleValue();
        };
        magicMissile.rangeInSquares = 60;

        RegisterSpell(magicMissile);
    }