コード例 #1
0
ファイル: Item.cs プロジェクト: ptrefall/ForaysIntoNorrendrin
 private ItemUseResult UseOrb(int radius,bool never_target_enemies,Actor user,List<Tile> line,OrbTargetingDelegate orb_effect)
 {
     ItemUseResult result = new ItemUseResult(true,true);
     if(line == null){
         if(!identified[type]){
             radius = 0;
         }
         line = user.GetTargetTile(12,radius,false,!(never_target_enemies && identified[type]));
     }
     if(line != null){
         Tile t = line.LastOrDefault();
         Tile prev = line.LastBeforeSolidTile();
         Actor first = null;
         bool trigger_trap = true;
         Screen.CursorVisible = false;
         if(user != null){
             first = user.FirstActorInLine(line);
             B.Add(user.You("fling") + " the " + SingularName() + ". ",user);
             if(first != null && first != user){
                 trigger_trap = false;
                 t = first.tile();
                 if(player.CanSee(user)){
                     B.Add("It shatters on " + first.the_name + "! ",first);
                 }
                 else{
                     B.Add("Something shatters on " + first.the_name + "! ",first);
                 }
                 first.player_visibility_duration = -1;
                 first.attrs[AttrType.PLAYER_NOTICED]++;
             }
             else{
                 if(player.CanSee(user)){
                     B.Add("It shatters on " + t.the_name + "! ",t);
                 }
                 else{
                     B.Add("Something shatters on " + t.the_name + "! ",t);
                 }
             }
             user.AnimateProjectile(line.ToFirstSolidTileOrActor(),'*',color);
             Screen.CursorVisible = false;
         }
         else{
             trigger_trap = false;
         }
         Tile LOE_tile = t;
         if(!t.passable && prev != null){
             LOE_tile = prev;
         }
         orb_effect(t,LOE_tile,result);
         t.MakeNoise(2);
         if(trigger_trap && t.IsTrap()){
             t.TriggerTrap();
         }
         if(!revealed_by_light){
             result.IDed = false;
         }
     }
     else{
         result.used = false;
     }
     return result;
 }
コード例 #2
0
 public static bool Telekinesis(bool cast,Actor user,Tile t)
 {
     bool wand = !cast;
     if(t == null){
         return false;
     }
     if(t != null){
         if(wand && user == player && !Item.identified[ConsumableType.TELEKINESIS]){
             Item.identified[ConsumableType.TELEKINESIS] = true;
             B.Add("(It was a wand of telekinesis!) ");
             B.PrintAll();
         }
         List<Tile> ai_line = null;
         if(user != player && t == player.tile()){
             var fires = user.TilesWithinDistance(12).Where(x=>x.passable && x.actor() == null && x.Is(FeatureType.FIRE) && user.CanSee(x) && player.HasBresenhamLineWithCondition(x,false,y=>y.passable && y.actor() == null));
             if(fires.Count > 0){
                 ai_line = player.GetBestExtendedLineOfEffect(fires.Random());
             }
             else{
                 if(wand){
                     ai_line = player.GetBestExtendedLineOfEffect(user);
                 }
                 else{
                     ai_line = player.GetBestExtendedLineOfEffect(player.TileInDirection(Global.RandomDirection()));
                 }
             }
         }
         Actor a = t.actor();
         if(a == null && t.inv == null && !t.Is(FeatureType.GRENADE) && t.Is(FeatureType.TROLL_CORPSE,FeatureType.TROLL_BLOODWITCH_CORPSE)){
             ActorType troll_type = t.Is(FeatureType.TROLL_CORPSE)? ActorType.TROLL : ActorType.TROLL_BLOODWITCH;
             FeatureType troll_corpse = t.Is(FeatureType.TROLL_CORPSE)? FeatureType.TROLL_CORPSE : FeatureType.TROLL_BLOODWITCH_CORPSE;
             Event troll_event = Q.FindTargetedEvent(t,EventType.REGENERATING_FROM_DEATH);
             troll_event.dead = true;
             Actor troll = Actor.Create(troll_type,t.row,t.col);
             foreach(Event e in Q.list){
                 if(e.target == troll && e.type == EventType.MOVE){
                     e.tiebreaker = troll_event.tiebreaker;
                     e.dead = true;
                     break;
                 }
             }
             Actor.tiebreakers[troll_event.tiebreaker] = troll;
             troll.symbol = '%';
             troll.attrs[AttrType.CORPSE] = 1;
             troll.SetName(troll.name + "'s corpse");
             troll.curhp = troll_event.value;
             troll.attrs[AttrType.PERMANENT_DAMAGE] = troll_event.secondary_value;
             troll.attrs[AttrType.NO_ITEM]++;
             t.features.Remove(troll_corpse);
             a = troll;
         }
         if(a != null){
             string msg = "Throw " + a.TheName(true) + " in which direction? ";
             if(a == player){
                 msg = "Throw yourself in which direction? ";
             }
             List<Tile> line = null;
             if(user == player){
                 TargetInfo info = a.GetTarget(false,12,0,false,true,false,msg);
                 if(info != null){
                     line = info.extended_line;
                 }
             }
             else{
                 line = ai_line;
             }
             if(line != null){
                 if(line.Count == 1 && line[0].actor() == user){
                     if(wand){
                         return true;
                     }
                     return false;
                 }
                 if(cast){
                     B.Add(user.You("cast") + " telekinesis. ",user);
                     if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(SpellType.TELEKINESIS)){
                         a.curmp += Spell.Tier(SpellType.TELEKINESIS);
                         if(a.curmp > a.maxmp){
                             a.curmp = a.maxmp;
                         }
                         a.GainSpell(SpellType.TELEKINESIS);
                         B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a);
                     }
                 }
                 if(a == user && a == player){
                     B.Add("You throw yourself forward. ");
                 }
                 else{
                     if(line.Count == 1){
                         B.Add(user.YouVisible("throw") + " " + a.TheName(true) + " into the ceiling. ",user,a);
                     }
                     else{
                         B.Add(user.YouVisible("throw") + " " + a.TheName(true) + ". ",user,a);
                     }
                 }
                 B.DisplayNow();
                 user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 1;
                 a.attrs[AttrType.TELEKINETICALLY_THROWN] = 1;
                 a.attrs[AttrType.TURN_INTO_CORPSE]++;
                 if(line.Count == 1){
                     a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(3,6),user,"colliding with the ceiling");
                     a.CollideWith(a.tile());
                 }
                 else{
                     a.tile().KnockObjectBack(a,line,12,user);
                 }
                 a.attrs[AttrType.TELEKINETICALLY_THROWN] = 0;
                 user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 0;
                 if(a.curhp <= 0 && a.HasAttr(AttrType.REGENERATES_FROM_DEATH)){
                     a.attrs[AttrType.TURN_INTO_CORPSE] = 0;
                     a.attrs[AttrType.CORPSE] = 0;
                     a.attrs[AttrType.FROZEN] = 0;
                     a.attrs[AttrType.INVULNERABLE] = 0;
                     a.attrs[AttrType.SHIELDED] = 0;
                     a.attrs[AttrType.BLOCKING] = 0;
                     a.curhp = 1; //this is all pretty hacky - perhaps I should relocate the regenerating corpse through other means.
                     a.TakeDamage(DamageType.NORMAL,DamageClass.NO_TYPE,500,null);
                 }
                 else{
                     a.CorpseCleanup();
                 }
             }
             else{
                 if(wand){
                     return true;
                 }
                 return false;
             }
         }
         else{
             bool blast_fungus = false;
             if(t.type == TileType.BLAST_FUNGUS && !t.Is(FeatureType.GRENADE,FeatureType.WEB,FeatureType.FORASECT_EGG,FeatureType.BONES)){
                 blast_fungus = true;
             }
             if(t.inv != null || blast_fungus){
                 Item i = t.inv;
                 string itemname = "";
                 if(blast_fungus){
                     itemname = "the blast fungus";
                 }
                 else{
                     itemname = i.TheName(true);
                     if(i.quantity > 1){
                         itemname = "the " + i.SingularName();
                     }
                 }
                 string msg = "Throw " + itemname + " in which direction? ";
                 List<Tile> line = null;
                 if(user == player){
                     TargetInfo info = t.GetTarget(false,12,0,false,true,false,msg);
                     if(info != null){
                         line = info.extended_line;
                     }
                 }
                 else{
                     line = ai_line;
                 }
                 if(line != null){
                     if(line.Count > 13){
                         line = line.ToCount(13); //for range 12
                     }
                     if(cast){
                         B.Add(user.You("cast") + " telekinesis. ",user);
                     }
                     if(blast_fungus){
                         B.Add("The blast fungus is pulled from the floor. ",t);
                         B.Add("Its fuse ignites! ",t);
                         t.Toggle(null);
                         i = Item.Create(ConsumableType.BLAST_FUNGUS,t.row,t.col);
                         if(i != null){
                             i.other_data = 3;
                             i.revealed_by_light = true;
                             Q.Add(new Event(i,100,EventType.BLAST_FUNGUS));
                             Screen.AnimateMapCell(t.row,t.col,new colorchar('3',Color.Red),100);
                         }
                     }
                     if(line.Count == 1){
                         B.Add(user.YouVisible("throw") + " " + itemname + " into the ceiling. ",user,t);
                     }
                     else{
                         B.Add(user.YouVisible("throw") + " " + itemname + ". ",user,t);
                     }
                     B.DisplayNow();
                     if(i.quantity > 1){
                         i.quantity--;
                         bool revealed = i.revealed_by_light;
                         i = new Item(i,-1,-1);
                         i.revealed_by_light = revealed;
                     }
                     else{
                         t.inv = null;
                         Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col));
                     }
                     bool trigger_traps = false;
                     Tile t2 = line.LastBeforeSolidTile();
                     Actor first = user.FirstActorInLine(line);
                     if(t2 == line.LastOrDefault() && first == null){
                         trigger_traps = true;
                     }
                     if(first != null){
                         t2 = first.tile();
                     }
                     line = line.ToFirstSolidTileOrActor();
                     //if(line.Count > 0){
                     //	line.RemoveAt(line.Count - 1);
                     //}
                     if(line.Count > 0){
                         line.RemoveAt(line.Count - 1);
                     }
                     {
                         Tile first_unseen = null;
                         foreach(Tile tile2 in line){
                             if(!tile2.seen){
                                 first_unseen = tile2;
                                 break;
                             }
                         }
                         if(first_unseen != null){
                             line = line.To(first_unseen);
                             if(line.Count > 0){
                                 line.RemoveAt(line.Count - 1);
                             }
                         }
                     }
                     if(line.Count > 0){ //or > 1 ?
                         user.AnimateProjectile(line,i.symbol,i.color);
                     }
                     if(first == user){
                         B.Add(user.You("catch",true) + " it! ",user);
                         if(user.inv.Count < Global.MAX_INVENTORY_SIZE){
                             user.GetItem(i);
                         }
                         else{
                             B.Add("Your pack is too full to fit anything else. ");
                             i.ignored = true;
                             user.tile().GetItem(i);
                         }
                     }
                     else{
                         if(first != null){
                             B.Add("It hits " + first.the_name + ". ",first);
                         }
                         if(i.IsBreakable()){
                             if(i.quantity > 1){
                                 B.Add(i.TheName(true) + " break! ",t2);
                             }
                             else{
                                 B.Add(i.TheName(true) + " breaks! ",t2);
                             }
                             if(i.NameOfItemType() == "orb"){
                                 i.Use(null,new List<Tile>{t2});
                             }
                             else{
                                 i.CheckForMimic();
                             }
                         }
                         else{
                             t2.GetItem(i);
                         }
                         t2.MakeNoise(2);
                     }
                     if(first != null && first != user && first != player){
                         first.player_visibility_duration = -1;
                         first.attrs[AttrType.PLAYER_NOTICED]++;
                     }
                     else{
                         if(trigger_traps && t2.IsTrap()){
                             t2.TriggerTrap();
                         }
                     }
                 }
                 else{
                     if(wand){
                         return true;
                     }
                     return false;
                 }
             }
             else{
                 if(!t.Is(FeatureType.GRENADE) && (t.Is(TileType.DOOR_C,TileType.DOOR_O,TileType.POISON_BULB) || t.Is(FeatureType.WEB,FeatureType.FORASECT_EGG,FeatureType.BONES))){
                     if(cast){
                         B.Add(user.You("cast") + " telekinesis. ",user);
                     }
                     if(t.Is(TileType.DOOR_C)){
                         B.Add("The door slams open. ",t);
                         t.Toggle(null);
                     }
                     else{
                         if(t.Is(TileType.DOOR_O)){
                             B.Add("The door slams open. ",t);
                             t.Toggle(null);
                         }
                     }
                     if(t.Is(TileType.POISON_BULB)){
                         t.Bump(0);
                     }
                     if(t.Is(FeatureType.WEB)){
                         B.Add("The web is destroyed. ",t);
                         t.RemoveFeature(FeatureType.WEB);
                     }
                     if(t.Is(FeatureType.FORASECT_EGG)){
                         B.Add("The egg is destroyed. ",t);
                         t.RemoveFeature(FeatureType.FORASECT_EGG); //todo: forasect pathing?
                     }
                     if(t.Is(FeatureType.BONES)){
                         B.Add("The bones are scattered. ",t);
                         t.RemoveFeature(FeatureType.BONES);
                     }
                 }
                 else{
                     bool grenade = t.Is(FeatureType.GRENADE);
                     bool barrel = t.Is(TileType.BARREL);
                     bool flaming_barrel = barrel && t.IsBurning();
                     bool torch = t.Is(TileType.STANDING_TORCH);
                     string feature_name = "";
                     int impact_damage_dice = 3;
                     colorchar vis = new colorchar(t.symbol,t.color);
                     switch(t.type){
                     case TileType.CRACKED_WALL:
                         feature_name = "cracked wall";
                         break;
                     case TileType.RUBBLE:
                         feature_name = "pile of rubble";
                         break;
                     case TileType.STATUE:
                         feature_name = "statue";
                         break;
                     }
                     if(grenade){
                         impact_damage_dice = 0;
                         feature_name = "grenade";
                         vis.c = ',';
                         vis.color = Color.Red;
                     }
                     if(flaming_barrel){
                         feature_name = "flaming barrel of oil";
                     }
                     if(barrel){
                         feature_name = "barrel";
                     }
                     if(torch){
                         feature_name = "torch";
                     }
                     if(feature_name == ""){
                         if(wand){
                             if(user == player){
                                 B.Add("The wand grabs at empty space. ",t);
                             }
                             return true;
                         }
                         return false;
                     }
                     string msg = "Throw the " + feature_name + " in which direction? ";
                     bool passable_hack = !t.passable;
                     if(passable_hack){
                         t.passable = true;
                     }
                     List<Tile> line = null;
                     if(user == player){
                         TargetInfo info = t.GetTarget(false,12,0,false,true,false,msg);
                         if(info != null){
                             line = info.extended_line;
                         }
                     }
                     else{
                         line = ai_line;
                     }
                     if(passable_hack){
                         t.passable = false;
                     }
                     if(line != null){
                         if(cast){
                             B.Add(user.You("cast") + " telekinesis. ",user);
                         }
                         if(line.Count == 1){
                             B.Add(user.YouVisible("throw") + " the " + feature_name + " into the ceiling. ",user,t);
                         }
                         else{
                             B.Add(user.YouVisible("throw") + " the " + feature_name + ". ",user,t);
                         }
                         B.DisplayNow();
                         user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 1;
                         switch(t.type){
                         case TileType.CRACKED_WALL:
                         case TileType.RUBBLE:
                         case TileType.STATUE:
                         case TileType.BARREL:
                         case TileType.STANDING_TORCH:
                             if(flaming_barrel){
                                 t.RemoveFeature(FeatureType.FIRE);
                             }
                             t.Toggle(null,TileType.FLOOR);
                             foreach(Tile neighbor in t.TilesAtDistance(1)){
                                 neighbor.solid_rock = false;
                             }
                             break;
                         }
                         if(grenade){
                             t.RemoveFeature(FeatureType.GRENADE);
                             Event e = Q.FindTargetedEvent(t,EventType.GRENADE);
                             if(e != null){
                                 e.dead = true;
                             }
                         }
                         Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col));
                         colorchar[,] mem = Screen.GetCurrentMap();
                         int current_row = t.row;
                         int current_col = t.col;
                         //
                         int knockback_strength = 12;
                         if(line.Count == 1){
                             knockback_strength = 0;
                         }
                         int i=0;
                         while(true){
                             Tile t2 = line[i];
                             if(t2 == t){
                                 break;
                             }
                             ++i;
                         }
                         line.RemoveRange(0,i+1);
                         while(knockback_strength > 0){ //if the knockback strength is greater than 1, you're passing *over* at least one tile.
                             Tile t2 = line[0];
                             line.RemoveAt(0);
                             if(!t2.passable){
                                 if(t2.Is(TileType.CRACKED_WALL,TileType.DOOR_C,TileType.HIDDEN_DOOR) && impact_damage_dice > 0){
                                     string tilename = t2.TheName(true);
                                     if(t2.type == TileType.HIDDEN_DOOR){
                                         tilename = "a hidden door";
                                         t2.Toggle(null);
                                     }
                                     B.Add("The " + feature_name + " flies into " + tilename + ". ",t2);
                                     t2.Toggle(null);
                                     Screen.WriteMapChar(current_row,current_col,mem[current_row,current_col]);
                                 }
                                 else{
                                     B.Add("The " + feature_name + " flies into " + t2.TheName(true) + ". ",t2);
                                     if(impact_damage_dice > 0){
                                         t2.Bump(M.tile[current_row,current_col].DirectionOf(t2));
                                     }
                                     Screen.WriteMapChar(current_row,current_col,mem[current_row,current_col]);
                                 }
                                 knockback_strength = 0;
                                 break;
                             }
                             else{
                                 if(t2.actor() != null){
                                     B.Add("The " + feature_name + " flies into " + t2.actor().TheName(true) + ". ",t2);
                                     if(t2.actor().type != ActorType.SPORE_POD && !t2.actor().HasAttr(AttrType.SELF_TK_NO_DAMAGE)){
                                         t2.actor().TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(impact_damage_dice,6),user,"colliding with a " + feature_name);
                                     }
                                     knockback_strength = 0;
                                     Screen.WriteMapChar(t2.row,t2.col,vis);
                                     Screen.WriteMapChar(current_row,current_col,mem[current_row,current_col]);
                                     current_row = t2.row;
                                     current_col = t2.col;
                                     break;
                                 }
                                 else{
                                     if(t2.Is(FeatureType.WEB)){ //unless perhaps grenades should get stuck and explode in the web?
                                         t2.RemoveFeature(FeatureType.WEB);
                                     }
                                     Screen.WriteMapChar(t2.row,t2.col,vis);
                                     Screen.WriteMapChar(current_row,current_col,mem[current_row,current_col]);
                                     current_row = t2.row;
                                     current_col = t2.col;
                                     Game.GLUpdate();
                                     Thread.Sleep(20);
                                 }
                             }
                             //M.Draw();
                             knockback_strength--;
                         }
                         Tile current = M.tile[current_row,current_col];
                         if(grenade){
                             B.Add("The grenade explodes! ",current);
                             current.ApplyExplosion(1,user,"an exploding grenade");
                         }
                         if(barrel){
                             B.Add("The barrel smashes! ",current);
                             List<Tile> cone = current.TilesWithinDistance(1).Where(x=>x.passable);
                             List<Tile> added = new List<Tile>();
                             foreach(Tile t3 in cone){
                                 foreach(int dir in U.FourDirections){
                                     if(R.CoinFlip() && t3.TileInDirection(dir).passable){
                                         added.AddUnique(t3.TileInDirection(dir));
                                     }
                                 }
                             }
                             cone.AddRange(added);
                             foreach(Tile t3 in cone){
                                 t3.AddFeature(FeatureType.OIL);
                                 if(t3.actor() != null && !t3.actor().HasAttr(AttrType.OIL_COVERED,AttrType.SLIMED)){
                                     if(t3.actor().IsBurning()){
                                         t3.actor().ApplyBurning();
                                     }
                                     else{
                                         t3.actor().attrs[AttrType.OIL_COVERED] = 1;
                                         B.Add(t3.actor().YouAre() + " covered in oil. ",t3.actor());
                                         if(t3.actor() == player){
                                             Help.TutorialTip(TutorialTopic.Oiled);
                                         }
                                     }
                                 }
                             }
                             if(flaming_barrel){
                                 current.ApplyEffect(DamageType.FIRE);
                             }
                         }
                         if(torch){
                             current.AddFeature(FeatureType.FIRE);
                         }
                         user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 0;
                     }
                     else{
                         if(wand){
                             return true;
                         }
                         return false;
                     }
                 }
             }
         }
     }
     else{
         return false;
     }
     return true;
 }
