Exemplo n.º 1
0
 public static void DebugDisplayDijkstra(PosArray<int> d,int default_cost)
 {
     int h = d.objs.GetLength(0);
     int w = d.objs.GetLength(1);
     for(int i=0;i<h;++i){
         for(int j=0;j<w;++j){
             if(d[i,j] == U.DijkstraMax){
                 Screen.WriteMapChar(i,j,'!',Color.DarkGray);
             }
             else{
                 if(d[i,j] == U.DijkstraMin){
                     Screen.WriteMapChar(i,j,'#',Color.Gray);
                 }
                 else{
                     if(d[i,j] == 0){
                         Screen.WriteMapChar(i,j,'0',Color.White);
                     }
                     else{
                         int cost = d[i,j] / default_cost;
                         if(cost < 10){
                             Screen.WriteMapChar(i,j,cost.ToString()[0],Color.Cyan);
                         }
                         else{
                             Screen.WriteMapChar(i,j,'+',Color.Blue);
                         }
                     }
                 }
             }
         }
     }
     Input.ReadKey();
 }
Exemplo n.º 2
0
        public static void DebugDisplayDijkstra(PosArray <int> d, int default_cost)
        {
            int h = d.objs.GetLength(0);
            int w = d.objs.GetLength(1);

            for (int i = 0; i < h; ++i)
            {
                for (int j = 0; j < w; ++j)
                {
                    if (d[i, j] == U.DijkstraMax)
                    {
                        Screen.WriteMapChar(i, j, '!', Color.DarkGray);
                    }
                    else
                    {
                        if (d[i, j] == U.DijkstraMin)
                        {
                            Screen.WriteMapChar(i, j, '#', Color.Gray);
                        }
                        else
                        {
                            if (d[i, j] == 0)
                            {
                                Screen.WriteMapChar(i, j, '0', Color.White);
                            }
                            else
                            {
                                int cost = d[i, j] / default_cost;
                                if (cost < 10)
                                {
                                    if (cost < 0)
                                    {
                                        if (cost < -26)
                                        {
                                            Screen.WriteMapChar(i, j, '-', Color.DarkRed);
                                        }
                                        else
                                        {
                                            char ch = (char)(Math.Abs(cost) + (int)('A') - 1);
                                            Screen.WriteMapChar(i, j, ch, Color.Red);
                                        }
                                    }
                                    else
                                    {
                                        Screen.WriteMapChar(i, j, cost.ToString()[0], Color.Cyan);
                                    }
                                }
                                else
                                {
                                    Screen.WriteMapChar(i, j, '+', Color.Blue);
                                }
                            }
                        }
                    }
                }
            }
            Input.ReadKey();
        }
        public static void DebugDisplayDijkstra(PosArray <int> d, int default_cost)
        {
            int h = d.objs.GetLength(0);
            int w = d.objs.GetLength(1);

            for (int i = 0; i < h; ++i)
            {
                for (int j = 0; j < w; ++j)
                {
                    if (d[i, j] == U.DijkstraMax)
                    {
                        Screen.WriteMapChar(i, j, '!', Color.DarkGray);
                    }
                    else
                    {
                        if (d[i, j] == U.DijkstraMin)
                        {
                            Screen.WriteMapChar(i, j, '#', Color.Gray);
                        }
                        else
                        {
                            if (d[i, j] == 0)
                            {
                                Screen.WriteMapChar(i, j, '0', Color.White);
                            }
                            else
                            {
                                int cost = d[i, j] / default_cost;
                                if (cost < 10)
                                {
                                    Screen.WriteMapChar(i, j, cost.ToString()[0], Color.Cyan);
                                }
                                else
                                {
                                    Screen.WriteMapChar(i, j, '+', Color.Blue);
                                }
                            }
                        }
                    }
                }
            }
            Global.ReadKey();
        }
Exemplo n.º 4
0
 public TextPanel(GLWindow parent_window,int rows,int cols,int cell_h_px,int cell_w_px,int v_offset_px,int h_offset_px,string font_filename,int char_width_px,int padding_between_chars_px,bool antialiased_font)
 {
     memory = new PosArray<colorchar>(rows,cols);
     colorchar cch = TextPanel.GetBlackChar();
     for(int i=0;i<rows;++i){
         for(int j=0;j<cols;++j){
             memory[i,j] = cch;
         }
     }
     height = rows * cell_h_px;
     width = cols * cell_w_px;
     string shader = antialiased_font? Shader.AAFontFS() : Shader.FontFS();
     surface = Surface.Create(parent_window,font_filename,shader,false,2,4,4); //todo: maybe a bool to control whether the panel gets added to the window's list?
     SpriteType.DefineSingleRowSprite(surface,char_width_px,padding_between_chars_px); //also todo, TransparentFontFS exists now...?
     CellLayout.CreateGrid(surface,rows,cols,cell_h_px,cell_w_px,0,0);
     surface.SetOffsetInPixels(h_offset_px,v_offset_px);
     surface.SetEasyLayoutCounts(rows*cols);
     surface.DefaultUpdatePositions();
     UpdateSurface(0,rows*cols-1);
 }
Exemplo n.º 5
0
 public Dungeon(int height,int width,int seed)
 {
     H = height;
     W = width;
     map = new PosArray<CellType>(H,W);
     R.SetSeed(seed); //this is a bad idea
 }
Exemplo n.º 6
0
 public bool TakeDamage(Damage dmg,string cause_of_death)
 {
     //returns true if still alive
     if(dmg.amount == 0){
         return true;
     }
     bool damage_dealt = false;
     int old_hp = curhp;
     if(curhp <= 0 && dmg.amount < 1000){ //then we're dealing with a corpse, and they don't take normal amounts of damage
         return true;
     }
     bool ice_removed = false;
     if(dmg.amount < 1000){
         if(HasAttr(AttrType.FROZEN) && (dmg.major_damage || dmg.type == DamageType.FIRE)){ //this should ignore bleeding and poison, but not searing
             attrs[AttrType.FROZEN] -= dmg.amount;
             if(attrs[AttrType.FROZEN] <= 0){
                 attrs[AttrType.FROZEN] = 0;
                 B.Add("The ice breaks! ",this);
                 ice_removed = true;
             }
             dmg.amount = 0;
             if(dmg.type == DamageType.FIRE && HasAttr(AttrType.FROZEN)){
                 attrs[AttrType.FROZEN] = 0;
                 B.Add("The ice melts! ",this);
                 ice_removed = true;
             }
         }
         if(dmg.type == DamageType.FIRE && HasAttr(AttrType.OIL_COVERED)){
             if(HasAttr(AttrType.IMMUNE_BURNING)){
                 B.Add("The oil burns off of " + the_name + ". ",this);
                 attrs[AttrType.OIL_COVERED] = 0;
             }
             else{
                 B.Add(You("catch",true) + " fire! ",this);
                 ApplyBurning();
             }
         }
         if(dmg.type == DamageType.COLD && HasAttr(AttrType.SLIMED)){
             attrs[AttrType.SLIMED] = 0;
             B.Add("The slime freezes and falls from " + the_name + ". ",this);
         }
         if(!dmg.major_damage && HasAttr(AttrType.MINOR_IMMUNITY,AttrType.SHIELDED)){
             return true;
         }
         if(HasAttr(AttrType.MECHANICAL_SHIELD)){
             B.Add(Your() + " shield moves to protect it from harm. ",this);
             return true;
         }
         if(dmg.major_damage){
             if(HasAttr(AttrType.BLOCKING)){
                 B.Add(You("block") + "! ",this); //todo: extra effects will go here eventually
                 attrs[AttrType.BLOCKING]--;
                 return true;
             }
             else{
                 if(HasAttr(AttrType.SHIELDED)){
                     B.Add(Your() + " shield flashes! ",this);
                     attrs[AttrType.SHIELDED]--;
                     return true;
                 }
                 else{
                     if(HasAttr(AttrType.VULNERABLE)){
                         attrs[AttrType.VULNERABLE] = 0;
                         if(this == player){
                             B.Add("Ouch! ");
                         }
                         else{
                             B.Add(YouAre() + " devastated! ",this);
                         }
                         foreach(Event e in Q.list){
                             if(!e.dead && e.target == this && e.type == EventType.REMOVE_ATTR && e.attr == AttrType.VULNERABLE){
                                 e.dead = true;
                             }
                         }
                         dmg.amount += R.Roll(3,6);
                     }
                 }
             }
         }
         if(HasAttr(AttrType.INVULNERABLE)){
             dmg.amount = 0;
         }
         /*if(HasAttr(AttrType.TOUGH) && dmg.damclass == DamageClass.PHYSICAL){
             dmg.amount -= 2;
         }
         if(HasAttr(AttrType.ARCANE_SHIELDED)){
             if(attrs[AttrType.ARCANE_SHIELDED] >= dmg.amount){
                 attrs[AttrType.ARCANE_SHIELDED] -= dmg.amount;
                 dmg.amount = 0;
             }
             else{
                 dmg.amount -= attrs[AttrType.ARCANE_SHIELDED];
                 attrs[AttrType.ARCANE_SHIELDED] = 0;
             }
             if(!HasAttr(AttrType.ARCANE_SHIELDED)){
                 B.Add(Your() + " shield fades. ",this);
             }
         }
         if(dmg.damclass == DamageClass.MAGICAL){
             dmg.amount -= TotalSkill(SkillType.SPIRIT) / 2;
         }
         if(HasAttr(AttrType.DAMAGE_REDUCTION) && dmg.amount > 5){
             dmg.amount = 5;
         }*/
         if(dmg.amount > 15 && magic_trinkets.Contains(MagicTrinketType.BELT_OF_WARDING)){
             dmg.amount = 15;
             B.Add(Your() + " " + MagicTrinket.Name(MagicTrinketType.BELT_OF_WARDING) + " softens the blow. ",this);
         }
         dmg.amount -= attrs[AttrType.DAMAGE_RESISTANCE];
         switch(dmg.type){
         case DamageType.NORMAL:
             if(dmg.amount > 0){
                 curhp -= dmg.amount;
                 damage_dealt = true;
             }
             else{
                 if(!ice_removed){
                     B.Add(YouAre() + " undamaged. ",this);
                 }
             }
             break;
         case DamageType.MAGIC:
             if(dmg.amount > 0){
                 curhp -= dmg.amount;
                 damage_dealt = true;
             }
             else{
                 if(!ice_removed){
                     B.Add(YouAre() + " unharmed. ",this);
                 }
             }
             break;
         case DamageType.FIRE:
         {
             if(HasAttr(AttrType.IMMUNE_FIRE)){
                 dmg.amount = 0;
                 //B.Add(the_name + " is immune! ",this);
                 if(HasFeat(FeatType.BOILING_BLOOD) && attrs[AttrType.BLOOD_BOILED] == 5){
                     RefreshDuration(AttrType.IMMUNE_FIRE,1000);
                     RefreshDuration(AttrType.BLOOD_BOILED,1000,"Your blood cools. ");
                 }
             }
             if(dmg.amount > 0){
                 curhp -= dmg.amount;
                 damage_dealt = true;
             }
             else{
                 if((this == player || dmg.amount > 1) && !ice_removed && !HasAttr(AttrType.IMMUNE_FIRE) && !HasAttr(AttrType.JUST_SEARED)){
                     B.Add(YouAre() + " unburnt. ",this);
                 }
             }
             break;
         }
         case DamageType.COLD:
         {
             if(HasAttr(AttrType.IMMUNE_COLD)){
                 dmg.amount = 0;
                 //B.Add(YouAre() + " unharmed. ",this);
             }
             if(dmg.amount > 0){
                 curhp -= dmg.amount;
                 damage_dealt = true;
                 if(type == ActorType.GIANT_SLUG){
                     B.Add("The cold leaves " + the_name + " vulnerable. ",this);
                     RefreshDuration(AttrType.VULNERABLE,R.Between(7,13)*100,the_name + " is no longer vulnerable. ",this);
                 }
             }
             else{
                 if(!ice_removed && !HasAttr(AttrType.IMMUNE_COLD)){
                     B.Add(YouAre() + " unharmed. ",this);
                 }
             }
             break;
         }
         case DamageType.ELECTRIC:
         {
             if(HasAttr(AttrType.IMMUNE_ELECTRICITY)){
                 dmg.amount = 0;
             }
             if(dmg.amount > 0){
                 curhp -= dmg.amount;
                 damage_dealt = true;
             }
             else{
                 if(!ice_removed && !HasAttr(AttrType.IMMUNE_ELECTRICITY)){
                     B.Add(YouAre() + " unharmed. ",this);
                 }
             }
             break;
         }
         case DamageType.POISON:
             if(HasAttr(AttrType.NONLIVING)){
                 dmg.amount = 0;
             }
             if(dmg.amount > 0){
                 curhp -= dmg.amount;
                 damage_dealt = true;
                 if(type == ActorType.PLAYER){
                     B.Add("The poison burns! ");
                 }
                 else{
                     if(R.Roll(1,5) == 5 && !HasAttr(AttrType.PLANTLIKE)){ //hmm
                         B.Add(the_name + " shudders. ",this);
                     }
                 }
             }
             break;
         case DamageType.NONE:
             break;
         }
     }
     else{
         if(curhp > 0){
             curhp = 0;
         }
     }
     /*if(dmg.source != null && dmg.source == player && dmg.damclass == DamageClass.PHYSICAL && resisted && !cause_of_death.Contains("arrow")){
         Help.TutorialTip(TutorialTopic.Resistance);
     }*/
     if(damage_dealt){
         Interrupt();
         attrs[AttrType.AMNESIA_STUN] = 0;
         if(dmg.major_damage){
             recover_time = Q.turn + 500;
             attrs[AttrType.BANDAGED] = 0;
         }
         if(HasAttr(AttrType.ASLEEP)){
             attrs[AttrType.ASLEEP] = 0;
             attrs[AttrType.JUST_AWOKE] = 1;
             if(this == player){
                 Input.FlushInput();
                 B.Add("You wake up. ");
             }
         }
         if(dmg.source != null){
             if(type != ActorType.PLAYER && dmg.source != this && !HasAttr(AttrType.CONFUSED)){
                 target = dmg.source;
                 target_location = M.tile[dmg.source.row,dmg.source.col];
                 if(dmg.source.IsHiddenFrom(this)){
                     player_visibility_duration = -1;
                 }
                 if(type == ActorType.CAVERN_HAG && dmg.source == player){
                     attrs[AttrType.COOLDOWN_2] = 1;
                 }
                 if(type == ActorType.CRUSADING_KNIGHT && dmg.source == player && !HasAttr(AttrType.COOLDOWN_1) && !M.wiz_lite && !CanSee(player) && curhp > 0){
                     List<string> verb = new List<string>{"Show yourself","Reveal yourself","Unfold thyself","Present yourself","Unveil yourself","Make yourself known"};
                     List<string> adjective = new List<string>{"despicable","filthy","foul","nefarious","vulgar","sorry","unworthy"};
                     List<string> noun = new List<string>{"villain","blackguard","devil","scoundrel","wretch","cur","rogue"};
                     //B.Add(TheName(true) + " shouts \"" + verb.Random() + ", " + adjective.Random() + " " + noun.Random() + "!\" ");
                     B.Add("\"" + verb.Random() + ", " + adjective.Random() + " " + noun.Random() + "!\" ");
                     B.Add(the_name + " raises a gauntlet. ",this);
                     B.Add("Sunlight fills the dungeon. ");
                     M.wiz_lite = true;
                     M.wiz_dark = false;
                     Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING));
                     M.Draw();
                     B.Print(true);
                     attrs[AttrType.COOLDOWN_1]++;
                     foreach(Actor a in M.AllActors()){
                         if(a != this && a != player && !a.HasAttr(AttrType.BLINDSIGHT) && HasLOS(a)){
                             a.ApplyStatus(AttrType.BLIND,R.Between(5,9)*100);
                             /*B.Add(a.YouAre() + " blinded! ",a);
                             a.RefreshDuration(AttrType.BLIND,R.Between(5,9)*100,a.YouAre() + " no longer blinded. ",a);*/
                         }
                     }
                     if(!player.HasAttr(AttrType.BLINDSIGHT) && HasLOS(player)){ //do the player last, so all the previous messages can be seen.
                         player.ApplyStatus(AttrType.BLIND,R.Between(5,9)*100);
                         /*B.Add(player.YouAre() + " blinded! ");
                         player.RefreshDuration(AttrType.BLIND,R.Between(5,9)*100,player.YouAre() + " no longer blinded. ");*/
                     }
                 }
                 /*if(HasAttr(AttrType.RADIANT_HALO) && !M.wiz_dark && DistanceFrom(dmg.source) <= LightRadius() && HasLOS(dmg.source)){
                     B.Add(YourVisible() + " radiant halo burns " + dmg.source.TheName(true) + ". ",this,dmg.source);
                     int amount = R.Roll(2,6);
                     if(amount >= dmg.source.curhp){
                         amount = dmg.source.curhp - 1;
                     }
                     if(dmg.source.curhp > 1){ //this should prevent infinite loops if one haloed entity attacks another
                         dmg.source.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,amount,this);
                     }
                 }*/
                 if(dmg.source == player && IsFinalLevelDemon() && attrs[AttrType.COOLDOWN_2] < 2){
                     attrs[AttrType.COOLDOWN_2]++;
                 }
             }
         }
         if(HasAttr(AttrType.SPORE_BURST)){
             if(type == ActorType.SPORE_POD){
                 curhp = 0;
                 if(player.CanSee(this)){
                     B.Add("The spore pod bursts! ",this);
                 }
                 else{
                     if(DistanceFrom(player) == 1){
                         B.Add(YouVisible("burst") + "! ");
                     }
                 }
                 List<Tile> area = tile().AddGaseousFeature(FeatureType.SPORES,18);
                 Event.RemoveGas(area,600,FeatureType.SPORES,12);
             }
             else{
                 if(!HasAttr(AttrType.COOLDOWN_1) && dmg.major_damage){
                     RefreshDuration(AttrType.COOLDOWN_1,50); //cooldown added mostly to prevent several triggers while surrounded by fire. todo: this cooldown is no longer necessary now that searing is minor damage.
                     B.Add(You("retaliate") + " with a burst of spores! ",this);
                     List<Tile> area = tile().AddGaseousFeature(FeatureType.SPORES,8);
                     Event.RemoveGas(area,600,FeatureType.SPORES,12);
                 }
             }
         }
         if(HasFeat(FeatType.BOILING_BLOOD) && dmg.type != DamageType.POISON){
             if(attrs[AttrType.BLOOD_BOILED] < 5){
                 B.Add("Your blood boils! ");
                 if(attrs[AttrType.BLOOD_BOILED] == 4){
                     RefreshDuration(AttrType.IMMUNE_FIRE,1000);
                 }
                 GainAttrRefreshDuration(AttrType.BLOOD_BOILED,1000,"Your blood cools. ");
                 if(this == player){
                     Help.TutorialTip(TutorialTopic.IncreasedSpeed);
                 }
             }
             else{
                 RefreshDuration(AttrType.IMMUNE_FIRE,1000);
                 RefreshDuration(AttrType.BLOOD_BOILED,1000,"Your blood cools. ");
             }
         }
         if(type == ActorType.DREAM_SPRITE && (dmg.source != null || (HasLOE(player) && DistanceFrom(player) <= 12))){
             attrs[AttrType.COOLDOWN_2] = 1;
         }
         if(type == ActorType.MECHANICAL_KNIGHT){
             if(old_hp == 5){
                 curhp = 0;
             }
             else{
                 if(old_hp == 10){
                     curhp = 5;
                     switch(R.Roll(3)){
                     case 1:
                         B.Add(Your() + " arms are destroyed! ",this);
                         attrs[AttrType.COOLDOWN_1] = 1;
                         attrs[AttrType.MECHANICAL_SHIELD] = 0;
                         break;
                     case 2:
                         B.Add(Your() + " legs are destroyed! ",this);
                         attrs[AttrType.COOLDOWN_1] = 2;
                         path.Clear();
                         target_location = null;
                         break;
                     case 3:
                         B.Add(Your() + " head is destroyed! ",this);
                         attrs[AttrType.COOLDOWN_1] = 3;
                         break;
                     }
                 }
             }
         }
         if(dmg.type == DamageType.FIRE && (type == ActorType.TROLL || type == ActorType.TROLL_BLOODWITCH)){
             attrs[AttrType.PERMANENT_DAMAGE] += dmg.amount; //permanent damage doesn't regenerate
         }
         if(dmg.type == DamageType.FIRE && type == ActorType.SKITTERMOSS && !HasAttr(AttrType.COOLDOWN_1)){
             attrs[AttrType.COOLDOWN_1]++;
             B.Add("The fire kills " + Your() + " insects. ",this);
             color = Color.White;
         }
         if(type == ActorType.ALASI_SCOUT && old_hp == maxhp){
             B.Add("The glow leaves " + Your() + " sword. ",this);
             attrs[AttrType.KEEPS_DISTANCE] = 0;
         }
     }
     if(curhp <= 0){
         if(type == ActorType.PLAYER){
             if(magic_trinkets.Contains(MagicTrinketType.PENDANT_OF_LIFE)){
                 curhp = 1;
                 /*attrs[AttrType.INVULNERABLE]++;
                 Q.Add(new Event(this,1,AttrType.INVULNERABLE));*/
                 if(R.CoinFlip()){
                     B.Add("Your pendant glows brightly, then crumbles to dust! ");
                     magic_trinkets.Remove(MagicTrinketType.PENDANT_OF_LIFE);
                 }
                 else{
                     B.Add("Your pendant glows brightly! ");
                 }
             }
             else{
                 if(cause_of_death.Length > 0 && cause_of_death[0] == '*'){
                     Global.KILLED_BY = cause_of_death.Substring(1);
                 }
                 else{
                     Global.KILLED_BY = "killed by " + cause_of_death;
                 }
                 M.Draw();
                 if(Global.GAME_OVER == false){
                     B.Add("You die. ");
                 }
                 B.PrintAll();
                 Global.GAME_OVER = true;
                 return false;
             }
         }
         else{
             if(HasAttr(AttrType.BOSS_MONSTER)){
                 M.Draw();
                 B.Add("The fire drake dies. ");
                 B.PrintAll();
                 if(player.curhp > 0){
                     B.Add("The threat to your nation has been slain! You begin the long trek home to deliver the good news... ");
                     Global.KILLED_BY = "Died of ripe old age";
                 }
                 else{
                     B.Add("The threat to your nation has been slain! Unfortunately, you won't be able to deliver the news... ");
                 }
                 B.PrintAll();
                 Global.GAME_OVER = true;
                 Global.BOSS_KILLED = true;
             }
             if(dmg.amount < 1000){ //everything that deals this much damage prints its own message
                 if(type == ActorType.BERSERKER){
                     if(!HasAttr(AttrType.COOLDOWN_1)){
                         attrs[AttrType.COOLDOWN_1]++;
                         Q.Add(new Event(this,R.Between(3,5)*100,AttrType.COOLDOWN_1)); //changed from 350
                         Q.KillEvents(this,AttrType.COOLDOWN_2);
                         if(!HasAttr(AttrType.COOLDOWN_2)){
                             attrs[AttrType.COOLDOWN_2] = DirectionOf(player);
                         }
                         B.Add(the_name + " somehow remains standing! He screams with fury! ",this);
                     }
                     return true;
                 }
                 if(type == ActorType.GHOST){
                     Event e = Q.FindTargetedEvent(this,EventType.TOMBSTONE_GHOST);
                     if(e != null){
                         e.dead = true;
                         Q.Add(new Event(null,e.area,R.Between(3,6)*100,EventType.TOMBSTONE_GHOST));
                     }
                 }
                 if(HasAttr(AttrType.REGENERATES_FROM_DEATH) && dmg.type != DamageType.FIRE){
                     B.Add(the_name + " collapses, still twitching. ",this);
                 }
                 else{
                     if(HasAttr(AttrType.REASSEMBLES)){
                         if(Weapon.IsBlunt(dmg.weapon_used) && R.CoinFlip()){
                             B.Add(the_name + " is smashed to pieces. ",this);
                             attrs[AttrType.REASSEMBLES] = 0;
                         }
                         else{
                             B.Add(the_name + " collapses into a pile of bones. ",this);
                         }
                     }
                     else{
                         if(!HasAttr(AttrType.BOSS_MONSTER) && type != ActorType.SPORE_POD){
                             if(HasAttr(AttrType.NONLIVING)){
                                 B.Add(the_name + " is destroyed. ",this);
                             }
                             else{
                                 if(type == ActorType.FINAL_LEVEL_CULTIST && dmg.type == DamageType.FIRE){
                                     B.Add(the_name + " is consumed by flames. ",this);
                                     List<int> valid_circles = new List<int>();
                                     for(int i=0;i<5;++i){
                                         if(M.FinalLevelSummoningCircle(i).PositionsWithinDistance(2,M.tile).Any(x=>M.tile[x].Is(TileType.DEMONIC_IDOL))){
                                             valid_circles.Add(i);
                                         }
                                     }
                                     int nearest = valid_circles.WhereLeast(x=>DistanceFrom(M.FinalLevelSummoningCircle(x))).Random();
                                     pos circle = M.FinalLevelSummoningCircle(nearest);
                                     if(M.actor[circle] != null){
                                         circle = circle.PositionsWithinDistance(3,M.tile).Where(x=>M.tile[x].passable && M.actor[x] == null).Random();
                                     }
                                     M.final_level_cultist_count[nearest]++;
                                     if(M.final_level_cultist_count[nearest] >= 5){
                                         M.final_level_cultist_count[nearest] = 0;
                                         List<ActorType> valid_types = new List<ActorType>{ActorType.MINOR_DEMON};
                                         if(M.final_level_demon_count > 3){
                                             valid_types.Add(ActorType.FROST_DEMON);
                                         }
                                         if(M.final_level_demon_count > 5){
                                             valid_types.Add(ActorType.BEAST_DEMON);
                                         }
                                         if(M.final_level_demon_count > 11){
                                             valid_types.Add(ActorType.DEMON_LORD);
                                         }
                                         if(M.final_level_demon_count > 21){ //eventually, the majority will be demon lords
                                             valid_types.Add(ActorType.DEMON_LORD);
                                         }
                                         if(M.final_level_demon_count > 25){
                                             valid_types.Add(ActorType.DEMON_LORD);
                                         }
                                         if(M.final_level_demon_count > 31){
                                             valid_types.Add(ActorType.DEMON_LORD);
                                         }
                                         if(M.final_level_demon_count > 36){
                                             valid_types.Add(ActorType.DEMON_LORD);
                                             valid_types.Add(ActorType.DEMON_LORD);
                                             valid_types.Add(ActorType.DEMON_LORD);
                                         }
                                         if(player.CanSee(M.tile[circle])){
                                             B.Add("The flames leap and swirl, and a demon appears! ");
                                         }
                                         else{
                                             B.Add("You feel an evil presence. ");
                                         }
                                         Create(valid_types.Random(),circle.row,circle.col);
                                         if(M.actor[circle] != null){
                                             M.actor[circle].player_visibility_duration = -1;
                                             M.actor[circle].attrs[AttrType.PLAYER_NOTICED] = 1;
                                             if(M.actor[circle].type != ActorType.DEMON_LORD){
                                                 M.actor[circle].attrs[AttrType.NO_ITEM] = 1;
                                             }
                                         }
                                         M.final_level_demon_count++;
                                     }
                                 }
                                 else{
                                     B.Add(the_name + " dies. ",this);
                                 }
                             }
                         }
                         if(IsFinalLevelDemon()){
                             bool circles = false;
                             bool demons = false;
                             for(int i=0;i<5;++i){
                                 Tile circle = M.tile[M.FinalLevelSummoningCircle(i)];
                                 if(circle.TilesWithinDistance(3).Any(x=>x.type == TileType.DEMONIC_IDOL)){
                                     circles = true;
                                     break;
                                 }
                             }
                             foreach(Actor a in M.AllActors()){
                                 if(a != this && a.IsFinalLevelDemon()){
                                     demons = true;
                                     break;
                                 }
                             }
                             if(!circles && !demons){ //victory
                                 curhp = 100;
                                 B.Add("As the last demon falls, your victory gives you a new surge of strength. ");
                                 B.PrintAll();
                                 B.Add("Kersai's summoning has been stopped. His cult will no longer threaten the area. ");
                                 B.PrintAll();
                                 B.Add("You begin the journey home to deliver the news. ");
                                 B.PrintAll();
                                 Global.GAME_OVER = true;
                                 Global.BOSS_KILLED = true;
                                 Global.KILLED_BY = "nothing";
                                 return false;
                             }
                         }
                         if(HasAttr(AttrType.REGENERATES_FROM_DEATH) && dmg.type == DamageType.FIRE){
                             attrs[AttrType.REGENERATES_FROM_DEATH] = 0;
                         }
                     }
                 }
             }
             if(HasAttr(AttrType.TURN_INTO_CORPSE)){
                 attrs[AttrType.CORPSE] = attrs[AttrType.TURN_INTO_CORPSE];
                 attrs[AttrType.TURN_INTO_CORPSE] = 0;
                 if(!HasAttr(AttrType.NO_CORPSE_KNOCKBACK)){
                     if(HasAttr(AttrType.NONLIVING)){
                         SetName("destroyed " + name);
                     }
                     else{
                         SetName(name + "'s corpse");
                     }
                 }
                 return false;
             }
             if(HasAttr(AttrType.BURNING)){
                 tile().AddFeature(FeatureType.FIRE);
             }
             if(LightRadius() > 0){
                 UpdateRadius(LightRadius(),0);
             }
             if(type == ActorType.SHADOW){
                 type = ActorType.ZOMBIE; //awful awful hack. (CalculateDimming checks for Shadows)
                 CalculateDimming();
                 type = ActorType.SHADOW;
             }
             if(HasAttr(AttrType.REGENERATES_FROM_DEATH)){
                 Tile troll = null;
                 foreach(Tile t in M.ReachableTilesByDistance(row,col,false)){
                     if(!t.Is(TileType.DOOR_O) && !t.Is(FeatureType.TROLL_CORPSE,FeatureType.TROLL_BLOODWITCH_CORPSE,FeatureType.BONES)){
                         if(type == ActorType.TROLL){
                             t.AddFeature(FeatureType.TROLL_CORPSE);
                         }
                         else{
                             t.AddFeature(FeatureType.TROLL_BLOODWITCH_CORPSE);
                         }
                         troll = t;
                         break;
                     }
                 }
                 if(curhp > -3){
                     curhp = -3;
                 }
                 //curhp -= R.Roll(10)+5;
                 if(curhp < -50){
                     curhp = -50;
                 }
                 Event e = new Event(troll,100,EventType.REGENERATING_FROM_DEATH);
                 e.value = curhp;
                 e.secondary_value = attrs[AttrType.PERMANENT_DAMAGE];
                 e.tiebreaker = tiebreakers.IndexOf(this);
                 Q.Add(e);
             }
             if(HasAttr(AttrType.REASSEMBLES)){
                 Tile sk = null;
                 foreach(Tile t in M.ReachableTilesByDistance(row,col,false)){
                     if(!t.Is(TileType.DOOR_O) && !t.Is(FeatureType.TROLL_CORPSE,FeatureType.TROLL_BLOODWITCH_CORPSE,FeatureType.BONES)){
                         if(type == ActorType.SKELETON){
                             t.AddFeature(FeatureType.BONES);
                         }
                         sk = t;
                         break;
                     }
                 }
                 Event e = new Event(sk,R.Between(10,20)*100,EventType.REASSEMBLING);
                 e.tiebreaker = tiebreakers.IndexOf(this);
                 Q.Add(e);
             }
             if(type == ActorType.STONE_GOLEM){
                 List<Tile> deleted = new List<Tile>();
                 while(true){
                     bool changed = false;
                     foreach(Tile t in TilesWithinDistance(3)){
                         if(t.Is(TileType.STALAGMITE) && HasLOE(t)){
                             t.Toggle(null);
                             deleted.Add(t);
                             changed = true;
                         }
                     }
                     if(!changed){
                         break;
                     }
                 }
                 Q.RemoveTilesFromEventAreas(deleted,EventType.STALAGMITE);
                 List<Tile> area = new List<Tile>();
                 foreach(Tile t in TilesWithinDistance(3)){
                     if((t.IsTrap() || t.Is(TileType.FLOOR,TileType.GRAVE_DIRT,TileType.GRAVEL)) && t.inv == null && (t.actor() == null || t.actor() == this) && HasLOE(t)){
                         if(R.CoinFlip()){
                             area.Add(t);
                         }
                     }
                 }
                 if(area.Count > 0){
                     foreach(Tile t in area){
                         TileType previous_type = t.type;
                         t.Toggle(null,TileType.STALAGMITE);
                         t.toggles_into = previous_type;
                     }
                     Q.Add(new Event(area,150,EventType.STALAGMITE,5));
                 }
             }
             if(type == ActorType.VULGAR_DEMON && DistanceFrom(player) == 1){
                 B.Add("The vulgar demon possesses your " + player.EquippedWeapon + "! ");
                 B.Print(true);
                 player.EquippedWeapon.status[EquipmentStatus.POSSESSED] = true;
                 Help.TutorialTip(TutorialTopic.Possessed);
             }
             if(type == ActorType.DREAM_SPRITE){
                 int num = R.Roll(5) + 4;
                 List<Tile> new_area = tile().AddGaseousFeature(FeatureType.PIXIE_DUST,num);
                 if(new_area.Count > 0){
                     Event.RemoveGas(new_area,400,FeatureType.PIXIE_DUST,25);
                 }
             }
             if(type == ActorType.FROSTLING){
                 if(player.CanSee(tile()) && player.HasLOS(tile())){
                     AnimateExplosion(this,2,Color.RandomIce,'*');
                     B.Add("The air freezes around the defeated frostling. ",this);
                 }
                 foreach(Tile t in TilesWithinDistance(2)){
                     if(HasLOE(t)){
                         t.ApplyEffect(DamageType.COLD);
                         Actor a = t.actor();
                         if(a != null && a != this){
                             a.ApplyFreezing();
                         }
                     }
                 }
             }
             if(player.HasAttr(AttrType.CONVICTION)){
                 player.attrs[AttrType.KILLSTREAK]++;
             }
             if(HasAttr(AttrType.GRABBING)){
                 Actor grabbed = ActorInDirection(attrs[AttrType.GRABBING]);
                 if(grabbed != null && grabbed.HasAttr(AttrType.GRABBED)){
                     grabbed.attrs[AttrType.GRABBED]--;
                 }
             }
             if(HasAttr(AttrType.HUMANOID_INTELLIGENCE) || type == ActorType.ZOMBIE){
                 if(R.OneIn(3) && !HasAttr(AttrType.NO_ITEM)){
                     tile().GetItem(Item.Create(Item.RandomItem(),-1,-1));
                 }
             }
             foreach(Item item in inv){
                 tile().GetItem(item);
             }
             Q.KillEvents(this,EventType.ANY_EVENT);
             M.RemoveTargets(this);
             int idx = tiebreakers.IndexOf(this);
             if(idx != -1){
                 tiebreakers[idx] = null;
             }
             if(group != null){
                 if(type == ActorType.DREAM_WARRIOR || type == ActorType.DREAM_SPRITE){
                     List<Actor> temp = new List<Actor>();
                     foreach(Actor a in group){
                         if(a != this){
                             temp.Add(a);
                             a.group = null;
                         }
                     }
                     foreach(Actor a in temp){
                         a.Kill();
                     }
                 }
                 else{
                     if(group.Count >= 2 && this == group[0] && HasAttr(AttrType.WANDERING)){
                         if(type != ActorType.NECROMANCER){
                             group[1].attrs[AttrType.WANDERING]++;
                         }
                     }
                     if(group.Count <= 2 || type == ActorType.NECROMANCER){
                         foreach(Actor a in group){
                             if(a != this){
                                 a.group = null;
                             }
                         }
                         group.Clear();
                         group = null;
                     }
                     else{
                         group.Remove(this);
                         group = null;
                     }
                 }
             }
             M.actor[row,col] = null;
             return false;
         }
     }
     else{
         if(HasFeat(FeatType.FEEL_NO_PAIN) && damage_dealt && curhp < 20 && old_hp >= 20){
             B.Add("You can feel no pain! ");
             attrs[AttrType.INVULNERABLE]++;
             Q.Add(new Event(this,500,AttrType.INVULNERABLE,"You can feel pain again. "));
         }
         if(magic_trinkets.Contains(MagicTrinketType.CLOAK_OF_SAFETY) && damage_dealt && dmg.amount >= curhp){
             B.PrintAll();
             M.Draw();
             if(B.YesOrNoPrompt("Your cloak starts to vanish. Use your cloak to escape?",false)){
                 PosArray<bool> good = new PosArray<bool>(ROWS,COLS);
                 foreach(Tile t in M.AllTiles()){
                     if(t.passable){
                         good[t.row,t.col] = true;
                     }
                     else{
                         good[t.row,t.col] = false;
                     }
                 }
                 foreach(Actor a in M.AllActors()){
                     foreach(Tile t in M.AllTiles()){
                         if(good[t.row,t.col]){
                             if(a.DistanceFrom(t) < 6 || a.HasLOS(t.row,t.col)){ //was CanSee, but this is safer
                                 good[t.row,t.col] = false;
                             }
                         }
                     }
                 }
                 List<Tile> tilelist = new List<Tile>();
                 Tile destination = null;
                 for(int i=4;i<COLS;++i){
                     foreach(pos p in PositionsAtDistance(i)){
                         if(good[p]){
                             tilelist.Add(M.tile[p.row,p.col]);
                         }
                     }
                     if(tilelist.Count > 0){
                         destination = tilelist[R.Roll(1,tilelist.Count)-1];
                         break;
                     }
                 }
                 if(destination != null){
                     Move(destination.row,destination.col);
                 }
                 else{
                     for(int i=0;i<9999;++i){
                         int rr = R.Roll(1,ROWS-2);
                         int rc = R.Roll(1,COLS-2);
                         if(M.tile[rr,rc].passable && M.actor[rr,rc] == null && DistanceFrom(rr,rc) >= 6 && !M.tile[rr,rc].IsTrap()){
                             Move(rr,rc);
                             break;
                         }
                     }
                 }
                 B.Add("You escape. ");
             }
             B.Add("Your cloak vanishes completely! ");
             magic_trinkets.Remove(MagicTrinketType.CLOAK_OF_SAFETY);
         }
     }
     return true;
 }
