예제 #1
0
 public bool TakeDamage(Damage dmg,string cause_of_death)
 {
     //returns true if still alive
     if(dmg.amount == 0){
         return true;
     }
     bool damage_dealt = false;
     int old_hp = curhp;
     if(curhp <= 0 && dmg.amount < 1000){ //then we're dealing with a corpse, and they don't take normal amounts of damage
         return true;
     }
     bool ice_removed = false;
     if(dmg.amount < 1000){
         if(HasAttr(AttrType.FROZEN) && (dmg.major_damage || dmg.type == DamageType.FIRE)){ //this should ignore bleeding and poison, but not searing
             attrs[AttrType.FROZEN] -= dmg.amount;
             if(attrs[AttrType.FROZEN] <= 0){
                 attrs[AttrType.FROZEN] = 0;
                 B.Add("The ice breaks! ",this);
                 ice_removed = true;
             }
             dmg.amount = 0;
             if(dmg.type == DamageType.FIRE && HasAttr(AttrType.FROZEN)){
                 attrs[AttrType.FROZEN] = 0;
                 B.Add("The ice melts! ",this);
                 ice_removed = true;
             }
         }
         if(dmg.type == DamageType.FIRE && HasAttr(AttrType.OIL_COVERED)){
             if(HasAttr(AttrType.IMMUNE_BURNING)){
                 B.Add("The oil burns off of " + the_name + ". ",this);
                 attrs[AttrType.OIL_COVERED] = 0;
             }
             else{
                 B.Add(You("catch",true) + " fire! ",this);
                 ApplyBurning();
             }
         }
         if(dmg.type == DamageType.COLD && HasAttr(AttrType.SLIMED)){
             attrs[AttrType.SLIMED] = 0;
             B.Add("The slime freezes and falls from " + the_name + ". ",this);
         }
         if(!dmg.major_damage && HasAttr(AttrType.MINOR_IMMUNITY,AttrType.SHIELDED)){
             return true;
         }
         if(HasAttr(AttrType.MECHANICAL_SHIELD)){
             B.Add(Your() + " shield moves to protect it from harm. ",this);
             return true;
         }
         if(dmg.major_damage){
             if(HasAttr(AttrType.BLOCKING)){
                 B.Add(You("block") + "! ",this); //todo: extra effects will go here eventually
                 attrs[AttrType.BLOCKING]--;
                 return true;
             }
             else{
                 if(HasAttr(AttrType.SHIELDED)){
                     B.Add(Your() + " shield flashes! ",this);
                     attrs[AttrType.SHIELDED]--;
                     return true;
                 }
                 else{
                     if(HasAttr(AttrType.VULNERABLE)){
                         attrs[AttrType.VULNERABLE] = 0;
                         if(this == player){
                             B.Add("Ouch! ");
                         }
                         else{
                             B.Add(YouAre() + " devastated! ",this);
                         }
                         foreach(Event e in Q.list){
                             if(!e.dead && e.target == this && e.type == EventType.REMOVE_ATTR && e.attr == AttrType.VULNERABLE){
                                 e.dead = true;
                             }
                         }
                         dmg.amount += R.Roll(3,6);
                     }
                 }
             }
         }
         if(HasAttr(AttrType.INVULNERABLE)){
             dmg.amount = 0;
         }
         /*if(HasAttr(AttrType.TOUGH) && dmg.damclass == DamageClass.PHYSICAL){
             dmg.amount -= 2;
         }
         if(HasAttr(AttrType.ARCANE_SHIELDED)){
             if(attrs[AttrType.ARCANE_SHIELDED] >= dmg.amount){
                 attrs[AttrType.ARCANE_SHIELDED] -= dmg.amount;
                 dmg.amount = 0;
             }
             else{
                 dmg.amount -= attrs[AttrType.ARCANE_SHIELDED];
                 attrs[AttrType.ARCANE_SHIELDED] = 0;
             }
             if(!HasAttr(AttrType.ARCANE_SHIELDED)){
                 B.Add(Your() + " shield fades. ",this);
             }
         }
         if(dmg.damclass == DamageClass.MAGICAL){
             dmg.amount -= TotalSkill(SkillType.SPIRIT) / 2;
         }
         if(HasAttr(AttrType.DAMAGE_REDUCTION) && dmg.amount > 5){
             dmg.amount = 5;
         }*/
         if(dmg.amount > 15 && magic_trinkets.Contains(MagicTrinketType.BELT_OF_WARDING)){
             dmg.amount = 15;
             B.Add(Your() + " " + MagicTrinket.Name(MagicTrinketType.BELT_OF_WARDING) + " softens the blow. ",this);
         }
         dmg.amount -= attrs[AttrType.DAMAGE_RESISTANCE];
         switch(dmg.type){
         case DamageType.NORMAL:
             if(dmg.amount > 0){
                 curhp -= dmg.amount;
                 damage_dealt = true;
             }
             else{
                 if(!ice_removed){
                     B.Add(YouAre() + " undamaged. ",this);
                 }
             }
             break;
         case DamageType.MAGIC:
             if(dmg.amount > 0){
                 curhp -= dmg.amount;
                 damage_dealt = true;
             }
             else{
                 if(!ice_removed){
                     B.Add(YouAre() + " unharmed. ",this);
                 }
             }
             break;
         case DamageType.FIRE:
         {
             if(HasAttr(AttrType.IMMUNE_FIRE)){
                 dmg.amount = 0;
                 //B.Add(the_name + " is immune! ",this);
                 if(HasFeat(FeatType.BOILING_BLOOD) && attrs[AttrType.BLOOD_BOILED] == 5){
                     RefreshDuration(AttrType.IMMUNE_FIRE,1000);
                     RefreshDuration(AttrType.BLOOD_BOILED,1000,"Your blood cools. ");
                 }
             }
             if(dmg.amount > 0){
                 curhp -= dmg.amount;
                 damage_dealt = true;
             }
             else{
                 if((this == player || dmg.amount > 1) && !ice_removed && !HasAttr(AttrType.IMMUNE_FIRE) && !HasAttr(AttrType.JUST_SEARED)){
                     B.Add(YouAre() + " unburnt. ",this);
                 }
             }
             break;
         }
         case DamageType.COLD:
         {
             if(HasAttr(AttrType.IMMUNE_COLD)){
                 dmg.amount = 0;
                 //B.Add(YouAre() + " unharmed. ",this);
             }
             if(dmg.amount > 0){
                 curhp -= dmg.amount;
                 damage_dealt = true;
                 if(type == ActorType.GIANT_SLUG){
                     B.Add("The cold leaves " + the_name + " vulnerable. ",this);
                     RefreshDuration(AttrType.VULNERABLE,R.Between(7,13)*100,the_name + " is no longer vulnerable. ",this);
                 }
             }
             else{
                 if(!ice_removed && !HasAttr(AttrType.IMMUNE_COLD)){
                     B.Add(YouAre() + " unharmed. ",this);
                 }
             }
             break;
         }
         case DamageType.ELECTRIC:
         {
             if(HasAttr(AttrType.IMMUNE_ELECTRICITY)){
                 dmg.amount = 0;
             }
             if(dmg.amount > 0){
                 curhp -= dmg.amount;
                 damage_dealt = true;
             }
             else{
                 if(!ice_removed && !HasAttr(AttrType.IMMUNE_ELECTRICITY)){
                     B.Add(YouAre() + " unharmed. ",this);
                 }
             }
             break;
         }
         case DamageType.POISON:
             if(HasAttr(AttrType.NONLIVING)){
                 dmg.amount = 0;
             }
             if(dmg.amount > 0){
                 curhp -= dmg.amount;
                 damage_dealt = true;
                 if(type == ActorType.PLAYER){
                     B.Add("The poison burns! ");
                 }
                 else{
                     if(R.Roll(1,5) == 5 && !HasAttr(AttrType.PLANTLIKE)){ //hmm
                         B.Add(the_name + " shudders. ",this);
                     }
                 }
             }
             break;
         case DamageType.NONE:
             break;
         }
     }
     else{
         if(curhp > 0){
             curhp = 0;
         }
     }
     /*if(dmg.source != null && dmg.source == player && dmg.damclass == DamageClass.PHYSICAL && resisted && !cause_of_death.Contains("arrow")){
         Help.TutorialTip(TutorialTopic.Resistance);
     }*/
     if(damage_dealt){
         Interrupt();
         attrs[AttrType.AMNESIA_STUN] = 0;
         if(dmg.major_damage){
             recover_time = Q.turn + 500;
             attrs[AttrType.BANDAGED] = 0;
         }
         if(HasAttr(AttrType.ASLEEP)){
             attrs[AttrType.ASLEEP] = 0;
             attrs[AttrType.JUST_AWOKE] = 1;
             if(this == player){
                 Input.FlushInput();
                 B.Add("You wake up. ");
             }
         }
         if(dmg.source != null){
             if(type != ActorType.PLAYER && dmg.source != this && !HasAttr(AttrType.CONFUSED)){
                 target = dmg.source;
                 target_location = M.tile[dmg.source.row,dmg.source.col];
                 if(dmg.source.IsHiddenFrom(this)){
                     player_visibility_duration = -1;
                 }
                 if(type == ActorType.CAVERN_HAG && dmg.source == player){
                     attrs[AttrType.COOLDOWN_2] = 1;
                 }
                 if(type == ActorType.CRUSADING_KNIGHT && dmg.source == player && !HasAttr(AttrType.COOLDOWN_1) && !M.wiz_lite && !CanSee(player) && curhp > 0){
                     List<string> verb = new List<string>{"Show yourself","Reveal yourself","Unfold thyself","Present yourself","Unveil yourself","Make yourself known"};
                     List<string> adjective = new List<string>{"despicable","filthy","foul","nefarious","vulgar","sorry","unworthy"};
                     List<string> noun = new List<string>{"villain","blackguard","devil","scoundrel","wretch","cur","rogue"};
                     //B.Add(TheName(true) + " shouts \"" + verb.Random() + ", " + adjective.Random() + " " + noun.Random() + "!\" ");
                     B.Add("\"" + verb.Random() + ", " + adjective.Random() + " " + noun.Random() + "!\" ");
                     B.Add(the_name + " raises a gauntlet. ",this);
                     B.Add("Sunlight fills the dungeon. ");
                     M.wiz_lite = true;
                     M.wiz_dark = false;
                     Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING));
                     M.Draw();
                     B.Print(true);
                     attrs[AttrType.COOLDOWN_1]++;
                     foreach(Actor a in M.AllActors()){
                         if(a != this && a != player && !a.HasAttr(AttrType.BLINDSIGHT) && HasLOS(a)){
                             a.ApplyStatus(AttrType.BLIND,R.Between(5,9)*100);
                             /*B.Add(a.YouAre() + " blinded! ",a);
                             a.RefreshDuration(AttrType.BLIND,R.Between(5,9)*100,a.YouAre() + " no longer blinded. ",a);*/
                         }
                     }
                     if(!player.HasAttr(AttrType.BLINDSIGHT) && HasLOS(player)){ //do the player last, so all the previous messages can be seen.
                         player.ApplyStatus(AttrType.BLIND,R.Between(5,9)*100);
                         /*B.Add(player.YouAre() + " blinded! ");
                         player.RefreshDuration(AttrType.BLIND,R.Between(5,9)*100,player.YouAre() + " no longer blinded. ");*/
                     }
                 }
                 /*if(HasAttr(AttrType.RADIANT_HALO) && !M.wiz_dark && DistanceFrom(dmg.source) <= LightRadius() && HasLOS(dmg.source)){
                     B.Add(YourVisible() + " radiant halo burns " + dmg.source.TheName(true) + ". ",this,dmg.source);
                     int amount = R.Roll(2,6);
                     if(amount >= dmg.source.curhp){
                         amount = dmg.source.curhp - 1;
                     }
                     if(dmg.source.curhp > 1){ //this should prevent infinite loops if one haloed entity attacks another
                         dmg.source.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,amount,this);
                     }
                 }*/
                 if(dmg.source == player && IsFinalLevelDemon() && attrs[AttrType.COOLDOWN_2] < 2){
                     attrs[AttrType.COOLDOWN_2]++;
                 }
             }
         }
         if(HasAttr(AttrType.SPORE_BURST)){
             if(type == ActorType.SPORE_POD){
                 curhp = 0;
                 if(player.CanSee(this)){
                     B.Add("The spore pod bursts! ",this);
                 }
                 else{
                     if(DistanceFrom(player) == 1){
                         B.Add(YouVisible("burst") + "! ");
                     }
                 }
                 List<Tile> area = tile().AddGaseousFeature(FeatureType.SPORES,18);
                 Event.RemoveGas(area,600,FeatureType.SPORES,12);
             }
             else{
                 if(!HasAttr(AttrType.COOLDOWN_1) && dmg.major_damage){
                     RefreshDuration(AttrType.COOLDOWN_1,50); //cooldown added mostly to prevent several triggers while surrounded by fire. todo: this cooldown is no longer necessary now that searing is minor damage.
                     B.Add(You("retaliate") + " with a burst of spores! ",this);
                     List<Tile> area = tile().AddGaseousFeature(FeatureType.SPORES,8);
                     Event.RemoveGas(area,600,FeatureType.SPORES,12);
                 }
             }
         }
         if(HasFeat(FeatType.BOILING_BLOOD) && dmg.type != DamageType.POISON){
             if(attrs[AttrType.BLOOD_BOILED] < 5){
                 B.Add("Your blood boils! ");
                 if(attrs[AttrType.BLOOD_BOILED] == 4){
                     RefreshDuration(AttrType.IMMUNE_FIRE,1000);
                 }
                 GainAttrRefreshDuration(AttrType.BLOOD_BOILED,1000,"Your blood cools. ");
                 if(this == player){
                     Help.TutorialTip(TutorialTopic.IncreasedSpeed);
                 }
             }
             else{
                 RefreshDuration(AttrType.IMMUNE_FIRE,1000);
                 RefreshDuration(AttrType.BLOOD_BOILED,1000,"Your blood cools. ");
             }
         }
         if(type == ActorType.DREAM_SPRITE && (dmg.source != null || (HasLOE(player) && DistanceFrom(player) <= 12))){
             attrs[AttrType.COOLDOWN_2] = 1;
         }
         if(type == ActorType.MECHANICAL_KNIGHT){
             if(old_hp == 5){
                 curhp = 0;
             }
             else{
                 if(old_hp == 10){
                     curhp = 5;
                     switch(R.Roll(3)){
                     case 1:
                         B.Add(Your() + " arms are destroyed! ",this);
                         attrs[AttrType.COOLDOWN_1] = 1;
                         attrs[AttrType.MECHANICAL_SHIELD] = 0;
                         break;
                     case 2:
                         B.Add(Your() + " legs are destroyed! ",this);
                         attrs[AttrType.COOLDOWN_1] = 2;
                         path.Clear();
                         target_location = null;
                         break;
                     case 3:
                         B.Add(Your() + " head is destroyed! ",this);
                         attrs[AttrType.COOLDOWN_1] = 3;
                         break;
                     }
                 }
             }
         }
         if(dmg.type == DamageType.FIRE && (type == ActorType.TROLL || type == ActorType.TROLL_BLOODWITCH)){
             attrs[AttrType.PERMANENT_DAMAGE] += dmg.amount; //permanent damage doesn't regenerate
         }
         if(dmg.type == DamageType.FIRE && type == ActorType.SKITTERMOSS && !HasAttr(AttrType.COOLDOWN_1)){
             attrs[AttrType.COOLDOWN_1]++;
             B.Add("The fire kills " + Your() + " insects. ",this);
             color = Color.White;
         }
         if(type == ActorType.ALASI_SCOUT && old_hp == maxhp){
             B.Add("The glow leaves " + Your() + " sword. ",this);
             attrs[AttrType.KEEPS_DISTANCE] = 0;
         }
     }
     if(curhp <= 0){
         if(type == ActorType.PLAYER){
             if(magic_trinkets.Contains(MagicTrinketType.PENDANT_OF_LIFE)){
                 curhp = 1;
                 /*attrs[AttrType.INVULNERABLE]++;
                 Q.Add(new Event(this,1,AttrType.INVULNERABLE));*/
                 if(R.CoinFlip()){
                     B.Add("Your pendant glows brightly, then crumbles to dust! ");
                     magic_trinkets.Remove(MagicTrinketType.PENDANT_OF_LIFE);
                 }
                 else{
                     B.Add("Your pendant glows brightly! ");
                 }
             }
             else{
                 if(cause_of_death.Length > 0 && cause_of_death[0] == '*'){
                     Global.KILLED_BY = cause_of_death.Substring(1);
                 }
                 else{
                     Global.KILLED_BY = "killed by " + cause_of_death;
                 }
                 M.Draw();
                 if(Global.GAME_OVER == false){
                     B.Add("You die. ");
                 }
                 B.PrintAll();
                 Global.GAME_OVER = true;
                 return false;
             }
         }
         else{
             if(HasAttr(AttrType.BOSS_MONSTER)){
                 M.Draw();
                 B.Add("The fire drake dies. ");
                 B.PrintAll();
                 if(player.curhp > 0){
                     B.Add("The threat to your nation has been slain! You begin the long trek home to deliver the good news... ");
                     Global.KILLED_BY = "Died of ripe old age";
                 }
                 else{
                     B.Add("The threat to your nation has been slain! Unfortunately, you won't be able to deliver the news... ");
                 }
                 B.PrintAll();
                 Global.GAME_OVER = true;
                 Global.BOSS_KILLED = true;
             }
             if(dmg.amount < 1000){ //everything that deals this much damage prints its own message
                 if(type == ActorType.BERSERKER){
                     if(!HasAttr(AttrType.COOLDOWN_1)){
                         attrs[AttrType.COOLDOWN_1]++;
                         Q.Add(new Event(this,R.Between(3,5)*100,AttrType.COOLDOWN_1)); //changed from 350
                         Q.KillEvents(this,AttrType.COOLDOWN_2);
                         if(!HasAttr(AttrType.COOLDOWN_2)){
                             attrs[AttrType.COOLDOWN_2] = DirectionOf(player);
                         }
                         B.Add(the_name + " somehow remains standing! He screams with fury! ",this);
                     }
                     return true;
                 }
                 if(type == ActorType.GHOST){
                     Event e = Q.FindTargetedEvent(this,EventType.TOMBSTONE_GHOST);
                     if(e != null){
                         e.dead = true;
                         Q.Add(new Event(null,e.area,R.Between(3,6)*100,EventType.TOMBSTONE_GHOST));
                     }
                 }
                 if(HasAttr(AttrType.REGENERATES_FROM_DEATH) && dmg.type != DamageType.FIRE){
                     B.Add(the_name + " collapses, still twitching. ",this);
                 }
                 else{
                     if(HasAttr(AttrType.REASSEMBLES)){
                         if(Weapon.IsBlunt(dmg.weapon_used) && R.CoinFlip()){
                             B.Add(the_name + " is smashed to pieces. ",this);
                             attrs[AttrType.REASSEMBLES] = 0;
                         }
                         else{
                             B.Add(the_name + " collapses into a pile of bones. ",this);
                         }
                     }
                     else{
                         if(!HasAttr(AttrType.BOSS_MONSTER) && type != ActorType.SPORE_POD){
                             if(HasAttr(AttrType.NONLIVING)){
                                 B.Add(the_name + " is destroyed. ",this);
                             }
                             else{
                                 if(type == ActorType.FINAL_LEVEL_CULTIST && dmg.type == DamageType.FIRE){
                                     B.Add(the_name + " is consumed by flames. ",this);
                                     List<int> valid_circles = new List<int>();
                                     for(int i=0;i<5;++i){
                                         if(M.FinalLevelSummoningCircle(i).PositionsWithinDistance(2,M.tile).Any(x=>M.tile[x].Is(TileType.DEMONIC_IDOL))){
                                             valid_circles.Add(i);
                                         }
                                     }
                                     int nearest = valid_circles.WhereLeast(x=>DistanceFrom(M.FinalLevelSummoningCircle(x))).Random();
                                     pos circle = M.FinalLevelSummoningCircle(nearest);
                                     if(M.actor[circle] != null){
                                         circle = circle.PositionsWithinDistance(3,M.tile).Where(x=>M.tile[x].passable && M.actor[x] == null).Random();
                                     }
                                     M.final_level_cultist_count[nearest]++;
                                     if(M.final_level_cultist_count[nearest] >= 5){
                                         M.final_level_cultist_count[nearest] = 0;
                                         List<ActorType> valid_types = new List<ActorType>{ActorType.MINOR_DEMON};
                                         if(M.final_level_demon_count > 3){
                                             valid_types.Add(ActorType.FROST_DEMON);
                                         }
                                         if(M.final_level_demon_count > 5){
                                             valid_types.Add(ActorType.BEAST_DEMON);
                                         }
                                         if(M.final_level_demon_count > 11){
                                             valid_types.Add(ActorType.DEMON_LORD);
                                         }
                                         if(M.final_level_demon_count > 21){ //eventually, the majority will be demon lords
                                             valid_types.Add(ActorType.DEMON_LORD);
                                         }
                                         if(M.final_level_demon_count > 25){
                                             valid_types.Add(ActorType.DEMON_LORD);
                                         }
                                         if(M.final_level_demon_count > 31){
                                             valid_types.Add(ActorType.DEMON_LORD);
                                         }
                                         if(M.final_level_demon_count > 36){
                                             valid_types.Add(ActorType.DEMON_LORD);
                                             valid_types.Add(ActorType.DEMON_LORD);
                                             valid_types.Add(ActorType.DEMON_LORD);
                                         }
                                         if(player.CanSee(M.tile[circle])){
                                             B.Add("The flames leap and swirl, and a demon appears! ");
                                         }
                                         else{
                                             B.Add("You feel an evil presence. ");
                                         }
                                         Create(valid_types.Random(),circle.row,circle.col);
                                         if(M.actor[circle] != null){
                                             M.actor[circle].player_visibility_duration = -1;
                                             M.actor[circle].attrs[AttrType.PLAYER_NOTICED] = 1;
                                             if(M.actor[circle].type != ActorType.DEMON_LORD){
                                                 M.actor[circle].attrs[AttrType.NO_ITEM] = 1;
                                             }
                                         }
                                         M.final_level_demon_count++;
                                     }
                                 }
                                 else{
                                     B.Add(the_name + " dies. ",this);
                                 }
                             }
                         }
                         if(IsFinalLevelDemon()){
                             bool circles = false;
                             bool demons = false;
                             for(int i=0;i<5;++i){
                                 Tile circle = M.tile[M.FinalLevelSummoningCircle(i)];
                                 if(circle.TilesWithinDistance(3).Any(x=>x.type == TileType.DEMONIC_IDOL)){
                                     circles = true;
                                     break;
                                 }
                             }
                             foreach(Actor a in M.AllActors()){
                                 if(a != this && a.IsFinalLevelDemon()){
                                     demons = true;
                                     break;
                                 }
                             }
                             if(!circles && !demons){ //victory
                                 curhp = 100;
                                 B.Add("As the last demon falls, your victory gives you a new surge of strength. ");
                                 B.PrintAll();
                                 B.Add("Kersai's summoning has been stopped. His cult will no longer threaten the area. ");
                                 B.PrintAll();
                                 B.Add("You begin the journey home to deliver the news. ");
                                 B.PrintAll();
                                 Global.GAME_OVER = true;
                                 Global.BOSS_KILLED = true;
                                 Global.KILLED_BY = "nothing";
                                 return false;
                             }
                         }
                         if(HasAttr(AttrType.REGENERATES_FROM_DEATH) && dmg.type == DamageType.FIRE){
                             attrs[AttrType.REGENERATES_FROM_DEATH] = 0;
                         }
                     }
                 }
             }
             if(HasAttr(AttrType.TURN_INTO_CORPSE)){
                 attrs[AttrType.CORPSE] = attrs[AttrType.TURN_INTO_CORPSE];
                 attrs[AttrType.TURN_INTO_CORPSE] = 0;
                 if(!HasAttr(AttrType.NO_CORPSE_KNOCKBACK)){
                     if(HasAttr(AttrType.NONLIVING)){
                         SetName("destroyed " + name);
                     }
                     else{
                         SetName(name + "'s corpse");
                     }
                 }
                 return false;
             }
             if(HasAttr(AttrType.BURNING)){
                 tile().AddFeature(FeatureType.FIRE);
             }
             if(LightRadius() > 0){
                 UpdateRadius(LightRadius(),0);
             }
             if(type == ActorType.SHADOW){
                 type = ActorType.ZOMBIE; //awful awful hack. (CalculateDimming checks for Shadows)
                 CalculateDimming();
                 type = ActorType.SHADOW;
             }
             if(HasAttr(AttrType.REGENERATES_FROM_DEATH)){
                 Tile troll = null;
                 foreach(Tile t in M.ReachableTilesByDistance(row,col,false)){
                     if(!t.Is(TileType.DOOR_O) && !t.Is(FeatureType.TROLL_CORPSE,FeatureType.TROLL_BLOODWITCH_CORPSE,FeatureType.BONES)){
                         if(type == ActorType.TROLL){
                             t.AddFeature(FeatureType.TROLL_CORPSE);
                         }
                         else{
                             t.AddFeature(FeatureType.TROLL_BLOODWITCH_CORPSE);
                         }
                         troll = t;
                         break;
                     }
                 }
                 if(curhp > -3){
                     curhp = -3;
                 }
                 //curhp -= R.Roll(10)+5;
                 if(curhp < -50){
                     curhp = -50;
                 }
                 Event e = new Event(troll,100,EventType.REGENERATING_FROM_DEATH);
                 e.value = curhp;
                 e.secondary_value = attrs[AttrType.PERMANENT_DAMAGE];
                 e.tiebreaker = tiebreakers.IndexOf(this);
                 Q.Add(e);
             }
             if(HasAttr(AttrType.REASSEMBLES)){
                 Tile sk = null;
                 foreach(Tile t in M.ReachableTilesByDistance(row,col,false)){
                     if(!t.Is(TileType.DOOR_O) && !t.Is(FeatureType.TROLL_CORPSE,FeatureType.TROLL_BLOODWITCH_CORPSE,FeatureType.BONES)){
                         if(type == ActorType.SKELETON){
                             t.AddFeature(FeatureType.BONES);
                         }
                         sk = t;
                         break;
                     }
                 }
                 Event e = new Event(sk,R.Between(10,20)*100,EventType.REASSEMBLING);
                 e.tiebreaker = tiebreakers.IndexOf(this);
                 Q.Add(e);
             }
             if(type == ActorType.STONE_GOLEM){
                 List<Tile> deleted = new List<Tile>();
                 while(true){
                     bool changed = false;
                     foreach(Tile t in TilesWithinDistance(3)){
                         if(t.Is(TileType.STALAGMITE) && HasLOE(t)){
                             t.Toggle(null);
                             deleted.Add(t);
                             changed = true;
                         }
                     }
                     if(!changed){
                         break;
                     }
                 }
                 Q.RemoveTilesFromEventAreas(deleted,EventType.STALAGMITE);
                 List<Tile> area = new List<Tile>();
                 foreach(Tile t in TilesWithinDistance(3)){
                     if((t.IsTrap() || t.Is(TileType.FLOOR,TileType.GRAVE_DIRT,TileType.GRAVEL)) && t.inv == null && (t.actor() == null || t.actor() == this) && HasLOE(t)){
                         if(R.CoinFlip()){
                             area.Add(t);
                         }
                     }
                 }
                 if(area.Count > 0){
                     foreach(Tile t in area){
                         TileType previous_type = t.type;
                         t.Toggle(null,TileType.STALAGMITE);
                         t.toggles_into = previous_type;
                     }
                     Q.Add(new Event(area,150,EventType.STALAGMITE,5));
                 }
             }
             if(type == ActorType.VULGAR_DEMON && DistanceFrom(player) == 1){
                 B.Add("The vulgar demon possesses your " + player.EquippedWeapon + "! ");
                 B.Print(true);
                 player.EquippedWeapon.status[EquipmentStatus.POSSESSED] = true;
                 Help.TutorialTip(TutorialTopic.Possessed);
             }
             if(type == ActorType.DREAM_SPRITE){
                 int num = R.Roll(5) + 4;
                 List<Tile> new_area = tile().AddGaseousFeature(FeatureType.PIXIE_DUST,num);
                 if(new_area.Count > 0){
                     Event.RemoveGas(new_area,400,FeatureType.PIXIE_DUST,25);
                 }
             }
             if(type == ActorType.FROSTLING){
                 if(player.CanSee(tile()) && player.HasLOS(tile())){
                     AnimateExplosion(this,2,Color.RandomIce,'*');
                     B.Add("The air freezes around the defeated frostling. ",this);
                 }
                 foreach(Tile t in TilesWithinDistance(2)){
                     if(HasLOE(t)){
                         t.ApplyEffect(DamageType.COLD);
                         Actor a = t.actor();
                         if(a != null && a != this){
                             a.ApplyFreezing();
                         }
                     }
                 }
             }
             if(player.HasAttr(AttrType.CONVICTION)){
                 player.attrs[AttrType.KILLSTREAK]++;
             }
             if(HasAttr(AttrType.GRABBING)){
                 Actor grabbed = ActorInDirection(attrs[AttrType.GRABBING]);
                 if(grabbed != null && grabbed.HasAttr(AttrType.GRABBED)){
                     grabbed.attrs[AttrType.GRABBED]--;
                 }
             }
             if(HasAttr(AttrType.HUMANOID_INTELLIGENCE) || type == ActorType.ZOMBIE){
                 if(R.OneIn(3) && !HasAttr(AttrType.NO_ITEM)){
                     tile().GetItem(Item.Create(Item.RandomItem(),-1,-1));
                 }
             }
             foreach(Item item in inv){
                 tile().GetItem(item);
             }
             Q.KillEvents(this,EventType.ANY_EVENT);
             M.RemoveTargets(this);
             int idx = tiebreakers.IndexOf(this);
             if(idx != -1){
                 tiebreakers[idx] = null;
             }
             if(group != null){
                 if(type == ActorType.DREAM_WARRIOR || type == ActorType.DREAM_SPRITE){
                     List<Actor> temp = new List<Actor>();
                     foreach(Actor a in group){
                         if(a != this){
                             temp.Add(a);
                             a.group = null;
                         }
                     }
                     foreach(Actor a in temp){
                         a.Kill();
                     }
                 }
                 else{
                     if(group.Count >= 2 && this == group[0] && HasAttr(AttrType.WANDERING)){
                         if(type != ActorType.NECROMANCER){
                             group[1].attrs[AttrType.WANDERING]++;
                         }
                     }
                     if(group.Count <= 2 || type == ActorType.NECROMANCER){
                         foreach(Actor a in group){
                             if(a != this){
                                 a.group = null;
                             }
                         }
                         group.Clear();
                         group = null;
                     }
                     else{
                         group.Remove(this);
                         group = null;
                     }
                 }
             }
             M.actor[row,col] = null;
             return false;
         }
     }
     else{
         if(HasFeat(FeatType.FEEL_NO_PAIN) && damage_dealt && curhp < 20 && old_hp >= 20){
             B.Add("You can feel no pain! ");
             attrs[AttrType.INVULNERABLE]++;
             Q.Add(new Event(this,500,AttrType.INVULNERABLE,"You can feel pain again. "));
         }
         if(magic_trinkets.Contains(MagicTrinketType.CLOAK_OF_SAFETY) && damage_dealt && dmg.amount >= curhp){
             B.PrintAll();
             M.Draw();
             if(B.YesOrNoPrompt("Your cloak starts to vanish. Use your cloak to escape?",false)){
                 PosArray<bool> good = new PosArray<bool>(ROWS,COLS);
                 foreach(Tile t in M.AllTiles()){
                     if(t.passable){
                         good[t.row,t.col] = true;
                     }
                     else{
                         good[t.row,t.col] = false;
                     }
                 }
                 foreach(Actor a in M.AllActors()){
                     foreach(Tile t in M.AllTiles()){
                         if(good[t.row,t.col]){
                             if(a.DistanceFrom(t) < 6 || a.HasLOS(t.row,t.col)){ //was CanSee, but this is safer
                                 good[t.row,t.col] = false;
                             }
                         }
                     }
                 }
                 List<Tile> tilelist = new List<Tile>();
                 Tile destination = null;
                 for(int i=4;i<COLS;++i){
                     foreach(pos p in PositionsAtDistance(i)){
                         if(good[p]){
                             tilelist.Add(M.tile[p.row,p.col]);
                         }
                     }
                     if(tilelist.Count > 0){
                         destination = tilelist[R.Roll(1,tilelist.Count)-1];
                         break;
                     }
                 }
                 if(destination != null){
                     Move(destination.row,destination.col);
                 }
                 else{
                     for(int i=0;i<9999;++i){
                         int rr = R.Roll(1,ROWS-2);
                         int rc = R.Roll(1,COLS-2);
                         if(M.tile[rr,rc].passable && M.actor[rr,rc] == null && DistanceFrom(rr,rc) >= 6 && !M.tile[rr,rc].IsTrap()){
                             Move(rr,rc);
                             break;
                         }
                     }
                 }
                 B.Add("You escape. ");
             }
             B.Add("Your cloak vanishes completely! ");
             magic_trinkets.Remove(MagicTrinketType.CLOAK_OF_SAFETY);
         }
     }
     return true;
 }
