private ItemUseResult UseOrb(int radius,bool never_target_enemies,Actor user,List<Tile> line,OrbTargetingDelegate orb_effect) { ItemUseResult result = new ItemUseResult(true,true); if(line == null){ if(!identified[type]){ radius = 0; } line = user.GetTargetTile(12,radius,false,!(never_target_enemies && identified[type])); } if(line != null){ Tile t = line.LastOrDefault(); Tile prev = line.LastBeforeSolidTile(); Actor first = null; bool trigger_trap = true; Screen.CursorVisible = false; if(user != null){ first = user.FirstActorInLine(line); B.Add(user.You("fling") + " the " + SingularName() + ". ",user); if(first != null && first != user){ trigger_trap = false; t = first.tile(); if(player.CanSee(user)){ B.Add("It shatters on " + first.the_name + "! ",first); } else{ B.Add("Something shatters on " + first.the_name + "! ",first); } first.player_visibility_duration = -1; first.attrs[AttrType.PLAYER_NOTICED]++; } else{ if(player.CanSee(user)){ B.Add("It shatters on " + t.the_name + "! ",t); } else{ B.Add("Something shatters on " + t.the_name + "! ",t); } } user.AnimateProjectile(line.ToFirstSolidTileOrActor(),'*',color); Screen.CursorVisible = false; } else{ trigger_trap = false; } Tile LOE_tile = t; if(!t.passable && prev != null){ LOE_tile = prev; } orb_effect(t,LOE_tile,result); t.MakeNoise(2); if(trigger_trap && t.IsTrap()){ t.TriggerTrap(); } if(!revealed_by_light){ result.IDed = false; } } else{ result.used = false; } return result; }
public static bool Telekinesis(bool cast,Actor user,Tile t) { bool wand = !cast; if(t == null){ return false; } if(t != null){ if(wand && user == player && !Item.identified[ConsumableType.TELEKINESIS]){ Item.identified[ConsumableType.TELEKINESIS] = true; B.Add("(It was a wand of telekinesis!) "); B.PrintAll(); } List<Tile> ai_line = null; if(user != player && t == player.tile()){ var fires = user.TilesWithinDistance(12).Where(x=>x.passable && x.actor() == null && x.Is(FeatureType.FIRE) && user.CanSee(x) && player.HasBresenhamLineWithCondition(x,false,y=>y.passable && y.actor() == null)); if(fires.Count > 0){ ai_line = player.GetBestExtendedLineOfEffect(fires.Random()); } else{ if(wand){ ai_line = player.GetBestExtendedLineOfEffect(user); } else{ ai_line = player.GetBestExtendedLineOfEffect(player.TileInDirection(Global.RandomDirection())); } } } Actor a = t.actor(); if(a == null && t.inv == null && !t.Is(FeatureType.GRENADE) && t.Is(FeatureType.TROLL_CORPSE,FeatureType.TROLL_BLOODWITCH_CORPSE)){ ActorType troll_type = t.Is(FeatureType.TROLL_CORPSE)? ActorType.TROLL : ActorType.TROLL_BLOODWITCH; FeatureType troll_corpse = t.Is(FeatureType.TROLL_CORPSE)? FeatureType.TROLL_CORPSE : FeatureType.TROLL_BLOODWITCH_CORPSE; Event troll_event = Q.FindTargetedEvent(t,EventType.REGENERATING_FROM_DEATH); troll_event.dead = true; Actor troll = Actor.Create(troll_type,t.row,t.col); foreach(Event e in Q.list){ if(e.target == troll && e.type == EventType.MOVE){ e.tiebreaker = troll_event.tiebreaker; e.dead = true; break; } } Actor.tiebreakers[troll_event.tiebreaker] = troll; troll.symbol = '%'; troll.attrs[AttrType.CORPSE] = 1; troll.SetName(troll.name + "'s corpse"); troll.curhp = troll_event.value; troll.attrs[AttrType.PERMANENT_DAMAGE] = troll_event.secondary_value; troll.attrs[AttrType.NO_ITEM]++; t.features.Remove(troll_corpse); a = troll; } if(a != null){ string msg = "Throw " + a.TheName(true) + " in which direction? "; if(a == player){ msg = "Throw yourself in which direction? "; } List<Tile> line = null; if(user == player){ TargetInfo info = a.GetTarget(false,12,0,false,true,false,msg); if(info != null){ line = info.extended_line; } } else{ line = ai_line; } if(line != null){ if(line.Count == 1 && line[0].actor() == user){ if(wand){ return true; } return false; } if(cast){ B.Add(user.You("cast") + " telekinesis. ",user); if(a.type == ActorType.ALASI_BATTLEMAGE && !a.HasSpell(SpellType.TELEKINESIS)){ a.curmp += Spell.Tier(SpellType.TELEKINESIS); if(a.curmp > a.maxmp){ a.curmp = a.maxmp; } a.GainSpell(SpellType.TELEKINESIS); B.Add("Runes on " + a.Your() + " armor align themselves with the spell. ",a); } } if(a == user && a == player){ B.Add("You throw yourself forward. "); } else{ if(line.Count == 1){ B.Add(user.YouVisible("throw") + " " + a.TheName(true) + " into the ceiling. ",user,a); } else{ B.Add(user.YouVisible("throw") + " " + a.TheName(true) + ". ",user,a); } } B.DisplayNow(); user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 1; a.attrs[AttrType.TELEKINETICALLY_THROWN] = 1; a.attrs[AttrType.TURN_INTO_CORPSE]++; if(line.Count == 1){ a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(3,6),user,"colliding with the ceiling"); a.CollideWith(a.tile()); } else{ a.tile().KnockObjectBack(a,line,12,user); } a.attrs[AttrType.TELEKINETICALLY_THROWN] = 0; user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 0; if(a.curhp <= 0 && a.HasAttr(AttrType.REGENERATES_FROM_DEATH)){ a.attrs[AttrType.TURN_INTO_CORPSE] = 0; a.attrs[AttrType.CORPSE] = 0; a.attrs[AttrType.FROZEN] = 0; a.attrs[AttrType.INVULNERABLE] = 0; a.attrs[AttrType.SHIELDED] = 0; a.attrs[AttrType.BLOCKING] = 0; a.curhp = 1; //this is all pretty hacky - perhaps I should relocate the regenerating corpse through other means. a.TakeDamage(DamageType.NORMAL,DamageClass.NO_TYPE,500,null); } else{ a.CorpseCleanup(); } } else{ if(wand){ return true; } return false; } } else{ bool blast_fungus = false; if(t.type == TileType.BLAST_FUNGUS && !t.Is(FeatureType.GRENADE,FeatureType.WEB,FeatureType.FORASECT_EGG,FeatureType.BONES)){ blast_fungus = true; } if(t.inv != null || blast_fungus){ Item i = t.inv; string itemname = ""; if(blast_fungus){ itemname = "the blast fungus"; } else{ itemname = i.TheName(true); if(i.quantity > 1){ itemname = "the " + i.SingularName(); } } string msg = "Throw " + itemname + " in which direction? "; List<Tile> line = null; if(user == player){ TargetInfo info = t.GetTarget(false,12,0,false,true,false,msg); if(info != null){ line = info.extended_line; } } else{ line = ai_line; } if(line != null){ if(line.Count > 13){ line = line.ToCount(13); //for range 12 } if(cast){ B.Add(user.You("cast") + " telekinesis. ",user); } if(blast_fungus){ B.Add("The blast fungus is pulled from the floor. ",t); B.Add("Its fuse ignites! ",t); t.Toggle(null); i = Item.Create(ConsumableType.BLAST_FUNGUS,t.row,t.col); if(i != null){ i.other_data = 3; i.revealed_by_light = true; Q.Add(new Event(i,100,EventType.BLAST_FUNGUS)); Screen.AnimateMapCell(t.row,t.col,new colorchar('3',Color.Red),100); } } if(line.Count == 1){ B.Add(user.YouVisible("throw") + " " + itemname + " into the ceiling. ",user,t); } else{ B.Add(user.YouVisible("throw") + " " + itemname + ". ",user,t); } B.DisplayNow(); if(i.quantity > 1){ i.quantity--; bool revealed = i.revealed_by_light; i = new Item(i,-1,-1); i.revealed_by_light = revealed; } else{ t.inv = null; Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col)); } bool trigger_traps = false; Tile t2 = line.LastBeforeSolidTile(); Actor first = user.FirstActorInLine(line); if(t2 == line.LastOrDefault() && first == null){ trigger_traps = true; } if(first != null){ t2 = first.tile(); } line = line.ToFirstSolidTileOrActor(); //if(line.Count > 0){ // line.RemoveAt(line.Count - 1); //} if(line.Count > 0){ line.RemoveAt(line.Count - 1); } { Tile first_unseen = null; foreach(Tile tile2 in line){ if(!tile2.seen){ first_unseen = tile2; break; } } if(first_unseen != null){ line = line.To(first_unseen); if(line.Count > 0){ line.RemoveAt(line.Count - 1); } } } if(line.Count > 0){ //or > 1 ? user.AnimateProjectile(line,i.symbol,i.color); } if(first == user){ B.Add(user.You("catch",true) + " it! ",user); if(user.inv.Count < Global.MAX_INVENTORY_SIZE){ user.GetItem(i); } else{ B.Add("Your pack is too full to fit anything else. "); i.ignored = true; user.tile().GetItem(i); } } else{ if(first != null){ B.Add("It hits " + first.the_name + ". ",first); } if(i.IsBreakable()){ if(i.quantity > 1){ B.Add(i.TheName(true) + " break! ",t2); } else{ B.Add(i.TheName(true) + " breaks! ",t2); } if(i.NameOfItemType() == "orb"){ i.Use(null,new List<Tile>{t2}); } else{ i.CheckForMimic(); } } else{ t2.GetItem(i); } t2.MakeNoise(2); } if(first != null && first != user && first != player){ first.player_visibility_duration = -1; first.attrs[AttrType.PLAYER_NOTICED]++; } else{ if(trigger_traps && t2.IsTrap()){ t2.TriggerTrap(); } } } else{ if(wand){ return true; } return false; } } else{ if(!t.Is(FeatureType.GRENADE) && (t.Is(TileType.DOOR_C,TileType.DOOR_O,TileType.POISON_BULB) || t.Is(FeatureType.WEB,FeatureType.FORASECT_EGG,FeatureType.BONES))){ if(cast){ B.Add(user.You("cast") + " telekinesis. ",user); } if(t.Is(TileType.DOOR_C)){ B.Add("The door slams open. ",t); t.Toggle(null); } else{ if(t.Is(TileType.DOOR_O)){ B.Add("The door slams open. ",t); t.Toggle(null); } } if(t.Is(TileType.POISON_BULB)){ t.Bump(0); } if(t.Is(FeatureType.WEB)){ B.Add("The web is destroyed. ",t); t.RemoveFeature(FeatureType.WEB); } if(t.Is(FeatureType.FORASECT_EGG)){ B.Add("The egg is destroyed. ",t); t.RemoveFeature(FeatureType.FORASECT_EGG); //todo: forasect pathing? } if(t.Is(FeatureType.BONES)){ B.Add("The bones are scattered. ",t); t.RemoveFeature(FeatureType.BONES); } } else{ bool grenade = t.Is(FeatureType.GRENADE); bool barrel = t.Is(TileType.BARREL); bool flaming_barrel = barrel && t.IsBurning(); bool torch = t.Is(TileType.STANDING_TORCH); string feature_name = ""; int impact_damage_dice = 3; colorchar vis = new colorchar(t.symbol,t.color); switch(t.type){ case TileType.CRACKED_WALL: feature_name = "cracked wall"; break; case TileType.RUBBLE: feature_name = "pile of rubble"; break; case TileType.STATUE: feature_name = "statue"; break; } if(grenade){ impact_damage_dice = 0; feature_name = "grenade"; vis.c = ','; vis.color = Color.Red; } if(flaming_barrel){ feature_name = "flaming barrel of oil"; } if(barrel){ feature_name = "barrel"; } if(torch){ feature_name = "torch"; } if(feature_name == ""){ if(wand){ if(user == player){ B.Add("The wand grabs at empty space. ",t); } return true; } return false; } string msg = "Throw the " + feature_name + " in which direction? "; bool passable_hack = !t.passable; if(passable_hack){ t.passable = true; } List<Tile> line = null; if(user == player){ TargetInfo info = t.GetTarget(false,12,0,false,true,false,msg); if(info != null){ line = info.extended_line; } } else{ line = ai_line; } if(passable_hack){ t.passable = false; } if(line != null){ if(cast){ B.Add(user.You("cast") + " telekinesis. ",user); } if(line.Count == 1){ B.Add(user.YouVisible("throw") + " the " + feature_name + " into the ceiling. ",user,t); } else{ B.Add(user.YouVisible("throw") + " the " + feature_name + ". ",user,t); } B.DisplayNow(); user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 1; switch(t.type){ case TileType.CRACKED_WALL: case TileType.RUBBLE: case TileType.STATUE: case TileType.BARREL: case TileType.STANDING_TORCH: if(flaming_barrel){ t.RemoveFeature(FeatureType.FIRE); } t.Toggle(null,TileType.FLOOR); foreach(Tile neighbor in t.TilesAtDistance(1)){ neighbor.solid_rock = false; } break; } if(grenade){ t.RemoveFeature(FeatureType.GRENADE); Event e = Q.FindTargetedEvent(t,EventType.GRENADE); if(e != null){ e.dead = true; } } Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col)); colorchar[,] mem = Screen.GetCurrentMap(); int current_row = t.row; int current_col = t.col; // int knockback_strength = 12; if(line.Count == 1){ knockback_strength = 0; } int i=0; while(true){ Tile t2 = line[i]; if(t2 == t){ break; } ++i; } line.RemoveRange(0,i+1); while(knockback_strength > 0){ //if the knockback strength is greater than 1, you're passing *over* at least one tile. Tile t2 = line[0]; line.RemoveAt(0); if(!t2.passable){ if(t2.Is(TileType.CRACKED_WALL,TileType.DOOR_C,TileType.HIDDEN_DOOR) && impact_damage_dice > 0){ string tilename = t2.TheName(true); if(t2.type == TileType.HIDDEN_DOOR){ tilename = "a hidden door"; t2.Toggle(null); } B.Add("The " + feature_name + " flies into " + tilename + ". ",t2); t2.Toggle(null); Screen.WriteMapChar(current_row,current_col,mem[current_row,current_col]); } else{ B.Add("The " + feature_name + " flies into " + t2.TheName(true) + ". ",t2); if(impact_damage_dice > 0){ t2.Bump(M.tile[current_row,current_col].DirectionOf(t2)); } Screen.WriteMapChar(current_row,current_col,mem[current_row,current_col]); } knockback_strength = 0; break; } else{ if(t2.actor() != null){ B.Add("The " + feature_name + " flies into " + t2.actor().TheName(true) + ". ",t2); if(t2.actor().type != ActorType.SPORE_POD && !t2.actor().HasAttr(AttrType.SELF_TK_NO_DAMAGE)){ t2.actor().TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(impact_damage_dice,6),user,"colliding with a " + feature_name); } knockback_strength = 0; Screen.WriteMapChar(t2.row,t2.col,vis); Screen.WriteMapChar(current_row,current_col,mem[current_row,current_col]); current_row = t2.row; current_col = t2.col; break; } else{ if(t2.Is(FeatureType.WEB)){ //unless perhaps grenades should get stuck and explode in the web? t2.RemoveFeature(FeatureType.WEB); } Screen.WriteMapChar(t2.row,t2.col,vis); Screen.WriteMapChar(current_row,current_col,mem[current_row,current_col]); current_row = t2.row; current_col = t2.col; Game.GLUpdate(); Thread.Sleep(20); } } //M.Draw(); knockback_strength--; } Tile current = M.tile[current_row,current_col]; if(grenade){ B.Add("The grenade explodes! ",current); current.ApplyExplosion(1,user,"an exploding grenade"); } if(barrel){ B.Add("The barrel smashes! ",current); List<Tile> cone = current.TilesWithinDistance(1).Where(x=>x.passable); List<Tile> added = new List<Tile>(); foreach(Tile t3 in cone){ foreach(int dir in U.FourDirections){ if(R.CoinFlip() && t3.TileInDirection(dir).passable){ added.AddUnique(t3.TileInDirection(dir)); } } } cone.AddRange(added); foreach(Tile t3 in cone){ t3.AddFeature(FeatureType.OIL); if(t3.actor() != null && !t3.actor().HasAttr(AttrType.OIL_COVERED,AttrType.SLIMED)){ if(t3.actor().IsBurning()){ t3.actor().ApplyBurning(); } else{ t3.actor().attrs[AttrType.OIL_COVERED] = 1; B.Add(t3.actor().YouAre() + " covered in oil. ",t3.actor()); if(t3.actor() == player){ Help.TutorialTip(TutorialTopic.Oiled); } } } } if(flaming_barrel){ current.ApplyEffect(DamageType.FIRE); } } if(torch){ current.AddFeature(FeatureType.FIRE); } user.attrs[AttrType.SELF_TK_NO_DAMAGE] = 0; } else{ if(wand){ return true; } return false; } } } } } else{ return false; } return true; }
public bool KnockObjectBack(Actor a,List<Tile> line,int knockback_strength,Actor damage_source) { if(knockback_strength == 0){ //note that TURN_INTO_CORPSE should be set for 'a' - therefore it won't be removed and we can do what we want with it. return a.CollideWith(a.tile()); } int i=0; while(true){ Tile t = line[i]; if(t.actor() == a){ break; } ++i; } line.RemoveRange(0,i+1); if(line.Count == 0){ return a.CollideWith(a.tile()); } bool immobile = a.MovementPrevented(line[0]); string knocked_back_message = ""; if(!a.HasAttr(AttrType.TELEKINETICALLY_THROWN,AttrType.SELF_TK_NO_DAMAGE) && !immobile && player.CanSee(a)){ //if the player can see it now, don't check CanSee later. knocked_back_message = a.YouAre() + " knocked back. "; //B.Add(a.YouAre() + " knocked back. ",a); } int dice = 1; int damage_dice_to_other = 1; if(a.HasAttr(AttrType.TELEKINETICALLY_THROWN)){ dice = 3; damage_dice_to_other = 3; } if(a.HasAttr(AttrType.SELF_TK_NO_DAMAGE)){ dice = 0; } if(a.type == ActorType.SPORE_POD){ dice = 0; damage_dice_to_other = 0; } while(knockback_strength > 1){ //if the knockback strength is greater than 1, you're passing *over* at least one tile. Tile t = line[0]; line.RemoveAt(0); immobile = a.MovementPrevented(t); if(immobile){ if(player.CanSee(a.tile())){ B.Add(a.YouVisibleAre() + " knocked about. ",a); } if(a.type == ActorType.SPORE_POD){ return true; } return a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"crashing into the floor"); } if(!t.passable){ string deathstringname = t.AName(false); if(t.Is(TileType.CRACKED_WALL,TileType.DOOR_C,TileType.HIDDEN_DOOR) && !a.HasAttr(AttrType.SMALL)){ string tilename = t.TheName(true); if(t.type == TileType.HIDDEN_DOOR){ tilename = "a hidden door"; t.Toggle(null); } if(player.CanSee(a.tile())){ B.Add(a.YouVisibleAre() + " knocked through " + tilename + ". ",a,t); } else{ B.Add(knocked_back_message); } knocked_back_message = ""; //knockback_strength -= 2; //removing the distance modification for now t.Toggle(null); a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"slamming into " + deathstringname); a.Move(t.row,t.col); if(a.HasAttr(AttrType.BLEEDING) && !a.HasAttr(AttrType.SHIELDED,AttrType.INVULNERABLE,AttrType.SELF_TK_NO_DAMAGE)){ if(a.type == ActorType.HOMUNCULUS){ if(R.CoinFlip()){ t.AddFeature(FeatureType.OIL); } } else{ if(t.symbol == '.' && t.color == Color.White && R.CoinFlip()){ t.color = a.BloodColor(); } } } } else{ if(player.CanSee(a.tile())){ B.Add(a.YouVisibleAre() + " knocked into " + t.TheName(true) + ". ",a,t); } else{ B.Add(knocked_back_message); } knocked_back_message = ""; if(a.type != ActorType.SPORE_POD){ Color blood = a.BloodColor(); if(blood != Color.Black && R.CoinFlip() && t.Is(TileType.WALL) && !a.HasAttr(AttrType.SHIELDED,AttrType.INVULNERABLE,AttrType.SELF_TK_NO_DAMAGE)){ t.color = blood; } a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"slamming into " + deathstringname); } if(!a.HasAttr(AttrType.SMALL)){ t.Bump(a.DirectionOf(t)); } a.CollideWith(a.tile()); return !a.HasAttr(AttrType.CORPSE); } } else{ if(t.actor() != null){ if(player.CanSee(a.tile()) || player.CanSee(t)){ B.Add(a.YouVisibleAre() + " knocked into " + t.actor().TheName(true) + ". ",a,t.actor()); } else{ B.Add(knocked_back_message); } knocked_back_message = ""; string actorname = t.actor().AName(false); string actorname2 = a.AName(false); if(t.actor().type != ActorType.SPORE_POD && !t.actor().HasAttr(AttrType.SELF_TK_NO_DAMAGE)){ t.actor().TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(damage_dice_to_other,6),damage_source,"colliding with " + actorname2); } if(a.type != ActorType.SPORE_POD){ a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"colliding with " + actorname); } a.CollideWith(a.tile()); return !a.HasAttr(AttrType.CORPSE); } else{ if(t.Is(FeatureType.WEB) && !a.HasAttr(AttrType.SMALL)){ t.RemoveFeature(FeatureType.WEB); } a.Move(t.row,t.col,false); if(t.Is(FeatureType.WEB) && a.HasAttr(AttrType.SMALL) && !a.HasAttr(AttrType.SLIMED,AttrType.OIL_COVERED,AttrType.BURNING)){ knockback_strength = 0; } if(a.HasAttr(AttrType.BLEEDING) && !a.HasAttr(AttrType.SHIELDED,AttrType.INVULNERABLE,AttrType.SELF_TK_NO_DAMAGE)){ if(a.type == ActorType.HOMUNCULUS){ if(R.CoinFlip()){ t.AddFeature(FeatureType.OIL); } } else{ if(t.symbol == '.' && t.color == Color.White && R.CoinFlip()){ t.color = a.BloodColor(); } } } } } M.Draw(); knockback_strength--; } if(knockback_strength < 1){ return !a.HasAttr(AttrType.CORPSE); } bool slip = false; int extra_slip_tiles = -1; bool slip_message_printed = false; do{ Tile t = line[0]; line.RemoveAt(0); immobile = a.MovementPrevented(t); if(immobile){ if(player.CanSee(a.tile())){ B.Add(a.YouVisibleAre() + " knocked about. ",a); } if(a.type == ActorType.SPORE_POD){ return true; } return a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"crashing into the floor"); } if(!t.passable){ string deathstringname = t.AName(false); if(t.Is(TileType.CRACKED_WALL,TileType.DOOR_C,TileType.HIDDEN_DOOR) && !a.HasAttr(AttrType.SMALL)){ string tilename = t.TheName(true); if(t.type == TileType.HIDDEN_DOOR){ tilename = "a hidden door"; t.Toggle(null); } if(player.CanSee(a.tile())){ B.Add(a.YouVisibleAre() + " knocked through " + tilename + ". ",a,t); } else{ B.Add(knocked_back_message); } knocked_back_message = ""; t.Toggle(null); a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"slamming into " + deathstringname); a.Move(t.row,t.col); if(a.HasAttr(AttrType.BLEEDING) && !a.HasAttr(AttrType.SHIELDED,AttrType.INVULNERABLE,AttrType.SELF_TK_NO_DAMAGE)){ if(a.type == ActorType.HOMUNCULUS){ if(R.CoinFlip()){ t.AddFeature(FeatureType.OIL); } } else{ if(t.symbol == '.' && t.color == Color.White && R.CoinFlip()){ t.color = a.BloodColor(); } } } return !a.HasAttr(AttrType.CORPSE); } else{ if(player.CanSee(a.tile())){ B.Add(a.YouVisibleAre() + " knocked into " + t.TheName(true) + ". ",a,t); } else{ B.Add(knocked_back_message); } knocked_back_message = ""; if(a.type != ActorType.SPORE_POD){ Color blood = a.BloodColor(); if(blood != Color.Black && R.CoinFlip() && t.Is(TileType.WALL) && !a.HasAttr(AttrType.SHIELDED,AttrType.INVULNERABLE,AttrType.SELF_TK_NO_DAMAGE)){ t.color = blood; } a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"slamming into " + deathstringname); } if(!a.HasAttr(AttrType.SMALL)){ t.Bump(a.DirectionOf(t)); } a.CollideWith(a.tile()); return !a.HasAttr(AttrType.CORPSE); } } else{ if(t.actor() != null){ if(player.CanSee(a.tile()) || player.CanSee(t)){ B.Add(a.YouVisibleAre() + " knocked into " + t.actor().TheName(true) + ". ",a,t.actor()); } else{ B.Add(knocked_back_message); } knocked_back_message = ""; string actorname = t.actor().AName(false); string actorname2 = a.AName(false); if(t.actor().type != ActorType.SPORE_POD && !t.actor().HasAttr(AttrType.SELF_TK_NO_DAMAGE)){ t.actor().TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(damage_dice_to_other,6),damage_source,"colliding with " + actorname2); } if(a.type != ActorType.SPORE_POD){ a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"colliding with " + actorname); } a.CollideWith(a.tile()); return !a.HasAttr(AttrType.CORPSE); } else{ slip = false; if(t.IsSlippery()){ B.Add(knocked_back_message); knocked_back_message = ""; slip = true; if(!slip_message_printed){ slip_message_printed = true; B.Add(a.You("slide") + "! "); } } else{ if(extra_slip_tiles > 0){ extra_slip_tiles--; } if(extra_slip_tiles == -1 && a.HasAttr(AttrType.SLIMED,AttrType.OIL_COVERED) && !t.IsWater()){ B.Add(knocked_back_message); knocked_back_message = ""; extra_slip_tiles = 2; if(!slip_message_printed){ slip_message_printed = true; B.Add(a.You("slide") + "! "); } } } /*if(extra_slip_tiles > 0){ extra_slip_tiles--; } if(t.IsSlippery()){ B.Add(knocked_back_message); knocked_back_message = ""; slip = true; if(!slip_message_printed){ slip_message_printed = true; B.Add(a.You("slide") + "! "); } } else{ if(extra_slip_tiles == -1 && a.HasAttr(AttrType.SLIMED,AttrType.OIL_COVERED)){ B.Add(knocked_back_message); knocked_back_message = ""; extra_slip_tiles = 2; if(!slip_message_printed){ slip_message_printed = true; B.Add(a.You("slide") + "! "); } } }*/ bool interrupted = false; if(t.inv != null && t.inv.type == ConsumableType.DETONATION){ //this will cause a new knockback effect and end the current one B.Add(knocked_back_message); knocked_back_message = ""; interrupted = true; } if(t.IsTrap()){ if(t.type == TileType.FLING_TRAP){ //otherwise you'd teleport around, continuing to slide from your previous position. interrupted = true; } B.Add(knocked_back_message); knocked_back_message = ""; } if(t.Is(FeatureType.WEB) && !a.HasAttr(AttrType.SMALL)){ t.RemoveFeature(FeatureType.WEB); } a.Move(t.row,t.col); if(a.HasAttr(AttrType.BLEEDING) && !a.HasAttr(AttrType.SHIELDED,AttrType.INVULNERABLE,AttrType.SELF_TK_NO_DAMAGE)){ if(a.type == ActorType.HOMUNCULUS){ if(R.CoinFlip()){ t.AddFeature(FeatureType.OIL); } } else{ if(t.symbol == '.' && t.color == Color.White && R.CoinFlip()){ t.color = a.BloodColor(); } } } if(a.HasAttr(AttrType.FROZEN)){ interrupted = true; } if(a.HasAttr(AttrType.SMALL) && t.Is(FeatureType.WEB) && !a.HasAttr(AttrType.SLIMED,AttrType.OIL_COVERED,AttrType.BURNING)){ B.Add(knocked_back_message); interrupted = true; } else{ if(a.tile().IsWater()){ interrupted = true; } B.Add(knocked_back_message); a.CollideWith(a.tile()); } knocked_back_message = ""; if(interrupted){ return !a.HasAttr(AttrType.CORPSE); } } } M.Draw(); } while(slip || extra_slip_tiles > 0); if(knocked_back_message != ""){ B.Add(knocked_back_message); //this probably never happens } return !a.HasAttr(AttrType.CORPSE); }
public bool Use(Actor user,List<Tile> line) { bool used = true; bool IDed = true; switch(type){ case ConsumableType.HEALING: user.curhp = user.maxhp; B.Add(user.Your() + " wounds are healed completely. ",user); break; case ConsumableType.REGENERATION: { if(user == player){ B.Add("Your blood tingles. "); } else{ B.Add(user.the_name + " looks energized. ",user); } user.attrs[AttrType.REGENERATING]++; int duration = 100; Q.Add(new Event(user,duration*100,AttrType.REGENERATING)); break; } case ConsumableType.STONEFORM: { B.Add(user.You("transform") + " into a being of animated stone. ",user); int duration = R.Roll(2,20) + 20; List<AttrType> attributes = new List<AttrType>{AttrType.REGENERATING,AttrType.BRUTISH_STRENGTH,AttrType.VIGOR,AttrType.SILENCE_AURA,AttrType.SHADOW_CLOAK,AttrType.CAN_DODGE,AttrType.MENTAL_IMMUNITY,AttrType.DETECTING_MONSTERS,AttrType.MYSTIC_MIND}; foreach(AttrType at in attributes){ //in the rare case where a monster drinks this potion, it can lose these natural statuses permanently. this might eventually be fixed. if(user.HasAttr(at)){ user.attrs[at] = 0; Q.KillEvents(user,at); switch(at){ case AttrType.REGENERATING: B.Add(user.You("no longer regenerate") + ". ",user); break; case AttrType.BRUTISH_STRENGTH: B.Add(user.Your() + " brutish strength fades. ",user); break; case AttrType.VIGOR: B.Add(user.Your() + " extraordinary speed fades. ",user); break; case AttrType.SILENCED: B.Add(user.You("no longer radiate") + " an aura of silence. ",user); break; case AttrType.SHADOW_CLOAK: B.Add(user.YouAre() + " no longer cloaked. ",user); break; case AttrType.MYSTIC_MIND: B.Add(user.Your() + " consciousness returns to normal. ",user); break; } } } if(user.HasAttr(AttrType.PSEUDO_VAMPIRIC)){ user.attrs[AttrType.LIGHT_SENSITIVE] = 0; user.attrs[AttrType.FLYING] = 0; user.attrs[AttrType.PSEUDO_VAMPIRIC] = 0; Q.KillEvents(user,AttrType.LIGHT_SENSITIVE); Q.KillEvents(user,AttrType.FLYING); Q.KillEvents(user,AttrType.PSEUDO_VAMPIRIC); B.Add(user.YouAre() + " no longer vampiric. ",user); } if(user.HasAttr(AttrType.ROOTS)){ foreach(Event e in Q.list){ if(e.target == user && !e.dead){ if(e.attr == AttrType.IMMOBILE && e.msg.Contains("rooted to the ground")){ e.dead = true; user.attrs[AttrType.IMMOBILE]--; B.Add(user.YouAre() + " no longer rooted to the ground. ",user); } else{ if(e.attr == AttrType.BONUS_DEFENSE && e.value == 10){ e.dead = true; //this would break if there were other timed effects that gave the same amount of defense user.attrs[AttrType.BONUS_DEFENSE] -= 10; } else{ if(e.attr == AttrType.ROOTS){ e.dead = true; user.attrs[AttrType.ROOTS]--; } } } } } } if(user.HasAttr(AttrType.BURNING)){ user.RefreshDuration(AttrType.BURNING,0); } user.attrs[AttrType.IMMUNE_BURNING]++; Q.Add(new Event(user,duration*100,AttrType.IMMUNE_BURNING)); user.attrs[AttrType.DAMAGE_RESISTANCE]++; Q.Add(new Event(user,duration*100,AttrType.DAMAGE_RESISTANCE)); user.attrs[AttrType.NONLIVING]++; Q.Add(new Event(user,duration*100,AttrType.NONLIVING)); user.RefreshDuration(AttrType.STONEFORM,duration*100,user.Your() + " rocky form reverts to flesh. ",user); if(user == player){ Help.TutorialTip(TutorialTopic.Stoneform); } break; } case ConsumableType.VAMPIRISM: { B.Add(user.You("become") + " vampiric. ",user); B.Add(user.You("rise") + " into the air. ",user); int duration = R.Roll(2,20) + 20; user.RefreshDuration(AttrType.LIGHT_SENSITIVE,duration*100); user.RefreshDuration(AttrType.FLYING,duration*100); user.attrs[AttrType.DESCENDING] = 0; user.RefreshDuration(AttrType.PSEUDO_VAMPIRIC,duration*100,user.YouAre() + " no longer vampiric. ",user); if(user == player){ Help.TutorialTip(TutorialTopic.Vampirism); } break; } case ConsumableType.BRUTISH_STRENGTH: { if(user == player){ B.Add("You feel a surge of strength. "); } else{ B.Add(user.Your() + " muscles ripple. ",user); } user.RefreshDuration(AttrType.BRUTISH_STRENGTH,(R.Roll(3,6)+16)*100,user.Your() + " incredible strength wears off. ",user); if(user == player){ Help.TutorialTip(TutorialTopic.BrutishStrength); } break; } case ConsumableType.ROOTS: { if(user.HasAttr(AttrType.ROOTS)){ foreach(Event e in Q.list){ if(e.target == user && !e.dead){ if(e.attr == AttrType.IMMOBILE && e.msg.Contains("rooted to the ground")){ e.dead = true; user.attrs[AttrType.IMMOBILE]--; } else{ if(e.attr == AttrType.BONUS_DEFENSE && e.value == 10){ e.dead = true; //this would break if there were other timed effects that gave 5 defense user.attrs[AttrType.BONUS_DEFENSE] -= 10; } else{ if(e.attr == AttrType.ROOTS){ e.dead = true; user.attrs[AttrType.ROOTS]--; } } } } } B.Add(user.Your() + " roots extend deeper into the ground. ",user); } else{ B.Add(user.You("grow") + " roots and a hard shell of bark. ",user); } int duration = R.Roll(20) + 20; user.RefreshDuration(AttrType.ROOTS,duration*100); user.attrs[AttrType.BONUS_DEFENSE] += 10; Q.Add(new Event(user,duration*100,AttrType.BONUS_DEFENSE,10)); user.attrs[AttrType.IMMOBILE]++; Q.Add(new Event(user,duration*100,AttrType.IMMOBILE,user.YouAre() + " no longer rooted to the ground. ",user)); if(user == player){ Help.TutorialTip(TutorialTopic.Roots); } if(user.HasAttr(AttrType.FLYING) && user.tile().IsTrap()){ user.tile().TriggerTrap(); } break; } case ConsumableType.HASTE: { B.Add(user.You("start") + " moving with extraordinary speed. ",user); int duration = (R.Roll(2,10) + 10) * 100; user.RefreshDuration(AttrType.CAN_DODGE,duration); //todo: dodging tip goes here user.RefreshDuration(AttrType.VIGOR,duration,user.Your() + " extraordinary speed fades. ",user); if(user == player){ Help.TutorialTip(TutorialTopic.IncreasedSpeed); } break; } case ConsumableType.SILENCE: { B.Add("A hush falls around " + user.the_name + ". ",user); user.RefreshDuration(AttrType.SILENCE_AURA,(R.Roll(2,20)+20)*100,user.You("no longer radiate") + " an aura of silence. ",user); if(user == player){ Help.TutorialTip(TutorialTopic.Silenced); } break; } case ConsumableType.CLOAKING: if(user.tile().IsLit()){ if(user == player){ B.Add("You would feel at home in the shadows. "); } else{ B.Add("A shadow moves across " + user.the_name + ". ",user); } } else{ B.Add(user.You("fade") + " away in the darkness. ",user); } user.RefreshDuration(AttrType.SHADOW_CLOAK,(R.Roll(2,20)+30)*100,user.YouAre() + " no longer cloaked. ",user); break; case ConsumableType.MYSTIC_MIND: { B.Add(user.Your() + " mind expands. ",user); int duration = R.Roll(2,20)+60; user.attrs[AttrType.ASLEEP] = 0; //user.RefreshDuration(AttrType.MAGICAL_DROWSINESS,0); user.RefreshDuration(AttrType.CONFUSED,0); user.RefreshDuration(AttrType.STUNNED,0); user.RefreshDuration(AttrType.ENRAGED,0); user.RefreshDuration(AttrType.MENTAL_IMMUNITY,duration*100); user.RefreshDuration(AttrType.DETECTING_MONSTERS,duration*100); user.RefreshDuration(AttrType.MYSTIC_MIND,duration*100,user.Your() + " consciousness returns to normal. ",user); if(user == player){ Help.TutorialTip(TutorialTopic.MysticMind); } break; } case ConsumableType.BLINKING: { List<Tile> tiles = user.TilesWithinDistance(8).Where(x => x.passable && x.actor() == null && user.ApproximateEuclideanDistanceFromX10(x) >= 45); if(tiles.Count > 0 && !user.HasAttr(AttrType.IMMOBILE)){ Tile t = tiles.Random(); B.Add(user.You("step") + " through a rip in reality. ",M.tile[user.p],t); user.AnimateStorm(2,3,4,'*',Color.DarkMagenta); user.Move(t.row,t.col); M.Draw(); user.AnimateStorm(2,3,4,'*',Color.DarkMagenta); } else{ B.Add("Nothing happens. ",user); IDed = false; } break; } case ConsumableType.PASSAGE: { if(user.HasAttr(AttrType.IMMOBILE)){ B.Add("Nothing happens. ",user); IDed = false; break; } List<int> valid_dirs = new List<int>(); foreach(int dir in U.FourDirections){ Tile t = user.TileInDirection(dir); if(t != null && t.Is(TileType.WALL,TileType.CRACKED_WALL,TileType.WAX_WALL,TileType.DOOR_C,TileType.HIDDEN_DOOR,TileType.STONE_SLAB)){ while(!t.passable){ if(t.row == 0 || t.row == Global.ROWS-1 || t.col == 0 || t.col == Global.COLS-1){ break; } t = t.TileInDirection(dir); } if(t.passable){ valid_dirs.Add(dir); } } } if(valid_dirs.Count > 0){ int dir = valid_dirs.Random(); Tile t = user.TileInDirection(dir); colorchar ch = new colorchar(Color.Cyan,'!'); switch(user.DirectionOf(t)){ case 8: case 2: ch.c = '|'; break; case 4: case 6: ch.c = '-'; break; } List<Tile> tiles = new List<Tile>(); List<colorchar> memlist = new List<colorchar>(); Screen.CursorVisible = false; Tile last_wall = null; while(!t.passable){ tiles.Add(t); memlist.Add(Screen.MapChar(t.row,t.col)); Screen.WriteMapChar(t.row,t.col,ch); Game.GLUpdate(); Thread.Sleep(35); last_wall = t; t = t.TileInDirection(dir); } Input.FlushInput(); if(t.actor() == null){ int r = user.row; int c = user.col; user.Move(t.row,t.col); Screen.WriteMapChar(r,c,M.VisibleColorChar(r,c)); Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col)); int idx = 0; foreach(Tile tile in tiles){ Screen.WriteMapChar(tile.row,tile.col,memlist[idx++]); Game.GLUpdate(); Thread.Sleep(35); } Input.FlushInput(); B.Add(user.You("travel") + " through the passage. ",user,t); } else{ Tile destination = null; List<Tile> adjacent = t.TilesAtDistance(1).Where(x=>x.passable && x.actor() == null && x.DistanceFrom(last_wall) == 1); if(adjacent.Count > 0){ destination = adjacent.Random(); } else{ foreach(Tile tile in M.ReachableTilesByDistance(t.row,t.col,false)){ if(tile.actor() == null){ destination = tile; break; } } } if(destination != null){ int r = user.row; int c = user.col; user.Move(destination.row,destination.col); Screen.WriteMapChar(r,c,M.VisibleColorChar(r,c)); Screen.WriteMapChar(destination.row,destination.col,M.VisibleColorChar(destination.row,destination.col)); int idx = 0; foreach(Tile tile in tiles){ Screen.WriteMapChar(tile.row,tile.col,memlist[idx++]); Game.GLUpdate(); Thread.Sleep(35); } Input.FlushInput(); B.Add(user.You("travel") + " through the passage. ",user,destination); } else{ B.Add("Something blocks " + user.Your() + " movement through the passage. ",user); } } } else{ B.Add("Nothing happens. ",user); IDed = false; } break; } case ConsumableType.TIME: if(user == player){ B.Add("Time stops for a moment. ",user); } else{ B.Add("Time warps around " + user.the_name + "! ",user); B.PrintAll(); } if(Fire.fire_event == null){ //this prevents fire from updating while time is frozen Fire.fire_event = new Event(0,EventType.FIRE); Fire.fire_event.tiebreaker = 0; Q.Add(Fire.fire_event); } Q.turn -= 200; break; case ConsumableType.KNOWLEDGE: { if(user == player){ B.Add("Knowledge fills your mind. "); Event hiddencheck = null; foreach(Event e in Q.list){ if(!e.dead && e.type == EventType.CHECK_FOR_HIDDEN){ hiddencheck = e; break; } } int max_dist = 0; List<Tile> last_tiles = new List<Tile>(); foreach(Tile t in M.ReachableTilesByDistance(user.row,user.col,true,TileType.STONE_SLAB,TileType.DOOR_C,TileType.STALAGMITE,TileType.RUBBLE,TileType.HIDDEN_DOOR)){ if(t.type != TileType.FLOOR){ t.seen = true; if(t.type != TileType.WALL){ t.revealed_by_light = true; } if(t.IsTrap() || t.Is(TileType.HIDDEN_DOOR)){ if(hiddencheck != null){ hiddencheck.area.Remove(t); } } if(t.IsTrap()){ t.name = Tile.Prototype(t.type).name; t.a_name = Tile.Prototype(t.type).a_name; t.the_name = Tile.Prototype(t.type).the_name; t.symbol = Tile.Prototype(t.type).symbol; t.color = Tile.Prototype(t.type).color; } if(t.Is(TileType.HIDDEN_DOOR)){ t.Toggle(null); } colorchar ch2 = Screen.BlankChar(); if(t.inv != null){ t.inv.revealed_by_light = true; ch2.c = t.inv.symbol; ch2.color = t.inv.color; M.last_seen[t.row,t.col] = ch2; } else{ if(t.features.Count > 0){ ch2 = t.FeatureVisual(); M.last_seen[t.row,t.col] = ch2; } else{ ch2.c = t.symbol; ch2.color = t.color; if(ch2.c == '#' && ch2.color == Color.RandomGlowingFungus){ ch2.color = Color.Gray; } M.last_seen[t.row,t.col] = ch2; } } Screen.WriteMapChar(t.row,t.col,t.symbol,Color.RandomRainbow); //Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col)); if(user.DistanceFrom(t) > max_dist){ max_dist = user.DistanceFrom(t); Game.GLUpdate(); Thread.Sleep(10); while(last_tiles.Count > 0){ Tile t2 = last_tiles.RemoveRandom(); Screen.WriteMapChar(t2.row,t2.col,M.last_seen[t2.row,t2.col]); //Screen.WriteMapChar(t2.row,t2.col,M.VisibleColorChar(t2.row,t2.col)); } } last_tiles.Add(t); } } if(user.inv.Count > 0){ foreach(Item i in user.inv){ identified[i.type] = true; if(i.NameOfItemType() == "wand"){ i.other_data = -1; } } } } else{ B.Add(user.the_name + " looks more knowledgeable. ",user); } break; } case ConsumableType.SUNLIGHT: if(M.wiz_lite == false){ B.Add("The air itself seems to shine. "); M.wiz_lite = true; M.wiz_dark = false; Q.KillEvents(null,EventType.NORMAL_LIGHTING); Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING)); } else{ B.Add("The air grows even brighter for a moment. "); Q.KillEvents(null,EventType.NORMAL_LIGHTING); Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING)); } break; case ConsumableType.DARKNESS: if(M.wiz_dark == false){ B.Add("The air itself grows dark. "); if(player.light_radius > 0){ B.Add("Your light is extinguished! "); } M.wiz_dark = true; M.wiz_lite = false; Q.KillEvents(null,EventType.NORMAL_LIGHTING); Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING)); } else{ B.Add("The air grows even darker for a moment. "); Q.KillEvents(null,EventType.NORMAL_LIGHTING); Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING)); } break; case ConsumableType.RENEWAL: { B.Add("A glow envelops " + user.the_name + ". ",user); //B.Add("A glow envelops " + user.Your() + " equipment. ",user); bool repaired = false; foreach(EquipmentStatus eqstatus in Enum.GetValues(typeof(EquipmentStatus))){ foreach(Weapon w in user.weapons){ if(w.status[eqstatus]){ repaired = true; w.status[eqstatus] = false; } } foreach(Armor a in user.armors){ if(a.status[eqstatus]){ repaired = true; a.status[eqstatus] = false; } } } if(repaired){ B.Add(user.Your() + " equipment looks as good as new! ",user); } if(user.HasAttr(AttrType.SLIMED)){ B.Add(user.YouAre() + " no longer covered in slime. ",user); user.attrs[AttrType.SLIMED] = 0; } if(user.HasAttr(AttrType.OIL_COVERED)){ B.Add(user.YouAre() + " no longer covered in oil. ",user); user.attrs[AttrType.OIL_COVERED] = 0; } int recharged = 0; foreach(Item i in user.inv){ if(i.NameOfItemType() == "wand"){ i.charges++; recharged++; } } if(recharged > 0){ if(recharged == 1){ B.Add("The glow charges " + user.Your() + " wand. ",user); } else{ B.Add("The glow charges " + user.Your() + " wands. ",user); } } break; } case ConsumableType.CALLING: { bool found = false; if(user == player){ for(int dist = 1;dist < Math.Max(Global.ROWS,Global.COLS);++dist){ List<Tile> tiles = user.TilesAtDistance(dist).Where(x=>x.actor() != null && !x.actor().HasAttr(AttrType.IMMOBILE)); if(tiles.Count > 0){ Actor a = tiles.Random().actor(); Tile t2 = user.TileInDirection(user.DirectionOf(a)); if(t2.passable && t2.actor() == null){ B.Add("The scroll calls " + a.a_name + " to you. "); a.Move(t2.row,t2.col); found = true; break; } foreach(Tile t in M.ReachableTilesByDistance(user.row,user.col,false)){ if(t.actor() == null){ B.Add("The scroll calls " + a.a_name + " to you. "); a.Move(t.row,t.col); found = true; break; } } if(found){ break; } } } } else{ if(!player.HasAttr(AttrType.IMMOBILE) && user.DistanceFrom(player) > 1){ Tile t2 = user.TileInDirection(user.DirectionOf(player)); if(t2.passable && t2.actor() == null){ B.Add("The scroll calls you to " + user.TheName(true) + ". "); player.Move(t2.row,t2.col); found = true; } if(!found){ foreach(Tile t in M.ReachableTilesByDistance(user.row,user.col,false)){ if(t.actor() == null){ B.Add("The scroll calls you to " + user.TheName(true) + ". "); player.Move(t.row,t.col); found = true; break; } } } } } if(!found){ B.Add("Nothing happens. ",user); IDed = false; } break; } case ConsumableType.TRAP_CLEARING: { List<Tile> traps = new List<Tile>(); { List<Tile>[] traparray = new List<Tile>[5]; for(int i=0;i<5;++i){ traparray[i] = new List<Tile>(); } for(int i=0;i<=12;++i){ foreach(Tile t in user.TilesAtDistance(i)){ //all this ensures that the traps go off in the best order switch(t.type){ case TileType.ALARM_TRAP: case TileType.TELEPORT_TRAP: case TileType.ICE_TRAP: case TileType.BLINDING_TRAP: case TileType.SHOCK_TRAP: case TileType.FIRE_TRAP: case TileType.SCALDING_OIL_TRAP: traparray[0].Add(t); break; case TileType.POISON_GAS_TRAP: case TileType.GRENADE_TRAP: traparray[1].Add(t); break; case TileType.SLIDING_WALL_TRAP: case TileType.PHANTOM_TRAP: traparray[2].Add(t); break; case TileType.LIGHT_TRAP: case TileType.DARKNESS_TRAP: traparray[3].Add(t); break; case TileType.FLING_TRAP: case TileType.STONE_RAIN_TRAP: traparray[4].Add(t); break; } } } for(int i=0;i<5;++i){ foreach(Tile t in traparray[i]){ traps.Add(t); } } } if(traps.Count > 0){ B.Add("*CLICK*. "); foreach(Tile t in traps){ t.TriggerTrap(false); } } else{ B.Add("Nothing happens. ",user); IDed = false; } break; } case ConsumableType.ENCHANTMENT: { if(user == player){ EnchantmentType ench = (EnchantmentType)R.Between(0,4); while(ench == user.EquippedWeapon.enchantment){ ench = (EnchantmentType)R.Between(0,4); } B.Add("Your " + user.EquippedWeapon.NameWithEnchantment() + " glows brightly! "); user.EquippedWeapon.enchantment = ench; B.Add("Your " + user.EquippedWeapon.NameWithoutEnchantment() + " is now a " + user.EquippedWeapon.NameWithEnchantment() + "! "); } else{ B.Add("Nothing happens. ",user); IDed = false; } break; } case ConsumableType.THUNDERCLAP: { B.Add("Thunder crashes! ",user); var scr = Screen.GetCurrentMap(); List<Tile>[] printed = new List<Tile>[13]; Color leading_edge_color = Color.White; Color trail_color = Color.DarkCyan; if(Global.LINUX && !Screen.GLMode){ leading_edge_color = Color.Gray; } for(int dist=0;dist<=12;++dist){ printed[dist] = new List<Tile>(); foreach(Tile t in user.TilesAtDistance(dist)){ if(t.seen && user.HasLOE(t)){ printed[dist].Add(t); } } foreach(Tile t in printed[dist]){ colorchar cch = M.VisibleColorChar(t.row,t.col); cch.bgcolor = leading_edge_color; if(cch.color == leading_edge_color){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); } if(dist > 0){ foreach(Tile t in printed[dist-1]){ colorchar cch = M.VisibleColorChar(t.row,t.col); cch.bgcolor = trail_color; if(cch.color == trail_color){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); } if(dist > 4){ foreach(Tile t in printed[dist-5]){ Screen.WriteMapChar(t.row,t.col,scr[t.row,t.col]); } } } Game.GLUpdate(); Thread.Sleep(10); } List<Actor> actors = new List<Actor>(); for(int dist=0;dist<=12;++dist){ foreach(Tile t in user.TilesAtDistance(dist).Randomize()){ if(user.HasLOE(t)){ if(t.actor() != null && t.actor() != user){ actors.Add(t.actor()); } t.BreakFragileFeatures(); } } } foreach(Actor a in actors){ if(a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(4,6),user,"a scroll of thunderclap")){ a.ApplyStatus(AttrType.STUNNED,R.Between(5,10)*100); } } user.MakeNoise(12); break; } case ConsumableType.FIRE_RING: { List<pos> cells = new List<pos>(); List<Tile> valid = new List<Tile>(); foreach(Tile t in user.TilesWithinDistance(3)){ if(t.passable && user.DistanceFrom(t) > 1 && user.HasLOE(t) && user.ApproximateEuclideanDistanceFromX10(t) < 45){ valid.Add(t); cells.Add(t.p); } } if(valid.Count > 0){ if(player.CanSee(user)){ B.Add("A ring of fire surrounds " + user.the_name + ". "); } else{ B.Add("A ring of fire appears! ",user.tile()); } valid.Randomize(); foreach(Tile t in valid){ t.AddFeature(FeatureType.FIRE); } Screen.AnimateMapCells(cells,new colorchar('&',Color.RandomFire)); } else{ B.Add("Nothing happens. ",user); IDed = false; } break; } case ConsumableType.RAGE: { B.Add("A murderous red glow cascades outward. ",user); List<Tile>[] printed = new List<Tile>[13]; Color leading_edge_color = Color.Red; Color trail_color = Color.DarkRed; if(Global.LINUX && !Screen.GLMode){ leading_edge_color = Color.DarkRed; } for(int dist=0;dist<=12;++dist){ printed[dist] = new List<Tile>(); foreach(Tile t in user.TilesAtDistance(dist)){ if(t.seen && user.HasLOS(t)){ printed[dist].Add(t); } } foreach(Tile t in printed[dist]){ colorchar cch = M.VisibleColorChar(t.row,t.col); cch.bgcolor = leading_edge_color; if(cch.color == leading_edge_color){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); } if(dist > 0){ foreach(Tile t in printed[dist-1]){ colorchar cch = M.VisibleColorChar(t.row,t.col); cch.bgcolor = trail_color; if(cch.color == trail_color){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); } } Game.GLUpdate(); Thread.Sleep(5); } int actors_affected = 0; string name_is = ""; foreach(Actor a in M.AllActors()){ if(a != user && user.DistanceFrom(a) <= 12 && user.HasLOS(a)){ a.ApplyStatus(AttrType.ENRAGED,R.Between(10,17)*100,false,"",a.You("calm") + " down. ",a.You("resist") + "! "); actors_affected++; if(player.CanSee(a)){ name_is = a.YouAre(); } } } if(actors_affected > 0){ if(actors_affected == 1){ B.Add(name_is + " enraged! "); } else{ B.Add("Bloodlust fills the air. "); } } break; } case ConsumableType.FREEZING: { ItemUseResult orb_result = UseOrb(2,false,user,line,(t,LOE_tile,results)=>{ Screen.AnimateExplosion(t,2,new colorchar('*',Color.RandomIce)); List<Tile> targets = new List<Tile>(); foreach(Tile t2 in t.TilesWithinDistance(2)){ if(LOE_tile.HasLOE(t2)){ targets.Add(t2); } } while(targets.Count > 0){ Tile t2 = targets.RemoveRandom(); t2.ApplyEffect(DamageType.COLD); Actor ac = t2.actor(); if(ac != null){ ac.ApplyFreezing(); } } }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.FLAMES: { ItemUseResult orb_result = UseOrb(2,false,user,line,(t,LOE_tile,results)=>{ List<Tile> area = new List<Tile>(); List<pos> cells = new List<pos>(); foreach(Tile tile in t.TilesWithinDistance(2)){ if(LOE_tile.HasLOE(tile)){ if(tile.passable){ tile.AddFeature(FeatureType.FIRE); } else{ tile.ApplyEffect(DamageType.FIRE); } if(tile.Is(FeatureType.FIRE)){ area.Add(tile); } cells.Add(tile.p); } } Screen.AnimateMapCells(cells,new colorchar('&',Color.RandomFire)); }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.FOG: { ItemUseResult orb_result = UseOrb(3,false,user,line,(t,LOE_tile,results)=>{ List<Tile> area = new List<Tile>(); List<pos> cells = new List<pos>(); colorchar cch = new colorchar('*',Color.Gray); for(int i=0;i<=3;++i){ foreach(Tile tile in t.TilesAtDistance(i)){ if(tile.passable && LOE_tile.HasLOE(tile)){ tile.AddFeature(FeatureType.FOG); area.Add(tile); cells.Add(tile.p); if(tile.seen){ M.last_seen[tile.row,tile.col] = cch; } } } Screen.AnimateMapCells(cells,cch,40); } Q.RemoveTilesFromEventAreas(area,EventType.REMOVE_GAS); Event.RemoveGas(area,800,FeatureType.FOG,25); //Q.Add(new Event(area,600,EventType.FOG,25)); }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.DETONATION: { ItemUseResult orb_result = UseOrb(3,false,user,line,(t,LOE_tile,results)=>{ LOE_tile.ApplyExplosion(3,user,"an orb of detonation"); }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.BREACHING: { ItemUseResult orb_result = UseOrb(5,false,user,line,(t,LOE_tile,results)=>{ int max_dist = -1; foreach(Tile t2 in M.TilesByDistance(t.row,t.col,false,true)){ if(t.DistanceFrom(t2) > 5){ break; } if(t2.Is(TileType.WALL,TileType.WAX_WALL,TileType.STALAGMITE,TileType.CRACKED_WALL,TileType.DOOR_C)){ Screen.WriteMapChar(t2.row,t2.col,t2.symbol,Color.RandomBreached); if(t.DistanceFrom(t2) > max_dist){ max_dist = t.DistanceFrom(t2); Game.GLUpdate(); //todo: stalagmites - if I add them to caves, they should no longer always vanish. check for an event, maybe? Thread.Sleep(50); } } } List<Tile> area = new List<Tile>(); foreach(Tile tile in t.TilesWithinDistance(5)){ if(tile.Is(TileType.WALL,TileType.WAX_WALL,TileType.STALAGMITE,TileType.CRACKED_WALL,TileType.DOOR_C) && tile.p.BoundsCheck(M.tile,false)){ TileType prev_type = tile.type; if(tile.Is(TileType.STALAGMITE)){ tile.Toggle(null,TileType.FLOOR); } else{ tile.Toggle(null,TileType.BREACHED_WALL); tile.toggles_into = prev_type; area.Add(tile); } foreach(Tile neighbor in tile.TilesWithinDistance(1)){ neighbor.solid_rock = false; } } } if(area.Count > 0){ Q.Add(new Event(t,area,500,EventType.BREACH)); } }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.SHIELDING: { ItemUseResult orb_result = UseOrb(1,true,user,line,(t,LOE_tile,results)=>{ List<Tile> area = new List<Tile>(); List<pos> cells = new List<pos>(); List<colorchar> symbols = new List<colorchar>(); foreach(Tile tile in t.TilesWithinDistance(1)){ if(tile.passable && LOE_tile.HasLOE(tile)){ colorchar cch = tile.visual; if(tile.actor() != null){ if(!tile.actor().HasAttr(AttrType.SHIELDED)){ tile.actor().attrs[AttrType.SHIELDED] = 1; B.Add(tile.actor().YouAre() + " shielded. ",tile.actor()); } if(player.CanSee(tile.actor())){ cch = tile.actor().visual; } } cch.bgcolor = Color.Blue; if(Global.LINUX && !Screen.GLMode){ cch.bgcolor = Color.DarkBlue; } if(cch.color == cch.bgcolor){ cch.color = Color.Black; } if(cch.c == '.'){ cch.c = '+'; } symbols.Add(cch); cells.Add(tile.p); area.Add(tile); } } Screen.AnimateMapCells(cells,symbols,150); foreach(Tile tile in area){ if(player.CanSee(tile)){ B.Add("A zone of protection is created. "); break; } } Q.Add(new Event(area,100,EventType.SHIELDING,R.Roll(2,6)+6)); }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.TELEPORTAL: { ItemUseResult orb_result = UseOrb(0,false,user,line,(t,LOE_tile,results)=>{ LOE_tile.AddFeature(FeatureType.TELEPORTAL); if(LOE_tile.Is(FeatureType.TELEPORTAL)){ Q.Add(new Event(LOE_tile,0,EventType.TELEPORTAL,100)); } }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.PAIN: { ItemUseResult orb_result = UseOrb(5,false,user,line,(t,LOE_tile,results)=>{ List<pos> cells = new List<pos>(); List<colorchar> symbols = new List<colorchar>(); foreach(Tile tile in t.TilesWithinDistance(5)){ if(LOE_tile.HasLOE(tile)){ Actor a = tile.actor(); if(a != null){ if(a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(2,6),user,"an orb of pain")){ a.ApplyStatus(AttrType.VULNERABLE,(R.Roll(2,6)+6)*100); if(a == player){ Help.TutorialTip(TutorialTopic.Vulnerable); } } } symbols.Add(new colorchar('*',Color.RandomDoom)); /*if(tile.DistanceFrom(t) % 2 == 0){ symbols.Add(new colorchar('*',Color.DarkMagenta)); } else{ symbols.Add(new colorchar('*',Color.DarkRed)); }*/ cells.Add(tile.p); } } player.AnimateVisibleMapCells(cells,symbols,80); }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.CONFUSION: { ItemUseResult orb_result = UseOrb(2,false,user,line,(t,LOE_tile,results)=>{ List<Tile> area = new List<Tile>(); List<pos> cells = new List<pos>(); colorchar cch = new colorchar('*',Color.RandomConfusion); for(int i=0;i<=2;++i){ foreach(Tile tile in t.TilesAtDistance(i)){ if(tile.passable && LOE_tile.HasLOE(tile)){ tile.AddFeature(FeatureType.CONFUSION_GAS); area.Add(tile); cells.Add(tile.p); if(tile.seen){ M.last_seen[tile.row,tile.col] = cch; } } } Screen.AnimateMapCells(cells,cch,40); } Q.RemoveTilesFromEventAreas(area,EventType.REMOVE_GAS); Event.RemoveGas(area,R.Between(7,9)*100,FeatureType.CONFUSION_GAS,20); }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.BLADES: { ItemUseResult orb_result = UseOrb(1,false,user,line,(t,LOE_tile,results)=>{ List<Tile> targets = new List<Tile>(); foreach(Tile t2 in t.TilesWithinDistance(1)){ if(t2.passable && t2.actor() == null && LOE_tile.HasLOE(t2)){ targets.Add(t2); } } targets.Randomize(); foreach(Tile t2 in targets){ Actor a = Actor.Create(ActorType.BLADE,t2.row,t2.col); if(a != null){ a.speed = 50; } } }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.DUST_STORM: { ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{ List<Tile> area = new List<Tile>(); List<pos> cells = new List<pos>(); foreach(Tile neighbor in LOE_tile.TilesWithinDistance(1)){ if(neighbor.passable){ area.Add(neighbor); } } List<Tile> added = new List<Tile>(); foreach(Tile n1 in area){ foreach(int dir in U.FourDirections){ if(R.CoinFlip() && n1.TileInDirection(dir).passable){ added.Add(n1.TileInDirection(dir)); } } } foreach(Tile n1 in added){ area.AddUnique(n1); } colorchar cch = new colorchar('*',Color.TerrainDarkGray); foreach(Tile t2 in area){ t2.AddFeature(FeatureType.THICK_DUST); cells.Add(t2.p); if(t2.seen){ M.last_seen[t2.row,t2.col] = cch; } Actor a = t2.actor(); if(a != null && t2.Is(FeatureType.THICK_DUST)){ if(!a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE,AttrType.BLINDSIGHT)){ if(a == player){ B.Add("Thick dust fills the air! "); } a.ApplyStatus(AttrType.BLIND,R.Between(1,3)*100); } } } Screen.AnimateMapCells(cells,cch,80); Q.RemoveTilesFromEventAreas(area,EventType.REMOVE_GAS); Event.RemoveGas(area,R.Between(20,25)*100,FeatureType.THICK_DUST,8); }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.FLESH_TO_FIRE: { ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{ Actor a = targeting.targeted.actor(); if(a != null){ B.Add("Jets of flame erupt from " + a.TheName(true) + ". ",a,targeting.targeted); Screen.AnimateMapCell(a.row,a.col,new colorchar('&',Color.RandomFire)); int dmg = (a.curhp+1)/2; if(a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,dmg,user,"a wand of flesh to fire")){ a.ApplyBurning(); } } else{ if(targeting.targeted.Is(FeatureType.TROLL_CORPSE)){ B.Add("Jets of flame erupt from the troll corpse. ",a,targeting.targeted); targeting.targeted.ApplyEffect(DamageType.FIRE); if(targeting.targeted.Is(FeatureType.TROLL_CORPSE)){ //if it's still there because of thick gas, it still gets destroyed. targeting.targeted.RemoveFeature(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ",targeting.targeted); } } else{ if(targeting.targeted.Is(FeatureType.TROLL_BLOODWITCH_CORPSE)){ B.Add("Jets of flame erupt from the troll bloodwitch corpse. ",a,targeting.targeted); targeting.targeted.ApplyEffect(DamageType.FIRE); if(targeting.targeted.Is(FeatureType.TROLL_BLOODWITCH_CORPSE)){ //if it's still there because of thick gas, it still gets destroyed. targeting.targeted.RemoveFeature(FeatureType.TROLL_BLOODWITCH_CORPSE); B.Add("The troll bloodwitch corpse burns to ashes! ",targeting.targeted); } } else{ B.Add("Nothing happens. ",user); results.IDed = false; } } } }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.INVISIBILITY: { ItemUseResult wand_result = UseWand(false,false,user,line,(LOE_tile,targeting,results)=>{ Actor a = targeting.targeted.actor(); if(a != null){ B.Add(a.You("vanish",true) + " from view. ",a); if(a.light_radius > 0 && !M.wiz_dark && !M.wiz_lite){ B.Add(a.Your() + " light still reveals " + a.Your() + " location. ",a); } a.RefreshDuration(AttrType.INVISIBLE,(R.Between(2,20)+30)*100,a.YouAre() + " no longer invisible. ",a); } else{ B.Add("Nothing happens. ",user); results.IDed = false; } }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.REACH: { ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{ Actor a = targeting.targeted.actor(); if(a != null && a != user){ user.Attack(0,a,true); } else{ B.Add("Nothing happens. ",user); results.IDed = false; } }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.SLUMBER: { ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{ Actor a = targeting.targeted.actor(); if(a != null){ if(a.HasAttr(AttrType.MENTAL_IMMUNITY)){ if(a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE)){ B.Add(a.You("resist") + " becoming dormant. ",a); } else{ B.Add(a.You("resist") + " falling asleep. ",a); } } else{ if(a.ResistedBySpirit()){ if(player.HasLOS(a)){ if(a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE)){ B.Add(a.You("resist") + " becoming dormant. ",a); } else{ B.Add(a.You("almost fall") + " asleep. ",a); } } } else{ if(player.HasLOS(a)){ if(a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE)){ B.Add(a.You("become") + " dormant. ",a); } else{ B.Add(a.You("fall") + " asleep. ",a); } } a.attrs[AttrType.ASLEEP] = 6 + R.Roll(4,6); } } } else{ B.Add("Nothing happens. ",user); results.IDed = false; } }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.TELEKINESIS: { ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{ if(!SharedEffect.Telekinesis(false,user,targeting.targeted)){ results.used = false; } }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.WEBS: { ItemUseResult wand_result = UseWand(true,true,user,line,(LOE_tile,targeting,results)=>{ if(targeting.targeted == user.tile()){ B.Add("Nothing happens. ",user); results.IDed = false; } else{ Screen.CursorVisible = false; foreach(Tile t in targeting.line_to_targeted){ if(t.passable && t != user.tile()){ t.AddFeature(FeatureType.WEB); if(t.seen){ Screen.WriteMapChar(t.row,t.col,';',Color.White); Game.GLUpdate(); Thread.Sleep(15); } } } M.Draw(); } }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.BLAST_FUNGUS: { if(line == null){ line = user.GetTargetTile(12,0,false,true); } if(line != null){ revealed_by_light = true; ignored = true; Tile t = line.LastBeforeSolidTile(); Actor first = user.FirstActorInLine(line); B.Add(user.You("fling") + " " + TheName() + ". "); if(first != null && first != user){ t = first.tile(); B.Add("It hits " + first.the_name + ". ",first); } line = line.ToFirstSolidTileOrActor(); if(line.Count > 0){ line.RemoveAt(line.Count - 1); } int idx = 0; foreach(Tile tile2 in line){ if(tile2.seen){ ++idx; } else{ line = line.To(tile2); if(line.Count > 0){ line.RemoveAt(line.Count - 1); } break; } } if(line.Count > 0){ user.AnimateProjectile(line,symbol,color); } t.GetItem(this); //inv.Remove(i); t.MakeNoise(2); if(first != null && first != user){ first.player_visibility_duration = -1; first.attrs[AttrType.PLAYER_NOTICED]++; } else{ if(t.IsTrap()){ t.TriggerTrap(); } } } else{ used = false; } break; } case ConsumableType.BANDAGES: if(!user.HasAttr(AttrType.BANDAGED)){ user.attrs[AttrType.BANDAGED] = 20; //user.recover_time = Q.turn + 100; B.Add(user.You("apply",false,true) + " a bandage. ",user); } else{ B.Add(user.the_name + " can't apply another bandage yet. ",user); used = false; } break; case ConsumableType.FLINT_AND_STEEL: { int dir = -1; if(user == player){ dir = user.GetDirection("Which direction? ",false,true); } else{ dir = user.DirectionOf(player); } if(dir != -1){ Tile t = user.TileInDirection(dir); B.Add(user.You("use") + " your flint & steel. ",user); if(t.actor() != null && t.actor().HasAttr(AttrType.OIL_COVERED) && !t.Is(FeatureType.POISON_GAS,FeatureType.THICK_DUST)){ t.actor().ApplyBurning(); } if(!t.Is(TileType.WAX_WALL)){ t.ApplyEffect(DamageType.FIRE); } } else{ used = false; } break; } default: used = false; break; } if(used){ if(IDed){ bool seen = true; //i'll try letting orbs always be IDed. keep an eye on this. /*bool seen = (user == player); if(user != player){ if(player.CanSee(line[0])){ //fix this line - or at least check for null/empty seen = true; } if(user != null && player.CanSee(user)){ //heck, I could even check to see whose turn it is, if I really wanted to be hacky. seen = true; } }*/ if(!identified[type] && seen){ identified[type] = true; B.Add("(It was " + SingularName(true) + "!) "); } } else{ if(!unIDed_name[type].Contains("{tried}")){ unIDed_name[type] = unIDed_name[type] + " {tried}"; } } if(quantity > 1){ --quantity; } else{ if(type == ConsumableType.BANDAGES){ --other_data; if(user != null && other_data == 0){ B.Add(user.You("use") + " your last bandage. ",user); user.inv.Remove(this); } } else{ if(type == ConsumableType.FLINT_AND_STEEL){ if(R.OneIn(3)){ --other_data; if(user != null){ if(other_data == 2){ B.Add("Your flint & steel shows signs of wear. ",user); } if(other_data == 1){ B.Add("Your flint & steel is almost depleted. ",user); } if(other_data == 0){ B.Add("Your flint & steel is used up. ",user); user.inv.Remove(this); } } } } else{ if(NameOfItemType() == "wand"){ if(charges > 0){ --charges; if(other_data >= 0){ ++other_data; } } else{ other_data = -1; } } else{ if(user != null){ user.inv.Remove(this); } } } } } CheckForMimic(); } return used; }
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 bool Attack(int attack_idx,Actor a,bool attack_is_part_of_another_action) { //returns true if attack hit AttackInfo info = attack[type][attack_idx]; pos original_pos = p; pos target_original_pos = a.p; if(EquippedWeapon.type != WeaponType.NO_WEAPON){ info = EquippedWeapon.Attack(); } info.damage.source = this; if(a.HasFeat(FeatType.DEFLECT_ATTACK) && DistanceFrom(a) == 1){ //Actor other = a.ActorsWithinDistance(1).Where(x=>x.DistanceFrom(this) == 1).Random(); Actor other = a.ActorsWithinDistance(1).Where(x=>x != this).RandomOrDefault(); if(other != a){ B.Add(a.You("deflect") + "! ",this,a); return Attack(attack_idx,other,attack_is_part_of_another_action); } } if(!attack_is_part_of_another_action && StunnedThisTurn()){ return false; } if(!attack_is_part_of_another_action && exhaustion == 100 && R.CoinFlip()){ B.Add(You("fumble") + " from exhaustion. ",this); Q1(); //this is checked in PlayerWalk if attack_is_part_of_another_action is true return false; } if(!attack_is_part_of_another_action && this == player && EquippedWeapon.status[EquipmentStatus.POSSESSED] && R.CoinFlip()){ List<Actor> actors = ActorsWithinDistance(1); Actor chosen = actors.RandomOrDefault(); if(chosen != a){ if(chosen == this){ B.Add("Your possessed " + EquippedWeapon.NameWithEnchantment() + " tries to attack you! "); B.Add("You fight it off! "); //this is also checked in PlayerWalk if attack_is_part_of_another_action is true Q1(); return false; } else{ return Attack(attack_idx,chosen); } } } bool player_in_combat = false; if(this == player || a == player){ player_in_combat = true; } /*if(a == player && (type == ActorType.DREAM_WARRIOR_CLONE || type == ActorType.DREAM_SPRITE_CLONE)){ player_in_combat = false; }*/ if(player_in_combat){ player.attrs[AttrType.IN_COMBAT]++; } if(a.HasAttr(AttrType.CAN_DODGE) && a.CanSee(this)){ int dodge_dir = R.Roll(9); Tile dodge_tile = a.TileInDirection(dodge_dir); bool failed_to_dodge = false; if(HasAttr(AttrType.CONFUSED,AttrType.SLOWED,AttrType.STUNNED) && R.CoinFlip()){ failed_to_dodge = true; } if(a.tile().Is(FeatureType.WEB) && !a.HasAttr(AttrType.BURNING,AttrType.OIL_COVERED,AttrType.SLIMED,AttrType.BRUTISH_STRENGTH)){ failed_to_dodge = true; } if(!failed_to_dodge && dodge_tile.passable && dodge_tile.actor() == null && !a.MovementPrevented(dodge_tile) && !a.HasAttr(AttrType.PARALYZED)){ B.Add(a.You("dodge") + " " + YourVisible() + " attack. ",this,a); if(player.CanSee(a)){ Help.TutorialTip(TutorialTopic.Dodging); } if(a == player){ B.DisplayNow(); Screen.AnimateMapCell(a.row,a.col,new colorchar('!',Color.Green),80); } a.Move(dodge_tile.row,dodge_tile.col); if(a != player && DistanceFrom(dodge_tile) > 1){ M.Draw(); Thread.Sleep(40); } if(!attack_is_part_of_another_action){ Q.Add(new Event(this,info.cost)); } return false; } } if(a.HasFeat(FeatType.CUNNING_DODGE) && !this.HasAttr(AttrType.DODGED)){ attrs[AttrType.DODGED]++; B.Add(a.You("dodge") + " " + YourVisible() + " attack. ",this,a); if(!attack_is_part_of_another_action){ Q.Add(new Event(this,info.cost)); } return false; } if(IsInvisibleHere() || a.IsInvisibleHere()){ Help.TutorialTip(TutorialTopic.FightingTheUnseen); } //pos pos_of_target = new pos(a.row,a.col); bool a_moved_last_turn = !a.HasAttr(AttrType.TURNS_HERE); bool drive_back_applied = HasFeat(FeatType.DRIVE_BACK); if(drive_back_applied && !ConfirmsSafetyPrompts(a.tile())){ drive_back_applied = false; } bool drive_back_nowhere_to_run = false; if(!attack_is_part_of_another_action && drive_back_applied){ //doesn't work while moving drive_back_nowhere_to_run = true; int dir = DirectionOf(a); foreach(int next_dir in new List<int>{dir,dir.RotateDir(true),dir.RotateDir(false)}){ Tile t = a.TileInDirection(next_dir); if(t.passable && t.actor() == null && !a.MovementPrevented(t)){ drive_back_nowhere_to_run = false; break; } } /*if(a.TileInDirection(dir).passable && a.ActorInDirection(dir) == null && !a.GrabPreventsMovement(TileInDirection(dir))){ drive_back_nowhere_to_run = false; } if(a.TileInDirection(dir.RotateDir(true)).passable && a.ActorInDirection(dir.RotateDir(true)) == null && !a.GrabPreventsMovement(TileInDirection(dir.RotateDir(true)))){ drive_back_nowhere_to_run = false; } if(a.TileInDirection(dir.RotateDir(false)).passable && a.ActorInDirection(dir.RotateDir(false)) == null && !a.GrabPreventsMovement(TileInDirection(dir.RotateDir(false)))){ drive_back_nowhere_to_run = false; }*/ if(a.tile().IsSlippery() && !(a.tile().Is(TileType.ICE) && a.type == ActorType.FROSTLING)){ if(R.OneIn(5) && !HasAttr(AttrType.FLYING,AttrType.NONEUCLIDEAN_MOVEMENT) && !Is(ActorType.GIANT_SLUG,ActorType.MACHINE_OF_WAR,ActorType.MUD_ELEMENTAL)){ drive_back_nowhere_to_run = true; } } if(a.HasAttr(AttrType.FROZEN) || a.HasAttr(AttrType.IMMOBILE)){ drive_back_nowhere_to_run = true; //todo: exception for noneuclidean monsters? i think they'll just move out of the way. } } bool obscured_vision_miss = false; { bool fog = false; bool hidden = false; if((this.tile().Is(FeatureType.FOG,FeatureType.THICK_DUST) || a.tile().Is(FeatureType.FOG,FeatureType.THICK_DUST))){ fog = true; } if(a.IsHiddenFrom(this) || !CanSee(a) || (a.IsInvisibleHere() && !HasAttr(AttrType.BLINDSIGHT))){ hidden = true; } if(!HasAttr(AttrType.DETECTING_MONSTERS) && (fog || hidden) && R.CoinFlip()){ obscured_vision_miss = true; } } int plus_to_hit = TotalSkill(SkillType.COMBAT); bool sneak_attack = false; if(this.IsHiddenFrom(a) || !a.CanSee(this) || (this == player && IsInvisibleHere() && !a.HasAttr(AttrType.BLINDSIGHT))){ sneak_attack = true; a.attrs[AttrType.SEES_ADJACENT_PLAYER] = 1; if(DistanceFrom(a) > 2 && this != player){ sneak_attack = false; //no phantom blade sneak attacks from outside your view - but the player can sneak attack at this range with a wand of reach. } } //...insert any other changes to sneak attack calculation here... if(sneak_attack || HasAttr(AttrType.LUNGING_AUTO_HIT) || (EquippedWeapon == Dagger && !tile().IsLit()) || (EquippedWeapon == Staff && a_moved_last_turn) || a.HasAttr(AttrType.SWITCHING_ARMOR)){ //some attacks get +25% accuracy. this usually totals 100% vs. unarmored targets. plus_to_hit += 25; } plus_to_hit -= a.TotalSkill(SkillType.DEFENSE) * 3; bool attack_roll_hit = a.IsHit(plus_to_hit); bool blocked_by_armor_miss = false; bool blocked_by_root_shell_miss = false; bool mace_through_armor = false; if(!attack_roll_hit){ int armor_value = a.TotalProtectionFromArmor(); if(a != player){ armor_value = a.TotalSkill(SkillType.DEFENSE); //if monsters have Defense skill, it's from armor } int roll = R.Roll(25 - plus_to_hit); if(roll <= armor_value * 3){ bool mace = (EquippedWeapon == Mace || type == ActorType.CRUSADING_KNIGHT || type == ActorType.PHANTOM_CRUSADER); if(mace){ attack_roll_hit = true; mace_through_armor = true; } else{ if(type == ActorType.CORROSIVE_OOZE || type == ActorType.LASHER_FUNGUS){ //this is a bit hacky, but these are the only ones that aren't stopped by armor right now. attack_roll_hit = true; } else{ blocked_by_armor_miss = true; } } } else{ if(a.HasAttr(AttrType.ROOTS) && roll <= (armor_value + 10) * 3){ //potion of roots gives 10 defense blocked_by_root_shell_miss = true; } } } bool hit = true; if(obscured_vision_miss){ //this calculation turned out to be pretty complicated hit = false; } else{ if(blocked_by_armor_miss || blocked_by_root_shell_miss){ hit = false; } else{ if(drive_back_nowhere_to_run || attack_roll_hit){ hit = true; } else{ hit = false; } } } if(a.HasAttr(AttrType.GRABBED) && attrs[AttrType.GRABBING] == DirectionOf(a)){ hit = true; //one more modifier: automatically hit things you're grabbing. } bool weapon_just_poisoned = false; if(!hit){ if(blocked_by_armor_miss){ bool initial_message_printed = false; //for better pronoun usage if(info.blocked != ""){ initial_message_printed = true; string s = info.blocked + ". "; int pos = -1; do{ pos = s.IndexOf('&'); if(pos != -1){ s = s.Substring(0,pos) + TheName(true) + s.Substring(pos+1); } } while(pos != -1); // do{ pos = s.IndexOf('*'); if(pos != -1){ s = s.Substring(0,pos) + a.TheName(true) + s.Substring(pos+1); } } while(pos != -1); B.Add(s,this,a); } if(a.HasFeat(FeatType.ARMOR_MASTERY) && !(a.type == ActorType.ALASI_SCOUT && attack_idx == 1)){ B.Add(a.YourVisible() + " armor blocks the attack, leaving " + TheName(true) + " off-balance. ",a,this); RefreshDuration(AttrType.SUSCEPTIBLE_TO_CRITS,100); } else{ if(initial_message_printed){ B.Add(a.YourVisible() + " armor blocks the attack. ",this,a); } else{ B.Add(a.YourVisible() + " armor blocks " + YourVisible() + " attack. ",this,a); } } if(a.EquippedArmor.type == ArmorType.FULL_PLATE && !HasAttr(AttrType.BRUTISH_STRENGTH)){ a.IncreaseExhaustion(3); Help.TutorialTip(TutorialTopic.HeavyPlateArmor); } } else{ if(blocked_by_root_shell_miss){ B.Add(a.YourVisible() + " root shell blocks " + YourVisible() + " attack. ",this,a); } else{ if(obscured_vision_miss){ B.Add(Your() + " attack goes wide. ",this); } else{ if(!attack_is_part_of_another_action && drive_back_applied && !MovementPrevented(M.tile[target_original_pos])){ B.Add(You("drive") + " " + a.TheName(true) + " back. ",this,a); /*if(!a.HasAttr(AttrType.FROZEN) && !HasAttr(AttrType.FROZEN)){ a.AI_Step(this,true); AI_Step(a); }*/ Tile dest = null; int dir = DirectionOf(target_original_pos); foreach(int next_dir in new List<int>{dir,dir.RotateDir(true),dir.RotateDir(false)}){ Tile t = a.TileInDirection(next_dir); if(t.passable && t.actor() == null && !a.MovementPrevented(t)){ dest = t; break; } } if(dest != null){ a.AI_MoveOrOpen(dest.row,dest.col); if(M.actor[target_original_pos] == null){ AI_MoveOrOpen(target_original_pos.row,target_original_pos.col); } } } else{ if(info.miss != ""){ string s = info.miss + ". "; int pos = -1; do{ pos = s.IndexOf('&'); if(pos != -1){ s = s.Substring(0,pos) + TheName(true) + s.Substring(pos+1); } } while(pos != -1); // do{ pos = s.IndexOf('*'); if(pos != -1){ s = s.Substring(0,pos) + a.TheName(true) + s.Substring(pos+1); } } while(pos != -1); B.Add(s,this,a); } else{ B.Add(YouVisible("miss",true) + " " + a.TheName(true) + ". ",this,a); } } } } } if(type == ActorType.SWORDSMAN || type == ActorType.PHANTOM_SWORDMASTER || type == ActorType.ALASI_SOLDIER){ attrs[AttrType.COMBO_ATTACK] = 0; } } else{ string s = info.hit + ". "; if(!attack_is_part_of_another_action && HasFeat(FeatType.NECK_SNAP) && a.HasAttr(AttrType.MEDIUM_HUMANOID) && (IsHiddenFrom(a) || a.IsHelpless())){ if(!a.HasAttr(AttrType.RESIST_NECK_SNAP)){ B.Add(You("silently snap") + " " + a.Your() + " neck. "); a.Kill(); Q1(); return true; } else{ B.Add(You("silently snap") + " " + a.Your() + " neck. "); B.Add("It doesn't seem to affect " + a.the_name + ". "); } } bool crit = false; int crit_chance = 8; //base crit rate is 1/8 if(EquippedWeapon.type == WeaponType.DAGGER && !tile().IsLit()){ crit_chance /= 2; } if(a.EquippedArmor != null && (a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] || a.EquippedArmor.status[EquipmentStatus.DAMAGED] || a.HasAttr(AttrType.SWITCHING_ARMOR))){ crit_chance /= 2; } if(a.HasAttr(AttrType.SUSCEPTIBLE_TO_CRITS)){ //caused by armor mastery crit_chance /= 2; } if(EquippedWeapon.enchantment == EnchantmentType.PRECISION && !EquippedWeapon.status[EquipmentStatus.NEGATED]){ crit_chance /= 2; } if(drive_back_nowhere_to_run){ crit_chance /= 2; } if(crit_chance <= 1 || R.OneIn(crit_chance)){ crit = true; } int pos = -1; do{ pos = s.IndexOf('&'); if(pos != -1){ s = s.Substring(0,pos) + TheName(true) + s.Substring(pos+1); } } while(pos != -1); // do{ pos = s.IndexOf('*'); if(pos != -1){ s = s.Substring(0,pos) + a.TheName(true) + s.Substring(pos+1); } } while(pos != -1); int dice = info.damage.dice; if(sneak_attack && crit && this == player){ if(!a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE,AttrType.BOSS_MONSTER) && a.type != ActorType.CYCLOPEAN_TITAN){ switch(EquippedWeapon.type){ //todo: should this check for shielded/blocking? case WeaponType.SWORD: B.Add("You run " + a.TheName(true) + " through! "); break; case WeaponType.MACE: B.Add("You bash " + a.YourVisible() + " head in! "); break; case WeaponType.DAGGER: B.Add("You pierce one of " + a.YourVisible() + " vital organs! "); break; case WeaponType.STAFF: B.Add("You bring your staff down on " + a.YourVisible() + " head with a loud crack! "); break; case WeaponType.BOW: B.Add("You choke " + a.TheName(true) + " with your bowstring! "); break; default: break; } Help.TutorialTip(TutorialTopic.InstantKills); MakeNoise(6); if(a.type == ActorType.BERSERKER && a.target == this){ a.attrs[AttrType.SHIELDED] = 0; a.TakeDamage(DamageType.NORMAL,DamageClass.NO_TYPE,a.curhp,this); } else{ a.Kill(); } if(!attack_is_part_of_another_action){ Q1(); } return true; } } if(sneak_attack && (this == player || a == player)){ B.Add(YouVisible("strike") + " from hiding! "); if(type != ActorType.PLAYER){ if(a == player && attrs[AttrType.TURNS_VISIBLE] >= 0){ B.PrintAll(); } attrs[AttrType.TURNS_VISIBLE] = -1; attrs[AttrType.NOTICED] = 1; attrs[AttrType.DANGER_SENSED] = 1; } else{ a.player_visibility_duration = -1; a.attrs[AttrType.PLAYER_NOTICED] = 1; } } if(a == player){ if(a.HasAttr(AttrType.SWITCHING_ARMOR)){ B.Add("You're unguarded! "); } else{ if(a.EquippedArmor.status[EquipmentStatus.DAMAGED]){ B.Add("Your damaged armor leaves you open! "); } else{ if(crit && R.CoinFlip()){ if(a.EquippedArmor.status[EquipmentStatus.WEAK_POINT]){ B.Add(TheName(true) + " finds a weak point. "); } } } } } if(mace_through_armor){ if(type == ActorType.CRUSADING_KNIGHT || type == ActorType.PHANTOM_CRUSADER){ B.Add(YourVisible() + " huge mace punches through " + a.YourVisible() + " armor. ",this,a); } else{ B.Add(YourVisible() + " mace punches through " + a.YourVisible() + " armor. ",this,a); } } else{ B.Add(s,this,a); } if(crit && info.crit != AttackEffect.NO_CRIT){ if(this == player || a == player){ Help.TutorialTip(TutorialTopic.CriticalHits); } } if(a == player && !player.CanSee(this)){ Screen.AnimateMapCell(row,col,new colorchar('?',Color.DarkGray),50); } if(a.type == ActorType.GHOST && EquippedWeapon.enchantment != EnchantmentType.NO_ENCHANTMENT && !EquippedWeapon.status[EquipmentStatus.NEGATED]){ EquippedWeapon.status[EquipmentStatus.NEGATED] = true; B.Add(Your() + " " + EquippedWeapon.NameWithEnchantment() + "'s magic is suppressed! ",this); Help.TutorialTip(TutorialTopic.Negated); } if(!Help.displayed[TutorialTopic.SwitchingEquipment] && this == player && a.Is(ActorType.SPORE_POD,ActorType.SKELETON,ActorType.STONE_GOLEM,ActorType.MECHANICAL_KNIGHT,ActorType.MACHINE_OF_WAR) && EquippedWeapon.type == WeaponType.SWORD){ Help.TutorialTip(TutorialTopic.SwitchingEquipment); } int dmg = R.Roll(dice,6); bool no_max_damage_message = false; List<AttackEffect> effects = new List<AttackEffect>(); if(crit && info.crit != AttackEffect.NO_CRIT){ effects.AddUnique(info.crit); } if(info.effects != null){ foreach(AttackEffect effect in info.effects){ effects.AddUnique(effect); } } if(type == ActorType.DEMON_LORD && DistanceFrom(a) == 2){ effects.AddUnique(AttackEffect.PULL); } if(type == ActorType.SWORDSMAN && attrs[AttrType.COMBO_ATTACK] == 2){ effects.AddUnique(AttackEffect.BLEED); effects.AddUnique(AttackEffect.STRONG_KNOCKBACK); } if(type == ActorType.PHANTOM_SWORDMASTER && attrs[AttrType.COMBO_ATTACK] == 2){ effects.AddUnique(AttackEffect.PERCENT_DAMAGE); effects.AddUnique(AttackEffect.STRONG_KNOCKBACK); } if(type == ActorType.ALASI_SOLDIER){ if(attrs[AttrType.COMBO_ATTACK] == 1){ effects.AddUnique(AttackEffect.ONE_TURN_STUN); } else{ if(attrs[AttrType.COMBO_ATTACK] == 2){ effects.AddUnique(AttackEffect.ONE_TURN_PARALYZE); } } } if(type == ActorType.WILD_BOAR && HasAttr(AttrType.COOLDOWN_1)){ effects.AddUnique(AttackEffect.FLING); } if(type == ActorType.ALASI_SENTINEL && R.OneIn(3)){ effects.AddUnique(AttackEffect.FLING); } if(this == player && a.type == ActorType.CYCLOPEAN_TITAN && crit){ effects = new List<AttackEffect>(); //remove all other effects (so far) and check for edged weapons if(EquippedWeapon == Sword || EquippedWeapon == Dagger){ effects.Add(AttackEffect.PERMANENT_BLIND); } } if(EquippedWeapon.status[EquipmentStatus.POISONED]){ effects.AddUnique(AttackEffect.POISON); } if(HasAttr(AttrType.PSEUDO_VAMPIRIC)){ effects.AddUnique(AttackEffect.DRAIN_LIFE); } if(HasAttr(AttrType.BRUTISH_STRENGTH)){ effects.AddUnique(AttackEffect.MAX_DAMAGE); effects.AddUnique(AttackEffect.STRONG_KNOCKBACK); effects.Remove(AttackEffect.KNOCKBACK); //strong knockback replaces these effects.Remove(AttackEffect.TRIP); effects.Remove(AttackEffect.FLING); } if(EquippedWeapon != null && !EquippedWeapon.status[EquipmentStatus.NEGATED]){ switch(EquippedWeapon.enchantment){ case EnchantmentType.CHILLING: effects.AddUnique(AttackEffect.CHILL); break; case EnchantmentType.DISRUPTION: effects.AddUnique(AttackEffect.DISRUPTION); //not entirely sure that these should be crit effects break; case EnchantmentType.VICTORY: if(a.maxhp > 1){ // no illusions, phantoms, or minions effects.AddUnique(AttackEffect.VICTORY); } break; } } if(type == ActorType.SKITTERMOSS && HasAttr(AttrType.COOLDOWN_1)){ effects.Remove(AttackEffect.INFEST); } if(a.HasAttr(AttrType.NONLIVING)){ effects.Remove(AttackEffect.DRAIN_LIFE); } foreach(AttackEffect effect in effects){ //pre-damage effects - these can alter the amount of damage. switch(effect){ case AttackEffect.MAX_DAMAGE: dmg = Math.Max(dmg,dice * 6); break; case AttackEffect.PERCENT_DAMAGE: dmg = Math.Max(dmg,(a.maxhp+1)/2); no_max_damage_message = true; if(!EquippedWeapon.status[EquipmentStatus.DULLED]){ if(this == player){ B.Add("Your sword cuts deep! "); } else{ B.Add(Your() + " attack cuts deep! ",this); } } break; case AttackEffect.ONE_HP: dmg = a.curhp - 1; if(a.HasAttr(AttrType.VULNERABLE)){ a.attrs[AttrType.VULNERABLE] = 0; } if(a == player){ B.Add("You shudder. "); } no_max_damage_message = true; break; } } if(dice < 2){ no_max_damage_message = true; } if(a.type == ActorType.SPORE_POD && EquippedWeapon.IsBlunt()){ no_max_damage_message = true; } if(EquippedWeapon.status[EquipmentStatus.MERCIFUL]){ no_max_damage_message = true; } if(a.HasAttr(AttrType.RESIST_WEAPONS) && EquippedWeapon.type != WeaponType.NO_WEAPON){ B.Add("Your " + EquippedWeapon.NameWithoutEnchantment() + " isn't very effective. "); dmg = dice; //minimum damage } else{ if(EquippedWeapon.status[EquipmentStatus.DULLED]){ B.Add("Your dull " + EquippedWeapon.NameWithoutEnchantment() + " isn't very effective. "); dmg = dice; //minimum damage } else{ if(type == ActorType.MUD_TENTACLE){ //getting surrounded by these guys should be dangerous, but not simply because of their damage. dmg = dice; } } } if(dmg >= dice * 6 && !no_max_damage_message){ if(this == player){ B.Add("It was a good hit! "); } else{ if(a == player){ B.Add("Ow! "); } } } dmg += TotalSkill(SkillType.COMBAT); if(a.type == ActorType.SPORE_POD && EquippedWeapon.IsBlunt()){ dmg = 0; dice = 0; effects.AddUnique(AttackEffect.STRONG_KNOCKBACK); B.Add("Your " + EquippedWeapon.NameWithoutEnchantment() + " knocks the spore pod away. ",a); } if(EquippedWeapon.status[EquipmentStatus.MERCIFUL] && dmg >= a.curhp){ dmg = a.curhp - 1; B.Add("Your " + EquippedWeapon.NameWithoutEnchantment() + " refuses to finish " + a.TheName(true) + ". "); B.Print(true); } if(a.HasAttr(AttrType.DULLS_BLADES) && R.CoinFlip() && (EquippedWeapon == Sword || EquippedWeapon == Dagger)){ EquippedWeapon.status[EquipmentStatus.DULLED] = true; B.Add(Your() + " " + EquippedWeapon.NameWithEnchantment() + " becomes dull! ",this); Help.TutorialTip(TutorialTopic.Dulled); } if(a.type == ActorType.CORROSIVE_OOZE && R.CoinFlip() && (EquippedWeapon == Sword || EquippedWeapon == Dagger || EquippedWeapon == Mace)){ EquippedWeapon.status[EquipmentStatus.DULLED] = true; B.Add("The acid dulls " + Your() + " " + EquippedWeapon.NameWithEnchantment() + "! ",this); Help.TutorialTip(TutorialTopic.Acidified); Help.TutorialTip(TutorialTopic.Dulled); } if(a.HasAttr(AttrType.CAN_POISON_WEAPONS) && R.CoinFlip() && EquippedWeapon.type != WeaponType.NO_WEAPON && !EquippedWeapon.status[EquipmentStatus.POISONED]){ EquippedWeapon.status[EquipmentStatus.POISONED] = true; weapon_just_poisoned = true; B.Add(Your() + " " + EquippedWeapon.NameWithEnchantment() + " is covered in poison! ",this); } int r = a.row; int c = a.col; bool still_alive = true; bool knockback_effect = effects.Contains(AttackEffect.KNOCKBACK) || effects.Contains(AttackEffect.STRONG_KNOCKBACK) || effects.Contains(AttackEffect.TRIP) || effects.Contains(AttackEffect.FLING) || effects.Contains(AttackEffect.SWAP_POSITIONS) || effects.Contains(AttackEffect.DRAIN_LIFE); if(knockback_effect){ a.attrs[AttrType.TURN_INTO_CORPSE]++; } Color blood = a.BloodColor(); bool homunculus = a.type == ActorType.HOMUNCULUS; if(dmg > 0){ Damage damage = new Damage(info.damage.type,info.damage.damclass,true,this,dmg); damage.weapon_used = EquippedWeapon.type; still_alive = a.TakeDamage(damage,a_name); } if(homunculus){ //todo: or will this happen on any major damage, not just melee attacks? M.tile[target_original_pos].AddFeature(FeatureType.OIL); } else{ if(blood != Color.Black && (!still_alive || !a.HasAttr(AttrType.FROZEN,AttrType.INVULNERABLE))){ /*List<Tile> valid = new List<Tile>{M.tile[target_original_pos]}; for(int i=-1;i<=1;++i){ valid.Add(M.tile[target_original_pos].TileInDirection(original_pos.DirectionOf(target_original_pos).RotateDir(true,i))); } for(int i=dmg/10;i>0;--i){ Tile t = valid.RemoveRandom(); if(t.Is(TileType.WALL) || t.name == "floor"){ t.color = blood; } }*/ List<Tile> cone = M.tile[target_original_pos].GetCone(original_pos.DirectionOf(target_original_pos),dmg>=20? 2 : 1,false); cone.Add(M.tile[target_original_pos].TileInDirection(original_pos.DirectionOf(target_original_pos))); cone.Add(M.tile[target_original_pos].TileInDirection(original_pos.DirectionOf(target_original_pos))); cone.Add(M.tile[target_original_pos].TileInDirection(original_pos.DirectionOf(target_original_pos))); for(int i=(dmg-5)/5;i>0;--i){ if(cone.Count == 0){ break; } Tile t = cone.Random(); while(cone.Remove(t)){ } //remove all if(t.Is(TileType.WALL) || t.name == "floor"){ t.color = blood; switch(blood){ case Color.DarkRed: M.aesthetics[t.p] = AestheticFeature.BloodDarkRed; break; default: M.aesthetics[t.p] = AestheticFeature.BloodOther; break; } } } } } if(still_alive){ //post-damage crit effects that require the target to still be alive foreach(AttackEffect effect in effects){ if(still_alive){ switch(effect){ //todo: some of these messages shouldn't be printed if the effect already exists case AttackEffect.CONFUSE: a.ApplyStatus(AttrType.CONFUSED,R.Between(2,3)*100); break; case AttackEffect.BLEED: if(!a.HasAttr(AttrType.NONLIVING,AttrType.FROZEN)){ if(a.HasAttr(AttrType.BLEEDING)){ if(a == player){ if(a.attrs[AttrType.BLEEDING] > 15){ B.Add("Your bleeding worsens. "); } else{ B.Add("You're bleeding badly now! "); } } else{ B.Add(a.YouAre() + " bleeding badly! ",a); } } a.attrs[AttrType.BLEEDING] += R.Between(10,15); if(a.attrs[AttrType.BLEEDING] > 25){ a.attrs[AttrType.BLEEDING] = 25; //this seems like a reasonable cap, so repeated bleed effects don't just last *forever*. } if(a == player){ Help.TutorialTip(TutorialTopic.Bleeding); } } break; case AttackEffect.BLIND: a.ApplyStatus(AttrType.BLIND,R.Between(5,7)*100); //B.Add(a.YouAre() + " blinded! ",a); //a.RefreshDuration(AttrType.BLIND,R.Between(5,7)*100); break; case AttackEffect.PERMANENT_BLIND: { if(!a.HasAttr(AttrType.COOLDOWN_1)){ B.Add("You drive your " + EquippedWeapon.NameWithoutEnchantment() + " into its eye, blinding it! "); Q.KillEvents(a,AttrType.BLIND); a.attrs[AttrType.BLIND] = 1; a.attrs[AttrType.COOLDOWN_1] = 1; } break; } case AttackEffect.DIM_VISION: if(a.ResistedBySpirit()){ B.Add(a.Your() + " vision is dimmed, but only for a moment. ",a); } else{ B.Add(a.Your() + " vision is dimmed. ",a); a.RefreshDuration(AttrType.DIM_VISION,(R.Roll(2,20)+20)*100); } break; case AttackEffect.CHILL: if(!a.HasAttr(AttrType.IMMUNE_COLD)){ B.Add(a.the_name + " is chilled. ",a); if(!a.HasAttr(AttrType.CHILLED)){ a.attrs[AttrType.CHILLED] = 1; } else{ a.attrs[AttrType.CHILLED] *= 2; } if(!a.TakeDamage(DamageType.COLD,DamageClass.MAGICAL,a.attrs[AttrType.CHILLED],this)){ still_alive = false; } } break; case AttackEffect.DISRUPTION: if(a.HasAttr(AttrType.NONLIVING)){ B.Add(a.the_name + " is disrupted. ",a); if(!a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,a.maxhp / 5,this)){ still_alive = false; } } break; case AttackEffect.FREEZE: a.tile().ApplyEffect(DamageType.COLD); a.ApplyFreezing(); break; case AttackEffect.GRAB: if(!HasAttr(AttrType.GRABBING) && DistanceFrom(a) == 1 && !a.HasAttr(AttrType.FROZEN)){ a.attrs[AttrType.GRABBED]++; attrs[AttrType.GRABBING] = DirectionOf(a); B.Add(YouVisible("grab") + " " + a.TheName(true) + ". ",this,a); if(a == player){ Help.TutorialTip(TutorialTopic.Grabbed); } } break; case AttackEffect.POISON: if(!a.HasAttr(AttrType.NONLIVING,AttrType.CAN_POISON_WEAPONS,AttrType.INVULNERABLE,AttrType.FROZEN)){ a.ApplyStatus(AttrType.POISONED,(R.Roll(2,6)+2)*100); } break; case AttackEffect.PARALYZE: if(!a.HasAttr(AttrType.NONLIVING) || type != ActorType.CARRION_CRAWLER){ if(a.ResistedBySpirit()){ B.Add(a.Your() + " muscles stiffen, but only for a moment. ",a); } else{ if(a == player){ B.Add("You suddenly can't move! "); } else{ B.Add(a.YouAre() + " paralyzed. ",a); } a.attrs[AttrType.PARALYZED] = R.Between(3,5); } } break; case AttackEffect.ONE_TURN_PARALYZE: Event e = Q.FindAttrEvent(a,AttrType.STUNNED); if(e != null && e.delay == 100 && e.TimeToExecute() == Q.turn){ //if the target was hit with a 1-turn stun that's about to expire, don't print a message for it. e.msg = ""; } if(a.ResistedBySpirit()){ B.Add(a.Your() + " muscles stiffen, but only for a moment. ",a); } else{ B.Add(a.YouAre() + " paralyzed! ",a); a.attrs[AttrType.PARALYZED] = 2; //setting it to 1 means it would end immediately } break; case AttackEffect.INFLICT_VULNERABILITY: a.ApplyStatus(AttrType.VULNERABLE,R.Between(2,4)*100); /*B.Add(a.You("become") + " vulnerable. ",a); a.RefreshDuration(AttrType.VULNERABLE,R.Between(2,4)*100);*/ if(a == player){ Help.TutorialTip(TutorialTopic.Vulnerable); } break; case AttackEffect.IGNITE: break; case AttackEffect.INFEST: if(a == player && !a.EquippedArmor.status[EquipmentStatus.INFESTED]){ B.Add("Thousands of insects crawl into your " + a.EquippedArmor.NameWithoutEnchantment() + "! "); a.EquippedArmor.status[EquipmentStatus.INFESTED] = true; Help.TutorialTip(TutorialTopic.Infested); } break; case AttackEffect.SLOW: a.ApplyStatus(AttrType.SLOWED,R.Between(4,6)*100); break; case AttackEffect.REDUCE_ACCURACY: //also about 2d4 turns? break; case AttackEffect.SLIME: B.Add(a.YouAre() + " covered in slime. ",a); a.attrs[AttrType.SLIMED] = 1; if(a == player){ Help.TutorialTip(TutorialTopic.Slimed); } break; case AttackEffect.STUN: //2d3 turns, at most { a.ApplyStatus(AttrType.STUNNED,R.Roll(2,3)*100); /*B.Add(a.YouAre() + " stunned! ",a); a.RefreshDuration(AttrType.STUNNED,a.DurationOfMagicalEffect(R.Roll(2,3)) * 100,a.YouAre() + " no longer stunned. ",a);*/ if(a == player){ Help.TutorialTip(TutorialTopic.Stunned); } break; } case AttackEffect.ONE_TURN_STUN: { a.ApplyStatus(AttrType.STUNNED,100); /*B.Add(a.YouAre() + " stunned! ",a); a.RefreshDuration(AttrType.STUNNED,100,a.YouAre() + " no longer stunned. ",a);*/ if(a == player){ Help.TutorialTip(TutorialTopic.Stunned); } break; } case AttackEffect.SILENCE: { if(a.ResistedBySpirit()){ if(!HasAttr(AttrType.SILENCED)){ B.Add(a.You("resist") + " being silenced. ",a); } } else{ if(!HasAttr(AttrType.SILENCED)){ B.Add(TheName(true) + " silences " + a.the_name + ". ",a); } a.RefreshDuration(AttrType.SILENCED,R.Between(3,4)*100,a.YouAre() + " no longer silenced. ",a); } if(a == player){ Help.TutorialTip(TutorialTopic.Silenced); } break; } case AttackEffect.WEAK_POINT: if(!a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] && a == player){ a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] = true; B.Add(YouVisible("expose") + " a weak point on your armor! ",this); Help.TutorialTip(TutorialTopic.WeakPoint); } break; case AttackEffect.WORN_OUT: if(a == player && !a.EquippedArmor.status[EquipmentStatus.DAMAGED]){ if(a.EquippedArmor.status[EquipmentStatus.WORN_OUT]){ a.EquippedArmor.status[EquipmentStatus.WORN_OUT] = false; a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] = false; a.EquippedArmor.status[EquipmentStatus.DAMAGED] = true; B.Add(a.Your() + " " + a.EquippedArmor.NameWithEnchantment() + " is damaged! "); Help.TutorialTip(TutorialTopic.Damaged); } else{ a.EquippedArmor.status[EquipmentStatus.WORN_OUT] = true; B.Add(a.Your() + " " + a.EquippedArmor.NameWithEnchantment() + " looks worn out. "); Help.TutorialTip(TutorialTopic.WornOut); } } break; case AttackEffect.ACID: if(a == player && !a.HasAttr(AttrType.ACIDIFIED) && R.CoinFlip()){ a.RefreshDuration(AttrType.ACIDIFIED,300); if(a.EquippedArmor != a.Leather && !a.EquippedArmor.status[EquipmentStatus.DAMAGED]){ B.Add("The acid hisses as it touches your " + a.EquippedArmor.NameWithEnchantment() + "! "); if(a.EquippedArmor.status[EquipmentStatus.WORN_OUT]){ a.EquippedArmor.status[EquipmentStatus.WORN_OUT] = false; a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] = false; a.EquippedArmor.status[EquipmentStatus.DAMAGED] = true; B.Add(a.Your() + " " + a.EquippedArmor.NameWithEnchantment() + " is damaged! "); } else{ a.EquippedArmor.status[EquipmentStatus.WORN_OUT] = true; B.Add(a.Your() + " " + a.EquippedArmor.NameWithEnchantment() + " looks worn out. "); } Help.TutorialTip(TutorialTopic.Acidified); } } break; case AttackEffect.PULL: { List<Tile> tiles = tile().NeighborsBetween(a.row,a.col).Where(x=>x.actor() == null && x.passable); if(tiles.Count > 0){ Tile t = tiles.Random(); if(!a.MovementPrevented(t)){ B.Add(TheName(true) + " pulls " + a.TheName(true) + " closer. ",this,a); a.Move(t.row,t.col); } } break; } case AttackEffect.STEAL: { if(a.inv != null && a.inv.Count > 0){ Item i = a.inv.Random(); Item stolen = i; if(i.quantity > 1){ stolen = new Item(i,i.row,i.col); stolen.revealed_by_light = i.revealed_by_light; i.quantity--; } else{ a.inv.Remove(stolen); } GetItem(stolen); B.Add(YouVisible("steal") + " " + a.YourVisible() + " " + stolen.SingularName() + "! ",this,a); B.PrintAll(); } break; } case AttackEffect.EXHAUST: { if(a == player){ B.Add("You feel fatigued. "); } a.IncreaseExhaustion(R.Roll(2,4)); break; } } } } } foreach(AttackEffect effect in effects){ //effects that don't care whether the target is still alive switch(effect){ case AttackEffect.DRAIN_LIFE: { if(curhp < maxhp){ curhp += 10; if(curhp > maxhp){ curhp = maxhp; } B.Add(You("drain") + " some life from " + a.TheName(true) + ". ",this); } break; } case AttackEffect.VICTORY: if(!still_alive){ curhp += 5; if(curhp > maxhp){ curhp = maxhp; } } break; case AttackEffect.STALAGMITES: { List<Tile> tiles = new List<Tile>(); foreach(Tile t in M.tile[r,c].TilesWithinDistance(1)){ //if(t.actor() == null && (t.type == TileType.FLOOR || t.type == TileType.STALAGMITE)){ if(t.actor() == null && t.inv == null && (t.IsTrap() || t.Is(TileType.FLOOR,TileType.GRAVE_DIRT,TileType.GRAVEL,TileType.STALAGMITE))){ if(R.CoinFlip()){ tiles.Add(t); } } } foreach(Tile t in tiles){ if(t.type == TileType.STALAGMITE){ Q.KillEvents(t,EventType.STALAGMITE); } else{ TileType previous_type = t.type; t.Toggle(this,TileType.STALAGMITE); t.toggles_into = previous_type; } } Q.Add(new Event(tiles,150,EventType.STALAGMITE)); break; } case AttackEffect.MAKE_NOISE: break; case AttackEffect.SWAP_POSITIONS: if(original_pos.DistanceFrom(target_original_pos) == 1 && p.Equals(original_pos) && M.actor[target_original_pos] != null && !M.actor[target_original_pos].HasAttr(AttrType.IMMOBILE)){ B.Add(YouVisible("move") + " past " + M.actor[target_original_pos].TheName(true) + ". ",this,M.actor[target_original_pos]); Move(target_original_pos.row,target_original_pos.col); } break; case AttackEffect.TRIP: if(!a.HasAttr(AttrType.FLYING) && (a.curhp > 0 || !a.HasAttr(AttrType.NO_CORPSE_KNOCKBACK))){ B.Add(YouVisible("trip") + " " + a.TheName(true) + ". ",this,a); a.IncreaseExhaustion(R.Between(2,4)); a.CollideWith(a.tile());//todo: if it's a corpse, ONLY trip it if something is going to happen when it collides with the floor. } break; case AttackEffect.KNOCKBACK: if(a.curhp > 0 || !a.HasAttr(AttrType.NO_CORPSE_KNOCKBACK)){ KnockObjectBack(a,3,this); } break; case AttackEffect.STRONG_KNOCKBACK: if(a.curhp > 0 || !a.HasAttr(AttrType.NO_CORPSE_KNOCKBACK)){ KnockObjectBack(a,5,this); } break; case AttackEffect.FLING: if(a.curhp > 0 || !a.HasAttr(AttrType.NO_CORPSE_KNOCKBACK)){ attrs[AttrType.JUST_FLUNG] = 1; int dir = DirectionOf(a).RotateDir(true,4); Tile t = null; if(tile().p.PosInDir(dir).PosInDir(dir).BoundsCheck(M.tile,true)){ Tile t2 = tile().TileInDirection(dir).TileInDirection(dir); if(HasLOE(t2)){ t = t2; } } if(t == null){ if(tile().p.PosInDir(dir).BoundsCheck(M.tile,false)){ Tile t2 = tile().TileInDirection(dir); if(HasLOE(t2)){ t = t2; } } } if(t == null){ t = tile(); } B.Add(YouVisible("fling") + " " + a.TheName(true) + "! ",this,a); foreach(Tile nearby in M.ReachableTilesByDistance(t.row,t.col,false)){ if(nearby.passable && nearby.actor() == null && HasLOE(nearby)){ a.Move(nearby.row,nearby.col); a.CollideWith(nearby); break; } } } break; } } if(knockback_effect){ if(a.curhp > 0 && this != player){ target_location = target.tile(); } a.CorpseCleanup(); } if(type == ActorType.SWORDSMAN || type == ActorType.PHANTOM_SWORDMASTER || type == ActorType.ALASI_SOLDIER){ if(attrs[AttrType.COMBO_ATTACK] == 1 && (type == ActorType.SWORDSMAN || type == ActorType.PHANTOM_SWORDMASTER)){ B.Add(the_name + " prepares a devastating strike! ",this); } attrs[AttrType.COMBO_ATTACK]++; if(attrs[AttrType.COMBO_ATTACK] == 3){ //all these have 3-part combos attrs[AttrType.COMBO_ATTACK] = 0; } } } /*if(!hit && HasAttr(AttrType.BRUTISH_STRENGTH) && p.Equals(original_pos) && M.actor[target_original_pos] != null){ Actor a2 = M.actor[target_original_pos]; if(a2.HasAttr(AttrType.NO_CORPSE_KNOCKBACK) && a2.maxhp == 1){ B.Add(YouVisible("push",true) + " " + a2.TheName(true) + ". ",this,a2); a2.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,false,1,this); } else{ a2.attrs[AttrType.TURN_INTO_CORPSE]++; KnockObjectBack(a2,5); a2.CorpseCleanup(); } }*/ if(!hit && sneak_attack && this != player){ attrs[AttrType.TURNS_VISIBLE] = -1; attrs[AttrType.NOTICED]++; } if(!attack_is_part_of_another_action && hit && HasAttr(AttrType.BRUTISH_STRENGTH) && p.Equals(original_pos) && M.actor[target_original_pos] == null && DistanceFrom(target_original_pos) == 1 && !MovementPrevented(M.tile[target_original_pos])){ Tile t = M.tile[target_original_pos]; if(t.IsTrap()){ t.SetName(Tile.Prototype(t.type).name); t.TurnToFloor(); } if(HasFeat(FeatType.WHIRLWIND_STYLE)){ WhirlwindMove(t.row,t.col); } else{ Move(t.row,t.col); } } if(hit && EquippedWeapon.enchantment == EnchantmentType.ECHOES && !EquippedWeapon.status[EquipmentStatus.NEGATED]){ List<Tile> line = GetBestExtendedLineOfEffect(target_original_pos.row,target_original_pos.col); int idx = line.IndexOf(M.tile[target_original_pos]); if(idx != -1 && line.Count > idx + 1){ Actor next = line[idx+1].actor(); if(next != null && next != this){ Attack(attack_idx,next,true); } } } //if(!attack_is_part_of_another_action && EquippedWeapon == Staff && p.Equals(original_pos) && a_moved_last_turn && !HasAttr(AttrType.IMMOBILE) && M.tile[target_original_pos].passable && (M.actor[target_original_pos] == null || !M.actor[target_original_pos].HasAttr(AttrType.IMMOBILE))){ if(!attack_is_part_of_another_action && EquippedWeapon == Staff && p.Equals(original_pos) && a_moved_last_turn && !MovementPrevented(M.tile[target_original_pos]) && M.tile[target_original_pos].passable && (M.actor[target_original_pos] == null || !M.actor[target_original_pos].MovementPrevented(this))){ if(M.actor[target_original_pos] != null){ M.actor[target_original_pos].attrs[AttrType.TURNS_HERE]++; //this is a hack to prevent fast monsters from swapping *back* on the next hit. } if(HasFeat(FeatType.WHIRLWIND_STYLE)){ WhirlwindMove(target_original_pos.row,target_original_pos.col,true,new List<Actor>{M.actor[target_original_pos]}); //whirlwind move, but don't attack the original target again } else{ Move(target_original_pos.row,target_original_pos.col); } } if(!attack_is_part_of_another_action && EquippedWeapon.status[EquipmentStatus.POISONED] && !weapon_just_poisoned && R.OneIn(16)){ ApplyStatus(AttrType.POISONED,(R.Roll(2,6)+2)*100,"You manage to poison yourself with your " + EquippedWeapon.NameWithoutEnchantment() + ". ","","You resist the poison dripping from your " + EquippedWeapon.NameWithoutEnchantment() + ". "); } if(!attack_is_part_of_another_action && EquippedWeapon.status[EquipmentStatus.HEAVY] && R.CoinFlip() && !HasAttr(AttrType.BRUTISH_STRENGTH)){ B.Add("Attacking with your heavy " + EquippedWeapon.NameWithoutEnchantment() + " exhausts you. "); IncreaseExhaustion(5); } MakeNoise(6); if(!attack_is_part_of_another_action){ Q.Add(new Event(this,info.cost)); } return hit; }
public void ActiveAI() { if(path.Count > 0){ path.Clear(); } if(!HasAttr(AttrType.AGGRESSION_MESSAGE_PRINTED)){ PrintAggressionMessage(); } switch(type){ case ActorType.GIANT_BAT: case ActorType.PHANTOM_BLIGHTWING: if(DistanceFrom(target) == 1){ int idx = R.Roll(1,2) - 1; Attack(idx,target); if(target != null && R.CoinFlip()){ //chance of retreating AI_Step(target,true); } } else{ if(R.CoinFlip()){ AI_Step(target); QS(); } else{ AI_Step(TileInDirection(Global.RandomDirection())); QS(); } } break; case ActorType.BLOOD_MOTH: { Tile brightest = null; if(!M.wiz_dark && !M.wiz_lite && !HasAttr(AttrType.BLIND)){ List<Tile> valid = M.AllTiles().Where(x=>x.light_value > 0 && CanSee(x)); valid = valid.WhereGreatest(x=>{ int result = x.light_radius; if(x.Is(FeatureType.FIRE) && result == 0){ result = 1; } if(x.inv != null && x.inv.light_radius > result){ result = x.inv.light_radius; } if(x.actor() != null && x.actor().LightRadius() > result){ result = x.actor().LightRadius(); } return result; }); valid = valid.WhereLeast(x=>DistanceFrom(x)); if(valid.Count > 0){ brightest = valid.RandomOrDefault(); } } if(brightest != null){ if(DistanceFrom(brightest) <= 1){ if(target != null && brightest == target.tile()){ Attack(0,target); if(target == player && player.curhp > 0){ Help.TutorialTip(TutorialTopic.Torch); } } else{ List<Tile> open = new List<Tile>(); foreach(Tile t in TilesAtDistance(1)){ if(t.DistanceFrom(brightest) <= 1 && t.passable && t.actor() == null){ open.Add(t); } } if(open.Count > 0){ AI_Step(open.Random()); } QS(); } } else{ List<Tile> tiles = new List<Tile>(); if(brightest.row == row || brightest.col == col){ int targetdir = DirectionOf(brightest); for(int i=-1;i<=1;++i){ pos adj = p.PosInDir(targetdir.RotateDir(true,i)); if(M.tile[adj].passable && M.actor[adj] == null){ tiles.Add(M.tile[adj]); } } } if(tiles.Count > 0){ AI_Step(tiles.Random()); } else{ AI_Step(brightest); } QS(); } } else{ int dir = Global.RandomDirection(); if(!TileInDirection(dir).passable && TilesAtDistance(1).Where(t => !t.passable).Count > 4){ dir = Global.RandomDirection(); } if(TileInDirection(dir).passable && ActorInDirection(dir) == null){ AI_Step(TileInDirection(dir)); QS(); } else{ if(curhp < maxhp && target != null && ActorInDirection(dir) == target){ Attack(0,target); } else{ if(player.HasLOS(TileInDirection(dir)) && player.HasLOS(this)){ if(!TileInDirection(dir).passable){ B.Add(the_name + " brushes up against " + TileInDirection(dir).the_name + ". ",this); } else{ if(ActorInDirection(dir) != null){ B.Add(the_name + " brushes up against " + ActorInDirection(dir).TheName(true) + ". ",this); } } } QS(); } } } /*PhysicalObject brightest = null; if(!M.wiz_lite && !M.wiz_dark){ List<PhysicalObject> current_brightest = new List<PhysicalObject>(); foreach(Tile t in M.AllTiles()){ int pos_radius = t.light_radius; PhysicalObject pos_obj = t; if(t.Is(FeatureType.FIRE) && pos_radius == 0){ pos_radius = 1; } if(t.inv != null && t.inv.light_radius > pos_radius){ pos_radius = t.inv.light_radius; pos_obj = t.inv; } if(t.actor() != null && t.actor().LightRadius() > pos_radius){ pos_radius = t.actor().LightRadius(); pos_obj = t.actor(); } if(pos_radius > 0){ if(current_brightest.Count == 0 && CanSee(t)){ current_brightest.Add(pos_obj); } else{ foreach(PhysicalObject o in current_brightest){ int object_radius = o.light_radius; if(o is Actor){ object_radius = (o as Actor).LightRadius(); } if(object_radius == 0 && o is Tile && (o as Tile).Is(FeatureType.FIRE)){ object_radius = 1; } if(pos_radius > object_radius){ if(CanSee(t)){ current_brightest.Clear(); current_brightest.Add(pos_obj); break; } } else{ if(pos_radius == object_radius && DistanceFrom(t) < DistanceFrom(o)){ if(CanSee(t)){ current_brightest.Clear(); current_brightest.Add(pos_obj); break; } } else{ if(pos_radius == object_radius && DistanceFrom(t) == DistanceFrom(o) && pos_obj == player){ if(CanSee(t)){ current_brightest.Clear(); current_brightest.Add(pos_obj); break; } } } } } } } } if(current_brightest.Count > 0){ brightest = current_brightest.Random(); } } if(brightest != null){ if(DistanceFrom(brightest) <= 1){ if(brightest == target){ Attack(0,target); if(target == player && player.curhp > 0){ Help.TutorialTip(TutorialTopic.Torch); } } else{ List<Tile> open = new List<Tile>(); foreach(Tile t in TilesAtDistance(1)){ if(t.DistanceFrom(brightest) <= 1 && t.passable && t.actor() == null){ open.Add(t); } } if(open.Count > 0){ AI_Step(open.Random()); } QS(); } } else{ AI_Step(brightest); QS(); } } else{ int dir = Global.RandomDirection(); if(TilesAtDistance(1).Where(t => !t.passable).Count > 4 && !TileInDirection(dir).passable){ dir = Global.RandomDirection(); } if(TileInDirection(dir).passable && ActorInDirection(dir) == null){ AI_Step(TileInDirection(dir)); QS(); } else{ if(curhp < maxhp && target != null && ActorInDirection(dir) == target){ Attack(0,target); } else{ if(player.HasLOS(TileInDirection(dir)) && player.HasLOS(this)){ if(!TileInDirection(dir).passable){ B.Add(the_name + " brushes up against " + TileInDirection(dir).the_name + ". ",this); } else{ if(ActorInDirection(dir) != null){ B.Add(the_name + " brushes up against " + ActorInDirection(dir).TheName(true) + ". ",this); } } } QS(); } } }*/ break; } case ActorType.CARNIVOROUS_BRAMBLE: case ActorType.MUD_TENTACLE: if(DistanceFrom(target) == 1){ Attack(0,target); if(target == player && player.curhp > 0){ Help.TutorialTip(TutorialTopic.RangedAttacks); } } else{ QS(); } break; case ActorType.FROSTLING: { if(DistanceFrom(target) == 1){ if(R.CoinFlip()){ Attack(0,target); } else{ if(AI_Step(target,true)){ QS(); } else{ Attack(0,target); } } } else{ if(FirstActorInLine(target) == target && !HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 6){ int cooldown = R.Roll(1,4); if(cooldown != 1){ RefreshDuration(AttrType.COOLDOWN_1,cooldown*100); } AnimateBoltProjectile(target,Color.RandomIce); if(R.CoinFlip()){ B.Add(TheName(true) + " hits " + target.the_name + " with a blast of cold. ",target); target.TakeDamage(DamageType.COLD,DamageClass.PHYSICAL,R.Roll(2,6),this,"a frostling"); } else{ B.Add(TheName(true) + " misses " + target.the_name + " with a blast of cold. ",target); } foreach(Tile t in GetBestLineOfEffect(target)){ t.ApplyEffect(DamageType.COLD); } Q1(); } else{ if(!HasAttr(AttrType.COOLDOWN_2)){ AI_Step(target); } else{ AI_Sidestep(target); //message for this? hmm. } QS(); } } break; } case ActorType.SWORDSMAN: case ActorType.PHANTOM_SWORDMASTER: if(DistanceFrom(target) == 1){ pos target_pos = target.p; Attack(0,target); if(target != null && target.p.Equals(target_pos)){ List<Tile> valid_dirs = new List<Tile>(); foreach(Tile t in target.TilesAtDistance(1)){ if(t.passable && t.actor() == null && DistanceFrom(t) == 1){ valid_dirs.Add(t); } } if(valid_dirs.Count > 0){ AI_Step(valid_dirs.Random()); } } } else{ attrs[AttrType.COMBO_ATTACK] = 0; AI_Step(target); QS(); } break; case ActorType.DREAM_WARRIOR: if(DistanceFrom(target) == 1){ if(curhp <= 10 && !HasAttr(AttrType.COOLDOWN_1)){ //todo: changed to 20hp and a 10hp threshold...better? attrs[AttrType.COOLDOWN_1]++; List<Tile> openspaces = new List<Tile>(); foreach(Tile t in target.TilesAtDistance(1)){ if(t.passable && t.actor() == null){ openspaces.Add(t); } } foreach(Tile t in openspaces){ if(group == null){ group = new List<Actor>{this}; } Create(ActorType.DREAM_WARRIOR_CLONE,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent); t.actor().player_visibility_duration = -1; t.actor().attrs[AttrType.NO_ITEM]++; group.Add(M.actor[t.row,t.col]); M.actor[t.row,t.col].group = group; group.Randomize(); } openspaces.Add(tile()); Tile newtile = openspaces[R.Roll(openspaces.Count)-1]; if(newtile != tile()){ Move(newtile.row,newtile.col,false); } if(openspaces.Count > 1){ B.Add(the_name + " is suddenly standing all around " + target.the_name + ". ",this,target); Q1(); } else{ Attack(0,target); } } else{ Attack(0,target); } } else{ AI_Step(target); QS(); } break; case ActorType.SPITTING_COBRA: if(DistanceFrom(target) <= 3 && !HasAttr(AttrType.COOLDOWN_1) && FirstActorInLine(target) == target){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(50,75)*100); B.Add(TheName(true) + " spits poison in " + target.YourVisible() + " eyes! ",this,target); AnimateBoltProjectile(target,Color.DarkGreen); if(!target.HasAttr(AttrType.NONLIVING)){ target.ApplyStatus(AttrType.BLIND,R.Between(5,8)*100); /*B.Add(target.YouAre() + " blind! ",target); target.RefreshDuration(AttrType.BLIND,R.Between(5,8)*100,target.YouAre() + " no longer blinded. ",target);*/ } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ List<Tile> tiles = new List<Tile>(); if(target.row == row || target.col == col){ int targetdir = DirectionOf(target); for(int i=-1;i<=1;++i){ pos adj = p.PosInDir(targetdir.RotateDir(true,i)); if(M.tile[adj].passable && M.actor[adj] == null){ tiles.Add(M.tile[adj]); } } } if(tiles.Count > 0){ AI_Step(tiles.Random()); } else{ AI_Step(target); } QS(); } } break; case ActorType.KOBOLD: if(!HasAttr(AttrType.COOLDOWN_1)){ if(DistanceFrom(target) > 12){ AI_Step(target); QS(); } else{ if(FirstActorInLine(target) != target){ AI_Sidestep(target); QS(); } else{ attrs[AttrType.COOLDOWN_1]++; AnimateBoltProjectile(target,Color.DarkCyan,30); if(player.CanSee(this)){ B.Add(the_name + " fires a dart at " + target.the_name + ". ",this,target); } else{ B.Add("A dart hits " + target.the_name + "! ",target); if(player.CanSee(tile()) && !IsInvisibleHere()){ attrs[AttrType.TURNS_VISIBLE] = -1; attrs[AttrType.NOTICED] = 1; B.Add("You spot " + the_name + " that fired it. ",this); //B.Add("You notice " + a_name + ". ",tile()); } } if(target.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(6),this,"a kobold's dart")){ target.ApplyStatus(AttrType.VULNERABLE,R.Between(2,4)*100); /*if(!target.HasAttr(AttrType.VULNERABLE)){ B.Add(target.YouFeel() + " vulnerable. ",target); } target.RefreshDuration(AttrType.VULNERABLE,R.Between(2,4)*100,target.YouFeel() + " less vulnerable. ",target);*/ if(target == player){ Help.TutorialTip(TutorialTopic.Vulnerable); } } Q1(); } } } else{ if(DistanceFrom(target) <= 2){ AI_Flee(); QS(); } else{ B.Add(the_name + " starts reloading. ",this); attrs[AttrType.COOLDOWN_1] = 0; Q1(); RefreshDuration(AttrType.COOLDOWN_2,R.Between(5,6)*100 - 50); //Q.Add(new Event(this,R.Between(5,6)*100,EventType.MOVE)); } } break; case ActorType.SPORE_POD: if(DistanceFrom(target) == 1){ TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,100,null); } else{ AI_Step(target); QS(); } break; case ActorType.FORASECT: { bool burrow = false; if((curhp * 2 <= maxhp || DistanceFrom(target) > 6) && R.CoinFlip()){ burrow = true; } if(DistanceFrom(target) <= 6 && DistanceFrom(target) > 1){ if(R.OneIn(10)){ burrow = true; } } if(burrow && !HasAttr(AttrType.COOLDOWN_1)){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(8,11)*100); if(curhp * 2 <= maxhp){ Burrow(TilesWithinDistance(6)); } else{ Burrow(GetCone(DirectionOf(target),6,true)); } } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; } case ActorType.POLTERGEIST: if(inv.Count == 0){ if(DistanceFrom(target) == 1){ pos target_p = target.p; if(Attack(0,target) && M.actor[target_p] != null && M.actor[target_p].inv.Any(i=>!i.do_not_stack)){ target = M.actor[target_p]; Item item = target.inv.Where(i=>!i.do_not_stack).Random(); if(item.quantity > 1){ inv.Add(new Item(item,-1,-1)); item.quantity--; B.Add(YouVisible("steal") + " " + target.YourVisible() + " " + inv[0].Name() + "! ",this,target); } else{ inv.Add(item); target.inv.Remove(item); B.Add(YouVisible("steal") + " " + target.YourVisible() + " " + item.Name() + "! ",this,target); } } } else{ AI_Step(target); QS(); } } else{ attrs[AttrType.KEEPS_DISTANCE] = 1; List<Tile> line = target.GetBestExtendedLineOfEffect(this); Tile next = null; bool found = false; foreach(Tile t in line){ if(found){ next = t; break; } else{ if(t.actor() == this){ found = true; } } } if(next != null){ if(next.passable && next.actor() == null && AI_Step(next)){ QS(); } else{ if(!next.passable){ B.Add(the_name + " disappears into " + next.the_name + ". ",this); foreach(Tile t in TilesWithinDistance(1)){ if(t.DistanceFrom(next) == 1 && t.name == "floor"){ t.AddFeature(FeatureType.SLIME); } } Event e = null; foreach(Event e2 in Q.list){ if(e2.target == this && e2.type == EventType.POLTERGEIST){ e = e2; break; } } if(e != null){ e.target = inv[0]; Actor.tiebreakers[e.tiebreaker] = null; } inv.Clear(); Kill(); } else{ if(next.actor() != null){ if(!next.actor().HasAttr(AttrType.IMMOBILE)){ Move(next.row,next.col); QS(); } else{ if(next.actor().HasAttr(AttrType.IMMOBILE)){ if(AI_Step(next)){ QS(); } else{ if(DistanceFrom(target) == 1){ Attack(1,target); } else{ QS(); } } } } } else{ QS(); } } } } } break; case ActorType.CULTIST: case ActorType.FINAL_LEVEL_CULTIST: if(curhp <= 10 && !HasAttr(AttrType.COOLDOWN_1)){ attrs[AttrType.COOLDOWN_1]++; string invocation; switch(R.Roll(4)){ case 1: invocation = "ae vatra kersai"; break; case 2: invocation = "kersai dzaggath"; break; case 3: invocation = "od fir od bahgal"; break; case 4: invocation = "denei kersai nammat"; break; default: invocation = "denommus pilgni"; break; } if(R.CoinFlip()){ B.Add(You("whisper") + " '" + invocation + "'. ",this); } else{ B.Add(You("scream") + " '" + invocation.ToUpper() + "'. ",this); } if(HasAttr(AttrType.SLIMED)){ B.Add("Nothing happens. ",this); } else{ B.Add("Flames erupt from " + the_name + ". ",this); AnimateExplosion(this,1,Color.RandomFire,'*'); ApplyBurning(); foreach(Tile t in TilesWithinDistance(1)){ t.ApplyEffect(DamageType.FIRE); if(t.actor() != null){ t.actor().ApplyBurning(); } } } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; case ActorType.GOBLIN_ARCHER: case ActorType.PHANTOM_ARCHER: switch(DistanceFrom(target)){ case 1: /*if(target.EnemiesAdjacent() > 1){ Attack(0,target); } else{*/ if(AI_Flee()){ QS(); } else{ Attack(0,target); } //} break; case 2: if(FirstActorInLine(target) == target){ FireArrow(target); } else{ if(AI_Flee()){ QS(); } else{ if(AI_Sidestep(target)){ B.Add(the_name + " tries to line up a shot. ",this); } QS(); } } break; case 3: case 4: case 5: case 6: case 7: case 8: if(FirstActorInLine(target) == target){ FireArrow(target); } else{ if(AI_Sidestep(target)){ B.Add(the_name + " tries to line up a shot. ",this); } QS(); } break; default: AI_Step(target); QS(); break; } break; case ActorType.GOBLIN_SHAMAN: { if(SilencedThisTurn()){ return; } if(DistanceFrom(target) == 1){ if(exhaustion > 50){ Attack(0,target); } else{ CastCloseRangeSpellOrAttack(target); } } else{ if(DistanceFrom(target) > 12){ AI_Step(target); QS(); } else{ if(FirstActorInLine(target) != target || R.CoinFlip()){ AI_Step(target); QS(); } else{ CastRangedSpellOrMove(target); } } } break; } case ActorType.PHASE_SPIDER: if(DistanceFrom(target) == 1){ Attack(0,target); } else{ Tile t = target.TilesAtDistance(DistanceFrom(target)-1).Where(x=>x.passable && x.actor() == null).RandomOrDefault(); if(t != null){ Move(t.row,t.col); } QS(); } break; case ActorType.ZOMBIE: case ActorType.PHANTOM_ZOMBIE: if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); if(DistanceFrom(target) == 1){ Attack(1,target); } else{ QS(); } } break; case ActorType.ROBED_ZEALOT: if(HasAttr(AttrType.COOLDOWN_3)){ if(DistanceFrom(target) <= 12 && HasLOS(target)){ target.AnimateExplosion(target,1,Color.Yellow,'*'); B.Add(YouVisible("smite") + " " + target.the_name + "! ",target); int amount = target.curhp / 10; bool still_alive = target.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,Math.Max(amount,1),this,"a zealot's wrath"); attrs[AttrType.COOLDOWN_3]--; attrs[AttrType.DETECTING_MONSTERS]--; if(!HasAttr(AttrType.COOLDOWN_3)){ B.Add(YouVisible("stop") + " praying. "); if(still_alive && target.EquippedWeapon.type != WeaponType.NO_WEAPON && !target.EquippedWeapon.status[EquipmentStatus.MERCIFUL]){ target.EquippedWeapon.status[EquipmentStatus.MERCIFUL] = true; B.Add(target.You("feel") + " a strange power enter " + target.Your() + " " + target.EquippedWeapon.NameWithoutEnchantment() + "! ",target); B.PrintAll(); Help.TutorialTip(TutorialTopic.Merciful); } } } else{ attrs[AttrType.COOLDOWN_3]--; attrs[AttrType.DETECTING_MONSTERS]--; } Q1(); } else{ if(!HasAttr(AttrType.COOLDOWN_1)){ attrs[AttrType.COOLDOWN_1] = maxhp; //initialize this value here instead of complicating the spawning code } if(DistanceFrom(target) <= 12 && !HasAttr(AttrType.COOLDOWN_2) && curhp < attrs[AttrType.COOLDOWN_1]){ //if the ability is ready and additional damage has been taken... RefreshDuration(AttrType.COOLDOWN_2,R.Between(11,13)*100); attrs[AttrType.COOLDOWN_1] = curhp; attrs[AttrType.COOLDOWN_3] = 4; attrs[AttrType.DETECTING_MONSTERS] = 4; B.Add(YouVisible("start") + " praying. "); B.Add(the_name + " points directly at you. ",this); Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } } /*if(HasAttr(AttrType.COOLDOWN_2)){ attrs[AttrType.COOLDOWN_2] = 0; B.Add(the_name + " finishes the prayer. ",this); if(DistanceFrom(target) == 1 && target.EquippedWeapon.type != WeaponType.NO_WEAPON){ target.EquippedWeapon.status[EquipmentStatus.MERCIFUL] = true; B.Add("You feel a strange power enter " + target.Your() + " " + target.EquippedWeapon.NameWithoutEnchantment() + "! ",target); B.PrintAll(); Help.TutorialTip(TutorialTopic.Merciful); } Q1(); } else{ if((maxhp / 5) * 4 > curhp && !HasAttr(AttrType.COOLDOWN_1)){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(14,16)*100); attrs[AttrType.COOLDOWN_2]++; B.Add(the_name + " starts praying. ",this); B.Add("A fiery halo appears above " + the_name + ". ",this); RefreshDuration(AttrType.RADIANT_HALO,R.Between(8,10)*100,Your() + " halo fades. ",this); Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } }*/ break; case ActorType.GIANT_SLUG: { if(DistanceFrom(target) == 1){ Attack(R.Between(0,1),target); } else{ if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12 && FirstActorInLine(target) == target){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(11,14)*100); B.Add(TheName(true) + " spits slime at " + target.the_name + ". ",target); List<Tile> slimed = GetBestLineOfEffect(target); List<Tile> added = new List<Tile>(); foreach(Tile t in slimed){ foreach(int dir in U.FourDirections){ Tile neighbor = t.TileInDirection(dir); if(R.OneIn(3) && neighbor.passable && !slimed.Contains(neighbor)){ added.AddUnique(neighbor); } } } slimed.AddRange(added); List<pos> cells = new List<pos>(); List<Actor> slimed_actors = new List<Actor>(); for(int i=0;slimed.Count > 0;++i){ List<Tile> removed = new List<Tile>(); foreach(Tile t in slimed){ if(DistanceFrom(t) == i){ t.AddFeature(FeatureType.SLIME); if(t.actor() != null && t.actor() != this && !t.actor().HasAttr(AttrType.SLIMED,AttrType.FROZEN)){ slimed_actors.Add(t.actor()); } removed.Add(t); if(DistanceFrom(t) > 0){ cells.Add(t.p); } } } foreach(Tile t in removed){ slimed.Remove(t); } if(cells.Count > 0){ Screen.AnimateMapCells(cells,new colorchar(',',Color.Green),20); } } M.Draw(); slimed_actors.AddUnique(target); foreach(Actor a in slimed_actors){ a.attrs[AttrType.SLIMED] = 1; a.attrs[AttrType.OIL_COVERED] = 0; a.RefreshDuration(AttrType.BURNING,0); B.Add(a.YouAre() + " covered in slime. ",a); } Q1(); } else{ AI_Step(target); if(tile().Is(FeatureType.SLIME)){ speed = 50; QS(); //normal speed is 150 speed = 150; } else{ QS(); } } } break; } case ActorType.BANSHEE: { if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(13,15)*100); if(player.CanSee(this)){ if(player.IsSilencedHere()){ B.Add(You("seem") + " to scream. ",this); } else{ B.Add(You("scream") + ". ",this); } } else{ if(!player.IsSilencedHere()){ B.Add("You hear a scream! "); } } if(!target.IsSilencedHere()){ if(target.ResistedBySpirit() || target.HasAttr(AttrType.MENTAL_IMMUNITY)){ B.Add(target.You("remain") + " courageous. ",target); } else{ B.Add(target.YouAre() + " terrified! ",target); RefreshDuration(AttrType.TERRIFYING,R.Between(5,8)*100,target.YouAre() + " no longer afraid. ",target); Help.TutorialTip(TutorialTopic.Afraid); } } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; } case ActorType.CAVERN_HAG: if(curhp < maxhp && HasAttr(AttrType.COOLDOWN_2) && !HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12){ B.Add(TheName(true) + " curses you! "); if(target.ResistedBySpirit()){ B.Add("You resist the curse. "); } else{ switch(R.Roll(4)){ case 1: //light allergy B.Add("You become allergic to light! "); target.RefreshDuration(AttrType.LIGHT_SENSITIVE,(R.Roll(2,20) + 70) * 100,"You are no longer allergic to light. "); break; case 2: //aggravate monsters B.Add("Every sound you make becomes amplified and echoes across the dungeon. "); target.RefreshDuration(AttrType.AGGRAVATING,(R.Roll(2,20) + 70) * 100,"Your sounds are no longer amplified. "); break; case 3: //cursed weapon B.Add("Your " + target.EquippedWeapon + " becomes stuck to your hand! "); target.EquippedWeapon.status[EquipmentStatus.STUCK] = true; Help.TutorialTip(TutorialTopic.Stuck); break; case 4: //heavy weapon B.Add("Your " + target.EquippedWeapon + " suddenly feels much heavier. "); target.EquippedWeapon.status[EquipmentStatus.HEAVY] = true; Help.TutorialTip(TutorialTopic.Heavy); break; } } attrs[AttrType.COOLDOWN_1]++; Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; case ActorType.BERSERKER: { if(HasAttr(AttrType.COOLDOWN_2)){ int dir = attrs[AttrType.COOLDOWN_2]; bool cw = R.CoinFlip(); if(TileInDirection(dir).passable && ActorInDirection(dir) == null && !MovementPrevented(TileInDirection(dir))){ B.Add(the_name + " leaps forward swinging his axe! ",this); Move(TileInDirection(dir).row,TileInDirection(dir).col); M.Draw(); for(int i=-1;i<=1;++i){ Screen.AnimateBoltProjectile(new List<Tile>{tile(),TileInDirection(dir.RotateDir(cw,i))},Color.Red,30); } for(int i=-1;i<=1;++i){ Actor a = ActorInDirection(dir.RotateDir(cw,i)); if(a != null){ B.Add(YourVisible() + " axe hits " + a.TheName(true) + ". ",this,a); a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(4,6),this,"a berserker's axe"); } TileInDirection(dir.RotateDir(cw,i)).Bump(dir.RotateDir(cw,i)); } Q1(); } else{ if(ActorInDirection(dir) != null || MovementPrevented(TileInDirection(dir)) || TileInDirection(dir).Is(TileType.STANDING_TORCH,TileType.BARREL,TileType.POISON_BULB)){ B.Add(the_name + " swings his axe furiously! ",this); for(int i=-1;i<=1;++i){ Screen.AnimateBoltProjectile(new List<Tile>{tile(),TileInDirection(dir.RotateDir(cw,i))},Color.Red,30); } for(int i=-1;i<=1;++i){ Actor a = ActorInDirection(dir.RotateDir(cw,i)); if(a != null){ B.Add(YourVisible() + " axe hits " + a.TheName(true) + ". ",this,a); a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(4,6),this,"a berserker's axe"); } TileInDirection(dir.RotateDir(cw,i)).Bump(dir.RotateDir(cw,i)); } Q1(); } else{ if(target != null && HasLOS(target)){ B.Add(the_name + " turns to face " + target.the_name + ". ",this); attrs[AttrType.COOLDOWN_2] = DirectionOf(target); Q1(); } } } } else{ if(DistanceFrom(target) == 1){ Attack(0,target); if(target != null && R.Roll(3) == 3){ B.Add(the_name + " screams with fury! ",this); attrs[AttrType.COOLDOWN_2] = DirectionOf(target); Q.Add(new Event(this,350,AttrType.COOLDOWN_2,Your() + " rage diminishes. ",this)); } } else{ AI_Step(target); QS(); } } break; } case ActorType.DIRE_RAT: { bool slip_past = false; if(DistanceFrom(target) == 1){ foreach(Actor a in ActorsAtDistance(1)){ if(a.type == ActorType.DIRE_RAT && a.DistanceFrom(target) > this.DistanceFrom(target)){ bool can_walk = false; foreach(Tile t in a.TilesAtDistance(1)){ if(t.DistanceFrom(target) < a.DistanceFrom(target) && t.passable && t.actor() == null){ can_walk = true; break; } } if(!can_walk){ //there's a rat that would benefit from a space opening up - now check to see whether a move is possible foreach(Tile t in target.TilesAtDistance(1)){ if(t.passable && t.actor() == null){ slip_past = true; break; } } break; } } } } if(slip_past){ bool moved = false; foreach(Tile t in TilesAtDistance(1)){ if(t.DistanceFrom(target) == 1 && t.passable && t.actor() == null){ AI_Step(t); QS(); moved = true; break; } } if(!moved){ Tile t = target.TilesAtDistance(1).Where(x=>x.passable && x.actor() == null).RandomOrDefault(); if(t != null){ B.Add(TheName(true) + " slips past " + target.TheName(true) + ". ",this,target); Move(t.row,t.col); Q.Add(new Event(this,Speed() + 100,EventType.MOVE)); } else{ QS(); } } } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; } case ActorType.SKULKING_KILLER: { if(HasAttr(AttrType.KEEPS_DISTANCE)){ bool try_to_hide = false; if(AI_Flee()){ try_to_hide = true; QS(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ //give up on fleeing, just attack attrs[AttrType.COOLDOWN_2] = 0; attrs[AttrType.KEEPS_DISTANCE] = 0; AI_Step(target); QS(); } } if(try_to_hide){ bool visible = player.CanSee(this); if(!R.OneIn(5) && (!player.HasLOE(this) || !visible || DistanceFrom(player) > 12)){ //just to add some uncertainty attrs[AttrType.COOLDOWN_2]++; if(attrs[AttrType.COOLDOWN_2] >= 3){ attrs[AttrType.KEEPS_DISTANCE] = 0; attrs[AttrType.COOLDOWN_2] = 0; if(!visible){ attrs[AttrType.TURNS_VISIBLE] = 0; } } } } } else{ if(DistanceFrom(target) == 1){ if(Attack(0,target)){ attrs[AttrType.KEEPS_DISTANCE] = 1; } } else{ AI_Step(target); QS(); } } /*if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 3 && R.OneIn(3) && HasLOE(target)){ attrs[AttrType.COOLDOWN_1]++; AnimateProjectile(target,Color.DarkYellow,'%'); Input.FlushInput(); if(target.CanSee(this)){ B.Add(the_name + " throws a bola at " + target.the_name + ". ",this,target); } else{ B.Add("A bola whirls toward " + target.the_name + ". ",this,target); } attrs[AttrType.TURNS_VISIBLE] = -1; target.RefreshDuration(AttrType.SLOWED,(R.Roll(3)+6)*100,target.YouAre() + " no longer slowed. ",target); B.Add(target.YouAre() + " slowed by the bola. ",target); Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } }*/ break; } case ActorType.WILD_BOAR: if(DistanceFrom(target) == 1){ Attack(0,target); if(HasAttr(AttrType.JUST_FLUNG)){ //if it just flung its target... attrs[AttrType.JUST_FLUNG] = 0; attrs[AttrType.COOLDOWN_1] = 0; } else{ //...otherwise it might prepare to fling again if(!HasAttr(AttrType.COOLDOWN_1)){ if(!HasAttr(AttrType.COOLDOWN_2) || R.OneIn(5)){ attrs[AttrType.COOLDOWN_2]++; B.Add(the_name + " lowers its head. ",this); attrs[AttrType.COOLDOWN_1]++; } } } } else{ AI_Step(target); if(!HasAttr(AttrType.COOLDOWN_2)){ attrs[AttrType.COOLDOWN_2]++; B.Add(the_name + " lowers its head. ",this); attrs[AttrType.COOLDOWN_1]++; } QS(); } break; case ActorType.DREAM_SPRITE: if(!HasAttr(AttrType.COOLDOWN_1)){ if(DistanceFrom(target) <= 12 && FirstActorInLine(target) == target){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(3,4)*100); bool visible = false; List<List<Tile>> lines = new List<List<Tile>>{GetBestLineOfEffect(target)}; if(group != null && group.Count > 0){ foreach(Actor a in group){ if(target == player && player.CanSee(a)){ visible = true; } if(a.type == ActorType.DREAM_SPRITE_CLONE){ a.attrs[AttrType.COOLDOWN_1]++; //for them, it means 'skip next turn' if(a.FirstActorInLine(target) == target){ lines.Add(a.GetBestLineOfEffect(target)); } } } } foreach(List<Tile> line in lines){ if(line.Count > 0){ line.RemoveAt(0); } } if(visible){ B.Add(the_name + " hits " + target.the_name + " with stinging magic. ",target); } else{ B.Add(TheName(true) + " hits " + target.the_name + " with stinging magic. ",target); } int max = lines.WhereGreatest(x=>x.Count)[0].Count; for(int i=0;i<max;++i){ List<pos> cells = new List<pos>(); foreach(List<Tile> line in lines){ if(line.Count > i){ cells.Add(line[i].p); } } Screen.AnimateMapCells(cells,new colorchar('*',Color.RandomRainbow)); } target.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(2,6),this,"a blast of fairy magic"); Q1(); } else{ if(DistanceFrom(target) > 12){ AI_Step(target); } else{ AI_Sidestep(target); } QS(); } } else{ if(DistanceFrom(target) > 5){ AI_Step(target); } else{ if(DistanceFrom(target) < 3){ AI_Flee(); } else{ Tile t = TilesAtDistance(1).Where(x=>x.passable && x.actor() == null).RandomOrDefault(); if(t != null){ AI_Step(t); } } } QS(); } break; case ActorType.DREAM_SPRITE_CLONE: if(HasAttr(AttrType.COOLDOWN_1)){ attrs[AttrType.COOLDOWN_1] = 0; Q1(); } else{ if(DistanceFrom(target) > 5){ AI_Step(target); } else{ if(DistanceFrom(target) < 3){ AI_Flee(); } else{ Tile t = TilesAtDistance(1).Where(x=>x.passable && x.actor() == null).RandomOrDefault(); if(t != null){ AI_Step(t); } } } QS(); } break; case ActorType.CLOUD_ELEMENTAL: { List<pos> cloud = M.tile.GetFloodFillPositions(p,false,x=>M.tile[x].features.Contains(FeatureType.FOG)); PhysicalObject[] objs = new PhysicalObject[cloud.Count + 1]; int idx = 0; foreach(pos p2 in cloud){ objs[idx++] = M.tile[p2]; } objs[idx] = this; List<colorchar> chars = new List<colorchar>(); colorchar cch = new colorchar('*',Color.RandomLightning); if(cloud.Contains(target.p)){ B.Add(the_name + " electrifies the cloud! ",objs); foreach(pos p2 in cloud){ if(M.actor[p2] != null && M.actor[p2] != this){ M.actor[p2].TakeDamage(DamageType.ELECTRIC,DamageClass.PHYSICAL,R.Roll(3,6),this,"*electrocuted by a cloud elemental"); } if(M.actor[p2] == this){ chars.Add(visual); } else{ chars.Add(cch); } } Screen.AnimateMapCells(cloud,chars,50); Q1(); } else{ if(DistanceFrom(target) == 1){ Tile t = TilesAtDistance(1).Where(x=>x.actor() == null && x.passable).RandomOrDefault(); if(t != null){ AI_Step(t); } QS(); } else{ if(R.OneIn(4)){ Tile t = TilesAtDistance(1).Where(x=>x.actor() == null && x.passable).RandomOrDefault(); if(t != null){ AI_Step(t); } QS(); } else{ AI_Step(target); QS(); } } } break; } case ActorType.DERANGED_ASCETIC: if(DistanceFrom(target) == 1){ Attack(R.Roll(3)-1,target); } else{ AI_Step(target); QS(); } break; case ActorType.SNEAK_THIEF: { if(DistanceFrom(target) <= 12 && !R.OneIn(3) && AI_UseRandomItem()){ Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); if(target != null){ List<Tile> valid_dirs = new List<Tile>(); foreach(Tile t in target.TilesAtDistance(1)){ if(t.passable && t.actor() == null && DistanceFrom(t) == 1){ valid_dirs.Add(t); } } if(valid_dirs.Count > 0){ AI_Step(valid_dirs.Random()); } } } else{ AI_Step(target); QS(); } } break; } case ActorType.WARG: { bool howl = false; if(DistanceFrom(target) == 1){ if(R.CoinFlip() || group == null || group.Count < 2 || HasAttr(AttrType.COOLDOWN_1)){ Attack(0,target); } else{ howl = true; } } else{ if(group == null || group.Count < 2 || HasAttr(AttrType.COOLDOWN_1)){ if(AI_Step(target)){ QS(); } else{ howl = true; } } else{ howl = true; } } if(howl){ if(group == null || group.Count < 2){ Q1(); break; } B.Add(TheName(true) + " howls. "); PosArray<int> paths = new PosArray<int>(ROWS,COLS); foreach(Actor packmate in group){ packmate.RefreshDuration(AttrType.COOLDOWN_1,2000); if(packmate != this){ var dijkstra = M.tile.GetDijkstraMap(new List<pos>{target.p},x=>!M.tile[x].passable,y=>M.actor[y] != null? 5 : paths[y]+1); if(!dijkstra[packmate.p].IsValidDijkstraValue()){ continue; } List<pos> new_path = new List<pos>(); pos p = packmate.p; while(!p.Equals(target.p)){ p = p.PositionsAtDistance(1,dijkstra).Where(x=>dijkstra[x].IsValidDijkstraValue()).WhereLeast(x=>dijkstra[x]).Random(); new_path.Add(p); paths[p]++; } packmate.path = new_path; } } Q1(); } break; } case ActorType.RUNIC_TRANSCENDENT: { if(SilencedThisTurn()){ return; } if(!HasSpell(SpellType.MERCURIAL_SPHERE)){ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } return; } if(curmp < 2){ B.Add(the_name + " absorbs mana from the universe. ",this); curmp = maxmp; Q1(); } else{ if(M.safetymap == null){ M.UpdateSafetyMap(player); } Tile t = TilesAtDistance(1).Where(x=>x.DistanceFrom(target) == 3 && x.passable && x.actor() == null).WhereLeast(x=>M.safetymap[x.p]).RandomOrDefault(); if(t != null){ //check safety map. if there's a safer spot at distance 3, step there. AI_Step(t); } else{ if(DistanceFrom(target) > 3){ AI_Step(target); } else{ if(DistanceFrom(target) < 3){ AI_Flee(); } } } if(DistanceFrom(target) <= 12 && FirstActorInLine(target) != null && FirstActorInLine(target).DistanceFrom(target) <= 3){ CastSpell(SpellType.MERCURIAL_SPHERE,target); } else{ QS(); } } break; } case ActorType.CARRION_CRAWLER: if(DistanceFrom(target) == 1){ if(!target.HasAttr(AttrType.PARALYZED)){ Attack(0,target); } else{ Attack(1,target); } } else{ AI_Step(target); QS(); } break; case ActorType.MECHANICAL_KNIGHT: if(attrs[AttrType.COOLDOWN_1] == 3){ //no head int dir = Global.RandomDirection(); if(R.CoinFlip()){ Actor a = ActorInDirection(dir); if(a != null){ if(!Attack(0,a)){ B.Add(the_name + " drops its guard! ",this); attrs[AttrType.MECHANICAL_SHIELD] = 0; } } else{ B.Add(the_name + " attacks empty space. ",this); TileInDirection(dir).Bump(dir); B.Add(the_name + " drops its guard! ",this); attrs[AttrType.MECHANICAL_SHIELD] = 0; Q1(); } } else{ Tile t = TileInDirection(dir); if(t.passable){ if(t.actor() == null){ AI_Step(t); QS(); } else{ B.Add(the_name + " bumps into " + t.actor().TheName(true) + ". ",this); QS(); } } else{ B.Add(the_name + " bumps into " + t.TheName(true) + ". ",this); t.Bump(DirectionOf(t)); QS(); } } } else{ if(DistanceFrom(target) == 1){ if(attrs[AttrType.COOLDOWN_1] == 1){ //no arms Attack(1,target); } else{ if(!Attack(0,target)){ B.Add(the_name + " drops its guard! ",this); attrs[AttrType.MECHANICAL_SHIELD] = 0; } } } else{ if(attrs[AttrType.COOLDOWN_1] != 2){ //no legs AI_Step(target); } QS(); } } break; case ActorType.ALASI_BATTLEMAGE: if(SilencedThisTurn()){ return; } if(DistanceFrom(target) > 12){ AI_Step(target); QS(); } else{ if(DistanceFrom(target) == 1){ if(exhaustion < 50){ CastCloseRangeSpellOrAttack(null,target,true); } else{ Attack(0,target); } } else{ CastRangedSpellOrMove(target); } } break; case ActorType.ALASI_SOLDIER: if(DistanceFrom(target) > 2){ AI_Step(target); QS(); attrs[AttrType.COMBO_ATTACK] = 0; } else{ if(FirstActorInLine(target) != null && !FirstActorInLine(target).name.Contains("alasi")){ //I had planned to make this attack possibly hit multiple targets, but not yet. Attack(0,target); } else{ if(AI_Step(target)){ QS(); } else{ AI_Sidestep(target); QS(); } attrs[AttrType.COMBO_ATTACK] = 0; } } break; case ActorType.SKITTERMOSS: if(DistanceFrom(target) == 1){ Attack(0,target); if(target != null && R.CoinFlip()){ //chance of retreating AI_Step(target,true); } } else{ if(R.CoinFlip()){ AI_Step(target); QS(); } else{ AI_Step(TileInDirection(Global.RandomDirection())); QS(); } } break; case ActorType.ALASI_SCOUT: { if(DistanceFrom(target) == 1){ Attack(0,target); } else{ if(curhp == maxhp){ if(FirstActorInLine(target) == target){ Attack(1,target); } else{ AI_Sidestep(target); QS(); } } else{ AI_Step(target); QS(); } } break; } case ActorType.MUD_ELEMENTAL: { int count = 0; int walls = 0; foreach(Tile t in target.TilesAtDistance(1)){ if(t.p.BoundsCheck(M.tile,false) && t.type == TileType.WALL){ ++walls; if(t.actor() == null){ ++count; } } } if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12 && count >= 2 || (count == 1 && walls == 1)){ RefreshDuration(AttrType.COOLDOWN_1,150); foreach(Tile t in target.TilesAtDistance(1)){ if(t.p.BoundsCheck(M.tile,false) && t.type == TileType.WALL && t.actor() == null){ Create(ActorType.MUD_TENTACLE,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent); M.actor[t.p].player_visibility_duration = -1; } } if(count >= 2){ if(player.CanSee(this)){ B.Add(the_name + " calls mud tentacles from the walls! "); } else{ B.Add("Mud tentacles emerge from the walls! "); } } else{ if(player.CanSee(this)){ B.Add(the_name + " calls a mud tentacle from the wall! "); } else{ B.Add("A mud tentacle emerges from the wall! "); } } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; } case ActorType.FLAMETONGUE_TOAD: { bool burrow = false; if((curhp * 3 <= maxhp || DistanceFrom(target) > 6) && R.CoinFlip()){ burrow = true; } if(DistanceFrom(target) <= 6 && DistanceFrom(target) > 1){ if(R.OneIn(20)){ burrow = true; } } if(burrow && !HasAttr(AttrType.COOLDOWN_1)){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(12,16)*100); if(curhp * 3 <= maxhp){ Burrow(TilesWithinDistance(6)); } else{ Burrow(GetCone(DirectionOf(target),6,true)); } } else{ if(!HasAttr(AttrType.COOLDOWN_2) && FirstActorInLine(target) != null && FirstActorInLine(target).DistanceFrom(target) <= 1){ RefreshDuration(AttrType.COOLDOWN_2,R.Between(10,14)*100); Actor first = FirstActorInLine(target); B.Add(TheName(true) + " breathes fire! ",this,first); AnimateProjectile(first,'*',Color.RandomFire); AnimateExplosion(first,1,'*',Color.RandomFire); foreach(Tile t in GetBestLineOfEffect(first)){ t.ApplyEffect(DamageType.FIRE); } foreach(Tile t in first.TilesWithinDistance(1)){ t.ApplyEffect(DamageType.FIRE); if(t.actor() != null){ t.actor().ApplyBurning(); } } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } } break; } case ActorType.ENTRANCER: if(group == null){ if(AI_Flee()){ QS(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ QS(); } } } else{ Actor thrall = group[1]; if(CanSee(thrall) && HasLOE(thrall)){ //cooldown 1 is teleport. cooldown 2 is shield. //if the thrall is visible and you have LOE, the next goal is for the entrancer to be somewhere on the line that starts at the target and extends through the thrall. List<Tile> line_from_target = target.GetBestExtendedLineOfEffect(thrall); bool on_line = line_from_target.Contains(tile()); bool space_near_target = line_from_target.Count > 1 && line_from_target[1].passable && line_from_target[1].actor() == null; if(on_line && DistanceFrom(target) > thrall.DistanceFrom(target)){ if(!HasAttr(AttrType.COOLDOWN_2) && thrall.curhp <= thrall.maxhp / 2){ //check whether you can shield it, if the thrall is low on health. RefreshDuration(AttrType.COOLDOWN_2,1500); B.Add(TheName(true) + " shields " + thrall.TheName(true) + ". ",this,thrall); B.DisplayNow(); Screen.AnimateStorm(thrall.p,1,2,5,'*',Color.White); thrall.attrs[AttrType.SHIELDED] = 1; Q1(); } else{ //check whether you can teleport the thrall closer. if(!HasAttr(AttrType.COOLDOWN_1) && thrall.DistanceFrom(target) > 1 && space_near_target){ Tile dest = line_from_target[1]; RefreshDuration(AttrType.COOLDOWN_1,400); B.Add(TheName(true) + " teleports " + thrall.TheName(true) + ". ",this,thrall); M.Draw(); thrall.Move(dest.row,dest.col); B.DisplayNow(); Screen.AnimateStorm(dest.p,1,1,4,thrall.symbol,thrall.color); foreach(Tile t2 in thrall.GetBestLineOfEffect(dest)){ Screen.AnimateStorm(t2.p,1,1,4,thrall.symbol,thrall.color); } Q1(); } else{ //check whether you can shield it, if the thrall isn't low on health. if(!HasAttr(AttrType.COOLDOWN_2)){ RefreshDuration(AttrType.COOLDOWN_2,1500); B.Add(TheName(true) + " shields " + thrall.TheName(true) + ". ",this,thrall); B.DisplayNow(); Screen.AnimateStorm(thrall.p,1,2,5,'*',Color.White); thrall.attrs[AttrType.SHIELDED] = 1; Q1(); } else{ //check whether you are adjacent to thrall and can step away while remaining on line. List<Tile> valid = line_from_target.Where(x=>DistanceFrom(x) == 1 && x.actor() == null && x.passable); if(DistanceFrom(thrall) == 1 && valid.Count > 0){ AI_Step(valid.Random()); } QS(); } } } } else{ if(on_line){ //if on the line but not behind the thrall, we might be able to swap places or teleport if(DistanceFrom(thrall) == 1){ Move(thrall.row,thrall.col); QS(); } else{ Tile dest = null; foreach(Tile t in line_from_target){ if(t.passable && t.actor() == null){ dest = t; break; } } if(dest != null){ RefreshDuration(AttrType.COOLDOWN_1,400); B.Add(TheName(true) + " teleports " + thrall.TheName(true) + ". ",this,thrall); M.Draw(); thrall.Move(dest.row,dest.col); B.DisplayNow(); Screen.AnimateStorm(dest.p,1,1,4,thrall.symbol,thrall.color); foreach(Tile t2 in thrall.GetBestLineOfEffect(dest)){ Screen.AnimateStorm(t2.p,1,1,4,thrall.symbol,thrall.color); } } Q1(); } } else{ //if there's a free adjacent space on the line and behind the thrall, step there. List<Tile> valid = line_from_target.From(thrall).Where(x=>x.passable && x.actor() == null && x.DistanceFrom(this) == 1); if(valid.Count > 0){ AI_Step(valid.Random()); QS(); } else{ //if you can teleport and there's a free tile on the line between you and the target, teleport the thrall there. List<Tile> valid_between = GetBestLineOfEffect(target).Where(x=>x.passable && x.actor() == null && thrall.HasLOE(x)); if(!HasAttr(AttrType.COOLDOWN_1) && valid_between.Count > 0){ Tile dest = valid_between.Random(); RefreshDuration(AttrType.COOLDOWN_1,400); B.Add(TheName(true) + " teleports " + thrall.TheName(true) + ". ",this,thrall); M.Draw(); thrall.Move(dest.row,dest.col); B.DisplayNow(); Screen.AnimateStorm(dest.p,1,1,4,thrall.symbol,thrall.color); foreach(Tile t2 in thrall.GetBestLineOfEffect(dest)){ Screen.AnimateStorm(t2.p,1,1,4,thrall.symbol,thrall.color); } Q1(); } else{ //step toward a tile on the line (and behind the thrall) List<Tile> valid_behind_thrall = line_from_target.From(thrall).Where(x=>x.passable && x.actor() == null); if(valid_behind_thrall.Count > 0){ AI_Step(valid_behind_thrall.Random()); } QS(); } } } } //the old code: /*if(DistanceFrom(target) < thrall.DistanceFrom(target) && DistanceFrom(thrall) == 1){ Move(thrall.row,thrall.col); QS(); } else{ if(DistanceFrom(target) == 1 && curhp < maxhp){ List<Tile> safe = TilesAtDistance(1).Where(t=>t.passable && t.actor() == null && target.GetBestExtendedLineOfEffect(thrall).Contains(t)); if(DistanceFrom(thrall) == 1 && safe.Count > 0){ AI_Step(safe.Random()); QS(); } else{ if(AI_Flee()){ QS(); } else{ Attack(0,target); } } } else{ if(!HasAttr(AttrType.COOLDOWN_1) && (thrall.DistanceFrom(target) > 1 || !target.GetBestExtendedLineOfEffect(thrall).Any(t=>t.actor()==this))){ //the entrancer tries to be smart about placing the thrall in a position that blocks ranged attacks List<Tile> closest = new List<Tile>(); int dist = 99; foreach(Tile t in thrall.TilesWithinDistance(2).Where(x=>x.passable && (x.actor()==null || x.actor()==thrall))){ if(t.DistanceFrom(target) < dist){ closest.Clear(); closest.Add(t); dist = t.DistanceFrom(target); } else{ if(t.DistanceFrom(target) == dist){ closest.Add(t); } } } List<Tile> in_line = new List<Tile>(); foreach(Tile t in closest){ if(target.GetBestExtendedLineOfEffect(t).Any(x=>x.actor()==this)){ in_line.Add(t); } } Tile tile2 = null; if(in_line.Count > 0){ tile2 = in_line.Random(); } else{ if(closest.Count > 0){ tile2 = closest.Random(); } } if(tile2 != null && tile2.actor() != thrall){ GainAttr(AttrType.COOLDOWN_1,400); B.Add(TheName(true) + " teleports " + thrall.TheName(true) + ". ",this,thrall); M.Draw(); thrall.Move(tile2.row,tile2.col); B.DisplayNow(); Screen.AnimateStorm(tile2.p,1,1,4,thrall.symbol,thrall.color); foreach(Tile t2 in thrall.GetBestLineOfEffect(tile2)){ Screen.AnimateStorm(t2.p,1,1,4,thrall.symbol,thrall.color); } Q1(); } else{ List<Tile> safe = target.GetBestExtendedLineOfEffect(thrall).Where(t=>t.passable && t.actor() == null && t.DistanceFrom(target) > thrall.DistanceFrom(target)).WhereLeast(t=>DistanceFrom(t)); if(safe.Count > 0){ if(safe.Any(t=>t.DistanceFrom(target) > 2)){ AI_Step(safe.Where(t=>t.DistanceFrom(target) > 2).Random()); } else{ AI_Step(safe.Random()); } } QS(); } } else{ if(!HasAttr(AttrType.COOLDOWN_2) && thrall.attrs[AttrType.ARCANE_SHIELDED] < 25){ GainAttr(AttrType.COOLDOWN_2,1500); B.Add(TheName(true) + " shields " + thrall.TheName(true) + ". ",this,thrall); B.DisplayNow(); Screen.AnimateStorm(thrall.p,1,2,5,'*',Color.White); thrall.attrs[AttrType.ARCANE_SHIELDED] = 25; Q1(); } else{ List<Tile> safe = target.GetBestExtendedLineOfEffect(thrall).Where(t=>t.passable && t.actor() == null).WhereLeast(t=>DistanceFrom(t)); if(safe.Count > 0){ if(safe.Any(t=>t.DistanceFrom(target) > 2)){ AI_Step(safe.Where(t=>t.DistanceFrom(target) > 2).Random()); } else{ AI_Step(safe.Random()); } } QS(); } } } }*/ } else{ group[1].FindPath(this); //call for help if(AI_Flee()){ QS(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ QS(); } } } } break; case ActorType.ORC_GRENADIER: if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 8){ attrs[AttrType.COOLDOWN_1]++; Q.Add(new Event(this,(R.Roll(2)*100)+150,AttrType.COOLDOWN_1)); B.Add(TheName(true) + " tosses a grenade toward " + target.the_name + ". ",target); List<Tile> tiles = new List<Tile>(); foreach(Tile tile in target.TilesWithinDistance(1)){ if(tile.passable && !tile.Is(FeatureType.GRENADE)){ tiles.Add(tile); } } Tile t = tiles[R.Roll(tiles.Count)-1]; if(t.actor() != null){ if(t.actor() == player){ B.Add("It lands under you! "); } else{ B.Add("It lands under " + t.actor().the_name + ". ",t.actor()); } } else{ if(t.inv != null){ B.Add("It lands under " + t.inv.TheName() + ". ",t); } } t.features.Add(FeatureType.GRENADE); Q.Add(new Event(t,100,EventType.GRENADE)); Q1(); } else{ if(curhp <= 18){ if(AI_Step(target,true)){ QS(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ QS(); } } } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } } break; case ActorType.MARBLE_HORROR: if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } break; case ActorType.SPELLMUDDLE_PIXIE: if(DistanceFrom(target) == 1){ Attack(0,target); if(target != null && R.CoinFlip()){ AI_Step(target,true); } } else{ AI_Step(target); QS(); } break; case ActorType.OGRE_BARBARIAN: //if has grabbed target, check for open spaces near the opposite side. //if one is found, slam target into that tile, then do the attack. //otherwise, slam target into a solid tile (target doesn't move), then attack. //if nothing is grabbed yet, just keep attacking. if(DistanceFrom(target) == 1){ if(target.HasAttr(AttrType.GRABBED) && attrs[AttrType.GRABBING] == DirectionOf(target) && !target.MovementPrevented(tile())){ Tile t = null; Tile opposite = TileInDirection(DirectionOf(target).RotateDir(true,4)); if(opposite.passable && opposite.actor() == null){ t = opposite; } if(t == null){ List<Tile> near_opposite = new List<Tile>(); foreach(int i in new int[]{-1,1}){ Tile near = TileInDirection(DirectionOf(target).RotateDir(true,4+i)); if(near.passable && near.actor() == null){ near_opposite.Add(near); } } if(near_opposite.Count > 0){ t = near_opposite.Random(); } } if(t != null){ target.attrs[AttrType.TURN_INTO_CORPSE]++; Attack(1,target); target.Move(t.row,t.col); target.CollideWith(target.tile()); target.CorpseCleanup(); } else{ target.attrs[AttrType.TURN_INTO_CORPSE]++; Attack(1,target); target.CollideWith(target.tile()); target.CorpseCleanup(); } } else{ Attack(0,target); } } else{ if(speed == 100){ speed = 50; } if(!HasAttr(AttrType.COOLDOWN_1) && target == player && player.CanSee(this)){ B.Add(the_name + " charges! "); attrs[AttrType.COOLDOWN_1] = 1; } AI_Step(target); if(!HasAttr(AttrType.COOLDOWN_1) && target == player && player.CanSee(this)){ //check twice so the message appears ASAP B.Add(the_name + " charges! "); attrs[AttrType.COOLDOWN_1] = 1; } QS(); } break; case ActorType.MARBLE_HORROR_STATUE: QS(); break; case ActorType.PYREN_ARCHER: //still considering some sort of fire trail movement ability for this guy switch(DistanceFrom(target)){ case 1: if(target.EnemiesAdjacent() > 1){ Attack(0,target); } else{ if(AI_Flee()){ QS(); } else{ Attack(0,target); } } break; case 2: if(FirstActorInLine(target) == target){ FireArrow(target); } else{ if(AI_Flee()){ QS(); } else{ if(AI_Sidestep(target)){ B.Add(the_name + " tries to line up a shot. ",this); } QS(); } } break; case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: if(FirstActorInLine(target) == target){ FireArrow(target); } else{ if(AI_Sidestep(target)){ B.Add(the_name + " tries to line up a shot. ",this); } QS(); } break; default: AI_Step(target); QS(); break; } break; case ActorType.CYCLOPEAN_TITAN: { if(DistanceFrom(target) == 1){ Attack(0,target); } else{ if(DistanceFrom(target) > 2 && DistanceFrom(target) <= 12 && R.OneIn(15) && FirstActorInLine(target) == target){ B.Add(TheName(true) + " lobs a huge rock! ",this,target); AnimateProjectile(target,'*',Color.Gray); pos tp = target.p; int plus_to_hit = -target.TotalSkill(SkillType.DEFENSE)*3; if(target.IsHit(plus_to_hit)){ B.Add("It hits " + target.the_name + "! ",target); if(target.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(4,6),this,"a cyclopean titan's rock")){ if(R.OneIn(8)){ target.ApplyStatus(AttrType.STUNNED,R.Between(3,4)*100); } } } else{ int armor_value = target.TotalProtectionFromArmor(); if(target != player){ armor_value = target.TotalSkill(SkillType.DEFENSE); //if monsters have Defense skill, it's from armor } int roll = R.Roll(25 - plus_to_hit); if(roll <= armor_value * 3){ B.Add(target.Your() + " armor blocks it! ",target); } else{ if(target.HasAttr(AttrType.ROOTS) && roll <= (armor_value + 10) * 3){ //potion of roots gives 10 defense B.Add(target.Your() + " root shell blocks it! ",target); } else{ B.Add(target.You("avoid") + " it! ",target); } } } foreach(pos neighbor in tp.PositionsWithinDistance(1,M.tile)){ Tile t = M.tile[neighbor]; if(t.Is(TileType.FLOOR) && R.OneIn(4)){ t.Toggle(null,TileType.GRAVEL); } } Q1(); } else{ bool smashed = false; if(DistanceFrom(target) == 2 && !HasLOE(target)){ Tile t = FirstSolidTileInLine(target); if(t != null && !t.passable){ smashed = true; B.Add(You("smash",true) + " through " + t.TheName(true) + "! ",t); foreach(int dir in DirectionOf(t).GetArc(1)){ TileInDirection(dir).Smash(dir); } Move(t.row,t.col); QS(); } } if(!smashed){ AI_Step(target); QS(); } } } break; } case ActorType.ALASI_SENTINEL: if(DistanceFrom(target) == 1){ Attack(0,target); if(HasAttr(AttrType.JUST_FLUNG)){ attrs[AttrType.JUST_FLUNG] = 0; } else{ if(target != null){ List<Tile> valid_dirs = new List<Tile>(); foreach(Tile t in target.TilesAtDistance(1)){ if(t.passable && t.actor() == null && DistanceFrom(t) == 1){ valid_dirs.Add(t); } } if(valid_dirs.Count > 0){ AI_Step(valid_dirs.Random()); } } } } else{ AI_Step(target); QS(); } break; case ActorType.NOXIOUS_WORM: if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12 && HasLOE(target)){ B.Add(TheName(true) + " breathes poisonous gas. "); List<Tile> area = new List<Tile>(); foreach(Tile t in target.TilesWithinDistance(1)){ if(t.passable && target.HasLOE(t)){ t.AddFeature(FeatureType.POISON_GAS); area.Add(t); } } List<Tile> area2 = target.tile().AddGaseousFeature(FeatureType.POISON_GAS,8); area.AddRange(area2); Event.RemoveGas(area,600,FeatureType.POISON_GAS,18); RefreshDuration(AttrType.COOLDOWN_1,(R.Roll(6) + 18) * 100); Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; case ActorType.LASHER_FUNGUS: { if(DistanceFrom(target) <= 12){ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ if(FirstActorInLine(target) == target){ List<Tile> line = GetBestLineOfEffect(target.row,target.col); line.Remove(line[line.Count-1]); AnimateBoltBeam(line,Color.DarkGreen); pos target_p = target.p; if(Attack(1,target) && M.actor[target_p] != null){ target = M.actor[target_p]; int rowchange = 0; int colchange = 0; if(target.row < row){ rowchange = 1; } if(target.row > row){ rowchange = -1; } if(target.col < col){ colchange = 1; } if(target.col > col){ colchange = -1; } if(!target.AI_MoveOrOpen(target.row+rowchange,target.col+colchange)){ bool moved = false; if(Math.Abs(target.row - row) > Math.Abs(target.col - col)){ if(target.AI_Step(M.tile[row,target.col])){ moved = true; } } else{ if(Math.Abs(target.row - row) < Math.Abs(target.col - col)){ if(target.AI_Step(M.tile[target.row,col])){ moved = true; } } else{ if(target.AI_Step(this)){ moved = true; } } } if(!moved){ //todo: this still isn't ideal. maybe I need an AI_Step that only considers 3 directions - right now, it'll make you move even if it isn't closer. B.Add(target.You("do",true) + "n't move far. ",target); } } } } else{ Q1(); } } } else{ Q1(); } break; } case ActorType.LUMINOUS_AVENGER: { if(DistanceFrom(target) <= 3){ List<Tile> ext = GetBestExtendedLineOfEffect(target); int max_count = Math.Min(5,ext.Count); //look 4 spaces away unless the line is even shorter than that. List<Actor> targets = new List<Actor>(); Tile destination = null; for(int i=0;i<max_count;++i){ Tile t = ext[i]; if(t.passable){ if(t.actor() == null){ if(targets.Contains(target)){ destination = t; } } else{ if(t.actor() != this){ targets.Add(t.actor()); } } } else{ break; } } if(destination != null){ Move(destination.row,destination.col); foreach(Tile t in ext.To(destination)){ colorchar cch = M.VisibleColorChar(t.row,t.col); cch.bgcolor = Color.Yellow; if(Global.LINUX && !Screen.GLMode){ cch.bgcolor = Color.DarkYellow; } if(cch.color == cch.bgcolor){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); Game.GLUpdate(); Thread.Sleep(15); } foreach(Actor a in targets){ Attack(0,a,true); } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } } else{ AI_Step(target); QS(); } break; } case ActorType.VAMPIRE: if(DistanceFrom(target) == 1){ Attack(0,target); } else{ if(DistanceFrom(target) <= 12){ if(tile().IsLit() && !HasAttr(AttrType.COOLDOWN_1)){ attrs[AttrType.COOLDOWN_1]++; B.Add(the_name + " gestures. ",this); List<Tile> tiles = new List<Tile>(); foreach(Tile t in target.TilesWithinDistance(6)){ if(t.passable && t.actor() == null && DistanceFrom(t) >= DistanceFrom(target) && target.HasLOS(t) && target.HasLOE(t)){ tiles.Add(t); } } if(tiles.Count == 0){ foreach(Tile t in target.TilesWithinDistance(6)){ //same, but with no distance requirement if(t.passable && t.actor() == null && target.HasLOS(t) && target.HasLOE(t)){ tiles.Add(t); } } } if(tiles.Count == 0){ B.Add("Nothing happens. ",this); } else{ if(tiles.Count == 1){ B.Add("A blood moth appears! "); } else{ B.Add("Blood moths appear! "); } for(int i=0;i<2;++i){ if(tiles.Count > 0){ Tile t = tiles.RemoveRandom(); Create(ActorType.BLOOD_MOTH,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent); M.actor[t.row,t.col].player_visibility_duration = -1; } } } Q1(); } else{ AI_Step(target); QS(); } } else{ AI_Step(target); QS(); } } break; case ActorType.ORC_WARMAGE: { if(SilencedThisTurn()){ return; } switch(DistanceFrom(target)){ case 1: { List<SpellType> close_range = new List<SpellType>(); close_range.Add(SpellType.MAGIC_HAMMER); close_range.Add(SpellType.MAGIC_HAMMER); close_range.Add(SpellType.BLINK); if(target.EnemiesAdjacent() > 1 || R.CoinFlip()){ CastCloseRangeSpellOrAttack(close_range,target,false); } else{ if(AI_Step(target,true)){ QS(); } else{ CastCloseRangeSpellOrAttack(close_range,target,false); } } break; } case 2: if(R.CoinFlip()){ if(AI_Step(target,true)){ QS(); } else{ if(FirstActorInLine(target) == target){ CastRangedSpellOrMove(target); } else{ AI_Sidestep(target); QS(); } } } else{ if(FirstActorInLine(target) == target){ CastRangedSpellOrMove(target); } else{ if(AI_Step(target,true)){ QS(); } else{ AI_Sidestep(target); QS(); } } } break; case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: if(FirstActorInLine(target) == target){ CastRangedSpellOrMove(target); } else{ AI_Sidestep(target); QS(); } break; default: AI_Step(target); QS(); break; } break; } case ActorType.NECROMANCER: { if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12){ attrs[AttrType.COOLDOWN_1]++; Q.Add(new Event(this,(R.Roll(4)+8)*100,AttrType.COOLDOWN_1)); B.Add(the_name + " calls out to the dead. ",this); ActorType summon = R.CoinFlip()? ActorType.SKELETON : ActorType.ZOMBIE; List<Tile> tiles = new List<Tile>(); foreach(Tile tile in TilesWithinDistance(2)){ if(tile.passable && tile.actor() == null && DirectionOf(tile) == DirectionOf(target)){ tiles.Add(tile); } } if(tiles.Count == 0){ foreach(Tile tile in TilesWithinDistance(2)){ if(tile.passable && tile.actor() == null){ tiles.Add(tile); } } } if(tiles.Count == 0 || (group != null && group.Count > 3)){ B.Add("Nothing happens. ",this); } else{ Tile t = tiles.Random(); B.Add(Prototype(summon).a_name + " digs through the floor! "); Create(summon,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent); M.actor[t.row,t.col].player_visibility_duration = -1; if(group == null){ group = new List<Actor>{this}; } group.Add(M.actor[t.row,t.col]); M.actor[t.row,t.col].group = group; } Q1(); } else{ bool blast = false; switch(DistanceFrom(target)){ case 1: if(AI_Step(target,true)){ QS(); } else{ Attack(0,target); } break; case 2: if(R.CoinFlip() && FirstActorInLine(target) == target){ blast = true; } else{ if(AI_Step(target,true)){ QS(); } else{ blast = true; } } break; case 3: case 4: case 5: case 6: if(FirstActorInLine(target) == target){ blast = true; } else{ AI_Sidestep(target); QS(); } break; default: AI_Step(target); QS(); break; } if(blast){ B.Add(TheName(true) + " fires dark energy at " + target.TheName(true) + ". ",this,target); AnimateBoltProjectile(target,Color.DarkBlue); if(target.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(6),this,"*blasted by a necromancer")){ target.IncreaseExhaustion(R.Roll(3)); } Q1(); } } break; } case ActorType.STALKING_WEBSTRIDER: { bool burrow = false; if(DistanceFrom(target) >= 2 && DistanceFrom(target) <= 6){ if(R.CoinFlip() && !target.tile().Is(FeatureType.WEB)){ burrow = true; } } if((DistanceFrom(target) > 6 || target.HasAttr(AttrType.POISONED))){ burrow = true; } if(burrow && !HasAttr(AttrType.COOLDOWN_1)){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(5,8)*100); if(DistanceFrom(target) <= 2){ Burrow(TilesWithinDistance(6)); } else{ Burrow(GetCone(DirectionOf(target),6,true)); } } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; } case ActorType.ORC_ASSASSIN: if(DistanceFrom(target) > 2 && attrs[AttrType.TURNS_VISIBLE] < 0){ Tile t = TilesAtDistance(1).Where(x=>x.passable && x.actor() == null && target.DistanceFrom(x) == target.DistanceFrom(this)-1 && !target.CanSee(x)).RandomOrDefault(); if(t != null){ AI_Step(t); FindPath(target); //so it won't forget where the target is... QS(); } else{ AI_Step(target); QS(); } } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); if(DistanceFrom(target) == 1){ Attack(1,target); } else{ QS(); } } } break; case ActorType.MACHINE_OF_WAR: { if(attrs[AttrType.COOLDOWN_1] % 2 == 0){ //the machine of war moves on even turns and fires on odd turns. AI_Step(target); QS(); } else{ if(DistanceFrom(target) <= 12 && FirstActorInLine(target) == target){ B.Add(TheName(true) + " fires a stream of scalding oil at " + target.the_name + ". ",target); List<Tile> covered_in_oil = GetBestLineOfEffect(target); List<Tile> added = new List<Tile>(); foreach(Tile t in covered_in_oil){ foreach(int dir in U.FourDirections){ Tile neighbor = t.TileInDirection(dir); if(R.OneIn(3) && neighbor.passable && !covered_in_oil.Contains(neighbor)){ added.AddUnique(neighbor); } } } covered_in_oil.AddRange(added); List<pos> cells = new List<pos>(); List<Actor> oiled_actors = new List<Actor>(); for(int i=0;covered_in_oil.Count > 0;++i){ List<Tile> removed = new List<Tile>(); foreach(Tile t in covered_in_oil){ if(DistanceFrom(t) == i){ t.AddFeature(FeatureType.OIL); if(t.actor() != null && t.actor() != this){ oiled_actors.Add(t.actor()); } removed.Add(t); if(DistanceFrom(t) > 0){ cells.Add(t.p); } } } foreach(Tile t in removed){ covered_in_oil.Remove(t); } if(cells.Count > 0){ Screen.AnimateMapCells(cells,new colorchar(',',Color.DarkYellow),20); } } oiled_actors.AddUnique(target); M.Draw(); foreach(Actor a in oiled_actors){ if(a.TakeDamage(DamageType.FIRE,DamageClass.PHYSICAL,R.Roll(4,6),this,"a stream of scalding oil")){ if(a.IsBurning()){ a.ApplyBurning(); } else{ if(!a.HasAttr(AttrType.SLIMED,AttrType.FROZEN)){ a.attrs[AttrType.OIL_COVERED]++; B.Add(a.YouAre() + " covered in oil. ",a); } } } } Q1(); } else{ Q1(); } } break; } case ActorType.IMPOSSIBLE_NIGHTMARE: { if(DistanceFrom(target) == 1){ Attack(0,target); } else{ Tile t = target.TilesAtDistance(DistanceFrom(target)-1).Where(x=>x.passable && x.actor() == null).RandomOrDefault(); if(t != null){ Move(t.row,t.col); //todo: fear effect? } QS(); } break; } case ActorType.FIRE_DRAKE: /*if(player.magic_trinkets.Contains(MagicTrinketType.RING_OF_RESISTANCE) && DistanceFrom(player) <= 12 && CanSee(player)){ B.Add(the_name + " exhales an orange mist toward you. "); foreach(Tile t in GetBestLineOfEffect(player)){ Screen.AnimateStorm(t.p,1,2,3,'*',Color.Red); } B.Add("Your ring of resistance melts and drips onto the floor! "); player.magic_trinkets.Remove(MagicTrinketType.RING_OF_RESISTANCE); Q.Add(new Event(this,100,EventType.MOVE)); } else{ if(player.EquippedArmor == ArmorType.FULL_PLATE_OF_RESISTANCE && DistanceFrom(player) <= 12 && CanSee(player)){ B.Add(the_name + " exhales an orange mist toward you. "); foreach(Tile t in GetBestLine(player)){ Screen.AnimateStorm(t.p,1,2,3,'*',Color.Red); } B.Add("The runes drip from your full plate of resistance! "); player.EquippedArmor = ArmorType.FULL_PLATE; player.UpdateOnEquip(ArmorType.FULL_PLATE_OF_RESISTANCE,ArmorType.FULL_PLATE); Q.Add(new Event(this,100,EventType.MOVE)); } else{*/ if(!HasAttr(AttrType.COOLDOWN_1)){ if(DistanceFrom(target) <= 12){ attrs[AttrType.COOLDOWN_1]++; int cooldown = (R.Roll(1,4)+1) * 100; Q.Add(new Event(this,cooldown,AttrType.COOLDOWN_1)); AnimateBeam(target,Color.RandomFire,'*'); B.Add(TheName(true) + " breathes fire. ",target); target.TakeDamage(DamageType.FIRE,DamageClass.PHYSICAL,R.Roll(6,6),this,"*roasted by fire breath"); target.ApplyBurning(); Q.Add(new Event(this,200,EventType.MOVE)); } else{ AI_Step(target); QS(); } } else{ if(DistanceFrom(target) == 1){ Attack(R.Roll(1,2)-1,target); } else{ AI_Step(target); QS(); } } //} //} break; case ActorType.GHOST: { attrs[AttrType.AGGRESSION_MESSAGE_PRINTED] = 1; bool tombstone = false; foreach(Tile t in TilesWithinDistance(1)){ if(t.type == TileType.TOMBSTONE){ tombstone = true; } } if(!tombstone){ B.Add("The ghost vanishes. ",this); Kill(); return; } if(target == null || DistanceFrom(target) > 2){ List<Tile> valid = TilesAtDistance(1).Where(x=>x.TilesWithinDistance(1).Any(y=>y.type == TileType.TOMBSTONE)); if(valid.Count > 0){ AI_Step(valid.Random()); } QS(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ List<Tile> valid = tile().NeighborsBetween(target.row,target.col).Where(x=>x.passable && x.actor() == null && x.TilesWithinDistance(1).Any(y=>y.type == TileType.TOMBSTONE)); if(valid.Count == 0){ valid = TilesAtDistance(1).Where(x=>x.TilesWithinDistance(1).Any(y=>y.type == TileType.TOMBSTONE)); } if(valid.Count > 0){ AI_Step(valid.Random()); } QS(); } } break; } case ActorType.BLADE: { attrs[AttrType.AGGRESSION_MESSAGE_PRINTED] = 1; List<Actor> valid_targets = new List<Actor>(); //this is based on EnragedMove(), with an exception for other blades int max_dist = Math.Max(Math.Max(row,col),Math.Max(ROWS-row,COLS-col)); //this should find the farthest edge of the map for(int i=1;i<max_dist && valid_targets.Count == 0;++i){ foreach(Actor a in ActorsAtDistance(i)){ if(a.type != ActorType.BLADE && CanSee(a) && HasLOE(a)){ valid_targets.Add(a); } } } if(valid_targets.Count > 0){ if(target == null || !valid_targets.Contains(target)){ //keep old target if possible target = valid_targets.Random(); } if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } else{ if(target != null){ SeekAI(); } else{ QS(); } } break; } case ActorType.PHANTOM_CONSTRICTOR: case ActorType.PHANTOM_WASP: { if(DistanceFrom(target) == 1){ Attack(0,target); } else{ List<Tile> tiles = new List<Tile>(); //i should turn this "slither" movement into a standardized attribute or something if(target.row == row || target.col == col){ int targetdir = DirectionOf(target); for(int i=-1;i<=1;++i){ pos adj = p.PosInDir(targetdir.RotateDir(true,i)); if(M.tile[adj].passable && M.actor[adj] == null){ tiles.Add(M.tile[adj]); } } } if(tiles.Count > 0){ AI_Step(tiles.Random()); } else{ AI_Step(target); } QS(); } break; } case ActorType.MINOR_DEMON: case ActorType.FROST_DEMON: case ActorType.BEAST_DEMON: case ActorType.DEMON_LORD: { int damage_threshold = 1; if(type == ActorType.BEAST_DEMON){ damage_threshold = 0; } if(target == player && attrs[AttrType.COOLDOWN_2] > damage_threshold && CanSee(target)){ switch(type){ case ActorType.MINOR_DEMON: case ActorType.BEAST_DEMON: if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } break; case ActorType.FROST_DEMON: if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12 && FirstActorInLine(target) == target){ attrs[AttrType.COOLDOWN_1] = 1; AnimateProjectile(target,'*',Color.RandomIce); foreach(Tile t in GetBestLineOfEffect(target)){ t.ApplyEffect(DamageType.COLD); } B.Add(TheName(true) + " fires a chilling sphere. ",target); if(target.TakeDamage(DamageType.COLD,DamageClass.PHYSICAL,R.Roll(3,6),this,"a frost demon")){ target.ApplyStatus(AttrType.SLOWED,R.Between(4,7)*100); //target.RefreshDuration(AttrType.SLOWED,R.Between(4,7)*100,target.YouAre() + " no longer slowed. ",target); } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; case ActorType.DEMON_LORD: if(DistanceFrom(target) > 2){ AI_Step(target); QS(); } else{ if(FirstActorInLine(target) != null){ Attack(0,target); } else{ if(AI_Step(target)){ QS(); } else{ AI_Sidestep(target); QS(); } } } break; } } else{ if(row >= 7 && row <= 12 && col >= 30 && col <= 35){ //near the center foreach(Actor a in ActorsAtDistance(1)){ if(a.IsFinalLevelDemon()){ List<Tile> dist2 = new List<Tile>(); foreach(Tile t in TilesWithinDistance(5)){ if(t.TilesAtDistance(2).Any(x=>x.type == TileType.FIRE_RIFT) && !t.TilesAtDistance(1).Any(x=>x.type == TileType.FIRE_RIFT)){ dist2.Add(t); } } //if there's another distance 2 (from the center) tile with no adjacent demons, move there //List<Tile> valid = dist2.Where(x=>DistanceFrom(x) == 1 && x.actor() == null && !x.TilesAtDistance(1).Any(y=>y.actor() != null && y.actor().Is(ActorType.MINOR_DEMON,ActorType.FROST_DEMON,ActorType.BEAST_DEMON,ActorType.DEMON_LORD))); List<Tile> valid = dist2.Where(x=>DistanceFrom(x) == 1); valid = valid.Where(x=>x.actor() == null && !x.TilesAtDistance(1).Any(y=>y.actor() != null && y.actor() != this && y.actor().IsFinalLevelDemon())); if(valid.Count > 0){ AI_Step(valid.Random()); } break; } } if(player.HasLOS(this)){ B.Add(TheName(true) + " chants. ",this); } M.IncrementClock(); Q1(); } else{ if(path != null && path.Count > 0){ if(!PathStep()){ QS(); } } else{ FindPath(9+R.Between(0,1),32+R.Between(0,1)); if(!PathStep()){ QS(); } } } } break; } default: if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } break; } }
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; }
public async Task Execute(){ if(!dead){ switch(evtype){ case EventType.MOVE: { Actor temp = target as Actor; await temp.Input(); break; } case EventType.REMOVE_ATTR: { Actor temp = target as Actor; if(temp.atype == ActorType.BERSERKER && attr == AttrType.COOLDOWN_2){ temp.attrs[attr] = 0; } else{ temp.attrs[attr] -= value; } if(attr == AttrType.TELEPORTING || attr == AttrType.ARCANE_SHIELDED){ temp.attrs[attr] = 0; } if(attr==AttrType.ENHANCED_TORCH && temp.light_radius > 0){ temp.UpdateRadius(temp.LightRadius(),6 - temp.attrs[AttrType.DIM_LIGHT],true); //where 6 is the default radius if(temp.attrs[AttrType.ON_FIRE] > temp.light_radius){ temp.UpdateRadius(temp.light_radius,temp.attrs[AttrType.ON_FIRE]); } } if(attr==AttrType.SLOWED){ if(temp.atype != ActorType.PLAYER){ temp.speed = Actor.Prototype(temp.atype).speed; } else{ if(temp.HasAttr(AttrType.LONG_STRIDE)){ temp.speed = 80; } else{ temp.speed = 100; } } } if(attr==AttrType.AFRAID && target == player){ Global.FlushInput(); } if(attr==AttrType.BLOOD_BOILED){ temp.speed += (10 * value); } if(attr==AttrType.CONVICTION){ if(temp.HasAttr(AttrType.IN_COMBAT)){ temp.attrs[Forays.AttrType.CONVICTION] += value; //whoops, undo that } else{ temp.attrs[Forays.AttrType.BONUS_SPIRIT] -= value; //otherwise, set things to normal temp.attrs[Forays.AttrType.BONUS_COMBAT] -= value / 2; if(temp.attrs[Forays.AttrType.KILLSTREAK] >= 2){ B.Add("You wipe off your weapon. "); } temp.attrs[Forays.AttrType.KILLSTREAK] = 0; } } if(attr==AttrType.STUNNED && msg.Search(new System.Text.RegularExpressions.Regex("disoriented")) > 0){ if(!player.CanSee(target)){ msg = ""; } } if(attr==AttrType.POISONED && temp == player){ if(temp.HasAttr(AttrType.POISONED)){ B.Add("The poison begins to subside. "); } else{ B.Add("You are no longer poisoned. "); } } if(attr==AttrType.COOLDOWN_1 && temp.atype == ActorType.BERSERKER){ B.Add(temp.Your() + " rage diminishes. ",temp); B.Add(temp.the_name + " dies. ",temp); await temp.TakeDamage(DamageType.NORMAL, DamageClass.NO_TYPE, 8888, null); } break; } case EventType.CHECK_FOR_HIDDEN: { List<Tile> removed = new List<Tile>(); foreach(Tile t in area){ if(player.CanSee(t)){ int exponent = player.DistanceFrom(t) + 1; if(player.HasAttr(AttrType.KEEN_EYES)){ --exponent; } if(!t.IsLit()){ if(!player.HasAttr(AttrType.SHADOWSIGHT)){ ++exponent; } } if(exponent > 8){ exponent = 8; //because 1 in 256 is enough. } int difficulty = 1; for(int i=exponent;i>0;--i){ difficulty = difficulty * 2; } if(Global.Roll(difficulty) == difficulty){ if(t.IsTrap() || t.Is(TileType.FIRE_GEYSER) || t.Is(TileType.FOG_VENT) || t.Is(TileType.POISON_GAS_VENT)){ t.name = Tile.Prototype(t.ttype).name; t.a_name = Tile.Prototype(t.ttype).a_name; t.the_name = Tile.Prototype(t.ttype).the_name; t.symbol = Tile.Prototype(t.ttype).symbol; t.color = Tile.Prototype(t.ttype).color; B.Add("You notice " + t.a_name + ". "); } else{ if(t.ttype == TileType.HIDDEN_DOOR){ t.Toggle(null); B.Add("You notice a hidden door. "); } } removed.Add(t); } } } foreach(Tile t in removed){ area.Remove(t); } if(area.Count > 0){ Q.Add(new Event(area,100,EventType.CHECK_FOR_HIDDEN)); } break; } case EventType.RELATIVELY_SAFE: { if(M.AllActors().Count == 1 && !Q.Contains(EventType.POLTERGEIST) && !Q.Contains(EventType.BOSS_ARRIVE) && !Q.Contains(EventType.REGENERATING_FROM_DEATH) && !Q.Contains(EventType.MIMIC) && !Q.Contains(EventType.MARBLE_HORROR)){ B.Add("The dungeon is still and silent. "); await B.PrintAll(); } else{ Q.Add(new Event((Global.Roll(20)+40)*100,EventType.RELATIVELY_SAFE)); } break; } case EventType.POLTERGEIST: { if(target != null && target is Actor){ //target can either be a stolen item, or the currently manifested poltergeist. Q.Add(new Event(target,area,(Global.Roll(8)+6)*100,EventType.POLTERGEIST,AttrType.NO_ATTR,0,"")); break; //if it's manifested, the event does nothing for now. } if(area.Any(t => t.actor() == player)){ bool manifested = false; if(value == 0){ B.Add("You feel like you're being watched. "); } else{ if(target != null){ //if it has a stolen item Tile tile = null; tile = area.Where(t => t.actor() == null && t.DistanceFrom(player) >= 2 && t.HasLOE(player) && t.FirstActorInLine(player) == player).Random(); if(tile != null){ Actor temporary = new Actor(ActorType.POLTERGEIST,"something","G",Color.DarkGreen,1,1,0,0); temporary.a_name = "something"; temporary.the_name = "something"; temporary.p = tile.p; temporary.inv = new List<Item>(); temporary.inv.Add(target as Item); Item item = temporary.inv[0]; if(item.symbol == "*"){ //orbs if(item.itype == ConsumableType.SUNLIGHT || item.itype == ConsumableType.DARKNESS){ B.Add(temporary.You("throw") + " " + item.AName() + ". ",temporary); B.DisplayNow(); Screen.AnimateProjectile(tile.GetBestExtendedLineOfEffect(player).ToFirstObstruction(),new colorchar(item.color,item.symbol)); B.Add(item.TheName() + " shatters on you! "); } await temporary.inv[0].Use(temporary,temporary.GetBestExtendedLineOfEffect(player)); } else{ B.Add(temporary.You("throw") + " " + item.AName() + ". ",temporary); B.DisplayNow(); Screen.AnimateProjectile(tile.GetBestExtendedLineOfEffect(player).ToFirstObstruction(),new colorchar(item.color,item.symbol)); player.tile().GetItem(item); B.Add(item.TheName() + " hits you. "); await player.TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(6), temporary, "a flying " + item.Name()); } target = null; } else{ Q.Add(new Event(target,area,100,EventType.POLTERGEIST,AttrType.NO_ATTR,value,"")); return; //try again next turn } } else{ if(value >= 3 && area.Any(t => t.DistanceFrom(player) == 1 && t.passable && t.actor() == null)){ Tile tile = area.Where(t => t.DistanceFrom(player) == 1 && t.passable && t.actor() == null).Random(); B.DisplayNow(); for(int i=4;i>0;--i){ Screen.AnimateStorm(tile.p,i,2,1,"G",Color.DarkGreen); } Actor a = Actor.Create(ActorType.POLTERGEIST,tile.row,tile.col); Q.KillEvents(a,EventType.MOVE); a.Q0(); a.player_visibility_duration = -1; foreach(Event e in Q.list){ if( e != null && e.target != null && e.target == a && e.evtype == EventType.MOVE){ e.tiebreaker = this.tiebreaker; break; } } Actor.tiebreakers[tiebreaker] = a; B.Add("A poltergeist manifests in front of you! "); Q.Add(new Event(a,area,(Global.Roll(8)+6)*100,EventType.POLTERGEIST,AttrType.NO_ATTR,0,"")); manifested = true; } else{ if(player.tile().ttype == TileType.DOOR_O){ B.Add("The door slams closed on you! "); await player.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,Global.Roll(6),null,"a slamming door"); } else{ Tile tile = null; //check for items to throw... tile = area.Where(t => t.inv != null && t.actor() == null && t.DistanceFrom(player) >= 2 && t.HasLOE(player) && t.FirstActorInLine(player) == player).Random(); if(tile != null){ Actor temporary = new Actor(ActorType.POLTERGEIST,"something","G",Color.DarkGreen,1,1,0,0); temporary.a_name = "something"; temporary.the_name = "something"; temporary.p = tile.p; temporary.inv = new List<Item>(); if(tile.inv.quantity <= 1){ temporary.inv.Add(tile.inv); tile.inv = null; } else{ temporary.inv.Add(new Item(tile.inv,-1,-1)); tile.inv.quantity--; } M.Draw(); Item item = temporary.inv[0]; if(item.symbol == "*"){ //orbs if(item.itype == ConsumableType.SUNLIGHT || item.itype == ConsumableType.DARKNESS){ B.Add(temporary.You("throw") + " " + item.TheName() + ". ",temporary); B.DisplayNow(); Screen.AnimateProjectile(tile.GetBestExtendedLineOfEffect(player).ToFirstObstruction(),new colorchar(item.color,item.symbol)); B.Add(item.TheName() + " shatters on you! "); } await temporary.inv[0].Use(temporary, temporary.GetBestExtendedLineOfEffect(player)); } else{ B.Add(temporary.You("throw") + " " + item.TheName() + ". ",temporary); B.DisplayNow(); Screen.AnimateProjectile(tile.GetBestExtendedLineOfEffect(player).ToFirstObstruction(),new colorchar(item.color,item.symbol)); player.tile().GetItem(item); B.Add(item.TheName() + " hits you. "); await player.TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(6), temporary, "a flying " + item.Name()); } } else{ if(area.Any(t => t.ttype == TileType.DOOR_O || t.ttype == TileType.DOOR_C)){ Tile door = area.Where(t=>t.ttype == TileType.DOOR_O || t.ttype == TileType.DOOR_C).Random(); if(door.ttype == TileType.DOOR_C){ if(player.CanSee(door)){ B.Add("The door flies open! ",door); } else{ if(door.seen || player.DistanceFrom(door) <= 12){ B.Add("You hear a door slamming. "); } } door.Toggle(null); } else{ if(door.actor() == null){ if(player.CanSee(door)){ B.Add("The door slams closed! ",door); } else{ if(door.seen || player.DistanceFrom(door) <= 12){ B.Add("You hear a door slamming. "); } } door.Toggle(null); } else{ if(player.CanSee(door)){ B.Add("The door slams closed on " + door.actor().TheVisible() + "! ",door); } else{ if(player.DistanceFrom(door) <= 12){ B.Add("You hear a door slamming and a grunt of pain. "); } } await door.actor().TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(6), null, "a slamming door"); } } } else{ B.Add("You hear mocking laughter from nearby. "); } } } } } } if(!manifested){ Q.Add(new Event(target,area,(Global.Roll(8)+6)*100,EventType.POLTERGEIST,AttrType.NO_ATTR,value+1,"")); } } else{ Q.Add(new Event(target,area,(Global.Roll(8)+6)*100,EventType.POLTERGEIST,AttrType.NO_ATTR,0,"")); } break; } case EventType.MIMIC: { Item item = target as Item; if(area[0].inv != item){ //it could have been picked up by the player or moved in another way foreach(Tile t in M.AllTiles()){ //if it was moved, make the correction to the event's area. if(t.inv == item){ area = new List<Tile>{t}; break; } } } if(area[0].inv == item){ bool attacked = false; if(player.DistanceFrom(area[0]) == 1 && area[0].actor() == null){ if(player.Stealth() * 5 < Global.Roll(1,100)){ B.Add(item.TheName() + " suddenly grows tentacles! "); attacked = true; area[0].inv = null; Actor a = Actor.Create(ActorType.MIMIC,area[0].row,area[0].col); Q.KillEvents(a,EventType.MOVE); a.Q0(); a.player_visibility_duration = -1; a.symbol = item.symbol; a.color = item.color; foreach(Event e in Q.list){ if(e.target == a && e.evtype == EventType.MOVE){ e.tiebreaker = this.tiebreaker; break; } } Actor.tiebreakers[tiebreaker] = a; } } if(!attacked){ Q.Add(new Event(target,area,100,EventType.MIMIC,AttrType.NO_ATTR,0,"")); } } else{ //if the item is missing, we assume that the player just picked it up List<Tile> open = new List<Tile>(); foreach(Tile t in player.TilesAtDistance(1)){ if(t.passable && t.actor() == null){ open.Add(t); } } if(open.Count > 0){ Tile t = open.Random(); B.Add(item.TheName() + " suddenly grows tentacles! "); Actor a = Actor.Create(ActorType.MIMIC,t.row,t.col); Q.KillEvents(a,EventType.MOVE); a.Q0(); a.player_visibility_duration = -1; a.symbol = item.symbol; a.color = item.color; foreach(Event e in Q.list){ if(e.target == a && e.evtype == EventType.MOVE){ e.tiebreaker = this.tiebreaker; break; } } Actor.tiebreakers[tiebreaker] = a; player.inv.Remove(item); } else{ B.Add("Your pack feels lighter. "); player.inv.Remove(item); } } break; } case EventType.GRENADE: { Tile t = target as Tile; if(t.Is(FeatureType.GRENADE)){ t.features.Remove(FeatureType.GRENADE); B.Add("The grenade explodes! ",t); if(t.seen){ Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col)); } B.DisplayNow(); List<pos> cells = new List<pos>(); foreach(Tile tile in t.TilesWithinDistance(1)){ if(tile.passable && tile.seen){ cells.Add(tile.p); } } Screen.AnimateMapCells(cells,new colorchar('*',Color.DarkRed)); //Screen.AnimateExplosion(t,1,new colorchar('*',Color.DarkRed)); foreach(Actor a in t.ActorsWithinDistance(1)){ await a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,Global.Roll(3,6),null,"an exploding grenade"); } if(t.actor() != null){ int dir = Global.RandomDirection(); await t.actor().GetKnockedBack(t.TileInDirection(t.actor().RotateDirection(dir,true,4))); } if(player.DistanceFrom(t) <= 3){ player.MakeNoise(); //hacky - todo change } } break; } case EventType.BLAST_FUNGUS: { Tile t = target as Tile; if(t.Is(FeatureType.FUNGUS_PRIMED)){ t.features.Remove(FeatureType.FUNGUS_PRIMED); B.Add("The blast fungus explodes! ",t); if(t.seen){ Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col)); } B.DisplayNow(); for(int i=1;i<=3;++i){ List<pos> cells = new List<pos>(); foreach(Tile tile in t.TilesWithinDistance(i)){ if(t.HasLOE(tile) && tile.passable && tile.seen){ cells.Add(tile.p); } } Screen.AnimateMapCells(cells,new colorchar('*',Color.DarkRed)); } foreach(Actor a in t.ActorsWithinDistance(3)){ if(t.HasLOE(a)){ await a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,Global.Roll(5,6),null,"an exploding blast fungus"); } } if(t.actor() != null){ int dir = Global.RandomDirection(); await t.actor().GetKnockedBack(t.TileInDirection(t.actor().RotateDirection(dir,true,4))); } if(player.DistanceFrom(t) <= 3){ player.MakeNoise(); //hacky - todo change } } if(t.Is(FeatureType.FUNGUS_ACTIVE)){ t.features.Remove(FeatureType.FUNGUS_ACTIVE); t.features.Add(FeatureType.FUNGUS_PRIMED); Q.Add(new Event(t,100,EventType.BLAST_FUNGUS)); } break; } case EventType.STALAGMITE: { int stalagmites = 0; foreach(Tile tile in area){ if(tile.ttype == TileType.STALAGMITE){ stalagmites++; } } if(stalagmites > 0){ if(stalagmites > 1){ B.Add("The stalagmites crumble. ",area.ToArray()); } else{ B.Add("The stalagmite crumbles. ",area.ToArray()); } foreach(Tile tile in area){ if(tile.ttype == TileType.STALAGMITE){ tile.Toggle(null); } } } break; } case EventType.FIRE_GEYSER: { int frequency = value / 10; //5-25 int variance = value % 10; //0-9 int variance_amount = (frequency * variance) / 10; int number_of_values = variance_amount*2 + 1; int minimum_value = frequency - variance_amount; if(minimum_value < 5){ int diff = 5 - minimum_value; number_of_values -= diff; minimum_value = 5; } int delay = ((minimum_value - 1) + Global.Roll(number_of_values)) * 100; Q.Add(new Event(target,delay+200,EventType.FIRE_GEYSER,value)); Q.Add(new Event(target,delay,EventType.FIRE_GEYSER_ERUPTION,2)); break; } case EventType.FIRE_GEYSER_ERUPTION: { if(target.name == "floor"){ Event hiddencheck = null; Tile t = target as Tile; foreach(Event e in Q.list){ if(!e.dead && e.evtype == EventType.CHECK_FOR_HIDDEN){ hiddencheck = e; break; } } if(player.HasLOS(t)){ //t.seen = true; if(hiddencheck != null){ hiddencheck.area.Remove(t); } t.name = Tile.Prototype(t.ttype).name; t.a_name = Tile.Prototype(t.ttype).a_name; t.the_name = Tile.Prototype(t.ttype).the_name; t.symbol = Tile.Prototype(t.ttype).symbol; t.color = Tile.Prototype(t.ttype).color; } } if(value >= 0){ //a value of -1 means 'reset light radius to 0' if(target.light_radius == 0){ target.UpdateRadius(0,8,true); } B.Add(target.the_name + " spouts flames! ",target); M.Draw(); for(int i=0;i<3;++i){ List<pos> cells = new List<pos>(); List<Tile> tiles = target.TilesWithinDistance(1); for(int j=0;j<5;++j){ Tile t = tiles.RemoveRandom(); if(player.CanSee(t)){ cells.Add(t.p); } } if(cells.Count > 0){ Screen.AnimateMapCells(cells,new colorchar('*',Color.Red),35); } } foreach(Tile t in target.TilesWithinDistance(1)){ Actor a = t.actor(); if(a != null){ if(await a.TakeDamage(DamageType.FIRE,DamageClass.PHYSICAL,Global.Roll(2,6),null,"a fiery eruption")){ if(!a.HasAttr(AttrType.RESIST_FIRE) && !a.HasAttr(AttrType.IMMUNE_FIRE) && !a.HasAttr(AttrType.ON_FIRE) && !a.HasAttr(AttrType.CATCHING_FIRE) && !a.HasAttr(AttrType.STARTED_CATCHING_FIRE_THIS_TURN)){ if(a.name == "you"){ B.Add("You start to catch fire! "); } else{ B.Add(a.the_name + " starts to catch fire. ",a); } a.attrs[AttrType.CATCHING_FIRE] = 1; } } } if(t.Is(FeatureType.TROLL_CORPSE)){ t.features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ",t); } if(t.Is(FeatureType.TROLL_SEER_CORPSE)){ t.features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ",t); } } Q.Add(new Event(target,100,EventType.FIRE_GEYSER_ERUPTION,value - 1)); } else{ target.UpdateRadius(8,0,true); } break; } case EventType.FOG_VENT: { if(target.name == "floor"){ Event hiddencheck = null; Tile t = target as Tile; foreach(Event e in Q.list){ if(!e.dead && e.evtype == EventType.CHECK_FOR_HIDDEN){ hiddencheck = e; break; } } if(player.CanSee(t)){ //t.seen = true; if(hiddencheck != null){ hiddencheck.area.Remove(t); } t.name = Tile.Prototype(t.ttype).name; t.a_name = Tile.Prototype(t.ttype).a_name; t.the_name = Tile.Prototype(t.ttype).the_name; t.symbol = Tile.Prototype(t.ttype).symbol; t.color = Tile.Prototype(t.ttype).color; } } Tile current = target as Tile; if(!current.Is(FeatureType.FOG)){ current.AddOpaqueFeature(FeatureType.FOG); Q.Add(new Event(new List<Tile>{current},400,EventType.FOG)); } else{ for(int tries=0;tries<50;++tries){ List<Tile> open = new List<Tile>(); foreach(Tile t in current.TilesAtDistance(1)){ if(t.passable){ open.Add(t); } } if(open.Count > 0){ Tile possible = open.Random(); if(!possible.Is(FeatureType.FOG)){ possible.AddOpaqueFeature(FeatureType.FOG); Q.Add(new Event(new List<Tile>{possible},400,EventType.FOG)); break; } else{ current = possible; } } else{ break; } } } Q.Add(new Event(target,100,EventType.FOG_VENT)); break; } case EventType.FOG: { List<Tile> removed = new List<Tile>(); foreach(Tile t in area){ if(t.Is(FeatureType.FOG) && Global.OneIn(4)){ t.RemoveOpaqueFeature(FeatureType.FOG); removed.Add(t); } } foreach(Tile t in removed){ area.Remove(t); } if(area.Count > 0){ Q.Add(new Event(area,100,EventType.FOG)); } break; } case EventType.POISON_GAS_VENT: { if(target.name == "floor"){ Event hiddencheck = null; Tile t = target as Tile; foreach(Event e in Q.list){ if(!e.dead && e.evtype == EventType.CHECK_FOR_HIDDEN){ hiddencheck = e; break; } } if(player.CanSee(t)){ //t.seen = true; if(hiddencheck != null){ hiddencheck.area.Remove(t); } t.name = Tile.Prototype(t.ttype).name; t.a_name = Tile.Prototype(t.ttype).a_name; t.the_name = Tile.Prototype(t.ttype).the_name; t.symbol = Tile.Prototype(t.ttype).symbol; t.color = Tile.Prototype(t.ttype).color; } } Tile current = target as Tile; if(Global.OneIn(7)){ int num = Global.Roll(5) + 2; List<Tile> new_area = new List<Tile>(); for(int i=0;i<num;++i){ if(!current.Is(FeatureType.POISON_GAS)){ current.features.Add(FeatureType.POISON_GAS); new_area.Add(current); } else{ for(int tries=0;tries<50;++tries){ List<Tile> open = new List<Tile>(); foreach(Tile t in current.TilesAtDistance(1)){ if(t.passable){ open.Add(t); } } if(open.Count > 0){ Tile possible = open.Random(); if(!possible.Is(FeatureType.POISON_GAS)){ possible.features.Add(FeatureType.POISON_GAS); new_area.Add(possible); break; } else{ current = possible; } } else{ break; } } } } if(new_area.Count > 0){ B.Add("Toxic vapors pour from " + target.the_name + "! ",target); Q.Add(new Event(new_area,200,EventType.POISON_GAS)); } } Q.Add(new Event(target,100,EventType.POISON_GAS_VENT)); break; } case EventType.POISON_GAS: { List<Tile> removed = new List<Tile>(); foreach(Tile t in area){ if(t.Is(FeatureType.POISON_GAS) && Global.OneIn(6)){ t.RemoveOpaqueFeature(FeatureType.POISON_GAS); removed.Add(t); } } foreach(Tile t in removed){ area.Remove(t); } if(area.Count > 0){ Q.Add(new Event(area,100,EventType.POISON_GAS)); } break; } case EventType.STONE_SLAB: { Tile t = target as Tile; if(t.ttype == TileType.STONE_SLAB && (t.IsLitFromAnywhere(true) || area.Any(x=>x.actor()!=null))){ bool vis = player.CanSee(t); t.Toggle(null,Forays.TileType.FLOOR); if(!vis && player.CanSee(t)){ vis = true; } if(vis){ B.Add("The stone slab rises with a grinding sound. "); } else{ if(player.DistanceFrom(t) <= 6){ B.Add("You hear a grinding sound. "); } } } else{ if(t.ttype == TileType.FLOOR && !t.IsLitFromAnywhere(true) && t.actor() == null && !area.Any(x=>x.actor()!=null)){ bool vis = player.CanSee(t); t.Toggle(null,Forays.TileType.STONE_SLAB); if(!vis && player.CanSee(t)){ vis = true; } if(vis){ B.Add("The stone slab descends with a grinding sound. "); } else{ if(player.DistanceFrom(t) <= 6){ B.Add("You hear a grinding sound. "); } } } } Q.Add(new Event(target,area,100,EventType.STONE_SLAB)); break; } case EventType.MARBLE_HORROR: { Tile t = target as Tile; if(t.ttype == TileType.STATUE){ if(value == 1 && player.CanSee(t) && !t.IsLit() && t.actor() == null){ //if target was visible last turn & this turn, and it's currently in darkness... t.TransformTo(TileType.FLOOR); Actor a = Actor.Create(ActorType.MARBLE_HORROR,t.row,t.col,true,true); foreach(Event e in Q.list){ if(e.target == a && e.evtype == EventType.MOVE){ e.dead = true; break; } } a.Q0(); switch(Global.Roll(2)){ case 1: B.Add("You think that statue might have just moved... "); break; case 2: B.Add("The statue turns its head to face you. "); break; } } else{ if(player.CanSee(t)){ Q.Add(new Event(target,100,EventType.MARBLE_HORROR,1)); } else{ Q.Add(new Event(target,100,EventType.MARBLE_HORROR,0)); } } } break; } case EventType.REGENERATING_FROM_DEATH: { if(target.tile().Is(FeatureType.TROLL_CORPSE)){ //otherwise, assume it was destroyed by fire value++; if(value > 0 && target.actor() == null){ Actor a = Actor.Create(ActorType.TROLL,target.row,target.col); foreach(Event e in Q.list){ if(e.target == M.actor[target.row,target.col] && e.evtype == EventType.MOVE){ e.tiebreaker = this.tiebreaker; break; } } Actor.tiebreakers[tiebreaker] = a; target.actor().curhp = value; target.actor().level = 0; target.actor().attrs[Forays.AttrType.NO_ITEM]++; B.Add("The troll stands up! ",target); target.actor().player_visibility_duration = -1; if(target.tile().ttype == TileType.DOOR_C){ target.tile().Toggle(target.actor()); } target.tile().features.Remove(FeatureType.TROLL_CORPSE); if(Global.OneIn(3)){ target.actor().attrs[Forays.AttrType.WANDERING]++; } } else{ int roll = Global.Roll(20); if(value == -1){ roll = 1; } if(value == 0){ roll = 3; } switch(roll){ case 1: case 2: B.Add("The troll's corpse twitches. ",target); break; case 3: case 4: B.Add("You hear sounds coming from the troll's corpse. ",target); break; case 5: B.Add("The troll on the floor regenerates. ",target); break; default: break; } Q.Add(new Event(target,null,100,EventType.REGENERATING_FROM_DEATH,attr,value,"")); } } if(target.tile().Is(FeatureType.TROLL_SEER_CORPSE)){ //otherwise, assume it was destroyed by fire value++; if(value > 0 && target.actor() == null){ Actor a = Actor.Create(ActorType.TROLL_SEER,target.row,target.col); foreach(Event e in Q.list){ if(e.target == M.actor[target.row,target.col] && e.evtype == EventType.MOVE){ e.tiebreaker = this.tiebreaker; break; } } Actor.tiebreakers[tiebreaker] = a; target.actor().curhp = value; target.actor().level = 0; target.actor().attrs[Forays.AttrType.NO_ITEM]++; B.Add("The troll seer stands up! ",target); target.actor().player_visibility_duration = -1; if(attr == AttrType.COOLDOWN_1){ target.actor().attrs[Forays.AttrType.COOLDOWN_1]++; } if(target.tile().ttype == TileType.DOOR_C){ target.tile().Toggle(target.actor()); } target.tile().features.Remove(FeatureType.TROLL_SEER_CORPSE); if(Global.OneIn(3)){ target.actor().attrs[Forays.AttrType.WANDERING]++; } } else{ int roll = Global.Roll(20); if(value == -1){ roll = 1; } if(value == 0){ roll = 3; } switch(roll){ case 1: case 2: B.Add("The troll seer's corpse twitches. ",target); break; case 3: case 4: B.Add("You hear sounds coming from the troll seer's corpse. ",target); break; case 5: B.Add("The troll seer on the floor regenerates. ",target); break; default: break; } Q.Add(new Event(target,null,100,EventType.REGENERATING_FROM_DEATH,attr,value,"")); } } break; } case EventType.QUICKFIRE: { List<Actor> actors = new List<Actor>(); if(value >= 0){ foreach(Tile t in area){ if(t.actor() != null){ actors.Add(t.actor()); } if(t.Is(FeatureType.TROLL_CORPSE)){ t.features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ",t); } if(t.Is(FeatureType.TROLL_SEER_CORPSE)){ t.features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ",t); } if(t.Is(FeatureType.FUNGUS)){ Q.Add(new Event(t,200,EventType.BLAST_FUNGUS)); Actor.B.Add("The blast fungus starts to smolder in the light. ",t); t.features.Remove(FeatureType.FUNGUS); t.features.Add(FeatureType.FUNGUS_ACTIVE); } } } if(value > 0){ int radius = 4 - value; List<Tile> added = new List<Tile>(); foreach(Tile t in target.TilesWithinDistance(radius)){ if(t.passable && !t.Is(FeatureType.QUICKFIRE) && t.IsAdjacentTo(FeatureType.QUICKFIRE) && !area.Contains(t)){ added.Add(t); } } foreach(Tile t in added){ area.Add(t); t.features.Add(FeatureType.QUICKFIRE); } } if(value < 0){ int radius = 4 + value; List<Tile> removed = new List<Tile>(); foreach(Tile t in area){ if(t.DistanceFrom(target) == radius){ removed.Add(t); } else{ if(t.actor() != null){ actors.Add(t.actor()); } if(t.Is(FeatureType.TROLL_CORPSE)){ t.features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ",t); } if(t.Is(FeatureType.TROLL_SEER_CORPSE)){ t.features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ",t); } if(t.Is(FeatureType.FUNGUS)){ Q.Add(new Event(t,200,EventType.BLAST_FUNGUS)); Actor.B.Add("The blast fungus starts to smolder in the light. ",t); t.features.Remove(FeatureType.FUNGUS); t.features.Add(FeatureType.FUNGUS_ACTIVE); } } } foreach(Tile t in removed){ area.Remove(t); t.features.Remove(FeatureType.QUICKFIRE); } } foreach(Actor a in actors){ if(!a.HasAttr(AttrType.IMMUNE_FIRE) && !a.HasAttr(AttrType.INVULNERABLE)){ if(player.CanSee(a.tile())){ B.Add("The quickfire burns " + a.the_name + ". ",a); } await a.TakeDamage(DamageType.FIRE, DamageClass.PHYSICAL, Global.Roll(6), null, "quickfire"); } } --value; if(value > -5){ Q.Add(new Event(target,area,100,EventType.QUICKFIRE,AttrType.NO_ATTR,value,"")); } break; } case EventType.BOSS_SIGN: { string s = ""; switch(Global.Roll(8)){ case 1: s = "You see scratch marks on the walls and floor. "; break; case 2: s = "There are deep gouges in the floor here. "; break; case 3: s = "The floor here is scorched and blackened. "; break; case 4: s = "You notice bones of an unknown sort on the floor. "; break; case 5: s = "You hear a distant roar. "; break; case 6: s = "You smell smoke. "; break; case 7: s = "You spot a large reddish scale on the floor. "; break; case 8: s = "A small tremor shakes the area. "; break; default: s = "Debug message. "; break; } if(!player.HasAttr(AttrType.RESTING)){ B.AddIfEmpty(s); } Q.Add(new Event((Global.Roll(20)+35)*100,EventType.BOSS_SIGN)); break; } case EventType.BOSS_ARRIVE: { bool spawned = false; Actor a = null; if(M.AllActors().Count == 1 && !Q.Contains(EventType.POLTERGEIST)){ List<Tile> trolls = new List<Tile>(); foreach (Event current in Q.list){ if(current.evtype == EventType.REGENERATING_FROM_DEATH){ trolls.Add((current.target) as Tile); } } foreach(Tile troll in trolls){ if(troll.Is(FeatureType.TROLL_CORPSE)){ B.Add("The troll corpse burns to ashes! ",troll); troll.features.Remove(FeatureType.TROLL_CORPSE); } else{ if(troll.Is(FeatureType.TROLL_SEER_CORPSE)){ B.Add("The troll seer corpse burns to ashes! ",troll); troll.features.Remove(FeatureType.TROLL_SEER_CORPSE); } } } Q.KillEvents(null,EventType.REGENERATING_FROM_DEATH); List<Tile> goodtiles = M.AllTiles(); List<Tile> removed = new List<Tile>(); foreach(Tile t in goodtiles){ if(!t.passable || t.Is(TileType.CHASM) || player.CanSee(t)){ removed.Add(t); } } foreach(Tile t in removed){ goodtiles.Remove(t); } if(goodtiles.Count > 0){ B.Add("You hear a loud crash and a nearby roar! "); Tile t = goodtiles[Global.Roll(goodtiles.Count)-1]; a = Actor.Create(ActorType.FIRE_DRAKE,t.row,t.col,true,false); spawned = true; } else{ if(M.AllTiles().Any(t=>t.passable && !t.Is(TileType.CHASM) && t.actor() == null)){ B.Add("You hear a loud crash and a nearby roar! "); Tile tile = M.AllTiles().Where(t=>t.passable && !t.Is(TileType.CHASM) && t.actor() == null).Random(); a = Actor.Create(ActorType.FIRE_DRAKE,tile.row,tile.col,true,false); spawned = true; } } } if(!spawned){ Q.Add(new Event(null,null,(Global.Roll(20)+10)*100,EventType.BOSS_ARRIVE,attr,value,"")); } else{ if(value > 0){ a.curhp = value; } else{ //if there's no good value, this means that this is the first appearance. B.Add("The ground shakes as dust and rocks fall from the cavern ceiling. "); B.Add("This place is falling apart! "); List<Tile> floors = M.AllTiles().Where(t=>t.passable && t.ttype != TileType.CHASM && player.tile() != t); Tile tile = null; if(floors.Count > 0){ tile = floors.Random(); (tile as Tile).Toggle(null,TileType.CHASM); } Q.Add(new Event(tile,100,EventType.FLOOR_COLLAPSE)); Q.Add(new Event((Global.Roll(20)+20)*100,EventType.CEILING_COLLAPSE)); } } break; } case EventType.FLOOR_COLLAPSE: { Tile current = target as Tile; int tries = 0; if(current != null){ for(tries=0;tries<50;++tries){ List<Tile> open = new List<Tile>(); foreach(Tile t in current.TilesAtDistance(1)){ if(t.passable || t.Is(TileType.RUBBLE)){ open.Add(t); } } if(open.Count > 0){ Tile possible = open.Random(); if(!possible.Is(TileType.CHASM)){ possible.Toggle(null,TileType.CHASM); List<Tile> open_neighbors = possible.TilesAtDistance(1).Where(t=>t.passable && t.ttype != TileType.CHASM); int num_neighbors = open_neighbors.Count; while(open_neighbors.Count > num_neighbors/2){ Tile neighbor = open_neighbors.RemoveRandom(); neighbor.Toggle(null,TileType.CHASM); } break; } else{ current = possible; } } else{ break; } } } if(tries == 50 || current == null){ List<Tile> floors = M.AllTiles().Where(t=>t.passable && t.ttype != TileType.CHASM && player.tile() != t); if(floors.Count > 0){ target = floors.Random(); (target as Tile).Toggle(null,TileType.CHASM); } } Q.Add(new Event(target,100,EventType.FLOOR_COLLAPSE)); break; } case EventType.CEILING_COLLAPSE: { B.Add("The ground shakes and debris falls from the ceiling! "); for(int i=1;i<Global.ROWS-1;++i){ for(int j=1;j<Global.COLS-1;++j){ Tile t = M.tile[i,j]; if(t.Is(TileType.WALL)){ int num_walls = t.TilesAtDistance(1).Where(x=>x.Is(TileType.WALL)).Count; if(num_walls < 8 && Global.OneIn(20)){ if(Global.CoinFlip()){ t.Toggle(null,Forays.TileType.FLOOR); foreach(Tile neighbor in t.TilesAtDistance(1)){ neighbor.solid_rock = false; } } else{ t.Toggle(null,Forays.TileType.RUBBLE); foreach(Tile neighbor in t.TilesAtDistance(1)){ neighbor.solid_rock = false; if(neighbor.ttype == TileType.FLOOR && Global.OneIn(10)){ neighbor.Toggle(null,Forays.TileType.RUBBLE); } } } } } else{ int num_walls = t.TilesAtDistance(1).Where(x=>x.Is(TileType.WALL)).Count; if(num_walls == 0 && Global.OneIn(100)){ if(Global.OneIn(6)){ t.Toggle(null,Forays.TileType.RUBBLE); } foreach(Tile neighbor in t.TilesAtDistance(1)){ if(neighbor.ttype == TileType.FLOOR && Global.OneIn(6)){ neighbor.Toggle(null,Forays.TileType.RUBBLE); } } } } } } Q.Add(new Event((Global.Roll(20)+20)*100,EventType.CEILING_COLLAPSE)); break; } } if(msg != ""){ if(msg_objs == null){ B.Add(msg); } else{ B.Add(msg,msg_objs.ToArray()); } } } }