Exemplo n.º 7
0
 public PosArray<colorchar> GetCurrentScreen()
 {
     int rows = memory.objs.GetLength(0);
     int cols = memory.objs.GetLength(1);
     PosArray<colorchar> result = new PosArray<colorchar>(rows,cols);
     for(int i=0;i<rows;++i){
         for(int j=0;j<cols;++j){
             result[i,j] = memory[i,j];
         }
     }
     return result;
 }
Exemplo n.º 8
0
 public static void CreateMap()
 {
     tile = new PosArray<Tile>(ROWS,COLS);
     actor = new PosArray<Actor>(ROWS,COLS);
     var noise = U.GetNoise(ROWS,COLS);
     //float min = 999.0f;
     //float max = -999.0f;
     cutoff_elevation = -5;
     for(int i=0;i<ROWS;++i){
         for(int j=0;j<COLS;++j){
             /*if(noise[i,j] > max){
                 max = noise[i,j];
             }
             if(noise[i,j] < min){
                 min = noise[i,j];
             }*/
             //Tile.Create(i,j,0);
             Tile.Create(i,j,(int)(noise[i,j] * 6.0f)); //was 9.0 (but that was too much)
             if(tile[i,j].elevation - 4 < cutoff_elevation){
                 cutoff_elevation = tile[i,j].elevation - 5;
             }
             //Tile.Create(i,j,R.Between(-2,2));
         }
     }
 }
Exemplo n.º 9
0
 public void GenerateFeatures(PosArray<CellType> map,List<pos> interesting_tiles)
 {
     List<DungeonFeature> features = new List<DungeonFeature>();
     foreach(DungeonFeature df in Enum.GetValues(typeof(DungeonFeature))){
         features.Add(df);
     }
     int[] rarity = null;
     switch(level_types[current_level-1]){
     case LevelType.Standard:
         rarity = new int[]{30,40,15,30,
             25,6,8,15,15,3,3,4,4,4};
         break;
     case LevelType.Cave:
         rarity = new int[]{30,15,10,15,
             15,100,8,10,30,5,25,6,3,4};
         break;
     case LevelType.Mine:
         rarity = new int[]{30,20,5,16,
             18,6,7,15,10,3,5,30,1,1};
         break;
     case LevelType.Hive:
         rarity = new int[]{30,15,100,50,
             50,100,4,10,10,100,100,15,25,0};
         break;
     case LevelType.Fortress:
         rarity = new int[]{30,100,100,100,
             100,1,8,25,8,6,1,100,20,15};
         break;
     case LevelType.Garden:
         rarity = new int[]{20,50,50,0,
             20,30,20,20,20,20,5,5,8,15};
         break;
     case LevelType.Crypt:
         rarity = new int[]{30,50,50,25,
             25,30,8,10,15,20,30,5,8,8};
         break;
     case LevelType.Slime: //todo
     default:
         rarity = new int[]{30,20,15,12,
             10,4,8,10,7,3,3,3,4,10};
         break;
     }
     /*int[] rarity = new int[]{30,20,15,12,
             10,4,8,10,7,3,3,3,4};
     int[] frequency = new int[]{1,1,2,2,3,3,3,
         4,4,4,4,2,2,5,5,5,6,5,5,8};*/
     int[] removal_chance = new int[]{95,20,10,60,
         30,25,70,50,60,35,12,10,10,20};
     /*List<DungeonFeature> feature_pool = new List<DungeonFeature>();
     for(int i=0;i<20;++i){
         for(int j=frequency[i];j>0;--j){
             feature_pool.Add(features[i]);
         }
     }*/
     List<DungeonFeature> feature_pool = new List<DungeonFeature>();
     while(feature_pool.Count < 3){
         feature_pool.Clear();
         for(int i=0;i<14;++i){
             if(rarity[i] > 0 && R.OneIn(rarity[i])){
                 feature_pool.Add(features[i]);
             }
         }
     }
     List<DungeonFeature> selected_features = new List<DungeonFeature>();
     for(int i=0;i<5 && feature_pool.Count > 0;++i){
         selected_features.Add(feature_pool.RemoveRandom());
     }
     List<DungeonFeature> result = new List<DungeonFeature>();
     for(int count=5;count>0 && selected_features.Count > 0;--count){
         DungeonFeature df = selected_features.Random();
         if(R.PercentChance(removal_chance[(int)df])){
             selected_features.Remove(df);
         }
         result.Add(df);
     }
     List<pos> thin_walls = null;
     if(result.Contains(DungeonFeature.CRACKED_WALL)){
         thin_walls = map.AllPositions().Where(x=>map[x].IsWall() && x.HasOppositePairWhere(true,y=>y.BoundsCheck(tile) && map[y].IsFloor()));
     }
     while(result.Count > 0){
         DungeonFeature df = result.RemoveRandom();
         switch(df){
         case DungeonFeature.POOL_OF_RESTORATION:
         case DungeonFeature.FIRE_PIT:
         {
             for(int i=0;i<50;++i){
                 int rr = R.Roll(ROWS-4)+1;
                 int rc = R.Roll(COLS-4)+1;
                 if(interesting_tiles.Count > 0){
                     pos p = interesting_tiles.RemoveRandom();
                     rr = p.row;
                     rc = p.col;
                     map[p] = CellType.RoomInterior;
                 }
                 if(map[rr,rc].IsFloor()){
                     bool floors = true;
                     foreach(pos p in new pos(rr,rc).PositionsAtDistance(1,map)){
                         if(!map[p].IsFloor()){
                             floors = false;
                             break;
                         }
                     }
                     if(floors){
                         if(df == DungeonFeature.POOL_OF_RESTORATION){
                             map[rr,rc] = CellType.Pool;
                         }
                         if(df == DungeonFeature.FIRE_PIT){
                             map[rr,rc] = CellType.FirePit;
                         }
                         break;
                     }
                 }
             }
             break;
         }
         case DungeonFeature.BARREL:
         case DungeonFeature.TORCH:
             for(int i=0;i<50;++i){
                 int rr = R.Roll(ROWS-2);
                 int rc = R.Roll(COLS-2);
                 if(map[rr,rc].IsRoomType() && map[rr,rc].IsFloor()){
                     if(df == DungeonFeature.BARREL){
                         map[rr,rc] = CellType.Barrel;
                     }
                     if(df == DungeonFeature.TORCH){
                         map[rr,rc] = CellType.Torch;
                     }
                     break;
                 }
             }
             break;
         case DungeonFeature.WEBS:
         case DungeonFeature.RUBBLE:
         {
             for(int i=0;i<50;++i){
                 int rr = R.Roll(ROWS-2);
                 int rc = R.Roll(COLS-2);
                 if(map[rr,rc].IsRoomType()){
                     CellType cell = CellType.Webs;
                     int max_radius = 2;
                     switch(df){
                     case DungeonFeature.WEBS:
                         cell = CellType.Webs;
                         max_radius = 3;
                         break;
                     case DungeonFeature.RUBBLE:
                         cell = CellType.Rubble;
                         max_radius = 2;
                         break;
                     }
                     map[rr,rc] = cell;
                     for(int j=1;j<=max_radius;++j){
                         List<pos> added = new List<pos>();
                         foreach(pos p in new pos(rr,rc).PositionsWithinDistance(j,map)){
                             if(map[p] == cell){
                                 foreach(pos neighbor in p.CardinalAdjacentPositions()){
                                     if(map[neighbor].IsFloor() && R.CoinFlip()){
                                         added.AddUnique(neighbor);
                                     }
                                 }
                             }
                         }
                         foreach(pos p in added){
                             /*if(df == DungeonFeature.RUBBLE){
                                 foreach(pos neighbor in p.CardinalAdjacentPositions()){
                                     if(!added.Contains(neighbor) && map[neighbor].IsFloor() && R.OneIn(3)){
                                         map[neighbor] = CellType.Gravel;
                                     }
                                 }
                             }*/
                             map[p] = cell;
                         }
                     }
                     break;
                 }
             }
             break;
         }
         case DungeonFeature.SLIME:
         case DungeonFeature.OIL:
         {
             for(int i=0;i<50;++i){
                 int rr = R.Roll(ROWS-2);
                 int rc = R.Roll(COLS-2);
                 if(map[rr,rc].IsFloor()){
                     CellType cell = CellType.Wall;
                     int max_radius = 2;
                     switch(df){
                     case DungeonFeature.SLIME:
                         cell = CellType.Slime;
                         max_radius = 3;
                         break;
                     case DungeonFeature.OIL:
                         cell = CellType.Oil;
                         max_radius = 3;
                         break;
                     }
                     map[rr,rc] = cell;
                     for(int j=1;j<=max_radius;++j){
                         List<pos> added = new List<pos>();
                         foreach(pos p in new pos(rr,rc).PositionsWithinDistance(j,map)){
                             if(map[p] == cell){
                                 foreach(pos neighbor in p.CardinalAdjacentPositions()){
                                     if(map[neighbor].IsFloor() && R.CoinFlip()){
                                         added.AddUnique(neighbor);
                                     }
                                 }
                             }
                         }
                         foreach(pos p in added){
                             map[p] = cell;
                         }
                     }
                     break;
                 }
             }
             break;
         }
         case DungeonFeature.FIRE_GEYSER:
         {
             for(int i=0;i<50;++i){
                 int rr = R.Roll(ROWS-4)+1;
                 int rc = R.Roll(COLS-4)+1;
                 if(map[rr,rc].IsFloor()){
                     bool floors = true;
                     foreach(pos p in new pos(rr,rc).PositionsAtDistance(1,map)){
                         if(!map[p].IsFloor()){
                             floors = false;
                             break;
                         }
                     }
                     if(floors){
                         map[rr,rc] = CellType.Geyser;
                         break;
                     }
                 }
             }
             break;
         }
         case DungeonFeature.VINES:
         {
             for(int i=0;i<500;++i){
                 int rr = R.Roll(ROWS-2);
                 int rc = R.Roll(COLS-2);
                 pos p = new pos(rr,rc);
                 if(map[p].IsRoomType() && p.HasAdjacentWhere(x=>map.BoundsCheck(x) && map[x].IsWall())){
                     PosArray<bool> vine = map.GetFloodFillArray(p,false,x=>map[x].IsRoomType() && x.HasAdjacentWhere(y=>map.BoundsCheck(y) && map[y].IsWall()) && !R.OneIn(3)); //changed from one in 6 so vines won't fill caves so often
                     rr = R.Roll(ROWS-2);
                     rc = R.Roll(COLS-2);
                     pos p2 = new pos(rr,rc);
                     PosArray<bool> new_vine = new PosArray<bool>(ROWS,COLS);
                     int max = Math.Max(ROWS,COLS);
                     for(int dist=0;dist<max;++dist){
                         bool found = false;
                         foreach(pos possible_vine in p2.PositionsAtDistance(dist)){
                             if(possible_vine.BoundsCheck(new_vine,false)){
                                 found = true;
                                 if(vine[possible_vine] && possible_vine.PositionsAtDistance(1,new_vine).Where(x=>new_vine[x] || map[x] == CellType.Vine).Count < 3){
                                     new_vine[possible_vine] = true;
                                 }
                             }
                         }
                         if(!found){
                             break;
                         }
                     }
                     List<pos> added = new List<pos>();
                     for(int s=1;s<ROWS-1;++s){
                         for(int t=1;t<COLS-1;++t){
                             if(new_vine[s,t]){
                                 pos neighbor = new pos(s,t);
                                 foreach(int dir in U.FourDirections){
                                     if(R.OneIn(6) && map[neighbor.PosInDir(dir)].IsFloor()){
                                         added.AddUnique(neighbor.PosInDir(dir));
                                     }
                                 }
                             }
                         }
                     }
                     foreach(pos neighbor in added){
                         new_vine[neighbor] = true;
                     }
                     for(int s=1;s<ROWS-1;++s){
                         for(int t=1;t<COLS-1;++t){
                             if(new_vine[s,t]){
                                 if(R.OneIn(35)){
                                     map[s,t] = CellType.PoisonBulb;
                                 }
                                 else{
                                     map[s,t] = CellType.Vine;
                                 }
                             }
                         }
                     }
                     break;
                 }
             }
             break;
         }
         case DungeonFeature.BLAST_FUNGUS:
         case DungeonFeature.FOG_VENT:
         case DungeonFeature.POISON_VENT:
             for(int i=0;i<50;++i){
                 int rr = R.Roll(ROWS-2);
                 int rc = R.Roll(COLS-2);
                 if(map[rr,rc].IsFloor()){
                     if(df == DungeonFeature.BLAST_FUNGUS){
                         map[rr,rc] = CellType.BlastFungus;
                     }
                     if(df == DungeonFeature.FOG_VENT){
                         map[rr,rc] = CellType.FogVent;
                     }
                     if(df == DungeonFeature.POISON_VENT){
                         map[rr,rc] = CellType.PoisonVent;
                     }
                     break;
                 }
             }
             break;
         case DungeonFeature.CRACKED_WALL:
             for(int i=R.Between(2,4);i>0;--i){
                 if(thin_walls.Count > 0){
                     map[thin_walls.RemoveRandom()] = CellType.CrackedWall;
                 }
             }
             break;
         }
     }
 }
Exemplo n.º 10
0
 private void InitializeNewLevel()
 {
     for(int i=0;i<ROWS;++i){
         for(int j=0;j<COLS;++j){
             if(actor[i,j] != null){
                 if(actor[i,j] != player){
                     actor[i,j].inv.Clear();
                     actor[i,j].target = null;
                     if(actor[i,j].group != null){
                         actor[i,j].group.Clear();
                         actor[i,j].group = null;
                     }
                 }
                 actor[i,j] = null;
             }
             if(tile[i,j] != null){
                 tile[i,j].inv = null;
             }
             tile[i,j] = null;
         }
     }
     wiz_lite = false;
     wiz_dark = false;
     if(current_level % 2 == 1){
         feat_gained_this_level = false;
     }
     generated_this_level = new Dict<ActorType, int>();
     monster_density = new PosArray<int>(ROWS,COLS);
     aesthetics = new PosArray<AestheticFeature>(ROWS,COLS);
     dungeon_description = new PosArray<string>(ROWS,COLS);
     safetymap = null;
     travel_map = null;
     Q.ResetForNewLevel();
     last_seen = new colorchar[ROWS,COLS];
     Fire.fire_event = null;
     Fire.burning_objects.Clear();
     if(player.IsBurning()){
         Fire.AddBurningObject(player);
     }
     Actor.tiebreakers = new List<Actor>{player};
     Actor.interrupted_path = new pos(-1,-1);
 }
Exemplo n.º 11
0
 public void UpdateSafetyMap(params PhysicalObject[] sources)
 {
     List<cell> sourcelist = new List<cell>();
     foreach(PhysicalObject o in sources){
         sourcelist.Add(new cell(o.row,o.col,0));
     }
     IntLocationDelegate get_cost = (r,c) => {
         if(actor[r,c] != null){
             return 20 + (10 * actor[r,c].attrs[AttrType.TURNS_HERE]);
             //return 20;
         }
         else{
             if(tile[r,c].Is(TileType.DOOR_C,TileType.RUBBLE)){
                 return 20;
             }
             else{
                 return 10;
             }
         }
     };
     PosArray<int> a = GetDijkstraMap(Global.ROWS,Global.COLS,
                                     (s,t)=>!tile[s,t].passable && !tile[s,t].IsDoorType(false),
                                     //(s,t)=>tile[s,t].Is(TileType.WALL,TileType.HIDDEN_DOOR,TileType.STONE_SLAB,TileType.STATUE),
                                     (u,v) => 10,sourcelist);
     for(int i=0;i<Global.ROWS;++i){
         for(int j=0;j<Global.COLS;++j){
             if(a[i,j] != U.DijkstraMin){
                 if(a[i,j] != U.DijkstraMax && a[i,j] > 200){
                     a[i,j] = 200; //todo: testing this modification. the idea here is to make more "best" spots to flee to, reducing the draw of the few farthest ones.
                 }
                 a[i,j] = -(a[i,j] * 14) / 10; //changed from 1.2 to 1.4 to test
             }
         }
     }
     foreach(PhysicalObject o in sources){
         a[o.row,o.col] = U.DijkstraMin; //now the player (or other sources) become blocking
     }
     UpdateDijkstraMap(a,get_cost);
     /*foreach(PhysicalObject o in sources){ //add a penalty for tiles adjacent to the player
         foreach(pos p in o.PositionsAtDistance(1)){
             if(a[p] != U.DijkstraMax && a[p] != U.DijkstraMin){
                 a[p] += 30;
             }
         }
     }*/
     safetymap = a;
 }
Exemplo n.º 12
0
 public void CalculatePoppyDistanceMap()
 {
     poppy_distance_map = tile.GetDijkstraMap(x=>tile[x].passable && !tile[x].Is(TileType.POPPY_FIELD),x=>!tile[x].Is(TileType.POPPY_FIELD));
 }