コード例 #3
0
 public bool KnockObjectBack(Actor a,List<Tile> line,int knockback_strength,Actor damage_source)
 {
     if(knockback_strength == 0){ //note that TURN_INTO_CORPSE should be set for 'a' - therefore it won't be removed and we can do what we want with it.
         return a.CollideWith(a.tile());
     }
     int i=0;
     while(true){
         Tile t = line[i];
         if(t.actor() == a){
             break;
         }
         ++i;
     }
     line.RemoveRange(0,i+1);
     if(line.Count == 0){
         return a.CollideWith(a.tile());
     }
     bool immobile = a.MovementPrevented(line[0]);
     string knocked_back_message = "";
     if(!a.HasAttr(AttrType.TELEKINETICALLY_THROWN,AttrType.SELF_TK_NO_DAMAGE) && !immobile && player.CanSee(a)){ //if the player can see it now, don't check CanSee later.
         knocked_back_message = a.YouAre() + " knocked back. ";
         //B.Add(a.YouAre() + " knocked back. ",a);
     }
     int dice = 1;
     int damage_dice_to_other = 1;
     if(a.HasAttr(AttrType.TELEKINETICALLY_THROWN)){
         dice = 3;
         damage_dice_to_other = 3;
     }
     if(a.HasAttr(AttrType.SELF_TK_NO_DAMAGE)){
         dice = 0;
     }
     if(a.type == ActorType.SPORE_POD){
         dice = 0;
         damage_dice_to_other = 0;
     }
     while(knockback_strength > 1){ //if the knockback strength is greater than 1, you're passing *over* at least one tile.
         Tile t = line[0];
         line.RemoveAt(0);
         immobile = a.MovementPrevented(t);
         if(immobile){
             if(player.CanSee(a.tile())){
                 B.Add(a.YouVisibleAre() + " knocked about. ",a);
             }
             if(a.type == ActorType.SPORE_POD){
                 return true;
             }
             return a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"crashing into the floor");
         }
         if(!t.passable){
             string deathstringname = t.AName(false);
             if(t.Is(TileType.CRACKED_WALL,TileType.DOOR_C,TileType.HIDDEN_DOOR) && !a.HasAttr(AttrType.SMALL)){
                 string tilename = t.TheName(true);
                 if(t.type == TileType.HIDDEN_DOOR){
                     tilename = "a hidden door";
                     t.Toggle(null);
                 }
                 if(player.CanSee(a.tile())){
                     B.Add(a.YouVisibleAre() + " knocked through " + tilename + ". ",a,t);
                 }
                 else{
                     B.Add(knocked_back_message);
                 }
                 knocked_back_message = "";
                 //knockback_strength -= 2; //removing the distance modification for now
                 t.Toggle(null);
                 a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"slamming into " + deathstringname);
                 a.Move(t.row,t.col);
                 if(a.HasAttr(AttrType.BLEEDING) && !a.HasAttr(AttrType.SHIELDED,AttrType.INVULNERABLE,AttrType.SELF_TK_NO_DAMAGE)){
                     if(a.type == ActorType.HOMUNCULUS){
                         if(R.CoinFlip()){
                             t.AddFeature(FeatureType.OIL);
                         }
                     }
                     else{
                         if(t.symbol == '.' && t.color == Color.White && R.CoinFlip()){
                             t.color = a.BloodColor();
                         }
                     }
                 }
             }
             else{
                 if(player.CanSee(a.tile())){
                     B.Add(a.YouVisibleAre() + " knocked into " + t.TheName(true) + ". ",a,t);
                 }
                 else{
                     B.Add(knocked_back_message);
                 }
                 knocked_back_message = "";
                 if(a.type != ActorType.SPORE_POD){
                     Color blood = a.BloodColor();
                     if(blood != Color.Black && R.CoinFlip() && t.Is(TileType.WALL) && !a.HasAttr(AttrType.SHIELDED,AttrType.INVULNERABLE,AttrType.SELF_TK_NO_DAMAGE)){
                         t.color = blood;
                     }
                     a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"slamming into " + deathstringname);
                 }
                 if(!a.HasAttr(AttrType.SMALL)){
                     t.Bump(a.DirectionOf(t));
                 }
                 a.CollideWith(a.tile());
                 return !a.HasAttr(AttrType.CORPSE);
             }
         }
         else{
             if(t.actor() != null){
                 if(player.CanSee(a.tile()) || player.CanSee(t)){
                     B.Add(a.YouVisibleAre() + " knocked into " + t.actor().TheName(true) + ". ",a,t.actor());
                 }
                 else{
                     B.Add(knocked_back_message);
                 }
                 knocked_back_message = "";
                 string actorname = t.actor().AName(false);
                 string actorname2 = a.AName(false);
                 if(t.actor().type != ActorType.SPORE_POD && !t.actor().HasAttr(AttrType.SELF_TK_NO_DAMAGE)){
                     t.actor().TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(damage_dice_to_other,6),damage_source,"colliding with " + actorname2);
                 }
                 if(a.type != ActorType.SPORE_POD){
                     a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"colliding with " + actorname);
                 }
                 a.CollideWith(a.tile());
                 return !a.HasAttr(AttrType.CORPSE);
             }
             else{
                 if(t.Is(FeatureType.WEB) && !a.HasAttr(AttrType.SMALL)){
                     t.RemoveFeature(FeatureType.WEB);
                 }
                 a.Move(t.row,t.col,false);
                 if(t.Is(FeatureType.WEB) && a.HasAttr(AttrType.SMALL) && !a.HasAttr(AttrType.SLIMED,AttrType.OIL_COVERED,AttrType.BURNING)){
                     knockback_strength = 0;
                 }
                 if(a.HasAttr(AttrType.BLEEDING) && !a.HasAttr(AttrType.SHIELDED,AttrType.INVULNERABLE,AttrType.SELF_TK_NO_DAMAGE)){
                     if(a.type == ActorType.HOMUNCULUS){
                         if(R.CoinFlip()){
                             t.AddFeature(FeatureType.OIL);
                         }
                     }
                     else{
                         if(t.symbol == '.' && t.color == Color.White && R.CoinFlip()){
                             t.color = a.BloodColor();
                         }
                     }
                 }
             }
         }
         M.Draw();
         knockback_strength--;
     }
     if(knockback_strength < 1){
         return !a.HasAttr(AttrType.CORPSE);
     }
     bool slip = false;
     int extra_slip_tiles = -1;
     bool slip_message_printed = false;
     do{
         Tile t = line[0];
         line.RemoveAt(0);
         immobile = a.MovementPrevented(t);
         if(immobile){
             if(player.CanSee(a.tile())){
                 B.Add(a.YouVisibleAre() + " knocked about. ",a);
             }
             if(a.type == ActorType.SPORE_POD){
                 return true;
             }
             return a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"crashing into the floor");
         }
         if(!t.passable){
             string deathstringname = t.AName(false);
             if(t.Is(TileType.CRACKED_WALL,TileType.DOOR_C,TileType.HIDDEN_DOOR) && !a.HasAttr(AttrType.SMALL)){
                 string tilename = t.TheName(true);
                 if(t.type == TileType.HIDDEN_DOOR){
                     tilename = "a hidden door";
                     t.Toggle(null);
                 }
                 if(player.CanSee(a.tile())){
                     B.Add(a.YouVisibleAre() + " knocked through " + tilename + ". ",a,t);
                 }
                 else{
                     B.Add(knocked_back_message);
                 }
                 knocked_back_message = "";
                 t.Toggle(null);
                 a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"slamming into " + deathstringname);
                 a.Move(t.row,t.col);
                 if(a.HasAttr(AttrType.BLEEDING) && !a.HasAttr(AttrType.SHIELDED,AttrType.INVULNERABLE,AttrType.SELF_TK_NO_DAMAGE)){
                     if(a.type == ActorType.HOMUNCULUS){
                         if(R.CoinFlip()){
                             t.AddFeature(FeatureType.OIL);
                         }
                     }
                     else{
                         if(t.symbol == '.' && t.color == Color.White && R.CoinFlip()){
                             t.color = a.BloodColor();
                         }
                     }
                 }
                 return !a.HasAttr(AttrType.CORPSE);
             }
             else{
                 if(player.CanSee(a.tile())){
                     B.Add(a.YouVisibleAre() + " knocked into " + t.TheName(true) + ". ",a,t);
                 }
                 else{
                     B.Add(knocked_back_message);
                 }
                 knocked_back_message = "";
                 if(a.type != ActorType.SPORE_POD){
                     Color blood = a.BloodColor();
                     if(blood != Color.Black && R.CoinFlip() && t.Is(TileType.WALL) && !a.HasAttr(AttrType.SHIELDED,AttrType.INVULNERABLE,AttrType.SELF_TK_NO_DAMAGE)){
                         t.color = blood;
                     }
                     a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"slamming into " + deathstringname);
                 }
                 if(!a.HasAttr(AttrType.SMALL)){
                     t.Bump(a.DirectionOf(t));
                 }
                 a.CollideWith(a.tile());
                 return !a.HasAttr(AttrType.CORPSE);
             }
         }
         else{
             if(t.actor() != null){
                 if(player.CanSee(a.tile()) || player.CanSee(t)){
                     B.Add(a.YouVisibleAre() + " knocked into " + t.actor().TheName(true) + ". ",a,t.actor());
                 }
                 else{
                     B.Add(knocked_back_message);
                 }
                 knocked_back_message = "";
                 string actorname = t.actor().AName(false);
                 string actorname2 = a.AName(false);
                 if(t.actor().type != ActorType.SPORE_POD && !t.actor().HasAttr(AttrType.SELF_TK_NO_DAMAGE)){
                     t.actor().TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(damage_dice_to_other,6),damage_source,"colliding with " + actorname2);
                 }
                 if(a.type != ActorType.SPORE_POD){
                     a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"colliding with " + actorname);
                 }
                 a.CollideWith(a.tile());
                 return !a.HasAttr(AttrType.CORPSE);
             }
             else{
                 slip = false;
                 if(t.IsSlippery()){
                     B.Add(knocked_back_message);
                     knocked_back_message = "";
                     slip = true;
                     if(!slip_message_printed){
                         slip_message_printed = true;
                         B.Add(a.You("slide") + "! ");
                     }
                 }
                 else{
                     if(extra_slip_tiles > 0){
                         extra_slip_tiles--;
                     }
                     if(extra_slip_tiles == -1 && a.HasAttr(AttrType.SLIMED,AttrType.OIL_COVERED) && !t.IsWater()){
                         B.Add(knocked_back_message);
                         knocked_back_message = "";
                         extra_slip_tiles = 2;
                         if(!slip_message_printed){
                             slip_message_printed = true;
                             B.Add(a.You("slide") + "! ");
                         }
                     }
                 }
                 /*if(extra_slip_tiles > 0){
                     extra_slip_tiles--;
                 }
                 if(t.IsSlippery()){
                     B.Add(knocked_back_message);
                     knocked_back_message = "";
                     slip = true;
                     if(!slip_message_printed){
                         slip_message_printed = true;
                         B.Add(a.You("slide") + "! ");
                     }
                 }
                 else{
                     if(extra_slip_tiles == -1 && a.HasAttr(AttrType.SLIMED,AttrType.OIL_COVERED)){
                         B.Add(knocked_back_message);
                         knocked_back_message = "";
                         extra_slip_tiles = 2;
                         if(!slip_message_printed){
                             slip_message_printed = true;
                             B.Add(a.You("slide") + "! ");
                         }
                     }
                 }*/
                 bool interrupted = false;
                 if(t.inv != null && t.inv.type == ConsumableType.DETONATION){ //this will cause a new knockback effect and end the current one
                     B.Add(knocked_back_message);
                     knocked_back_message = "";
                     interrupted = true;
                 }
                 if(t.IsTrap()){
                     if(t.type == TileType.FLING_TRAP){ //otherwise you'd teleport around, continuing to slide from your previous position.
                         interrupted = true;
                     }
                     B.Add(knocked_back_message);
                     knocked_back_message = "";
                 }
                 if(t.Is(FeatureType.WEB) && !a.HasAttr(AttrType.SMALL)){
                     t.RemoveFeature(FeatureType.WEB);
                 }
                 a.Move(t.row,t.col);
                 if(a.HasAttr(AttrType.BLEEDING) && !a.HasAttr(AttrType.SHIELDED,AttrType.INVULNERABLE,AttrType.SELF_TK_NO_DAMAGE)){
                     if(a.type == ActorType.HOMUNCULUS){
                         if(R.CoinFlip()){
                             t.AddFeature(FeatureType.OIL);
                         }
                     }
                     else{
                         if(t.symbol == '.' && t.color == Color.White && R.CoinFlip()){
                             t.color = a.BloodColor();
                         }
                     }
                 }
                 if(a.HasAttr(AttrType.FROZEN)){
                     interrupted = true;
                 }
                 if(a.HasAttr(AttrType.SMALL) && t.Is(FeatureType.WEB) && !a.HasAttr(AttrType.SLIMED,AttrType.OIL_COVERED,AttrType.BURNING)){
                     B.Add(knocked_back_message);
                     interrupted = true;
                 }
                 else{
                     if(a.tile().IsWater()){
                         interrupted = true;
                     }
                     B.Add(knocked_back_message);
                     a.CollideWith(a.tile());
                 }
                 knocked_back_message = "";
                 if(interrupted){
                     return !a.HasAttr(AttrType.CORPSE);
                 }
             }
         }
         M.Draw();
     }
     while(slip || extra_slip_tiles > 0);
     if(knocked_back_message != ""){
         B.Add(knocked_back_message); //this probably never happens
     }
     return !a.HasAttr(AttrType.CORPSE);
 }
