public bool Use(Actor user,List<Tile> line) { bool used = true; bool IDed = true; switch(type){ case ConsumableType.HEALING: user.curhp = user.maxhp; B.Add(user.Your() + " wounds are healed completely. ",user); break; case ConsumableType.REGENERATION: { if(user == player){ B.Add("Your blood tingles. "); } else{ B.Add(user.the_name + " looks energized. ",user); } user.attrs[AttrType.REGENERATING]++; int duration = 100; Q.Add(new Event(user,duration*100,AttrType.REGENERATING)); break; } case ConsumableType.STONEFORM: { B.Add(user.You("transform") + " into a being of animated stone. ",user); int duration = R.Roll(2,20) + 20; List<AttrType> attributes = new List<AttrType>{AttrType.REGENERATING,AttrType.BRUTISH_STRENGTH,AttrType.VIGOR,AttrType.SILENCE_AURA,AttrType.SHADOW_CLOAK,AttrType.CAN_DODGE,AttrType.MENTAL_IMMUNITY,AttrType.DETECTING_MONSTERS,AttrType.MYSTIC_MIND}; foreach(AttrType at in attributes){ //in the rare case where a monster drinks this potion, it can lose these natural statuses permanently. this might eventually be fixed. if(user.HasAttr(at)){ user.attrs[at] = 0; Q.KillEvents(user,at); switch(at){ case AttrType.REGENERATING: B.Add(user.You("no longer regenerate") + ". ",user); break; case AttrType.BRUTISH_STRENGTH: B.Add(user.Your() + " brutish strength fades. ",user); break; case AttrType.VIGOR: B.Add(user.Your() + " extraordinary speed fades. ",user); break; case AttrType.SILENCED: B.Add(user.You("no longer radiate") + " an aura of silence. ",user); break; case AttrType.SHADOW_CLOAK: B.Add(user.YouAre() + " no longer cloaked. ",user); break; case AttrType.MYSTIC_MIND: B.Add(user.Your() + " consciousness returns to normal. ",user); break; } } } if(user.HasAttr(AttrType.PSEUDO_VAMPIRIC)){ user.attrs[AttrType.LIGHT_SENSITIVE] = 0; user.attrs[AttrType.FLYING] = 0; user.attrs[AttrType.PSEUDO_VAMPIRIC] = 0; Q.KillEvents(user,AttrType.LIGHT_SENSITIVE); Q.KillEvents(user,AttrType.FLYING); Q.KillEvents(user,AttrType.PSEUDO_VAMPIRIC); B.Add(user.YouAre() + " no longer vampiric. ",user); } if(user.HasAttr(AttrType.ROOTS)){ foreach(Event e in Q.list){ if(e.target == user && !e.dead){ if(e.attr == AttrType.IMMOBILE && e.msg.Contains("rooted to the ground")){ e.dead = true; user.attrs[AttrType.IMMOBILE]--; B.Add(user.YouAre() + " no longer rooted to the ground. ",user); } else{ if(e.attr == AttrType.BONUS_DEFENSE && e.value == 10){ e.dead = true; //this would break if there were other timed effects that gave the same amount of defense user.attrs[AttrType.BONUS_DEFENSE] -= 10; } else{ if(e.attr == AttrType.ROOTS){ e.dead = true; user.attrs[AttrType.ROOTS]--; } } } } } } if(user.HasAttr(AttrType.BURNING)){ user.RefreshDuration(AttrType.BURNING,0); } user.attrs[AttrType.IMMUNE_BURNING]++; Q.Add(new Event(user,duration*100,AttrType.IMMUNE_BURNING)); user.attrs[AttrType.DAMAGE_RESISTANCE]++; Q.Add(new Event(user,duration*100,AttrType.DAMAGE_RESISTANCE)); user.attrs[AttrType.NONLIVING]++; Q.Add(new Event(user,duration*100,AttrType.NONLIVING)); user.RefreshDuration(AttrType.STONEFORM,duration*100,user.Your() + " rocky form reverts to flesh. ",user); if(user == player){ Help.TutorialTip(TutorialTopic.Stoneform); } break; } case ConsumableType.VAMPIRISM: { B.Add(user.You("become") + " vampiric. ",user); B.Add(user.You("rise") + " into the air. ",user); int duration = R.Roll(2,20) + 20; user.RefreshDuration(AttrType.LIGHT_SENSITIVE,duration*100); user.RefreshDuration(AttrType.FLYING,duration*100); user.attrs[AttrType.DESCENDING] = 0; user.RefreshDuration(AttrType.PSEUDO_VAMPIRIC,duration*100,user.YouAre() + " no longer vampiric. ",user); if(user == player){ Help.TutorialTip(TutorialTopic.Vampirism); } break; } case ConsumableType.BRUTISH_STRENGTH: { if(user == player){ B.Add("You feel a surge of strength. "); } else{ B.Add(user.Your() + " muscles ripple. ",user); } user.RefreshDuration(AttrType.BRUTISH_STRENGTH,(R.Roll(3,6)+16)*100,user.Your() + " incredible strength wears off. ",user); if(user == player){ Help.TutorialTip(TutorialTopic.BrutishStrength); } break; } case ConsumableType.ROOTS: { if(user.HasAttr(AttrType.ROOTS)){ foreach(Event e in Q.list){ if(e.target == user && !e.dead){ if(e.attr == AttrType.IMMOBILE && e.msg.Contains("rooted to the ground")){ e.dead = true; user.attrs[AttrType.IMMOBILE]--; } else{ if(e.attr == AttrType.BONUS_DEFENSE && e.value == 10){ e.dead = true; //this would break if there were other timed effects that gave 5 defense user.attrs[AttrType.BONUS_DEFENSE] -= 10; } else{ if(e.attr == AttrType.ROOTS){ e.dead = true; user.attrs[AttrType.ROOTS]--; } } } } } B.Add(user.Your() + " roots extend deeper into the ground. ",user); } else{ B.Add(user.You("grow") + " roots and a hard shell of bark. ",user); } int duration = R.Roll(20) + 20; user.RefreshDuration(AttrType.ROOTS,duration*100); user.attrs[AttrType.BONUS_DEFENSE] += 10; Q.Add(new Event(user,duration*100,AttrType.BONUS_DEFENSE,10)); user.attrs[AttrType.IMMOBILE]++; Q.Add(new Event(user,duration*100,AttrType.IMMOBILE,user.YouAre() + " no longer rooted to the ground. ",user)); if(user == player){ Help.TutorialTip(TutorialTopic.Roots); } if(user.HasAttr(AttrType.FLYING) && user.tile().IsTrap()){ user.tile().TriggerTrap(); } break; } case ConsumableType.HASTE: { B.Add(user.You("start") + " moving with extraordinary speed. ",user); int duration = (R.Roll(2,10) + 10) * 100; user.RefreshDuration(AttrType.CAN_DODGE,duration); //todo: dodging tip goes here user.RefreshDuration(AttrType.VIGOR,duration,user.Your() + " extraordinary speed fades. ",user); if(user == player){ Help.TutorialTip(TutorialTopic.IncreasedSpeed); } break; } case ConsumableType.SILENCE: { B.Add("A hush falls around " + user.the_name + ". ",user); user.RefreshDuration(AttrType.SILENCE_AURA,(R.Roll(2,20)+20)*100,user.You("no longer radiate") + " an aura of silence. ",user); if(user == player){ Help.TutorialTip(TutorialTopic.Silenced); } break; } case ConsumableType.CLOAKING: if(user.tile().IsLit()){ if(user == player){ B.Add("You would feel at home in the shadows. "); } else{ B.Add("A shadow moves across " + user.the_name + ". ",user); } } else{ B.Add(user.You("fade") + " away in the darkness. ",user); } user.RefreshDuration(AttrType.SHADOW_CLOAK,(R.Roll(2,20)+30)*100,user.YouAre() + " no longer cloaked. ",user); break; case ConsumableType.MYSTIC_MIND: { B.Add(user.Your() + " mind expands. ",user); int duration = R.Roll(2,20)+60; user.attrs[AttrType.ASLEEP] = 0; //user.RefreshDuration(AttrType.MAGICAL_DROWSINESS,0); user.RefreshDuration(AttrType.CONFUSED,0); user.RefreshDuration(AttrType.STUNNED,0); user.RefreshDuration(AttrType.ENRAGED,0); user.RefreshDuration(AttrType.MENTAL_IMMUNITY,duration*100); user.RefreshDuration(AttrType.DETECTING_MONSTERS,duration*100); user.RefreshDuration(AttrType.MYSTIC_MIND,duration*100,user.Your() + " consciousness returns to normal. ",user); if(user == player){ Help.TutorialTip(TutorialTopic.MysticMind); } break; } case ConsumableType.BLINKING: { List<Tile> tiles = user.TilesWithinDistance(8).Where(x => x.passable && x.actor() == null && user.ApproximateEuclideanDistanceFromX10(x) >= 45); if(tiles.Count > 0 && !user.HasAttr(AttrType.IMMOBILE)){ Tile t = tiles.Random(); B.Add(user.You("step") + " through a rip in reality. ",M.tile[user.p],t); user.AnimateStorm(2,3,4,'*',Color.DarkMagenta); user.Move(t.row,t.col); M.Draw(); user.AnimateStorm(2,3,4,'*',Color.DarkMagenta); } else{ B.Add("Nothing happens. ",user); IDed = false; } break; } case ConsumableType.PASSAGE: { if(user.HasAttr(AttrType.IMMOBILE)){ B.Add("Nothing happens. ",user); IDed = false; break; } List<int> valid_dirs = new List<int>(); foreach(int dir in U.FourDirections){ Tile t = user.TileInDirection(dir); if(t != null && t.Is(TileType.WALL,TileType.CRACKED_WALL,TileType.WAX_WALL,TileType.DOOR_C,TileType.HIDDEN_DOOR,TileType.STONE_SLAB)){ while(!t.passable){ if(t.row == 0 || t.row == Global.ROWS-1 || t.col == 0 || t.col == Global.COLS-1){ break; } t = t.TileInDirection(dir); } if(t.passable){ valid_dirs.Add(dir); } } } if(valid_dirs.Count > 0){ int dir = valid_dirs.Random(); Tile t = user.TileInDirection(dir); 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>(); Screen.CursorVisible = false; Tile last_wall = null; while(!t.passable){ tiles.Add(t); memlist.Add(Screen.MapChar(t.row,t.col)); Screen.WriteMapChar(t.row,t.col,ch); Game.GLUpdate(); Thread.Sleep(35); last_wall = t; t = t.TileInDirection(dir); } Input.FlushInput(); if(t.actor() == null){ int r = user.row; int c = user.col; user.Move(t.row,t.col); Screen.WriteMapChar(r,c,M.VisibleColorChar(r,c)); Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col)); int idx = 0; foreach(Tile tile in tiles){ Screen.WriteMapChar(tile.row,tile.col,memlist[idx++]); Game.GLUpdate(); Thread.Sleep(35); } Input.FlushInput(); B.Add(user.You("travel") + " through the passage. ",user,t); } else{ Tile destination = null; List<Tile> adjacent = t.TilesAtDistance(1).Where(x=>x.passable && x.actor() == null && x.DistanceFrom(last_wall) == 1); if(adjacent.Count > 0){ destination = adjacent.Random(); } else{ foreach(Tile tile in M.ReachableTilesByDistance(t.row,t.col,false)){ if(tile.actor() == null){ destination = tile; break; } } } if(destination != null){ int r = user.row; int c = user.col; user.Move(destination.row,destination.col); Screen.WriteMapChar(r,c,M.VisibleColorChar(r,c)); Screen.WriteMapChar(destination.row,destination.col,M.VisibleColorChar(destination.row,destination.col)); int idx = 0; foreach(Tile tile in tiles){ Screen.WriteMapChar(tile.row,tile.col,memlist[idx++]); Game.GLUpdate(); Thread.Sleep(35); } Input.FlushInput(); B.Add(user.You("travel") + " through the passage. ",user,destination); } else{ B.Add("Something blocks " + user.Your() + " movement through the passage. ",user); } } } else{ B.Add("Nothing happens. ",user); IDed = false; } break; } case ConsumableType.TIME: if(user == player){ B.Add("Time stops for a moment. ",user); } else{ B.Add("Time warps around " + user.the_name + "! ",user); B.PrintAll(); } if(Fire.fire_event == null){ //this prevents fire from updating while time is frozen Fire.fire_event = new Event(0,EventType.FIRE); Fire.fire_event.tiebreaker = 0; Q.Add(Fire.fire_event); } Q.turn -= 200; break; case ConsumableType.KNOWLEDGE: { if(user == player){ B.Add("Knowledge fills your mind. "); Event hiddencheck = null; foreach(Event e in Q.list){ if(!e.dead && e.type == EventType.CHECK_FOR_HIDDEN){ hiddencheck = e; break; } } int max_dist = 0; List<Tile> last_tiles = new List<Tile>(); foreach(Tile t in M.ReachableTilesByDistance(user.row,user.col,true,TileType.STONE_SLAB,TileType.DOOR_C,TileType.STALAGMITE,TileType.RUBBLE,TileType.HIDDEN_DOOR)){ if(t.type != TileType.FLOOR){ t.seen = true; if(t.type != TileType.WALL){ t.revealed_by_light = true; } if(t.IsTrap() || t.Is(TileType.HIDDEN_DOOR)){ if(hiddencheck != null){ hiddencheck.area.Remove(t); } } if(t.IsTrap()){ t.name = Tile.Prototype(t.type).name; t.a_name = Tile.Prototype(t.type).a_name; t.the_name = Tile.Prototype(t.type).the_name; t.symbol = Tile.Prototype(t.type).symbol; t.color = Tile.Prototype(t.type).color; } if(t.Is(TileType.HIDDEN_DOOR)){ t.Toggle(null); } colorchar ch2 = Screen.BlankChar(); if(t.inv != null){ t.inv.revealed_by_light = true; ch2.c = t.inv.symbol; ch2.color = t.inv.color; M.last_seen[t.row,t.col] = ch2; } else{ if(t.features.Count > 0){ ch2 = t.FeatureVisual(); M.last_seen[t.row,t.col] = ch2; } else{ ch2.c = t.symbol; ch2.color = t.color; if(ch2.c == '#' && ch2.color == Color.RandomGlowingFungus){ ch2.color = Color.Gray; } M.last_seen[t.row,t.col] = ch2; } } Screen.WriteMapChar(t.row,t.col,t.symbol,Color.RandomRainbow); //Screen.WriteMapChar(t.row,t.col,M.VisibleColorChar(t.row,t.col)); if(user.DistanceFrom(t) > max_dist){ max_dist = user.DistanceFrom(t); Game.GLUpdate(); Thread.Sleep(10); while(last_tiles.Count > 0){ Tile t2 = last_tiles.RemoveRandom(); Screen.WriteMapChar(t2.row,t2.col,M.last_seen[t2.row,t2.col]); //Screen.WriteMapChar(t2.row,t2.col,M.VisibleColorChar(t2.row,t2.col)); } } last_tiles.Add(t); } } if(user.inv.Count > 0){ foreach(Item i in user.inv){ identified[i.type] = true; if(i.NameOfItemType() == "wand"){ i.other_data = -1; } } } } else{ B.Add(user.the_name + " looks more knowledgeable. ",user); } break; } case ConsumableType.SUNLIGHT: if(M.wiz_lite == false){ B.Add("The air itself seems to shine. "); M.wiz_lite = true; M.wiz_dark = false; Q.KillEvents(null,EventType.NORMAL_LIGHTING); Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING)); } else{ B.Add("The air grows even brighter for a moment. "); Q.KillEvents(null,EventType.NORMAL_LIGHTING); Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING)); } break; case ConsumableType.DARKNESS: if(M.wiz_dark == false){ B.Add("The air itself grows dark. "); if(player.light_radius > 0){ B.Add("Your light is extinguished! "); } M.wiz_dark = true; M.wiz_lite = false; Q.KillEvents(null,EventType.NORMAL_LIGHTING); Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING)); } else{ B.Add("The air grows even darker for a moment. "); Q.KillEvents(null,EventType.NORMAL_LIGHTING); Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING)); } break; case ConsumableType.RENEWAL: { B.Add("A glow envelops " + user.the_name + ". ",user); //B.Add("A glow envelops " + user.Your() + " equipment. ",user); bool repaired = false; foreach(EquipmentStatus eqstatus in Enum.GetValues(typeof(EquipmentStatus))){ foreach(Weapon w in user.weapons){ if(w.status[eqstatus]){ repaired = true; w.status[eqstatus] = false; } } foreach(Armor a in user.armors){ if(a.status[eqstatus]){ repaired = true; a.status[eqstatus] = false; } } } if(repaired){ B.Add(user.Your() + " equipment looks as good as new! ",user); } if(user.HasAttr(AttrType.SLIMED)){ B.Add(user.YouAre() + " no longer covered in slime. ",user); user.attrs[AttrType.SLIMED] = 0; } if(user.HasAttr(AttrType.OIL_COVERED)){ B.Add(user.YouAre() + " no longer covered in oil. ",user); user.attrs[AttrType.OIL_COVERED] = 0; } int recharged = 0; foreach(Item i in user.inv){ if(i.NameOfItemType() == "wand"){ i.charges++; recharged++; } } if(recharged > 0){ if(recharged == 1){ B.Add("The glow charges " + user.Your() + " wand. ",user); } else{ B.Add("The glow charges " + user.Your() + " wands. ",user); } } break; } case ConsumableType.CALLING: { bool found = false; if(user == player){ for(int dist = 1;dist < Math.Max(Global.ROWS,Global.COLS);++dist){ List<Tile> tiles = user.TilesAtDistance(dist).Where(x=>x.actor() != null && !x.actor().HasAttr(AttrType.IMMOBILE)); if(tiles.Count > 0){ Actor a = tiles.Random().actor(); Tile t2 = user.TileInDirection(user.DirectionOf(a)); if(t2.passable && t2.actor() == null){ B.Add("The scroll calls " + a.a_name + " to you. "); a.Move(t2.row,t2.col); found = true; break; } foreach(Tile t in M.ReachableTilesByDistance(user.row,user.col,false)){ if(t.actor() == null){ B.Add("The scroll calls " + a.a_name + " to you. "); a.Move(t.row,t.col); found = true; break; } } if(found){ break; } } } } else{ if(!player.HasAttr(AttrType.IMMOBILE) && user.DistanceFrom(player) > 1){ Tile t2 = user.TileInDirection(user.DirectionOf(player)); if(t2.passable && t2.actor() == null){ B.Add("The scroll calls you to " + user.TheName(true) + ". "); player.Move(t2.row,t2.col); found = true; } if(!found){ foreach(Tile t in M.ReachableTilesByDistance(user.row,user.col,false)){ if(t.actor() == null){ B.Add("The scroll calls you to " + user.TheName(true) + ". "); player.Move(t.row,t.col); found = true; break; } } } } } if(!found){ B.Add("Nothing happens. ",user); IDed = false; } break; } case ConsumableType.TRAP_CLEARING: { List<Tile> traps = new List<Tile>(); { List<Tile>[] traparray = new List<Tile>[5]; for(int i=0;i<5;++i){ traparray[i] = new List<Tile>(); } for(int i=0;i<=12;++i){ foreach(Tile t in user.TilesAtDistance(i)){ //all this ensures that the traps go off in the best order switch(t.type){ case TileType.ALARM_TRAP: case TileType.TELEPORT_TRAP: case TileType.ICE_TRAP: case TileType.BLINDING_TRAP: case TileType.SHOCK_TRAP: case TileType.FIRE_TRAP: case TileType.SCALDING_OIL_TRAP: traparray[0].Add(t); break; case TileType.POISON_GAS_TRAP: case TileType.GRENADE_TRAP: traparray[1].Add(t); break; case TileType.SLIDING_WALL_TRAP: case TileType.PHANTOM_TRAP: traparray[2].Add(t); break; case TileType.LIGHT_TRAP: case TileType.DARKNESS_TRAP: traparray[3].Add(t); break; case TileType.FLING_TRAP: case TileType.STONE_RAIN_TRAP: traparray[4].Add(t); break; } } } for(int i=0;i<5;++i){ foreach(Tile t in traparray[i]){ traps.Add(t); } } } if(traps.Count > 0){ B.Add("*CLICK*. "); foreach(Tile t in traps){ t.TriggerTrap(false); } } else{ B.Add("Nothing happens. ",user); IDed = false; } break; } case ConsumableType.ENCHANTMENT: { if(user == player){ EnchantmentType ench = (EnchantmentType)R.Between(0,4); while(ench == user.EquippedWeapon.enchantment){ ench = (EnchantmentType)R.Between(0,4); } B.Add("Your " + user.EquippedWeapon.NameWithEnchantment() + " glows brightly! "); user.EquippedWeapon.enchantment = ench; B.Add("Your " + user.EquippedWeapon.NameWithoutEnchantment() + " is now a " + user.EquippedWeapon.NameWithEnchantment() + "! "); } else{ B.Add("Nothing happens. ",user); IDed = false; } break; } case ConsumableType.THUNDERCLAP: { B.Add("Thunder crashes! ",user); var scr = Screen.GetCurrentMap(); List<Tile>[] printed = new List<Tile>[13]; Color leading_edge_color = Color.White; Color trail_color = Color.DarkCyan; if(Global.LINUX && !Screen.GLMode){ leading_edge_color = Color.Gray; } for(int dist=0;dist<=12;++dist){ printed[dist] = new List<Tile>(); foreach(Tile t in user.TilesAtDistance(dist)){ if(t.seen && user.HasLOE(t)){ printed[dist].Add(t); } } foreach(Tile t in printed[dist]){ colorchar cch = M.VisibleColorChar(t.row,t.col); cch.bgcolor = leading_edge_color; if(cch.color == leading_edge_color){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); } if(dist > 0){ foreach(Tile t in printed[dist-1]){ colorchar cch = M.VisibleColorChar(t.row,t.col); cch.bgcolor = trail_color; if(cch.color == trail_color){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); } if(dist > 4){ foreach(Tile t in printed[dist-5]){ Screen.WriteMapChar(t.row,t.col,scr[t.row,t.col]); } } } Game.GLUpdate(); Thread.Sleep(10); } List<Actor> actors = new List<Actor>(); for(int dist=0;dist<=12;++dist){ foreach(Tile t in user.TilesAtDistance(dist).Randomize()){ if(user.HasLOE(t)){ if(t.actor() != null && t.actor() != user){ actors.Add(t.actor()); } t.BreakFragileFeatures(); } } } foreach(Actor a in actors){ if(a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(4,6),user,"a scroll of thunderclap")){ a.ApplyStatus(AttrType.STUNNED,R.Between(5,10)*100); } } user.MakeNoise(12); break; } case ConsumableType.FIRE_RING: { List<pos> cells = new List<pos>(); List<Tile> valid = new List<Tile>(); foreach(Tile t in user.TilesWithinDistance(3)){ if(t.passable && user.DistanceFrom(t) > 1 && user.HasLOE(t) && user.ApproximateEuclideanDistanceFromX10(t) < 45){ valid.Add(t); cells.Add(t.p); } } if(valid.Count > 0){ if(player.CanSee(user)){ B.Add("A ring of fire surrounds " + user.the_name + ". "); } else{ B.Add("A ring of fire appears! ",user.tile()); } valid.Randomize(); foreach(Tile t in valid){ t.AddFeature(FeatureType.FIRE); } Screen.AnimateMapCells(cells,new colorchar('&',Color.RandomFire)); } else{ B.Add("Nothing happens. ",user); IDed = false; } break; } case ConsumableType.RAGE: { B.Add("A murderous red glow cascades outward. ",user); List<Tile>[] printed = new List<Tile>[13]; Color leading_edge_color = Color.Red; Color trail_color = Color.DarkRed; if(Global.LINUX && !Screen.GLMode){ leading_edge_color = Color.DarkRed; } for(int dist=0;dist<=12;++dist){ printed[dist] = new List<Tile>(); foreach(Tile t in user.TilesAtDistance(dist)){ if(t.seen && user.HasLOS(t)){ printed[dist].Add(t); } } foreach(Tile t in printed[dist]){ colorchar cch = M.VisibleColorChar(t.row,t.col); cch.bgcolor = leading_edge_color; if(cch.color == leading_edge_color){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); } if(dist > 0){ foreach(Tile t in printed[dist-1]){ colorchar cch = M.VisibleColorChar(t.row,t.col); cch.bgcolor = trail_color; if(cch.color == trail_color){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); } } Game.GLUpdate(); Thread.Sleep(5); } int actors_affected = 0; string name_is = ""; foreach(Actor a in M.AllActors()){ if(a != user && user.DistanceFrom(a) <= 12 && user.HasLOS(a)){ a.ApplyStatus(AttrType.ENRAGED,R.Between(10,17)*100,false,"",a.You("calm") + " down. ",a.You("resist") + "! "); actors_affected++; if(player.CanSee(a)){ name_is = a.YouAre(); } } } if(actors_affected > 0){ if(actors_affected == 1){ B.Add(name_is + " enraged! "); } else{ B.Add("Bloodlust fills the air. "); } } break; } case ConsumableType.FREEZING: { ItemUseResult orb_result = UseOrb(2,false,user,line,(t,LOE_tile,results)=>{ Screen.AnimateExplosion(t,2,new colorchar('*',Color.RandomIce)); List<Tile> targets = new List<Tile>(); foreach(Tile t2 in t.TilesWithinDistance(2)){ if(LOE_tile.HasLOE(t2)){ targets.Add(t2); } } while(targets.Count > 0){ Tile t2 = targets.RemoveRandom(); t2.ApplyEffect(DamageType.COLD); Actor ac = t2.actor(); if(ac != null){ ac.ApplyFreezing(); } } }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.FLAMES: { ItemUseResult orb_result = UseOrb(2,false,user,line,(t,LOE_tile,results)=>{ List<Tile> area = new List<Tile>(); List<pos> cells = new List<pos>(); foreach(Tile tile in t.TilesWithinDistance(2)){ if(LOE_tile.HasLOE(tile)){ if(tile.passable){ tile.AddFeature(FeatureType.FIRE); } else{ tile.ApplyEffect(DamageType.FIRE); } if(tile.Is(FeatureType.FIRE)){ area.Add(tile); } cells.Add(tile.p); } } Screen.AnimateMapCells(cells,new colorchar('&',Color.RandomFire)); }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.FOG: { ItemUseResult orb_result = UseOrb(3,false,user,line,(t,LOE_tile,results)=>{ List<Tile> area = new List<Tile>(); List<pos> cells = new List<pos>(); colorchar cch = new colorchar('*',Color.Gray); for(int i=0;i<=3;++i){ foreach(Tile tile in t.TilesAtDistance(i)){ if(tile.passable && LOE_tile.HasLOE(tile)){ tile.AddFeature(FeatureType.FOG); area.Add(tile); cells.Add(tile.p); if(tile.seen){ M.last_seen[tile.row,tile.col] = cch; } } } Screen.AnimateMapCells(cells,cch,40); } Q.RemoveTilesFromEventAreas(area,EventType.REMOVE_GAS); Event.RemoveGas(area,800,FeatureType.FOG,25); //Q.Add(new Event(area,600,EventType.FOG,25)); }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.DETONATION: { ItemUseResult orb_result = UseOrb(3,false,user,line,(t,LOE_tile,results)=>{ LOE_tile.ApplyExplosion(3,user,"an orb of detonation"); }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.BREACHING: { ItemUseResult orb_result = UseOrb(5,false,user,line,(t,LOE_tile,results)=>{ int max_dist = -1; foreach(Tile t2 in M.TilesByDistance(t.row,t.col,false,true)){ if(t.DistanceFrom(t2) > 5){ break; } if(t2.Is(TileType.WALL,TileType.WAX_WALL,TileType.STALAGMITE,TileType.CRACKED_WALL,TileType.DOOR_C)){ Screen.WriteMapChar(t2.row,t2.col,t2.symbol,Color.RandomBreached); if(t.DistanceFrom(t2) > max_dist){ max_dist = t.DistanceFrom(t2); Game.GLUpdate(); //todo: stalagmites - if I add them to caves, they should no longer always vanish. check for an event, maybe? Thread.Sleep(50); } } } List<Tile> area = new List<Tile>(); foreach(Tile tile in t.TilesWithinDistance(5)){ if(tile.Is(TileType.WALL,TileType.WAX_WALL,TileType.STALAGMITE,TileType.CRACKED_WALL,TileType.DOOR_C) && tile.p.BoundsCheck(M.tile,false)){ TileType prev_type = tile.type; if(tile.Is(TileType.STALAGMITE)){ tile.Toggle(null,TileType.FLOOR); } else{ tile.Toggle(null,TileType.BREACHED_WALL); tile.toggles_into = prev_type; area.Add(tile); } foreach(Tile neighbor in tile.TilesWithinDistance(1)){ neighbor.solid_rock = false; } } } if(area.Count > 0){ Q.Add(new Event(t,area,500,EventType.BREACH)); } }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.SHIELDING: { ItemUseResult orb_result = UseOrb(1,true,user,line,(t,LOE_tile,results)=>{ List<Tile> area = new List<Tile>(); List<pos> cells = new List<pos>(); List<colorchar> symbols = new List<colorchar>(); foreach(Tile tile in t.TilesWithinDistance(1)){ if(tile.passable && LOE_tile.HasLOE(tile)){ colorchar cch = tile.visual; if(tile.actor() != null){ if(!tile.actor().HasAttr(AttrType.SHIELDED)){ tile.actor().attrs[AttrType.SHIELDED] = 1; B.Add(tile.actor().YouAre() + " shielded. ",tile.actor()); } if(player.CanSee(tile.actor())){ cch = tile.actor().visual; } } cch.bgcolor = Color.Blue; if(Global.LINUX && !Screen.GLMode){ cch.bgcolor = Color.DarkBlue; } if(cch.color == cch.bgcolor){ cch.color = Color.Black; } if(cch.c == '.'){ cch.c = '+'; } symbols.Add(cch); cells.Add(tile.p); area.Add(tile); } } Screen.AnimateMapCells(cells,symbols,150); foreach(Tile tile in area){ if(player.CanSee(tile)){ B.Add("A zone of protection is created. "); break; } } Q.Add(new Event(area,100,EventType.SHIELDING,R.Roll(2,6)+6)); }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.TELEPORTAL: { ItemUseResult orb_result = UseOrb(0,false,user,line,(t,LOE_tile,results)=>{ LOE_tile.AddFeature(FeatureType.TELEPORTAL); if(LOE_tile.Is(FeatureType.TELEPORTAL)){ Q.Add(new Event(LOE_tile,0,EventType.TELEPORTAL,100)); } }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.PAIN: { ItemUseResult orb_result = UseOrb(5,false,user,line,(t,LOE_tile,results)=>{ List<pos> cells = new List<pos>(); List<colorchar> symbols = new List<colorchar>(); foreach(Tile tile in t.TilesWithinDistance(5)){ if(LOE_tile.HasLOE(tile)){ Actor a = tile.actor(); if(a != null){ if(a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(2,6),user,"an orb of pain")){ a.ApplyStatus(AttrType.VULNERABLE,(R.Roll(2,6)+6)*100); if(a == player){ Help.TutorialTip(TutorialTopic.Vulnerable); } } } symbols.Add(new colorchar('*',Color.RandomDoom)); /*if(tile.DistanceFrom(t) % 2 == 0){ symbols.Add(new colorchar('*',Color.DarkMagenta)); } else{ symbols.Add(new colorchar('*',Color.DarkRed)); }*/ cells.Add(tile.p); } } player.AnimateVisibleMapCells(cells,symbols,80); }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.CONFUSION: { ItemUseResult orb_result = UseOrb(2,false,user,line,(t,LOE_tile,results)=>{ List<Tile> area = new List<Tile>(); List<pos> cells = new List<pos>(); colorchar cch = new colorchar('*',Color.RandomConfusion); for(int i=0;i<=2;++i){ foreach(Tile tile in t.TilesAtDistance(i)){ if(tile.passable && LOE_tile.HasLOE(tile)){ tile.AddFeature(FeatureType.CONFUSION_GAS); area.Add(tile); cells.Add(tile.p); if(tile.seen){ M.last_seen[tile.row,tile.col] = cch; } } } Screen.AnimateMapCells(cells,cch,40); } Q.RemoveTilesFromEventAreas(area,EventType.REMOVE_GAS); Event.RemoveGas(area,R.Between(7,9)*100,FeatureType.CONFUSION_GAS,20); }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.BLADES: { ItemUseResult orb_result = UseOrb(1,false,user,line,(t,LOE_tile,results)=>{ List<Tile> targets = new List<Tile>(); foreach(Tile t2 in t.TilesWithinDistance(1)){ if(t2.passable && t2.actor() == null && LOE_tile.HasLOE(t2)){ targets.Add(t2); } } targets.Randomize(); foreach(Tile t2 in targets){ Actor a = Actor.Create(ActorType.BLADE,t2.row,t2.col); if(a != null){ a.speed = 50; } } }); used = orb_result.used; IDed = orb_result.IDed; break; } case ConsumableType.DUST_STORM: { ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{ List<Tile> area = new List<Tile>(); List<pos> cells = new List<pos>(); foreach(Tile neighbor in LOE_tile.TilesWithinDistance(1)){ if(neighbor.passable){ area.Add(neighbor); } } List<Tile> added = new List<Tile>(); foreach(Tile n1 in area){ foreach(int dir in U.FourDirections){ if(R.CoinFlip() && n1.TileInDirection(dir).passable){ added.Add(n1.TileInDirection(dir)); } } } foreach(Tile n1 in added){ area.AddUnique(n1); } colorchar cch = new colorchar('*',Color.TerrainDarkGray); foreach(Tile t2 in area){ t2.AddFeature(FeatureType.THICK_DUST); cells.Add(t2.p); if(t2.seen){ M.last_seen[t2.row,t2.col] = cch; } Actor a = t2.actor(); if(a != null && t2.Is(FeatureType.THICK_DUST)){ if(!a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE,AttrType.BLINDSIGHT)){ if(a == player){ B.Add("Thick dust fills the air! "); } a.ApplyStatus(AttrType.BLIND,R.Between(1,3)*100); } } } Screen.AnimateMapCells(cells,cch,80); Q.RemoveTilesFromEventAreas(area,EventType.REMOVE_GAS); Event.RemoveGas(area,R.Between(20,25)*100,FeatureType.THICK_DUST,8); }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.FLESH_TO_FIRE: { ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{ Actor a = targeting.targeted.actor(); if(a != null){ B.Add("Jets of flame erupt from " + a.TheName(true) + ". ",a,targeting.targeted); Screen.AnimateMapCell(a.row,a.col,new colorchar('&',Color.RandomFire)); int dmg = (a.curhp+1)/2; if(a.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,dmg,user,"a wand of flesh to fire")){ a.ApplyBurning(); } } else{ if(targeting.targeted.Is(FeatureType.TROLL_CORPSE)){ B.Add("Jets of flame erupt from the troll corpse. ",a,targeting.targeted); targeting.targeted.ApplyEffect(DamageType.FIRE); if(targeting.targeted.Is(FeatureType.TROLL_CORPSE)){ //if it's still there because of thick gas, it still gets destroyed. targeting.targeted.RemoveFeature(FeatureType.TROLL_CORPSE); B.Add("The troll corpse burns to ashes! ",targeting.targeted); } } else{ if(targeting.targeted.Is(FeatureType.TROLL_BLOODWITCH_CORPSE)){ B.Add("Jets of flame erupt from the troll bloodwitch corpse. ",a,targeting.targeted); targeting.targeted.ApplyEffect(DamageType.FIRE); if(targeting.targeted.Is(FeatureType.TROLL_BLOODWITCH_CORPSE)){ //if it's still there because of thick gas, it still gets destroyed. targeting.targeted.RemoveFeature(FeatureType.TROLL_BLOODWITCH_CORPSE); B.Add("The troll bloodwitch corpse burns to ashes! ",targeting.targeted); } } else{ B.Add("Nothing happens. ",user); results.IDed = false; } } } }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.INVISIBILITY: { ItemUseResult wand_result = UseWand(false,false,user,line,(LOE_tile,targeting,results)=>{ Actor a = targeting.targeted.actor(); if(a != null){ B.Add(a.You("vanish",true) + " from view. ",a); if(a.light_radius > 0 && !M.wiz_dark && !M.wiz_lite){ B.Add(a.Your() + " light still reveals " + a.Your() + " location. ",a); } a.RefreshDuration(AttrType.INVISIBLE,(R.Between(2,20)+30)*100,a.YouAre() + " no longer invisible. ",a); } else{ B.Add("Nothing happens. ",user); results.IDed = false; } }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.REACH: { ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{ Actor a = targeting.targeted.actor(); if(a != null && a != user){ user.Attack(0,a,true); } else{ B.Add("Nothing happens. ",user); results.IDed = false; } }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.SLUMBER: { ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{ Actor a = targeting.targeted.actor(); if(a != null){ if(a.HasAttr(AttrType.MENTAL_IMMUNITY)){ if(a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE)){ B.Add(a.You("resist") + " becoming dormant. ",a); } else{ B.Add(a.You("resist") + " falling asleep. ",a); } } else{ if(a.ResistedBySpirit()){ if(player.HasLOS(a)){ if(a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE)){ B.Add(a.You("resist") + " becoming dormant. ",a); } else{ B.Add(a.You("almost fall") + " asleep. ",a); } } } else{ if(player.HasLOS(a)){ if(a.HasAttr(AttrType.NONLIVING,AttrType.PLANTLIKE)){ B.Add(a.You("become") + " dormant. ",a); } else{ B.Add(a.You("fall") + " asleep. ",a); } } a.attrs[AttrType.ASLEEP] = 6 + R.Roll(4,6); } } } else{ B.Add("Nothing happens. ",user); results.IDed = false; } }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.TELEKINESIS: { ItemUseResult wand_result = UseWand(true,false,user,line,(LOE_tile,targeting,results)=>{ if(!SharedEffect.Telekinesis(false,user,targeting.targeted)){ results.used = false; } }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.WEBS: { ItemUseResult wand_result = UseWand(true,true,user,line,(LOE_tile,targeting,results)=>{ if(targeting.targeted == user.tile()){ B.Add("Nothing happens. ",user); results.IDed = false; } else{ Screen.CursorVisible = false; foreach(Tile t in targeting.line_to_targeted){ if(t.passable && t != user.tile()){ t.AddFeature(FeatureType.WEB); if(t.seen){ Screen.WriteMapChar(t.row,t.col,';',Color.White); Game.GLUpdate(); Thread.Sleep(15); } } } M.Draw(); } }); used = wand_result.used; IDed = wand_result.IDed; break; } case ConsumableType.BLAST_FUNGUS: { if(line == null){ line = user.GetTargetTile(12,0,false,true); } if(line != null){ revealed_by_light = true; ignored = true; Tile t = line.LastBeforeSolidTile(); Actor first = user.FirstActorInLine(line); B.Add(user.You("fling") + " " + TheName() + ". "); if(first != null && first != user){ t = first.tile(); B.Add("It hits " + first.the_name + ". ",first); } line = line.ToFirstSolidTileOrActor(); if(line.Count > 0){ line.RemoveAt(line.Count - 1); } int idx = 0; foreach(Tile tile2 in line){ if(tile2.seen){ ++idx; } else{ line = line.To(tile2); if(line.Count > 0){ line.RemoveAt(line.Count - 1); } break; } } if(line.Count > 0){ user.AnimateProjectile(line,symbol,color); } t.GetItem(this); //inv.Remove(i); t.MakeNoise(2); if(first != null && first != user){ first.player_visibility_duration = -1; first.attrs[AttrType.PLAYER_NOTICED]++; } else{ if(t.IsTrap()){ t.TriggerTrap(); } } } else{ used = false; } break; } case ConsumableType.BANDAGES: if(!user.HasAttr(AttrType.BANDAGED)){ user.attrs[AttrType.BANDAGED] = 20; //user.recover_time = Q.turn + 100; B.Add(user.You("apply",false,true) + " a bandage. ",user); } else{ B.Add(user.the_name + " can't apply another bandage yet. ",user); used = false; } break; case ConsumableType.FLINT_AND_STEEL: { int dir = -1; if(user == player){ dir = user.GetDirection("Which direction? ",false,true); } else{ dir = user.DirectionOf(player); } if(dir != -1){ Tile t = user.TileInDirection(dir); B.Add(user.You("use") + " your flint & steel. ",user); if(t.actor() != null && t.actor().HasAttr(AttrType.OIL_COVERED) && !t.Is(FeatureType.POISON_GAS,FeatureType.THICK_DUST)){ t.actor().ApplyBurning(); } if(!t.Is(TileType.WAX_WALL)){ t.ApplyEffect(DamageType.FIRE); } } else{ used = false; } break; } default: used = false; break; } if(used){ if(IDed){ bool seen = true; //i'll try letting orbs always be IDed. keep an eye on this. /*bool seen = (user == player); if(user != player){ if(player.CanSee(line[0])){ //fix this line - or at least check for null/empty seen = true; } if(user != null && player.CanSee(user)){ //heck, I could even check to see whose turn it is, if I really wanted to be hacky. seen = true; } }*/ if(!identified[type] && seen){ identified[type] = true; B.Add("(It was " + SingularName(true) + "!) "); } } else{ if(!unIDed_name[type].Contains("{tried}")){ unIDed_name[type] = unIDed_name[type] + " {tried}"; } } if(quantity > 1){ --quantity; } else{ if(type == ConsumableType.BANDAGES){ --other_data; if(user != null && other_data == 0){ B.Add(user.You("use") + " your last bandage. ",user); user.inv.Remove(this); } } else{ if(type == ConsumableType.FLINT_AND_STEEL){ if(R.OneIn(3)){ --other_data; if(user != null){ if(other_data == 2){ B.Add("Your flint & steel shows signs of wear. ",user); } if(other_data == 1){ B.Add("Your flint & steel is almost depleted. ",user); } if(other_data == 0){ B.Add("Your flint & steel is used up. ",user); user.inv.Remove(this); } } } } else{ if(NameOfItemType() == "wand"){ if(charges > 0){ --charges; if(other_data >= 0){ ++other_data; } } else{ other_data = -1; } } else{ if(user != null){ user.inv.Remove(this); } } } } } CheckForMimic(); } return used; }
public void Toggle(Actor toggler,TileType toggle_to) { bool lighting_update = false; List<PhysicalObject> light_sources = new List<PhysicalObject>(); TileType original_type = type; bool original_passable = passable; if(opaque != Prototype(toggle_to).opaque){ for(int i=row-1;i<=row+1;++i){ for(int j=col-1;j<=col+1;++j){ if(M.tile[i,j].IsLit(player.row,player.col,true)){ lighting_update = true; } } } } if(lighting_update){ for(int i=row-Global.MAX_LIGHT_RADIUS;i<=row+Global.MAX_LIGHT_RADIUS;++i){ for(int j=col-Global.MAX_LIGHT_RADIUS;j<=col+Global.MAX_LIGHT_RADIUS;++j){ if(i>0 && i<ROWS-1 && j>0 && j<COLS-1){ if(M.actor[i,j] != null && M.actor[i,j].LightRadius() > 0){ light_sources.Add(M.actor[i,j]); M.actor[i,j].UpdateRadius(M.actor[i,j].LightRadius(),0); } if(M.tile[i,j].inv != null && M.tile[i,j].inv.light_radius > 0){ light_sources.Add(M.tile[i,j].inv); M.tile[i,j].inv.UpdateRadius(M.tile[i,j].inv.light_radius,0); } if(M.tile[i,j].light_radius > 0){ light_sources.Add(M.tile[i,j]); M.tile[i,j].UpdateRadius(M.tile[i,j].light_radius,0); } else{ if(M.tile[i,j].Is(FeatureType.FIRE)){ light_sources.Add(M.tile[i,j]); M.tile[i,j].UpdateRadius(1,0); } } } } } } TransformTo(toggle_to); if(lighting_update){ foreach(PhysicalObject o in light_sources){ if(o is Actor){ Actor a = o as Actor; a.UpdateRadius(0,a.LightRadius()); } else{ if(o is Tile && o.light_radius == 0 && (o as Tile).Is(FeatureType.FIRE)){ o.UpdateRadius(0,1); } else{ o.UpdateRadius(0,o.light_radius); } } } } if(Prototype(type).revealed_by_light){ revealed_by_light = true; } if(toggler != null && toggler != player){ if(type == TileType.DOOR_C && original_type == TileType.DOOR_O){ if(player.CanSee(this)){ B.Add(toggler.TheName(true) + " closes the door. "); } } if(type == TileType.DOOR_O && original_type == TileType.DOOR_C){ if(player.CanSee(this)){ B.Add(toggler.TheName(true) + " opens the door. "); } } } if(toggler != null){ if(original_type == TileType.RUBBLE){ B.Add(toggler.YouVisible("scatter") + " the rubble. ",this); } } if(!passable && original_passable){ if(features.Contains(FeatureType.STABLE_TELEPORTAL)){ Event e = Q.FindTargetedEvent(this,EventType.TELEPORTAL); if(e != null){ foreach(Tile t in e.area){ Event e2 = Q.FindTargetedEvent(t,EventType.TELEPORTAL); if(e2 != null && t.features.Contains(FeatureType.STABLE_TELEPORTAL)){ e2.area.Remove(this); if(e2.area.Count == 0){ t.RemoveFeature(FeatureType.STABLE_TELEPORTAL); t.AddFeature(FeatureType.INACTIVE_TELEPORTAL); e2.dead = true; } } } } } foreach(FeatureType ft in new List<FeatureType>(features)){ RemoveFeature(ft); } } CheckForSpriteUpdate(); }
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; }