Exemplo n.º 13
0
 public PosArray<CellType> GenerateMap(LevelType type)
 {
     PosArray<CellType> result = new PosArray<CellType>(ROWS,COLS);
     Dungeon d = new Dungeon(ROWS,COLS);
     switch(type){
     case LevelType.Standard:
         while(true){
             d.CreateBasicMap();
             d.ConnectDiagonals();
             d.RemoveUnconnectedAreas();
             d.RemoveDeadEndCorridors();
             d.AddDoors(25);
             d.AlterRooms(5,2,2,1,0);
             d.MarkInterestingLocations();
             d.RemoveUnconnectedAreas();
             if(d.NumberOfFloors() < 320 || d.HasLargeUnusedSpaces(300)){
                 d.Clear();
             }
             else{
                 for(int i=0;i<ROWS;++i){
                     for(int j=0;j<COLS;++j){
                         result[i,j] = d.map[i,j];
                     }
                 }
                 return result;
             }
         }
     case LevelType.Cave:
     {
         int roll = R.Roll(2);
         if(R.OneIn(20)){
             roll = 3;
         }
         switch(roll){ //three different algorithms
         case 1:
         {
             while(true){
                 d.FillWithRandomWalls(25);
                 d.ApplyCellularAutomataXYRule(3);
                 d.ConnectDiagonals();
                 d.ImproveMapEdges(5);
                 d.RemoveDeadEndCorridors();
                 d.RemoveUnconnectedAreas();
                 d.MarkInterestingLocationsNonRectangular();
                 if(d.NumberOfFloors() < 320 || d.HasLargeUnusedSpaces(300)){
                     d.Clear();
                 }
                 else{
                     for(int i=0;i<ROWS;++i){
                         for(int j=0;j<COLS;++j){
                             result[i,j] = d.map[i,j];
                         }
                     }
                     return result;
                 }
             }
         }
         case 2:
         {
             while(true){
                 d.CreateTwistyCave(true,40);
                 U.DefaultMetric = DistanceMetric.Manhattan;
                 var dijk = d.map.GetDijkstraMap(x=>!d.map[x].IsWall(),x=>false);
                 for(int i=1;i<ROWS-1;++i){
                     for(int j=1;j<COLS-1;++j){
                         U.DefaultMetric = DistanceMetric.Manhattan;
                         if(dijk[i,j] == 1){
                             pos p = new pos(i,j);
                             List<pos> floors = null;
                             foreach(int dir in U.FourDirections){
                                 pos n = p.PosInDir(dir);
                                 if(dijk[n] == 1){
                                     if(floors == null){
                                         floors = p.PositionsAtDistance(1,dijk).Where(x=>dijk[x] == 0);
                                     }
                                     List<pos> floors2 = new List<pos>();
                                     foreach(pos n2 in n.PositionsAtDistance(1,dijk)){
                                         if(dijk[n2] == 0 && !floors.Contains(n2)){
                                             floors2.Add(n2);
                                         }
                                     }
                                     if(floors2.Count > 0 && R.OneIn(5)){ //IIRC this checks each pair twice, so that affects the chance here
                                         pos f1 = floors.Random();
                                         pos f2 = floors2.Random();
                                         U.DefaultMetric = DistanceMetric.Chebyshev;
                                         int dist = d.map.PathingDistanceFrom(f1,f2,x=>!d.map[x].IsPassable() && d.map[x] != CellType.Door);
                                         if(dist > 22 || (dist > 8 && R.OneIn(4))){
                                             CellType rubble = R.OneIn(8)? CellType.Rubble : CellType.CorridorIntersection;
                                             d[p] = R.OneIn(3)? rubble : CellType.CorridorIntersection;
                                             d[n] = R.OneIn(3)? rubble : CellType.CorridorIntersection;
                                             List<pos> neighbors = new List<pos>();
                                             foreach(pos nearby in p.PositionsAtDistance(1)){
                                                 if(nearby.BoundsCheck(d.map,false) && nearby.DistanceFrom(n) == 1){
                                                     neighbors.Add(nearby);
                                                 }
                                             }
                                             while(neighbors.Count > 0){
                                                 pos neighbor = neighbors.RemoveRandom();
                                                 if(R.OneIn(neighbors.Count + 3) && !d.SeparatesMultipleAreas(neighbor)){
                                                     d[neighbor] = R.OneIn(2)? CellType.Rubble : CellType.CorridorIntersection;
                                                 }
                                             }
                                         }
                                     }
                                     break;
                                 }
                             }
                         }
                     }
                 }
                 /*List<pos> thin_walls = d.map.AllPositions().Where(x=>d.map[x].IsWall() && x.HasOppositePairWhere(true,y=>y.BoundsCheck() && d.map[y].IsFloor()));
                 while(thin_walls.Count > 0){
                     pos p = thin_walls.Random();
                     foreach(int dir in new int[]{8,4}){
                         if(d.map[p.PosInDir(dir)] != CellType.Wall && d.map[p.PosInDir(dir.RotateDir(true,4))] != CellType.Wall){
                             var dijkstra = d.map.GetDijkstraMap(x=>d[x] == CellType.Wall,new List<pos>{p.PosInDir(dir)}); //todo: this would be better as "get distance"
                             if(Math.Abs(dijkstra[p.PosInDir(dir)] - dijkstra[p.PosInDir(dir.RotateDir(true,4))]) > 30){
                                 d.map[p] = CellType.CorridorIntersection;
                                 break;
                             }
                         }
                     }
                     thin_walls.Remove(p); //todo: move thin-wall-removal to schism
                 }*/
                 d.ConnectDiagonals();
                 d.RemoveUnconnectedAreas();
                 d.ImproveMapEdges(5);
                 d.SmoothCorners(60);
                 d.RemoveDeadEndCorridors();
                 d.MarkInterestingLocationsNonRectangular();
                 if(d.NumberOfFloors() < 320 || d.HasLargeUnusedSpaces(300)){
                     d.Clear();
                 }
                 else{
                     for(int i=0;i<ROWS;++i){
                         for(int j=0;j<COLS;++j){
                             result[i,j] = d.map[i,j];
                         }
                     }
                     U.DefaultMetric = DistanceMetric.Chebyshev;
                     return result;
                 }
             }
         }
         case 3:
         {
             d.RoomHeightMax = 3;
             d.RoomWidthMax = 3;
             while(true){
                 int successes = 0;
                 int consecutive_failures = 0;
                 while(successes < 13){
                     if(d.CreateRoom()){
                         ++successes;
                         consecutive_failures = 0;
                     }
                     else{
                         if(consecutive_failures++ >= 50){
                             d.Clear();
                             successes = 0;
                             consecutive_failures = 0;
                         }
                     }
                 }
                 d.CaveWidenRooms(100,50);
                 d.AddRockFormations(40,2);
                 List<pos> thin_walls = d.map.AllPositions().Where(x=>d.map[x].IsWall() && x.HasOppositePairWhere(true,y=>y.BoundsCheck(tile) && d.map[y].IsFloor()));
                 while(!d.IsFullyConnected() && thin_walls.Count > 0){
                     pos p = thin_walls.Random();
                     d.map[p] = CellType.CorridorIntersection;
                     foreach(pos neighbor in p.PositionsWithinDistance(1,d.map)){
                         thin_walls.Remove(neighbor);
                     }
                 }
                 d.ConnectDiagonals();
                 d.RemoveDeadEndCorridors();
                 d.RemoveUnconnectedAreas();
                 d.MarkInterestingLocationsNonRectangular();
                 if(d.NumberOfFloors() < 320 || d.HasLargeUnusedSpaces(300)){ //todo: add 'proper coverage' check here - make sure it stretches across enough of the map.
                     d.Clear();
                 }
                 else{
                     for(int i=0;i<ROWS;++i){
                         for(int j=0;j<COLS;++j){
                             result[i,j] = d.map[i,j];
                         }
                     }
                     return result;
                 }
             }
         }
         }
         break;
     }
     case LevelType.Hive:
     {
         d.RoomHeightMax = 3;
         d.RoomWidthMax = 3;
         while(true){
             int successes = 0;
             int consecutive_failures = 0;
             while(successes < 35){
                 if(d.CreateRoom()){
                     ++successes;
                     consecutive_failures = 0;
                 }
                 else{
                     if(consecutive_failures++ >= 40){
                         d.Clear();
                         successes = 0;
                         consecutive_failures = 0;
                     }
                 }
             }
             d.CaveWidenRooms(100,10);
             d.CaveWidenRooms(3,20);
             List<pos> thin_walls = d.map.AllPositions().Where(x=>d.map[x].IsWall() && x.HasOppositePairWhere(true,y=>y.BoundsCheck(tile) && d.map[y].IsFloor()));
             while(!d.IsFullyConnected() && thin_walls.Count > 0){
                 pos p = thin_walls.Random();
                 d.map[p] = CellType.CorridorIntersection;
                 foreach(pos neighbor in p.PositionsWithinDistance(2,d.map)){
                     thin_walls.Remove(neighbor);
                 }
             }
             d.ConnectDiagonals();
             d.RemoveDeadEndCorridors();
             d.RemoveUnconnectedAreas();
             d.MarkInterestingLocations();
             //to find rooms big enough for stuff in the center:
         //var dijkstra = d.map.GetDijkstraMap(x=>d.map[x].IsWall(),d.map.AllPositions().Where(x=>d.map[x].IsWall() && x.HasAdjacentWhere(y=>d.map.BoundsCheck(y) && !d.map[y].IsWall())));
             if(d.NumberOfFloors() < 340 || d.HasLargeUnusedSpaces(300)){ //todo: add 'proper coverage' check here - make sure it stretches across enough of the map.
                 d.Clear();
             }
             else{
                 for(int i=0;i<ROWS;++i){
                     for(int j=0;j<COLS;++j){
                         result[i,j] = d.map[i,j];
                     }
                 }
                 return result;
             }
         }
     }
     case LevelType.Mine:
     {
         d.CorridorExtraLengthChance = 0;
         d.CorridorChainSizeMax = 10;
         while(true){
             d.RoomHeightMin = 8;
             d.RoomWidthMin = 8;
             d.RoomHeightMax = 8;
             d.RoomWidthMax = 10;
             d.MinimumSpaceBetweenCorridors = 3;
             d.CorridorLengthMin = 3;
             d.CorridorLengthMax = 5;
             while(!d.CreateRoom()){}
             d.RoomHeightMin = 5;
             d.RoomWidthMin = 5;
             d.RoomHeightMax = 5;
             d.RoomWidthMax = 5;
             while(!d.CreateRoom()){}
             while(!d.CreateRoom()){}
             /*for(int i=0;i<10;++i){
                 d.CreateRoom();
             }
             d.AddRockFormations(100,2);*/
             d.MinimumSpaceBetweenCorridors = 5;
             d.CorridorLengthMin = 4;
             d.CorridorLengthMax = 12;
             for(int i=0;i<70;++i){
                 d.CreateCorridor();
             }
             d.CorridorLengthMin = 3;
             d.CorridorLengthMax = 5;
             d.MinimumSpaceBetweenCorridors = 3;
             for(int i=0;i<350;++i){
                 d.CreateCorridor();
             }
             d.RemoveUnconnectedAreas();
             d.ConnectDiagonals(true);
             d.RemoveUnconnectedAreas();
             d.MarkInterestingLocations();
             if(d.NumberOfFloors() < 250 || d.HasLargeUnusedSpaces(300)){
                 d.Clear();
             }
             else{
                 for(int i=0;i<ROWS;++i){
                     for(int j=0;j<COLS;++j){
                         result[i,j] = d.map[i,j];
                     }
                 }
                 return result;
             }
         }
     }
     case LevelType.Fortress:
         while(true){
             int H = ROWS;
             int W = COLS;
             for(int i=H/2-1;i<H/2+1;++i){
                 for(int j=1;j<W-1;++j){
                     if(j==1 || j==W-2){
                         d.map[i,j] = CellType.RoomCorner;
                     }
                     else{
                         d.map[i,j] = CellType.RoomEdge;
                     }
                 }
             }
             for(int i=0;i<700;++i){
                 if(R.OneIn(5)){
                     d.CreateCorridor();
                 }
                 else{
                     d.CreateRoom();
                 }
             }
             bool reflect_features = R.PercentChance(80);
             if(reflect_features){
                 d.AddDoors(25);
                 d.AddPillars(30);
             }
             d.Reflect(true,false);
             d.ConnectDiagonals();
             d.RemoveDeadEndCorridors();
             d.RemoveUnconnectedAreas();
             if(!reflect_features){
                 d.AddDoors(25);
                 d.AddPillars(30);
             }
             bool door_right = false;
             bool door_left = false;
             int rightmost_door = 0;
             int leftmost_door = 999;
             for(int j=0;j<22;++j){
                 if(d[H/2-2,j].IsCorridorType()){
                     door_left = true;
                     if(leftmost_door == 999){
                         leftmost_door = j;
                     }
                 }
                 if(d[H/2-2,W-1-j].IsCorridorType()){
                     door_right = true;
                     if(rightmost_door == 0){
                         rightmost_door = W-1-j;
                     }
                 }
             }
             if(!door_left || !door_right){
                 d.Clear();
                 continue;
             }
             for(int j=1;j<leftmost_door-6;++j){
                 d[H/2-1,j] = CellType.Wall;
                 d[H/2,j] = CellType.Wall;
             }
             for(int j=W-2;j>rightmost_door+6;--j){
                 d[H/2-1,j] = CellType.Wall;
                 d[H/2,j] = CellType.Wall;
             }
             for(int j=1;j<W-1;++j){
                 if(d[H/2-1,j].IsFloor()){
                     d[H/2-1,j] = CellType.Statue;
                     d[H/2,j] = CellType.Statue;
                     break;
                 }
                 else{
                     if(d[H/2-1,j] == CellType.Statue){
                         break;
                     }
                 }
             }
             for(int j=W-2;j>0;--j){
                 if(d[H/2-1,j].IsFloor()){
                     d[H/2-1,j] = CellType.Statue;
                     d[H/2,j] = CellType.Statue;
                     break;
                 }
                 else{
                     if(d[H/2-1,j] == CellType.Statue){
                         break;
                     }
                 }
             }
             for(int i=H/2-1;i<H/2+1;++i){
                 for(int j=1;j<W-1;++j){
                     if(d[i,j] == CellType.RoomCorner || d[i,j] == CellType.RoomEdge){
                         d[i,j] = CellType.CorridorIntersection;
                     }
                 }
             }
             d.MarkInterestingLocations();
             if(d.NumberOfFloors() < 420 || d.HasLargeUnusedSpaces(300)){
                 d.Clear();
             }
             else{
                 for(int i=0;i<ROWS;++i){
                     for(int j=0;j<COLS;++j){
                         result[i,j] = d.map[i,j];
                     }
                 }
                 return result;
             }
         }
     case LevelType.Slime:
         while(true){
             /*for(int i=1;i<ROWS-1;++i){
                 for(int j=1;j<COLS-1;++j){
                     if(d[i,j].IsWall()){
                         if(!d[i+1,j+1].IsWall()){
                             d[i,j] = d[i+1,j+1];
                         }
                         else{
                             if(!d[i+1,j].IsWall()){
                                 d[i,j] = d[i+1,j];
                             }
                             else{
                                 if(!d[i,j+1].IsWall()){
                                     d[i,j] = d[i,j+1];
                                 }
                             }
                         }
                     }
                 }
             }*/
             d.CreateBasicMap();
             d.ConnectDiagonals();
             d.RemoveUnconnectedAreas();
             d.AddDoors(25);
             d.CaveWidenRooms(30,30);
             d.RemoveDeadEndCorridors();
             d.AddPillars(30);
             d.MarkInterestingLocations();
             if(d.NumberOfFloors() < 120 || d.HasLargeUnusedSpaces(300)){
                 d.Clear();
             }
             else{
                 for(int i=0;i<ROWS;++i){
                     for(int j=0;j<COLS;++j){
                         result[i,j] = d.map[i,j];
                     }
                 }
                 return result;
             }
         }
     case LevelType.Garden:
     {
         d.RoomHeightMin = 4;
         d.RoomHeightMax = 10;
         d.RoomWidthMin = 4;
         d.RoomWidthMax = 10;
         while(true){
             d.CreateBasicMap();
             d.ConnectDiagonals();
             d.RemoveUnconnectedAreas();
             d.RemoveDeadEndCorridors();
             var dijkstra = d.map.GetDijkstraMap(x=>d[x].IsPassable(),x=>false);
             List<pos> possible_room_centers = d.map.PositionsWhere(x=>dijkstra[x] == 3 && x.row > 1 && x.row < ROWS-2 && x.col > 1 && x.col < COLS-2);
             int rooms = 0;
             while(rooms < 6 && possible_room_centers.Count > 0){
                 pos p = possible_room_centers.RemoveRandom();
                 List<int> valid_dirs = new List<int>();
                 foreach(int dir in U.FourDirections){
                     pos p2 = p.PosInDir(dir).PosInDir(dir).PosInDir(dir);
                     if(p2.BoundsCheck(d.map) && d[p2].IsPassable() && d[p2] != CellType.RoomCorner){
                         valid_dirs.Add(dir);
                     }
                 }
                 if(valid_dirs.Count > 0){
                     foreach(pos neighbor in p.PositionsWithinDistance(1,d.map)){
                         d[neighbor] = CellType.RoomInterior;
                     }
                     possible_room_centers.RemoveWhere(x=>p.DistanceFrom(x) <= 3);
                     foreach(int dir in valid_dirs){
                         d[p.PosInDir(dir).PosInDir(dir)] = CellType.CorridorIntersection;
                     }
                     ++rooms;
                 }
             }
             CellType water_type = CellType.ShallowWater;
             if(R.OneIn(8)){
                 water_type = CellType.Ice;
             }
             d.ForEachRectangularRoom((start_r,start_c,end_r,end_c)=>{
                 int room_height = (end_r - start_r) + 1;
                 int room_width = (end_c - start_c) + 1;
                 if(room_height <= 4 && room_width <= 4){
                     if(room_height == 3 && room_width == 3){
                         return true;
                     }
                     List<pos> water = new List<pos>();
                     if(!new pos(start_r+1,start_c).PositionsAtDistance(1,d.map).Any(x=>d[x].IsCorridorType())){
                         water.Add(new pos(start_r+1,start_c));
                         water.Add(new pos(start_r+2,start_c));
                     }
                     if(!new pos(start_r,start_c+1).PositionsAtDistance(1,d.map).Any(x=>d[x].IsCorridorType())){
                         water.Add(new pos(start_r,start_c+1));
                         water.Add(new pos(start_r,start_c+2));
                     }
                     if(!new pos(end_r-1,end_c).PositionsAtDistance(1,d.map).Any(x=>d[x].IsCorridorType())){
                         water.Add(new pos(end_r-1,end_c));
                         water.Add(new pos(end_r-2,end_c));
                     }
                     if(!new pos(end_r,end_c-1).PositionsAtDistance(1,d.map).Any(x=>d[x].IsCorridorType())){
                         water.Add(new pos(end_r,end_c-1));
                         water.Add(new pos(end_r,end_c-2));
                     }
                     foreach(pos p in water){
                         d[p] = water_type;
                     }
                     d[start_r,start_c] = CellType.Statue;
                     d[start_r,end_c] = CellType.Statue;
                     d[end_r,start_c] = CellType.Statue;
                     d[end_r,end_c] = CellType.Statue;
                 }
                 else{
                     CellType center_type = CellType.RoomFeature1;
                     switch(R.Roll(3)){
                     case 1:
                         center_type = water_type;
                         break;
                     case 2:
                         center_type = CellType.Poppies;
                         break;
                     case 3:
                         center_type = CellType.Brush;
                         break;
                     }
                     bool statues = R.CoinFlip();
                     CellType statue_type = CellType.Statue;
                     if(room_height <= 8 && room_width <= 8 && R.OneIn(8)){
                         statue_type = CellType.Torch;
                     }
                     CellType edge_type = CellType.ShallowWater;
                     if(center_type != water_type && !R.OneIn(4)){
                         edge_type = CellType.ShallowWater;
                     }
                     else{
                         int vine_chance = 50;
                         if(!statues){
                             vine_chance = 80;
                         }
                         if(R.PercentChance(vine_chance)){
                             edge_type = CellType.Vine;
                         }
                         else{
                             edge_type = CellType.Gravel;
                         }
                         if(R.OneIn(32)){
                             if(R.CoinFlip()){
                                 edge_type = CellType.Statue;
                             }
                             else{
                                 edge_type = CellType.GlowingFungus;
                             }
                         }
                     }
                     bool gravel = R.OneIn(16);
                     bool edges = R.CoinFlip();
                     if(room_height < 6 || room_width < 6){
                         edges = false;
                     }
                     if(room_height >= 8 && room_width >= 8){
                         edges = !R.OneIn(4);
                     }
                     if(edges){
                         for(int i=start_r;i<=end_r;++i){
                             for(int j=start_c;j<=end_c;++j){
                                 if(i == start_r || i == end_r || j == start_c || j == end_c){ //edges
                                     if(statues && (i == start_r || i == end_r) && (j == start_c || j == end_c)){ //corners
                                         d[i,j] = statue_type;
                                     }
                                     else{
                                         pos p = new pos(i,j);
                                         if(!p.CardinalAdjacentPositions().Any(x=>d[x].IsCorridorType())){
                                             d[i,j] = edge_type;
                                         }
                                     }
                                 }
                                 else{
                                     if(i == start_r+1 || i == end_r-1 || j == start_c+1 || j == end_c-1){ //the path
                                         if(gravel){
                                             d[i,j] = CellType.Gravel;
                                         }
                                     }
                                     else{
                                         d[i,j] = center_type;
                                     }
                                 }
                             }
                         }
                     }
                     else{
                         for(int i=start_r;i<=end_r;++i){
                             for(int j=start_c;j<=end_c;++j){
                                 if(i == start_r || i == end_r || j == start_c || j == end_c){
                                     if(gravel){
                                         d[i,j] = CellType.Gravel;
                                     }
                                 }
                                 else{
                                     d[i,j] = center_type;
                                 }
                             }
                         }
                     }
                     if(center_type == water_type && room_height % 2 == 1 && room_width % 2 == 1){
                         statue_type = CellType.Statue;
                         if(room_height <= 7 && room_width <= 7 && R.OneIn(12)){
                             statue_type = CellType.Torch;
                         }
                         d[(start_r+end_r)/2,(start_c+end_c)/2] = statue_type;
                     }
                 }
                 return true;
             });
             d.ConnectDiagonals();
             d.RemoveUnconnectedAreas();
             d.AddDoors(10);
             d.RemoveDeadEndCorridors();
             d.MarkInterestingLocations();
             if(d.NumberOfFloors() < 320 || d.HasLargeUnusedSpaces(300)){
                 d.Clear();
             }
             else{
                 for(int i=0;i<ROWS;++i){
                     for(int j=0;j<COLS;++j){
                         result[i,j] = d.map[i,j];
                     }
                 }
                 return result;
             }
         }
     }
     case LevelType.Crypt:
     {
         while(true){
             pos room1origin = new pos(ROWS/2,R.Roll(COLS/8 - 1) + COLS/8 - 1);
             pos room2origin = new pos(ROWS/2,R.Roll(COLS/8 - 1) + (COLS*6 / 8) - 1);
             while(!d.CreateRoom(room1origin.row,room1origin.col)){} //left half
             while(!d.CreateRoom(room2origin.row,room2origin.col)){} //right half
             d.CaveWidenRooms(100,150);
             d.MoveRoom(room1origin,4);
             d.MoveRoom(room2origin,6);
             var dijkstra = d.map.GetDijkstraMap(x=>d.map[x] == CellType.Wall,x=>false); //todo: among these Map dijkstra maps I have, like, 3 different ways of testing for walls. are these all correct?
             int distance_from_walls = 3;
             List<pos> central_room = d.map.PositionsWhere(x=>dijkstra[x] > distance_from_walls);
             int required_consecutive = 3;
             for(int i=0;i<ROWS;++i){ //first, check each row...
                 for(int j=0;j<COLS;++j){
                     List<pos> this_row = new List<pos>();
                     while(j < COLS && dijkstra[i,j] > distance_from_walls){
                         this_row.Add(new pos(i,j));
                         ++j;
                     }
                     if(this_row.Count < required_consecutive){
                         foreach(pos p in this_row){
                             central_room.Remove(p);
                         }
                     }
                 }
             }
             for(int j=0;j<COLS;++j){ //...then each column
                 for(int i=0;i<ROWS;++i){
                     List<pos> this_col = new List<pos>();
                     while(i < ROWS && dijkstra[i,j] > distance_from_walls){
                         this_col.Add(new pos(i,j));
                         ++i;
                     }
                     if(this_col.Count < required_consecutive){
                         foreach(pos p in this_col){
                             central_room.Remove(p);
                         }
                     }
                 }
             }
             central_room = d.map.GetFloodFillPositions(central_room.Where(x=>x.PositionsWithinDistance(1).All(y=>central_room.Contains(y))),false,x=>central_room.Contains(x));
             List<pos> walls = new List<pos>();
             foreach(pos p in central_room){
                 d.map[p] = CellType.InterestingLocation;
                 foreach(pos neighbor in p.PositionsAtDistance(1,d.map)){
                     if(!central_room.Contains(neighbor)){
                         d.map[neighbor] = CellType.Wall;
                         walls.Add(neighbor);
                     }
                 }
             }
             while(true){
                 List<pos> potential_doors = new List<pos>();
                 foreach(pos p in walls){
                     foreach(int dir in U.FourDirections){
                         if(d.map[p.PosInDir(dir)] == CellType.InterestingLocation && d.map[p.PosInDir(dir.RotateDir(true,4))].IsRoomType() && d.map[p.PosInDir(dir.RotateDir(true,4))] != CellType.InterestingLocation){
                             potential_doors.Add(p);
                             break;
                         }
                     }
                 }
                 if(potential_doors.Count > 0){
                     pos p = potential_doors.Random();
                     d.map[p] = CellType.Door;
                     List<pos> room = d.map.GetFloodFillPositions(p,true,x=>d.map[x] == CellType.InterestingLocation);
                     foreach(pos p2 in room){
                         d.map[p2] = CellType.RoomInterior;
                     }
                 }
                 else{
                     break;
                 }
             }
             dijkstra = d.map.GetDijkstraMap(x=>d.map[x] == CellType.Wall,x=>false);
             int num_chests = 0;
             d.ForEachRoom(list=>{
                 if(central_room.Contains(list[0])){
                     if(num_chests++ < 2){
                         d[list.Random()] = CellType.Chest;
                     }
                     return true;
                 }
                 List<pos> room = list.Where(x=>dijkstra[x] > 1);
                 int start_r = room.WhereLeast(x=>x.row)[0].row;
                 int end_r = room.WhereGreatest(x=>x.row)[0].row;
                 int start_c = room.WhereLeast(x=>x.col)[0].col;
                 int end_c = room.WhereGreatest(x=>x.col)[0].col;
                 List<List<pos>> offsets = new List<List<pos>>();
                 for(int i=0;i<4;++i){
                     offsets.Add(new List<pos>());
                 }
                 for(int i=start_r;i<=end_r;i+=2){
                     for(int j=start_c;j<=end_c;j+=2){
                         if(room.Contains(new pos(i,j))){
                             offsets[0].Add(new pos(i,j));
                         }
                         if(i+1 <= end_r && room.Contains(new pos(i+1,j))){
                             offsets[1].Add(new pos(i+1,j));
                         }
                         if(j+1 <= end_c && room.Contains(new pos(i,j+1))){
                             offsets[2].Add(new pos(i,j+1));
                         }
                         if(i+1 <= end_r && j+1 <= end_c && room.Contains(new pos(i+1,j+1))){
                             offsets[3].Add(new pos(i+1,j+1));
                         }
                     }
                 }
                 List<pos> tombstones = offsets.WhereGreatest(x=>x.Count).RandomOrDefault();
                 if(tombstones != null){
                     foreach(pos p in tombstones){
                         d.map[p] = CellType.Tombstone;
                     }
                 }
                 return true;
             });
             for(int i=0;i<ROWS;++i){
                 for(int j=0;j<COLS;++j){
                     if(d[i,j] == CellType.Door){
                         pos p = new pos(i,j);
                         List<pos> potential_statues = p.PositionsAtDistance(1,d.map).Where(x=>!d[x].IsWall() && !central_room.Contains(x) && p.DirectionOf(x) % 2 != 0 && !x.PositionsAtDistance(1,d.map).Any(y=>d[y].Is(CellType.Tombstone)));
                         if(potential_statues.Count == 2){
                             d[potential_statues[0]] = CellType.Statue;
                             d[potential_statues[1]] = CellType.Statue;
                         }
                     }
                 }
             }
             List<pos> room_one = null;
             List<pos> room_two = null;
             for(int j=0;j<COLS && room_one == null;++j){
                 for(int i=0;i<ROWS;++i){
                     if(d[i,j] != CellType.Wall){
                         room_one = d.map.GetFloodFillPositions(new pos(i,j),false,x=>!d[x].IsWall());
                         break;
                     }
                 }
             }
             for(int j=COLS-1;j>=0 && room_two == null;--j){
                 for(int i=0;i<ROWS;++i){
                     if(d[i,j] != CellType.Wall){
                         room_two = d.map.GetFloodFillPositions(new pos(i,j),false,x=>!d[x].IsWall());
                         break;
                     }
                 }
             }
             if(room_one.WhereGreatest(x=>x.col).Random().DistanceFrom(room_two.WhereLeast(x=>x.col).Random()) < 12){
                 d.Clear();
                 continue;
             }
             Dungeon d2 = new Dungeon(ROWS,COLS);
             int tries = 0;
             while(tries < 10){
                 d2.CreateBasicMap();
                 d2.ConnectDiagonals();
                 for(int i=0;i<ROWS;++i){
                     for(int j=0;j<COLS;++j){
                         if(d[i,j] != CellType.Wall){
                             pos p = new pos(i,j);
                             foreach(pos neighbor in p.PositionsAtDistance(1,d2.map)){
                                 d2[neighbor] = CellType.Wall;
                             }
                         }
                     }
                 }
                 d2.RemoveUnconnectedAreas();
                 List<pos> room_one_walls = new List<pos>();
                 List<pos> room_two_walls = new List<pos>();
                 for(int i=0;i<ROWS;++i){
                     for(int j=COLS-1;j>=0;--j){
                         pos p = new pos(i,j);
                         if(room_one.Contains(p)){
                             room_one_walls.Add(p);
                             break;
                         }
                     }
                     for(int j=0;j<COLS;++j){
                         pos p = new pos(i,j);
                         if(room_two.Contains(p)){
                             room_two_walls.Add(p);
                             break;
                         }
                     }
                 }
                 List<pos> room_one_valid_connections = new List<pos>();
                 List<pos> room_two_valid_connections = new List<pos>();
                 foreach(pos p in room_one_walls){
                     pos next = p.PosInDir(6);
                     while(BoundsCheck(next) && p.DistanceFrom(next) < 7){
                         if(d2[next] != CellType.Wall){
                             room_one_valid_connections.Add(p.PosInDir(6));
                             break;
                         }
                         next = next.PosInDir(6);
                     }
                 }
                 foreach(pos p in room_two_walls){
                     pos next = p.PosInDir(4);
                     while(BoundsCheck(next) && p.DistanceFrom(next) < 7){
                         if(d2[next] != CellType.Wall){
                             room_two_valid_connections.Add(p.PosInDir(4));
                             break;
                         }
                         next = next.PosInDir(4);
                     }
                 }
                 if(room_one_valid_connections.Count > 0 && room_two_valid_connections.Count > 0){
                     pos one = room_one_valid_connections.Random();
                     while(true){
                         if(d2[one] == CellType.Wall){
                             d2[one] = CellType.CorridorHorizontal;
                         }
                         else{
                             break;
                         }
                         one = one.PosInDir(6);
                     }
                     pos two = room_two_valid_connections.Random();
                     while(true){
                         if(d2[two] == CellType.Wall){
                             d2[two] = CellType.CorridorHorizontal;
                         }
                         else{
                             break;
                         }
                         two = two.PosInDir(4);
                     }
                     break;
                 }
                 else{
                     d2.Clear();
                 }
                 ++tries;
             }
             if(tries == 10){
                 d.Clear();
                 continue;
             }
             for(int i=0;i<ROWS;++i){
                 for(int j=0;j<COLS;++j){
                     if(d2[i,j] != CellType.Wall){
                         d[i,j] = d2[i,j];
                     }
                 }
             }
             //d.CaveWidenRooms(100,20);
             //d.MakeCavesMoreRectangular(4);
             //d.RemoveDeadEndCorridors();
             //d.MakeCavesMoreRectangular(1 + num++ / 10);
             //d.Clear();
             //continue;
             d.ConnectDiagonals();
             d.RemoveUnconnectedAreas();
             d.RemoveDeadEndCorridors();
             d.MarkInterestingLocations();
             d.RemoveUnconnectedAreas();
             if(d.NumberOfFloors() < 340 || d.HasLargeUnusedSpaces(350)){
                 d.Clear();
             }
             else{
                 for(int i=0;i<ROWS;++i){
                     for(int j=0;j<COLS;++j){
                         result[i,j] = d.map[i,j];
                     }
                 }
                 return result;
             }
         }
         }
     }
     return null;
 }