コード例 #4
0
ファイル: Item.cs プロジェクト: ptrefall/ForaysIntoNorrendrin
 public bool Use(Actor user,List<Tile> line)
 {
     bool used = true;
     bool IDed = true;
     switch(type){
     case ConsumableType.HEALING:
         user.curhp = user.maxhp;
         B.Add(user.Your() + " wounds are healed completely. ",user);
         break;
     case ConsumableType.REGENERATION:
     {
         if(user == player){
             B.Add("Your blood tingles. ");
         }
         else{
             B.Add(user.the_name + " looks energized. ",user);
         }
         user.attrs[AttrType.REGENERATING]++;
         int duration = 100;
         Q.Add(new Event(user,duration*100,AttrType.REGENERATING));
         break;
     }
     case ConsumableType.STONEFORM:
     {
         B.Add(user.You("transform") + " into a being of animated stone. ",user);
         int duration = R.Roll(2,20) + 20;
         List<AttrType> attributes = new List<AttrType>{AttrType.REGENERATING,AttrType.BRUTISH_STRENGTH,AttrType.VIGOR,AttrType.SILENCE_AURA,AttrType.SHADOW_CLOAK,AttrType.CAN_DODGE,AttrType.MENTAL_IMMUNITY,AttrType.DETECTING_MONSTERS,AttrType.MYSTIC_MIND};
         foreach(AttrType at in attributes){ //in the rare case where a monster drinks this potion, it can lose these natural statuses permanently. this might eventually be fixed.
             if(user.HasAttr(at)){
                 user.attrs[at] = 0;
                 Q.KillEvents(user,at);
                 switch(at){
                 case AttrType.REGENERATING:
                     B.Add(user.You("no longer regenerate") + ". ",user);
                     break;
                 case AttrType.BRUTISH_STRENGTH:
                     B.Add(user.Your() + " brutish strength fades. ",user);
                     break;
                 case AttrType.VIGOR:
                     B.Add(user.Your() + " extraordinary speed fades. ",user);
                     break;
                 case AttrType.SILENCED:
                     B.Add(user.You("no longer radiate") + " an aura of silence. ",user);
                     break;
                 case AttrType.SHADOW_CLOAK:
                     B.Add(user.YouAre() + " no longer cloaked. ",user);
                     break;
                 case AttrType.MYSTIC_MIND:
                     B.Add(user.Your() + " consciousness returns to normal. ",user);
                     break;
                 }
             }
         }
         if(user.HasAttr(AttrType.PSEUDO_VAMPIRIC)){
             user.attrs[AttrType.LIGHT_SENSITIVE] = 0;
             user.attrs[AttrType.FLYING] = 0;
             user.attrs[AttrType.PSEUDO_VAMPIRIC] = 0;
             Q.KillEvents(user,AttrType.LIGHT_SENSITIVE);
             Q.KillEvents(user,AttrType.FLYING);
             Q.KillEvents(user,AttrType.PSEUDO_VAMPIRIC);
             B.Add(user.YouAre() + " no longer vampiric. ",user);
         }
         if(user.HasAttr(AttrType.ROOTS)){
             foreach(Event e in Q.list){
                 if(e.target == user && !e.dead){
                     if(e.attr == AttrType.IMMOBILE && e.msg.Contains("rooted to the ground")){
                         e.dead = true;
                         user.attrs[AttrType.IMMOBILE]--;
                         B.Add(user.YouAre() + " no longer rooted to the ground. ",user);
                     }
                     else{
                         if(e.attr == AttrType.BONUS_DEFENSE && e.value == 10){
                             e.dead = true; //this would break if there were other timed effects that gave the same amount of defense
                             user.attrs[AttrType.BONUS_DEFENSE] -= 10;
                         }
                         else{
                             if(e.attr == AttrType.ROOTS){
                                 e.dead = true;
                                 user.attrs[AttrType.ROOTS]--;
                             }
                         }
                     }
                 }
             }
         }
         if(user.HasAttr(AttrType.BURNING)){
             user.RefreshDuration(AttrType.BURNING,0);
         }
         user.attrs[AttrType.IMMUNE_BURNING]++;
         Q.Add(new Event(user,duration*100,AttrType.IMMUNE_BURNING));
         user.attrs[AttrType.DAMAGE_RESISTANCE]++;
         Q.Add(new Event(user,duration*100,AttrType.DAMAGE_RESISTANCE));
         user.attrs[AttrType.NONLIVING]++;
         Q.Add(new Event(user,duration*100,AttrType.NONLIVING));
         user.RefreshDuration(AttrType.STONEFORM,duration*100,user.Your() + " rocky form reverts to flesh. ",user);
         if(user == player){
             Help.TutorialTip(TutorialTopic.Stoneform);
         }
         break;
     }
     case ConsumableType.VAMPIRISM:
     {
         B.Add(user.You("become") + " vampiric. ",user);
         B.Add(user.You("rise") + " into the air. ",user);
         int duration = R.Roll(2,20) + 20;
         user.RefreshDuration(AttrType.LIGHT_SENSITIVE,duration*100);
         user.RefreshDuration(AttrType.FLYING,duration*100);
         user.attrs[AttrType.DESCENDING] = 0;
         user.RefreshDuration(AttrType.PSEUDO_VAMPIRIC,duration*100,user.YouAre() + " no longer vampiric. ",user);
         if(user == player){
             Help.TutorialTip(TutorialTopic.Vampirism);
         }
         break;
     }
     case ConsumableType.BRUTISH_STRENGTH:
     {
         if(user == player){
             B.Add("You feel a surge of strength. ");
         }
         else{
             B.Add(user.Your() + " muscles ripple. ",user);
         }
         user.RefreshDuration(AttrType.BRUTISH_STRENGTH,(R.Roll(3,6)+16)*100,user.Your() + " incredible strength wears off. ",user);
         if(user == player){
             Help.TutorialTip(TutorialTopic.BrutishStrength);
         }
         break;
     }
     case ConsumableType.ROOTS:
     {
         if(user.HasAttr(AttrType.ROOTS)){
             foreach(Event e in Q.list){
                 if(e.target == user && !e.dead){
                     if(e.attr == AttrType.IMMOBILE && e.msg.Contains("rooted to the ground")){
                         e.dead = true;
                         user.attrs[AttrType.IMMOBILE]--;
                     }
                     else{
                         if(e.attr == AttrType.BONUS_DEFENSE && e.value == 10){
                             e.dead = true; //this would break if there were other timed effects that gave 5 defense
                             user.attrs[AttrType.BONUS_DEFENSE] -= 10;
                         }
                         else{
                             if(e.attr == AttrType.ROOTS){
                                 e.dead = true;
                                 user.attrs[AttrType.ROOTS]--;
                             }
                         }
                     }
                 }
             }
             B.Add(user.Your() + " roots extend deeper into the ground. ",user);
         }
         else{
             B.Add(user.You("grow") + " roots and a hard shell of bark. ",user);
         }
         int duration = R.Roll(20) + 20;
         user.RefreshDuration(AttrType.ROOTS,duration*100);
         user.attrs[AttrType.BONUS_DEFENSE] += 10;
         Q.Add(new Event(user,duration*100,AttrType.BONUS_DEFENSE,10));
         user.attrs[AttrType.IMMOBILE]++;
         Q.Add(new Event(user,duration*100,AttrType.IMMOBILE,user.YouAre() + " no longer rooted to the ground. ",user));
         if(user == player){
             Help.TutorialTip(TutorialTopic.Roots);
         }
         if(user.HasAttr(AttrType.FLYING) && user.tile().IsTrap()){
             user.tile().TriggerTrap();
         }
         break;
     }
     case ConsumableType.HASTE:
     {
         B.Add(user.You("start") + " moving with extraordinary speed. ",user);
         int duration = (R.Roll(2,10) + 10) * 100;
         user.RefreshDuration(AttrType.CAN_DODGE,duration); //todo: dodging tip goes here
         user.RefreshDuration(AttrType.VIGOR,duration,user.Your() + " extraordinary speed fades. ",user);
         if(user == player){
             Help.TutorialTip(TutorialTopic.IncreasedSpeed);
         }
         break;
     }
     case ConsumableType.SILENCE:
     {
         B.Add("A hush falls around " + user.the_name + ". ",user);
         user.RefreshDuration(AttrType.SILENCE_AURA,(R.Roll(2,20)+20)*100,user.You("no longer radiate") + " an aura of silence. ",user);
         if(user == player){
             Help.TutorialTip(TutorialTopic.Silenced);
         }
         break;
     }
     case ConsumableType.CLOAKING:
         if(user.tile().IsLit()){
             if(user == player){
                 B.Add("You would feel at home in the shadows. ");
             }
             else{
                 B.Add("A shadow moves across " + user.the_name + ". ",user);
             }
         }
         else{
             B.Add(user.You("fade") + " away in the darkness. ",user);
         }
         user.RefreshDuration(AttrType.SHADOW_CLOAK,(R.Roll(2,20)+30)*100,user.YouAre() + " no longer cloaked. ",user);
         break;
     case ConsumableType.MYSTIC_MIND:
     {
         B.Add(user.Your() + " mind expands. ",user);
         int duration = R.Roll(2,20)+60;
         user.attrs[AttrType.ASLEEP] = 0;
         //user.RefreshDuration(AttrType.MAGICAL_DROWSINESS,0);
         user.RefreshDuration(AttrType.CONFUSED,0);
         user.RefreshDuration(AttrType.STUNNED,0);
         user.RefreshDuration(AttrType.ENRAGED,0);
         user.RefreshDuration(AttrType.MENTAL_IMMUNITY,duration*100);
         user.RefreshDuration(AttrType.DETECTING_MONSTERS,duration*100);
         user.RefreshDuration(AttrType.MYSTIC_MIND,duration*100,user.Your() + " consciousness returns to normal. ",user);
         if(user == player){
             Help.TutorialTip(TutorialTopic.MysticMind);
         }
         break;
     }
     case ConsumableType.BLINKING:
     {
         List<Tile> tiles = user.TilesWithinDistance(8).Where(x => x.passable && x.actor() == null && user.ApproximateEuclideanDistanceFromX10(x) >= 45);
         if(tiles.Count > 0 && !user.HasAttr(AttrType.IMMOBILE)){
             Tile t = tiles.Random();
             B.Add(user.You("step") + " through a rip in reality. ",M.tile[user.p],t);
             user.AnimateStorm(2,3,4,'*',Color.DarkMagenta);
             user.Move(t.row,t.col);
             M.Draw();
             user.AnimateStorm(2,3,4,'*',Color.DarkMagenta);
         }
         else{
             B.Add("Nothing happens. ",user);
             IDed = false;
         }
         break;
     }
     case ConsumableType.PASSAGE:
     {
         if(user.HasAttr(AttrType.IMMOBILE)){
             B.Add("Nothing happens. ",user);
             IDed = false;
             break;
         }
         List<int> valid_dirs = new List<int>();
         foreach(int dir in U.FourDirections){
             Tile t = user.TileInDirection(dir);
             if(t != null && t.Is(TileType.WALL,TileType.CRACKED_WALL,TileType.WAX_WALL,TileType.DOOR_C,TileType.HIDDEN_DOOR,TileType.STONE_SLAB)){
                 while(!t.passable){
                     if(t.row == 0 || t.row == Global.ROWS-1 || t.col == 0 || t.col == Global.COLS-1){
                         break;
                     }
                     t = t.TileInDirection(dir);
                 }
                 if(t.passable){
                     valid_dirs.Add(dir);
                 }
             }
         }
         if(valid_dirs.Count > 0){
             int dir = valid_dirs.Random();
             Tile t = user.TileInDirection(dir);
             colorchar ch = new colorchar(Color.Cyan,'!');
             switch(user.DirectionOf(t)){
             case 8:
             case 2:
                 ch.c = '|';
                 break;
             case 4:
             case 6:
                 ch.c = '-';
                 break;
             }
             List<Tile> tiles = new List<Tile>();
             List<colorchar> memlist = new List<colorchar>();
             Screen.CursorVisible = false;
             Tile last_wall = null;
             while(!t.passable){
                 tiles.Add(t);
                 memlist.Add(Screen.MapChar(t.row,t.col));
                 Screen.WriteMapChar(t.row,t.col,ch);
                 Game.GLUpdate();
                 Thread.Sleep(35);
                 last_wall = t;
                 t = t.TileInDirection(dir);
             }
             Input.FlushInput();
             if(t.actor() == null){
                 int r = user.row;
                 int c = user.col;
                 user.Move(t.row,t.col);
                 Screen.WriteMapChar(r,c,M.VisibleColorChar(r,c));
                 Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col));
                 int idx = 0;
                 foreach(Tile tile in tiles){
                     Screen.WriteMapChar(tile.row,tile.col,memlist[idx++]);
                     Game.GLUpdate();
                     Thread.Sleep(35);
                 }
                 Input.FlushInput();
                 B.Add(user.You("travel") + " through the passage. ",user,t);
             }
             else{
                 Tile destination = null;
                 List<Tile> adjacent = t.TilesAtDistance(1).Where(x=>x.passable && x.actor() == null && x.DistanceFrom(last_wall) == 1);
                 if(adjacent.Count > 0){
                     destination = adjacent.Random();
                 }
                 else{
                     foreach(Tile tile in M.ReachableTilesByDistance(t.row,t.col,false)){
                         if(tile.actor() == null){
                             destination = tile;
                             break;
                         }
                     }
                 }
                 if(destination != null){
                     int r = user.row;
                     int c = user.col;
                     user.Move(destination.row,destination.col);
                     Screen.WriteMapChar(r,c,M.VisibleColorChar(r,c));
                     Screen.WriteMapChar(destination.row,destination.col,M.VisibleColorChar(destination.row,destination.col));
                     int idx = 0;
                     foreach(Tile tile in tiles){
                         Screen.WriteMapChar(tile.row,tile.col,memlist[idx++]);
                         Game.GLUpdate();
                         Thread.Sleep(35);
                     }
                     Input.FlushInput();
                     B.Add(user.You("travel") + " through the passage. ",user,destination);
                 }
                 else{
                     B.Add("Something blocks " + user.Your() + " movement through the passage. ",user);
                 }
             }
         }
         else{
             B.Add("Nothing happens. ",user);
             IDed = false;
         }
         break;
     }
     case ConsumableType.TIME:
         if(user == player){
             B.Add("Time stops for a moment. ",user);
         }
         else{
             B.Add("Time warps around " + user.the_name + "! ",user);
             B.PrintAll();
         }
         if(Fire.fire_event == null){ //this prevents fire from updating while time is frozen
             Fire.fire_event = new Event(0,EventType.FIRE);
             Fire.fire_event.tiebreaker = 0;
             Q.Add(Fire.fire_event);
         }
         Q.turn -= 200;
         break;
     case ConsumableType.KNOWLEDGE:
     {
         if(user == player){
             B.Add("Knowledge fills your mind. ");
             Event hiddencheck = null;
             foreach(Event e in Q.list){
                 if(!e.dead && e.type == EventType.CHECK_FOR_HIDDEN){
                     hiddencheck = e;
                     break;
                 }
             }
             int max_dist = 0;
             List<Tile> last_tiles = new List<Tile>();
             foreach(Tile t in M.ReachableTilesByDistance(user.row,user.col,true,TileType.STONE_SLAB,TileType.DOOR_C,TileType.STALAGMITE,TileType.RUBBLE,TileType.HIDDEN_DOOR)){
                 if(t.type != TileType.FLOOR){
                     t.seen = true;
                     if(t.type != TileType.WALL){
                         t.revealed_by_light = true;
                     }
                     if(t.IsTrap() || t.Is(TileType.HIDDEN_DOOR)){
                         if(hiddencheck != null){
                             hiddencheck.area.Remove(t);
                         }
                     }
                     if(t.IsTrap()){
                         t.name = Tile.Prototype(t.type).name;
                         t.a_name = Tile.Prototype(t.type).a_name;
                         t.the_name = Tile.Prototype(t.type).the_name;
                         t.symbol = Tile.Prototype(t.type).symbol;
                         t.color = Tile.Prototype(t.type).color;
                     }
                     if(t.Is(TileType.HIDDEN_DOOR)){
                         t.Toggle(null);
                     }
                     colorchar ch2 = Screen.BlankChar();
                     if(t.inv != null){
                         t.inv.revealed_by_light = true;
                         ch2.c = t.inv.symbol;
                         ch2.color = t.inv.color;
                         M.last_seen[t.row,t.col] = ch2;
                     }
                     else{
                         if(t.features.Count > 0){
                             ch2 = t.FeatureVisual();
                             M.last_seen[t.row,t.col] = ch2;
                         }
                         else{
                             ch2.c = t.symbol;
                             ch2.color = t.color;
                             if(ch2.c == '#' && ch2.color == Color.RandomGlowingFungus){
                                 ch2.color = Color.Gray;
                             }
                             M.last_seen[t.row,t.col] = ch2;
                         }
                     }
                     Screen.WriteMapChar(t.row,t.col,t.symbol,Color.RandomRainbow);
                     //Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col));
                     if(user.DistanceFrom(t) > max_dist){
                         max_dist = user.DistanceFrom(t);
                         Game.GLUpdate();
                         Thread.Sleep(10);
                         while(last_tiles.Count > 0){
                             Tile t2 = last_tiles.RemoveRandom();
                             Screen.WriteMapChar(t2.row,t2.col,M.last_seen[t2.row,t2.col]);
                             //Screen.WriteMapChar(t2.row,t2.col,M.VisibleColorChar(t2.row,t2.col));
                         }
                     }
                     last_tiles.Add(t);
                 }
             }
             if(user.inv.Count > 0){
                 foreach(Item i in user.inv){
                     identified[i.type] = true;
                     if(i.NameOfItemType() == "wand"){
                         i.other_data = -1;
                     }
                 }
             }
         }
         else{
             B.Add(user.the_name + " looks more knowledgeable. ",user);
         }
         break;
     }
     case ConsumableType.SUNLIGHT:
         if(M.wiz_lite == false){
             B.Add("The air itself seems to shine. ");
             M.wiz_lite = true;
             M.wiz_dark = false;
             Q.KillEvents(null,EventType.NORMAL_LIGHTING);
             Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING));
         }
         else{
             B.Add("The air grows even brighter for a moment. ");
             Q.KillEvents(null,EventType.NORMAL_LIGHTING);
             Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING));
         }
         break;
     case ConsumableType.DARKNESS:
         if(M.wiz_dark == false){
             B.Add("The air itself grows dark. ");
             if(player.light_radius > 0){
                 B.Add("Your light is extinguished! ");
             }
             M.wiz_dark = true;
             M.wiz_lite = false;
             Q.KillEvents(null,EventType.NORMAL_LIGHTING);
             Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING));
         }
         else{
             B.Add("The air grows even darker for a moment. ");
             Q.KillEvents(null,EventType.NORMAL_LIGHTING);
             Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING));
         }
         break;
     case ConsumableType.RENEWAL:
     {
         B.Add("A glow envelops " + user.the_name + ". ",user);
         //B.Add("A glow envelops " + user.Your() + " equipment. ",user);
         bool repaired = false;
         foreach(EquipmentStatus eqstatus in Enum.GetValues(typeof(EquipmentStatus))){
             foreach(Weapon w in user.weapons){
                 if(w.status[eqstatus]){
                     repaired = true;
                     w.status[eqstatus] = false;
                 }
             }
             foreach(Armor a in user.armors){
                 if(a.status[eqstatus]){
                     repaired = true;
                     a.status[eqstatus] = false;
                 }
             }
         }
         if(repaired){
             B.Add(user.Your() + " equipment looks as good as new! ",user);
         }
         if(user.HasAttr(AttrType.SLIMED)){
             B.Add(user.YouAre() + " no longer covered in slime. ",user);
             user.attrs[AttrType.SLIMED] = 0;
         }
         if(user.HasAttr(AttrType.OIL_COVERED)){
             B.Add(user.YouAre() + " no longer covered in oil. ",user);
             user.attrs[AttrType.OIL_COVERED] = 0;
         }
         int recharged = 0;
         foreach(Item i in user.inv){
             if(i.NameOfItemType() == "wand"){
                 i.charges++;
                 recharged++;
             }
         }
         if(recharged > 0){
             if(recharged == 1){
                 B.Add("The glow charges " + user.Your() + " wand. ",user);
             }
             else{
                 B.Add("The glow charges " + user.Your() + " wands. ",user);
             }
         }
         break;
     }
     case ConsumableType.CALLING:
     {
         bool found = false;
         if(user == player){
             for(int dist = 1;dist < Math.Max(Global.ROWS,Global.COLS);++dist){
                 List<Tile> tiles = user.TilesAtDistance(dist).Where(x=>x.actor() != null && !x.actor().HasAttr(AttrType.IMMOBILE));
                 if(tiles.Count > 0){
                     Actor a = tiles.Random().actor();
                     Tile t2 = user.TileInDirection(user.DirectionOf(a));
                     if(t2.passable && t2.actor() == null){
                         B.Add("The scroll calls " + a.a_name + " to you. ");
                         a.Move(t2.row,t2.col);
                         found = true;
                         break;
                     }
                     foreach(Tile t in M.ReachableTilesByDistance(user.row,user.col,false)){
                         if(t.actor() == null){
                             B.Add("The scroll calls " + a.a_name + " to you. ");
                             a.Move(t.row,t.col);
                             found = true;
                             break;
                         }
                     }
                     if(found){
                         break;
                     }
                 }
             }
         }
         else{
             if(!player.HasAttr(AttrType.IMMOBILE) && user.DistanceFrom(player) > 1){
                 Tile t2 = user.TileInDirection(user.DirectionOf(player));
                 if(t2.passable && t2.actor() == null){
                     B.Add("The scroll calls you to " + user.TheName(true) + ". ");
                     player.Move(t2.row,t2.col);
                     found = true;
                 }
                 if(!found){
                     foreach(Tile t in M.ReachableTilesByDistance(user.row,user.col,false)){
                         if(t.actor() == null){
                             B.Add("The scroll calls you to " + user.TheName(true) + ". ");
                             player.Move(t.row,t.col);
                             found = true;
                             break;
                         }
                     }
                 }
             }
         }
         if(!found){
             B.Add("Nothing happens. ",user);
             IDed = false;
         }
         break;
     }
     case ConsumableType.TRAP_CLEARING:
     {
         List<Tile> traps = new List<Tile>();
         {
             List<Tile>[] traparray = new List<Tile>[5];
             for(int i=0;i<5;++i){
                 traparray[i] = new List<Tile>();
             }
             for(int i=0;i<=12;++i){
                 foreach(Tile t in user.TilesAtDistance(i)){ //all this ensures that the traps go off in the best order
                     switch(t.type){
                     case TileType.ALARM_TRAP:
                     case TileType.TELEPORT_TRAP:
                     case TileType.ICE_TRAP:
                     case TileType.BLINDING_TRAP:
                     case TileType.SHOCK_TRAP:
                     case TileType.FIRE_TRAP:
                     case TileType.SCALDING_OIL_TRAP:
                         traparray[0].Add(t);
                         break;
                     case TileType.POISON_GAS_TRAP:
                     case TileType.GRENADE_TRAP:
                         traparray[1].Add(t);
                         break;
                     case TileType.SLIDING_WALL_TRAP:
                     case TileType.PHANTOM_TRAP:
                         traparray[2].Add(t);
                         break;
                     case TileType.LIGHT_TRAP:
                     case TileType.DARKNESS_TRAP:
                         traparray[3].Add(t);
                         break;
                     case TileType.FLING_TRAP:
                     case TileType.STONE_RAIN_TRAP:
                         traparray[4].Add(t);
                         break;
                     }
                 }
             }
             for(int i=0;i<5;++i){
                 foreach(Tile t in traparray[i]){
                     traps.Add(t);
                 }
             }
         }
         if(traps.Count > 0){
             B.Add("*CLICK*. ");
             foreach(Tile t in traps){
                 t.TriggerTrap(false);
             }
         }
         else{
             B.Add("Nothing happens. ",user);
             IDed = false;
         }
         break;
     }
     case ConsumableType.ENCHANTMENT:
     {
         if(user == player){
             EnchantmentType ench = (EnchantmentType)R.Between(0,4);
             while(ench == user.EquippedWeapon.enchantment){
                 ench = (EnchantmentType)R.Between(0,4);
             }
             B.Add("Your " + user.EquippedWeapon.NameWithEnchantment() + " glows brightly! ");
             user.EquippedWeapon.enchantment = ench;
             B.Add("Your " + user.EquippedWeapon.NameWithoutEnchantment() + " is now a " + user.EquippedWeapon.NameWithEnchantment() + "! ");
         }
         else{
             B.Add("Nothing happens. ",user);
             IDed = false;
         }
         break;
     }
     case ConsumableType.THUNDERCLAP:
     {
         B.Add("Thunder crashes! ",user);
         var scr = Screen.GetCurrentMap();
         List<Tile>[] printed = new List<Tile>[13];
         Color leading_edge_color = Color.White;
         Color trail_color = Color.DarkCyan;
         if(Global.LINUX && !Screen.GLMode){
             leading_edge_color = Color.Gray;
         }
         for(int dist=0;dist<=12;++dist){
             printed[dist] = new List<Tile>();
             foreach(Tile t in user.TilesAtDistance(dist)){
                 if(t.seen && user.HasLOE(t)){
                     printed[dist].Add(t);
                 }
             }
             foreach(Tile t in printed[dist]){
                 colorchar cch = M.VisibleColorChar(t.row,t.col);
                 cch.bgcolor = leading_edge_color;
                 if(cch.color == leading_edge_color){
                     cch.color = Color.Black;
                 }
                 Screen.WriteMapChar(t.row,t.col,cch);
             }
             if(dist > 0){
                 foreach(Tile t in printed[dist-1]){
                     colorchar cch = M.VisibleColorChar(t.row,t.col);
                     cch.bgcolor = trail_color;
                     if(cch.color == trail_color){
                         cch.color = Color.Black;
                     }
                     Screen.WriteMapChar(t.row,t.col,cch);
                 }
                 if(dist > 4){
                     foreach(Tile t in printed[dist-5]){
                         Screen.WriteMapChar(t.row,t.col,scr[t.row,t.col]);
                     }
                 }
             }
             Game.GLUpdate();
             Thread.Sleep(10);
         }
         List<Actor> actors = new List<Actor>();
         for(int dist=0;dist<=12;++dist){
             foreach(Tile t in user.TilesAtDistance(dist).Randomize()){
                 if(user.HasLOE(t)){
                     if(t.actor() != null && t.actor() != user){
                         actors.Add(t.actor());
                     }
                     t.BreakFragileFeatures();
                 }
             }
         }
         foreach(Actor a in actors){
             if(a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(4,6),user,"a scroll of thunderclap")){
                 a.ApplyStatus(AttrType.STUNNED,R.Between(5,10)*100);
             }
         }
         user.MakeNoise(12);
         break;
     }
     case ConsumableType.FIRE_RING:
     {
         List<pos> cells = new List<pos>();
         List<Tile> valid = new List<Tile>();
         foreach(Tile t in user.TilesWithinDistance(3)){
             if(t.passable && user.DistanceFrom(t) > 1 && user.HasLOE(t) && user.ApproximateEuclideanDistanceFromX10(t) < 45){
                 valid.Add(t);
                 cells.Add(t.p);
             }
         }
         if(valid.Count > 0){
             if(player.CanSee(user)){
                 B.Add("A ring of fire surrounds " + user.the_name + ". ");
             }
             else{
                 B.Add("A ring of fire appears! ",user.tile());
             }
             valid.Randomize();
             foreach(Tile t in valid){
                 t.AddFeature(FeatureType.FIRE);
             }
             Screen.AnimateMapCells(cells,new colorchar('&',Color.RandomFire));
         }
         else{
             B.Add("Nothing happens. ",user);
             IDed = false;
         }
         break;
     }
     case ConsumableType.RAGE:
     {
         B.Add("A murderous red glow cascades outward. ",user);
         List<Tile>[] printed = new List<Tile>[13];
         Color leading_edge_color = Color.Red;
         Color trail_color = Color.DarkRed;
         if(Global.LINUX && !Screen.GLMode){
             leading_edge_color = Color.DarkRed;
         }
         for(int dist=0;dist<=12;++dist){
             printed[dist] = new List<Tile>();
             foreach(Tile t in user.TilesAtDistance(dist)){
                 if(t.seen && user.HasLOS(t)){
                     printed[dist].Add(t);
                 }
             }
             foreach(Tile t in printed[dist]){
                 colorchar cch = M.VisibleColorChar(t.row,t.col);
                 cch.bgcolor = leading_edge_color;
                 if(cch.color == leading_edge_color){
                     cch.color = Color.Black;
                 }
                 Screen.WriteMapChar(t.row,t.col,cch);
             }
             if(dist > 0){
                 foreach(Tile t in printed[dist-1]){
                     colorchar cch = M.VisibleColorChar(t.row,t.col);
                     cch.bgcolor = trail_color;
                     if(cch.color == trail_color){
                         cch.color = Color.Black;
                     }
                     Screen.WriteMapChar(t.row,t.col,cch);
                 }
             }
             Game.GLUpdate();
             Thread.Sleep(5);
         }
         int actors_affected = 0;
         string name_is = "";
         foreach(Actor a in M.AllActors()){
             if(a != user && user.DistanceFrom(a) <= 12 && user.HasLOS(a)){
                 a.ApplyStatus(AttrType.ENRAGED,R.Between(10,17)*100,false,"",a.You("calm") + " down. ",a.You("resist") + "! ");
                 actors_affected++;
                 if(player.CanSee(a)){
                     name_is = a.YouAre();
                 }
             }
         }
         if(actors_affected > 0){
             if(actors_affected == 1){
                 B.Add(name_is + " enraged! ");
             }
             else{
                 B.Add("Bloodlust fills the air. ");
             }
         }
         break;
     }
     case ConsumableType.FREEZING:
     {
         ItemUseResult orb_result = UseOrb(2,false,user,line,(t,LOE_tile,results)=>{
             Screen.AnimateExplosion(t,2,new colorchar('*',Color.RandomIce));
             List<Tile> targets = new List<Tile>();
             foreach(Tile t2 in t.TilesWithinDistance(2)){
                 if(LOE_tile.HasLOE(t2)){
                     targets.Add(t2);
                 }
             }
             while(targets.Count > 0){
                 Tile t2 = targets.RemoveRandom();
                 t2.ApplyEffect(DamageType.COLD);
                 Actor ac = t2.actor();
                 if(ac != null){
                     ac.ApplyFreezing();
                 }
             }
         });
         used = orb_result.used;
         IDed = orb_result.IDed;
         break;
     }
     case ConsumableType.FLAMES:
     {
         ItemUseResult orb_result = UseOrb(2,false,user,line,(t,LOE_tile,results)=>{
             List<Tile> area = new List<Tile>();
             List<pos> cells = new List<pos>();
             foreach(Tile tile in t.TilesWithinDistance(2)){
                 if(LOE_tile.HasLOE(tile)){
                     if(tile.passable){
                         tile.AddFeature(FeatureType.FIRE);
                     }
                     else{
                         tile.ApplyEffect(DamageType.FIRE);
                     }
                     if(tile.Is(FeatureType.FIRE)){
                         area.Add(tile);
                     }
                     cells.Add(tile.p);
                 }
             }
             Screen.AnimateMapCells(cells,new colorchar('&',Color.RandomFire));
         });
         used = orb_result.used;
         IDed = orb_result.IDed;
         break;
     }
     case ConsumableType.FOG:
     {
         ItemUseResult orb_result = UseOrb(3,false,user,line,(t,LOE_tile,results)=>{
             List<Tile> area = new List<Tile>();
             List<pos> cells = new List<pos>();
             colorchar cch = new colorchar('*',Color.Gray);
             for(int i=0;i<=3;++i){
                 foreach(Tile tile in t.TilesAtDistance(i)){
                     if(tile.passable && LOE_tile.HasLOE(tile)){
                         tile.AddFeature(FeatureType.FOG);
                         area.Add(tile);
                         cells.Add(tile.p);
                         if(tile.seen){
                             M.last_seen[tile.row,tile.col] = cch;
                         }
                     }
                 }
                 Screen.AnimateMapCells(cells,cch,40);
             }
             Q.RemoveTilesFromEventAreas(area,EventType.REMOVE_GAS);
             Event.RemoveGas(area,800,FeatureType.FOG,25);
             //Q.Add(new Event(area,600,EventType.FOG,25));
         });
         used = orb_result.used;
         IDed = orb_result.IDed;
         break;
     }
     case ConsumableType.DETONATION:
     {
         ItemUseResult orb_result = UseOrb(3,false,user,line,(t,LOE_tile,results)=>{
             LOE_tile.ApplyExplosion(3,user,"an orb of detonation");
         });
         used = orb_result.used;
         IDed = orb_result.IDed;
         break;
     }
     case ConsumableType.BREACHING:
     {
         ItemUseResult orb_result = UseOrb(5,false,user,line,(t,LOE_tile,results)=>{
             int max_dist = -1;
             foreach(Tile t2 in M.TilesByDistance(t.row,t.col,false,true)){
                 if(t.DistanceFrom(t2) > 5){
                     break;
                 }
                 if(t2.Is(TileType.WALL,TileType.WAX_WALL,TileType.STALAGMITE,TileType.CRACKED_WALL,TileType.DOOR_C)){
                     Screen.WriteMapChar(t2.row,t2.col,t2.symbol,Color.RandomBreached);
                     if(t.DistanceFrom(t2) > max_dist){
                         max_dist = t.DistanceFrom(t2);
                         Game.GLUpdate(); //todo: stalagmites - if I add them to caves, they should no longer always vanish. check for an event, maybe?
                         Thread.Sleep(50);
                     }
                 }
             }
             List<Tile> area = new List<Tile>();
             foreach(Tile tile in t.TilesWithinDistance(5)){
                 if(tile.Is(TileType.WALL,TileType.WAX_WALL,TileType.STALAGMITE,TileType.CRACKED_WALL,TileType.DOOR_C) && tile.p.BoundsCheck(M.tile,false)){
                     TileType prev_type = tile.type;
                     if(tile.Is(TileType.STALAGMITE)){
                         tile.Toggle(null,TileType.FLOOR);
                     }
                     else{
                         tile.Toggle(null,TileType.BREACHED_WALL);
                         tile.toggles_into = prev_type;
                         area.Add(tile);
                     }
                     foreach(Tile neighbor in tile.TilesWithinDistance(1)){
                         neighbor.solid_rock = false;
                     }
                 }
             }
             if(area.Count > 0){
                 Q.Add(new Event(t,area,500,EventType.BREACH));
             }
         });
         used = orb_result.used;
         IDed = orb_result.IDed;
         break;
     }
     case ConsumableType.SHIELDING:
     {
         ItemUseResult orb_result = UseOrb(1,true,user,line,(t,LOE_tile,results)=>{
             List<Tile> area = new List<Tile>();
             List<pos> cells = new List<pos>();
             List<colorchar> symbols = new List<colorchar>();
             foreach(Tile tile in t.TilesWithinDistance(1)){
                 if(tile.passable && LOE_tile.HasLOE(tile)){
                     colorchar cch = tile.visual;
                     if(tile.actor() != null){
                         if(!tile.actor().HasAttr(AttrType.SHIELDED)){
                             tile.actor().attrs[AttrType.SHIELDED] = 1;
                             B.Add(tile.actor().YouAre() + " shielded. ",tile.actor());
                         }
                         if(player.CanSee(tile.actor())){
                             cch = tile.actor().visual;
                         }
                     }
                     cch.bgcolor = Color.Blue;
                     if(Global.LINUX && !Screen.GLMode){
                         cch.bgcolor = Color.DarkBlue;
                     }
                     if(cch.color == cch.bgcolor){
                         cch.color = Color.Black;
                     }
                     if(cch.c == '.'){
                         cch.c = '+';
                     }
                     symbols.Add(cch);
                     cells.Add(tile.p);
                     area.Add(tile);
                 }
             }
             Screen.AnimateMapCells(cells,symbols,150);
             foreach(Tile tile in area){
                 if(player.CanSee(tile)){
                     B.Add("A zone of protection is created. ");
                     break;
                 }
             }
             Q.Add(new Event(area,100,EventType.SHIELDING,R.Roll(2,6)+6));
         });
         used = orb_result.used;
         IDed = orb_result.IDed;
         break;
     }
     case ConsumableType.TELEPORTAL:
     {
         ItemUseResult orb_result = UseOrb(0,false,user,line,(t,LOE_tile,results)=>{
             LOE_tile.AddFeature(FeatureType.TELEPORTAL);
             if(LOE_tile.Is(FeatureType.TELEPORTAL)){
                 Q.Add(new Event(LOE_tile,0,EventType.TELEPORTAL,100));
             }
         });
         used = orb_result.used;
         IDed = orb_result.IDed;
         break;
     }
     case ConsumableType.PAIN:
     {
         ItemUseResult orb_result = UseOrb(5,false,user,line,(t,LOE_tile,results)=>{
             List<pos> cells = new List<pos>();
             List<colorchar> symbols = new List<colorchar>();
             foreach(Tile tile in t.TilesWithinDistance(5)){
                 if(LOE_tile.HasLOE(tile)){
                     Actor a = tile.actor();
                     if(a != null){
                         if(a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(2,6),user,"an orb of pain")){
                             a.ApplyStatus(AttrType.VULNERABLE,(R.Roll(2,6)+6)*100);
                             if(a == player){
                                 Help.TutorialTip(TutorialTopic.Vulnerable);
                             }
                         }
                     }
                     symbols.Add(new colorchar('*',Color.RandomDoom));
                     /*if(tile.DistanceFrom(t) % 2 == 0){
                         symbols.Add(new colorchar('*',Color.DarkMagenta));
                     }
                     else{
                         symbols.Add(new colorchar('*',Color.DarkRed));
                     }*/
                     cells.Add(tile.p);
                 }
             }
             player.AnimateVisibleMapCells(cells,symbols,80);
         });
         used = orb_result.used;
         IDed = orb_result.IDed;
         break;
     }
     case ConsumableType.CONFUSION:
     {
         ItemUseResult orb_result = UseOrb(2,false,user,line,(t,LOE_tile,results)=>{
             List<Tile> area = new List<Tile>();
             List<pos> cells = new List<pos>();
             colorchar cch = new colorchar('*',Color.RandomConfusion);
             for(int i=0;i<=2;++i){
                 foreach(Tile tile in t.TilesAtDistance(i)){
                     if(tile.passable && LOE_tile.HasLOE(tile)){
                         tile.AddFeature(FeatureType.CONFUSION_GAS);
                         area.Add(tile);
                         cells.Add(tile.p);
                         if(tile.seen){
                             M.last_seen[tile.row,tile.col] = cch;
                         }
                     }
                 }
                 Screen.AnimateMapCells(cells,cch,40);
             }
             Q.RemoveTilesFromEventAreas(area,EventType.REMOVE_GAS);
             Event.RemoveGas(area,R.Between(7,9)*100,FeatureType.CONFUSION_GAS,20);
         });
         used = orb_result.used;
         IDed = orb_result.IDed;
         break;
     }
     case ConsumableType.BLADES:
     {
         ItemUseResult orb_result = UseOrb(1,false,user,line,(t,LOE_tile,results)=>{
             List<Tile> targets = new List<Tile>();
             foreach(Tile t2 in t.TilesWithinDistance(1)){
                 if(t2.passable && t2.actor() == null && LOE_tile.HasLOE(t2)){
                     targets.Add(t2);
                 }
             }
             targets.Randomize();
             foreach(Tile t2 in targets){
                 Actor a = Actor.Create(ActorType.BLADE,t2.row,t2.col);
                 if(a != null){
                     a.speed = 50;
                 }
             }
         });
         used = orb_result.used;
         IDed = orb_result.IDed;
         break;
     }
     case ConsumableType.DUST_STORM:
     {
         ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{
             List<Tile> area = new List<Tile>();
             List<pos> cells = new List<pos>();
             foreach(Tile neighbor in LOE_tile.TilesWithinDistance(1)){
                 if(neighbor.passable){
                     area.Add(neighbor);
                 }
             }
             List<Tile> added = new List<Tile>();
             foreach(Tile n1 in area){
                 foreach(int dir in U.FourDirections){
                     if(R.CoinFlip() && n1.TileInDirection(dir).passable){
                         added.Add(n1.TileInDirection(dir));
                     }
                 }
             }
             foreach(Tile n1 in added){
                 area.AddUnique(n1);
             }
             colorchar cch = new colorchar('*',Color.TerrainDarkGray);
             foreach(Tile t2 in area){
                 t2.AddFeature(FeatureType.THICK_DUST);
                 cells.Add(t2.p);
                 if(t2.seen){
                     M.last_seen[t2.row,t2.col] = cch;
                 }
                 Actor a = t2.actor();
                 if(a != null && t2.Is(FeatureType.THICK_DUST)){
                     if(!a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE,AttrType.BLINDSIGHT)){
                         if(a == player){
                             B.Add("Thick dust fills the air! ");
                         }
                         a.ApplyStatus(AttrType.BLIND,R.Between(1,3)*100);
                     }
                 }
             }
             Screen.AnimateMapCells(cells,cch,80);
             Q.RemoveTilesFromEventAreas(area,EventType.REMOVE_GAS);
             Event.RemoveGas(area,R.Between(20,25)*100,FeatureType.THICK_DUST,8);
         });
         used = wand_result.used;
         IDed = wand_result.IDed;
         break;
     }
     case ConsumableType.FLESH_TO_FIRE:
     {
         ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{
             Actor a = targeting.targeted.actor();
             if(a != null){
                 B.Add("Jets of flame erupt from " + a.TheName(true) + ". ",a,targeting.targeted);
                 Screen.AnimateMapCell(a.row,a.col,new colorchar('&',Color.RandomFire));
                 int dmg = (a.curhp+1)/2;
                 if(a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,dmg,user,"a wand of flesh to fire")){
                     a.ApplyBurning();
                 }
             }
             else{
                 if(targeting.targeted.Is(FeatureType.TROLL_CORPSE)){
                     B.Add("Jets of flame erupt from the troll corpse. ",a,targeting.targeted);
                     targeting.targeted.ApplyEffect(DamageType.FIRE);
                     if(targeting.targeted.Is(FeatureType.TROLL_CORPSE)){ //if it's still there because of thick gas, it still gets destroyed.
                         targeting.targeted.RemoveFeature(FeatureType.TROLL_CORPSE);
                         B.Add("The troll corpse burns to ashes! ",targeting.targeted);
                     }
                 }
                 else{
                     if(targeting.targeted.Is(FeatureType.TROLL_BLOODWITCH_CORPSE)){
                         B.Add("Jets of flame erupt from the troll bloodwitch corpse. ",a,targeting.targeted);
                         targeting.targeted.ApplyEffect(DamageType.FIRE);
                         if(targeting.targeted.Is(FeatureType.TROLL_BLOODWITCH_CORPSE)){ //if it's still there because of thick gas, it still gets destroyed.
                             targeting.targeted.RemoveFeature(FeatureType.TROLL_BLOODWITCH_CORPSE);
                             B.Add("The troll bloodwitch corpse burns to ashes! ",targeting.targeted);
                         }
                     }
                     else{
                         B.Add("Nothing happens. ",user);
                         results.IDed = false;
                     }
                 }
             }
         });
         used = wand_result.used;
         IDed = wand_result.IDed;
         break;
     }
     case ConsumableType.INVISIBILITY:
     {
         ItemUseResult wand_result = UseWand(false,false,user,line,(LOE_tile,targeting,results)=>{
             Actor a = targeting.targeted.actor();
             if(a != null){
                 B.Add(a.You("vanish",true) + " from view. ",a);
                 if(a.light_radius > 0 && !M.wiz_dark && !M.wiz_lite){
                     B.Add(a.Your() + " light still reveals " + a.Your() + " location. ",a);
                 }
                 a.RefreshDuration(AttrType.INVISIBLE,(R.Between(2,20)+30)*100,a.YouAre() + " no longer invisible. ",a);
             }
             else{
                 B.Add("Nothing happens. ",user);
                 results.IDed = false;
             }
         });
         used = wand_result.used;
         IDed = wand_result.IDed;
         break;
     }
     case ConsumableType.REACH:
     {
         ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{
             Actor a = targeting.targeted.actor();
             if(a != null && a != user){
                 user.Attack(0,a,true);
             }
             else{
                 B.Add("Nothing happens. ",user);
                 results.IDed = false;
             }
         });
         used = wand_result.used;
         IDed = wand_result.IDed;
         break;
     }
     case ConsumableType.SLUMBER:
     {
         ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{
             Actor a = targeting.targeted.actor();
             if(a != null){
                 if(a.HasAttr(AttrType.MENTAL_IMMUNITY)){
                     if(a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE)){
                         B.Add(a.You("resist") + " becoming dormant. ",a);
                     }
                     else{
                         B.Add(a.You("resist") + " falling asleep. ",a);
                     }
                 }
                 else{
                     if(a.ResistedBySpirit()){
                         if(player.HasLOS(a)){
                             if(a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE)){
                                 B.Add(a.You("resist") + " becoming dormant. ",a);
                             }
                             else{
                                 B.Add(a.You("almost fall") + " asleep. ",a);
                             }
                         }
                     }
                     else{
                         if(player.HasLOS(a)){
                             if(a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE)){
                                 B.Add(a.You("become") + " dormant. ",a);
                             }
                             else{
                                 B.Add(a.You("fall") + " asleep. ",a);
                             }
                         }
                         a.attrs[AttrType.ASLEEP] = 6 + R.Roll(4,6);
                     }
                 }
             }
             else{
                 B.Add("Nothing happens. ",user);
                 results.IDed = false;
             }
         });
         used = wand_result.used;
         IDed = wand_result.IDed;
         break;
     }
     case ConsumableType.TELEKINESIS:
     {
         ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{
             if(!SharedEffect.Telekinesis(false,user,targeting.targeted)){
                 results.used = false;
             }
         });
         used = wand_result.used;
         IDed = wand_result.IDed;
         break;
     }
     case ConsumableType.WEBS:
     {
         ItemUseResult wand_result = UseWand(true,true,user,line,(LOE_tile,targeting,results)=>{
             if(targeting.targeted == user.tile()){
                 B.Add("Nothing happens. ",user);
                 results.IDed = false;
             }
             else{
                 Screen.CursorVisible = false;
                 foreach(Tile t in targeting.line_to_targeted){
                     if(t.passable && t != user.tile()){
                         t.AddFeature(FeatureType.WEB);
                         if(t.seen){
                             Screen.WriteMapChar(t.row,t.col,';',Color.White);
                             Game.GLUpdate();
                             Thread.Sleep(15);
                         }
                     }
                 }
                 M.Draw();
             }
         });
         used = wand_result.used;
         IDed = wand_result.IDed;
         break;
     }
     case ConsumableType.BLAST_FUNGUS:
     {
         if(line == null){
             line = user.GetTargetTile(12,0,false,true);
         }
         if(line != null){
             revealed_by_light = true;
             ignored = true;
             Tile t = line.LastBeforeSolidTile();
             Actor first = user.FirstActorInLine(line);
             B.Add(user.You("fling") + " " + TheName() + ". ");
             if(first != null && first != user){
                 t = first.tile();
                 B.Add("It hits " + first.the_name + ". ",first);
             }
             line = line.ToFirstSolidTileOrActor();
             if(line.Count > 0){
                 line.RemoveAt(line.Count - 1);
             }
             int idx = 0;
             foreach(Tile tile2 in line){
                 if(tile2.seen){
                     ++idx;
                 }
                 else{
                     line = line.To(tile2);
                     if(line.Count > 0){
                         line.RemoveAt(line.Count - 1);
                     }
                     break;
                 }
             }
             if(line.Count > 0){
                 user.AnimateProjectile(line,symbol,color);
             }
             t.GetItem(this);
             //inv.Remove(i);
             t.MakeNoise(2);
             if(first != null && first != user){
                 first.player_visibility_duration = -1;
                 first.attrs[AttrType.PLAYER_NOTICED]++;
             }
             else{
                 if(t.IsTrap()){
                     t.TriggerTrap();
                 }
             }
         }
         else{
             used = false;
         }
         break;
     }
     case ConsumableType.BANDAGES:
         if(!user.HasAttr(AttrType.BANDAGED)){
             user.attrs[AttrType.BANDAGED] = 20;
             //user.recover_time = Q.turn + 100;
             B.Add(user.You("apply",false,true) + " a bandage. ",user);
         }
         else{
             B.Add(user.the_name + " can't apply another bandage yet. ",user);
             used = false;
         }
         break;
     case ConsumableType.FLINT_AND_STEEL:
     {
         int dir = -1;
         if(user == player){
             dir = user.GetDirection("Which direction? ",false,true);
         }
         else{
             dir = user.DirectionOf(player);
         }
         if(dir != -1){
             Tile t = user.TileInDirection(dir);
             B.Add(user.You("use") + " your flint & steel. ",user);
             if(t.actor() != null && t.actor().HasAttr(AttrType.OIL_COVERED) && !t.Is(FeatureType.POISON_GAS,FeatureType.THICK_DUST)){
                 t.actor().ApplyBurning();
             }
             if(!t.Is(TileType.WAX_WALL)){
                 t.ApplyEffect(DamageType.FIRE);
             }
         }
         else{
             used = false;
         }
         break;
     }
     default:
         used = false;
         break;
     }
     if(used){
         if(IDed){
             bool seen = true; //i'll try letting orbs always be IDed. keep an eye on this.
             /*bool seen = (user == player);
             if(user != player){
                 if(player.CanSee(line[0])){ //fix this line - or at least check for null/empty
                     seen = true;
                 }
                 if(user != null && player.CanSee(user)){ //heck, I could even check to see whose turn it is, if I really wanted to be hacky.
                     seen = true;
                 }
             }*/
             if(!identified[type] && seen){
                 identified[type] = true;
                 B.Add("(It was " + SingularName(true) + "!) ");
             }
         }
         else{
             if(!unIDed_name[type].Contains("{tried}")){
                 unIDed_name[type] = unIDed_name[type] + " {tried}";
             }
         }
         if(quantity > 1){
             --quantity;
         }
         else{
             if(type == ConsumableType.BANDAGES){
                 --other_data;
                 if(user != null && other_data == 0){
                     B.Add(user.You("use") + " your last bandage. ",user);
                     user.inv.Remove(this);
                 }
             }
             else{
                 if(type == ConsumableType.FLINT_AND_STEEL){
                     if(R.OneIn(3)){
                         --other_data;
                         if(user != null){
                             if(other_data == 2){
                                 B.Add("Your flint & steel shows signs of wear. ",user);
                             }
                             if(other_data == 1){
                                 B.Add("Your flint & steel is almost depleted. ",user);
                             }
                             if(other_data == 0){
                                 B.Add("Your flint & steel is used up. ",user);
                                 user.inv.Remove(this);
                             }
                         }
                     }
                 }
                 else{
                     if(NameOfItemType() == "wand"){
                         if(charges > 0){
                             --charges;
                             if(other_data >= 0){
                                 ++other_data;
                             }
                         }
                         else{
                             other_data = -1;
                         }
                     }
                     else{
                         if(user != null){
                             user.inv.Remove(this);
                         }
                     }
                 }
             }
         }
         CheckForMimic();
     }
     return used;
 }
