public bool KnockObjectBack(Actor a,List<Tile> line,int knockback_strength,Actor damage_source) { if(knockback_strength == 0){ //note that TURN_INTO_CORPSE should be set for 'a' - therefore it won't be removed and we can do what we want with it. return a.CollideWith(a.tile()); } int i=0; while(true){ Tile t = line[i]; if(t.actor() == a){ break; } ++i; } line.RemoveRange(0,i+1); if(line.Count == 0){ return a.CollideWith(a.tile()); } bool immobile = a.MovementPrevented(line[0]); string knocked_back_message = ""; if(!a.HasAttr(AttrType.TELEKINETICALLY_THROWN,AttrType.SELF_TK_NO_DAMAGE) && !immobile && player.CanSee(a)){ //if the player can see it now, don't check CanSee later. knocked_back_message = a.YouAre() + " knocked back. "; //B.Add(a.YouAre() + " knocked back. ",a); } int dice = 1; int damage_dice_to_other = 1; if(a.HasAttr(AttrType.TELEKINETICALLY_THROWN)){ dice = 3; damage_dice_to_other = 3; } if(a.HasAttr(AttrType.SELF_TK_NO_DAMAGE)){ dice = 0; } if(a.type == ActorType.SPORE_POD){ dice = 0; damage_dice_to_other = 0; } while(knockback_strength > 1){ //if the knockback strength is greater than 1, you're passing *over* at least one tile. Tile t = line[0]; line.RemoveAt(0); immobile = a.MovementPrevented(t); if(immobile){ if(player.CanSee(a.tile())){ B.Add(a.YouVisibleAre() + " knocked about. ",a); } if(a.type == ActorType.SPORE_POD){ return true; } return a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"crashing into the floor"); } if(!t.passable){ string deathstringname = t.AName(false); if(t.Is(TileType.CRACKED_WALL,TileType.DOOR_C,TileType.HIDDEN_DOOR) && !a.HasAttr(AttrType.SMALL)){ string tilename = t.TheName(true); if(t.type == TileType.HIDDEN_DOOR){ tilename = "a hidden door"; t.Toggle(null); } if(player.CanSee(a.tile())){ B.Add(a.YouVisibleAre() + " knocked through " + tilename + ". ",a,t); } else{ B.Add(knocked_back_message); } knocked_back_message = ""; //knockback_strength -= 2; //removing the distance modification for now t.Toggle(null); a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"slamming into " + deathstringname); a.Move(t.row,t.col); if(a.HasAttr(AttrType.BLEEDING) && !a.HasAttr(AttrType.SHIELDED,AttrType.INVULNERABLE,AttrType.SELF_TK_NO_DAMAGE)){ if(a.type == ActorType.HOMUNCULUS){ if(R.CoinFlip()){ t.AddFeature(FeatureType.OIL); } } else{ if(t.symbol == '.' && t.color == Color.White && R.CoinFlip()){ t.color = a.BloodColor(); } } } } else{ if(player.CanSee(a.tile())){ B.Add(a.YouVisibleAre() + " knocked into " + t.TheName(true) + ". ",a,t); } else{ B.Add(knocked_back_message); } knocked_back_message = ""; if(a.type != ActorType.SPORE_POD){ Color blood = a.BloodColor(); if(blood != Color.Black && R.CoinFlip() && t.Is(TileType.WALL) && !a.HasAttr(AttrType.SHIELDED,AttrType.INVULNERABLE,AttrType.SELF_TK_NO_DAMAGE)){ t.color = blood; } a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"slamming into " + deathstringname); } if(!a.HasAttr(AttrType.SMALL)){ t.Bump(a.DirectionOf(t)); } a.CollideWith(a.tile()); return !a.HasAttr(AttrType.CORPSE); } } else{ if(t.actor() != null){ if(player.CanSee(a.tile()) || player.CanSee(t)){ B.Add(a.YouVisibleAre() + " knocked into " + t.actor().TheName(true) + ". ",a,t.actor()); } else{ B.Add(knocked_back_message); } knocked_back_message = ""; string actorname = t.actor().AName(false); string actorname2 = a.AName(false); if(t.actor().type != ActorType.SPORE_POD && !t.actor().HasAttr(AttrType.SELF_TK_NO_DAMAGE)){ t.actor().TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(damage_dice_to_other,6),damage_source,"colliding with " + actorname2); } if(a.type != ActorType.SPORE_POD){ a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"colliding with " + actorname); } a.CollideWith(a.tile()); return !a.HasAttr(AttrType.CORPSE); } else{ if(t.Is(FeatureType.WEB) && !a.HasAttr(AttrType.SMALL)){ t.RemoveFeature(FeatureType.WEB); } a.Move(t.row,t.col,false); if(t.Is(FeatureType.WEB) && a.HasAttr(AttrType.SMALL) && !a.HasAttr(AttrType.SLIMED,AttrType.OIL_COVERED,AttrType.BURNING)){ knockback_strength = 0; } if(a.HasAttr(AttrType.BLEEDING) && !a.HasAttr(AttrType.SHIELDED,AttrType.INVULNERABLE,AttrType.SELF_TK_NO_DAMAGE)){ if(a.type == ActorType.HOMUNCULUS){ if(R.CoinFlip()){ t.AddFeature(FeatureType.OIL); } } else{ if(t.symbol == '.' && t.color == Color.White && R.CoinFlip()){ t.color = a.BloodColor(); } } } } } M.Draw(); knockback_strength--; } if(knockback_strength < 1){ return !a.HasAttr(AttrType.CORPSE); } bool slip = false; int extra_slip_tiles = -1; bool slip_message_printed = false; do{ Tile t = line[0]; line.RemoveAt(0); immobile = a.MovementPrevented(t); if(immobile){ if(player.CanSee(a.tile())){ B.Add(a.YouVisibleAre() + " knocked about. ",a); } if(a.type == ActorType.SPORE_POD){ return true; } return a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"crashing into the floor"); } if(!t.passable){ string deathstringname = t.AName(false); if(t.Is(TileType.CRACKED_WALL,TileType.DOOR_C,TileType.HIDDEN_DOOR) && !a.HasAttr(AttrType.SMALL)){ string tilename = t.TheName(true); if(t.type == TileType.HIDDEN_DOOR){ tilename = "a hidden door"; t.Toggle(null); } if(player.CanSee(a.tile())){ B.Add(a.YouVisibleAre() + " knocked through " + tilename + ". ",a,t); } else{ B.Add(knocked_back_message); } knocked_back_message = ""; t.Toggle(null); a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"slamming into " + deathstringname); a.Move(t.row,t.col); if(a.HasAttr(AttrType.BLEEDING) && !a.HasAttr(AttrType.SHIELDED,AttrType.INVULNERABLE,AttrType.SELF_TK_NO_DAMAGE)){ if(a.type == ActorType.HOMUNCULUS){ if(R.CoinFlip()){ t.AddFeature(FeatureType.OIL); } } else{ if(t.symbol == '.' && t.color == Color.White && R.CoinFlip()){ t.color = a.BloodColor(); } } } return !a.HasAttr(AttrType.CORPSE); } else{ if(player.CanSee(a.tile())){ B.Add(a.YouVisibleAre() + " knocked into " + t.TheName(true) + ". ",a,t); } else{ B.Add(knocked_back_message); } knocked_back_message = ""; if(a.type != ActorType.SPORE_POD){ Color blood = a.BloodColor(); if(blood != Color.Black && R.CoinFlip() && t.Is(TileType.WALL) && !a.HasAttr(AttrType.SHIELDED,AttrType.INVULNERABLE,AttrType.SELF_TK_NO_DAMAGE)){ t.color = blood; } a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"slamming into " + deathstringname); } if(!a.HasAttr(AttrType.SMALL)){ t.Bump(a.DirectionOf(t)); } a.CollideWith(a.tile()); return !a.HasAttr(AttrType.CORPSE); } } else{ if(t.actor() != null){ if(player.CanSee(a.tile()) || player.CanSee(t)){ B.Add(a.YouVisibleAre() + " knocked into " + t.actor().TheName(true) + ". ",a,t.actor()); } else{ B.Add(knocked_back_message); } knocked_back_message = ""; string actorname = t.actor().AName(false); string actorname2 = a.AName(false); if(t.actor().type != ActorType.SPORE_POD && !t.actor().HasAttr(AttrType.SELF_TK_NO_DAMAGE)){ t.actor().TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(damage_dice_to_other,6),damage_source,"colliding with " + actorname2); } if(a.type != ActorType.SPORE_POD){ a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(dice,6),damage_source,"colliding with " + actorname); } a.CollideWith(a.tile()); return !a.HasAttr(AttrType.CORPSE); } else{ slip = false; if(t.IsSlippery()){ B.Add(knocked_back_message); knocked_back_message = ""; slip = true; if(!slip_message_printed){ slip_message_printed = true; B.Add(a.You("slide") + "! "); } } else{ if(extra_slip_tiles > 0){ extra_slip_tiles--; } if(extra_slip_tiles == -1 && a.HasAttr(AttrType.SLIMED,AttrType.OIL_COVERED) && !t.IsWater()){ B.Add(knocked_back_message); knocked_back_message = ""; extra_slip_tiles = 2; if(!slip_message_printed){ slip_message_printed = true; B.Add(a.You("slide") + "! "); } } } /*if(extra_slip_tiles > 0){ extra_slip_tiles--; } if(t.IsSlippery()){ B.Add(knocked_back_message); knocked_back_message = ""; slip = true; if(!slip_message_printed){ slip_message_printed = true; B.Add(a.You("slide") + "! "); } } else{ if(extra_slip_tiles == -1 && a.HasAttr(AttrType.SLIMED,AttrType.OIL_COVERED)){ B.Add(knocked_back_message); knocked_back_message = ""; extra_slip_tiles = 2; if(!slip_message_printed){ slip_message_printed = true; B.Add(a.You("slide") + "! "); } } }*/ bool interrupted = false; if(t.inv != null && t.inv.type == ConsumableType.DETONATION){ //this will cause a new knockback effect and end the current one B.Add(knocked_back_message); knocked_back_message = ""; interrupted = true; } if(t.IsTrap()){ if(t.type == TileType.FLING_TRAP){ //otherwise you'd teleport around, continuing to slide from your previous position. interrupted = true; } B.Add(knocked_back_message); knocked_back_message = ""; } if(t.Is(FeatureType.WEB) && !a.HasAttr(AttrType.SMALL)){ t.RemoveFeature(FeatureType.WEB); } a.Move(t.row,t.col); if(a.HasAttr(AttrType.BLEEDING) && !a.HasAttr(AttrType.SHIELDED,AttrType.INVULNERABLE,AttrType.SELF_TK_NO_DAMAGE)){ if(a.type == ActorType.HOMUNCULUS){ if(R.CoinFlip()){ t.AddFeature(FeatureType.OIL); } } else{ if(t.symbol == '.' && t.color == Color.White && R.CoinFlip()){ t.color = a.BloodColor(); } } } if(a.HasAttr(AttrType.FROZEN)){ interrupted = true; } if(a.HasAttr(AttrType.SMALL) && t.Is(FeatureType.WEB) && !a.HasAttr(AttrType.SLIMED,AttrType.OIL_COVERED,AttrType.BURNING)){ B.Add(knocked_back_message); interrupted = true; } else{ if(a.tile().IsWater()){ interrupted = true; } B.Add(knocked_back_message); a.CollideWith(a.tile()); } knocked_back_message = ""; if(interrupted){ return !a.HasAttr(AttrType.CORPSE); } } } M.Draw(); } while(slip || extra_slip_tiles > 0); if(knocked_back_message != ""){ B.Add(knocked_back_message); //this probably never happens } return !a.HasAttr(AttrType.CORPSE); }
public bool 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 async Task<bool> Attack(int attack_idx, Actor a) { //returns true if attack hit if (await StunnedThisTurn()) { return false; } //pos pos_of_target = new pos(a.row,a.col); AttackInfo info = AttackList.Attack(atype, attack_idx); if (weapons[0] != WeaponType.NO_WEAPON) { info.damage = Weapon.Damage(weapons[0]); } info.damage.source = this; int plus_to_hit = TotalSkill(SkillType.COMBAT); bool sneak_attack = false; if (this.IsHiddenFrom(a) || !a.CanSee(this) || (this == player && HasAttr(AttrType.SHADOW_CLOAK) && !tile().IsLit() && !a.HasAttr(AttrType.BLINDSIGHT))) { sneak_attack = true; } if (sneak_attack) { //sneak attacks get +25% accuracy. this usually totals 100% vs. unarmored targets. plus_to_hit += 25; } if (HasAttr(AttrType.BLESSED)) { plus_to_hit += 10; } plus_to_hit -= a.ArmorClass() * 2; bool hit = a.IsHit(plus_to_hit); if (HasFeat(FeatType.DRIVE_BACK)) { bool nowhere_to_run = true; int dir = DirectionOf(a); if (a.TileInDirection(dir).passable && a.ActorInDirection(dir) == null) { nowhere_to_run = false; } if (a.TileInDirection(RotateDirection(dir, true)).passable && a.ActorInDirection(RotateDirection(dir, true)) == null) { nowhere_to_run = false; } if (a.TileInDirection(RotateDirection(dir, false)).passable && a.ActorInDirection(RotateDirection(dir, false)) == null) { nowhere_to_run = false; } if (a.HasAttr(AttrType.FROZEN) || a.HasAttr(AttrType.NEVER_MOVES)) { nowhere_to_run = true; } if (nowhere_to_run) { hit = true; } } bool no_armor_message = false; //no_armor_message means "don't print 'your armor blocks the attack' for misses" if (a.HasAttr(AttrType.DEFENSIVE_STANCE) && Global.CoinFlip()) { hit = false; no_armor_message = true; } if ((this.tile().Is(FeatureType.FOG) || a.tile().Is(FeatureType.FOG)) && Global.CoinFlip()) { hit = false; no_armor_message = true; } if (a.IsHiddenFrom(this) || !CanSee(a) || (a == player && a.HasAttr(AttrType.SHADOW_CLOAK) && !a.tile().IsLit() && !HasAttr(AttrType.BLINDSIGHT))) { if (Global.CoinFlip()) { hit = false; no_armor_message = true; } } bool player_in_combat = false; if (this == player || a == player) { player_in_combat = true; } if (attack_idx == 2 && (atype == ActorType.FROSTLING || atype == ActorType.FIRE_DRAKE)) { hit = true; //hack! these are the 2 'area' attacks that always hit player_in_combat = false; } if (a == player && atype == ActorType.DREAM_CLONE) { player_in_combat = false; } if (player_in_combat) { player.attrs[Forays.AttrType.IN_COMBAT]++; } string s = info.desc + ". "; if (hit) { if (HasFeat(FeatType.NECK_SNAP) && a.HasAttr(AttrType.MEDIUM_HUMANOID) && IsHiddenFrom(a)) { if (!HasAttr(AttrType.RESIST_NECK_SNAP)) { B.Add(You("silently snap") + " " + a.Your() + " neck. "); await a.TakeDamage(DamageType.NORMAL, DamageClass.NO_TYPE, 9001, this); Q1(); return true; } else { B.Add(You("silently snap") + " " + a.Your() + " neck. "); B.Add("It doesn't seem to affect " + a.the_name + ". "); } } int dice = info.damage.dice; bool crit = false; int pos = s.IndexOf("&"); if (pos != -1) { s = s.Substring(0, pos) + TheVisible() + s.Substring(pos + 1); } pos = s.IndexOf("^"); if (pos != -1) { string sc = ""; int critical_target = 20; if (weapons[0] == WeaponType.DAGGER) { critical_target -= 2; } if (HasFeat(FeatType.LETHALITY)) { //10% crit plus 5% for each 20% health the target is missing critical_target -= 2; int fifth = a.maxhp / 5; //uses int because it assumes everything has a multiple of 5hp int totaldamage = a.maxhp - a.curhp; if (fifth > 0) { int missing_fifths = totaldamage / fifth; critical_target -= missing_fifths; } } if ((info.damage.type == DamageType.NORMAL || info.damage.type == DamageType.PIERCING || info.damage.type == DamageType.BASHING || info.damage.type == DamageType.SLASHING) && Global.Roll(1, 20) >= critical_target) { //maybe this should become a check for physical damage - todo? crit = true; sc = "critically "; } s = s.Substring(0, pos) + sc + s.Substring(pos + 1); } pos = s.IndexOf("*"); if (pos != -1) { s = s.Substring(0, pos) + a.TheVisible() + s.Substring(pos + 1); } if (sneak_attack && crit) { if (!a.HasAttr(AttrType.UNDEAD) && !a.HasAttr(AttrType.CONSTRUCT) && !a.HasAttr(AttrType.PLANTLIKE) && !a.HasAttr(AttrType.BOSS_MONSTER)) { if (a.atype != ActorType.PLAYER) { //being nice to the player here... switch (weapons[0]) { case WeaponType.SWORD: case WeaponType.FLAMEBRAND: B.Add("You run " + a.TheVisible() + " through! "); break; case WeaponType.MACE: case WeaponType.MACE_OF_FORCE: B.Add("You bash " + a.YourVisible() + " head in! "); break; case WeaponType.DAGGER: case WeaponType.VENOMOUS_DAGGER: B.Add("You pierce one of " + a.YourVisible() + " vital organs! "); break; case WeaponType.STAFF: case WeaponType.STAFF_OF_MAGIC: B.Add("You bring your staff down on " + a.YourVisible() + " head with a loud crack! "); break; case WeaponType.BOW: case WeaponType.HOLY_LONGBOW: B.Add("You choke " + a.TheVisible() + " with your bowstring! "); break; default: break; } MakeNoise(); await a.TakeDamage(DamageType.NORMAL, DamageClass.NO_TYPE, 1337, this); Q1(); return true; } else { //...but not too nice B.Add(AVisible() + " strikes from hiding! "); B.Add("The deadly attack leaves you stunned! "); int lotsofdamage = Math.Max(dice * 6, a.curhp / 2); a.attrs[AttrType.STUNNED]++; Q.Add(new Event(a, Global.Roll(2, 5) * 100, AttrType.STUNNED, "You are no longer stunned. ")); await a.TakeDamage(DamageType.NORMAL, DamageClass.PHYSICAL, lotsofdamage, this, a_name); } } } if (sneak_attack) { B.Add(YouVisible("strike") + " from hiding! "); if (atype != ActorType.PLAYER) { attrs[AttrType.TURNS_VISIBLE] = -1; attrs[Forays.AttrType.NOTICED]++; } else { a.player_visibility_duration = -1; a.attrs[Forays.AttrType.PLAYER_NOTICED]++; } } B.Add(s, this, a); int dmg; if (crit) { dmg = dice * 6; } else { dmg = Global.Roll(dice, 6); } dmg += TotalSkill(SkillType.COMBAT); int r = a.row; int c = a.col; bool troll = (a.atype == ActorType.TROLL || a.atype == ActorType.TROLL_SEER); bool mech_shield = a.HasAttr(AttrType.MECHANICAL_SHIELD); if (crit && mech_shield) { a.attrs[Forays.AttrType.MECHANICAL_SHIELD] = 0; } await a.TakeDamage(info.damage.type, info.damage.damclass, dmg, this, a_name); if (crit && mech_shield) { a.attrs[Forays.AttrType.MECHANICAL_SHIELD]++; } if (M.actor[r, c] != null) { if (HasAttr(AttrType.FIRE_HIT) || attrs[AttrType.ON_FIRE] >= 3) { //todo: a frostling's ranged attack shouldn't apply this if (!a.HasAttr(AttrType.INVULNERABLE)) { //to prevent the message int amount = Global.Roll(6); if (!a.HasAttr(AttrType.RESIST_FIRE) || amount / a.attrs[AttrType.RESIST_FIRE] > 0) { //todo i think resistance is wrong here B.Add(a.YouAre() + " burned. ", a); } await a.TakeDamage(DamageType.FIRE, DamageClass.PHYSICAL, amount, this, a_name); } } } if (troll && HasAttr(AttrType.FIRE_HIT) && M.tile[r, c].Is(FeatureType.TROLL_CORPSE)) { M.tile[r, c].features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ", M.tile[r, c]); } if (troll && HasAttr(AttrType.FIRE_HIT) && M.tile[r, c].Is(FeatureType.TROLL_SEER_CORPSE)) { M.tile[r, c].features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ", M.tile[r, c]); } if (HasAttr(AttrType.COLD_HIT) && attack_idx == 0 && M.actor[r, c] != null) { //hack: only applies to attack 0 if (!a.HasAttr(AttrType.INVULNERABLE)) { //to prevent the message B.Add(a.YouAre() + " chilled. ", a); await a.TakeDamage(DamageType.COLD, DamageClass.PHYSICAL, Global.Roll(1, 6), this, a_name); } } if (HasAttr(AttrType.POISON_HIT) && M.actor[r, c] != null) { if (!a.HasAttr(AttrType.UNDEAD) && !a.HasAttr(AttrType.CONSTRUCT) && !a.HasAttr(AttrType.POISON_HIT) && !a.HasAttr(AttrType.IMMUNE_TOXINS)) { if (a.HasAttr(AttrType.POISONED)) { B.Add(a.YouAre() + " more poisoned. ", a); } else { B.Add(a.YouAre() + " poisoned. ", a); } a.attrs[AttrType.POISONED]++; Q.Add(new Event(a, (Global.Roll(6) + 6) * 100, AttrType.POISONED)); } } if (HasAttr(AttrType.PARALYSIS_HIT) && attack_idx == 1 && atype == ActorType.CARRION_CRAWLER && M.actor[r, c] != null) { if (!a.HasAttr(AttrType.IMMUNE_TOXINS)) { //hack: carrion crawler only B.Add(a.YouAre() + " paralyzed. ", a); a.attrs[AttrType.PARALYZED] = Global.Roll(1, 3) + 3; } } if (HasAttr(AttrType.FORCE_HIT) && M.actor[r, c] != null) { if (Global.OneIn(3)) { if (Global.CoinFlip()) { await a.GetKnockedBack(this); } else { if (!a.HasAttr(AttrType.STUNNED)) { B.Add(a.YouAre() + " stunned. ", a); a.attrs[AttrType.STUNNED]++; int duration = (Global.Roll(4) + 3) * 100; if (crit) { duration += 250; crit = false; //note this - don't try to use crit again after this on-hit stuff. } Q.Add(new Event(a, duration, AttrType.STUNNED, a.YouAre() + " no longer stunned. ", new PhysicalObject[] { a })); } } } } if (HasAttr(AttrType.DIM_VISION_HIT) && M.actor[r, c] != null) { string str = ""; if (a.atype == ActorType.PLAYER) { B.Add("Your vision grows weak. "); str = "Your vision returns to normal. "; } //a.attrs[AttrType.DIM_VISION]++; //Q.Add(new Event(a,a.DurationOfMagicalEffect(Global.Roll(2,20)+20)*100,AttrType.DIM_VISION,str)); a.GainAttrRefreshDuration(AttrType.DIM_VISION, a.DurationOfMagicalEffect(Global.Roll(2, 20) + 20) * 100, str); } if (HasAttr(AttrType.STALAGMITE_HIT)) { List<Tile> tiles = new List<Tile>(); foreach (Tile t in M.tile[r, c].TilesWithinDistance(1)) { if (t.actor() == null && (t.ttype == TileType.FLOOR || t.ttype == TileType.STALAGMITE)) { if (Global.CoinFlip()) { //50% for each... tiles.Add(t); } } } foreach (Tile t in tiles) { if (t.ttype == TileType.STALAGMITE) { Q.KillEvents(t, EventType.STALAGMITE); } else { t.Toggle(this, TileType.STALAGMITE); } } Q.Add(new Event(tiles, 150, EventType.STALAGMITE)); } if (HasAttr(AttrType.GRAB_HIT) && M.actor[r, c] != null && !HasAttr(AttrType.GRABBING) && DistanceFrom(a) == 1) { a.attrs[Forays.AttrType.GRABBED]++; attrs[Forays.AttrType.GRABBING] = DirectionOf(a); B.Add(the_name + " grabs " + a.the_name + ". ", this, a); } if (HasAttr(AttrType.LIFE_DRAIN_HIT) && curhp < maxhp) { curhp += 10; if (curhp > maxhp) { curhp = maxhp; } B.Add(YouFeel() + " restored. ", this); } if (HasAttr(AttrType.STUN_HIT) && M.actor[r, c] != null) { B.Add(a.YouAre() + " stunned. ", a); int duration = 550; if (crit) { duration += 250; crit = false; } a.GainAttrRefreshDuration(AttrType.STUNNED, duration, a.YouAre() + " no longer stunned. ", a); } if (crit && M.actor[r, c] != null) { B.Add(a.YouAre() + " stunned. ", a); a.GainAttrRefreshDuration(AttrType.STUNNED, 250, a.YouAre() + " no longer stunned. ", a); } if (M.actor[r, c] != null && a.atype == ActorType.SWORDSMAN) { if (a.attrs[AttrType.BONUS_COMBAT] > 0) { B.Add(a.the_name + " returns to a defensive stance. ", a); a.attrs[AttrType.BONUS_COMBAT] = 0; } a.attrs[AttrType.COOLDOWN_1]++; Q.Add(new Event(a, 100, AttrType.COOLDOWN_1)); } } else { if (a.HasAttr(AttrType.DEFENSIVE_STANCE) || (a.HasFeat(FeatType.FULL_DEFENSE) && Global.CoinFlip())) { //make an attack against a random enemy next to a List<Actor> list = a.ActorsWithinDistance(1, true); list.Remove(this); //don't consider yourself or the original target if (list.Count > 0) { B.Add(a.You("deflect") + " the attack. ", this, a); return await Attack(attack_idx, list[Global.Roll(1, list.Count) - 1]); } //this would currently enter an infinite loop if two adjacent things used it at the same time } if (this == player || a == player || player.CanSee(this) || player.CanSee(a)) { //didn't change this yet if (s == "& lunges forward and ^hits *. ") { B.Add(the_name + " lunges forward and misses " + a.the_name + ". "); } else { if (s == "& hits * with a blast of cold. ") { B.Add(the_name + " nearly hits " + a.the_name + " with a blast of cold. "); } else { if (s.Length >= 20 && s.Substring(0, 20) == "& extends a tentacle") { B.Add(the_name + " misses " + a.the_name + " with a tentacle. "); } else { if (HasFeat(FeatType.DRIVE_BACK)) { B.Add(You("drive") + " " + a.TheVisible() + " back. "); } else { if (a.ArmorClass() > 0 && !no_armor_message) { if (a.atype != ActorType.PLAYER) { B.Add(a.YourVisible() + " armor blocks " + YourVisible() + " attack. "); } else { int miss_chance = 25 - plus_to_hit; if (Global.Roll(miss_chance) <= Armor.Protection(a.armors[0]) * 2) { B.Add(a.YourVisible() + " armor blocks " + YourVisible() + " attack. "); } else { B.Add(YouVisible("miss", true) + " " + a.TheVisible() + ". "); } } } else { B.Add(YouVisible("miss", true) + " " + a.TheVisible() + ". "); } } } } } } if (HasFeat(FeatType.DRIVE_BACK)) { if (!a.HasAttr(AttrType.FROZEN) && !HasAttr(AttrType.FROZEN)) { await a.AI_Step(this, true); await AI_Step(a); } } if (a.atype == ActorType.SWORDSMAN) { if (a.attrs[AttrType.BONUS_COMBAT] > 0) { B.Add(a.the_name + " returns to a defensive stance. ", a); a.attrs[AttrType.BONUS_COMBAT] = 0; } a.attrs[AttrType.COOLDOWN_1]++; Q.Add(new Event(a, 100, AttrType.COOLDOWN_1)); } } MakeNoise(); Q.Add(new Event(this, info.cost)); return hit; }
public async Task<bool> Use(Actor user,List<Tile> line){ bool used = true; switch(itype){ case ConsumableType.HEALING: await user.TakeDamage(DamageType.HEAL,DamageClass.NO_TYPE,50,null); //was Roll(8,6) B.Add("A blue glow surrounds " + user.the_name + ". ",new PhysicalObject[]{user}); break; case ConsumableType.TOXIN_IMMUNITY: if(!user.HasAttr(AttrType.IMMUNE_TOXINS)){ if(user.HasAttr(AttrType.POISONED)){ user.attrs[AttrType.POISONED] = 0; B.Add(user.YouFeel() + " relieved. ",user); } user.GainAttr(AttrType.IMMUNE_TOXINS,5100,user.YouAre() + " no longer immune to toxins. ",new PhysicalObject[]{user}); } else{ B.Add("Nothing happens. ",user); } break; case ConsumableType.REGENERATION: { user.attrs[AttrType.REGENERATING]++; if(user.name == "you"){ B.Add("Your blood tingles. ",user); } else{ B.Add(user.the_name + " looks energized. ",user); } int duration = 60; //was Roll(10)+20 Q.Add(new Event(user,duration*100,AttrType.REGENERATING)); break; } /*case ConsumableType.RESISTANCE: { user.attrs[AttrType.RESIST_FIRE]++; user.attrs[AttrType.RESIST_COLD]++; user.attrs[AttrType.RESIST_ELECTRICITY]++; B.Add(user.YouFeel() + " insulated. ",user); int duration = Global.Roll(2,10)+5; Q.Add(new Event(user,duration*100,AttrType.RESIST_FIRE)); Q.Add(new Event(user,duration*100,AttrType.RESIST_COLD)); Q.Add(new Event(user,duration*100,AttrType.RESIST_ELECTRICITY,user.YouFeel() + " less insulated. ",user)); if(user.HasAttr(AttrType.ON_FIRE) || user.HasAttr(AttrType.CATCHING_FIRE) || user.HasAttr(AttrType.STARTED_CATCHING_FIRE_THIS_TURN)){ B.Add(user.YouAre() + " no longer on fire. ",user); int oldradius = user.LightRadius(); user.attrs[AttrType.ON_FIRE] = 0; user.attrs[AttrType.CATCHING_FIRE] = 0; user.attrs[AttrType.STARTED_CATCHING_FIRE_THIS_TURN] = 0; if(oldradius != user.LightRadius()){ user.UpdateRadius(oldradius,user.LightRadius()); } } break; }*/ case ConsumableType.CLARITY: user.ResetSpells(); if(user.name == "you"){ B.Add("Your mind clears. "); } else{ B.Add(user.the_name + " seems focused. ",user); } break; case ConsumableType.CLOAKING: if(user.tile().IsLit()){ B.Add("You would feel at home in the shadows. "); } else{ B.Add("You fade away in the darkness. "); } user.GainAttrRefreshDuration(AttrType.SHADOW_CLOAK,(Global.Roll(41)+29)*100,"You are no longer cloaked. ",user); break; case ConsumableType.BLINKING: for(int i=0;i<9999;++i){ int rr = Global.Roll(1,17) - 9; int rc = Global.Roll(1,17) - 9; if(Math.Abs(rr) + Math.Abs(rc) >= 6){ rr += user.row; rc += user.col; if(M.BoundsCheck(rr,rc) && M.tile[rr,rc].passable && M.actor[rr,rc] == null){ B.Add(user.You("step") + " through a rip in reality. ",new PhysicalObject[]{M.tile[user.row,user.col],M.tile[rr,rc]}); user.AnimateStorm(2,3,4,"*",Color.DarkMagenta); await user.Move(rr, rc); M.Draw(); user.AnimateStorm(2,3,4,"*",Color.DarkMagenta); break; } } } break; case ConsumableType.TELEPORTATION: for(int i=0;i<9999;++i){ int rr = Global.Roll(1,Global.ROWS-2); int rc = Global.Roll(1,Global.COLS-2); if(Math.Abs(rr-user.row) >= 10 || Math.Abs(rc-user.col) >= 10 || (Math.Abs(rr-user.row) >= 7 && Math.Abs(rc-user.col) >= 7)){ if(M.BoundsCheck(rr,rc) && M.tile[rr,rc].passable && M.actor[rr,rc] == null){ B.Add(user.You("jump") + " through a rift in reality. ", new PhysicalObject[] { M.tile[user.row, user.col], M.tile[rr, rc] }); user.AnimateStorm(3,3,10,"*",Color.Green); await user.Move(rr, rc); M.Draw(); user.AnimateStorm(3,3,10,"*",Color.Green); break; } } } break; case ConsumableType.PASSAGE: { int i = user.DirectionOfOnlyUnblocked(TileType.WALL,true); if(i == 0){ B.Add("This item requires an adjacent wall. "); used = false; break; } else{ i = await user.GetDirection(true,false); Tile t = user.TileInDirection(i); if(t != null){ if(t.ttype == TileType.WALL){ Game.Console.CursorVisible = false; colorchar ch = new colorchar(Color.Cyan,"!"); switch(user.DirectionOf(t)){ case 8: case 2: ch.c = "|"; break; case 4: case 6: ch.c = "-"; break; } List<Tile> tiles = new List<Tile>(); List<colorchar> memlist = new List<colorchar>(); while(!t.passable){ if(t.row == 0 || t.row == Global.ROWS-1 || t.col == 0 || t.col == Global.COLS-1){ break; } tiles.Add(t); memlist.Add(Screen.MapChar(t.row,t.col)); Screen.WriteMapChar(t.row,t.col,ch); await Task.Delay(35); t = t.TileInDirection(i); } if(t.passable && M.actor[t.row,t.col] == null){ if(M.tile[user.row,user.col].inv != null){ Screen.WriteMapChar(user.row,user.col,new colorchar(user.tile().inv.color,user.tile().inv.symbol)); } else{ Screen.WriteMapChar(user.row,user.col,new colorchar(user.tile().color,user.tile().symbol)); } Screen.WriteMapChar(t.row,t.col,new colorchar(user.color,user.symbol)); int j = 0; foreach(Tile tile in tiles){ Screen.WriteMapChar(tile.row,tile.col,memlist[j++]); await Task.Delay(35); } B.Add(user.You("travel") + " through the passage. ",user,t); await user.Move(t.row, t.col); } else{ int j = 0; foreach(Tile tile in tiles){ Screen.WriteMapChar(tile.row,tile.col,memlist[j++]); await Task.Delay(35); } B.Add("The passage is blocked. ",user); } } else{ B.Add("This item requires an adjacent wall. "); used = false; break; } } else{ used = false; } } break; } case ConsumableType.TIME: B.Add("Time stops for a moment. "); Q.turn -= 200; break; case ConsumableType.DETECT_MONSTERS: { //user.attrs[AttrType.DETECTING_MONSTERS]++; B.Add("The scroll reveals " + user.Your() + " foes. ",user); int duration = Global.Roll(20)+30; //Q.Add(new Event(user,duration*100,AttrType.DETECTING_MONSTERS,user.Your() + " foes are no longer revealed. ",user)); user.GainAttrRefreshDuration(AttrType.DETECTING_MONSTERS,duration*100,user.Your() + " foes are no longer revealed. ",user); break; } case ConsumableType.MAGIC_MAP: { B.Add("The scroll reveals the layout of this level. "); Event hiddencheck = null; foreach(Event e in Q.list){ if(!e.dead && e.evtype == EventType.CHECK_FOR_HIDDEN){ hiddencheck = e; break; } } foreach(Tile t in M.AllTiles()){ if(t.ttype != TileType.FLOOR){ bool good = false; foreach(Tile neighbor in t.TilesAtDistance(1)){ if(neighbor.ttype != TileType.WALL){ good = true; } } if(good){ t.seen = true; if(t.IsTrapOrVent() || t.Is(TileType.HIDDEN_DOOR)){ if(hiddencheck != null){ hiddencheck.area.Remove(t); } } if(t.IsTrapOrVent()){ t.name = Tile.Prototype(t.ttype).name; t.a_name = Tile.Prototype(t.ttype).a_name; t.the_name = Tile.Prototype(t.ttype).the_name; t.symbol = Tile.Prototype(t.ttype).symbol; t.color = Tile.Prototype(t.ttype).color; } if(t.Is(TileType.HIDDEN_DOOR)){ t.Toggle(null); } } } } break; } case ConsumableType.SUNLIGHT: if(!M.wiz_lite){ M.wiz_lite = true; M.wiz_dark = false; B.Add("The air itself seems to shine. "); } else{ B.Add("Nothing happens. "); } break; case ConsumableType.DARKNESS: if(!M.wiz_dark){ M.wiz_dark = true; M.wiz_lite = false; B.Add("The air itself grows dark. "); } else{ B.Add("Nothing happens. "); } break; case ConsumableType.PRISMATIC: { if(line == null){ line = await user.GetTarget(12,1); } if(line != null){ Tile t = line.Last(); Tile prev = line.LastBeforeSolidTile(); Actor first = user.FirstActorInLine(line); //todo - consider allowing thrown items to pass over actors, because they fly in an arc B.Add(user.You("throw") + " the prismatic orb. ",user); if(first != null){ t = first.tile(); B.Add("It shatters on " + first.the_name + "! ",first); } else{ B.Add("It shatters on " + t.the_name + "! ",t); } user.AnimateProjectile(line.ToFirstObstruction(),"*",Color.RandomPrismatic); List<DamageType> dmg = new List<DamageType>(); dmg.Add(DamageType.FIRE); dmg.Add(DamageType.COLD); dmg.Add(DamageType.ELECTRIC); while(dmg.Count > 0){ DamageType damtype = dmg.Random(); colorchar ch = new colorchar(Color.Black,"*"); switch(damtype){ case DamageType.FIRE: ch.color = Color.RandomFire; break; case DamageType.COLD: ch.color = Color.RandomIce; break; case DamageType.ELECTRIC: ch.color = Color.RandomLightning; break; } B.DisplayNow(); Screen.AnimateExplosion(t,1,ch,100); if(t.passable){ foreach(Tile t2 in t.TilesWithinDistance(1)){ if(t2.actor() != null){ await t2.actor().TakeDamage(damtype,DamageClass.MAGICAL,Global.Roll(2,6),user,"a prismatic orb"); } if(damtype == DamageType.FIRE && t2.Is(FeatureType.TROLL_CORPSE)){ t2.features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ",t2); } if(damtype == DamageType.FIRE && t2.Is(FeatureType.TROLL_SEER_CORPSE)){ t2.features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ",t2); } } } else{ foreach(Tile t2 in t.TilesWithinDistance(1)){ if(prev != null && prev.HasBresenhamLine(t2.row,t2.col)){ if(t2.actor() != null){ await t2.actor().TakeDamage(damtype,DamageClass.MAGICAL,Global.Roll(2,6),user,"a prismatic orb"); } if(damtype == DamageType.FIRE && t2.Is(FeatureType.TROLL_CORPSE)){ t2.features.Remove(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ",t2); } if(damtype == DamageType.FIRE && t2.Is(FeatureType.TROLL_SEER_CORPSE)){ t2.features.Remove(FeatureType.TROLL_SEER_CORPSE); B.Add("The troll seer corpse burns to ashes! ",t2); } } } } dmg.Remove(damtype); } } else{ used = false; } break; } case ConsumableType.FREEZING: { if(line == null){ line = await user.GetTarget(12,3); } if(line != null){ Tile t = line.Last(); Tile prev = line.LastBeforeSolidTile(); Actor first = user.FirstActorInLine(line); B.Add(user.You("throw") + " the freezing orb. ",user); if(first != null){ t = first.tile(); B.Add("It shatters on " + first.the_name + "! ",first); } else{ B.Add("It shatters on " + t.the_name + "! ",t); } user.AnimateProjectile(line.ToFirstObstruction(),"*",Color.RandomIce); user.AnimateExplosion(t,3,"*",Color.Cyan); List<Actor> targets = new List<Actor>(); if(t.passable){ foreach(Actor ac in t.ActorsWithinDistance(3)){ if(t.HasLOE(ac)){ targets.Add(ac); } } } else{ foreach(Actor ac in t.ActorsWithinDistance(3)){ if(prev != null && prev.HasLOE(ac)){ targets.Add(ac); } } } while(targets.Count > 0){ Actor ac = targets.RemoveRandom(); B.Add(ac.YouAre() + " encased in ice. ",ac); ac.attrs[Forays.AttrType.FROZEN] = 25; } } else{ used = false; } break; } case ConsumableType.QUICKFIRE: { if(line == null){ line = await user.GetTarget(12,-1); } if(line != null){ Tile t = line.Last(); Tile prev = line.LastBeforeSolidTile(); Actor first = user.FirstActorInLine(line); B.Add(user.You("throw") + " the orb of quickfire. ",user); if(first != null){ t = first.tile(); B.Add("It shatters on " + first.the_name + "! ",first); } else{ B.Add("It shatters on " + t.the_name + "! ",t); } user.AnimateProjectile(line.ToFirstObstruction(),"*",Color.RandomFire); if(t.passable){ t.features.Add(FeatureType.QUICKFIRE); Q.Add(new Event(t,new List<Tile>{t},100,EventType.QUICKFIRE,AttrType.NO_ATTR,3,"")); } else{ prev.features.Add(FeatureType.QUICKFIRE); Q.Add(new Event(prev,new List<Tile>{prev},100,EventType.QUICKFIRE,AttrType.NO_ATTR,3,"")); } } else{ used = false; } break; } case ConsumableType.FOG: { if(line == null){ line = await user.GetTarget(12,-3); } if(line != null){ Tile t = line.Last(); Tile prev = line.LastBeforeSolidTile(); Actor first = user.FirstActorInLine(line); B.Add(user.You("throw") + " the orb of fog. ",user); if(first != null){ t = first.tile(); B.Add("It shatters on " + first.the_name + "! ",first); } else{ B.Add("It shatters on " + t.the_name + "! ",t); } user.AnimateProjectile(line.ToFirstObstruction(),"*",Color.Gray); List<Tile> area = new List<Tile>(); List<pos> cells = new List<pos>(); if(t.passable){ foreach(Tile tile in t.TilesWithinDistance(3)){ if(tile.passable && t.HasLOE(tile)){ tile.AddOpaqueFeature(FeatureType.FOG); area.Add(tile); cells.Add(tile.p); } } } else{ foreach(Tile tile in t.TilesWithinDistance(3)){ if(prev != null && tile.passable && prev.HasLOE(tile)){ tile.AddOpaqueFeature(FeatureType.FOG); area.Add(tile); cells.Add(tile.p); } } } Screen.AnimateMapCells(cells,new colorchar("*",Color.Gray)); Q.Add(new Event(area,400,EventType.FOG)); } else{ used = false; } break; } case ConsumableType.BANDAGE: await user.TakeDamage(DamageType.HEAL,DamageClass.NO_TYPE,1,null); if(user.HasAttr(AttrType.MAGICAL_BLOOD)){ user.recover_time = Q.turn + 200; } else{ user.recover_time = Q.turn + 500; } if(user.name == "you"){ B.Add("You apply a bandage. "); } else{ B.Add(user.the_name + " applies a bandage. ",user); } break; default: used = false; break; } if(used){ if(quantity > 1){ --quantity; } else{ if(user != null){ user.inv.Remove(this); } } } return used; }