Exemplo n.º 14
0
 public static void UpdateDijkstraMap(PosArray<int> map,IntLocationDelegate get_cost)
 {
     PriorityQueue<cell> frontier = new PriorityQueue<cell>(c => -c.value);
     int height = map.objs.GetLength(0);
     int width = map.objs.GetLength(1);
     for(int i=0;i<height;++i){
         for(int j=0;j<width;++j){
             if(map[i,j] != U.DijkstraMin){
                 int v = map[i,j];
                 bool good = true;
                 for(int s=-1;s<=1 && good;++s){
                     for(int t=-1;t<=1 && good;++t){
                         if(i+s >= 0 && i+s < height && j+t >= 0 && j+t < width){
                             if(map[i+s,j+t] < v && map[i+s,j+t] != U.DijkstraMin){
                                 good = false;
                             }
                         }
                     }
                 }
                 if(good){ //find local minima and add them to the frontier
                     frontier.Add(new cell(i,j,v));
                 }
             }
         }
     }
     while(frontier.list.Count > 0){
         cell c = frontier.Pop();
         for(int s=-1;s<=1;++s){
             for(int t=-1;t<=1;++t){
                 if(c.row+s >= 0 && c.row+s < height && c.col+t >= 0 && c.col+t < width){
                     int cost = get_cost(c.row+s,c.col+t);
                     if(map[c.row+s,c.col+t] > c.value+cost){
                         map[c.row+s,c.col+t] = c.value+cost;
                         frontier.Add(new cell(c.row+s,c.col+t,c.value+cost));
                     }
                 }
             }
         }
     }
 }
Exemplo n.º 15
0
 //todo: remove dijkstra from this file, use the one in Utility?  (do I have UpdateDijkstra in Utility?)
 public static PosArray<int> GetDijkstraMap(int height,int width,BooleanLocationDelegate is_blocked,IntLocationDelegate get_cost,List<cell> sources)
 {
     PriorityQueue<cell> frontier = new PriorityQueue<cell>(c => -c.value);
     PosArray<int> map = new PosArray<int>(height,width);
     for(int i=0;i<height;++i){
         for(int j=0;j<width;++j){
             if(is_blocked(i,j)){
                 map[i,j] = U.DijkstraMin;
             }
             else{
                 map[i,j] = U.DijkstraMax;
             }
         }
     }
     foreach(cell c in sources){
         map[c.row,c.col] = c.value;
         frontier.Add(c);
     }
     while(frontier.list.Count > 0){
         cell c = frontier.Pop();
         for(int s=-1;s<=1;++s){
             for(int t=-1;t<=1;++t){
                 if(c.row+s >= 0 && c.row+s < height && c.col+t >= 0 && c.col+t < width){
                     int cost = get_cost(c.row+s,c.col+t);
                     if(map[c.row+s,c.col+t] > c.value+cost){
                         map[c.row+s,c.col+t] = c.value+cost;
                         frontier.Add(new cell(c.row+s,c.col+t,c.value+cost));
                     }
                 }
             }
         }
     }
     for(int i=0;i<height;++i){
         for(int j=0;j<width;++j){
             if(map[i,j] == U.DijkstraMax){
                 map[i,j] = U.DijkstraMin; //any unreachable areas are marked unpassable
             }
         }
     }
     return map;
 }
Exemplo n.º 16
0
 public void MarkInterestingLocationsNonRectangular()
 {
     var dijkstra = map.GetDijkstraMap(x=>!map[x].IsPassable(),x=>false);
     PosArray<int> values = new PosArray<int>(H,W);
     for(int i=0;i<H;++i){
         for(int j=0;j<W;++j){
             if(dijkstra[i,j].IsValidDijkstraValue() && dijkstra[i,j] > 1){
                 values[i,j] = dijkstra[i,j] * 4;
                 foreach(pos p in new pos(i,j).PositionsAtDistance(1,dijkstra)){
                     if(dijkstra[p].IsValidDijkstraValue()){
                         values[i,j] += dijkstra[p] * 2;
                     }
                 }
                 foreach(pos p in new pos(i,j).PositionsAtDistance(2,dijkstra)){
                     if(dijkstra[p].IsValidDijkstraValue()){
                         values[i,j] += dijkstra[p];
                     }
                 }
             }
         }
     }
     for(int i=0;i<H;++i){
         for(int j=0;j<W;++j){
             if(values[i,j] > 0){
                 if(!new pos(i,j).PositionsAtDistance(1,values).Any(x=>values[x] > values[i,j])){
                     map[i,j] = CellType.InterestingLocation;
                 }
             }
         }
     }
 }
Exemplo n.º 17
0
 public void AlterRooms(int no_change_freq,int add_pillars_freq,int cross_room_freq,int cave_widen_freq,int cave_fill_freq)
 {
     List<int> modification = new List<int>();
     for(int i=0;i<no_change_freq;++i){
         modification.Add(0);
     }
     for(int i=0;i<add_pillars_freq;++i){
         modification.Add(1);
     }
     for(int i=0;i<cross_room_freq;++i){
         modification.Add(2);
     }
     for(int i=0;i<cave_widen_freq;++i){
         modification.Add(3);
     }
     for(int i=0;i<cave_fill_freq;++i){
         modification.Add(4);
     }
     if(modification.Count == 0){
         return;
     }
     ForEachRectangularRoom((start_r,start_c,end_r,end_c) => {
         int mod = modification.Random();
         switch(mod){
         case 0:
             return true;
         case 1:
         {
             int height = end_r - start_r + 1;
             int width = end_c - start_c + 1;
             if(height > 3 || width > 3){
                 List<PillarArrangement> layouts = new List<PillarArrangement>();
                 if(height % 2 == 1 && width % 2 == 1){
                     layouts.Add(PillarArrangement.Single);
                 }
                 if((height % 2 == 1 || width % 2 == 1) && height != 4 && width != 4){
                     layouts.Add(PillarArrangement.Row);
                 }
                 if(height >= 5 && width >= 5){
                     layouts.Add(PillarArrangement.Corners);
                 }
                 if(height > 2 && width > 2 && height != 4 && width != 4){
                     layouts.Add(PillarArrangement.Full);
                 }
                 if((width % 2 == 1 && width >= 5) || (height % 2 == 1 && height >= 5)){
                     layouts.Add(PillarArrangement.StatueEdges);
                 }
                 if(layouts.Count == 0 || CoinFlip()){ //otherwise they're too common
                     layouts.Add(PillarArrangement.StatueCorners);
                 }
                 if(layouts.Count > 0){
                     CellType pillar = CellType.Pillar;
                     switch(layouts.Random()){
                     case PillarArrangement.Single:
                         map[(start_r + end_r)/2,(start_c + end_c)/2] = pillar;
                         break;
                     case PillarArrangement.Row:
                     {
                         bool vertical;
                         if(width % 2 == 1 && height % 2 == 0){
                             vertical = true;
                         }
                         else{
                             if(height % 2 == 1 && width % 2 == 0){
                                 vertical = false;
                             }
                             else{
                                 vertical = CoinFlip();
                             }
                         }
                         if(vertical){
                             if(height % 2 == 1){
                                 for(int i=start_r+1;i<=end_r-1;i+=2){
                                     map[i,(start_c + end_c)/2] = pillar;
                                 }
                             }
                             else{
                                 int offset = 0;
                                 if(height % 4 == 0){
                                     offset = Roll(2) - 1;
                                 }
                                 for(int i=start_r+1+offset;i<(start_r + end_r)/2;i+=2){
                                     map[i,(start_c + end_c)/2] = pillar;
                                 }
                                 for(int i=end_r-1-offset;i>(start_r + end_r)/2+1;i-=2){
                                     map[i,(start_c + end_c)/2] = pillar;
                                 }
                             }
                         }
                         else{
                             if(width % 2 == 1){
                                 for(int i=start_c+1;i<=end_c-1;i+=2){
                                     map[(start_r + end_r)/2,i] = pillar;
                                 }
                             }
                             else{
                                 int offset = 0;
                                 if(width % 4 == 0){
                                     offset = Roll(2) - 1;
                                 }
                                 for(int i=start_c+1+offset;i<(start_c + end_c)/2;i+=2){
                                     map[(start_r + end_r)/2,i] = pillar;
                                 }
                                 for(int i=end_c-1-offset;i>(start_c + end_c)/2+1;i-=2){
                                     map[(start_r + end_r)/2,i] = pillar;
                                 }
                             }
                         }
                         break;
                     }
                     case PillarArrangement.Corners:
                     {
                         int v_offset = 0;
                         int h_offset = 0;
                         if(height % 4 == 0){
                             v_offset = Roll(2) - 1;
                         }
                         if(width % 4 == 0){
                             h_offset = Roll(2) - 1;
                         }
                         map[start_r + 1 + v_offset,start_c + 1 + h_offset] = pillar;
                         map[start_r + 1 + v_offset,end_c - 1 - h_offset] = pillar;
                         map[end_r - 1 - v_offset,start_c + 1 + h_offset] = pillar;
                         map[end_r - 1 - v_offset,end_c - 1 - h_offset] = pillar;
                         break;
                     }
                     case PillarArrangement.Full:
                     {
                         int v_offset = 0;
                         int h_offset = 0;
                         if(height % 4 == 0){
                             v_offset = Roll(2) - 1;
                         }
                         if(width % 4 == 0){
                             h_offset = Roll(2) - 1;
                         }
                         int half_r = (start_r + end_r)/2;
                         int half_c = (start_c + end_c)/2;
                         int half_r_offset = (start_r + end_r + 1)/2;
                         int half_c_offset = (start_c + end_c + 1)/2;
                         for(int i=start_r+1+v_offset;i<half_r;i+=2){
                             for(int j=start_c+1+h_offset;j<half_c;j+=2){
                                 map[i,j] = pillar;
                             }
                         }
                         for(int i=start_r+1+v_offset;i<half_r;i+=2){
                             for(int j=end_c-1-h_offset;j>half_c_offset;j-=2){
                                 map[i,j] = pillar;
                             }
                         }
                         for(int i=end_r-1-v_offset;i>half_r_offset;i-=2){
                             for(int j=start_c+1+h_offset;j<half_c;j+=2){
                                 map[i,j] = pillar;
                             }
                         }
                         for(int i=end_r-1-v_offset;i>half_r_offset;i-=2){
                             for(int j=end_c-1-h_offset;j>half_c_offset;j-=2){
                                 map[i,j] = pillar;
                             }
                         }
                         if((width+1) % 4 == 0){
                             if(height % 2 == 1){
                                 for(int i=start_r+1;i<=end_r-1;i+=2){
                                     map[i,half_c] = pillar;
                                 }
                             }
                             else{
                                 int offset = 0;
                                 if(height % 4 == 0){
                                     offset = Roll(2) - 1;
                                 }
                                 for(int i=start_r+1+offset;i<half_r;i+=2){
                                     map[i,half_c] = pillar;
                                 }
                                 for(int i=end_r-1-offset;i>half_r_offset;i-=2){
                                     map[i,half_c] = pillar;
                                 }
                             }
                         }
                         if((height+1) % 4 == 0){
                             if(width % 2 == 1){
                                 for(int i=start_c+1;i<=end_c-1;i+=2){
                                     map[half_r,i] = pillar;
                                 }
                             }
                             else{
                                 int offset = 0;
                                 if(width % 4 == 0){
                                     offset = Roll(2) - 1;
                                 }
                                 for(int i=start_c+1+offset;i<half_c;i+=2){
                                     map[half_r,i] = pillar;
                                 }
                                 for(int i=end_c-1-offset;i>half_c_offset;i-=2){
                                     map[half_r,i] = pillar;
                                 }
                             }
                         }
                         break;
                     }
                     case PillarArrangement.StatueCorners:
                         map[start_r,start_c] = CellType.Statue;
                         map[start_r,end_c] = CellType.Statue;
                         map[end_r,start_c] = CellType.Statue;
                         map[end_r,end_c] = CellType.Statue;
                         break;
                     case PillarArrangement.StatueEdges:
                     {
                         map[start_r,start_c] = CellType.Statue;
                         map[start_r,end_c] = CellType.Statue;
                         map[end_r,start_c] = CellType.Statue;
                         map[end_r,end_c] = CellType.Statue;
                         if(width % 2 == 1 && width > 3){
                             int half_c = (start_c + end_c)/2;
                             int corridors = new pos(start_r,half_c).CardinalAdjacentPositions().Where(x => BoundsCheck(x) && map[x].IsCorridorType()).Count;
                             if(corridors == 0){
                                 map[start_r,half_c] = CellType.Statue;
                             }
                             corridors = new pos(end_r,half_c).CardinalAdjacentPositions().Where(x => BoundsCheck(x) && map[x].IsCorridorType()).Count;
                             if(corridors == 0){
                                 map[end_r,half_c] = CellType.Statue;
                             }
                         }
                         if(height % 2 == 1 && height > 3){
                             int half_r = (start_r + end_r)/2;
                             int corridors = new pos(half_r,start_c).CardinalAdjacentPositions().Where(x => BoundsCheck(x) && map[x].IsCorridorType()).Count;
                             if(corridors == 0){
                                 map[half_r,start_c] = CellType.Statue;
                             }
                             corridors = new pos(half_r,end_c).CardinalAdjacentPositions().Where(x => BoundsCheck(x) && map[x].IsCorridorType()).Count;
                             if(corridors == 0){
                                 map[half_r,end_c] = CellType.Statue;
                             }
                         }
                         break;
                     }
                     default:
                         break;
                     }
                 }
             }
             return true;
         }
         case 2:
         {
             int height = end_r - start_r + 1;
             int width = end_c - start_c + 1;
             if(height < 4 || width < 4){ //nothing happens until we get above 4x4
                 return true;
             }
             int rows_to_convert = Roll((height/2)-1);
             int cols_to_convert = Roll((width/2)-1);
             if(rows_to_convert == 1 && cols_to_convert == 1){
                 return true;
             }
             List<pos> blocked = new List<pos>();
             for(int i=start_r;i<=end_r;++i){
                 for(int j=start_c;j<=end_c;++j){
                     if((i < start_r + rows_to_convert || i > end_r - rows_to_convert) && (j < start_c + cols_to_convert || j > end_c - cols_to_convert)){
                         pos p = new pos(i,j);
                         foreach(pos neighbor in p.CardinalAdjacentPositions()){
                             if(map[neighbor].IsCorridorType()){
                                 blocked.Add(p);
                             }
                         }
                         map[i,j] = CellType.Wall;
                     }
                 }
             }
             blocked.Randomize();
             foreach(pos p in blocked){
                 bool done = false;
                 foreach(pos neighbor in p.CardinalAdjacentPositions()){
                     if(map[neighbor].IsRoomType()){
                         map[p] = CellType.RoomInterior;
                         done = true;
                         break;
                     }
                 }
                 if(!done){
                     List<int> valid_dirs = new List<int>();
                     foreach(int dir in U.FourDirections){
                         pos next = p.PosInDir(dir);
                         while(next.row >= start_r && next.row <= end_r && next.col >= start_c && next.col <= end_c){
                             if(next.CardinalAdjacentPositions().Any(x=>map[x].IsRoomType())){
                                 valid_dirs.Add(dir);
                                 break;
                             }
                             next = next.PosInDir(dir);
                         }
                     }
                     int valid_dir = valid_dirs.RandomOrDefault();
                     pos next2 = p.PosInDir(valid_dir);
                     List<pos> new_corridor = new List<pos>{p};
                     while(true){
                         new_corridor.Add(next2);
                         if(next2.CardinalAdjacentPositions().Any(x=>map[x].IsRoomType())){
                             break;
                         }
                         next2 = next2.PosInDir(valid_dir);
                     }
                     foreach(pos p2 in new_corridor){
                         map[p2] = CellType.RoomInterior;
                     }
                 }
             }
             return true;
         }
         case 3:
         {
             List<pos> list = map.PositionsWhere(x=>x.row >= start_r && x.row <= end_r && x.col >= start_c && x.col <= end_c);
             PosArray<CellType> old_map = new PosArray<CellType>(H,W);
             foreach(pos p in list){
                 old_map[p] = map[p];
                 map[p] = CellType.Wall;
             }
             PosArray<bool> rock = new PosArray<bool>(H,W);
             for(int i=0;i<H;++i){
                 for(int j=0;j<W;++j){
                     pos p = new pos(i,j);
                     rock[p] = true;
                     if(BoundsCheck(i,j,false)){
                         foreach(pos neighbor in p.AdjacentPositionsClockwise()){
                             if(map[neighbor] != CellType.Wall){
                                 rock[p] = false;
                                 break;
                             }
                         }
                     }
                 }
             }
             foreach(pos p in list){
                 map[p] = CellType.RoomInterior; //todo: might this step be extraneous?
             }
             List<pos> frontier = new List<pos>();
             {
                 PosArray<bool> in_list = new PosArray<bool>(H,W);
                 foreach(pos p in list){
                     in_list[p] = true;
                 }
                 for(int i=0;i<H;++i){
                     for(int j=0;j<W;++j){
                         pos p = new pos(i,j);
                         if(in_list[p]){
                             foreach(pos neighbor in p.PositionsAtDistance(1,in_list)){
                                 if(!in_list[neighbor]){
                                     frontier.Add(p);
                                     break;
                                 }
                             }
                         }
                     }
                 }
             }
             int fail_counter = 0;
             int num_added = 0;
             bool finished = false;
             while(!finished){
                 if(frontier.Count == 0 || num_added >= 30){ //todo check this value
                     finished = true;
                     break;
                 }
                 pos f = frontier.RemoveRandom();
                 foreach(pos neighbor in f.CardinalAdjacentPositions()){
                     if(!BoundsCheck(neighbor,false) || !rock[neighbor.row,neighbor.col]){
                         ++fail_counter; //this might now be unreachable
                         if(!BoundsCheck(neighbor,false)){
                             fail_counter += 25; //fail quicker when against the edge of the map to prevent ugliness
                         } //however, this doesn't actually fail as quickly as it should - i've overlooked something.
                         if(fail_counter >= 50){
                             finished = true;
                             break;
                         }
                     }
                     else{
                         if(map[neighbor] != CellType.RoomInterior){
                             map[neighbor] = CellType.RoomInterior;
                             ++num_added;
                             bool add_neighbor = true;
                             foreach(pos n2 in neighbor.CardinalAdjacentPositions()){
                                 if(!BoundsCheck(n2,false) || !rock[n2.row,n2.col]){
                                     add_neighbor = false;
                                     ++fail_counter; //this might now be unreachable
                                     if(!BoundsCheck(neighbor,false)){
                                         fail_counter += 25; //fail quicker when against the edge of the map to prevent ugliness
                                     } //however, this doesn't actually fail as quickly as it should - i've overlooked something.
                                     if(fail_counter >= 50){
                                         finished = true;
                                     }
                                     break;
                                 }
                             }
                             if(finished){
                                 break;
                             }
                             if(add_neighbor){
                                 frontier.Add(neighbor);
                             }
                         }
                     }
                 }
             }
             foreach(pos p in list){
                 map[p] = old_map[p];
             }
             return true;
         }
         case 4:
         {
             List<pos> list = map.PositionsWhere(x=>x.row >= start_r && x.row <= end_r && x.col >= start_c && x.col <= end_c);
             Dungeon room = new Dungeon((end_r - start_r) + 3,(end_c - start_c) + 3); //includes borders
             List<pos> map_exits = list.Where(x=>x.CardinalAdjacentPositions().Any(y=>map[y].IsCorridorType())); //grab the positions from list that have any adjacent corridor-type cells
             if(map_exits.Count < 2){
                 return true;
             }
             List<pos> room_exits = new List<pos>();
             foreach(pos exit in map_exits){
                 room_exits.Add(new pos(exit.row-start_r+1,exit.col-start_c+1));
             }
             int tries = 0;
             while(true){
                 room.FillWithRandomWalls(25);
                 room.ApplyCellularAutomataXYRule(3);
                 room.ConnectDiagonals();
                 room.RemoveDeadEndCorridors();
                 room.RemoveUnconnectedAreas();
                 bool exits_open = true;
                 foreach(pos p in room_exits){
                     if(!room[p].IsPassable()){
                         exits_open = false;
                     }
                 }
                 if(exits_open){
                     for(int i=start_r;i<=end_r;++i){
                         for(int j=start_c;j<=end_c;++j){
                             if(list.Contains(new pos(i,j))){
                                 map[i,j] = room[(i-start_r)+1,(j-start_c)+1];
                             }
                         }
                     }
                     break;
                 }
                 ++tries;
                 if(tries > 50){
                     return false;
                 }
             }
             return true;
         }
         default:
             break;
         }
         return true;
     });
 }
Exemplo n.º 18
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));
        }
Exemplo n.º 19
0
 public static void DebugCreateRandomActors()
 {
     actor = new PosArray<Actor>(ROWS,COLS);
     for(int n=0;n<50;++n){
         pos p = actor.RandomPosition(true);
         actor[p] = Actor.Create(Unit.Create(R.Between(2,4),Species.Random,Job.Random,null));
         actor[p].p = p;
         //Actor a = actor[p];
         /*Console.Error.WriteLine(a.species.name + " " + a.job.name);
         Console.Error.WriteLine("Health: {0,2}   Initiative: {1,2}   Movement: {2} ",a.health,a.initiative,a.movement);
         foreach(Skill sk in a.skills){
             Console.Error.WriteLine("{0,-20}",sk.name);
         }
         Console.Error.WriteLine();
         Console.Error.WriteLine();*/
     }
 }