コード例 #5
0
 public async Task<bool> Attack(int attack_idx, Actor a)
 { //returns true if attack hit
     if (await StunnedThisTurn())
     {
         return false;
     }
     //pos pos_of_target = new pos(a.row,a.col);
     AttackInfo info = AttackList.Attack(atype, attack_idx);
     if (weapons[0] != WeaponType.NO_WEAPON)
     {
         info.damage = Weapon.Damage(weapons[0]);
     }
     info.damage.source = this;
     int plus_to_hit = TotalSkill(SkillType.COMBAT);
     bool sneak_attack = false;
     if (this.IsHiddenFrom(a) || !a.CanSee(this) || (this == player && HasAttr(AttrType.SHADOW_CLOAK) && !tile().IsLit() && !a.HasAttr(AttrType.BLINDSIGHT)))
     {
         sneak_attack = true;
     }
     if (sneak_attack)
     { //sneak attacks get +25% accuracy. this usually totals 100% vs. unarmored targets.
         plus_to_hit += 25;
     }
     if (HasAttr(AttrType.BLESSED))
     {
         plus_to_hit += 10;
     }
     plus_to_hit -= a.ArmorClass() * 2;
     bool hit = a.IsHit(plus_to_hit);
     if (HasFeat(FeatType.DRIVE_BACK))
     {
         bool nowhere_to_run = true;
         int dir = DirectionOf(a);
         if (a.TileInDirection(dir).passable && a.ActorInDirection(dir) == null)
         {
             nowhere_to_run = false;
         }
         if (a.TileInDirection(RotateDirection(dir, true)).passable && a.ActorInDirection(RotateDirection(dir, true)) == null)
         {
             nowhere_to_run = false;
         }
         if (a.TileInDirection(RotateDirection(dir, false)).passable && a.ActorInDirection(RotateDirection(dir, false)) == null)
         {
             nowhere_to_run = false;
         }
         if (a.HasAttr(AttrType.FROZEN) || a.HasAttr(AttrType.NEVER_MOVES))
         {
             nowhere_to_run = true;
         }
         if (nowhere_to_run)
         {
             hit = true;
         }
     }
     bool no_armor_message = false; //no_armor_message means "don't print 'your armor blocks the attack' for misses"
     if (a.HasAttr(AttrType.DEFENSIVE_STANCE) && Global.CoinFlip())
     {
         hit = false;
         no_armor_message = true;
     }
     if ((this.tile().Is(FeatureType.FOG) || a.tile().Is(FeatureType.FOG)) && Global.CoinFlip())
     {
         hit = false;
         no_armor_message = true;
     }
     if (a.IsHiddenFrom(this) || !CanSee(a) || (a == player && a.HasAttr(AttrType.SHADOW_CLOAK) && !a.tile().IsLit() && !HasAttr(AttrType.BLINDSIGHT)))
     {
         if (Global.CoinFlip())
         {
             hit = false;
             no_armor_message = true;
         }
     }
     bool player_in_combat = false;
     if (this == player || a == player)
     {
         player_in_combat = true;
     }
     if (attack_idx == 2 && (atype == ActorType.FROSTLING || atype == ActorType.FIRE_DRAKE))
     {
         hit = true; //hack! these are the 2 'area' attacks that always hit
         player_in_combat = false;
     }
     if (a == player && atype == ActorType.DREAM_CLONE)
     {
         player_in_combat = false;
     }
     if (player_in_combat)
     {
         player.attrs[Forays.AttrType.IN_COMBAT]++;
     }
     string s = info.desc + ". ";
     if (hit)
     {
         if (HasFeat(FeatType.NECK_SNAP) && a.HasAttr(AttrType.MEDIUM_HUMANOID) && IsHiddenFrom(a))
         {
             if (!HasAttr(AttrType.RESIST_NECK_SNAP))
             {
                 B.Add(You("silently snap") + " " + a.Your() + " neck. ");
                 await a.TakeDamage(DamageType.NORMAL, DamageClass.NO_TYPE, 9001, this);
                 Q1();
                 return true;
             }
             else
             {
                 B.Add(You("silently snap") + " " + a.Your() + " neck. ");
                 B.Add("It doesn't seem to affect " + a.the_name + ". ");
             }
         }
         int dice = info.damage.dice;
         bool crit = false;
         int pos = s.IndexOf("&");
         if (pos != -1)
         {
             s = s.Substring(0, pos) + TheVisible() + s.Substring(pos + 1);
         }
         pos = s.IndexOf("^");
         if (pos != -1)
         {
             string sc = "";
             int critical_target = 20;
             if (weapons[0] == WeaponType.DAGGER)
             {
                 critical_target -= 2;
             }
             if (HasFeat(FeatType.LETHALITY))
             { //10% crit plus 5% for each 20% health the target is missing
                 critical_target -= 2;
                 int fifth = a.maxhp / 5; //uses int because it assumes everything has a multiple of 5hp
                 int totaldamage = a.maxhp - a.curhp;
                 if (fifth > 0)
                 {
                     int missing_fifths = totaldamage / fifth;
                     critical_target -= missing_fifths;
                 }
             }
             if ((info.damage.type == DamageType.NORMAL || info.damage.type == DamageType.PIERCING
             || info.damage.type == DamageType.BASHING || info.damage.type == DamageType.SLASHING)
             && Global.Roll(1, 20) >= critical_target)
             { //maybe this should become a check for physical damage - todo?
                 crit = true;
                 sc = "critically ";
             }
             s = s.Substring(0, pos) + sc + s.Substring(pos + 1);
         }
         pos = s.IndexOf("*");
         if (pos != -1)
         {
             s = s.Substring(0, pos) + a.TheVisible() + s.Substring(pos + 1);
         }
         if (sneak_attack && crit)
         {
             if (!a.HasAttr(AttrType.UNDEAD) && !a.HasAttr(AttrType.CONSTRUCT)
                 && !a.HasAttr(AttrType.PLANTLIKE) && !a.HasAttr(AttrType.BOSS_MONSTER))
             {
                 if (a.atype != ActorType.PLAYER)
                 { //being nice to the player here...
                     switch (weapons[0])
                     {
                         case WeaponType.SWORD:
                         case WeaponType.FLAMEBRAND:
                             B.Add("You run " + a.TheVisible() + " through! ");
                             break;
                         case WeaponType.MACE:
                         case WeaponType.MACE_OF_FORCE:
                             B.Add("You bash " + a.YourVisible() + " head in! ");
                             break;
                         case WeaponType.DAGGER:
                         case WeaponType.VENOMOUS_DAGGER:
                             B.Add("You pierce one of " + a.YourVisible() + " vital organs! ");
                             break;
                         case WeaponType.STAFF:
                         case WeaponType.STAFF_OF_MAGIC:
                             B.Add("You bring your staff down on " + a.YourVisible() + " head with a loud crack! ");
                             break;
                         case WeaponType.BOW:
                         case WeaponType.HOLY_LONGBOW:
                             B.Add("You choke " + a.TheVisible() + " with your bowstring! ");
                             break;
                         default:
                             break;
                     }
                     MakeNoise();
                     await a.TakeDamage(DamageType.NORMAL, DamageClass.NO_TYPE, 1337, this);
                     Q1();
                     return true;
                 }
                 else
                 { //...but not too nice
                     B.Add(AVisible() + " strikes from hiding! ");
                     B.Add("The deadly attack leaves you stunned! ");
                     int lotsofdamage = Math.Max(dice * 6, a.curhp / 2);
                     a.attrs[AttrType.STUNNED]++;
                     Q.Add(new Event(a, Global.Roll(2, 5) * 100, AttrType.STUNNED, "You are no longer stunned. "));
                     await a.TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, lotsofdamage, this, a_name);
                 }
             }
         }
         if (sneak_attack)
         {
             B.Add(YouVisible("strike") + " from hiding! ");
             if (atype != ActorType.PLAYER)
             {
                 attrs[AttrType.TURNS_VISIBLE] = -1;
                 attrs[Forays.AttrType.NOTICED]++;
             }
             else
             {
                 a.player_visibility_duration = -1;
                 a.attrs[Forays.AttrType.PLAYER_NOTICED]++;
             }
         }
         B.Add(s, this, a);
         int dmg;
         if (crit)
         {
             dmg = dice * 6;
         }
         else
         {
             dmg = Global.Roll(dice, 6);
         }
         dmg += TotalSkill(SkillType.COMBAT);
         int r = a.row;
         int c = a.col;
         bool troll = (a.atype == ActorType.TROLL || a.atype == ActorType.TROLL_SEER);
         bool mech_shield = a.HasAttr(AttrType.MECHANICAL_SHIELD);
         if (crit && mech_shield)
         {
             a.attrs[Forays.AttrType.MECHANICAL_SHIELD] = 0;
         }
         await a.TakeDamage(info.damage.type, info.damage.damclass, dmg, this, a_name);
         if (crit && mech_shield)
         {
             a.attrs[Forays.AttrType.MECHANICAL_SHIELD]++;
         }
         if (M.actor[r, c] != null)
         {
             if (HasAttr(AttrType.FIRE_HIT) || attrs[AttrType.ON_FIRE] >= 3)
             { //todo: a frostling's ranged attack shouldn't apply this
                 if (!a.HasAttr(AttrType.INVULNERABLE))
                 { //to prevent the message
                     int amount = Global.Roll(6);
                     if (!a.HasAttr(AttrType.RESIST_FIRE) || amount / a.attrs[AttrType.RESIST_FIRE] > 0)
                     { //todo i think resistance is wrong here
                         B.Add(a.YouAre() + " burned. ", a);
                     }
                     await a.TakeDamage(DamageType.FIRE, DamageClass.PHYSICAL, amount, this, a_name);
                 }
             }
         }
         if (troll && HasAttr(AttrType.FIRE_HIT) && M.tile[r, c].Is(FeatureType.TROLL_CORPSE))
         {
             M.tile[r, c].features.Remove(FeatureType.TROLL_CORPSE);
             B.Add("The troll corpse burns to ashes! ", M.tile[r, c]);
         }
         if (troll && HasAttr(AttrType.FIRE_HIT) && M.tile[r, c].Is(FeatureType.TROLL_SEER_CORPSE))
         {
             M.tile[r, c].features.Remove(FeatureType.TROLL_SEER_CORPSE);
             B.Add("The troll seer corpse burns to ashes! ", M.tile[r, c]);
         }
         if (HasAttr(AttrType.COLD_HIT) && attack_idx == 0 && M.actor[r, c] != null)
         {
             //hack: only applies to attack 0
             if (!a.HasAttr(AttrType.INVULNERABLE))
             { //to prevent the message
                 B.Add(a.YouAre() + " chilled. ", a);
                 await a.TakeDamage(DamageType.COLD, DamageClass.PHYSICAL, Global.Roll(1, 6), this, a_name);
             }
         }
         if (HasAttr(AttrType.POISON_HIT) && M.actor[r, c] != null)
         {
             if (!a.HasAttr(AttrType.UNDEAD) && !a.HasAttr(AttrType.CONSTRUCT)
             && !a.HasAttr(AttrType.POISON_HIT) && !a.HasAttr(AttrType.IMMUNE_TOXINS))
             {
                 if (a.HasAttr(AttrType.POISONED))
                 {
                     B.Add(a.YouAre() + " more poisoned. ", a);
                 }
                 else
                 {
                     B.Add(a.YouAre() + " poisoned. ", a);
                 }
                 a.attrs[AttrType.POISONED]++;
                 Q.Add(new Event(a, (Global.Roll(6) + 6) * 100, AttrType.POISONED));
             }
         }
         if (HasAttr(AttrType.PARALYSIS_HIT) && attack_idx == 1 && atype == ActorType.CARRION_CRAWLER && M.actor[r, c] != null)
         {
             if (!a.HasAttr(AttrType.IMMUNE_TOXINS))
             {
                 //hack: carrion crawler only
                 B.Add(a.YouAre() + " paralyzed. ", a);
                 a.attrs[AttrType.PARALYZED] = Global.Roll(1, 3) + 3;
             }
         }
         if (HasAttr(AttrType.FORCE_HIT) && M.actor[r, c] != null)
         {
             if (Global.OneIn(3))
             {
                 if (Global.CoinFlip())
                 {
                     await a.GetKnockedBack(this);
                 }
                 else
                 {
                     if (!a.HasAttr(AttrType.STUNNED))
                     {
                         B.Add(a.YouAre() + " stunned. ", a);
                         a.attrs[AttrType.STUNNED]++;
                         int duration = (Global.Roll(4) + 3) * 100;
                         if (crit)
                         {
                             duration += 250;
                             crit = false; //note this - don't try to use crit again after this on-hit stuff.
                         }
                         Q.Add(new Event(a, duration, AttrType.STUNNED, a.YouAre() + " no longer stunned. ", new PhysicalObject[] { a }));
                     }
                 }
             }
         }
         if (HasAttr(AttrType.DIM_VISION_HIT) && M.actor[r, c] != null)
         {
             string str = "";
             if (a.atype == ActorType.PLAYER)
             {
                 B.Add("Your vision grows weak. ");
                 str = "Your vision returns to normal. ";
             }
             //a.attrs[AttrType.DIM_VISION]++;
             //Q.Add(new Event(a,a.DurationOfMagicalEffect(Global.Roll(2,20)+20)*100,AttrType.DIM_VISION,str));
             a.GainAttrRefreshDuration(AttrType.DIM_VISION, a.DurationOfMagicalEffect(Global.Roll(2, 20) + 20) * 100, str);
         }
         if (HasAttr(AttrType.STALAGMITE_HIT))
         {
             List<Tile> tiles = new List<Tile>();
             foreach (Tile t in M.tile[r, c].TilesWithinDistance(1))
             {
                 if (t.actor() == null && (t.ttype == TileType.FLOOR || t.ttype == TileType.STALAGMITE))
                 {
                     if (Global.CoinFlip())
                     { //50% for each...
                         tiles.Add(t);
                     }
                 }
             }
             foreach (Tile t in tiles)
             {
                 if (t.ttype == TileType.STALAGMITE)
                 {
                     Q.KillEvents(t, EventType.STALAGMITE);
                 }
                 else
                 {
                     t.Toggle(this, TileType.STALAGMITE);
                 }
             }
             Q.Add(new Event(tiles, 150, EventType.STALAGMITE));
         }
         if (HasAttr(AttrType.GRAB_HIT) && M.actor[r, c] != null && !HasAttr(AttrType.GRABBING) && DistanceFrom(a) == 1)
         {
             a.attrs[Forays.AttrType.GRABBED]++;
             attrs[Forays.AttrType.GRABBING] = DirectionOf(a);
             B.Add(the_name + " grabs " + a.the_name + ". ", this, a);
         }
         if (HasAttr(AttrType.LIFE_DRAIN_HIT) && curhp < maxhp)
         {
             curhp += 10;
             if (curhp > maxhp)
             {
                 curhp = maxhp;
             }
             B.Add(YouFeel() + " restored. ", this);
         }
         if (HasAttr(AttrType.STUN_HIT) && M.actor[r, c] != null)
         {
             B.Add(a.YouAre() + " stunned. ", a);
             int duration = 550;
             if (crit)
             {
                 duration += 250;
                 crit = false;
             }
             a.GainAttrRefreshDuration(AttrType.STUNNED, duration, a.YouAre() + " no longer stunned. ", a);
         }
         if (crit && M.actor[r, c] != null)
         {
             B.Add(a.YouAre() + " stunned. ", a);
             a.GainAttrRefreshDuration(AttrType.STUNNED, 250, a.YouAre() + " no longer stunned. ", a);
         }
         if (M.actor[r, c] != null && a.atype == ActorType.SWORDSMAN)
         {
             if (a.attrs[AttrType.BONUS_COMBAT] > 0)
             {
                 B.Add(a.the_name + " returns to a defensive stance. ", a);
                 a.attrs[AttrType.BONUS_COMBAT] = 0;
             }
             a.attrs[AttrType.COOLDOWN_1]++;
             Q.Add(new Event(a, 100, AttrType.COOLDOWN_1));
         }
     }
     else
     {
         if (a.HasAttr(AttrType.DEFENSIVE_STANCE) || (a.HasFeat(FeatType.FULL_DEFENSE) && Global.CoinFlip()))
         {
             //make an attack against a random enemy next to a
             List<Actor> list = a.ActorsWithinDistance(1, true);
             list.Remove(this); //don't consider yourself or the original target
             if (list.Count > 0)
             {
                 B.Add(a.You("deflect") + " the attack. ", this, a);
                 return await Attack(attack_idx, list[Global.Roll(1, list.Count) - 1]);
             }
             //this would currently enter an infinite loop if two adjacent things used it at the same time
         }
         if (this == player || a == player || player.CanSee(this) || player.CanSee(a))
         { //didn't change this yet
             if (s == "& lunges forward and ^hits *. ")
             {
                 B.Add(the_name + " lunges forward and misses " + a.the_name + ". ");
             }
             else
             {
                 if (s == "& hits * with a blast of cold. ")
                 {
                     B.Add(the_name + " nearly hits " + a.the_name + " with a blast of cold. ");
                 }
                 else
                 {
                     if (s.Length >= 20 && s.Substring(0, 20) == "& extends a tentacle")
                     {
                         B.Add(the_name + " misses " + a.the_name + " with a tentacle. ");
                     }
                     else
                     {
                         if (HasFeat(FeatType.DRIVE_BACK))
                         {
                             B.Add(You("drive") + " " + a.TheVisible() + " back. ");
                         }
                         else
                         {
                             if (a.ArmorClass() > 0 && !no_armor_message)
                             {
                                 if (a.atype != ActorType.PLAYER)
                                 {
                                     B.Add(a.YourVisible() + " armor blocks " + YourVisible() + " attack. ");
                                 }
                                 else
                                 {
                                     int miss_chance = 25 - plus_to_hit;
                                     if (Global.Roll(miss_chance) <= Armor.Protection(a.armors[0]) * 2)
                                     {
                                         B.Add(a.YourVisible() + " armor blocks " + YourVisible() + " attack. ");
                                     }
                                     else
                                     {
                                         B.Add(YouVisible("miss", true) + " " + a.TheVisible() + ". ");
                                     }
                                 }
                             }
                             else
                             {
                                 B.Add(YouVisible("miss", true) + " " + a.TheVisible() + ". ");
                             }
                         }
                     }
                 }
             }
         }
         if (HasFeat(FeatType.DRIVE_BACK))
         {
             if (!a.HasAttr(AttrType.FROZEN) && !HasAttr(AttrType.FROZEN))
             {
                 await a.AI_Step(this, true);
                 await AI_Step(a);
             }
         }
         if (a.atype == ActorType.SWORDSMAN)
         {
             if (a.attrs[AttrType.BONUS_COMBAT] > 0)
             {
                 B.Add(a.the_name + " returns to a defensive stance. ", a);
                 a.attrs[AttrType.BONUS_COMBAT] = 0;
             }
             a.attrs[AttrType.COOLDOWN_1]++;
             Q.Add(new Event(a, 100, AttrType.COOLDOWN_1));
         }
     }
     MakeNoise();
     Q.Add(new Event(this, info.cost));
     return hit;
 }
