/* * This file is part of Deliantra, the Roguelike Realtime MMORPG. * * Copyright (©) 2010 Marc Alexander Lehmann / Robin Redeker / the Deliantra team * * Deliantra is free software: you can redistribute it and/or modify it under * the terms of the Affero GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the Affero GNU General Public License * and the GNU General Public License along with this program. If not, see * . * * The authors can be reached via e-mail to */ #include #include Layout::Layout (int w, int h) : w(w), h(h) { int size = (sizeof (char *) + sizeof (char) * h) * w; col = (char **)salloc (size); char *data = (char *)(col + w); for (int x = w; x--; ) col [x] = data + x * h; } Layout::~Layout () { int size = (sizeof (char *) + sizeof (char) * h) * w; sfree ((char *)col, size); } void Layout::fill (char fill) { memset (col [0], fill, w * h); } void Layout::rect (int x1, int y1, int x2, int y2, char fill) { for (; x1 < x2; ++x1) memset (col [x1] + y1, fill, y2 - y1); } void Layout::border (char fill) { for (int i = 0; i < w; i++) col [i][0] = col [i][h - 1] = fill; for (int j = 0; j < h; j++) col [0][j] = col [w - 1][j] = fill; } void Layout::fill_rand (int percent) { percent = lerp (percent, 0, 100, 0, 256); for (int x = w - 1; --x > 0; ) for (int y = h - 1; --y > 0; ) col [x][y] = rmg_rndm (256) > percent ? 0 : '#'; } ///////////////////////////////////////////////////////////////////////////// // erode by cellular automata void Layout::erode_1_2 (int c1, int c2, int repeat) { Layout neu (w, h); while (repeat--) { for (int x = 0; x < w; ++x) { coroapi::cede_to_tick (); for (int y = 0; y < h; ++y) { int n1 = 0, n2 = 0; // a 5x5 area, dx, dy, distance (1 == <= 1, 0 <= 2) static I8 dds[][3] = { { -2, -1, 0 }, { -2, 0, 0 }, { -2, 1, 0 }, { -1, -2, 0 }, { -1, -1, 1 }, { -1, 0, 1 }, { -1, 1, 1 }, { -1, 2, 0 }, { 0, -2, 0 }, { 0, -1, 1 }, { 0, 0, 1 }, { 0, 1, 1 }, { 0, 2, 0 }, { 1, -2, 0 }, { 1, -1, 1 }, { 1, 0, 1 }, { 1, 1, 1 }, { 1, 2, 0 }, { 2, -1, 0 }, { 2, 0, 0 }, { 2, 1, 0 }, }; for (int i = array_length (dds); i--; ) { int nx = x + dds [i][0]; int ny = y + dds [i][1]; if (!IN_RANGE_EXC (nx, 0, w) || !IN_RANGE_EXC (ny, 0, h) || !col [nx][ny]) { n1 += dds [i][2]; n2++; } } neu [x][y] = n1 >= c1 || n2 <= c2 ? '#' : 0; } } swap (neu); } } ///////////////////////////////////////////////////////////////////////////// void Layout::print () { for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { U8 c = (U8)col [x][y]; if (!c) c = ' '; else if (c < 10) c += '0'; else if (c < 32) c += 'a' - 10; putc ((char)c, stdout); } putc ('\n', stdout); } putc ('\n', stdout); } ///////////////////////////////////////////////////////////////////////////// // isolation remover - ensures single connected area typedef fixed_stack pointlist; static noinline void push_flood_fill (Layout &dist, pointlist &seeds, int x, int y) { if (dist [x][y]) return; while (y > 0 && !dist [x][y - 1]) --y; int y0 = y; while (y < dist.h && !dist [x][y]) { seeds.push (point (x, y)); dist [x][y] = 1; ++y; } while (--y >= y0) { if (x > 0) push_flood_fill (dist, seeds, x - 1, y); if (x < dist.w - 1) push_flood_fill (dist, seeds, x + 1, y); } } static inline void make_tunnel (Layout &dist, pointlist &seeds, int x, int y, U8 d) { for (;;) { if (x > 1 && U8 (dist [x - 1][y]) < d && dist [x - 1][y] > 1) --x; else if (x < dist.w - 2 && U8 (dist [x + 1][y]) < d && dist [x + 1][y] > 1) ++x; else if (y > 1 && U8 (dist [x][y - 1]) < d && dist [x][y - 1] > 1) --y; else if (y < dist.h - 2 && U8 (dist [x][y + 1]) < d && dist [x][y + 1] > 1) ++y; else break; d = dist [x][y]; dist [x][y] = 1; seeds.push (point (x, y)); } } static void inline maybe_push (Layout &dist, pointlist &seeds, int x, int y, U8 d) { char &D = dist [x][y]; if (U8 (D) > d) // if wall and higher distance, lower distance D = d; else if (D) // otherwise, if it's no room, this space is uninteresting return; seeds.push (point (x, y)); } static void isolation_remover (Layout &maze, bool dirty) { Layout dist (maze.w, maze.h); // dist contains // 0 == invisited rooms // 1 == visited rooms // 2+ shortest distance to random near room // phase 1, initialise dist array, find seed int cnt = 0; int x, y; for (int i = 0; i < maze.w; ++i) for (int j = 0; j < maze.h; ++j) { if (maze [i][j] == '#') dist [i][j] = U8 (255); else { dist [i][j] = 0; if (!rmg_rndm (++cnt)) x = i, y = j; } } if (!cnt) return; fixed_stack seeds (maze.w * maze.h * 5); // found first free space - picking the first one gives // us a slight bias for tunnels, but usually you won't // notice that in-game seeds.push (point (x, y)); // phase 2, while we have seeds, if // seed is empty, floodfill, else grow while (seeds.size) { coroapi::cede_to_tick (); point p = seeds.remove (rmg_rndm (seeds.size)); x = p.x; y = p.y; if (!dist [x][y]) { // found new isolated area, make tunnel if (!dirty) push_flood_fill (dist, seeds, x, y); make_tunnel (dist, seeds, x, y, 255); if (dirty) push_flood_fill (dist, seeds, x, y); } else { // nothing here, continue to expand U8 d = U8 (dist [x][y]) + 1; if (x < dist.w - 1) maybe_push (dist, seeds, x + 1, y, d); if (x > 0) maybe_push (dist, seeds, x - 1, y, d); if (y < dist.h - 1) maybe_push (dist, seeds, x, y + 1, d); if (y > 0) maybe_push (dist, seeds, x, y - 1, d); } } // now copy the tunnels over for (int x = 0; x < maze.w; ++x) for (int y = 0; y < maze.h; ++y) if (maze [x][y] == '#' && dist [x][y] == 1) maze [x][y] = 0; } void Layout::isolation_remover (bool dirty) { ::isolation_remover (*this, dirty); } ///////////////////////////////////////////////////////////////////////////// // inspired mostly by http://www.jimrandomh.org/misc/caves.txt void Layout::gen_cave (int subtype) { switch (subtype) { // a rough cave case 0: fill_rand (rmg_rndm (80, 95)); break; // corridors case 1: fill_rand (rmg_rndm (5, 40)); erode_1_2 (5, 2, 10); erode_1_2 (5, -1, 10); erode_1_2 (5, 2, 1); break; // somewhat open, roundish case 2: fill_rand (45); erode_1_2 (5, 0, 5); erode_1_2 (5, 1, 1); break; // wide open, some room-like structures case 3: fill_rand (45); erode_1_2 (5, 2, 4); erode_1_2 (5, -1, 3); break; } border (); isolation_remover (); } #if 0 static struct demo { demo () { Layout maze (40, 25); rmg_rndm.seed (time (0)); for(int i=1;i<10;i) { maze.fill_rand (97); maze.border (); maze.isolation_remover (); maze.print (); } exit (1); } } demo; #endif