public void RemoveUnconnectedAreas() { //leaving only the largest int[,] num = new int[H,W]; for(int i=0;i<H;++i){ for(int j=0;j<W;++j){ if(map[i,j].IsPassable()){ num[i,j] = 0; } else{ num[i,j] = -1; } } } int count = 0; for(int i=0;i<H;++i){ for(int j=0;j<W;++j){ if(num[i,j] == 0){ count++; num[i,j] = count; bool changed = true; while(changed){ changed = false; for(int s=0;s<H;++s){ for(int t=0;t<W;++t){ if(num[s,t] == count){ for(int ds=-1;ds<=1;++ds){ for(int dt=-1;dt<=1;++dt){ if(BoundsCheck(s+ds,t+dt,true) && num[s+ds,t+dt] == 0){ num[s+ds,t+dt] = count; changed = true; } } } } } } } } } } int biggest_area = -1; int size_of_biggest_area = 0; for(int k=1;k<=count;++k){ int size = 0; for(int i=0;i<H;++i){ for(int j=0;j<W;++j){ if(num[i,j] == k){ size++; } } } if(size > size_of_biggest_area){ size_of_biggest_area = size; biggest_area = k; } } for(int i=0;i<H;++i){ for(int j=0;j<W;++j){ if(num[i,j] != biggest_area){ if(!map[i,j].IsPassable()){ pos p = new pos(i,j); bool make_wall = true; foreach(pos neighbor in p.PositionsAtDistance(1,map)){ if(num[neighbor.row,neighbor.col] == biggest_area){ make_wall = false; break; } } if(make_wall){ map[i,j] = CellType.Wall; } } else{ map[i,j] = CellType.Wall; } } } } }
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 pos MoveRoom(List<pos> room,int direction) { List<CellType> prev_types = new List<CellType>(); foreach(pos p in room){ prev_types.Add(map[p]); map[p] = CellType.Wall; } List<pos> edge_positions = room.Where(x=>!room.Contains(x.PosInDir(direction))); pos offset = new pos(0,0); while(true){ pos new_offset = offset.PosInDir(direction); bool good = true; foreach(pos p in edge_positions){ pos n = new pos(p.row + new_offset.row,p.col + new_offset.col); foreach(pos neighbor in n.PositionsAtDistance(1)){ if(!neighbor.BoundsCheck(map,true) || !map[neighbor].IsWall()){ good = false; break; } } if(!good){ break; } } if(good){ offset = new_offset; } else{ break; } } int idx = 0; foreach(pos p in room){ map[p.row + offset.row,p.col + offset.col] = prev_types[idx++]; } return offset; }
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 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; } } }
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 void GenerateLevel() { if(current_level < 20){ ++current_level; } InitializeNewLevel(); PosArray<CellType> map = GenerateMap(level_types[current_level-1]); List<pos> interesting_tiles = new List<pos>(); for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if(map[i,j] == CellType.InterestingLocation){ interesting_tiles.Add(new pos(i,j)); } } } int attempts = 0; List<CellType> shrines = null; if(current_level % 2 == 1){ shrines = new List<CellType>{CellType.SpecialFeature1,CellType.SpecialFeature2,CellType.SpecialFeature3,CellType.SpecialFeature4,CellType.SpecialFeature5}; shrines.Randomize(); // after this initialization step, shrines are only removed from the front of the list, to enable prediction of pairs. nextLevelShrines = new List<CellType>(); int n = R.Between(0,5); for(int i=0;i<n;++i){ nextLevelShrines.Add(shrines.RemoveLast()); } } else{ shrines = nextLevelShrines; nextLevelShrines = null; } while(shrines.Count > 0){ attempts = 0; for(bool done=false;!done;++attempts){ 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; } pos temp = new pos(rr,rc); if(shrines.Count > 1){ if(map[rr,rc].IsFloor()){ if(attempts > 1000){ bool good = true; foreach(pos p in temp.PositionsWithinDistance(4,map)){ CellType ch = map[p]; if(ch.Is(CellType.SpecialFeature1,CellType.SpecialFeature2,CellType.SpecialFeature3,CellType.SpecialFeature4,CellType.SpecialFeature5)){ good = false; } } if(good){ List<pos> dist2 = new List<pos>(); foreach(pos p2 in temp.PositionsAtDistance(2,map)){ if(map[p2].IsFloor()){ dist2.Add(p2); } } if(dist2.Count > 0){ map[rr,rc] = shrines.RemoveFirst(); pos p2 = dist2.Random(); map[p2.row,p2.col] = shrines.RemoveFirst(); done = true; break; } } else{ interesting_tiles.Remove(temp); } } bool floors = true; foreach(pos p in temp.PositionsAtDistance(1,map)){ if(!map[p.row,p.col].IsFloor()){ floors = false; } } foreach(pos p in temp.PositionsWithinDistance(3,map)){ CellType ch = map[p]; if(ch.Is(CellType.SpecialFeature1,CellType.SpecialFeature2,CellType.SpecialFeature3,CellType.SpecialFeature4,CellType.SpecialFeature5)){ floors = false; } } if(floors){ if(R.CoinFlip()){ map[rr-1,rc] = shrines.RemoveFirst(); map[rr+1,rc] = shrines.RemoveFirst(); } else{ map[rr,rc-1] = shrines.RemoveFirst(); map[rr,rc+1] = shrines.RemoveFirst(); } CellType center = CellType.Wall; while(center == CellType.Wall){ switch(R.Roll(5)){ case 1: if(level_types[current_level-1] != LevelType.Hive && level_types[current_level-1] != LevelType.Garden){ center = CellType.Pillar; } break; case 2: center = CellType.Statue; break; case 3: if(level_types[current_level-1] != LevelType.Garden){ center = CellType.FirePit; } break; case 4: center = CellType.ShallowWater; break; case 5: if(level_types[current_level-1] != LevelType.Hive){ center = CellType.Torch; } break; } } map[rr,rc] = center; interesting_tiles.Remove(temp); done = true; break; } } } else{ if(map[rr,rc].IsFloor()){ bool good = true; foreach(pos p in temp.PositionsWithinDistance(2,map)){ CellType ch = map[p]; if(ch.Is(CellType.SpecialFeature1,CellType.SpecialFeature2,CellType.SpecialFeature3,CellType.SpecialFeature4,CellType.SpecialFeature5)){ good = false; } } if(good){ if(attempts > 1000){ map[rr,rc] = shrines.RemoveFirst(); interesting_tiles.Remove(temp); done = true; break; } else{ bool floors = true; foreach(pos p in temp.PositionsAtDistance(1,map)){ if(!map[p.row,p.col].IsFloor()){ floors = false; } } if(floors){ map[rr,rc] = shrines.RemoveFirst(); interesting_tiles.Remove(temp); done = true; break; } } } } if(map[rr,rc].IsWall()){ if(!map[rr+1,rc].IsFloor() && !map[rr-1,rc].IsFloor() && !map[rr,rc-1].IsFloor() && !map[rr,rc+1].IsFloor()){ continue; //no floors? retry. } bool no_good = false; foreach(pos p in temp.PositionsWithinDistance(2,map)){ CellType ch = map[p]; if(ch.Is(CellType.SpecialFeature1,CellType.SpecialFeature2,CellType.SpecialFeature3,CellType.SpecialFeature4,CellType.SpecialFeature5)){ no_good = true; } } if(no_good){ continue; } int walls = 0; foreach(pos p in temp.PositionsAtDistance(1,map)){ if(map[p].IsWall()){ ++walls; } } if(walls >= 5){ int successive_walls = 0; CellType[] rotated = new CellType[8]; for(int i=0;i<8;++i){ pos temp2; temp2 = temp.PosInDir(8.RotateDir(true,i)); rotated[i] = map[temp2.row,temp2.col]; } for(int i=0;i<15;++i){ if(rotated[i%8].IsWall()){ ++successive_walls; } else{ successive_walls = 0; } if(successive_walls == 5){ done = true; map[rr,rc] = shrines.RemoveFirst(); interesting_tiles.Remove(temp); break; } } } } } } } int num_chests = R.Between(0,1); if(level_types[current_level-1] == LevelType.Crypt){ num_chests -= map.PositionsWhere(x=>map[x] == CellType.Chest).Count; } for(int i=0;i<num_chests;++i){ int tries = 0; for(bool done=false;!done;++tries){ 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[rr,rc] = CellType.RoomInterior; } if(map[rr,rc].IsFloor()){ bool floors = true; pos temp = new pos(rr,rc); foreach(pos p in temp.PositionsAtDistance(1,map)){ if(!map[p.row,p.col].IsFloor()){ floors = false; } } if(floors || tries > 1000){ //after 1000 tries, place it anywhere map[rr,rc] = CellType.Chest; done = true; } } } } attempts = 0; for(bool done=false;!done;++attempts){ 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; pos temp = new pos(rr,rc); foreach(pos p in temp.PositionsAtDistance(1,map)){ if(!map[p.row,p.col].IsFloor()){ floors = false; } } if(floors || attempts > 1000){ map[rr,rc] = CellType.Stairs; done = true; } } } if(level_types[current_level-1] != LevelType.Garden){ GenerateFloorTypes(map); GenerateFeatures(map,interesting_tiles); } int num_traps = R.Roll(2,3); for(int i=0;i<num_traps;++i){ int tries = 0; for(bool done=false;!done && tries < 100;++tries){ int rr = R.Roll(ROWS-2); int rc = R.Roll(COLS-2); if(map[rr,rc].IsFloor() && map[rr,rc] != CellType.ShallowWater){ map[rr,rc] = CellType.Trap; done = true; } } } List<Tile> hidden = new List<Tile>(); Event grave_dirt_event = null; Event poppy_event = null; Tile stairs = null; for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ //Screen.WriteMapChar(i,j,map[i,j]); switch(map[i,j]){ case CellType.Wall: case CellType.Pillar: Tile.Create(TileType.WALL,i,j); break; case CellType.Door: if(R.OneIn(120)){ if(R.CoinFlip()){ Tile.Create(TileType.STONE_SLAB,i,j); Q.Add(new Event(tile[i,j],new List<Tile>{tile[i,j]},100,EventType.STONE_SLAB)); } else{ Tile.Create(TileType.HIDDEN_DOOR,i,j); hidden.Add(tile[i,j]); } } else{ Tile.Create(TileType.DOOR_C,i,j); } break; case CellType.Stairs: if(current_level < 20){ Tile.Create(TileType.STAIRS,i,j); stairs = tile[i,j]; } else{ if(current_level == 20){ Tile.Create(TileType.STAIRS,i,j); tile[i,j].color = Color.Red; tile[i,j].SetName("scorched stairway"); stairs = tile[i,j]; } else{ Tile.Create(TileType.FLOOR,i,j); } } break; case CellType.Statue: Tile.Create(TileType.STATUE,i,j); break; case CellType.Rubble: Tile.Create(TileType.RUBBLE,i,j); break; case CellType.FirePit: Tile.Create(TileType.FIREPIT,i,j); break; case CellType.Pool: Tile.Create(TileType.POOL_OF_RESTORATION,i,j); break; case CellType.BlastFungus: Tile.Create(TileType.BLAST_FUNGUS,i,j); break; case CellType.CrackedWall: Tile.Create(TileType.CRACKED_WALL,i,j); break; case CellType.Chest: Tile.Create(TileType.CHEST,i,j); break; case CellType.Trap: { TileType type = Tile.RandomTrap(); Tile.Create(type,i,j); if(tile[i,j].IsTrap()){ tile[i,j].name = "floor"; tile[i,j].the_name = "the floor"; tile[i,j].a_name = "a floor"; tile[i,j].symbol = '.'; tile[i,j].color = Color.White; hidden.Add(tile[i,j]); } break; } case CellType.Geyser: Tile.Create(TileType.FIRE_GEYSER,i,j); int frequency = R.Roll(31) + 8; //9-39 int variance = R.Roll(10) - 1; //0-9 int variance_amount = (frequency * variance) / 10; int number_of_values = variance_amount*2 + 1; int minimum_value = frequency - variance_amount; if(minimum_value < 5){ int diff = 5 - minimum_value; number_of_values -= diff; minimum_value = 5; } int delay = ((minimum_value - 1) + R.Roll(number_of_values)) * 100; Q.Add(new Event(tile[i,j],delay + 200,EventType.FIRE_GEYSER,(frequency*10)+variance)); //notice the hacky way the value is stored Q.Add(new Event(tile[i,j],delay,EventType.FIRE_GEYSER_ERUPTION,2)); break; case CellType.FogVent: Tile.Create(TileType.FOG_VENT,i,j); Q.Add(new Event(tile[i,j],100,EventType.FOG_VENT)); break; case CellType.PoisonVent: Tile.Create(TileType.POISON_GAS_VENT,i,j); Q.Add(new Event(tile[i,j],100,EventType.POISON_GAS_VENT)); break; case CellType.Tombstone: { Tile t = Tile.Create(TileType.TOMBSTONE,i,j); if(R.OneIn(8)){ Q.Add(new Event(null,new List<Tile>{t},100,EventType.TOMBSTONE_GHOST)); } break; } case CellType.HiddenDoor: Tile.Create(TileType.HIDDEN_DOOR,i,j); hidden.Add(tile[i,j]); break; case CellType.SpecialFeature1: Tile.Create(TileType.COMBAT_SHRINE,i,j); break; case CellType.SpecialFeature2: Tile.Create(TileType.DEFENSE_SHRINE,i,j); break; case CellType.SpecialFeature3: Tile.Create(TileType.MAGIC_SHRINE,i,j); break; case CellType.SpecialFeature4: Tile.Create(TileType.SPIRIT_SHRINE,i,j); break; case CellType.SpecialFeature5: Tile.Create(TileType.STEALTH_SHRINE,i,j); break; case CellType.DeepWater: case CellType.ShallowWater: Tile.Create(TileType.WATER,i,j); break; case CellType.Barrel: Tile.Create(TileType.BARREL,i,j); break; case CellType.Brush: Tile.Create(TileType.BRUSH,i,j); break; case CellType.GlowingFungus: Tile.Create(TileType.GLOWING_FUNGUS,i,j); break; case CellType.GraveDirt: Tile.Create(TileType.GRAVE_DIRT,i,j); if(grave_dirt_event == null){ grave_dirt_event = new Event(new List<Tile>{tile[i,j]},100,EventType.GRAVE_DIRT); Q.Add(grave_dirt_event); } else{ grave_dirt_event.area.Add(tile[i,j]); } break; case CellType.Gravel: Tile.Create(TileType.GRAVEL,i,j); break; case CellType.Ice: Tile.Create(TileType.ICE,i,j); break; case CellType.Oil: Tile.Create(TileType.FLOOR,i,j); tile[i,j].AddFeature(FeatureType.OIL); break; case CellType.PoisonBulb: Tile.Create(TileType.POISON_BULB,i,j); break; case CellType.Poppies: Tile.Create(TileType.POPPY_FIELD,i,j); if(poppy_event == null){ poppy_event = new Event(new List<Tile>{tile[i,j]},100,EventType.POPPIES); Q.Add(poppy_event); } else{ poppy_event.area.Add(tile[i,j]); } break; case CellType.Slime: Tile.Create(TileType.FLOOR,i,j); tile[i,j].AddFeature(FeatureType.SLIME); break; case CellType.Torch: Tile.Create(TileType.STANDING_TORCH,i,j); break; case CellType.Vine: Tile.Create(TileType.VINE,i,j); break; case CellType.Webs: Tile.Create(TileType.FLOOR,i,j); tile[i,j].AddFeature(FeatureType.WEB); break; default: Tile.Create(TileType.FLOOR,i,j); break; } //alltiles.Add(tile[i,j]); tile[i,j].solid_rock = true; } } //Input.ReadKey(); player.ResetForNewLevel(); foreach(Tile t in AllTiles()){ if(t.light_radius > 0){ t.UpdateRadius(0,t.light_radius); } } int num_items = R.Between(0,2); for(int i=num_items;i>0;--i){ SpawnItem(); } bool poltergeist_spawned = false; //i'm not sure this is the right call, but for now bool mimic_spawned = false; // i'm limiting these guys, to avoid "empty" levels bool marble_horror_spawned = false; int num_monsters = R.Roll(2,2) + 4; if(num_monsters == 6){ num_monsters += R.Roll(3); //this works out to 7/12 seven, 4/12 eight, and 1/12 nine. } for(int i=num_monsters;i>0;--i){ ActorType type = MobType(); if(type == ActorType.POLTERGEIST){ if(!poltergeist_spawned){ SpawnMob(type); poltergeist_spawned = true; } else{ ++i; //try again.. } } else{ if(type == ActorType.MIMIC){ if(!mimic_spawned){ SpawnMob(type); mimic_spawned = true; } else{ ++i; } } else{ if(type == ActorType.MARBLE_HORROR){ Tile statue = AllTiles().Where(t=>t.type == TileType.STATUE).RandomOrDefault(); if(!marble_horror_spawned && statue != null){ SpawnMob(type); marble_horror_spawned = true; } else{ ++i; } } else{ if(type == ActorType.ENTRANCER){ if(i >= 2){ //need 2 slots here Actor entrancer = SpawnMob(type); entrancer.attrs[AttrType.WANDERING]++; List<Tile> tiles = new List<Tile>(); int dist = 1; while(tiles.Count == 0 && dist < 100){ foreach(Tile t in entrancer.TilesAtDistance(dist)){ if(t.passable && !t.IsTrap() && t.actor() == null){ tiles.Add(t); } } ++dist; } if(tiles.Count > 0){ ActorType thralltype = ActorType.SPECIAL; bool done = false; while(!done){ thralltype = MobType(); switch(thralltype){ //not on the list: group/stealth/ranged/rare/immobile/hazardous monsters, plus carrion crawler, mechanical knight, mud elemental, and luminous avenger case ActorType.ROBED_ZEALOT: case ActorType.WILD_BOAR: case ActorType.TROLL: case ActorType.DERANGED_ASCETIC: case ActorType.ALASI_BATTLEMAGE: case ActorType.ALASI_SOLDIER: case ActorType.SKITTERMOSS: case ActorType.STONE_GOLEM: case ActorType.OGRE_BARBARIAN: case ActorType.SNEAK_THIEF: case ActorType.CRUSADING_KNIGHT: case ActorType.CORROSIVE_OOZE: case ActorType.ALASI_SENTINEL: case ActorType.CYCLOPEAN_TITAN: done = true; break; } } Tile t = tiles.Random(); Actor thrall = Actor.Create(thralltype,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent); if(entrancer.group == null){ entrancer.group = new List<Actor>{entrancer}; } entrancer.group.Add(thrall); thrall.group = entrancer.group; --i; } } else{ ++i; } } else{ Actor a = SpawnMob(type); if(type == ActorType.WARG){ if(a.group != null){ foreach(Actor a2 in a.group){ a2.attrs[AttrType.WANDERING]++; } } } else{ if(a.AlwaysWanders() || (R.PercentChance(40) && a.CanWanderAtLevelGen())){ a.attrs[AttrType.WANDERING]++; } } } } } } } for(int i=(current_level-3)/4;i>0;--i){ //yes, this is all copied and pasted for a one-line change. i'll try to fix it later. if(R.CoinFlip()){ //generate some shallow monsters ActorType type = ShallowMobType(); if(type == ActorType.POLTERGEIST){ if(!poltergeist_spawned){ SpawnMob(type); poltergeist_spawned = true; } else{ ++i; //try again.. } } else{ if(type == ActorType.MIMIC){ if(!mimic_spawned){ SpawnMob(type); mimic_spawned = true; } else{ ++i; } } else{ if(type == ActorType.MARBLE_HORROR){ Tile statue = AllTiles().Where(t=>t.type == TileType.STATUE).RandomOrDefault(); if(!marble_horror_spawned && statue != null){ SpawnMob(type); marble_horror_spawned = true; } else{ ++i; } } else{ if(type == ActorType.ENTRANCER){ if(i >= 2){ //need 2 slots here Actor entrancer = SpawnMob(type); entrancer.attrs[AttrType.WANDERING]++; List<Tile> tiles = new List<Tile>(); int dist = 1; while(tiles.Count == 0 && dist < 100){ foreach(Tile t in entrancer.TilesAtDistance(dist)){ if(t.passable && !t.IsTrap() && t.actor() == null){ tiles.Add(t); } } ++dist; } if(tiles.Count > 0){ ActorType thralltype = ActorType.SPECIAL; bool done = false; while(!done){ thralltype = MobType(); switch(thralltype){ case ActorType.ROBED_ZEALOT: case ActorType.DERANGED_ASCETIC: case ActorType.BERSERKER: case ActorType.TROLL: case ActorType.CRUSADING_KNIGHT: case ActorType.SKITTERMOSS: case ActorType.OGRE_BARBARIAN: case ActorType.SNEAK_THIEF: case ActorType.STONE_GOLEM: case ActorType.LUMINOUS_AVENGER: case ActorType.WILD_BOAR: case ActorType.ALASI_SOLDIER: case ActorType.CORROSIVE_OOZE: case ActorType.ALASI_SENTINEL: done = true; break; } } Tile t = tiles.Random(); Actor thrall = Actor.Create(thralltype,t.row,t.col,TiebreakerAssignment.InsertAfterCurrent); if(entrancer.group == null){ entrancer.group = new List<Actor>{entrancer}; } entrancer.group.Add(thrall); thrall.group = entrancer.group; --i; } } else{ ++i; } } else{ Actor a = SpawnMob(type); if(type == ActorType.WARG){ if(a.group != null){ foreach(Actor a2 in a.group){ a2.attrs[AttrType.WANDERING]++; } } } else{ if(a.AlwaysWanders() || (R.PercentChance(40) && a.CanWanderAtLevelGen())){ a.attrs[AttrType.WANDERING]++; } } } } } } } } int minimum_distance_from_stairs = 0; PosArray<int> distance_from_stairs = null; if(stairs != null){ distance_from_stairs = tile.GetDijkstraMap(new List<pos>{stairs.p},x=>tile[x].BlocksConnectivityOfMap()); minimum_distance_from_stairs = distance_from_stairs[distance_from_stairs.PositionsWhere(x=>distance_from_stairs[x].IsValidDijkstraValue()).WhereGreatest(x=>distance_from_stairs[x]).Random()] / 2; } bool[,] good_location = new bool[ROWS,COLS]; for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if(tile[i,j].Is(TileType.FLOOR) && !tile[i,j].Is(FeatureType.WEB) && (stairs == null || distance_from_stairs[i,j] >= minimum_distance_from_stairs)){ good_location[i,j] = true; } else{ good_location[i,j] = false; } } } foreach(Actor a in AllActors()){ if(a != player){ good_location[a.row,a.col] = false; for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if(good_location[i,j] && a.HasLOS(i,j)){ good_location[i,j] = false; } } } } } bool at_least_one_good = false; for(int i=0;i<ROWS && !at_least_one_good;++i){ for(int j=0;j<COLS && !at_least_one_good;++j){ if(good_location[i,j]){ at_least_one_good = true; } } } if(!at_least_one_good){ foreach(Actor a in AllActors()){ if(a != player){ good_location[a.row,a.col] = false; for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if(good_location[i,j] && a.CanSee(i,j)){ //checking CanSee this time good_location[i,j] = false; } } } } } } List<Tile> goodtiles = new List<Tile>(); for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if(good_location[i,j]){ goodtiles.Add(tile[i,j]); } } } if(goodtiles.Count > 0){ Tile t = goodtiles.Random(); int light = player.light_radius; int burning = player.attrs[AttrType.BURNING]; int shining = player.attrs[AttrType.SHINING]; player.light_radius = 0; player.attrs[AttrType.BURNING] = 0; player.attrs[AttrType.SHINING] = 0; player.Move(t.row,t.col); player.light_radius = light; player.attrs[AttrType.BURNING] = burning; player.attrs[AttrType.SHINING] = shining; player.UpdateRadius(0,player.LightRadius()); } else{ for(bool done=false;!done;){ int rr = R.Roll(ROWS-2); int rc = R.Roll(COLS-2); bool good = true; foreach(Tile t in tile[rr,rc].TilesWithinDistance(1)){ if(t.IsTrap()){ good = false; } } if(good && tile[rr,rc].passable && actor[rr,rc] == null){ int light = player.light_radius; int burning = player.attrs[AttrType.BURNING]; int shining = player.attrs[AttrType.SHINING]; player.light_radius = 0; player.attrs[AttrType.BURNING] = 0; player.attrs[AttrType.SHINING] = 0; player.Move(rr,rc); player.light_radius = light; player.attrs[AttrType.BURNING] = burning; player.attrs[AttrType.SHINING] = shining; player.UpdateRadius(0,player.LightRadius()); done = true; } } } actor[player.row,player.col] = player; //this line fixes a bug that occurs when the player ends up in the same position on a new level Screen.screen_center_col = player.col; if(R.CoinFlip()){ //todo: copied and pasted below bool done = false; for(int tries=0;!done && tries<500;++tries){ int rr = R.Roll(ROWS-4) + 1; int rc = R.Roll(COLS-4) + 1; bool good = true; foreach(Tile t in tile[rr,rc].TilesWithinDistance(2)){ if(t.type != TileType.WALL){ good = false; break; } } if(good){ List<int> dirs = new List<int>(); bool long_corridor = false; int connections = 0; for(int i=2;i<=8;i+=2){ Tile t = tile[rr,rc].TileInDirection(i).TileInDirection(i); bool good_dir = true; int distance = -1; while(good_dir && t != null && t.type == TileType.WALL){ if(t.TileInDirection(i.RotateDir(false,2)).type != TileType.WALL){ good_dir = false; } if(t.TileInDirection(i.RotateDir(true,2)).type != TileType.WALL){ good_dir = false; } t = t.TileInDirection(i); if(t != null && t.type == TileType.STATUE){ good_dir = false; } ++distance; } if(good_dir && t != null){ dirs.Add(i); ++connections; if(distance >= 4){ long_corridor = true; } } } if(dirs.Count > 0){ List<TileType> all_possible_traps = new List<TileType>{TileType.GRENADE_TRAP,TileType.POISON_GAS_TRAP,TileType.PHANTOM_TRAP,TileType.FIRE_TRAP,TileType.SHOCK_TRAP,TileType.SCALDING_OIL_TRAP,TileType.FLING_TRAP,TileType.STONE_RAIN_TRAP}; List<TileType> possible_traps = new List<TileType>(); //int trap_roll = R.Roll(7); int num_types = R.Between(2,3); /*if(trap_roll == 1){ num_types = 1; } else{ if(trap_roll <= 4){ num_types = 2; } else{ num_types = 3; } }*/ for(int i=0;i<num_types;++i){ TileType tt = all_possible_traps.Random(); if(possible_traps.Contains(tt)){ --i; } else{ possible_traps.Add(tt); } } bool stone_slabs = false; //(instead of hidden doors) if(R.OneIn(4)){ stone_slabs = true; } foreach(int i in dirs){ Tile t = tile[rr,rc].TileInDirection(i); int distance = -2; //distance of the corridor between traps and secret door while(t.type == TileType.WALL){ ++distance; t = t.TileInDirection(i); } if(long_corridor && distance < 4){ continue; } t = tile[rr,rc].TileInDirection(i); while(t.type == TileType.WALL){ if(distance >= 4){ TileType tt = TileType.FLOOR; if(R.Roll(3) >= 2){ tt = possible_traps.Random(); hidden.Add(t); } t.TransformTo(tt); t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; if(t.DistanceFrom(tile[rr,rc]) < distance+2){ Tile neighbor = t.TileInDirection(i.RotateDir(false,2)); if(neighbor.TileInDirection(i.RotateDir(false,1)).type == TileType.WALL && neighbor.TileInDirection(i.RotateDir(false,2)).type == TileType.WALL && neighbor.TileInDirection(i.RotateDir(false,3)).type == TileType.WALL){ tt = TileType.FLOOR; if(R.Roll(3) >= 2){ tt = possible_traps.Random(); } neighbor.TransformTo(tt); if(possible_traps.Contains(tt)){ neighbor.name = "floor"; neighbor.the_name = "the floor"; neighbor.a_name = "a floor"; neighbor.symbol = '.'; neighbor.color = Color.White; hidden.Add(neighbor); } } neighbor = t.TileInDirection(i.RotateDir(true,2)); if(neighbor.TileInDirection(i.RotateDir(true,1)).type == TileType.WALL && neighbor.TileInDirection(i.RotateDir(true,2)).type == TileType.WALL && neighbor.TileInDirection(i.RotateDir(true,3)).type == TileType.WALL){ tt = TileType.FLOOR; if(R.Roll(3) >= 2){ tt = possible_traps.Random(); } neighbor.TransformTo(tt); if(possible_traps.Contains(tt)){ neighbor.name = "floor"; neighbor.the_name = "the floor"; neighbor.a_name = "a floor"; neighbor.symbol = '.'; neighbor.color = Color.White; hidden.Add(neighbor); } } } } else{ TileType tt = TileType.FLOOR; if(R.CoinFlip()){ tt = Tile.RandomTrap(); hidden.Add(t); } t.TransformTo(tt); if(tt != TileType.FLOOR){ t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; } } t = t.TileInDirection(i); } t = t.TileInDirection(i.RotateDir(true,4)); if(stone_slabs){ t.TransformTo(TileType.STONE_SLAB); Q.Add(new Event(t,new List<Tile>{t.TileInDirection(i.RotateDir(true,4))},100,EventType.STONE_SLAB)); } else{ t.TransformTo(TileType.HIDDEN_DOOR); hidden.AddUnique(t); } t = t.TileInDirection(i.RotateDir(true,4)); if(R.CoinFlip()){ if(t.IsTrap()){ t.type = TileType.ALARM_TRAP; } else{ t.TransformTo(TileType.ALARM_TRAP); t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; hidden.AddUnique(t); } } } if(long_corridor && connections == 1){ foreach(Tile t in tile[rr,rc].TilesWithinDistance(1)){ t.TransformTo(possible_traps.Random()); t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; hidden.Add(t); } tile[rr,rc].TileInDirection(dirs[0].RotateDir(true,4)).TransformTo(TileType.CHEST); tile[rr,rc].TileInDirection(dirs[0].RotateDir(true,4)).color = Color.Yellow; } else{ foreach(Tile t in tile[rr,rc].TilesAtDistance(1)){ t.TransformTo(Tile.RandomTrap()); t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; hidden.Add(t); } tile[rr,rc].TransformTo(TileType.CHEST); tile[rr,rc].color = Color.Yellow; } done = true; } } } } /*if(R.CoinFlip()){ bool done = false; for(int tries=0;!done && tries<500;++tries){ int rr = R.Roll(ROWS-4) + 1; int rc = R.Roll(COLS-4) + 1; bool good = true; foreach(Tile t in tile[rr,rc].TilesWithinDistance(2)){ if(t.type != TileType.WALL){ good = false; break; } } if(good){ List<int> dirs = new List<int>(); bool long_corridor = false; int connections = 0; for(int i=2;i<=8;i+=2){ Tile t = tile[rr,rc].TileInDirection(i).TileInDirection(i); bool good_dir = true; int distance = -1; while(good_dir && t != null && t.type == TileType.WALL){ if(t.TileInDirection(i.RotateDir(false,2)).type != TileType.WALL){ good_dir = false; } if(t.TileInDirection(i.RotateDir(true,2)).type != TileType.WALL){ good_dir = false; } t = t.TileInDirection(i); if(t != null && t.type == TileType.STATUE){ good_dir = false; } ++distance; } if(good_dir && t != null){ dirs.Add(i); ++connections; if(distance >= 4){ long_corridor = true; } } } if(dirs.Count > 0){ List<TileType> possible_traps = new List<TileType>(); int trap_roll = R.Roll(7); if(trap_roll == 1 || trap_roll == 4 || trap_roll == 5 || trap_roll == 7){ possible_traps.Add(TileType.GRENADE_TRAP); } if(trap_roll == 2 || trap_roll == 4 || trap_roll == 6 || trap_roll == 7){ possible_traps.Add(TileType.POISON_GAS_TRAP); } if(trap_roll == 3 || trap_roll == 5 || trap_roll == 6 || trap_roll == 7){ possible_traps.Add(TileType.PHANTOM_TRAP); } bool stone_slabs = false; //(instead of hidden doors) if(R.OneIn(4)){ stone_slabs = true; } foreach(int i in dirs){ Tile t = tile[rr,rc].TileInDirection(i); int distance = -2; //distance of the corridor between traps and secret door while(t.type == TileType.WALL){ ++distance; t = t.TileInDirection(i); } if(long_corridor && distance < 4){ continue; } t = tile[rr,rc].TileInDirection(i); while(t.type == TileType.WALL){ if(distance >= 4){ TileType tt = TileType.FLOOR; if(R.Roll(3) >= 2){ tt = possible_traps.Random(); hidden.Add(t); } t.TransformTo(tt); t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; if(t.DistanceFrom(tile[rr,rc]) < distance+2){ Tile neighbor = t.TileInDirection(i.RotateDir(false,2)); if(neighbor.TileInDirection(i.RotateDir(false,1)).type == TileType.WALL && neighbor.TileInDirection(i.RotateDir(false,2)).type == TileType.WALL && neighbor.TileInDirection(i.RotateDir(false,3)).type == TileType.WALL){ tt = TileType.FLOOR; if(R.Roll(3) >= 2){ tt = possible_traps.Random(); } neighbor.TransformTo(tt); if(possible_traps.Contains(tt)){ neighbor.name = "floor"; neighbor.the_name = "the floor"; neighbor.a_name = "a floor"; neighbor.symbol = '.'; neighbor.color = Color.White; hidden.Add(neighbor); } } neighbor = t.TileInDirection(i.RotateDir(true,2)); if(neighbor.TileInDirection(i.RotateDir(true,1)).type == TileType.WALL && neighbor.TileInDirection(i.RotateDir(true,2)).type == TileType.WALL && neighbor.TileInDirection(i.RotateDir(true,3)).type == TileType.WALL){ tt = TileType.FLOOR; if(R.Roll(3) >= 2){ tt = possible_traps.Random(); } neighbor.TransformTo(tt); if(possible_traps.Contains(tt)){ neighbor.name = "floor"; neighbor.the_name = "the floor"; neighbor.a_name = "a floor"; neighbor.symbol = '.'; neighbor.color = Color.White; hidden.Add(neighbor); } } } } else{ TileType tt = TileType.FLOOR; if(R.CoinFlip()){ tt = Tile.RandomTrap(); hidden.Add(t); } t.TransformTo(tt); if(tt != TileType.FLOOR){ t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; } } t = t.TileInDirection(i); } t = t.TileInDirection(i.RotateDir(true,4)); if(stone_slabs){ t.TransformTo(TileType.STONE_SLAB); Q.Add(new Event(t,new List<Tile>{t.TileInDirection(i.RotateDir(true,4))},100,EventType.STONE_SLAB)); } else{ t.TransformTo(TileType.HIDDEN_DOOR); hidden.AddUnique(t); } t = t.TileInDirection(i.RotateDir(true,4)); if(R.CoinFlip()){ if(t.IsTrap()){ t.type = TileType.ALARM_TRAP; } else{ t.TransformTo(TileType.ALARM_TRAP); t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; hidden.AddUnique(t); } } } if(long_corridor && connections == 1){ foreach(Tile t in tile[rr,rc].TilesWithinDistance(1)){ t.TransformTo(possible_traps.Random()); t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; hidden.Add(t); } tile[rr,rc].TileInDirection(dirs[0].RotateDir(true,4)).TransformTo(TileType.CHEST); tile[rr,rc].TileInDirection(dirs[0].RotateDir(true,4)).color = Color.Yellow; } else{ foreach(Tile t in tile[rr,rc].TilesAtDistance(1)){ t.TransformTo(Tile.RandomTrap()); t.name = "floor"; t.the_name = "the floor"; t.a_name = "a floor"; t.symbol = '.'; t.color = Color.White; hidden.Add(t); } tile[rr,rc].TransformTo(TileType.CHEST); tile[rr,rc].color = Color.Yellow; } done = true; } } } }*/ foreach(Tile t in AllTiles()){ if(t.type != TileType.WALL){ foreach(Tile neighbor in t.TilesAtDistance(1)){ neighbor.solid_rock = false; } } if(t.type == TileType.GLOWING_FUNGUS){ foreach(Tile neighbor in t.TilesAtDistance(1)){ if(neighbor.type == TileType.WALL){ neighbor.color = Color.RandomGlowingFungus; } } } } if(level_types[current_level-1] == LevelType.Hive){ var dijkstra = tile.GetDijkstraMap(x=>tile[x].type != TileType.WALL,x=>false); for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ if((dijkstra[i,j] == 1 && !R.OneIn(20)) || (dijkstra[i,j] == 2 && R.CoinFlip())){ if(i == 0 || j == 0 || i == ROWS-1 || j == COLS-1){ tile[i,j].color = Color.DarkYellow; tile[i,j].SetName("waxy wall"); //borders become waxy but don't burn } else{ tile[i,j].Toggle(null,TileType.WAX_WALL); } } } } } if(level_types[current_level-1] == LevelType.Fortress){ foreach(pos p in tile.PositionsWhere(x=>tile[x].Is(TileType.WALL,TileType.HIDDEN_DOOR))){ tile[p].color = Color.TerrainDarkGray; } } string desc = GetDungeonDescription(); for(int i=0;i<ROWS;++i){ for(int j=0;j<COLS;++j){ //todo fix this! if(!tile.BoundsCheck(i,j,false)) continue; pos p = new pos(i,j); if(SchismExtensionMethods.Extensions.ConsecutiveAdjacent(p,x=>!tile[x].BlocksConnectivityOfMap()) >= 3){ //todo, fix check! dungeon_description[i,j] = desc; } else{ dungeon_description[i,j] = "You pass through a hallway of hewn stone."; //todo, fix message! } } } if(poppy_event != null){ CalculatePoppyDistanceMap(); } if(hidden.Count > 0){ Event e = new Event(hidden,100,EventType.CHECK_FOR_HIDDEN); e.tiebreaker = 0; Q.Add(e); } { Event e = new Event(10000,EventType.RELATIVELY_SAFE); e.tiebreaker = 0; Q.Add(e); } { Event e = new Event(R.Between(400,450)*100,EventType.SPAWN_WANDERING_MONSTER); e.tiebreaker = 0; Q.Add(e); } if(current_level == 1){ B.Add("In the mountain pass where travelers vanish, a stone staircase leads downward... Welcome, " + Actor.player_name + "! "); } else{ B.Add(LevelMessage()); } }