コード例 #6
0
 public bool Attack(int attack_idx,Actor a,bool attack_is_part_of_another_action)
 {
     //returns true if attack hit
     AttackInfo info = attack[type][attack_idx];
     pos original_pos = p;
     pos target_original_pos = a.p;
     if(EquippedWeapon.type != WeaponType.NO_WEAPON){
         info = EquippedWeapon.Attack();
     }
     info.damage.source = this;
     if(a.HasFeat(FeatType.DEFLECT_ATTACK) && DistanceFrom(a) == 1){
         //Actor other = a.ActorsWithinDistance(1).Where(x=>x.DistanceFrom(this) == 1).Random();
         Actor other = a.ActorsWithinDistance(1).Where(x=>x != this).RandomOrDefault();
         if(other != a){
             B.Add(a.You("deflect") + "! ",this,a);
             return Attack(attack_idx,other,attack_is_part_of_another_action);
         }
     }
     if(!attack_is_part_of_another_action && StunnedThisTurn()){
         return false;
     }
     if(!attack_is_part_of_another_action && exhaustion == 100 && R.CoinFlip()){
         B.Add(You("fumble") + " from exhaustion. ",this);
         Q1(); //this is checked in PlayerWalk if attack_is_part_of_another_action is true
         return false;
     }
     if(!attack_is_part_of_another_action && this == player && EquippedWeapon.status[EquipmentStatus.POSSESSED] && R.CoinFlip()){
         List<Actor> actors = ActorsWithinDistance(1);
         Actor chosen = actors.RandomOrDefault();
         if(chosen != a){
             if(chosen == this){
                 B.Add("Your possessed " + EquippedWeapon.NameWithEnchantment() + " tries to attack you! ");
                 B.Add("You fight it off! "); //this is also checked in PlayerWalk if attack_is_part_of_another_action is true
                 Q1();
                 return false;
             }
             else{
                 return Attack(attack_idx,chosen);
             }
         }
     }
     bool player_in_combat = false;
     if(this == player || a == player){
         player_in_combat = true;
     }
     /*if(a == player && (type == ActorType.DREAM_WARRIOR_CLONE || type == ActorType.DREAM_SPRITE_CLONE)){
         player_in_combat = false;
     }*/
     if(player_in_combat){
         player.attrs[AttrType.IN_COMBAT]++;
     }
     if(a.HasAttr(AttrType.CAN_DODGE) && a.CanSee(this)){
         int dodge_dir = R.Roll(9);
         Tile dodge_tile = a.TileInDirection(dodge_dir);
         bool failed_to_dodge = false;
         if(HasAttr(AttrType.CONFUSED,AttrType.SLOWED,AttrType.STUNNED) && R.CoinFlip()){
             failed_to_dodge = true;
         }
         if(a.tile().Is(FeatureType.WEB) && !a.HasAttr(AttrType.BURNING,AttrType.OIL_COVERED,AttrType.SLIMED,AttrType.BRUTISH_STRENGTH)){
             failed_to_dodge = true;
         }
         if(!failed_to_dodge && dodge_tile.passable && dodge_tile.actor() == null && !a.MovementPrevented(dodge_tile) && !a.HasAttr(AttrType.PARALYZED)){
             B.Add(a.You("dodge") + " " + YourVisible() + " attack. ",this,a);
             if(player.CanSee(a)){
                 Help.TutorialTip(TutorialTopic.Dodging);
             }
             if(a == player){
                 B.DisplayNow();
                 Screen.AnimateMapCell(a.row,a.col,new colorchar('!',Color.Green),80);
             }
             a.Move(dodge_tile.row,dodge_tile.col);
             if(a != player && DistanceFrom(dodge_tile) > 1){
                 M.Draw();
                 Thread.Sleep(40);
             }
             if(!attack_is_part_of_another_action){
                 Q.Add(new Event(this,info.cost));
             }
             return false;
         }
     }
     if(a.HasFeat(FeatType.CUNNING_DODGE) && !this.HasAttr(AttrType.DODGED)){
         attrs[AttrType.DODGED]++;
         B.Add(a.You("dodge") + " " + YourVisible() + " attack. ",this,a);
         if(!attack_is_part_of_another_action){
             Q.Add(new Event(this,info.cost));
         }
         return false;
     }
     if(IsInvisibleHere() || a.IsInvisibleHere()){
         Help.TutorialTip(TutorialTopic.FightingTheUnseen);
     }
     //pos pos_of_target = new pos(a.row,a.col);
     bool a_moved_last_turn = !a.HasAttr(AttrType.TURNS_HERE);
     bool drive_back_applied = HasFeat(FeatType.DRIVE_BACK);
     if(drive_back_applied && !ConfirmsSafetyPrompts(a.tile())){
         drive_back_applied = false;
     }
     bool drive_back_nowhere_to_run = false;
     if(!attack_is_part_of_another_action && drive_back_applied){ //doesn't work while moving
         drive_back_nowhere_to_run = true;
         int dir = DirectionOf(a);
         foreach(int next_dir in new List<int>{dir,dir.RotateDir(true),dir.RotateDir(false)}){
             Tile t = a.TileInDirection(next_dir);
             if(t.passable && t.actor() == null && !a.MovementPrevented(t)){
                 drive_back_nowhere_to_run = false;
                 break;
             }
         }
         /*if(a.TileInDirection(dir).passable && a.ActorInDirection(dir) == null && !a.GrabPreventsMovement(TileInDirection(dir))){
             drive_back_nowhere_to_run = false;
         }
         if(a.TileInDirection(dir.RotateDir(true)).passable && a.ActorInDirection(dir.RotateDir(true)) == null && !a.GrabPreventsMovement(TileInDirection(dir.RotateDir(true)))){
             drive_back_nowhere_to_run = false;
         }
         if(a.TileInDirection(dir.RotateDir(false)).passable && a.ActorInDirection(dir.RotateDir(false)) == null && !a.GrabPreventsMovement(TileInDirection(dir.RotateDir(false)))){
             drive_back_nowhere_to_run = false;
         }*/
         if(a.tile().IsSlippery() && !(a.tile().Is(TileType.ICE) && a.type == ActorType.FROSTLING)){
             if(R.OneIn(5) && !HasAttr(AttrType.FLYING,AttrType.NONEUCLIDEAN_MOVEMENT) && !Is(ActorType.GIANT_SLUG,ActorType.MACHINE_OF_WAR,ActorType.MUD_ELEMENTAL)){
                 drive_back_nowhere_to_run = true;
             }
         }
         if(a.HasAttr(AttrType.FROZEN) || a.HasAttr(AttrType.IMMOBILE)){
             drive_back_nowhere_to_run = true; //todo: exception for noneuclidean monsters? i think they'll just move out of the way.
         }
     }
     bool obscured_vision_miss = false;
     {
         bool fog = false;
         bool hidden = false;
         if((this.tile().Is(FeatureType.FOG,FeatureType.THICK_DUST) || a.tile().Is(FeatureType.FOG,FeatureType.THICK_DUST))){
             fog = true;
         }
         if(a.IsHiddenFrom(this) || !CanSee(a) || (a.IsInvisibleHere() && !HasAttr(AttrType.BLINDSIGHT))){
             hidden = true;
         }
         if(!HasAttr(AttrType.DETECTING_MONSTERS) && (fog || hidden) && R.CoinFlip()){
             obscured_vision_miss = true;
         }
     }
     int plus_to_hit = TotalSkill(SkillType.COMBAT);
     bool sneak_attack = false;
     if(this.IsHiddenFrom(a) || !a.CanSee(this) || (this == player && IsInvisibleHere() && !a.HasAttr(AttrType.BLINDSIGHT))){
         sneak_attack = true;
         a.attrs[AttrType.SEES_ADJACENT_PLAYER] = 1;
         if(DistanceFrom(a) > 2 && this != player){
             sneak_attack = false; //no phantom blade sneak attacks from outside your view - but the player can sneak attack at this range with a wand of reach.
         }
     } //...insert any other changes to sneak attack calculation here...
     if(sneak_attack || HasAttr(AttrType.LUNGING_AUTO_HIT) || (EquippedWeapon == Dagger && !tile().IsLit()) || (EquippedWeapon == Staff && a_moved_last_turn) || a.HasAttr(AttrType.SWITCHING_ARMOR)){ //some attacks get +25% accuracy. this usually totals 100% vs. unarmored targets.
         plus_to_hit += 25;
     }
     plus_to_hit -= a.TotalSkill(SkillType.DEFENSE) * 3;
     bool attack_roll_hit = a.IsHit(plus_to_hit);
     bool blocked_by_armor_miss = false;
     bool blocked_by_root_shell_miss = false;
     bool mace_through_armor = false;
     if(!attack_roll_hit){
         int armor_value = a.TotalProtectionFromArmor();
         if(a != player){
             armor_value = a.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){
             bool mace = (EquippedWeapon == Mace || type == ActorType.CRUSADING_KNIGHT || type == ActorType.PHANTOM_CRUSADER);
             if(mace){
                 attack_roll_hit = true;
                 mace_through_armor = true;
             }
             else{
                 if(type == ActorType.CORROSIVE_OOZE || type == ActorType.LASHER_FUNGUS){ //this is a bit hacky, but these are the only ones that aren't stopped by armor right now.
                     attack_roll_hit = true;
                 }
                 else{
                     blocked_by_armor_miss = true;
                 }
             }
         }
         else{
             if(a.HasAttr(AttrType.ROOTS) && roll <= (armor_value + 10) * 3){ //potion of roots gives 10 defense
                 blocked_by_root_shell_miss = true;
             }
         }
     }
     bool hit = true;
     if(obscured_vision_miss){ //this calculation turned out to be pretty complicated
         hit = false;
     }
     else{
         if(blocked_by_armor_miss || blocked_by_root_shell_miss){
             hit = false;
         }
         else{
             if(drive_back_nowhere_to_run || attack_roll_hit){
                 hit = true;
             }
             else{
                 hit = false;
             }
         }
     }
     if(a.HasAttr(AttrType.GRABBED) && attrs[AttrType.GRABBING] == DirectionOf(a)){
         hit = true; //one more modifier: automatically hit things you're grabbing.
     }
     bool weapon_just_poisoned = false;
     if(!hit){
         if(blocked_by_armor_miss){
             bool initial_message_printed = false; //for better pronoun usage
             if(info.blocked != ""){
                 initial_message_printed = true;
                 string s = info.blocked + ". ";
                 int pos = -1;
                 do{
                     pos = s.IndexOf('&');
                     if(pos != -1){
                         s = s.Substring(0,pos) + TheName(true) + s.Substring(pos+1);
                     }
                 }
                 while(pos != -1);
                 //
                 do{
                     pos = s.IndexOf('*');
                     if(pos != -1){
                         s = s.Substring(0,pos) + a.TheName(true) + s.Substring(pos+1);
                     }
                 }
                 while(pos != -1);
                 B.Add(s,this,a);
             }
             if(a.HasFeat(FeatType.ARMOR_MASTERY) && !(a.type == ActorType.ALASI_SCOUT && attack_idx == 1)){
                 B.Add(a.YourVisible() + " armor blocks the attack, leaving " + TheName(true) + " off-balance. ",a,this);
                 RefreshDuration(AttrType.SUSCEPTIBLE_TO_CRITS,100);
             }
             else{
                 if(initial_message_printed){
                     B.Add(a.YourVisible() + " armor blocks the attack. ",this,a);
                 }
                 else{
                     B.Add(a.YourVisible() + " armor blocks " + YourVisible() + " attack. ",this,a);
                 }
             }
             if(a.EquippedArmor.type == ArmorType.FULL_PLATE && !HasAttr(AttrType.BRUTISH_STRENGTH)){
                 a.IncreaseExhaustion(3);
                 Help.TutorialTip(TutorialTopic.HeavyPlateArmor);
             }
         }
         else{
             if(blocked_by_root_shell_miss){
                 B.Add(a.YourVisible() + " root shell blocks " + YourVisible() + " attack. ",this,a);
             }
             else{
                 if(obscured_vision_miss){
                     B.Add(Your() + " attack goes wide. ",this);
                 }
                 else{
                     if(!attack_is_part_of_another_action && drive_back_applied && !MovementPrevented(M.tile[target_original_pos])){
                         B.Add(You("drive") + " " + a.TheName(true) + " back. ",this,a);
                         /*if(!a.HasAttr(AttrType.FROZEN) && !HasAttr(AttrType.FROZEN)){
                             a.AI_Step(this,true);
                             AI_Step(a);
                         }*/
                         Tile dest = null;
                         int dir = DirectionOf(target_original_pos);
                         foreach(int next_dir in new List<int>{dir,dir.RotateDir(true),dir.RotateDir(false)}){
                             Tile t = a.TileInDirection(next_dir);
                             if(t.passable && t.actor() == null && !a.MovementPrevented(t)){
                                 dest = t;
                                 break;
                             }
                         }
                         if(dest != null){
                             a.AI_MoveOrOpen(dest.row,dest.col);
                             if(M.actor[target_original_pos] == null){
                                 AI_MoveOrOpen(target_original_pos.row,target_original_pos.col);
                             }
                         }
                     }
                     else{
                         if(info.miss != ""){
                             string s = info.miss + ". ";
                             int pos = -1;
                             do{
                                 pos = s.IndexOf('&');
                                 if(pos != -1){
                                     s = s.Substring(0,pos) + TheName(true) + s.Substring(pos+1);
                                 }
                             }
                             while(pos != -1);
                             //
                             do{
                                 pos = s.IndexOf('*');
                                 if(pos != -1){
                                     s = s.Substring(0,pos) + a.TheName(true) + s.Substring(pos+1);
                                 }
                             }
                             while(pos != -1);
                             B.Add(s,this,a);
                         }
                         else{
                             B.Add(YouVisible("miss",true) + " " + a.TheName(true) + ". ",this,a);
                         }
                     }
                 }
             }
         }
         if(type == ActorType.SWORDSMAN || type == ActorType.PHANTOM_SWORDMASTER || type == ActorType.ALASI_SOLDIER){
             attrs[AttrType.COMBO_ATTACK] = 0;
         }
     }
     else{
         string s = info.hit + ". ";
         if(!attack_is_part_of_another_action && HasFeat(FeatType.NECK_SNAP) && a.HasAttr(AttrType.MEDIUM_HUMANOID) && (IsHiddenFrom(a) || a.IsHelpless())){
             if(!a.HasAttr(AttrType.RESIST_NECK_SNAP)){
                 B.Add(You("silently snap") + " " + a.Your() + " neck. ");
                 a.Kill();
                 Q1();
                 return true;
             }
             else{
                 B.Add(You("silently snap") + " " + a.Your() + " neck. ");
                 B.Add("It doesn't seem to affect " + a.the_name + ". ");
             }
         }
         bool crit = false;
         int crit_chance = 8; //base crit rate is 1/8
         if(EquippedWeapon.type == WeaponType.DAGGER && !tile().IsLit()){
             crit_chance /= 2;
         }
         if(a.EquippedArmor != null && (a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] || a.EquippedArmor.status[EquipmentStatus.DAMAGED] || a.HasAttr(AttrType.SWITCHING_ARMOR))){
             crit_chance /= 2;
         }
         if(a.HasAttr(AttrType.SUSCEPTIBLE_TO_CRITS)){ //caused by armor mastery
             crit_chance /= 2;
         }
         if(EquippedWeapon.enchantment == EnchantmentType.PRECISION && !EquippedWeapon.status[EquipmentStatus.NEGATED]){
             crit_chance /= 2;
         }
         if(drive_back_nowhere_to_run){
             crit_chance /= 2;
         }
         if(crit_chance <= 1 || R.OneIn(crit_chance)){
             crit = true;
         }
         int pos = -1;
         do{
             pos = s.IndexOf('&');
             if(pos != -1){
                 s = s.Substring(0,pos) + TheName(true) + s.Substring(pos+1);
             }
         }
         while(pos != -1);
         //
         do{
             pos = s.IndexOf('*');
             if(pos != -1){
                 s = s.Substring(0,pos) + a.TheName(true) + s.Substring(pos+1);
             }
         }
         while(pos != -1);
         int dice = info.damage.dice;
         if(sneak_attack && crit && this == player){
             if(!a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE,AttrType.BOSS_MONSTER) && a.type != ActorType.CYCLOPEAN_TITAN){
                 switch(EquippedWeapon.type){ //todo: should this check for shielded/blocking?
                 case WeaponType.SWORD:
                     B.Add("You run " + a.TheName(true) + " through! ");
                     break;
                 case WeaponType.MACE:
                     B.Add("You bash " + a.YourVisible() + " head in! ");
                     break;
                 case WeaponType.DAGGER:
                     B.Add("You pierce one of " + a.YourVisible() + " vital organs! ");
                     break;
                 case WeaponType.STAFF:
                     B.Add("You bring your staff down on " + a.YourVisible() + " head with a loud crack! ");
                     break;
                 case WeaponType.BOW:
                     B.Add("You choke " + a.TheName(true) + " with your bowstring! ");
                     break;
                 default:
                     break;
                 }
                 Help.TutorialTip(TutorialTopic.InstantKills);
                 MakeNoise(6);
                 if(a.type == ActorType.BERSERKER && a.target == this){
                     a.attrs[AttrType.SHIELDED] = 0;
                     a.TakeDamage(DamageType.NORMAL,DamageClass.NO_TYPE,a.curhp,this);
                 }
                 else{
                     a.Kill();
                 }
                 if(!attack_is_part_of_another_action){
                     Q1();
                 }
                 return true;
             }
         }
         if(sneak_attack && (this == player || a == player)){
             B.Add(YouVisible("strike") + " from hiding! ");
             if(type != ActorType.PLAYER){
                 if(a == player && attrs[AttrType.TURNS_VISIBLE] >= 0){
                     B.PrintAll();
                 }
                 attrs[AttrType.TURNS_VISIBLE] = -1;
                 attrs[AttrType.NOTICED] = 1;
                 attrs[AttrType.DANGER_SENSED] = 1;
             }
             else{
                 a.player_visibility_duration = -1;
                 a.attrs[AttrType.PLAYER_NOTICED] = 1;
             }
         }
         if(a == player){
             if(a.HasAttr(AttrType.SWITCHING_ARMOR)){
                 B.Add("You're unguarded! ");
             }
             else{
                 if(a.EquippedArmor.status[EquipmentStatus.DAMAGED]){
                     B.Add("Your damaged armor leaves you open! ");
                 }
                 else{
                     if(crit && R.CoinFlip()){
                         if(a.EquippedArmor.status[EquipmentStatus.WEAK_POINT]){
                             B.Add(TheName(true) + " finds a weak point. ");
                         }
                     }
                 }
             }
         }
         if(mace_through_armor){
             if(type == ActorType.CRUSADING_KNIGHT || type == ActorType.PHANTOM_CRUSADER){
                 B.Add(YourVisible() + " huge mace punches through " + a.YourVisible() + " armor. ",this,a);
             }
             else{
                 B.Add(YourVisible() + " mace punches through " + a.YourVisible() + " armor. ",this,a);
             }
         }
         else{
             B.Add(s,this,a);
         }
         if(crit && info.crit != AttackEffect.NO_CRIT){
             if(this == player || a == player){
                 Help.TutorialTip(TutorialTopic.CriticalHits);
             }
         }
         if(a == player && !player.CanSee(this)){
             Screen.AnimateMapCell(row,col,new colorchar('?',Color.DarkGray),50);
         }
         if(a.type == ActorType.GHOST && EquippedWeapon.enchantment != EnchantmentType.NO_ENCHANTMENT && !EquippedWeapon.status[EquipmentStatus.NEGATED]){
             EquippedWeapon.status[EquipmentStatus.NEGATED] = true;
             B.Add(Your() + " " + EquippedWeapon.NameWithEnchantment() + "'s magic is suppressed! ",this);
             Help.TutorialTip(TutorialTopic.Negated);
         }
         if(!Help.displayed[TutorialTopic.SwitchingEquipment] && this == player && a.Is(ActorType.SPORE_POD,ActorType.SKELETON,ActorType.STONE_GOLEM,ActorType.MECHANICAL_KNIGHT,ActorType.MACHINE_OF_WAR) && EquippedWeapon.type == WeaponType.SWORD){
             Help.TutorialTip(TutorialTopic.SwitchingEquipment);
         }
         int dmg = R.Roll(dice,6);
         bool no_max_damage_message = false;
         List<AttackEffect> effects = new List<AttackEffect>();
         if(crit && info.crit != AttackEffect.NO_CRIT){
             effects.AddUnique(info.crit);
         }
         if(info.effects != null){
             foreach(AttackEffect effect in info.effects){
                 effects.AddUnique(effect);
             }
         }
         if(type == ActorType.DEMON_LORD && DistanceFrom(a) == 2){
             effects.AddUnique(AttackEffect.PULL);
         }
         if(type == ActorType.SWORDSMAN && attrs[AttrType.COMBO_ATTACK] == 2){
             effects.AddUnique(AttackEffect.BLEED);
             effects.AddUnique(AttackEffect.STRONG_KNOCKBACK);
         }
         if(type == ActorType.PHANTOM_SWORDMASTER && attrs[AttrType.COMBO_ATTACK] == 2){
             effects.AddUnique(AttackEffect.PERCENT_DAMAGE);
             effects.AddUnique(AttackEffect.STRONG_KNOCKBACK);
         }
         if(type == ActorType.ALASI_SOLDIER){
             if(attrs[AttrType.COMBO_ATTACK] == 1){
                 effects.AddUnique(AttackEffect.ONE_TURN_STUN);
             }
             else{
                 if(attrs[AttrType.COMBO_ATTACK] == 2){
                     effects.AddUnique(AttackEffect.ONE_TURN_PARALYZE);
                 }
             }
         }
         if(type == ActorType.WILD_BOAR && HasAttr(AttrType.COOLDOWN_1)){
             effects.AddUnique(AttackEffect.FLING);
         }
         if(type == ActorType.ALASI_SENTINEL && R.OneIn(3)){
             effects.AddUnique(AttackEffect.FLING);
         }
         if(this == player && a.type == ActorType.CYCLOPEAN_TITAN && crit){
             effects = new List<AttackEffect>(); //remove all other effects (so far) and check for edged weapons
             if(EquippedWeapon == Sword || EquippedWeapon == Dagger){
                 effects.Add(AttackEffect.PERMANENT_BLIND);
             }
         }
         if(EquippedWeapon.status[EquipmentStatus.POISONED]){
             effects.AddUnique(AttackEffect.POISON);
         }
         if(HasAttr(AttrType.PSEUDO_VAMPIRIC)){
             effects.AddUnique(AttackEffect.DRAIN_LIFE);
         }
         if(HasAttr(AttrType.BRUTISH_STRENGTH)){
             effects.AddUnique(AttackEffect.MAX_DAMAGE);
             effects.AddUnique(AttackEffect.STRONG_KNOCKBACK);
             effects.Remove(AttackEffect.KNOCKBACK); //strong knockback replaces these
             effects.Remove(AttackEffect.TRIP);
             effects.Remove(AttackEffect.FLING);
         }
         if(EquippedWeapon != null && !EquippedWeapon.status[EquipmentStatus.NEGATED]){
             switch(EquippedWeapon.enchantment){
             case EnchantmentType.CHILLING:
                 effects.AddUnique(AttackEffect.CHILL);
                 break;
             case EnchantmentType.DISRUPTION:
                 effects.AddUnique(AttackEffect.DISRUPTION); //not entirely sure that these should be crit effects
                 break;
             case EnchantmentType.VICTORY:
                 if(a.maxhp > 1){ // no illusions, phantoms, or minions
                     effects.AddUnique(AttackEffect.VICTORY);
                 }
                 break;
             }
         }
         if(type == ActorType.SKITTERMOSS && HasAttr(AttrType.COOLDOWN_1)){
             effects.Remove(AttackEffect.INFEST);
         }
         if(a.HasAttr(AttrType.NONLIVING)){
             effects.Remove(AttackEffect.DRAIN_LIFE);
         }
         foreach(AttackEffect effect in effects){ //pre-damage effects - these can alter the amount of damage.
             switch(effect){
             case AttackEffect.MAX_DAMAGE:
                 dmg = Math.Max(dmg,dice * 6);
                 break;
             case AttackEffect.PERCENT_DAMAGE:
                 dmg = Math.Max(dmg,(a.maxhp+1)/2);
                 no_max_damage_message = true;
                 if(!EquippedWeapon.status[EquipmentStatus.DULLED]){
                     if(this == player){
                         B.Add("Your sword cuts deep! ");
                     }
                     else{
                         B.Add(Your() + " attack cuts deep! ",this);
                     }
                 }
                 break;
             case AttackEffect.ONE_HP:
                 dmg = a.curhp - 1;
                 if(a.HasAttr(AttrType.VULNERABLE)){
                     a.attrs[AttrType.VULNERABLE] = 0;
                 }
                 if(a == player){
                     B.Add("You shudder. ");
                 }
                 no_max_damage_message = true;
                 break;
             }
         }
         if(dice < 2){
             no_max_damage_message = true;
         }
         if(a.type == ActorType.SPORE_POD && EquippedWeapon.IsBlunt()){
             no_max_damage_message = true;
         }
         if(EquippedWeapon.status[EquipmentStatus.MERCIFUL]){
             no_max_damage_message = true;
         }
         if(a.HasAttr(AttrType.RESIST_WEAPONS) && EquippedWeapon.type != WeaponType.NO_WEAPON){
             B.Add("Your " + EquippedWeapon.NameWithoutEnchantment() + " isn't very effective. ");
             dmg = dice; //minimum damage
         }
         else{
             if(EquippedWeapon.status[EquipmentStatus.DULLED]){
                 B.Add("Your dull " + EquippedWeapon.NameWithoutEnchantment() + " isn't very effective. ");
                 dmg = dice; //minimum damage
             }
             else{
                 if(type == ActorType.MUD_TENTACLE){ //getting surrounded by these guys should be dangerous, but not simply because of their damage.
                     dmg = dice;
                 }
             }
         }
         if(dmg >= dice * 6 && !no_max_damage_message){
             if(this == player){
                 B.Add("It was a good hit! ");
             }
             else{
                 if(a == player){
                     B.Add("Ow! ");
                 }
             }
         }
         dmg += TotalSkill(SkillType.COMBAT);
         if(a.type == ActorType.SPORE_POD && EquippedWeapon.IsBlunt()){
             dmg = 0;
             dice = 0;
             effects.AddUnique(AttackEffect.STRONG_KNOCKBACK);
             B.Add("Your " + EquippedWeapon.NameWithoutEnchantment() + " knocks the spore pod away. ",a);
         }
         if(EquippedWeapon.status[EquipmentStatus.MERCIFUL] && dmg >= a.curhp){
             dmg = a.curhp - 1;
             B.Add("Your " + EquippedWeapon.NameWithoutEnchantment() + " refuses to finish " + a.TheName(true) + ". ");
             B.Print(true);
         }
         if(a.HasAttr(AttrType.DULLS_BLADES) && R.CoinFlip() && (EquippedWeapon == Sword || EquippedWeapon == Dagger)){
             EquippedWeapon.status[EquipmentStatus.DULLED] = true;
             B.Add(Your() + " " + EquippedWeapon.NameWithEnchantment() + " becomes dull! ",this);
             Help.TutorialTip(TutorialTopic.Dulled);
         }
         if(a.type == ActorType.CORROSIVE_OOZE && R.CoinFlip() && (EquippedWeapon == Sword || EquippedWeapon == Dagger || EquippedWeapon == Mace)){
             EquippedWeapon.status[EquipmentStatus.DULLED] = true;
             B.Add("The acid dulls " + Your() + " " + EquippedWeapon.NameWithEnchantment() + "! ",this);
             Help.TutorialTip(TutorialTopic.Acidified);
             Help.TutorialTip(TutorialTopic.Dulled);
         }
         if(a.HasAttr(AttrType.CAN_POISON_WEAPONS) && R.CoinFlip() && EquippedWeapon.type != WeaponType.NO_WEAPON && !EquippedWeapon.status[EquipmentStatus.POISONED]){
             EquippedWeapon.status[EquipmentStatus.POISONED] = true;
             weapon_just_poisoned = true;
             B.Add(Your() + " " + EquippedWeapon.NameWithEnchantment() + " is covered in poison! ",this);
         }
         int r = a.row;
         int c = a.col;
         bool still_alive = true;
         bool knockback_effect = effects.Contains(AttackEffect.KNOCKBACK) || effects.Contains(AttackEffect.STRONG_KNOCKBACK) || effects.Contains(AttackEffect.TRIP) || effects.Contains(AttackEffect.FLING) || effects.Contains(AttackEffect.SWAP_POSITIONS) || effects.Contains(AttackEffect.DRAIN_LIFE);
         if(knockback_effect){
             a.attrs[AttrType.TURN_INTO_CORPSE]++;
         }
         Color blood = a.BloodColor();
         bool homunculus = a.type == ActorType.HOMUNCULUS;
         if(dmg > 0){
             Damage damage = new Damage(info.damage.type,info.damage.damclass,true,this,dmg);
             damage.weapon_used = EquippedWeapon.type;
             still_alive = a.TakeDamage(damage,a_name);
         }
         if(homunculus){ //todo: or will this happen on any major damage, not just melee attacks?
             M.tile[target_original_pos].AddFeature(FeatureType.OIL);
         }
         else{
             if(blood != Color.Black && (!still_alive || !a.HasAttr(AttrType.FROZEN,AttrType.INVULNERABLE))){
                 /*List<Tile> valid = new List<Tile>{M.tile[target_original_pos]};
                 for(int i=-1;i<=1;++i){
                     valid.Add(M.tile[target_original_pos].TileInDirection(original_pos.DirectionOf(target_original_pos).RotateDir(true,i)));
                 }
                 for(int i=dmg/10;i>0;--i){
                     Tile t = valid.RemoveRandom();
                     if(t.Is(TileType.WALL) || t.name == "floor"){
                         t.color = blood;
                     }
                 }*/
                 List<Tile> cone = M.tile[target_original_pos].GetCone(original_pos.DirectionOf(target_original_pos),dmg>=20? 2 : 1,false);
                 cone.Add(M.tile[target_original_pos].TileInDirection(original_pos.DirectionOf(target_original_pos)));
                 cone.Add(M.tile[target_original_pos].TileInDirection(original_pos.DirectionOf(target_original_pos)));
                 cone.Add(M.tile[target_original_pos].TileInDirection(original_pos.DirectionOf(target_original_pos)));
                 for(int i=(dmg-5)/5;i>0;--i){
                     if(cone.Count == 0){
                         break;
                     }
                     Tile t = cone.Random();
                     while(cone.Remove(t)){ } //remove all
                     if(t.Is(TileType.WALL) || t.name == "floor"){
                         t.color = blood;
                         switch(blood){
                         case Color.DarkRed:
                         M.aesthetics[t.p] = AestheticFeature.BloodDarkRed;
                         break;
                         default:
                         M.aesthetics[t.p] = AestheticFeature.BloodOther;
                         break;
                         }
                     }
                 }
             }
         }
         if(still_alive){ //post-damage crit effects that require the target to still be alive
             foreach(AttackEffect effect in effects){
                 if(still_alive){
                     switch(effect){ //todo: some of these messages shouldn't be printed if the effect already exists
                     case AttackEffect.CONFUSE:
                         a.ApplyStatus(AttrType.CONFUSED,R.Between(2,3)*100);
                         break;
                     case AttackEffect.BLEED:
                         if(!a.HasAttr(AttrType.NONLIVING,AttrType.FROZEN)){
                             if(a.HasAttr(AttrType.BLEEDING)){
                                 if(a == player){
                                     if(a.attrs[AttrType.BLEEDING] > 15){
                                         B.Add("Your bleeding worsens. ");
                                     }
                                     else{
                                         B.Add("You're bleeding badly now! ");
                                     }
                                 }
                                 else{
                                     B.Add(a.YouAre() + " bleeding badly! ",a);
                                 }
                             }
                             a.attrs[AttrType.BLEEDING] += R.Between(10,15);
                             if(a.attrs[AttrType.BLEEDING] > 25){
                                 a.attrs[AttrType.BLEEDING] = 25; //this seems like a reasonable cap, so repeated bleed effects don't just last *forever*.
                             }
                             if(a == player){
                                 Help.TutorialTip(TutorialTopic.Bleeding);
                             }
                         }
                         break;
                     case AttackEffect.BLIND:
                         a.ApplyStatus(AttrType.BLIND,R.Between(5,7)*100);
                         //B.Add(a.YouAre() + " blinded! ",a);
                         //a.RefreshDuration(AttrType.BLIND,R.Between(5,7)*100);
                         break;
                     case AttackEffect.PERMANENT_BLIND:
                     {
                         if(!a.HasAttr(AttrType.COOLDOWN_1)){
                             B.Add("You drive your " + EquippedWeapon.NameWithoutEnchantment() + " into its eye, blinding it! ");
                             Q.KillEvents(a,AttrType.BLIND);
                             a.attrs[AttrType.BLIND] = 1;
                             a.attrs[AttrType.COOLDOWN_1] = 1;
                         }
                         break;
                     }
                     case AttackEffect.DIM_VISION:
                         if(a.ResistedBySpirit()){
                             B.Add(a.Your() + " vision is dimmed, but only for a moment. ",a);
                         }
                         else{
                             B.Add(a.Your() + " vision is dimmed. ",a);
                             a.RefreshDuration(AttrType.DIM_VISION,(R.Roll(2,20)+20)*100);
                         }
                         break;
                     case AttackEffect.CHILL:
                         if(!a.HasAttr(AttrType.IMMUNE_COLD)){
                             B.Add(a.the_name + " is chilled. ",a);
                             if(!a.HasAttr(AttrType.CHILLED)){
                                 a.attrs[AttrType.CHILLED] = 1;
                             }
                             else{
                                 a.attrs[AttrType.CHILLED] *= 2;
                             }
                             if(!a.TakeDamage(DamageType.COLD,DamageClass.MAGICAL,a.attrs[AttrType.CHILLED],this)){
                                 still_alive = false;
                             }
                         }
                         break;
                     case AttackEffect.DISRUPTION:
                         if(a.HasAttr(AttrType.NONLIVING)){
                             B.Add(a.the_name + " is disrupted. ",a);
                             if(!a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,a.maxhp / 5,this)){
                                 still_alive = false;
                             }
                         }
                         break;
                     case AttackEffect.FREEZE:
                         a.tile().ApplyEffect(DamageType.COLD);
                         a.ApplyFreezing();
                         break;
                     case AttackEffect.GRAB:
                         if(!HasAttr(AttrType.GRABBING) && DistanceFrom(a) == 1 && !a.HasAttr(AttrType.FROZEN)){
                             a.attrs[AttrType.GRABBED]++;
                             attrs[AttrType.GRABBING] = DirectionOf(a);
                             B.Add(YouVisible("grab") + " " + a.TheName(true) + ". ",this,a);
                             if(a == player){
                                 Help.TutorialTip(TutorialTopic.Grabbed);
                             }
                         }
                         break;
                     case AttackEffect.POISON:
                         if(!a.HasAttr(AttrType.NONLIVING,AttrType.CAN_POISON_WEAPONS,AttrType.INVULNERABLE,AttrType.FROZEN)){
                             a.ApplyStatus(AttrType.POISONED,(R.Roll(2,6)+2)*100);
                         }
                         break;
                     case AttackEffect.PARALYZE:
                         if(!a.HasAttr(AttrType.NONLIVING) || type != ActorType.CARRION_CRAWLER){
                             if(a.ResistedBySpirit()){
                                 B.Add(a.Your() + " muscles stiffen, but only for a moment. ",a);
                             }
                             else{
                                 if(a == player){
                                     B.Add("You suddenly can't move! ");
                                 }
                                 else{
                                     B.Add(a.YouAre() + " paralyzed. ",a);
                                 }
                                 a.attrs[AttrType.PARALYZED] = R.Between(3,5);
                             }
                         }
                         break;
                     case AttackEffect.ONE_TURN_PARALYZE:
                         Event e = Q.FindAttrEvent(a,AttrType.STUNNED);
                         if(e != null && e.delay == 100 && e.TimeToExecute() == Q.turn){ //if the target was hit with a 1-turn stun that's about to expire, don't print a message for it.
                             e.msg = "";
                         }
                         if(a.ResistedBySpirit()){
                             B.Add(a.Your() + " muscles stiffen, but only for a moment. ",a);
                         }
                         else{
                             B.Add(a.YouAre() + " paralyzed! ",a);
                             a.attrs[AttrType.PARALYZED] = 2; //setting it to 1 means it would end immediately
                         }
                         break;
                     case AttackEffect.INFLICT_VULNERABILITY:
                         a.ApplyStatus(AttrType.VULNERABLE,R.Between(2,4)*100);
                         /*B.Add(a.You("become") + " vulnerable. ",a);
                         a.RefreshDuration(AttrType.VULNERABLE,R.Between(2,4)*100);*/
                         if(a == player){
                             Help.TutorialTip(TutorialTopic.Vulnerable);
                         }
                         break;
                     case AttackEffect.IGNITE:
                         break;
                     case AttackEffect.INFEST:
                         if(a == player && !a.EquippedArmor.status[EquipmentStatus.INFESTED]){
                             B.Add("Thousands of insects crawl into your " + a.EquippedArmor.NameWithoutEnchantment() + "! ");
                             a.EquippedArmor.status[EquipmentStatus.INFESTED] = true;
                             Help.TutorialTip(TutorialTopic.Infested);
                         }
                         break;
                     case AttackEffect.SLOW:
                         a.ApplyStatus(AttrType.SLOWED,R.Between(4,6)*100);
                         break;
                     case AttackEffect.REDUCE_ACCURACY: //also about 2d4 turns?
                         break;
                     case AttackEffect.SLIME:
                         B.Add(a.YouAre() + " covered in slime. ",a);
                         a.attrs[AttrType.SLIMED] = 1;
                         if(a == player){
                             Help.TutorialTip(TutorialTopic.Slimed);
                         }
                         break;
                     case AttackEffect.STUN: //2d3 turns, at most
                     {
                         a.ApplyStatus(AttrType.STUNNED,R.Roll(2,3)*100);
                         /*B.Add(a.YouAre() + " stunned! ",a);
                         a.RefreshDuration(AttrType.STUNNED,a.DurationOfMagicalEffect(R.Roll(2,3)) * 100,a.YouAre() + " no longer stunned. ",a);*/
                         if(a == player){
                             Help.TutorialTip(TutorialTopic.Stunned);
                         }
                         break;
                     }
                     case AttackEffect.ONE_TURN_STUN:
                     {
                         a.ApplyStatus(AttrType.STUNNED,100);
                         /*B.Add(a.YouAre() + " stunned! ",a);
                         a.RefreshDuration(AttrType.STUNNED,100,a.YouAre() + " no longer stunned. ",a);*/
                         if(a == player){
                             Help.TutorialTip(TutorialTopic.Stunned);
                         }
                         break;
                     }
                     case AttackEffect.SILENCE:
                     {
                         if(a.ResistedBySpirit()){
                             if(!HasAttr(AttrType.SILENCED)){
                                 B.Add(a.You("resist") + " being silenced. ",a);
                             }
                         }
                         else{
                             if(!HasAttr(AttrType.SILENCED)){
                                 B.Add(TheName(true) + " silences " + a.the_name + ". ",a);
                             }
                             a.RefreshDuration(AttrType.SILENCED,R.Between(3,4)*100,a.YouAre() + " no longer silenced. ",a);
                         }
                         if(a == player){
                             Help.TutorialTip(TutorialTopic.Silenced);
                         }
                         break;
                     }
                     case AttackEffect.WEAK_POINT:
                         if(!a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] && a == player){
                             a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] = true;
                             B.Add(YouVisible("expose") + " a weak point on your armor! ",this);
                             Help.TutorialTip(TutorialTopic.WeakPoint);
                         }
                         break;
                     case AttackEffect.WORN_OUT:
                         if(a == player && !a.EquippedArmor.status[EquipmentStatus.DAMAGED]){
                             if(a.EquippedArmor.status[EquipmentStatus.WORN_OUT]){
                                 a.EquippedArmor.status[EquipmentStatus.WORN_OUT] = false;
                                 a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] = false;
                                 a.EquippedArmor.status[EquipmentStatus.DAMAGED] = true;
                                 B.Add(a.Your() + " " + a.EquippedArmor.NameWithEnchantment() + " is damaged! ");
                                 Help.TutorialTip(TutorialTopic.Damaged);
                             }
                             else{
                                 a.EquippedArmor.status[EquipmentStatus.WORN_OUT] = true;
                                 B.Add(a.Your() + " " + a.EquippedArmor.NameWithEnchantment() + " looks worn out. ");
                                 Help.TutorialTip(TutorialTopic.WornOut);
                             }
                         }
                         break;
                     case AttackEffect.ACID:
                         if(a == player && !a.HasAttr(AttrType.ACIDIFIED) && R.CoinFlip()){
                             a.RefreshDuration(AttrType.ACIDIFIED,300);
                             if(a.EquippedArmor != a.Leather && !a.EquippedArmor.status[EquipmentStatus.DAMAGED]){
                                 B.Add("The acid hisses as it touches your " + a.EquippedArmor.NameWithEnchantment() + "! ");
                                 if(a.EquippedArmor.status[EquipmentStatus.WORN_OUT]){
                                     a.EquippedArmor.status[EquipmentStatus.WORN_OUT] = false;
                                     a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] = false;
                                     a.EquippedArmor.status[EquipmentStatus.DAMAGED] = true;
                                     B.Add(a.Your() + " " + a.EquippedArmor.NameWithEnchantment() + " is damaged! ");
                                 }
                                 else{
                                     a.EquippedArmor.status[EquipmentStatus.WORN_OUT] = true;
                                     B.Add(a.Your() + " " + a.EquippedArmor.NameWithEnchantment() + " looks worn out. ");
                                 }
                                 Help.TutorialTip(TutorialTopic.Acidified);
                             }
                         }
                         break;
                     case AttackEffect.PULL:
                     {
                         List<Tile> tiles = tile().NeighborsBetween(a.row,a.col).Where(x=>x.actor() == null && x.passable);
                         if(tiles.Count > 0){
                             Tile t = tiles.Random();
                             if(!a.MovementPrevented(t)){
                                 B.Add(TheName(true) + " pulls " + a.TheName(true) + " closer. ",this,a);
                                 a.Move(t.row,t.col);
                             }
                         }
                         break;
                     }
                     case AttackEffect.STEAL:
                     {
                         if(a.inv != null && a.inv.Count > 0){
                             Item i = a.inv.Random();
                             Item stolen = i;
                             if(i.quantity > 1){
                                 stolen = new Item(i,i.row,i.col);
                                 stolen.revealed_by_light = i.revealed_by_light;
                                 i.quantity--;
                             }
                             else{
                                 a.inv.Remove(stolen);
                             }
                             GetItem(stolen);
                             B.Add(YouVisible("steal") + " " + a.YourVisible() + " " + stolen.SingularName() + "! ",this,a);
                             B.PrintAll();
                         }
                         break;
                     }
                     case AttackEffect.EXHAUST:
                     {
                         if(a == player){
                             B.Add("You feel fatigued. ");
                         }
                         a.IncreaseExhaustion(R.Roll(2,4));
                         break;
                     }
                     }
                 }
             }
         }
         foreach(AttackEffect effect in effects){ //effects that don't care whether the target is still alive
             switch(effect){
             case AttackEffect.DRAIN_LIFE:
             {
                 if(curhp < maxhp){
                     curhp += 10;
                     if(curhp > maxhp){
                         curhp = maxhp;
                     }
                     B.Add(You("drain") + " some life from " + a.TheName(true) + ". ",this);
                 }
                 break;
             }
             case AttackEffect.VICTORY:
                 if(!still_alive){
                     curhp += 5;
                     if(curhp > maxhp){
                         curhp = maxhp;
                     }
                 }
                 break;
             case AttackEffect.STALAGMITES:
             {
                 List<Tile> tiles = new List<Tile>();
                 foreach(Tile t in M.tile[r,c].TilesWithinDistance(1)){
                     //if(t.actor() == null && (t.type == TileType.FLOOR || t.type == TileType.STALAGMITE)){
                     if(t.actor() == null && t.inv == null && (t.IsTrap() || t.Is(TileType.FLOOR,TileType.GRAVE_DIRT,TileType.GRAVEL,TileType.STALAGMITE))){
                         if(R.CoinFlip()){
                             tiles.Add(t);
                         }
                     }
                 }
                 foreach(Tile t in tiles){
                     if(t.type == TileType.STALAGMITE){
                         Q.KillEvents(t,EventType.STALAGMITE);
                     }
                     else{
                         TileType previous_type = t.type;
                         t.Toggle(this,TileType.STALAGMITE);
                         t.toggles_into = previous_type;
                     }
                 }
                 Q.Add(new Event(tiles,150,EventType.STALAGMITE));
                 break;
             }
             case AttackEffect.MAKE_NOISE:
                 break;
             case AttackEffect.SWAP_POSITIONS:
                 if(original_pos.DistanceFrom(target_original_pos) == 1 && p.Equals(original_pos) && M.actor[target_original_pos] != null && !M.actor[target_original_pos].HasAttr(AttrType.IMMOBILE)){
                     B.Add(YouVisible("move") + " past " + M.actor[target_original_pos].TheName(true) + ". ",this,M.actor[target_original_pos]);
                     Move(target_original_pos.row,target_original_pos.col);
                 }
                 break;
             case AttackEffect.TRIP:
                 if(!a.HasAttr(AttrType.FLYING) && (a.curhp > 0 || !a.HasAttr(AttrType.NO_CORPSE_KNOCKBACK))){
                     B.Add(YouVisible("trip") + " " + a.TheName(true) + ". ",this,a);
                     a.IncreaseExhaustion(R.Between(2,4));
                     a.CollideWith(a.tile());//todo: if it's a corpse, ONLY trip it if something is going to happen when it collides with the floor.
                 }
                 break;
             case AttackEffect.KNOCKBACK:
                 if(a.curhp > 0 || !a.HasAttr(AttrType.NO_CORPSE_KNOCKBACK)){
                     KnockObjectBack(a,3,this);
                 }
                 break;
             case AttackEffect.STRONG_KNOCKBACK:
                 if(a.curhp > 0 || !a.HasAttr(AttrType.NO_CORPSE_KNOCKBACK)){
                     KnockObjectBack(a,5,this);
                 }
                 break;
             case AttackEffect.FLING:
                 if(a.curhp > 0 || !a.HasAttr(AttrType.NO_CORPSE_KNOCKBACK)){
                     attrs[AttrType.JUST_FLUNG] = 1;
                     int dir = DirectionOf(a).RotateDir(true,4);
                     Tile t = null;
                     if(tile().p.PosInDir(dir).PosInDir(dir).BoundsCheck(M.tile,true)){
                         Tile t2 = tile().TileInDirection(dir).TileInDirection(dir);
                         if(HasLOE(t2)){
                             t = t2;
                         }
                     }
                     if(t == null){
                         if(tile().p.PosInDir(dir).BoundsCheck(M.tile,false)){
                             Tile t2 = tile().TileInDirection(dir);
                             if(HasLOE(t2)){
                                 t = t2;
                             }
                         }
                     }
                     if(t == null){
                         t = tile();
                     }
                     B.Add(YouVisible("fling") + " " + a.TheName(true) + "! ",this,a);
                     foreach(Tile nearby in M.ReachableTilesByDistance(t.row,t.col,false)){
                         if(nearby.passable && nearby.actor() == null && HasLOE(nearby)){
                             a.Move(nearby.row,nearby.col);
                             a.CollideWith(nearby);
                             break;
                         }
                     }
                 }
                 break;
             }
         }
         if(knockback_effect){
             if(a.curhp > 0 && this != player){
                 target_location = target.tile();
             }
             a.CorpseCleanup();
         }
         if(type == ActorType.SWORDSMAN || type == ActorType.PHANTOM_SWORDMASTER || type == ActorType.ALASI_SOLDIER){
             if(attrs[AttrType.COMBO_ATTACK] == 1 && (type == ActorType.SWORDSMAN || type == ActorType.PHANTOM_SWORDMASTER)){
                 B.Add(the_name + " prepares a devastating strike! ",this);
             }
             attrs[AttrType.COMBO_ATTACK]++;
             if(attrs[AttrType.COMBO_ATTACK] == 3){ //all these have 3-part combos
                 attrs[AttrType.COMBO_ATTACK] = 0;
             }
         }
     }
     /*if(!hit && HasAttr(AttrType.BRUTISH_STRENGTH) && p.Equals(original_pos) && M.actor[target_original_pos] != null){
         Actor a2 = M.actor[target_original_pos];
         if(a2.HasAttr(AttrType.NO_CORPSE_KNOCKBACK) && a2.maxhp == 1){
             B.Add(YouVisible("push",true) + " " + a2.TheName(true) + ". ",this,a2);
             a2.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,false,1,this);
         }
         else{
             a2.attrs[AttrType.TURN_INTO_CORPSE]++;
             KnockObjectBack(a2,5);
             a2.CorpseCleanup();
         }
     }*/
     if(!hit && sneak_attack && this != player){
         attrs[AttrType.TURNS_VISIBLE] = -1;
         attrs[AttrType.NOTICED]++;
     }
     if(!attack_is_part_of_another_action && hit && HasAttr(AttrType.BRUTISH_STRENGTH) && p.Equals(original_pos) && M.actor[target_original_pos] == null && DistanceFrom(target_original_pos) == 1 && !MovementPrevented(M.tile[target_original_pos])){
         Tile t = M.tile[target_original_pos];
         if(t.IsTrap()){
             t.SetName(Tile.Prototype(t.type).name);
             t.TurnToFloor();
         }
         if(HasFeat(FeatType.WHIRLWIND_STYLE)){
             WhirlwindMove(t.row,t.col);
         }
         else{
             Move(t.row,t.col);
         }
     }
     if(hit && EquippedWeapon.enchantment == EnchantmentType.ECHOES && !EquippedWeapon.status[EquipmentStatus.NEGATED]){
         List<Tile> line = GetBestExtendedLineOfEffect(target_original_pos.row,target_original_pos.col);
         int idx = line.IndexOf(M.tile[target_original_pos]);
         if(idx != -1 && line.Count > idx + 1){
             Actor next = line[idx+1].actor();
             if(next != null && next != this){
                 Attack(attack_idx,next,true);
             }
         }
     }
     //if(!attack_is_part_of_another_action && EquippedWeapon == Staff && p.Equals(original_pos) && a_moved_last_turn && !HasAttr(AttrType.IMMOBILE) && M.tile[target_original_pos].passable && (M.actor[target_original_pos] == null || !M.actor[target_original_pos].HasAttr(AttrType.IMMOBILE))){
     if(!attack_is_part_of_another_action && EquippedWeapon == Staff && p.Equals(original_pos) && a_moved_last_turn && !MovementPrevented(M.tile[target_original_pos]) && M.tile[target_original_pos].passable && (M.actor[target_original_pos] == null || !M.actor[target_original_pos].MovementPrevented(this))){
         if(M.actor[target_original_pos] != null){
             M.actor[target_original_pos].attrs[AttrType.TURNS_HERE]++; //this is a hack to prevent fast monsters from swapping *back* on the next hit.
         }
         if(HasFeat(FeatType.WHIRLWIND_STYLE)){
             WhirlwindMove(target_original_pos.row,target_original_pos.col,true,new List<Actor>{M.actor[target_original_pos]}); //whirlwind move, but don't attack the original target again
         }
         else{
             Move(target_original_pos.row,target_original_pos.col);
         }
     }
     if(!attack_is_part_of_another_action && EquippedWeapon.status[EquipmentStatus.POISONED] && !weapon_just_poisoned && R.OneIn(16)){
         ApplyStatus(AttrType.POISONED,(R.Roll(2,6)+2)*100,"You manage to poison yourself with your " + EquippedWeapon.NameWithoutEnchantment() + ". ","","You resist the poison dripping from your " + EquippedWeapon.NameWithoutEnchantment() + ". ");
     }
     if(!attack_is_part_of_another_action && EquippedWeapon.status[EquipmentStatus.HEAVY] && R.CoinFlip() && !HasAttr(AttrType.BRUTISH_STRENGTH)){
         B.Add("Attacking with your heavy " + EquippedWeapon.NameWithoutEnchantment() + " exhausts you. ");
         IncreaseExhaustion(5);
     }
     MakeNoise(6);
     if(!attack_is_part_of_another_action){
         Q.Add(new Event(this,info.cost));
     }
     return hit;
 }
