コード例 #1
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;
 }
コード例 #2
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;
		}