예제 #2
0
 public Tile(Tile t,int r,int c)
 {
     type = t.type;
     name = t.name;
     a_name = t.a_name;
     the_name = t.the_name;
     symbol = t.symbol;
     color = t.color;
     if(t.type == TileType.BRUSH){
         if(R.CoinFlip()){
             color = Color.Yellow;
         }
         if(R.OneIn(20)){
             color = Color.Green;
         }
     }
     passable = t.passable;
     opaque = t.opaque;
     seen = false;
     revealed_by_light = t.revealed_by_light;
     solid_rock = false;
     light_value = 0;
     toggles_into = t.toggles_into;
     inv = null;
     row = r;
     col = c;
     light_radius = t.light_radius;
     direction_exited = 0;
     sprite_offset = t.sprite_offset;
 }
예제 #3
0
 public static Tile Create(TileType type,int r,int c)
 {
     Tile t = null;
     if(M.tile[r,c] == null){
         t = new Tile(proto[type],r,c);
         M.tile[r,c] = t; //bounds checking here?
     }
     return t;
 }
 public static void Targeting_DisplayContents(Tile tc,string always_displayed,string unseen_area_message,bool include_monsters,bool first_iteration)
 {
     if(always_displayed == ""){
         if(include_monsters && tc.actor() == player){
             if(!first_iteration){
                 string s = "You're standing here. ";
                 //if(tc.ContentsCount() == 0 && tc.type == TileType.FLOOR){
                 if(tc.ContentsCount() == 0 && tc.name == "floor"){
                     B.DisplayNow(s);
                 }
                 else{
                     B.DisplayNow(s + tc.ContentsString() + " here. ");
                 }
             }
         }
         else{
             if(player.CanSee(tc)){
                 B.DisplayNow(tc.ContentsString(include_monsters) + ". ");
                 if(!Help.displayed[TutorialTopic.Traps] && tc.IsKnownTrap()){
                     Help.TutorialTip(TutorialTopic.Traps);
                 }
                 else{
                     if(!Help.displayed[TutorialTopic.NotRevealedByLight] && ((tc.IsShrine() || tc.IsKnownTrap()) && !tc.revealed_by_light) || (tc.inv != null && !tc.inv.revealed_by_light)){
                         Help.TutorialTip(TutorialTopic.NotRevealedByLight);
                     }
                     else{
                         if(!Help.displayed[TutorialTopic.Fire] && tc.Is(FeatureType.FIRE)){
                             Help.TutorialTip(TutorialTopic.Fire);
                         }
                         else{
                             switch(tc.type){
                             case TileType.BLAST_FUNGUS:
                             Help.TutorialTip(TutorialTopic.BlastFungus,true);
                             break;
                             case TileType.CRACKED_WALL:
                             Help.TutorialTip(TutorialTopic.CrackedWall,true);
                             break;
                             case TileType.FIREPIT:
                             Help.TutorialTip(TutorialTopic.FirePit,true);
                             break;
                             case TileType.POOL_OF_RESTORATION:
                             Help.TutorialTip(TutorialTopic.PoolOfRestoration,true);
                             break;
                             case TileType.STONE_SLAB:
                             case TileType.STONE_SLAB_OPEN:
                             Help.TutorialTip(TutorialTopic.StoneSlab,true);
                             break;
                             case TileType.COMBAT_SHRINE:
                             case TileType.DEFENSE_SHRINE:
                             case TileType.MAGIC_SHRINE:
                             case TileType.SPIRIT_SHRINE:
                             case TileType.STEALTH_SHRINE:
                             Help.TutorialTip(TutorialTopic.Shrines,true);
                             break;
                             }
                         }
                     }
                 }
             }
             else{
                 if(include_monsters && tc.actor() != null && player.CanSee(tc.actor())){
                     B.DisplayNow("You sense " + tc.actor().a_name + " " + tc.actor().WoundStatus() + ". ");
                 }
                 else{
                     if(tc.seen){
                         if(tc.inv != null){
                             char itemch = tc.inv.symbol;
                             char screench = Screen.MapChar(tc.row,tc.col).c;
                             if(itemch == screench){ //hacky, but it seems to work (when a monster drops an item you haven't seen yet)
                                 if(tc.inv.quantity > 1){
                                     B.DisplayNow("You can no longer see these " + tc.inv.Name(true) + ". ");
                                 }
                                 else{
                                     B.DisplayNow("You can no longer see this " + tc.inv.Name(true) + ". ");
                                 }
                             }
                             else{
                                 B.DisplayNow("You can no longer see this " + tc.Name(true) + ". ");
                             }
                         }
                         else{
                             B.DisplayNow("You can no longer see this " + tc.Name(true) + ". ");
                         }
                     }
                     else{
                         B.DisplayNow(unseen_area_message);
                     }
                 }
             }
         }
     }
     else{
         B.DisplayNow(always_displayed);
     }
 }
 public static void Targeting_ShowLine(Tile tc,int radius,colorchar[,] mem,List<Tile> line,List<Tile> oldline,ref bool blocked,TileDelegate is_blocking)
 {
     foreach(Tile t in line){
         if(t.row != player.row || t.col != player.col || tc.actor() != player){
             colorchar cch = mem[t.row,t.col];
             if(t.row == tc.row && t.col == tc.col){
                 if(!blocked){
                     cch.bgcolor = Color.Green;
                     if(Global.LINUX && !Screen.GLMode){ //no bright bg in terminals
                         cch.bgcolor = Color.DarkGreen;
                     }
                     if(cch.color == cch.bgcolor){
                         cch.color = Color.Black;
                     }
                     Screen.WriteMapChar(t.row,t.col,cch);
                 }
                 else{
                     cch.bgcolor = Color.Red;
                     if(Global.LINUX && !Screen.GLMode){
                         cch.bgcolor = Color.DarkRed;
                     }
                     if(cch.color == cch.bgcolor){
                         cch.color = Color.Black;
                     }
                     Screen.WriteMapChar(t.row,t.col,cch);
                 }
             }
             else{
                 if(!blocked){
                     cch.bgcolor = Color.DarkGreen;
                     if(cch.color == cch.bgcolor){
                         cch.color = Color.Black;
                     }
                     Screen.WriteMapChar(t.row,t.col,cch);
                 }
                 else{
                     cch.bgcolor = Color.DarkRed;
                     if(cch.color == cch.bgcolor){
                         cch.color = Color.Black;
                     }
                     Screen.WriteMapChar(t.row,t.col,cch);
                 }
             }
             if(is_blocking(t)){
                 blocked = true;
             }
         }
         oldline.Remove(t);
     }
     if(radius > 0){
         foreach(Tile t in tc.TilesWithinDistance(radius,true)){
             if(!line.Contains(t)){
                 colorchar cch = mem[t.row,t.col];
                 if(blocked){
                     cch.bgcolor = Color.DarkRed;
                 }
                 else{
                     cch.bgcolor = Color.DarkGreen;
                 }
                 if(cch.color == cch.bgcolor){
                     cch.color = Color.Black;
                 }
                 Screen.WriteMapChar(t.row,t.col,cch);
                 oldline.Remove(t);
             }
         }
     }
 }
예제 #6
0
 public async Task SeekAI()
 {
     if (await PathStep())
     {
         return;
     }
     switch (atype)
     {
         /*case ActorType.SHAMBLING_SCARECROW:
             if(Global.CoinFlip()){
                 AI_Step(TileInDirection(Global.RandomDirection()));
             }
             else{
                 if(Global.Roll(1,3) == 3 && DistanceFrom(player) <= 10){
                     if(player.CanSee(this)){
                         B.Add(the_name + " emits an eerie whistling sound. ");
                     }
                     else{
                         B.Add("You hear an eerie whistling sound. ");
                     }
                 }
             }
             Q1();
             break;*/
         case ActorType.BLOOD_MOTH:
             {
                 PhysicalObject brightest = null;
                 if (!M.wiz_lite)
                 {
                     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.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)
                                 {
                                     if (pos_radius > o.light_radius)
                                     {
                                         if (CanSee(t))
                                         {
                                             current_brightest.Clear();
                                             current_brightest.Add(pos_obj);
                                             break;
                                         }
                                     }
                                     else
                                     {
                                         if (pos_radius == o.light_radius && DistanceFrom(t) < DistanceFrom(o))
                                         {
                                             if (CanSee(t))
                                             {
                                                 current_brightest.Clear();
                                                 current_brightest.Add(pos_obj);
                                                 break;
                                             }
                                         }
                                         else
                                         {
                                             if (pos_radius == o.light_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)
                     {
                         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)
                         {
                             await AI_Step(open.Random());
                         }
                         QS();
                     }
                     else
                     {
                         await 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)
                     {
                         await AI_Step(TileInDirection(dir));
                         QS();
                     }
                     else
                     {
                         if (player.HasLOS(TileInDirection(dir)))
                         {
                             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).TheVisible() + ". ", this);
                                 }
                             }
                         }
                         QS();
                     }
                 }
                 break;
             }
         case ActorType.PHASE_SPIDER:
             if (DistanceFrom(target) <= 10)
             {
                 if (Global.Roll(1, 4) == 4)
                 { //teleport into target's LOS somewhere nearby
                     List<Tile> tilelist = new List<Tile>();
                     for (int i = 0; i < ROWS; ++i)
                     {
                         for (int j = 0; j < COLS; ++j)
                         {
                             if (M.tile[i, j].passable && M.actor[i, j] == null)
                             {
                                 if (DistanceFrom(i, j) <= 10 && target.DistanceFrom(i, j) <= 10 && target.CanSee(i, j))
                                 {
                                     tilelist.Add(M.tile[i, j]);
                                 }
                             }
                         }
                     }
                     if (tilelist.Count > 0)
                     {
                         Tile t = tilelist[Global.Roll(1, tilelist.Count) - 1];
                         await Move(t.row, t.col);
                     }
                     QS();
                 }
                 else
                 { //do nothing
                     QS();
                 }
             }
             else
             { //forget about target, do nothing
                 target = null;
                 QS();
             }
             break;
         case ActorType.ORC_WARMAGE:
             foreach (Actor a in ActorsWithinDistance(2))
             {
                 if (a.HasAttr(AttrType.SPELL_DISRUPTION) && a.HasLOE(this))
                 {
                     QS();
                     return;
                 }
             }
             if (!HasAttr(AttrType.BLOODSCENT))
             {
                 await CastSpell(SpellType.BLOODSCENT);
             }
             else
             {
                 QS();
             }
             break;
         case ActorType.CARNIVOROUS_BRAMBLE:
         case ActorType.MUD_TENTACLE:
         case ActorType.LASHER_FUNGUS:
         case ActorType.MARBLE_HORROR_STATUE:
             QS();
             break;
         case ActorType.FIRE_DRAKE:
             FindPath(player);
             QS();
             break;
         default:
             if (target_location != null)
             {
                 if (DistanceFrom(target_location) == 1 && M.actor[target_location.p] != null)
                 {
                     if (GrabPreventsMovement(target_location) || M.actor[target_location.p].GrabPreventsMovement(tile())
                     || HasAttr(AttrType.NEVER_MOVES) || M.actor[target_location.p].HasAttr(AttrType.NEVER_MOVES))
                     {
                         QS(); //todo: should target_location be cleared here?
                     }
                     else
                     {
                         await Move(target_location.row, target_location.col); //swap places
                         target_location = null;
                         QS();
                     }
                 }
                 else
                 {
                     if (await AI_Step(target_location))
                     {
                         QS();
                         if (DistanceFrom(target_location) == 0)
                         {
                             target_location = null;
                         }
                     }
                     else
                     { //could not move, end turn.
                         if (DistanceFrom(target_location) == 1 && !target_location.passable)
                         {
                             target_location = null;
                         }
                         QS();
                     }
                 }
             }
             else
             {
                 if (DistanceFrom(target) <= 5)
                 {
                     if (DistanceFrom(target) <= 3)
                     {
                         List<pos> path2 = GetPath(target, 4);
                         if (path2.Count > 0)
                         {
                             path = path2;
                             player_visibility_duration = -1; //stay at -1 while in close pursuit
                         }
                     }
                     else
                     {
                         List<pos> path2 = GetPath(target, 8);
                         if (path2.Count <= 10)
                         {
                             path = path2;
                         }
                     }
                     //FindPath(target,8);
                     if (await PathStep())
                     {
                         return;
                     }
                     QS();
                 }
                 else
                 { //if they're too far away, forget them and end turn.
                     target = null;
                     if (group != null && group[0] != this)
                     {
                         if (DistanceFrom(group[0]) > 1)
                         {
                             int dir = DirectionOf(group[0]);
                             bool found = false;
                             for (int i = -1; i <= 1; ++i)
                             {
                                 Actor a = ActorInDirection(RotateDirection(dir, true, i));
                                 if (a != null && group.Contains(a))
                                 {
                                     found = true;
                                     break;
                                 }
                             }
                             if (!found)
                             {
                                 if (HasLOS(group[0]))
                                 {
                                     await AI_Step(group[0]);
                                 }
                                 else
                                 {
                                     FindPath(group[0], 8);
                                     if (await PathStep())
                                     {
                                         return;
                                     }
                                 }
                             }
                         }
                     }
                     QS();
                 }
             }
             break;
     }
 }
