public static void DebugDisplayDijkstra(PosArray<int> d,int default_cost) { int h = d.objs.GetLength(0); int w = d.objs.GetLength(1); for(int i=0;i<h;++i){ for(int j=0;j<w;++j){ if(d[i,j] == U.DijkstraMax){ Screen.WriteMapChar(i,j,'!',Color.DarkGray); } else{ if(d[i,j] == U.DijkstraMin){ Screen.WriteMapChar(i,j,'#',Color.Gray); } else{ if(d[i,j] == 0){ Screen.WriteMapChar(i,j,'0',Color.White); } else{ int cost = d[i,j] / default_cost; if(cost < 10){ Screen.WriteMapChar(i,j,cost.ToString()[0],Color.Cyan); } else{ Screen.WriteMapChar(i,j,'+',Color.Blue); } } } } } } Input.ReadKey(); }
public static void DebugDisplayDijkstra(PosArray <int> d, int default_cost) { int h = d.objs.GetLength(0); int w = d.objs.GetLength(1); for (int i = 0; i < h; ++i) { for (int j = 0; j < w; ++j) { if (d[i, j] == U.DijkstraMax) { Screen.WriteMapChar(i, j, '!', Color.DarkGray); } else { if (d[i, j] == U.DijkstraMin) { Screen.WriteMapChar(i, j, '#', Color.Gray); } else { if (d[i, j] == 0) { Screen.WriteMapChar(i, j, '0', Color.White); } else { int cost = d[i, j] / default_cost; if (cost < 10) { if (cost < 0) { if (cost < -26) { Screen.WriteMapChar(i, j, '-', Color.DarkRed); } else { char ch = (char)(Math.Abs(cost) + (int)('A') - 1); Screen.WriteMapChar(i, j, ch, Color.Red); } } else { Screen.WriteMapChar(i, j, cost.ToString()[0], Color.Cyan); } } else { Screen.WriteMapChar(i, j, '+', Color.Blue); } } } } } } Input.ReadKey(); }
public static void DebugDisplayDijkstra(PosArray <int> d, int default_cost) { int h = d.objs.GetLength(0); int w = d.objs.GetLength(1); for (int i = 0; i < h; ++i) { for (int j = 0; j < w; ++j) { if (d[i, j] == U.DijkstraMax) { Screen.WriteMapChar(i, j, '!', Color.DarkGray); } else { if (d[i, j] == U.DijkstraMin) { Screen.WriteMapChar(i, j, '#', Color.Gray); } else { if (d[i, j] == 0) { Screen.WriteMapChar(i, j, '0', Color.White); } else { int cost = d[i, j] / default_cost; if (cost < 10) { Screen.WriteMapChar(i, j, cost.ToString()[0], Color.Cyan); } else { Screen.WriteMapChar(i, j, '+', Color.Blue); } } } } } } Global.ReadKey(); }
public TextPanel(GLWindow parent_window,int rows,int cols,int cell_h_px,int cell_w_px,int v_offset_px,int h_offset_px,string font_filename,int char_width_px,int padding_between_chars_px,bool antialiased_font) { memory = new PosArray<colorchar>(rows,cols); colorchar cch = TextPanel.GetBlackChar(); for(int i=0;i<rows;++i){ for(int j=0;j<cols;++j){ memory[i,j] = cch; } } height = rows * cell_h_px; width = cols * cell_w_px; string shader = antialiased_font? Shader.AAFontFS() : Shader.FontFS(); surface = Surface.Create(parent_window,font_filename,shader,false,2,4,4); //todo: maybe a bool to control whether the panel gets added to the window's list? SpriteType.DefineSingleRowSprite(surface,char_width_px,padding_between_chars_px); //also todo, TransparentFontFS exists now...? CellLayout.CreateGrid(surface,rows,cols,cell_h_px,cell_w_px,0,0); surface.SetOffsetInPixels(h_offset_px,v_offset_px); surface.SetEasyLayoutCounts(rows*cols); surface.DefaultUpdatePositions(); UpdateSurface(0,rows*cols-1); }
public Dungeon(int height,int width,int seed) { H = height; W = width; map = new PosArray<CellType>(H,W); R.SetSeed(seed); //this is a bad idea }
public bool TakeDamage(Damage dmg,string cause_of_death) { //returns true if still alive if(dmg.amount == 0){ return true; } bool damage_dealt = false; int old_hp = curhp; if(curhp <= 0 && dmg.amount < 1000){ //then we're dealing with a corpse, and they don't take normal amounts of damage return true; } bool ice_removed = false; if(dmg.amount < 1000){ if(HasAttr(AttrType.FROZEN) && (dmg.major_damage || dmg.type == DamageType.FIRE)){ //this should ignore bleeding and poison, but not searing attrs[AttrType.FROZEN] -= dmg.amount; if(attrs[AttrType.FROZEN] <= 0){ attrs[AttrType.FROZEN] = 0; B.Add("The ice breaks! ",this); ice_removed = true; } dmg.amount = 0; if(dmg.type == DamageType.FIRE && HasAttr(AttrType.FROZEN)){ attrs[AttrType.FROZEN] = 0; B.Add("The ice melts! ",this); ice_removed = true; } } if(dmg.type == DamageType.FIRE && HasAttr(AttrType.OIL_COVERED)){ if(HasAttr(AttrType.IMMUNE_BURNING)){ B.Add("The oil burns off of " + the_name + ". ",this); attrs[AttrType.OIL_COVERED] = 0; } else{ B.Add(You("catch",true) + " fire! ",this); ApplyBurning(); } } if(dmg.type == DamageType.COLD && HasAttr(AttrType.SLIMED)){ attrs[AttrType.SLIMED] = 0; B.Add("The slime freezes and falls from " + the_name + ". ",this); } if(!dmg.major_damage && HasAttr(AttrType.MINOR_IMMUNITY,AttrType.SHIELDED)){ return true; } if(HasAttr(AttrType.MECHANICAL_SHIELD)){ B.Add(Your() + " shield moves to protect it from harm. ",this); return true; } if(dmg.major_damage){ if(HasAttr(AttrType.BLOCKING)){ B.Add(You("block") + "! ",this); //todo: extra effects will go here eventually attrs[AttrType.BLOCKING]--; return true; } else{ if(HasAttr(AttrType.SHIELDED)){ B.Add(Your() + " shield flashes! ",this); attrs[AttrType.SHIELDED]--; return true; } else{ if(HasAttr(AttrType.VULNERABLE)){ attrs[AttrType.VULNERABLE] = 0; if(this == player){ B.Add("Ouch! "); } else{ B.Add(YouAre() + " devastated! ",this); } foreach(Event e in Q.list){ if(!e.dead && e.target == this && e.type == EventType.REMOVE_ATTR && e.attr == AttrType.VULNERABLE){ e.dead = true; } } dmg.amount += R.Roll(3,6); } } } } if(HasAttr(AttrType.INVULNERABLE)){ dmg.amount = 0; } /*if(HasAttr(AttrType.TOUGH) && dmg.damclass == DamageClass.PHYSICAL){ dmg.amount -= 2; } if(HasAttr(AttrType.ARCANE_SHIELDED)){ if(attrs[AttrType.ARCANE_SHIELDED] >= dmg.amount){ attrs[AttrType.ARCANE_SHIELDED] -= dmg.amount; dmg.amount = 0; } else{ dmg.amount -= attrs[AttrType.ARCANE_SHIELDED]; attrs[AttrType.ARCANE_SHIELDED] = 0; } if(!HasAttr(AttrType.ARCANE_SHIELDED)){ B.Add(Your() + " shield fades. ",this); } } if(dmg.damclass == DamageClass.MAGICAL){ dmg.amount -= TotalSkill(SkillType.SPIRIT) / 2; } if(HasAttr(AttrType.DAMAGE_REDUCTION) && dmg.amount > 5){ dmg.amount = 5; }*/ if(dmg.amount > 15 && magic_trinkets.Contains(MagicTrinketType.BELT_OF_WARDING)){ dmg.amount = 15; B.Add(Your() + " " + MagicTrinket.Name(MagicTrinketType.BELT_OF_WARDING) + " softens the blow. ",this); } dmg.amount -= attrs[AttrType.DAMAGE_RESISTANCE]; switch(dmg.type){ case DamageType.NORMAL: if(dmg.amount > 0){ curhp -= dmg.amount; damage_dealt = true; } else{ if(!ice_removed){ B.Add(YouAre() + " undamaged. ",this); } } break; case DamageType.MAGIC: if(dmg.amount > 0){ curhp -= dmg.amount; damage_dealt = true; } else{ if(!ice_removed){ B.Add(YouAre() + " unharmed. ",this); } } break; case DamageType.FIRE: { if(HasAttr(AttrType.IMMUNE_FIRE)){ dmg.amount = 0; //B.Add(the_name + " is immune! ",this); if(HasFeat(FeatType.BOILING_BLOOD) && attrs[AttrType.BLOOD_BOILED] == 5){ RefreshDuration(AttrType.IMMUNE_FIRE,1000); RefreshDuration(AttrType.BLOOD_BOILED,1000,"Your blood cools. "); } } if(dmg.amount > 0){ curhp -= dmg.amount; damage_dealt = true; } else{ if((this == player || dmg.amount > 1) && !ice_removed && !HasAttr(AttrType.IMMUNE_FIRE) && !HasAttr(AttrType.JUST_SEARED)){ B.Add(YouAre() + " unburnt. ",this); } } break; } case DamageType.COLD: { if(HasAttr(AttrType.IMMUNE_COLD)){ dmg.amount = 0; //B.Add(YouAre() + " unharmed. ",this); } if(dmg.amount > 0){ curhp -= dmg.amount; damage_dealt = true; if(type == ActorType.GIANT_SLUG){ B.Add("The cold leaves " + the_name + " vulnerable. ",this); RefreshDuration(AttrType.VULNERABLE,R.Between(7,13)*100,the_name + " is no longer vulnerable. ",this); } } else{ if(!ice_removed && !HasAttr(AttrType.IMMUNE_COLD)){ B.Add(YouAre() + " unharmed. ",this); } } break; } case DamageType.ELECTRIC: { if(HasAttr(AttrType.IMMUNE_ELECTRICITY)){ dmg.amount = 0; } if(dmg.amount > 0){ curhp -= dmg.amount; damage_dealt = true; } else{ if(!ice_removed && !HasAttr(AttrType.IMMUNE_ELECTRICITY)){ B.Add(YouAre() + " unharmed. ",this); } } break; } case DamageType.POISON: if(HasAttr(AttrType.NONLIVING)){ dmg.amount = 0; } if(dmg.amount > 0){ curhp -= dmg.amount; damage_dealt = true; if(type == ActorType.PLAYER){ B.Add("The poison burns! "); } else{ if(R.Roll(1,5) == 5 && !HasAttr(AttrType.PLANTLIKE)){ //hmm B.Add(the_name + " shudders. ",this); } } } break; case DamageType.NONE: break; } } else{ if(curhp > 0){ curhp = 0; } } /*if(dmg.source != null && dmg.source == player && dmg.damclass == DamageClass.PHYSICAL && resisted && !cause_of_death.Contains("arrow")){ Help.TutorialTip(TutorialTopic.Resistance); }*/ if(damage_dealt){ Interrupt(); attrs[AttrType.AMNESIA_STUN] = 0; if(dmg.major_damage){ recover_time = Q.turn + 500; attrs[AttrType.BANDAGED] = 0; } if(HasAttr(AttrType.ASLEEP)){ attrs[AttrType.ASLEEP] = 0; attrs[AttrType.JUST_AWOKE] = 1; if(this == player){ Input.FlushInput(); B.Add("You wake up. "); } } if(dmg.source != null){ if(type != ActorType.PLAYER && dmg.source != this && !HasAttr(AttrType.CONFUSED)){ target = dmg.source; target_location = M.tile[dmg.source.row,dmg.source.col]; if(dmg.source.IsHiddenFrom(this)){ player_visibility_duration = -1; } if(type == ActorType.CAVERN_HAG && dmg.source == player){ attrs[AttrType.COOLDOWN_2] = 1; } if(type == ActorType.CRUSADING_KNIGHT && dmg.source == player && !HasAttr(AttrType.COOLDOWN_1) && !M.wiz_lite && !CanSee(player) && curhp > 0){ List<string> verb = new List<string>{"Show yourself","Reveal yourself","Unfold thyself","Present yourself","Unveil yourself","Make yourself known"}; List<string> adjective = new List<string>{"despicable","filthy","foul","nefarious","vulgar","sorry","unworthy"}; List<string> noun = new List<string>{"villain","blackguard","devil","scoundrel","wretch","cur","rogue"}; //B.Add(TheName(true) + " shouts \"" + verb.Random() + ", " + adjective.Random() + " " + noun.Random() + "!\" "); B.Add("\"" + verb.Random() + ", " + adjective.Random() + " " + noun.Random() + "!\" "); B.Add(the_name + " raises a gauntlet. ",this); B.Add("Sunlight fills the dungeon. "); M.wiz_lite = true; M.wiz_dark = false; Q.Add(new Event((R.Roll(2,20) + 120) * 100,EventType.NORMAL_LIGHTING)); M.Draw(); B.Print(true); attrs[AttrType.COOLDOWN_1]++; foreach(Actor a in M.AllActors()){ if(a != this && a != player && !a.HasAttr(AttrType.BLINDSIGHT) && HasLOS(a)){ a.ApplyStatus(AttrType.BLIND,R.Between(5,9)*100); /*B.Add(a.YouAre() + " blinded! ",a); a.RefreshDuration(AttrType.BLIND,R.Between(5,9)*100,a.YouAre() + " no longer blinded. ",a);*/ } } if(!player.HasAttr(AttrType.BLINDSIGHT) && HasLOS(player)){ //do the player last, so all the previous messages can be seen. player.ApplyStatus(AttrType.BLIND,R.Between(5,9)*100); /*B.Add(player.YouAre() + " blinded! "); player.RefreshDuration(AttrType.BLIND,R.Between(5,9)*100,player.YouAre() + " no longer blinded. ");*/ } } /*if(HasAttr(AttrType.RADIANT_HALO) && !M.wiz_dark && DistanceFrom(dmg.source) <= LightRadius() && HasLOS(dmg.source)){ B.Add(YourVisible() + " radiant halo burns " + dmg.source.TheName(true) + ". ",this,dmg.source); int amount = R.Roll(2,6); if(amount >= dmg.source.curhp){ amount = dmg.source.curhp - 1; } if(dmg.source.curhp > 1){ //this should prevent infinite loops if one haloed entity attacks another dmg.source.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,amount,this); } }*/ if(dmg.source == player && IsFinalLevelDemon() && attrs[AttrType.COOLDOWN_2] < 2){ attrs[AttrType.COOLDOWN_2]++; } } } if(HasAttr(AttrType.SPORE_BURST)){ if(type == ActorType.SPORE_POD){ curhp = 0; if(player.CanSee(this)){ B.Add("The spore pod bursts! ",this); } else{ if(DistanceFrom(player) == 1){ B.Add(YouVisible("burst") + "! "); } } List<Tile> area = tile().AddGaseousFeature(FeatureType.SPORES,18); Event.RemoveGas(area,600,FeatureType.SPORES,12); } else{ if(!HasAttr(AttrType.COOLDOWN_1) && dmg.major_damage){ RefreshDuration(AttrType.COOLDOWN_1,50); //cooldown added mostly to prevent several triggers while surrounded by fire. todo: this cooldown is no longer necessary now that searing is minor damage. B.Add(You("retaliate") + " with a burst of spores! ",this); List<Tile> area = tile().AddGaseousFeature(FeatureType.SPORES,8); Event.RemoveGas(area,600,FeatureType.SPORES,12); } } } if(HasFeat(FeatType.BOILING_BLOOD) && dmg.type != DamageType.POISON){ if(attrs[AttrType.BLOOD_BOILED] < 5){ B.Add("Your blood boils! "); if(attrs[AttrType.BLOOD_BOILED] == 4){ RefreshDuration(AttrType.IMMUNE_FIRE,1000); } GainAttrRefreshDuration(AttrType.BLOOD_BOILED,1000,"Your blood cools. "); if(this == player){ Help.TutorialTip(TutorialTopic.IncreasedSpeed); } } else{ RefreshDuration(AttrType.IMMUNE_FIRE,1000); RefreshDuration(AttrType.BLOOD_BOILED,1000,"Your blood cools. "); } } if(type == ActorType.DREAM_SPRITE && (dmg.source != null || (HasLOE(player) && DistanceFrom(player) <= 12))){ attrs[AttrType.COOLDOWN_2] = 1; } if(type == ActorType.MECHANICAL_KNIGHT){ if(old_hp == 5){ curhp = 0; } else{ if(old_hp == 10){ curhp = 5; switch(R.Roll(3)){ case 1: B.Add(Your() + " arms are destroyed! ",this); attrs[AttrType.COOLDOWN_1] = 1; attrs[AttrType.MECHANICAL_SHIELD] = 0; break; case 2: B.Add(Your() + " legs are destroyed! ",this); attrs[AttrType.COOLDOWN_1] = 2; path.Clear(); target_location = null; break; case 3: B.Add(Your() + " head is destroyed! ",this); attrs[AttrType.COOLDOWN_1] = 3; break; } } } } if(dmg.type == DamageType.FIRE && (type == ActorType.TROLL || type == ActorType.TROLL_BLOODWITCH)){ attrs[AttrType.PERMANENT_DAMAGE] += dmg.amount; //permanent damage doesn't regenerate } if(dmg.type == DamageType.FIRE && type == ActorType.SKITTERMOSS && !HasAttr(AttrType.COOLDOWN_1)){ attrs[AttrType.COOLDOWN_1]++; B.Add("The fire kills " + Your() + " insects. ",this); color = Color.White; } if(type == ActorType.ALASI_SCOUT && old_hp == maxhp){ B.Add("The glow leaves " + Your() + " sword. ",this); attrs[AttrType.KEEPS_DISTANCE] = 0; } } if(curhp <= 0){ if(type == ActorType.PLAYER){ if(magic_trinkets.Contains(MagicTrinketType.PENDANT_OF_LIFE)){ curhp = 1; /*attrs[AttrType.INVULNERABLE]++; Q.Add(new Event(this,1,AttrType.INVULNERABLE));*/ if(R.CoinFlip()){ B.Add("Your pendant glows brightly, then crumbles to dust! "); magic_trinkets.Remove(MagicTrinketType.PENDANT_OF_LIFE); } else{ B.Add("Your pendant glows brightly! "); } } else{ if(cause_of_death.Length > 0 && cause_of_death[0] == '*'){ Global.KILLED_BY = cause_of_death.Substring(1); } else{ Global.KILLED_BY = "killed by " + cause_of_death; } M.Draw(); if(Global.GAME_OVER == false){ B.Add("You die. "); } B.PrintAll(); Global.GAME_OVER = true; return false; } } else{ if(HasAttr(AttrType.BOSS_MONSTER)){ M.Draw(); B.Add("The fire drake dies. "); B.PrintAll(); if(player.curhp > 0){ B.Add("The threat to your nation has been slain! You begin the long trek home to deliver the good news... "); Global.KILLED_BY = "Died of ripe old age"; } else{ B.Add("The threat to your nation has been slain! Unfortunately, you won't be able to deliver the news... "); } B.PrintAll(); Global.GAME_OVER = true; Global.BOSS_KILLED = true; } if(dmg.amount < 1000){ //everything that deals this much damage prints its own message if(type == ActorType.BERSERKER){ if(!HasAttr(AttrType.COOLDOWN_1)){ attrs[AttrType.COOLDOWN_1]++; Q.Add(new Event(this,R.Between(3,5)*100,AttrType.COOLDOWN_1)); //changed from 350 Q.KillEvents(this,AttrType.COOLDOWN_2); if(!HasAttr(AttrType.COOLDOWN_2)){ attrs[AttrType.COOLDOWN_2] = DirectionOf(player); } B.Add(the_name + " somehow remains standing! He screams with fury! ",this); } return true; } if(type == ActorType.GHOST){ Event e = Q.FindTargetedEvent(this,EventType.TOMBSTONE_GHOST); if(e != null){ e.dead = true; Q.Add(new Event(null,e.area,R.Between(3,6)*100,EventType.TOMBSTONE_GHOST)); } } if(HasAttr(AttrType.REGENERATES_FROM_DEATH) && dmg.type != DamageType.FIRE){ B.Add(the_name + " collapses, still twitching. ",this); } else{ if(HasAttr(AttrType.REASSEMBLES)){ if(Weapon.IsBlunt(dmg.weapon_used) && R.CoinFlip()){ B.Add(the_name + " is smashed to pieces. ",this); attrs[AttrType.REASSEMBLES] = 0; } else{ B.Add(the_name + " collapses into a pile of bones. ",this); } } else{ if(!HasAttr(AttrType.BOSS_MONSTER) && type != ActorType.SPORE_POD){ if(HasAttr(AttrType.NONLIVING)){ B.Add(the_name + " is destroyed. ",this); } else{ if(type == ActorType.FINAL_LEVEL_CULTIST && dmg.type == DamageType.FIRE){ B.Add(the_name + " is consumed by flames. ",this); List<int> valid_circles = new List<int>(); for(int i=0;i<5;++i){ if(M.FinalLevelSummoningCircle(i).PositionsWithinDistance(2,M.tile).Any(x=>M.tile[x].Is(TileType.DEMONIC_IDOL))){ valid_circles.Add(i); } } int nearest = valid_circles.WhereLeast(x=>DistanceFrom(M.FinalLevelSummoningCircle(x))).Random(); pos circle = M.FinalLevelSummoningCircle(nearest); if(M.actor[circle] != null){ circle = circle.PositionsWithinDistance(3,M.tile).Where(x=>M.tile[x].passable && M.actor[x] == null).Random(); } M.final_level_cultist_count[nearest]++; if(M.final_level_cultist_count[nearest] >= 5){ M.final_level_cultist_count[nearest] = 0; List<ActorType> valid_types = new List<ActorType>{ActorType.MINOR_DEMON}; if(M.final_level_demon_count > 3){ valid_types.Add(ActorType.FROST_DEMON); } if(M.final_level_demon_count > 5){ valid_types.Add(ActorType.BEAST_DEMON); } if(M.final_level_demon_count > 11){ valid_types.Add(ActorType.DEMON_LORD); } if(M.final_level_demon_count > 21){ //eventually, the majority will be demon lords valid_types.Add(ActorType.DEMON_LORD); } if(M.final_level_demon_count > 25){ valid_types.Add(ActorType.DEMON_LORD); } if(M.final_level_demon_count > 31){ valid_types.Add(ActorType.DEMON_LORD); } if(M.final_level_demon_count > 36){ valid_types.Add(ActorType.DEMON_LORD); valid_types.Add(ActorType.DEMON_LORD); valid_types.Add(ActorType.DEMON_LORD); } if(player.CanSee(M.tile[circle])){ B.Add("The flames leap and swirl, and a demon appears! "); } else{ B.Add("You feel an evil presence. "); } Create(valid_types.Random(),circle.row,circle.col); if(M.actor[circle] != null){ M.actor[circle].player_visibility_duration = -1; M.actor[circle].attrs[AttrType.PLAYER_NOTICED] = 1; if(M.actor[circle].type != ActorType.DEMON_LORD){ M.actor[circle].attrs[AttrType.NO_ITEM] = 1; } } M.final_level_demon_count++; } } else{ B.Add(the_name + " dies. ",this); } } } if(IsFinalLevelDemon()){ bool circles = false; bool demons = false; for(int i=0;i<5;++i){ Tile circle = M.tile[M.FinalLevelSummoningCircle(i)]; if(circle.TilesWithinDistance(3).Any(x=>x.type == TileType.DEMONIC_IDOL)){ circles = true; break; } } foreach(Actor a in M.AllActors()){ if(a != this && a.IsFinalLevelDemon()){ demons = true; break; } } if(!circles && !demons){ //victory curhp = 100; B.Add("As the last demon falls, your victory gives you a new surge of strength. "); B.PrintAll(); B.Add("Kersai's summoning has been stopped. His cult will no longer threaten the area. "); B.PrintAll(); B.Add("You begin the journey home to deliver the news. "); B.PrintAll(); Global.GAME_OVER = true; Global.BOSS_KILLED = true; Global.KILLED_BY = "nothing"; return false; } } if(HasAttr(AttrType.REGENERATES_FROM_DEATH) && dmg.type == DamageType.FIRE){ attrs[AttrType.REGENERATES_FROM_DEATH] = 0; } } } } if(HasAttr(AttrType.TURN_INTO_CORPSE)){ attrs[AttrType.CORPSE] = attrs[AttrType.TURN_INTO_CORPSE]; attrs[AttrType.TURN_INTO_CORPSE] = 0; if(!HasAttr(AttrType.NO_CORPSE_KNOCKBACK)){ if(HasAttr(AttrType.NONLIVING)){ SetName("destroyed " + name); } else{ SetName(name + "'s corpse"); } } return false; } if(HasAttr(AttrType.BURNING)){ tile().AddFeature(FeatureType.FIRE); } if(LightRadius() > 0){ UpdateRadius(LightRadius(),0); } if(type == ActorType.SHADOW){ type = ActorType.ZOMBIE; //awful awful hack. (CalculateDimming checks for Shadows) CalculateDimming(); type = ActorType.SHADOW; } if(HasAttr(AttrType.REGENERATES_FROM_DEATH)){ Tile troll = null; foreach(Tile t in M.ReachableTilesByDistance(row,col,false)){ if(!t.Is(TileType.DOOR_O) && !t.Is(FeatureType.TROLL_CORPSE,FeatureType.TROLL_BLOODWITCH_CORPSE,FeatureType.BONES)){ if(type == ActorType.TROLL){ t.AddFeature(FeatureType.TROLL_CORPSE); } else{ t.AddFeature(FeatureType.TROLL_BLOODWITCH_CORPSE); } troll = t; break; } } if(curhp > -3){ curhp = -3; } //curhp -= R.Roll(10)+5; if(curhp < -50){ curhp = -50; } Event e = new Event(troll,100,EventType.REGENERATING_FROM_DEATH); e.value = curhp; e.secondary_value = attrs[AttrType.PERMANENT_DAMAGE]; e.tiebreaker = tiebreakers.IndexOf(this); Q.Add(e); } if(HasAttr(AttrType.REASSEMBLES)){ Tile sk = null; foreach(Tile t in M.ReachableTilesByDistance(row,col,false)){ if(!t.Is(TileType.DOOR_O) && !t.Is(FeatureType.TROLL_CORPSE,FeatureType.TROLL_BLOODWITCH_CORPSE,FeatureType.BONES)){ if(type == ActorType.SKELETON){ t.AddFeature(FeatureType.BONES); } sk = t; break; } } Event e = new Event(sk,R.Between(10,20)*100,EventType.REASSEMBLING); e.tiebreaker = tiebreakers.IndexOf(this); Q.Add(e); } if(type == ActorType.STONE_GOLEM){ List<Tile> deleted = new List<Tile>(); while(true){ bool changed = false; foreach(Tile t in TilesWithinDistance(3)){ if(t.Is(TileType.STALAGMITE) && HasLOE(t)){ t.Toggle(null); deleted.Add(t); changed = true; } } if(!changed){ break; } } Q.RemoveTilesFromEventAreas(deleted,EventType.STALAGMITE); List<Tile> area = new List<Tile>(); foreach(Tile t in TilesWithinDistance(3)){ if((t.IsTrap() || t.Is(TileType.FLOOR,TileType.GRAVE_DIRT,TileType.GRAVEL)) && t.inv == null && (t.actor() == null || t.actor() == this) && HasLOE(t)){ if(R.CoinFlip()){ area.Add(t); } } } if(area.Count > 0){ foreach(Tile t in area){ TileType previous_type = t.type; t.Toggle(null,TileType.STALAGMITE); t.toggles_into = previous_type; } Q.Add(new Event(area,150,EventType.STALAGMITE,5)); } } if(type == ActorType.VULGAR_DEMON && DistanceFrom(player) == 1){ B.Add("The vulgar demon possesses your " + player.EquippedWeapon + "! "); B.Print(true); player.EquippedWeapon.status[EquipmentStatus.POSSESSED] = true; Help.TutorialTip(TutorialTopic.Possessed); } if(type == ActorType.DREAM_SPRITE){ int num = R.Roll(5) + 4; List<Tile> new_area = tile().AddGaseousFeature(FeatureType.PIXIE_DUST,num); if(new_area.Count > 0){ Event.RemoveGas(new_area,400,FeatureType.PIXIE_DUST,25); } } if(type == ActorType.FROSTLING){ if(player.CanSee(tile()) && player.HasLOS(tile())){ AnimateExplosion(this,2,Color.RandomIce,'*'); B.Add("The air freezes around the defeated frostling. ",this); } foreach(Tile t in TilesWithinDistance(2)){ if(HasLOE(t)){ t.ApplyEffect(DamageType.COLD); Actor a = t.actor(); if(a != null && a != this){ a.ApplyFreezing(); } } } } if(player.HasAttr(AttrType.CONVICTION)){ player.attrs[AttrType.KILLSTREAK]++; } if(HasAttr(AttrType.GRABBING)){ Actor grabbed = ActorInDirection(attrs[AttrType.GRABBING]); if(grabbed != null && grabbed.HasAttr(AttrType.GRABBED)){ grabbed.attrs[AttrType.GRABBED]--; } } if(HasAttr(AttrType.HUMANOID_INTELLIGENCE) || type == ActorType.ZOMBIE){ if(R.OneIn(3) && !HasAttr(AttrType.NO_ITEM)){ tile().GetItem(Item.Create(Item.RandomItem(),-1,-1)); } } foreach(Item item in inv){ tile().GetItem(item); } Q.KillEvents(this,EventType.ANY_EVENT); M.RemoveTargets(this); int idx = tiebreakers.IndexOf(this); if(idx != -1){ tiebreakers[idx] = null; } if(group != null){ if(type == ActorType.DREAM_WARRIOR || type == ActorType.DREAM_SPRITE){ List<Actor> temp = new List<Actor>(); foreach(Actor a in group){ if(a != this){ temp.Add(a); a.group = null; } } foreach(Actor a in temp){ a.Kill(); } } else{ if(group.Count >= 2 && this == group[0] && HasAttr(AttrType.WANDERING)){ if(type != ActorType.NECROMANCER){ group[1].attrs[AttrType.WANDERING]++; } } if(group.Count <= 2 || type == ActorType.NECROMANCER){ foreach(Actor a in group){ if(a != this){ a.group = null; } } group.Clear(); group = null; } else{ group.Remove(this); group = null; } } } M.actor[row,col] = null; return false; } } else{ if(HasFeat(FeatType.FEEL_NO_PAIN) && damage_dealt && curhp < 20 && old_hp >= 20){ B.Add("You can feel no pain! "); attrs[AttrType.INVULNERABLE]++; Q.Add(new Event(this,500,AttrType.INVULNERABLE,"You can feel pain again. ")); } if(magic_trinkets.Contains(MagicTrinketType.CLOAK_OF_SAFETY) && damage_dealt && dmg.amount >= curhp){ B.PrintAll(); M.Draw(); if(B.YesOrNoPrompt("Your cloak starts to vanish. Use your cloak to escape?",false)){ PosArray<bool> good = new PosArray<bool>(ROWS,COLS); foreach(Tile t in M.AllTiles()){ if(t.passable){ good[t.row,t.col] = true; } else{ good[t.row,t.col] = false; } } foreach(Actor a in M.AllActors()){ foreach(Tile t in M.AllTiles()){ if(good[t.row,t.col]){ if(a.DistanceFrom(t) < 6 || a.HasLOS(t.row,t.col)){ //was CanSee, but this is safer good[t.row,t.col] = false; } } } } List<Tile> tilelist = new List<Tile>(); Tile destination = null; for(int i=4;i<COLS;++i){ foreach(pos p in PositionsAtDistance(i)){ if(good[p]){ tilelist.Add(M.tile[p.row,p.col]); } } if(tilelist.Count > 0){ destination = tilelist[R.Roll(1,tilelist.Count)-1]; break; } } if(destination != null){ Move(destination.row,destination.col); } else{ for(int i=0;i<9999;++i){ int rr = R.Roll(1,ROWS-2); int rc = R.Roll(1,COLS-2); if(M.tile[rr,rc].passable && M.actor[rr,rc] == null && DistanceFrom(rr,rc) >= 6 && !M.tile[rr,rc].IsTrap()){ Move(rr,rc); break; } } } B.Add("You escape. "); } B.Add("Your cloak vanishes completely! "); magic_trinkets.Remove(MagicTrinketType.CLOAK_OF_SAFETY); } } return true; }
public PosArray<colorchar> GetCurrentScreen() { int rows = memory.objs.GetLength(0); int cols = memory.objs.GetLength(1); PosArray<colorchar> result = new PosArray<colorchar>(rows,cols); for(int i=0;i<rows;++i){ for(int j=0;j<cols;++j){ result[i,j] = memory[i,j]; } } return result; }
public static void CreateMap() { tile = new PosArray<Tile>(ROWS,COLS); actor = new PosArray<Actor>(ROWS,COLS); var noise = U.GetNoise(ROWS,COLS); //float min = 999.0f; //float max = -999.0f; cutoff_elevation = -5; for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ /*if(noise[i,j] > max){ max = noise[i,j]; } if(noise[i,j] < min){ min = noise[i,j]; }*/ //Tile.Create(i,j,0); Tile.Create(i,j,(int)(noise[i,j] * 6.0f)); //was 9.0 (but that was too much) if(tile[i,j].elevation - 4 < cutoff_elevation){ cutoff_elevation = tile[i,j].elevation - 5; } //Tile.Create(i,j,R.Between(-2,2)); } } }
public void GenerateFeatures(PosArray<CellType> map,List<pos> interesting_tiles) { List<DungeonFeature> features = new List<DungeonFeature>(); foreach(DungeonFeature df in Enum.GetValues(typeof(DungeonFeature))){ features.Add(df); } int[] rarity = null; switch(level_types[current_level-1]){ case LevelType.Standard: rarity = new int[]{30,40,15,30, 25,6,8,15,15,3,3,4,4,4}; break; case LevelType.Cave: rarity = new int[]{30,15,10,15, 15,100,8,10,30,5,25,6,3,4}; break; case LevelType.Mine: rarity = new int[]{30,20,5,16, 18,6,7,15,10,3,5,30,1,1}; break; case LevelType.Hive: rarity = new int[]{30,15,100,50, 50,100,4,10,10,100,100,15,25,0}; break; case LevelType.Fortress: rarity = new int[]{30,100,100,100, 100,1,8,25,8,6,1,100,20,15}; break; case LevelType.Garden: rarity = new int[]{20,50,50,0, 20,30,20,20,20,20,5,5,8,15}; break; case LevelType.Crypt: rarity = new int[]{30,50,50,25, 25,30,8,10,15,20,30,5,8,8}; break; case LevelType.Slime: //todo default: rarity = new int[]{30,20,15,12, 10,4,8,10,7,3,3,3,4,10}; break; } /*int[] rarity = new int[]{30,20,15,12, 10,4,8,10,7,3,3,3,4}; int[] frequency = new int[]{1,1,2,2,3,3,3, 4,4,4,4,2,2,5,5,5,6,5,5,8};*/ int[] removal_chance = new int[]{95,20,10,60, 30,25,70,50,60,35,12,10,10,20}; /*List<DungeonFeature> feature_pool = new List<DungeonFeature>(); for(int i=0;i<20;++i){ for(int j=frequency[i];j>0;--j){ feature_pool.Add(features[i]); } }*/ List<DungeonFeature> feature_pool = new List<DungeonFeature>(); while(feature_pool.Count < 3){ feature_pool.Clear(); for(int i=0;i<14;++i){ if(rarity[i] > 0 && R.OneIn(rarity[i])){ feature_pool.Add(features[i]); } } } List<DungeonFeature> selected_features = new List<DungeonFeature>(); for(int i=0;i<5 && feature_pool.Count > 0;++i){ selected_features.Add(feature_pool.RemoveRandom()); } List<DungeonFeature> result = new List<DungeonFeature>(); for(int count=5;count>0 && selected_features.Count > 0;--count){ DungeonFeature df = selected_features.Random(); if(R.PercentChance(removal_chance[(int)df])){ selected_features.Remove(df); } result.Add(df); } List<pos> thin_walls = null; if(result.Contains(DungeonFeature.CRACKED_WALL)){ thin_walls = map.AllPositions().Where(x=>map[x].IsWall() && x.HasOppositePairWhere(true,y=>y.BoundsCheck(tile) && map[y].IsFloor())); } while(result.Count > 0){ DungeonFeature df = result.RemoveRandom(); switch(df){ case DungeonFeature.POOL_OF_RESTORATION: case DungeonFeature.FIRE_PIT: { for(int i=0;i<50;++i){ int rr = R.Roll(ROWS-4)+1; int rc = R.Roll(COLS-4)+1; if(interesting_tiles.Count > 0){ pos p = interesting_tiles.RemoveRandom(); rr = p.row; rc = p.col; map[p] = CellType.RoomInterior; } if(map[rr,rc].IsFloor()){ bool floors = true; foreach(pos p in new pos(rr,rc).PositionsAtDistance(1,map)){ if(!map[p].IsFloor()){ floors = false; break; } } if(floors){ if(df == DungeonFeature.POOL_OF_RESTORATION){ map[rr,rc] = CellType.Pool; } if(df == DungeonFeature.FIRE_PIT){ map[rr,rc] = CellType.FirePit; } break; } } } break; } case DungeonFeature.BARREL: case DungeonFeature.TORCH: for(int i=0;i<50;++i){ int rr = R.Roll(ROWS-2); int rc = R.Roll(COLS-2); if(map[rr,rc].IsRoomType() && map[rr,rc].IsFloor()){ if(df == DungeonFeature.BARREL){ map[rr,rc] = CellType.Barrel; } if(df == DungeonFeature.TORCH){ map[rr,rc] = CellType.Torch; } break; } } break; case DungeonFeature.WEBS: case DungeonFeature.RUBBLE: { for(int i=0;i<50;++i){ int rr = R.Roll(ROWS-2); int rc = R.Roll(COLS-2); if(map[rr,rc].IsRoomType()){ CellType cell = CellType.Webs; int max_radius = 2; switch(df){ case DungeonFeature.WEBS: cell = CellType.Webs; max_radius = 3; break; case DungeonFeature.RUBBLE: cell = CellType.Rubble; max_radius = 2; break; } map[rr,rc] = cell; for(int j=1;j<=max_radius;++j){ List<pos> added = new List<pos>(); foreach(pos p in new pos(rr,rc).PositionsWithinDistance(j,map)){ if(map[p] == cell){ foreach(pos neighbor in p.CardinalAdjacentPositions()){ if(map[neighbor].IsFloor() && R.CoinFlip()){ added.AddUnique(neighbor); } } } } foreach(pos p in added){ /*if(df == DungeonFeature.RUBBLE){ foreach(pos neighbor in p.CardinalAdjacentPositions()){ if(!added.Contains(neighbor) && map[neighbor].IsFloor() && R.OneIn(3)){ map[neighbor] = CellType.Gravel; } } }*/ map[p] = cell; } } break; } } break; } case DungeonFeature.SLIME: case DungeonFeature.OIL: { for(int i=0;i<50;++i){ int rr = R.Roll(ROWS-2); int rc = R.Roll(COLS-2); if(map[rr,rc].IsFloor()){ CellType cell = CellType.Wall; int max_radius = 2; switch(df){ case DungeonFeature.SLIME: cell = CellType.Slime; max_radius = 3; break; case DungeonFeature.OIL: cell = CellType.Oil; max_radius = 3; break; } map[rr,rc] = cell; for(int j=1;j<=max_radius;++j){ List<pos> added = new List<pos>(); foreach(pos p in new pos(rr,rc).PositionsWithinDistance(j,map)){ if(map[p] == cell){ foreach(pos neighbor in p.CardinalAdjacentPositions()){ if(map[neighbor].IsFloor() && R.CoinFlip()){ added.AddUnique(neighbor); } } } } foreach(pos p in added){ map[p] = cell; } } break; } } break; } case DungeonFeature.FIRE_GEYSER: { for(int i=0;i<50;++i){ int rr = R.Roll(ROWS-4)+1; int rc = R.Roll(COLS-4)+1; if(map[rr,rc].IsFloor()){ bool floors = true; foreach(pos p in new pos(rr,rc).PositionsAtDistance(1,map)){ if(!map[p].IsFloor()){ floors = false; break; } } if(floors){ map[rr,rc] = CellType.Geyser; break; } } } break; } case DungeonFeature.VINES: { for(int i=0;i<500;++i){ int rr = R.Roll(ROWS-2); int rc = R.Roll(COLS-2); pos p = new pos(rr,rc); if(map[p].IsRoomType() && p.HasAdjacentWhere(x=>map.BoundsCheck(x) && map[x].IsWall())){ PosArray<bool> vine = map.GetFloodFillArray(p,false,x=>map[x].IsRoomType() && x.HasAdjacentWhere(y=>map.BoundsCheck(y) && map[y].IsWall()) && !R.OneIn(3)); //changed from one in 6 so vines won't fill caves so often rr = R.Roll(ROWS-2); rc = R.Roll(COLS-2); pos p2 = new pos(rr,rc); PosArray<bool> new_vine = new PosArray<bool>(ROWS,COLS); int max = Math.Max(ROWS,COLS); for(int dist=0;dist<max;++dist){ bool found = false; foreach(pos possible_vine in p2.PositionsAtDistance(dist)){ if(possible_vine.BoundsCheck(new_vine,false)){ found = true; if(vine[possible_vine] && possible_vine.PositionsAtDistance(1,new_vine).Where(x=>new_vine[x] || map[x] == CellType.Vine).Count < 3){ new_vine[possible_vine] = true; } } } if(!found){ break; } } List<pos> added = new List<pos>(); for(int s=1;s<ROWS-1;++s){ for(int t=1;t<COLS-1;++t){ if(new_vine[s,t]){ pos neighbor = new pos(s,t); foreach(int dir in U.FourDirections){ if(R.OneIn(6) && map[neighbor.PosInDir(dir)].IsFloor()){ added.AddUnique(neighbor.PosInDir(dir)); } } } } } foreach(pos neighbor in added){ new_vine[neighbor] = true; } for(int s=1;s<ROWS-1;++s){ for(int t=1;t<COLS-1;++t){ if(new_vine[s,t]){ if(R.OneIn(35)){ map[s,t] = CellType.PoisonBulb; } else{ map[s,t] = CellType.Vine; } } } } break; } } break; } case DungeonFeature.BLAST_FUNGUS: case DungeonFeature.FOG_VENT: case DungeonFeature.POISON_VENT: for(int i=0;i<50;++i){ int rr = R.Roll(ROWS-2); int rc = R.Roll(COLS-2); if(map[rr,rc].IsFloor()){ if(df == DungeonFeature.BLAST_FUNGUS){ map[rr,rc] = CellType.BlastFungus; } if(df == DungeonFeature.FOG_VENT){ map[rr,rc] = CellType.FogVent; } if(df == DungeonFeature.POISON_VENT){ map[rr,rc] = CellType.PoisonVent; } break; } } break; case DungeonFeature.CRACKED_WALL: for(int i=R.Between(2,4);i>0;--i){ if(thin_walls.Count > 0){ map[thin_walls.RemoveRandom()] = CellType.CrackedWall; } } break; } } }
private void InitializeNewLevel() { for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if(actor[i,j] != null){ if(actor[i,j] != player){ actor[i,j].inv.Clear(); actor[i,j].target = null; if(actor[i,j].group != null){ actor[i,j].group.Clear(); actor[i,j].group = null; } } actor[i,j] = null; } if(tile[i,j] != null){ tile[i,j].inv = null; } tile[i,j] = null; } } wiz_lite = false; wiz_dark = false; if(current_level % 2 == 1){ feat_gained_this_level = false; } generated_this_level = new Dict<ActorType, int>(); monster_density = new PosArray<int>(ROWS,COLS); aesthetics = new PosArray<AestheticFeature>(ROWS,COLS); dungeon_description = new PosArray<string>(ROWS,COLS); safetymap = null; travel_map = null; Q.ResetForNewLevel(); last_seen = new colorchar[ROWS,COLS]; Fire.fire_event = null; Fire.burning_objects.Clear(); if(player.IsBurning()){ Fire.AddBurningObject(player); } Actor.tiebreakers = new List<Actor>{player}; Actor.interrupted_path = new pos(-1,-1); }
public void UpdateSafetyMap(params PhysicalObject[] sources) { List<cell> sourcelist = new List<cell>(); foreach(PhysicalObject o in sources){ sourcelist.Add(new cell(o.row,o.col,0)); } IntLocationDelegate get_cost = (r,c) => { if(actor[r,c] != null){ return 20 + (10 * actor[r,c].attrs[AttrType.TURNS_HERE]); //return 20; } else{ if(tile[r,c].Is(TileType.DOOR_C,TileType.RUBBLE)){ return 20; } else{ return 10; } } }; PosArray<int> a = GetDijkstraMap(Global.ROWS,Global.COLS, (s,t)=>!tile[s,t].passable && !tile[s,t].IsDoorType(false), //(s,t)=>tile[s,t].Is(TileType.WALL,TileType.HIDDEN_DOOR,TileType.STONE_SLAB,TileType.STATUE), (u,v) => 10,sourcelist); for(int i=0;i<Global.ROWS;++i){ for(int j=0;j<Global.COLS;++j){ if(a[i,j] != U.DijkstraMin){ if(a[i,j] != U.DijkstraMax && a[i,j] > 200){ a[i,j] = 200; //todo: testing this modification. the idea here is to make more "best" spots to flee to, reducing the draw of the few farthest ones. } a[i,j] = -(a[i,j] * 14) / 10; //changed from 1.2 to 1.4 to test } } } foreach(PhysicalObject o in sources){ a[o.row,o.col] = U.DijkstraMin; //now the player (or other sources) become blocking } UpdateDijkstraMap(a,get_cost); /*foreach(PhysicalObject o in sources){ //add a penalty for tiles adjacent to the player foreach(pos p in o.PositionsAtDistance(1)){ if(a[p] != U.DijkstraMax && a[p] != U.DijkstraMin){ a[p] += 30; } } }*/ safetymap = a; }
public void CalculatePoppyDistanceMap() { poppy_distance_map = tile.GetDijkstraMap(x=>tile[x].passable && !tile[x].Is(TileType.POPPY_FIELD),x=>!tile[x].Is(TileType.POPPY_FIELD)); }
public PosArray<CellType> GenerateMap(LevelType type) { PosArray<CellType> result = new PosArray<CellType>(ROWS,COLS); Dungeon d = new Dungeon(ROWS,COLS); switch(type){ case LevelType.Standard: while(true){ d.CreateBasicMap(); d.ConnectDiagonals(); d.RemoveUnconnectedAreas(); d.RemoveDeadEndCorridors(); d.AddDoors(25); d.AlterRooms(5,2,2,1,0); d.MarkInterestingLocations(); d.RemoveUnconnectedAreas(); if(d.NumberOfFloors() < 320 || d.HasLargeUnusedSpaces(300)){ d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } return result; } } case LevelType.Cave: { int roll = R.Roll(2); if(R.OneIn(20)){ roll = 3; } switch(roll){ //three different algorithms case 1: { while(true){ d.FillWithRandomWalls(25); d.ApplyCellularAutomataXYRule(3); d.ConnectDiagonals(); d.ImproveMapEdges(5); d.RemoveDeadEndCorridors(); d.RemoveUnconnectedAreas(); d.MarkInterestingLocationsNonRectangular(); if(d.NumberOfFloors() < 320 || d.HasLargeUnusedSpaces(300)){ d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } return result; } } } case 2: { while(true){ d.CreateTwistyCave(true,40); U.DefaultMetric = DistanceMetric.Manhattan; var dijk = d.map.GetDijkstraMap(x=>!d.map[x].IsWall(),x=>false); for(int i=1;i<ROWS-1;++i){ for(int j=1;j<COLS-1;++j){ U.DefaultMetric = DistanceMetric.Manhattan; if(dijk[i,j] == 1){ pos p = new pos(i,j); List<pos> floors = null; foreach(int dir in U.FourDirections){ pos n = p.PosInDir(dir); if(dijk[n] == 1){ if(floors == null){ floors = p.PositionsAtDistance(1,dijk).Where(x=>dijk[x] == 0); } List<pos> floors2 = new List<pos>(); foreach(pos n2 in n.PositionsAtDistance(1,dijk)){ if(dijk[n2] == 0 && !floors.Contains(n2)){ floors2.Add(n2); } } if(floors2.Count > 0 && R.OneIn(5)){ //IIRC this checks each pair twice, so that affects the chance here pos f1 = floors.Random(); pos f2 = floors2.Random(); U.DefaultMetric = DistanceMetric.Chebyshev; int dist = d.map.PathingDistanceFrom(f1,f2,x=>!d.map[x].IsPassable() && d.map[x] != CellType.Door); if(dist > 22 || (dist > 8 && R.OneIn(4))){ CellType rubble = R.OneIn(8)? CellType.Rubble : CellType.CorridorIntersection; d[p] = R.OneIn(3)? rubble : CellType.CorridorIntersection; d[n] = R.OneIn(3)? rubble : CellType.CorridorIntersection; List<pos> neighbors = new List<pos>(); foreach(pos nearby in p.PositionsAtDistance(1)){ if(nearby.BoundsCheck(d.map,false) && nearby.DistanceFrom(n) == 1){ neighbors.Add(nearby); } } while(neighbors.Count > 0){ pos neighbor = neighbors.RemoveRandom(); if(R.OneIn(neighbors.Count + 3) && !d.SeparatesMultipleAreas(neighbor)){ d[neighbor] = R.OneIn(2)? CellType.Rubble : CellType.CorridorIntersection; } } } } break; } } } } } /*List<pos> thin_walls = d.map.AllPositions().Where(x=>d.map[x].IsWall() && x.HasOppositePairWhere(true,y=>y.BoundsCheck() && d.map[y].IsFloor())); while(thin_walls.Count > 0){ pos p = thin_walls.Random(); foreach(int dir in new int[]{8,4}){ if(d.map[p.PosInDir(dir)] != CellType.Wall && d.map[p.PosInDir(dir.RotateDir(true,4))] != CellType.Wall){ var dijkstra = d.map.GetDijkstraMap(x=>d[x] == CellType.Wall,new List<pos>{p.PosInDir(dir)}); //todo: this would be better as "get distance" if(Math.Abs(dijkstra[p.PosInDir(dir)] - dijkstra[p.PosInDir(dir.RotateDir(true,4))]) > 30){ d.map[p] = CellType.CorridorIntersection; break; } } } thin_walls.Remove(p); //todo: move thin-wall-removal to schism }*/ d.ConnectDiagonals(); d.RemoveUnconnectedAreas(); d.ImproveMapEdges(5); d.SmoothCorners(60); d.RemoveDeadEndCorridors(); d.MarkInterestingLocationsNonRectangular(); if(d.NumberOfFloors() < 320 || d.HasLargeUnusedSpaces(300)){ d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } U.DefaultMetric = DistanceMetric.Chebyshev; return result; } } } case 3: { d.RoomHeightMax = 3; d.RoomWidthMax = 3; while(true){ int successes = 0; int consecutive_failures = 0; while(successes < 13){ if(d.CreateRoom()){ ++successes; consecutive_failures = 0; } else{ if(consecutive_failures++ >= 50){ d.Clear(); successes = 0; consecutive_failures = 0; } } } d.CaveWidenRooms(100,50); d.AddRockFormations(40,2); List<pos> thin_walls = d.map.AllPositions().Where(x=>d.map[x].IsWall() && x.HasOppositePairWhere(true,y=>y.BoundsCheck(tile) && d.map[y].IsFloor())); while(!d.IsFullyConnected() && thin_walls.Count > 0){ pos p = thin_walls.Random(); d.map[p] = CellType.CorridorIntersection; foreach(pos neighbor in p.PositionsWithinDistance(1,d.map)){ thin_walls.Remove(neighbor); } } d.ConnectDiagonals(); d.RemoveDeadEndCorridors(); d.RemoveUnconnectedAreas(); d.MarkInterestingLocationsNonRectangular(); if(d.NumberOfFloors() < 320 || d.HasLargeUnusedSpaces(300)){ //todo: add 'proper coverage' check here - make sure it stretches across enough of the map. d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } return result; } } } } break; } case LevelType.Hive: { d.RoomHeightMax = 3; d.RoomWidthMax = 3; while(true){ int successes = 0; int consecutive_failures = 0; while(successes < 35){ if(d.CreateRoom()){ ++successes; consecutive_failures = 0; } else{ if(consecutive_failures++ >= 40){ d.Clear(); successes = 0; consecutive_failures = 0; } } } d.CaveWidenRooms(100,10); d.CaveWidenRooms(3,20); List<pos> thin_walls = d.map.AllPositions().Where(x=>d.map[x].IsWall() && x.HasOppositePairWhere(true,y=>y.BoundsCheck(tile) && d.map[y].IsFloor())); while(!d.IsFullyConnected() && thin_walls.Count > 0){ pos p = thin_walls.Random(); d.map[p] = CellType.CorridorIntersection; foreach(pos neighbor in p.PositionsWithinDistance(2,d.map)){ thin_walls.Remove(neighbor); } } d.ConnectDiagonals(); d.RemoveDeadEndCorridors(); d.RemoveUnconnectedAreas(); d.MarkInterestingLocations(); //to find rooms big enough for stuff in the center: //var dijkstra = d.map.GetDijkstraMap(x=>d.map[x].IsWall(),d.map.AllPositions().Where(x=>d.map[x].IsWall() && x.HasAdjacentWhere(y=>d.map.BoundsCheck(y) && !d.map[y].IsWall()))); if(d.NumberOfFloors() < 340 || d.HasLargeUnusedSpaces(300)){ //todo: add 'proper coverage' check here - make sure it stretches across enough of the map. d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } return result; } } } case LevelType.Mine: { d.CorridorExtraLengthChance = 0; d.CorridorChainSizeMax = 10; while(true){ d.RoomHeightMin = 8; d.RoomWidthMin = 8; d.RoomHeightMax = 8; d.RoomWidthMax = 10; d.MinimumSpaceBetweenCorridors = 3; d.CorridorLengthMin = 3; d.CorridorLengthMax = 5; while(!d.CreateRoom()){} d.RoomHeightMin = 5; d.RoomWidthMin = 5; d.RoomHeightMax = 5; d.RoomWidthMax = 5; while(!d.CreateRoom()){} while(!d.CreateRoom()){} /*for(int i=0;i<10;++i){ d.CreateRoom(); } d.AddRockFormations(100,2);*/ d.MinimumSpaceBetweenCorridors = 5; d.CorridorLengthMin = 4; d.CorridorLengthMax = 12; for(int i=0;i<70;++i){ d.CreateCorridor(); } d.CorridorLengthMin = 3; d.CorridorLengthMax = 5; d.MinimumSpaceBetweenCorridors = 3; for(int i=0;i<350;++i){ d.CreateCorridor(); } d.RemoveUnconnectedAreas(); d.ConnectDiagonals(true); d.RemoveUnconnectedAreas(); d.MarkInterestingLocations(); if(d.NumberOfFloors() < 250 || d.HasLargeUnusedSpaces(300)){ d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } return result; } } } case LevelType.Fortress: while(true){ int H = ROWS; int W = COLS; for(int i=H/2-1;i<H/2+1;++i){ for(int j=1;j<W-1;++j){ if(j==1 || j==W-2){ d.map[i,j] = CellType.RoomCorner; } else{ d.map[i,j] = CellType.RoomEdge; } } } for(int i=0;i<700;++i){ if(R.OneIn(5)){ d.CreateCorridor(); } else{ d.CreateRoom(); } } bool reflect_features = R.PercentChance(80); if(reflect_features){ d.AddDoors(25); d.AddPillars(30); } d.Reflect(true,false); d.ConnectDiagonals(); d.RemoveDeadEndCorridors(); d.RemoveUnconnectedAreas(); if(!reflect_features){ d.AddDoors(25); d.AddPillars(30); } bool door_right = false; bool door_left = false; int rightmost_door = 0; int leftmost_door = 999; for(int j=0;j<22;++j){ if(d[H/2-2,j].IsCorridorType()){ door_left = true; if(leftmost_door == 999){ leftmost_door = j; } } if(d[H/2-2,W-1-j].IsCorridorType()){ door_right = true; if(rightmost_door == 0){ rightmost_door = W-1-j; } } } if(!door_left || !door_right){ d.Clear(); continue; } for(int j=1;j<leftmost_door-6;++j){ d[H/2-1,j] = CellType.Wall; d[H/2,j] = CellType.Wall; } for(int j=W-2;j>rightmost_door+6;--j){ d[H/2-1,j] = CellType.Wall; d[H/2,j] = CellType.Wall; } for(int j=1;j<W-1;++j){ if(d[H/2-1,j].IsFloor()){ d[H/2-1,j] = CellType.Statue; d[H/2,j] = CellType.Statue; break; } else{ if(d[H/2-1,j] == CellType.Statue){ break; } } } for(int j=W-2;j>0;--j){ if(d[H/2-1,j].IsFloor()){ d[H/2-1,j] = CellType.Statue; d[H/2,j] = CellType.Statue; break; } else{ if(d[H/2-1,j] == CellType.Statue){ break; } } } for(int i=H/2-1;i<H/2+1;++i){ for(int j=1;j<W-1;++j){ if(d[i,j] == CellType.RoomCorner || d[i,j] == CellType.RoomEdge){ d[i,j] = CellType.CorridorIntersection; } } } d.MarkInterestingLocations(); if(d.NumberOfFloors() < 420 || d.HasLargeUnusedSpaces(300)){ d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } return result; } } case LevelType.Slime: while(true){ /*for(int i=1;i<ROWS-1;++i){ for(int j=1;j<COLS-1;++j){ if(d[i,j].IsWall()){ if(!d[i+1,j+1].IsWall()){ d[i,j] = d[i+1,j+1]; } else{ if(!d[i+1,j].IsWall()){ d[i,j] = d[i+1,j]; } else{ if(!d[i,j+1].IsWall()){ d[i,j] = d[i,j+1]; } } } } } }*/ d.CreateBasicMap(); d.ConnectDiagonals(); d.RemoveUnconnectedAreas(); d.AddDoors(25); d.CaveWidenRooms(30,30); d.RemoveDeadEndCorridors(); d.AddPillars(30); d.MarkInterestingLocations(); if(d.NumberOfFloors() < 120 || d.HasLargeUnusedSpaces(300)){ d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } return result; } } case LevelType.Garden: { d.RoomHeightMin = 4; d.RoomHeightMax = 10; d.RoomWidthMin = 4; d.RoomWidthMax = 10; while(true){ d.CreateBasicMap(); d.ConnectDiagonals(); d.RemoveUnconnectedAreas(); d.RemoveDeadEndCorridors(); var dijkstra = d.map.GetDijkstraMap(x=>d[x].IsPassable(),x=>false); List<pos> possible_room_centers = d.map.PositionsWhere(x=>dijkstra[x] == 3 && x.row > 1 && x.row < ROWS-2 && x.col > 1 && x.col < COLS-2); int rooms = 0; while(rooms < 6 && possible_room_centers.Count > 0){ pos p = possible_room_centers.RemoveRandom(); List<int> valid_dirs = new List<int>(); foreach(int dir in U.FourDirections){ pos p2 = p.PosInDir(dir).PosInDir(dir).PosInDir(dir); if(p2.BoundsCheck(d.map) && d[p2].IsPassable() && d[p2] != CellType.RoomCorner){ valid_dirs.Add(dir); } } if(valid_dirs.Count > 0){ foreach(pos neighbor in p.PositionsWithinDistance(1,d.map)){ d[neighbor] = CellType.RoomInterior; } possible_room_centers.RemoveWhere(x=>p.DistanceFrom(x) <= 3); foreach(int dir in valid_dirs){ d[p.PosInDir(dir).PosInDir(dir)] = CellType.CorridorIntersection; } ++rooms; } } CellType water_type = CellType.ShallowWater; if(R.OneIn(8)){ water_type = CellType.Ice; } d.ForEachRectangularRoom((start_r,start_c,end_r,end_c)=>{ int room_height = (end_r - start_r) + 1; int room_width = (end_c - start_c) + 1; if(room_height <= 4 && room_width <= 4){ if(room_height == 3 && room_width == 3){ return true; } List<pos> water = new List<pos>(); if(!new pos(start_r+1,start_c).PositionsAtDistance(1,d.map).Any(x=>d[x].IsCorridorType())){ water.Add(new pos(start_r+1,start_c)); water.Add(new pos(start_r+2,start_c)); } if(!new pos(start_r,start_c+1).PositionsAtDistance(1,d.map).Any(x=>d[x].IsCorridorType())){ water.Add(new pos(start_r,start_c+1)); water.Add(new pos(start_r,start_c+2)); } if(!new pos(end_r-1,end_c).PositionsAtDistance(1,d.map).Any(x=>d[x].IsCorridorType())){ water.Add(new pos(end_r-1,end_c)); water.Add(new pos(end_r-2,end_c)); } if(!new pos(end_r,end_c-1).PositionsAtDistance(1,d.map).Any(x=>d[x].IsCorridorType())){ water.Add(new pos(end_r,end_c-1)); water.Add(new pos(end_r,end_c-2)); } foreach(pos p in water){ d[p] = water_type; } d[start_r,start_c] = CellType.Statue; d[start_r,end_c] = CellType.Statue; d[end_r,start_c] = CellType.Statue; d[end_r,end_c] = CellType.Statue; } else{ CellType center_type = CellType.RoomFeature1; switch(R.Roll(3)){ case 1: center_type = water_type; break; case 2: center_type = CellType.Poppies; break; case 3: center_type = CellType.Brush; break; } bool statues = R.CoinFlip(); CellType statue_type = CellType.Statue; if(room_height <= 8 && room_width <= 8 && R.OneIn(8)){ statue_type = CellType.Torch; } CellType edge_type = CellType.ShallowWater; if(center_type != water_type && !R.OneIn(4)){ edge_type = CellType.ShallowWater; } else{ int vine_chance = 50; if(!statues){ vine_chance = 80; } if(R.PercentChance(vine_chance)){ edge_type = CellType.Vine; } else{ edge_type = CellType.Gravel; } if(R.OneIn(32)){ if(R.CoinFlip()){ edge_type = CellType.Statue; } else{ edge_type = CellType.GlowingFungus; } } } bool gravel = R.OneIn(16); bool edges = R.CoinFlip(); if(room_height < 6 || room_width < 6){ edges = false; } if(room_height >= 8 && room_width >= 8){ edges = !R.OneIn(4); } if(edges){ for(int i=start_r;i<=end_r;++i){ for(int j=start_c;j<=end_c;++j){ if(i == start_r || i == end_r || j == start_c || j == end_c){ //edges if(statues && (i == start_r || i == end_r) && (j == start_c || j == end_c)){ //corners d[i,j] = statue_type; } else{ pos p = new pos(i,j); if(!p.CardinalAdjacentPositions().Any(x=>d[x].IsCorridorType())){ d[i,j] = edge_type; } } } else{ if(i == start_r+1 || i == end_r-1 || j == start_c+1 || j == end_c-1){ //the path if(gravel){ d[i,j] = CellType.Gravel; } } else{ d[i,j] = center_type; } } } } } else{ for(int i=start_r;i<=end_r;++i){ for(int j=start_c;j<=end_c;++j){ if(i == start_r || i == end_r || j == start_c || j == end_c){ if(gravel){ d[i,j] = CellType.Gravel; } } else{ d[i,j] = center_type; } } } } if(center_type == water_type && room_height % 2 == 1 && room_width % 2 == 1){ statue_type = CellType.Statue; if(room_height <= 7 && room_width <= 7 && R.OneIn(12)){ statue_type = CellType.Torch; } d[(start_r+end_r)/2,(start_c+end_c)/2] = statue_type; } } return true; }); d.ConnectDiagonals(); d.RemoveUnconnectedAreas(); d.AddDoors(10); d.RemoveDeadEndCorridors(); d.MarkInterestingLocations(); if(d.NumberOfFloors() < 320 || d.HasLargeUnusedSpaces(300)){ d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } return result; } } } case LevelType.Crypt: { while(true){ pos room1origin = new pos(ROWS/2,R.Roll(COLS/8 - 1) + COLS/8 - 1); pos room2origin = new pos(ROWS/2,R.Roll(COLS/8 - 1) + (COLS*6 / 8) - 1); while(!d.CreateRoom(room1origin.row,room1origin.col)){} //left half while(!d.CreateRoom(room2origin.row,room2origin.col)){} //right half d.CaveWidenRooms(100,150); d.MoveRoom(room1origin,4); d.MoveRoom(room2origin,6); var dijkstra = d.map.GetDijkstraMap(x=>d.map[x] == CellType.Wall,x=>false); //todo: among these Map dijkstra maps I have, like, 3 different ways of testing for walls. are these all correct? int distance_from_walls = 3; List<pos> central_room = d.map.PositionsWhere(x=>dijkstra[x] > distance_from_walls); int required_consecutive = 3; for(int i=0;i<ROWS;++i){ //first, check each row... for(int j=0;j<COLS;++j){ List<pos> this_row = new List<pos>(); while(j < COLS && dijkstra[i,j] > distance_from_walls){ this_row.Add(new pos(i,j)); ++j; } if(this_row.Count < required_consecutive){ foreach(pos p in this_row){ central_room.Remove(p); } } } } for(int j=0;j<COLS;++j){ //...then each column for(int i=0;i<ROWS;++i){ List<pos> this_col = new List<pos>(); while(i < ROWS && dijkstra[i,j] > distance_from_walls){ this_col.Add(new pos(i,j)); ++i; } if(this_col.Count < required_consecutive){ foreach(pos p in this_col){ central_room.Remove(p); } } } } central_room = d.map.GetFloodFillPositions(central_room.Where(x=>x.PositionsWithinDistance(1).All(y=>central_room.Contains(y))),false,x=>central_room.Contains(x)); List<pos> walls = new List<pos>(); foreach(pos p in central_room){ d.map[p] = CellType.InterestingLocation; foreach(pos neighbor in p.PositionsAtDistance(1,d.map)){ if(!central_room.Contains(neighbor)){ d.map[neighbor] = CellType.Wall; walls.Add(neighbor); } } } while(true){ List<pos> potential_doors = new List<pos>(); foreach(pos p in walls){ foreach(int dir in U.FourDirections){ if(d.map[p.PosInDir(dir)] == CellType.InterestingLocation && d.map[p.PosInDir(dir.RotateDir(true,4))].IsRoomType() && d.map[p.PosInDir(dir.RotateDir(true,4))] != CellType.InterestingLocation){ potential_doors.Add(p); break; } } } if(potential_doors.Count > 0){ pos p = potential_doors.Random(); d.map[p] = CellType.Door; List<pos> room = d.map.GetFloodFillPositions(p,true,x=>d.map[x] == CellType.InterestingLocation); foreach(pos p2 in room){ d.map[p2] = CellType.RoomInterior; } } else{ break; } } dijkstra = d.map.GetDijkstraMap(x=>d.map[x] == CellType.Wall,x=>false); int num_chests = 0; d.ForEachRoom(list=>{ if(central_room.Contains(list[0])){ if(num_chests++ < 2){ d[list.Random()] = CellType.Chest; } return true; } List<pos> room = list.Where(x=>dijkstra[x] > 1); int start_r = room.WhereLeast(x=>x.row)[0].row; int end_r = room.WhereGreatest(x=>x.row)[0].row; int start_c = room.WhereLeast(x=>x.col)[0].col; int end_c = room.WhereGreatest(x=>x.col)[0].col; List<List<pos>> offsets = new List<List<pos>>(); for(int i=0;i<4;++i){ offsets.Add(new List<pos>()); } for(int i=start_r;i<=end_r;i+=2){ for(int j=start_c;j<=end_c;j+=2){ if(room.Contains(new pos(i,j))){ offsets[0].Add(new pos(i,j)); } if(i+1 <= end_r && room.Contains(new pos(i+1,j))){ offsets[1].Add(new pos(i+1,j)); } if(j+1 <= end_c && room.Contains(new pos(i,j+1))){ offsets[2].Add(new pos(i,j+1)); } if(i+1 <= end_r && j+1 <= end_c && room.Contains(new pos(i+1,j+1))){ offsets[3].Add(new pos(i+1,j+1)); } } } List<pos> tombstones = offsets.WhereGreatest(x=>x.Count).RandomOrDefault(); if(tombstones != null){ foreach(pos p in tombstones){ d.map[p] = CellType.Tombstone; } } return true; }); for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if(d[i,j] == CellType.Door){ pos p = new pos(i,j); List<pos> potential_statues = p.PositionsAtDistance(1,d.map).Where(x=>!d[x].IsWall() && !central_room.Contains(x) && p.DirectionOf(x) % 2 != 0 && !x.PositionsAtDistance(1,d.map).Any(y=>d[y].Is(CellType.Tombstone))); if(potential_statues.Count == 2){ d[potential_statues[0]] = CellType.Statue; d[potential_statues[1]] = CellType.Statue; } } } } List<pos> room_one = null; List<pos> room_two = null; for(int j=0;j<COLS && room_one == null;++j){ for(int i=0;i<ROWS;++i){ if(d[i,j] != CellType.Wall){ room_one = d.map.GetFloodFillPositions(new pos(i,j),false,x=>!d[x].IsWall()); break; } } } for(int j=COLS-1;j>=0 && room_two == null;--j){ for(int i=0;i<ROWS;++i){ if(d[i,j] != CellType.Wall){ room_two = d.map.GetFloodFillPositions(new pos(i,j),false,x=>!d[x].IsWall()); break; } } } if(room_one.WhereGreatest(x=>x.col).Random().DistanceFrom(room_two.WhereLeast(x=>x.col).Random()) < 12){ d.Clear(); continue; } Dungeon d2 = new Dungeon(ROWS,COLS); int tries = 0; while(tries < 10){ d2.CreateBasicMap(); d2.ConnectDiagonals(); for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if(d[i,j] != CellType.Wall){ pos p = new pos(i,j); foreach(pos neighbor in p.PositionsAtDistance(1,d2.map)){ d2[neighbor] = CellType.Wall; } } } } d2.RemoveUnconnectedAreas(); List<pos> room_one_walls = new List<pos>(); List<pos> room_two_walls = new List<pos>(); for(int i=0;i<ROWS;++i){ for(int j=COLS-1;j>=0;--j){ pos p = new pos(i,j); if(room_one.Contains(p)){ room_one_walls.Add(p); break; } } for(int j=0;j<COLS;++j){ pos p = new pos(i,j); if(room_two.Contains(p)){ room_two_walls.Add(p); break; } } } List<pos> room_one_valid_connections = new List<pos>(); List<pos> room_two_valid_connections = new List<pos>(); foreach(pos p in room_one_walls){ pos next = p.PosInDir(6); while(BoundsCheck(next) && p.DistanceFrom(next) < 7){ if(d2[next] != CellType.Wall){ room_one_valid_connections.Add(p.PosInDir(6)); break; } next = next.PosInDir(6); } } foreach(pos p in room_two_walls){ pos next = p.PosInDir(4); while(BoundsCheck(next) && p.DistanceFrom(next) < 7){ if(d2[next] != CellType.Wall){ room_two_valid_connections.Add(p.PosInDir(4)); break; } next = next.PosInDir(4); } } if(room_one_valid_connections.Count > 0 && room_two_valid_connections.Count > 0){ pos one = room_one_valid_connections.Random(); while(true){ if(d2[one] == CellType.Wall){ d2[one] = CellType.CorridorHorizontal; } else{ break; } one = one.PosInDir(6); } pos two = room_two_valid_connections.Random(); while(true){ if(d2[two] == CellType.Wall){ d2[two] = CellType.CorridorHorizontal; } else{ break; } two = two.PosInDir(4); } break; } else{ d2.Clear(); } ++tries; } if(tries == 10){ d.Clear(); continue; } for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if(d2[i,j] != CellType.Wall){ d[i,j] = d2[i,j]; } } } //d.CaveWidenRooms(100,20); //d.MakeCavesMoreRectangular(4); //d.RemoveDeadEndCorridors(); //d.MakeCavesMoreRectangular(1 + num++ / 10); //d.Clear(); //continue; d.ConnectDiagonals(); d.RemoveUnconnectedAreas(); d.RemoveDeadEndCorridors(); d.MarkInterestingLocations(); d.RemoveUnconnectedAreas(); if(d.NumberOfFloors() < 340 || d.HasLargeUnusedSpaces(350)){ d.Clear(); } else{ for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ result[i,j] = d.map[i,j]; } } return result; } } } } return null; }
public static void UpdateDijkstraMap(PosArray<int> map,IntLocationDelegate get_cost) { PriorityQueue<cell> frontier = new PriorityQueue<cell>(c => -c.value); int height = map.objs.GetLength(0); int width = map.objs.GetLength(1); for(int i=0;i<height;++i){ for(int j=0;j<width;++j){ if(map[i,j] != U.DijkstraMin){ int v = map[i,j]; bool good = true; for(int s=-1;s<=1 && good;++s){ for(int t=-1;t<=1 && good;++t){ if(i+s >= 0 && i+s < height && j+t >= 0 && j+t < width){ if(map[i+s,j+t] < v && map[i+s,j+t] != U.DijkstraMin){ good = false; } } } } if(good){ //find local minima and add them to the frontier frontier.Add(new cell(i,j,v)); } } } } while(frontier.list.Count > 0){ cell c = frontier.Pop(); for(int s=-1;s<=1;++s){ for(int t=-1;t<=1;++t){ if(c.row+s >= 0 && c.row+s < height && c.col+t >= 0 && c.col+t < width){ int cost = get_cost(c.row+s,c.col+t); if(map[c.row+s,c.col+t] > c.value+cost){ map[c.row+s,c.col+t] = c.value+cost; frontier.Add(new cell(c.row+s,c.col+t,c.value+cost)); } } } } } }
//todo: remove dijkstra from this file, use the one in Utility? (do I have UpdateDijkstra in Utility?) public static PosArray<int> GetDijkstraMap(int height,int width,BooleanLocationDelegate is_blocked,IntLocationDelegate get_cost,List<cell> sources) { PriorityQueue<cell> frontier = new PriorityQueue<cell>(c => -c.value); PosArray<int> map = new PosArray<int>(height,width); for(int i=0;i<height;++i){ for(int j=0;j<width;++j){ if(is_blocked(i,j)){ map[i,j] = U.DijkstraMin; } else{ map[i,j] = U.DijkstraMax; } } } foreach(cell c in sources){ map[c.row,c.col] = c.value; frontier.Add(c); } while(frontier.list.Count > 0){ cell c = frontier.Pop(); for(int s=-1;s<=1;++s){ for(int t=-1;t<=1;++t){ if(c.row+s >= 0 && c.row+s < height && c.col+t >= 0 && c.col+t < width){ int cost = get_cost(c.row+s,c.col+t); if(map[c.row+s,c.col+t] > c.value+cost){ map[c.row+s,c.col+t] = c.value+cost; frontier.Add(new cell(c.row+s,c.col+t,c.value+cost)); } } } } } for(int i=0;i<height;++i){ for(int j=0;j<width;++j){ if(map[i,j] == U.DijkstraMax){ map[i,j] = U.DijkstraMin; //any unreachable areas are marked unpassable } } } return map; }
public void MarkInterestingLocationsNonRectangular() { var dijkstra = map.GetDijkstraMap(x=>!map[x].IsPassable(),x=>false); PosArray<int> values = new PosArray<int>(H,W); for(int i=0;i<H;++i){ for(int j=0;j<W;++j){ if(dijkstra[i,j].IsValidDijkstraValue() && dijkstra[i,j] > 1){ values[i,j] = dijkstra[i,j] * 4; foreach(pos p in new pos(i,j).PositionsAtDistance(1,dijkstra)){ if(dijkstra[p].IsValidDijkstraValue()){ values[i,j] += dijkstra[p] * 2; } } foreach(pos p in new pos(i,j).PositionsAtDistance(2,dijkstra)){ if(dijkstra[p].IsValidDijkstraValue()){ values[i,j] += dijkstra[p]; } } } } } for(int i=0;i<H;++i){ for(int j=0;j<W;++j){ if(values[i,j] > 0){ if(!new pos(i,j).PositionsAtDistance(1,values).Any(x=>values[x] > values[i,j])){ map[i,j] = CellType.InterestingLocation; } } } } }
public void AlterRooms(int no_change_freq,int add_pillars_freq,int cross_room_freq,int cave_widen_freq,int cave_fill_freq) { List<int> modification = new List<int>(); for(int i=0;i<no_change_freq;++i){ modification.Add(0); } for(int i=0;i<add_pillars_freq;++i){ modification.Add(1); } for(int i=0;i<cross_room_freq;++i){ modification.Add(2); } for(int i=0;i<cave_widen_freq;++i){ modification.Add(3); } for(int i=0;i<cave_fill_freq;++i){ modification.Add(4); } if(modification.Count == 0){ return; } ForEachRectangularRoom((start_r,start_c,end_r,end_c) => { int mod = modification.Random(); switch(mod){ case 0: return true; case 1: { int height = end_r - start_r + 1; int width = end_c - start_c + 1; if(height > 3 || width > 3){ List<PillarArrangement> layouts = new List<PillarArrangement>(); if(height % 2 == 1 && width % 2 == 1){ layouts.Add(PillarArrangement.Single); } if((height % 2 == 1 || width % 2 == 1) && height != 4 && width != 4){ layouts.Add(PillarArrangement.Row); } if(height >= 5 && width >= 5){ layouts.Add(PillarArrangement.Corners); } if(height > 2 && width > 2 && height != 4 && width != 4){ layouts.Add(PillarArrangement.Full); } if((width % 2 == 1 && width >= 5) || (height % 2 == 1 && height >= 5)){ layouts.Add(PillarArrangement.StatueEdges); } if(layouts.Count == 0 || CoinFlip()){ //otherwise they're too common layouts.Add(PillarArrangement.StatueCorners); } if(layouts.Count > 0){ CellType pillar = CellType.Pillar; switch(layouts.Random()){ case PillarArrangement.Single: map[(start_r + end_r)/2,(start_c + end_c)/2] = pillar; break; case PillarArrangement.Row: { bool vertical; if(width % 2 == 1 && height % 2 == 0){ vertical = true; } else{ if(height % 2 == 1 && width % 2 == 0){ vertical = false; } else{ vertical = CoinFlip(); } } if(vertical){ if(height % 2 == 1){ for(int i=start_r+1;i<=end_r-1;i+=2){ map[i,(start_c + end_c)/2] = pillar; } } else{ int offset = 0; if(height % 4 == 0){ offset = Roll(2) - 1; } for(int i=start_r+1+offset;i<(start_r + end_r)/2;i+=2){ map[i,(start_c + end_c)/2] = pillar; } for(int i=end_r-1-offset;i>(start_r + end_r)/2+1;i-=2){ map[i,(start_c + end_c)/2] = pillar; } } } else{ if(width % 2 == 1){ for(int i=start_c+1;i<=end_c-1;i+=2){ map[(start_r + end_r)/2,i] = pillar; } } else{ int offset = 0; if(width % 4 == 0){ offset = Roll(2) - 1; } for(int i=start_c+1+offset;i<(start_c + end_c)/2;i+=2){ map[(start_r + end_r)/2,i] = pillar; } for(int i=end_c-1-offset;i>(start_c + end_c)/2+1;i-=2){ map[(start_r + end_r)/2,i] = pillar; } } } break; } case PillarArrangement.Corners: { int v_offset = 0; int h_offset = 0; if(height % 4 == 0){ v_offset = Roll(2) - 1; } if(width % 4 == 0){ h_offset = Roll(2) - 1; } map[start_r + 1 + v_offset,start_c + 1 + h_offset] = pillar; map[start_r + 1 + v_offset,end_c - 1 - h_offset] = pillar; map[end_r - 1 - v_offset,start_c + 1 + h_offset] = pillar; map[end_r - 1 - v_offset,end_c - 1 - h_offset] = pillar; break; } case PillarArrangement.Full: { int v_offset = 0; int h_offset = 0; if(height % 4 == 0){ v_offset = Roll(2) - 1; } if(width % 4 == 0){ h_offset = Roll(2) - 1; } int half_r = (start_r + end_r)/2; int half_c = (start_c + end_c)/2; int half_r_offset = (start_r + end_r + 1)/2; int half_c_offset = (start_c + end_c + 1)/2; for(int i=start_r+1+v_offset;i<half_r;i+=2){ for(int j=start_c+1+h_offset;j<half_c;j+=2){ map[i,j] = pillar; } } for(int i=start_r+1+v_offset;i<half_r;i+=2){ for(int j=end_c-1-h_offset;j>half_c_offset;j-=2){ map[i,j] = pillar; } } for(int i=end_r-1-v_offset;i>half_r_offset;i-=2){ for(int j=start_c+1+h_offset;j<half_c;j+=2){ map[i,j] = pillar; } } for(int i=end_r-1-v_offset;i>half_r_offset;i-=2){ for(int j=end_c-1-h_offset;j>half_c_offset;j-=2){ map[i,j] = pillar; } } if((width+1) % 4 == 0){ if(height % 2 == 1){ for(int i=start_r+1;i<=end_r-1;i+=2){ map[i,half_c] = pillar; } } else{ int offset = 0; if(height % 4 == 0){ offset = Roll(2) - 1; } for(int i=start_r+1+offset;i<half_r;i+=2){ map[i,half_c] = pillar; } for(int i=end_r-1-offset;i>half_r_offset;i-=2){ map[i,half_c] = pillar; } } } if((height+1) % 4 == 0){ if(width % 2 == 1){ for(int i=start_c+1;i<=end_c-1;i+=2){ map[half_r,i] = pillar; } } else{ int offset = 0; if(width % 4 == 0){ offset = Roll(2) - 1; } for(int i=start_c+1+offset;i<half_c;i+=2){ map[half_r,i] = pillar; } for(int i=end_c-1-offset;i>half_c_offset;i-=2){ map[half_r,i] = pillar; } } } break; } case PillarArrangement.StatueCorners: map[start_r,start_c] = CellType.Statue; map[start_r,end_c] = CellType.Statue; map[end_r,start_c] = CellType.Statue; map[end_r,end_c] = CellType.Statue; break; case PillarArrangement.StatueEdges: { map[start_r,start_c] = CellType.Statue; map[start_r,end_c] = CellType.Statue; map[end_r,start_c] = CellType.Statue; map[end_r,end_c] = CellType.Statue; if(width % 2 == 1 && width > 3){ int half_c = (start_c + end_c)/2; int corridors = new pos(start_r,half_c).CardinalAdjacentPositions().Where(x => BoundsCheck(x) && map[x].IsCorridorType()).Count; if(corridors == 0){ map[start_r,half_c] = CellType.Statue; } corridors = new pos(end_r,half_c).CardinalAdjacentPositions().Where(x => BoundsCheck(x) && map[x].IsCorridorType()).Count; if(corridors == 0){ map[end_r,half_c] = CellType.Statue; } } if(height % 2 == 1 && height > 3){ int half_r = (start_r + end_r)/2; int corridors = new pos(half_r,start_c).CardinalAdjacentPositions().Where(x => BoundsCheck(x) && map[x].IsCorridorType()).Count; if(corridors == 0){ map[half_r,start_c] = CellType.Statue; } corridors = new pos(half_r,end_c).CardinalAdjacentPositions().Where(x => BoundsCheck(x) && map[x].IsCorridorType()).Count; if(corridors == 0){ map[half_r,end_c] = CellType.Statue; } } break; } default: break; } } } return true; } case 2: { int height = end_r - start_r + 1; int width = end_c - start_c + 1; if(height < 4 || width < 4){ //nothing happens until we get above 4x4 return true; } int rows_to_convert = Roll((height/2)-1); int cols_to_convert = Roll((width/2)-1); if(rows_to_convert == 1 && cols_to_convert == 1){ return true; } List<pos> blocked = new List<pos>(); for(int i=start_r;i<=end_r;++i){ for(int j=start_c;j<=end_c;++j){ if((i < start_r + rows_to_convert || i > end_r - rows_to_convert) && (j < start_c + cols_to_convert || j > end_c - cols_to_convert)){ pos p = new pos(i,j); foreach(pos neighbor in p.CardinalAdjacentPositions()){ if(map[neighbor].IsCorridorType()){ blocked.Add(p); } } map[i,j] = CellType.Wall; } } } blocked.Randomize(); foreach(pos p in blocked){ bool done = false; foreach(pos neighbor in p.CardinalAdjacentPositions()){ if(map[neighbor].IsRoomType()){ map[p] = CellType.RoomInterior; done = true; break; } } if(!done){ List<int> valid_dirs = new List<int>(); foreach(int dir in U.FourDirections){ pos next = p.PosInDir(dir); while(next.row >= start_r && next.row <= end_r && next.col >= start_c && next.col <= end_c){ if(next.CardinalAdjacentPositions().Any(x=>map[x].IsRoomType())){ valid_dirs.Add(dir); break; } next = next.PosInDir(dir); } } int valid_dir = valid_dirs.RandomOrDefault(); pos next2 = p.PosInDir(valid_dir); List<pos> new_corridor = new List<pos>{p}; while(true){ new_corridor.Add(next2); if(next2.CardinalAdjacentPositions().Any(x=>map[x].IsRoomType())){ break; } next2 = next2.PosInDir(valid_dir); } foreach(pos p2 in new_corridor){ map[p2] = CellType.RoomInterior; } } } return true; } case 3: { List<pos> list = map.PositionsWhere(x=>x.row >= start_r && x.row <= end_r && x.col >= start_c && x.col <= end_c); PosArray<CellType> old_map = new PosArray<CellType>(H,W); foreach(pos p in list){ old_map[p] = map[p]; map[p] = CellType.Wall; } PosArray<bool> rock = new PosArray<bool>(H,W); for(int i=0;i<H;++i){ for(int j=0;j<W;++j){ pos p = new pos(i,j); rock[p] = true; if(BoundsCheck(i,j,false)){ foreach(pos neighbor in p.AdjacentPositionsClockwise()){ if(map[neighbor] != CellType.Wall){ rock[p] = false; break; } } } } } foreach(pos p in list){ map[p] = CellType.RoomInterior; //todo: might this step be extraneous? } List<pos> frontier = new List<pos>(); { PosArray<bool> in_list = new PosArray<bool>(H,W); foreach(pos p in list){ in_list[p] = true; } for(int i=0;i<H;++i){ for(int j=0;j<W;++j){ pos p = new pos(i,j); if(in_list[p]){ foreach(pos neighbor in p.PositionsAtDistance(1,in_list)){ if(!in_list[neighbor]){ frontier.Add(p); break; } } } } } } int fail_counter = 0; int num_added = 0; bool finished = false; while(!finished){ if(frontier.Count == 0 || num_added >= 30){ //todo check this value finished = true; break; } pos f = frontier.RemoveRandom(); foreach(pos neighbor in f.CardinalAdjacentPositions()){ if(!BoundsCheck(neighbor,false) || !rock[neighbor.row,neighbor.col]){ ++fail_counter; //this might now be unreachable if(!BoundsCheck(neighbor,false)){ fail_counter += 25; //fail quicker when against the edge of the map to prevent ugliness } //however, this doesn't actually fail as quickly as it should - i've overlooked something. if(fail_counter >= 50){ finished = true; break; } } else{ if(map[neighbor] != CellType.RoomInterior){ map[neighbor] = CellType.RoomInterior; ++num_added; bool add_neighbor = true; foreach(pos n2 in neighbor.CardinalAdjacentPositions()){ if(!BoundsCheck(n2,false) || !rock[n2.row,n2.col]){ add_neighbor = false; ++fail_counter; //this might now be unreachable if(!BoundsCheck(neighbor,false)){ fail_counter += 25; //fail quicker when against the edge of the map to prevent ugliness } //however, this doesn't actually fail as quickly as it should - i've overlooked something. if(fail_counter >= 50){ finished = true; } break; } } if(finished){ break; } if(add_neighbor){ frontier.Add(neighbor); } } } } } foreach(pos p in list){ map[p] = old_map[p]; } return true; } case 4: { List<pos> list = map.PositionsWhere(x=>x.row >= start_r && x.row <= end_r && x.col >= start_c && x.col <= end_c); Dungeon room = new Dungeon((end_r - start_r) + 3,(end_c - start_c) + 3); //includes borders List<pos> map_exits = list.Where(x=>x.CardinalAdjacentPositions().Any(y=>map[y].IsCorridorType())); //grab the positions from list that have any adjacent corridor-type cells if(map_exits.Count < 2){ return true; } List<pos> room_exits = new List<pos>(); foreach(pos exit in map_exits){ room_exits.Add(new pos(exit.row-start_r+1,exit.col-start_c+1)); } int tries = 0; while(true){ room.FillWithRandomWalls(25); room.ApplyCellularAutomataXYRule(3); room.ConnectDiagonals(); room.RemoveDeadEndCorridors(); room.RemoveUnconnectedAreas(); bool exits_open = true; foreach(pos p in room_exits){ if(!room[p].IsPassable()){ exits_open = false; } } if(exits_open){ for(int i=start_r;i<=end_r;++i){ for(int j=start_c;j<=end_c;++j){ if(list.Contains(new pos(i,j))){ map[i,j] = room[(i-start_r)+1,(j-start_c)+1]; } } } break; } ++tries; if(tries > 50){ return false; } } return true; } default: break; } return true; }); }
public void GenerateFinalLevel() { final_level_cultist_count = new int[5]; final_level_demon_count = 0; final_level_clock = 0; current_level = 21; InitializeNewLevel(); string[] final_map = FinalLevelLayout(); PosArray<CellType> map = new PosArray<CellType>(ROWS,COLS); PosArray<bool> doors = new PosArray<bool>(ROWS,COLS); List<List<pos>> door_sets = new List<List<pos>>(); for(int i=0;i<ROWS;++i){ string s = final_map[i]; for(int j=0;j<COLS;++j){ switch(s[j]){ case '#': map[i,j] = CellType.Wall; break; case '.': map[i,j] = CellType.RoomInterior; break; case '2': map[i,j] = CellType.RoomFeature1; break; case '&': map[i,j] = CellType.RoomFeature2; break; case '*': map[i,j] = CellType.RoomFeature3; break; case 'X': map[i,j] = CellType.RoomFeature4; break; case '+': map[i,j] = CellType.Wall; if(!doors[i,j]){ doors[i,j] = true; pos p = new pos(i,j); List<pos> door_set = new List<pos>{p}; foreach(int dir in new int[]{2,6}){ p = new pos(i,j); while(true){ p = p.PosInDir(dir); if(p.BoundsCheck(tile) && final_map[p.row][p.col] == '+'){ doors[p] = true; door_set.Add(p); } else{ break; } } } door_sets.Add(door_set); } break; } } } Dungeon d = new Dungeon(ROWS,COLS); d.map = map; while(!d.IsFullyConnected() && door_sets.Count > 0){ List<pos> door_set = door_sets.RemoveRandom(); d.map[door_set.Random()] = CellType.RoomInterior; } List<Tile> flames = new List<Tile>(); for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ switch(map[i,j]){ case CellType.Wall: Tile.Create(TileType.WALL,i,j); break; case CellType.RoomFeature1: Tile.Create(TileType.DEMONIC_IDOL,i,j); break; case CellType.RoomFeature2: Tile.Create(TileType.FLOOR,i,j); flames.Add(tile[i,j]); break; case CellType.RoomFeature3: Tile.Create(TileType.FLOOR,i,j); tile[i,j].color = Color.RandomDoom; break; case CellType.RoomFeature4: Tile.Create(TileType.FIRE_RIFT,i,j); break; default: Tile.Create(TileType.FLOOR,i,j); break; } tile[i,j].solid_rock = true; } } //todo! add dungeon descriptions for final level. //todo: ^^^ or else it crashes ^^^ player.ResetForNewLevel(); foreach(Tile t in AllTiles()){ if(t.light_radius > 0){ t.UpdateRadius(0,t.light_radius); } } foreach(Tile t in flames){ t.AddFeature(FeatureType.FIRE); } int light = player.light_radius; int fire = player.attrs[AttrType.BURNING]; player.light_radius = 0; player.attrs[AttrType.BURNING] = 0; player.Move(6,7); player.UpdateRadius(0,Math.Max(light,fire),true); player.light_radius = light; player.attrs[AttrType.BURNING] = fire; foreach(Tile t in AllTiles()){ if(t.type != TileType.WALL){ foreach(Tile neighbor in t.TilesAtDistance(1)){ neighbor.solid_rock = false; } } } for(int i=0;i<3;++i){ Actor a = SpawnMob(ActorType.CULTIST); List<Actor> group = new List<Actor>(a.group); a.group.Clear(); if(a != null && group != null){ int ii = 0; foreach(Actor a2 in group){ ++ii; pos circle = FinalLevelSummoningCircle(ii); a2.FindPath(circle.row,circle.col); a2.attrs[AttrType.COOLDOWN_2] = ii; a2.type = ActorType.FINAL_LEVEL_CULTIST; a2.group = null; if(!R.OneIn(20)){ a2.attrs[AttrType.NO_ITEM] = 1; } } } } Q.Add(new Event(500,EventType.FINAL_LEVEL_SPAWN_CULTISTS)); }
public static void DebugCreateRandomActors() { actor = new PosArray<Actor>(ROWS,COLS); for(int n=0;n<50;++n){ pos p = actor.RandomPosition(true); actor[p] = Actor.Create(Unit.Create(R.Between(2,4),Species.Random,Job.Random,null)); actor[p].p = p; //Actor a = actor[p]; /*Console.Error.WriteLine(a.species.name + " " + a.job.name); Console.Error.WriteLine("Health: {0,2} Initiative: {1,2} Movement: {2} ",a.health,a.initiative,a.movement); foreach(Skill sk in a.skills){ Console.Error.WriteLine("{0,-20}",sk.name); } Console.Error.WriteLine(); Console.Error.WriteLine();*/ } }
public void GenerateFloorTypes(PosArray<CellType> map) { List<FloorType> floors = new List<FloorType>(); int[] rarity = null; switch(level_types[current_level-1]){ case LevelType.Standard: rarity = new int[]{2,2,7,7,8,30,20}; break; case LevelType.Cave: rarity = new int[]{5,4,3,2,5,30,10}; break; case LevelType.Mine: rarity = new int[]{20,10,2,4,8,100,10}; break; case LevelType.Hive: rarity = new int[]{15,10,5,20,20,20,100}; break; case LevelType.Fortress: rarity = new int[]{20,20,5,10,20,100,15}; break; case LevelType.Crypt: rarity = new int[]{6,10,5,4,4,30,4}; break; case LevelType.Garden: rarity = new int[]{0,0,0,0,0,0,0}; //special handling, see GenerateLevel() break; case LevelType.Slime: //todo default: rarity = new int[]{2,2,4,4,6,30,10}; break; } foreach(FloorType f in Enum.GetValues(typeof(FloorType))){ floors.Add(f); } List<FloorType> floortype_pool = new List<FloorType>(); while(floortype_pool.Count == 0){ //floortype_pool.Clear(); for(int i=0;i<7;++i){ if(rarity[i] > 0 && R.OneIn(rarity[i])){ floortype_pool.Add(floors[i]); } } } int num = R.Between(4,7); if(level_types[current_level-1] == LevelType.Mine){ num = R.Between(1,3); } for(;num>0;--num){ FloorType f = floortype_pool.Random(); for(int i=0;i<50;++i){ int rr = R.Roll(ROWS-2); int rc = R.Roll(COLS-2); if(map[rr,rc].IsRoomType() && map[rr,rc].IsFloor()){ CellType cell = CellType.Wall; int max_radius = 0; switch(f){ case FloorType.GlowingFungus: cell = CellType.GlowingFungus; max_radius = R.Between(2,6); break; case FloorType.Gravel: cell = CellType.Gravel; max_radius = R.Between(3,4); break; case FloorType.Brush: cell = CellType.Brush; max_radius = R.Between(4,7); break; case FloorType.Water: cell = CellType.ShallowWater; max_radius = R.Between(4,6); break; case FloorType.PoppyField: cell = CellType.Poppies; max_radius = R.Between(4,5); break; case FloorType.Ice: cell = CellType.Ice; max_radius = R.Between(4,6); break; case FloorType.GraveDirt: cell = CellType.GraveDirt; max_radius = R.Between(3,5); break; } if(level_types[current_level-1] == LevelType.Fortress && max_radius > 2){ max_radius = 2; } map[rr,rc] = cell; for(int j=1;j<=max_radius;++j){ List<pos> added = new List<pos>(); foreach(pos p in new pos(rr,rc).PositionsWithinDistance(j,map)){ if(map[p] == cell){ foreach(int dir in U.FourDirections){ pos neighbor = p.PosInDir(dir); if(!neighbor.BoundsCheck(map)){ continue; } if(R.CoinFlip()){ if(map[neighbor].IsFloor()){ if(cell.Is(CellType.ShallowWater,CellType.Ice,CellType.GraveDirt)){ bool valid = true; foreach(pos p2 in neighbor.CardinalAdjacentPositions()){ if(map[p2].IsCorridorType()){ valid = false; break; } } if(valid){ added.AddUnique(neighbor); } } else{ added.AddUnique(neighbor); } } else{ if(cell.Is(CellType.ShallowWater,CellType.Gravel,CellType.GraveDirt) && neighbor.BoundsCheck(tile,false) && map[neighbor].IsWall()){ bool valid = true; for(int ii=-1;ii<=1;++ii){ if(!map[neighbor.PosInDir(dir.RotateDir(true,ii))].IsWall()){ valid = false; break; } } if(valid){ added.AddUnique(neighbor); } } } } } } } foreach(pos p in added){ map[p] = cell; } } if(cell == CellType.GraveDirt){ if(!new pos(rr,rc).PositionsAtDistance(1,map).Any(x=>map[x] == CellType.Tombstone)){ map[rr,rc] = CellType.Tombstone; } } break; } } } }
public static void DebugDisplayDijkstra(PosArray <int> d) { DebugDisplayDijkstra(d, 1); }
protected ButtonPanel(int rows,int cols,int vert_offset_px,int horiz_offset_px) : base(G.Window,rows,cols,cell_h,cell_w,vert_offset_px,horiz_offset_px,filename,font_w,font_padding,aa_font) { buttons = new PosArray<Button>(rows,cols); internal_main_button = GetFittedButton(0,0,rows,cols,Command.None); }
public static void DebugDisplayDijkstra(PosArray<int> d) { DebugDisplayDijkstra(d,1); }
public void CaveWidenRooms(int percent_chance_per_room,int number_of_tiles_to_add) { List<List<pos>> roomlist = new List<List<pos>>(); ForEachRoom(list=>{ if(PercentChance(percent_chance_per_room)){ roomlist.Add(list); } return true; }); while(roomlist.Count > 0){ List<pos> list = roomlist.RemoveRandom(); PosArray<CellType> old_map = new PosArray<CellType>(H,W); foreach(pos p in list){ old_map[p] = map[p]; map[p] = CellType.Wall; } PosArray<bool> rock = new PosArray<bool>(H,W); for(int i=0;i<H;++i){ for(int j=0;j<W;++j){ pos p = new pos(i,j); rock[p] = true; if(BoundsCheck(i,j,false)){ foreach(pos neighbor in p.AdjacentPositionsClockwise()){ if(map[neighbor] != CellType.Wall){ rock[p] = false; break; } } } } } foreach(pos p in list){ map[p] = CellType.RoomInterior; //todo: might this step be extraneous? } List<pos> frontier = new List<pos>(); { PosArray<bool> in_list = new PosArray<bool>(H,W); foreach(pos p in list){ in_list[p] = true; } for(int i=0;i<H;++i){ for(int j=0;j<W;++j){ pos p = new pos(i,j); if(in_list[p]){ foreach(pos neighbor in p.PositionsAtDistance(1,in_list)){ if(!in_list[neighbor]){ frontier.Add(p); break; } } } } } } int fail_counter = 0; int num_added = 0; bool finished = false; while(!finished){ if(frontier.Count == 0 || num_added >= number_of_tiles_to_add){ finished = true; break; } pos f = frontier.RemoveRandom(); foreach(pos neighbor in f.CardinalAdjacentPositions()){ if(!BoundsCheck(neighbor,false) || !rock[neighbor.row,neighbor.col]){ ++fail_counter; //this might now be unreachable if(!BoundsCheck(neighbor,false)){ fail_counter += 25; //fail quicker when against the edge of the map to prevent ugliness } //however, this doesn't actually fail as quickly as it should - i've overlooked something. if(fail_counter >= 50){ finished = true; break; } } else{ if(map[neighbor] != CellType.RoomInterior){ map[neighbor] = CellType.RoomInterior; ++num_added; bool add_neighbor = true; foreach(pos n2 in neighbor.CardinalAdjacentPositions()){ if(!BoundsCheck(n2,false) || !rock[n2.row,n2.col]){ add_neighbor = false; ++fail_counter; //this might now be unreachable if(!BoundsCheck(neighbor,false)){ fail_counter += 25; //fail quicker when against the edge of the map to prevent ugliness } //however, this doesn't actually fail as quickly as it should - i've overlooked something. if(fail_counter >= 50){ finished = true; } break; } } if(finished){ break; } if(add_neighbor){ frontier.Add(neighbor); } } } } } foreach(pos p in list){ map[p] = old_map[p]; } } }
public PosArray<colorchar> GetCurrentRect(int row,int col,int height,int width) { PosArray<colorchar> result = new PosArray<colorchar>(height,width); for(int i=0;i<height;++i){ for(int j=0;j<width;++j){ result[i,j] = memory[row+i,col+j]; } } return result; }
public Dungeon(int height,int width) { H = height; W = width; map = new PosArray<CellType>(H,W); }
public bool CreateTwistyCave(bool two_walls_between_corridors,int percent_coverage,int density_threshold,DensityUpdateDelegate update_density) { int target_number_of_floors = (H * W * percent_coverage) / 100; List<pos> frontier = new List<pos>(); PosArray<int> density = new PosArray<int>(H,W); pos origin = new pos(R.Between(2,H-3),R.Between(2,W-3)); frontier.Add(origin); map[origin] = CellType.RoomInterior; int count = 1; bool pick_random = false; while(frontier.Count > 0 && count < target_number_of_floors){ pos p; if(pick_random || R.PercentChance(5)){ p = frontier.RemoveRandom(); pick_random = false; } else{ p = frontier.RemoveLast(); } if(density_threshold > 0 && density[p] >= density_threshold){ continue; } List<int> valid_dirs = new List<int>(); foreach(int dir in U.FourDirections){ pos neighbor = p.PosInDir(dir); if(BoundsCheck(neighbor,false) && map[neighbor].IsWall()){ bool valid = true; if(two_walls_between_corridors){ int idx = 0; foreach(int dir2 in dir.GetArc(1)){ if(!map[neighbor.PosInDir(dir2)].IsWall()){ valid = false; break; } else{ if(idx == 1){ pos n = neighbor.PosInDir(dir2).PosInDir(dir2); if(n.BoundsCheck(map) && !map[n].IsWall()){ valid = false; break; } } else{ foreach(int dir3 in dir2.GetArc(1)){ pos n = neighbor.PosInDir(dir2).PosInDir(dir3); if(n.BoundsCheck(map) && !map[n].IsWall()){ valid = false; break; } } } } ++idx; } } else{ foreach(int dir2 in dir.GetArc(1)){ if(!map[neighbor.PosInDir(dir2)].IsWall()){ valid = false; break; } } } if(valid){ valid_dirs.Add(dir); } } } if(valid_dirs.Count == 0){ pick_random = true; } else{ if(valid_dirs.Count == 1 && R.CoinFlip()){ pick_random = true; } else{ valid_dirs.Randomize(); foreach(int i in valid_dirs){ pos neighbor = p.PosInDir(i); map[neighbor] = CellType.RoomInterior; ++count; update_density(neighbor,density,map); if(!pick_random){ //todo: should this check be removed? it can lead to abandoned paths that don't ever get filled frontier.Add(neighbor); } } } } } foreach(pos p in frontier){ if(BoundsCheck(p,false)){ map[p] = CellType.RoomInterior; } } return true; }
public void ActiveAI() { if(path.Count > 0){ path.Clear(); } if(!HasAttr(AttrType.AGGRESSION_MESSAGE_PRINTED)){ PrintAggressionMessage(); } switch(type){ case ActorType.GIANT_BAT: case ActorType.PHANTOM_BLIGHTWING: if(DistanceFrom(target) == 1){ int idx = R.Roll(1,2) - 1; Attack(idx,target); if(target != null && R.CoinFlip()){ //chance of retreating AI_Step(target,true); } } else{ if(R.CoinFlip()){ AI_Step(target); QS(); } else{ AI_Step(TileInDirection(Global.RandomDirection())); QS(); } } break; case ActorType.BLOOD_MOTH: { Tile brightest = null; if(!M.wiz_dark && !M.wiz_lite && !HasAttr(AttrType.BLIND)){ List<Tile> valid = M.AllTiles().Where(x=>x.light_value > 0 && CanSee(x)); valid = valid.WhereGreatest(x=>{ int result = x.light_radius; if(x.Is(FeatureType.FIRE) && result == 0){ result = 1; } if(x.inv != null && x.inv.light_radius > result){ result = x.inv.light_radius; } if(x.actor() != null && x.actor().LightRadius() > result){ result = x.actor().LightRadius(); } return result; }); valid = valid.WhereLeast(x=>DistanceFrom(x)); if(valid.Count > 0){ brightest = valid.RandomOrDefault(); } } if(brightest != null){ if(DistanceFrom(brightest) <= 1){ if(target != null && brightest == target.tile()){ Attack(0,target); if(target == player && player.curhp > 0){ Help.TutorialTip(TutorialTopic.Torch); } } else{ List<Tile> open = new List<Tile>(); foreach(Tile t in TilesAtDistance(1)){ if(t.DistanceFrom(brightest) <= 1 && t.passable && t.actor() == null){ open.Add(t); } } if(open.Count > 0){ AI_Step(open.Random()); } QS(); } } else{ List<Tile> tiles = new List<Tile>(); if(brightest.row == row || brightest.col == col){ int targetdir = DirectionOf(brightest); for(int i=-1;i<=1;++i){ pos adj = p.PosInDir(targetdir.RotateDir(true,i)); if(M.tile[adj].passable && M.actor[adj] == null){ tiles.Add(M.tile[adj]); } } } if(tiles.Count > 0){ AI_Step(tiles.Random()); } else{ AI_Step(brightest); } QS(); } } else{ int dir = Global.RandomDirection(); if(!TileInDirection(dir).passable && TilesAtDistance(1).Where(t => !t.passable).Count > 4){ dir = Global.RandomDirection(); } if(TileInDirection(dir).passable && ActorInDirection(dir) == null){ AI_Step(TileInDirection(dir)); QS(); } else{ if(curhp < maxhp && target != null && ActorInDirection(dir) == target){ Attack(0,target); } else{ if(player.HasLOS(TileInDirection(dir)) && player.HasLOS(this)){ if(!TileInDirection(dir).passable){ B.Add(the_name + " brushes up against " + TileInDirection(dir).the_name + ". ",this); } else{ if(ActorInDirection(dir) != null){ B.Add(the_name + " brushes up against " + ActorInDirection(dir).TheName(true) + ". ",this); } } } QS(); } } } /*PhysicalObject brightest = null; if(!M.wiz_lite && !M.wiz_dark){ List<PhysicalObject> current_brightest = new List<PhysicalObject>(); foreach(Tile t in M.AllTiles()){ int pos_radius = t.light_radius; PhysicalObject pos_obj = t; if(t.Is(FeatureType.FIRE) && pos_radius == 0){ pos_radius = 1; } if(t.inv != null && t.inv.light_radius > pos_radius){ pos_radius = t.inv.light_radius; pos_obj = t.inv; } if(t.actor() != null && t.actor().LightRadius() > pos_radius){ pos_radius = t.actor().LightRadius(); pos_obj = t.actor(); } if(pos_radius > 0){ if(current_brightest.Count == 0 && CanSee(t)){ current_brightest.Add(pos_obj); } else{ foreach(PhysicalObject o in current_brightest){ int object_radius = o.light_radius; if(o is Actor){ object_radius = (o as Actor).LightRadius(); } if(object_radius == 0 && o is Tile && (o as Tile).Is(FeatureType.FIRE)){ object_radius = 1; } if(pos_radius > object_radius){ if(CanSee(t)){ current_brightest.Clear(); current_brightest.Add(pos_obj); break; } } else{ if(pos_radius == object_radius && DistanceFrom(t) < DistanceFrom(o)){ if(CanSee(t)){ current_brightest.Clear(); current_brightest.Add(pos_obj); break; } } else{ if(pos_radius == object_radius && DistanceFrom(t) == DistanceFrom(o) && pos_obj == player){ if(CanSee(t)){ current_brightest.Clear(); current_brightest.Add(pos_obj); break; } } } } } } } } if(current_brightest.Count > 0){ brightest = current_brightest.Random(); } } if(brightest != null){ if(DistanceFrom(brightest) <= 1){ if(brightest == target){ Attack(0,target); if(target == player && player.curhp > 0){ Help.TutorialTip(TutorialTopic.Torch); } } else{ List<Tile> open = new List<Tile>(); foreach(Tile t in TilesAtDistance(1)){ if(t.DistanceFrom(brightest) <= 1 && t.passable && t.actor() == null){ open.Add(t); } } if(open.Count > 0){ AI_Step(open.Random()); } QS(); } } else{ AI_Step(brightest); QS(); } } else{ int dir = Global.RandomDirection(); if(TilesAtDistance(1).Where(t => !t.passable).Count > 4 && !TileInDirection(dir).passable){ dir = Global.RandomDirection(); } if(TileInDirection(dir).passable && ActorInDirection(dir) == null){ AI_Step(TileInDirection(dir)); QS(); } else{ if(curhp < maxhp && target != null && ActorInDirection(dir) == target){ Attack(0,target); } else{ if(player.HasLOS(TileInDirection(dir)) && player.HasLOS(this)){ if(!TileInDirection(dir).passable){ B.Add(the_name + " brushes up against " + TileInDirection(dir).the_name + ". ",this); } else{ if(ActorInDirection(dir) != null){ B.Add(the_name + " brushes up against " + ActorInDirection(dir).TheName(true) + ". ",this); } } } QS(); } } }*/ break; } case ActorType.CARNIVOROUS_BRAMBLE: case ActorType.MUD_TENTACLE: if(DistanceFrom(target) == 1){ Attack(0,target); if(target == player && player.curhp > 0){ Help.TutorialTip(TutorialTopic.RangedAttacks); } } else{ QS(); } break; case ActorType.FROSTLING: { if(DistanceFrom(target) == 1){ if(R.CoinFlip()){ Attack(0,target); } else{ if(AI_Step(target,true)){ QS(); } else{ Attack(0,target); } } } else{ if(FirstActorInLine(target) == target && !HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 6){ int cooldown = R.Roll(1,4); if(cooldown != 1){ RefreshDuration(AttrType.COOLDOWN_1,cooldown*100); } AnimateBoltProjectile(target,Color.RandomIce); if(R.CoinFlip()){ B.Add(TheName(true) + " hits " + target.the_name + " with a blast of cold. ",target); target.TakeDamage(DamageType.COLD,DamageClass.PHYSICAL,R.Roll(2,6),this,"a frostling"); } else{ B.Add(TheName(true) + " misses " + target.the_name + " with a blast of cold. ",target); } foreach(Tile t in GetBestLineOfEffect(target)){ t.ApplyEffect(DamageType.COLD); } Q1(); } else{ if(!HasAttr(AttrType.COOLDOWN_2)){ AI_Step(target); } else{ AI_Sidestep(target); //message for this? hmm. } QS(); } } break; } case ActorType.SWORDSMAN: case ActorType.PHANTOM_SWORDMASTER: if(DistanceFrom(target) == 1){ pos target_pos = target.p; Attack(0,target); if(target != null && target.p.Equals(target_pos)){ List<Tile> valid_dirs = new List<Tile>(); foreach(Tile t in target.TilesAtDistance(1)){ if(t.passable && t.actor() == null && DistanceFrom(t) == 1){ valid_dirs.Add(t); } } if(valid_dirs.Count > 0){ AI_Step(valid_dirs.Random()); } } } else{ attrs[AttrType.COMBO_ATTACK] = 0; AI_Step(target); QS(); } break; case ActorType.DREAM_WARRIOR: if(DistanceFrom(target) == 1){ if(curhp <= 10 && !HasAttr(AttrType.COOLDOWN_1)){ //todo: changed to 20hp and a 10hp threshold...better? attrs[AttrType.COOLDOWN_1]++; List<Tile> openspaces = new List<Tile>(); foreach(Tile t in target.TilesAtDistance(1)){ if(t.passable && t.actor() == null){ openspaces.Add(t); } } foreach(Tile t in openspaces){ if(group == null){ group = new List<Actor>{this}; } Create(ActorType.DREAM_WARRIOR_CLONE,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent); t.actor().player_visibility_duration = -1; t.actor().attrs[AttrType.NO_ITEM]++; group.Add(M.actor[t.row,t.col]); M.actor[t.row,t.col].group = group; group.Randomize(); } openspaces.Add(tile()); Tile newtile = openspaces[R.Roll(openspaces.Count)-1]; if(newtile != tile()){ Move(newtile.row,newtile.col,false); } if(openspaces.Count > 1){ B.Add(the_name + " is suddenly standing all around " + target.the_name + ". ",this,target); Q1(); } else{ Attack(0,target); } } else{ Attack(0,target); } } else{ AI_Step(target); QS(); } break; case ActorType.SPITTING_COBRA: if(DistanceFrom(target) <= 3 && !HasAttr(AttrType.COOLDOWN_1) && FirstActorInLine(target) == target){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(50,75)*100); B.Add(TheName(true) + " spits poison in " + target.YourVisible() + " eyes! ",this,target); AnimateBoltProjectile(target,Color.DarkGreen); if(!target.HasAttr(AttrType.NONLIVING)){ target.ApplyStatus(AttrType.BLIND,R.Between(5,8)*100); /*B.Add(target.YouAre() + " blind! ",target); target.RefreshDuration(AttrType.BLIND,R.Between(5,8)*100,target.YouAre() + " no longer blinded. ",target);*/ } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ List<Tile> tiles = new List<Tile>(); if(target.row == row || target.col == col){ int targetdir = DirectionOf(target); for(int i=-1;i<=1;++i){ pos adj = p.PosInDir(targetdir.RotateDir(true,i)); if(M.tile[adj].passable && M.actor[adj] == null){ tiles.Add(M.tile[adj]); } } } if(tiles.Count > 0){ AI_Step(tiles.Random()); } else{ AI_Step(target); } QS(); } } break; case ActorType.KOBOLD: if(!HasAttr(AttrType.COOLDOWN_1)){ if(DistanceFrom(target) > 12){ AI_Step(target); QS(); } else{ if(FirstActorInLine(target) != target){ AI_Sidestep(target); QS(); } else{ attrs[AttrType.COOLDOWN_1]++; AnimateBoltProjectile(target,Color.DarkCyan,30); if(player.CanSee(this)){ B.Add(the_name + " fires a dart at " + target.the_name + ". ",this,target); } else{ B.Add("A dart hits " + target.the_name + "! ",target); if(player.CanSee(tile()) && !IsInvisibleHere()){ attrs[AttrType.TURNS_VISIBLE] = -1; attrs[AttrType.NOTICED] = 1; B.Add("You spot " + the_name + " that fired it. ",this); //B.Add("You notice " + a_name + ". ",tile()); } } if(target.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(6),this,"a kobold's dart")){ target.ApplyStatus(AttrType.VULNERABLE,R.Between(2,4)*100); /*if(!target.HasAttr(AttrType.VULNERABLE)){ B.Add(target.YouFeel() + " vulnerable. ",target); } target.RefreshDuration(AttrType.VULNERABLE,R.Between(2,4)*100,target.YouFeel() + " less vulnerable. ",target);*/ if(target == player){ Help.TutorialTip(TutorialTopic.Vulnerable); } } Q1(); } } } else{ if(DistanceFrom(target) <= 2){ AI_Flee(); QS(); } else{ B.Add(the_name + " starts reloading. ",this); attrs[AttrType.COOLDOWN_1] = 0; Q1(); RefreshDuration(AttrType.COOLDOWN_2,R.Between(5,6)*100 - 50); //Q.Add(new Event(this,R.Between(5,6)*100,EventType.MOVE)); } } break; case ActorType.SPORE_POD: if(DistanceFrom(target) == 1){ TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,100,null); } else{ AI_Step(target); QS(); } break; case ActorType.FORASECT: { bool burrow = false; if((curhp * 2 <= maxhp || DistanceFrom(target) > 6) && R.CoinFlip()){ burrow = true; } if(DistanceFrom(target) <= 6 && DistanceFrom(target) > 1){ if(R.OneIn(10)){ burrow = true; } } if(burrow && !HasAttr(AttrType.COOLDOWN_1)){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(8,11)*100); if(curhp * 2 <= maxhp){ Burrow(TilesWithinDistance(6)); } else{ Burrow(GetCone(DirectionOf(target),6,true)); } } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; } case ActorType.POLTERGEIST: if(inv.Count == 0){ if(DistanceFrom(target) == 1){ pos target_p = target.p; if(Attack(0,target) && M.actor[target_p] != null && M.actor[target_p].inv.Any(i=>!i.do_not_stack)){ target = M.actor[target_p]; Item item = target.inv.Where(i=>!i.do_not_stack).Random(); if(item.quantity > 1){ inv.Add(new Item(item,-1,-1)); item.quantity--; B.Add(YouVisible("steal") + " " + target.YourVisible() + " " + inv[0].Name() + "! ",this,target); } else{ inv.Add(item); target.inv.Remove(item); B.Add(YouVisible("steal") + " " + target.YourVisible() + " " + item.Name() + "! ",this,target); } } } else{ AI_Step(target); QS(); } } else{ attrs[AttrType.KEEPS_DISTANCE] = 1; List<Tile> line = target.GetBestExtendedLineOfEffect(this); Tile next = null; bool found = false; foreach(Tile t in line){ if(found){ next = t; break; } else{ if(t.actor() == this){ found = true; } } } if(next != null){ if(next.passable && next.actor() == null && AI_Step(next)){ QS(); } else{ if(!next.passable){ B.Add(the_name + " disappears into " + next.the_name + ". ",this); foreach(Tile t in TilesWithinDistance(1)){ if(t.DistanceFrom(next) == 1 && t.name == "floor"){ t.AddFeature(FeatureType.SLIME); } } Event e = null; foreach(Event e2 in Q.list){ if(e2.target == this && e2.type == EventType.POLTERGEIST){ e = e2; break; } } if(e != null){ e.target = inv[0]; Actor.tiebreakers[e.tiebreaker] = null; } inv.Clear(); Kill(); } else{ if(next.actor() != null){ if(!next.actor().HasAttr(AttrType.IMMOBILE)){ Move(next.row,next.col); QS(); } else{ if(next.actor().HasAttr(AttrType.IMMOBILE)){ if(AI_Step(next)){ QS(); } else{ if(DistanceFrom(target) == 1){ Attack(1,target); } else{ QS(); } } } } } else{ QS(); } } } } } break; case ActorType.CULTIST: case ActorType.FINAL_LEVEL_CULTIST: if(curhp <= 10 && !HasAttr(AttrType.COOLDOWN_1)){ attrs[AttrType.COOLDOWN_1]++; string invocation; switch(R.Roll(4)){ case 1: invocation = "ae vatra kersai"; break; case 2: invocation = "kersai dzaggath"; break; case 3: invocation = "od fir od bahgal"; break; case 4: invocation = "denei kersai nammat"; break; default: invocation = "denommus pilgni"; break; } if(R.CoinFlip()){ B.Add(You("whisper") + " '" + invocation + "'. ",this); } else{ B.Add(You("scream") + " '" + invocation.ToUpper() + "'. ",this); } if(HasAttr(AttrType.SLIMED)){ B.Add("Nothing happens. ",this); } else{ B.Add("Flames erupt from " + the_name + ". ",this); AnimateExplosion(this,1,Color.RandomFire,'*'); ApplyBurning(); foreach(Tile t in TilesWithinDistance(1)){ t.ApplyEffect(DamageType.FIRE); if(t.actor() != null){ t.actor().ApplyBurning(); } } } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; case ActorType.GOBLIN_ARCHER: case ActorType.PHANTOM_ARCHER: switch(DistanceFrom(target)){ case 1: /*if(target.EnemiesAdjacent() > 1){ Attack(0,target); } else{*/ if(AI_Flee()){ QS(); } else{ Attack(0,target); } //} break; case 2: if(FirstActorInLine(target) == target){ FireArrow(target); } else{ if(AI_Flee()){ QS(); } else{ if(AI_Sidestep(target)){ B.Add(the_name + " tries to line up a shot. ",this); } QS(); } } break; case 3: case 4: case 5: case 6: case 7: case 8: if(FirstActorInLine(target) == target){ FireArrow(target); } else{ if(AI_Sidestep(target)){ B.Add(the_name + " tries to line up a shot. ",this); } QS(); } break; default: AI_Step(target); QS(); break; } break; case ActorType.GOBLIN_SHAMAN: { if(SilencedThisTurn()){ return; } if(DistanceFrom(target) == 1){ if(exhaustion > 50){ Attack(0,target); } else{ CastCloseRangeSpellOrAttack(target); } } else{ if(DistanceFrom(target) > 12){ AI_Step(target); QS(); } else{ if(FirstActorInLine(target) != target || R.CoinFlip()){ AI_Step(target); QS(); } else{ CastRangedSpellOrMove(target); } } } break; } case ActorType.PHASE_SPIDER: if(DistanceFrom(target) == 1){ Attack(0,target); } else{ Tile t = target.TilesAtDistance(DistanceFrom(target)-1).Where(x=>x.passable && x.actor() == null).RandomOrDefault(); if(t != null){ Move(t.row,t.col); } QS(); } break; case ActorType.ZOMBIE: case ActorType.PHANTOM_ZOMBIE: if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); if(DistanceFrom(target) == 1){ Attack(1,target); } else{ QS(); } } break; case ActorType.ROBED_ZEALOT: if(HasAttr(AttrType.COOLDOWN_3)){ if(DistanceFrom(target) <= 12 && HasLOS(target)){ target.AnimateExplosion(target,1,Color.Yellow,'*'); B.Add(YouVisible("smite") + " " + target.the_name + "! ",target); int amount = target.curhp / 10; bool still_alive = target.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,Math.Max(amount,1),this,"a zealot's wrath"); attrs[AttrType.COOLDOWN_3]--; attrs[AttrType.DETECTING_MONSTERS]--; if(!HasAttr(AttrType.COOLDOWN_3)){ B.Add(YouVisible("stop") + " praying. "); if(still_alive && target.EquippedWeapon.type != WeaponType.NO_WEAPON && !target.EquippedWeapon.status[EquipmentStatus.MERCIFUL]){ target.EquippedWeapon.status[EquipmentStatus.MERCIFUL] = true; B.Add(target.You("feel") + " a strange power enter " + target.Your() + " " + target.EquippedWeapon.NameWithoutEnchantment() + "! ",target); B.PrintAll(); Help.TutorialTip(TutorialTopic.Merciful); } } } else{ attrs[AttrType.COOLDOWN_3]--; attrs[AttrType.DETECTING_MONSTERS]--; } Q1(); } else{ if(!HasAttr(AttrType.COOLDOWN_1)){ attrs[AttrType.COOLDOWN_1] = maxhp; //initialize this value here instead of complicating the spawning code } if(DistanceFrom(target) <= 12 && !HasAttr(AttrType.COOLDOWN_2) && curhp < attrs[AttrType.COOLDOWN_1]){ //if the ability is ready and additional damage has been taken... RefreshDuration(AttrType.COOLDOWN_2,R.Between(11,13)*100); attrs[AttrType.COOLDOWN_1] = curhp; attrs[AttrType.COOLDOWN_3] = 4; attrs[AttrType.DETECTING_MONSTERS] = 4; B.Add(YouVisible("start") + " praying. "); B.Add(the_name + " points directly at you. ",this); Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } } /*if(HasAttr(AttrType.COOLDOWN_2)){ attrs[AttrType.COOLDOWN_2] = 0; B.Add(the_name + " finishes the prayer. ",this); if(DistanceFrom(target) == 1 && target.EquippedWeapon.type != WeaponType.NO_WEAPON){ target.EquippedWeapon.status[EquipmentStatus.MERCIFUL] = true; B.Add("You feel a strange power enter " + target.Your() + " " + target.EquippedWeapon.NameWithoutEnchantment() + "! ",target); B.PrintAll(); Help.TutorialTip(TutorialTopic.Merciful); } Q1(); } else{ if((maxhp / 5) * 4 > curhp && !HasAttr(AttrType.COOLDOWN_1)){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(14,16)*100); attrs[AttrType.COOLDOWN_2]++; B.Add(the_name + " starts praying. ",this); B.Add("A fiery halo appears above " + the_name + ". ",this); RefreshDuration(AttrType.RADIANT_HALO,R.Between(8,10)*100,Your() + " halo fades. ",this); Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } }*/ break; case ActorType.GIANT_SLUG: { if(DistanceFrom(target) == 1){ Attack(R.Between(0,1),target); } else{ if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12 && FirstActorInLine(target) == target){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(11,14)*100); B.Add(TheName(true) + " spits slime at " + target.the_name + ". ",target); List<Tile> slimed = GetBestLineOfEffect(target); List<Tile> added = new List<Tile>(); foreach(Tile t in slimed){ foreach(int dir in U.FourDirections){ Tile neighbor = t.TileInDirection(dir); if(R.OneIn(3) && neighbor.passable && !slimed.Contains(neighbor)){ added.AddUnique(neighbor); } } } slimed.AddRange(added); List<pos> cells = new List<pos>(); List<Actor> slimed_actors = new List<Actor>(); for(int i=0;slimed.Count > 0;++i){ List<Tile> removed = new List<Tile>(); foreach(Tile t in slimed){ if(DistanceFrom(t) == i){ t.AddFeature(FeatureType.SLIME); if(t.actor() != null && t.actor() != this && !t.actor().HasAttr(AttrType.SLIMED,AttrType.FROZEN)){ slimed_actors.Add(t.actor()); } removed.Add(t); if(DistanceFrom(t) > 0){ cells.Add(t.p); } } } foreach(Tile t in removed){ slimed.Remove(t); } if(cells.Count > 0){ Screen.AnimateMapCells(cells,new colorchar(',',Color.Green),20); } } M.Draw(); slimed_actors.AddUnique(target); foreach(Actor a in slimed_actors){ a.attrs[AttrType.SLIMED] = 1; a.attrs[AttrType.OIL_COVERED] = 0; a.RefreshDuration(AttrType.BURNING,0); B.Add(a.YouAre() + " covered in slime. ",a); } Q1(); } else{ AI_Step(target); if(tile().Is(FeatureType.SLIME)){ speed = 50; QS(); //normal speed is 150 speed = 150; } else{ QS(); } } } break; } case ActorType.BANSHEE: { if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(13,15)*100); if(player.CanSee(this)){ if(player.IsSilencedHere()){ B.Add(You("seem") + " to scream. ",this); } else{ B.Add(You("scream") + ". ",this); } } else{ if(!player.IsSilencedHere()){ B.Add("You hear a scream! "); } } if(!target.IsSilencedHere()){ if(target.ResistedBySpirit() || target.HasAttr(AttrType.MENTAL_IMMUNITY)){ B.Add(target.You("remain") + " courageous. ",target); } else{ B.Add(target.YouAre() + " terrified! ",target); RefreshDuration(AttrType.TERRIFYING,R.Between(5,8)*100,target.YouAre() + " no longer afraid. ",target); Help.TutorialTip(TutorialTopic.Afraid); } } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; } case ActorType.CAVERN_HAG: if(curhp < maxhp && HasAttr(AttrType.COOLDOWN_2) && !HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12){ B.Add(TheName(true) + " curses you! "); if(target.ResistedBySpirit()){ B.Add("You resist the curse. "); } else{ switch(R.Roll(4)){ case 1: //light allergy B.Add("You become allergic to light! "); target.RefreshDuration(AttrType.LIGHT_SENSITIVE,(R.Roll(2,20) + 70) * 100,"You are no longer allergic to light. "); break; case 2: //aggravate monsters B.Add("Every sound you make becomes amplified and echoes across the dungeon. "); target.RefreshDuration(AttrType.AGGRAVATING,(R.Roll(2,20) + 70) * 100,"Your sounds are no longer amplified. "); break; case 3: //cursed weapon B.Add("Your " + target.EquippedWeapon + " becomes stuck to your hand! "); target.EquippedWeapon.status[EquipmentStatus.STUCK] = true; Help.TutorialTip(TutorialTopic.Stuck); break; case 4: //heavy weapon B.Add("Your " + target.EquippedWeapon + " suddenly feels much heavier. "); target.EquippedWeapon.status[EquipmentStatus.HEAVY] = true; Help.TutorialTip(TutorialTopic.Heavy); break; } } attrs[AttrType.COOLDOWN_1]++; Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; case ActorType.BERSERKER: { if(HasAttr(AttrType.COOLDOWN_2)){ int dir = attrs[AttrType.COOLDOWN_2]; bool cw = R.CoinFlip(); if(TileInDirection(dir).passable && ActorInDirection(dir) == null && !MovementPrevented(TileInDirection(dir))){ B.Add(the_name + " leaps forward swinging his axe! ",this); Move(TileInDirection(dir).row,TileInDirection(dir).col); M.Draw(); for(int i=-1;i<=1;++i){ Screen.AnimateBoltProjectile(new List<Tile>{tile(),TileInDirection(dir.RotateDir(cw,i))},Color.Red,30); } for(int i=-1;i<=1;++i){ Actor a = ActorInDirection(dir.RotateDir(cw,i)); if(a != null){ B.Add(YourVisible() + " axe hits " + a.TheName(true) + ". ",this,a); a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(4,6),this,"a berserker's axe"); } TileInDirection(dir.RotateDir(cw,i)).Bump(dir.RotateDir(cw,i)); } Q1(); } else{ if(ActorInDirection(dir) != null || MovementPrevented(TileInDirection(dir)) || TileInDirection(dir).Is(TileType.STANDING_TORCH,TileType.BARREL,TileType.POISON_BULB)){ B.Add(the_name + " swings his axe furiously! ",this); for(int i=-1;i<=1;++i){ Screen.AnimateBoltProjectile(new List<Tile>{tile(),TileInDirection(dir.RotateDir(cw,i))},Color.Red,30); } for(int i=-1;i<=1;++i){ Actor a = ActorInDirection(dir.RotateDir(cw,i)); if(a != null){ B.Add(YourVisible() + " axe hits " + a.TheName(true) + ". ",this,a); a.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(4,6),this,"a berserker's axe"); } TileInDirection(dir.RotateDir(cw,i)).Bump(dir.RotateDir(cw,i)); } Q1(); } else{ if(target != null && HasLOS(target)){ B.Add(the_name + " turns to face " + target.the_name + ". ",this); attrs[AttrType.COOLDOWN_2] = DirectionOf(target); Q1(); } } } } else{ if(DistanceFrom(target) == 1){ Attack(0,target); if(target != null && R.Roll(3) == 3){ B.Add(the_name + " screams with fury! ",this); attrs[AttrType.COOLDOWN_2] = DirectionOf(target); Q.Add(new Event(this,350,AttrType.COOLDOWN_2,Your() + " rage diminishes. ",this)); } } else{ AI_Step(target); QS(); } } break; } case ActorType.DIRE_RAT: { bool slip_past = false; if(DistanceFrom(target) == 1){ foreach(Actor a in ActorsAtDistance(1)){ if(a.type == ActorType.DIRE_RAT && a.DistanceFrom(target) > this.DistanceFrom(target)){ bool can_walk = false; foreach(Tile t in a.TilesAtDistance(1)){ if(t.DistanceFrom(target) < a.DistanceFrom(target) && t.passable && t.actor() == null){ can_walk = true; break; } } if(!can_walk){ //there's a rat that would benefit from a space opening up - now check to see whether a move is possible foreach(Tile t in target.TilesAtDistance(1)){ if(t.passable && t.actor() == null){ slip_past = true; break; } } break; } } } } if(slip_past){ bool moved = false; foreach(Tile t in TilesAtDistance(1)){ if(t.DistanceFrom(target) == 1 && t.passable && t.actor() == null){ AI_Step(t); QS(); moved = true; break; } } if(!moved){ Tile t = target.TilesAtDistance(1).Where(x=>x.passable && x.actor() == null).RandomOrDefault(); if(t != null){ B.Add(TheName(true) + " slips past " + target.TheName(true) + ". ",this,target); Move(t.row,t.col); Q.Add(new Event(this,Speed() + 100,EventType.MOVE)); } else{ QS(); } } } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; } case ActorType.SKULKING_KILLER: { if(HasAttr(AttrType.KEEPS_DISTANCE)){ bool try_to_hide = false; if(AI_Flee()){ try_to_hide = true; QS(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ //give up on fleeing, just attack attrs[AttrType.COOLDOWN_2] = 0; attrs[AttrType.KEEPS_DISTANCE] = 0; AI_Step(target); QS(); } } if(try_to_hide){ bool visible = player.CanSee(this); if(!R.OneIn(5) && (!player.HasLOE(this) || !visible || DistanceFrom(player) > 12)){ //just to add some uncertainty attrs[AttrType.COOLDOWN_2]++; if(attrs[AttrType.COOLDOWN_2] >= 3){ attrs[AttrType.KEEPS_DISTANCE] = 0; attrs[AttrType.COOLDOWN_2] = 0; if(!visible){ attrs[AttrType.TURNS_VISIBLE] = 0; } } } } } else{ if(DistanceFrom(target) == 1){ if(Attack(0,target)){ attrs[AttrType.KEEPS_DISTANCE] = 1; } } else{ AI_Step(target); QS(); } } /*if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 3 && R.OneIn(3) && HasLOE(target)){ attrs[AttrType.COOLDOWN_1]++; AnimateProjectile(target,Color.DarkYellow,'%'); Input.FlushInput(); if(target.CanSee(this)){ B.Add(the_name + " throws a bola at " + target.the_name + ". ",this,target); } else{ B.Add("A bola whirls toward " + target.the_name + ". ",this,target); } attrs[AttrType.TURNS_VISIBLE] = -1; target.RefreshDuration(AttrType.SLOWED,(R.Roll(3)+6)*100,target.YouAre() + " no longer slowed. ",target); B.Add(target.YouAre() + " slowed by the bola. ",target); Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } }*/ break; } case ActorType.WILD_BOAR: if(DistanceFrom(target) == 1){ Attack(0,target); if(HasAttr(AttrType.JUST_FLUNG)){ //if it just flung its target... attrs[AttrType.JUST_FLUNG] = 0; attrs[AttrType.COOLDOWN_1] = 0; } else{ //...otherwise it might prepare to fling again if(!HasAttr(AttrType.COOLDOWN_1)){ if(!HasAttr(AttrType.COOLDOWN_2) || R.OneIn(5)){ attrs[AttrType.COOLDOWN_2]++; B.Add(the_name + " lowers its head. ",this); attrs[AttrType.COOLDOWN_1]++; } } } } else{ AI_Step(target); if(!HasAttr(AttrType.COOLDOWN_2)){ attrs[AttrType.COOLDOWN_2]++; B.Add(the_name + " lowers its head. ",this); attrs[AttrType.COOLDOWN_1]++; } QS(); } break; case ActorType.DREAM_SPRITE: if(!HasAttr(AttrType.COOLDOWN_1)){ if(DistanceFrom(target) <= 12 && FirstActorInLine(target) == target){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(3,4)*100); bool visible = false; List<List<Tile>> lines = new List<List<Tile>>{GetBestLineOfEffect(target)}; if(group != null && group.Count > 0){ foreach(Actor a in group){ if(target == player && player.CanSee(a)){ visible = true; } if(a.type == ActorType.DREAM_SPRITE_CLONE){ a.attrs[AttrType.COOLDOWN_1]++; //for them, it means 'skip next turn' if(a.FirstActorInLine(target) == target){ lines.Add(a.GetBestLineOfEffect(target)); } } } } foreach(List<Tile> line in lines){ if(line.Count > 0){ line.RemoveAt(0); } } if(visible){ B.Add(the_name + " hits " + target.the_name + " with stinging magic. ",target); } else{ B.Add(TheName(true) + " hits " + target.the_name + " with stinging magic. ",target); } int max = lines.WhereGreatest(x=>x.Count)[0].Count; for(int i=0;i<max;++i){ List<pos> cells = new List<pos>(); foreach(List<Tile> line in lines){ if(line.Count > i){ cells.Add(line[i].p); } } Screen.AnimateMapCells(cells,new colorchar('*',Color.RandomRainbow)); } target.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(2,6),this,"a blast of fairy magic"); Q1(); } else{ if(DistanceFrom(target) > 12){ AI_Step(target); } else{ AI_Sidestep(target); } QS(); } } else{ if(DistanceFrom(target) > 5){ AI_Step(target); } else{ if(DistanceFrom(target) < 3){ AI_Flee(); } else{ Tile t = TilesAtDistance(1).Where(x=>x.passable && x.actor() == null).RandomOrDefault(); if(t != null){ AI_Step(t); } } } QS(); } break; case ActorType.DREAM_SPRITE_CLONE: if(HasAttr(AttrType.COOLDOWN_1)){ attrs[AttrType.COOLDOWN_1] = 0; Q1(); } else{ if(DistanceFrom(target) > 5){ AI_Step(target); } else{ if(DistanceFrom(target) < 3){ AI_Flee(); } else{ Tile t = TilesAtDistance(1).Where(x=>x.passable && x.actor() == null).RandomOrDefault(); if(t != null){ AI_Step(t); } } } QS(); } break; case ActorType.CLOUD_ELEMENTAL: { List<pos> cloud = M.tile.GetFloodFillPositions(p,false,x=>M.tile[x].features.Contains(FeatureType.FOG)); PhysicalObject[] objs = new PhysicalObject[cloud.Count + 1]; int idx = 0; foreach(pos p2 in cloud){ objs[idx++] = M.tile[p2]; } objs[idx] = this; List<colorchar> chars = new List<colorchar>(); colorchar cch = new colorchar('*',Color.RandomLightning); if(cloud.Contains(target.p)){ B.Add(the_name + " electrifies the cloud! ",objs); foreach(pos p2 in cloud){ if(M.actor[p2] != null && M.actor[p2] != this){ M.actor[p2].TakeDamage(DamageType.ELECTRIC,DamageClass.PHYSICAL,R.Roll(3,6),this,"*electrocuted by a cloud elemental"); } if(M.actor[p2] == this){ chars.Add(visual); } else{ chars.Add(cch); } } Screen.AnimateMapCells(cloud,chars,50); Q1(); } else{ if(DistanceFrom(target) == 1){ Tile t = TilesAtDistance(1).Where(x=>x.actor() == null && x.passable).RandomOrDefault(); if(t != null){ AI_Step(t); } QS(); } else{ if(R.OneIn(4)){ Tile t = TilesAtDistance(1).Where(x=>x.actor() == null && x.passable).RandomOrDefault(); if(t != null){ AI_Step(t); } QS(); } else{ AI_Step(target); QS(); } } } break; } case ActorType.DERANGED_ASCETIC: if(DistanceFrom(target) == 1){ Attack(R.Roll(3)-1,target); } else{ AI_Step(target); QS(); } break; case ActorType.SNEAK_THIEF: { if(DistanceFrom(target) <= 12 && !R.OneIn(3) && AI_UseRandomItem()){ Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); if(target != null){ List<Tile> valid_dirs = new List<Tile>(); foreach(Tile t in target.TilesAtDistance(1)){ if(t.passable && t.actor() == null && DistanceFrom(t) == 1){ valid_dirs.Add(t); } } if(valid_dirs.Count > 0){ AI_Step(valid_dirs.Random()); } } } else{ AI_Step(target); QS(); } } break; } case ActorType.WARG: { bool howl = false; if(DistanceFrom(target) == 1){ if(R.CoinFlip() || group == null || group.Count < 2 || HasAttr(AttrType.COOLDOWN_1)){ Attack(0,target); } else{ howl = true; } } else{ if(group == null || group.Count < 2 || HasAttr(AttrType.COOLDOWN_1)){ if(AI_Step(target)){ QS(); } else{ howl = true; } } else{ howl = true; } } if(howl){ if(group == null || group.Count < 2){ Q1(); break; } B.Add(TheName(true) + " howls. "); PosArray<int> paths = new PosArray<int>(ROWS,COLS); foreach(Actor packmate in group){ packmate.RefreshDuration(AttrType.COOLDOWN_1,2000); if(packmate != this){ var dijkstra = M.tile.GetDijkstraMap(new List<pos>{target.p},x=>!M.tile[x].passable,y=>M.actor[y] != null? 5 : paths[y]+1); if(!dijkstra[packmate.p].IsValidDijkstraValue()){ continue; } List<pos> new_path = new List<pos>(); pos p = packmate.p; while(!p.Equals(target.p)){ p = p.PositionsAtDistance(1,dijkstra).Where(x=>dijkstra[x].IsValidDijkstraValue()).WhereLeast(x=>dijkstra[x]).Random(); new_path.Add(p); paths[p]++; } packmate.path = new_path; } } Q1(); } break; } case ActorType.RUNIC_TRANSCENDENT: { if(SilencedThisTurn()){ return; } if(!HasSpell(SpellType.MERCURIAL_SPHERE)){ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } return; } if(curmp < 2){ B.Add(the_name + " absorbs mana from the universe. ",this); curmp = maxmp; Q1(); } else{ if(M.safetymap == null){ M.UpdateSafetyMap(player); } Tile t = TilesAtDistance(1).Where(x=>x.DistanceFrom(target) == 3 && x.passable && x.actor() == null).WhereLeast(x=>M.safetymap[x.p]).RandomOrDefault(); if(t != null){ //check safety map. if there's a safer spot at distance 3, step there. AI_Step(t); } else{ if(DistanceFrom(target) > 3){ AI_Step(target); } else{ if(DistanceFrom(target) < 3){ AI_Flee(); } } } if(DistanceFrom(target) <= 12 && FirstActorInLine(target) != null && FirstActorInLine(target).DistanceFrom(target) <= 3){ CastSpell(SpellType.MERCURIAL_SPHERE,target); } else{ QS(); } } break; } case ActorType.CARRION_CRAWLER: if(DistanceFrom(target) == 1){ if(!target.HasAttr(AttrType.PARALYZED)){ Attack(0,target); } else{ Attack(1,target); } } else{ AI_Step(target); QS(); } break; case ActorType.MECHANICAL_KNIGHT: if(attrs[AttrType.COOLDOWN_1] == 3){ //no head int dir = Global.RandomDirection(); if(R.CoinFlip()){ Actor a = ActorInDirection(dir); if(a != null){ if(!Attack(0,a)){ B.Add(the_name + " drops its guard! ",this); attrs[AttrType.MECHANICAL_SHIELD] = 0; } } else{ B.Add(the_name + " attacks empty space. ",this); TileInDirection(dir).Bump(dir); B.Add(the_name + " drops its guard! ",this); attrs[AttrType.MECHANICAL_SHIELD] = 0; Q1(); } } else{ Tile t = TileInDirection(dir); if(t.passable){ if(t.actor() == null){ AI_Step(t); QS(); } else{ B.Add(the_name + " bumps into " + t.actor().TheName(true) + ". ",this); QS(); } } else{ B.Add(the_name + " bumps into " + t.TheName(true) + ". ",this); t.Bump(DirectionOf(t)); QS(); } } } else{ if(DistanceFrom(target) == 1){ if(attrs[AttrType.COOLDOWN_1] == 1){ //no arms Attack(1,target); } else{ if(!Attack(0,target)){ B.Add(the_name + " drops its guard! ",this); attrs[AttrType.MECHANICAL_SHIELD] = 0; } } } else{ if(attrs[AttrType.COOLDOWN_1] != 2){ //no legs AI_Step(target); } QS(); } } break; case ActorType.ALASI_BATTLEMAGE: if(SilencedThisTurn()){ return; } if(DistanceFrom(target) > 12){ AI_Step(target); QS(); } else{ if(DistanceFrom(target) == 1){ if(exhaustion < 50){ CastCloseRangeSpellOrAttack(null,target,true); } else{ Attack(0,target); } } else{ CastRangedSpellOrMove(target); } } break; case ActorType.ALASI_SOLDIER: if(DistanceFrom(target) > 2){ AI_Step(target); QS(); attrs[AttrType.COMBO_ATTACK] = 0; } else{ if(FirstActorInLine(target) != null && !FirstActorInLine(target).name.Contains("alasi")){ //I had planned to make this attack possibly hit multiple targets, but not yet. Attack(0,target); } else{ if(AI_Step(target)){ QS(); } else{ AI_Sidestep(target); QS(); } attrs[AttrType.COMBO_ATTACK] = 0; } } break; case ActorType.SKITTERMOSS: if(DistanceFrom(target) == 1){ Attack(0,target); if(target != null && R.CoinFlip()){ //chance of retreating AI_Step(target,true); } } else{ if(R.CoinFlip()){ AI_Step(target); QS(); } else{ AI_Step(TileInDirection(Global.RandomDirection())); QS(); } } break; case ActorType.ALASI_SCOUT: { if(DistanceFrom(target) == 1){ Attack(0,target); } else{ if(curhp == maxhp){ if(FirstActorInLine(target) == target){ Attack(1,target); } else{ AI_Sidestep(target); QS(); } } else{ AI_Step(target); QS(); } } break; } case ActorType.MUD_ELEMENTAL: { int count = 0; int walls = 0; foreach(Tile t in target.TilesAtDistance(1)){ if(t.p.BoundsCheck(M.tile,false) && t.type == TileType.WALL){ ++walls; if(t.actor() == null){ ++count; } } } if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12 && count >= 2 || (count == 1 && walls == 1)){ RefreshDuration(AttrType.COOLDOWN_1,150); foreach(Tile t in target.TilesAtDistance(1)){ if(t.p.BoundsCheck(M.tile,false) && t.type == TileType.WALL && t.actor() == null){ Create(ActorType.MUD_TENTACLE,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent); M.actor[t.p].player_visibility_duration = -1; } } if(count >= 2){ if(player.CanSee(this)){ B.Add(the_name + " calls mud tentacles from the walls! "); } else{ B.Add("Mud tentacles emerge from the walls! "); } } else{ if(player.CanSee(this)){ B.Add(the_name + " calls a mud tentacle from the wall! "); } else{ B.Add("A mud tentacle emerges from the wall! "); } } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; } case ActorType.FLAMETONGUE_TOAD: { bool burrow = false; if((curhp * 3 <= maxhp || DistanceFrom(target) > 6) && R.CoinFlip()){ burrow = true; } if(DistanceFrom(target) <= 6 && DistanceFrom(target) > 1){ if(R.OneIn(20)){ burrow = true; } } if(burrow && !HasAttr(AttrType.COOLDOWN_1)){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(12,16)*100); if(curhp * 3 <= maxhp){ Burrow(TilesWithinDistance(6)); } else{ Burrow(GetCone(DirectionOf(target),6,true)); } } else{ if(!HasAttr(AttrType.COOLDOWN_2) && FirstActorInLine(target) != null && FirstActorInLine(target).DistanceFrom(target) <= 1){ RefreshDuration(AttrType.COOLDOWN_2,R.Between(10,14)*100); Actor first = FirstActorInLine(target); B.Add(TheName(true) + " breathes fire! ",this,first); AnimateProjectile(first,'*',Color.RandomFire); AnimateExplosion(first,1,'*',Color.RandomFire); foreach(Tile t in GetBestLineOfEffect(first)){ t.ApplyEffect(DamageType.FIRE); } foreach(Tile t in first.TilesWithinDistance(1)){ t.ApplyEffect(DamageType.FIRE); if(t.actor() != null){ t.actor().ApplyBurning(); } } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } } break; } case ActorType.ENTRANCER: if(group == null){ if(AI_Flee()){ QS(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ QS(); } } } else{ Actor thrall = group[1]; if(CanSee(thrall) && HasLOE(thrall)){ //cooldown 1 is teleport. cooldown 2 is shield. //if the thrall is visible and you have LOE, the next goal is for the entrancer to be somewhere on the line that starts at the target and extends through the thrall. List<Tile> line_from_target = target.GetBestExtendedLineOfEffect(thrall); bool on_line = line_from_target.Contains(tile()); bool space_near_target = line_from_target.Count > 1 && line_from_target[1].passable && line_from_target[1].actor() == null; if(on_line && DistanceFrom(target) > thrall.DistanceFrom(target)){ if(!HasAttr(AttrType.COOLDOWN_2) && thrall.curhp <= thrall.maxhp / 2){ //check whether you can shield it, if the thrall is low on health. RefreshDuration(AttrType.COOLDOWN_2,1500); B.Add(TheName(true) + " shields " + thrall.TheName(true) + ". ",this,thrall); B.DisplayNow(); Screen.AnimateStorm(thrall.p,1,2,5,'*',Color.White); thrall.attrs[AttrType.SHIELDED] = 1; Q1(); } else{ //check whether you can teleport the thrall closer. if(!HasAttr(AttrType.COOLDOWN_1) && thrall.DistanceFrom(target) > 1 && space_near_target){ Tile dest = line_from_target[1]; RefreshDuration(AttrType.COOLDOWN_1,400); B.Add(TheName(true) + " teleports " + thrall.TheName(true) + ". ",this,thrall); M.Draw(); thrall.Move(dest.row,dest.col); B.DisplayNow(); Screen.AnimateStorm(dest.p,1,1,4,thrall.symbol,thrall.color); foreach(Tile t2 in thrall.GetBestLineOfEffect(dest)){ Screen.AnimateStorm(t2.p,1,1,4,thrall.symbol,thrall.color); } Q1(); } else{ //check whether you can shield it, if the thrall isn't low on health. if(!HasAttr(AttrType.COOLDOWN_2)){ RefreshDuration(AttrType.COOLDOWN_2,1500); B.Add(TheName(true) + " shields " + thrall.TheName(true) + ". ",this,thrall); B.DisplayNow(); Screen.AnimateStorm(thrall.p,1,2,5,'*',Color.White); thrall.attrs[AttrType.SHIELDED] = 1; Q1(); } else{ //check whether you are adjacent to thrall and can step away while remaining on line. List<Tile> valid = line_from_target.Where(x=>DistanceFrom(x) == 1 && x.actor() == null && x.passable); if(DistanceFrom(thrall) == 1 && valid.Count > 0){ AI_Step(valid.Random()); } QS(); } } } } else{ if(on_line){ //if on the line but not behind the thrall, we might be able to swap places or teleport if(DistanceFrom(thrall) == 1){ Move(thrall.row,thrall.col); QS(); } else{ Tile dest = null; foreach(Tile t in line_from_target){ if(t.passable && t.actor() == null){ dest = t; break; } } if(dest != null){ RefreshDuration(AttrType.COOLDOWN_1,400); B.Add(TheName(true) + " teleports " + thrall.TheName(true) + ". ",this,thrall); M.Draw(); thrall.Move(dest.row,dest.col); B.DisplayNow(); Screen.AnimateStorm(dest.p,1,1,4,thrall.symbol,thrall.color); foreach(Tile t2 in thrall.GetBestLineOfEffect(dest)){ Screen.AnimateStorm(t2.p,1,1,4,thrall.symbol,thrall.color); } } Q1(); } } else{ //if there's a free adjacent space on the line and behind the thrall, step there. List<Tile> valid = line_from_target.From(thrall).Where(x=>x.passable && x.actor() == null && x.DistanceFrom(this) == 1); if(valid.Count > 0){ AI_Step(valid.Random()); QS(); } else{ //if you can teleport and there's a free tile on the line between you and the target, teleport the thrall there. List<Tile> valid_between = GetBestLineOfEffect(target).Where(x=>x.passable && x.actor() == null && thrall.HasLOE(x)); if(!HasAttr(AttrType.COOLDOWN_1) && valid_between.Count > 0){ Tile dest = valid_between.Random(); RefreshDuration(AttrType.COOLDOWN_1,400); B.Add(TheName(true) + " teleports " + thrall.TheName(true) + ". ",this,thrall); M.Draw(); thrall.Move(dest.row,dest.col); B.DisplayNow(); Screen.AnimateStorm(dest.p,1,1,4,thrall.symbol,thrall.color); foreach(Tile t2 in thrall.GetBestLineOfEffect(dest)){ Screen.AnimateStorm(t2.p,1,1,4,thrall.symbol,thrall.color); } Q1(); } else{ //step toward a tile on the line (and behind the thrall) List<Tile> valid_behind_thrall = line_from_target.From(thrall).Where(x=>x.passable && x.actor() == null); if(valid_behind_thrall.Count > 0){ AI_Step(valid_behind_thrall.Random()); } QS(); } } } } //the old code: /*if(DistanceFrom(target) < thrall.DistanceFrom(target) && DistanceFrom(thrall) == 1){ Move(thrall.row,thrall.col); QS(); } else{ if(DistanceFrom(target) == 1 && curhp < maxhp){ List<Tile> safe = TilesAtDistance(1).Where(t=>t.passable && t.actor() == null && target.GetBestExtendedLineOfEffect(thrall).Contains(t)); if(DistanceFrom(thrall) == 1 && safe.Count > 0){ AI_Step(safe.Random()); QS(); } else{ if(AI_Flee()){ QS(); } else{ Attack(0,target); } } } else{ if(!HasAttr(AttrType.COOLDOWN_1) && (thrall.DistanceFrom(target) > 1 || !target.GetBestExtendedLineOfEffect(thrall).Any(t=>t.actor()==this))){ //the entrancer tries to be smart about placing the thrall in a position that blocks ranged attacks List<Tile> closest = new List<Tile>(); int dist = 99; foreach(Tile t in thrall.TilesWithinDistance(2).Where(x=>x.passable && (x.actor()==null || x.actor()==thrall))){ if(t.DistanceFrom(target) < dist){ closest.Clear(); closest.Add(t); dist = t.DistanceFrom(target); } else{ if(t.DistanceFrom(target) == dist){ closest.Add(t); } } } List<Tile> in_line = new List<Tile>(); foreach(Tile t in closest){ if(target.GetBestExtendedLineOfEffect(t).Any(x=>x.actor()==this)){ in_line.Add(t); } } Tile tile2 = null; if(in_line.Count > 0){ tile2 = in_line.Random(); } else{ if(closest.Count > 0){ tile2 = closest.Random(); } } if(tile2 != null && tile2.actor() != thrall){ GainAttr(AttrType.COOLDOWN_1,400); B.Add(TheName(true) + " teleports " + thrall.TheName(true) + ". ",this,thrall); M.Draw(); thrall.Move(tile2.row,tile2.col); B.DisplayNow(); Screen.AnimateStorm(tile2.p,1,1,4,thrall.symbol,thrall.color); foreach(Tile t2 in thrall.GetBestLineOfEffect(tile2)){ Screen.AnimateStorm(t2.p,1,1,4,thrall.symbol,thrall.color); } Q1(); } else{ List<Tile> safe = target.GetBestExtendedLineOfEffect(thrall).Where(t=>t.passable && t.actor() == null && t.DistanceFrom(target) > thrall.DistanceFrom(target)).WhereLeast(t=>DistanceFrom(t)); if(safe.Count > 0){ if(safe.Any(t=>t.DistanceFrom(target) > 2)){ AI_Step(safe.Where(t=>t.DistanceFrom(target) > 2).Random()); } else{ AI_Step(safe.Random()); } } QS(); } } else{ if(!HasAttr(AttrType.COOLDOWN_2) && thrall.attrs[AttrType.ARCANE_SHIELDED] < 25){ GainAttr(AttrType.COOLDOWN_2,1500); B.Add(TheName(true) + " shields " + thrall.TheName(true) + ". ",this,thrall); B.DisplayNow(); Screen.AnimateStorm(thrall.p,1,2,5,'*',Color.White); thrall.attrs[AttrType.ARCANE_SHIELDED] = 25; Q1(); } else{ List<Tile> safe = target.GetBestExtendedLineOfEffect(thrall).Where(t=>t.passable && t.actor() == null).WhereLeast(t=>DistanceFrom(t)); if(safe.Count > 0){ if(safe.Any(t=>t.DistanceFrom(target) > 2)){ AI_Step(safe.Where(t=>t.DistanceFrom(target) > 2).Random()); } else{ AI_Step(safe.Random()); } } QS(); } } } }*/ } else{ group[1].FindPath(this); //call for help if(AI_Flee()){ QS(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ QS(); } } } } break; case ActorType.ORC_GRENADIER: if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 8){ attrs[AttrType.COOLDOWN_1]++; Q.Add(new Event(this,(R.Roll(2)*100)+150,AttrType.COOLDOWN_1)); B.Add(TheName(true) + " tosses a grenade toward " + target.the_name + ". ",target); List<Tile> tiles = new List<Tile>(); foreach(Tile tile in target.TilesWithinDistance(1)){ if(tile.passable && !tile.Is(FeatureType.GRENADE)){ tiles.Add(tile); } } Tile t = tiles[R.Roll(tiles.Count)-1]; if(t.actor() != null){ if(t.actor() == player){ B.Add("It lands under you! "); } else{ B.Add("It lands under " + t.actor().the_name + ". ",t.actor()); } } else{ if(t.inv != null){ B.Add("It lands under " + t.inv.TheName() + ". ",t); } } t.features.Add(FeatureType.GRENADE); Q.Add(new Event(t,100,EventType.GRENADE)); Q1(); } else{ if(curhp <= 18){ if(AI_Step(target,true)){ QS(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ QS(); } } } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } } break; case ActorType.MARBLE_HORROR: if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } break; case ActorType.SPELLMUDDLE_PIXIE: if(DistanceFrom(target) == 1){ Attack(0,target); if(target != null && R.CoinFlip()){ AI_Step(target,true); } } else{ AI_Step(target); QS(); } break; case ActorType.OGRE_BARBARIAN: //if has grabbed target, check for open spaces near the opposite side. //if one is found, slam target into that tile, then do the attack. //otherwise, slam target into a solid tile (target doesn't move), then attack. //if nothing is grabbed yet, just keep attacking. if(DistanceFrom(target) == 1){ if(target.HasAttr(AttrType.GRABBED) && attrs[AttrType.GRABBING] == DirectionOf(target) && !target.MovementPrevented(tile())){ Tile t = null; Tile opposite = TileInDirection(DirectionOf(target).RotateDir(true,4)); if(opposite.passable && opposite.actor() == null){ t = opposite; } if(t == null){ List<Tile> near_opposite = new List<Tile>(); foreach(int i in new int[]{-1,1}){ Tile near = TileInDirection(DirectionOf(target).RotateDir(true,4+i)); if(near.passable && near.actor() == null){ near_opposite.Add(near); } } if(near_opposite.Count > 0){ t = near_opposite.Random(); } } if(t != null){ target.attrs[AttrType.TURN_INTO_CORPSE]++; Attack(1,target); target.Move(t.row,t.col); target.CollideWith(target.tile()); target.CorpseCleanup(); } else{ target.attrs[AttrType.TURN_INTO_CORPSE]++; Attack(1,target); target.CollideWith(target.tile()); target.CorpseCleanup(); } } else{ Attack(0,target); } } else{ if(speed == 100){ speed = 50; } if(!HasAttr(AttrType.COOLDOWN_1) && target == player && player.CanSee(this)){ B.Add(the_name + " charges! "); attrs[AttrType.COOLDOWN_1] = 1; } AI_Step(target); if(!HasAttr(AttrType.COOLDOWN_1) && target == player && player.CanSee(this)){ //check twice so the message appears ASAP B.Add(the_name + " charges! "); attrs[AttrType.COOLDOWN_1] = 1; } QS(); } break; case ActorType.MARBLE_HORROR_STATUE: QS(); break; case ActorType.PYREN_ARCHER: //still considering some sort of fire trail movement ability for this guy switch(DistanceFrom(target)){ case 1: if(target.EnemiesAdjacent() > 1){ Attack(0,target); } else{ if(AI_Flee()){ QS(); } else{ Attack(0,target); } } break; case 2: if(FirstActorInLine(target) == target){ FireArrow(target); } else{ if(AI_Flee()){ QS(); } else{ if(AI_Sidestep(target)){ B.Add(the_name + " tries to line up a shot. ",this); } QS(); } } break; case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: if(FirstActorInLine(target) == target){ FireArrow(target); } else{ if(AI_Sidestep(target)){ B.Add(the_name + " tries to line up a shot. ",this); } QS(); } break; default: AI_Step(target); QS(); break; } break; case ActorType.CYCLOPEAN_TITAN: { if(DistanceFrom(target) == 1){ Attack(0,target); } else{ if(DistanceFrom(target) > 2 && DistanceFrom(target) <= 12 && R.OneIn(15) && FirstActorInLine(target) == target){ B.Add(TheName(true) + " lobs a huge rock! ",this,target); AnimateProjectile(target,'*',Color.Gray); pos tp = target.p; int plus_to_hit = -target.TotalSkill(SkillType.DEFENSE)*3; if(target.IsHit(plus_to_hit)){ B.Add("It hits " + target.the_name + "! ",target); if(target.TakeDamage(DamageType.NORMAL,DamageClass.PHYSICAL,R.Roll(4,6),this,"a cyclopean titan's rock")){ if(R.OneIn(8)){ target.ApplyStatus(AttrType.STUNNED,R.Between(3,4)*100); } } } else{ int armor_value = target.TotalProtectionFromArmor(); if(target != player){ armor_value = target.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){ B.Add(target.Your() + " armor blocks it! ",target); } else{ if(target.HasAttr(AttrType.ROOTS) && roll <= (armor_value + 10) * 3){ //potion of roots gives 10 defense B.Add(target.Your() + " root shell blocks it! ",target); } else{ B.Add(target.You("avoid") + " it! ",target); } } } foreach(pos neighbor in tp.PositionsWithinDistance(1,M.tile)){ Tile t = M.tile[neighbor]; if(t.Is(TileType.FLOOR) && R.OneIn(4)){ t.Toggle(null,TileType.GRAVEL); } } Q1(); } else{ bool smashed = false; if(DistanceFrom(target) == 2 && !HasLOE(target)){ Tile t = FirstSolidTileInLine(target); if(t != null && !t.passable){ smashed = true; B.Add(You("smash",true) + " through " + t.TheName(true) + "! ",t); foreach(int dir in DirectionOf(t).GetArc(1)){ TileInDirection(dir).Smash(dir); } Move(t.row,t.col); QS(); } } if(!smashed){ AI_Step(target); QS(); } } } break; } case ActorType.ALASI_SENTINEL: if(DistanceFrom(target) == 1){ Attack(0,target); if(HasAttr(AttrType.JUST_FLUNG)){ attrs[AttrType.JUST_FLUNG] = 0; } else{ if(target != null){ List<Tile> valid_dirs = new List<Tile>(); foreach(Tile t in target.TilesAtDistance(1)){ if(t.passable && t.actor() == null && DistanceFrom(t) == 1){ valid_dirs.Add(t); } } if(valid_dirs.Count > 0){ AI_Step(valid_dirs.Random()); } } } } else{ AI_Step(target); QS(); } break; case ActorType.NOXIOUS_WORM: if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12 && HasLOE(target)){ B.Add(TheName(true) + " breathes poisonous gas. "); List<Tile> area = new List<Tile>(); foreach(Tile t in target.TilesWithinDistance(1)){ if(t.passable && target.HasLOE(t)){ t.AddFeature(FeatureType.POISON_GAS); area.Add(t); } } List<Tile> area2 = target.tile().AddGaseousFeature(FeatureType.POISON_GAS,8); area.AddRange(area2); Event.RemoveGas(area,600,FeatureType.POISON_GAS,18); RefreshDuration(AttrType.COOLDOWN_1,(R.Roll(6) + 18) * 100); Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; case ActorType.LASHER_FUNGUS: { if(DistanceFrom(target) <= 12){ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ if(FirstActorInLine(target) == target){ List<Tile> line = GetBestLineOfEffect(target.row,target.col); line.Remove(line[line.Count-1]); AnimateBoltBeam(line,Color.DarkGreen); pos target_p = target.p; if(Attack(1,target) && M.actor[target_p] != null){ target = M.actor[target_p]; int rowchange = 0; int colchange = 0; if(target.row < row){ rowchange = 1; } if(target.row > row){ rowchange = -1; } if(target.col < col){ colchange = 1; } if(target.col > col){ colchange = -1; } if(!target.AI_MoveOrOpen(target.row+rowchange,target.col+colchange)){ bool moved = false; if(Math.Abs(target.row - row) > Math.Abs(target.col - col)){ if(target.AI_Step(M.tile[row,target.col])){ moved = true; } } else{ if(Math.Abs(target.row - row) < Math.Abs(target.col - col)){ if(target.AI_Step(M.tile[target.row,col])){ moved = true; } } else{ if(target.AI_Step(this)){ moved = true; } } } if(!moved){ //todo: this still isn't ideal. maybe I need an AI_Step that only considers 3 directions - right now, it'll make you move even if it isn't closer. B.Add(target.You("do",true) + "n't move far. ",target); } } } } else{ Q1(); } } } else{ Q1(); } break; } case ActorType.LUMINOUS_AVENGER: { if(DistanceFrom(target) <= 3){ List<Tile> ext = GetBestExtendedLineOfEffect(target); int max_count = Math.Min(5,ext.Count); //look 4 spaces away unless the line is even shorter than that. List<Actor> targets = new List<Actor>(); Tile destination = null; for(int i=0;i<max_count;++i){ Tile t = ext[i]; if(t.passable){ if(t.actor() == null){ if(targets.Contains(target)){ destination = t; } } else{ if(t.actor() != this){ targets.Add(t.actor()); } } } else{ break; } } if(destination != null){ Move(destination.row,destination.col); foreach(Tile t in ext.To(destination)){ colorchar cch = M.VisibleColorChar(t.row,t.col); cch.bgcolor = Color.Yellow; if(Global.LINUX && !Screen.GLMode){ cch.bgcolor = Color.DarkYellow; } if(cch.color == cch.bgcolor){ cch.color = Color.Black; } Screen.WriteMapChar(t.row,t.col,cch); Game.GLUpdate(); Thread.Sleep(15); } foreach(Actor a in targets){ Attack(0,a,true); } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } } else{ AI_Step(target); QS(); } break; } case ActorType.VAMPIRE: if(DistanceFrom(target) == 1){ Attack(0,target); } else{ if(DistanceFrom(target) <= 12){ if(tile().IsLit() && !HasAttr(AttrType.COOLDOWN_1)){ attrs[AttrType.COOLDOWN_1]++; B.Add(the_name + " gestures. ",this); List<Tile> tiles = new List<Tile>(); foreach(Tile t in target.TilesWithinDistance(6)){ if(t.passable && t.actor() == null && DistanceFrom(t) >= DistanceFrom(target) && target.HasLOS(t) && target.HasLOE(t)){ tiles.Add(t); } } if(tiles.Count == 0){ foreach(Tile t in target.TilesWithinDistance(6)){ //same, but with no distance requirement if(t.passable && t.actor() == null && target.HasLOS(t) && target.HasLOE(t)){ tiles.Add(t); } } } if(tiles.Count == 0){ B.Add("Nothing happens. ",this); } else{ if(tiles.Count == 1){ B.Add("A blood moth appears! "); } else{ B.Add("Blood moths appear! "); } for(int i=0;i<2;++i){ if(tiles.Count > 0){ Tile t = tiles.RemoveRandom(); Create(ActorType.BLOOD_MOTH,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent); M.actor[t.row,t.col].player_visibility_duration = -1; } } } Q1(); } else{ AI_Step(target); QS(); } } else{ AI_Step(target); QS(); } } break; case ActorType.ORC_WARMAGE: { if(SilencedThisTurn()){ return; } switch(DistanceFrom(target)){ case 1: { List<SpellType> close_range = new List<SpellType>(); close_range.Add(SpellType.MAGIC_HAMMER); close_range.Add(SpellType.MAGIC_HAMMER); close_range.Add(SpellType.BLINK); if(target.EnemiesAdjacent() > 1 || R.CoinFlip()){ CastCloseRangeSpellOrAttack(close_range,target,false); } else{ if(AI_Step(target,true)){ QS(); } else{ CastCloseRangeSpellOrAttack(close_range,target,false); } } break; } case 2: if(R.CoinFlip()){ if(AI_Step(target,true)){ QS(); } else{ if(FirstActorInLine(target) == target){ CastRangedSpellOrMove(target); } else{ AI_Sidestep(target); QS(); } } } else{ if(FirstActorInLine(target) == target){ CastRangedSpellOrMove(target); } else{ if(AI_Step(target,true)){ QS(); } else{ AI_Sidestep(target); QS(); } } } break; case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: if(FirstActorInLine(target) == target){ CastRangedSpellOrMove(target); } else{ AI_Sidestep(target); QS(); } break; default: AI_Step(target); QS(); break; } break; } case ActorType.NECROMANCER: { if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12){ attrs[AttrType.COOLDOWN_1]++; Q.Add(new Event(this,(R.Roll(4)+8)*100,AttrType.COOLDOWN_1)); B.Add(the_name + " calls out to the dead. ",this); ActorType summon = R.CoinFlip()? ActorType.SKELETON : ActorType.ZOMBIE; List<Tile> tiles = new List<Tile>(); foreach(Tile tile in TilesWithinDistance(2)){ if(tile.passable && tile.actor() == null && DirectionOf(tile) == DirectionOf(target)){ tiles.Add(tile); } } if(tiles.Count == 0){ foreach(Tile tile in TilesWithinDistance(2)){ if(tile.passable && tile.actor() == null){ tiles.Add(tile); } } } if(tiles.Count == 0 || (group != null && group.Count > 3)){ B.Add("Nothing happens. ",this); } else{ Tile t = tiles.Random(); B.Add(Prototype(summon).a_name + " digs through the floor! "); Create(summon,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent); M.actor[t.row,t.col].player_visibility_duration = -1; if(group == null){ group = new List<Actor>{this}; } group.Add(M.actor[t.row,t.col]); M.actor[t.row,t.col].group = group; } Q1(); } else{ bool blast = false; switch(DistanceFrom(target)){ case 1: if(AI_Step(target,true)){ QS(); } else{ Attack(0,target); } break; case 2: if(R.CoinFlip() && FirstActorInLine(target) == target){ blast = true; } else{ if(AI_Step(target,true)){ QS(); } else{ blast = true; } } break; case 3: case 4: case 5: case 6: if(FirstActorInLine(target) == target){ blast = true; } else{ AI_Sidestep(target); QS(); } break; default: AI_Step(target); QS(); break; } if(blast){ B.Add(TheName(true) + " fires dark energy at " + target.TheName(true) + ". ",this,target); AnimateBoltProjectile(target,Color.DarkBlue); if(target.TakeDamage(DamageType.MAGIC,DamageClass.MAGICAL,R.Roll(6),this,"*blasted by a necromancer")){ target.IncreaseExhaustion(R.Roll(3)); } Q1(); } } break; } case ActorType.STALKING_WEBSTRIDER: { bool burrow = false; if(DistanceFrom(target) >= 2 && DistanceFrom(target) <= 6){ if(R.CoinFlip() && !target.tile().Is(FeatureType.WEB)){ burrow = true; } } if((DistanceFrom(target) > 6 || target.HasAttr(AttrType.POISONED))){ burrow = true; } if(burrow && !HasAttr(AttrType.COOLDOWN_1)){ RefreshDuration(AttrType.COOLDOWN_1,R.Between(5,8)*100); if(DistanceFrom(target) <= 2){ Burrow(TilesWithinDistance(6)); } else{ Burrow(GetCone(DirectionOf(target),6,true)); } } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; } case ActorType.ORC_ASSASSIN: if(DistanceFrom(target) > 2 && attrs[AttrType.TURNS_VISIBLE] < 0){ Tile t = TilesAtDistance(1).Where(x=>x.passable && x.actor() == null && target.DistanceFrom(x) == target.DistanceFrom(this)-1 && !target.CanSee(x)).RandomOrDefault(); if(t != null){ AI_Step(t); FindPath(target); //so it won't forget where the target is... QS(); } else{ AI_Step(target); QS(); } } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); if(DistanceFrom(target) == 1){ Attack(1,target); } else{ QS(); } } } break; case ActorType.MACHINE_OF_WAR: { if(attrs[AttrType.COOLDOWN_1] % 2 == 0){ //the machine of war moves on even turns and fires on odd turns. AI_Step(target); QS(); } else{ if(DistanceFrom(target) <= 12 && FirstActorInLine(target) == target){ B.Add(TheName(true) + " fires a stream of scalding oil at " + target.the_name + ". ",target); List<Tile> covered_in_oil = GetBestLineOfEffect(target); List<Tile> added = new List<Tile>(); foreach(Tile t in covered_in_oil){ foreach(int dir in U.FourDirections){ Tile neighbor = t.TileInDirection(dir); if(R.OneIn(3) && neighbor.passable && !covered_in_oil.Contains(neighbor)){ added.AddUnique(neighbor); } } } covered_in_oil.AddRange(added); List<pos> cells = new List<pos>(); List<Actor> oiled_actors = new List<Actor>(); for(int i=0;covered_in_oil.Count > 0;++i){ List<Tile> removed = new List<Tile>(); foreach(Tile t in covered_in_oil){ if(DistanceFrom(t) == i){ t.AddFeature(FeatureType.OIL); if(t.actor() != null && t.actor() != this){ oiled_actors.Add(t.actor()); } removed.Add(t); if(DistanceFrom(t) > 0){ cells.Add(t.p); } } } foreach(Tile t in removed){ covered_in_oil.Remove(t); } if(cells.Count > 0){ Screen.AnimateMapCells(cells,new colorchar(',',Color.DarkYellow),20); } } oiled_actors.AddUnique(target); M.Draw(); foreach(Actor a in oiled_actors){ if(a.TakeDamage(DamageType.FIRE,DamageClass.PHYSICAL,R.Roll(4,6),this,"a stream of scalding oil")){ if(a.IsBurning()){ a.ApplyBurning(); } else{ if(!a.HasAttr(AttrType.SLIMED,AttrType.FROZEN)){ a.attrs[AttrType.OIL_COVERED]++; B.Add(a.YouAre() + " covered in oil. ",a); } } } } Q1(); } else{ Q1(); } } break; } case ActorType.IMPOSSIBLE_NIGHTMARE: { if(DistanceFrom(target) == 1){ Attack(0,target); } else{ Tile t = target.TilesAtDistance(DistanceFrom(target)-1).Where(x=>x.passable && x.actor() == null).RandomOrDefault(); if(t != null){ Move(t.row,t.col); //todo: fear effect? } QS(); } break; } case ActorType.FIRE_DRAKE: /*if(player.magic_trinkets.Contains(MagicTrinketType.RING_OF_RESISTANCE) && DistanceFrom(player) <= 12 && CanSee(player)){ B.Add(the_name + " exhales an orange mist toward you. "); foreach(Tile t in GetBestLineOfEffect(player)){ Screen.AnimateStorm(t.p,1,2,3,'*',Color.Red); } B.Add("Your ring of resistance melts and drips onto the floor! "); player.magic_trinkets.Remove(MagicTrinketType.RING_OF_RESISTANCE); Q.Add(new Event(this,100,EventType.MOVE)); } else{ if(player.EquippedArmor == ArmorType.FULL_PLATE_OF_RESISTANCE && DistanceFrom(player) <= 12 && CanSee(player)){ B.Add(the_name + " exhales an orange mist toward you. "); foreach(Tile t in GetBestLine(player)){ Screen.AnimateStorm(t.p,1,2,3,'*',Color.Red); } B.Add("The runes drip from your full plate of resistance! "); player.EquippedArmor = ArmorType.FULL_PLATE; player.UpdateOnEquip(ArmorType.FULL_PLATE_OF_RESISTANCE,ArmorType.FULL_PLATE); Q.Add(new Event(this,100,EventType.MOVE)); } else{*/ if(!HasAttr(AttrType.COOLDOWN_1)){ if(DistanceFrom(target) <= 12){ attrs[AttrType.COOLDOWN_1]++; int cooldown = (R.Roll(1,4)+1) * 100; Q.Add(new Event(this,cooldown,AttrType.COOLDOWN_1)); AnimateBeam(target,Color.RandomFire,'*'); B.Add(TheName(true) + " breathes fire. ",target); target.TakeDamage(DamageType.FIRE,DamageClass.PHYSICAL,R.Roll(6,6),this,"*roasted by fire breath"); target.ApplyBurning(); Q.Add(new Event(this,200,EventType.MOVE)); } else{ AI_Step(target); QS(); } } else{ if(DistanceFrom(target) == 1){ Attack(R.Roll(1,2)-1,target); } else{ AI_Step(target); QS(); } } //} //} break; case ActorType.GHOST: { attrs[AttrType.AGGRESSION_MESSAGE_PRINTED] = 1; bool tombstone = false; foreach(Tile t in TilesWithinDistance(1)){ if(t.type == TileType.TOMBSTONE){ tombstone = true; } } if(!tombstone){ B.Add("The ghost vanishes. ",this); Kill(); return; } if(target == null || DistanceFrom(target) > 2){ List<Tile> valid = TilesAtDistance(1).Where(x=>x.TilesWithinDistance(1).Any(y=>y.type == TileType.TOMBSTONE)); if(valid.Count > 0){ AI_Step(valid.Random()); } QS(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ List<Tile> valid = tile().NeighborsBetween(target.row,target.col).Where(x=>x.passable && x.actor() == null && x.TilesWithinDistance(1).Any(y=>y.type == TileType.TOMBSTONE)); if(valid.Count == 0){ valid = TilesAtDistance(1).Where(x=>x.TilesWithinDistance(1).Any(y=>y.type == TileType.TOMBSTONE)); } if(valid.Count > 0){ AI_Step(valid.Random()); } QS(); } } break; } case ActorType.BLADE: { attrs[AttrType.AGGRESSION_MESSAGE_PRINTED] = 1; List<Actor> valid_targets = new List<Actor>(); //this is based on EnragedMove(), with an exception for other blades int max_dist = Math.Max(Math.Max(row,col),Math.Max(ROWS-row,COLS-col)); //this should find the farthest edge of the map for(int i=1;i<max_dist && valid_targets.Count == 0;++i){ foreach(Actor a in ActorsAtDistance(i)){ if(a.type != ActorType.BLADE && CanSee(a) && HasLOE(a)){ valid_targets.Add(a); } } } if(valid_targets.Count > 0){ if(target == null || !valid_targets.Contains(target)){ //keep old target if possible target = valid_targets.Random(); } if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } else{ if(target != null){ SeekAI(); } else{ QS(); } } break; } case ActorType.PHANTOM_CONSTRICTOR: case ActorType.PHANTOM_WASP: { if(DistanceFrom(target) == 1){ Attack(0,target); } else{ List<Tile> tiles = new List<Tile>(); //i should turn this "slither" movement into a standardized attribute or something if(target.row == row || target.col == col){ int targetdir = DirectionOf(target); for(int i=-1;i<=1;++i){ pos adj = p.PosInDir(targetdir.RotateDir(true,i)); if(M.tile[adj].passable && M.actor[adj] == null){ tiles.Add(M.tile[adj]); } } } if(tiles.Count > 0){ AI_Step(tiles.Random()); } else{ AI_Step(target); } QS(); } break; } case ActorType.MINOR_DEMON: case ActorType.FROST_DEMON: case ActorType.BEAST_DEMON: case ActorType.DEMON_LORD: { int damage_threshold = 1; if(type == ActorType.BEAST_DEMON){ damage_threshold = 0; } if(target == player && attrs[AttrType.COOLDOWN_2] > damage_threshold && CanSee(target)){ switch(type){ case ActorType.MINOR_DEMON: case ActorType.BEAST_DEMON: if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } break; case ActorType.FROST_DEMON: if(!HasAttr(AttrType.COOLDOWN_1) && DistanceFrom(target) <= 12 && FirstActorInLine(target) == target){ attrs[AttrType.COOLDOWN_1] = 1; AnimateProjectile(target,'*',Color.RandomIce); foreach(Tile t in GetBestLineOfEffect(target)){ t.ApplyEffect(DamageType.COLD); } B.Add(TheName(true) + " fires a chilling sphere. ",target); if(target.TakeDamage(DamageType.COLD,DamageClass.PHYSICAL,R.Roll(3,6),this,"a frost demon")){ target.ApplyStatus(AttrType.SLOWED,R.Between(4,7)*100); //target.RefreshDuration(AttrType.SLOWED,R.Between(4,7)*100,target.YouAre() + " no longer slowed. ",target); } Q1(); } else{ if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } } break; case ActorType.DEMON_LORD: if(DistanceFrom(target) > 2){ AI_Step(target); QS(); } else{ if(FirstActorInLine(target) != null){ Attack(0,target); } else{ if(AI_Step(target)){ QS(); } else{ AI_Sidestep(target); QS(); } } } break; } } else{ if(row >= 7 && row <= 12 && col >= 30 && col <= 35){ //near the center foreach(Actor a in ActorsAtDistance(1)){ if(a.IsFinalLevelDemon()){ List<Tile> dist2 = new List<Tile>(); foreach(Tile t in TilesWithinDistance(5)){ if(t.TilesAtDistance(2).Any(x=>x.type == TileType.FIRE_RIFT) && !t.TilesAtDistance(1).Any(x=>x.type == TileType.FIRE_RIFT)){ dist2.Add(t); } } //if there's another distance 2 (from the center) tile with no adjacent demons, move there //List<Tile> valid = dist2.Where(x=>DistanceFrom(x) == 1 && x.actor() == null && !x.TilesAtDistance(1).Any(y=>y.actor() != null && y.actor().Is(ActorType.MINOR_DEMON,ActorType.FROST_DEMON,ActorType.BEAST_DEMON,ActorType.DEMON_LORD))); List<Tile> valid = dist2.Where(x=>DistanceFrom(x) == 1); valid = valid.Where(x=>x.actor() == null && !x.TilesAtDistance(1).Any(y=>y.actor() != null && y.actor() != this && y.actor().IsFinalLevelDemon())); if(valid.Count > 0){ AI_Step(valid.Random()); } break; } } if(player.HasLOS(this)){ B.Add(TheName(true) + " chants. ",this); } M.IncrementClock(); Q1(); } else{ if(path != null && path.Count > 0){ if(!PathStep()){ QS(); } } else{ FindPath(9+R.Between(0,1),32+R.Between(0,1)); if(!PathStep()){ QS(); } } } } break; } default: if(DistanceFrom(target) == 1){ Attack(0,target); } else{ AI_Step(target); QS(); } break; } }