public bool IsCornerFloor(pos p) { if(p.BoundsCheck(map,false)){ if(map[p].IsPassable() && p.ConsecutiveAdjacent(x=>map[x].IsWall()) == 5){ int num_diagonal_walls = 0; foreach(int dir in U.DiagonalDirections){ if(map[p.PosInDir(dir)].IsWall()){ ++num_diagonal_walls; } } if(num_diagonal_walls == 3){ return true; } } } return false; }
public async Task<bool> CastSpell(SpellType spell, PhysicalObject obj, bool force_of_will) { //returns false if targeting is canceled. if ((await StunnedThisTurn()) && !force_of_will) { //eventually this will be moved to the last possible second return true; //returns true because turn was used up. } if (!HasSpell(spell)) { return false; } foreach (Actor a in ActorsWithinDistance(2)) { if (a.HasAttr(AttrType.SPELL_DISRUPTION) && a.HasLOE(this)) { if (this == player) { if (CanSee(a)) { B.Add(a.Your() + " presence disrupts your spell! "); } else { B.Add("Something disrupts your spell! "); } } return false; } } Tile t = null; List<Tile> line = null; if (obj != null) { t = M.tile[obj.row, obj.col]; if (spell == SpellType.FORCE_BEAM) { //force beam requires a line for proper knockback line = GetBestExtendedLine(t); } else { line = GetBestLine(t); } } int bonus = 0; //used for bonus damage on spells - currently, only Master's Edge adds bonus damage. if (FailRate(spell) > 0) { int fail = FailRate(spell); if (force_of_will) { fail = magic_penalty * 5; fail -= skills[SkillType.SPIRIT] * 2; if (fail < 0) { fail = 0; } } if (Global.Roll(1, 100) - fail <= 0) { if (player.CanSee(this)) { B.Add("Sparks fly from " + Your() + " fingers. ", this); } else { if (player.DistanceFrom(this) <= 4 || (player.DistanceFrom(this) <= 12 && player.HasLOS(row, col))) { B.Add("You hear words of magic, but nothing happens. "); } } Q1(); return true; } } if (HasFeat(FeatType.MASTERS_EDGE)) { foreach (SpellType s in spells_in_order) { if (Spell.IsDamaging(s)) { if (s == spell) { bonus = 1; } break; } } } switch (spell) { case SpellType.SHINE: if (!HasAttr(AttrType.ENHANCED_TORCH)) { B.Add("You cast shine. "); if (!M.wiz_dark) { B.Add("Your torch begins to shine brightly. "); } attrs[AttrType.ENHANCED_TORCH]++; if (light_radius > 0) { UpdateRadius(LightRadius(), Global.MAX_LIGHT_RADIUS - attrs[AttrType.DIM_LIGHT] * 2, true); } Q.Add(new Event(9500, "Your torch begins to flicker a bit. ")); Q.Add(new Event(this, 10000, AttrType.ENHANCED_TORCH, "Your torch no longer shines as brightly. ")); } else { B.Add("Your torch is already shining brightly! "); return false; } break; /* case SpellType.MAGIC_MISSILE: if(t == null){ t = await GetTarget(); } if(t != null){ B.Add(You("cast") + " magic missile. ",this); Actor a = FirstActorInLine(t); if(a != null){ AnimateBoltProjectile(a,Color.Magenta); B.Add("The missile hits " + a.the_name + ". ",a); a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,Global.Roll(1+bonus,6),this); } else{ AnimateBoltProjectile(t,Color.Magenta); if(t.IsLit()){ B.Add("The missile hits " + t.the_name + ". "); } else{ B.Add("You attack the darkness. "); } } } else{ return false; } break; case SpellType.DETECT_MONSTERS: if(!HasAttr(AttrType.DETECTING_MONSTERS)){ B.Add(You("cast") + " detect monsters. ",this); if(type == ActorType.PLAYER){ B.Add("You can sense beings around you. "); Q.Add(new Event(this,2100,AttrType.DETECTING_MONSTERS,"You can no longer sense beings around you. ")); } else{ Q.Add(new Event(this,2100,AttrType.DETECTING_MONSTERS)); } attrs[AttrType.DETECTING_MONSTERS]++; } else{ B.Add("You are already detecting monsters! "); return false; } break;*/ case SpellType.IMMOLATE: if (t == null) { line = await GetTarget(12); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " immolate. ", this); Actor a = FirstActorInLine(line); if (a != null) { AnimateBeam(line.ToFirstObstruction(), "*", Color.RandomFire); if (!a.HasAttr(AttrType.RESIST_FIRE) && !a.HasAttr(AttrType.CATCHING_FIRE) && !a.HasAttr(AttrType.ON_FIRE)) { if (a.name == "you") { B.Add("You start to catch fire! "); } else { B.Add(a.the_name + " starts to catch fire. ", a); } a.attrs[AttrType.CATCHING_FIRE]++; } else { B.Add(a.You("shrug") + " off the flames. ", a); } } else { foreach (Tile t2 in line) { if (t2.Is(FeatureType.TROLL_CORPSE) || t2.Is(FeatureType.TROLL_SEER_CORPSE)) { line = line.To(t2); } } AnimateBeam(line, "*", Color.RandomFire); B.Add(You("throw") + " flames. ", this); if (line.Last().Is(FeatureType.TROLL_CORPSE)) { line.Last().features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ", line.Last()); } if (line.Last().Is(FeatureType.TROLL_SEER_CORPSE)) { line.Last().features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ", line.Last()); } } } else { return false; } break; case SpellType.FORCE_PALM: if (t == null) { t = TileInDirection(await GetDirection()); } if (t != null) { Actor a = M.actor[t.row, t.col]; B.Add(You("cast") + " force palm. ", this); //AnimateMapCell(t,Color.DarkCyan,"*"); B.DisplayNow(); Screen.AnimateMapCell(t.row, t.col, new colorchar("*", Color.Blue), 100); if (a != null) { B.Add(You("strike") + " " + a.TheVisible() + ". ", new PhysicalObject[] { this, a }); string s = a.the_name; string s2 = a.a_name; List<Tile> line2 = GetBestExtendedLine(a.row, a.col); int idx = line2.IndexOf(M.tile[a.row, a.col]); Tile next = line2[idx + 1]; await a.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(1 + bonus, 6), this, a_name); if (Global.Roll(1, 10) <= 7) { if (M.actor[t.row, t.col] != null) { await a.GetKnockedBack(this); } else { if (!next.passable) { B.Add(s + "'s corpse is knocked into " + next.the_name + ". ", new PhysicalObject[] { t, next }); } else { if (M.actor[next.row, next.col] != null) { B.Add(s + "'s corpse is knocked into " + M.actor[next.row, next.col].the_name + ". ", new PhysicalObject[] { t, M.actor[next.row, next.col] }); await M.actor[next.row, next.col].TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(1, 6), this, s2 + "'s falling corpse"); } } } } } else { if (t.passable) { B.Add("You strike at empty space. "); } else { B.Add("You strike " + t.the_name + " with your palm. "); if (t.ttype == TileType.DOOR_C) { //heh, why not? B.Add("It flies open! "); t.Toggle(this); } if (t.ttype == TileType.HIDDEN_DOOR) { //and this one gives it an actual use B.Add("A hidden door flies open! "); t.Toggle(this); t.Toggle(this); } } } } else { return false; } break; case SpellType.FREEZE: if (t == null) { line = await GetTarget(12); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " freeze. ", this); Actor a = FirstActorInLine(line); if (a != null) { AnimateBoltBeam(line.ToFirstObstruction(), Color.Cyan); if (!a.HasAttr(AttrType.FROZEN) && !a.HasAttr(AttrType.UNFROZEN)) { B.Add(a.YouAre() + " encased in ice. ", a); a.attrs[AttrType.FROZEN] = 25; } else { B.Add("The beam dissipates on the remaining ice. ", a); } } else { AnimateBoltBeam(line, Color.Cyan); B.Add("A bit of ice forms on " + t.the_name + ". ", t); } } else { return false; } break; case SpellType.BLINK: for (int i = 0; i < 9999; ++i) { int a = Global.Roll(1, 17) - 9; //-8 to 8 int b = Global.Roll(1, 17) - 9; if (Math.Abs(a) + Math.Abs(b) >= 6) { a += row; b += col; if (M.BoundsCheck(a, b) && M.tile[a, b].passable && M.actor[a, b] == null) { B.Add(You("cast") + " blink. ", this); B.Add(You("step") + " through a rip in reality. ", this); AnimateStorm(2, 3, 4, "*", Color.DarkMagenta); await Move(a, b); M.Draw(); AnimateStorm(2, 3, 4, "*", Color.DarkMagenta); break; } } } break; case SpellType.SCORCH: if (t == null) { line = await GetTarget(12); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " scorch. ", this); Actor a = FirstActorInLine(line); if (a != null) { AnimateProjectile(line.ToFirstObstruction(), "*", Color.RandomFire); B.Add("The scorching bolt hits " + a.the_name + ". ", a); await a.TakeDamage(DamageType.FIRE, DamageClass.MAGICAL, Global.Roll(2 + bonus, 6), this, a_name); } else { foreach (Tile t2 in line) { if (t2.Is(FeatureType.TROLL_CORPSE) || t2.Is(FeatureType.TROLL_SEER_CORPSE)) { line = line.To(t2); } } AnimateProjectile(line, "*", Color.RandomFire); B.Add("The scorching bolt hits " + t.the_name + ". ", t); if (line.Last().Is(FeatureType.TROLL_CORPSE)) { line.Last().features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ", line.Last()); } if (line.Last().Is(FeatureType.TROLL_SEER_CORPSE)) { line.Last().features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ", line.Last()); } } } else { return false; } break; case SpellType.BLOODSCENT: if (!HasAttr(AttrType.BLOODSCENT)) { B.Add(You("cast") + " bloodscent. ", this); attrs[Forays.AttrType.BLOODSCENT]++; if (atype == ActorType.PLAYER) { B.Add("You smell fear. "); Q.Add(new Event(this, 10000, Forays.AttrType.BLOODSCENT, "You lose the scent. ")); } else { Q.Add(new Event(this, 10000, Forays.AttrType.BLOODSCENT)); } } else { B.Add("You can already smell the blood of your enemies. "); return false; } break; case SpellType.LIGHTNING_BOLT: if (t == null) { line = await GetTarget(12); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " lightning bolt. ", this); PhysicalObject bolt_target = null; List<Actor> damage_targets = new List<Actor>(); foreach (Tile t2 in line) { if (t2.actor() != null && t2.actor() != this) { bolt_target = t2.actor(); damage_targets.Add(t2.actor()); break; } else { if (t2.ConductsElectricity()) { bolt_target = t2; break; } } } if (bolt_target != null) { Dict<PhysicalObject, List<PhysicalObject>> chain = new Dict<PhysicalObject, List<PhysicalObject>>(); chain[this] = new List<PhysicalObject> { bolt_target }; List<PhysicalObject> last_added = new List<PhysicalObject> { bolt_target }; for (bool done = false; !done; ) { done = true; List<PhysicalObject> new_last_added = new List<PhysicalObject>(); foreach (PhysicalObject added in last_added) { List<PhysicalObject> sort_list = new List<PhysicalObject>(); foreach (Tile nearby in added.TilesWithinDistance(3, true)) { if (nearby.actor() != null || nearby.ConductsElectricity()) { if (added.HasLOE(nearby)) { if (nearby.actor() != null) { bolt_target = nearby.actor(); } else { bolt_target = nearby; } bool contains_value = false; foreach (PhysicalObject k in chain.d.Keys) { List<PhysicalObject> list = chain.d[k]; foreach (PhysicalObject o in list) { if (o == bolt_target) { contains_value = true; break; } } if (contains_value) { break; } } if (!chain.d.ContainsKey(bolt_target) && !contains_value) { if (bolt_target as Actor != null) { damage_targets.AddUnique(bolt_target as Actor); } done = false; if (sort_list.Count == 0) { sort_list.Add(bolt_target); } else { int idx = 0; foreach (PhysicalObject o in sort_list) { if (bolt_target.DistanceFrom(added) < o.DistanceFrom(added)) { sort_list.Insert(idx, bolt_target); break; } ++idx; } if (idx == sort_list.Count) { sort_list.Add(bolt_target); } } if (chain[added] == null) { chain[added] = new List<PhysicalObject> { bolt_target }; } else { chain[added].Add(bolt_target); } } } } } foreach (PhysicalObject o in sort_list) { new_last_added.Add(o); } } if (!done) { last_added = new_last_added; } } //whew. the tree structure is complete. start at chain[this] and go from there... Dict<int, List<pos>> frames = new Dict<int, List<pos>>(); Dict<PhysicalObject, int> line_length = new Dict<PhysicalObject, int>(); line_length[this] = 0; List<PhysicalObject> current = new List<PhysicalObject> { this }; List<PhysicalObject> next = new List<PhysicalObject>(); while (current.Count > 0) { foreach (PhysicalObject o in current) { if (chain[o] != null) { foreach (PhysicalObject o2 in chain[o]) { List<Tile> bres = o.GetBestLine(o2); bres.RemoveAt(0); line_length[o2] = bres.Count + line_length[o]; int idx = 0; foreach (Tile t2 in bres) { if (frames[idx + line_length[o]] != null) { frames[idx + line_length[o]].Add(new pos(t2.row, t2.col)); } else { frames[idx + line_length[o]] = new List<pos> { new pos(t2.row, t2.col) }; } ++idx; } next.Add(o2); } } } current = next; next = new List<PhysicalObject>(); } List<pos> frame = frames[0]; for (int i = 0; frame != null; ++i) { foreach (pos p in frame) { Screen.WriteMapChar(p.row, p.col, "*", Color.RandomLightning); } await Task.Delay(50); frame = frames[i]; } foreach (Actor ac in damage_targets) { B.Add("The bolt hits " + ac.the_name + ". ", ac); await ac.TakeDamage(DamageType.ELECTRIC, DamageClass.MAGICAL, Global.Roll(2 + bonus, 6), this, a_name); } } else { AnimateBeam(line, "*", Color.RandomLightning); B.Add("The bolt hits " + t.the_name + ". ", t); } } else { return false; } break; case SpellType.SHADOWSIGHT: if (!HasAttr(AttrType.SHADOWSIGHT)) { B.Add("You cast shadowsight. "); B.Add("Your eyes pierce the darkness. "); int duration = 10001; GainAttr(AttrType.SHADOWSIGHT, duration, "You no longer see as well in darkness. "); GainAttr(AttrType.LOW_LIGHT_VISION, duration); } else { B.Add("Your eyes are already attuned to darkness. "); return false; } break; /*case SpellType.BURNING_HANDS: if(t == null){ t = TileInDirection(GetDirection()); } if(t != null){ B.Add(You("cast") + " burning hands. ",this); AnimateMapCell(t,Color.DarkRed,'*'); Actor a = M.actor[t.row,t.col]; if(a != null){ B.Add(You("project") + " flames onto " + a.the_name + ". ",this,a); a.TakeDamage(DamageType.FIRE,DamageClass.MAGICAL,Global.Roll(3+bonus,6),this); if(M.actor[t.row,t.col] != null && Global.Roll(1,10) <= 2){ B.Add(a.You("start") + " to catch fire! ",a); a.attrs[AttrType.CATCHING_FIRE]++; } } else{ B.Add("You project flames from your hands. "); } } else{ return false; } break; case SpellType.NIMBUS: { if(HasAttr(AttrType.NIMBUS_ON)){ B.Add("You're already surrounded by a nimbus. "); return false; } else{ B.Add(You("cast") + " nimbus. ",this); B.Add("An electric glow surrounds " + the_name + ". ",this); attrs[AttrType.NIMBUS_ON]++; int duration = (Global.Roll(5)+5)*100; Q.Add(new Event(this,duration,AttrType.NIMBUS_ON,"The electric glow fades from " + the_name + ". ",this)); } break; }*/ case SpellType.VOLTAIC_SURGE: { List<Actor> targets = new List<Actor>(); foreach (Actor a in ActorsWithinDistance(2, true)) { if (HasLOE(a)) { targets.Add(a); } } B.Add(You("cast") + " voltaic surge. ", this); AnimateExplosion(this, 2, Color.RandomLightning, "*"); if (targets.Count == 0) { B.Add("The air around " + the_name + " crackles. ", this); } else { while (targets.Count > 0) { Actor a = targets.Random(); targets.Remove(a); B.Add("Electricity blasts " + a.the_name + ". ", a); await a.TakeDamage(DamageType.ELECTRIC, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name); } } break; } case SpellType.MAGIC_HAMMER: if (t == null) { t = TileInDirection(await GetDirection()); } if (t != null) { Actor a = t.actor(); B.Add(You("cast") + " magic hammer. ", this); B.DisplayNow(); Screen.AnimateMapCell(t.row, t.col, new colorchar("*", Color.Magenta), 100); if (a != null) { B.Add(You("smash", true) + " " + a.TheVisible() + ". ", this, a); if (await a.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(4 + bonus, 6), this, a_name)) { a.GainAttrRefreshDuration(AttrType.STUNNED, 201, a.YouAre() + " no longer stunned. ", a); B.Add(a.YouAre() + " stunned. ", a); } } else { B.Add("You smash " + t.the_name + ". "); } } else { return false; } break; case SpellType.RETREAT: //this is a player-only spell for now because it uses target_location to track position B.Add("You cast retreat. "); if (target_location == null) { target_location = M.tile[row, col]; B.Add("You create a rune of transport on " + M.tile[row, col].the_name + ". "); target_location.features.Add(FeatureType.RUNE_OF_RETREAT); } else { if (M.actor[target_location.row, target_location.col] == null && target_location.passable) { B.Add("You activate your rune of transport. "); await Move(target_location.row, target_location.col); target_location.features.Remove(FeatureType.RUNE_OF_RETREAT); target_location = null; } else { B.Add("Something blocks your transport. "); } } break; case SpellType.GLACIAL_BLAST: if (t == null) { line = await GetTarget(12); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " glacial blast. ", this); Actor a = FirstActorInLine(line); if (a != null) { AnimateProjectile(line.ToFirstObstruction(), "*", Color.RandomIce); B.Add("The glacial blast hits " + a.the_name + ". ", a); await a.TakeDamage(DamageType.COLD, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name); } else { AnimateProjectile(line, "*", Color.RandomIce); B.Add("The glacial blast hits " + t.the_name + ". ", t); } } else { return false; } break; case SpellType.PASSAGE: { int i = DirectionOfOnlyUnblocked(TileType.WALL, true); if (i == 0) { B.Add("There's no wall here. ", this); return false; } else { if (t == null) { i = await GetDirection(true, false); t = TileInDirection(i); } else { i = DirectionOf(t); } if (t != null) { if (t.ttype == TileType.WALL) { B.Add(You("cast") + " passage. ", this); colorchar ch = new colorchar(Color.Cyan, "!"); if (this == player) { Game.Console.CursorVisible = false; switch (DirectionOf(t)) { case 8: case 2: ch.c = "|"; break; case 4: case 6: ch.c = "-"; break; } } List<Tile> tiles = new List<Tile>(); List<colorchar> memlist = new List<colorchar>(); while (!t.passable) { if (t.row == 0 || t.row == ROWS - 1 || t.col == 0 || t.col == COLS - 1) { break; } if (this == player) { tiles.Add(t); memlist.Add(Screen.MapChar(t.row, t.col)); Screen.WriteMapChar(t.row, t.col, ch); await Task.Delay(35); // Thread.Sleep(35); } t = t.TileInDirection(i); } if (t.passable && M.actor[t.row, t.col] == null) { if (this == player) { if (M.tile[row, col].inv != null) { Screen.WriteMapChar(row, col, new colorchar(tile().inv.color, tile().inv.symbol)); } else { Screen.WriteMapChar(row, col, new colorchar(tile().color, tile().symbol)); } Screen.WriteMapChar(t.row, t.col, new colorchar(color, symbol)); int j = 0; foreach (Tile tile in tiles) { Screen.WriteMapChar(tile.row, tile.col, memlist[j++]); await Task.Delay(35); //Thread.Sleep(35); } } await Move(t.row, t.col); M.Draw(); B.Add(You("travel") + " through the passage. ", this); } else { if (this == player) { int j = 0; foreach (Tile tile in tiles) { Screen.WriteMapChar(tile.row, tile.col, memlist[j++]); await Task.Delay(35); //Thread.Sleep(35); } B.Add("The passage is blocked. "); } } } else { if (this == player) { B.Add("There's no wall here. ", this); } return false; } } else { return false; } } break; } case SpellType.FLASHFIRE: if (t == null) { line = await GetTarget(12, 2); if (line != null) { t = line.Last(); } } if (t != null) { Actor a = FirstActorInLine(line); if (a != null) { t = a.tile(); } B.Add(You("cast") + " flashfire. ", this); AnimateBoltProjectile(line.ToFirstObstruction(), Color.Red); AnimateExplosion(t, 2, "*", Color.RandomFire); B.Add("Fwoosh! ", new PhysicalObject[] { this, t }); List<Actor> targets = new List<Actor>(); Tile prev = line.ToFirstObstruction()[line.ToFirstObstruction().Count - 2]; foreach (Actor ac in t.ActorsWithinDistance(2)) { if (t.passable) { if (t.HasBresenhamLine(ac.row, ac.col)) { targets.Add(ac); } } else { if (prev.HasBresenhamLine(ac.row, ac.col)) { targets.Add(ac); } } } foreach (Tile t2 in t.TilesWithinDistance(2)) { if (t.passable) { if (t.HasBresenhamLine(t2.row, t2.col)) { if (t2.actor() != null) { targets.Add(t2.actor()); } if (t2.Is(FeatureType.TROLL_CORPSE)) { t2.features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ", t2); } if (t2.Is(FeatureType.TROLL_SEER_CORPSE)) { t2.features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ", t2); } } } else { if (prev.HasBresenhamLine(t2.row, t2.col)) { if (t2.actor() != null) { targets.Add(t2.actor()); } if (t2.Is(FeatureType.TROLL_CORPSE)) { t2.features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ", t2); } if (t2.Is(FeatureType.TROLL_SEER_CORPSE)) { t2.features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ", t2); } } } } while (targets.Count > 0) { Actor ac = targets.RemoveRandom(); B.Add("The explosion hits " + ac.the_name + ". ", ac); await ac.TakeDamage(DamageType.FIRE, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name); } } else { return false; } break; case SpellType.SONIC_BOOM: if (t == null) { line = await GetTarget(12); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " sonic boom. ", this); Actor a = FirstActorInLine(line); if (a != null) { AnimateProjectile(line.ToFirstObstruction(), "~", Color.Yellow); B.Add("A wave of sound hits " + a.the_name + ". ", a); int r = a.row; int c = a.col; await a.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name); if (Global.Roll(1, 10) <= 5 && M.actor[r, c] != null && !M.actor[r, c].HasAttr(AttrType.STUNNED)) { B.Add(a.YouAre() + " stunned. ", a); a.attrs[AttrType.STUNNED]++; int duration = DurationOfMagicalEffect((Global.Roll(1, 4) + 2)) * 100; Q.Add(new Event(a, duration, AttrType.STUNNED, a.YouAre() + " no longer stunned. ", new PhysicalObject[] { a })); } } else { AnimateProjectile(line, "~", Color.Yellow); B.Add("Sonic boom! "); } } else { return false; } break; case SpellType.COLLAPSE: if (t == null) { line = await GetTarget(12, -1); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " collapse. ", this); B.DisplayNow(); for (int dist = 2; dist > 0; --dist) { List<pos> cells = new List<pos>(); List<colorchar> chars = new List<colorchar>(); pos p2 = new pos(t.row - dist, t.col - dist); if (p2.BoundsCheck()) { cells.Add(p2); chars.Add(new colorchar("\\", Color.DarkGreen)); } p2 = new pos(t.row - dist, t.col + dist); if (p2.BoundsCheck()) { cells.Add(p2); chars.Add(new colorchar("/", Color.DarkGreen)); } p2 = new pos(t.row + dist, t.col - dist); if (p2.BoundsCheck()) { cells.Add(p2); chars.Add(new colorchar("/", Color.DarkGreen)); } p2 = new pos(t.row + dist, t.col + dist); if (p2.BoundsCheck()) { cells.Add(p2); chars.Add(new colorchar("\\", Color.DarkGreen)); } Screen.AnimateMapCells(cells, chars); } Screen.AnimateMapCell(t.row, t.col, new colorchar("X", Color.DarkGreen)); Actor a = t.actor(); if (a != null) { B.Add("Part of the ceiling falls onto " + a.the_name + ". ", a); await a.TakeDamage(DamageType.BASHING, DamageClass.PHYSICAL, Global.Roll(4 + bonus, 6), this, a_name); } else { if (t.row == 0 || t.col == 0 || t.row == ROWS - 1 || t.col == COLS - 1) { B.Add("The wall resists. "); } else { if (t.ttype == TileType.WALL || t.ttype == TileType.HIDDEN_DOOR) { B.Add("The wall crashes down! "); t.TurnToFloor(); foreach (Tile neighbor in t.TilesAtDistance(1)) { if (neighbor.solid_rock) { neighbor.solid_rock = false; } } } } } List<Tile> open_spaces = new List<Tile>(); foreach (Tile neighbor in t.TilesWithinDistance(1)) { if (neighbor.passable) { if (a == null || neighbor != t) { //don't hit the same guy again open_spaces.Add(neighbor); } } } int count = 4; if (open_spaces.Count < 4) { count = open_spaces.Count; } for (; count > 0; --count) { Tile chosen = open_spaces.Random(); open_spaces.Remove(chosen); if (chosen.actor() != null) { B.Add("A rock falls onto " + chosen.actor().the_name + ". ", chosen.actor()); await chosen.actor().TakeDamage(DamageType.BASHING, Forays.DamageClass.PHYSICAL, Global.Roll(2, 6), this, a_name); } else { TileType prev = chosen.ttype; chosen.TransformTo(TileType.RUBBLE); chosen.toggles_into = prev; } } } else { return false; } break; case SpellType.FORCE_BEAM: if (t == null) { line = await GetTarget(); if (line != null) { t = line.Last(); } } if (t != null) { B.Add(You("cast") + " force beam. ", this); B.DisplayNow(); //List<Tile> line2 = GetBestExtendedLine(t.row,t.col); List<Tile> full_line = new List<Tile>(line); line = line.GetRange(0, Math.Min(13, line.Count)); for (int i = 0; i < 3; ++i) { //hits thrice Actor firstactor = null; Actor nextactor = null; Tile firsttile = null; Tile nexttile = null; foreach (Tile tile in line) { if (!tile.passable) { firsttile = tile; break; } if (M.actor[tile.row, tile.col] != null && M.actor[tile.row, tile.col] != this) { int idx = full_line.IndexOf(tile); firsttile = tile; firstactor = M.actor[tile.row, tile.col]; nexttile = full_line[idx + 1]; nextactor = M.actor[nexttile.row, nexttile.col]; break; } } AnimateBoltBeam(line.ToFirstObstruction(), Color.Cyan); if (firstactor != null) { string s = firstactor.TheVisible(); string s2 = firstactor.a_name; await firstactor.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(1 + bonus, 6), this, a_name); if (M.actor[firsttile.row, firsttile.col] != null) { await firstactor.GetKnockedBack(full_line); } else { if (!nexttile.passable) { B.Add(s + "'s corpse is knocked into " + nexttile.the_name + ". ", new PhysicalObject[] { firsttile, nexttile }); } else { if (nextactor != null) { B.Add(s + "'s corpse is knocked into " + nextactor.TheVisible() + ". ", new PhysicalObject[] { firsttile, nextactor }); await nextactor.TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(1, 6), this, s2 + "'s falling corpse"); } } } } } } else { return false; } break; /*case SpellType.DISINTEGRATE: if(t == null){ t = await GetTarget(); } if(t != null){ B.Add(You("cast") + " disintegrate. ",this); Actor a = FirstActorInLine(t); if(a != null){ AnimateBoltBeam(a,Color.DarkGreen); B.Add(You("direct") + " destructive energies toward " + a.the_name + ". ",this,a); a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,Global.Roll(8+bonus,6),this); } else{ AnimateBoltBeam(t,Color.DarkGreen); if(t.type == TileType.WALL || t.type == TileType.DOOR_C || t.type == TileType.DOOR_O || t.type == TileType.CHEST){ B.Add(You("direct") + " destructive energies toward " + t.the_name + ". ",this,t); B.Add(t.the_name + " turns to dust. ",t); t.TurnToFloor(); } } } else{ return false; } break;*/ case SpellType.AMNESIA: if (t == null) { t = TileInDirection(await GetDirection()); } if (t != null) { Actor a = t.actor(); if (a != null) { B.Add(You("cast") + " amnesia. ", this); /*for(int i=0;i<4;++i){ List<pos> cells = new List<pos>(); List<colorchar> chars = new List<colorchar>(); List<pos> nearby = a.p.PositionsWithinDistance(2); for(int j=0;j<4;++j){ cells.Add(nearby.RemoveRandom()); chars.Add(new colorchar('*',Color.RandomPrismatic)); } Screen.AnimateMapCells(cells,chars); }*/ a.AnimateStorm(2, 4, 4, "*", Color.RandomPrismatic); B.Add("You fade from " + a.TheVisible() + "'s awareness. "); a.player_visibility_duration = 0; a.target = null; a.target_location = null; a.attrs[Forays.AttrType.AMNESIA_STUN]++; } else { B.Add("There's nothing to target there. "); return false; } } else { return false; } break; case SpellType.BLIZZARD: { List<Actor> targets = ActorsWithinDistance(5, true); B.Add(You("cast") + " blizzard. ", this); AnimateStorm(5, 8, 24, "*", Color.RandomIce); B.Add("A massive ice storm surrounds " + the_name + ". ", this); while (targets.Count > 0) { int idx = Global.Roll(1, targets.Count) - 1; Actor a = targets[idx]; targets.Remove(a); B.Add("The blizzard hits " + a.the_name + ". ", a); int r = a.row; int c = a.col; await a.TakeDamage(DamageType.COLD, DamageClass.MAGICAL, Global.Roll(5 + bonus, 6), this, a_name); if (M.actor[r, c] != null && Global.Roll(1, 10) <= 8) { B.Add(a.the_name + " is encased in ice. ", a); a.attrs[AttrType.FROZEN] = 25; } } break; } case SpellType.BLESS: if (!HasAttr(AttrType.BLESSED)) { B.Add(You("cast") + " bless. ", this); B.Add(You("shine") + " briefly with inner light. ", this); attrs[AttrType.BLESSED]++; Q.Add(new Event(this, 400, AttrType.BLESSED)); } else { B.Add(YouAre() + " already blessed! ", this); return false; } break; case SpellType.MINOR_HEAL: B.Add(You("cast") + " minor heal. ", this); B.Add("A bluish glow surrounds " + the_name + ". ", this); await TakeDamage(DamageType.HEAL, DamageClass.NO_TYPE, Global.Roll(4, 6), null); break; case SpellType.HOLY_SHIELD: if (!HasAttr(AttrType.HOLY_SHIELDED)) { B.Add(You("cast") + " holy shield. ", this); B.Add("A fiery halo appears above " + the_name + ". ", this); attrs[AttrType.HOLY_SHIELDED]++; int duration = (Global.Roll(3, 2) + 1) * 100; Q.Add(new Event(this, duration, AttrType.HOLY_SHIELDED, the_name + "'s halo fades. ", new PhysicalObject[] { this })); } else { B.Add(Your() + " holy shield is already active. ", this); return false; } break; } if (atype == ActorType.PLAYER && spell != SpellType.AMNESIA) { MakeNoise(); } if (!force_of_will) { if (Spell.Level(spell) - TotalSkill(SkillType.MAGIC) > 0) { if (HasFeat(FeatType.STUDENTS_LUCK)) { if (Global.CoinFlip()) { magic_penalty++; B.Add(YouFeel() + " drained. ", this); } else { if (atype == ActorType.PLAYER) { B.Add("You feel lucky. "); //punk } } } else { magic_penalty++; B.Add(YouFeel() + " drained. ", this); } } } else { magic_penalty += 5; if (magic_penalty > 20) { magic_penalty = 20; } B.Add("You drain your magic reserves. "); } Q1(); return true; }
public void GenerateFinalLevel() { final_level_cultist_count = new int[5]; final_level_demon_count = 0; final_level_clock = 0; current_level = 21; InitializeNewLevel(); string[] final_map = FinalLevelLayout(); PosArray<CellType> map = new PosArray<CellType>(ROWS,COLS); PosArray<bool> doors = new PosArray<bool>(ROWS,COLS); List<List<pos>> door_sets = new List<List<pos>>(); for(int i=0;i<ROWS;++i){ string s = final_map[i]; for(int j=0;j<COLS;++j){ switch(s[j]){ case '#': map[i,j] = CellType.Wall; break; case '.': map[i,j] = CellType.RoomInterior; break; case '2': map[i,j] = CellType.RoomFeature1; break; case '&': map[i,j] = CellType.RoomFeature2; break; case '*': map[i,j] = CellType.RoomFeature3; break; case 'X': map[i,j] = CellType.RoomFeature4; break; case '+': map[i,j] = CellType.Wall; if(!doors[i,j]){ doors[i,j] = true; pos p = new pos(i,j); List<pos> door_set = new List<pos>{p}; foreach(int dir in new int[]{2,6}){ p = new pos(i,j); while(true){ p = p.PosInDir(dir); if(p.BoundsCheck(tile) && final_map[p.row][p.col] == '+'){ doors[p] = true; door_set.Add(p); } else{ break; } } } door_sets.Add(door_set); } break; } } } Dungeon d = new Dungeon(ROWS,COLS); d.map = map; while(!d.IsFullyConnected() && door_sets.Count > 0){ List<pos> door_set = door_sets.RemoveRandom(); d.map[door_set.Random()] = CellType.RoomInterior; } List<Tile> flames = new List<Tile>(); for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ switch(map[i,j]){ case CellType.Wall: Tile.Create(TileType.WALL,i,j); break; case CellType.RoomFeature1: Tile.Create(TileType.DEMONIC_IDOL,i,j); break; case CellType.RoomFeature2: Tile.Create(TileType.FLOOR,i,j); flames.Add(tile[i,j]); break; case CellType.RoomFeature3: Tile.Create(TileType.FLOOR,i,j); tile[i,j].color = Color.RandomDoom; break; case CellType.RoomFeature4: Tile.Create(TileType.FIRE_RIFT,i,j); break; default: Tile.Create(TileType.FLOOR,i,j); break; } tile[i,j].solid_rock = true; } } //todo! add dungeon descriptions for final level. //todo: ^^^ or else it crashes ^^^ player.ResetForNewLevel(); foreach(Tile t in AllTiles()){ if(t.light_radius > 0){ t.UpdateRadius(0,t.light_radius); } } foreach(Tile t in flames){ t.AddFeature(FeatureType.FIRE); } int light = player.light_radius; int fire = player.attrs[AttrType.BURNING]; player.light_radius = 0; player.attrs[AttrType.BURNING] = 0; player.Move(6,7); player.UpdateRadius(0,Math.Max(light,fire),true); player.light_radius = light; player.attrs[AttrType.BURNING] = fire; foreach(Tile t in AllTiles()){ if(t.type != TileType.WALL){ foreach(Tile neighbor in t.TilesAtDistance(1)){ neighbor.solid_rock = false; } } } for(int i=0;i<3;++i){ Actor a = SpawnMob(ActorType.CULTIST); List<Actor> group = new List<Actor>(a.group); a.group.Clear(); if(a != null && group != null){ int ii = 0; foreach(Actor a2 in group){ ++ii; pos circle = FinalLevelSummoningCircle(ii); a2.FindPath(circle.row,circle.col); a2.attrs[AttrType.COOLDOWN_2] = ii; a2.type = ActorType.FINAL_LEVEL_CULTIST; a2.group = null; if(!R.OneIn(20)){ a2.attrs[AttrType.NO_ITEM] = 1; } } } } Q.Add(new Event(500,EventType.FINAL_LEVEL_SPAWN_CULTISTS)); }
public bool CastSpell(SpellType spell,PhysicalObject obj) { //returns false if targeting is canceled. if(StunnedThisTurn()){ //eventually this will be moved to the last possible second return true; //returns true because turn was used up. } if(!HasSpell(spell)){ return false; } if(HasAttr(AttrType.SILENCED)){ if(this == player){ B.Add("You can't cast while silenced. "); } return false; } foreach(Actor a in ActorsWithinDistance(2)){ if(a.HasAttr(AttrType.SILENCE_AURA) && a.HasLOE(this)){ if(this == player){ if(CanSee(a)){ B.Add(a.Your() + " aura of silence disrupts your spell! "); } else{ B.Add("An aura of silence disrupts your spell! "); } } return false; } } int required_mana = Spell.Tier(spell); if(HasAttr(AttrType.CHAIN_CAST) && required_mana > 1){ required_mana--; } if(curmp < required_mana && this == player){ int missing_mana = required_mana - curmp; if(exhaustion + missing_mana*5 > 100){ B.Add("You're too exhausted! "); return false; } if(!B.YesOrNoPrompt("Really exhaust yourself to cast this spell?")){ return false; } Screen.CursorVisible = false; } Tile t = null; List<Tile> line = null; if(obj != null){ t = M.tile[obj.row,obj.col]; line = GetBestLineOfEffect(t); } if(exhaustion > 0){ int fail = Spell.FailRate(spell,exhaustion); if(R.PercentChance(fail)){ if(HasFeat(FeatType.FORCE_OF_WILL)){ B.Add("You focus your will. "); } else{ if(player.CanSee(this)){ B.Add("Sparks fly from " + Your() + " fingers. ",this); } else{ if(player.DistanceFrom(this) <= 4 || (player.DistanceFrom(this) <= 12 && player.HasLOE(row,col))){ B.Add("You hear words of magic, but nothing happens. "); } } if(this == player){ Help.TutorialTip(TutorialTopic.SpellFailure); } Q1(); return true; } } } int bonus = 0; //used for bonus damage on spells if(HasFeat(FeatType.MASTERS_EDGE)){ foreach(SpellType s in spells_in_order){ if(Spell.IsDamaging(s)){ if(s == spell){ bonus = 1; } break; } } } if(HasAttr(AttrType.EMPOWERED_SPELLS)){ bonus++; } switch(spell){ case SpellType.RADIANCE: { if(M.wiz_dark){ B.Add("The magical darkness makes this spell impossible to cast. "); return false; } if(t == null){ line = GetTargetTile(12,0,true,false); if(line != null){ t = line.LastOrDefault(); } } if(t != null){ B.Add(You("cast") + " radiance. ",this); PhysicalObject o = null; int rad = -1; if(t.actor() != null){ Actor a = t.actor(); o = a; int old_rad = a.LightRadius(); a.RefreshDuration(AttrType.SHINING,(R.Roll(2,20)+40)*100,a.You("no longer shine") + ". ",a); if(old_rad != a.LightRadius()){ a.UpdateRadius(old_rad,a.LightRadius()); } if(a != player){ a.attrs[AttrType.TURNS_VISIBLE] = -1; } rad = a.LightRadius(); } else{ if(t.inv != null && t.inv.light_radius > 0){ o = t.inv; rad = o.light_radius; } else{ if(t.light_radius > 0){ o = t; rad = o.light_radius; } } } if(o != null){ if(o is Item){ B.Add((o as Item).TheName(false) + " shines brightly. ",o); } else{ B.Add(o.You("shine") + " brightly. ",o); } foreach(Actor a in o.ActorsWithinDistance(rad).Where(x=>x != this && o.HasLOS(x))){ B.Add("The light burns " + a.the_name + ". ",a); a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(1+bonus,6),this,"a shining " + o.name); } } else{ B.Add("Nothing happens. "); } } else{ return false; } break; } case SpellType.FORCE_PALM: if(t == null){ t = TileInDirection(GetDirection()); } if(t != null){ Actor a = M.actor[t.row,t.col]; B.Add(You("cast") + " force palm. ",this); B.DisplayNow(); Screen.AnimateMapCell(t.row,t.col,new colorchar('*',Color.Blue),100); bool self_knockback = false; if(a != null){ B.Add(You("strike") + " " + a.TheName(true) + ". ",this,a); if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(spell)){ a.curmp += Spell.Tier(spell); if(a.curmp > a.maxmp){ a.curmp = a.maxmp; } a.GainSpell(spell); B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a); } a.attrs[AttrType.TURN_INTO_CORPSE]++; a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(1+bonus,6),this,a_name); if(a.HasAttr(AttrType.IMMOBILE,AttrType.FROZEN)){ self_knockback = true; } else{ if(a.curhp > 0 || !a.HasAttr(AttrType.NO_CORPSE_KNOCKBACK)){ KnockObjectBack(a,1,this); } } a.CorpseCleanup(); } else{ if(t.passable){ B.Add("You strike at empty space. "); } else{ B.Add("You strike " + t.the_name + " with your palm. "); switch(t.type){ case TileType.DOOR_C: B.Add("It flies open! "); t.Toggle(this); break; case TileType.HIDDEN_DOOR: B.Add("A hidden door flies open! "); t.Toggle(this); t.Toggle(this); break; case TileType.RUBBLE: B.Add("It scatters! "); t.Toggle(null); break; case TileType.CRACKED_WALL: B.Add("It falls to pieces! "); t.Toggle(null,TileType.FLOOR); foreach(Tile neighbor in t.TilesAtDistance(1)){ neighbor.solid_rock = false; } break; case TileType.BARREL: case TileType.STANDING_TORCH: case TileType.POISON_BULB: t.Bump(DirectionOf(t)); break; default: self_knockback = true; break; } } } if(self_knockback){ attrs[AttrType.TURN_INTO_CORPSE]++; t.KnockObjectBack(this,1,this); CorpseCleanup(); } } else{ return false; } break; case SpellType.DETECT_MOVEMENT: B.Add(You("cast") + " detect movement. ",this); if(this == player){ B.Add("Your senses sharpen. "); if(!HasAttr(AttrType.DETECTING_MOVEMENT)){ previous_footsteps = new List<pos>(); //prevents old footsteps from appearing } RefreshDuration(AttrType.DETECTING_MOVEMENT,(R.Roll(2,20)+30)*100,"You no longer detect movement. "); } else{ RefreshDuration(AttrType.DETECTING_MOVEMENT,(R.Roll(2,20)+30)*100); } break; case SpellType.FLYING_LEAP: B.Add(You("cast") + " flying leap. ",this); RefreshDuration(AttrType.FLYING_LEAP,250); RefreshDuration(AttrType.FLYING,250); attrs[AttrType.DESCENDING] = 0; B.Add(You("move") + " quickly through the air. ",this); if(this == player){ Help.TutorialTip(TutorialTopic.IncreasedSpeed); } break; case SpellType.MERCURIAL_SPHERE: if(t == null){ line = GetTargetLine(12); if(line != null && line.LastOrDefault() != tile()){ t = line.LastOrDefault(); } } if(t != null){ B.Add(You("cast") + " mercurial sphere. ",this); Actor a = FirstActorInLine(line); line = line.ToFirstSolidTileOrActor(); M.Draw(); AnimateProjectile(line,'*',Color.Blue); List<string> targets = new List<string>(); List<Tile> locations = new List<Tile>(); if(a != null){ for(int i=0;i<4;++i){ if(player.CanSee(a)){ targets.AddUnique(a.the_name); } Tile atile = a.tile(); if(a != this){ if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(spell)){ a.curmp += Spell.Tier(spell); if(a.curmp > a.maxmp){ a.curmp = a.maxmp; } a.GainSpell(spell); B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a); } a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(2+bonus,6),this,a_name); } a = atile.ActorsWithinDistance(3,true).Where(x=>atile.HasLOE(x)).RandomOrDefault(); locations.AddUnique(atile); if(a == null){ break; } if(i < 3){ Screen.AnimateProjectile(atile.GetBestLineOfEffect(a),new colorchar('*',Color.Blue)); } } int unknown = locations.Count - targets.Count; //every location for which we didn't see an actor if(unknown > 0){ if(unknown == 1){ targets.Add("one unseen creature"); } else{ targets.Add(unknown.ToString() + " unseen creatures"); } } if(targets.Contains("you")){ targets.Remove("you"); targets.Add("you"); //move it to the end of the list } if(targets.Count == 1){ B.Add("The sphere hits " + targets[0] + ". ",locations.ToArray()); } else{ B.Add("The sphere bounces between " + targets.ConcatenateListWithCommas() + ". ",locations.ToArray()); } } } else{ return false; } break; case SpellType.GREASE: { Tile prev = null; if(t == null){ line = GetTargetTile(12,1,true,false); if(line != null){ t = line.LastOrDefault(); prev = line.LastBeforeSolidTile(); } } if(t != null){ Tile LOE_tile = t; if(!t.passable && prev != null){ LOE_tile = prev; } B.Add(You("cast") + " grease. ",this); B.Add("Oil covers the floor. ",t); foreach(Tile neighbor in t.TilesWithinDistance(1)){ if(neighbor.passable && LOE_tile.HasLOE(neighbor)){ neighbor.AddFeature(FeatureType.OIL); } } } else{ return false; } break; } case SpellType.BLINK: if(HasAttr(AttrType.IMMOBILE)){ if(this == player){ B.Add("You can't blink while immobilized. "); return false; } else{ B.Add(You("cast") + " blink. ",this); B.Add("The spell fails. ",this); Q1(); return true; } } for(int i=0;i<9999;++i){ int a = R.Roll(1,17) - 9; //-8 to 8 int b = R.Roll(1,17) - 9; if(Math.Abs(a) + Math.Abs(b) >= 6){ a += row; b += col; if(M.BoundsCheck(a,b) && M.tile[a,b].passable && M.actor[a,b] == null){ B.Add(You("cast") + " blink. ",this); B.Add(You("step") + " through a rip in reality. ",this); if(player.CanSee(this)){ AnimateStorm(2,3,4,'*',Color.DarkMagenta); } Move(a,b); M.Draw(); if(player.CanSee(this)){ AnimateStorm(2,3,4,'*',Color.DarkMagenta); } break; } } } break; case SpellType.FREEZE: if(t == null){ line = GetTargetLine(12); if(line != null && line.LastOrDefault() != tile()){ t = line.LastOrDefault(); } } if(t != null){ B.Add(You("cast") + " freeze. ",this); Actor a = FirstActorInLine(line); AnimateBoltBeam(line.ToFirstSolidTileOrActor(),Color.Cyan); foreach(Tile t2 in line){ t2.ApplyEffect(DamageType.COLD); } if(a != null){ a.ApplyFreezing(); } } else{ return false; } break; case SpellType.SCORCH: if(t == null){ line = GetTargetLine(12); if(line != null && line.LastOrDefault() != tile()){ t = line.LastOrDefault(); } } if(t != null){ B.Add(You("cast") + " scorch. ",this); Actor a = FirstActorInLine(line); line = line.ToFirstSolidTileOrActor(); AnimateProjectile(line,'*',Color.RandomFire); foreach(Tile t2 in line){ t2.ApplyEffect(DamageType.FIRE); } if(a != null){ B.Add("The scorching bolt hits " + a.the_name + ". ",a); if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(spell)){ a.curmp += Spell.Tier(spell); if(a.curmp > a.maxmp){ a.curmp = a.maxmp; } a.GainSpell(spell); B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a); } //if(a.TakeDamage(DamageType.FIRE,DamageClass.MAGICAL,R.Roll(1+bonus,6),this,a_name)){ //todo: testing this without damage a.ApplyBurning(); //} } } else{ return false; } break; case SpellType.LIGHTNING_BOLT: //todo: limit the bolt to 12 tiles or not? should it spread over an entire huge lake? 12 tiles is still quite a lot. if(t == null){ line = GetTargetLine(12); if(line != null && line.LastOrDefault() != tile()){ t = line.LastOrDefault(); } } if(t != null){ B.Add(You("cast") + " lightning bolt. ",this); PhysicalObject bolt_target = null; List<Actor> damage_targets = new List<Actor>(); foreach(Tile t2 in line){ if(t2.actor() != null && t2.actor() != this){ bolt_target = t2.actor(); damage_targets.Add(t2.actor()); break; } else{ if(t2.ConductsElectricity()){ bolt_target = t2; break; } } } if(bolt_target != null){ //this code, man Dict<PhysicalObject,List<PhysicalObject>> chain = new Dict<PhysicalObject,List<PhysicalObject>>(); chain[this] = new List<PhysicalObject>{bolt_target}; List<PhysicalObject> last_added = new List<PhysicalObject>{bolt_target}; for(bool done=false;!done;){ done = true; List<PhysicalObject> new_last_added = new List<PhysicalObject>(); foreach(PhysicalObject added in last_added){ List<PhysicalObject> sort_list = new List<PhysicalObject>(); foreach(Tile nearby in added.TilesWithinDistance(3,true)){ if(nearby.actor() != null || nearby.ConductsElectricity()){ if(added.HasLOE(nearby)){ if(nearby.actor() != null){ bolt_target = nearby.actor(); } else{ bolt_target = nearby; } bool contains_value = false; foreach(List<PhysicalObject> list in chain.d.Values){ foreach(PhysicalObject o in list){ if(o == bolt_target){ contains_value = true; break; } } if(contains_value){ break; } } if(!chain.d.ContainsKey(bolt_target) && !contains_value){ if(bolt_target as Actor != null){ damage_targets.AddUnique(bolt_target as Actor); } done = false; if(sort_list.Count == 0){ sort_list.Add(bolt_target); } else{ int idx = 0; foreach(PhysicalObject o in sort_list){ if(bolt_target.DistanceFrom(added) < o.DistanceFrom(added)){ sort_list.Insert(idx,bolt_target); break; } ++idx; } if(idx == sort_list.Count){ sort_list.Add(bolt_target); } } if(chain[added] == null){ chain[added] = new List<PhysicalObject>{bolt_target}; } else{ chain[added].Add(bolt_target); } } } } } foreach(PhysicalObject o in sort_list){ new_last_added.Add(o); } } if(!done){ last_added = new_last_added; } } //whew. the tree structure is complete. start at chain[this] and go from there... Dict<int,List<pos>> frames = new Dict<int,List<pos>>(); Dict<PhysicalObject,int> line_length = new Dict<PhysicalObject,int>(); line_length[this] = 0; List<PhysicalObject> current = new List<PhysicalObject>{this}; List<PhysicalObject> next = new List<PhysicalObject>(); while(current.Count > 0){ foreach(PhysicalObject o in current){ if(chain[o] != null){ foreach(PhysicalObject o2 in chain[o]){ List<Tile> bres = o.GetBestLineOfEffect(o2); bres.RemoveAt(0); line_length[o2] = bres.Count + line_length[o]; int idx = 0; foreach(Tile t2 in bres){ if(frames[idx + line_length[o]] != null){ frames[idx + line_length[o]].Add(new pos(t2.row,t2.col)); } else{ frames[idx + line_length[o]] = new List<pos>{new pos(t2.row,t2.col)}; } ++idx; } next.Add(o2); } } } current = next; next = new List<PhysicalObject>(); } List<pos> frame = frames[0]; for(int i=0;frame != null;++i){ foreach(pos p in frame){ Screen.WriteMapChar(p.row,p.col,'*',Color.RandomLightning); } Game.GLUpdate(); Thread.Sleep(50); frame = frames[i]; } foreach(Actor a in damage_targets){ B.Add("The bolt hits " + a.the_name + ". ",a); if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(spell)){ a.curmp += Spell.Tier(spell); if(a.curmp > a.maxmp){ a.curmp = a.maxmp; } a.GainSpell(spell); B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a); } a.TakeDamage(DamageType.ELECTRIC,DamageClass.MAGICAL,R.Roll(3+bonus,6),this,a_name); } } else{ AnimateBeam(line,'*',Color.RandomLightning); B.Add("The bolt hits " + t.the_name + ". ",t); } } else{ return false; } break; case SpellType.MAGIC_HAMMER: if(t == null){ t = TileInDirection(GetDirection()); } if(t != null){ Actor a = t.actor(); B.Add(You("cast") + " magic hammer. ",this); M.Draw(); B.DisplayNow(); Screen.AnimateMapCell(t.row,t.col,new colorchar('*',Color.Magenta),100); if(a != null){ B.Add(You("wallop") + " " + a.TheName(true) + ". ",this,a); if(a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(4+bonus,6),this,a_name)){ a.ApplyStatus(AttrType.STUNNED,201); /*a.RefreshDuration(AttrType.STUNNED,a.DurationOfMagicalEffect(2) * 100 + 1,a.YouAre() + " no longer stunned. ",a); //todo fix this so it doesn't use the +1, since nothing else does that any more. B.Add(a.YouAre() + " stunned. ",a);*/ } } else{ B.Add("You strike " + t.the_name + ". "); } } else{ return false; } break; case SpellType.PORTAL: //player-only for now { t = tile(); if(t.Is(FeatureType.INACTIVE_TELEPORTAL,FeatureType.STABLE_TELEPORTAL,FeatureType.TELEPORTAL) || t.Is(TileType.DOOR_O,TileType.STAIRS)){ B.Add("You can't create a portal here. "); return false; } B.Add("You cast portal. "); List<Tile> other_portals = M.AllTiles().Where(x=>x.Is(FeatureType.INACTIVE_TELEPORTAL,FeatureType.STABLE_TELEPORTAL)); if(other_portals.Count == 0){ B.Add("You create a dormant portal. "); t.AddFeature(FeatureType.INACTIVE_TELEPORTAL); } else{ if(other_portals.Count == 1){ //it should be inactive in this case B.Add("You open a portal. "); t.AddFeature(FeatureType.STABLE_TELEPORTAL); Tile t2 = other_portals[0]; t2.RemoveFeature(FeatureType.INACTIVE_TELEPORTAL); t2.AddFeature(FeatureType.STABLE_TELEPORTAL); Q.Add(new Event(t,new List<Tile>{t2},100,EventType.TELEPORTAL,AttrType.NO_ATTR,100,"")); Q.Add(new Event(t2,new List<Tile>{t},100,EventType.TELEPORTAL,AttrType.NO_ATTR,100,"")); } else{ B.Add("You open a portal. "); t.AddFeature(FeatureType.STABLE_TELEPORTAL); Q.Add(new Event(t,other_portals,100,EventType.TELEPORTAL,AttrType.NO_ATTR,100,"")); foreach(Tile t2 in other_portals){ Event e = Q.FindTargetedEvent(t2,EventType.TELEPORTAL); if(e != null){ e.area.Add(t); } } } } break; } case SpellType.PASSAGE: { if(this == player && HasAttr(AttrType.IMMOBILE)){ B.Add("You can't travel through a passage while immobilized. "); return false; } int dir = -1; if(t == null){ dir = GetDirection(true,false); t = TileInDirection(dir); } else{ dir = DirectionOf(t); } if(t != null){ if(t.Is(TileType.WALL,TileType.CRACKED_WALL,TileType.WAX_WALL,TileType.DOOR_C,TileType.HIDDEN_DOOR,TileType.STONE_SLAB)){ B.Add(You("cast") + " passage. ",this); colorchar ch = new colorchar(Color.Cyan,'!'); if(this == player){ Screen.CursorVisible = false; switch(DirectionOf(t)){ case 8: case 2: ch.c = '|'; break; case 4: case 6: ch.c = '-'; break; } } else{ if(HasAttr(AttrType.IMMOBILE)){ B.Add("The spell fails. ",this); Q1(); return true; } } List<Tile> tiles = new List<Tile>(); List<colorchar> memlist = new List<colorchar>(); Screen.CursorVisible = false; Tile last_wall = null; while(!t.passable){ if(t.row == 0 || t.row == ROWS-1 || t.col == 0 || t.col == COLS-1){ break; } if(this == player){ tiles.Add(t); memlist.Add(Screen.MapChar(t.row,t.col)); Screen.WriteMapChar(t.row,t.col,ch); Game.GLUpdate(); Thread.Sleep(35); } last_wall = t; t = t.TileInDirection(dir); } Input.FlushInput(); if(t.passable){ if(t.actor() == null){ int r = row; int c = col; Move(t.row,t.col); if(this == player){ Screen.WriteMapChar(r,c,M.VisibleColorChar(r,c)); Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col)); int idx = 0; foreach(Tile tile in tiles){ Screen.WriteMapChar(tile.row,tile.col,memlist[idx++]); Game.GLUpdate(); Thread.Sleep(35); } } Input.FlushInput(); B.Add(You("travel") + " through the passage. ",this,t); } else{ Tile destination = null; List<Tile> adjacent = t.TilesAtDistance(1).Where(x=>x.passable && x.actor() == null && x.DistanceFrom(last_wall) == 1); if(adjacent.Count > 0){ destination = adjacent.Random(); } else{ foreach(Tile tile in M.ReachableTilesByDistance(t.row,t.col,false)){ if(tile.actor() == null){ destination = tile; break; } } } if(destination != null){ int r = row; int c = col; Move(destination.row,destination.col); if(this == player){ Screen.WriteMapChar(r,c,M.VisibleColorChar(r,c)); Screen.WriteMapChar(destination.row,destination.col,M.VisibleColorChar(destination.row,destination.col)); int idx = 0; foreach(Tile tile in tiles){ Screen.WriteMapChar(tile.row,tile.col,memlist[idx++]); Game.GLUpdate(); Thread.Sleep(35); } } Input.FlushInput(); B.Add(You("travel") + " through the passage. ",this,destination); } else{ B.Add("Something blocks " + Your() + " movement through the passage. ",this); } } } else{ if(this == player){ int idx = 0; foreach(Tile tile in tiles){ Screen.WriteMapChar(tile.row,tile.col,memlist[idx++]); Game.GLUpdate(); Thread.Sleep(35); } Input.FlushInput(); B.Add("The passage is blocked. ",this); } } } else{ if(this == player){ B.Add("There's no wall here. "); } return false; } } else{ return false; } break; } case SpellType.DOOM: if(t == null){ line = GetTargetLine(12); if(line != null && line.LastOrDefault() != tile()){ t = line.LastOrDefault(); } } if(t != null){ B.Add(You("cast") + " doom. ",this); Actor a = FirstActorInLine(line); if(a != null){ AnimateProjectile(line.ToFirstSolidTileOrActor(),'*',Color.RandomDoom); if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(spell)){ a.curmp += Spell.Tier(spell); if(a.curmp > a.maxmp){ a.curmp = a.maxmp; } a.GainSpell(spell); B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a); } if(a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(4+bonus,6),this,a_name)){ a.ApplyStatus(AttrType.VULNERABLE,R.Between(4,8)*100); /*if(!a.HasAttr(AttrType.VULNERABLE)){ B.Add(a.You("become") + " vulnerable. ",a); } a.RefreshDuration(AttrType.VULNERABLE,a.DurationOfMagicalEffect(R.Between(4,8)) * 100,a.YouAre() + " no longer so vulnerable. ",a);*/ if(a == player){ Help.TutorialTip(TutorialTopic.Vulnerable); } } } else{ AnimateProjectile(line,'*',Color.RandomDoom); } } else{ return false; } break; case SpellType.AMNESIA: if(t == null){ t = TileInDirection(GetDirection()); } if(t != null){ Actor a = t.actor(); if(a != null && (CanSee(a) || a.HasAttr(AttrType.DANGER_SENSED))){ B.Add(You("cast") + " amnesia. ",this); a.AnimateStorm(2,4,4,'*',Color.RandomRainbow); if(a.ResistedBySpirit() || a.HasAttr(AttrType.MENTAL_IMMUNITY)){ B.Add(a.You("resist") + "! ",a); } else{ B.Add("You fade from " + a.TheName(true) + "'s awareness. "); a.player_visibility_duration = 0; a.target = null; a.target_location = null; a.attrs[AttrType.AMNESIA_STUN] = 7; } } else{ B.Add("There's nothing to target there. "); return false; } } else{ return false; } break; case SpellType.SHADOWSIGHT: B.Add("You cast shadowsight. "); B.Add("Your eyes pierce the darkness. "); int duration = (R.Roll(2,20) + 60) * 100; RefreshDuration(AttrType.SHADOWSIGHT,duration,"Your shadowsight wears off. "); RefreshDuration(AttrType.LOW_LIGHT_VISION,duration); break; case SpellType.BLIZZARD: { List<Actor> targets = ActorsWithinDistance(5,true).Where(x=>HasLOE(x)); B.Add(You("cast") + " blizzard. ",this); AnimateStorm(5,8,24,'*',Color.RandomIce); B.Add("An ice storm surrounds " + the_name + ". ",this); foreach(Tile t2 in TilesWithinDistance(5).Where(x=>HasLOE(x))){ t2.ApplyEffect(DamageType.COLD); } while(targets.Count > 0){ int idx = R.Roll(1,targets.Count) - 1; Actor a = targets[idx]; targets.Remove(a); //B.Add("The blizzard hits " + a.the_name + ". ",a); if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(spell)){ a.curmp += Spell.Tier(spell); if(a.curmp > a.maxmp){ a.curmp = a.maxmp; } a.GainSpell(spell); B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a); } if(a.TakeDamage(DamageType.COLD,DamageClass.MAGICAL,R.Roll(5+bonus,6),this,a_name)){ if(!a.HasAttr(AttrType.BURNING,AttrType.IMMUNE_COLD)){ a.ApplyStatus(AttrType.SLOWED,R.Between(6,10)*100); /*B.Add(a.YouAre() + " slowed. ",a); a.RefreshDuration(AttrType.SLOWED,a.DurationOfMagicalEffect(R.Between(6,10)) * 100,a.YouAre() + " no longer slowed. ",a);*/ } } } break; } case SpellType.TELEKINESIS: if(t == null){ line = GetTargetTile(12,0,true,true); if(line != null){ t = line.LastOrDefault(); } } if(!SharedEffect.Telekinesis(true,this,t)){ return false; } break; case SpellType.COLLAPSE: if(t == null){ line = GetTargetTile(12,0,true,false); if(line != null){ t = line.LastOrDefault(); } } if(t != null){ B.Add(You("cast") + " collapse. ",this); B.DisplayNow(); for(int dist=2;dist>0;--dist){ List<pos> cells = new List<pos>(); List<colorchar> chars = new List<colorchar>(); pos p2 = new pos(t.row-dist,t.col-dist); if(p2.BoundsCheck(M.tile)){ cells.Add(p2); chars.Add(new colorchar('\\',Color.DarkGreen)); } p2 = new pos(t.row-dist,t.col+dist); if(p2.BoundsCheck(M.tile)){ cells.Add(p2); chars.Add(new colorchar('/',Color.DarkGreen)); } p2 = new pos(t.row+dist,t.col-dist); if(p2.BoundsCheck(M.tile)){ cells.Add(p2); chars.Add(new colorchar('/',Color.DarkGreen)); } p2 = new pos(t.row+dist,t.col+dist); if(p2.BoundsCheck(M.tile)){ cells.Add(p2); chars.Add(new colorchar('\\',Color.DarkGreen)); } Screen.AnimateMapCells(cells,chars); } Screen.AnimateMapCell(t.row,t.col,new colorchar('X',Color.DarkGreen)); foreach(Tile neighbor in t.TilesWithinDistance(1).Randomize()){ if(neighbor.p.BoundsCheck(M.tile,false)){ if(neighbor.IsTrap()){ B.Add("A falling stone triggers a trap. ",neighbor); //neighbor.TriggerTrap(); } if(neighbor.passable){ neighbor.ApplyEffect(DamageType.NORMAL); //break items and set off traps } if((neighbor == t && t.Is(TileType.WALL,TileType.FLOOR,TileType.RUBBLE,TileType.CRACKED_WALL)) || neighbor.Is(TileType.RUBBLE,TileType.FLOOR)){ neighbor.Toggle(null,TileType.GRAVEL); neighbor.RemoveFeature(FeatureType.SLIME); neighbor.RemoveFeature(FeatureType.OIL); foreach(Tile n2 in neighbor.TilesAtDistance(1)){ n2.solid_rock = false; } } else{ if(neighbor.Is(TileType.CRACKED_WALL)){ neighbor.Toggle(null,R.CoinFlip()? TileType.RUBBLE : TileType.GRAVEL); foreach(Tile n2 in neighbor.TilesAtDistance(1)){ n2.solid_rock = false; } } else{ if(neighbor.Is(TileType.WALL)){ TileType new_type = TileType.FLOOR; switch(R.Roll(3)){ case 1: new_type = TileType.CRACKED_WALL; break; case 2: new_type = TileType.RUBBLE; break; case 3: new_type = TileType.GRAVEL; break; } neighbor.Toggle(null,new_type); foreach(Tile n2 in neighbor.TilesAtDistance(1)){ n2.solid_rock = false; } } } } } } foreach(Actor a in t.ActorsWithinDistance(1)){ if(a != this){ B.Add("Rubble falls on " + a.TheName(true) + ". ",a.tile()); if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(spell)){ a.curmp += Spell.Tier(spell); if(a.curmp > a.maxmp){ a.curmp = a.maxmp; } a.GainSpell(spell); B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a); } a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(3+bonus,6),this,"falling rubble"); } } t.MakeNoise(6); } else{ return false; } break; case SpellType.STONE_SPIKES: { Tile prev = null; if(t == null){ line = GetTargetTile(12,2,true,true); if(line != null){ t = line.LastOrDefault(); prev = line.LastBeforeSolidTile(); } } if(t != null){ Tile LOE_tile = t; if(!t.passable && prev != null){ LOE_tile = prev; } B.Add(You("cast") + " stone spikes. ",this); B.Add("Stalagmites shoot up from the ground! "); List<Tile> deleted = new List<Tile>(); while(true){ bool changed = false; foreach(Tile nearby in t.TilesWithinDistance(2)){ if(nearby.Is(TileType.STALAGMITE) && LOE_tile.HasLOE(nearby)){ nearby.Toggle(null); deleted.Add(nearby); changed = true; } } if(!changed){ break; } } Q.RemoveTilesFromEventAreas(deleted,EventType.STALAGMITE); List<Tile> area = new List<Tile>(); List<Actor> affected_actors = new List<Actor>(); foreach(Tile t2 in t.TilesWithinDistance(2)){ if(LOE_tile.HasLOE(t2)){ if(t2.actor() != null){ affected_actors.Add(t2.actor()); } else{ if((t2.IsTrap() || t2.Is(TileType.FLOOR,TileType.GRAVE_DIRT,TileType.GRAVEL)) && t2.inv == null){ if(!R.OneIn(4)){ area.Add(t2); } } } } } foreach(Actor a in affected_actors){ if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(spell)){ a.curmp += Spell.Tier(spell); if(a.curmp > a.maxmp){ a.curmp = a.maxmp; } a.GainSpell(spell); B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a); } if(a != this){ a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(4+bonus,6),this,a_name); } } if(area.Count > 0){ foreach(Tile t2 in area){ TileType previous_type = t2.type; t2.Toggle(null,TileType.STALAGMITE); t2.toggles_into = previous_type; } Q.Add(new Event(area,150,EventType.STALAGMITE,5)); } } else{ return false; } break; } } if(curmp >= required_mana){ curmp -= required_mana; } else{ IncreaseExhaustion((required_mana - curmp)*5); curmp = 0; } if(HasFeat(FeatType.ARCANE_INTERFERENCE)){ foreach(Actor a in ActorsWithinDistance(12,true)){ if(a.maxmp > 0 && HasLOE(a)){ a.ApplyStatus(AttrType.STUNNED,R.Between(3,6)*100); if(a.HasSpell(spell)){ B.Add(a.the_name + " can no longer cast " + Spell.Name(spell) + ". ",a); a.spells[spell] = false; } } } /*bool empowered = false; foreach(Actor a in ActorsWithinDistance(12,true)){ if(a.HasSpell(spell) && HasLOE(a)){ B.Add(a.the_name + " can no longer cast " + Spell.Name(spell) + ". ",a); a.spells[spell] = false; empowered = true; } } if(empowered){ B.Add("Arcane feedback empowers your spells! "); RefreshDuration(AttrType.EMPOWERED_SPELLS,R.Between(7,12)*100,Your() + " spells are no longer empowered. ",this); }*/ } if(HasFeat(FeatType.CHAIN_CASTING)){ RefreshDuration(AttrType.CHAIN_CAST,100); } MakeNoise(4); if(this == player && !Help.displayed[TutorialTopic.CastingWithoutMana]){ int max_tier = spells_in_order.Greatest(x=>Spell.Tier(x)); if(curmp < max_tier){ Help.TutorialTip(TutorialTopic.CastingWithoutMana); } } Q1(); return true; }