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_; }
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; }
public AttackInfo(AttackInfo a) { cost = a.cost; damage = a.damage; desc = a.desc; }
public bool Attack(int attack_idx,Actor a,bool attack_is_part_of_another_action) { //returns true if attack hit AttackInfo info = attack[type][attack_idx]; pos original_pos = p; pos target_original_pos = a.p; if(EquippedWeapon.type != WeaponType.NO_WEAPON){ info = EquippedWeapon.Attack(); } info.damage.source = this; if(a.HasFeat(FeatType.DEFLECT_ATTACK) && DistanceFrom(a) == 1){ //Actor other = a.ActorsWithinDistance(1).Where(x=>x.DistanceFrom(this) == 1).Random(); Actor other = a.ActorsWithinDistance(1).Where(x=>x != this).RandomOrDefault(); if(other != a){ B.Add(a.You("deflect") + "! ",this,a); return Attack(attack_idx,other,attack_is_part_of_another_action); } } if(!attack_is_part_of_another_action && StunnedThisTurn()){ return false; } if(!attack_is_part_of_another_action && exhaustion == 100 && R.CoinFlip()){ B.Add(You("fumble") + " from exhaustion. ",this); Q1(); //this is checked in PlayerWalk if attack_is_part_of_another_action is true return false; } if(!attack_is_part_of_another_action && this == player && EquippedWeapon.status[EquipmentStatus.POSSESSED] && R.CoinFlip()){ List<Actor> actors = ActorsWithinDistance(1); Actor chosen = actors.RandomOrDefault(); if(chosen != a){ if(chosen == this){ B.Add("Your possessed " + EquippedWeapon.NameWithEnchantment() + " tries to attack you! "); B.Add("You fight it off! "); //this is also checked in PlayerWalk if attack_is_part_of_another_action is true Q1(); return false; } else{ return Attack(attack_idx,chosen); } } } bool player_in_combat = false; if(this == player || a == player){ player_in_combat = true; } /*if(a == player && (type == ActorType.DREAM_WARRIOR_CLONE || type == ActorType.DREAM_SPRITE_CLONE)){ player_in_combat = false; }*/ if(player_in_combat){ player.attrs[AttrType.IN_COMBAT]++; } if(a.HasAttr(AttrType.CAN_DODGE) && a.CanSee(this)){ int dodge_dir = R.Roll(9); Tile dodge_tile = a.TileInDirection(dodge_dir); bool failed_to_dodge = false; if(HasAttr(AttrType.CONFUSED,AttrType.SLOWED,AttrType.STUNNED) && R.CoinFlip()){ failed_to_dodge = true; } if(a.tile().Is(FeatureType.WEB) && !a.HasAttr(AttrType.BURNING,AttrType.OIL_COVERED,AttrType.SLIMED,AttrType.BRUTISH_STRENGTH)){ failed_to_dodge = true; } if(!failed_to_dodge && dodge_tile.passable && dodge_tile.actor() == null && !a.MovementPrevented(dodge_tile) && !a.HasAttr(AttrType.PARALYZED)){ B.Add(a.You("dodge") + " " + YourVisible() + " attack. ",this,a); if(player.CanSee(a)){ Help.TutorialTip(TutorialTopic.Dodging); } if(a == player){ B.DisplayNow(); Screen.AnimateMapCell(a.row,a.col,new colorchar('!',Color.Green),80); } a.Move(dodge_tile.row,dodge_tile.col); if(a != player && DistanceFrom(dodge_tile) > 1){ M.Draw(); Thread.Sleep(40); } if(!attack_is_part_of_another_action){ Q.Add(new Event(this,info.cost)); } return false; } } if(a.HasFeat(FeatType.CUNNING_DODGE) && !this.HasAttr(AttrType.DODGED)){ attrs[AttrType.DODGED]++; B.Add(a.You("dodge") + " " + YourVisible() + " attack. ",this,a); if(!attack_is_part_of_another_action){ Q.Add(new Event(this,info.cost)); } return false; } if(IsInvisibleHere() || a.IsInvisibleHere()){ Help.TutorialTip(TutorialTopic.FightingTheUnseen); } //pos pos_of_target = new pos(a.row,a.col); bool a_moved_last_turn = !a.HasAttr(AttrType.TURNS_HERE); bool drive_back_applied = HasFeat(FeatType.DRIVE_BACK); if(drive_back_applied && !ConfirmsSafetyPrompts(a.tile())){ drive_back_applied = false; } bool drive_back_nowhere_to_run = false; if(!attack_is_part_of_another_action && drive_back_applied){ //doesn't work while moving drive_back_nowhere_to_run = true; int dir = DirectionOf(a); foreach(int next_dir in new List<int>{dir,dir.RotateDir(true),dir.RotateDir(false)}){ Tile t = a.TileInDirection(next_dir); if(t.passable && t.actor() == null && !a.MovementPrevented(t)){ drive_back_nowhere_to_run = false; break; } } /*if(a.TileInDirection(dir).passable && a.ActorInDirection(dir) == null && !a.GrabPreventsMovement(TileInDirection(dir))){ drive_back_nowhere_to_run = false; } if(a.TileInDirection(dir.RotateDir(true)).passable && a.ActorInDirection(dir.RotateDir(true)) == null && !a.GrabPreventsMovement(TileInDirection(dir.RotateDir(true)))){ drive_back_nowhere_to_run = false; } if(a.TileInDirection(dir.RotateDir(false)).passable && a.ActorInDirection(dir.RotateDir(false)) == null && !a.GrabPreventsMovement(TileInDirection(dir.RotateDir(false)))){ drive_back_nowhere_to_run = false; }*/ if(a.tile().IsSlippery() && !(a.tile().Is(TileType.ICE) && a.type == ActorType.FROSTLING)){ if(R.OneIn(5) && !HasAttr(AttrType.FLYING,AttrType.NONEUCLIDEAN_MOVEMENT) && !Is(ActorType.GIANT_SLUG,ActorType.MACHINE_OF_WAR,ActorType.MUD_ELEMENTAL)){ drive_back_nowhere_to_run = true; } } if(a.HasAttr(AttrType.FROZEN) || a.HasAttr(AttrType.IMMOBILE)){ drive_back_nowhere_to_run = true; //todo: exception for noneuclidean monsters? i think they'll just move out of the way. } } bool obscured_vision_miss = false; { bool fog = false; bool hidden = false; if((this.tile().Is(FeatureType.FOG,FeatureType.THICK_DUST) || a.tile().Is(FeatureType.FOG,FeatureType.THICK_DUST))){ fog = true; } if(a.IsHiddenFrom(this) || !CanSee(a) || (a.IsInvisibleHere() && !HasAttr(AttrType.BLINDSIGHT))){ hidden = true; } if(!HasAttr(AttrType.DETECTING_MONSTERS) && (fog || hidden) && R.CoinFlip()){ obscured_vision_miss = true; } } int plus_to_hit = TotalSkill(SkillType.COMBAT); bool sneak_attack = false; if(this.IsHiddenFrom(a) || !a.CanSee(this) || (this == player && IsInvisibleHere() && !a.HasAttr(AttrType.BLINDSIGHT))){ sneak_attack = true; a.attrs[AttrType.SEES_ADJACENT_PLAYER] = 1; if(DistanceFrom(a) > 2 && this != player){ sneak_attack = false; //no phantom blade sneak attacks from outside your view - but the player can sneak attack at this range with a wand of reach. } } //...insert any other changes to sneak attack calculation here... if(sneak_attack || HasAttr(AttrType.LUNGING_AUTO_HIT) || (EquippedWeapon == Dagger && !tile().IsLit()) || (EquippedWeapon == Staff && a_moved_last_turn) || a.HasAttr(AttrType.SWITCHING_ARMOR)){ //some attacks get +25% accuracy. this usually totals 100% vs. unarmored targets. plus_to_hit += 25; } plus_to_hit -= a.TotalSkill(SkillType.DEFENSE) * 3; bool attack_roll_hit = a.IsHit(plus_to_hit); bool blocked_by_armor_miss = false; bool blocked_by_root_shell_miss = false; bool mace_through_armor = false; if(!attack_roll_hit){ int armor_value = a.TotalProtectionFromArmor(); if(a != player){ armor_value = a.TotalSkill(SkillType.DEFENSE); //if monsters have Defense skill, it's from armor } int roll = R.Roll(25 - plus_to_hit); if(roll <= armor_value * 3){ bool mace = (EquippedWeapon == Mace || type == ActorType.CRUSADING_KNIGHT || type == ActorType.PHANTOM_CRUSADER); if(mace){ attack_roll_hit = true; mace_through_armor = true; } else{ if(type == ActorType.CORROSIVE_OOZE || type == ActorType.LASHER_FUNGUS){ //this is a bit hacky, but these are the only ones that aren't stopped by armor right now. attack_roll_hit = true; } else{ blocked_by_armor_miss = true; } } } else{ if(a.HasAttr(AttrType.ROOTS) && roll <= (armor_value + 10) * 3){ //potion of roots gives 10 defense blocked_by_root_shell_miss = true; } } } bool hit = true; if(obscured_vision_miss){ //this calculation turned out to be pretty complicated hit = false; } else{ if(blocked_by_armor_miss || blocked_by_root_shell_miss){ hit = false; } else{ if(drive_back_nowhere_to_run || attack_roll_hit){ hit = true; } else{ hit = false; } } } if(a.HasAttr(AttrType.GRABBED) && attrs[AttrType.GRABBING] == DirectionOf(a)){ hit = true; //one more modifier: automatically hit things you're grabbing. } bool weapon_just_poisoned = false; if(!hit){ if(blocked_by_armor_miss){ bool initial_message_printed = false; //for better pronoun usage if(info.blocked != ""){ initial_message_printed = true; string s = info.blocked + ". "; int pos = -1; do{ pos = s.IndexOf('&'); if(pos != -1){ s = s.Substring(0,pos) + TheName(true) + s.Substring(pos+1); } } while(pos != -1); // do{ pos = s.IndexOf('*'); if(pos != -1){ s = s.Substring(0,pos) + a.TheName(true) + s.Substring(pos+1); } } while(pos != -1); B.Add(s,this,a); } if(a.HasFeat(FeatType.ARMOR_MASTERY) && !(a.type == ActorType.ALASI_SCOUT && attack_idx == 1)){ B.Add(a.YourVisible() + " armor blocks the attack, leaving " + TheName(true) + " off-balance. ",a,this); RefreshDuration(AttrType.SUSCEPTIBLE_TO_CRITS,100); } else{ if(initial_message_printed){ B.Add(a.YourVisible() + " armor blocks the attack. ",this,a); } else{ B.Add(a.YourVisible() + " armor blocks " + YourVisible() + " attack. ",this,a); } } if(a.EquippedArmor.type == ArmorType.FULL_PLATE && !HasAttr(AttrType.BRUTISH_STRENGTH)){ a.IncreaseExhaustion(3); Help.TutorialTip(TutorialTopic.HeavyPlateArmor); } } else{ if(blocked_by_root_shell_miss){ B.Add(a.YourVisible() + " root shell blocks " + YourVisible() + " attack. ",this,a); } else{ if(obscured_vision_miss){ B.Add(Your() + " attack goes wide. ",this); } else{ if(!attack_is_part_of_another_action && drive_back_applied && !MovementPrevented(M.tile[target_original_pos])){ B.Add(You("drive") + " " + a.TheName(true) + " back. ",this,a); /*if(!a.HasAttr(AttrType.FROZEN) && !HasAttr(AttrType.FROZEN)){ a.AI_Step(this,true); AI_Step(a); }*/ Tile dest = null; int dir = DirectionOf(target_original_pos); foreach(int next_dir in new List<int>{dir,dir.RotateDir(true),dir.RotateDir(false)}){ Tile t = a.TileInDirection(next_dir); if(t.passable && t.actor() == null && !a.MovementPrevented(t)){ dest = t; break; } } if(dest != null){ a.AI_MoveOrOpen(dest.row,dest.col); if(M.actor[target_original_pos] == null){ AI_MoveOrOpen(target_original_pos.row,target_original_pos.col); } } } else{ if(info.miss != ""){ string s = info.miss + ". "; int pos = -1; do{ pos = s.IndexOf('&'); if(pos != -1){ s = s.Substring(0,pos) + TheName(true) + s.Substring(pos+1); } } while(pos != -1); // do{ pos = s.IndexOf('*'); if(pos != -1){ s = s.Substring(0,pos) + a.TheName(true) + s.Substring(pos+1); } } while(pos != -1); B.Add(s,this,a); } else{ B.Add(YouVisible("miss",true) + " " + a.TheName(true) + ". ",this,a); } } } } } if(type == ActorType.SWORDSMAN || type == ActorType.PHANTOM_SWORDMASTER || type == ActorType.ALASI_SOLDIER){ attrs[AttrType.COMBO_ATTACK] = 0; } } else{ string s = info.hit + ". "; if(!attack_is_part_of_another_action && HasFeat(FeatType.NECK_SNAP) && a.HasAttr(AttrType.MEDIUM_HUMANOID) && (IsHiddenFrom(a) || a.IsHelpless())){ if(!a.HasAttr(AttrType.RESIST_NECK_SNAP)){ B.Add(You("silently snap") + " " + a.Your() + " neck. "); a.Kill(); Q1(); return true; } else{ B.Add(You("silently snap") + " " + a.Your() + " neck. "); B.Add("It doesn't seem to affect " + a.the_name + ". "); } } bool crit = false; int crit_chance = 8; //base crit rate is 1/8 if(EquippedWeapon.type == WeaponType.DAGGER && !tile().IsLit()){ crit_chance /= 2; } if(a.EquippedArmor != null && (a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] || a.EquippedArmor.status[EquipmentStatus.DAMAGED] || a.HasAttr(AttrType.SWITCHING_ARMOR))){ crit_chance /= 2; } if(a.HasAttr(AttrType.SUSCEPTIBLE_TO_CRITS)){ //caused by armor mastery crit_chance /= 2; } if(EquippedWeapon.enchantment == EnchantmentType.PRECISION && !EquippedWeapon.status[EquipmentStatus.NEGATED]){ crit_chance /= 2; } if(drive_back_nowhere_to_run){ crit_chance /= 2; } if(crit_chance <= 1 || R.OneIn(crit_chance)){ crit = true; } int pos = -1; do{ pos = s.IndexOf('&'); if(pos != -1){ s = s.Substring(0,pos) + TheName(true) + s.Substring(pos+1); } } while(pos != -1); // do{ pos = s.IndexOf('*'); if(pos != -1){ s = s.Substring(0,pos) + a.TheName(true) + s.Substring(pos+1); } } while(pos != -1); int dice = info.damage.dice; if(sneak_attack && crit && this == player){ if(!a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE,AttrType.BOSS_MONSTER) && a.type != ActorType.CYCLOPEAN_TITAN){ switch(EquippedWeapon.type){ //todo: should this check for shielded/blocking? case WeaponType.SWORD: B.Add("You run " + a.TheName(true) + " through! "); break; case WeaponType.MACE: B.Add("You bash " + a.YourVisible() + " head in! "); break; case WeaponType.DAGGER: B.Add("You pierce one of " + a.YourVisible() + " vital organs! "); break; case WeaponType.STAFF: B.Add("You bring your staff down on " + a.YourVisible() + " head with a loud crack! "); break; case WeaponType.BOW: B.Add("You choke " + a.TheName(true) + " with your bowstring! "); break; default: break; } Help.TutorialTip(TutorialTopic.InstantKills); MakeNoise(6); if(a.type == ActorType.BERSERKER && a.target == this){ a.attrs[AttrType.SHIELDED] = 0; a.TakeDamage(DamageType.NORMAL,DamageClass.NO_TYPE,a.curhp,this); } else{ a.Kill(); } if(!attack_is_part_of_another_action){ Q1(); } return true; } } if(sneak_attack && (this == player || a == player)){ B.Add(YouVisible("strike") + " from hiding! "); if(type != ActorType.PLAYER){ if(a == player && attrs[AttrType.TURNS_VISIBLE] >= 0){ B.PrintAll(); } attrs[AttrType.TURNS_VISIBLE] = -1; attrs[AttrType.NOTICED] = 1; attrs[AttrType.DANGER_SENSED] = 1; } else{ a.player_visibility_duration = -1; a.attrs[AttrType.PLAYER_NOTICED] = 1; } } if(a == player){ if(a.HasAttr(AttrType.SWITCHING_ARMOR)){ B.Add("You're unguarded! "); } else{ if(a.EquippedArmor.status[EquipmentStatus.DAMAGED]){ B.Add("Your damaged armor leaves you open! "); } else{ if(crit && R.CoinFlip()){ if(a.EquippedArmor.status[EquipmentStatus.WEAK_POINT]){ B.Add(TheName(true) + " finds a weak point. "); } } } } } if(mace_through_armor){ if(type == ActorType.CRUSADING_KNIGHT || type == ActorType.PHANTOM_CRUSADER){ B.Add(YourVisible() + " huge mace punches through " + a.YourVisible() + " armor. ",this,a); } else{ B.Add(YourVisible() + " mace punches through " + a.YourVisible() + " armor. ",this,a); } } else{ B.Add(s,this,a); } if(crit && info.crit != AttackEffect.NO_CRIT){ if(this == player || a == player){ Help.TutorialTip(TutorialTopic.CriticalHits); } } if(a == player && !player.CanSee(this)){ Screen.AnimateMapCell(row,col,new colorchar('?',Color.DarkGray),50); } if(a.type == ActorType.GHOST && EquippedWeapon.enchantment != EnchantmentType.NO_ENCHANTMENT && !EquippedWeapon.status[EquipmentStatus.NEGATED]){ EquippedWeapon.status[EquipmentStatus.NEGATED] = true; B.Add(Your() + " " + EquippedWeapon.NameWithEnchantment() + "'s magic is suppressed! ",this); Help.TutorialTip(TutorialTopic.Negated); } if(!Help.displayed[TutorialTopic.SwitchingEquipment] && this == player && a.Is(ActorType.SPORE_POD,ActorType.SKELETON,ActorType.STONE_GOLEM,ActorType.MECHANICAL_KNIGHT,ActorType.MACHINE_OF_WAR) && EquippedWeapon.type == WeaponType.SWORD){ Help.TutorialTip(TutorialTopic.SwitchingEquipment); } int dmg = R.Roll(dice,6); bool no_max_damage_message = false; List<AttackEffect> effects = new List<AttackEffect>(); if(crit && info.crit != AttackEffect.NO_CRIT){ effects.AddUnique(info.crit); } if(info.effects != null){ foreach(AttackEffect effect in info.effects){ effects.AddUnique(effect); } } if(type == ActorType.DEMON_LORD && DistanceFrom(a) == 2){ effects.AddUnique(AttackEffect.PULL); } if(type == ActorType.SWORDSMAN && attrs[AttrType.COMBO_ATTACK] == 2){ effects.AddUnique(AttackEffect.BLEED); effects.AddUnique(AttackEffect.STRONG_KNOCKBACK); } if(type == ActorType.PHANTOM_SWORDMASTER && attrs[AttrType.COMBO_ATTACK] == 2){ effects.AddUnique(AttackEffect.PERCENT_DAMAGE); effects.AddUnique(AttackEffect.STRONG_KNOCKBACK); } if(type == ActorType.ALASI_SOLDIER){ if(attrs[AttrType.COMBO_ATTACK] == 1){ effects.AddUnique(AttackEffect.ONE_TURN_STUN); } else{ if(attrs[AttrType.COMBO_ATTACK] == 2){ effects.AddUnique(AttackEffect.ONE_TURN_PARALYZE); } } } if(type == ActorType.WILD_BOAR && HasAttr(AttrType.COOLDOWN_1)){ effects.AddUnique(AttackEffect.FLING); } if(type == ActorType.ALASI_SENTINEL && R.OneIn(3)){ effects.AddUnique(AttackEffect.FLING); } if(this == player && a.type == ActorType.CYCLOPEAN_TITAN && crit){ effects = new List<AttackEffect>(); //remove all other effects (so far) and check for edged weapons if(EquippedWeapon == Sword || EquippedWeapon == Dagger){ effects.Add(AttackEffect.PERMANENT_BLIND); } } if(EquippedWeapon.status[EquipmentStatus.POISONED]){ effects.AddUnique(AttackEffect.POISON); } if(HasAttr(AttrType.PSEUDO_VAMPIRIC)){ effects.AddUnique(AttackEffect.DRAIN_LIFE); } if(HasAttr(AttrType.BRUTISH_STRENGTH)){ effects.AddUnique(AttackEffect.MAX_DAMAGE); effects.AddUnique(AttackEffect.STRONG_KNOCKBACK); effects.Remove(AttackEffect.KNOCKBACK); //strong knockback replaces these effects.Remove(AttackEffect.TRIP); effects.Remove(AttackEffect.FLING); } if(EquippedWeapon != null && !EquippedWeapon.status[EquipmentStatus.NEGATED]){ switch(EquippedWeapon.enchantment){ case EnchantmentType.CHILLING: effects.AddUnique(AttackEffect.CHILL); break; case EnchantmentType.DISRUPTION: effects.AddUnique(AttackEffect.DISRUPTION); //not entirely sure that these should be crit effects break; case EnchantmentType.VICTORY: if(a.maxhp > 1){ // no illusions, phantoms, or minions effects.AddUnique(AttackEffect.VICTORY); } break; } } if(type == ActorType.SKITTERMOSS && HasAttr(AttrType.COOLDOWN_1)){ effects.Remove(AttackEffect.INFEST); } if(a.HasAttr(AttrType.NONLIVING)){ effects.Remove(AttackEffect.DRAIN_LIFE); } foreach(AttackEffect effect in effects){ //pre-damage effects - these can alter the amount of damage. switch(effect){ case AttackEffect.MAX_DAMAGE: dmg = Math.Max(dmg,dice * 6); break; case AttackEffect.PERCENT_DAMAGE: dmg = Math.Max(dmg,(a.maxhp+1)/2); no_max_damage_message = true; if(!EquippedWeapon.status[EquipmentStatus.DULLED]){ if(this == player){ B.Add("Your sword cuts deep! "); } else{ B.Add(Your() + " attack cuts deep! ",this); } } break; case AttackEffect.ONE_HP: dmg = a.curhp - 1; if(a.HasAttr(AttrType.VULNERABLE)){ a.attrs[AttrType.VULNERABLE] = 0; } if(a == player){ B.Add("You shudder. "); } no_max_damage_message = true; break; } } if(dice < 2){ no_max_damage_message = true; } if(a.type == ActorType.SPORE_POD && EquippedWeapon.IsBlunt()){ no_max_damage_message = true; } if(EquippedWeapon.status[EquipmentStatus.MERCIFUL]){ no_max_damage_message = true; } if(a.HasAttr(AttrType.RESIST_WEAPONS) && EquippedWeapon.type != WeaponType.NO_WEAPON){ B.Add("Your " + EquippedWeapon.NameWithoutEnchantment() + " isn't very effective. "); dmg = dice; //minimum damage } else{ if(EquippedWeapon.status[EquipmentStatus.DULLED]){ B.Add("Your dull " + EquippedWeapon.NameWithoutEnchantment() + " isn't very effective. "); dmg = dice; //minimum damage } else{ if(type == ActorType.MUD_TENTACLE){ //getting surrounded by these guys should be dangerous, but not simply because of their damage. dmg = dice; } } } if(dmg >= dice * 6 && !no_max_damage_message){ if(this == player){ B.Add("It was a good hit! "); } else{ if(a == player){ B.Add("Ow! "); } } } dmg += TotalSkill(SkillType.COMBAT); if(a.type == ActorType.SPORE_POD && EquippedWeapon.IsBlunt()){ dmg = 0; dice = 0; effects.AddUnique(AttackEffect.STRONG_KNOCKBACK); B.Add("Your " + EquippedWeapon.NameWithoutEnchantment() + " knocks the spore pod away. ",a); } if(EquippedWeapon.status[EquipmentStatus.MERCIFUL] && dmg >= a.curhp){ dmg = a.curhp - 1; B.Add("Your " + EquippedWeapon.NameWithoutEnchantment() + " refuses to finish " + a.TheName(true) + ". "); B.Print(true); } if(a.HasAttr(AttrType.DULLS_BLADES) && R.CoinFlip() && (EquippedWeapon == Sword || EquippedWeapon == Dagger)){ EquippedWeapon.status[EquipmentStatus.DULLED] = true; B.Add(Your() + " " + EquippedWeapon.NameWithEnchantment() + " becomes dull! ",this); Help.TutorialTip(TutorialTopic.Dulled); } if(a.type == ActorType.CORROSIVE_OOZE && R.CoinFlip() && (EquippedWeapon == Sword || EquippedWeapon == Dagger || EquippedWeapon == Mace)){ EquippedWeapon.status[EquipmentStatus.DULLED] = true; B.Add("The acid dulls " + Your() + " " + EquippedWeapon.NameWithEnchantment() + "! ",this); Help.TutorialTip(TutorialTopic.Acidified); Help.TutorialTip(TutorialTopic.Dulled); } if(a.HasAttr(AttrType.CAN_POISON_WEAPONS) && R.CoinFlip() && EquippedWeapon.type != WeaponType.NO_WEAPON && !EquippedWeapon.status[EquipmentStatus.POISONED]){ EquippedWeapon.status[EquipmentStatus.POISONED] = true; weapon_just_poisoned = true; B.Add(Your() + " " + EquippedWeapon.NameWithEnchantment() + " is covered in poison! ",this); } int r = a.row; int c = a.col; bool still_alive = true; bool knockback_effect = effects.Contains(AttackEffect.KNOCKBACK) || effects.Contains(AttackEffect.STRONG_KNOCKBACK) || effects.Contains(AttackEffect.TRIP) || effects.Contains(AttackEffect.FLING) || effects.Contains(AttackEffect.SWAP_POSITIONS) || effects.Contains(AttackEffect.DRAIN_LIFE); if(knockback_effect){ a.attrs[AttrType.TURN_INTO_CORPSE]++; } Color blood = a.BloodColor(); bool homunculus = a.type == ActorType.HOMUNCULUS; if(dmg > 0){ Damage damage = new Damage(info.damage.type,info.damage.damclass,true,this,dmg); damage.weapon_used = EquippedWeapon.type; still_alive = a.TakeDamage(damage,a_name); } if(homunculus){ //todo: or will this happen on any major damage, not just melee attacks? M.tile[target_original_pos].AddFeature(FeatureType.OIL); } else{ if(blood != Color.Black && (!still_alive || !a.HasAttr(AttrType.FROZEN,AttrType.INVULNERABLE))){ /*List<Tile> valid = new List<Tile>{M.tile[target_original_pos]}; for(int i=-1;i<=1;++i){ valid.Add(M.tile[target_original_pos].TileInDirection(original_pos.DirectionOf(target_original_pos).RotateDir(true,i))); } for(int i=dmg/10;i>0;--i){ Tile t = valid.RemoveRandom(); if(t.Is(TileType.WALL) || t.name == "floor"){ t.color = blood; } }*/ List<Tile> cone = M.tile[target_original_pos].GetCone(original_pos.DirectionOf(target_original_pos),dmg>=20? 2 : 1,false); cone.Add(M.tile[target_original_pos].TileInDirection(original_pos.DirectionOf(target_original_pos))); cone.Add(M.tile[target_original_pos].TileInDirection(original_pos.DirectionOf(target_original_pos))); cone.Add(M.tile[target_original_pos].TileInDirection(original_pos.DirectionOf(target_original_pos))); for(int i=(dmg-5)/5;i>0;--i){ if(cone.Count == 0){ break; } Tile t = cone.Random(); while(cone.Remove(t)){ } //remove all if(t.Is(TileType.WALL) || t.name == "floor"){ t.color = blood; switch(blood){ case Color.DarkRed: M.aesthetics[t.p] = AestheticFeature.BloodDarkRed; break; default: M.aesthetics[t.p] = AestheticFeature.BloodOther; break; } } } } } if(still_alive){ //post-damage crit effects that require the target to still be alive foreach(AttackEffect effect in effects){ if(still_alive){ switch(effect){ //todo: some of these messages shouldn't be printed if the effect already exists case AttackEffect.CONFUSE: a.ApplyStatus(AttrType.CONFUSED,R.Between(2,3)*100); break; case AttackEffect.BLEED: if(!a.HasAttr(AttrType.NONLIVING,AttrType.FROZEN)){ if(a.HasAttr(AttrType.BLEEDING)){ if(a == player){ if(a.attrs[AttrType.BLEEDING] > 15){ B.Add("Your bleeding worsens. "); } else{ B.Add("You're bleeding badly now! "); } } else{ B.Add(a.YouAre() + " bleeding badly! ",a); } } a.attrs[AttrType.BLEEDING] += R.Between(10,15); if(a.attrs[AttrType.BLEEDING] > 25){ a.attrs[AttrType.BLEEDING] = 25; //this seems like a reasonable cap, so repeated bleed effects don't just last *forever*. } if(a == player){ Help.TutorialTip(TutorialTopic.Bleeding); } } break; case AttackEffect.BLIND: a.ApplyStatus(AttrType.BLIND,R.Between(5,7)*100); //B.Add(a.YouAre() + " blinded! ",a); //a.RefreshDuration(AttrType.BLIND,R.Between(5,7)*100); break; case AttackEffect.PERMANENT_BLIND: { if(!a.HasAttr(AttrType.COOLDOWN_1)){ B.Add("You drive your " + EquippedWeapon.NameWithoutEnchantment() + " into its eye, blinding it! "); Q.KillEvents(a,AttrType.BLIND); a.attrs[AttrType.BLIND] = 1; a.attrs[AttrType.COOLDOWN_1] = 1; } break; } case AttackEffect.DIM_VISION: if(a.ResistedBySpirit()){ B.Add(a.Your() + " vision is dimmed, but only for a moment. ",a); } else{ B.Add(a.Your() + " vision is dimmed. ",a); a.RefreshDuration(AttrType.DIM_VISION,(R.Roll(2,20)+20)*100); } break; case AttackEffect.CHILL: if(!a.HasAttr(AttrType.IMMUNE_COLD)){ B.Add(a.the_name + " is chilled. ",a); if(!a.HasAttr(AttrType.CHILLED)){ a.attrs[AttrType.CHILLED] = 1; } else{ a.attrs[AttrType.CHILLED] *= 2; } if(!a.TakeDamage(DamageType.COLD,DamageClass.MAGICAL,a.attrs[AttrType.CHILLED],this)){ still_alive = false; } } break; case AttackEffect.DISRUPTION: if(a.HasAttr(AttrType.NONLIVING)){ B.Add(a.the_name + " is disrupted. ",a); if(!a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,a.maxhp / 5,this)){ still_alive = false; } } break; case AttackEffect.FREEZE: a.tile().ApplyEffect(DamageType.COLD); a.ApplyFreezing(); break; case AttackEffect.GRAB: if(!HasAttr(AttrType.GRABBING) && DistanceFrom(a) == 1 && !a.HasAttr(AttrType.FROZEN)){ a.attrs[AttrType.GRABBED]++; attrs[AttrType.GRABBING] = DirectionOf(a); B.Add(YouVisible("grab") + " " + a.TheName(true) + ". ",this,a); if(a == player){ Help.TutorialTip(TutorialTopic.Grabbed); } } break; case AttackEffect.POISON: if(!a.HasAttr(AttrType.NONLIVING,AttrType.CAN_POISON_WEAPONS,AttrType.INVULNERABLE,AttrType.FROZEN)){ a.ApplyStatus(AttrType.POISONED,(R.Roll(2,6)+2)*100); } break; case AttackEffect.PARALYZE: if(!a.HasAttr(AttrType.NONLIVING) || type != ActorType.CARRION_CRAWLER){ if(a.ResistedBySpirit()){ B.Add(a.Your() + " muscles stiffen, but only for a moment. ",a); } else{ if(a == player){ B.Add("You suddenly can't move! "); } else{ B.Add(a.YouAre() + " paralyzed. ",a); } a.attrs[AttrType.PARALYZED] = R.Between(3,5); } } break; case AttackEffect.ONE_TURN_PARALYZE: Event e = Q.FindAttrEvent(a,AttrType.STUNNED); if(e != null && e.delay == 100 && e.TimeToExecute() == Q.turn){ //if the target was hit with a 1-turn stun that's about to expire, don't print a message for it. e.msg = ""; } if(a.ResistedBySpirit()){ B.Add(a.Your() + " muscles stiffen, but only for a moment. ",a); } else{ B.Add(a.YouAre() + " paralyzed! ",a); a.attrs[AttrType.PARALYZED] = 2; //setting it to 1 means it would end immediately } break; case AttackEffect.INFLICT_VULNERABILITY: a.ApplyStatus(AttrType.VULNERABLE,R.Between(2,4)*100); /*B.Add(a.You("become") + " vulnerable. ",a); a.RefreshDuration(AttrType.VULNERABLE,R.Between(2,4)*100);*/ if(a == player){ Help.TutorialTip(TutorialTopic.Vulnerable); } break; case AttackEffect.IGNITE: break; case AttackEffect.INFEST: if(a == player && !a.EquippedArmor.status[EquipmentStatus.INFESTED]){ B.Add("Thousands of insects crawl into your " + a.EquippedArmor.NameWithoutEnchantment() + "! "); a.EquippedArmor.status[EquipmentStatus.INFESTED] = true; Help.TutorialTip(TutorialTopic.Infested); } break; case AttackEffect.SLOW: a.ApplyStatus(AttrType.SLOWED,R.Between(4,6)*100); break; case AttackEffect.REDUCE_ACCURACY: //also about 2d4 turns? break; case AttackEffect.SLIME: B.Add(a.YouAre() + " covered in slime. ",a); a.attrs[AttrType.SLIMED] = 1; if(a == player){ Help.TutorialTip(TutorialTopic.Slimed); } break; case AttackEffect.STUN: //2d3 turns, at most { a.ApplyStatus(AttrType.STUNNED,R.Roll(2,3)*100); /*B.Add(a.YouAre() + " stunned! ",a); a.RefreshDuration(AttrType.STUNNED,a.DurationOfMagicalEffect(R.Roll(2,3)) * 100,a.YouAre() + " no longer stunned. ",a);*/ if(a == player){ Help.TutorialTip(TutorialTopic.Stunned); } break; } case AttackEffect.ONE_TURN_STUN: { a.ApplyStatus(AttrType.STUNNED,100); /*B.Add(a.YouAre() + " stunned! ",a); a.RefreshDuration(AttrType.STUNNED,100,a.YouAre() + " no longer stunned. ",a);*/ if(a == player){ Help.TutorialTip(TutorialTopic.Stunned); } break; } case AttackEffect.SILENCE: { if(a.ResistedBySpirit()){ if(!HasAttr(AttrType.SILENCED)){ B.Add(a.You("resist") + " being silenced. ",a); } } else{ if(!HasAttr(AttrType.SILENCED)){ B.Add(TheName(true) + " silences " + a.the_name + ". ",a); } a.RefreshDuration(AttrType.SILENCED,R.Between(3,4)*100,a.YouAre() + " no longer silenced. ",a); } if(a == player){ Help.TutorialTip(TutorialTopic.Silenced); } break; } case AttackEffect.WEAK_POINT: if(!a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] && a == player){ a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] = true; B.Add(YouVisible("expose") + " a weak point on your armor! ",this); Help.TutorialTip(TutorialTopic.WeakPoint); } break; case AttackEffect.WORN_OUT: if(a == player && !a.EquippedArmor.status[EquipmentStatus.DAMAGED]){ if(a.EquippedArmor.status[EquipmentStatus.WORN_OUT]){ a.EquippedArmor.status[EquipmentStatus.WORN_OUT] = false; a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] = false; a.EquippedArmor.status[EquipmentStatus.DAMAGED] = true; B.Add(a.Your() + " " + a.EquippedArmor.NameWithEnchantment() + " is damaged! "); Help.TutorialTip(TutorialTopic.Damaged); } else{ a.EquippedArmor.status[EquipmentStatus.WORN_OUT] = true; B.Add(a.Your() + " " + a.EquippedArmor.NameWithEnchantment() + " looks worn out. "); Help.TutorialTip(TutorialTopic.WornOut); } } break; case AttackEffect.ACID: if(a == player && !a.HasAttr(AttrType.ACIDIFIED) && R.CoinFlip()){ a.RefreshDuration(AttrType.ACIDIFIED,300); if(a.EquippedArmor != a.Leather && !a.EquippedArmor.status[EquipmentStatus.DAMAGED]){ B.Add("The acid hisses as it touches your " + a.EquippedArmor.NameWithEnchantment() + "! "); if(a.EquippedArmor.status[EquipmentStatus.WORN_OUT]){ a.EquippedArmor.status[EquipmentStatus.WORN_OUT] = false; a.EquippedArmor.status[EquipmentStatus.WEAK_POINT] = false; a.EquippedArmor.status[EquipmentStatus.DAMAGED] = true; B.Add(a.Your() + " " + a.EquippedArmor.NameWithEnchantment() + " is damaged! "); } else{ a.EquippedArmor.status[EquipmentStatus.WORN_OUT] = true; B.Add(a.Your() + " " + a.EquippedArmor.NameWithEnchantment() + " looks worn out. "); } Help.TutorialTip(TutorialTopic.Acidified); } } break; case AttackEffect.PULL: { List<Tile> tiles = tile().NeighborsBetween(a.row,a.col).Where(x=>x.actor() == null && x.passable); if(tiles.Count > 0){ Tile t = tiles.Random(); if(!a.MovementPrevented(t)){ B.Add(TheName(true) + " pulls " + a.TheName(true) + " closer. ",this,a); a.Move(t.row,t.col); } } break; } case AttackEffect.STEAL: { if(a.inv != null && a.inv.Count > 0){ Item i = a.inv.Random(); Item stolen = i; if(i.quantity > 1){ stolen = new Item(i,i.row,i.col); stolen.revealed_by_light = i.revealed_by_light; i.quantity--; } else{ a.inv.Remove(stolen); } GetItem(stolen); B.Add(YouVisible("steal") + " " + a.YourVisible() + " " + stolen.SingularName() + "! ",this,a); B.PrintAll(); } break; } case AttackEffect.EXHAUST: { if(a == player){ B.Add("You feel fatigued. "); } a.IncreaseExhaustion(R.Roll(2,4)); break; } } } } } foreach(AttackEffect effect in effects){ //effects that don't care whether the target is still alive switch(effect){ case AttackEffect.DRAIN_LIFE: { if(curhp < maxhp){ curhp += 10; if(curhp > maxhp){ curhp = maxhp; } B.Add(You("drain") + " some life from " + a.TheName(true) + ". ",this); } break; } case AttackEffect.VICTORY: if(!still_alive){ curhp += 5; if(curhp > maxhp){ curhp = maxhp; } } break; case AttackEffect.STALAGMITES: { List<Tile> tiles = new List<Tile>(); foreach(Tile t in M.tile[r,c].TilesWithinDistance(1)){ //if(t.actor() == null && (t.type == TileType.FLOOR || t.type == TileType.STALAGMITE)){ if(t.actor() == null && t.inv == null && (t.IsTrap() || t.Is(TileType.FLOOR,TileType.GRAVE_DIRT,TileType.GRAVEL,TileType.STALAGMITE))){ if(R.CoinFlip()){ tiles.Add(t); } } } foreach(Tile t in tiles){ if(t.type == TileType.STALAGMITE){ Q.KillEvents(t,EventType.STALAGMITE); } else{ TileType previous_type = t.type; t.Toggle(this,TileType.STALAGMITE); t.toggles_into = previous_type; } } Q.Add(new Event(tiles,150,EventType.STALAGMITE)); break; } case AttackEffect.MAKE_NOISE: break; case AttackEffect.SWAP_POSITIONS: if(original_pos.DistanceFrom(target_original_pos) == 1 && p.Equals(original_pos) && M.actor[target_original_pos] != null && !M.actor[target_original_pos].HasAttr(AttrType.IMMOBILE)){ B.Add(YouVisible("move") + " past " + M.actor[target_original_pos].TheName(true) + ". ",this,M.actor[target_original_pos]); Move(target_original_pos.row,target_original_pos.col); } break; case AttackEffect.TRIP: if(!a.HasAttr(AttrType.FLYING) && (a.curhp > 0 || !a.HasAttr(AttrType.NO_CORPSE_KNOCKBACK))){ B.Add(YouVisible("trip") + " " + a.TheName(true) + ". ",this,a); a.IncreaseExhaustion(R.Between(2,4)); a.CollideWith(a.tile());//todo: if it's a corpse, ONLY trip it if something is going to happen when it collides with the floor. } break; case AttackEffect.KNOCKBACK: if(a.curhp > 0 || !a.HasAttr(AttrType.NO_CORPSE_KNOCKBACK)){ KnockObjectBack(a,3,this); } break; case AttackEffect.STRONG_KNOCKBACK: if(a.curhp > 0 || !a.HasAttr(AttrType.NO_CORPSE_KNOCKBACK)){ KnockObjectBack(a,5,this); } break; case AttackEffect.FLING: if(a.curhp > 0 || !a.HasAttr(AttrType.NO_CORPSE_KNOCKBACK)){ attrs[AttrType.JUST_FLUNG] = 1; int dir = DirectionOf(a).RotateDir(true,4); Tile t = null; if(tile().p.PosInDir(dir).PosInDir(dir).BoundsCheck(M.tile,true)){ Tile t2 = tile().TileInDirection(dir).TileInDirection(dir); if(HasLOE(t2)){ t = t2; } } if(t == null){ if(tile().p.PosInDir(dir).BoundsCheck(M.tile,false)){ Tile t2 = tile().TileInDirection(dir); if(HasLOE(t2)){ t = t2; } } } if(t == null){ t = tile(); } B.Add(YouVisible("fling") + " " + a.TheName(true) + "! ",this,a); foreach(Tile nearby in M.ReachableTilesByDistance(t.row,t.col,false)){ if(nearby.passable && nearby.actor() == null && HasLOE(nearby)){ a.Move(nearby.row,nearby.col); a.CollideWith(nearby); break; } } } break; } } if(knockback_effect){ if(a.curhp > 0 && this != player){ target_location = target.tile(); } a.CorpseCleanup(); } if(type == ActorType.SWORDSMAN || type == ActorType.PHANTOM_SWORDMASTER || type == ActorType.ALASI_SOLDIER){ if(attrs[AttrType.COMBO_ATTACK] == 1 && (type == ActorType.SWORDSMAN || type == ActorType.PHANTOM_SWORDMASTER)){ B.Add(the_name + " prepares a devastating strike! ",this); } attrs[AttrType.COMBO_ATTACK]++; if(attrs[AttrType.COMBO_ATTACK] == 3){ //all these have 3-part combos attrs[AttrType.COMBO_ATTACK] = 0; } } } /*if(!hit && HasAttr(AttrType.BRUTISH_STRENGTH) && p.Equals(original_pos) && M.actor[target_original_pos] != null){ Actor a2 = M.actor[target_original_pos]; if(a2.HasAttr(AttrType.NO_CORPSE_KNOCKBACK) && a2.maxhp == 1){ B.Add(YouVisible("push",true) + " " + a2.TheName(true) + ". ",this,a2); a2.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,false,1,this); } else{ a2.attrs[AttrType.TURN_INTO_CORPSE]++; KnockObjectBack(a2,5); a2.CorpseCleanup(); } }*/ if(!hit && sneak_attack && this != player){ attrs[AttrType.TURNS_VISIBLE] = -1; attrs[AttrType.NOTICED]++; } if(!attack_is_part_of_another_action && hit && HasAttr(AttrType.BRUTISH_STRENGTH) && p.Equals(original_pos) && M.actor[target_original_pos] == null && DistanceFrom(target_original_pos) == 1 && !MovementPrevented(M.tile[target_original_pos])){ Tile t = M.tile[target_original_pos]; if(t.IsTrap()){ t.SetName(Tile.Prototype(t.type).name); t.TurnToFloor(); } if(HasFeat(FeatType.WHIRLWIND_STYLE)){ WhirlwindMove(t.row,t.col); } else{ Move(t.row,t.col); } } if(hit && EquippedWeapon.enchantment == EnchantmentType.ECHOES && !EquippedWeapon.status[EquipmentStatus.NEGATED]){ List<Tile> line = GetBestExtendedLineOfEffect(target_original_pos.row,target_original_pos.col); int idx = line.IndexOf(M.tile[target_original_pos]); if(idx != -1 && line.Count > idx + 1){ Actor next = line[idx+1].actor(); if(next != null && next != this){ Attack(attack_idx,next,true); } } } //if(!attack_is_part_of_another_action && EquippedWeapon == Staff && p.Equals(original_pos) && a_moved_last_turn && !HasAttr(AttrType.IMMOBILE) && M.tile[target_original_pos].passable && (M.actor[target_original_pos] == null || !M.actor[target_original_pos].HasAttr(AttrType.IMMOBILE))){ if(!attack_is_part_of_another_action && EquippedWeapon == Staff && p.Equals(original_pos) && a_moved_last_turn && !MovementPrevented(M.tile[target_original_pos]) && M.tile[target_original_pos].passable && (M.actor[target_original_pos] == null || !M.actor[target_original_pos].MovementPrevented(this))){ if(M.actor[target_original_pos] != null){ M.actor[target_original_pos].attrs[AttrType.TURNS_HERE]++; //this is a hack to prevent fast monsters from swapping *back* on the next hit. } if(HasFeat(FeatType.WHIRLWIND_STYLE)){ WhirlwindMove(target_original_pos.row,target_original_pos.col,true,new List<Actor>{M.actor[target_original_pos]}); //whirlwind move, but don't attack the original target again } else{ Move(target_original_pos.row,target_original_pos.col); } } if(!attack_is_part_of_another_action && EquippedWeapon.status[EquipmentStatus.POISONED] && !weapon_just_poisoned && R.OneIn(16)){ ApplyStatus(AttrType.POISONED,(R.Roll(2,6)+2)*100,"You manage to poison yourself with your " + EquippedWeapon.NameWithoutEnchantment() + ". ","","You resist the poison dripping from your " + EquippedWeapon.NameWithoutEnchantment() + ". "); } if(!attack_is_part_of_another_action && EquippedWeapon.status[EquipmentStatus.HEAVY] && R.CoinFlip() && !HasAttr(AttrType.BRUTISH_STRENGTH)){ B.Add("Attacking with your heavy " + EquippedWeapon.NameWithoutEnchantment() + " exhausts you. "); IncreaseExhaustion(5); } MakeNoise(6); if(!attack_is_part_of_another_action){ Q.Add(new Event(this,info.cost)); } return hit; }
public 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; }