예제 #7
0
 public async Task<bool> TakeDamage(Damage dmg, string cause_of_death)
 { //returns true if still alive
     bool damage_dealt = false;
     int old_hp = curhp;
     if (HasAttr(AttrType.FROZEN))
     {
         //attrs[Forays.AttrType.FROZEN] -= (dmg.amount+1) / 2;
         attrs[Forays.AttrType.FROZEN] -= (dmg.amount * 9) / 10;
         if (attrs[Forays.AttrType.FROZEN] <= 0)
         {
             attrs[Forays.AttrType.FROZEN] = 0;
             B.Add("The ice breaks! ", this);
         }
         //dmg.amount = dmg.amount / 2;
         dmg.amount = dmg.amount / 10;
     }
     if (HasAttr(AttrType.MECHANICAL_SHIELD))
     {
         B.Add(Your() + " shield moves to protect it from harm. ", this);
         return true;
     }
     if (HasAttr(AttrType.INVULNERABLE))
     {
         dmg.amount = 0;
     }
     if (HasAttr(AttrType.TOUGH) && dmg.damclass == DamageClass.PHYSICAL)
     {
         dmg.amount -= 2;
     }
     if (dmg.damclass == DamageClass.MAGICAL)
     {
         dmg.amount -= TotalSkill(SkillType.SPIRIT) / 2;
     }
     if (HasAttr(AttrType.ARCANE_SHIELDED))
     {
         if (attrs[Forays.AttrType.ARCANE_SHIELDED] >= dmg.amount)
         {
             attrs[Forays.AttrType.ARCANE_SHIELDED] -= dmg.amount;
             if (attrs[Forays.AttrType.ARCANE_SHIELDED] < 0)
             {
                 attrs[Forays.AttrType.ARCANE_SHIELDED] = 0;
             }
             dmg.amount = 0;
         }
         else
         {
             dmg.amount -= attrs[Forays.AttrType.ARCANE_SHIELDED];
             attrs[Forays.AttrType.ARCANE_SHIELDED] = 0;
         }
         if (!HasAttr(AttrType.ARCANE_SHIELDED))
         {
             B.Add(Your() + " arcane shield crumbles. ", this);
         }
     }
     bool resisted = false;
     switch (dmg.type)
     {
         case DamageType.NORMAL:
             if (dmg.amount > 0)
             {
                 curhp -= dmg.amount;
                 damage_dealt = true;
             }
             else
             {
                 B.Add(YouAre() + " undamaged. ", this);
             }
             break;
         case DamageType.SLASHING:
             {
                 int div = 1;
                 if (HasAttr(AttrType.RESIST_SLASH))
                 {
                     for (int i = attrs[AttrType.RESIST_SLASH]; i > 0; --i)
                     {
                         div = div * 2;
                     }
                     B.Add(You("resist") + ". ", this);
                     resisted = true;
                 }
                 dmg.amount = dmg.amount / div;
                 if (dmg.amount > 0)
                 {
                     curhp -= dmg.amount;
                     damage_dealt = true;
                 }
                 else
                 {
                     B.Add(YouAre() + " unharmed. ", this);
                 }
                 break;
             }
         case DamageType.BASHING:
             {
                 int div = 1;
                 if (HasAttr(AttrType.RESIST_BASH))
                 {
                     for (int i = attrs[AttrType.RESIST_BASH]; i > 0; --i)
                     {
                         div = div * 2;
                     }
                     B.Add(You("resist") + ". ", this);
                     resisted = true;
                 }
                 dmg.amount = dmg.amount / div;
                 if (dmg.amount > 0)
                 {
                     curhp -= dmg.amount;
                     damage_dealt = true;
                 }
                 else
                 {
                     B.Add(YouAre() + " unharmed. ", this);
                 }
                 break;
             }
         case DamageType.PIERCING:
             {
                 int div = 1;
                 if (HasAttr(AttrType.RESIST_PIERCE))
                 {
                     for (int i = attrs[AttrType.RESIST_PIERCE]; i > 0; --i)
                     {
                         div = div * 2;
                     }
                     B.Add(You("resist") + ". ", this);
                     resisted = true;
                 }
                 dmg.amount = dmg.amount / div;
                 if (dmg.amount > 0)
                 {
                     curhp -= dmg.amount;
                     damage_dealt = true;
                 }
                 else
                 {
                     B.Add(YouAre() + " unharmed. ", this);
                 }
                 break;
             }
         case DamageType.MAGIC:
             if (dmg.amount > 0)
             {
                 curhp -= dmg.amount;
                 damage_dealt = true;
             }
             else
             {
                 B.Add(YouAre() + " unharmed. ", this);
             }
             break;
         case DamageType.FIRE:
             {
                 int div = 1;
                 if (HasAttr(AttrType.IMMUNE_FIRE))
                 {
                     dmg.amount = 0;
                     //B.Add(the_name + " is immune! ",this);
                 }
                 else
                 {
                     if (HasAttr(AttrType.RESIST_FIRE))
                     {
                         for (int i = attrs[AttrType.RESIST_FIRE]; i > 0; --i)
                         {
                             div = div * 2;
                         }
                         B.Add(You("resist") + ". ", this);
                         resisted = true;
                     }
                 }
                 dmg.amount = dmg.amount / div;
                 if (dmg.amount > 0)
                 {
                     curhp -= dmg.amount;
                     damage_dealt = true;
                     /*if(type == ActorType.SHAMBLING_SCARECROW && speed != 50){
                         speed = 50;
                         if(attrs[AttrType.ON_FIRE] >= LightRadius()){
                             UpdateRadius(LightRadius(),LightRadius()+1);
                         }
                         attrs[AttrType.ON_FIRE]++;
                         B.Add(the_name + " leaps about as it catches fire! ",this);
                     }*/
                 }
                 else
                 {
                     if (atype != ActorType.CORPSETOWER_BEHEMOTH)
                     {
                         B.Add(YouAre() + " unburnt. ", this);
                     }
                 }
                 break;
             }
         case DamageType.COLD:
             {
                 int div = 1;
                 if (HasAttr(AttrType.IMMUNE_COLD))
                 {
                     dmg.amount = 0;
                     //B.Add(YouAre() + " unharmed. ",this);
                 }
                 else
                 {
                     if (HasAttr(AttrType.RESIST_COLD))
                     {
                         for (int i = attrs[AttrType.RESIST_COLD]; i > 0; --i)
                         {
                             div = div * 2;
                         }
                         B.Add(You("resist") + ". ", this);
                         resisted = true;
                     }
                 }
                 dmg.amount = dmg.amount / div;
                 if (dmg.amount > 0)
                 {
                     curhp -= dmg.amount;
                     damage_dealt = true;
                 }
                 else
                 {
                     B.Add(YouAre() + " unharmed. ", this);
                 }
                 break;
             }
         case DamageType.ELECTRIC:
             {
                 int div = 1;
                 if (HasAttr(AttrType.RESIST_ELECTRICITY))
                 {
                     for (int i = attrs[AttrType.RESIST_ELECTRICITY]; i > 0; --i)
                     {
                         div = div * 2;
                     }
                     B.Add(You("resist") + ". ", this);
                     resisted = true;
                 }
                 dmg.amount = dmg.amount / div;
                 if (dmg.amount > 0)
                 {
                     curhp -= dmg.amount;
                     damage_dealt = true;
                 }
                 else
                 {
                     B.Add(YouAre() + " unharmed. ", this);
                 }
                 break;
             }
         case DamageType.POISON:
             if (HasAttr(AttrType.UNDEAD) || HasAttr(AttrType.CONSTRUCT) || HasAttr(AttrType.IMMUNE_TOXINS))
             {
                 dmg.amount = 0;
             }
             if (dmg.amount > 0)
             {
                 curhp -= dmg.amount;
                 damage_dealt = true;
                 if (atype == ActorType.PLAYER)
                 {
                     if (tile().Is(FeatureType.POISON_GAS))
                     {
                         B.Add("The poisonous gas burns your skin! ");
                     }
                     else
                     {
                         B.Add("You feel the poison coursing through your veins! ");
                     }
                 }
                 else
                 {
                     if (Global.Roll(1, 5) == 5)
                     {
                         B.Add(the_name + " shudders. ", this);
                     }
                 }
             }
             break;
         case DamageType.HEAL:
             curhp += dmg.amount;
             if (curhp > maxhp)
             {
                 curhp = maxhp;
             }
             break;
         case DamageType.NONE:
             break;
     }
     if (dmg.source != null && dmg.source == player && dmg.damclass == DamageClass.PHYSICAL && resisted && !(cause_of_death.Search(new Regex("arrow")) > -1))
     {
         await Help.TutorialTip(TutorialTopic.Resistance);
     }
     if (damage_dealt)
     {
         if (HasAttr(AttrType.MAGICAL_BLOOD))
         {
             recover_time = Q.turn + 200;
         }
         else
         {
             recover_time = Q.turn + 500;
         }
         Interrupt();
         if (HasAttr(AttrType.ASLEEP))
         {
             attrs[Forays.AttrType.ASLEEP] = 0;
             Global.FlushInput();
         }
         if (dmg.source != null)
         {
             if (atype != ActorType.PLAYER && dmg.source != this)
             {
                 target = dmg.source;
                 target_location = M.tile[dmg.source.row, dmg.source.col];
                 if (dmg.source.IsHiddenFrom(this))
                 {
                     player_visibility_duration = -1;
                 }
                 if (atype == ActorType.CRUSADING_KNIGHT && dmg.source == player && !HasAttr(AttrType.COOLDOWN_1) && !M.wiz_lite && !CanSee(player) && curhp > 0)
                 {
                     List<string> verb = new List<string> { "Show yourself", "Reveal yourself", "Unfold thyself", "Present yourself", "Unveil yourself", "Make yourself known" };
                     List<string> adjective = new List<string> { "despicable", "filthy", "foul", "nefarious", "vulgar", "sorry", "unworthy" };
                     List<string> noun = new List<string> { "villain", "blackguard", "devil", "scoundrel", "wretch", "cur", "rogue" };
                     B.Add(TheVisible() + " shouts \"" + verb.Random() + ", " + adjective.Random() + " " + noun.Random() + "!\" ");
                     B.Add(the_name + " raises a gauntlet. ", this);
                     B.Add("Sunlight fills the dungeon. ");
                     M.wiz_lite = true;
                     M.wiz_dark = false;
                     attrs[Forays.AttrType.COOLDOWN_1]++;
                 }
             }
         }
         if (HasAttr(AttrType.SPORE_BURST) && !HasAttr(AttrType.COOLDOWN_1))
         {
             attrs[AttrType.COOLDOWN_1]++;
             Q.Add(new Event(this, (Global.Roll(1, 5) + 1) * 100, AttrType.COOLDOWN_1));
             B.Add(You("retaliate") + " with a burst of spores! ", this);
             for (int i = 2; i <= 8; i += 2)
             {
                 AnimateStorm(i, 1, (((i * 2) + 1) * ((i * 2) + 1)) / 4, "*", Color.DarkYellow);
             }
             foreach (Actor a in ActorsWithinDistance(8))
             {
                 if (HasLOE(a.row, a.col) && a != this)
                 {
                     B.Add("The spores hit " + a.the_name + ". ", a);
                     if (!a.HasAttr(AttrType.UNDEAD) && !a.HasAttr(AttrType.CONSTRUCT)
                     && !a.HasAttr(AttrType.SPORE_BURST) && !a.HasAttr(AttrType.IMMUNE_TOXINS))
                     {
                         int duration = Global.Roll(2, 4);
                         a.attrs[AttrType.POISONED]++;
                         Q.Add(new Event(a, duration * 100, AttrType.POISONED));
                         if (a.name == "you")
                         {
                             B.Add("You are poisoned. ");
                         }
                         if (!a.HasAttr(AttrType.STUNNED))
                         {
                             a.attrs[AttrType.STUNNED]++;
                             Q.Add(new Event(a, duration * 100, AttrType.STUNNED, a.YouAre() + " no longer stunned. ", new PhysicalObject[] { a }));
                             B.Add(a.YouAre() + " stunned. ", a);
                         }
                     }
                     else
                     {
                         B.Add(a.YouAre() + " unaffected. ", a);
                     }
                 }
             }
         }
         if (HasAttr(AttrType.HOLY_SHIELDED) && dmg.source != null)
         {
             B.Add(YourVisible() + " holy shield burns " + dmg.source.TheVisible() + ". ", new PhysicalObject[] { this, dmg.source });
             int amount = Global.Roll(2, 6);
             if (amount >= dmg.source.curhp)
             {
                 amount = dmg.source.curhp - 1;
             }
             await dmg.source.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, amount, this); //doesn't yet prevent loops involving 2 holy shields.
         }
         if (HasFeat(FeatType.BOILING_BLOOD) && dmg.type != DamageType.POISON && attrs[AttrType.BLOOD_BOILED] < 5)
         {
             //if(!Global.Option(OptionType.NO_BLOOD_BOIL_MESSAGE)){
             B.Add("Your blood boils! ");
             //}
             speed -= 10;
             attrs[AttrType.BLOOD_BOILED]++;
             Q.KillEvents(this, AttrType.BLOOD_BOILED); //eventually replace this with refreshduration
             //GainAttr(AttrType.BLOOD_BOILED,1001,attrs[Forays.AttrType.BLOOD_BOILED],"Your blood cools. ");
             Q.Add(new Event(this, 1000, Forays.AttrType.BLOOD_BOILED, attrs[Forays.AttrType.BLOOD_BOILED], "Your blood cools. "));
         }
         if (atype == ActorType.MECHANICAL_KNIGHT)
         {
             if (curhp <= 10 && curhp > 0 && !HasAttr(AttrType.COOLDOWN_1) && !HasAttr(AttrType.COOLDOWN_2))
             {
                 if (Global.CoinFlip())
                 {
                     B.Add(Your() + " arms are destroyed! ", this);
                     attrs[Forays.AttrType.COOLDOWN_1]++;
                     attrs[Forays.AttrType.MECHANICAL_SHIELD] = 0;
                 }
                 else
                 {
                     B.Add(Your() + " legs are destroyed! ", this);
                     attrs[Forays.AttrType.COOLDOWN_2]++;
                     attrs[Forays.AttrType.NEVER_MOVES]++;
                     path.Clear();
                     target_location = null;
                 }
             }
         }
     }
     if (curhp <= 0)
     {
         if (atype == ActorType.PLAYER)
         {
             if (magic_items.Contains(MagicItemType.PENDANT_OF_LIFE))
             {
                 magic_items.Remove(MagicItemType.PENDANT_OF_LIFE);
                 curhp = 1;
                 B.Add("Your pendant glows brightly, then crumbles to dust. ");
             }
             else
             {
                 if (cause_of_death.Length > 0 && cause_of_death[0] == '*')
                 {
                     Global.KILLED_BY = cause_of_death.Substring(1);
                 }
                 else
                 {
                     Global.KILLED_BY = "killed by " + cause_of_death;
                 }
                 M.Draw();
                 if (Global.GAME_OVER == false)
                 {
                     B.Add("You die. ");
                 }
                 await B.PrintAll();
                 Global.GAME_OVER = true;
                 return false;
             }
         }
         else
         {
             if (HasAttr(AttrType.BOSS_MONSTER))
             {
                 M.Draw();
                 B.Add("The fire drake dies. ");
                 await B.PrintAll();
                 if (player.curhp > 0)
                 {
                     B.Add("The threat to your nation has been slain! You begin the long trek home to deliver the good news... ");
                     Global.KILLED_BY = "Died of ripe old age";
                 }
                 else
                 {
                     B.Add("The threat to your nation has been slain! Unfortunately, you won't be able to deliver the news... ");
                 }
                 await B.PrintAll();
                 Global.GAME_OVER = true;
                 Global.BOSS_KILLED = true;
             }
             if (atype == ActorType.BERSERKER && dmg.amount < 1000)
             { //hack
                 if (!HasAttr(AttrType.COOLDOWN_1))
                 {
                     attrs[AttrType.COOLDOWN_1]++;
                     Q.Add(new Event(this, 350, AttrType.COOLDOWN_1));
                     Q.KillEvents(this, AttrType.COOLDOWN_2);
                     if (!HasAttr(AttrType.COOLDOWN_2))
                     {
                         attrs[AttrType.COOLDOWN_2] = DirectionOf(player);
                     }
                     B.Add(the_name + " somehow remains standing! He screams with fury! ", this);
                 }
                 return true;
             }
             if (HasAttr(AttrType.REGENERATES_FROM_DEATH) && dmg.type != DamageType.FIRE)
             {
                 B.Add(the_name + " falls to the ground, still twitching. ", this);
                 Tile troll = null;
                 for (int i = 0; i < COLS && troll == null; ++i)
                 {
                     foreach (Tile t in TilesAtDistance(i))
                     {
                         if (t.passable && !t.Is(FeatureType.TROLL_CORPSE)
                         && !t.Is(FeatureType.TROLL_SEER_CORPSE) && !t.Is(FeatureType.QUICKFIRE))
                         {
                             if (atype == ActorType.TROLL)
                             {
                                 t.features.Add(FeatureType.TROLL_CORPSE);
                             }
                             else
                             {
                                 t.features.Add(FeatureType.TROLL_SEER_CORPSE);
                             }
                             troll = t;
                             break;
                         }
                     }
                 }
                 curhp -= Global.Roll(10) + 5;
                 if (curhp < -50)
                 {
                     curhp = -50;
                 }
                 AttrType attr = HasAttr(AttrType.COOLDOWN_1) ? AttrType.COOLDOWN_1 : AttrType.NO_ATTR;
                 Q.Add(new Event(troll, null, 200, EventType.REGENERATING_FROM_DEATH, attr, curhp, ""));
             }
             else
             {
                 if (dmg.amount < 1000 && !HasAttr(AttrType.BOSS_MONSTER))
                 { //everything that deals this much damage
                     if (HasAttr(AttrType.UNDEAD) || HasAttr(AttrType.CONSTRUCT))
                     { //prints its own message
                         B.Add(the_name + " is destroyed. ", this);
                     }
                     else
                     {
                         B.Add(the_name + " dies. ", this);
                     }
                 }
             }
             if (LightRadius() > 0)
             {
                 UpdateRadius(LightRadius(), 0);
             }
             if (atype == ActorType.SHADOW)
             {
                 if (player.HasAttr(AttrType.DIM_LIGHT))
                 {
                     atype = ActorType.ZOMBIE; //awful awful hack. (CalculateDimming checks for Shadows)
                     CalculateDimming();
                 }
             }
             if (atype == ActorType.STONE_GOLEM)
             {
                 foreach (Tile t in TilesWithinDistance(4))
                 {
                     if (t.name == "floor" && (t.actor() == null || t.actor() == this) && HasLOE(t))
                     {
                         if (DistanceFrom(t) <= 2 || Global.CoinFlip())
                         {
                             t.TransformTo(TileType.RUBBLE);
                         }
                     }
                 }
             }
             if (player.HasAttr(AttrType.CONVICTION))
             {
                 player.attrs[Forays.AttrType.KILLSTREAK]++;
             }
             if ((HasAttr(AttrType.HUMANOID_INTELLIGENCE) && atype != ActorType.DREAM_CLONE && atype != ActorType.FIRE_DRAKE)
                || atype == ActorType.ZOMBIE)
             {
                 if (Global.CoinFlip() && !HasAttr(AttrType.NO_ITEM))
                 {
                     tile().GetItem(Item.Create(Item.RandomItem(), -1, -1));
                 }
             }
             foreach (Item item in inv)
             {
                 tile().GetItem(item);
             }
             /*int divisor = 1;
             if(HasAttr(AttrType.SMALL_GROUP)){ divisor = 2; }
             if(HasAttr(AttrType.MEDIUM_GROUP)){ divisor = 3; }
             if(HasAttr(AttrType.LARGE_GROUP)){ divisor = 5; }
             if(!Global.GAME_OVER){
                 player.GainXP(xp + (level*(10 + level - player.level))/divisor); //experimentally giving the player any
             }*/
             Q.KillEvents(this, EventType.ANY_EVENT);					// XP that the monster had collected. currently always 0.
             M.RemoveTargets(this);
             int idx = Actor.tiebreakers.IndexOf(this);
             if (idx != -1)
             {
                 Actor.tiebreakers[Actor.tiebreakers.IndexOf(this)] = null;
             }
             if (group != null)
             {
                 if (group.Count >= 2 && this == group[0] && HasAttr(AttrType.WANDERING))
                 {
                     if (atype != ActorType.NECROMANCER && atype != ActorType.DREAM_WARRIOR)
                     {
                         group[1].attrs[Forays.AttrType.WANDERING]++;
                     }
                 }
                 if (group.Count <= 2 || atype == ActorType.NECROMANCER || atype == ActorType.DREAM_WARRIOR)
                 {
                     foreach (Actor a in group)
                     {
                         if (a != this)
                         {
                             a.group = null;
                         }
                     }
                     group.Clear();
                     group = null;
                 }
                 else
                 {
                     group.Remove(this);
                     group = null;
                 }
             }
             M.actor[row, col] = null;
             return false;
         }
     }
     else
     {
         if (HasFeat(FeatType.FEEL_NO_PAIN) && damage_dealt && curhp < 20 && old_hp >= 20)
         {
             B.Add("You can feel no pain! ");
             attrs[AttrType.INVULNERABLE]++;
             Q.Add(new Event(this, 500, AttrType.INVULNERABLE, "You can feel pain again. "));
         }
         if (magic_items.Contains(MagicItemType.CLOAK_OF_DISAPPEARANCE) && damage_dealt && dmg.amount >= curhp)
         {
             await B.PrintAll();
             M.Draw();
             B.DisplayNow("Your cloak starts to vanish. Use your cloak to escape?(y/n): ");
             Game.Console.CursorVisible = true;
             ConsoleKeyInfo command;
             bool done = false;
             while (!done)
             {
                 command = await Game.Console.ReadKey(true);
                 switch (command.KeyChar)
                 {
                     case 'n':
                     case 'N':
                         done = true;
                         break;
                     case 'y':
                     case 'Y':
                         done = true;
                         bool[,] good = new bool[ROWS, COLS];
                         foreach (Tile t in M.AllTiles())
                         {
                             if (t.passable)
                             {
                                 good[t.row, t.col] = true;
                             }
                             else
                             {
                                 good[t.row, t.col] = false;
                             }
                         }
                         foreach (Actor a in M.AllActors())
                         {
                             foreach (Tile t in M.AllTiles())
                             {
                                 if (good[t.row, t.col])
                                 {
                                     if (a.DistanceFrom(t) < 6 || a.HasLOS(t.row, t.col))
                                     { //was CanSee, but this is safer
                                         good[t.row, t.col] = false;
                                     }
                                 }
                             }
                         }
                         List<Tile> tilelist = new List<Tile>();
                         Tile destination = null;
                         for (int i = 4; i < COLS; ++i)
                         {
                             foreach (pos p in PositionsAtDistance(i))
                             {
                                 if (good[p.row, p.col])
                                 {
                                     tilelist.Add(M.tile[p.row, p.col]);
                                 }
                             }
                             if (tilelist.Count > 0)
                             {
                                 destination = tilelist[Global.Roll(1, tilelist.Count) - 1];
                                 break;
                             }
                         }
                         if (destination != null)
                         {
                             await Move(destination.row, destination.col);
                         }
                         else
                         {
                             for (int i = 0; i < 9999; ++i)
                             {
                                 int rr = Global.Roll(1, ROWS - 2);
                                 int rc = Global.Roll(1, COLS - 2);
                                 if (M.tile[rr, rc].passable && M.actor[rr, rc] == null && DistanceFrom(rr, rc) >= 6 && !M.tile[rr, rc].IsTrap())
                                 {
                                     await Move(rr, rc);
                                     break;
                                 }
                             }
                         }
                         B.Add("You escape. ");
                         break;
                     default:
                         break;
                 }
             }
             B.Add("Your cloak vanishes completely! ");
             magic_items.Remove(MagicItemType.CLOAK_OF_DISAPPEARANCE);
         }
     }
     return true;
 }
예제 #8
0
 public Actor(ActorType type_,string name_,char symbol_,Color color_,int maxhp_,int speed_,int light_radius_,params AttrType[] attrlist)
 {
     type = type_;
     SetName(name_);
     symbol = symbol_;
     color = color_;
     maxhp = maxhp_;
     curhp = maxhp;
     maxmp = 0;
     curmp = maxmp;
     speed = speed_;
     light_radius = light_radius_;
     target = null;
     inv = null;
     target_location = null;
     time_of_last_action = 0;
     recover_time = 0;
     player_visibility_duration = 0;
     exhaustion = 0;
     foreach(AttrType at in attrlist){
         attrs[at]++;
     }//row and col are -1
     switch(type){
     case ActorType.PLAYER:
         sprite_offset = new pos(0,32);
         break;
     case ActorType.MUD_TENTACLE:
         sprite_offset = new pos(13,32);
         break;
     case ActorType.GHOST:
         sprite_offset = new pos(13,34);
         break;
     default:
         if(type >= ActorType.MINOR_DEMON && type <= ActorType.DEMON_LORD){
             int diff = type - ActorType.MINOR_DEMON;
             sprite_offset = new pos(13,36+diff);
         }
         else{ //phantoms are handled in CreatePhantom()
             int diff = type - ActorType.GOBLIN;
             sprite_offset = new pos(4 + diff / 8,32 + (diff % 8)*2);
         }
         break;
     }
 }
예제 #9
0
 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;
 }
예제 #10
0
 public Actor(Actor a,int r,int c)
 {
     type = a.type;
     name = a.name;
     the_name = a.the_name;
     a_name = a.a_name;
     symbol = a.symbol;
     color = a.color;
     maxhp = a.maxhp;
     curhp = maxhp;
     maxmp = a.maxmp;
     curmp = maxmp;
     speed = a.speed;
     light_radius = a.light_radius;
     target = null;
     inv = new List<Item>();
     row = r;
     col = c;
     target_location = null;
     time_of_last_action = 0;
     recover_time = 0;
     player_visibility_duration = 0;
     weapons.AddFirst(new Weapon(WeaponType.NO_WEAPON));
     armors.AddFirst(new Armor(ArmorType.NO_ARMOR));
     attrs = new Dict<AttrType, int>(a.attrs);
     skills = new Dict<SkillType,int>(a.skills);
     feats = new Dict<FeatType,bool>(a.feats);
     spells = new Dict<SpellType,bool>(a.spells);
     exhaustion = 0;
     sprite_offset = a.sprite_offset;
 }
예제 #11
0
 public bool AI_Step(PhysicalObject obj,bool flee)
 {
     if(HasAttr(AttrType.IMMOBILE) || (type == ActorType.MECHANICAL_KNIGHT && attrs[AttrType.COOLDOWN_1] == 2)){
         return false;
     }
     if(SlippedOrStruggled()){
         return true;
     }
     List<int> dirs = new List<int>();
     List<int> sideways_directions = new List<int>();
     AI_Step_Build_Direction_Lists(tile(),obj,flee,dirs,sideways_directions);
     List<int> partially_blocked_dirs = new List<int>();
     foreach(int i in dirs){
         if(ActorInDirection(i) != null && ActorInDirection(i).IsHiddenFrom(this)){
             player_visibility_duration = -1;
             if(ActorInDirection(i) == player){
                 attrs[AttrType.PLAYER_NOTICED]++;
             }
             target = player; //not extensible yet
             target_location = M.tile[player.row,player.col];
             string walks = " walks straight into you! ";
             if(HasAttr(AttrType.FLYING)){
                 walks = " flies straight into you! ";
             }
             if(!IsHiddenFrom(player)){
                 B.Add(TheName(true) + walks);
                 if(!HasAttr(AttrType.MINDLESS) && player.CanSee(this)){
                     B.Add(the_name + " looks startled. ");
                 }
             }
             else{
                 attrs[AttrType.TURNS_VISIBLE] = -1;
                 attrs[AttrType.NOTICED]++;
                 B.Add(AName(true) + walks);
                 if(!HasAttr(AttrType.MINDLESS) && player.CanSee(this)){
                     B.Add(the_name + " looks just as surprised as you. ");
                 }
             }
             return true;
         }
         Tile t = TileInDirection(i);
         if(t.Is(TileType.RUBBLE) && (path == null || path.Count == 0 || t != M.tile[path[0]])){ //other tiles might go here eventually
             partially_blocked_dirs.Add(i);
         }
         else{
             if(AI_WillingToMove(tile(),t,obj) && AI_MoveOrOpen(i)){
                 return true;
             }
         }
     }
     foreach(int i in partially_blocked_dirs){
         if(AI_WillingToMove(tile(),TileInDirection(i),obj) && AI_MoveOrOpen(i)){
             return true;
         }
     }
     foreach(int i in sideways_directions){
         if(AI_WillingToMove(tile(),TileInDirection(i),obj) && AI_MoveOrOpen(i)){
             return true;
         }
     }
     return false;
 }
예제 #12
0
 private bool CheckForFear(Tile t)
 {
     int least_frightening_value = 99;
     List<Tile> least_frightening = new List<Tile>();
     List<Actor> attackable_actors = new List<Actor>();
     foreach(Tile neighbor in TilesAtDistance(1)){
         if(neighbor.passable){
             if(neighbor.actor() != null && CanSee(neighbor.actor())){
                 if(!neighbor.actor().HasAttr(AttrType.TERRIFYING)){
                     attackable_actors.Add(neighbor.actor());
                 }
                 continue;
             }
             int value = 0;
             foreach(int dir2 in U.EightDirections){
                 Actor n2 = neighbor.ActorInDirection(dir2);
                 if(n2 != null && n2.HasAttr(AttrType.TERRIFYING) && CanSee(n2)){
                     ++value;
                 }
             }
             if(value < least_frightening_value){
                 least_frightening_value = value;
                 least_frightening.Clear();
                 least_frightening.Add(neighbor);
             }
             else{
                 if(value == least_frightening_value){
                     least_frightening.Add(neighbor);
                 }
             }
         }
     }
     foreach(Actor a in attackable_actors){
         least_frightening.Add(a.tile());
     }
     if(!least_frightening.Contains(t)){
         B.Add("You're too afraid! ");
         List<pos> cells = new List<pos>();
         List<colorchar> chars = new List<colorchar>();
         foreach(Tile neighbor in TilesAtDistance(1)){
             if(neighbor.passable && !least_frightening.Contains(neighbor)){
                 cells.Add(neighbor.p);
                 colorchar cch = M.VisibleColorChar(neighbor.row,neighbor.col);
                 cch.bgcolor = Color.DarkMagenta;
                 if(!CanSee(neighbor)){
                     cch.color = Color.Black;
                     cch.c = ' ';
                 }
                 chars.Add(cch);
             }
         }
         AnimateVisibleMapCells(cells,chars);
         return true;
     }
     return false;
 }
예제 #13
0
 private bool AI_WillingToMove(Tile start,Tile next_step,PhysicalObject final_destination)
 {
     if(!next_step.p.BoundsCheck(M.tile,false)){
         return false;
     }
     int destination_danger = GetDangerRating(next_step); //DESTINATION: DANGER!
     int danger_threshold = (target != null? 1 : 0);
     if(destination_danger > danger_threshold && destination_danger > GetDangerRating(start)){
         return false;
     } //so, if it's actually too dangerous, the above check'll handle that. Next, let's check for unnecessary ventures into minor hazards:
     if(destination_danger > 0 && final_destination != null){
         if(!AI_CheckNextStep(next_step,final_destination,false)){
             return false;
         }
     }
     if(next_step.Is(TileType.CHASM,TileType.FIRE_RIFT) && (!HasAttr(AttrType.FLYING) || HasAttr(AttrType.DESCENDING))){
         return false;
     }
     if(HasAttr(AttrType.AVOIDS_LIGHT) && !M.wiz_dark && !M.wiz_lite){
         if(next_step.light_value > 0 && start.light_value == 0){
             if(!(type == ActorType.DARKNESS_DWELLER && HasAttr(AttrType.COOLDOWN_2))){
                 return false;
             }
         }
     }
     return true;
 }
예제 #14
0
 private bool AI_CheckNextStep(Tile start,PhysicalObject obj,bool flee)
 {
     List<int> dirs = new List<int>();
     List<int> sideways_directions = new List<int>();
     AI_Step_Build_Direction_Lists(start,obj,flee,dirs,sideways_directions);
     List<int> partially_blocked_dirs = new List<int>();
     Tile t = null;
     foreach(int i in dirs){
         t = start.TileInDirection(i);
         if(t.Is(TileType.RUBBLE)){ //other tiles might go here eventually
             partially_blocked_dirs.Add(i);
         }
         else{
             if(AI_WillingToMove(start,t,null)){
                 return true;
             }
         }
     }
     foreach(int i in partially_blocked_dirs){
         if(AI_WillingToMove(start,TileInDirection(i),null)){
             return true;
         }
     }
     return false;
 }
예제 #15
0
 public Actor(ActorType type_, string name_, string symbol_, Color color_, int maxhp_, int speed_, int level_, int light_radius_)
 {
     atype = type_;
     SetName(name_);
     symbol = symbol_;
     color = color_;
     maxhp = maxhp_;
     curhp = maxhp;
     speed = speed_;
     level = level_;
     light_radius = light_radius_;
     target = null;
     inv = null;
     target_location = null;
     time_of_last_action = 0;
     recover_time = 0;
     player_visibility_duration = 0;
     weapons.Insert(0, WeaponType.NO_WEAPON);
     armors.Insert(0, ArmorType.NO_ARMOR);
     F = new SpellType[13];
     for (int i = 0; i < 13; ++i)
     {
         F[i] = SpellType.NO_SPELL;
     }
     magic_penalty = 0;
 }