Exemplo n.º 20
0
 public void GenerateFloorTypes(PosArray<CellType> map)
 {
     List<FloorType> floors = new List<FloorType>();
     int[] rarity = null;
     switch(level_types[current_level-1]){
     case LevelType.Standard:
         rarity = new int[]{2,2,7,7,8,30,20};
         break;
     case LevelType.Cave:
         rarity = new int[]{5,4,3,2,5,30,10};
         break;
     case LevelType.Mine:
         rarity = new int[]{20,10,2,4,8,100,10};
         break;
     case LevelType.Hive:
         rarity = new int[]{15,10,5,20,20,20,100};
         break;
     case LevelType.Fortress:
         rarity = new int[]{20,20,5,10,20,100,15};
         break;
     case LevelType.Crypt:
         rarity = new int[]{6,10,5,4,4,30,4};
         break;
     case LevelType.Garden:
         rarity = new int[]{0,0,0,0,0,0,0}; //special handling, see GenerateLevel()
         break;
     case LevelType.Slime: //todo
     default:
         rarity = new int[]{2,2,4,4,6,30,10};
         break;
     }
     foreach(FloorType f in Enum.GetValues(typeof(FloorType))){
         floors.Add(f);
     }
     List<FloorType> floortype_pool = new List<FloorType>();
     while(floortype_pool.Count == 0){
         //floortype_pool.Clear();
         for(int i=0;i<7;++i){
             if(rarity[i] > 0 && R.OneIn(rarity[i])){
                 floortype_pool.Add(floors[i]);
             }
         }
     }
     int num = R.Between(4,7);
     if(level_types[current_level-1] == LevelType.Mine){
         num = R.Between(1,3);
     }
     for(;num>0;--num){
         FloorType f = floortype_pool.Random();
         for(int i=0;i<50;++i){
             int rr = R.Roll(ROWS-2);
             int rc = R.Roll(COLS-2);
             if(map[rr,rc].IsRoomType() && map[rr,rc].IsFloor()){
                 CellType cell = CellType.Wall;
                 int max_radius = 0;
                 switch(f){
                 case FloorType.GlowingFungus:
                     cell = CellType.GlowingFungus;
                     max_radius = R.Between(2,6);
                     break;
                 case FloorType.Gravel:
                     cell = CellType.Gravel;
                     max_radius = R.Between(3,4);
                     break;
                 case FloorType.Brush:
                     cell = CellType.Brush;
                     max_radius = R.Between(4,7);
                     break;
                 case FloorType.Water:
                     cell = CellType.ShallowWater;
                     max_radius = R.Between(4,6);
                     break;
                 case FloorType.PoppyField:
                     cell = CellType.Poppies;
                     max_radius = R.Between(4,5);
                     break;
                 case FloorType.Ice:
                     cell = CellType.Ice;
                     max_radius = R.Between(4,6);
                     break;
                 case FloorType.GraveDirt:
                     cell = CellType.GraveDirt;
                     max_radius = R.Between(3,5);
                     break;
                 }
                 if(level_types[current_level-1] == LevelType.Fortress && max_radius > 2){
                     max_radius = 2;
                 }
                 map[rr,rc] = cell;
                 for(int j=1;j<=max_radius;++j){
                     List<pos> added = new List<pos>();
                     foreach(pos p in new pos(rr,rc).PositionsWithinDistance(j,map)){
                         if(map[p] == cell){
                             foreach(int dir in U.FourDirections){
                                 pos neighbor = p.PosInDir(dir);
                                 if(!neighbor.BoundsCheck(map)){
                                     continue;
                                 }
                                 if(R.CoinFlip()){
                                     if(map[neighbor].IsFloor()){
                                         if(cell.Is(CellType.ShallowWater,CellType.Ice,CellType.GraveDirt)){
                                             bool valid = true;
                                             foreach(pos p2 in neighbor.CardinalAdjacentPositions()){
                                                 if(map[p2].IsCorridorType()){
                                                     valid = false;
                                                     break;
                                                 }
                                             }
                                             if(valid){
                                                 added.AddUnique(neighbor);
                                             }
                                         }
                                         else{
                                             added.AddUnique(neighbor);
                                         }
                                     }
                                     else{
                                         if(cell.Is(CellType.ShallowWater,CellType.Gravel,CellType.GraveDirt) && neighbor.BoundsCheck(tile,false) && map[neighbor].IsWall()){
                                             bool valid = true;
                                             for(int ii=-1;ii<=1;++ii){
                                                 if(!map[neighbor.PosInDir(dir.RotateDir(true,ii))].IsWall()){
                                                     valid = false;
                                                     break;
                                                 }
                                             }
                                             if(valid){
                                                 added.AddUnique(neighbor);
                                             }
                                         }
                                     }
                                 }
                             }
                         }
                     }
                     foreach(pos p in added){
                         map[p] = cell;
                     }
                 }
                 if(cell == CellType.GraveDirt){
                     if(!new pos(rr,rc).PositionsAtDistance(1,map).Any(x=>map[x] == CellType.Tombstone)){
                         map[rr,rc] = CellType.Tombstone;
                     }
                 }
                 break;
             }
         }
     }
 }
Exemplo n.º 21
0
 public static void DebugDisplayDijkstra(PosArray <int> d)
 {
     DebugDisplayDijkstra(d, 1);
 }
Exemplo n.º 22
0
 protected ButtonPanel(int rows,int cols,int vert_offset_px,int horiz_offset_px)
     : base(G.Window,rows,cols,cell_h,cell_w,vert_offset_px,horiz_offset_px,filename,font_w,font_padding,aa_font)
 {
     buttons = new PosArray<Button>(rows,cols);
     internal_main_button = GetFittedButton(0,0,rows,cols,Command.None);
 }
Exemplo n.º 23
0
 public static void DebugDisplayDijkstra(PosArray<int> d)
 {
     DebugDisplayDijkstra(d,1);
 }
Exemplo n.º 24
0
 public void CaveWidenRooms(int percent_chance_per_room,int number_of_tiles_to_add)
 {
     List<List<pos>> roomlist = new List<List<pos>>();
     ForEachRoom(list=>{
         if(PercentChance(percent_chance_per_room)){
             roomlist.Add(list);
         }
         return true;
     });
     while(roomlist.Count > 0){
         List<pos> list = roomlist.RemoveRandom();
         PosArray<CellType> old_map = new PosArray<CellType>(H,W);
         foreach(pos p in list){
             old_map[p] = map[p];
             map[p] = CellType.Wall;
         }
         PosArray<bool> rock = new PosArray<bool>(H,W);
         for(int i=0;i<H;++i){
             for(int j=0;j<W;++j){
                 pos p = new pos(i,j);
                 rock[p] = true;
                 if(BoundsCheck(i,j,false)){
                     foreach(pos neighbor in p.AdjacentPositionsClockwise()){
                         if(map[neighbor] != CellType.Wall){
                             rock[p] = false;
                             break;
                         }
                     }
                 }
             }
         }
         foreach(pos p in list){
             map[p] = CellType.RoomInterior; //todo: might this step be extraneous?
         }
         List<pos> frontier = new List<pos>();
         {
             PosArray<bool> in_list = new PosArray<bool>(H,W);
             foreach(pos p in list){
                 in_list[p] = true;
             }
             for(int i=0;i<H;++i){
                 for(int j=0;j<W;++j){
                     pos p = new pos(i,j);
                     if(in_list[p]){
                         foreach(pos neighbor in p.PositionsAtDistance(1,in_list)){
                             if(!in_list[neighbor]){
                                 frontier.Add(p);
                                 break;
                             }
                         }
                     }
                 }
             }
         }
         int fail_counter = 0;
         int num_added = 0;
         bool finished = false;
         while(!finished){
             if(frontier.Count == 0 || num_added >= number_of_tiles_to_add){
                 finished = true;
                 break;
             }
             pos f = frontier.RemoveRandom();
             foreach(pos neighbor in f.CardinalAdjacentPositions()){
                 if(!BoundsCheck(neighbor,false) || !rock[neighbor.row,neighbor.col]){
                     ++fail_counter; //this might now be unreachable
                     if(!BoundsCheck(neighbor,false)){
                         fail_counter += 25; //fail quicker when against the edge of the map to prevent ugliness
                     } //however, this doesn't actually fail as quickly as it should - i've overlooked something.
                     if(fail_counter >= 50){
                         finished = true;
                         break;
                     }
                 }
                 else{
                     if(map[neighbor] != CellType.RoomInterior){
                         map[neighbor] = CellType.RoomInterior;
                         ++num_added;
                         bool add_neighbor = true;
                         foreach(pos n2 in neighbor.CardinalAdjacentPositions()){
                             if(!BoundsCheck(n2,false) || !rock[n2.row,n2.col]){
                                 add_neighbor = false;
                                 ++fail_counter; //this might now be unreachable
                                 if(!BoundsCheck(neighbor,false)){
                                     fail_counter += 25; //fail quicker when against the edge of the map to prevent ugliness
                                 } //however, this doesn't actually fail as quickly as it should - i've overlooked something.
                                 if(fail_counter >= 50){
                                     finished = true;
                                 }
                                 break;
                             }
                         }
                         if(finished){
                             break;
                         }
                         if(add_neighbor){
                             frontier.Add(neighbor);
                         }
                     }
                 }
             }
         }
         foreach(pos p in list){
             map[p] = old_map[p];
         }
     }
 }
Exemplo n.º 25
0
 public PosArray<colorchar> GetCurrentRect(int row,int col,int height,int width)
 {
     PosArray<colorchar> result = new PosArray<colorchar>(height,width);
     for(int i=0;i<height;++i){
         for(int j=0;j<width;++j){
             result[i,j] = memory[row+i,col+j];
         }
     }
     return result;
 }
Exemplo n.º 26
0
 public Dungeon(int height,int width)
 {
     H = height;
     W = width;
     map = new PosArray<CellType>(H,W);
 }
Exemplo n.º 27
0
 public bool CreateTwistyCave(bool two_walls_between_corridors,int percent_coverage,int density_threshold,DensityUpdateDelegate update_density)
 {
     int target_number_of_floors = (H * W * percent_coverage) / 100;
     List<pos> frontier = new List<pos>();
     PosArray<int> density = new PosArray<int>(H,W);
     pos origin = new pos(R.Between(2,H-3),R.Between(2,W-3));
     frontier.Add(origin);
     map[origin] = CellType.RoomInterior;
     int count = 1;
     bool pick_random = false;
     while(frontier.Count > 0 && count < target_number_of_floors){
         pos p;
         if(pick_random || R.PercentChance(5)){
             p = frontier.RemoveRandom();
             pick_random = false;
         }
         else{
             p = frontier.RemoveLast();
         }
         if(density_threshold > 0 && density[p] >= density_threshold){
             continue;
         }
         List<int> valid_dirs = new List<int>();
         foreach(int dir in U.FourDirections){
             pos neighbor = p.PosInDir(dir);
             if(BoundsCheck(neighbor,false) && map[neighbor].IsWall()){
                 bool valid = true;
                 if(two_walls_between_corridors){
                     int idx = 0;
                     foreach(int dir2 in dir.GetArc(1)){
                         if(!map[neighbor.PosInDir(dir2)].IsWall()){
                             valid = false;
                             break;
                         }
                         else{
                             if(idx == 1){
                                 pos n = neighbor.PosInDir(dir2).PosInDir(dir2);
                                 if(n.BoundsCheck(map) && !map[n].IsWall()){
                                     valid = false;
                                     break;
                                 }
                             }
                             else{
                                 foreach(int dir3 in dir2.GetArc(1)){
                                     pos n = neighbor.PosInDir(dir2).PosInDir(dir3);
                                     if(n.BoundsCheck(map) && !map[n].IsWall()){
                                         valid = false;
                                         break;
                                     }
                                 }
                             }
                         }
                         ++idx;
                     }
                 }
                 else{
                     foreach(int dir2 in dir.GetArc(1)){
                         if(!map[neighbor.PosInDir(dir2)].IsWall()){
                             valid = false;
                             break;
                         }
                     }
                 }
                 if(valid){
                     valid_dirs.Add(dir);
                 }
             }
         }
         if(valid_dirs.Count == 0){
             pick_random = true;
         }
         else{
             if(valid_dirs.Count == 1 && R.CoinFlip()){
                 pick_random = true;
             }
             else{
                 valid_dirs.Randomize();
                 foreach(int i in valid_dirs){
                     pos neighbor = p.PosInDir(i);
                     map[neighbor] = CellType.RoomInterior;
                     ++count;
                     update_density(neighbor,density,map);
                     if(!pick_random){ //todo: should this check be removed? it can lead to abandoned paths that don't ever get filled
                         frontier.Add(neighbor);
                     }
                 }
             }
         }
     }
     foreach(pos p in frontier){
         if(BoundsCheck(p,false)){
             map[p] = CellType.RoomInterior;
         }
     }
     return true;
 }