コード例 #7
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;
     }
 }
コード例 #8
0
		public async Task<bool> Use(Actor user,List<Tile> line){
			bool used = true;
			switch(itype){
			case ConsumableType.HEALING:
				await user.TakeDamage(DamageType.HEAL,DamageClass.NO_TYPE,50,null); //was Roll(8,6)
				B.Add("A blue glow surrounds " + user.the_name + ". ",new PhysicalObject[]{user});
				break;
			case ConsumableType.TOXIN_IMMUNITY:
				if(!user.HasAttr(AttrType.IMMUNE_TOXINS)){
					if(user.HasAttr(AttrType.POISONED)){
						user.attrs[AttrType.POISONED] = 0;
						B.Add(user.YouFeel() + " relieved. ",user);
					}
					user.GainAttr(AttrType.IMMUNE_TOXINS,5100,user.YouAre() + " no longer immune to toxins. ",new PhysicalObject[]{user});
				}
				else{
					B.Add("Nothing happens. ",user);
				}
				break;
			case ConsumableType.REGENERATION:
			{
				user.attrs[AttrType.REGENERATING]++;
				if(user.name == "you"){
					B.Add("Your blood tingles. ",user);
				}
				else{
					B.Add(user.the_name + " looks energized. ",user);
				}
				int duration = 60; //was Roll(10)+20
				Q.Add(new Event(user,duration*100,AttrType.REGENERATING));
				break;
			}
			/*case ConsumableType.RESISTANCE:
				{
				user.attrs[AttrType.RESIST_FIRE]++;
				user.attrs[AttrType.RESIST_COLD]++;
				user.attrs[AttrType.RESIST_ELECTRICITY]++;
				B.Add(user.YouFeel() + " insulated. ",user);
				int duration = Global.Roll(2,10)+5;
				Q.Add(new Event(user,duration*100,AttrType.RESIST_FIRE));
				Q.Add(new Event(user,duration*100,AttrType.RESIST_COLD));
				Q.Add(new Event(user,duration*100,AttrType.RESIST_ELECTRICITY,user.YouFeel() + " less insulated. ",user));
				if(user.HasAttr(AttrType.ON_FIRE) || user.HasAttr(AttrType.CATCHING_FIRE)
				|| user.HasAttr(AttrType.STARTED_CATCHING_FIRE_THIS_TURN)){
					B.Add(user.YouAre() + " no longer on fire. ",user);
					int oldradius = user.LightRadius();
					user.attrs[AttrType.ON_FIRE] = 0;
					user.attrs[AttrType.CATCHING_FIRE] = 0;
					user.attrs[AttrType.STARTED_CATCHING_FIRE_THIS_TURN] = 0;
					if(oldradius != user.LightRadius()){
						user.UpdateRadius(oldradius,user.LightRadius());
					}
				}
				break;
				}*/
			case ConsumableType.CLARITY:
				user.ResetSpells();
				if(user.name == "you"){
					B.Add("Your mind clears. ");
				}
				else{
					B.Add(user.the_name + " seems focused. ",user);
				}
				break;
			case ConsumableType.CLOAKING:
				if(user.tile().IsLit()){
					B.Add("You would feel at home in the shadows. ");
				}
				else{
					B.Add("You fade away in the darkness. ");
				}
				user.GainAttrRefreshDuration(AttrType.SHADOW_CLOAK,(Global.Roll(41)+29)*100,"You are no longer cloaked. ",user);
				break;
			case ConsumableType.BLINKING:
				for(int i=0;i<9999;++i){
					int rr = Global.Roll(1,17) - 9;
					int rc = Global.Roll(1,17) - 9;
					if(Math.Abs(rr) + Math.Abs(rc) >= 6){
						rr += user.row;
						rc += user.col;
						if(M.BoundsCheck(rr,rc) && M.tile[rr,rc].passable && M.actor[rr,rc] == null){
							B.Add(user.You("step") + " through a rip in reality. ",new PhysicalObject[]{M.tile[user.row,user.col],M.tile[rr,rc]});
							user.AnimateStorm(2,3,4,"*",Color.DarkMagenta);
                            await user.Move(rr, rc);
							M.Draw();
							user.AnimateStorm(2,3,4,"*",Color.DarkMagenta);
							break;
						}
					}
				}
				break;
			case ConsumableType.TELEPORTATION:
				for(int i=0;i<9999;++i){
					int rr = Global.Roll(1,Global.ROWS-2);
					int rc = Global.Roll(1,Global.COLS-2);
					if(Math.Abs(rr-user.row) >= 10 || Math.Abs(rc-user.col) >= 10 || (Math.Abs(rr-user.row) >= 7 && Math.Abs(rc-user.col) >= 7)){
						if(M.BoundsCheck(rr,rc) && M.tile[rr,rc].passable && M.actor[rr,rc] == null){
                            B.Add(user.You("jump") + " through a rift in reality. ", new PhysicalObject[] { M.tile[user.row, user.col], M.tile[rr, rc] });
							user.AnimateStorm(3,3,10,"*",Color.Green);
                            await user.Move(rr, rc);
							M.Draw();
							user.AnimateStorm(3,3,10,"*",Color.Green);
							break;
						}
					}
				}
				break;
			case ConsumableType.PASSAGE:
				{
				int i = user.DirectionOfOnlyUnblocked(TileType.WALL,true);
				if(i == 0){
					B.Add("This item requires an adjacent wall. ");
					used = false;
					break;
				}
				else{
					i = await user.GetDirection(true,false);
					Tile t = user.TileInDirection(i);
					if(t != null){
						if(t.ttype == TileType.WALL){
							Game.Console.CursorVisible = false;
							colorchar ch = new colorchar(Color.Cyan,"!");
							switch(user.DirectionOf(t)){
							case 8:
							case 2:
								ch.c = "|";
								break;
							case 4:
							case 6:
								ch.c = "-";
								break;
							}
							List<Tile> tiles = new List<Tile>();
							List<colorchar> memlist = new List<colorchar>();
							while(!t.passable){
								if(t.row == 0 || t.row == Global.ROWS-1 || t.col == 0 || t.col == Global.COLS-1){
									break;
								}
								tiles.Add(t);
								memlist.Add(Screen.MapChar(t.row,t.col));
								Screen.WriteMapChar(t.row,t.col,ch);
                                await Task.Delay(35);
								t = t.TileInDirection(i);
							}
							if(t.passable && M.actor[t.row,t.col] == null){
								if(M.tile[user.row,user.col].inv != null){
									Screen.WriteMapChar(user.row,user.col,new colorchar(user.tile().inv.color,user.tile().inv.symbol));
								}
								else{
									Screen.WriteMapChar(user.row,user.col,new colorchar(user.tile().color,user.tile().symbol));
								}
								Screen.WriteMapChar(t.row,t.col,new colorchar(user.color,user.symbol));
								int j = 0;
								foreach(Tile tile in tiles){
									Screen.WriteMapChar(tile.row,tile.col,memlist[j++]);
                                    await Task.Delay(35);
								}
								B.Add(user.You("travel") + " through the passage. ",user,t);
                                await user.Move(t.row, t.col);
							}
							else{
								int j = 0;
								foreach(Tile tile in tiles){
									Screen.WriteMapChar(tile.row,tile.col,memlist[j++]);
                                    await Task.Delay(35);
								}
								B.Add("The passage is blocked. ",user);
							}
						}
						else{
							B.Add("This item requires an adjacent wall. ");
							used = false;
							break;
						}
					}
					else{
						used = false;
					}
				}
				break;
				}
			case ConsumableType.TIME:
				B.Add("Time stops for a moment. ");
				Q.turn -= 200;
				break;
			case ConsumableType.DETECT_MONSTERS:
			{
				//user.attrs[AttrType.DETECTING_MONSTERS]++;
				B.Add("The scroll reveals " + user.Your() + " foes. ",user);
				int duration = Global.Roll(20)+30;
				//Q.Add(new Event(user,duration*100,AttrType.DETECTING_MONSTERS,user.Your() + " foes are no longer revealed. ",user));
				user.GainAttrRefreshDuration(AttrType.DETECTING_MONSTERS,duration*100,user.Your() + " foes are no longer revealed. ",user);
				break;
			}
			case ConsumableType.MAGIC_MAP:
			{
				B.Add("The scroll reveals the layout of this level. ");
				Event hiddencheck = null;
				foreach(Event e in Q.list){
					if(!e.dead && e.evtype == EventType.CHECK_FOR_HIDDEN){
						hiddencheck = e;
						break;
					}
				}
				foreach(Tile t in M.AllTiles()){
					if(t.ttype != TileType.FLOOR){
						bool good = false;
						foreach(Tile neighbor in t.TilesAtDistance(1)){
							if(neighbor.ttype != TileType.WALL){
								good = true;
							}
						}
						if(good){
							t.seen = true;
							if(t.IsTrapOrVent() || t.Is(TileType.HIDDEN_DOOR)){
								if(hiddencheck != null){
									hiddencheck.area.Remove(t);
								}
							}
							if(t.IsTrapOrVent()){
								t.name = Tile.Prototype(t.ttype).name;
								t.a_name = Tile.Prototype(t.ttype).a_name;
								t.the_name = Tile.Prototype(t.ttype).the_name;
								t.symbol = Tile.Prototype(t.ttype).symbol;
								t.color = Tile.Prototype(t.ttype).color;
							}
							if(t.Is(TileType.HIDDEN_DOOR)){
								t.Toggle(null);
							}
						}
					}
				}
				break;
			}
			case ConsumableType.SUNLIGHT:
				if(!M.wiz_lite){
					M.wiz_lite = true;
					M.wiz_dark = false;
					B.Add("The air itself seems to shine. ");
				}
				else{
					B.Add("Nothing happens. ");
				}
				break;
			case ConsumableType.DARKNESS:
				if(!M.wiz_dark){
					M.wiz_dark = true;
					M.wiz_lite = false;
					B.Add("The air itself grows dark. ");
				}
				else{
					B.Add("Nothing happens. ");
				}
				break;
			case ConsumableType.PRISMATIC:
			{
				if(line == null){
					line = await user.GetTarget(12,1);
				}
				if(line != null){
					Tile t = line.Last();
					Tile prev = line.LastBeforeSolidTile();
					Actor first = user.FirstActorInLine(line); //todo - consider allowing thrown items to pass over actors, because they fly in an arc
					B.Add(user.You("throw") + " the prismatic orb. ",user);
					if(first != null){
						t = first.tile();
						B.Add("It shatters on " + first.the_name + "! ",first);
					}
					else{
						B.Add("It shatters on " + t.the_name + "! ",t);
					}
					user.AnimateProjectile(line.ToFirstObstruction(),"*",Color.RandomPrismatic);
					List<DamageType> dmg = new List<DamageType>();
					dmg.Add(DamageType.FIRE);
					dmg.Add(DamageType.COLD);
					dmg.Add(DamageType.ELECTRIC);
					while(dmg.Count > 0){
						DamageType damtype = dmg.Random();
						colorchar ch = new colorchar(Color.Black,"*");
						switch(damtype){
						case DamageType.FIRE:
							ch.color = Color.RandomFire;
							break;
						case DamageType.COLD:
							ch.color = Color.RandomIce;
							break;
						case DamageType.ELECTRIC:
							ch.color = Color.RandomLightning;
							break;
						}
						B.DisplayNow();
						Screen.AnimateExplosion(t,1,ch,100);
						if(t.passable){
							foreach(Tile t2 in t.TilesWithinDistance(1)){
								if(t2.actor() != null){
									await t2.actor().TakeDamage(damtype,DamageClass.MAGICAL,Global.Roll(2,6),user,"a prismatic orb");
								}
								if(damtype == DamageType.FIRE && t2.Is(FeatureType.TROLL_CORPSE)){
									t2.features.Remove(FeatureType.TROLL_CORPSE);
									B.Add("The troll corpse burns to ashes! ",t2);
								}
								if(damtype == DamageType.FIRE && t2.Is(FeatureType.TROLL_SEER_CORPSE)){
									t2.features.Remove(FeatureType.TROLL_SEER_CORPSE);
									B.Add("The troll seer corpse burns to ashes! ",t2);
								}
							}
						}
						else{
							foreach(Tile t2 in t.TilesWithinDistance(1)){
								if(prev != null && prev.HasBresenhamLine(t2.row,t2.col)){
									if(t2.actor() != null){
										await t2.actor().TakeDamage(damtype,DamageClass.MAGICAL,Global.Roll(2,6),user,"a prismatic orb");
									}
									if(damtype == DamageType.FIRE && t2.Is(FeatureType.TROLL_CORPSE)){
										t2.features.Remove(FeatureType.TROLL_CORPSE);
										B.Add("The troll corpse burns to ashes! ",t2);
									}
									if(damtype == DamageType.FIRE && t2.Is(FeatureType.TROLL_SEER_CORPSE)){
										t2.features.Remove(FeatureType.TROLL_SEER_CORPSE);
										B.Add("The troll seer corpse burns to ashes! ",t2);
									}
								}
							}
						}
						dmg.Remove(damtype);
					}
				}
				else{
					used = false;
				}
				break;
			}
			case ConsumableType.FREEZING:
			{
				if(line == null){
					line = await user.GetTarget(12,3);
				}
				if(line != null){
					Tile t = line.Last();
					Tile prev = line.LastBeforeSolidTile();
					Actor first = user.FirstActorInLine(line);
					B.Add(user.You("throw") + " the freezing orb. ",user);
					if(first != null){
						t = first.tile();
						B.Add("It shatters on " + first.the_name + "! ",first);
					}
					else{
						B.Add("It shatters on " + t.the_name + "! ",t);
					}
					user.AnimateProjectile(line.ToFirstObstruction(),"*",Color.RandomIce);
					user.AnimateExplosion(t,3,"*",Color.Cyan);
					List<Actor> targets = new List<Actor>();
					if(t.passable){
						foreach(Actor ac in t.ActorsWithinDistance(3)){
							if(t.HasLOE(ac)){
								targets.Add(ac);
							}
						}
					}
					else{
						foreach(Actor ac in t.ActorsWithinDistance(3)){
							if(prev != null && prev.HasLOE(ac)){
								targets.Add(ac);
							}
						}
					}
					while(targets.Count > 0){
						Actor ac = targets.RemoveRandom();
						B.Add(ac.YouAre() + " encased in ice. ",ac);
						ac.attrs[Forays.AttrType.FROZEN] = 25;
					}
				}
				else{
					used = false;
				}
				break;
			}
			case ConsumableType.QUICKFIRE:
			{
				if(line == null){
					line = await user.GetTarget(12,-1);
				}
				if(line != null){
					Tile t = line.Last();
					Tile prev = line.LastBeforeSolidTile();
					Actor first = user.FirstActorInLine(line);
					B.Add(user.You("throw") + " the orb of quickfire. ",user);
					if(first != null){
						t = first.tile();
						B.Add("It shatters on " + first.the_name + "! ",first);
					}
					else{
						B.Add("It shatters on " + t.the_name + "! ",t);
					}
					user.AnimateProjectile(line.ToFirstObstruction(),"*",Color.RandomFire);
					if(t.passable){
						t.features.Add(FeatureType.QUICKFIRE);
						Q.Add(new Event(t,new List<Tile>{t},100,EventType.QUICKFIRE,AttrType.NO_ATTR,3,""));
					}
					else{
						prev.features.Add(FeatureType.QUICKFIRE);
						Q.Add(new Event(prev,new List<Tile>{prev},100,EventType.QUICKFIRE,AttrType.NO_ATTR,3,""));
					}
				}
				else{
					used = false;
				}
				break;
			}
			case ConsumableType.FOG:
			{
				if(line == null){
					line = await user.GetTarget(12,-3);
				}
				if(line != null){
					Tile t = line.Last();
					Tile prev = line.LastBeforeSolidTile();
					Actor first = user.FirstActorInLine(line);
					B.Add(user.You("throw") + " the orb of fog. ",user);
					if(first != null){
						t = first.tile();
						B.Add("It shatters on " + first.the_name + "! ",first);
					}
					else{
						B.Add("It shatters on " + t.the_name + "! ",t);
					}
					user.AnimateProjectile(line.ToFirstObstruction(),"*",Color.Gray);
					List<Tile> area = new List<Tile>();
					List<pos> cells = new List<pos>();
					if(t.passable){
						foreach(Tile tile in t.TilesWithinDistance(3)){
							if(tile.passable && t.HasLOE(tile)){
								tile.AddOpaqueFeature(FeatureType.FOG);
								area.Add(tile);
								cells.Add(tile.p);
							}
						}
					}
					else{
						foreach(Tile tile in t.TilesWithinDistance(3)){
							if(prev != null && tile.passable && prev.HasLOE(tile)){
								tile.AddOpaqueFeature(FeatureType.FOG);
								area.Add(tile);
								cells.Add(tile.p);
							}
						}
					}
					Screen.AnimateMapCells(cells,new colorchar("*",Color.Gray));
					Q.Add(new Event(area,400,EventType.FOG));
				}
				else{
					used = false;
				}
				break;
			}
			case ConsumableType.BANDAGE:
				await user.TakeDamage(DamageType.HEAL,DamageClass.NO_TYPE,1,null);
				if(user.HasAttr(AttrType.MAGICAL_BLOOD)){
					user.recover_time = Q.turn + 200;
				}
				else{
					user.recover_time = Q.turn + 500;
				}
				if(user.name == "you"){
					B.Add("You apply a bandage. ");
				}
				else{
					B.Add(user.the_name + " applies a bandage. ",user);
				}
				break;
			default:
				used = false;
				break;
			}
			if(used){
				if(quantity > 1){
					--quantity;
				}
				else{
					if(user != null){
						user.inv.Remove(this);
					}
				}
			}
			return used;
		}