예제 #16
0
 public bool CollideWith(Tile t)
 {
     if(t.Is(TileType.FIREPIT) && !t.Is(FeatureType.SLIME)){
         B.Add(You("fall") + " into the fire pit. ",this);
         ApplyBurning();
     }
     if(IsBurning()){
         t.ApplyEffect(DamageType.FIRE);
     }
     if(t.Is(FeatureType.SLIME) && !HasAttr(AttrType.SLIMED)){
         B.Add(YouAre() + " covered in slime. ",this);
         attrs[AttrType.SLIMED] = 1;
         attrs[AttrType.OIL_COVERED] = 0;
         RefreshDuration(AttrType.BURNING,0);
         if(this == player){
             Help.TutorialTip(TutorialTopic.Slimed);
         }
     }
     else{
         if(t.Is(FeatureType.OIL) && !HasAttr(AttrType.SLIMED,AttrType.OIL_COVERED)){
             B.Add(YouAre() + " covered in oil. ",this);
             attrs[AttrType.OIL_COVERED] = 1;
             if(this == player){
                 Help.TutorialTip(TutorialTopic.Oiled);
             }
         }
         else{
             if(t.IsBurning()){
                 ApplyBurning();
             }
         }
     }
     if(!HasAttr(AttrType.SMALL)){
         t.ApplyEffect(DamageType.NORMAL);
     }
     return !HasAttr(AttrType.CORPSE);
 }
예제 #17
0
 public async Task InputAI()
 {
     bool no_act = false;
     if (CanSee(player))
     {
         if (target_location == null && HasAttr(AttrType.BLOODSCENT))
         { //orc warmages etc. when they first notice
             player_visibility_duration = -1;
             target = player;
             target_location = M.tile[player.row, player.col];
             if ((player.IsWithinSightRangeOf(this) || tile().IsLit()) && player.HasLOS(this))
             {
                 B.Add(the_name + "'s gaze meets your eyes! ", this);
                 if (DistanceFrom(player) <= 6)
                 {
                     player.MakeNoise();
                 }
             }
             B.Add(the_name + " snarls loudly. ", this);
             Q1();
             no_act = true;
         }
         else
         {
             target = player;
             target_location = M.tile[player.row, player.col];
             player_visibility_duration = -1;
         }
     }
     else
     {
         if ((IsWithinSightRangeOf(player.row, player.col) || M.tile[player.row, player.col].IsLit()) //if they're stealthed and nearby...
             && HasLOS(player.row, player.col)
             && (!player.HasAttr(AttrType.SHADOW_CLOAK) || HasAttr(AttrType.PLAYER_NOTICED) || player.tile().IsLit() || HasAttr(AttrType.BLINDSIGHT)))
         {
             int multiplier = HasAttr(AttrType.KEEN_SENSES) ? 5 : 10; //animals etc. are approximately twice as hard to sneak past
             if (player.Stealth() * DistanceFrom(player) * multiplier - player_visibility_duration++ * 5 < Global.Roll(1, 100))
             {
                 player_visibility_duration = -1;
                 attrs[Forays.AttrType.PLAYER_NOTICED]++;
                 target = player;
                 target_location = M.tile[player.row, player.col];
                 if (group != null)
                 {
                     foreach (Actor a in group)
                     {
                         if (a != this && DistanceFrom(a) < 3)
                         {
                             a.player_visibility_duration = -1;
                             a.attrs[Forays.AttrType.PLAYER_NOTICED]++;
                             a.target = player;
                             a.target_location = M.tile[player.row, player.col];
                         }
                     }
                 }
                 switch (atype)
                 {
                     case ActorType.RAT:
                     case ActorType.DIRE_RAT:
                         B.Add(TheVisible() + " squeaks at you. ");
                         player.MakeNoise();
                         break;
                     case ActorType.GOBLIN:
                     case ActorType.GOBLIN_ARCHER:
                     case ActorType.GOBLIN_SHAMAN:
                         B.Add(TheVisible() + " growls. ");
                         player.MakeNoise();
                         break;
                     case ActorType.BLOOD_MOTH:
                         if (!M.wiz_lite && !M.wiz_dark && player.LightRadius() > 0)
                         {
                             B.Add(the_name + " notices your light. ", this);
                         }
                         break;
                     case ActorType.CULTIST:
                     case ActorType.ROBED_ZEALOT:
                         B.Add(TheVisible() + " yells. ");
                         player.MakeNoise();
                         break;
                     case ActorType.ZOMBIE:
                         B.Add(TheVisible() + " moans. Uhhhhhhghhh. ");
                         player.MakeNoise();
                         break;
                     case ActorType.WOLF:
                         B.Add(TheVisible() + " snarls at you. ");
                         player.MakeNoise();
                         break;
                     case ActorType.FROSTLING:
                         B.Add(TheVisible() + " makes a chittering sound. ");
                         player.MakeNoise();
                         break;
                     case ActorType.SWORDSMAN:
                     case ActorType.BERSERKER:
                     case ActorType.CRUSADING_KNIGHT:
                         B.Add(TheVisible() + " shouts. ");
                         player.MakeNoise();
                         break;
                     case ActorType.BANSHEE:
                         B.Add(TheVisible() + " shrieks. ");
                         player.MakeNoise();
                         break;
                     case ActorType.WARG:
                         B.Add(TheVisible() + " howls. ");
                         player.MakeNoise();
                         break;
                     case ActorType.DERANGED_ASCETIC:
                         B.Add(TheVisible() + " starts babbling incoherently. ");
                         break;
                     case ActorType.CAVERN_HAG:
                         B.Add(TheVisible() + " cackles. ");
                         player.MakeNoise();
                         break;
                     case ActorType.COMPY:
                         B.Add(TheVisible() + " squeaks. ");
                         player.MakeNoise();
                         break;
                     case ActorType.OGRE:
                         B.Add(TheVisible() + " bellows at you. ");
                         player.MakeNoise();
                         break;
                     case ActorType.SHADOW:
                         B.Add(TheVisible() + " hisses faintly. ");
                         break;
                     case ActorType.ORC_GRENADIER:
                     case ActorType.ORC_WARMAGE:
                         B.Add(TheVisible() + " snarls loudly. ");
                         player.MakeNoise();
                         break;
                     case ActorType.ENTRANCER:
                         B.Add(the_name + " stares at you for a moment. ", this);
                         break;
                     case ActorType.STONE_GOLEM:
                         B.Add(the_name + " starts moving. ", this);
                         break;
                     case ActorType.NECROMANCER:
                         B.Add(TheVisible() + " starts chanting in low tones. ");
                         break;
                     case ActorType.TROLL:
                     case ActorType.TROLL_SEER:
                         B.Add(TheVisible() + " growls viciously. ");
                         player.MakeNoise();
                         break;
                     case ActorType.CARNIVOROUS_BRAMBLE:
                     case ActorType.MIMIC:
                     case ActorType.MUD_TENTACLE:
                     case ActorType.MARBLE_HORROR:
                     case ActorType.MARBLE_HORROR_STATUE:
                     case ActorType.LASHER_FUNGUS:
                         break;
                     default:
                         B.Add(the_name + " notices you. ", this);
                         break;
                 }
                 Q1();
                 no_act = true;
             }
         }
         else
         {
             if (player_visibility_duration >= 0)
             { //if they hadn't seen the player yet...
                 player_visibility_duration = 0;
             }
             else
             {
                 if (target_location == null && player_visibility_duration-- == -(10 + attrs[Forays.AttrType.ALERTED] * 40))
                 {
                     if (attrs[Forays.AttrType.ALERTED] < 2)
                     { //they'll forget the player after 10 turns the first time and
                         attrs[Forays.AttrType.ALERTED]++; //50 turns the second time, but that's the limit
                         player_visibility_duration = 0;
                         target = null;
                     }
                 }
             }
         }
     }
     if (atype == ActorType.MARBLE_HORROR && tile().IsLit())
     {
         B.Add("The marble horror reverts to its statue form. ", this);
         atype = ActorType.MARBLE_HORROR_STATUE;
         SetName("marble horror statue");
         attrs[Forays.AttrType.NEVER_MOVES] = 1;
         attrs[Forays.AttrType.INVULNERABLE] = 1;
         attrs[Forays.AttrType.IMMUNE_FIRE] = 1;
     }
     if (atype == ActorType.MARBLE_HORROR_STATUE && !tile().IsLit())
     {
         B.Add("The marble horror animates once more. ", this);
         atype = ActorType.MARBLE_HORROR;
         SetName("marble horror");
         attrs[Forays.AttrType.NEVER_MOVES] = 0;
         attrs[Forays.AttrType.INVULNERABLE] = 0;
         attrs[Forays.AttrType.IMMUNE_FIRE] = 0;
     }
     if (atype == ActorType.COMPY && group != null && target != null)
     {
         if (!group.Any(a => a.curhp < a.maxhp) && target.curhp >= 20 && !target.HasAttr(AttrType.ASLEEP) && !target.HasAttr(AttrType.PARALYZED)
         && !target.HasAttr(AttrType.IN_COMBAT))
         {
             target = null;
             target_location = null;
         }
     }
     if (!no_act && atype != ActorType.CULTIST && atype != ActorType.CORPSETOWER_BEHEMOTH && atype != ActorType.BLOOD_MOTH
     && atype != ActorType.MUD_TENTACLE && atype != ActorType.DREAM_CLONE && atype != ActorType.ZOMBIE
     && atype != ActorType.CARNIVOROUS_BRAMBLE)
     {
         if (HasAttr(AttrType.HUMANOID_INTELLIGENCE))
         {
             if (HasAttr(AttrType.CATCHING_FIRE) && Global.OneIn(10))
             {
                 attrs[AttrType.CATCHING_FIRE] = 0;
                 B.Add(the_name + " stops the flames from spreading. ", this);
                 Q1();
                 no_act = true;
             }
             else
             {
                 if (HasAttr(AttrType.ON_FIRE))
                 {
                     if (attrs[AttrType.ON_FIRE] == 1 && Global.OneIn(4))
                     {
                         bool update = false;
                         int oldradius = LightRadius();
                         if (attrs[AttrType.ON_FIRE] > light_radius)
                         {
                             update = true;
                         }
                         attrs[AttrType.ON_FIRE] = 0;
                         if (update)
                         {
                             UpdateRadius(oldradius, LightRadius());
                         }
                         B.Add(the_name + " puts out the fire. ", this);
                         Q1();
                         no_act = true;
                     }
                     else
                     {
                         if (attrs[AttrType.ON_FIRE] > 1 && Global.Roll(10) <= 8)
                         {
                             bool update = false;
                             int oldradius = LightRadius();
                             if (attrs[AttrType.ON_FIRE] > light_radius)
                             {
                                 update = true;
                             }
                             int i = 2;
                             if (Global.Roll(1, 3) == 3)
                             { // 1 in 3 times, no progress against the fire
                                 i = 1;
                             }
                             attrs[AttrType.ON_FIRE] -= i;
                             if (attrs[AttrType.ON_FIRE] < 0)
                             {
                                 attrs[AttrType.ON_FIRE] = 0;
                             }
                             if (update)
                             {
                                 UpdateRadius(oldradius, LightRadius());
                             }
                             if (HasAttr(AttrType.ON_FIRE))
                             {
                                 B.Add(the_name + " puts out some of the fire. ", this);
                             }
                             else
                             {
                                 B.Add(the_name + " puts out the fire. ", this);
                             }
                             Q1();
                             no_act = true;
                         }
                         else
                         {
                             if (attrs[AttrType.ON_FIRE] > 2 && Global.Roll(2) + attrs[AttrType.ON_FIRE] >= 5)
                             {
                                 if (HasAttr(AttrType.MEDIUM_HUMANOID))
                                 {
                                     B.Add(the_name + " runs around with arms flailing. ", this);
                                 }
                                 else
                                 {
                                     B.Add(the_name + " flails about. ", this);
                                 }
                                 await AI_Step(TileInDirection(Global.RandomDirection()));
                                 Q1();
                                 no_act = true;
                             }
                             else
                             {
                                 bool update = false;
                                 int oldradius = LightRadius();
                                 if (attrs[AttrType.ON_FIRE] > light_radius)
                                 {
                                     update = true;
                                 }
                                 int i = 2;
                                 if (Global.Roll(1, 3) == 3)
                                 { // 1 in 3 times, no progress against the fire
                                     i = 1;
                                 }
                                 attrs[AttrType.ON_FIRE] -= i;
                                 if (attrs[AttrType.ON_FIRE] < 0)
                                 {
                                     attrs[AttrType.ON_FIRE] = 0;
                                 }
                                 if (update)
                                 {
                                     UpdateRadius(oldradius, LightRadius());
                                 }
                                 if (HasAttr(AttrType.ON_FIRE))
                                 {
                                     B.Add(the_name + " puts out some of the fire. ", this);
                                 }
                                 else
                                 {
                                     B.Add(the_name + " puts out the fire. ", this);
                                 }
                                 Q1();
                                 no_act = true;
                             }
                         }
                     }
                 }
             }
         }
         else
         {
             if (HasAttr(AttrType.CATCHING_FIRE) && Global.CoinFlip())
             {
                 attrs[AttrType.CATCHING_FIRE] = 0;
                 if (atype == ActorType.SHADOW)
                 {
                     B.Add(the_name + " reforms itself to stop the flames. ", this);
                 }
                 else
                 {
                     if (atype == ActorType.BANSHEE || atype == ActorType.VAMPIRE)
                     {
                         B.Add(the_name + " stops the flames from spreading. ", this);
                     }
                     else
                     {
                         B.Add(the_name + " rolls on the ground to stop the flames. ", this);
                     }
                 }
                 Q1();
                 no_act = true;
             }
             else
             {
                 if (HasAttr(AttrType.ON_FIRE) && Global.Roll(3) >= 2)
                 {
                     bool update = false;
                     int oldradius = LightRadius();
                     if (attrs[AttrType.ON_FIRE] > light_radius)
                     {
                         update = true;
                     }
                     int i = 2;
                     if (Global.Roll(1, 3) == 3)
                     { // 1 in 3 times, no progress against the fire
                         i = 1;
                     }
                     attrs[AttrType.ON_FIRE] -= i;
                     if (attrs[AttrType.ON_FIRE] < 0)
                     {
                         attrs[AttrType.ON_FIRE] = 0;
                     }
                     if (update)
                     {
                         UpdateRadius(oldradius, LightRadius());
                     }
                     if (HasAttr(AttrType.ON_FIRE))
                     {
                         if (atype == ActorType.SHADOW)
                         {
                             B.Add(the_name + " reforms itself to put out some of the fire. ", this);
                         }
                         else
                         {
                             if (atype == ActorType.BANSHEE)
                             {
                                 B.Add(the_name + " puts out some of the fire. ", this);
                             }
                             else
                             {
                                 B.Add(the_name + " rolls on the ground to put out some of the fire. ", this);
                             }
                         }
                     }
                     else
                     {
                         if (atype == ActorType.SHADOW)
                         {
                             B.Add(the_name + " reforms itself to put out the fire. ", this);
                         }
                         else
                         {
                             if (atype == ActorType.BANSHEE)
                             {
                                 B.Add(the_name + " puts out the fire. ", this);
                             }
                             else
                             {
                                 B.Add(the_name + " rolls on the ground to put out the fire. ", this);
                             }
                         }
                     }
                     Q1();
                     no_act = true;
                 }
             }
         }
     }
     if (tile().Is(FeatureType.QUICKFIRE) || tile().Is(FeatureType.POISON_GAS) || (HasAttr(AttrType.LIGHT_ALLERGY) && tile().IsLit()))
     {
         List<Tile> dangerous_terrain = new List<Tile>();
         bool dangerous_terrain_here = false;
         if (HasAttr(AttrType.LIGHT_ALLERGY) && target == null)
         { //ignore this if the vampire sees the player already
             foreach (Tile t in TilesWithinDistance(1))
             {
                 if (t.IsLit() && t.passable)
                 {
                     dangerous_terrain.Add(t);
                     if (t == tile())
                     {
                         dangerous_terrain_here = true;
                     }
                 }
             }
         }
         if (!HasAttr(AttrType.IMMUNE_FIRE) && !HasAttr(AttrType.INVULNERABLE) && !HasAttr(AttrType.RESIST_FIRE))
         {
             if (atype != ActorType.ZOMBIE && atype != ActorType.CORPSETOWER_BEHEMOTH && atype != ActorType.SKELETON && atype != ActorType.SKELETAL_SABERTOOTH
             && atype != ActorType.CULTIST && atype != ActorType.PHASE_SPIDER && atype != ActorType.MARBLE_HORROR && atype != ActorType.MECHANICAL_KNIGHT)
             {
                 foreach (Tile t in TilesWithinDistance(1))
                 {
                     if (t.Is(FeatureType.QUICKFIRE))
                     {
                         dangerous_terrain.AddUnique(t);
                         if (t == tile())
                         {
                             dangerous_terrain_here = true;
                         }
                     }
                 }
             }
         }
         if (!HasAttr(AttrType.IMMUNE_TOXINS) && !HasAttr(AttrType.UNDEAD) && !HasAttr(AttrType.CONSTRUCT))
         {
             if (atype != ActorType.CULTIST && atype != ActorType.PHASE_SPIDER && atype != ActorType.MECHANICAL_KNIGHT)
             {
                 foreach (Tile t in TilesWithinDistance(1))
                 {
                     if (t.Is(FeatureType.POISON_GAS))
                     {
                         dangerous_terrain.AddUnique(t);
                         if (t == tile())
                         {
                             dangerous_terrain_here = true;
                         }
                     }
                 }
             }
         }
         if (dangerous_terrain_here)
         {
             /*if(target == null || DistanceFrom(target) > 1 || Global.CoinFlip()){
             }*/
             List<Tile> safe = new List<Tile>();
             foreach (Tile t in TilesAtDistance(1))
             {
                 if (t.passable && t.actor() == null && !dangerous_terrain.Contains(t))
                 {
                     safe.Add(t);
                 }
             }
             if (safe.Count > 0)
             {
                 if (await AI_Step(safe.Random()))
                 {
                     QS();
                     no_act = true;
                 }
             }
         }
     }
     if (atype == ActorType.MECHANICAL_KNIGHT && !HasAttr(AttrType.COOLDOWN_1))
     {
         attrs[Forays.AttrType.MECHANICAL_SHIELD] = 1; //if the knight was off balance, it regains its shield here.
     }
     if (group != null && group.Count == 0)
     { //this shouldn't happen, but does. this stops it from crashing.
         group = null;
     }
     if (!no_act)
     {
         if (target != null)
         {
             if (CanSee(target))
             {
                 await ActiveAI();
             }
             else
             {
                 await SeekAI();
             }
         }
         else
         {
             await IdleAI();
         }
     }
     if (atype == ActorType.DARKNESS_DWELLER)
     {
         if (HasAttr(AttrType.COOLDOWN_2))
         {
             if (tile().IsLit())
             {
                 attrs[Forays.AttrType.COOLDOWN_2] = 5;
             }
             else
             {
                 attrs[Forays.AttrType.COOLDOWN_2]--;
             }
         }
         else
         {
             if (tile().IsLit())
             {
                 B.Add(the_name + " is blinded by the light! ", this);
                 attrs[Forays.AttrType.COOLDOWN_1]++;
                 attrs[Forays.AttrType.COOLDOWN_2] = 5;
                 Q.Add(new Event(this, (Global.Roll(2) + 4) * 100, AttrType.COOLDOWN_1, the_name + " is no longer blinded. ", new PhysicalObject[] { this }));
             }
         }
     }
     if (atype == ActorType.SHADOW)
     {
         CalculateDimming();
     }
 }
예제 #18
0
 public bool ConfirmsSafetyPrompts(Tile t)
 {
     if(CanSee(t) && !HasAttr(AttrType.CONFUSED)){
         if(t.IsKnownTrap() && !HasAttr(AttrType.FLYING,AttrType.BRUTISH_STRENGTH) && !t.name.Contains("(safe)")){
             if(!B.YesOrNoPrompt("Really step on " + t.TheName(true) + "?")){
                 return false;
             }
         }
         if(t.IsBurning() && !IsBurning() && !HasAttr(AttrType.SLIMED,AttrType.NONLIVING)){ //nonliving indicates stoneform
             if(!B.YesOrNoPrompt("Really walk into the fire?")){
                 return false;
             }
         }
         if(HasAttr(AttrType.OIL_COVERED) && !HasAttr(AttrType.IMMUNE_BURNING,AttrType.IMMUNE_FIRE)){
             bool fire = false;
             foreach(Tile t2 in t.TilesWithinDistance(1)){
                 if((t2.actor() != null && t2.actor().IsBurning()) || t2.IsBurning()){
                     fire = true;
                     break;
                 }
             }
             if(fire && !B.YesOrNoPrompt("Really step next to the fire?")){
                 return false;
             }
         }
         if(t.Is(FeatureType.WEB) && !HasAttr(AttrType.SLIMED,AttrType.OIL_COVERED,AttrType.BRUTISH_STRENGTH) && !B.YesOrNoPrompt("Really walk into the web?")){
             return false;
         }
         if(!HasAttr(AttrType.NONLIVING)){
             if(t.Is(FeatureType.CONFUSION_GAS) && !B.YesOrNoPrompt("Really walk into the confusion gas?")){
                 return false;
             }
             if(t.Is(FeatureType.POISON_GAS) && !HasAttr(AttrType.POISONED) && !B.YesOrNoPrompt("Really walk into the poison gas?")){
                 return false;
             }
             if(t.Is(FeatureType.SPORES) && (!HasAttr(AttrType.POISONED) || !HasAttr(AttrType.STUNNED)) && !B.YesOrNoPrompt("Really walk into the spores?")){
                 return false;
             }
             if(t.Is(FeatureType.THICK_DUST) && !B.YesOrNoPrompt("Really walk into the cloud of dust?")){
                 return false;
             }
         }
     }
     return true;
 }
예제 #19
0
 public async Task<bool> AI_Step(PhysicalObject obj, bool flee)
 {
     int rowchange = 0;
     int colchange = 0;
     if (obj.row < row)
     {
         rowchange = -1;
     }
     if (obj.row > row)
     {
         rowchange = 1;
     }
     if (obj.col < col)
     {
         colchange = -1;
     }
     if (obj.col > col)
     {
         colchange = 1;
     }
     if (flee)
     {
         rowchange = -rowchange;
         colchange = -colchange;
     }
     List<int> dirs = new List<int>();
     if (rowchange == -1)
     {
         if (colchange == -1)
         {
             dirs.Add(7);
         }
         if (colchange == 0)
         {
             dirs.Add(8);
         }
         if (colchange == 1)
         {
             dirs.Add(9);
         }
     }
     if (rowchange == 0)
     {
         if (colchange == -1)
         {
             dirs.Add(4);
         }
         if (colchange == 1)
         {
             dirs.Add(6);
         }
     }
     if (rowchange == 1)
     {
         if (colchange == -1)
         {
             dirs.Add(1);
         }
         if (colchange == 0)
         {
             dirs.Add(2);
         }
         if (colchange == 1)
         {
             dirs.Add(3);
         }
     }
     if (dirs.Count == 0) { return true; }
     bool cw = Global.CoinFlip();
     dirs.Add(RotateDirection(dirs[0], cw));
     dirs.Add(RotateDirection(dirs[0], !cw)); //building a list of directions to try: first the primary direction,
     cw = Global.CoinFlip(); 				//then the ones next to it, then the ones next to THOSE(in random order)
     dirs.Add(RotateDirection(RotateDirection(dirs[0], cw), cw));
     dirs.Add(RotateDirection(RotateDirection(dirs[0], !cw), !cw)); //this completes the list of 5 directions.
     foreach (int i in dirs)
     {
         if (ActorInDirection(i) != null && ActorInDirection(i).IsHiddenFrom(this))
         {
             player_visibility_duration = -1;
             if (ActorInDirection(i) == player)
             {
                 attrs[Forays.AttrType.PLAYER_NOTICED]++;
             }
             target = player; //not extensible yet
             target_location = M.tile[player.row, player.col];
             string walks = " walks straight into you! ";
             if (HasAttr(AttrType.FLYING))
             {
                 walks = " flies straight into you! ";
             }
             if (!IsHiddenFrom(player))
             {
                 B.Add(TheVisible() + walks);
                 if (player.CanSee(this))
                 {
                     B.Add(the_name + " looks startled. ");
                 }
             }
             else
             {
                 attrs[AttrType.TURNS_VISIBLE] = -1;
                 attrs[Forays.AttrType.NOTICED]++;
                 B.Add(AVisible() + walks);
                 if (player.CanSee(this))
                 {
                     B.Add(the_name + " looks just as surprised as you. ");
                 }
             }
             return true;
         }
         if (await AI_MoveOrOpen(i))
         {
             return true;
         }
     }
     return false;
 }
예제 #20
0
 public int GetDangerRating(Tile t)
 {
     //0 is no danger. 1 is minor danger, often ignored in favor of chasing the player. 2 is major danger, always avoided.
     if(HasAttr(AttrType.MINDLESS) || (type == ActorType.BERSERKER && HasAttr(AttrType.COOLDOWN_2)) || type == ActorType.FINAL_LEVEL_CULTIST){
         return 0;
     }
     int dr = attrs[AttrType.DAMAGE_RESISTANCE];
     int result = 0;
     if(HasAttr(AttrType.HUMANOID_INTELLIGENCE)){
         if(!HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE)){
             if(t.Is(FeatureType.CONFUSION_GAS) && !HasAttr(AttrType.MENTAL_IMMUNITY)){
                 return 2;
             }
             if(t.Is(FeatureType.SPORES) && (!HasAttr(AttrType.MENTAL_IMMUNITY) || dr < 2)){
                 if(HasAttr(AttrType.MENTAL_IMMUNITY,AttrType.STUNNED) && (HasAttr(AttrType.POISONED) || dr > 0)){
                     result = 1;
                 }
                 else{
                     return 2;
                 }
             }
             if(t.Is(FeatureType.POISON_GAS) && type != ActorType.NOXIOUS_WORM && dr < 2){
                 if(HasAttr(AttrType.POISONED) || dr > 0){
                     result = 1;
                 }
                 else{
                     return 2;
                 }
             }
             /*if(t.Is(TileType.POPPY_FIELD) && !HasAttr(AttrType.MAGICAL_DROWSINESS,AttrType.MENTAL_IMMUNITY)){ //todo
                 int counter = attrs[AttrType.POPPY_COUNTER] + M.poppy_distance_map[t.p]; //todo: check for valid dijkstra int here?
                 if(counter > 3){
                     return 2;
                 }
                 if(counter > 1){
                     result = 1;
                 }
             }*/
             if(t.Is(TileType.POPPY_FIELD) && !HasAttr(AttrType.MENTAL_IMMUNITY)){
                 if(M.poppy_distance_map[t.p] == 1){ //the outermost layer is considered less dangerous
                     if(attrs[AttrType.POPPY_COUNTER] >= 3){
                         return 2;
                     }
                     else{
                         if(this != player){
                             //result = 1;
                         }
                         //result = 1;
                     }
                 }
                 else{
                     if(M.poppy_distance_map[t.p].IsValidDijkstraValue()){
                         if(attrs[AttrType.POPPY_COUNTER] >= 2){
                             return 2;
                         }
                         else{
                             if(this != player){
                                 result = 1;
                             }
                             //result = 1;
                         }
                     }
                 }
             }
         }
         if(t.Is(FeatureType.THICK_DUST) && !HasAttr(AttrType.BLINDSIGHT,AttrType.PLANTLIKE)){
             return 2;
         }
         foreach(Tile neighbor in t.TilesWithinDistance(1)){
             if(neighbor.Is(FeatureType.GRENADE)){
                 return 2;
             }
         }
     }
     if(!HasAttr(AttrType.IMMUNE_FIRE) && dr < 3){
         bool might_catch = false;
         int searing_tiles = 0;
         bool currently_flammable = (t.IsCurrentlyFlammable() || HasAttr(AttrType.OIL_COVERED)) && HasAttr(AttrType.HUMANOID_INTELLIGENCE);
         foreach(Tile neighbor in t.TilesWithinDistance(1)){
             if(neighbor.IsBurning()){
                 searing_tiles++;
                 if(currently_flammable){
                     might_catch = true;
                 }
             }
         }
         if(dr == 0){
             if(searing_tiles > 3){
                 return 2;
             }
             else{
                 if(searing_tiles > 0){
                     if(type == ActorType.ALASI_SCOUT && curhp == maxhp){
                         return 2;
                     }
                     result = 1;
                 }
             }
         }
         if((might_catch || t.IsBurning()) && !HasAttr(AttrType.IMMUNE_BURNING)){
             if(dr == 2){
                 result = 1;
             }
             else{
                 return 2;
             }
         }
     }
     return result;
 }