Exemplo n.º 28
0
 public void ActiveAI()
 {
     if(path.Count > 0){
         path.Clear();
     }
     if(!HasAttr(AttrType.AGGRESSION_MESSAGE_PRINTED)){
         PrintAggressionMessage();
     }
     switch(type){
     case ActorType.GIANT_BAT:
     case ActorType.PHANTOM_BLIGHTWING:
         if(DistanceFrom(target) == 1){
             int idx = R.Roll(1,2) - 1;
             Attack(idx,target);
             if(target != null && R.CoinFlip()){ //chance of retreating
                 AI_Step(target,true);
             }
         }
         else{
             if(R.CoinFlip()){
                 AI_Step(target);
                 QS();
             }
             else{
                 AI_Step(TileInDirection(Global.RandomDirection()));
                 QS();
             }
         }
         break;
     case ActorType.BLOOD_MOTH:
     {
         Tile brightest = null;
         if(!M.wiz_dark && !M.wiz_lite && !HasAttr(AttrType.BLIND)){
             List<Tile> valid = M.AllTiles().Where(x=>x.light_value > 0 && CanSee(x));
             valid = valid.WhereGreatest(x=>{
                 int result = x.light_radius;
                 if(x.Is(FeatureType.FIRE) && result == 0){
                     result = 1;
                 }
                 if(x.inv != null && x.inv.light_radius > result){
                     result = x.inv.light_radius;
                 }
                 if(x.actor() != null && x.actor().LightRadius() > result){
                     result = x.actor().LightRadius();
                 }
                 return result;
             });
             valid = valid.WhereLeast(x=>DistanceFrom(x));
             if(valid.Count > 0){
                 brightest = valid.RandomOrDefault();
             }
         }
         if(brightest != null){
             if(DistanceFrom(brightest) <= 1){
                 if(target != null && brightest == target.tile()){
                     Attack(0,target);
                     if(target == player && player.curhp > 0){
                         Help.TutorialTip(TutorialTopic.Torch);
                     }
                 }
                 else{
                     List<Tile> open = new List<Tile>();
                     foreach(Tile t in TilesAtDistance(1)){
                         if(t.DistanceFrom(brightest) <= 1 && t.passable && t.actor() == null){
                             open.Add(t);
                         }
                     }
                     if(open.Count > 0){
                         AI_Step(open.Random());
                     }
                     QS();
                 }
             }
             else{
                 List<Tile> tiles = new List<Tile>();
                 if(brightest.row == row || brightest.col == col){
                     int targetdir = DirectionOf(brightest);
                     for(int i=-1;i<=1;++i){
                         pos adj = p.PosInDir(targetdir.RotateDir(true,i));
                         if(M.tile[adj].passable && M.actor[adj] == null){
                             tiles.Add(M.tile[adj]);
                         }
                     }
                 }
                 if(tiles.Count > 0){
                     AI_Step(tiles.Random());
                 }
                 else{
                     AI_Step(brightest);
                 }
                 QS();
             }
         }
         else{
             int dir = Global.RandomDirection();
             if(!TileInDirection(dir).passable && TilesAtDistance(1).Where(t => !t.passable).Count > 4){
                 dir = Global.RandomDirection();
             }
             if(TileInDirection(dir).passable && ActorInDirection(dir) == null){
                 AI_Step(TileInDirection(dir));
                 QS();
             }
             else{
                 if(curhp < maxhp && target != null && ActorInDirection(dir) == target){
                     Attack(0,target);
                 }
                 else{
                     if(player.HasLOS(TileInDirection(dir)) && player.HasLOS(this)){
                         if(!TileInDirection(dir).passable){
                             B.Add(the_name + " brushes up against " + TileInDirection(dir).the_name + ". ",this);
                         }
                         else{
                             if(ActorInDirection(dir) != null){
                                 B.Add(the_name + " brushes up against " + ActorInDirection(dir).TheName(true) + ". ",this);
                             }
                         }
                     }
                     QS();
                 }
             }
         }
         /*PhysicalObject brightest = null;
         if(!M.wiz_lite && !M.wiz_dark){
             List<PhysicalObject> current_brightest = new List<PhysicalObject>();
             foreach(Tile t in M.AllTiles()){
                 int pos_radius = t.light_radius;
                 PhysicalObject pos_obj = t;
                 if(t.Is(FeatureType.FIRE) && pos_radius == 0){
                     pos_radius = 1;
                 }
                 if(t.inv != null && t.inv.light_radius > pos_radius){
                     pos_radius = t.inv.light_radius;
                     pos_obj = t.inv;
                 }
                 if(t.actor() != null && t.actor().LightRadius() > pos_radius){
                     pos_radius = t.actor().LightRadius();
                     pos_obj = t.actor();
                 }
                 if(pos_radius > 0){
                     if(current_brightest.Count == 0 && CanSee(t)){
                         current_brightest.Add(pos_obj);
                     }
                     else{
                         foreach(PhysicalObject o in current_brightest){
                             int object_radius = o.light_radius;
                             if(o is Actor){
                                 object_radius = (o as Actor).LightRadius();
                             }
                             if(object_radius == 0 && o is Tile && (o as Tile).Is(FeatureType.FIRE)){
                                 object_radius = 1;
                             }
                             if(pos_radius > object_radius){
                                 if(CanSee(t)){
                                     current_brightest.Clear();
                                     current_brightest.Add(pos_obj);
                                     break;
                                 }
                             }
                             else{
                                 if(pos_radius == object_radius && DistanceFrom(t) < DistanceFrom(o)){
                                     if(CanSee(t)){
                                         current_brightest.Clear();
                                         current_brightest.Add(pos_obj);
                                         break;
                                     }
                                 }
                                 else{
                                     if(pos_radius == object_radius && DistanceFrom(t) == DistanceFrom(o) && pos_obj == player){
                                         if(CanSee(t)){
                                             current_brightest.Clear();
                                             current_brightest.Add(pos_obj);
                                             break;
                                         }
                                     }
                                 }
                             }
                         }
                     }
                 }
             }
             if(current_brightest.Count > 0){
                 brightest = current_brightest.Random();
             }
         }
         if(brightest != null){
             if(DistanceFrom(brightest) <= 1){
                 if(brightest == target){
                     Attack(0,target);
                     if(target == player && player.curhp > 0){
                         Help.TutorialTip(TutorialTopic.Torch);
                     }
                 }
                 else{
                     List<Tile> open = new List<Tile>();
                     foreach(Tile t in TilesAtDistance(1)){
                         if(t.DistanceFrom(brightest) <= 1 && t.passable && t.actor() == null){
                             open.Add(t);
                         }
                     }
                     if(open.Count > 0){
                         AI_Step(open.Random());
                     }
                     QS();
                 }
             }
             else{
                 AI_Step(brightest);
                 QS();
             }
         }
         else{
             int dir = Global.RandomDirection();
             if(TilesAtDistance(1).Where(t => !t.passable).Count > 4 && !TileInDirection(dir).passable){
                 dir = Global.RandomDirection();
             }
             if(TileInDirection(dir).passable && ActorInDirection(dir) == null){
                 AI_Step(TileInDirection(dir));
                 QS();
             }
             else{
                 if(curhp < maxhp && target != null && ActorInDirection(dir) == target){
                     Attack(0,target);
                 }
                 else{
                     if(player.HasLOS(TileInDirection(dir)) && player.HasLOS(this)){
                         if(!TileInDirection(dir).passable){
                             B.Add(the_name + " brushes up against " + TileInDirection(dir).the_name + ". ",this);
                         }
                         else{
                             if(ActorInDirection(dir) != null){
                                 B.Add(the_name + " brushes up against " + ActorInDirection(dir).TheName(true) + ". ",this);
                             }
                         }
                     }
                     QS();
                 }
             }
         }*/
         break;
     }
     case ActorType.CARNIVOROUS_BRAMBLE:
     case ActorType.MUD_TENTACLE:
         if(DistanceFrom(target) == 1){
             Attack(0,target);
             if(target == player && player.curhp > 0){
                 Help.TutorialTip(TutorialTopic.RangedAttacks);
             }
         }
         else{
             QS();
         }
         break;
     case ActorType.FROSTLING:
     {
         if(DistanceFrom(target) == 1){
             if(R.CoinFlip()){
                 Attack(0,target);
             }
             else{
                 if(AI_Step(target,true)){
                     QS();
                 }
                 else{
                     Attack(0,target);
                 }
             }
         }
         else{
             if(FirstActorInLine(target) == target && !HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 6){
                 int cooldown = R.Roll(1,4);
                 if(cooldown != 1){
                     RefreshDuration(AttrType.COOLDOWN_1,cooldown*100);
                 }
                 AnimateBoltProjectile(target,Color.RandomIce);
                 if(R.CoinFlip()){
                     B.Add(TheName(true) + " hits " + target.the_name + " with a blast of cold. ",target);
                     target.TakeDamage(DamageType.COLD,DamageClass.PHYSICAL,R.Roll(2,6),this,"a frostling");
                 }
                 else{
                     B.Add(TheName(true) + " misses " + target.the_name + " with a blast of cold. ",target);
                 }
                 foreach(Tile t in GetBestLineOfEffect(target)){
                     t.ApplyEffect(DamageType.COLD);
                 }
                 Q1();
             }
             else{
                 if(!HasAttr(AttrType.COOLDOWN_2)){
                     AI_Step(target);
                 }
                 else{
                     AI_Sidestep(target); //message for this? hmm.
                 }
                 QS();
             }
         }
         break;
     }
     case ActorType.SWORDSMAN:
     case ActorType.PHANTOM_SWORDMASTER:
         if(DistanceFrom(target) == 1){
             pos target_pos = target.p;
             Attack(0,target);
             if(target != null && target.p.Equals(target_pos)){
                 List<Tile> valid_dirs = new List<Tile>();
                 foreach(Tile t in target.TilesAtDistance(1)){
                     if(t.passable && t.actor() == null && DistanceFrom(t) == 1){
                         valid_dirs.Add(t);
                     }
                 }
                 if(valid_dirs.Count > 0){
                     AI_Step(valid_dirs.Random());
                 }
             }
         }
         else{
             attrs[AttrType.COMBO_ATTACK] = 0;
             AI_Step(target);
             QS();
         }
         break;
     case ActorType.DREAM_WARRIOR:
         if(DistanceFrom(target) == 1){
             if(curhp <= 10 && !HasAttr(AttrType.COOLDOWN_1)){ //todo: changed to 20hp and a 10hp threshold...better?
                 attrs[AttrType.COOLDOWN_1]++;
                 List<Tile> openspaces = new List<Tile>();
                 foreach(Tile t in target.TilesAtDistance(1)){
                     if(t.passable && t.actor() == null){
                         openspaces.Add(t);
                     }
                 }
                 foreach(Tile t in openspaces){
                     if(group == null){
                         group = new List<Actor>{this};
                     }
                     Create(ActorType.DREAM_WARRIOR_CLONE,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent);
                     t.actor().player_visibility_duration = -1;
                     t.actor().attrs[AttrType.NO_ITEM]++;
                     group.Add(M.actor[t.row,t.col]);
                     M.actor[t.row,t.col].group = group;
                     group.Randomize();
                 }
                 openspaces.Add(tile());
                 Tile newtile = openspaces[R.Roll(openspaces.Count)-1];
                 if(newtile != tile()){
                     Move(newtile.row,newtile.col,false);
                 }
                 if(openspaces.Count > 1){
                     B.Add(the_name + " is suddenly standing all around " + target.the_name + ". ",this,target);
                     Q1();
                 }
                 else{
                     Attack(0,target);
                 }
             }
             else{
                 Attack(0,target);
             }
         }
         else{
             AI_Step(target);
             QS();
         }
         break;
     case ActorType.SPITTING_COBRA:
         if(DistanceFrom(target) <= 3 && !HasAttr(AttrType.COOLDOWN_1) && FirstActorInLine(target) == target){
             RefreshDuration(AttrType.COOLDOWN_1,R.Between(50,75)*100);
             B.Add(TheName(true) + " spits poison in " + target.YourVisible() + " eyes! ",this,target);
             AnimateBoltProjectile(target,Color.DarkGreen);
             if(!target.HasAttr(AttrType.NONLIVING)){
                 target.ApplyStatus(AttrType.BLIND,R.Between(5,8)*100);
                 /*B.Add(target.YouAre() + " blind! ",target);
                 target.RefreshDuration(AttrType.BLIND,R.Between(5,8)*100,target.YouAre() + " no longer blinded. ",target);*/
             }
             Q1();
         }
         else{
             if(DistanceFrom(target) == 1){
                 Attack(0,target);
             }
             else{
                 List<Tile> tiles = new List<Tile>();
                 if(target.row == row || target.col == col){
                     int targetdir = DirectionOf(target);
                     for(int i=-1;i<=1;++i){
                         pos adj = p.PosInDir(targetdir.RotateDir(true,i));
                         if(M.tile[adj].passable && M.actor[adj] == null){
                             tiles.Add(M.tile[adj]);
                         }
                     }
                 }
                 if(tiles.Count > 0){
                     AI_Step(tiles.Random());
                 }
                 else{
                     AI_Step(target);
                 }
                 QS();
             }
         }
         break;
     case ActorType.KOBOLD:
         if(!HasAttr(AttrType.COOLDOWN_1)){
             if(DistanceFrom(target) > 12){
                 AI_Step(target);
                 QS();
             }
             else{
                 if(FirstActorInLine(target) != target){
                     AI_Sidestep(target);
                     QS();
                 }
                 else{
                     attrs[AttrType.COOLDOWN_1]++;
                     AnimateBoltProjectile(target,Color.DarkCyan,30);
                     if(player.CanSee(this)){
                         B.Add(the_name + " fires a dart at " + target.the_name + ". ",this,target);
                     }
                     else{
                         B.Add("A dart hits " + target.the_name + "! ",target);
                         if(player.CanSee(tile()) && !IsInvisibleHere()){
                             attrs[AttrType.TURNS_VISIBLE] = -1;
                             attrs[AttrType.NOTICED] = 1;
                             B.Add("You spot " + the_name + " that fired it. ",this);
                             //B.Add("You notice " + a_name + ". ",tile());
                         }
                     }
                     if(target.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(6),this,"a kobold's dart")){
                         target.ApplyStatus(AttrType.VULNERABLE,R.Between(2,4)*100);
                         /*if(!target.HasAttr(AttrType.VULNERABLE)){
                             B.Add(target.YouFeel() + " vulnerable. ",target);
                         }
                         target.RefreshDuration(AttrType.VULNERABLE,R.Between(2,4)*100,target.YouFeel() + " less vulnerable. ",target);*/
                         if(target == player){
                             Help.TutorialTip(TutorialTopic.Vulnerable);
                         }
                     }
                     Q1();
                 }
             }
         }
         else{
             if(DistanceFrom(target) <= 2){
                 AI_Flee();
                 QS();
             }
             else{
                 B.Add(the_name + " starts reloading. ",this);
                 attrs[AttrType.COOLDOWN_1] = 0;
                 Q1();
                 RefreshDuration(AttrType.COOLDOWN_2,R.Between(5,6)*100 - 50);
                 //Q.Add(new Event(this,R.Between(5,6)*100,EventType.MOVE));
             }
         }
         break;
     case ActorType.SPORE_POD:
         if(DistanceFrom(target) == 1){
             TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,100,null);
         }
         else{
             AI_Step(target);
             QS();
         }
         break;
     case ActorType.FORASECT:
     {
         bool burrow = false;
         if((curhp * 2 <= maxhp || DistanceFrom(target) > 6) && R.CoinFlip()){
             burrow = true;
         }
         if(DistanceFrom(target) <= 6 && DistanceFrom(target) > 1){
             if(R.OneIn(10)){
                 burrow = true;
             }
         }
         if(burrow && !HasAttr(AttrType.COOLDOWN_1)){
             RefreshDuration(AttrType.COOLDOWN_1,R.Between(8,11)*100);
             if(curhp * 2 <= maxhp){
                 Burrow(TilesWithinDistance(6));
             }
             else{
                 Burrow(GetCone(DirectionOf(target),6,true));
             }
         }
         else{
             if(DistanceFrom(target) == 1){
                 Attack(0,target);
             }
             else{
                 AI_Step(target);
                 QS();
             }
         }
         break;
     }
     case ActorType.POLTERGEIST:
         if(inv.Count == 0){
             if(DistanceFrom(target) == 1){
                 pos target_p = target.p;
                 if(Attack(0,target) && M.actor[target_p] != null && M.actor[target_p].inv.Any(i=>!i.do_not_stack)){
                     target = M.actor[target_p];
                     Item item = target.inv.Where(i=>!i.do_not_stack).Random();
                     if(item.quantity > 1){
                         inv.Add(new Item(item,-1,-1));
                         item.quantity--;
                         B.Add(YouVisible("steal") + " " + target.YourVisible() + " " + inv[0].Name() + "! ",this,target);
                     }
                     else{
                         inv.Add(item);
                         target.inv.Remove(item);
                         B.Add(YouVisible("steal") + " " + target.YourVisible() + " " + item.Name() + "! ",this,target);
                     }
                 }
             }
             else{
                 AI_Step(target);
                 QS();
             }
         }
         else{
             attrs[AttrType.KEEPS_DISTANCE] = 1;
             List<Tile> line = target.GetBestExtendedLineOfEffect(this);
             Tile next = null;
             bool found = false;
             foreach(Tile t in line){
                 if(found){
                     next = t;
                     break;
                 }
                 else{
                     if(t.actor() == this){
                         found = true;
                     }
                 }
             }
             if(next != null){
                 if(next.passable && next.actor() == null && AI_Step(next)){
                     QS();
                 }
                 else{
                     if(!next.passable){
                         B.Add(the_name + " disappears into " + next.the_name + ". ",this);
                         foreach(Tile t in TilesWithinDistance(1)){
                             if(t.DistanceFrom(next) == 1 && t.name == "floor"){
                                 t.AddFeature(FeatureType.SLIME);
                             }
                         }
                         Event e = null;
                         foreach(Event e2 in Q.list){
                             if(e2.target == this && e2.type == EventType.POLTERGEIST){
                                 e = e2;
                                 break;
                             }
                         }
                         if(e != null){
      								e.target = inv[0];
                             Actor.tiebreakers[e.tiebreaker] = null;
                         }
                         inv.Clear();
                         Kill();
                     }
                     else{
                         if(next.actor() != null){
                             if(!next.actor().HasAttr(AttrType.IMMOBILE)){
                                 Move(next.row,next.col);
                                 QS();
                             }
                             else{
                                 if(next.actor().HasAttr(AttrType.IMMOBILE)){
                                     if(AI_Step(next)){
                                         QS();
                                     }
                                     else{
                                         if(DistanceFrom(target) == 1){
                                             Attack(1,target);
                                         }
                                         else{
                                             QS();
                                         }
                                     }
                                 }
                             }
                         }
                         else{
                             QS();
                         }
                     }
                 }
             }
         }
         break;
     case ActorType.CULTIST:
     case ActorType.FINAL_LEVEL_CULTIST:
         if(curhp <= 10 && !HasAttr(AttrType.COOLDOWN_1)){
             attrs[AttrType.COOLDOWN_1]++;
             string invocation;
             switch(R.Roll(4)){
             case 1:
                 invocation = "ae vatra kersai";
                 break;
             case 2:
                 invocation = "kersai dzaggath";
                 break;
             case 3:
                 invocation = "od fir od bahgal";
                 break;
             case 4:
                 invocation = "denei kersai nammat";
                 break;
             default:
                 invocation = "denommus pilgni";
                 break;
             }
             if(R.CoinFlip()){
                 B.Add(You("whisper") + " '" + invocation + "'. ",this);
             }
             else{
                 B.Add(You("scream") + " '" + invocation.ToUpper() + "'. ",this);
             }
             if(HasAttr(AttrType.SLIMED)){
                 B.Add("Nothing happens. ",this);
             }
             else{
                 B.Add("Flames erupt from " + the_name + ". ",this);
                 AnimateExplosion(this,1,Color.RandomFire,'*');
                 ApplyBurning();
                 foreach(Tile t in TilesWithinDistance(1)){
                     t.ApplyEffect(DamageType.FIRE);
                     if(t.actor() != null){
                         t.actor().ApplyBurning();
                     }
                 }
             }
             Q1();
         }
         else{
             if(DistanceFrom(target) == 1){
                 Attack(0,target);
             }
             else{
                 AI_Step(target);
                 QS();
             }
         }
         break;
     case ActorType.GOBLIN_ARCHER:
     case ActorType.PHANTOM_ARCHER:
         switch(DistanceFrom(target)){
         case 1:
             /*if(target.EnemiesAdjacent() > 1){
                 Attack(0,target);
             }
             else{*/
                 if(AI_Flee()){
                     QS();
                 }
                 else{
                     Attack(0,target);
                 }
             //}
             break;
         case 2:
             if(FirstActorInLine(target) == target){
                 FireArrow(target);
             }
             else{
                 if(AI_Flee()){
                     QS();
                 }
                 else{
                     if(AI_Sidestep(target)){
                         B.Add(the_name + " tries to line up a shot. ",this);
                     }
                     QS();
                 }
             }
             break;
         case 3:
         case 4:
         case 5:
         case 6:
         case 7:
         case 8:
             if(FirstActorInLine(target) == target){
                 FireArrow(target);
             }
             else{
                 if(AI_Sidestep(target)){
                     B.Add(the_name + " tries to line up a shot. ",this);
                 }
                 QS();
             }
             break;
         default:
             AI_Step(target);
             QS();
             break;
         }
         break;
     case ActorType.GOBLIN_SHAMAN:
     {
         if(SilencedThisTurn()){
             return;
         }
         if(DistanceFrom(target) == 1){
             if(exhaustion > 50){
                 Attack(0,target);
             }
             else{
                 CastCloseRangeSpellOrAttack(target);
             }
         }
         else{
             if(DistanceFrom(target) > 12){
                 AI_Step(target);
                 QS();
             }
             else{
                 if(FirstActorInLine(target) != target || R.CoinFlip()){
                     AI_Step(target);
                     QS();
                 }
                 else{
                     CastRangedSpellOrMove(target);
                 }
             }
         }
         break;
     }
     case ActorType.PHASE_SPIDER:
         if(DistanceFrom(target) == 1){
             Attack(0,target);
         }
         else{
             Tile t = target.TilesAtDistance(DistanceFrom(target)-1).Where(x=>x.passable && x.actor() == null).RandomOrDefault();
             if(t != null){
                 Move(t.row,t.col);
             }
             QS();
         }
         break;
     case ActorType.ZOMBIE:
     case ActorType.PHANTOM_ZOMBIE:
         if(DistanceFrom(target) == 1){
             Attack(0,target);
         }
         else{
             AI_Step(target);
             if(DistanceFrom(target) == 1){
                 Attack(1,target);
             }
             else{
                 QS();
             }
         }
         break;
     case ActorType.ROBED_ZEALOT:
         if(HasAttr(AttrType.COOLDOWN_3)){
             if(DistanceFrom(target) <= 12 && HasLOS(target)){
                 target.AnimateExplosion(target,1,Color.Yellow,'*');
                 B.Add(YouVisible("smite") + " " + target.the_name + "! ",target);
                 int amount = target.curhp / 10;
                 bool still_alive = target.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,Math.Max(amount,1),this,"a zealot's wrath");
                 attrs[AttrType.COOLDOWN_3]--;
                 attrs[AttrType.DETECTING_MONSTERS]--;
                 if(!HasAttr(AttrType.COOLDOWN_3)){
                     B.Add(YouVisible("stop") + " praying. ");
                     if(still_alive && target.EquippedWeapon.type != WeaponType.NO_WEAPON && !target.EquippedWeapon.status[EquipmentStatus.MERCIFUL]){
                         target.EquippedWeapon.status[EquipmentStatus.MERCIFUL] = true;
                         B.Add(target.You("feel") + " a strange power enter " + target.Your() + " " + target.EquippedWeapon.NameWithoutEnchantment() + "! ",target);
                         B.PrintAll();
                         Help.TutorialTip(TutorialTopic.Merciful);
                     }
                 }
             }
             else{
                 attrs[AttrType.COOLDOWN_3]--;
                 attrs[AttrType.DETECTING_MONSTERS]--;
             }
             Q1();
         }
         else{
             if(!HasAttr(AttrType.COOLDOWN_1)){
                 attrs[AttrType.COOLDOWN_1] = maxhp; //initialize this value here instead of complicating the spawning code
             }
             if(DistanceFrom(target) <= 12 && !HasAttr(AttrType.COOLDOWN_2) && curhp < attrs[AttrType.COOLDOWN_1]){ //if the ability is ready and additional damage has been taken...
                 RefreshDuration(AttrType.COOLDOWN_2,R.Between(11,13)*100);
                 attrs[AttrType.COOLDOWN_1] = curhp;
                 attrs[AttrType.COOLDOWN_3] = 4;
                 attrs[AttrType.DETECTING_MONSTERS] = 4;
                 B.Add(YouVisible("start") + " praying. ");
                 B.Add(the_name + " points directly at you. ",this);
                 Q1();
             }
             else{
                 if(DistanceFrom(target) == 1){
                     Attack(0,target);
                 }
                 else{
                     AI_Step(target);
                     QS();
                 }
             }
         }
         /*if(HasAttr(AttrType.COOLDOWN_2)){
             attrs[AttrType.COOLDOWN_2] = 0;
             B.Add(the_name + " finishes the prayer. ",this);
             if(DistanceFrom(target) == 1 && target.EquippedWeapon.type != WeaponType.NO_WEAPON){
                 target.EquippedWeapon.status[EquipmentStatus.MERCIFUL] = true;
                 B.Add("You feel a strange power enter " + target.Your() + " " + target.EquippedWeapon.NameWithoutEnchantment() + "! ",target);
                 B.PrintAll();
                 Help.TutorialTip(TutorialTopic.Merciful);
             }
             Q1();
         }
         else{
             if((maxhp / 5) * 4 > curhp && !HasAttr(AttrType.COOLDOWN_1)){
                 RefreshDuration(AttrType.COOLDOWN_1,R.Between(14,16)*100);
                 attrs[AttrType.COOLDOWN_2]++;
                 B.Add(the_name + " starts praying. ",this);
                 B.Add("A fiery halo appears above " + the_name + ". ",this);
                 RefreshDuration(AttrType.RADIANT_HALO,R.Between(8,10)*100,Your() + " halo fades. ",this);
                 Q1();
             }
             else{
                 if(DistanceFrom(target) == 1){
                     Attack(0,target);
                 }
                 else{
                     AI_Step(target);
                     QS();
                 }
             }
         }*/
         break;
     case ActorType.GIANT_SLUG:
     {
         if(DistanceFrom(target) == 1){
             Attack(R.Between(0,1),target);
         }
         else{
             if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12 && FirstActorInLine(target) == target){
                 RefreshDuration(AttrType.COOLDOWN_1,R.Between(11,14)*100);
                 B.Add(TheName(true) + " spits slime at " + target.the_name + ". ",target);
                 List<Tile> slimed = GetBestLineOfEffect(target);
                 List<Tile> added = new List<Tile>();
                 foreach(Tile t in slimed){
                     foreach(int dir in U.FourDirections){
                         Tile neighbor = t.TileInDirection(dir);
                         if(R.OneIn(3) && neighbor.passable && !slimed.Contains(neighbor)){
                             added.AddUnique(neighbor);
                         }
                     }
                 }
                 slimed.AddRange(added);
                 List<pos> cells = new List<pos>();
                 List<Actor> slimed_actors = new List<Actor>();
                 for(int i=0;slimed.Count > 0;++i){
                     List<Tile> removed = new List<Tile>();
                     foreach(Tile t in slimed){
                         if(DistanceFrom(t) == i){
                             t.AddFeature(FeatureType.SLIME);
                             if(t.actor() != null && t.actor() != this && !t.actor().HasAttr(AttrType.SLIMED,AttrType.FROZEN)){
                                 slimed_actors.Add(t.actor());
                             }
                             removed.Add(t);
                             if(DistanceFrom(t) > 0){
                                 cells.Add(t.p);
                             }
                         }
                     }
                     foreach(Tile t in removed){
                         slimed.Remove(t);
                     }
                     if(cells.Count > 0){
                         Screen.AnimateMapCells(cells,new colorchar(',',Color.Green),20);
                     }
                 }
                 M.Draw();
                 slimed_actors.AddUnique(target);
                 foreach(Actor a in slimed_actors){
                     a.attrs[AttrType.SLIMED] = 1;
                     a.attrs[AttrType.OIL_COVERED] = 0;
                     a.RefreshDuration(AttrType.BURNING,0);
                     B.Add(a.YouAre() + " covered in slime. ",a);
                 }
                 Q1();
             }
             else{
                 AI_Step(target);
                 if(tile().Is(FeatureType.SLIME)){
                     speed = 50;
                     QS(); //normal speed is 150
                     speed = 150;
                 }
                 else{
                     QS();
                 }
             }
         }
         break;
     }
     case ActorType.BANSHEE:
     {
         if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12){
             RefreshDuration(AttrType.COOLDOWN_1,R.Between(13,15)*100);
             if(player.CanSee(this)){
                 if(player.IsSilencedHere()){
                     B.Add(You("seem") + " to scream. ",this);
                 }
                 else{
                     B.Add(You("scream") + ". ",this);
                 }
             }
             else{
                 if(!player.IsSilencedHere()){
                     B.Add("You hear a scream! ");
                 }
             }
             if(!target.IsSilencedHere()){
                 if(target.ResistedBySpirit() || target.HasAttr(AttrType.MENTAL_IMMUNITY)){
                     B.Add(target.You("remain") + " courageous. ",target);
                 }
                 else{
                     B.Add(target.YouAre() + " terrified! ",target);
                     RefreshDuration(AttrType.TERRIFYING,R.Between(5,8)*100,target.YouAre() + " no longer afraid. ",target);
                     Help.TutorialTip(TutorialTopic.Afraid);
                 }
             }
             Q1();
         }
         else{
             if(DistanceFrom(target) == 1){
                 Attack(0,target);
             }
             else{
                 AI_Step(target);
                 QS();
             }
         }
         break;
     }
     case ActorType.CAVERN_HAG:
         if(curhp < maxhp && HasAttr(AttrType.COOLDOWN_2) && !HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12){
             B.Add(TheName(true) + " curses you! ");
             if(target.ResistedBySpirit()){
                 B.Add("You resist the curse. ");
             }
             else{
                 switch(R.Roll(4)){
                 case 1: //light allergy
                     B.Add("You become allergic to light! ");
                     target.RefreshDuration(AttrType.LIGHT_SENSITIVE,(R.Roll(2,20) + 70) * 100,"You are no longer allergic to light. ");
                     break;
                 case 2: //aggravate monsters
                     B.Add("Every sound you make becomes amplified and echoes across the dungeon. ");
                     target.RefreshDuration(AttrType.AGGRAVATING,(R.Roll(2,20) + 70) * 100,"Your sounds are no longer amplified. ");
                     break;
                 case 3: //cursed weapon
                     B.Add("Your " + target.EquippedWeapon + " becomes stuck to your hand! ");
                     target.EquippedWeapon.status[EquipmentStatus.STUCK] = true;
                     Help.TutorialTip(TutorialTopic.Stuck);
                     break;
                 case 4: //heavy weapon
                     B.Add("Your " + target.EquippedWeapon + " suddenly feels much heavier. ");
                     target.EquippedWeapon.status[EquipmentStatus.HEAVY] = true;
                     Help.TutorialTip(TutorialTopic.Heavy);
                     break;
                 }
             }
             attrs[AttrType.COOLDOWN_1]++;
             Q1();
         }
         else{
             if(DistanceFrom(target) == 1){
                 Attack(0,target);
             }
             else{
                 AI_Step(target);
                 QS();
             }
         }
         break;
     case ActorType.BERSERKER:
     {
         if(HasAttr(AttrType.COOLDOWN_2)){
             int dir = attrs[AttrType.COOLDOWN_2];
             bool cw = R.CoinFlip();
             if(TileInDirection(dir).passable && ActorInDirection(dir) == null && !MovementPrevented(TileInDirection(dir))){
                 B.Add(the_name + " leaps forward swinging his axe! ",this);
                 Move(TileInDirection(dir).row,TileInDirection(dir).col);
                 M.Draw();
                 for(int i=-1;i<=1;++i){
                     Screen.AnimateBoltProjectile(new List<Tile>{tile(),TileInDirection(dir.RotateDir(cw,i))},Color.Red,30);
                 }
                 for(int i=-1;i<=1;++i){
                     Actor a = ActorInDirection(dir.RotateDir(cw,i));
                     if(a != null){
                         B.Add(YourVisible() + " axe hits " + a.TheName(true) + ". ",this,a);
                         a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(4,6),this,"a berserker's axe");
                     }
                     TileInDirection(dir.RotateDir(cw,i)).Bump(dir.RotateDir(cw,i));
                 }
                 Q1();
             }
             else{
                 if(ActorInDirection(dir) != null || MovementPrevented(TileInDirection(dir)) || TileInDirection(dir).Is(TileType.STANDING_TORCH,TileType.BARREL,TileType.POISON_BULB)){
                     B.Add(the_name + " swings his axe furiously! ",this);
                     for(int i=-1;i<=1;++i){
                         Screen.AnimateBoltProjectile(new List<Tile>{tile(),TileInDirection(dir.RotateDir(cw,i))},Color.Red,30);
                     }
                     for(int i=-1;i<=1;++i){
                         Actor a = ActorInDirection(dir.RotateDir(cw,i));
                         if(a != null){
                             B.Add(YourVisible() + " axe hits " + a.TheName(true) + ". ",this,a);
                             a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(4,6),this,"a berserker's axe");
                         }
                         TileInDirection(dir.RotateDir(cw,i)).Bump(dir.RotateDir(cw,i));
                     }
                     Q1();
                 }
                 else{
                     if(target != null && HasLOS(target)){
                         B.Add(the_name + " turns to face " + target.the_name + ". ",this);
                         attrs[AttrType.COOLDOWN_2] = DirectionOf(target);
                         Q1();
                     }
                 }
             }
         }
         else{
             if(DistanceFrom(target) == 1){
                 Attack(0,target);
                 if(target != null && R.Roll(3) == 3){
                     B.Add(the_name + " screams with fury! ",this);
                     attrs[AttrType.COOLDOWN_2] = DirectionOf(target);
                     Q.Add(new Event(this,350,AttrType.COOLDOWN_2,Your() + " rage diminishes. ",this));
                 }
             }
             else{
                 AI_Step(target);
                 QS();
             }
         }
         break;
     }
     case ActorType.DIRE_RAT:
     {
         bool slip_past = false;
         if(DistanceFrom(target) == 1){
             foreach(Actor a in ActorsAtDistance(1)){
                 if(a.type == ActorType.DIRE_RAT && a.DistanceFrom(target) > this.DistanceFrom(target)){
                     bool can_walk = false;
                     foreach(Tile t in a.TilesAtDistance(1)){
                         if(t.DistanceFrom(target) < a.DistanceFrom(target) && t.passable && t.actor() == null){
                             can_walk = true;
                             break;
                         }
                     }
                     if(!can_walk){ //there's a rat that would benefit from a space opening up - now check to see whether a move is possible
                         foreach(Tile t in target.TilesAtDistance(1)){
                             if(t.passable && t.actor() == null){
                                 slip_past = true;
                                 break;
                             }
                         }
                         break;
                     }
                 }
             }
         }
         if(slip_past){
             bool moved = false;
             foreach(Tile t in TilesAtDistance(1)){
                 if(t.DistanceFrom(target) == 1 && t.passable && t.actor() == null){
                     AI_Step(t);
                     QS();
                     moved = true;
                     break;
                 }
             }
             if(!moved){
                 Tile t = target.TilesAtDistance(1).Where(x=>x.passable && x.actor() == null).RandomOrDefault();
                 if(t != null){
                     B.Add(TheName(true) + " slips past " + target.TheName(true) + ". ",this,target);
                     Move(t.row,t.col);
                     Q.Add(new Event(this,Speed() + 100,EventType.MOVE));
                 }
                 else{
                     QS();
                 }
             }
         }
         else{
             if(DistanceFrom(target) == 1){
                 Attack(0,target);
             }
             else{
                 AI_Step(target);
                 QS();
             }
         }
         break;
     }
     case ActorType.SKULKING_KILLER:
     {
         if(HasAttr(AttrType.KEEPS_DISTANCE)){
             bool try_to_hide = false;
             if(AI_Flee()){
                 try_to_hide = true;
                 QS();
             }
             else{
                 if(DistanceFrom(target) == 1){
                     Attack(0,target);
                 }
                 else{ //give up on fleeing, just attack
                     attrs[AttrType.COOLDOWN_2] = 0;
                     attrs[AttrType.KEEPS_DISTANCE] = 0;
                     AI_Step(target);
                     QS();
                 }
             }
             if(try_to_hide){
                 bool visible = player.CanSee(this);
                 if(!R.OneIn(5) && (!player.HasLOE(this) || !visible || DistanceFrom(player) > 12)){ //just to add some uncertainty
                     attrs[AttrType.COOLDOWN_2]++;
                     if(attrs[AttrType.COOLDOWN_2] >= 3){
                         attrs[AttrType.KEEPS_DISTANCE] = 0;
                         attrs[AttrType.COOLDOWN_2] = 0;
                         if(!visible){
                             attrs[AttrType.TURNS_VISIBLE] = 0;
                         }
                     }
                 }
             }
         }
         else{
             if(DistanceFrom(target) == 1){
                 if(Attack(0,target)){
                     attrs[AttrType.KEEPS_DISTANCE] = 1;
                 }
             }
             else{
                 AI_Step(target);
                 QS();
             }
         }
         /*if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 3 && R.OneIn(3) && HasLOE(target)){
             attrs[AttrType.COOLDOWN_1]++;
             AnimateProjectile(target,Color.DarkYellow,'%');
             Input.FlushInput();
             if(target.CanSee(this)){
                 B.Add(the_name + " throws a bola at " + target.the_name + ". ",this,target);
             }
             else{
                 B.Add("A bola whirls toward " + target.the_name + ". ",this,target);
             }
             attrs[AttrType.TURNS_VISIBLE] = -1;
             target.RefreshDuration(AttrType.SLOWED,(R.Roll(3)+6)*100,target.YouAre() + " no longer slowed. ",target);
             B.Add(target.YouAre() + " slowed by the bola. ",target);
             Q1();
         }
         else{
             if(DistanceFrom(target) == 1){
                 Attack(0,target);
             }
             else{
                 AI_Step(target);
                 QS();
             }
         }*/
         break;
     }
     case ActorType.WILD_BOAR:
         if(DistanceFrom(target) == 1){
             Attack(0,target);
             if(HasAttr(AttrType.JUST_FLUNG)){ //if it just flung its target...
                 attrs[AttrType.JUST_FLUNG] = 0;
                 attrs[AttrType.COOLDOWN_1] = 0;
             }
             else{ //...otherwise it might prepare to fling again
                 if(!HasAttr(AttrType.COOLDOWN_1)){
                     if(!HasAttr(AttrType.COOLDOWN_2) || R.OneIn(5)){
                         attrs[AttrType.COOLDOWN_2]++;
                         B.Add(the_name + " lowers its head. ",this);
                         attrs[AttrType.COOLDOWN_1]++;
                     }
                 }
             }
         }
         else{
             AI_Step(target);
             if(!HasAttr(AttrType.COOLDOWN_2)){
                 attrs[AttrType.COOLDOWN_2]++;
                 B.Add(the_name + " lowers its head. ",this);
                 attrs[AttrType.COOLDOWN_1]++;
             }
             QS();
         }
         break;
     case ActorType.DREAM_SPRITE:
         if(!HasAttr(AttrType.COOLDOWN_1)){
             if(DistanceFrom(target) <= 12 && FirstActorInLine(target) == target){
                 RefreshDuration(AttrType.COOLDOWN_1,R.Between(3,4)*100);
                 bool visible = false;
                 List<List<Tile>> lines = new List<List<Tile>>{GetBestLineOfEffect(target)};
                 if(group != null && group.Count > 0){
                     foreach(Actor a in group){
                         if(target == player && player.CanSee(a)){
                             visible = true;
                         }
                         if(a.type == ActorType.DREAM_SPRITE_CLONE){
                             a.attrs[AttrType.COOLDOWN_1]++; //for them, it means 'skip next turn'
                             if(a.FirstActorInLine(target) == target){
                                 lines.Add(a.GetBestLineOfEffect(target));
                             }
                         }
                     }
                 }
                 foreach(List<Tile> line in lines){
                     if(line.Count > 0){
                         line.RemoveAt(0);
                     }
                 }
                 if(visible){
                     B.Add(the_name + " hits " + target.the_name + " with stinging magic. ",target);
                 }
                 else{
                     B.Add(TheName(true) + " hits " + target.the_name + " with stinging magic. ",target);
                 }
                 int max = lines.WhereGreatest(x=>x.Count)[0].Count;
                 for(int i=0;i<max;++i){
                     List<pos> cells = new List<pos>();
                     foreach(List<Tile> line in lines){
                         if(line.Count > i){
                             cells.Add(line[i].p);
                         }
                     }
                     Screen.AnimateMapCells(cells,new colorchar('*',Color.RandomRainbow));
                 }
                 target.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(2,6),this,"a blast of fairy magic");
                 Q1();
             }
             else{
                 if(DistanceFrom(target) > 12){
                     AI_Step(target);
                 }
                 else{
                     AI_Sidestep(target);
                 }
                 QS();
             }
         }
         else{
             if(DistanceFrom(target) > 5){
                 AI_Step(target);
             }
             else{
                 if(DistanceFrom(target) < 3){
                     AI_Flee();
                 }
                 else{
                     Tile t = TilesAtDistance(1).Where(x=>x.passable && x.actor() == null).RandomOrDefault();
                     if(t != null){
                         AI_Step(t);
                     }
                 }
             }
             QS();
         }
         break;
     case ActorType.DREAM_SPRITE_CLONE:
         if(HasAttr(AttrType.COOLDOWN_1)){
             attrs[AttrType.COOLDOWN_1] = 0;
             Q1();
         }
         else{
             if(DistanceFrom(target) > 5){
                 AI_Step(target);
             }
             else{
                 if(DistanceFrom(target) < 3){
                     AI_Flee();
                 }
                 else{
                     Tile t = TilesAtDistance(1).Where(x=>x.passable && x.actor() == null).RandomOrDefault();
                     if(t != null){
                         AI_Step(t);
                     }
                 }
             }
             QS();
         }
         break;
     case ActorType.CLOUD_ELEMENTAL:
     {
         List<pos> cloud = M.tile.GetFloodFillPositions(p,false,x=>M.tile[x].features.Contains(FeatureType.FOG));
         PhysicalObject[] objs = new PhysicalObject[cloud.Count + 1];
         int idx = 0;
         foreach(pos p2 in cloud){
             objs[idx++] = M.tile[p2];
         }
         objs[idx] = this;
         List<colorchar> chars = new List<colorchar>();
         colorchar cch = new colorchar('*',Color.RandomLightning);
         if(cloud.Contains(target.p)){
             B.Add(the_name + " electrifies the cloud! ",objs);
             foreach(pos p2 in cloud){
                 if(M.actor[p2] != null && M.actor[p2] != this){
                     M.actor[p2].TakeDamage(DamageType.ELECTRIC,DamageClass.PHYSICAL,R.Roll(3,6),this,"*electrocuted by a cloud elemental");
                 }
                 if(M.actor[p2] == this){
                     chars.Add(visual);
                 }
                 else{
                     chars.Add(cch);
                 }
             }
             Screen.AnimateMapCells(cloud,chars,50);
             Q1();
         }
         else{
             if(DistanceFrom(target) == 1){
                 Tile t = TilesAtDistance(1).Where(x=>x.actor() == null && x.passable).RandomOrDefault();
                 if(t != null){
                     AI_Step(t);
                 }
                 QS();
             }
             else{
                 if(R.OneIn(4)){
                     Tile t = TilesAtDistance(1).Where(x=>x.actor() == null && x.passable).RandomOrDefault();
                     if(t != null){
                         AI_Step(t);
                     }
                     QS();
                 }
                 else{
                     AI_Step(target);
                     QS();
                 }
             }
         }
         break;
     }
     case ActorType.DERANGED_ASCETIC:
         if(DistanceFrom(target) == 1){
             Attack(R.Roll(3)-1,target);
         }
         else{
             AI_Step(target);
             QS();
         }
         break;
     case ActorType.SNEAK_THIEF:
     {
         if(DistanceFrom(target) <= 12 && !R.OneIn(3) && AI_UseRandomItem()){
             Q1();
         }
         else{
             if(DistanceFrom(target) == 1){
                 Attack(0,target);
                 if(target != null){
                     List<Tile> valid_dirs = new List<Tile>();
                     foreach(Tile t in target.TilesAtDistance(1)){
                         if(t.passable && t.actor() == null && DistanceFrom(t) == 1){
                             valid_dirs.Add(t);
                         }
                     }
                     if(valid_dirs.Count > 0){
                         AI_Step(valid_dirs.Random());
                     }
                 }
             }
             else{
                 AI_Step(target);
                 QS();
             }
         }
         break;
     }
     case ActorType.WARG:
     {
         bool howl = false;
         if(DistanceFrom(target) == 1){
             if(R.CoinFlip() || group == null || group.Count < 2 || HasAttr(AttrType.COOLDOWN_1)){
                 Attack(0,target);
             }
             else{
                 howl = true;
             }
         }
         else{
             if(group == null || group.Count < 2 || HasAttr(AttrType.COOLDOWN_1)){
                 if(AI_Step(target)){
                     QS();
                 }
                 else{
                     howl = true;
                 }
             }
             else{
                 howl = true;
             }
         }
         if(howl){
             if(group == null || group.Count < 2){
                 Q1();
                 break;
             }
             B.Add(TheName(true) + " howls. ");
             PosArray<int> paths = new PosArray<int>(ROWS,COLS);
             foreach(Actor packmate in group){
                 packmate.RefreshDuration(AttrType.COOLDOWN_1,2000);
                 if(packmate != this){
                     var dijkstra = M.tile.GetDijkstraMap(new List<pos>{target.p},x=>!M.tile[x].passable,y=>M.actor[y] != null? 5 : paths[y]+1);
                     if(!dijkstra[packmate.p].IsValidDijkstraValue()){
                         continue;
                     }
                     List<pos> new_path = new List<pos>();
                     pos p = packmate.p;
                     while(!p.Equals(target.p)){
                         p = p.PositionsAtDistance(1,dijkstra).Where(x=>dijkstra[x].IsValidDijkstraValue()).WhereLeast(x=>dijkstra[x]).Random();
                         new_path.Add(p);
                         paths[p]++;
                     }
                     packmate.path = new_path;
                 }
             }
             Q1();
         }
         break;
     }
     case ActorType.RUNIC_TRANSCENDENT:
     {
         if(SilencedThisTurn()){
             return;
         }
         if(!HasSpell(SpellType.MERCURIAL_SPHERE)){
             if(DistanceFrom(target) == 1){
                 Attack(0,target);
             }
             else{
                 AI_Step(target);
                 QS();
             }
             return;
         }
         if(curmp < 2){
             B.Add(the_name + " absorbs mana from the universe. ",this);
             curmp = maxmp;
             Q1();
         }
         else{
             if(M.safetymap == null){
                 M.UpdateSafetyMap(player);
             }
             Tile t = TilesAtDistance(1).Where(x=>x.DistanceFrom(target) == 3 && x.passable && x.actor() == null).WhereLeast(x=>M.safetymap[x.p]).RandomOrDefault();
             if(t != null){ //check safety map. if there's a safer spot at distance 3, step there.
                 AI_Step(t);
             }
             else{
                 if(DistanceFrom(target) > 3){
                     AI_Step(target);
                 }
                 else{
                     if(DistanceFrom(target) < 3){
                         AI_Flee();
                     }
                 }
             }
             if(DistanceFrom(target) <= 12 && FirstActorInLine(target) != null && FirstActorInLine(target).DistanceFrom(target) <= 3){
                 CastSpell(SpellType.MERCURIAL_SPHERE,target);
             }
             else{
                 QS();
             }
         }
         break;
     }
     case ActorType.CARRION_CRAWLER:
         if(DistanceFrom(target) == 1){
             if(!target.HasAttr(AttrType.PARALYZED)){
                 Attack(0,target);
             }
             else{
                 Attack(1,target);
             }
         }
         else{
             AI_Step(target);
             QS();
         }
         break;
     case ActorType.MECHANICAL_KNIGHT:
         if(attrs[AttrType.COOLDOWN_1] == 3){ //no head
             int dir = Global.RandomDirection();
             if(R.CoinFlip()){
                 Actor a = ActorInDirection(dir);
                 if(a != null){
                     if(!Attack(0,a)){
                         B.Add(the_name + " drops its guard! ",this);
                         attrs[AttrType.MECHANICAL_SHIELD] = 0;
                     }
                 }
                 else{
                     B.Add(the_name + " attacks empty space. ",this);
                     TileInDirection(dir).Bump(dir);
                     B.Add(the_name + " drops its guard! ",this);
                     attrs[AttrType.MECHANICAL_SHIELD] = 0;
                     Q1();
                 }
             }
             else{
                 Tile t = TileInDirection(dir);
                 if(t.passable){
                     if(t.actor() == null){
                         AI_Step(t);
                         QS();
                     }
                     else{
                         B.Add(the_name + " bumps into " + t.actor().TheName(true) + ". ",this);
                         QS();
                     }
                 }
                 else{
                     B.Add(the_name + " bumps into " + t.TheName(true) + ". ",this);
                     t.Bump(DirectionOf(t));
                     QS();
                 }
             }
         }
         else{
             if(DistanceFrom(target) == 1){
                 if(attrs[AttrType.COOLDOWN_1] == 1){ //no arms
                     Attack(1,target);
                 }
                 else{
                     if(!Attack(0,target)){
                         B.Add(the_name + " drops its guard! ",this);
                         attrs[AttrType.MECHANICAL_SHIELD] = 0;
                     }
                 }
             }
             else{
                 if(attrs[AttrType.COOLDOWN_1] != 2){ //no legs
                     AI_Step(target);
                 }
                 QS();
             }
         }
         break;
     case ActorType.ALASI_BATTLEMAGE:
         if(SilencedThisTurn()){
             return;
         }
         if(DistanceFrom(target) > 12){
             AI_Step(target);
             QS();
         }
         else{
             if(DistanceFrom(target) == 1){
                 if(exhaustion < 50){
                     CastCloseRangeSpellOrAttack(null,target,true);
                 }
                 else{
                     Attack(0,target);
                 }
             }
             else{
                 CastRangedSpellOrMove(target);
             }
         }
         break;
     case ActorType.ALASI_SOLDIER:
         if(DistanceFrom(target) > 2){
             AI_Step(target);
             QS();
             attrs[AttrType.COMBO_ATTACK] = 0;
         }
         else{
             if(FirstActorInLine(target) != null && !FirstActorInLine(target).name.Contains("alasi")){ //I had planned to make this attack possibly hit multiple targets, but not yet.
                 Attack(0,target);
             }
             else{
                 if(AI_Step(target)){
                     QS();
                 }
                 else{
                     AI_Sidestep(target);
                     QS();
                 }
                 attrs[AttrType.COMBO_ATTACK] = 0;
             }
         }
         break;
     case ActorType.SKITTERMOSS:
         if(DistanceFrom(target) == 1){
             Attack(0,target);
             if(target != null && R.CoinFlip()){ //chance of retreating
                 AI_Step(target,true);
             }
         }
         else{
             if(R.CoinFlip()){
                 AI_Step(target);
                 QS();
             }
             else{
                 AI_Step(TileInDirection(Global.RandomDirection()));
                 QS();
             }
         }
         break;
     case ActorType.ALASI_SCOUT:
     {
         if(DistanceFrom(target) == 1){
             Attack(0,target);
         }
         else{
             if(curhp == maxhp){
                 if(FirstActorInLine(target) == target){
                     Attack(1,target);
                 }
                 else{
                     AI_Sidestep(target);
                     QS();
                 }
             }
             else{
                 AI_Step(target);
                 QS();
             }
         }
         break;
     }
     case ActorType.MUD_ELEMENTAL:
     {
         int count = 0;
         int walls = 0;
         foreach(Tile t in target.TilesAtDistance(1)){
             if(t.p.BoundsCheck(M.tile,false) && t.type == TileType.WALL){
                 ++walls;
                 if(t.actor() == null){
                     ++count;
                 }
             }
         }
         if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12 && count >= 2 || (count == 1 && walls == 1)){
             RefreshDuration(AttrType.COOLDOWN_1,150);
             foreach(Tile t in target.TilesAtDistance(1)){
                 if(t.p.BoundsCheck(M.tile,false) && t.type == TileType.WALL && t.actor() == null){
                     Create(ActorType.MUD_TENTACLE,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent);
                     M.actor[t.p].player_visibility_duration = -1;
                 }
             }
             if(count >= 2){
                 if(player.CanSee(this)){
                     B.Add(the_name + " calls mud tentacles from the walls! ");
                 }
                 else{
                     B.Add("Mud tentacles emerge from the walls! ");
                 }
             }
             else{
                 if(player.CanSee(this)){
                     B.Add(the_name + " calls a mud tentacle from the wall! ");
                 }
                 else{
                     B.Add("A mud tentacle emerges from the wall! ");
                 }
             }
             Q1();
         }
         else{
             if(DistanceFrom(target) == 1){
                 Attack(0,target);
             }
             else{
                 AI_Step(target);
                 QS();
             }
         }
         break;
     }
     case ActorType.FLAMETONGUE_TOAD:
     {
         bool burrow = false;
         if((curhp * 3 <= maxhp || DistanceFrom(target) > 6) && R.CoinFlip()){
             burrow = true;
         }
         if(DistanceFrom(target) <= 6 && DistanceFrom(target) > 1){
             if(R.OneIn(20)){
                 burrow = true;
             }
         }
         if(burrow && !HasAttr(AttrType.COOLDOWN_1)){
             RefreshDuration(AttrType.COOLDOWN_1,R.Between(12,16)*100);
             if(curhp * 3 <= maxhp){
                 Burrow(TilesWithinDistance(6));
             }
             else{
                 Burrow(GetCone(DirectionOf(target),6,true));
             }
         }
         else{
             if(!HasAttr(AttrType.COOLDOWN_2) && FirstActorInLine(target) != null && FirstActorInLine(target).DistanceFrom(target) <= 1){
                 RefreshDuration(AttrType.COOLDOWN_2,R.Between(10,14)*100);
                 Actor first = FirstActorInLine(target);
                 B.Add(TheName(true) + " breathes fire! ",this,first);
                 AnimateProjectile(first,'*',Color.RandomFire);
                 AnimateExplosion(first,1,'*',Color.RandomFire);
                 foreach(Tile t in GetBestLineOfEffect(first)){
                     t.ApplyEffect(DamageType.FIRE);
                 }
                 foreach(Tile t in first.TilesWithinDistance(1)){
                     t.ApplyEffect(DamageType.FIRE);
                     if(t.actor() != null){
                         t.actor().ApplyBurning();
                     }
                 }
                 Q1();
             }
             else{
                 if(DistanceFrom(target) == 1){
                     Attack(0,target);
                 }
                 else{
                     AI_Step(target);
                     QS();
                 }
             }
         }
         break;
     }
     case ActorType.ENTRANCER:
         if(group == null){
             if(AI_Flee()){
                 QS();
             }
             else{
                 if(DistanceFrom(target) == 1){
                     Attack(0,target);
                 }
                 else{
                     QS();
                 }
             }
         }
         else{
             Actor thrall = group[1];
             if(CanSee(thrall) && HasLOE(thrall)){ //cooldown 1 is teleport. cooldown 2 is shield.
                 //if the thrall is visible and you have LOE, the next goal is for the entrancer to be somewhere on the line that starts at the target and extends through the thrall.
                 List<Tile> line_from_target = target.GetBestExtendedLineOfEffect(thrall);
                 bool on_line = line_from_target.Contains(tile());
                 bool space_near_target = line_from_target.Count > 1 && line_from_target[1].passable && line_from_target[1].actor() == null;
                 if(on_line && DistanceFrom(target) > thrall.DistanceFrom(target)){
                     if(!HasAttr(AttrType.COOLDOWN_2) && thrall.curhp <= thrall.maxhp / 2){ //check whether you can shield it, if the thrall is low on health.
                         RefreshDuration(AttrType.COOLDOWN_2,1500);
                         B.Add(TheName(true) + " shields " + thrall.TheName(true) + ". ",this,thrall);
                         B.DisplayNow();
                         Screen.AnimateStorm(thrall.p,1,2,5,'*',Color.White);
                         thrall.attrs[AttrType.SHIELDED] = 1;
                         Q1();
                     }
                     else{ //check whether you can teleport the thrall closer.
                         if(!HasAttr(AttrType.COOLDOWN_1) && thrall.DistanceFrom(target) > 1 && space_near_target){
                             Tile dest = line_from_target[1];
                             RefreshDuration(AttrType.COOLDOWN_1,400);
                             B.Add(TheName(true) + " teleports " + thrall.TheName(true) + ". ",this,thrall);
                             M.Draw();
                             thrall.Move(dest.row,dest.col);
                             B.DisplayNow();
                             Screen.AnimateStorm(dest.p,1,1,4,thrall.symbol,thrall.color);
                             foreach(Tile t2 in thrall.GetBestLineOfEffect(dest)){
                                 Screen.AnimateStorm(t2.p,1,1,4,thrall.symbol,thrall.color);
                             }
                             Q1();
                         }
                         else{ //check whether you can shield it, if the thrall isn't low on health.
                             if(!HasAttr(AttrType.COOLDOWN_2)){
                                 RefreshDuration(AttrType.COOLDOWN_2,1500);
                                 B.Add(TheName(true) + " shields " + thrall.TheName(true) + ". ",this,thrall);
                                 B.DisplayNow();
                                 Screen.AnimateStorm(thrall.p,1,2,5,'*',Color.White);
                                 thrall.attrs[AttrType.SHIELDED] = 1;
                                 Q1();
                             }
                             else{ //check whether you are adjacent to thrall and can step away while remaining on line.
                                 List<Tile> valid = line_from_target.Where(x=>DistanceFrom(x) == 1 && x.actor() == null && x.passable);
                                 if(DistanceFrom(thrall) == 1 && valid.Count > 0){
                                     AI_Step(valid.Random());
                                 }
                                 QS();
                             }
                         }
                     }
                 }
                 else{
                     if(on_line){ //if on the line but not behind the thrall, we might be able to swap places or teleport
                         if(DistanceFrom(thrall) == 1){
                             Move(thrall.row,thrall.col);
                             QS();
                         }
                         else{
                             Tile dest = null;
                             foreach(Tile t in line_from_target){
                                 if(t.passable && t.actor() == null){
                                     dest = t;
                                     break;
                                 }
                             }
                             if(dest != null){
                                 RefreshDuration(AttrType.COOLDOWN_1,400);
                                 B.Add(TheName(true) + " teleports " + thrall.TheName(true) + ". ",this,thrall);
                                 M.Draw();
                                 thrall.Move(dest.row,dest.col);
                                 B.DisplayNow();
                                 Screen.AnimateStorm(dest.p,1,1,4,thrall.symbol,thrall.color);
                                 foreach(Tile t2 in thrall.GetBestLineOfEffect(dest)){
                                     Screen.AnimateStorm(t2.p,1,1,4,thrall.symbol,thrall.color);
                                 }
                             }
                             Q1();
                         }
                     }
                     else{ //if there's a free adjacent space on the line and behind the thrall, step there.
                         List<Tile> valid = line_from_target.From(thrall).Where(x=>x.passable && x.actor() == null && x.DistanceFrom(this) == 1);
                         if(valid.Count > 0){
                             AI_Step(valid.Random());
                             QS();
                         }
                         else{ //if you can teleport and there's a free tile on the line between you and the target, teleport the thrall there.
                             List<Tile> valid_between = GetBestLineOfEffect(target).Where(x=>x.passable && x.actor() == null && thrall.HasLOE(x));
                             if(!HasAttr(AttrType.COOLDOWN_1) && valid_between.Count > 0){
                                 Tile dest = valid_between.Random();
                                 RefreshDuration(AttrType.COOLDOWN_1,400);
                                 B.Add(TheName(true) + " teleports " + thrall.TheName(true) + ". ",this,thrall);
                                 M.Draw();
                                 thrall.Move(dest.row,dest.col);
                                 B.DisplayNow();
                                 Screen.AnimateStorm(dest.p,1,1,4,thrall.symbol,thrall.color);
                                 foreach(Tile t2 in thrall.GetBestLineOfEffect(dest)){
                                     Screen.AnimateStorm(t2.p,1,1,4,thrall.symbol,thrall.color);
                                 }
                                 Q1();
                             }
                             else{ //step toward a tile on the line (and behind the thrall)
                                 List<Tile> valid_behind_thrall = line_from_target.From(thrall).Where(x=>x.passable && x.actor() == null);
                                 if(valid_behind_thrall.Count > 0){
                                     AI_Step(valid_behind_thrall.Random());
                                 }
                                 QS();
                             }
                         }
                     }
                 }
                 //the old code:
                 /*if(DistanceFrom(target) < thrall.DistanceFrom(target) && DistanceFrom(thrall) == 1){
                     Move(thrall.row,thrall.col);
                     QS();
                 }
                 else{
                     if(DistanceFrom(target) == 1 && curhp < maxhp){
                         List<Tile> safe = TilesAtDistance(1).Where(t=>t.passable && t.actor() == null && target.GetBestExtendedLineOfEffect(thrall).Contains(t));
                         if(DistanceFrom(thrall) == 1 && safe.Count > 0){
                             AI_Step(safe.Random());
                             QS();
                         }
                         else{
                             if(AI_Flee()){
                                 QS();
                             }
                             else{
                                 Attack(0,target);
                             }
                         }
                     }
                     else{
                         if(!HasAttr(AttrType.COOLDOWN_1) && (thrall.DistanceFrom(target) > 1 || !target.GetBestExtendedLineOfEffect(thrall).Any(t=>t.actor()==this))){ //the entrancer tries to be smart about placing the thrall in a position that blocks ranged attacks
                             List<Tile> closest = new List<Tile>();
                             int dist = 99;
                             foreach(Tile t in thrall.TilesWithinDistance(2).Where(x=>x.passable && (x.actor()==null || x.actor()==thrall))){
                                 if(t.DistanceFrom(target) < dist){
                                     closest.Clear();
                                     closest.Add(t);
                                     dist = t.DistanceFrom(target);
                                 }
                                 else{
                                     if(t.DistanceFrom(target) == dist){
                                         closest.Add(t);
                                     }
                                 }
                             }
                             List<Tile> in_line = new List<Tile>();
                             foreach(Tile t in closest){
                                 if(target.GetBestExtendedLineOfEffect(t).Any(x=>x.actor()==this)){
                                     in_line.Add(t);
                                 }
                             }
                             Tile tile2 = null;
                             if(in_line.Count > 0){
                                 tile2 = in_line.Random();
                             }
                             else{
                                 if(closest.Count > 0){
                                     tile2 = closest.Random();
                                 }
                             }
                             if(tile2 != null && tile2.actor() != thrall){
                                 GainAttr(AttrType.COOLDOWN_1,400);
                                 B.Add(TheName(true) + " teleports " + thrall.TheName(true) + ". ",this,thrall);
                                 M.Draw();
                                 thrall.Move(tile2.row,tile2.col);
                                 B.DisplayNow();
                                 Screen.AnimateStorm(tile2.p,1,1,4,thrall.symbol,thrall.color);
                                 foreach(Tile t2 in thrall.GetBestLineOfEffect(tile2)){
                                     Screen.AnimateStorm(t2.p,1,1,4,thrall.symbol,thrall.color);
                                 }
                                 Q1();
                             }
                             else{
                                 List<Tile> safe = target.GetBestExtendedLineOfEffect(thrall).Where(t=>t.passable
                                 && t.actor() == null && t.DistanceFrom(target) > thrall.DistanceFrom(target)).WhereLeast(t=>DistanceFrom(t));
                                 if(safe.Count > 0){
                                     if(safe.Any(t=>t.DistanceFrom(target) > 2)){
                                         AI_Step(safe.Where(t=>t.DistanceFrom(target) > 2).Random());
                                     }
                                     else{
                                         AI_Step(safe.Random());
                                     }
                                 }
                                 QS();
                             }
                         }
                         else{
                             if(!HasAttr(AttrType.COOLDOWN_2) && thrall.attrs[AttrType.ARCANE_SHIELDED] < 25){
                                 GainAttr(AttrType.COOLDOWN_2,1500);
                                 B.Add(TheName(true) + " shields " + thrall.TheName(true) + ". ",this,thrall);
                                 B.DisplayNow();
                                 Screen.AnimateStorm(thrall.p,1,2,5,'*',Color.White);
                                 thrall.attrs[AttrType.ARCANE_SHIELDED] = 25;
                                 Q1();
                             }
                             else{
                                 List<Tile> safe = target.GetBestExtendedLineOfEffect(thrall).Where(t=>t.passable && t.actor() == null).WhereLeast(t=>DistanceFrom(t));
                                 if(safe.Count > 0){
                                     if(safe.Any(t=>t.DistanceFrom(target) > 2)){
                                         AI_Step(safe.Where(t=>t.DistanceFrom(target) > 2).Random());
                                     }
                                     else{
                                         AI_Step(safe.Random());
                                     }
                                 }
                                 QS();
                             }
                         }
                     }
                 }*/
             }
             else{
                 group[1].FindPath(this); //call for help
                 if(AI_Flee()){
                     QS();
                 }
                 else{
                     if(DistanceFrom(target) == 1){
                         Attack(0,target);
                     }
                     else{
                         QS();
                     }
                 }
             }
         }
         break;
     case ActorType.ORC_GRENADIER:
         if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 8){
             attrs[AttrType.COOLDOWN_1]++;
             Q.Add(new Event(this,(R.Roll(2)*100)+150,AttrType.COOLDOWN_1));
             B.Add(TheName(true) + " tosses a grenade toward " + target.the_name + ". ",target);
             List<Tile> tiles = new List<Tile>();
             foreach(Tile tile in target.TilesWithinDistance(1)){
                 if(tile.passable && !tile.Is(FeatureType.GRENADE)){
                     tiles.Add(tile);
                 }
             }
             Tile t = tiles[R.Roll(tiles.Count)-1];
             if(t.actor() != null){
                 if(t.actor() == player){
                     B.Add("It lands under you! ");
                 }
                 else{
                     B.Add("It lands under " + t.actor().the_name + ". ",t.actor());
                 }
             }
             else{
                 if(t.inv != null){
                     B.Add("It lands under " + t.inv.TheName() + ". ",t);
                 }
             }
             t.features.Add(FeatureType.GRENADE);
             Q.Add(new Event(t,100,EventType.GRENADE));
             Q1();
         }
         else{
             if(curhp <= 18){
                 if(AI_Step(target,true)){
                     QS();
                 }
                 else{
                     if(DistanceFrom(target) == 1){
                         Attack(0,target);
                     }
                     else{
                         QS();
                     }
                 }
             }
             else{
                 if(DistanceFrom(target) == 1){
                     Attack(0,target);
                 }
                 else{
                     AI_Step(target);
                     QS();
                 }
             }
         }
         break;
     case ActorType.MARBLE_HORROR:
         if(DistanceFrom(target) == 1){
             Attack(0,target);
         }
         else{
             AI_Step(target);
             QS();
         }
         break;
     case ActorType.SPELLMUDDLE_PIXIE:
         if(DistanceFrom(target) == 1){
             Attack(0,target);
             if(target != null && R.CoinFlip()){
                 AI_Step(target,true);
             }
         }
         else{
             AI_Step(target);
             QS();
         }
         break;
     case ActorType.OGRE_BARBARIAN:
         //if has grabbed target, check for open spaces near the opposite side.
         //if one is found, slam target into that tile, then do the attack.
         //otherwise, slam target into a solid tile (target doesn't move), then attack.
         //if nothing is grabbed yet, just keep attacking.
         if(DistanceFrom(target) == 1){
             if(target.HasAttr(AttrType.GRABBED) && attrs[AttrType.GRABBING] == DirectionOf(target) && !target.MovementPrevented(tile())){
                 Tile t = null;
                 Tile opposite = TileInDirection(DirectionOf(target).RotateDir(true,4));
                 if(opposite.passable && opposite.actor() == null){
                     t = opposite;
                 }
                 if(t == null){
                     List<Tile> near_opposite = new List<Tile>();
                     foreach(int i in new int[]{-1,1}){
                         Tile near = TileInDirection(DirectionOf(target).RotateDir(true,4+i));
                         if(near.passable && near.actor() == null){
                             near_opposite.Add(near);
                         }
                     }
                     if(near_opposite.Count > 0){
                         t = near_opposite.Random();
                     }
                 }
                 if(t != null){
                     target.attrs[AttrType.TURN_INTO_CORPSE]++;
                     Attack(1,target);
                     target.Move(t.row,t.col);
                     target.CollideWith(target.tile());
                     target.CorpseCleanup();
                 }
                 else{
                     target.attrs[AttrType.TURN_INTO_CORPSE]++;
                     Attack(1,target);
                     target.CollideWith(target.tile());
                     target.CorpseCleanup();
                 }
             }
             else{
                 Attack(0,target);
             }
         }
         else{
             if(speed == 100){
                 speed = 50;
             }
             if(!HasAttr(AttrType.COOLDOWN_1) && target == player && player.CanSee(this)){
                 B.Add(the_name + " charges! ");
                 attrs[AttrType.COOLDOWN_1] = 1;
             }
             AI_Step(target);
             if(!HasAttr(AttrType.COOLDOWN_1) && target == player && player.CanSee(this)){ //check twice so the message appears ASAP
                 B.Add(the_name + " charges! ");
                 attrs[AttrType.COOLDOWN_1] = 1;
             }
             QS();
         }
         break;
     case ActorType.MARBLE_HORROR_STATUE:
         QS();
         break;
     case ActorType.PYREN_ARCHER: //still considering some sort of fire trail movement ability for this guy
         switch(DistanceFrom(target)){
         case 1:
             if(target.EnemiesAdjacent() > 1){
                 Attack(0,target);
             }
             else{
                 if(AI_Flee()){
                     QS();
                 }
                 else{
                     Attack(0,target);
                 }
             }
             break;
         case 2:
             if(FirstActorInLine(target) == target){
                 FireArrow(target);
             }
             else{
                 if(AI_Flee()){
                     QS();
                 }
                 else{
                     if(AI_Sidestep(target)){
                         B.Add(the_name + " tries to line up a shot. ",this);
                     }
                     QS();
                 }
             }
             break;
         case 3:
         case 4:
         case 5:
         case 6:
         case 7:
         case 8:
         case 9:
         case 10:
         case 11:
         case 12:
             if(FirstActorInLine(target) == target){
                 FireArrow(target);
             }
             else{
                 if(AI_Sidestep(target)){
                     B.Add(the_name + " tries to line up a shot. ",this);
                 }
                 QS();
             }
             break;
         default:
             AI_Step(target);
             QS();
             break;
         }
         break;
     case ActorType.CYCLOPEAN_TITAN:
     {
         if(DistanceFrom(target) == 1){
             Attack(0,target);
         }
         else{
             if(DistanceFrom(target) > 2 && DistanceFrom(target) <= 12 && R.OneIn(15) && FirstActorInLine(target) == target){
                 B.Add(TheName(true) + " lobs a huge rock! ",this,target);
                 AnimateProjectile(target,'*',Color.Gray);
                 pos tp = target.p;
                 int plus_to_hit = -target.TotalSkill(SkillType.DEFENSE)*3;
                 if(target.IsHit(plus_to_hit)){
                     B.Add("It hits " + target.the_name + "! ",target);
                     if(target.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(4,6),this,"a cyclopean titan's rock")){
                         if(R.OneIn(8)){
                             target.ApplyStatus(AttrType.STUNNED,R.Between(3,4)*100);
                         }
                     }
                 }
                 else{
                     int armor_value = target.TotalProtectionFromArmor();
                     if(target != player){
                         armor_value = target.TotalSkill(SkillType.DEFENSE); //if monsters have Defense skill, it's from armor
                     }
                     int roll = R.Roll(25 - plus_to_hit);
                     if(roll <= armor_value * 3){
                         B.Add(target.Your() + " armor blocks it! ",target);
                     }
                     else{
                         if(target.HasAttr(AttrType.ROOTS) && roll <= (armor_value + 10) * 3){ //potion of roots gives 10 defense
                             B.Add(target.Your() + " root shell blocks it! ",target);
                         }
                         else{
                             B.Add(target.You("avoid") + " it! ",target);
                         }
                     }
                 }
                 foreach(pos neighbor in tp.PositionsWithinDistance(1,M.tile)){
                     Tile t = M.tile[neighbor];
                     if(t.Is(TileType.FLOOR) && R.OneIn(4)){
                         t.Toggle(null,TileType.GRAVEL);
                     }
                 }
                 Q1();
             }
             else{
                 bool smashed = false;
                 if(DistanceFrom(target) == 2 && !HasLOE(target)){
                     Tile t = FirstSolidTileInLine(target);
                     if(t != null && !t.passable){
                         smashed = true;
                         B.Add(You("smash",true) + " through " + t.TheName(true) + "! ",t);
                         foreach(int dir in DirectionOf(t).GetArc(1)){
                             TileInDirection(dir).Smash(dir);
                         }
                         Move(t.row,t.col);
                         QS();
                     }
                 }
                 if(!smashed){
                     AI_Step(target);
                     QS();
                 }
             }
         }
         break;
     }
     case ActorType.ALASI_SENTINEL:
         if(DistanceFrom(target) == 1){
             Attack(0,target);
             if(HasAttr(AttrType.JUST_FLUNG)){
                 attrs[AttrType.JUST_FLUNG] = 0;
             }
             else{
                 if(target != null){
                     List<Tile> valid_dirs = new List<Tile>();
                     foreach(Tile t in target.TilesAtDistance(1)){
                         if(t.passable && t.actor() == null && DistanceFrom(t) == 1){
                             valid_dirs.Add(t);
                         }
                     }
                     if(valid_dirs.Count > 0){
                         AI_Step(valid_dirs.Random());
                     }
                 }
             }
         }
         else{
             AI_Step(target);
             QS();
         }
         break;
     case ActorType.NOXIOUS_WORM:
         if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12 && HasLOE(target)){
             B.Add(TheName(true) + " breathes poisonous gas. ");
             List<Tile> area = new List<Tile>();
             foreach(Tile t in target.TilesWithinDistance(1)){
                 if(t.passable && target.HasLOE(t)){
                     t.AddFeature(FeatureType.POISON_GAS);
                     area.Add(t);
                 }
             }
             List<Tile> area2 = target.tile().AddGaseousFeature(FeatureType.POISON_GAS,8);
             area.AddRange(area2);
             Event.RemoveGas(area,600,FeatureType.POISON_GAS,18);
             RefreshDuration(AttrType.COOLDOWN_1,(R.Roll(6) + 18) * 100);
             Q1();
         }
         else{
             if(DistanceFrom(target) == 1){
                 Attack(0,target);
             }
             else{
                 AI_Step(target);
                 QS();
             }
         }
         break;
     case ActorType.LASHER_FUNGUS:
     {
         if(DistanceFrom(target) <= 12){
             if(DistanceFrom(target) == 1){
                 Attack(0,target);
             }
             else{
                 if(FirstActorInLine(target) == target){
                     List<Tile> line = GetBestLineOfEffect(target.row,target.col);
                     line.Remove(line[line.Count-1]);
                     AnimateBoltBeam(line,Color.DarkGreen);
                     pos target_p = target.p;
                     if(Attack(1,target) && M.actor[target_p] != null){
                         target = M.actor[target_p];
                         int rowchange = 0;
                         int colchange = 0;
                         if(target.row < row){
                             rowchange = 1;
                         }
                         if(target.row > row){
                             rowchange = -1;
                         }
                         if(target.col < col){
                             colchange = 1;
                         }
                         if(target.col > col){
                             colchange = -1;
                         }
                         if(!target.AI_MoveOrOpen(target.row+rowchange,target.col+colchange)){
                             bool moved = false;
                             if(Math.Abs(target.row - row) > Math.Abs(target.col - col)){
                                 if(target.AI_Step(M.tile[row,target.col])){
                                     moved = true;
                                 }
                             }
                             else{
                                 if(Math.Abs(target.row - row) < Math.Abs(target.col - col)){
                                     if(target.AI_Step(M.tile[target.row,col])){
                                         moved = true;
                                     }
                                 }
                                 else{
                                     if(target.AI_Step(this)){
                                         moved = true;
                                     }
                                 }
                             }
                             if(!moved){ //todo: this still isn't ideal. maybe I need an AI_Step that only considers 3 directions - right now, it'll make you move even if it isn't closer.
                                 B.Add(target.You("do",true) + "n't move far. ",target);
                             }
                         }
                     }
                 }
                 else{
                     Q1();
                 }
             }
         }
         else{
             Q1();
         }
         break;
     }
     case ActorType.LUMINOUS_AVENGER:
     {
         if(DistanceFrom(target) <= 3){
             List<Tile> ext = GetBestExtendedLineOfEffect(target);
             int max_count = Math.Min(5,ext.Count); //look 4 spaces away unless the line is even shorter than that.
             List<Actor> targets = new List<Actor>();
             Tile destination = null;
             for(int i=0;i<max_count;++i){
                 Tile t = ext[i];
                 if(t.passable){
                     if(t.actor() == null){
                         if(targets.Contains(target)){
                             destination = t;
                         }
                     }
                     else{
                         if(t.actor() != this){
                             targets.Add(t.actor());
                         }
                     }
                 }
                 else{
                     break;
                 }
             }
             if(destination != null){
                 Move(destination.row,destination.col);
                 foreach(Tile t in ext.To(destination)){
                     colorchar cch = M.VisibleColorChar(t.row,t.col);
                     cch.bgcolor = Color.Yellow;
                     if(Global.LINUX && !Screen.GLMode){
                         cch.bgcolor = Color.DarkYellow;
                     }
                     if(cch.color == cch.bgcolor){
                         cch.color = Color.Black;
                     }
                     Screen.WriteMapChar(t.row,t.col,cch);
                     Game.GLUpdate();
                     Thread.Sleep(15);
                 }
                 foreach(Actor a in targets){
                     Attack(0,a,true);
                 }
                 Q1();
             }
             else{
                 if(DistanceFrom(target) == 1){
                     Attack(0,target);
                 }
                 else{
                     AI_Step(target);
                     QS();
                 }
             }
         }
         else{
             AI_Step(target);
             QS();
         }
         break;
     }
     case ActorType.VAMPIRE:
         if(DistanceFrom(target) == 1){
             Attack(0,target);
         }
         else{
             if(DistanceFrom(target) <= 12){
                 if(tile().IsLit() && !HasAttr(AttrType.COOLDOWN_1)){
                     attrs[AttrType.COOLDOWN_1]++;
                     B.Add(the_name + " gestures. ",this);
                     List<Tile> tiles = new List<Tile>();
                     foreach(Tile t in target.TilesWithinDistance(6)){
                         if(t.passable && t.actor() == null && DistanceFrom(t) >= DistanceFrom(target)
                         && target.HasLOS(t) && target.HasLOE(t)){
                             tiles.Add(t);
                         }
                     }
                     if(tiles.Count == 0){
                         foreach(Tile t in target.TilesWithinDistance(6)){ //same, but with no distance requirement
                             if(t.passable && t.actor() == null && target.HasLOS(t) && target.HasLOE(t)){
                                 tiles.Add(t);
                             }
                         }
                     }
                     if(tiles.Count == 0){
                         B.Add("Nothing happens. ",this);
                     }
                     else{
                         if(tiles.Count == 1){
                             B.Add("A blood moth appears! ");
                         }
                         else{
                             B.Add("Blood moths appear! ");
                         }
                         for(int i=0;i<2;++i){
                             if(tiles.Count > 0){
                                 Tile t = tiles.RemoveRandom();
                                 Create(ActorType.BLOOD_MOTH,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent);
                                 M.actor[t.row,t.col].player_visibility_duration = -1;
                             }
                         }
                     }
                     Q1();
                 }
                 else{
                     AI_Step(target);
                     QS();
                 }
             }
             else{
                 AI_Step(target);
                 QS();
             }
         }
         break;
     case ActorType.ORC_WARMAGE:
     {
         if(SilencedThisTurn()){
             return;
         }
         switch(DistanceFrom(target)){
         case 1:
         {
             List<SpellType> close_range = new List<SpellType>();
             close_range.Add(SpellType.MAGIC_HAMMER);
             close_range.Add(SpellType.MAGIC_HAMMER);
             close_range.Add(SpellType.BLINK);
             if(target.EnemiesAdjacent() > 1 || R.CoinFlip()){
                 CastCloseRangeSpellOrAttack(close_range,target,false);
             }
             else{
                 if(AI_Step(target,true)){
                     QS();
                 }
                 else{
                     CastCloseRangeSpellOrAttack(close_range,target,false);
                 }
             }
             break;
         }
         case 2:
             if(R.CoinFlip()){
                 if(AI_Step(target,true)){
                     QS();
                 }
                 else{
                     if(FirstActorInLine(target) == target){
                         CastRangedSpellOrMove(target);
                     }
                     else{
                         AI_Sidestep(target);
                         QS();
                     }
                 }
             }
             else{
                 if(FirstActorInLine(target) == target){
                     CastRangedSpellOrMove(target);
                 }
                 else{
                     if(AI_Step(target,true)){
                         QS();
                     }
                     else{
                         AI_Sidestep(target);
                         QS();
                     }
                 }
             }
             break;
         case 3:
         case 4:
         case 5:
         case 6:
         case 7:
         case 8:
         case 9:
         case 10:
         case 11:
         case 12:
             if(FirstActorInLine(target) == target){
                 CastRangedSpellOrMove(target);
             }
             else{
                 AI_Sidestep(target);
                 QS();
             }
             break;
         default:
             AI_Step(target);
             QS();
             break;
         }
         break;
     }
     case ActorType.NECROMANCER:
     {
         if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12){
             attrs[AttrType.COOLDOWN_1]++;
             Q.Add(new Event(this,(R.Roll(4)+8)*100,AttrType.COOLDOWN_1));
             B.Add(the_name + " calls out to the dead. ",this);
             ActorType summon = R.CoinFlip()? ActorType.SKELETON : ActorType.ZOMBIE;
             List<Tile> tiles = new List<Tile>();
             foreach(Tile tile in TilesWithinDistance(2)){
                 if(tile.passable && tile.actor() == null && DirectionOf(tile) == DirectionOf(target)){
                     tiles.Add(tile);
                 }
             }
             if(tiles.Count == 0){
                 foreach(Tile tile in TilesWithinDistance(2)){
                     if(tile.passable && tile.actor() == null){
                         tiles.Add(tile);
                     }
                 }
             }
             if(tiles.Count == 0 || (group != null && group.Count > 3)){
                 B.Add("Nothing happens. ",this);
             }
             else{
                 Tile t = tiles.Random();
                 B.Add(Prototype(summon).a_name + " digs through the floor! ");
                 Create(summon,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent);
                 M.actor[t.row,t.col].player_visibility_duration = -1;
                 if(group == null){
                     group = new List<Actor>{this};
                 }
                 group.Add(M.actor[t.row,t.col]);
                 M.actor[t.row,t.col].group = group;
             }
             Q1();
         }
         else{
             bool blast = false;
             switch(DistanceFrom(target)){
             case 1:
                 if(AI_Step(target,true)){
                     QS();
                 }
                 else{
                     Attack(0,target);
                 }
                 break;
             case 2:
                 if(R.CoinFlip() && FirstActorInLine(target) == target){
                     blast = true;
                 }
                 else{
                     if(AI_Step(target,true)){
                         QS();
                     }
                     else{
                         blast = true;
                     }
                 }
                 break;
             case 3:
             case 4:
             case 5:
             case 6:
                 if(FirstActorInLine(target) == target){
                     blast = true;
                 }
                 else{
                     AI_Sidestep(target);
                     QS();
                 }
                 break;
             default:
                 AI_Step(target);
                 QS();
                 break;
             }
             if(blast){
                 B.Add(TheName(true) + " fires dark energy at " + target.TheName(true) + ". ",this,target);
                 AnimateBoltProjectile(target,Color.DarkBlue);
                 if(target.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(6),this,"*blasted by a necromancer")){
                     target.IncreaseExhaustion(R.Roll(3));
                 }
                 Q1();
             }
         }
         break;
     }
     case ActorType.STALKING_WEBSTRIDER:
     {
         bool burrow = false;
         if(DistanceFrom(target) >= 2 && DistanceFrom(target) <= 6){
             if(R.CoinFlip() && !target.tile().Is(FeatureType.WEB)){
                 burrow = true;
             }
         }
         if((DistanceFrom(target) > 6 || target.HasAttr(AttrType.POISONED))){
             burrow = true;
         }
         if(burrow && !HasAttr(AttrType.COOLDOWN_1)){
             RefreshDuration(AttrType.COOLDOWN_1,R.Between(5,8)*100);
             if(DistanceFrom(target) <= 2){
                 Burrow(TilesWithinDistance(6));
             }
             else{
                 Burrow(GetCone(DirectionOf(target),6,true));
             }
         }
         else{
             if(DistanceFrom(target) == 1){
                 Attack(0,target);
             }
             else{
                 AI_Step(target);
                 QS();
             }
         }
         break;
     }
     case ActorType.ORC_ASSASSIN:
         if(DistanceFrom(target) > 2 && attrs[AttrType.TURNS_VISIBLE] < 0){
             Tile t = TilesAtDistance(1).Where(x=>x.passable && x.actor() == null && target.DistanceFrom(x) == target.DistanceFrom(this)-1 && !target.CanSee(x)).RandomOrDefault();
             if(t != null){
                 AI_Step(t);
                 FindPath(target); //so it won't forget where the target is...
                 QS();
             }
             else{
                 AI_Step(target);
                 QS();
             }
         }
         else{
             if(DistanceFrom(target) == 1){
                 Attack(0,target);
             }
             else{
                 AI_Step(target);
                 if(DistanceFrom(target) == 1){
                     Attack(1,target);
                 }
                 else{
                     QS();
                 }
             }
         }
         break;
     case ActorType.MACHINE_OF_WAR:
     {
         if(attrs[AttrType.COOLDOWN_1] % 2 == 0){ //the machine of war moves on even turns and fires on odd turns.
             AI_Step(target);
             QS();
         }
         else{
             if(DistanceFrom(target) <= 12 && FirstActorInLine(target) == target){
                 B.Add(TheName(true) + " fires a stream of scalding oil at " + target.the_name + ". ",target);
                 List<Tile> covered_in_oil = GetBestLineOfEffect(target);
                 List<Tile> added = new List<Tile>();
                 foreach(Tile t in covered_in_oil){
                     foreach(int dir in U.FourDirections){
                         Tile neighbor = t.TileInDirection(dir);
                         if(R.OneIn(3) && neighbor.passable && !covered_in_oil.Contains(neighbor)){
                             added.AddUnique(neighbor);
                         }
                     }
                 }
                 covered_in_oil.AddRange(added);
                 List<pos> cells = new List<pos>();
                 List<Actor> oiled_actors = new List<Actor>();
                 for(int i=0;covered_in_oil.Count > 0;++i){
                     List<Tile> removed = new List<Tile>();
                     foreach(Tile t in covered_in_oil){
                         if(DistanceFrom(t) == i){
                             t.AddFeature(FeatureType.OIL);
                             if(t.actor() != null && t.actor() != this){
                                 oiled_actors.Add(t.actor());
                             }
                             removed.Add(t);
                             if(DistanceFrom(t) > 0){
                                 cells.Add(t.p);
                             }
                         }
                     }
                     foreach(Tile t in removed){
                         covered_in_oil.Remove(t);
                     }
                     if(cells.Count > 0){
                         Screen.AnimateMapCells(cells,new colorchar(',',Color.DarkYellow),20);
                     }
                 }
                 oiled_actors.AddUnique(target);
                 M.Draw();
                 foreach(Actor a in oiled_actors){
                     if(a.TakeDamage(DamageType.FIRE,DamageClass.PHYSICAL,R.Roll(4,6),this,"a stream of scalding oil")){
                         if(a.IsBurning()){
                             a.ApplyBurning();
                         }
                         else{
                             if(!a.HasAttr(AttrType.SLIMED,AttrType.FROZEN)){
                                 a.attrs[AttrType.OIL_COVERED]++;
                                 B.Add(a.YouAre() + " covered in oil. ",a);
                             }
                         }
                     }
                 }
                 Q1();
             }
             else{
                 Q1();
             }
         }
         break;
     }
     case ActorType.IMPOSSIBLE_NIGHTMARE:
     {
         if(DistanceFrom(target) == 1){
             Attack(0,target);
         }
         else{
             Tile t = target.TilesAtDistance(DistanceFrom(target)-1).Where(x=>x.passable && x.actor() == null).RandomOrDefault();
             if(t != null){
                 Move(t.row,t.col); //todo: fear effect?
             }
             QS();
         }
         break;
     }
     case ActorType.FIRE_DRAKE:
         /*if(player.magic_trinkets.Contains(MagicTrinketType.RING_OF_RESISTANCE) && DistanceFrom(player) <= 12 && CanSee(player)){
             B.Add(the_name + " exhales an orange mist toward you. ");
             foreach(Tile t in GetBestLineOfEffect(player)){
                 Screen.AnimateStorm(t.p,1,2,3,'*',Color.Red);
             }
             B.Add("Your ring of resistance melts and drips onto the floor! ");
             player.magic_trinkets.Remove(MagicTrinketType.RING_OF_RESISTANCE);
             Q.Add(new Event(this,100,EventType.MOVE));
         }
         else{
             if(player.EquippedArmor == ArmorType.FULL_PLATE_OF_RESISTANCE && DistanceFrom(player) <= 12 && CanSee(player)){
                 B.Add(the_name + " exhales an orange mist toward you. ");
                 foreach(Tile t in GetBestLine(player)){
                     Screen.AnimateStorm(t.p,1,2,3,'*',Color.Red);
                 }
                 B.Add("The runes drip from your full plate of resistance! ");
                 player.EquippedArmor = ArmorType.FULL_PLATE;
                 player.UpdateOnEquip(ArmorType.FULL_PLATE_OF_RESISTANCE,ArmorType.FULL_PLATE);
                 Q.Add(new Event(this,100,EventType.MOVE));
             }
             else{*/
             if(!HasAttr(AttrType.COOLDOWN_1)){
                 if(DistanceFrom(target) <= 12){
                     attrs[AttrType.COOLDOWN_1]++;
                     int cooldown = (R.Roll(1,4)+1) * 100;
                     Q.Add(new Event(this,cooldown,AttrType.COOLDOWN_1));
                     AnimateBeam(target,Color.RandomFire,'*');
                     B.Add(TheName(true) + " breathes fire. ",target);
                     target.TakeDamage(DamageType.FIRE,DamageClass.PHYSICAL,R.Roll(6,6),this,"*roasted by fire breath");
                 target.ApplyBurning();
                     Q.Add(new Event(this,200,EventType.MOVE));
                 }
                 else{
                     AI_Step(target);
                     QS();
                 }
             }
             else{
                 if(DistanceFrom(target) == 1){
                     Attack(R.Roll(1,2)-1,target);
                 }
                 else{
                     AI_Step(target);
                     QS();
                 }
             }
             //}
         //}
         break;
     case ActorType.GHOST:
     {
         attrs[AttrType.AGGRESSION_MESSAGE_PRINTED] = 1;
         bool tombstone = false;
         foreach(Tile t in TilesWithinDistance(1)){
             if(t.type == TileType.TOMBSTONE){
                 tombstone = true;
             }
         }
         if(!tombstone){
             B.Add("The ghost vanishes. ",this);
             Kill();
             return;
         }
         if(target == null || DistanceFrom(target) > 2){
             List<Tile> valid = TilesAtDistance(1).Where(x=>x.TilesWithinDistance(1).Any(y=>y.type == TileType.TOMBSTONE));
             if(valid.Count > 0){
                 AI_Step(valid.Random());
             }
             QS();
         }
         else{
             if(DistanceFrom(target) == 1){
                 Attack(0,target);
             }
             else{
                 List<Tile> valid = tile().NeighborsBetween(target.row,target.col).Where(x=>x.passable && x.actor() == null && x.TilesWithinDistance(1).Any(y=>y.type == TileType.TOMBSTONE));
                 if(valid.Count == 0){
                     valid = TilesAtDistance(1).Where(x=>x.TilesWithinDistance(1).Any(y=>y.type == TileType.TOMBSTONE));
                 }
                 if(valid.Count > 0){
                     AI_Step(valid.Random());
                 }
                 QS();
             }
         }
         break;
     }
     case ActorType.BLADE:
     {
         attrs[AttrType.AGGRESSION_MESSAGE_PRINTED] = 1;
         List<Actor> valid_targets = new List<Actor>(); //this is based on EnragedMove(), with an exception for other blades
         int max_dist = Math.Max(Math.Max(row,col),Math.Max(ROWS-row,COLS-col)); //this should find the farthest edge of the map
         for(int i=1;i<max_dist && valid_targets.Count == 0;++i){
             foreach(Actor a in ActorsAtDistance(i)){
                 if(a.type != ActorType.BLADE && CanSee(a) && HasLOE(a)){
                     valid_targets.Add(a);
                 }
             }
         }
         if(valid_targets.Count > 0){
             if(target == null || !valid_targets.Contains(target)){ //keep old target if possible
                 target = valid_targets.Random();
             }
             if(DistanceFrom(target) == 1){
                 Attack(0,target);
             }
             else{
                 AI_Step(target);
                 QS();
             }
         }
         else{
             if(target != null){
                 SeekAI();
             }
             else{
                 QS();
             }
         }
         break;
     }
     case ActorType.PHANTOM_CONSTRICTOR:
     case ActorType.PHANTOM_WASP:
     {
         if(DistanceFrom(target) == 1){
             Attack(0,target);
         }
         else{
             List<Tile> tiles = new List<Tile>(); //i should turn this "slither" movement into a standardized attribute or something
             if(target.row == row || target.col == col){
                 int targetdir = DirectionOf(target);
                 for(int i=-1;i<=1;++i){
                     pos adj = p.PosInDir(targetdir.RotateDir(true,i));
                     if(M.tile[adj].passable && M.actor[adj] == null){
                         tiles.Add(M.tile[adj]);
                     }
                 }
             }
             if(tiles.Count > 0){
                 AI_Step(tiles.Random());
             }
             else{
                 AI_Step(target);
             }
             QS();
         }
         break;
     }
     case ActorType.MINOR_DEMON:
     case ActorType.FROST_DEMON:
     case ActorType.BEAST_DEMON:
     case ActorType.DEMON_LORD:
     {
         int damage_threshold = 1;
         if(type == ActorType.BEAST_DEMON){
             damage_threshold = 0;
         }
         if(target == player && attrs[AttrType.COOLDOWN_2] > damage_threshold && CanSee(target)){
             switch(type){
             case ActorType.MINOR_DEMON:
             case ActorType.BEAST_DEMON:
                 if(DistanceFrom(target) == 1){
                     Attack(0,target);
                 }
                 else{
                     AI_Step(target);
                     QS();
                 }
                 break;
             case ActorType.FROST_DEMON:
                 if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12 && FirstActorInLine(target) == target){
                     attrs[AttrType.COOLDOWN_1] = 1;
                     AnimateProjectile(target,'*',Color.RandomIce);
                     foreach(Tile t in GetBestLineOfEffect(target)){
                         t.ApplyEffect(DamageType.COLD);
                     }
                     B.Add(TheName(true) + " fires a chilling sphere. ",target);
                     if(target.TakeDamage(DamageType.COLD,DamageClass.PHYSICAL,R.Roll(3,6),this,"a frost demon")){
                         target.ApplyStatus(AttrType.SLOWED,R.Between(4,7)*100);
                         //target.RefreshDuration(AttrType.SLOWED,R.Between(4,7)*100,target.YouAre() + " no longer slowed. ",target);
                     }
                     Q1();
                 }
                 else{
                     if(DistanceFrom(target) == 1){
                         Attack(0,target);
                     }
                     else{
                         AI_Step(target);
                         QS();
                     }
                 }
                 break;
             case ActorType.DEMON_LORD:
                 if(DistanceFrom(target) > 2){
                     AI_Step(target);
                     QS();
                 }
                 else{
                     if(FirstActorInLine(target) != null){
                         Attack(0,target);
                     }
                     else{
                         if(AI_Step(target)){
                             QS();
                         }
                         else{
                             AI_Sidestep(target);
                             QS();
                         }
                     }
                 }
                 break;
             }
         }
         else{
             if(row >= 7 && row <= 12 && col >= 30 && col <= 35){ //near the center
                 foreach(Actor a in ActorsAtDistance(1)){
                     if(a.IsFinalLevelDemon()){
                         List<Tile> dist2 = new List<Tile>();
                         foreach(Tile t in TilesWithinDistance(5)){
                             if(t.TilesAtDistance(2).Any(x=>x.type == TileType.FIRE_RIFT) && !t.TilesAtDistance(1).Any(x=>x.type == TileType.FIRE_RIFT)){
                                 dist2.Add(t);
                             }
                         } //if there's another distance 2 (from the center) tile with no adjacent demons, move there
                         //List<Tile> valid = dist2.Where(x=>DistanceFrom(x) == 1 && x.actor() == null && !x.TilesAtDistance(1).Any(y=>y.actor() != null && y.actor().Is(ActorType.MINOR_DEMON,ActorType.FROST_DEMON,ActorType.BEAST_DEMON,ActorType.DEMON_LORD)));
                         List<Tile> valid = dist2.Where(x=>DistanceFrom(x) == 1);
                         valid = valid.Where(x=>x.actor() == null && !x.TilesAtDistance(1).Any(y=>y.actor() != null && y.actor() != this && y.actor().IsFinalLevelDemon()));
                         if(valid.Count > 0){
                             AI_Step(valid.Random());
                         }
                         break;
                     }
                 }
                 if(player.HasLOS(this)){
                     B.Add(TheName(true) + " chants. ",this);
                 }
                 M.IncrementClock();
                 Q1();
             }
             else{
                 if(path != null && path.Count > 0){
                     if(!PathStep()){
                         QS();
                     }
                 }
                 else{
                     FindPath(9+R.Between(0,1),32+R.Between(0,1));
                     if(!PathStep()){
                         QS();
                     }
                 }
             }
         }
         break;
     }
     default:
         if(DistanceFrom(target) == 1){
             Attack(0,target);
         }
         else{
             AI_Step(target);
             QS();
         }
         break;
     }
 }