示例#1
0
 public bool IsCornerFloor(pos p)
 {
     if(p.BoundsCheck(map,false)){
         if(map[p].IsPassable() && p.ConsecutiveAdjacent(x=>map[x].IsWall()) == 5){
             int num_diagonal_walls = 0;
             foreach(int dir in U.DiagonalDirections){
                 if(map[p.PosInDir(dir)].IsWall()){
                     ++num_diagonal_walls;
                 }
             }
             if(num_diagonal_walls == 3){
                 return true;
             }
         }
     }
     return false;
 }
        public async Task<bool> CastSpell(SpellType spell, PhysicalObject obj, bool force_of_will)
        { //returns false if targeting is canceled.
            if ((await StunnedThisTurn()) && !force_of_will)
            { //eventually this will be moved to the last possible second
                return true; //returns true because turn was used up. 
            }
            if (!HasSpell(spell))
            {
                return false;
            }
            foreach (Actor a in ActorsWithinDistance(2))
            {
                if (a.HasAttr(AttrType.SPELL_DISRUPTION) && a.HasLOE(this))
                {
                    if (this == player)
                    {
                        if (CanSee(a))
                        {
                            B.Add(a.Your() + " presence disrupts your spell! ");
                        }
                        else
                        {
                            B.Add("Something disrupts your spell! ");
                        }
                    }
                    return false;
                }
            }
            Tile t = null;
            List<Tile> line = null;
            if (obj != null)
            {
                t = M.tile[obj.row, obj.col];
                if (spell == SpellType.FORCE_BEAM)
                { //force beam requires a line for proper knockback
                    line = GetBestExtendedLine(t);
                }
                else
                {
                    line = GetBestLine(t);
                }
            }
            int bonus = 0; //used for bonus damage on spells - currently, only Master's Edge adds bonus damage.
            if (FailRate(spell) > 0)
            {
                int fail = FailRate(spell);
                if (force_of_will)
                {
                    fail = magic_penalty * 5;
                    fail -= skills[SkillType.SPIRIT] * 2;
                    if (fail < 0)
                    {
                        fail = 0;
                    }
                }
                if (Global.Roll(1, 100) - fail <= 0)
                {
                    if (player.CanSee(this))
                    {
                        B.Add("Sparks fly from " + Your() + " fingers. ", this);
                    }
                    else
                    {
                        if (player.DistanceFrom(this) <= 4 || (player.DistanceFrom(this) <= 12 && player.HasLOS(row, col)))
                        {
                            B.Add("You hear words of magic, but nothing happens. ");
                        }
                    }
                    Q1();
                    return true;
                }
            }
            if (HasFeat(FeatType.MASTERS_EDGE))
            {
                foreach (SpellType s in spells_in_order)
                {
                    if (Spell.IsDamaging(s))
                    {
                        if (s == spell)
                        {
                            bonus = 1;
                        }
                        break;
                    }
                }
            }
            switch (spell)
            {
                case SpellType.SHINE:
                    if (!HasAttr(AttrType.ENHANCED_TORCH))
                    {
                        B.Add("You cast shine. ");
                        if (!M.wiz_dark)
                        {
                            B.Add("Your torch begins to shine brightly. ");
                        }
                        attrs[AttrType.ENHANCED_TORCH]++;
                        if (light_radius > 0)
                        {
                            UpdateRadius(LightRadius(), Global.MAX_LIGHT_RADIUS - attrs[AttrType.DIM_LIGHT] * 2, true);
                        }
                        Q.Add(new Event(9500, "Your torch begins to flicker a bit. "));
                        Q.Add(new Event(this, 10000, AttrType.ENHANCED_TORCH, "Your torch no longer shines as brightly. "));
                    }
                    else
                    {
                        B.Add("Your torch is already shining brightly! ");
                        return false;
                    }
                    break;
                /*			case SpellType.MAGIC_MISSILE:
                                if(t == null){
                                    t = await GetTarget();
                                }
                                if(t != null){
                                    B.Add(You("cast") + " magic missile. ",this);
                                    Actor a = FirstActorInLine(t);
                                    if(a != null){
                                        AnimateBoltProjectile(a,Color.Magenta);
                                        B.Add("The missile hits " + a.the_name + ". ",a);
                                        a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,Global.Roll(1+bonus,6),this);
                                    }
                                    else{
                                        AnimateBoltProjectile(t,Color.Magenta);
                                        if(t.IsLit()){
                                            B.Add("The missile hits " + t.the_name + ". ");
                                        }
                                        else{
                                            B.Add("You attack the darkness. ");
                                        }
                                    }
                                }
                                else{
                                    return false;
                                }
                                break;
                            case SpellType.DETECT_MONSTERS:
                                if(!HasAttr(AttrType.DETECTING_MONSTERS)){
                                    B.Add(You("cast") + " detect monsters. ",this);
                                    if(type == ActorType.PLAYER){
                                        B.Add("You can sense beings around you. ");
                                        Q.Add(new Event(this,2100,AttrType.DETECTING_MONSTERS,"You can no longer sense beings around you. "));
                                    }
                                    else{
                                        Q.Add(new Event(this,2100,AttrType.DETECTING_MONSTERS));
                                    }
                                    attrs[AttrType.DETECTING_MONSTERS]++;
                                }
                                else{
                                    B.Add("You are already detecting monsters! ");
                                    return false;
                                }
                                break;*/
                case SpellType.IMMOLATE:
                    if (t == null)
                    {
                        line = await GetTarget(12);
                        if (line != null)
                        {
                            t = line.Last();
                        }
                    }
                    if (t != null)
                    {
                        B.Add(You("cast") + " immolate. ", this);
                        Actor a = FirstActorInLine(line);
                        if (a != null)
                        {
                            AnimateBeam(line.ToFirstObstruction(), "*", Color.RandomFire);
                            if (!a.HasAttr(AttrType.RESIST_FIRE) && !a.HasAttr(AttrType.CATCHING_FIRE) && !a.HasAttr(AttrType.ON_FIRE))
                            {
                                if (a.name == "you")
                                {
                                    B.Add("You start to catch fire! ");
                                }
                                else
                                {
                                    B.Add(a.the_name + " starts to catch fire. ", a);
                                }
                                a.attrs[AttrType.CATCHING_FIRE]++;
                            }
                            else
                            {
                                B.Add(a.You("shrug") + " off the flames. ", a);
                            }
                        }
                        else
                        {
                            foreach (Tile t2 in line)
                            {
                                if (t2.Is(FeatureType.TROLL_CORPSE) || t2.Is(FeatureType.TROLL_SEER_CORPSE))
                                {
                                    line = line.To(t2);
                                }
                            }
                            AnimateBeam(line, "*", Color.RandomFire);
                            B.Add(You("throw") + " flames. ", this);
                            if (line.Last().Is(FeatureType.TROLL_CORPSE))
                            {
                                line.Last().features.Remove(FeatureType.TROLL_CORPSE);
                                B.Add("The troll corpse burns to ashes! ", line.Last());
                            }
                            if (line.Last().Is(FeatureType.TROLL_SEER_CORPSE))
                            {
                                line.Last().features.Remove(FeatureType.TROLL_SEER_CORPSE);
                                B.Add("The troll seer corpse burns to ashes! ", line.Last());
                            }
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.FORCE_PALM:
                    if (t == null)
                    {
                        t = TileInDirection(await GetDirection());
                    }
                    if (t != null)
                    {
                        Actor a = M.actor[t.row, t.col];
                        B.Add(You("cast") + " force palm. ", this);
                        //AnimateMapCell(t,Color.DarkCyan,"*");
                        B.DisplayNow();
                        Screen.AnimateMapCell(t.row, t.col, new colorchar("*", Color.Blue), 100);
                        if (a != null)
                        {
                            B.Add(You("strike") + " " + a.TheVisible() + ". ", new PhysicalObject[] { this, a });
                            string s = a.the_name;
                            string s2 = a.a_name;
                            List<Tile> line2 = GetBestExtendedLine(a.row, a.col);
                            int idx = line2.IndexOf(M.tile[a.row, a.col]);
                            Tile next = line2[idx + 1];
                            await a.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(1 + bonus, 6), this, a_name);
                            if (Global.Roll(1, 10) <= 7)
                            {
                                if (M.actor[t.row, t.col] != null)
                                {
                                    await a.GetKnockedBack(this);
                                }
                                else
                                {
                                    if (!next.passable)
                                    {
                                        B.Add(s + "'s corpse is knocked into " + next.the_name + ". ", new PhysicalObject[] { t, next });
                                    }
                                    else
                                    {
                                        if (M.actor[next.row, next.col] != null)
                                        {
                                            B.Add(s + "'s corpse is knocked into " + M.actor[next.row, next.col].the_name + ". ", new PhysicalObject[] { t, M.actor[next.row, next.col] });
                                            await M.actor[next.row, next.col].TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(1, 6), this, s2 + "'s falling corpse");
                                        }
                                    }
                                }
                            }
                        }
                        else
                        {
                            if (t.passable)
                            {
                                B.Add("You strike at empty space. ");
                            }
                            else
                            {
                                B.Add("You strike " + t.the_name + " with your palm. ");
                                if (t.ttype == TileType.DOOR_C)
                                { //heh, why not?
                                    B.Add("It flies open! ");
                                    t.Toggle(this);
                                }
                                if (t.ttype == TileType.HIDDEN_DOOR)
                                { //and this one gives it an actual use
                                    B.Add("A hidden door flies open! ");
                                    t.Toggle(this);
                                    t.Toggle(this);
                                }
                            }
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.FREEZE:
                    if (t == null)
                    {
                        line = await GetTarget(12);
                        if (line != null)
                        {
                            t = line.Last();
                        }
                    }
                    if (t != null)
                    {
                        B.Add(You("cast") + " freeze. ", this);
                        Actor a = FirstActorInLine(line);
                        if (a != null)
                        {
                            AnimateBoltBeam(line.ToFirstObstruction(), Color.Cyan);
                            if (!a.HasAttr(AttrType.FROZEN) && !a.HasAttr(AttrType.UNFROZEN))
                            {
                                B.Add(a.YouAre() + " encased in ice. ", a);
                                a.attrs[AttrType.FROZEN] = 25;
                            }
                            else
                            {
                                B.Add("The beam dissipates on the remaining ice. ", a);
                            }
                        }
                        else
                        {
                            AnimateBoltBeam(line, Color.Cyan);
                            B.Add("A bit of ice forms on " + t.the_name + ". ", t);
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.BLINK:
                    for (int i = 0; i < 9999; ++i)
                    {
                        int a = Global.Roll(1, 17) - 9; //-8 to 8
                        int b = Global.Roll(1, 17) - 9;
                        if (Math.Abs(a) + Math.Abs(b) >= 6)
                        {
                            a += row;
                            b += col;
                            if (M.BoundsCheck(a, b) && M.tile[a, b].passable && M.actor[a, b] == null)
                            {
                                B.Add(You("cast") + " blink. ", this);
                                B.Add(You("step") + " through a rip in reality. ", this);
                                AnimateStorm(2, 3, 4, "*", Color.DarkMagenta);
                                await Move(a, b);
                                M.Draw();
                                AnimateStorm(2, 3, 4, "*", Color.DarkMagenta);
                                break;
                            }
                        }
                    }
                    break;
                case SpellType.SCORCH:
                    if (t == null)
                    {
                        line = await GetTarget(12);
                        if (line != null)
                        {
                            t = line.Last();
                        }
                    }
                    if (t != null)
                    {
                        B.Add(You("cast") + " scorch. ", this);
                        Actor a = FirstActorInLine(line);
                        if (a != null)
                        {
                            AnimateProjectile(line.ToFirstObstruction(), "*", Color.RandomFire);
                            B.Add("The scorching bolt hits " + a.the_name + ". ", a);
                            await a.TakeDamage(DamageType.FIRE, DamageClass.MAGICAL, Global.Roll(2 + bonus, 6), this, a_name);
                        }
                        else
                        {
                            foreach (Tile t2 in line)
                            {
                                if (t2.Is(FeatureType.TROLL_CORPSE) || t2.Is(FeatureType.TROLL_SEER_CORPSE))
                                {
                                    line = line.To(t2);
                                }
                            }
                            AnimateProjectile(line, "*", Color.RandomFire);
                            B.Add("The scorching bolt hits " + t.the_name + ". ", t);
                            if (line.Last().Is(FeatureType.TROLL_CORPSE))
                            {
                                line.Last().features.Remove(FeatureType.TROLL_CORPSE);
                                B.Add("The troll corpse burns to ashes! ", line.Last());
                            }
                            if (line.Last().Is(FeatureType.TROLL_SEER_CORPSE))
                            {
                                line.Last().features.Remove(FeatureType.TROLL_SEER_CORPSE);
                                B.Add("The troll seer corpse burns to ashes! ", line.Last());
                            }
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.BLOODSCENT:
                    if (!HasAttr(AttrType.BLOODSCENT))
                    {
                        B.Add(You("cast") + " bloodscent. ", this);
                        attrs[Forays.AttrType.BLOODSCENT]++;
                        if (atype == ActorType.PLAYER)
                        {
                            B.Add("You smell fear. ");
                            Q.Add(new Event(this, 10000, Forays.AttrType.BLOODSCENT, "You lose the scent. "));
                        }
                        else
                        {
                            Q.Add(new Event(this, 10000, Forays.AttrType.BLOODSCENT));
                        }
                    }
                    else
                    {
                        B.Add("You can already smell the blood of your enemies. ");
                        return false;
                    }
                    break;
                case SpellType.LIGHTNING_BOLT:
                    if (t == null)
                    {
                        line = await GetTarget(12);
                        if (line != null)
                        {
                            t = line.Last();
                        }
                    }
                    if (t != null)
                    {
                        B.Add(You("cast") + " lightning bolt. ", this);
                        PhysicalObject bolt_target = null;
                        List<Actor> damage_targets = new List<Actor>();
                        foreach (Tile t2 in line)
                        {
                            if (t2.actor() != null && t2.actor() != this)
                            {
                                bolt_target = t2.actor();
                                damage_targets.Add(t2.actor());
                                break;
                            }
                            else
                            {
                                if (t2.ConductsElectricity())
                                {
                                    bolt_target = t2;
                                    break;
                                }
                            }
                        }
                        if (bolt_target != null)
                        {
                            Dict<PhysicalObject, List<PhysicalObject>> chain = new Dict<PhysicalObject, List<PhysicalObject>>();
                            chain[this] = new List<PhysicalObject> { bolt_target };
                            List<PhysicalObject> last_added = new List<PhysicalObject> { bolt_target };
                            for (bool done = false; !done; )
                            {
                                done = true;
                                List<PhysicalObject> new_last_added = new List<PhysicalObject>();
                                foreach (PhysicalObject added in last_added)
                                {
                                    List<PhysicalObject> sort_list = new List<PhysicalObject>();
                                    foreach (Tile nearby in added.TilesWithinDistance(3, true))
                                    {
                                        if (nearby.actor() != null || nearby.ConductsElectricity())
                                        {
                                            if (added.HasLOE(nearby))
                                            {
                                                if (nearby.actor() != null)
                                                {
                                                    bolt_target = nearby.actor();
                                                }
                                                else
                                                {
                                                    bolt_target = nearby;
                                                }
                                                bool contains_value = false;
                                                foreach (PhysicalObject k in chain.d.Keys)
                                                {
                                                    List<PhysicalObject> list = chain.d[k];
                                                    foreach (PhysicalObject o in list)
                                                    {
                                                        if (o == bolt_target)
                                                        {
                                                            contains_value = true;
                                                            break;
                                                        }
                                                    }
                                                    if (contains_value)
                                                    {
                                                        break;
                                                    }
                                                }
                                                if (!chain.d.ContainsKey(bolt_target) && !contains_value)
                                                {
                                                    if (bolt_target as Actor != null)
                                                    {
                                                        damage_targets.AddUnique(bolt_target as Actor);
                                                    }
                                                    done = false;
                                                    if (sort_list.Count == 0)
                                                    {
                                                        sort_list.Add(bolt_target);
                                                    }
                                                    else
                                                    {
                                                        int idx = 0;
                                                        foreach (PhysicalObject o in sort_list)
                                                        {
                                                            if (bolt_target.DistanceFrom(added) < o.DistanceFrom(added))
                                                            {
                                                                sort_list.Insert(idx, bolt_target);
                                                                break;
                                                            }
                                                            ++idx;
                                                        }
                                                        if (idx == sort_list.Count)
                                                        {
                                                            sort_list.Add(bolt_target);
                                                        }
                                                    }
                                                    if (chain[added] == null)
                                                    {
                                                        chain[added] = new List<PhysicalObject> { bolt_target };
                                                    }
                                                    else
                                                    {
                                                        chain[added].Add(bolt_target);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    foreach (PhysicalObject o in sort_list)
                                    {
                                        new_last_added.Add(o);
                                    }
                                }
                                if (!done)
                                {
                                    last_added = new_last_added;
                                }
                            } //whew. the tree structure is complete. start at chain[this] and go from there...
                            Dict<int, List<pos>> frames = new Dict<int, List<pos>>();
                            Dict<PhysicalObject, int> line_length = new Dict<PhysicalObject, int>();
                            line_length[this] = 0;
                            List<PhysicalObject> current = new List<PhysicalObject> { this };
                            List<PhysicalObject> next = new List<PhysicalObject>();
                            while (current.Count > 0)
                            {
                                foreach (PhysicalObject o in current)
                                {
                                    if (chain[o] != null)
                                    {
                                        foreach (PhysicalObject o2 in chain[o])
                                        {
                                            List<Tile> bres = o.GetBestLine(o2);
                                            bres.RemoveAt(0);
                                            line_length[o2] = bres.Count + line_length[o];
                                            int idx = 0;
                                            foreach (Tile t2 in bres)
                                            {
                                                if (frames[idx + line_length[o]] != null)
                                                {
                                                    frames[idx + line_length[o]].Add(new pos(t2.row, t2.col));
                                                }
                                                else
                                                {
                                                    frames[idx + line_length[o]] = new List<pos> { new pos(t2.row, t2.col) };
                                                }
                                                ++idx;
                                            }
                                            next.Add(o2);
                                        }
                                    }
                                }
                                current = next;
                                next = new List<PhysicalObject>();
                            }
                            List<pos> frame = frames[0];
                            for (int i = 0; frame != null; ++i)
                            {
                                foreach (pos p in frame)
                                {
                                    Screen.WriteMapChar(p.row, p.col, "*", Color.RandomLightning);
                                }
                                await Task.Delay(50);
                                frame = frames[i];
                            }
                            foreach (Actor ac in damage_targets)
                            {
                                B.Add("The bolt hits " + ac.the_name + ". ", ac);
                                await ac.TakeDamage(DamageType.ELECTRIC, DamageClass.MAGICAL, Global.Roll(2 + bonus, 6), this, a_name);
                            }
                        }
                        else
                        {
                            AnimateBeam(line, "*", Color.RandomLightning);
                            B.Add("The bolt hits " + t.the_name + ". ", t);
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.SHADOWSIGHT:
                    if (!HasAttr(AttrType.SHADOWSIGHT))
                    {
                        B.Add("You cast shadowsight. ");
                        B.Add("Your eyes pierce the darkness. ");
                        int duration = 10001;
                        GainAttr(AttrType.SHADOWSIGHT, duration, "You no longer see as well in darkness. ");
                        GainAttr(AttrType.LOW_LIGHT_VISION, duration);
                    }
                    else
                    {
                        B.Add("Your eyes are already attuned to darkness. ");
                        return false;
                    }
                    break;
                /*case SpellType.BURNING_HANDS:
                    if(t == null){
                        t = TileInDirection(GetDirection());
                    }
                    if(t != null){
                        B.Add(You("cast") + " burning hands. ",this);
                        AnimateMapCell(t,Color.DarkRed,'*');
                        Actor a = M.actor[t.row,t.col];
                        if(a != null){
                            B.Add(You("project") + " flames onto " + a.the_name + ". ",this,a);
                            a.TakeDamage(DamageType.FIRE,DamageClass.MAGICAL,Global.Roll(3+bonus,6),this);
                            if(M.actor[t.row,t.col] != null && Global.Roll(1,10) <= 2){
                                B.Add(a.You("start") + " to catch fire! ",a);
                                a.attrs[AttrType.CATCHING_FIRE]++;
                            }
                        }
                        else{
                            B.Add("You project flames from your hands. ");
                        }
                    }
                    else{
                        return false;
                    }
                    break;
                case SpellType.NIMBUS:
                {
                    if(HasAttr(AttrType.NIMBUS_ON)){
                        B.Add("You're already surrounded by a nimbus. ");
                        return false;
                    }
                    else{
                        B.Add(You("cast") + " nimbus. ",this);
                        B.Add("An electric glow surrounds " + the_name + ". ",this);
                        attrs[AttrType.NIMBUS_ON]++;
                        int duration = (Global.Roll(5)+5)*100;
                        Q.Add(new Event(this,duration,AttrType.NIMBUS_ON,"The electric glow fades from " + the_name + ". ",this));
                    }
                    break;
                }*/
                case SpellType.VOLTAIC_SURGE:
                    {
                        List<Actor> targets = new List<Actor>();
                        foreach (Actor a in ActorsWithinDistance(2, true))
                        {
                            if (HasLOE(a))
                            {
                                targets.Add(a);
                            }
                        }
                        B.Add(You("cast") + " voltaic surge. ", this);
                        AnimateExplosion(this, 2, Color.RandomLightning, "*");
                        if (targets.Count == 0)
                        {
                            B.Add("The air around " + the_name + " crackles. ", this);
                        }
                        else
                        {
                            while (targets.Count > 0)
                            {
                                Actor a = targets.Random();
                                targets.Remove(a);
                                B.Add("Electricity blasts " + a.the_name + ". ", a);
                                await a.TakeDamage(DamageType.ELECTRIC, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name);
                            }
                        }
                        break;
                    }
                case SpellType.MAGIC_HAMMER:
                    if (t == null)
                    {
                        t = TileInDirection(await GetDirection());
                    }
                    if (t != null)
                    {
                        Actor a = t.actor();
                        B.Add(You("cast") + " magic hammer. ", this);
                        B.DisplayNow();
                        Screen.AnimateMapCell(t.row, t.col, new colorchar("*", Color.Magenta), 100);
                        if (a != null)
                        {
                            B.Add(You("smash", true) + " " + a.TheVisible() + ". ", this, a);
                            if (await a.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(4 + bonus, 6), this, a_name))
                            {
                                a.GainAttrRefreshDuration(AttrType.STUNNED, 201, a.YouAre() + " no longer stunned. ", a);
                                B.Add(a.YouAre() + " stunned. ", a);
                            }
                        }
                        else
                        {
                            B.Add("You smash " + t.the_name + ". ");
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.RETREAT: //this is a player-only spell for now because it uses target_location to track position
                    B.Add("You cast retreat. ");
                    if (target_location == null)
                    {
                        target_location = M.tile[row, col];
                        B.Add("You create a rune of transport on " + M.tile[row, col].the_name + ". ");
                        target_location.features.Add(FeatureType.RUNE_OF_RETREAT);
                    }
                    else
                    {
                        if (M.actor[target_location.row, target_location.col] == null && target_location.passable)
                        {
                            B.Add("You activate your rune of transport. ");
                            await Move(target_location.row, target_location.col);
                            target_location.features.Remove(FeatureType.RUNE_OF_RETREAT);
                            target_location = null;
                        }
                        else
                        {
                            B.Add("Something blocks your transport. ");
                        }
                    }
                    break;
                case SpellType.GLACIAL_BLAST:
                    if (t == null)
                    {
                        line = await GetTarget(12);
                        if (line != null)
                        {
                            t = line.Last();
                        }
                    }
                    if (t != null)
                    {
                        B.Add(You("cast") + " glacial blast. ", this);
                        Actor a = FirstActorInLine(line);
                        if (a != null)
                        {
                            AnimateProjectile(line.ToFirstObstruction(), "*", Color.RandomIce);
                            B.Add("The glacial blast hits " + a.the_name + ". ", a);
                            await a.TakeDamage(DamageType.COLD, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name);
                        }
                        else
                        {
                            AnimateProjectile(line, "*", Color.RandomIce);
                            B.Add("The glacial blast hits " + t.the_name + ". ", t);
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.PASSAGE:
                    {
                        int i = DirectionOfOnlyUnblocked(TileType.WALL, true);
                        if (i == 0)
                        {
                            B.Add("There's no wall here. ", this);
                            return false;
                        }
                        else
                        {
                            if (t == null)
                            {
                                i = await GetDirection(true, false);
                                t = TileInDirection(i);
                            }
                            else
                            {
                                i = DirectionOf(t);
                            }
                            if (t != null)
                            {
                                if (t.ttype == TileType.WALL)
                                {
                                    B.Add(You("cast") + " passage. ", this);
                                    colorchar ch = new colorchar(Color.Cyan, "!");
                                    if (this == player)
                                    {
                                        Game.Console.CursorVisible = false;
                                        switch (DirectionOf(t))
                                        {
                                            case 8:
                                            case 2:
                                                ch.c = "|";
                                                break;
                                            case 4:
                                            case 6:
                                                ch.c = "-";
                                                break;
                                        }
                                    }
                                    List<Tile> tiles = new List<Tile>();
                                    List<colorchar> memlist = new List<colorchar>();
                                    while (!t.passable)
                                    {
                                        if (t.row == 0 || t.row == ROWS - 1 || t.col == 0 || t.col == COLS - 1)
                                        {
                                            break;
                                        }
                                        if (this == player)
                                        {
                                            tiles.Add(t);
                                            memlist.Add(Screen.MapChar(t.row, t.col));
                                            Screen.WriteMapChar(t.row, t.col, ch);

                                            await Task.Delay(35);
                                            //									Thread.Sleep(35);
                                        }
                                        t = t.TileInDirection(i);
                                    }
                                    if (t.passable && M.actor[t.row, t.col] == null)
                                    {
                                        if (this == player)
                                        {
                                            if (M.tile[row, col].inv != null)
                                            {
                                                Screen.WriteMapChar(row, col, new colorchar(tile().inv.color, tile().inv.symbol));
                                            }
                                            else
                                            {
                                                Screen.WriteMapChar(row, col, new colorchar(tile().color, tile().symbol));
                                            }
                                            Screen.WriteMapChar(t.row, t.col, new colorchar(color, symbol));
                                            int j = 0;
                                            foreach (Tile tile in tiles)
                                            {
                                                Screen.WriteMapChar(tile.row, tile.col, memlist[j++]);
                                                await Task.Delay(35);
                                                //Thread.Sleep(35);
                                            }
                                        }
                                        await Move(t.row, t.col);
                                        M.Draw();
                                        B.Add(You("travel") + " through the passage. ", this);
                                    }
                                    else
                                    {
                                        if (this == player)
                                        {
                                            int j = 0;
                                            foreach (Tile tile in tiles)
                                            {
                                                Screen.WriteMapChar(tile.row, tile.col, memlist[j++]);
                                                await Task.Delay(35);
                                                //Thread.Sleep(35);
                                            }
                                            B.Add("The passage is blocked. ");
                                        }
                                    }
                                }
                                else
                                {
                                    if (this == player)
                                    {
                                        B.Add("There's no wall here. ", this);
                                    }
                                    return false;
                                }
                            }
                            else
                            {
                                return false;
                            }
                        }
                        break;
                    }
                case SpellType.FLASHFIRE:
                    if (t == null)
                    {
                        line = await GetTarget(12, 2);
                        if (line != null)
                        {
                            t = line.Last();
                        }
                    }
                    if (t != null)
                    {
                        Actor a = FirstActorInLine(line);
                        if (a != null)
                        {
                            t = a.tile();
                        }
                        B.Add(You("cast") + " flashfire. ", this);
                        AnimateBoltProjectile(line.ToFirstObstruction(), Color.Red);
                        AnimateExplosion(t, 2, "*", Color.RandomFire);
                        B.Add("Fwoosh! ", new PhysicalObject[] { this, t });
                        List<Actor> targets = new List<Actor>();
                        Tile prev = line.ToFirstObstruction()[line.ToFirstObstruction().Count - 2];
                        foreach (Actor ac in t.ActorsWithinDistance(2))
                        {
                            if (t.passable)
                            {
                                if (t.HasBresenhamLine(ac.row, ac.col))
                                {
                                    targets.Add(ac);
                                }
                            }
                            else
                            {
                                if (prev.HasBresenhamLine(ac.row, ac.col))
                                {
                                    targets.Add(ac);
                                }
                            }
                        }
                        foreach (Tile t2 in t.TilesWithinDistance(2))
                        {
                            if (t.passable)
                            {
                                if (t.HasBresenhamLine(t2.row, t2.col))
                                {
                                    if (t2.actor() != null)
                                    {
                                        targets.Add(t2.actor());
                                    }
                                    if (t2.Is(FeatureType.TROLL_CORPSE))
                                    {
                                        t2.features.Remove(FeatureType.TROLL_CORPSE);
                                        B.Add("The troll corpse burns to ashes! ", t2);
                                    }
                                    if (t2.Is(FeatureType.TROLL_SEER_CORPSE))
                                    {
                                        t2.features.Remove(FeatureType.TROLL_SEER_CORPSE);
                                        B.Add("The troll seer corpse burns to ashes! ", t2);
                                    }
                                }
                            }
                            else
                            {
                                if (prev.HasBresenhamLine(t2.row, t2.col))
                                {
                                    if (t2.actor() != null)
                                    {
                                        targets.Add(t2.actor());
                                    }
                                    if (t2.Is(FeatureType.TROLL_CORPSE))
                                    {
                                        t2.features.Remove(FeatureType.TROLL_CORPSE);
                                        B.Add("The troll corpse burns to ashes! ", t2);
                                    }
                                    if (t2.Is(FeatureType.TROLL_SEER_CORPSE))
                                    {
                                        t2.features.Remove(FeatureType.TROLL_SEER_CORPSE);
                                        B.Add("The troll seer corpse burns to ashes! ", t2);
                                    }
                                }
                            }
                        }
                        while (targets.Count > 0)
                        {
                            Actor ac = targets.RemoveRandom();
                            B.Add("The explosion hits " + ac.the_name + ". ", ac);
                            await ac.TakeDamage(DamageType.FIRE, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name);
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.SONIC_BOOM:
                    if (t == null)
                    {
                        line = await GetTarget(12);
                        if (line != null)
                        {
                            t = line.Last();
                        }
                    }
                    if (t != null)
                    {
                        B.Add(You("cast") + " sonic boom. ", this);
                        Actor a = FirstActorInLine(line);
                        if (a != null)
                        {
                            AnimateProjectile(line.ToFirstObstruction(), "~", Color.Yellow);
                            B.Add("A wave of sound hits " + a.the_name + ". ", a);
                            int r = a.row;
                            int c = a.col;
                            await a.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name);
                            if (Global.Roll(1, 10) <= 5 && M.actor[r, c] != null && !M.actor[r, c].HasAttr(AttrType.STUNNED))
                            {
                                B.Add(a.YouAre() + " stunned. ", a);
                                a.attrs[AttrType.STUNNED]++;
                                int duration = DurationOfMagicalEffect((Global.Roll(1, 4) + 2)) * 100;
                                Q.Add(new Event(a, duration, AttrType.STUNNED, a.YouAre() + " no longer stunned. ", new PhysicalObject[] { a }));
                            }
                        }
                        else
                        {
                            AnimateProjectile(line, "~", Color.Yellow);
                            B.Add("Sonic boom! ");
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.COLLAPSE:
                    if (t == null)
                    {
                        line = await GetTarget(12, -1);
                        if (line != null)
                        {
                            t = line.Last();
                        }
                    }
                    if (t != null)
                    {
                        B.Add(You("cast") + " collapse. ", this);
                        B.DisplayNow();
                        for (int dist = 2; dist > 0; --dist)
                        {
                            List<pos> cells = new List<pos>();
                            List<colorchar> chars = new List<colorchar>();
                            pos p2 = new pos(t.row - dist, t.col - dist);
                            if (p2.BoundsCheck())
                            {
                                cells.Add(p2);
                                chars.Add(new colorchar("\\", Color.DarkGreen));
                            }
                            p2 = new pos(t.row - dist, t.col + dist);
                            if (p2.BoundsCheck())
                            {
                                cells.Add(p2);
                                chars.Add(new colorchar("/", Color.DarkGreen));
                            }
                            p2 = new pos(t.row + dist, t.col - dist);
                            if (p2.BoundsCheck())
                            {
                                cells.Add(p2);
                                chars.Add(new colorchar("/", Color.DarkGreen));
                            }
                            p2 = new pos(t.row + dist, t.col + dist);
                            if (p2.BoundsCheck())
                            {
                                cells.Add(p2);
                                chars.Add(new colorchar("\\", Color.DarkGreen));
                            }
                            Screen.AnimateMapCells(cells, chars);
                        }
                        Screen.AnimateMapCell(t.row, t.col, new colorchar("X", Color.DarkGreen));
                        Actor a = t.actor();
                        if (a != null)
                        {
                            B.Add("Part of the ceiling falls onto " + a.the_name + ". ", a);
                            await a.TakeDamage(DamageType.BASHING, DamageClass.PHYSICAL, Global.Roll(4 + bonus, 6), this, a_name);
                        }
                        else
                        {
                            if (t.row == 0 || t.col == 0 || t.row == ROWS - 1 || t.col == COLS - 1)
                            {
                                B.Add("The wall resists. ");
                            }
                            else
                            {
                                if (t.ttype == TileType.WALL || t.ttype == TileType.HIDDEN_DOOR)
                                {
                                    B.Add("The wall crashes down! ");
                                    t.TurnToFloor();
                                    foreach (Tile neighbor in t.TilesAtDistance(1))
                                    {
                                        if (neighbor.solid_rock)
                                        {
                                            neighbor.solid_rock = false;
                                        }
                                    }
                                }
                            }
                        }
                        List<Tile> open_spaces = new List<Tile>();
                        foreach (Tile neighbor in t.TilesWithinDistance(1))
                        {
                            if (neighbor.passable)
                            {
                                if (a == null || neighbor != t)
                                { //don't hit the same guy again
                                    open_spaces.Add(neighbor);
                                }
                            }
                        }
                        int count = 4;
                        if (open_spaces.Count < 4)
                        {
                            count = open_spaces.Count;
                        }
                        for (; count > 0; --count)
                        {
                            Tile chosen = open_spaces.Random();
                            open_spaces.Remove(chosen);
                            if (chosen.actor() != null)
                            {
                                B.Add("A rock falls onto " + chosen.actor().the_name + ". ", chosen.actor());
                                await chosen.actor().TakeDamage(DamageType.BASHING, Forays.DamageClass.PHYSICAL, Global.Roll(2, 6), this, a_name);
                            }
                            else
                            {
                                TileType prev = chosen.ttype;
                                chosen.TransformTo(TileType.RUBBLE);
                                chosen.toggles_into = prev;
                            }
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.FORCE_BEAM:
                    if (t == null)
                    {
                        line = await GetTarget();
                        if (line != null)
                        {
                            t = line.Last();
                        }
                    }
                    if (t != null)
                    {
                        B.Add(You("cast") + " force beam. ", this);
                        B.DisplayNow();
                        //List<Tile> line2 = GetBestExtendedLine(t.row,t.col);
                        List<Tile> full_line = new List<Tile>(line);
                        line = line.GetRange(0, Math.Min(13, line.Count));
                        for (int i = 0; i < 3; ++i)
                        { //hits thrice
                            Actor firstactor = null;
                            Actor nextactor = null;
                            Tile firsttile = null;
                            Tile nexttile = null;
                            foreach (Tile tile in line)
                            {
                                if (!tile.passable)
                                {
                                    firsttile = tile;
                                    break;
                                }
                                if (M.actor[tile.row, tile.col] != null && M.actor[tile.row, tile.col] != this)
                                {
                                    int idx = full_line.IndexOf(tile);
                                    firsttile = tile;
                                    firstactor = M.actor[tile.row, tile.col];
                                    nexttile = full_line[idx + 1];
                                    nextactor = M.actor[nexttile.row, nexttile.col];
                                    break;
                                }
                            }
                            AnimateBoltBeam(line.ToFirstObstruction(), Color.Cyan);
                            if (firstactor != null)
                            {
                                string s = firstactor.TheVisible();
                                string s2 = firstactor.a_name;
                                await firstactor.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(1 + bonus, 6), this, a_name);
                                if (M.actor[firsttile.row, firsttile.col] != null)
                                {
                                    await firstactor.GetKnockedBack(full_line);
                                }
                                else
                                {
                                    if (!nexttile.passable)
                                    {
                                        B.Add(s + "'s corpse is knocked into " + nexttile.the_name + ". ", new PhysicalObject[] { firsttile, nexttile });
                                    }
                                    else
                                    {
                                        if (nextactor != null)
                                        {
                                            B.Add(s + "'s corpse is knocked into " + nextactor.TheVisible() + ". ", new PhysicalObject[] { firsttile, nextactor });
                                            await nextactor.TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(1, 6), this, s2 + "'s falling corpse");
                                        }
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                /*case SpellType.DISINTEGRATE:
                    if(t == null){
                        t = await GetTarget();
                    }
                    if(t != null){
                        B.Add(You("cast") + " disintegrate. ",this);
                        Actor a = FirstActorInLine(t);
                        if(a != null){
                            AnimateBoltBeam(a,Color.DarkGreen);
                            B.Add(You("direct") + " destructive energies toward " + a.the_name + ". ",this,a);
                            a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,Global.Roll(8+bonus,6),this);
                        }
                        else{
                            AnimateBoltBeam(t,Color.DarkGreen);
                            if(t.type == TileType.WALL || t.type == TileType.DOOR_C || t.type == TileType.DOOR_O || t.type == TileType.CHEST){
                                B.Add(You("direct") + " destructive energies toward " + t.the_name + ". ",this,t);
                                B.Add(t.the_name + " turns to dust. ",t);
                                t.TurnToFloor();
                            }
                        }
                    }
                    else{
                        return false;
                    }
                    break;*/
                case SpellType.AMNESIA:
                    if (t == null)
                    {
                        t = TileInDirection(await GetDirection());
                    }
                    if (t != null)
                    {
                        Actor a = t.actor();
                        if (a != null)
                        {
                            B.Add(You("cast") + " amnesia. ", this);
                            /*for(int i=0;i<4;++i){
                                List<pos> cells = new List<pos>();
                                List<colorchar> chars = new List<colorchar>();
                                List<pos> nearby = a.p.PositionsWithinDistance(2);
                                for(int j=0;j<4;++j){
                                    cells.Add(nearby.RemoveRandom());
                                    chars.Add(new colorchar('*',Color.RandomPrismatic));
                                }
                                Screen.AnimateMapCells(cells,chars);
                            }*/
                            a.AnimateStorm(2, 4, 4, "*", Color.RandomPrismatic);
                            B.Add("You fade from " + a.TheVisible() + "'s awareness. ");
                            a.player_visibility_duration = 0;
                            a.target = null;
                            a.target_location = null;
                            a.attrs[Forays.AttrType.AMNESIA_STUN]++;
                        }
                        else
                        {
                            B.Add("There's nothing to target there. ");
                            return false;
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.BLIZZARD:
                    {
                        List<Actor> targets = ActorsWithinDistance(5, true);
                        B.Add(You("cast") + " blizzard. ", this);
                        AnimateStorm(5, 8, 24, "*", Color.RandomIce);
                        B.Add("A massive ice storm surrounds " + the_name + ". ", this);
                        while (targets.Count > 0)
                        {
                            int idx = Global.Roll(1, targets.Count) - 1;
                            Actor a = targets[idx];
                            targets.Remove(a);
                            B.Add("The blizzard hits " + a.the_name + ". ", a);
                            int r = a.row;
                            int c = a.col;
                            await a.TakeDamage(DamageType.COLD, DamageClass.MAGICAL, Global.Roll(5 + bonus, 6), this, a_name);
                            if (M.actor[r, c] != null && Global.Roll(1, 10) <= 8)
                            {
                                B.Add(a.the_name + " is encased in ice. ", a);
                                a.attrs[AttrType.FROZEN] = 25;
                            }
                        }
                        break;
                    }
                case SpellType.BLESS:
                    if (!HasAttr(AttrType.BLESSED))
                    {
                        B.Add(You("cast") + " bless. ", this);
                        B.Add(You("shine") + " briefly with inner light. ", this);
                        attrs[AttrType.BLESSED]++;
                        Q.Add(new Event(this, 400, AttrType.BLESSED));
                    }
                    else
                    {
                        B.Add(YouAre() + " already blessed! ", this);
                        return false;
                    }
                    break;
                case SpellType.MINOR_HEAL:
                    B.Add(You("cast") + " minor heal. ", this);
                    B.Add("A bluish glow surrounds " + the_name + ". ", this);
                    await TakeDamage(DamageType.HEAL, DamageClass.NO_TYPE, Global.Roll(4, 6), null);
                    break;
                case SpellType.HOLY_SHIELD:
                    if (!HasAttr(AttrType.HOLY_SHIELDED))
                    {
                        B.Add(You("cast") + " holy shield. ", this);
                        B.Add("A fiery halo appears above " + the_name + ". ", this);
                        attrs[AttrType.HOLY_SHIELDED]++;
                        int duration = (Global.Roll(3, 2) + 1) * 100;
                        Q.Add(new Event(this, duration, AttrType.HOLY_SHIELDED, the_name + "'s halo fades. ", new PhysicalObject[] { this }));
                    }
                    else
                    {
                        B.Add(Your() + " holy shield is already active. ", this);
                        return false;
                    }
                    break;
            }
            if (atype == ActorType.PLAYER && spell != SpellType.AMNESIA)
            {
                MakeNoise();
            }
            if (!force_of_will)
            {
                if (Spell.Level(spell) - TotalSkill(SkillType.MAGIC) > 0)
                {
                    if (HasFeat(FeatType.STUDENTS_LUCK))
                    {
                        if (Global.CoinFlip())
                        {
                            magic_penalty++;
                            B.Add(YouFeel() + " drained. ", this);
                        }
                        else
                        {
                            if (atype == ActorType.PLAYER)
                            {
                                B.Add("You feel lucky. "); //punk
                            }
                        }
                    }
                    else
                    {
                        magic_penalty++;
                        B.Add(YouFeel() + " drained. ", this);
                    }
                }
            }
            else
            {
                magic_penalty += 5;
                if (magic_penalty > 20)
                {
                    magic_penalty = 20;
                }
                B.Add("You drain your magic reserves. ");
            }
            Q1();
            return true;
        }
示例#3
0
        public void GenerateFinalLevel()
        {
            final_level_cultist_count = new int[5];
            final_level_demon_count = 0;
            final_level_clock = 0;
            current_level = 21;
            InitializeNewLevel();
            string[] final_map = FinalLevelLayout();
            PosArray<CellType> map = new PosArray<CellType>(ROWS,COLS);
            PosArray<bool> doors = new PosArray<bool>(ROWS,COLS);
            List<List<pos>> door_sets = new List<List<pos>>();
            for(int i=0;i<ROWS;++i){
                string s = final_map[i];
                for(int j=0;j<COLS;++j){
                    switch(s[j]){
                    case '#':
                        map[i,j] = CellType.Wall;
                        break;
                    case '.':
                        map[i,j] = CellType.RoomInterior;
                        break;
                    case '2':
                        map[i,j] = CellType.RoomFeature1;
                        break;
                    case '&':
                        map[i,j] = CellType.RoomFeature2;
                        break;
                    case '*':
                        map[i,j] = CellType.RoomFeature3;
                        break;
                    case 'X':
                        map[i,j] = CellType.RoomFeature4;
                        break;
                    case '+':
                        map[i,j] = CellType.Wall;
                        if(!doors[i,j]){
                            doors[i,j] = true;
                            pos p = new pos(i,j);
                            List<pos> door_set = new List<pos>{p};
                            foreach(int dir in new int[]{2,6}){
                                p = new pos(i,j);
                                while(true){
                                    p = p.PosInDir(dir);
                                    if(p.BoundsCheck(tile) && final_map[p.row][p.col] == '+'){
                                        doors[p] = true;
                                        door_set.Add(p);
                                    }
                                    else{
                                        break;
                                    }
                                }
                            }
                            door_sets.Add(door_set);
                        }
                        break;
                    }
                }
            }
            Dungeon d = new Dungeon(ROWS,COLS);
            d.map = map;
            while(!d.IsFullyConnected() && door_sets.Count > 0){
                List<pos> door_set = door_sets.RemoveRandom();
                d.map[door_set.Random()] = CellType.RoomInterior;
            }
            List<Tile> flames = new List<Tile>();
            for(int i=0;i<ROWS;++i){
                for(int j=0;j<COLS;++j){
                    switch(map[i,j]){
                    case CellType.Wall:
                        Tile.Create(TileType.WALL,i,j);
                        break;
                    case CellType.RoomFeature1:
                        Tile.Create(TileType.DEMONIC_IDOL,i,j);
                        break;
                    case CellType.RoomFeature2:
                        Tile.Create(TileType.FLOOR,i,j);
                        flames.Add(tile[i,j]);
                        break;
                    case CellType.RoomFeature3:
                        Tile.Create(TileType.FLOOR,i,j);
                        tile[i,j].color = Color.RandomDoom;
                        break;
                    case CellType.RoomFeature4:
                        Tile.Create(TileType.FIRE_RIFT,i,j);
                        break;
                    default:
                        Tile.Create(TileType.FLOOR,i,j);
                        break;
                    }
                    tile[i,j].solid_rock = true;
                }
            }

            //todo! add dungeon descriptions for final level.
            //todo: ^^^ or else it crashes ^^^

            player.ResetForNewLevel();
            foreach(Tile t in AllTiles()){
                if(t.light_radius > 0){
                    t.UpdateRadius(0,t.light_radius);
                }
            }
            foreach(Tile t in flames){
                t.AddFeature(FeatureType.FIRE);
            }
            int light = player.light_radius;
            int fire = player.attrs[AttrType.BURNING];
            player.light_radius = 0;
            player.attrs[AttrType.BURNING] = 0;
            player.Move(6,7);
            player.UpdateRadius(0,Math.Max(light,fire),true);
            player.light_radius = light;
            player.attrs[AttrType.BURNING] = fire;
            foreach(Tile t in AllTiles()){
                if(t.type != TileType.WALL){
                    foreach(Tile neighbor in t.TilesAtDistance(1)){
                        neighbor.solid_rock = false;
                    }
                }
            }
            for(int i=0;i<3;++i){
                Actor a = SpawnMob(ActorType.CULTIST);
                List<Actor> group = new List<Actor>(a.group);
                a.group.Clear();
                if(a != null && group != null){
                    int ii = 0;
                    foreach(Actor a2 in group){
                        ++ii;
                        pos circle = FinalLevelSummoningCircle(ii);
                        a2.FindPath(circle.row,circle.col);
                        a2.attrs[AttrType.COOLDOWN_2] = ii;
                        a2.type = ActorType.FINAL_LEVEL_CULTIST;
                        a2.group = null;
                        if(!R.OneIn(20)){
                            a2.attrs[AttrType.NO_ITEM] = 1;
                        }
                    }
                }
            }
            Q.Add(new Event(500,EventType.FINAL_LEVEL_SPAWN_CULTISTS));
        }
示例#4
0
 public bool CastSpell(SpellType spell,PhysicalObject obj)
 {
     //returns false if targeting is canceled.
     if(StunnedThisTurn()){ //eventually this will be moved to the last possible second
         return true; //returns true because turn was used up.
     }
     if(!HasSpell(spell)){
         return false;
     }
     if(HasAttr(AttrType.SILENCED)){
         if(this == player){
             B.Add("You can't cast while silenced. ");
         }
         return false;
     }
     foreach(Actor a in ActorsWithinDistance(2)){
         if(a.HasAttr(AttrType.SILENCE_AURA) && a.HasLOE(this)){
             if(this == player){
                 if(CanSee(a)){
                     B.Add(a.Your() + " aura of silence disrupts your spell! ");
                 }
                 else{
                     B.Add("An aura of silence disrupts your spell! ");
                 }
             }
             return false;
         }
     }
     int required_mana = Spell.Tier(spell);
     if(HasAttr(AttrType.CHAIN_CAST) && required_mana > 1){
         required_mana--;
     }
     if(curmp < required_mana && this == player){
         int missing_mana = required_mana - curmp;
         if(exhaustion + missing_mana*5 > 100){
             B.Add("You're too exhausted! ");
             return false;
         }
         if(!B.YesOrNoPrompt("Really exhaust yourself to cast this spell?")){
             return false;
         }
         Screen.CursorVisible = false;
     }
     Tile t = null;
     List<Tile> line = null;
     if(obj != null){
         t = M.tile[obj.row,obj.col];
         line = GetBestLineOfEffect(t);
     }
     if(exhaustion > 0){
         int fail = Spell.FailRate(spell,exhaustion);
         if(R.PercentChance(fail)){
             if(HasFeat(FeatType.FORCE_OF_WILL)){
                 B.Add("You focus your will. ");
             }
             else{
                 if(player.CanSee(this)){
                     B.Add("Sparks fly from " + Your() + " fingers. ",this);
                 }
                 else{
                     if(player.DistanceFrom(this) <= 4 || (player.DistanceFrom(this) <= 12 && player.HasLOE(row,col))){
                         B.Add("You hear words of magic, but nothing happens. ");
                     }
                 }
                 if(this == player){
                     Help.TutorialTip(TutorialTopic.SpellFailure);
                 }
                 Q1();
                 return true;
             }
         }
     }
     int bonus = 0; //used for bonus damage on spells
     if(HasFeat(FeatType.MASTERS_EDGE)){
         foreach(SpellType s in spells_in_order){
             if(Spell.IsDamaging(s)){
                 if(s == spell){
                     bonus = 1;
                 }
                 break;
             }
         }
     }
     if(HasAttr(AttrType.EMPOWERED_SPELLS)){
         bonus++;
     }
     switch(spell){
     case SpellType.RADIANCE:
     {
         if(M.wiz_dark){
             B.Add("The magical darkness makes this spell impossible to cast. ");
             return false;
         }
         if(t == null){
             line = GetTargetTile(12,0,true,false);
             if(line != null){
                 t = line.LastOrDefault();
             }
         }
         if(t != null){
             B.Add(You("cast") + " radiance. ",this);
             PhysicalObject o = null;
             int rad = -1;
             if(t.actor() != null){
                 Actor a = t.actor();
                 o = a;
                 int old_rad = a.LightRadius();
                 a.RefreshDuration(AttrType.SHINING,(R.Roll(2,20)+40)*100,a.You("no longer shine") + ". ",a);
                 if(old_rad != a.LightRadius()){
                     a.UpdateRadius(old_rad,a.LightRadius());
                 }
                 if(a != player){
                     a.attrs[AttrType.TURNS_VISIBLE] = -1;
                 }
                 rad = a.LightRadius();
             }
             else{
                 if(t.inv != null && t.inv.light_radius > 0){
                     o = t.inv;
                     rad = o.light_radius;
                 }
                 else{
                     if(t.light_radius > 0){
                         o = t;
                         rad = o.light_radius;
                     }
                 }
             }
             if(o != null){
                 if(o is Item){
                     B.Add((o as Item).TheName(false) + " shines brightly. ",o);
                 }
                 else{
                     B.Add(o.You("shine") + " brightly. ",o);
                 }
                 foreach(Actor a in o.ActorsWithinDistance(rad).Where(x=>x != this && o.HasLOS(x))){
                     B.Add("The light burns " + a.the_name + ". ",a);
                     a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(1+bonus,6),this,"a shining " + o.name);
                 }
             }
             else{
                 B.Add("Nothing happens. ");
             }
         }
         else{
             return false;
         }
         break;
     }
     case SpellType.FORCE_PALM:
         if(t == null){
             t = TileInDirection(GetDirection());
         }
         if(t != null){
             Actor a = M.actor[t.row,t.col];
             B.Add(You("cast") + " force palm. ",this);
             B.DisplayNow();
             Screen.AnimateMapCell(t.row,t.col,new colorchar('*',Color.Blue),100);
             bool self_knockback = false;
             if(a != null){
                 B.Add(You("strike") + " " + a.TheName(true) + ". ",this,a);
                 if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(spell)){
                     a.curmp += Spell.Tier(spell);
                     if(a.curmp > a.maxmp){
                         a.curmp = a.maxmp;
                     }
                     a.GainSpell(spell);
                     B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a);
                 }
                 a.attrs[AttrType.TURN_INTO_CORPSE]++;
                 a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(1+bonus,6),this,a_name);
                 if(a.HasAttr(AttrType.IMMOBILE,AttrType.FROZEN)){
                     self_knockback = true;
                 }
                 else{
                     if(a.curhp > 0 || !a.HasAttr(AttrType.NO_CORPSE_KNOCKBACK)){
                         KnockObjectBack(a,1,this);
                     }
                 }
                 a.CorpseCleanup();
             }
             else{
                 if(t.passable){
                     B.Add("You strike at empty space. ");
                 }
                 else{
                     B.Add("You strike " + t.the_name + " with your palm. ");
                     switch(t.type){
                     case TileType.DOOR_C:
                         B.Add("It flies open! ");
                         t.Toggle(this);
                         break;
                     case TileType.HIDDEN_DOOR:
                         B.Add("A hidden door flies open! ");
                         t.Toggle(this);
                         t.Toggle(this);
                         break;
                     case TileType.RUBBLE:
                         B.Add("It scatters! ");
                         t.Toggle(null);
                         break;
                     case TileType.CRACKED_WALL:
                         B.Add("It falls to pieces! ");
                         t.Toggle(null,TileType.FLOOR);
                         foreach(Tile neighbor in t.TilesAtDistance(1)){
                             neighbor.solid_rock = false;
                         }
                         break;
                     case TileType.BARREL:
                     case TileType.STANDING_TORCH:
                     case TileType.POISON_BULB:
                         t.Bump(DirectionOf(t));
                         break;
                     default:
                         self_knockback = true;
                         break;
                     }
                 }
             }
             if(self_knockback){
                 attrs[AttrType.TURN_INTO_CORPSE]++;
                 t.KnockObjectBack(this,1,this);
                 CorpseCleanup();
             }
         }
         else{
             return false;
         }
         break;
     case SpellType.DETECT_MOVEMENT:
         B.Add(You("cast") + " detect movement. ",this);
         if(this == player){
             B.Add("Your senses sharpen. ");
             if(!HasAttr(AttrType.DETECTING_MOVEMENT)){
                 previous_footsteps = new List<pos>(); //prevents old footsteps from appearing
             }
             RefreshDuration(AttrType.DETECTING_MOVEMENT,(R.Roll(2,20)+30)*100,"You no longer detect movement. ");
         }
         else{
             RefreshDuration(AttrType.DETECTING_MOVEMENT,(R.Roll(2,20)+30)*100);
         }
         break;
     case SpellType.FLYING_LEAP:
         B.Add(You("cast") + " flying leap. ",this);
         RefreshDuration(AttrType.FLYING_LEAP,250);
         RefreshDuration(AttrType.FLYING,250);
         attrs[AttrType.DESCENDING] = 0;
         B.Add(You("move") + " quickly through the air. ",this);
         if(this == player){
             Help.TutorialTip(TutorialTopic.IncreasedSpeed);
         }
         break;
     case SpellType.MERCURIAL_SPHERE:
         if(t == null){
             line = GetTargetLine(12);
             if(line != null && line.LastOrDefault() != tile()){
                 t = line.LastOrDefault();
             }
         }
         if(t != null){
             B.Add(You("cast") + " mercurial sphere. ",this);
             Actor a = FirstActorInLine(line);
             line = line.ToFirstSolidTileOrActor();
             M.Draw();
             AnimateProjectile(line,'*',Color.Blue);
             List<string> targets = new List<string>();
             List<Tile> locations = new List<Tile>();
             if(a != null){
                 for(int i=0;i<4;++i){
                     if(player.CanSee(a)){
                         targets.AddUnique(a.the_name);
                     }
                     Tile atile = a.tile();
                     if(a != this){
                         if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(spell)){
                             a.curmp += Spell.Tier(spell);
                             if(a.curmp > a.maxmp){
                                 a.curmp = a.maxmp;
                             }
                             a.GainSpell(spell);
                             B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a);
                         }
                         a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(2+bonus,6),this,a_name);
                     }
                     a = atile.ActorsWithinDistance(3,true).Where(x=>atile.HasLOE(x)).RandomOrDefault();
                     locations.AddUnique(atile);
                     if(a == null){
                         break;
                     }
                     if(i < 3){
                         Screen.AnimateProjectile(atile.GetBestLineOfEffect(a),new colorchar('*',Color.Blue));
                     }
                 }
                 int unknown = locations.Count - targets.Count; //every location for which we didn't see an actor
                 if(unknown > 0){
                     if(unknown == 1){
                         targets.Add("one unseen creature");
                     }
                     else{
                         targets.Add(unknown.ToString() + " unseen creatures");
                     }
                 }
                 if(targets.Contains("you")){
                     targets.Remove("you");
                     targets.Add("you"); //move it to the end of the list
                 }
                 if(targets.Count == 1){
                     B.Add("The sphere hits " + targets[0] + ". ",locations.ToArray());
                 }
                 else{
                     B.Add("The sphere bounces between " + targets.ConcatenateListWithCommas() + ". ",locations.ToArray());
                 }
             }
         }
         else{
             return false;
         }
         break;
     case SpellType.GREASE:
     {
         Tile prev = null;
         if(t == null){
             line = GetTargetTile(12,1,true,false);
             if(line != null){
                 t = line.LastOrDefault();
                 prev = line.LastBeforeSolidTile();
             }
         }
         if(t != null){
             Tile LOE_tile = t;
             if(!t.passable && prev != null){
                 LOE_tile = prev;
             }
             B.Add(You("cast") + " grease. ",this);
             B.Add("Oil covers the floor. ",t);
             foreach(Tile neighbor in t.TilesWithinDistance(1)){
                 if(neighbor.passable && LOE_tile.HasLOE(neighbor)){
                     neighbor.AddFeature(FeatureType.OIL);
                 }
             }
         }
         else{
             return false;
         }
         break;
     }
     case SpellType.BLINK:
         if(HasAttr(AttrType.IMMOBILE)){
             if(this == player){
                 B.Add("You can't blink while immobilized. ");
                 return false;
             }
             else{
                 B.Add(You("cast") + " blink. ",this);
                 B.Add("The spell fails. ",this);
                 Q1();
                 return true;
             }
         }
         for(int i=0;i<9999;++i){
             int a = R.Roll(1,17) - 9; //-8 to 8
             int b = R.Roll(1,17) - 9;
             if(Math.Abs(a) + Math.Abs(b) >= 6){
                 a += row;
                 b += col;
                 if(M.BoundsCheck(a,b) && M.tile[a,b].passable && M.actor[a,b] == null){
                     B.Add(You("cast") + " blink. ",this);
                     B.Add(You("step") + " through a rip in reality. ",this);
                     if(player.CanSee(this)){
                         AnimateStorm(2,3,4,'*',Color.DarkMagenta);
                     }
                     Move(a,b);
                     M.Draw();
                     if(player.CanSee(this)){
                         AnimateStorm(2,3,4,'*',Color.DarkMagenta);
                     }
                     break;
                 }
             }
         }
         break;
     case SpellType.FREEZE:
         if(t == null){
             line = GetTargetLine(12);
             if(line != null && line.LastOrDefault() != tile()){
                 t = line.LastOrDefault();
             }
         }
         if(t != null){
             B.Add(You("cast") + " freeze. ",this);
             Actor a = FirstActorInLine(line);
             AnimateBoltBeam(line.ToFirstSolidTileOrActor(),Color.Cyan);
             foreach(Tile t2 in line){
                 t2.ApplyEffect(DamageType.COLD);
             }
             if(a != null){
                 a.ApplyFreezing();
             }
         }
         else{
             return false;
         }
         break;
     case SpellType.SCORCH:
         if(t == null){
             line = GetTargetLine(12);
             if(line != null && line.LastOrDefault() != tile()){
                 t = line.LastOrDefault();
             }
         }
         if(t != null){
             B.Add(You("cast") + " scorch. ",this);
             Actor a = FirstActorInLine(line);
             line = line.ToFirstSolidTileOrActor();
             AnimateProjectile(line,'*',Color.RandomFire);
             foreach(Tile t2 in line){
                 t2.ApplyEffect(DamageType.FIRE);
             }
             if(a != null){
                 B.Add("The scorching bolt hits " + a.the_name + ". ",a);
                 if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(spell)){
                     a.curmp += Spell.Tier(spell);
                     if(a.curmp > a.maxmp){
                         a.curmp = a.maxmp;
                     }
                     a.GainSpell(spell);
                     B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a);
                 }
                 //if(a.TakeDamage(DamageType.FIRE,DamageClass.MAGICAL,R.Roll(1+bonus,6),this,a_name)){ //todo: testing this without damage
                     a.ApplyBurning();
                 //}
             }
         }
         else{
             return false;
         }
         break;
     case SpellType.LIGHTNING_BOLT: //todo: limit the bolt to 12 tiles or not? should it spread over an entire huge lake? 12 tiles is still quite a lot.
         if(t == null){
             line = GetTargetLine(12);
             if(line != null && line.LastOrDefault() != tile()){
                 t = line.LastOrDefault();
             }
         }
         if(t != null){
             B.Add(You("cast") + " lightning bolt. ",this);
             PhysicalObject bolt_target = null;
             List<Actor> damage_targets = new List<Actor>();
             foreach(Tile t2 in line){
                 if(t2.actor() != null && t2.actor() != this){
                     bolt_target = t2.actor();
                     damage_targets.Add(t2.actor());
                     break;
                 }
                 else{
                     if(t2.ConductsElectricity()){
                         bolt_target = t2;
                         break;
                     }
                 }
             }
             if(bolt_target != null){ //this code, man
                 Dict<PhysicalObject,List<PhysicalObject>> chain = new Dict<PhysicalObject,List<PhysicalObject>>();
                 chain[this] = new List<PhysicalObject>{bolt_target};
                 List<PhysicalObject> last_added = new List<PhysicalObject>{bolt_target};
                 for(bool done=false;!done;){
                     done = true;
                     List<PhysicalObject> new_last_added = new List<PhysicalObject>();
                     foreach(PhysicalObject added in last_added){
                         List<PhysicalObject> sort_list = new List<PhysicalObject>();
                         foreach(Tile nearby in added.TilesWithinDistance(3,true)){
                             if(nearby.actor() != null || nearby.ConductsElectricity()){
                                 if(added.HasLOE(nearby)){
                                     if(nearby.actor() != null){
                                         bolt_target = nearby.actor();
                                     }
                                     else{
                                         bolt_target = nearby;
                                     }
                                     bool contains_value = false;
                                     foreach(List<PhysicalObject> list in chain.d.Values){
                                         foreach(PhysicalObject o in list){
                                             if(o == bolt_target){
                                                 contains_value = true;
                                                 break;
                                             }
                                         }
                                         if(contains_value){
                                             break;
                                         }
                                     }
                                     if(!chain.d.ContainsKey(bolt_target) && !contains_value){
                                         if(bolt_target as Actor != null){
                                             damage_targets.AddUnique(bolt_target as Actor);
                                         }
                                         done = false;
                                         if(sort_list.Count == 0){
                                             sort_list.Add(bolt_target);
                                         }
                                         else{
                                             int idx = 0;
                                             foreach(PhysicalObject o in sort_list){
                                                 if(bolt_target.DistanceFrom(added) < o.DistanceFrom(added)){
                                                     sort_list.Insert(idx,bolt_target);
                                                     break;
                                                 }
                                                 ++idx;
                                             }
                                             if(idx == sort_list.Count){
                                                 sort_list.Add(bolt_target);
                                             }
                                         }
                                         if(chain[added] == null){
                                             chain[added] = new List<PhysicalObject>{bolt_target};
                                         }
                                         else{
                                             chain[added].Add(bolt_target);
                                         }
                                     }
                                 }
                             }
                         }
                         foreach(PhysicalObject o in sort_list){
                             new_last_added.Add(o);
                         }
                     }
                     if(!done){
                         last_added = new_last_added;
                     }
                 } //whew. the tree structure is complete. start at chain[this] and go from there...
                 Dict<int,List<pos>> frames = new Dict<int,List<pos>>();
                 Dict<PhysicalObject,int> line_length = new Dict<PhysicalObject,int>();
                 line_length[this] = 0;
                 List<PhysicalObject> current = new List<PhysicalObject>{this};
                 List<PhysicalObject> next = new List<PhysicalObject>();
                 while(current.Count > 0){
                     foreach(PhysicalObject o in current){
                         if(chain[o] != null){
                             foreach(PhysicalObject o2 in chain[o]){
                                 List<Tile> bres = o.GetBestLineOfEffect(o2);
                                 bres.RemoveAt(0);
                                 line_length[o2] = bres.Count + line_length[o];
                                 int idx = 0;
                                 foreach(Tile t2 in bres){
                                     if(frames[idx + line_length[o]] != null){
                                         frames[idx + line_length[o]].Add(new pos(t2.row,t2.col));
                                     }
                                     else{
                                         frames[idx + line_length[o]] = new List<pos>{new pos(t2.row,t2.col)};
                                     }
                                     ++idx;
                                 }
                                 next.Add(o2);
                             }
                         }
                     }
                     current = next;
                     next = new List<PhysicalObject>();
                 }
                 List<pos> frame = frames[0];
                 for(int i=0;frame != null;++i){
                     foreach(pos p in frame){
                         Screen.WriteMapChar(p.row,p.col,'*',Color.RandomLightning);
                     }
                     Game.GLUpdate();
                     Thread.Sleep(50);
                     frame = frames[i];
                 }
                 foreach(Actor a in damage_targets){
                     B.Add("The bolt hits " + a.the_name + ". ",a);
                     if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(spell)){
                         a.curmp += Spell.Tier(spell);
                         if(a.curmp > a.maxmp){
                             a.curmp = a.maxmp;
                         }
                         a.GainSpell(spell);
                         B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a);
                     }
                     a.TakeDamage(DamageType.ELECTRIC,DamageClass.MAGICAL,R.Roll(3+bonus,6),this,a_name);
                 }
             }
             else{
                 AnimateBeam(line,'*',Color.RandomLightning);
                 B.Add("The bolt hits " + t.the_name + ". ",t);
             }
         }
         else{
             return false;
         }
         break;
     case SpellType.MAGIC_HAMMER:
         if(t == null){
             t = TileInDirection(GetDirection());
         }
         if(t != null){
             Actor a = t.actor();
             B.Add(You("cast") + " magic hammer. ",this);
             M.Draw();
             B.DisplayNow();
             Screen.AnimateMapCell(t.row,t.col,new colorchar('*',Color.Magenta),100);
             if(a != null){
                 B.Add(You("wallop") + " " + a.TheName(true) + ". ",this,a);
                 if(a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(4+bonus,6),this,a_name)){
                     a.ApplyStatus(AttrType.STUNNED,201);
                     /*a.RefreshDuration(AttrType.STUNNED,a.DurationOfMagicalEffect(2) * 100 + 1,a.YouAre() + " no longer stunned. ",a); //todo fix this so it doesn't use the +1, since nothing else does that any more.
                     B.Add(a.YouAre() + " stunned. ",a);*/
                 }
             }
             else{
                 B.Add("You strike " + t.the_name + ". ");
             }
         }
         else{
             return false;
         }
         break;
     case SpellType.PORTAL: //player-only for now
     {
         t = tile();
         if(t.Is(FeatureType.INACTIVE_TELEPORTAL,FeatureType.STABLE_TELEPORTAL,FeatureType.TELEPORTAL) || t.Is(TileType.DOOR_O,TileType.STAIRS)){
             B.Add("You can't create a portal here. ");
             return false;
         }
         B.Add("You cast portal. ");
         List<Tile> other_portals = M.AllTiles().Where(x=>x.Is(FeatureType.INACTIVE_TELEPORTAL,FeatureType.STABLE_TELEPORTAL));
         if(other_portals.Count == 0){
             B.Add("You create a dormant portal. ");
             t.AddFeature(FeatureType.INACTIVE_TELEPORTAL);
         }
         else{
             if(other_portals.Count == 1){ //it should be inactive in this case
                 B.Add("You open a portal. ");
                 t.AddFeature(FeatureType.STABLE_TELEPORTAL);
                 Tile t2 = other_portals[0];
                 t2.RemoveFeature(FeatureType.INACTIVE_TELEPORTAL);
                 t2.AddFeature(FeatureType.STABLE_TELEPORTAL);
                 Q.Add(new Event(t,new List<Tile>{t2},100,EventType.TELEPORTAL,AttrType.NO_ATTR,100,""));
                 Q.Add(new Event(t2,new List<Tile>{t},100,EventType.TELEPORTAL,AttrType.NO_ATTR,100,""));
             }
             else{
                 B.Add("You open a portal. ");
                 t.AddFeature(FeatureType.STABLE_TELEPORTAL);
                 Q.Add(new Event(t,other_portals,100,EventType.TELEPORTAL,AttrType.NO_ATTR,100,""));
                 foreach(Tile t2 in other_portals){
                     Event e = Q.FindTargetedEvent(t2,EventType.TELEPORTAL);
                     if(e != null){
                         e.area.Add(t);
                     }
                 }
             }
         }
         break;
     }
     case SpellType.PASSAGE:
     {
         if(this == player && HasAttr(AttrType.IMMOBILE)){
             B.Add("You can't travel through a passage while immobilized. ");
             return false;
         }
         int dir = -1;
         if(t == null){
             dir = GetDirection(true,false);
             t = TileInDirection(dir);
         }
         else{
             dir = DirectionOf(t);
         }
         if(t != null){
             if(t.Is(TileType.WALL,TileType.CRACKED_WALL,TileType.WAX_WALL,TileType.DOOR_C,TileType.HIDDEN_DOOR,TileType.STONE_SLAB)){
                 B.Add(You("cast") + " passage. ",this);
                 colorchar ch = new colorchar(Color.Cyan,'!');
                 if(this == player){
                     Screen.CursorVisible = false;
                     switch(DirectionOf(t)){
                     case 8:
                     case 2:
                         ch.c = '|';
                         break;
                     case 4:
                     case 6:
                         ch.c = '-';
                         break;
                     }
                 }
                 else{
                     if(HasAttr(AttrType.IMMOBILE)){
                         B.Add("The spell fails. ",this);
                         Q1();
                         return true;
                     }
                 }
                 List<Tile> tiles = new List<Tile>();
                 List<colorchar> memlist = new List<colorchar>();
                 Screen.CursorVisible = false;
                 Tile last_wall = null;
                 while(!t.passable){
                     if(t.row == 0 || t.row == ROWS-1 || t.col == 0 || t.col == COLS-1){
                         break;
                     }
                     if(this == player){
                         tiles.Add(t);
                         memlist.Add(Screen.MapChar(t.row,t.col));
                         Screen.WriteMapChar(t.row,t.col,ch);
                         Game.GLUpdate();
                         Thread.Sleep(35);
                     }
                     last_wall = t;
                     t = t.TileInDirection(dir);
                 }
                 Input.FlushInput();
                 if(t.passable){
                     if(t.actor() == null){
                         int r = row;
                         int c = col;
                         Move(t.row,t.col);
                         if(this == player){
                             Screen.WriteMapChar(r,c,M.VisibleColorChar(r,c));
                             Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col));
                             int idx = 0;
                             foreach(Tile tile in tiles){
                                 Screen.WriteMapChar(tile.row,tile.col,memlist[idx++]);
                                 Game.GLUpdate();
                                 Thread.Sleep(35);
                             }
                         }
                         Input.FlushInput();
                         B.Add(You("travel") + " through the passage. ",this,t);
                     }
                     else{
                         Tile destination = null;
                         List<Tile> adjacent = t.TilesAtDistance(1).Where(x=>x.passable && x.actor() == null && x.DistanceFrom(last_wall) == 1);
                         if(adjacent.Count > 0){
                             destination = adjacent.Random();
                         }
                         else{
                             foreach(Tile tile in M.ReachableTilesByDistance(t.row,t.col,false)){
                                 if(tile.actor() == null){
                                     destination = tile;
                                     break;
                                 }
                             }
                         }
                         if(destination != null){
                             int r = row;
                             int c = col;
                             Move(destination.row,destination.col);
                             if(this == player){
                                 Screen.WriteMapChar(r,c,M.VisibleColorChar(r,c));
                                 Screen.WriteMapChar(destination.row,destination.col,M.VisibleColorChar(destination.row,destination.col));
                                 int idx = 0;
                                 foreach(Tile tile in tiles){
                                     Screen.WriteMapChar(tile.row,tile.col,memlist[idx++]);
                                     Game.GLUpdate();
                                     Thread.Sleep(35);
                                 }
                             }
                             Input.FlushInput();
                             B.Add(You("travel") + " through the passage. ",this,destination);
                         }
                         else{
                             B.Add("Something blocks " + Your() + " movement through the passage. ",this);
                         }
                     }
                 }
                 else{
                     if(this == player){
                         int idx = 0;
                         foreach(Tile tile in tiles){
                             Screen.WriteMapChar(tile.row,tile.col,memlist[idx++]);
                             Game.GLUpdate();
                             Thread.Sleep(35);
                         }
                         Input.FlushInput();
                         B.Add("The passage is blocked. ",this);
                     }
                 }
             }
             else{
                 if(this == player){
                     B.Add("There's no wall here. ");
                 }
                 return false;
             }
         }
         else{
             return false;
         }
         break;
     }
     case SpellType.DOOM:
         if(t == null){
             line = GetTargetLine(12);
             if(line != null && line.LastOrDefault() != tile()){
                 t = line.LastOrDefault();
             }
         }
         if(t != null){
             B.Add(You("cast") + " doom. ",this);
             Actor a = FirstActorInLine(line);
             if(a != null){
                 AnimateProjectile(line.ToFirstSolidTileOrActor(),'*',Color.RandomDoom);
                 if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(spell)){
                     a.curmp += Spell.Tier(spell);
                     if(a.curmp > a.maxmp){
                         a.curmp = a.maxmp;
                     }
                     a.GainSpell(spell);
                     B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a);
                 }
                 if(a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(4+bonus,6),this,a_name)){
                     a.ApplyStatus(AttrType.VULNERABLE,R.Between(4,8)*100);
                     /*if(!a.HasAttr(AttrType.VULNERABLE)){
                         B.Add(a.You("become") + " vulnerable. ",a);
                     }
                     a.RefreshDuration(AttrType.VULNERABLE,a.DurationOfMagicalEffect(R.Between(4,8)) * 100,a.YouAre() + " no longer so vulnerable. ",a);*/
                     if(a == player){
                         Help.TutorialTip(TutorialTopic.Vulnerable);
                     }
                 }
             }
             else{
                 AnimateProjectile(line,'*',Color.RandomDoom);
             }
         }
         else{
             return false;
         }
         break;
     case SpellType.AMNESIA:
         if(t == null){
             t = TileInDirection(GetDirection());
         }
         if(t != null){
             Actor a = t.actor();
             if(a != null && (CanSee(a) || a.HasAttr(AttrType.DANGER_SENSED))){
                 B.Add(You("cast") + " amnesia. ",this);
                 a.AnimateStorm(2,4,4,'*',Color.RandomRainbow);
                 if(a.ResistedBySpirit() || a.HasAttr(AttrType.MENTAL_IMMUNITY)){
                     B.Add(a.You("resist") + "! ",a);
                 }
                 else{
                     B.Add("You fade from " + a.TheName(true) + "'s awareness. ");
                     a.player_visibility_duration = 0;
                     a.target = null;
                     a.target_location = null;
                     a.attrs[AttrType.AMNESIA_STUN] = 7;
                 }
             }
             else{
                 B.Add("There's nothing to target there. ");
                 return false;
             }
         }
         else{
             return false;
         }
         break;
     case SpellType.SHADOWSIGHT:
         B.Add("You cast shadowsight. ");
         B.Add("Your eyes pierce the darkness. ");
         int duration = (R.Roll(2,20) + 60) * 100;
         RefreshDuration(AttrType.SHADOWSIGHT,duration,"Your shadowsight wears off. ");
         RefreshDuration(AttrType.LOW_LIGHT_VISION,duration);
         break;
     case SpellType.BLIZZARD:
     {
         List<Actor> targets = ActorsWithinDistance(5,true).Where(x=>HasLOE(x));
         B.Add(You("cast") + " blizzard. ",this);
         AnimateStorm(5,8,24,'*',Color.RandomIce);
         B.Add("An ice storm surrounds " + the_name + ". ",this);
         foreach(Tile t2 in TilesWithinDistance(5).Where(x=>HasLOE(x))){
             t2.ApplyEffect(DamageType.COLD);
         }
         while(targets.Count > 0){
             int idx = R.Roll(1,targets.Count) - 1;
             Actor a = targets[idx];
             targets.Remove(a);
             //B.Add("The blizzard hits " + a.the_name + ". ",a);
             if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(spell)){
                 a.curmp += Spell.Tier(spell);
                 if(a.curmp > a.maxmp){
                     a.curmp = a.maxmp;
                 }
                 a.GainSpell(spell);
                 B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a);
             }
             if(a.TakeDamage(DamageType.COLD,DamageClass.MAGICAL,R.Roll(5+bonus,6),this,a_name)){
                 if(!a.HasAttr(AttrType.BURNING,AttrType.IMMUNE_COLD)){
                     a.ApplyStatus(AttrType.SLOWED,R.Between(6,10)*100);
                     /*B.Add(a.YouAre() + " slowed. ",a);
                     a.RefreshDuration(AttrType.SLOWED,a.DurationOfMagicalEffect(R.Between(6,10)) * 100,a.YouAre() + " no longer slowed. ",a);*/
                 }
             }
         }
         break;
     }
     case SpellType.TELEKINESIS:
         if(t == null){
             line = GetTargetTile(12,0,true,true);
             if(line != null){
                 t = line.LastOrDefault();
             }
         }
         if(!SharedEffect.Telekinesis(true,this,t)){
             return false;
         }
         break;
     case SpellType.COLLAPSE:
         if(t == null){
             line = GetTargetTile(12,0,true,false);
             if(line != null){
                 t = line.LastOrDefault();
             }
         }
         if(t != null){
             B.Add(You("cast") + " collapse. ",this);
             B.DisplayNow();
             for(int dist=2;dist>0;--dist){
                 List<pos> cells = new List<pos>();
                 List<colorchar> chars = new List<colorchar>();
                 pos p2 = new pos(t.row-dist,t.col-dist);
                 if(p2.BoundsCheck(M.tile)){
                     cells.Add(p2);
                     chars.Add(new colorchar('\\',Color.DarkGreen));
                 }
                 p2 = new pos(t.row-dist,t.col+dist);
                 if(p2.BoundsCheck(M.tile)){
                     cells.Add(p2);
                     chars.Add(new colorchar('/',Color.DarkGreen));
                 }
                 p2 = new pos(t.row+dist,t.col-dist);
                 if(p2.BoundsCheck(M.tile)){
                     cells.Add(p2);
                     chars.Add(new colorchar('/',Color.DarkGreen));
                 }
                 p2 = new pos(t.row+dist,t.col+dist);
                 if(p2.BoundsCheck(M.tile)){
                     cells.Add(p2);
                     chars.Add(new colorchar('\\',Color.DarkGreen));
                 }
                 Screen.AnimateMapCells(cells,chars);
             }
             Screen.AnimateMapCell(t.row,t.col,new colorchar('X',Color.DarkGreen));
             foreach(Tile neighbor in t.TilesWithinDistance(1).Randomize()){
                 if(neighbor.p.BoundsCheck(M.tile,false)){
                     if(neighbor.IsTrap()){
                         B.Add("A falling stone triggers a trap. ",neighbor);
                         //neighbor.TriggerTrap();
                     }
                     if(neighbor.passable){
                         neighbor.ApplyEffect(DamageType.NORMAL); //break items and set off traps
                     }
                     if((neighbor == t && t.Is(TileType.WALL,TileType.FLOOR,TileType.RUBBLE,TileType.CRACKED_WALL)) || neighbor.Is(TileType.RUBBLE,TileType.FLOOR)){
                         neighbor.Toggle(null,TileType.GRAVEL);
                         neighbor.RemoveFeature(FeatureType.SLIME);
                         neighbor.RemoveFeature(FeatureType.OIL);
                         foreach(Tile n2 in neighbor.TilesAtDistance(1)){
                             n2.solid_rock = false;
                         }
                     }
                     else{
                         if(neighbor.Is(TileType.CRACKED_WALL)){
                             neighbor.Toggle(null,R.CoinFlip()? TileType.RUBBLE : TileType.GRAVEL);
                             foreach(Tile n2 in neighbor.TilesAtDistance(1)){
                                 n2.solid_rock = false;
                             }
                         }
                         else{
                             if(neighbor.Is(TileType.WALL)){
                                 TileType new_type = TileType.FLOOR;
                                 switch(R.Roll(3)){
                                 case 1:
                                     new_type = TileType.CRACKED_WALL;
                                     break;
                                 case 2:
                                     new_type = TileType.RUBBLE;
                                     break;
                                 case 3:
                                     new_type = TileType.GRAVEL;
                                     break;
                                 }
                                 neighbor.Toggle(null,new_type);
                                 foreach(Tile n2 in neighbor.TilesAtDistance(1)){
                                     n2.solid_rock = false;
                                 }
                             }
                         }
                     }
                 }
             }
             foreach(Actor a in t.ActorsWithinDistance(1)){
                 if(a != this){
                     B.Add("Rubble falls on " + a.TheName(true) + ". ",a.tile());
                     if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(spell)){
                         a.curmp += Spell.Tier(spell);
                         if(a.curmp > a.maxmp){
                             a.curmp = a.maxmp;
                         }
                         a.GainSpell(spell);
                         B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a);
                     }
                     a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(3+bonus,6),this,"falling rubble");
                 }
             }
             t.MakeNoise(6);
         }
         else{
             return false;
         }
         break;
     case SpellType.STONE_SPIKES:
     {
         Tile prev = null;
         if(t == null){
             line = GetTargetTile(12,2,true,true);
             if(line != null){
                 t = line.LastOrDefault();
                 prev = line.LastBeforeSolidTile();
             }
         }
         if(t != null){
             Tile LOE_tile = t;
             if(!t.passable && prev != null){
                 LOE_tile = prev;
             }
             B.Add(You("cast") + " stone spikes. ",this);
             B.Add("Stalagmites shoot up from the ground! ");
             List<Tile> deleted = new List<Tile>();
             while(true){
                 bool changed = false;
                 foreach(Tile nearby in t.TilesWithinDistance(2)){
                     if(nearby.Is(TileType.STALAGMITE) && LOE_tile.HasLOE(nearby)){
                         nearby.Toggle(null);
                         deleted.Add(nearby);
                         changed = true;
                     }
                 }
                 if(!changed){
                     break;
                 }
             }
             Q.RemoveTilesFromEventAreas(deleted,EventType.STALAGMITE);
             List<Tile> area = new List<Tile>();
             List<Actor> affected_actors = new List<Actor>();
             foreach(Tile t2 in t.TilesWithinDistance(2)){
                 if(LOE_tile.HasLOE(t2)){
                     if(t2.actor() != null){
                         affected_actors.Add(t2.actor());
                     }
                     else{
                         if((t2.IsTrap() || t2.Is(TileType.FLOOR,TileType.GRAVE_DIRT,TileType.GRAVEL)) && t2.inv == null){
                             if(!R.OneIn(4)){
                                 area.Add(t2);
                             }
                         }
                     }
                 }
             }
             foreach(Actor a in affected_actors){
                 if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(spell)){
                     a.curmp += Spell.Tier(spell);
                     if(a.curmp > a.maxmp){
                         a.curmp = a.maxmp;
                     }
                     a.GainSpell(spell);
                     B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a);
                 }
                 if(a != this){
                     a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(4+bonus,6),this,a_name);
                 }
             }
             if(area.Count > 0){
                 foreach(Tile t2 in area){
                     TileType previous_type = t2.type;
                     t2.Toggle(null,TileType.STALAGMITE);
                     t2.toggles_into = previous_type;
                 }
                 Q.Add(new Event(area,150,EventType.STALAGMITE,5));
             }
         }
         else{
             return false;
         }
         break;
     }
     }
     if(curmp >= required_mana){
         curmp -= required_mana;
     }
     else{
         IncreaseExhaustion((required_mana - curmp)*5);
         curmp = 0;
     }
     if(HasFeat(FeatType.ARCANE_INTERFERENCE)){
         foreach(Actor a in ActorsWithinDistance(12,true)){
             if(a.maxmp > 0 && HasLOE(a)){
                 a.ApplyStatus(AttrType.STUNNED,R.Between(3,6)*100);
                 if(a.HasSpell(spell)){
                     B.Add(a.the_name + " can no longer cast " + Spell.Name(spell) + ". ",a);
                     a.spells[spell] = false;
                 }
             }
         }
         /*bool empowered = false;
         foreach(Actor a in ActorsWithinDistance(12,true)){
             if(a.HasSpell(spell) && HasLOE(a)){
                 B.Add(a.the_name + " can no longer cast " + Spell.Name(spell) + ". ",a);
                 a.spells[spell] = false;
                 empowered = true;
             }
         }
         if(empowered){
             B.Add("Arcane feedback empowers your spells! ");
             RefreshDuration(AttrType.EMPOWERED_SPELLS,R.Between(7,12)*100,Your() + " spells are no longer empowered. ",this);
         }*/
     }
     if(HasFeat(FeatType.CHAIN_CASTING)){
         RefreshDuration(AttrType.CHAIN_CAST,100);
     }
     MakeNoise(4);
     if(this == player && !Help.displayed[TutorialTopic.CastingWithoutMana]){
         int max_tier = spells_in_order.Greatest(x=>Spell.Tier(x));
         if(curmp < max_tier){
             Help.TutorialTip(TutorialTopic.CastingWithoutMana);
         }
     }
     Q1();
     return true;
 }