예제 #21
0
        public async Task<bool> CastSpell(SpellType spell, PhysicalObject obj, bool force_of_will)
        { //returns false if targeting is canceled.
            if ((await StunnedThisTurn()) && !force_of_will)
            { //eventually this will be moved to the last possible second
                return true; //returns true because turn was used up. 
            }
            if (!HasSpell(spell))
            {
                return false;
            }
            foreach (Actor a in ActorsWithinDistance(2))
            {
                if (a.HasAttr(AttrType.SPELL_DISRUPTION) && a.HasLOE(this))
                {
                    if (this == player)
                    {
                        if (CanSee(a))
                        {
                            B.Add(a.Your() + " presence disrupts your spell! ");
                        }
                        else
                        {
                            B.Add("Something disrupts your spell! ");
                        }
                    }
                    return false;
                }
            }
            Tile t = null;
            List<Tile> line = null;
            if (obj != null)
            {
                t = M.tile[obj.row, obj.col];
                if (spell == SpellType.FORCE_BEAM)
                { //force beam requires a line for proper knockback
                    line = GetBestExtendedLine(t);
                }
                else
                {
                    line = GetBestLine(t);
                }
            }
            int bonus = 0; //used for bonus damage on spells - currently, only Master's Edge adds bonus damage.
            if (FailRate(spell) > 0)
            {
                int fail = FailRate(spell);
                if (force_of_will)
                {
                    fail = magic_penalty * 5;
                    fail -= skills[SkillType.SPIRIT] * 2;
                    if (fail < 0)
                    {
                        fail = 0;
                    }
                }
                if (Global.Roll(1, 100) - fail <= 0)
                {
                    if (player.CanSee(this))
                    {
                        B.Add("Sparks fly from " + Your() + " fingers. ", this);
                    }
                    else
                    {
                        if (player.DistanceFrom(this) <= 4 || (player.DistanceFrom(this) <= 12 && player.HasLOS(row, col)))
                        {
                            B.Add("You hear words of magic, but nothing happens. ");
                        }
                    }
                    Q1();
                    return true;
                }
            }
            if (HasFeat(FeatType.MASTERS_EDGE))
            {
                foreach (SpellType s in spells_in_order)
                {
                    if (Spell.IsDamaging(s))
                    {
                        if (s == spell)
                        {
                            bonus = 1;
                        }
                        break;
                    }
                }
            }
            switch (spell)
            {
                case SpellType.SHINE:
                    if (!HasAttr(AttrType.ENHANCED_TORCH))
                    {
                        B.Add("You cast shine. ");
                        if (!M.wiz_dark)
                        {
                            B.Add("Your torch begins to shine brightly. ");
                        }
                        attrs[AttrType.ENHANCED_TORCH]++;
                        if (light_radius > 0)
                        {
                            UpdateRadius(LightRadius(), Global.MAX_LIGHT_RADIUS - attrs[AttrType.DIM_LIGHT] * 2, true);
                        }
                        Q.Add(new Event(9500, "Your torch begins to flicker a bit. "));
                        Q.Add(new Event(this, 10000, AttrType.ENHANCED_TORCH, "Your torch no longer shines as brightly. "));
                    }
                    else
                    {
                        B.Add("Your torch is already shining brightly! ");
                        return false;
                    }
                    break;
                /*			case SpellType.MAGIC_MISSILE:
                                if(t == null){
                                    t = await GetTarget();
                                }
                                if(t != null){
                                    B.Add(You("cast") + " magic missile. ",this);
                                    Actor a = FirstActorInLine(t);
                                    if(a != null){
                                        AnimateBoltProjectile(a,Color.Magenta);
                                        B.Add("The missile hits " + a.the_name + ". ",a);
                                        a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,Global.Roll(1+bonus,6),this);
                                    }
                                    else{
                                        AnimateBoltProjectile(t,Color.Magenta);
                                        if(t.IsLit()){
                                            B.Add("The missile hits " + t.the_name + ". ");
                                        }
                                        else{
                                            B.Add("You attack the darkness. ");
                                        }
                                    }
                                }
                                else{
                                    return false;
                                }
                                break;
                            case SpellType.DETECT_MONSTERS:
                                if(!HasAttr(AttrType.DETECTING_MONSTERS)){
                                    B.Add(You("cast") + " detect monsters. ",this);
                                    if(type == ActorType.PLAYER){
                                        B.Add("You can sense beings around you. ");
                                        Q.Add(new Event(this,2100,AttrType.DETECTING_MONSTERS,"You can no longer sense beings around you. "));
                                    }
                                    else{
                                        Q.Add(new Event(this,2100,AttrType.DETECTING_MONSTERS));
                                    }
                                    attrs[AttrType.DETECTING_MONSTERS]++;
                                }
                                else{
                                    B.Add("You are already detecting monsters! ");
                                    return false;
                                }
                                break;*/
                case SpellType.IMMOLATE:
                    if (t == null)
                    {
                        line = await GetTarget(12);
                        if (line != null)
                        {
                            t = line.Last();
                        }
                    }
                    if (t != null)
                    {
                        B.Add(You("cast") + " immolate. ", this);
                        Actor a = FirstActorInLine(line);
                        if (a != null)
                        {
                            AnimateBeam(line.ToFirstObstruction(), "*", Color.RandomFire);
                            if (!a.HasAttr(AttrType.RESIST_FIRE) && !a.HasAttr(AttrType.CATCHING_FIRE) && !a.HasAttr(AttrType.ON_FIRE))
                            {
                                if (a.name == "you")
                                {
                                    B.Add("You start to catch fire! ");
                                }
                                else
                                {
                                    B.Add(a.the_name + " starts to catch fire. ", a);
                                }
                                a.attrs[AttrType.CATCHING_FIRE]++;
                            }
                            else
                            {
                                B.Add(a.You("shrug") + " off the flames. ", a);
                            }
                        }
                        else
                        {
                            foreach (Tile t2 in line)
                            {
                                if (t2.Is(FeatureType.TROLL_CORPSE) || t2.Is(FeatureType.TROLL_SEER_CORPSE))
                                {
                                    line = line.To(t2);
                                }
                            }
                            AnimateBeam(line, "*", Color.RandomFire);
                            B.Add(You("throw") + " flames. ", this);
                            if (line.Last().Is(FeatureType.TROLL_CORPSE))
                            {
                                line.Last().features.Remove(FeatureType.TROLL_CORPSE);
                                B.Add("The troll corpse burns to ashes! ", line.Last());
                            }
                            if (line.Last().Is(FeatureType.TROLL_SEER_CORPSE))
                            {
                                line.Last().features.Remove(FeatureType.TROLL_SEER_CORPSE);
                                B.Add("The troll seer corpse burns to ashes! ", line.Last());
                            }
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.FORCE_PALM:
                    if (t == null)
                    {
                        t = TileInDirection(await GetDirection());
                    }
                    if (t != null)
                    {
                        Actor a = M.actor[t.row, t.col];
                        B.Add(You("cast") + " force palm. ", this);
                        //AnimateMapCell(t,Color.DarkCyan,"*");
                        B.DisplayNow();
                        Screen.AnimateMapCell(t.row, t.col, new colorchar("*", Color.Blue), 100);
                        if (a != null)
                        {
                            B.Add(You("strike") + " " + a.TheVisible() + ". ", new PhysicalObject[] { this, a });
                            string s = a.the_name;
                            string s2 = a.a_name;
                            List<Tile> line2 = GetBestExtendedLine(a.row, a.col);
                            int idx = line2.IndexOf(M.tile[a.row, a.col]);
                            Tile next = line2[idx + 1];
                            await a.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(1 + bonus, 6), this, a_name);
                            if (Global.Roll(1, 10) <= 7)
                            {
                                if (M.actor[t.row, t.col] != null)
                                {
                                    await a.GetKnockedBack(this);
                                }
                                else
                                {
                                    if (!next.passable)
                                    {
                                        B.Add(s + "'s corpse is knocked into " + next.the_name + ". ", new PhysicalObject[] { t, next });
                                    }
                                    else
                                    {
                                        if (M.actor[next.row, next.col] != null)
                                        {
                                            B.Add(s + "'s corpse is knocked into " + M.actor[next.row, next.col].the_name + ". ", new PhysicalObject[] { t, M.actor[next.row, next.col] });
                                            await M.actor[next.row, next.col].TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(1, 6), this, s2 + "'s falling corpse");
                                        }
                                    }
                                }
                            }
                        }
                        else
                        {
                            if (t.passable)
                            {
                                B.Add("You strike at empty space. ");
                            }
                            else
                            {
                                B.Add("You strike " + t.the_name + " with your palm. ");
                                if (t.ttype == TileType.DOOR_C)
                                { //heh, why not?
                                    B.Add("It flies open! ");
                                    t.Toggle(this);
                                }
                                if (t.ttype == TileType.HIDDEN_DOOR)
                                { //and this one gives it an actual use
                                    B.Add("A hidden door flies open! ");
                                    t.Toggle(this);
                                    t.Toggle(this);
                                }
                            }
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.FREEZE:
                    if (t == null)
                    {
                        line = await GetTarget(12);
                        if (line != null)
                        {
                            t = line.Last();
                        }
                    }
                    if (t != null)
                    {
                        B.Add(You("cast") + " freeze. ", this);
                        Actor a = FirstActorInLine(line);
                        if (a != null)
                        {
                            AnimateBoltBeam(line.ToFirstObstruction(), Color.Cyan);
                            if (!a.HasAttr(AttrType.FROZEN) && !a.HasAttr(AttrType.UNFROZEN))
                            {
                                B.Add(a.YouAre() + " encased in ice. ", a);
                                a.attrs[AttrType.FROZEN] = 25;
                            }
                            else
                            {
                                B.Add("The beam dissipates on the remaining ice. ", a);
                            }
                        }
                        else
                        {
                            AnimateBoltBeam(line, Color.Cyan);
                            B.Add("A bit of ice forms on " + t.the_name + ". ", t);
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.BLINK:
                    for (int i = 0; i < 9999; ++i)
                    {
                        int a = Global.Roll(1, 17) - 9; //-8 to 8
                        int b = Global.Roll(1, 17) - 9;
                        if (Math.Abs(a) + Math.Abs(b) >= 6)
                        {
                            a += row;
                            b += col;
                            if (M.BoundsCheck(a, b) && M.tile[a, b].passable && M.actor[a, b] == null)
                            {
                                B.Add(You("cast") + " blink. ", this);
                                B.Add(You("step") + " through a rip in reality. ", this);
                                AnimateStorm(2, 3, 4, "*", Color.DarkMagenta);
                                await Move(a, b);
                                M.Draw();
                                AnimateStorm(2, 3, 4, "*", Color.DarkMagenta);
                                break;
                            }
                        }
                    }
                    break;
                case SpellType.SCORCH:
                    if (t == null)
                    {
                        line = await GetTarget(12);
                        if (line != null)
                        {
                            t = line.Last();
                        }
                    }
                    if (t != null)
                    {
                        B.Add(You("cast") + " scorch. ", this);
                        Actor a = FirstActorInLine(line);
                        if (a != null)
                        {
                            AnimateProjectile(line.ToFirstObstruction(), "*", Color.RandomFire);
                            B.Add("The scorching bolt hits " + a.the_name + ". ", a);
                            await a.TakeDamage(DamageType.FIRE, DamageClass.MAGICAL, Global.Roll(2 + bonus, 6), this, a_name);
                        }
                        else
                        {
                            foreach (Tile t2 in line)
                            {
                                if (t2.Is(FeatureType.TROLL_CORPSE) || t2.Is(FeatureType.TROLL_SEER_CORPSE))
                                {
                                    line = line.To(t2);
                                }
                            }
                            AnimateProjectile(line, "*", Color.RandomFire);
                            B.Add("The scorching bolt hits " + t.the_name + ". ", t);
                            if (line.Last().Is(FeatureType.TROLL_CORPSE))
                            {
                                line.Last().features.Remove(FeatureType.TROLL_CORPSE);
                                B.Add("The troll corpse burns to ashes! ", line.Last());
                            }
                            if (line.Last().Is(FeatureType.TROLL_SEER_CORPSE))
                            {
                                line.Last().features.Remove(FeatureType.TROLL_SEER_CORPSE);
                                B.Add("The troll seer corpse burns to ashes! ", line.Last());
                            }
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.BLOODSCENT:
                    if (!HasAttr(AttrType.BLOODSCENT))
                    {
                        B.Add(You("cast") + " bloodscent. ", this);
                        attrs[Forays.AttrType.BLOODSCENT]++;
                        if (atype == ActorType.PLAYER)
                        {
                            B.Add("You smell fear. ");
                            Q.Add(new Event(this, 10000, Forays.AttrType.BLOODSCENT, "You lose the scent. "));
                        }
                        else
                        {
                            Q.Add(new Event(this, 10000, Forays.AttrType.BLOODSCENT));
                        }
                    }
                    else
                    {
                        B.Add("You can already smell the blood of your enemies. ");
                        return false;
                    }
                    break;
                case SpellType.LIGHTNING_BOLT:
                    if (t == null)
                    {
                        line = await GetTarget(12);
                        if (line != null)
                        {
                            t = line.Last();
                        }
                    }
                    if (t != null)
                    {
                        B.Add(You("cast") + " lightning bolt. ", this);
                        PhysicalObject bolt_target = null;
                        List<Actor> damage_targets = new List<Actor>();
                        foreach (Tile t2 in line)
                        {
                            if (t2.actor() != null && t2.actor() != this)
                            {
                                bolt_target = t2.actor();
                                damage_targets.Add(t2.actor());
                                break;
                            }
                            else
                            {
                                if (t2.ConductsElectricity())
                                {
                                    bolt_target = t2;
                                    break;
                                }
                            }
                        }
                        if (bolt_target != null)
                        {
                            Dict<PhysicalObject, List<PhysicalObject>> chain = new Dict<PhysicalObject, List<PhysicalObject>>();
                            chain[this] = new List<PhysicalObject> { bolt_target };
                            List<PhysicalObject> last_added = new List<PhysicalObject> { bolt_target };
                            for (bool done = false; !done; )
                            {
                                done = true;
                                List<PhysicalObject> new_last_added = new List<PhysicalObject>();
                                foreach (PhysicalObject added in last_added)
                                {
                                    List<PhysicalObject> sort_list = new List<PhysicalObject>();
                                    foreach (Tile nearby in added.TilesWithinDistance(3, true))
                                    {
                                        if (nearby.actor() != null || nearby.ConductsElectricity())
                                        {
                                            if (added.HasLOE(nearby))
                                            {
                                                if (nearby.actor() != null)
                                                {
                                                    bolt_target = nearby.actor();
                                                }
                                                else
                                                {
                                                    bolt_target = nearby;
                                                }
                                                bool contains_value = false;
                                                foreach (PhysicalObject k in chain.d.Keys)
                                                {
                                                    List<PhysicalObject> list = chain.d[k];
                                                    foreach (PhysicalObject o in list)
                                                    {
                                                        if (o == bolt_target)
                                                        {
                                                            contains_value = true;
                                                            break;
                                                        }
                                                    }
                                                    if (contains_value)
                                                    {
                                                        break;
                                                    }
                                                }
                                                if (!chain.d.ContainsKey(bolt_target) && !contains_value)
                                                {
                                                    if (bolt_target as Actor != null)
                                                    {
                                                        damage_targets.AddUnique(bolt_target as Actor);
                                                    }
                                                    done = false;
                                                    if (sort_list.Count == 0)
                                                    {
                                                        sort_list.Add(bolt_target);
                                                    }
                                                    else
                                                    {
                                                        int idx = 0;
                                                        foreach (PhysicalObject o in sort_list)
                                                        {
                                                            if (bolt_target.DistanceFrom(added) < o.DistanceFrom(added))
                                                            {
                                                                sort_list.Insert(idx, bolt_target);
                                                                break;
                                                            }
                                                            ++idx;
                                                        }
                                                        if (idx == sort_list.Count)
                                                        {
                                                            sort_list.Add(bolt_target);
                                                        }
                                                    }
                                                    if (chain[added] == null)
                                                    {
                                                        chain[added] = new List<PhysicalObject> { bolt_target };
                                                    }
                                                    else
                                                    {
                                                        chain[added].Add(bolt_target);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    foreach (PhysicalObject o in sort_list)
                                    {
                                        new_last_added.Add(o);
                                    }
                                }
                                if (!done)
                                {
                                    last_added = new_last_added;
                                }
                            } //whew. the tree structure is complete. start at chain[this] and go from there...
                            Dict<int, List<pos>> frames = new Dict<int, List<pos>>();
                            Dict<PhysicalObject, int> line_length = new Dict<PhysicalObject, int>();
                            line_length[this] = 0;
                            List<PhysicalObject> current = new List<PhysicalObject> { this };
                            List<PhysicalObject> next = new List<PhysicalObject>();
                            while (current.Count > 0)
                            {
                                foreach (PhysicalObject o in current)
                                {
                                    if (chain[o] != null)
                                    {
                                        foreach (PhysicalObject o2 in chain[o])
                                        {
                                            List<Tile> bres = o.GetBestLine(o2);
                                            bres.RemoveAt(0);
                                            line_length[o2] = bres.Count + line_length[o];
                                            int idx = 0;
                                            foreach (Tile t2 in bres)
                                            {
                                                if (frames[idx + line_length[o]] != null)
                                                {
                                                    frames[idx + line_length[o]].Add(new pos(t2.row, t2.col));
                                                }
                                                else
                                                {
                                                    frames[idx + line_length[o]] = new List<pos> { new pos(t2.row, t2.col) };
                                                }
                                                ++idx;
                                            }
                                            next.Add(o2);
                                        }
                                    }
                                }
                                current = next;
                                next = new List<PhysicalObject>();
                            }
                            List<pos> frame = frames[0];
                            for (int i = 0; frame != null; ++i)
                            {
                                foreach (pos p in frame)
                                {
                                    Screen.WriteMapChar(p.row, p.col, "*", Color.RandomLightning);
                                }
                                await Task.Delay(50);
                                frame = frames[i];
                            }
                            foreach (Actor ac in damage_targets)
                            {
                                B.Add("The bolt hits " + ac.the_name + ". ", ac);
                                await ac.TakeDamage(DamageType.ELECTRIC, DamageClass.MAGICAL, Global.Roll(2 + bonus, 6), this, a_name);
                            }
                        }
                        else
                        {
                            AnimateBeam(line, "*", Color.RandomLightning);
                            B.Add("The bolt hits " + t.the_name + ". ", t);
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.SHADOWSIGHT:
                    if (!HasAttr(AttrType.SHADOWSIGHT))
                    {
                        B.Add("You cast shadowsight. ");
                        B.Add("Your eyes pierce the darkness. ");
                        int duration = 10001;
                        GainAttr(AttrType.SHADOWSIGHT, duration, "You no longer see as well in darkness. ");
                        GainAttr(AttrType.LOW_LIGHT_VISION, duration);
                    }
                    else
                    {
                        B.Add("Your eyes are already attuned to darkness. ");
                        return false;
                    }
                    break;
                /*case SpellType.BURNING_HANDS:
                    if(t == null){
                        t = TileInDirection(GetDirection());
                    }
                    if(t != null){
                        B.Add(You("cast") + " burning hands. ",this);
                        AnimateMapCell(t,Color.DarkRed,'*');
                        Actor a = M.actor[t.row,t.col];
                        if(a != null){
                            B.Add(You("project") + " flames onto " + a.the_name + ". ",this,a);
                            a.TakeDamage(DamageType.FIRE,DamageClass.MAGICAL,Global.Roll(3+bonus,6),this);
                            if(M.actor[t.row,t.col] != null && Global.Roll(1,10) <= 2){
                                B.Add(a.You("start") + " to catch fire! ",a);
                                a.attrs[AttrType.CATCHING_FIRE]++;
                            }
                        }
                        else{
                            B.Add("You project flames from your hands. ");
                        }
                    }
                    else{
                        return false;
                    }
                    break;
                case SpellType.NIMBUS:
                {
                    if(HasAttr(AttrType.NIMBUS_ON)){
                        B.Add("You're already surrounded by a nimbus. ");
                        return false;
                    }
                    else{
                        B.Add(You("cast") + " nimbus. ",this);
                        B.Add("An electric glow surrounds " + the_name + ". ",this);
                        attrs[AttrType.NIMBUS_ON]++;
                        int duration = (Global.Roll(5)+5)*100;
                        Q.Add(new Event(this,duration,AttrType.NIMBUS_ON,"The electric glow fades from " + the_name + ". ",this));
                    }
                    break;
                }*/
                case SpellType.VOLTAIC_SURGE:
                    {
                        List<Actor> targets = new List<Actor>();
                        foreach (Actor a in ActorsWithinDistance(2, true))
                        {
                            if (HasLOE(a))
                            {
                                targets.Add(a);
                            }
                        }
                        B.Add(You("cast") + " voltaic surge. ", this);
                        AnimateExplosion(this, 2, Color.RandomLightning, "*");
                        if (targets.Count == 0)
                        {
                            B.Add("The air around " + the_name + " crackles. ", this);
                        }
                        else
                        {
                            while (targets.Count > 0)
                            {
                                Actor a = targets.Random();
                                targets.Remove(a);
                                B.Add("Electricity blasts " + a.the_name + ". ", a);
                                await a.TakeDamage(DamageType.ELECTRIC, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name);
                            }
                        }
                        break;
                    }
                case SpellType.MAGIC_HAMMER:
                    if (t == null)
                    {
                        t = TileInDirection(await GetDirection());
                    }
                    if (t != null)
                    {
                        Actor a = t.actor();
                        B.Add(You("cast") + " magic hammer. ", this);
                        B.DisplayNow();
                        Screen.AnimateMapCell(t.row, t.col, new colorchar("*", Color.Magenta), 100);
                        if (a != null)
                        {
                            B.Add(You("smash", true) + " " + a.TheVisible() + ". ", this, a);
                            if (await a.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(4 + bonus, 6), this, a_name))
                            {
                                a.GainAttrRefreshDuration(AttrType.STUNNED, 201, a.YouAre() + " no longer stunned. ", a);
                                B.Add(a.YouAre() + " stunned. ", a);
                            }
                        }
                        else
                        {
                            B.Add("You smash " + t.the_name + ". ");
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.RETREAT: //this is a player-only spell for now because it uses target_location to track position
                    B.Add("You cast retreat. ");
                    if (target_location == null)
                    {
                        target_location = M.tile[row, col];
                        B.Add("You create a rune of transport on " + M.tile[row, col].the_name + ". ");
                        target_location.features.Add(FeatureType.RUNE_OF_RETREAT);
                    }
                    else
                    {
                        if (M.actor[target_location.row, target_location.col] == null && target_location.passable)
                        {
                            B.Add("You activate your rune of transport. ");
                            await Move(target_location.row, target_location.col);
                            target_location.features.Remove(FeatureType.RUNE_OF_RETREAT);
                            target_location = null;
                        }
                        else
                        {
                            B.Add("Something blocks your transport. ");
                        }
                    }
                    break;
                case SpellType.GLACIAL_BLAST:
                    if (t == null)
                    {
                        line = await GetTarget(12);
                        if (line != null)
                        {
                            t = line.Last();
                        }
                    }
                    if (t != null)
                    {
                        B.Add(You("cast") + " glacial blast. ", this);
                        Actor a = FirstActorInLine(line);
                        if (a != null)
                        {
                            AnimateProjectile(line.ToFirstObstruction(), "*", Color.RandomIce);
                            B.Add("The glacial blast hits " + a.the_name + ". ", a);
                            await a.TakeDamage(DamageType.COLD, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name);
                        }
                        else
                        {
                            AnimateProjectile(line, "*", Color.RandomIce);
                            B.Add("The glacial blast hits " + t.the_name + ". ", t);
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.PASSAGE:
                    {
                        int i = DirectionOfOnlyUnblocked(TileType.WALL, true);
                        if (i == 0)
                        {
                            B.Add("There's no wall here. ", this);
                            return false;
                        }
                        else
                        {
                            if (t == null)
                            {
                                i = await GetDirection(true, false);
                                t = TileInDirection(i);
                            }
                            else
                            {
                                i = DirectionOf(t);
                            }
                            if (t != null)
                            {
                                if (t.ttype == TileType.WALL)
                                {
                                    B.Add(You("cast") + " passage. ", this);
                                    colorchar ch = new colorchar(Color.Cyan, "!");
                                    if (this == player)
                                    {
                                        Game.Console.CursorVisible = false;
                                        switch (DirectionOf(t))
                                        {
                                            case 8:
                                            case 2:
                                                ch.c = "|";
                                                break;
                                            case 4:
                                            case 6:
                                                ch.c = "-";
                                                break;
                                        }
                                    }
                                    List<Tile> tiles = new List<Tile>();
                                    List<colorchar> memlist = new List<colorchar>();
                                    while (!t.passable)
                                    {
                                        if (t.row == 0 || t.row == ROWS - 1 || t.col == 0 || t.col == COLS - 1)
                                        {
                                            break;
                                        }
                                        if (this == player)
                                        {
                                            tiles.Add(t);
                                            memlist.Add(Screen.MapChar(t.row, t.col));
                                            Screen.WriteMapChar(t.row, t.col, ch);

                                            await Task.Delay(35);
                                            //									Thread.Sleep(35);
                                        }
                                        t = t.TileInDirection(i);
                                    }
                                    if (t.passable && M.actor[t.row, t.col] == null)
                                    {
                                        if (this == player)
                                        {
                                            if (M.tile[row, col].inv != null)
                                            {
                                                Screen.WriteMapChar(row, col, new colorchar(tile().inv.color, tile().inv.symbol));
                                            }
                                            else
                                            {
                                                Screen.WriteMapChar(row, col, new colorchar(tile().color, tile().symbol));
                                            }
                                            Screen.WriteMapChar(t.row, t.col, new colorchar(color, symbol));
                                            int j = 0;
                                            foreach (Tile tile in tiles)
                                            {
                                                Screen.WriteMapChar(tile.row, tile.col, memlist[j++]);
                                                await Task.Delay(35);
                                                //Thread.Sleep(35);
                                            }
                                        }
                                        await Move(t.row, t.col);
                                        M.Draw();
                                        B.Add(You("travel") + " through the passage. ", this);
                                    }
                                    else
                                    {
                                        if (this == player)
                                        {
                                            int j = 0;
                                            foreach (Tile tile in tiles)
                                            {
                                                Screen.WriteMapChar(tile.row, tile.col, memlist[j++]);
                                                await Task.Delay(35);
                                                //Thread.Sleep(35);
                                            }
                                            B.Add("The passage is blocked. ");
                                        }
                                    }
                                }
                                else
                                {
                                    if (this == player)
                                    {
                                        B.Add("There's no wall here. ", this);
                                    }
                                    return false;
                                }
                            }
                            else
                            {
                                return false;
                            }
                        }
                        break;
                    }
                case SpellType.FLASHFIRE:
                    if (t == null)
                    {
                        line = await GetTarget(12, 2);
                        if (line != null)
                        {
                            t = line.Last();
                        }
                    }
                    if (t != null)
                    {
                        Actor a = FirstActorInLine(line);
                        if (a != null)
                        {
                            t = a.tile();
                        }
                        B.Add(You("cast") + " flashfire. ", this);
                        AnimateBoltProjectile(line.ToFirstObstruction(), Color.Red);
                        AnimateExplosion(t, 2, "*", Color.RandomFire);
                        B.Add("Fwoosh! ", new PhysicalObject[] { this, t });
                        List<Actor> targets = new List<Actor>();
                        Tile prev = line.ToFirstObstruction()[line.ToFirstObstruction().Count - 2];
                        foreach (Actor ac in t.ActorsWithinDistance(2))
                        {
                            if (t.passable)
                            {
                                if (t.HasBresenhamLine(ac.row, ac.col))
                                {
                                    targets.Add(ac);
                                }
                            }
                            else
                            {
                                if (prev.HasBresenhamLine(ac.row, ac.col))
                                {
                                    targets.Add(ac);
                                }
                            }
                        }
                        foreach (Tile t2 in t.TilesWithinDistance(2))
                        {
                            if (t.passable)
                            {
                                if (t.HasBresenhamLine(t2.row, t2.col))
                                {
                                    if (t2.actor() != null)
                                    {
                                        targets.Add(t2.actor());
                                    }
                                    if (t2.Is(FeatureType.TROLL_CORPSE))
                                    {
                                        t2.features.Remove(FeatureType.TROLL_CORPSE);
                                        B.Add("The troll corpse burns to ashes! ", t2);
                                    }
                                    if (t2.Is(FeatureType.TROLL_SEER_CORPSE))
                                    {
                                        t2.features.Remove(FeatureType.TROLL_SEER_CORPSE);
                                        B.Add("The troll seer corpse burns to ashes! ", t2);
                                    }
                                }
                            }
                            else
                            {
                                if (prev.HasBresenhamLine(t2.row, t2.col))
                                {
                                    if (t2.actor() != null)
                                    {
                                        targets.Add(t2.actor());
                                    }
                                    if (t2.Is(FeatureType.TROLL_CORPSE))
                                    {
                                        t2.features.Remove(FeatureType.TROLL_CORPSE);
                                        B.Add("The troll corpse burns to ashes! ", t2);
                                    }
                                    if (t2.Is(FeatureType.TROLL_SEER_CORPSE))
                                    {
                                        t2.features.Remove(FeatureType.TROLL_SEER_CORPSE);
                                        B.Add("The troll seer corpse burns to ashes! ", t2);
                                    }
                                }
                            }
                        }
                        while (targets.Count > 0)
                        {
                            Actor ac = targets.RemoveRandom();
                            B.Add("The explosion hits " + ac.the_name + ". ", ac);
                            await ac.TakeDamage(DamageType.FIRE, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name);
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.SONIC_BOOM:
                    if (t == null)
                    {
                        line = await GetTarget(12);
                        if (line != null)
                        {
                            t = line.Last();
                        }
                    }
                    if (t != null)
                    {
                        B.Add(You("cast") + " sonic boom. ", this);
                        Actor a = FirstActorInLine(line);
                        if (a != null)
                        {
                            AnimateProjectile(line.ToFirstObstruction(), "~", Color.Yellow);
                            B.Add("A wave of sound hits " + a.the_name + ". ", a);
                            int r = a.row;
                            int c = a.col;
                            await a.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(3 + bonus, 6), this, a_name);
                            if (Global.Roll(1, 10) <= 5 && M.actor[r, c] != null && !M.actor[r, c].HasAttr(AttrType.STUNNED))
                            {
                                B.Add(a.YouAre() + " stunned. ", a);
                                a.attrs[AttrType.STUNNED]++;
                                int duration = DurationOfMagicalEffect((Global.Roll(1, 4) + 2)) * 100;
                                Q.Add(new Event(a, duration, AttrType.STUNNED, a.YouAre() + " no longer stunned. ", new PhysicalObject[] { a }));
                            }
                        }
                        else
                        {
                            AnimateProjectile(line, "~", Color.Yellow);
                            B.Add("Sonic boom! ");
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.COLLAPSE:
                    if (t == null)
                    {
                        line = await GetTarget(12, -1);
                        if (line != null)
                        {
                            t = line.Last();
                        }
                    }
                    if (t != null)
                    {
                        B.Add(You("cast") + " collapse. ", this);
                        B.DisplayNow();
                        for (int dist = 2; dist > 0; --dist)
                        {
                            List<pos> cells = new List<pos>();
                            List<colorchar> chars = new List<colorchar>();
                            pos p2 = new pos(t.row - dist, t.col - dist);
                            if (p2.BoundsCheck())
                            {
                                cells.Add(p2);
                                chars.Add(new colorchar("\\", Color.DarkGreen));
                            }
                            p2 = new pos(t.row - dist, t.col + dist);
                            if (p2.BoundsCheck())
                            {
                                cells.Add(p2);
                                chars.Add(new colorchar("/", Color.DarkGreen));
                            }
                            p2 = new pos(t.row + dist, t.col - dist);
                            if (p2.BoundsCheck())
                            {
                                cells.Add(p2);
                                chars.Add(new colorchar("/", Color.DarkGreen));
                            }
                            p2 = new pos(t.row + dist, t.col + dist);
                            if (p2.BoundsCheck())
                            {
                                cells.Add(p2);
                                chars.Add(new colorchar("\\", Color.DarkGreen));
                            }
                            Screen.AnimateMapCells(cells, chars);
                        }
                        Screen.AnimateMapCell(t.row, t.col, new colorchar("X", Color.DarkGreen));
                        Actor a = t.actor();
                        if (a != null)
                        {
                            B.Add("Part of the ceiling falls onto " + a.the_name + ". ", a);
                            await a.TakeDamage(DamageType.BASHING, DamageClass.PHYSICAL, Global.Roll(4 + bonus, 6), this, a_name);
                        }
                        else
                        {
                            if (t.row == 0 || t.col == 0 || t.row == ROWS - 1 || t.col == COLS - 1)
                            {
                                B.Add("The wall resists. ");
                            }
                            else
                            {
                                if (t.ttype == TileType.WALL || t.ttype == TileType.HIDDEN_DOOR)
                                {
                                    B.Add("The wall crashes down! ");
                                    t.TurnToFloor();
                                    foreach (Tile neighbor in t.TilesAtDistance(1))
                                    {
                                        if (neighbor.solid_rock)
                                        {
                                            neighbor.solid_rock = false;
                                        }
                                    }
                                }
                            }
                        }
                        List<Tile> open_spaces = new List<Tile>();
                        foreach (Tile neighbor in t.TilesWithinDistance(1))
                        {
                            if (neighbor.passable)
                            {
                                if (a == null || neighbor != t)
                                { //don't hit the same guy again
                                    open_spaces.Add(neighbor);
                                }
                            }
                        }
                        int count = 4;
                        if (open_spaces.Count < 4)
                        {
                            count = open_spaces.Count;
                        }
                        for (; count > 0; --count)
                        {
                            Tile chosen = open_spaces.Random();
                            open_spaces.Remove(chosen);
                            if (chosen.actor() != null)
                            {
                                B.Add("A rock falls onto " + chosen.actor().the_name + ". ", chosen.actor());
                                await chosen.actor().TakeDamage(DamageType.BASHING, Forays.DamageClass.PHYSICAL, Global.Roll(2, 6), this, a_name);
                            }
                            else
                            {
                                TileType prev = chosen.ttype;
                                chosen.TransformTo(TileType.RUBBLE);
                                chosen.toggles_into = prev;
                            }
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.FORCE_BEAM:
                    if (t == null)
                    {
                        line = await GetTarget();
                        if (line != null)
                        {
                            t = line.Last();
                        }
                    }
                    if (t != null)
                    {
                        B.Add(You("cast") + " force beam. ", this);
                        B.DisplayNow();
                        //List<Tile> line2 = GetBestExtendedLine(t.row,t.col);
                        List<Tile> full_line = new List<Tile>(line);
                        line = line.GetRange(0, Math.Min(13, line.Count));
                        for (int i = 0; i < 3; ++i)
                        { //hits thrice
                            Actor firstactor = null;
                            Actor nextactor = null;
                            Tile firsttile = null;
                            Tile nexttile = null;
                            foreach (Tile tile in line)
                            {
                                if (!tile.passable)
                                {
                                    firsttile = tile;
                                    break;
                                }
                                if (M.actor[tile.row, tile.col] != null && M.actor[tile.row, tile.col] != this)
                                {
                                    int idx = full_line.IndexOf(tile);
                                    firsttile = tile;
                                    firstactor = M.actor[tile.row, tile.col];
                                    nexttile = full_line[idx + 1];
                                    nextactor = M.actor[nexttile.row, nexttile.col];
                                    break;
                                }
                            }
                            AnimateBoltBeam(line.ToFirstObstruction(), Color.Cyan);
                            if (firstactor != null)
                            {
                                string s = firstactor.TheVisible();
                                string s2 = firstactor.a_name;
                                await firstactor.TakeDamage(DamageType.MAGIC, DamageClass.MAGICAL, Global.Roll(1 + bonus, 6), this, a_name);
                                if (M.actor[firsttile.row, firsttile.col] != null)
                                {
                                    await firstactor.GetKnockedBack(full_line);
                                }
                                else
                                {
                                    if (!nexttile.passable)
                                    {
                                        B.Add(s + "'s corpse is knocked into " + nexttile.the_name + ". ", new PhysicalObject[] { firsttile, nexttile });
                                    }
                                    else
                                    {
                                        if (nextactor != null)
                                        {
                                            B.Add(s + "'s corpse is knocked into " + nextactor.TheVisible() + ". ", new PhysicalObject[] { firsttile, nextactor });
                                            await nextactor.TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, Global.Roll(1, 6), this, s2 + "'s falling corpse");
                                        }
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                /*case SpellType.DISINTEGRATE:
                    if(t == null){
                        t = await GetTarget();
                    }
                    if(t != null){
                        B.Add(You("cast") + " disintegrate. ",this);
                        Actor a = FirstActorInLine(t);
                        if(a != null){
                            AnimateBoltBeam(a,Color.DarkGreen);
                            B.Add(You("direct") + " destructive energies toward " + a.the_name + ". ",this,a);
                            a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,Global.Roll(8+bonus,6),this);
                        }
                        else{
                            AnimateBoltBeam(t,Color.DarkGreen);
                            if(t.type == TileType.WALL || t.type == TileType.DOOR_C || t.type == TileType.DOOR_O || t.type == TileType.CHEST){
                                B.Add(You("direct") + " destructive energies toward " + t.the_name + ". ",this,t);
                                B.Add(t.the_name + " turns to dust. ",t);
                                t.TurnToFloor();
                            }
                        }
                    }
                    else{
                        return false;
                    }
                    break;*/
                case SpellType.AMNESIA:
                    if (t == null)
                    {
                        t = TileInDirection(await GetDirection());
                    }
                    if (t != null)
                    {
                        Actor a = t.actor();
                        if (a != null)
                        {
                            B.Add(You("cast") + " amnesia. ", this);
                            /*for(int i=0;i<4;++i){
                                List<pos> cells = new List<pos>();
                                List<colorchar> chars = new List<colorchar>();
                                List<pos> nearby = a.p.PositionsWithinDistance(2);
                                for(int j=0;j<4;++j){
                                    cells.Add(nearby.RemoveRandom());
                                    chars.Add(new colorchar('*',Color.RandomPrismatic));
                                }
                                Screen.AnimateMapCells(cells,chars);
                            }*/
                            a.AnimateStorm(2, 4, 4, "*", Color.RandomPrismatic);
                            B.Add("You fade from " + a.TheVisible() + "'s awareness. ");
                            a.player_visibility_duration = 0;
                            a.target = null;
                            a.target_location = null;
                            a.attrs[Forays.AttrType.AMNESIA_STUN]++;
                        }
                        else
                        {
                            B.Add("There's nothing to target there. ");
                            return false;
                        }
                    }
                    else
                    {
                        return false;
                    }
                    break;
                case SpellType.BLIZZARD:
                    {
                        List<Actor> targets = ActorsWithinDistance(5, true);
                        B.Add(You("cast") + " blizzard. ", this);
                        AnimateStorm(5, 8, 24, "*", Color.RandomIce);
                        B.Add("A massive ice storm surrounds " + the_name + ". ", this);
                        while (targets.Count > 0)
                        {
                            int idx = Global.Roll(1, targets.Count) - 1;
                            Actor a = targets[idx];
                            targets.Remove(a);
                            B.Add("The blizzard hits " + a.the_name + ". ", a);
                            int r = a.row;
                            int c = a.col;
                            await a.TakeDamage(DamageType.COLD, DamageClass.MAGICAL, Global.Roll(5 + bonus, 6), this, a_name);
                            if (M.actor[r, c] != null && Global.Roll(1, 10) <= 8)
                            {
                                B.Add(a.the_name + " is encased in ice. ", a);
                                a.attrs[AttrType.FROZEN] = 25;
                            }
                        }
                        break;
                    }
                case SpellType.BLESS:
                    if (!HasAttr(AttrType.BLESSED))
                    {
                        B.Add(You("cast") + " bless. ", this);
                        B.Add(You("shine") + " briefly with inner light. ", this);
                        attrs[AttrType.BLESSED]++;
                        Q.Add(new Event(this, 400, AttrType.BLESSED));
                    }
                    else
                    {
                        B.Add(YouAre() + " already blessed! ", this);
                        return false;
                    }
                    break;
                case SpellType.MINOR_HEAL:
                    B.Add(You("cast") + " minor heal. ", this);
                    B.Add("A bluish glow surrounds " + the_name + ". ", this);
                    await TakeDamage(DamageType.HEAL, DamageClass.NO_TYPE, Global.Roll(4, 6), null);
                    break;
                case SpellType.HOLY_SHIELD:
                    if (!HasAttr(AttrType.HOLY_SHIELDED))
                    {
                        B.Add(You("cast") + " holy shield. ", this);
                        B.Add("A fiery halo appears above " + the_name + ". ", this);
                        attrs[AttrType.HOLY_SHIELDED]++;
                        int duration = (Global.Roll(3, 2) + 1) * 100;
                        Q.Add(new Event(this, duration, AttrType.HOLY_SHIELDED, the_name + "'s halo fades. ", new PhysicalObject[] { this }));
                    }
                    else
                    {
                        B.Add(Your() + " holy shield is already active. ", this);
                        return false;
                    }
                    break;
            }
            if (atype == ActorType.PLAYER && spell != SpellType.AMNESIA)
            {
                MakeNoise();
            }
            if (!force_of_will)
            {
                if (Spell.Level(spell) - TotalSkill(SkillType.MAGIC) > 0)
                {
                    if (HasFeat(FeatType.STUDENTS_LUCK))
                    {
                        if (Global.CoinFlip())
                        {
                            magic_penalty++;
                            B.Add(YouFeel() + " drained. ", this);
                        }
                        else
                        {
                            if (atype == ActorType.PLAYER)
                            {
                                B.Add("You feel lucky. "); //punk
                            }
                        }
                    }
                    else
                    {
                        magic_penalty++;
                        B.Add(YouFeel() + " drained. ", this);
                    }
                }
            }
            else
            {
                magic_penalty += 5;
                if (magic_penalty > 20)
                {
                    magic_penalty = 20;
                }
                B.Add("You drain your magic reserves. ");
            }
            Q1();
            return true;
        }
예제 #22
0
 public void InputAI()
 {
     if(type == ActorType.DREAM_SPRITE && HasAttr(AttrType.COOLDOWN_2) && target != null){
         bool no_los_needed = !target.CanSee(this);
         Tile t = target.TilesAtDistance(DistanceFrom(target)).Where(x=>x.passable && x.actor() == null && x.DistanceFrom(this) > 1 && (no_los_needed || target.CanSee(x))).RandomOrDefault();
         if(t == null){ //gradually loosening the restrictions on placement...
             t = target.TilesAtDistance(DistanceFrom(target)).Where(x=>x.passable && x.actor() == null && (no_los_needed || target.CanSee(x))).RandomOrDefault();
         }
         if(t == null){
             t = target.TilesWithinDistance(12).Where(x=>x.passable && x.actor() == null && x.DistanceFrom(target) >= this.DistanceFrom(target) && x.DistanceFrom(this) > 1 && (no_los_needed || target.CanSee(x))).RandomOrDefault();
         }
         if(t == null){
             t = target.TilesWithinDistance(12).Where(x=>x.passable && x.actor() == null && x.DistanceFrom(target) >= this.DistanceFrom(target) && (no_los_needed || target.CanSee(x))).RandomOrDefault();
         }
         if(t == null){
             t = TilesAtDistance(2).Where(x=>x.passable && x.actor() == null && (no_los_needed || target.CanSee(x))).RandomOrDefault();
         }
         if(t == null){
             t = TilesWithinDistance(6).Where(x=>x.passable && x.actor() == null && (no_los_needed || target.CanSee(x))).RandomOrDefault();
         }
         if(t == null){
             t = M.AllTiles().Where(x=>x.passable && x.actor() == null && (no_los_needed || target.CanSee(x))).RandomOrDefault();
         }
         if(t != null){
             attrs[AttrType.COOLDOWN_2] = 0;
             if(group == null){
                 group = new List<Actor>{this};
             }
             Actor clone = Create(ActorType.DREAM_SPRITE_CLONE,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent);
             clone.speed = 100;
             bool seen = target.CanSee(clone);
             clone.player_visibility_duration = -1;
             group.Add(clone);
             clone.group = group;
             group.Randomize();
             List<Tile> valid_tiles = new List<Tile>();
             foreach(Actor a in group){
                 valid_tiles.Add(a.tile());
             }
             Tile newtile = valid_tiles.Random();
             if(newtile != tile()){
                 Move(newtile.row,newtile.col,false);
             }
             if(seen){
                 B.Add("Another " + name + " appears! ");
             }
         }
     }
     if(type == ActorType.MACHINE_OF_WAR){
         attrs[AttrType.COOLDOWN_1]++;
         if(attrs[AttrType.COOLDOWN_1] == 16){
             B.Add(the_name + " vents fire! ",this);
             if(HasAttr(AttrType.FROZEN)){
                 TakeDamage(DamageType.FIRE,DamageClass.PHYSICAL,1,null);
             }
             foreach(Tile t in TilesWithinDistance(1)){
                 if(t.actor() != null && t.actor() != this){
                     if(t.actor().TakeDamage(DamageType.FIRE,DamageClass.PHYSICAL,R.Roll(5,6),this,a_name)){
                         t.actor().ApplyBurning();
                     }
                 }
                 t.ApplyEffect(DamageType.FIRE);
             }
             attrs[AttrType.COOLDOWN_1] = 0;
         }
     }
     bool no_act = false;
     if(!no_act && HasAttr(AttrType.CONFUSED)){
         ConfusedMove();
         no_act = true;
     }
     if(!no_act && HasAttr(AttrType.BLIND)){
         Stagger();
         no_act = true;
     }
     if(!no_act && HasAttr(AttrType.ENRAGED)){
         EnragedMove();
         no_act = true;
     }
     bool aware_of_player = CanSee(player);
     if(HasAttr(AttrType.SEES_ADJACENT_PLAYER)){
         if(DistanceFrom(player) == 1){ //this allows them to attack when the player is shadow cloaked
             aware_of_player = true;
         }
         else{
             attrs[AttrType.SEES_ADJACENT_PLAYER] = 0;
         }
     }
     if(target == player && player.tile() == target_location && HasLOE(player)){
         aware_of_player = true;
     }
     if(target == player && player_visibility_duration == -1 && DistanceFrom(player) == 1){ //the comparison to -1 exactly is what makes them lose track of the player after a turn of movement - i think.
         aware_of_player = true;
     }
     if(aware_of_player){
         if(!Is(ActorType.BLADE) || target == null){
             target = player;
             target_location = M.tile[player.row,player.col];
             path.Clear();
         }
         player_visibility_duration = -1;
     }
     else{
         bool might_notice = false;
         if((IsWithinSightRangeOf(player.row,player.col) || (player.tile().IsLit() && !HasAttr(AttrType.BLINDSIGHT))) //if they're stealthed and nearby...
             && HasLOS(player.row,player.col) && (!player.IsInvisibleHere() || HasAttr(AttrType.BLINDSIGHT))){ //((removed player_noticed check from this line))
             might_notice = true;
         }
         if(type == ActorType.CLOUD_ELEMENTAL){
             List<pos> cloud = M.tile.GetFloodFillPositions(p,false,x=>M.tile[x].features.Contains(FeatureType.FOG));
             foreach(pos p2 in cloud){
                 if(player.DistanceFrom(p2) <= 12){
                     if(M.tile[p2].HasLOS(player)){
                         might_notice = true;
                         break;
                     }
                 }
             }
         }
         if(player.HasFeat(FeatType.CORNER_CLIMB) && DistanceFrom(player) > 1 && !player.tile().IsLit()){
             List<pos> valid_open_doors = new List<pos>();
             foreach(int dir in U.DiagonalDirections){
                 if(TileInDirection(dir).type == TileType.DOOR_O){
                     valid_open_doors.Add(TileInDirection(dir).p);
                 }
             }
             if(SchismExtensionMethods.Extensions.ConsecutiveAdjacent(player.p,x=>valid_open_doors.Contains(x) || M.tile[x].Is(TileType.WALL,TileType.CRACKED_WALL,TileType.DOOR_C,TileType.HIDDEN_DOOR,TileType.STATUE,TileType.STONE_SLAB,TileType.WAX_WALL)) >= 5){
                 might_notice = false;
             }
         }
         if(type == ActorType.ORC_WARMAGE && HasAttr(AttrType.DETECTING_MOVEMENT) && player_visibility_duration >= 0 && !player.HasAttr(AttrType.TURNS_HERE) && DistanceFrom(player) <= 12){
             might_notice = true;
         }
         if(!no_act && might_notice){
             int multiplier = HasAttr(AttrType.KEEN_SENSES)? 5 : 10; //animals etc. are approximately twice as hard to sneak past
             int stealth = player.TotalSkill(SkillType.STEALTH);
             if(HasAttr(AttrType.BLINDSIGHT) && !player.tile().IsLit()){ //if this monster has blindsight, take away the stealth bonus for being in darkness
                 stealth -= 2;
             }
             bool noticed = false;
             if(type == ActorType.ORC_WARMAGE && HasAttr(AttrType.DETECTING_MOVEMENT) && !player.HasAttr(AttrType.TURNS_HERE) && DistanceFrom(player) <= 12 && HasLOS(player)){
                 noticed = true;
             }
             if(stealth * DistanceFrom(player) * multiplier - player_visibility_duration++*5 < R.Roll(1,100)){
                 noticed = true;
             }
             if(noticed){
                 if(type == ActorType.BLADE){
                     if(target == null){
                         target = player;
                         target_location = M.tile[player.row,player.col];
                         path.Clear();
                     }
                 }
                 else{
                     target = player;
                     target_location = M.tile[player.row,player.col];
                     path.Clear();
                     PrintAggressionMessage();
                     Q1();
                     no_act = true;
                 }
                 player_visibility_duration = -1;
                 attrs[AttrType.PLAYER_NOTICED]++;
                 if(group != null){
                     foreach(Actor a in group){
                         if(a != this && DistanceFrom(a) < 3){
                             a.player_visibility_duration = -1;
                             a.attrs[AttrType.PLAYER_NOTICED]++;
                             a.target = player;
                             a.target_location = M.tile[player.row,player.col];
                         }
                     }
                 }
             }
         }
         else{
             if(player_visibility_duration >= 0){ //if they hadn't seen the player yet...
                 player_visibility_duration = 0;
             }
             else{
                 int alerted = 1 + attrs[AttrType.ALERTED];
                 int turns_required = 10 * alerted * alerted;
                 if(target_location == null && player_visibility_duration-- <= -turns_required){
                     attrs[AttrType.ALERTED]++;
                     player_visibility_duration = 0;
                     attrs[AttrType.AGGRESSION_MESSAGE_PRINTED] = 0;
                     target = null;
                 }
             }
         }
     }
     if(target != null && !HasAttr(AttrType.AGGRESSIVE) && curhp == maxhp){
         if(HasAttr(AttrType.TERRITORIAL) && DistanceFrom(target) > 3){
             target = null;
             target_location = null;
         }
         else{
             if(DistanceFrom(target) > 12){
                 target = null;
                 target_location = null;
             }
         }
     }
     if(type == ActorType.DARKNESS_DWELLER){
         if((tile().IsLit() && tile().light_value > 0) || M.wiz_lite){
             if(attrs[AttrType.COOLDOWN_1] < 7){
                 if(!HasAttr(AttrType.COOLDOWN_1) && player.HasLOS(this)){
                     B.Add(the_name + " is blinded by the light! ",this);
                 }
                 RefreshDuration(AttrType.BLIND,100,the_name + " can see again. ",this);
                 attrs[AttrType.COOLDOWN_1]++;
                 attrs[AttrType.COOLDOWN_2] = 7;
             }
         }
         else{
             Event e = Q.FindAttrEvent(this,AttrType.BLIND);
             if(e == null || e.delay == 100){
                 RefreshDuration(AttrType.BLIND,0);
             }
             if(attrs[AttrType.COOLDOWN_2] > 0){
                 attrs[AttrType.COOLDOWN_2]--;
                 if(!HasAttr(AttrType.COOLDOWN_2)){
                     attrs[AttrType.COOLDOWN_1] = 0;
                     B.Add(Your() + " eyes adjust to the darkness. ",this);
                 }
             }
         }
     }
     if(type == ActorType.MARBLE_HORROR && tile().IsLit()){
         B.Add("The marble horror reverts to its statue form. ",this);
         type = ActorType.MARBLE_HORROR_STATUE;
         SetName("marble horror statue");
         attrs[AttrType.IMMOBILE] = 1;
         attrs[AttrType.INVULNERABLE] = 1;
         if(HasAttr(AttrType.BURNING)){
             RefreshDuration(AttrType.BURNING,0);
         }
         attrs[AttrType.IMMUNE_BURNING] = 1;
         if(curhp > 0){
             curhp = maxhp;
         }
     }
     if(type == ActorType.MARBLE_HORROR_STATUE && !tile().IsLit()){
         B.Add("The marble horror animates once more. ",this);
         type = ActorType.MARBLE_HORROR;
         SetName("marble horror");
         attrs[AttrType.IMMOBILE] = 0;
         attrs[AttrType.INVULNERABLE] = 0;
         attrs[AttrType.IMMUNE_BURNING] = 0;
     }
     if(type == ActorType.CORPSETOWER_BEHEMOTH && tile().Is(TileType.FLOOR)){
         tile().Toggle(null,TileType.GRAVE_DIRT);
         bool found = false;
         foreach(Event e in Q.list){
             if(!e.dead && e.type == EventType.GRAVE_DIRT){
                 e.area.Add(tile());
                 found = true;
                 break;
             }
         }
         if(!found){
             Q.Add(new Event(new List<Tile>{tile()},100,EventType.GRAVE_DIRT));
         }
     }
     int danger_threshold = (target != null? 1 : 0);
     if(!no_act && GetDangerRating(tile()) > danger_threshold){
         if(HasAttr(AttrType.NONEUCLIDEAN_MOVEMENT) && target != null){
             List<Tile> safest = target.TilesWithinDistance(DistanceFrom(target)+1).Where(x=>x.passable && x.actor() == null && x.DistanceFrom(target) >= DistanceFrom(target)-1).WhereLeast(x=>GetDangerRating(x));
             if(CanSee(target)){
                 if(HasAttr(AttrType.KEEPS_DISTANCE)){
                     safest = safest.WhereGreatest(x=>x.DistanceFrom(target));
                 }
                 else{
                     safest = safest.WhereLeast(x=>x.DistanceFrom(target));
                 }
             }
             if(safest.Count > 0){
                 Tile t = safest.Random();
                 Move(t.row,t.col);
                 QS();
                 no_act = true;
             }
         }
         else{
             List<Tile> safest = TilesWithinDistance(1).Where(x=>x.passable && x.actor() == null).WhereLeast(x=>GetDangerRating(x));
             if(target != null && CanSee(target)){
                 if(HasAttr(AttrType.KEEPS_DISTANCE)){
                     safest = safest.WhereGreatest(x=>x.DistanceFrom(target));
                 }
                 else{
                     safest = safest.WhereLeast(x=>x.DistanceFrom(target));
                 }
             }
             if(safest.Count > 0){
                 AI_Step(safest.Random());
                 QS();
                 no_act = true;
             }
         }
     }
     if(type == ActorType.MECHANICAL_KNIGHT && attrs[AttrType.COOLDOWN_1] != 1){
         attrs[AttrType.MECHANICAL_SHIELD] = 1; //if the knight dropped its guard, it regains its shield here (unless it has no arms)
     }
     if(group != null && group.Count == 0){ //this shouldn't happen, but does. this stops it from crashing.
         group = null;
     }
     if(!no_act){
         if(Is(ActorType.BLOOD_MOTH,ActorType.GHOST,ActorType.BLADE,ActorType.MINOR_DEMON,ActorType.FROST_DEMON,ActorType.BEAST_DEMON,ActorType.DEMON_LORD) || (type == ActorType.BERSERKER && HasAttr(AttrType.COOLDOWN_2))){
             ActiveAI();
         }
         else{
             if(target != null){
                 if(CanSee(target) || (target == player && aware_of_player)){
                     ActiveAI();
                 }
                 else{
                     SeekAI();
                 }
             }
             else{
                 IdleAI();
             }
         }
     }
     if(type == ActorType.SHADOW){
         CalculateDimming();
     }
     if(type == ActorType.STALKING_WEBSTRIDER && !HasAttr(AttrType.BURROWING) && !tile().Is(FeatureType.WEB,FeatureType.FIRE)){
         if(target != null && (CanSee(target) || target == player && aware_of_player)){ //not while wandering, just while chasing the player.
             tile().AddFeature(FeatureType.WEB);
         }
     }
     if(type == ActorType.CLOUD_ELEMENTAL){
         List<Tile> area = new List<Tile>();
         foreach(Tile t in TilesWithinDistance(1)){
             if(t.passable){
                 t.AddFeature(FeatureType.FOG);
                 area.Add(t);
             }
         }
         List<Tile> area2 = tile().AddGaseousFeature(FeatureType.FOG,2);
         area.AddRange(area2);
         if(area.Count > 0){
             Q.RemoveTilesFromEventAreas(area,EventType.REMOVE_GAS);
             Event.RemoveGas(area,101,FeatureType.FOG,75);
         }
     }
     if(type == ActorType.NOXIOUS_WORM){
         List<Tile> area = tile().AddGaseousFeature(FeatureType.POISON_GAS,2);
         if(area.Count > 0){
             Q.RemoveTilesFromEventAreas(area,EventType.REMOVE_GAS);
             Event.RemoveGas(area,200,FeatureType.POISON_GAS,18);
         }
     }
     if(HasAttr(AttrType.SILENCE_AURA) && DistanceFrom(player) <= 2 && HasLOE(player)){
         if(!player.HasAttr(AttrType.SILENCE_AURA_MESSAGE_COOLDOWN)){
             if(player.CanSee(this)){
                 B.Add("Utter silence falls as " + the_name + " draws near. ");
             }
             else{
                 B.Add("Utter silence falls around you. ");
             }
             Help.TutorialTip(TutorialTopic.Silenced);
         }
         player.RefreshDuration(AttrType.SILENCE_AURA_MESSAGE_COOLDOWN,301);
     }
 }
 public static void Targeting_RemoveLine(Tile tc,bool done,List<Tile> line,colorchar[,] mem,int radius)
 {
     if(done){
         Screen.CursorVisible = false;
         foreach(Tile t in line){
             Screen.WriteMapChar(t.row,t.col,mem[t.row,t.col]);
         }
         if(radius > 0){
             foreach(Tile t in tc.TilesWithinDistance(radius,true)){
                 if(!line.Contains(t)){
                     Screen.WriteMapChar(t.row,t.col,mem[t.row,t.col]);
                 }
             }
         }
         Screen.CursorVisible = true;
     }
 }
예제 #24
0
		public Tile(Tile t,int r,int c){
			ttype = t.ttype;
			name = t.name;
			a_name = t.a_name;
			the_name = t.the_name;
			symbol = t.symbol;
			color = t.color;
			passable = t.passable;
			opaque = t.opaque;
			seen = false;
			solid_rock = false;
			light_value = 0;
			toggles_into = t.toggles_into;
			inv = null;
			row = r;
			col = c;
			light_radius = t.light_radius;
		}
예제 #25
0
 static void MainMenu()
 {
     ConsoleKeyInfo command;
     string recentname = "".PadRight(30);
     int recentdepth = -1;
     char recentwin = '-';
     string recentcause = "";
     bool on_highscore_list = false;
     MouseUI.PushButtonMap();
     while(true){
         Screen.Blank();
         int row = 8;
         int col = (Global.SCREEN_W - 28) / 2; //centering "Forays into Norrendrin x.y.z", which is 28 chars.
         Screen.WriteString(row++,col,new cstr(Color.Yellow,"Forays into Norrendrin " + Global.VERSION));
         Screen.WriteString(row++,col,new cstr(Color.Green,"".PadRight(28,'-')));
         col += 4; //recenter for menu options
         row++;
         bool saved_game = File.Exists("forays.sav");
         if(!saved_game){
             Screen.WriteString(row++,col,"[a] Start a new game");
         }
         else{
             Screen.WriteString(row++,col,"[a] Resume saved game");
         }
         Screen.WriteString(row++,col,"[b] How to play");
         Screen.WriteString(row++,col,"[c] High scores");
         Screen.WriteString(row++,col,"[d] Quit");
         for(int i=0;i<4;++i){
             Screen.WriteChar(i+row-4,col+1,new colorchar(Color.Cyan,(char)(i+'a')));
             MouseUI.CreateButton((ConsoleKey)(i + ConsoleKey.A),false,i+row-4,0,1,Global.SCREEN_W);
         }
         Screen.ResetColors();
         Screen.SetCursorPosition(Global.MAP_OFFSET_COLS,Global.MAP_OFFSET_ROWS+8);
         command = Input.ReadKey();
         switch(command.KeyChar){
         case 'a':
         {
             Global.GAME_OVER = false;
             Global.BOSS_KILLED = false;
             Global.SAVING = false;
             Global.LoadOptions();
             Game game = new Game();
             Actor.attack[ActorType.PLAYER] = new List<AttackInfo>{new AttackInfo(100,2,AttackEffect.NO_CRIT,"& hit *","& miss *","")};
             if(!saved_game){
                 game.player = new Actor(ActorType.PLAYER,"you",'@',Color.White,100,100,0,0,AttrType.HUMANOID_INTELLIGENCE);
                 game.player.inv = new List<Item>();
                 Actor.feats_in_order = new List<FeatType>();
                 Actor.spells_in_order = new List<SpellType>();
                 game.player.weapons.AddLast(new Weapon(WeaponType.SWORD));
                 game.player.weapons.AddLast(new Weapon(WeaponType.MACE));
                 game.player.weapons.AddLast(new Weapon(WeaponType.DAGGER));
                 game.player.weapons.AddLast(new Weapon(WeaponType.STAFF));
                 game.player.weapons.AddLast(new Weapon(WeaponType.BOW));
                 game.player.armors.AddLast(new Armor(ArmorType.LEATHER));
                 game.player.armors.AddLast(new Armor(ArmorType.CHAINMAIL));
                 game.player.armors.AddLast(new Armor(ArmorType.FULL_PLATE));
             }
             game.M = new Map(game);
             game.B = new Buffer(game);
             game.Q = new Queue(game);
             Map.Q = game.Q;
             Map.B = game.B;
             PhysicalObject.M = game.M;
             PhysicalObject.B = game.B;
             PhysicalObject.Q = game.Q;
             PhysicalObject.player = game.player;
             Event.Q = game.Q;
             Event.B = game.B;
             Event.M = game.M;
             Event.player = game.player;
             Fire.fire_event = null;
             Fire.burning_objects = new List<PhysicalObject>();
             if(!saved_game){
                 Actor.player_name = "";
                 if(File.Exists("name.txt")){
                     StreamReader file = new StreamReader("name.txt");
                     string base_name = file.ReadLine();
                     if(base_name == "%random%"){
                         Actor.player_name = Global.GenerateCharacterName();
                     }
                     else{
                         Actor.player_name = base_name;
                     }
                     int num = 0;
                     if(base_name != "%random%" && file.Peek() != -1){
                         num = Convert.ToInt32(file.ReadLine());
                         if(num > 1){
                             Actor.player_name = Actor.player_name + " " + Global.RomanNumeral(num);
                         }
                     }
                     file.Close();
                     if(num > 0){
                         StreamWriter fileout = new StreamWriter("name.txt",false);
                         fileout.WriteLine(base_name);
                         fileout.WriteLine(num+1);
                         fileout.Close();
                     }
                 }
                 if(Actor.player_name == ""){
                     MouseUI.PushButtonMap(MouseMode.NameEntry);
                     Screen.Blank();
                     /*for(int i=4;i<=7;++i){
                         Screen.WriteMapString(i,0,"".PadToMapSize());
                     }*/
                     string s = "";
                     int name_option = 0;
                     int c = 3;
                     while(true){
                         Screen.WriteMapString(4,c,"Enter name: ");
                         if(s == ""){
                             Screen.WriteMapString(6,c,"(Press [Enter] for a random name)".GetColorString());
                         }
                         else{
                             Screen.WriteMapString(6,c,"(Press [Enter] when finished)    ".GetColorString());
                         }
                         List<string> name_options = new List<string>{"Default: Choose a new name for each character","Static:  Use this name for every character","Legacy:  Name all future characters after this one","Random:  Name all future characters randomly"};
                         for(int i=0;i<4;++i){
                             Color option_color = Color.DarkGray;
                             if(i == name_option){
                                 option_color = Color.White;
                             }
                             Screen.WriteMapString(15+i,c,name_options[i],option_color);
                         }
                         Screen.WriteMapString(20,c,"(Press [Tab] to change naming preference)".GetColorString());
                         if(name_option != 0){
                             Screen.WriteMapString(22,c-5,"(To stop naming characters automatically, delete name.txt)",Color.Green);
                         }
                         else{
                             Screen.WriteMapString(22,c-5,"".PadToMapSize());
                         }
                         Screen.WriteMapString(4,c+12,s.PadRight(26));
                         Screen.SetCursorPosition(c + Global.MAP_OFFSET_COLS + 12 + s.Length,Global.MAP_OFFSET_ROWS + 4);
                         MouseUI.CreateButton(ConsoleKey.Enter,false,6+Global.MAP_OFFSET_ROWS,0,1,Global.SCREEN_W);
                         MouseUI.CreateButton(ConsoleKey.Tab,false,20+Global.MAP_OFFSET_ROWS,0,1,Global.SCREEN_W);
                         MouseUI.CreateButton(ConsoleKey.F21,false,15+Global.MAP_OFFSET_ROWS,0,1,Global.SCREEN_W);
                         MouseUI.CreateButton(ConsoleKey.F22,false,16+Global.MAP_OFFSET_ROWS,0,1,Global.SCREEN_W);
                         MouseUI.CreateButton(ConsoleKey.F23,false,17+Global.MAP_OFFSET_ROWS,0,1,Global.SCREEN_W);
                         MouseUI.CreateButton(ConsoleKey.F24,false,18+Global.MAP_OFFSET_ROWS,0,1,Global.SCREEN_W);
                         Screen.CursorVisible = true;
                         command = Input.ReadKey();
                         if((command.KeyChar >= '!' && command.KeyChar <= '~') || command.KeyChar == ' '){
                             if(s.Length < 26){
                                 s = s + command.KeyChar;
                             }
                         }
                         else{
                             if(command.Key == ConsoleKey.Backspace && s.Length > 0){
                                 s = s.Substring(0,s.Length-1);
                             }
                             else{
                                 if(command.Key == ConsoleKey.Escape){
                                     s = "";
                                 }
                                 else{
                                     if(command.Key == ConsoleKey.Tab){
                                         name_option = (name_option + 1) % 4;
                                     }
                                     else{
                                         if(command.Key == ConsoleKey.Enter){
                                             if(s.Length == 0){
                                                 s = Global.GenerateCharacterName();
                                             }
                                             else{
                                                 Actor.player_name = s;
                                                 break;
                                             }
                                         }
                                         else{
                                             switch(command.Key){
                                             case ConsoleKey.F21:
                                                 name_option = 0;
                                                 break;
                                             case ConsoleKey.F22:
                                                 name_option = 1;
                                                 break;
                                             case ConsoleKey.F23:
                                                 name_option = 2;
                                                 break;
                                             case ConsoleKey.F24:
                                                 name_option = 3;
                                                 break;
                                             }
                                         }
                                     }
                                 }
                             }
                         }
                     }
                     MouseUI.PopButtonMap();
                     switch(name_option){
                     case 1: //static
                     {
                         StreamWriter fileout = new StreamWriter("name.txt",false);
                         fileout.WriteLine(s);
                         fileout.WriteLine(0);
                         fileout.Close();
                         break;
                     }
                     case 2: //legacy
                     {
                         StreamWriter fileout = new StreamWriter("name.txt",false);
                         fileout.WriteLine(s);
                         fileout.WriteLine(2);
                         fileout.Close();
                         break;
                     }
                     case 3: //random
                     {
                         StreamWriter fileout = new StreamWriter("name.txt",false);
                         fileout.WriteLine("%random%");
                         fileout.WriteLine(0);
                         fileout.Close();
                         break;
                     }
                     }
                 }
                 {
                     Event e = new Event(game.player,0,EventType.MOVE);
                     e.tiebreaker = 0;
                     game.Q.Add(e);
                 }
                 Item.GenerateUnIDedNames();
                 game.M.GenerateLevelTypes();
                 game.M.GenerateLevel();
                 game.player.UpdateRadius(0,6,true);
                 Item.Create(ConsumableType.BANDAGES,game.player).other_data = 5;
                 Item.Create(ConsumableType.FLINT_AND_STEEL,game.player).other_data = 3;
                 game.player.inv[0].revealed_by_light = true;
                 game.player.inv[1].revealed_by_light = true;
             }
             else{ //loading
                 FileStream file = new FileStream("forays.sav",FileMode.Open);
                 BinaryReader b = new BinaryReader(file);
                 Dictionary<int,PhysicalObject> id = new Dictionary<int, PhysicalObject>();
                 id.Add(0,null);
                 Dict<PhysicalObject,int> missing_target_id = new Dict<PhysicalObject, int>();
                 List<Actor> need_targets = new List<Actor>();
                 Dict<PhysicalObject,int> missing_location_id = new Dict<PhysicalObject, int>();
                 List<Actor> need_location = new List<Actor>();
                 Actor.player_name = b.ReadString();
                 game.M.current_level = b.ReadInt32();
                 game.M.level_types = new List<LevelType>();
                 for(int i=0;i<20;++i){
                     game.M.level_types.Add((LevelType)b.ReadInt32());
                 }
                 game.M.wiz_lite = b.ReadBoolean();
                 game.M.wiz_dark = b.ReadBoolean();
                 for(int i=0;i<Global.ROWS;++i){
                     for(int j=0;j<Global.COLS;++j){
                         game.M.last_seen[i,j].c = b.ReadChar();
                         game.M.last_seen[i,j].color = (Color)b.ReadInt32();
                         game.M.last_seen[i,j].bgcolor = (Color)b.ReadInt32();
                     }
                 }
                 if(game.M.current_level == 21){
                     game.M.final_level_cultist_count = new int[5];
                     for(int i=0;i<5;++i){
                         game.M.final_level_cultist_count[i] = b.ReadInt32();
                     }
                     game.M.final_level_demon_count = b.ReadInt32();
                     game.M.final_level_clock = b.ReadInt32();
                 }
                 Actor.feats_in_order = new List<FeatType>();
                 Actor.spells_in_order = new List<SpellType>();
                 int num_featlist = b.ReadInt32();
                 for(int i=0;i<num_featlist;++i){
                     Actor.feats_in_order.Add((FeatType)b.ReadInt32());
                 }
                 int num_spelllist = b.ReadInt32();
                 for(int i=0;i<num_spelllist;++i){
                     Actor.spells_in_order.Add((SpellType)b.ReadInt32());
                 }
                 int num_actor_tiebreakers = b.ReadInt32();
                 Actor.tiebreakers = new List<Actor>(num_actor_tiebreakers);
                 for(int i=0;i<num_actor_tiebreakers;++i){
                     int ID = b.ReadInt32();
                     if(ID != 0){
                         Actor a = new Actor();
                         id.Add(ID,a);
                         a.row = b.ReadInt32();
                         a.col = b.ReadInt32();
                         if(a.row >= 0 && a.row < Global.ROWS && a.col >= 0 && a.col < Global.COLS){
                             game.M.actor[a.row,a.col] = a;
                         }
                         Actor.tiebreakers.Add(a);
                         a.name = b.ReadString();
                         a.the_name = b.ReadString();
                         a.a_name = b.ReadString();
                         a.symbol = b.ReadChar();
                         a.color = (Color)b.ReadInt32();
                         a.type = (ActorType)b.ReadInt32();
                         if(a.type == ActorType.PLAYER){
                             game.player = a;
                             Actor.player = a;
                             Buffer.player = a;
                             Item.player = a;
                             Map.player = a;
                             Event.player = a;
                             Tile.player = a;
                         }
                         a.maxhp = b.ReadInt32();
                         a.curhp = b.ReadInt32();
                         a.maxmp = b.ReadInt32();
                         a.curmp = b.ReadInt32();
                         a.speed = b.ReadInt32();
                         a.light_radius = b.ReadInt32();
                         int target_ID = b.ReadInt32();
                         if(id.ContainsKey(target_ID)){
                             a.target = (Actor)id[target_ID];
                         }
                         else{
                             a.target = null;
                             need_targets.Add(a);
                             missing_target_id[a] = target_ID;
                         }
                         int num_items = b.ReadInt32();
                         for(int j=0;j<num_items;++j){
                             int item_id = b.ReadInt32();
                             if(item_id != 0){
                                 Item item = new Item();
                                 id.Add(item_id,item);
                                 item.name = b.ReadString();
                                 item.the_name = b.ReadString();
                                 item.a_name = b.ReadString();
                                 item.symbol = b.ReadChar();
                                 item.color = (Color)b.ReadInt32();
                                 item.light_radius = b.ReadInt32();
                                 item.type = (ConsumableType)b.ReadInt32();
                                 item.quantity = b.ReadInt32();
                                 item.charges = b.ReadInt32();
                                 item.other_data = b.ReadInt32();
                                 item.ignored = b.ReadBoolean();
                                 item.do_not_stack = b.ReadBoolean();
                                 item.revealed_by_light = b.ReadBoolean();
                                 a.inv.Add(item);
                             }
                         }
                         int num_attrs = b.ReadInt32();
                         for(int j=0;j<num_attrs;++j){
                             AttrType t = (AttrType)b.ReadInt32();
                             a.attrs[t] = b.ReadInt32();
                         }
                         int num_skills = b.ReadInt32();
                         for(int j=0;j<num_skills;++j){
                             SkillType t = (SkillType)b.ReadInt32();
                             a.skills[t] = b.ReadInt32();
                         }
                         int num_feats = b.ReadInt32();
                         for(int j=0;j<num_feats;++j){
                             FeatType t = (FeatType)b.ReadInt32();
                             a.feats[t] = b.ReadBoolean();
                         }
                         int num_spells = b.ReadInt32();
                         for(int j=0;j<num_spells;++j){
                             SpellType t = (SpellType)b.ReadInt32();
                             a.spells[t] = b.ReadBoolean();
                         }
                         a.exhaustion = b.ReadInt32();
                         a.time_of_last_action = b.ReadInt32();
                         a.recover_time = b.ReadInt32();
                         int path_count = b.ReadInt32();
                         for(int j=0;j<path_count;++j){
                             int path_row = b.ReadInt32();
                             int path_col = b.ReadInt32();
                             a.path.Add(new pos(path_row,path_col));
                         }
                         int location_ID = b.ReadInt32();
                         if(id.ContainsKey(location_ID)){
                             a.target_location = (Tile)id[location_ID];
                         }
                         else{
                             a.target_location = null;
                             need_location.Add(a);
                             missing_location_id[a] = location_ID;
                         }
                         a.player_visibility_duration = b.ReadInt32();
                         int num_weapons = b.ReadInt32();
                         for(int j=0;j<num_weapons;++j){
                             Weapon w = new Weapon(WeaponType.NO_WEAPON);
                             w.type = (WeaponType)b.ReadInt32();
                             w.enchantment = (EnchantmentType)b.ReadInt32();
                             int num_statuses = b.ReadInt32();
                             for(int k=0;k<num_statuses;++k){
                                 EquipmentStatus st = (EquipmentStatus)b.ReadInt32();
                                 bool has_st = b.ReadBoolean();
                                 w.status[st] = has_st;
                             }
                             a.weapons.AddLast(w);
                         }
                         int num_armors = b.ReadInt32();
                         for(int j=0;j<num_armors;++j){
                             Armor ar = new Armor(ArmorType.NO_ARMOR);
                             ar.type = (ArmorType)b.ReadInt32();
                             ar.enchantment = (EnchantmentType)b.ReadInt32();
                             int num_statuses = b.ReadInt32();
                             for(int k=0;k<num_statuses;++k){
                                 EquipmentStatus st = (EquipmentStatus)b.ReadInt32();
                                 bool has_st = b.ReadBoolean();
                                 ar.status[st] = has_st;
                             }
                             a.armors.AddLast(ar);
                         }
                         int num_magic_trinkets = b.ReadInt32();
                         for(int j=0;j<num_magic_trinkets;++j){
                             a.magic_trinkets.Add((MagicTrinketType)b.ReadInt32());
                         }
                     }
                     else{
                         Actor.tiebreakers.Add(null);
                     }
                 }
                 int num_groups = b.ReadInt32();
                 for(int i=0;i<num_groups;++i){
                     List<Actor> group = new List<Actor>();
                     int group_size = b.ReadInt32();
                     for(int j=0;j<group_size;++j){
                         group.Add((Actor)id[b.ReadInt32()]);
                     }
                     foreach(Actor a in group){
                         a.group = group;
                     }
                 }
                 int num_tiles = b.ReadInt32();
                 for(int i=0;i<num_tiles;++i){
                     Tile t = new Tile();
                     int ID = b.ReadInt32();
                     id.Add(ID,t);
                     t.row = b.ReadInt32();
                     t.col = b.ReadInt32();
                     game.M.tile[t.row,t.col] = t;
                     t.name = b.ReadString();
                     t.the_name = b.ReadString();
                     t.a_name = b.ReadString();
                     t.symbol = b.ReadChar();
                     t.color = (Color)b.ReadInt32();
                     t.light_radius = b.ReadInt32();
                     t.type = (TileType)b.ReadInt32();
                     t.passable = b.ReadBoolean();
                     t.SetInternalOpacity(b.ReadBoolean());
                     t.SetInternalSeen(b.ReadBoolean());
                     //t.seen = b.ReadBoolean();
                     t.revealed_by_light = b.ReadBoolean();
                     t.solid_rock = b.ReadBoolean();
                     t.light_value = b.ReadInt32();
                     t.direction_exited = b.ReadInt32();
                     if(b.ReadBoolean()){ //indicates a toggles_into value
                         t.toggles_into = (TileType)b.ReadInt32();
                     }
                     else{
                         t.toggles_into = null;
                     }
                     int item_id = b.ReadInt32();
                     if(item_id != 0){
                         t.inv = new Item();
                         id.Add(item_id,t.inv);
                         t.inv.name = b.ReadString();
                         t.inv.the_name = b.ReadString();
                         t.inv.a_name = b.ReadString();
                         t.inv.symbol = b.ReadChar();
                         t.inv.color = (Color)b.ReadInt32();
                         t.inv.light_radius = b.ReadInt32();
                         t.inv.type = (ConsumableType)b.ReadInt32();
                         t.inv.quantity = b.ReadInt32();
                         t.inv.charges = b.ReadInt32();
                         t.inv.other_data = b.ReadInt32();
                         t.inv.ignored = b.ReadBoolean();
                         t.inv.do_not_stack = b.ReadBoolean();
                         t.inv.revealed_by_light = b.ReadBoolean();
                     }
                     else{
                         t.inv = null;
                     }
                     int num_features = b.ReadInt32();
                     for(int j=0;j<num_features;++j){
                         t.features.Add((FeatureType)b.ReadInt32());
                     }
                 }
                 foreach(Actor a in need_targets){
                     if(id.ContainsKey(missing_target_id[a])){
                         a.target = (Actor)id[missing_target_id[a]];
                     }
                     else{
                         throw new Exception("Error: some actors weren't loaded(1). ");
                     }
                 }
                 foreach(Actor a in need_location){
                     if(id.ContainsKey(missing_location_id[a])){
                         a.target_location = (Tile)id[missing_location_id[a]];
                     }
                     else{
                         throw new Exception("Error: some tiles weren't loaded(2). ");
                     }
                 }
                 int game_turn = b.ReadInt32();
                 game.Q.turn = -1; //this keeps events from being added incorrectly to the front of the queue while loading. turn is set correctly after events are all loaded.
                 int num_events = b.ReadInt32();
                 for(int i=0;i<num_events;++i){
                     Event e = new Event();
                     if(b.ReadBoolean()){ //if true, this is an item that doesn't exist elsewhere, so grab all its info.
                         int item_id = b.ReadInt32();
                         if(item_id != 0){
                             Item item = new Item();
                             id.Add(item_id,item);
                             item.name = b.ReadString();
                             item.the_name = b.ReadString();
                             item.a_name = b.ReadString();
                             item.symbol = b.ReadChar();
                             item.color = (Color)b.ReadInt32();
                             item.light_radius = b.ReadInt32();
                             item.type = (ConsumableType)b.ReadInt32();
                             item.quantity = b.ReadInt32();
                             item.charges = b.ReadInt32();
                             item.other_data = b.ReadInt32();
                             item.ignored = b.ReadBoolean();
                             item.do_not_stack = b.ReadBoolean();
                             item.revealed_by_light = b.ReadBoolean();
                             e.target = item;
                         }
                     }
                     else{
                         int target_ID = b.ReadInt32();
                         if(id.ContainsKey(target_ID)){
                             e.target = id[target_ID];
                         }
                         else{
                             throw new Exception("Error: some tiles/actors weren't loaded(4). ");
                         }
                     }
                     int area_count = b.ReadInt32();
                     for(int j=0;j<area_count;++j){
                         if(e.area == null){
                             e.area = new List<Tile>();
                         }
                         int tile_ID = b.ReadInt32();
                         if(id.ContainsKey(tile_ID)){
                             e.area.Add((Tile)id[tile_ID]);
                         }
                         else{
                             throw new Exception("Error: some tiles weren't loaded(5). ");
                         }
                     }
                     e.delay = b.ReadInt32();
                     e.type = (EventType)b.ReadInt32();
                     e.attr = (AttrType)b.ReadInt32();
                     e.feature = (FeatureType)b.ReadInt32();
                     e.value = b.ReadInt32();
                     e.secondary_value = b.ReadInt32();
                     e.msg = b.ReadString();
                     int objs_count = b.ReadInt32();
                     for(int j=0;j<objs_count;++j){
                         if(e.msg_objs == null){
                             e.msg_objs = new List<PhysicalObject>();
                         }
                         int obj_ID = b.ReadInt32();
                         if(id.ContainsKey(obj_ID)){
                             e.msg_objs.Add(id[obj_ID]);
                         }
                         else{
                             throw new Exception("Error: some actors/tiles weren't loaded(6). ");
                         }
                     }
                     e.time_created = b.ReadInt32();
                     e.dead = b.ReadBoolean();
                     e.tiebreaker = b.ReadInt32();
                     game.Q.Add(e);
                     if(e.type == EventType.FIRE && !e.dead){
                         Fire.fire_event = e;
                     }
                 }
                 game.Q.turn = game_turn;
                 foreach(Event e in game.Q.list){
                     if(e.type == EventType.MOVE && e.target == game.player){
                         game.Q.current_event = e;
                         break;
                     }
                 }
                 int num_footsteps = b.ReadInt32();
                 for(int i=0;i<num_footsteps;++i){
                     int step_row = b.ReadInt32();
                     int step_col = b.ReadInt32();
                     Actor.footsteps.Add(new pos(step_row,step_col));
                 }
                 int num_prev_footsteps = b.ReadInt32();
                 for(int i=0;i<num_prev_footsteps;++i){
                     int step_row = b.ReadInt32();
                     int step_col = b.ReadInt32();
                     Actor.previous_footsteps.Add(new pos(step_row,step_col));
                 }
                 Actor.interrupted_path.row = b.ReadInt32();
                 Actor.interrupted_path.col = b.ReadInt32();
                 UI.viewing_commands_idx = b.ReadInt32();
                 game.M.feat_gained_this_level = b.ReadBoolean();
                 game.M.extra_danger = b.ReadInt32();
                 int num_unIDed = b.ReadInt32();
                 for(int i=0;i<num_unIDed;++i){
                     ConsumableType ct = (ConsumableType)b.ReadInt32();
                     string s = b.ReadString();
                     Item.unIDed_name[ct] = s;
                 }
                 int num_IDed = b.ReadInt32();
                 for(int i=0;i<num_IDed;++i){
                     ConsumableType ct = (ConsumableType)b.ReadInt32();
                     bool IDed = b.ReadBoolean();
                     Item.identified[ct] = IDed;
                 }
                 int num_item_colors = b.ReadInt32();
                 for(int i=0;i<num_item_colors;++i){
                     ConsumableType ct = (ConsumableType)b.ReadInt32();
                     Item.proto[ct].color = (Color)b.ReadInt32();
                 }
                 int num_burning = b.ReadInt32();
                 for(int i=0;i<num_burning;++i){
                     int obj_ID = b.ReadInt32();
                     if(id.ContainsKey(obj_ID)){
                         Fire.burning_objects.Add(id[obj_ID]);
                     }
                     else{
                         throw new Exception("Error: some actors/tiles weren't loaded(7). ");
                     }
                 }
                 string[] messages = new string[Buffer.log_length];
                 int num_messages = b.ReadInt32();
                 for(int i=0;i<num_messages;++i){
                     messages[i] = b.ReadString();
                 }
                 for(int i=num_messages;i<Buffer.log_length;++i){
                     messages[i] = "";
                 }
                 int message_pos = b.ReadInt32();
                 game.B.LoadMessagesAndPosition(messages,message_pos,num_messages);
                 b.Close();
                 file.Close();
                 File.Delete("forays.sav");
                 Tile.Feature(FeatureType.TELEPORTAL).color = Item.Prototype(ConsumableType.TELEPORTAL).color;
                 game.M.CalculatePoppyDistanceMap();
                 game.M.UpdateDangerValues();
                 if(game.M.aesthetics == null) game.M.aesthetics = new PosArray<AestheticFeature>(Global.ROWS,Global.COLS); //todo! save these properly
                 if(game.M.dungeon_description == null){
                     game.M.dungeon_description = new PosArray<string>(Global.ROWS,Global.COLS);
                     for(int ii=0;ii<Global.ROWS;++ii){
                         for(int jj=0;jj<Global.COLS;++jj){
                             game.M.dungeon_description[ii,jj] = "";
                         }
                     }
                 } //todo fixme hack save properly
             }
             Game.NoClose = true;
             MouseUI.PushButtonMap(MouseMode.Map);
             MouseUI.CreateStatsButtons();
             try{
                 while(!Global.GAME_OVER){ game.Q.Pop(); }
             }
             catch(Exception e){
                 StreamWriter fileout = new StreamWriter("error.txt",false);
                 fileout.WriteLine(e.Message);
                 fileout.WriteLine(e.StackTrace);
                 fileout.Close();
                 MouseUI.IgnoreMouseMovement = true;
                 MouseUI.IgnoreMouseClicks = true;
                 Screen.CursorVisible = false;
                 Screen.Blank();
                 Screen.WriteString(12,0,"  An error has occured. See error.txt for more details. Press any key to quit.".PadOuter(Global.SCREEN_W));
                 Input.ReadKey();
                 Global.Quit();
             }
             MouseUI.PopButtonMap();
             MouseUI.IgnoreMouseMovement = false;
             Game.NoClose = false;
             Screen.CursorVisible = false;
             Global.SaveOptions();
             recentdepth = game.M.current_level;
             recentname = Actor.player_name;
             recentwin = Global.BOSS_KILLED? 'W' : '-';
             recentcause = Global.KILLED_BY;
             on_highscore_list = false;
             if(!Global.SAVING){
                 List<string> newhighscores = new List<string>();
                 int num_scores = 0;
                 bool added = false;
                 if(File.Exists("highscore.txt")){
                     StreamReader file = new StreamReader("highscore.txt");
                     string s = "";
                     while(s.Length < 2 || s.Substring(0,2) != "--"){
                         s = file.ReadLine();
                         newhighscores.Add(s);
                     }
                     s = "!!";
                     while(s.Substring(0,2) != "--"){
                         s = file.ReadLine();
                         if(s.Substring(0,2) == "--"){
                             if(!added && num_scores < Global.HIGH_SCORES){
                                 char symbol = Global.BOSS_KILLED? 'W' : '-';
                                 newhighscores.Add(game.M.current_level.ToString() + " " + symbol + " " + Actor.player_name + " -- " + Global.KILLED_BY);
                                 on_highscore_list = true;
                             }
                             newhighscores.Add(s);
                             break;
                         }
                         if(num_scores < Global.HIGH_SCORES){
                             string[] tokens = s.Split(' ');
                             int dlev = Convert.ToInt32(tokens[0]);
                             if(dlev < game.M.current_level || (dlev == game.M.current_level && Global.BOSS_KILLED)){
                                 if(!added){
                                     char symbol = Global.BOSS_KILLED? 'W' : '-';
                                     newhighscores.Add(game.M.current_level.ToString() + " " + symbol + " " + Actor.player_name + " -- " + Global.KILLED_BY);
                                     ++num_scores;
                                     added = true;
                                     on_highscore_list = true;
                                 }
                                 if(num_scores < Global.HIGH_SCORES){
                                     newhighscores.Add(s);
                                     ++num_scores;
                                 }
                             }
                             else{
                                 newhighscores.Add(s);
                                 ++num_scores;
                             }
                         }
                     }
                     file.Close();
                 }
                 else{
                     newhighscores.Add("High scores:");
                     newhighscores.Add("--");
                     char symbol = Global.BOSS_KILLED? 'W' : '-';
                     newhighscores.Add(game.M.current_level.ToString() + " " + symbol + " " + Actor.player_name + " -- " + Global.KILLED_BY);
                     newhighscores.Add("--");
                     on_highscore_list = true;
                 }
                 StreamWriter fileout = new StreamWriter("highscore.txt",false);
                 foreach(string str in newhighscores){
                     fileout.WriteLine(str);
                 }
                 fileout.Close();
             }
             if(!Global.QUITTING && !Global.SAVING){
                 GameOverScreen(game);
             }
             break;
         }
         case 'b':
         {
             Help.DisplayHelp();
             break;
         }
         case 'c':
         {
             MouseUI.PushButtonMap();
             Screen.Blank();
             List<string> scores = new List<string>();
             {
                 if(!File.Exists("highscore.txt")){
                     List<string> newhighscores = new List<string>();
                     newhighscores.Add("High scores:");
                     newhighscores.Add("--");
                     newhighscores.Add("--");
                     StreamWriter fileout = new StreamWriter("highscore.txt",false);
                     foreach(string str in newhighscores){
                         fileout.WriteLine(str);
                     }
                     fileout.Close();
                 }
                 StreamReader file = new StreamReader("highscore.txt");
                 string s = "";
                 while(s.Length < 2 || s.Substring(0,2) != "--"){
                     s = file.ReadLine();
                 }
                 s = "!!";
                 while(s.Substring(0,2) != "--"){
                     s = file.ReadLine();
                     if(s.Substring(0,2) == "--" || scores.Count == Global.HIGH_SCORES){
                         break;
                     }
                     else{
                         scores.Add(s);
                     }
                 }
                 file.Close();
             }
             if(scores.Count == Global.HIGH_SCORES && !on_highscore_list && recentdepth != -1){
                 scores.RemoveLast();
                 scores.Add(recentdepth.ToString() + " " + recentwin + " " + recentname + " -- " + recentcause);
             }
             int longest_name = 0;
             int longest_cause = 0;
             foreach(string s in scores){
                 string[] tokens = s.Split(' ');
                 string name_and_cause_of_death = s.Substring(tokens[0].Length + 3);
                 int idx = name_and_cause_of_death.LastIndexOf(" -- ");
                 string name = name_and_cause_of_death.Substring(0,idx);
                 string cause_of_death = name_and_cause_of_death.Substring(idx+4);
                 if(name.Length > longest_name){
                     longest_name = name.Length;
                 }
                 if(cause_of_death.Length > longest_cause){
                     longest_cause = cause_of_death.Length;
                 }
             }
             int total_spaces = Global.SCREEN_W - (longest_name + 4 + longest_cause); //max name length is 26 and max cause length is 42. Depth is the '4'.
             int half_spaces = total_spaces / 2;
             int half_spaces_offset = (total_spaces+1) / 2;
             int spaces1 = half_spaces / 4;
             int spaces2 = half_spaces - (half_spaces / 4);
             int spaces3 = half_spaces_offset - (half_spaces_offset / 4);
             int name_middle = spaces1 + longest_name/2;
             int depth_middle = spaces1 + spaces2 + longest_name + 1;
             int cause_middle = spaces1 + spaces2 + spaces3 + longest_name + 4 + (longest_cause-1)/2;
             Color primary = Color.Green;
             Color recent = Color.Cyan;
             Screen.WriteString(0,(Global.SCREEN_W - 11) / 2,new cstr("HIGH SCORES",Color.Yellow)); //"HIGH SCORES" has width 11
             Screen.WriteString(1,(Global.SCREEN_W - 11) / 2,new cstr("-----------",Color.Cyan));
             Screen.WriteString(2,name_middle-4,new cstr("Character",primary));
             Screen.WriteString(2,depth_middle-2,new cstr("Depth",primary));
             Screen.WriteString(2,cause_middle-6,new cstr("Cause of death",primary));
             bool written_recent = false;
             int line = 3;
             foreach(string s in scores){
                 if(line >= Global.SCREEN_H){
                     break;
                 }
                 string[] tokens = s.Split(' ');
                 int dlev = Convert.ToInt32(tokens[0]);
                 char winning = tokens[1][0];
                 string name_and_cause_of_death = s.Substring(tokens[0].Length + 3);
                 int idx = name_and_cause_of_death.LastIndexOf(" -- ");
                 string name = name_and_cause_of_death.Substring(0,idx);
                 string cause_of_death = name_and_cause_of_death.Substring(idx+4);
                 string cause_capitalized = cause_of_death.Substring(0,1).ToUpper() + cause_of_death.Substring(1);
                 Color current_color = Color.White;
                 if(!written_recent && name == recentname && dlev == recentdepth && winning == recentwin && cause_of_death == recentcause){
                     current_color = recent;
                     written_recent = true;
                 }
                 else{
                     current_color = Color.White;
                 }
                 Screen.WriteString(line,spaces1,new cstr(name,current_color));
                 Screen.WriteString(line,spaces1 + spaces2 + longest_name,new cstr(dlev.ToString().PadLeft(2),current_color));
                 Screen.WriteString(line,spaces1 + spaces2 + spaces3 + longest_name + 4,new cstr(cause_capitalized,current_color));
                 if(winning == 'W'){
                     Screen.WriteString(line,spaces1 + spaces2 + longest_name + 3,new cstr("W",Color.Yellow));
                 }
                 ++line;
             }
             Input.ReadKey();
             MouseUI.PopButtonMap();
             break;
         }
         case 'd':
             Global.Quit();
             break;
         default:
             break;
         }
         if(Global.QUITTING){
             Global.Quit();
         }
     }
 }
예제 #26
0
 public void ResetForNewLevel()
 {
     target = null;
     target_location = null;
     if (HasAttr(AttrType.DIM_LIGHT))
     {
         attrs[AttrType.DIM_LIGHT] = 0;
         if (light_radius > 0)
         {
             if (HasAttr(AttrType.ENHANCED_TORCH))
             {
                 light_radius = 12;
             }
             else
             {
                 light_radius = 6;
             }
         }
     }
     if (attrs[AttrType.RESTING] == -1)
     {
         attrs[AttrType.RESTING] = 0;
     }
     if (HasAttr(AttrType.GRABBED))
     {
         attrs[AttrType.GRABBED] = 0;
     }
     ResetSpells();
     Q.KillEvents(null, EventType.CHECK_FOR_HIDDEN);
 }
예제 #27
0
 private static void Define(TileType type_,string name_,char symbol_,Color color_,bool passable_,bool opaque_,TileType? toggles_into_)
 {
     proto[type_] = new Tile(type_,name_,symbol_,color_,passable_,opaque_,toggles_into_);
 }
예제 #28
0
 public Actor(Actor a, int r, int c)
 {
     atype = a.atype;
     name = a.name;
     the_name = a.the_name;
     a_name = a.a_name;
     symbol = a.symbol;
     color = a.color;
     maxhp = a.maxhp;
     curhp = maxhp;
     speed = a.speed;
     level = a.level;
     light_radius = a.light_radius;
     target = null;
     F = new SpellType[13];
     for (int i = 0; i < 13; ++i)
     {
         F[i] = SpellType.NO_SPELL;
     }
     inv = new List<Item>();
     row = r;
     col = c;
     target_location = null;
     time_of_last_action = 0;
     recover_time = 0;
     player_visibility_duration = 0;
     weapons = new List<WeaponType>(a.weapons);
     armors = new List<ArmorType>(a.armors);
     magic_items = new List<MagicItemType>(a.magic_items);
     attrs = new Dict<AttrType, int>(a.attrs);
     skills = new Dict<SkillType, int>(a.skills);
     feats = new Dict<FeatType, int>(a.feats);
     spells = new Dict<SpellType, int>(a.spells);
     magic_penalty = 0;
 }
예제 #29
0
 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;
 }
예제 #30
0
 public void SeekAI()
 {
     if(type == ActorType.MACHINE_OF_WAR && attrs[AttrType.COOLDOWN_1] % 2 == 1){
         Q1();
         return;
     }
     if(type == ActorType.ROBED_ZEALOT && HasAttr(AttrType.COOLDOWN_3)){ //todo: move most of these into the switch statement. use goto default.
         attrs[AttrType.COOLDOWN_3]--;
         Q1();
         return;
     }
     if(type == ActorType.SKULKING_KILLER && HasAttr(AttrType.KEEPS_DISTANCE) && !R.OneIn(5) && (!player.HasLOE(this) || !player.CanSee(this) || DistanceFrom(player) > 12)){
         attrs[AttrType.COOLDOWN_2]++;
         if(attrs[AttrType.COOLDOWN_2] >= 3){
             attrs[AttrType.KEEPS_DISTANCE] = 0;
             attrs[AttrType.COOLDOWN_2] = 0;
             if(!player.CanSee(this)){
                 attrs[AttrType.TURNS_VISIBLE] = 0;
             }
         }
         Q1();
         return;
     }
     if(type == ActorType.STALKING_WEBSTRIDER && tile().Is(FeatureType.WEB)){
         List<pos> webs = M.tile.GetFloodFillPositions(p,false,x=>M.tile[x].Is(FeatureType.WEB));
         if(webs.Contains(target.p)){
             FindPath(target);
             if(PathStep()){
                 return;
             }
             else{
                 path.Clear();
             }
         }
     }
     if(type == ActorType.SWORDSMAN || type == ActorType.PHANTOM_SWORDMASTER || type == ActorType.ALASI_SOLDIER){
         attrs[AttrType.COMBO_ATTACK] = 0;
     }
     if(PathStep()){ //todo: consider the placement of this call. does this all happen in the correct order?
         return;
     }
     switch(type){
     case ActorType.KOBOLD:
     {
         if(HasAttr(AttrType.COOLDOWN_1)){ //if the kobold needs to reload...
             if(target != null && DistanceFrom(target) <= 2){ //would be better as pathing distance, but oh well
                 AI_Flee();
                 QS();
                 return;
             }
             else{
                 B.Add(the_name + " starts reloading. ",this);
                 attrs[AttrType.COOLDOWN_1] = 0;
                 Q1();
                 RefreshDuration(AttrType.COOLDOWN_2,R.Between(5,6)*100 - 50);
                 return;
             }
         }
         else{
             goto default;
         }
     }
     case ActorType.PHASE_SPIDER:
     case ActorType.IMPOSSIBLE_NIGHTMARE:
         if(DistanceFrom(target_location) <= 12){
             Tile t = target_location.TilesAtDistance(DistanceFrom(target_location)-1).Where(x=>x.passable && x.actor() == null).RandomOrDefault();
             if(t != null){
                 Move(t.row,t.col);
             }
         }
         QS();
         break;
     case ActorType.ORC_WARMAGE: //warmages not following the player has worked pretty well so far. Maybe they could get a chance to go back to wandering?
         QS();
         break;
     case ActorType.CARNIVOROUS_BRAMBLE:
     case ActorType.MUD_TENTACLE:
     case ActorType.LASHER_FUNGUS:
     case ActorType.MARBLE_HORROR_STATUE:
         QS();
         break;
     case ActorType.CYCLOPEAN_TITAN:
     {
         bool smashed = false;
         if(target_location != null && target != null && DistanceFrom(target_location) == 1 && 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){
             goto default;
         }
         break;
     }
     case ActorType.FIRE_DRAKE:
         FindPath(player);
         QS();
         break;
     default:
     {
         if(target_location != null){
             if(DistanceFrom(target_location) == 1 && M.actor[target_location.p] != null){
                 if(MovementPrevented(target_location) || M.actor[target_location.p].MovementPrevented(tile()) || !AI_WillingToMove(tile(),target_location,target)){
                     QS();
                 }
                 else{
                     Move(target_location.row,target_location.col); //swap places
                     target_location = null;
                     attrs[AttrType.FOLLOW_DIRECTION_EXITED]++;
                     QS();
                 }
             }
             else{
                 int dist = DistanceFrom(target_location);
                 if(!HasLOE(target_location)){
                     List<pos> path2 = GetPath(target_location,dist+1);
                     if(path2.Count > 0){
                         path = path2;
                         if(PathStep()){
                             return;
                         }
                     }
                 }
                 if(AI_Step(target_location)){
                     QS();
                     if(DistanceFrom(target_location) == 0){
                         target_location = null;
                         attrs[AttrType.FOLLOW_DIRECTION_EXITED]++;
                     }
                     else{
                         if(DistanceFrom(target_location) == dist && !HasLOE(target_location)){ //if you didn't get any closer and you can't see it...
                             target_location = null;
                         }
                     }
                 }
                 else{ //could not move, end turn.
                     if((DistanceFrom(target_location) == 1 && !target_location.passable) || DistanceFrom(target_location) == 0){
                         target_location = null;
                     }
                     QS();
                 }
             }
             if(target_location == null){
                 if(!NeverWanders()){
                     if(group == null || group.Count < 2 || group[0] == this){
                         attrs[AttrType.WANDERING] = 1;
                     }
                 }
             }
         }
         else{
             if(DistanceFrom(target) <= 2 && !HasLOS(target) && !HasLOE(target)){ //this part is just for pillar dancing
                 List<pos> path2 = GetPath(target,2);
                 if(path2.Count > 0){
                     path = path2;
                     player_visibility_duration = -1; //stay at -1 while in close pursuit
                 }
                 if(PathStep()){
                     path.Clear(); //testing this; seems to be working.
                     return;
                 }
                 QS();
             }
             else{
                 if(HasAttr(AttrType.FOLLOW_DIRECTION_EXITED) && tile().direction_exited > 0){
                     AI_Step(TileInDirection(tile().direction_exited));
                     attrs[AttrType.FOLLOW_DIRECTION_EXITED] = 0;
                 }
                 else{
                     bool corridor = HasAttr(AttrType.DIRECTION_OF_PREVIOUS_TILE); //if it's 0 or -1, ignore it
                     foreach(int dir in U.FourDirections){
                         if(TileInDirection(dir).passable && TileInDirection(dir.RotateDir(true,1)).passable && TileInDirection(dir.RotateDir(true,2)).passable){
                             corridor = false;
                             break;
                         }
                     }
                     if(corridor){
                         List<int> blocked = new List<int>();
                         for(int i=-1;i<=1;++i){
                             blocked.Add(attrs[AttrType.DIRECTION_OF_PREVIOUS_TILE].RotateDir(true,i));
                         }
                         List<Tile> tiles = TilesAtDistance(1).Where(x=>x.passable && x.actor() == null && !blocked.Contains(DirectionOf(x)));
                         if(tiles.Count > 0){
                             bool multiple_paths = false;
                             foreach(Tile t1 in tiles){
                                 foreach(Tile t2 in tiles){
                                     if(t1 != t2 && t1.ApproximateEuclideanDistanceFromX10(t2) > 10){ //cardinally adjacent only
                                         multiple_paths = true;
                                         break;
                                     }
                                 }
                                 if(multiple_paths){
                                     if(player_visibility_duration < 0){
                                         player_visibility_duration -= 2; //at each fork in the road, the monster gets 3 steps closer to forgetting the player - just because 1 takes too long when the corridors are loopy.
                                     }
                                     break;
                                 }
                             }
                             if(!multiple_paths && player_visibility_duration < -1){ //this part could use some documentation.
                                 ++player_visibility_duration;
                             }
                             AI_Step(tiles.Random()); //this whole section deals with monsters following corridors instead of giving up on the chase.
                         }
                     }
                     else{
                         if(group != null && group[0] != this){ //groups try to get back together
                             if(DistanceFrom(group[0]) > 1){
                                 int dir = DirectionOf(group[0]);
                                 bool found = false;
                                 for(int i=-1;i<=1;++i){
                                     Actor a = ActorInDirection(dir.RotateDir(true,i));
                                     if(a != null && group.Contains(a)){
                                         found = true;
                                         break;
                                     }
                                 }
                                 if(!found){
                                     if(HasLOS(group[0])){
                                         AI_Step(group[0]);
                                     }
                                     else{
                                         FindPath(group[0],8);
                                         if(PathStep()){
                                             return;
                                         }
                                     }
                                 }
                             }
                         }
                     }
                 }
                 QS();
             }
         }
         break;
     }
     }
 }