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; }
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; }