コード例 #9
0
		public async Task Execute(){
			if(!dead){
				switch(evtype){
				case EventType.MOVE:
				{
					Actor temp = target as Actor;
					await temp.Input();
					break;
				}
				case EventType.REMOVE_ATTR:
				{
					Actor temp = target as Actor;
					if(temp.atype == ActorType.BERSERKER && attr == AttrType.COOLDOWN_2){
						temp.attrs[attr] = 0;
					}
					else{
						temp.attrs[attr] -= value;
					}
					if(attr == AttrType.TELEPORTING || attr == AttrType.ARCANE_SHIELDED){
						temp.attrs[attr] = 0;
					}
					if(attr==AttrType.ENHANCED_TORCH && temp.light_radius > 0){
						temp.UpdateRadius(temp.LightRadius(),6 - temp.attrs[AttrType.DIM_LIGHT],true); //where 6 is the default radius
						if(temp.attrs[AttrType.ON_FIRE] > temp.light_radius){
							temp.UpdateRadius(temp.light_radius,temp.attrs[AttrType.ON_FIRE]);
						}
					}
					if(attr==AttrType.SLOWED){
						if(temp.atype != ActorType.PLAYER){
							temp.speed = Actor.Prototype(temp.atype).speed;
						}
						else{
							if(temp.HasAttr(AttrType.LONG_STRIDE)){
								temp.speed = 80;
							}
							else{
								temp.speed = 100;
							}
						}
					}
					if(attr==AttrType.AFRAID && target == player){
						Global.FlushInput();
					}
					if(attr==AttrType.BLOOD_BOILED){
						temp.speed += (10 * value);
					}
					if(attr==AttrType.CONVICTION){
						if(temp.HasAttr(AttrType.IN_COMBAT)){
							temp.attrs[Forays.AttrType.CONVICTION] += value; //whoops, undo that
						}
						else{
							temp.attrs[Forays.AttrType.BONUS_SPIRIT] -= value;      //otherwise, set things to normal
							temp.attrs[Forays.AttrType.BONUS_COMBAT] -= value / 2;
							if(temp.attrs[Forays.AttrType.KILLSTREAK] >= 2){
								B.Add("You wipe off your weapon. ");
							}
							temp.attrs[Forays.AttrType.KILLSTREAK] = 0;
						}
					}
					if(attr==AttrType.STUNNED && msg.Search(new System.Text.RegularExpressions.Regex("disoriented")) > 0){
						if(!player.CanSee(target)){
							msg = "";
						}
					}
					if(attr==AttrType.POISONED && temp == player){
						if(temp.HasAttr(AttrType.POISONED)){
							B.Add("The poison begins to subside. ");
						}
						else{
							B.Add("You are no longer poisoned. ");
						}
					}
					if(attr==AttrType.COOLDOWN_1 && temp.atype == ActorType.BERSERKER){
						B.Add(temp.Your() + " rage diminishes. ",temp);
						B.Add(temp.the_name + " dies. ",temp);
                        await temp.TakeDamage(DamageType.NORMAL, DamageClass.NO_TYPE, 8888, null);
					}
					break;
				}
				case EventType.CHECK_FOR_HIDDEN:
				{
					List<Tile> removed = new List<Tile>();
					foreach(Tile t in area){
						if(player.CanSee(t)){
							int exponent = player.DistanceFrom(t) + 1;
							if(player.HasAttr(AttrType.KEEN_EYES)){
								--exponent;
							}
							if(!t.IsLit()){
								if(!player.HasAttr(AttrType.SHADOWSIGHT)){
									++exponent;
								}
							}
							if(exponent > 8){
								exponent = 8; //because 1 in 256 is enough.
							}
							int difficulty = 1;
							for(int i=exponent;i>0;--i){
								difficulty = difficulty * 2;
							}
							if(Global.Roll(difficulty) == difficulty){
								if(t.IsTrap() || t.Is(TileType.FIRE_GEYSER) || t.Is(TileType.FOG_VENT) || t.Is(TileType.POISON_GAS_VENT)){
									t.name = Tile.Prototype(t.ttype).name;
									t.a_name = Tile.Prototype(t.ttype).a_name;
									t.the_name = Tile.Prototype(t.ttype).the_name;
									t.symbol = Tile.Prototype(t.ttype).symbol;
									t.color = Tile.Prototype(t.ttype).color;
									B.Add("You notice " + t.a_name + ". ");
								}
								else{
									if(t.ttype == TileType.HIDDEN_DOOR){
										t.Toggle(null);
										B.Add("You notice a hidden door. ");
									}
								}
								removed.Add(t);
							}
						}
					}
					foreach(Tile t in removed){
						area.Remove(t);
					}
					if(area.Count > 0){
						Q.Add(new Event(area,100,EventType.CHECK_FOR_HIDDEN));
					}
					break;
				}
				case EventType.RELATIVELY_SAFE:
				{
					if(M.AllActors().Count == 1 && !Q.Contains(EventType.POLTERGEIST) && !Q.Contains(EventType.BOSS_ARRIVE)
					&& !Q.Contains(EventType.REGENERATING_FROM_DEATH) && !Q.Contains(EventType.MIMIC) && !Q.Contains(EventType.MARBLE_HORROR)){
						B.Add("The dungeon is still and silent. ");
                        await B.PrintAll();
					}
					else{
						Q.Add(new Event((Global.Roll(20)+40)*100,EventType.RELATIVELY_SAFE));
					}
					break;
				}
				case EventType.POLTERGEIST:
				{
					if(target != null && target is Actor){ //target can either be a stolen item, or the currently manifested poltergeist.
						Q.Add(new Event(target,area,(Global.Roll(8)+6)*100,EventType.POLTERGEIST,AttrType.NO_ATTR,0,""));
						break; //if it's manifested, the event does nothing for now.
					}
					if(area.Any(t => t.actor() == player)){
						bool manifested = false;
						if(value == 0){
							B.Add("You feel like you're being watched. ");
						}
						else{
							if(target != null){ //if it has a stolen item
								Tile tile = null;
								tile = area.Where(t => t.actor() == null && t.DistanceFrom(player) >= 2
								                  && t.HasLOE(player) && t.FirstActorInLine(player) == player).Random();
								if(tile != null){
									Actor temporary = new Actor(ActorType.POLTERGEIST,"something","G",Color.DarkGreen,1,1,0,0);
									temporary.a_name = "something";
									temporary.the_name = "something";
									temporary.p = tile.p;
									temporary.inv = new List<Item>();
									temporary.inv.Add(target as Item);
									Item item = temporary.inv[0];
									if(item.symbol == "*"){ //orbs
										if(item.itype == ConsumableType.SUNLIGHT || item.itype == ConsumableType.DARKNESS){
											B.Add(temporary.You("throw") + " " + item.AName() + ". ",temporary);
											B.DisplayNow();
											Screen.AnimateProjectile(tile.GetBestExtendedLineOfEffect(player).ToFirstObstruction(),new colorchar(item.color,item.symbol));
											B.Add(item.TheName() + " shatters on you! ");
										}
										await temporary.inv[0].Use(temporary,temporary.GetBestExtendedLineOfEffect(player));
									}
									else{
										B.Add(temporary.You("throw") + " " + item.AName() + ". ",temporary);
										B.DisplayNow();
										Screen.AnimateProjectile(tile.GetBestExtendedLineOfEffect(player).ToFirstObstruction(),new colorchar(item.color,item.symbol));
										player.tile().GetItem(item);
										B.Add(item.TheName() + " hits you. ");
                                        await player.TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(6), temporary, "a flying " + item.Name());
									}
									target = null;
								}
								else{
									Q.Add(new Event(target,area,100,EventType.POLTERGEIST,AttrType.NO_ATTR,value,""));
									return; //try again next turn
								}
							}
							else{
								if(value >= 3 && area.Any(t => t.DistanceFrom(player) == 1 && t.passable && t.actor() == null)){
									Tile tile = area.Where(t => t.DistanceFrom(player) == 1 && t.passable && t.actor() == null).Random();
									B.DisplayNow();
									for(int i=4;i>0;--i){
										Screen.AnimateStorm(tile.p,i,2,1,"G",Color.DarkGreen);
									}
									Actor a = Actor.Create(ActorType.POLTERGEIST,tile.row,tile.col);
									Q.KillEvents(a,EventType.MOVE);
									a.Q0();
									a.player_visibility_duration = -1;
									foreach(Event e in Q.list){
										if( e != null && e.target != null && e.target == a && e.evtype == EventType.MOVE){
											e.tiebreaker = this.tiebreaker;
											break;
										}
									}
									Actor.tiebreakers[tiebreaker] = a;
									B.Add("A poltergeist manifests in front of you! ");
									Q.Add(new Event(a,area,(Global.Roll(8)+6)*100,EventType.POLTERGEIST,AttrType.NO_ATTR,0,""));
									manifested = true;
								}
								else{
									if(player.tile().ttype == TileType.DOOR_O){
										B.Add("The door slams closed on you! ");
										await player.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,Global.Roll(6),null,"a slamming door");
									}
									else{
										Tile tile = null; //check for items to throw...
										tile = area.Where(t => t.inv != null && t.actor() == null && t.DistanceFrom(player) >= 2
										                  && t.HasLOE(player) && t.FirstActorInLine(player) == player).Random();
										if(tile != null){
											Actor temporary = new Actor(ActorType.POLTERGEIST,"something","G",Color.DarkGreen,1,1,0,0);
											temporary.a_name = "something";
											temporary.the_name = "something";
											temporary.p = tile.p;
											temporary.inv = new List<Item>();
											if(tile.inv.quantity <= 1){
												temporary.inv.Add(tile.inv);
												tile.inv = null;
											}
											else{
												temporary.inv.Add(new Item(tile.inv,-1,-1));
												tile.inv.quantity--;
											}
											M.Draw();
											Item item = temporary.inv[0];
											if(item.symbol == "*"){ //orbs
												if(item.itype == ConsumableType.SUNLIGHT || item.itype == ConsumableType.DARKNESS){
													B.Add(temporary.You("throw") + " " + item.TheName() + ". ",temporary);
													B.DisplayNow();
													Screen.AnimateProjectile(tile.GetBestExtendedLineOfEffect(player).ToFirstObstruction(),new colorchar(item.color,item.symbol));
													B.Add(item.TheName() + " shatters on you! ");
												}
                                                await temporary.inv[0].Use(temporary, temporary.GetBestExtendedLineOfEffect(player));
											}
											else{
												B.Add(temporary.You("throw") + " " + item.TheName() + ". ",temporary);
												B.DisplayNow();
												Screen.AnimateProjectile(tile.GetBestExtendedLineOfEffect(player).ToFirstObstruction(),new colorchar(item.color,item.symbol));
												player.tile().GetItem(item);
												B.Add(item.TheName() + " hits you. ");
                                                await player.TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(6), temporary, "a flying " + item.Name());
											}
										}
										else{
											if(area.Any(t => t.ttype == TileType.DOOR_O || t.ttype == TileType.DOOR_C)){
												Tile door = area.Where(t=>t.ttype == TileType.DOOR_O || t.ttype == TileType.DOOR_C).Random();
												if(door.ttype == TileType.DOOR_C){
													if(player.CanSee(door)){
														B.Add("The door flies open! ",door);
													}
													else{
														if(door.seen || player.DistanceFrom(door) <= 12){
															B.Add("You hear a door slamming. ");
														}
													}
													door.Toggle(null);
												}
												else{
													if(door.actor() == null){
														if(player.CanSee(door)){
															B.Add("The door slams closed! ",door);
														}
														else{
															if(door.seen || player.DistanceFrom(door) <= 12){
																B.Add("You hear a door slamming. ");
															}
														}
														door.Toggle(null);
													}
													else{
														if(player.CanSee(door)){
															B.Add("The door slams closed on " + door.actor().TheVisible() + "! ",door);
														}
														else{
															if(player.DistanceFrom(door) <= 12){
																B.Add("You hear a door slamming and a grunt of pain. ");
															}
														}
                                                        await door.actor().TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(6), null, "a slamming door");
													}
												}
											}
											else{
												B.Add("You hear mocking laughter from nearby. ");
											}
										}
									}
								}
							}
						}
						if(!manifested){
							Q.Add(new Event(target,area,(Global.Roll(8)+6)*100,EventType.POLTERGEIST,AttrType.NO_ATTR,value+1,""));
						}
					}
					else{
						Q.Add(new Event(target,area,(Global.Roll(8)+6)*100,EventType.POLTERGEIST,AttrType.NO_ATTR,0,""));
					}
					break;
				}
				case EventType.MIMIC:
				{
					Item item = target as Item;
					if(area[0].inv != item){ //it could have been picked up by the player or moved in another way
						foreach(Tile t in M.AllTiles()){ //if it was moved, make the correction to the event's area.
							if(t.inv == item){
								area = new List<Tile>{t};
								break;
							}
						}
					}
					if(area[0].inv == item){
						bool attacked = false;
						if(player.DistanceFrom(area[0]) == 1 && area[0].actor() == null){
							if(player.Stealth() * 5 < Global.Roll(1,100)){
								B.Add(item.TheName() + " suddenly grows tentacles! ");
								attacked = true;
								area[0].inv = null;
								Actor a = Actor.Create(ActorType.MIMIC,area[0].row,area[0].col);
								Q.KillEvents(a,EventType.MOVE);
								a.Q0();
								a.player_visibility_duration = -1;
								a.symbol = item.symbol;
								a.color = item.color;
								foreach(Event e in Q.list){
									if(e.target == a && e.evtype == EventType.MOVE){
										e.tiebreaker = this.tiebreaker;
										break;
									}
								}
								Actor.tiebreakers[tiebreaker] = a;
							}
						}
						if(!attacked){
							Q.Add(new Event(target,area,100,EventType.MIMIC,AttrType.NO_ATTR,0,""));
						}
					}
					else{ //if the item is missing, we assume that the player just picked it up
						List<Tile> open = new List<Tile>();
						foreach(Tile t in player.TilesAtDistance(1)){
							if(t.passable && t.actor() == null){
								open.Add(t);
							}
						}
						if(open.Count > 0){
							Tile t = open.Random();
							B.Add(item.TheName() + " suddenly grows tentacles! ");
							Actor a = Actor.Create(ActorType.MIMIC,t.row,t.col);
							Q.KillEvents(a,EventType.MOVE);
							a.Q0();
							a.player_visibility_duration = -1;
							a.symbol = item.symbol;
							a.color = item.color;
							foreach(Event e in Q.list){
								if(e.target == a && e.evtype == EventType.MOVE){
									e.tiebreaker = this.tiebreaker;
									break;
								}
							}
							Actor.tiebreakers[tiebreaker] = a;
							player.inv.Remove(item);
						}
						else{
							B.Add("Your pack feels lighter. ");
							player.inv.Remove(item);
						}
					}
					break;
				}
				case EventType.GRENADE:
					{
					Tile t = target as Tile;
					if(t.Is(FeatureType.GRENADE)){
						t.features.Remove(FeatureType.GRENADE);
						B.Add("The grenade explodes! ",t);
						if(t.seen){
							Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col));
						}
						B.DisplayNow();
						List<pos> cells = new List<pos>();
						foreach(Tile tile in t.TilesWithinDistance(1)){
							if(tile.passable && tile.seen){
								cells.Add(tile.p);
							}
						}
						Screen.AnimateMapCells(cells,new colorchar('*',Color.DarkRed));
						//Screen.AnimateExplosion(t,1,new colorchar('*',Color.DarkRed));
						foreach(Actor a in t.ActorsWithinDistance(1)){
							await a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,Global.Roll(3,6),null,"an exploding grenade");
						}
						if(t.actor() != null){
							int dir = Global.RandomDirection();
							await t.actor().GetKnockedBack(t.TileInDirection(t.actor().RotateDirection(dir,true,4)));
						}
						if(player.DistanceFrom(t) <= 3){
							player.MakeNoise(); //hacky - todo change
						}
					}
					break;
					}
				case EventType.BLAST_FUNGUS:
				{
					Tile t = target as Tile;
					if(t.Is(FeatureType.FUNGUS_PRIMED)){
						t.features.Remove(FeatureType.FUNGUS_PRIMED);
						B.Add("The blast fungus explodes! ",t);
						if(t.seen){
							Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col));
						}
						B.DisplayNow();
						for(int i=1;i<=3;++i){
							List<pos> cells = new List<pos>();
							foreach(Tile tile in t.TilesWithinDistance(i)){
								if(t.HasLOE(tile) && tile.passable && tile.seen){
									cells.Add(tile.p);
								}
							}
							Screen.AnimateMapCells(cells,new colorchar('*',Color.DarkRed));
						}
						foreach(Actor a in t.ActorsWithinDistance(3)){
							if(t.HasLOE(a)){
								await a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,Global.Roll(5,6),null,"an exploding blast fungus");
							}
						}
						if(t.actor() != null){
							int dir = Global.RandomDirection();
							await t.actor().GetKnockedBack(t.TileInDirection(t.actor().RotateDirection(dir,true,4)));
						}
						if(player.DistanceFrom(t) <= 3){
							player.MakeNoise(); //hacky - todo change
						}
					}
					if(t.Is(FeatureType.FUNGUS_ACTIVE)){
						t.features.Remove(FeatureType.FUNGUS_ACTIVE);
						t.features.Add(FeatureType.FUNGUS_PRIMED);
						Q.Add(new Event(t,100,EventType.BLAST_FUNGUS));
					}
					break;
				}
				case EventType.STALAGMITE:
				{
					int stalagmites = 0;
					foreach(Tile tile in area){
						if(tile.ttype == TileType.STALAGMITE){
							stalagmites++;
						}
					}
					if(stalagmites > 0){
						if(stalagmites > 1){
							B.Add("The stalagmites crumble. ",area.ToArray());
						}
						else{
							B.Add("The stalagmite crumbles. ",area.ToArray());
						}
						foreach(Tile tile in area){
							if(tile.ttype == TileType.STALAGMITE){
								tile.Toggle(null);
							}
						}
					}
					break;
				}
				case EventType.FIRE_GEYSER:
				{
					int frequency = value / 10; //5-25
					int variance = value % 10; //0-9
					int variance_amount = (frequency * variance) / 10;
					int number_of_values = variance_amount*2 + 1;
					int minimum_value = frequency - variance_amount;
					if(minimum_value < 5){
						int diff = 5 - minimum_value;
						number_of_values -= diff;
						minimum_value = 5;
					}
					int delay = ((minimum_value - 1) + Global.Roll(number_of_values)) * 100;
					Q.Add(new Event(target,delay+200,EventType.FIRE_GEYSER,value));
					Q.Add(new Event(target,delay,EventType.FIRE_GEYSER_ERUPTION,2));
					break;
				}
				case EventType.FIRE_GEYSER_ERUPTION:
				{
					if(target.name == "floor"){
						Event hiddencheck = null;
						Tile t = target as Tile;
						foreach(Event e in Q.list){
							if(!e.dead && e.evtype == EventType.CHECK_FOR_HIDDEN){
								hiddencheck = e;
								break;
							}
						}
						if(player.HasLOS(t)){
							//t.seen = true;
							if(hiddencheck != null){
								hiddencheck.area.Remove(t);
							}
							t.name = Tile.Prototype(t.ttype).name;
							t.a_name = Tile.Prototype(t.ttype).a_name;
							t.the_name = Tile.Prototype(t.ttype).the_name;
							t.symbol = Tile.Prototype(t.ttype).symbol;
							t.color = Tile.Prototype(t.ttype).color;
						}
					}
					if(value >= 0){ //a value of -1 means 'reset light radius to 0'
						if(target.light_radius == 0){
							target.UpdateRadius(0,8,true);
						}
						B.Add(target.the_name + " spouts flames! ",target);
						M.Draw();
						for(int i=0;i<3;++i){
							List<pos> cells = new List<pos>();
							List<Tile> tiles = target.TilesWithinDistance(1);
							for(int j=0;j<5;++j){
								Tile t = tiles.RemoveRandom();
								if(player.CanSee(t)){
									cells.Add(t.p);
								}
							}
							if(cells.Count > 0){
								Screen.AnimateMapCells(cells,new colorchar('*',Color.Red),35);
							}
						}
						foreach(Tile t in target.TilesWithinDistance(1)){
							Actor a = t.actor();
							if(a != null){
								if(await a.TakeDamage(DamageType.FIRE,DamageClass.PHYSICAL,Global.Roll(2,6),null,"a fiery eruption")){
									if(!a.HasAttr(AttrType.RESIST_FIRE) && !a.HasAttr(AttrType.IMMUNE_FIRE)
									&& !a.HasAttr(AttrType.ON_FIRE) && !a.HasAttr(AttrType.CATCHING_FIRE)
									&& !a.HasAttr(AttrType.STARTED_CATCHING_FIRE_THIS_TURN)){
										if(a.name == "you"){
											B.Add("You start to catch fire! ");
										}
										else{
											B.Add(a.the_name + " starts to catch fire. ",a);
										}
										a.attrs[AttrType.CATCHING_FIRE] = 1;
									}
								}
							}
							if(t.Is(FeatureType.TROLL_CORPSE)){
								t.features.Remove(FeatureType.TROLL_CORPSE);
								B.Add("The troll corpse burns to ashes! ",t);
							}
							if(t.Is(FeatureType.TROLL_SEER_CORPSE)){
								t.features.Remove(FeatureType.TROLL_SEER_CORPSE);
								B.Add("The troll seer corpse burns to ashes! ",t);
							}
						}
						Q.Add(new Event(target,100,EventType.FIRE_GEYSER_ERUPTION,value - 1));
					}
					else{
						target.UpdateRadius(8,0,true);
					}
					break;
				}
				case EventType.FOG_VENT:
				{
					if(target.name == "floor"){
						Event hiddencheck = null;
						Tile t = target as Tile;
						foreach(Event e in Q.list){
							if(!e.dead && e.evtype == EventType.CHECK_FOR_HIDDEN){
								hiddencheck = e;
								break;
							}
						}
						if(player.CanSee(t)){
							//t.seen = true;
							if(hiddencheck != null){
								hiddencheck.area.Remove(t);
							}
							t.name = Tile.Prototype(t.ttype).name;
							t.a_name = Tile.Prototype(t.ttype).a_name;
							t.the_name = Tile.Prototype(t.ttype).the_name;
							t.symbol = Tile.Prototype(t.ttype).symbol;
							t.color = Tile.Prototype(t.ttype).color;
						}
					}
					Tile current = target as Tile;
					if(!current.Is(FeatureType.FOG)){
						current.AddOpaqueFeature(FeatureType.FOG);
						Q.Add(new Event(new List<Tile>{current},400,EventType.FOG));
					}
					else{
						for(int tries=0;tries<50;++tries){
							List<Tile> open = new List<Tile>();
							foreach(Tile t in current.TilesAtDistance(1)){
								if(t.passable){
									open.Add(t);
								}
							}
							if(open.Count > 0){
								Tile possible = open.Random();
								if(!possible.Is(FeatureType.FOG)){
									possible.AddOpaqueFeature(FeatureType.FOG);
									Q.Add(new Event(new List<Tile>{possible},400,EventType.FOG));
									break;
								}
								else{
									current = possible;
								}
							}
							else{
								break;
							}
						}
					}
					Q.Add(new Event(target,100,EventType.FOG_VENT));
					break;
				}
				case EventType.FOG:
				{
					List<Tile> removed = new List<Tile>();
					foreach(Tile t in area){
						if(t.Is(FeatureType.FOG) && Global.OneIn(4)){
							t.RemoveOpaqueFeature(FeatureType.FOG);
							removed.Add(t);
						}
					}
					foreach(Tile t in removed){
						area.Remove(t);
					}
					if(area.Count > 0){
						Q.Add(new Event(area,100,EventType.FOG));
					}
					break;
				}
				case EventType.POISON_GAS_VENT:
				{
					if(target.name == "floor"){
						Event hiddencheck = null;
						Tile t = target as Tile;
						foreach(Event e in Q.list){
							if(!e.dead && e.evtype == EventType.CHECK_FOR_HIDDEN){
								hiddencheck = e;
								break;
							}
						}
						if(player.CanSee(t)){
							//t.seen = true;
							if(hiddencheck != null){
								hiddencheck.area.Remove(t);
							}
							t.name = Tile.Prototype(t.ttype).name;
							t.a_name = Tile.Prototype(t.ttype).a_name;
							t.the_name = Tile.Prototype(t.ttype).the_name;
							t.symbol = Tile.Prototype(t.ttype).symbol;
							t.color = Tile.Prototype(t.ttype).color;
						}
					}
					Tile current = target as Tile;
					if(Global.OneIn(7)){
						int num = Global.Roll(5) + 2;
						List<Tile> new_area = new List<Tile>();
						for(int i=0;i<num;++i){
							if(!current.Is(FeatureType.POISON_GAS)){
								current.features.Add(FeatureType.POISON_GAS);
								new_area.Add(current);
							}
							else{
								for(int tries=0;tries<50;++tries){
									List<Tile> open = new List<Tile>();
									foreach(Tile t in current.TilesAtDistance(1)){
										if(t.passable){
											open.Add(t);
										}
									}
									if(open.Count > 0){
										Tile possible = open.Random();
										if(!possible.Is(FeatureType.POISON_GAS)){
											possible.features.Add(FeatureType.POISON_GAS);
											new_area.Add(possible);
											break;
										}
										else{
											current = possible;
										}
									}
									else{
										break;
									}
								}
							}
						}
						if(new_area.Count > 0){
							B.Add("Toxic vapors pour from " + target.the_name + "! ",target);
							Q.Add(new Event(new_area,200,EventType.POISON_GAS));
						}
					}
					Q.Add(new Event(target,100,EventType.POISON_GAS_VENT));
					break;
				}
				case EventType.POISON_GAS:
				{
					List<Tile> removed = new List<Tile>();
					foreach(Tile t in area){
						if(t.Is(FeatureType.POISON_GAS) && Global.OneIn(6)){
							t.RemoveOpaqueFeature(FeatureType.POISON_GAS);
							removed.Add(t);
						}
					}
					foreach(Tile t in removed){
						area.Remove(t);
					}
					if(area.Count > 0){
						Q.Add(new Event(area,100,EventType.POISON_GAS));
					}
					break;
				}
				case EventType.STONE_SLAB:
				{
					Tile t = target as Tile;
					if(t.ttype == TileType.STONE_SLAB && (t.IsLitFromAnywhere(true) || area.Any(x=>x.actor()!=null))){
						bool vis = player.CanSee(t);
						t.Toggle(null,Forays.TileType.FLOOR);
						if(!vis && player.CanSee(t)){
							vis = true;
						}
						if(vis){
							B.Add("The stone slab rises with a grinding sound. ");
						}
						else{
							if(player.DistanceFrom(t) <= 6){
								B.Add("You hear a grinding sound. ");
							}
						}
					}
					else{
						if(t.ttype == TileType.FLOOR && !t.IsLitFromAnywhere(true) && t.actor() == null && !area.Any(x=>x.actor()!=null)){
							bool vis = player.CanSee(t);
							t.Toggle(null,Forays.TileType.STONE_SLAB);
							if(!vis && player.CanSee(t)){
								vis = true;
							}
							if(vis){
								B.Add("The stone slab descends with a grinding sound. ");
							}
							else{
								if(player.DistanceFrom(t) <= 6){
									B.Add("You hear a grinding sound. ");
								}
							}
						}
					}
					Q.Add(new Event(target,area,100,EventType.STONE_SLAB));
					break;
				}
				case EventType.MARBLE_HORROR:
				{
					Tile t = target as Tile;
					if(t.ttype == TileType.STATUE){
						if(value == 1 && player.CanSee(t) && !t.IsLit() && t.actor() == null){ //if target was visible last turn & this turn, and it's currently in darkness...
							t.TransformTo(TileType.FLOOR);
							Actor a = Actor.Create(ActorType.MARBLE_HORROR,t.row,t.col,true,true);
							foreach(Event e in Q.list){
								if(e.target == a && e.evtype == EventType.MOVE){
									e.dead = true;
									break;
								}
							}
							a.Q0();
							switch(Global.Roll(2)){
							case 1:
								B.Add("You think that statue might have just moved... ");
								break;
							case 2:
								B.Add("The statue turns its head to face you. ");
								break;
							}
						}
						else{
							if(player.CanSee(t)){
								Q.Add(new Event(target,100,EventType.MARBLE_HORROR,1));
							}
							else{
								Q.Add(new Event(target,100,EventType.MARBLE_HORROR,0));
							}
						}
					}
					break;
				}
				case EventType.REGENERATING_FROM_DEATH:
				{
					if(target.tile().Is(FeatureType.TROLL_CORPSE)){ //otherwise, assume it was destroyed by fire
						value++;
						if(value > 0 && target.actor() == null){
							Actor a = Actor.Create(ActorType.TROLL,target.row,target.col);
							foreach(Event e in Q.list){
								if(e.target == M.actor[target.row,target.col] && e.evtype == EventType.MOVE){
									e.tiebreaker = this.tiebreaker;
									break;
								}
							}
							Actor.tiebreakers[tiebreaker] = a;
							target.actor().curhp = value;
							target.actor().level = 0;
							target.actor().attrs[Forays.AttrType.NO_ITEM]++;
							B.Add("The troll stands up! ",target);
							target.actor().player_visibility_duration = -1;
							if(target.tile().ttype == TileType.DOOR_C){
								target.tile().Toggle(target.actor());
							}
							target.tile().features.Remove(FeatureType.TROLL_CORPSE);
							if(Global.OneIn(3)){
								target.actor().attrs[Forays.AttrType.WANDERING]++;
							}
						}
						else{
							int roll = Global.Roll(20);
							if(value == -1){
								roll = 1;
							}
							if(value == 0){
								roll = 3;
							}
							switch(roll){
							case 1:
							case 2:
								B.Add("The troll's corpse twitches. ",target);
								break;
							case 3:
							case 4:
								B.Add("You hear sounds coming from the troll's corpse. ",target);
								break;
							case 5:
								B.Add("The troll on the floor regenerates. ",target);
								break;
							default:
								break;
							}
							Q.Add(new Event(target,null,100,EventType.REGENERATING_FROM_DEATH,attr,value,""));
						}
					}
					if(target.tile().Is(FeatureType.TROLL_SEER_CORPSE)){ //otherwise, assume it was destroyed by fire
						value++;
						if(value > 0 && target.actor() == null){
							Actor a = Actor.Create(ActorType.TROLL_SEER,target.row,target.col);
							foreach(Event e in Q.list){
								if(e.target == M.actor[target.row,target.col] && e.evtype == EventType.MOVE){
									e.tiebreaker = this.tiebreaker;
									break;
								}
							}
							Actor.tiebreakers[tiebreaker] = a;
							target.actor().curhp = value;
							target.actor().level = 0;
							target.actor().attrs[Forays.AttrType.NO_ITEM]++;
							B.Add("The troll seer stands up! ",target);
							target.actor().player_visibility_duration = -1;
							if(attr == AttrType.COOLDOWN_1){
								target.actor().attrs[Forays.AttrType.COOLDOWN_1]++;
							}
							if(target.tile().ttype == TileType.DOOR_C){
								target.tile().Toggle(target.actor());
							}
							target.tile().features.Remove(FeatureType.TROLL_SEER_CORPSE);
							if(Global.OneIn(3)){
								target.actor().attrs[Forays.AttrType.WANDERING]++;
							}
						}
						else{
							int roll = Global.Roll(20);
							if(value == -1){
								roll = 1;
							}
							if(value == 0){
								roll = 3;
							}
							switch(roll){
							case 1:
							case 2:
								B.Add("The troll seer's corpse twitches. ",target);
								break;
							case 3:
							case 4:
								B.Add("You hear sounds coming from the troll seer's corpse. ",target);
								break;
							case 5:
								B.Add("The troll seer on the floor regenerates. ",target);
								break;
							default:
								break;
							}
							Q.Add(new Event(target,null,100,EventType.REGENERATING_FROM_DEATH,attr,value,""));
						}
					}
					break;
				}
				case EventType.QUICKFIRE:
				{
					List<Actor> actors = new List<Actor>();
					if(value >= 0){
						foreach(Tile t in area){
							if(t.actor() != null){
								actors.Add(t.actor());
							}
							if(t.Is(FeatureType.TROLL_CORPSE)){
								t.features.Remove(FeatureType.TROLL_CORPSE);
								B.Add("The troll corpse burns to ashes! ",t);
							}
							if(t.Is(FeatureType.TROLL_SEER_CORPSE)){
								t.features.Remove(FeatureType.TROLL_SEER_CORPSE);
								B.Add("The troll seer corpse burns to ashes! ",t);
							}
							if(t.Is(FeatureType.FUNGUS)){
								Q.Add(new Event(t,200,EventType.BLAST_FUNGUS));
								Actor.B.Add("The blast fungus starts to smolder in the light. ",t);
								t.features.Remove(FeatureType.FUNGUS);
								t.features.Add(FeatureType.FUNGUS_ACTIVE);
							}
						}
					}
					if(value > 0){
						int radius = 4 - value;
						List<Tile> added = new List<Tile>();
						foreach(Tile t in target.TilesWithinDistance(radius)){
							if(t.passable && !t.Is(FeatureType.QUICKFIRE)
							&& t.IsAdjacentTo(FeatureType.QUICKFIRE) && !area.Contains(t)){
								added.Add(t);
							}
						}
						foreach(Tile t in added){
							area.Add(t);
							t.features.Add(FeatureType.QUICKFIRE);
						}
					}
					if(value < 0){
						int radius = 4 + value;
						List<Tile> removed = new List<Tile>();
						foreach(Tile t in area){
							if(t.DistanceFrom(target) == radius){
								removed.Add(t);
							}
							else{
								if(t.actor() != null){
									actors.Add(t.actor());
								}
								if(t.Is(FeatureType.TROLL_CORPSE)){
									t.features.Remove(FeatureType.TROLL_CORPSE);
									B.Add("The troll corpse burns to ashes! ",t);
								}
								if(t.Is(FeatureType.TROLL_SEER_CORPSE)){
									t.features.Remove(FeatureType.TROLL_SEER_CORPSE);
									B.Add("The troll seer corpse burns to ashes! ",t);
								}
								if(t.Is(FeatureType.FUNGUS)){
									Q.Add(new Event(t,200,EventType.BLAST_FUNGUS));
									Actor.B.Add("The blast fungus starts to smolder in the light. ",t);
									t.features.Remove(FeatureType.FUNGUS);
									t.features.Add(FeatureType.FUNGUS_ACTIVE);
								}
							}
						}
						foreach(Tile t in removed){
							area.Remove(t);
							t.features.Remove(FeatureType.QUICKFIRE);
						}
					}
					foreach(Actor a in actors){
						if(!a.HasAttr(AttrType.IMMUNE_FIRE) && !a.HasAttr(AttrType.INVULNERABLE)){
							if(player.CanSee(a.tile())){
								B.Add("The quickfire burns " + a.the_name + ". ",a);
							}
                            await a.TakeDamage(DamageType.FIRE, DamageClass.PHYSICAL, Global.Roll(6), null, "quickfire");
						}
					}
					--value;
					if(value > -5){
						Q.Add(new Event(target,area,100,EventType.QUICKFIRE,AttrType.NO_ATTR,value,""));
					}
					break;
				}
				case EventType.BOSS_SIGN:
				{
					string s = "";
					switch(Global.Roll(8)){
					case 1:
						s = "You see scratch marks on the walls and floor. ";
						break;
					case 2:
						s = "There are deep gouges in the floor here. ";
						break;
					case 3:
						s = "The floor here is scorched and blackened. ";
						break;
					case 4:
						s = "You notice bones of an unknown sort on the floor. ";
						break;
					case 5:
						s = "You hear a distant roar. ";
						break;
					case 6:
						s = "You smell smoke. ";
						break;
					case 7:
						s = "You spot a large reddish scale on the floor. ";
						break;
					case 8:
						s = "A small tremor shakes the area. ";
						break;
					default:
						s = "Debug message. ";
						break;
					}
					if(!player.HasAttr(AttrType.RESTING)){
						B.AddIfEmpty(s);
					}
					Q.Add(new Event((Global.Roll(20)+35)*100,EventType.BOSS_SIGN));
					break;
				}
				case EventType.BOSS_ARRIVE:
				{
					bool spawned = false;
					Actor a = null;
					if(M.AllActors().Count == 1 && !Q.Contains(EventType.POLTERGEIST)){
						List<Tile> trolls = new List<Tile>();
						foreach (Event current in Q.list){
							if(current.evtype == EventType.REGENERATING_FROM_DEATH){
								trolls.Add((current.target) as Tile);
							}
						}
						foreach(Tile troll in trolls){
							if(troll.Is(FeatureType.TROLL_CORPSE)){
								B.Add("The troll corpse burns to ashes! ",troll);
								troll.features.Remove(FeatureType.TROLL_CORPSE);
							}
							else{
								if(troll.Is(FeatureType.TROLL_SEER_CORPSE)){
									B.Add("The troll seer corpse burns to ashes! ",troll);
									troll.features.Remove(FeatureType.TROLL_SEER_CORPSE);
								}
							}
						}
						Q.KillEvents(null,EventType.REGENERATING_FROM_DEATH);
						List<Tile> goodtiles = M.AllTiles();
						List<Tile> removed = new List<Tile>();
						foreach(Tile t in goodtiles){
							if(!t.passable || t.Is(TileType.CHASM) || player.CanSee(t)){
								removed.Add(t);
							}
						}
						foreach(Tile t in removed){
							goodtiles.Remove(t);
						}
						if(goodtiles.Count > 0){
							B.Add("You hear a loud crash and a nearby roar! ");
							Tile t = goodtiles[Global.Roll(goodtiles.Count)-1];
							a = Actor.Create(ActorType.FIRE_DRAKE,t.row,t.col,true,false);
							spawned = true;
						}
						else{
							if(M.AllTiles().Any(t=>t.passable && !t.Is(TileType.CHASM) && t.actor() == null)){
								B.Add("You hear a loud crash and a nearby roar! ");
								Tile tile = M.AllTiles().Where(t=>t.passable && !t.Is(TileType.CHASM) && t.actor() == null).Random();
								a = Actor.Create(ActorType.FIRE_DRAKE,tile.row,tile.col,true,false);
								spawned = true;
							}
						}
					}
					if(!spawned){
						Q.Add(new Event(null,null,(Global.Roll(20)+10)*100,EventType.BOSS_ARRIVE,attr,value,""));
					}
					else{
						if(value > 0){
							a.curhp = value;
						}
						else{ //if there's no good value, this means that this is the first appearance.
							B.Add("The ground shakes as dust and rocks fall from the cavern ceiling. ");
							B.Add("This place is falling apart! ");
							List<Tile> floors = M.AllTiles().Where(t=>t.passable && t.ttype != TileType.CHASM && player.tile() != t);
							Tile tile = null;
							if(floors.Count > 0){
								tile = floors.Random();
								(tile as Tile).Toggle(null,TileType.CHASM);
							}
							Q.Add(new Event(tile,100,EventType.FLOOR_COLLAPSE));
							Q.Add(new Event((Global.Roll(20)+20)*100,EventType.CEILING_COLLAPSE));

						}
					}
					break;
				}
				case EventType.FLOOR_COLLAPSE:
				{
					Tile current = target as Tile;
					int tries = 0;
					if(current != null){
						for(tries=0;tries<50;++tries){
							List<Tile> open = new List<Tile>();
							foreach(Tile t in current.TilesAtDistance(1)){
								if(t.passable || t.Is(TileType.RUBBLE)){
									open.Add(t);
								}
							}
							if(open.Count > 0){
								Tile possible = open.Random();
								if(!possible.Is(TileType.CHASM)){
									possible.Toggle(null,TileType.CHASM);
									List<Tile> open_neighbors = possible.TilesAtDistance(1).Where(t=>t.passable && t.ttype != TileType.CHASM);
									int num_neighbors = open_neighbors.Count;
									while(open_neighbors.Count > num_neighbors/2){
										Tile neighbor = open_neighbors.RemoveRandom();
										neighbor.Toggle(null,TileType.CHASM);
									}
									break;
								}
								else{
									current = possible;
								}
							}
							else{
								break;
							}
						}
					}
					if(tries == 50 || current == null){
						List<Tile> floors = M.AllTiles().Where(t=>t.passable && t.ttype != TileType.CHASM && player.tile() != t);
						if(floors.Count > 0){
							target = floors.Random();
							(target as Tile).Toggle(null,TileType.CHASM);
						}
					}
					Q.Add(new Event(target,100,EventType.FLOOR_COLLAPSE));
					break;
				}
				case EventType.CEILING_COLLAPSE:
				{
					B.Add("The ground shakes and debris falls from the ceiling! ");
					for(int i=1;i<Global.ROWS-1;++i){
						for(int j=1;j<Global.COLS-1;++j){
							Tile t = M.tile[i,j];
							if(t.Is(TileType.WALL)){
								int num_walls = t.TilesAtDistance(1).Where(x=>x.Is(TileType.WALL)).Count;
								if(num_walls < 8 && Global.OneIn(20)){
									if(Global.CoinFlip()){
										t.Toggle(null,Forays.TileType.FLOOR);
										foreach(Tile neighbor in t.TilesAtDistance(1)){
											neighbor.solid_rock = false;
										}
									}
									else{
										t.Toggle(null,Forays.TileType.RUBBLE);
										foreach(Tile neighbor in t.TilesAtDistance(1)){
											neighbor.solid_rock = false;
											if(neighbor.ttype == TileType.FLOOR && Global.OneIn(10)){
												neighbor.Toggle(null,Forays.TileType.RUBBLE);
											}
										}
									}
								}
							}
							else{
								int num_walls = t.TilesAtDistance(1).Where(x=>x.Is(TileType.WALL)).Count;
								if(num_walls == 0 && Global.OneIn(100)){
									if(Global.OneIn(6)){
										t.Toggle(null,Forays.TileType.RUBBLE);
									}
									foreach(Tile neighbor in t.TilesAtDistance(1)){
										if(neighbor.ttype == TileType.FLOOR && Global.OneIn(6)){
											neighbor.Toggle(null,Forays.TileType.RUBBLE);
										}
									}
								}
							}
						}
					}
					Q.Add(new Event((Global.Roll(20)+20)*100,EventType.CEILING_COLLAPSE));
					break;
				}
				}
				if(msg != ""){
					if(msg_objs == null){
						B.Add(msg);
					}
					else{
						B.Add(msg,msg_objs.ToArray());
					}
				}
			}
		}