public void From_should_return_entire_array_if_item_is_at_front_of_array()
 {
     var items = new List<string>() { "meat", "cheese", "beer", "bread" };
     var from = items.From("meat");
     Assert.True(from.SequenceEqual(items));
 }
 public void From_should_return_empty_set_when_item_not_found()
 {
     var items = new List<int>() {1, 2, 3, 4, 5, 6};
     var from7 = items.From(7).Take(2);
     from7.Count().ShouldBe(0);
 }
 public void From_should_return_correct_subset_from_array()
 {
     var items = new List<string>() {"meat", "cheese", "beer", "bread"};
     var from = items.From("beer");
     Assert.True(from.SequenceEqual(new[]{ "beer", "bread" }));
 }
Example #4
0
 public void FireArrow(List<Tile> line,bool free_attack)
 {
     if(!free_attack && StunnedThisTurn()){
         return;
     }
     int mod = -30; //bows have base accuracy 45%
     /*if(magic_trinkets.Contains(MagicTrinketType.RING_OF_KEEN_SIGHT)){
         mod = -15; //keen sight now only affects trap detection
     }*/
     if(this == player && Bow.status[EquipmentStatus.ONE_ARROW_LEFT]){
         mod = 25; //...but the last arrow gets a bonus
     }
     mod += TotalSkill(SkillType.COMBAT);
     Tile t = null;
     Actor a = null;
     bool solid_object_hit = false;
     bool no_terrain_collision_message = free_attack; //don't show "the arrow hits the wall" for echoes.
     List<string> misses = new List<string>();
     List<Actor> missed = new List<Actor>();
     List<Tile> animation_line = new List<Tile>(line);
     line.RemoveAt(0); //remove the source of the arrow first
     if(line.Count > 12){
         line = line.GetRange(0,Math.Min(12,line.Count));
     }
     int flaming_arrow_start = HasAttr(AttrType.FIERY_ARROWS)? 0 : -1; //tracks where an arrow caught fire
     bool blocked_by_armor_miss = false;
     bool blocked_by_root_shell_miss = false;
     for(int i=0;i<line.Count;++i){
         a = line[i].actor();
         t = line[i];
         if(a != null){
             no_terrain_collision_message = true;
             int plus_to_hit = mod - a.TotalSkill(SkillType.DEFENSE)*3;
             bool hit = true;
             if(!a.IsHit(plus_to_hit)){
                 hit = false;
                 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(55 - plus_to_hit);
                 if(roll <= armor_value * 3){
                     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;
                     }
                 }
             }
             else{
                 if(a.HasAttr(AttrType.TUMBLING)){
                     a.attrs[AttrType.TUMBLING] = 0;
                     hit = false;
                 }
             }
             if(R.CoinFlip() && !CanSee(a)){ //extra 50% miss chance for enemies you can't see
                 hit = false;
                 blocked_by_armor_miss = false;
                 blocked_by_root_shell_miss = false;
             }
             if(hit || blocked_by_armor_miss || blocked_by_root_shell_miss){
                 solid_object_hit = true;
                 break;
             }
             else{
                 misses.Add("The arrow misses " + a.the_name + ". ");
                 missed.Add(a);
             }
             a = null;
         }
         if(!t.passable){
             a = null;
             solid_object_hit = true;
             break;
         }
         if(flaming_arrow_start == -1 && t.IsBurning()){
             flaming_arrow_start = i;
         }
     }
     if(!free_attack){
         if(Bow != null && Bow.status[EquipmentStatus.ONE_ARROW_LEFT]){
             B.Add(You("take") + " careful aim. ",this);
             B.Add(You("fire") + " " + Your() + " last arrow. ",this);
         }
         else{
             if(HasAttr(AttrType.FIERY_ARROWS)){
                 B.Add(You("fire") + " a flaming arrow. ",this);
             }
             else{
                 B.Add(You("fire") + " an arrow. ",this);
             }
         }
         B.DisplayNow();
     }
     int idx = 0;
     /*foreach(Tile tile2 in animation_line){
         if(tile2.seen){
             ++idx; //todo: remove idx here, right?
         }
         else{
             animation_line = animation_line.To(tile2);
             if(animation_line.Count > 0){
                 animation_line.RemoveAt(animation_line.Count - 1);
             }
             break;
         }
     }*/
     if(animation_line.Count > 0){
         Screen.CursorVisible = false;
         PhysicalObject o = t;
         if(a != null){
             o = a;
         }
         animation_line = animation_line.To(o);
         if(flaming_arrow_start >= 0 && !M.wiz_dark && !player.HasAttr(AttrType.BLIND)){ //fire should be visible unless darkened or blind
             List<Tile> first_line = animation_line.ToCount(flaming_arrow_start+2);
             List<Tile> second_line = animation_line.FromCount(flaming_arrow_start+2);
             if(first_line.Count > 0){
                 Screen.AnimateBoltProjectile(animation_line.ToCount(flaming_arrow_start+2),Color.DarkYellow,20);
             }
             Screen.CursorVisible = false;
             if(second_line.Count > 0){
                 Screen.AnimateBoltProjectile(animation_line.FromCount(flaming_arrow_start+2),Color.RandomFire,20);
             }
         }
         else{
             Screen.AnimateBoltProjectile(animation_line,Color.DarkYellow,20);
         }
         Screen.CursorVisible = false;
         if(this == player && solid_object_hit && !player.CanSee(o) && (o is Actor || !o.tile().seen)){
             Screen.AnimateMapCell(o.row,o.col,new colorchar('?',Color.DarkGray),50);
         }
         Screen.CursorVisible = false;
     }
     idx = 0;
     foreach(string s in misses){
         B.Add(s,missed[idx]);
         if(missed[idx] != player){
             missed[idx].player_visibility_duration = -1;
             if(HasLOE(missed[idx])){
                 missed[idx].target = this;
                 missed[idx].target_location = tile();
             }
         }
         ++idx;
     }
     if(flaming_arrow_start != -1){
         foreach(Tile affected in line.FromCount(flaming_arrow_start+2)){
             affected.ApplyEffect(DamageType.FIRE);
             if((a != null && affected.actor() == a) || affected == t){
                 break;
             }
         }
     }
     if(a != null){
         pos target_original_position = a.p;
         if(a.HasAttr(AttrType.IMMUNE_ARROWS)){
             B.Add("The arrow sticks out ineffectively from " + a.the_name + ". ",a);
         }
         else{
             if(a.magic_trinkets.Contains(MagicTrinketType.BRACERS_OF_ARROW_DEFLECTION)){
                 B.Add(a.You("deflect") + " the arrow! ",a);
             }
             else{
                 if(blocked_by_armor_miss){
                     B.Add(a.YourVisible() + " armor blocks the arrow. ",a);
                 }
                 else{
                     if(blocked_by_root_shell_miss){
                         B.Add(a.YourVisible() + " root shell blocks the arrow. ",a);
                     }
                     else{
                         bool alive = true;
                         bool crit = false;
                         int crit_chance = 8; //base crit rate is 1/8
                         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)){
                             crit_chance /= 2;
                         }
                         if(Bow != null && Bow.enchantment == EnchantmentType.PRECISION && !Bow.status[EquipmentStatus.NEGATED]){
                             crit_chance /= 2;
                         }
                         if(crit_chance <= 1 || R.OneIn(crit_chance)){
                             crit = true;
                         }
                         if(this == player && IsHiddenFrom(a) && crit && !a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE,AttrType.BOSS_MONSTER) && a.type != ActorType.CYCLOPEAN_TITAN){ //none of the bow-wielding monsters should ever be hidden from the player
                             B.Add(a.the_name + " falls with your arrow between the eyes. ",a);
                             //B.Add("Headshot! ",a);
                             a.Kill();
                             alive = false;
                         }
                         else{
                             B.Add("The arrow hits " + a.the_name + ". ",a);
                             if(!a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(2,6)+TotalSkill(SkillType.COMBAT),this,a_name + "'s arrow")){
                                 alive = false;
                             }
                             if(crit && alive){
                                 if(a.type == ActorType.CYCLOPEAN_TITAN){
                                     if(!a.HasAttr(AttrType.COOLDOWN_1)){
                                         B.Add(YourVisible() + " arrow pierces its eye, blinding it! ",this,a);
                                         Q.KillEvents(a,AttrType.BLIND);
                                         a.attrs[AttrType.BLIND] = 1;
                                         a.attrs[AttrType.COOLDOWN_1] = 1;
                                     }
                                 }
                                 else{
                                     Event e = Q.FindAttrEvent(a,AttrType.IMMOBILE);
                                     if(!a.HasAttr(AttrType.IMMOBILE) || (e != null && e.msg.Contains("no longer pinned"))){ //i.e. don't pin naturally immobile monsters //todo - new refreshduration implementation should allow this to be done more naturally
                                         B.Add(a.YouAre() + " pinned! ",a);
                                         a.RefreshDuration(AttrType.IMMOBILE,100,a.YouAre() + " no longer pinned. ",a);
                                         if(a.HasAttr(AttrType.FLYING) && a.tile().IsTrap()){
                                             a.tile().TriggerTrap();
                                         }
                                     }
                                 }
                             }
                             if(alive && a.HasAttr(AttrType.NONLIVING)){
                                 if(Bow != null && Bow.enchantment == EnchantmentType.DISRUPTION && !Bow.status[EquipmentStatus.NEGATED]){
                                     B.Add(a.YouAre() + " disrupted! ",a);
                                     if(!a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,a.maxhp / 5,this)){
                                         alive = false;
                                     }
                                 }
                             }
                             if(alive && !a.HasAttr(AttrType.IMMUNE_COLD)){
                                 if(Bow != null && Bow.enchantment == EnchantmentType.CHILLING && !Bow.status[EquipmentStatus.NEGATED]){
                                     B.Add(a.YouAre() + " 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)){
                                         alive = false;
                                     }
                                 }
                             }
                             if(alive && flaming_arrow_start != -1){
                                 a.ApplyBurning();
                             }
                         }
                         if(!alive && Bow != null && Bow.enchantment == EnchantmentType.VICTORY && !Bow.status[EquipmentStatus.NEGATED]){
                             curhp += 5;
                             if(curhp > maxhp){
                                 curhp = maxhp;
                             }
                         }
                     }
                 }
             }
         }
         if(Bow != null && Bow.enchantment == EnchantmentType.ECHOES && !Bow.status[EquipmentStatus.NEGATED]){
             List<Tile> line2 = line.From(M.tile[target_original_position]);
             if(line2.Count > 1){
                 FireArrow(line2,true); //todo: does this need special handling? should a burning monster cause a flaming echo?
             }
         }
     }
     else{
         if(!no_terrain_collision_message || t.Is(TileType.POISON_BULB) || (t.Is(TileType.WAX_WALL) && flaming_arrow_start != -1)){
             B.Add("The arrow hits " + t.the_name + ". ",t);
             if(t.Is(TileType.POISON_BULB)){
                 t.Bump(DirectionOf(t));
             }
             if(flaming_arrow_start != -1){
                 t.ApplyEffect(DamageType.FIRE);
             }
         }
     }
     if(!free_attack){
         Q1();
     }
 }