public static bool Telekinesis(bool cast, Actor user, Tile t) { bool wand = !cast; if (t == null) { return(false); } if (t != null) { if (wand && user == player && !Item.identified[ConsumableType.TELEKINESIS]) { Item.identified[ConsumableType.TELEKINESIS] = true; B.Add("(It was a wand of telekinesis!) "); B.Print(true); } List <Tile> ai_line = null; if (user != player && t == player.tile()) { var fires = user.TilesWithinDistance(12).Where(x => x.passable && x.actor() == null && x.Is(FeatureType.FIRE) && user.CanSee(x) && player.HasBresenhamLineWithCondition(x, false, y => y.passable && y.actor() == null)); if (fires.Count > 0) { ai_line = player.GetBestExtendedLineOfEffect(fires.Random()); } else { if (wand) { ai_line = player.GetBestExtendedLineOfEffect(user); } else { ai_line = player.GetBestExtendedLineOfEffect(player.TileInDirection(Global.RandomDirection())); } } } Actor a = t.actor(); if (a == null && t.inv == null && !t.Is(FeatureType.GRENADE) && t.Is(FeatureType.TROLL_CORPSE, FeatureType.TROLL_BLOODWITCH_CORPSE)) { ActorType troll_type = t.Is(FeatureType.TROLL_CORPSE)? ActorType.TROLL : ActorType.TROLL_BLOODWITCH; FeatureType troll_corpse = t.Is(FeatureType.TROLL_CORPSE)? FeatureType.TROLL_CORPSE : FeatureType.TROLL_BLOODWITCH_CORPSE; Event troll_event = Q.FindTargetedEvent(t, EventType.REGENERATING_FROM_DEATH); troll_event.dead = true; Actor troll = Actor.Create(troll_type, t.row, t.col); foreach (Event e in Q.list) { if (e.target == troll && e.type == EventType.MOVE) { e.tiebreaker = troll_event.tiebreaker; e.dead = true; break; } } Actor.tiebreakers[troll_event.tiebreaker] = troll; troll.symbol = '%'; troll.attrs[AttrType.CORPSE] = 1; troll.Name = new Name(troll.GetName(Possessive) + " corpse"); troll.curhp = troll_event.value; troll.attrs[AttrType.PERMANENT_DAMAGE] = troll_event.secondary_value; troll.attrs[AttrType.NO_ITEM]++; t.features.Remove(troll_corpse); a = troll; } if (a != null) { string msg = "Throw " + a.GetName(true, The) + " in which direction? "; if (a == player) { msg = "Throw yourself in which direction? "; } List <Tile> line = null; if (user == player) { TargetInfo info = a.GetTarget(false, 12, 0, false, true, false, msg); if (info != null) { line = info.extended_line; } } else { line = ai_line; } if (line != null) { if (line.Count == 1 && line[0].actor() == user) { if (wand) { return(true); } return(false); } if (cast) { B.Add(user.GetName(false, The, Verb("cast")) + " telekinesis. ", user); user.MakeNoise(6); //should match spellVolume, hack if (a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(SpellType.TELEKINESIS)) { a.curmp += Spell.Tier(SpellType.TELEKINESIS); if (a.curmp > a.maxmp) { a.curmp = a.maxmp; } a.GainSpell(SpellType.TELEKINESIS); B.Add("Runes on " + a.GetName(false, The, Possessive) + " armor align themselves with the spell. ", a); } } if (a == user && a == player) { B.Add("You throw yourself forward. "); } else { if (line.Count == 1) { B.Add(user.GetName(true, The, Verb("throw")) + " " + a.GetName(true, The) + " into the ceiling. ", user, a); } else { B.Add(user.GetName(true, The, Verb("throw")) + " " + a.GetName(true, The) + ". ", user, a); } } B.DisplayContents(); user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 1; a.attrs[AttrType.TELEKINETICALLY_THROWN] = 1; a.attrs[AttrType.TURN_INTO_CORPSE]++; if (line.Count == 1) { a.TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, R.Roll(3, 6), user, "colliding with the ceiling"); a.CollideWith(a.tile()); } else { a.tile().KnockObjectBack(a, line, 12, user); } a.attrs[AttrType.TELEKINETICALLY_THROWN] = 0; user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 0; if (a.curhp <= 0 && a.HasAttr(AttrType.REGENERATES_FROM_DEATH)) { a.attrs[AttrType.TURN_INTO_CORPSE] = 0; a.attrs[AttrType.CORPSE] = 0; a.attrs[AttrType.FROZEN] = 0; a.attrs[AttrType.INVULNERABLE] = 0; a.attrs[AttrType.SHIELDED] = 0; a.attrs[AttrType.BLOCKING] = 0; a.curhp = 1; //this is all pretty hacky - perhaps I should relocate the regenerating corpse through other means. a.TakeDamage(DamageType.NORMAL, DamageClass.NO_TYPE, 500, null); } else { a.CorpseCleanup(); } } else { if (wand) { return(true); } return(false); } } else { bool blast_fungus = false; if (t.type == TileType.BLAST_FUNGUS && !t.Is(FeatureType.GRENADE, FeatureType.WEB, FeatureType.FORASECT_EGG, FeatureType.BONES)) { blast_fungus = true; } if (t.inv != null || blast_fungus) { Item i = t.inv; string itemname = ""; if (blast_fungus) { itemname = "the blast fungus"; } else { itemname = i.GetName(true, The, Extra); } string msg = "Throw " + itemname + " in which direction? "; List <Tile> line = null; if (user == player) { TargetInfo info = t.GetTarget(false, 12, 0, false, true, false, msg); if (info != null) { line = info.extended_line; } } else { line = ai_line; } if (line != null) { if (line.Count > 13) { line = line.ToCount(13); //for range 12 } if (cast) { B.Add(user.GetName(false, The, Verb("cast")) + " telekinesis. ", user); user.MakeNoise(6); //should match spellVolume } if (blast_fungus) { B.Add("The blast fungus is pulled from the floor. ", t); B.Add("Its fuse ignites! ", t); t.Toggle(null); i = Item.Create(ConsumableType.BLAST_FUNGUS, t.row, t.col); if (i != null) { i.other_data = 3; i.revealed_by_light = true; Q.Add(new Event(i, 100, EventType.BLAST_FUNGUS)); Screen.AnimateMapCell(t.row, t.col, new colorchar('3', Color.Red), 100); } } if (line.Count == 1) { B.Add(user.GetName(true, The, Verb("throw")) + " " + itemname + " into the ceiling. ", user, t); } else { B.Add(user.GetName(true, The, Verb("throw")) + " " + itemname + ". ", user, t); } B.DisplayContents(); if (i.quantity > 1) { i.quantity--; bool revealed = i.revealed_by_light; i = new Item(i, -1, -1); i.revealed_by_light = revealed; } else { t.inv = null; Screen.WriteMapChar(t.row, t.col, M.VisibleColorChar(t.row, t.col)); } bool trigger_traps = false; Tile t2 = line.LastBeforeSolidTile(); Actor first = user.FirstActorInLine(line); if (t2 == line.LastOrDefault() && first == null) { trigger_traps = true; } if (first != null) { t2 = first.tile(); } line = line.ToFirstSolidTileOrActor(); //if(line.Count > 0){ // line.RemoveAt(line.Count - 1); //} if (line.Count > 0) { line.RemoveAt(line.Count - 1); } { Tile first_unseen = null; foreach (Tile tile2 in line) { if (!tile2.seen) { first_unseen = tile2; break; } } if (first_unseen != null) { line = line.To(first_unseen); if (line.Count > 0) { line.RemoveAt(line.Count - 1); } } } if (line.Count > 0) //or > 1 ? { user.AnimateProjectile(line, i.symbol, i.color); } if (first == user) { B.Add(user.GetName(false, The, Verb("catch")) + " it! ", user); if (user.inv.Count < Global.MAX_INVENTORY_SIZE) { user.GetItem(i); } else { B.Add("Your pack is too full to fit anything else. "); i.ignored = true; user.tile().GetItem(i); } } else { if (first != null) { B.Add("It hits " + first.GetName(The) + ". ", first); } if (i.IsBreakable()) { B.Add($"{i.GetName(true,The,Extra,Verb("break"))}! ", t2); if (i.ItemClass == ConsumableClass.ORB) { i.Use(null, new List <Tile> { t2 }); } else { i.CheckForMimic(); } t2.MakeNoise(4); } else { t2.GetItem(i); } t2.MakeNoise(2); } if (first != null && first != user && first != player) { first.player_visibility_duration = -1; first.attrs[AttrType.PLAYER_NOTICED]++; } else { if (trigger_traps && t2.IsTrap()) { t2.TriggerTrap(); } } } else { if (wand) { return(true); } return(false); } } else { if (!t.Is(FeatureType.GRENADE) && (t.Is(TileType.DOOR_C, TileType.DOOR_O, TileType.POISON_BULB) || t.Is(FeatureType.WEB, FeatureType.FORASECT_EGG, FeatureType.BONES))) { if (cast) { B.Add(user.GetName(false, The, Verb("cast")) + " telekinesis. ", user); user.MakeNoise(6); //should match spellVolume } if (t.Is(TileType.DOOR_C)) { B.Add("The door slams open. ", t); t.Toggle(null); } else { if (t.Is(TileType.DOOR_O)) { B.Add("The door slams open. ", t); t.Toggle(null); } } if (t.Is(TileType.POISON_BULB)) { t.Bump(0); } if (t.Is(FeatureType.WEB)) { B.Add("The web is destroyed. ", t); t.RemoveFeature(FeatureType.WEB); } if (t.Is(FeatureType.FORASECT_EGG)) { B.Add("The egg is destroyed. ", t); t.RemoveFeature(FeatureType.FORASECT_EGG); //todo: forasect pathing? } if (t.Is(FeatureType.BONES)) { B.Add("The bones are scattered. ", t); t.RemoveFeature(FeatureType.BONES); } } else { bool grenade = t.Is(FeatureType.GRENADE); bool barrel = t.Is(TileType.BARREL); bool flaming_barrel = barrel && t.IsBurning(); bool torch = t.Is(TileType.STANDING_TORCH); string feature_name = ""; int impact_damage_dice = 3; colorchar vis = new colorchar(t.symbol, t.color); switch (t.type) { case TileType.CRACKED_WALL: feature_name = "cracked wall"; break; case TileType.RUBBLE: feature_name = "pile of rubble"; break; case TileType.STATUE: feature_name = "statue"; break; } if (grenade) { impact_damage_dice = 0; feature_name = "grenade"; vis.c = ','; vis.color = Color.Red; } if (flaming_barrel) { feature_name = "flaming barrel of oil"; } if (barrel) { feature_name = "barrel"; } if (torch) { feature_name = "torch"; } if (feature_name == "") { if (wand) { if (user == player) { B.Add("The wand grabs at empty space. ", t); } return(true); } return(false); } string msg = "Throw the " + feature_name + " in which direction? "; bool passable_hack = !t.passable; if (passable_hack) { t.passable = true; } List <Tile> line = null; if (user == player) { TargetInfo info = t.GetTarget(false, 12, 0, false, true, false, msg); if (info != null) { line = info.extended_line; } } else { line = ai_line; } if (passable_hack) { t.passable = false; } if (line != null) { if (cast) { B.Add(user.GetName(false, The, Verb("cast")) + " telekinesis. ", user); user.MakeNoise(6); //should match spellVolume } if (line.Count == 1) { B.Add(user.GetName(true, The, Verb("throw")) + " the " + feature_name + " into the ceiling. ", user, t); } else { B.Add(user.GetName(true, The, Verb("throw")) + " the " + feature_name + ". ", user, t); } B.DisplayContents(); user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 1; switch (t.type) { case TileType.CRACKED_WALL: case TileType.RUBBLE: case TileType.STATUE: case TileType.BARREL: case TileType.STANDING_TORCH: if (flaming_barrel) { t.RemoveFeature(FeatureType.FIRE); } t.Toggle(null, TileType.FLOOR); foreach (Tile neighbor in t.TilesAtDistance(1)) { neighbor.solid_rock = false; } break; } if (grenade) { t.RemoveFeature(FeatureType.GRENADE); Event e = Q.FindTargetedEvent(t, EventType.GRENADE); if (e != null) { e.dead = true; } } Screen.WriteMapChar(t.row, t.col, M.VisibleColorChar(t.row, t.col)); colorchar[,] mem = Screen.GetCurrentMap(); int current_row = t.row; int current_col = t.col; // int knockback_strength = 12; if (line.Count == 1) { knockback_strength = 0; } int i = 0; while (true) { Tile t2 = line[i]; if (t2 == t) { break; } ++i; } line.RemoveRange(0, i + 1); while (knockback_strength > 0) //if the knockback strength is greater than 1, you're passing *over* at least one tile. { Tile t2 = line[0]; line.RemoveAt(0); if (!t2.passable) { if (t2.Is(TileType.CRACKED_WALL, TileType.DOOR_C, TileType.HIDDEN_DOOR) && impact_damage_dice > 0) { string tilename = t2.GetName(true, The); if (t2.type == TileType.HIDDEN_DOOR) { tilename = "a hidden door"; t2.Toggle(null); } B.Add("The " + feature_name + " flies into " + tilename + ". ", t2); t2.Toggle(null); Screen.WriteMapChar(current_row, current_col, mem[current_row, current_col]); } else { B.Add("The " + feature_name + " flies into " + t2.GetName(true, The) + ". ", t2); if (impact_damage_dice > 0) { t2.Bump(M.tile[current_row, current_col].DirectionOf(t2)); } Screen.WriteMapChar(current_row, current_col, mem[current_row, current_col]); } knockback_strength = 0; break; } else { if (t2.actor() != null) { B.Add("The " + feature_name + " flies into " + t2.actor().GetName(true, The) + ". ", t2); if (t2.actor().type != ActorType.SPORE_POD && !t2.actor().HasAttr(AttrType.SELF_TK_NO_DAMAGE)) { t2.actor().TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, R.Roll(impact_damage_dice, 6), user, "colliding with a " + feature_name); } knockback_strength = 0; Screen.WriteMapChar(t2.row, t2.col, vis); Screen.WriteMapChar(current_row, current_col, mem[current_row, current_col]); current_row = t2.row; current_col = t2.col; break; } else { if (t2.Is(FeatureType.WEB)) //unless perhaps grenades should get stuck and explode in the web? { t2.RemoveFeature(FeatureType.WEB); } Screen.WriteMapChar(t2.row, t2.col, vis); Screen.WriteMapChar(current_row, current_col, mem[current_row, current_col]); current_row = t2.row; current_col = t2.col; Screen.GLUpdate(); Thread.Sleep(20); } } //M.Draw(); knockback_strength--; } Tile current = M.tile[current_row, current_col]; if (grenade) { B.Add("The grenade explodes! ", current); current.ApplyExplosion(1, user, "an exploding grenade"); } if (barrel) { B.Add("The barrel smashes! ", current); List <Tile> cone = current.TilesWithinDistance(1).Where(x => x.passable); List <Tile> added = new List <Tile>(); foreach (Tile t3 in cone) { foreach (int dir in U.FourDirections) { if (R.CoinFlip() && t3.TileInDirection(dir).passable) { added.AddUnique(t3.TileInDirection(dir)); } } } cone.AddRange(added); foreach (Tile t3 in cone) { t3.AddFeature(FeatureType.OIL); if (t3.actor() != null && !t3.actor().HasAttr(AttrType.OIL_COVERED, AttrType.SLIMED)) { if (t3.actor().IsBurning()) { t3.actor().ApplyBurning(); } else { t3.actor().attrs[AttrType.OIL_COVERED] = 1; B.Add(t3.actor().GetName(false, The, Are) + " covered in oil. ", t3.actor()); if (t3.actor() == player) { Help.TutorialTip(TutorialTopic.Oiled); } } } } if (flaming_barrel) { current.ApplyEffect(DamageType.FIRE); } } if (torch) { current.AddFeature(FeatureType.FIRE); } user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 0; } else { if (wand) { return(true); } return(false); } } } } } else { return(false); } return(true); }