예제 #1
0
        public AttackInfo(int cost_, int dice_, DamageType type_, DamageClass damclass_, string desc_)
        {
            cost = cost_;
            damage = new Damage(dice_, type_, damclass_, null);
            /*			damage.dice=dice_;
			damage.type=type_;
			damage.damclass=damclass_;*/
            desc = desc_;
        }
예제 #2
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;
 }
예제 #3
0
 public AttackInfo(AttackInfo a)
 {
     cost = a.cost;
     damage = a.damage;
     desc = a.desc;
 }
예제 #4
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;
 }
예제 #5
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;
 }