Unified · Split
| @@ -202,6 +202,10 @@ struct Spawn · 201 unmodified lines | ||
| 202 | 202 | char what[SZMONNAME]; |
| 203 | 203 | /* frequency of spawn, modulo turns */ |
| 204 | 204 | int freq; |
| 205 | + /* turn counter */ | |
| 206 | + int turn; | |
| 207 | + /* spawn baddies when enemy is around? */ | |
| 208 | + int istrap; | |
| 205 | 209 | }; |
| 206 | 210 | |
| 207 | 211 | struct Tile |
| @@ -260,12 +264,22 @@ struct Level · 52 unmodified lines | ||
| 260 | 264 | /* level.c */ |
| 261 | 265 | Level *genlevel(int width, int height, int type); |
| 262 | 266 | void freelevel(Level *l); |
| 267 | +void addspawn(Level *l, Point p, char *what, int freq, int istrap); | |
| 263 | 268 | Tile *tileat(Level *l, Point p); |
| 264 | 269 | #define flagat(l, p) (*(l->flags+(p.y*l->width)+p.x)) |
| 265 | 270 | #define hasflagat(l, p, F) (*(l->flags+(p.y*l->width)+p.x) & (F)) |
| 266 | 271 | #define setflagat(l, p, F) (*(l->flags+(p.y*l->width)+p.x) |= (F)) |
| 267 | 272 | #define clrflagat(l, p, F) (*(l->flags+(p.y*l->width)+p.x) &= ~(F)) |
| 268 | 273 | |
| 274 | +/* levelgen.c */ | |
| 275 | +Level *mklevel(int width, int height, int floor); | |
| 276 | +void several(Level *l, Point *p, int count, char *type, int r); | |
| 277 | +int mkldebug(Level *l); | |
| 278 | +int mklforest(Level *l); | |
| 279 | +int mklgraveyard(Level *l); | |
| 280 | +int mklvolcano(Level *l); | |
| 281 | +int mklcastle(Level *l); | |
| 282 | + | |
| 269 | 283 | struct Camera |
| 270 | 284 | { |
| 271 | 285 | Point pos; |
| @@ -67,7 +67,7 @@ movemons(void) · 66 unmodified lines | ||
| 67 | 67 | l = player->l; |
| 68 | 68 | |
| 69 | 69 | /* run ai on level, for spawns etc */ |
| 70 | − if(l->ai->exec != nil) | |
| 70 | + if(l->ai != nil && l->ai->exec != nil) | |
| 71 | 71 | l->ai->exec(l->ai); |
| 72 | 72 | |
| 73 | 73 | for(p.x = 0; p.x < l->width; p.x++){ |
| @@ -137,13 +137,8 @@ threadmain(int argc, char *argv[]) · 63 unmodified lines | ||
| 137 | 137 | } |
| 138 | 138 | |
| 139 | 139 | /* initial level */ |
| 140 | − if(debug > 0){ | |
| 141 | − if((level = genlevel(11, 11, 0)) == nil) | |
| 142 | − sysfatal("genlevel: %r"); | |
| 143 | − } else { | |
| 144 | − if((level = genlevel(nrand(LSIZE)+LSIZE, nrand(LSIZE)+LSIZE, nrand(3)+1)) == nil) | |
| 145 | − sysfatal("genlevel: %r"); | |
| 146 | − } | |
| 140 | + if((level = genlevel(nrand(LSIZE)+LSIZE, nrand(LSIZE)+LSIZE, nrand(3)+1)) == nil) | |
| 141 | + sysfatal("genlevel: %r"); | |
| 147 | 142 | |
| 148 | 143 | /* the player */ |
| 149 | 144 | player = mbyname("wizard"); |
| @@ -6,172 +6,8 @@ · 5 unmodified lines | ||
| 6 | 6 | #include "dat.h" |
| 7 | 7 | #include "alg.h" |
| 8 | 8 | |
| 9 | −/* clear removes all monsters/features near p. */ | |
| 10 | −static void | |
| 11 | −clear(Level *l, Point p, int dist) | |
| 12 | −{ | |
| 13 | − Rectangle r; | |
| 14 | − Point p2; | |
| 15 | − Tile *t; | |
| 16 | − | |
| 17 | − r = Rect(p.x-dist, p.y-dist, p.x+dist, p.y+dist); | |
| 18 | − rectclip(&r, l->r); | |
| 19 | − | |
| 20 | − for(p2.x = r.min.x; p2.x <= r.max.x; p2.x++){ | |
| 21 | − for(p2.y = r.min.y; p2.y <= r.max.y; p2.y++){ | |
| 22 | − if(eqpt(p, p2)) | |
| 23 | − continue; | |
| 24 | − t = tileat(l, p2); | |
| 25 | − t->unit = t->feat = 0; | |
| 26 | − if(t->monst){ | |
| 27 | − mfree(t->monst); | |
| 28 | − t->monst = nil; | |
| 29 | − } | |
| 30 | − flagat(l, p2) = 0; | |
| 31 | − } | |
| 32 | − } | |
| 33 | −} | |
| 34 | − | |
| 35 | −/* more sbias makes it straighter. */ | |
| 36 | −static void | |
| 37 | −drunk1(Level *l, Point p, uint count, uint sbias) | |
| 38 | −{ | |
| 39 | − int cur, last; | |
| 40 | − Point next; | |
| 41 | − Rectangle clipr; | |
| 42 | − Tile *t; | |
| 43 | − | |
| 44 | − clipr = insetrect(l->r, 1); | |
| 45 | − | |
| 46 | − last = nrand(NCARDINAL); | |
| 47 | − while(count > 0){ | |
| 48 | − if(nrand(sbias+1) == 0) | |
| 49 | − cur = nrand(NCARDINAL); | |
| 50 | − else | |
| 51 | − cur = last; | |
| 52 | − next = addpt(p, cardinals[cur]); | |
| 53 | − if(!ptinrect(next, clipr)) | |
| 54 | − continue; | |
| 55 | − last = cur; | |
| 56 | − p = next; | |
| 57 | − if(!eqpt(p, l->up) && !eqpt(p, l->down) && hasflagat(l, p, Fblocked|Fhasfeature)){ | |
| 58 | − t = tileat(l, p); | |
| 59 | − t->feat = 0; | |
| 60 | − flagat(l, p) = 0; | |
| 61 | − count--; | |
| 62 | − } | |
| 63 | − } | |
| 64 | −} | |
| 65 | − | |
| 66 | −/* | |
| 67 | − * drunken performs the drunkard's walk. as s1 and | |
| 68 | − * s2 increase, the paths become more biased toward | |
| 69 | − * 'straight'. it loops until the downstairs is | |
| 70 | − * reachable from the upstairs. | |
| 71 | − */ | |
| 72 | −static int | |
| 73 | −drunken(Level *l, int type, int howmuch, int s1, int s2) | |
| 74 | −{ | |
| 75 | − int cnt, npath, redos; | |
| 76 | − Point p, *path; | |
| 77 | − Tile *t; | |
| 78 | − | |
| 79 | − cnt = ((l->width * l->height) / howmuch); //* 2; | |
| 80 | − redos = 0; | |
| 81 | − | |
| 82 | −redo: | |
| 83 | − if(redos++ > 10) | |
| 84 | − return 0; | |
| 85 | − /* fill */ | |
| 86 | − for(p.x = 0; p.x < l->width; p.x++){ | |
| 87 | − for(p.y = 0; p.y < l->height; p.y++){ | |
| 88 | − if(!hasflagat(l, p, Fblocked|Fhasfeature)){ | |
| 89 | − t = tileat(l, p); | |
| 90 | − t->feat = type; | |
| 91 | − flagat(l, p) = (Fblocked|Fhasfeature); | |
| 92 | − } | |
| 93 | − } | |
| 94 | − } | |
| 95 | − | |
| 96 | − drunk1(l, l->up, cnt/2, s1); | |
| 97 | − drunk1(l, l->down, cnt/2, s2); | |
| 98 | − | |
| 99 | − /* check reachability */ | |
| 100 | − npath = pathfind(l, l->up, l->down, &path, Fblocked); | |
| 101 | − if(npath < 0) | |
| 102 | − goto redo; | |
| 103 | − else | |
| 104 | − free(path); | |
| 105 | − | |
| 106 | − return cnt * 2; | |
| 107 | −} | |
| 108 | − | |
| 109 | −/* create a monster in the idle state */ | |
| 110 | −static Monster* | |
| 111 | −mkmons(Level *l, Point p, char *type) | |
| 112 | −{ | |
| 113 | − Monster *m; | |
| 114 | − m = mbyname(type); | |
| 115 | − if(m == nil) | |
| 116 | − sysfatal("monstbyname: %r"); | |
| 117 | − m->l = l; | |
| 118 | − m->pt = p; | |
| 119 | − /* setup ai state */ | |
| 120 | − idle(m); | |
| 121 | − return m; | |
| 122 | −} | |
| 123 | − | |
| 124 | −/* genmonsters spawns count of type monsters at random places on l. */ | |
| 125 | −static void | |
| 126 | −genmonsters(Level *l, char *type, int count) | |
| 127 | −{ | |
| 128 | − int i; | |
| 129 | − Point p; | |
| 130 | − Tile *t; | |
| 131 | − | |
| 132 | − for(i = 0; i < count; i++){ | |
| 133 | − do{ | |
| 134 | − p = (Point){nrand(l->width), nrand(l->height)}; | |
| 135 | − }while(hasflagat(l, p, Fhasmonster|Fhasfeature|Fblocked)); | |
| 136 | − | |
| 137 | − t = tileat(l, p); | |
| 138 | − t->monst = mkmons(l, p, type); | |
| 139 | − t->unit = t->monst->md->tile; | |
| 140 | − setflagat(l, p, Fhasmonster); | |
| 141 | − } | |
| 142 | −} | |
| 143 | − | |
| 144 | −/* | |
| 145 | − * several is a recursive function, that spawns monsters | |
| 146 | − * centered at p[0] expaning outward according to a | |
| 147 | − * von neumann neighborhood with radius r. for example: | |
| 148 | − * several(l, p, 1, TGHOST, 2); | |
| 149 | − * spawns 13 ghosts centered at p on l. | |
| 150 | − */ | |
| 151 | −static void | |
| 152 | −several(Level *l, Point *p, int count, char *type, int r) | |
| 153 | −{ | |
| 154 | − int i, j, n; | |
| 155 | − Point *neigh; | |
| 156 | − Tile *t; | |
| 157 | − for(i = 0; i < count; i++){ | |
| 158 | − if(!hasflagat(l, p[i], Fhasmonster|Fblocked)){ | |
| 159 | − t = tileat(l, p[i]); | |
| 160 | − t->monst = mkmons(l, p[i], type); | |
| 161 | − t->unit = t->monst->md->tile; | |
| 162 | − setflagat(l, p[i], Fhasmonster); | |
| 163 | − } | |
| 164 | − if(r > 0){ | |
| 165 | − neigh = neighbor(l->r, p[i], &n); | |
| 166 | − for(j = 0; j < n; j++){ | |
| 167 | − several(l, neigh, n, type, r-1); | |
| 168 | − } | |
| 169 | − free(neigh); | |
| 170 | − } | |
| 171 | − } | |
| 172 | −} | |
| 173 | − | |
| 174 | −static void addspawn(Level *l, Point p, char *what, int freq) | |
| 9 | +void | |
| 10 | +addspawn(Level *l, Point p, char *what, int freq, int istrap) | |
| 175 | 11 | { |
| 176 | 12 | Spawn sp; |
| 177 | 13 | |
| @@ -179,6 +15,8 @@ static void addspawn(Level *l, Point p, char *what, int freq) · 1 unmodified line | ||
| 179 | 15 | sp.pt = p; |
| 180 | 16 | strcpy(sp.what, what); |
| 181 | 17 | sp.freq = freq; |
| 18 | + sp.turn = nrand(1000); | |
| 19 | + sp.istrap = istrap; | |
| 182 | 20 | l->spawns[l->nspawns++] = sp; |
| 183 | 21 | tileat(l, p)->feat = TSPAWN; |
| 184 | 22 | setflagat(l, p, Fhasfeature); |
| @@ -186,197 +24,60 @@ static void addspawn(Level *l, Point p, char *what, int freq) · 1 unmodified line | ||
| 186 | 24 | } |
| 187 | 25 | |
| 188 | 26 | static void |
| 189 | −levelexec(AIState *a) | |
| 27 | +lrandstairs(Level *l) | |
| 190 | 28 | { |
| 191 | − int i, j, n; | |
| 192 | − Point p, np, *neigh; | |
| 193 | − Level *l; | |
| 29 | + Rectangle in; | |
| 30 | + Point pup, pdown; | |
| 194 | 31 | Tile *t; |
| 195 | − MonsterData *md; | |
| 196 | − Monster *m; | |
| 197 | 32 | |
| 198 | − l = a->aux; | |
| 199 | − | |
| 200 | − for(i = 0; i < l->nspawns; i++){ | |
| 201 | − if(turn % l->spawns[i].freq == 0){ | |
| 202 | − p = l->spawns[i].pt; | |
| 203 | − | |
| 204 | − /* le trap */ | |
| 205 | − neigh = neighbor(l->r, p, &n); | |
| 206 | − for(j = 0; j < n; j++){ | |
| 207 | − np = neigh[j]; | |
| 208 | − if(hasflagat(l, np, Fhasmonster)){ | |
| 209 | − t = tileat(l, np); | |
| 210 | − m = t->monst; | |
| 211 | − md = mdbyname(l->spawns[i].what); | |
| 212 | − if(strcmp(m->md->name, l->spawns[i].what) != 0 && abs(m->align - md->align) > 15){ | |
| 213 | − several(l, &p, 1, l->spawns[i].what, 2); | |
| 214 | − } | |
| 215 | − } | |
| 216 | − } | |
| 33 | + in = insetrect(l->r, 3); | |
| 217 | 34 | |
| 218 | − free(neigh); | |
| 219 | − several(l, &p, 1, l->spawns[i].what, 0); | |
| 220 | − } | |
| 221 | − } | |
| 35 | + pup = (Point){nrand(Dx(in))+3, nrand(Dy(in))+3}; | |
| 36 | + do { | |
| 37 | + pdown = (Point){nrand(Dx(in))+3, nrand(Dy(in))+3}; | |
| 38 | + } while(eqpt(pdown, pup) || manhattan(pdown, pup) < ORTHOCOST*15); | |
| 39 | + | |
| 40 | + setflagat(l, pup, Fhasfeature|Fportal); | |
| 41 | + t = tileat(l, pup); | |
| 42 | + t->feat = TUPSTAIR; | |
| 43 | + t->portal = mallocz(sizeof(Portal), 1); | |
| 44 | + strcpy(t->portal->name, "staircase upstairs"); | |
| 45 | + l->up = pup; | |
| 46 | + | |
| 47 | + setflagat(l, pdown, Fhasfeature|Fportal); | |
| 48 | + t = tileat(l, pdown); | |
| 49 | + t->feat = TDOWNSTAIR; | |
| 50 | + t->portal = mallocz(sizeof(Portal), 1); | |
| 51 | + strcpy(t->portal->name, "staircase downstairs"); | |
| 52 | + l->down = pdown; | |
| 222 | 53 | } |
| 223 | 54 | |
| 224 | 55 | static int |
| 225 | 56 | gen(Level *l, int type) |
| 226 | 57 | { |
| 227 | − int space, npath, try; | |
| 228 | − Point pup, pdown, p, p2, *path; | |
| 229 | − Rectangle in; | |
| 230 | − Tile *t; | |
| 231 | − | |
| 232 | − space = 0; | |
| 233 | − try = 0; | |
| 234 | − | |
| 235 | − in = insetrect(l->r, 3); | |
| 236 | − pup = ZP; | |
| 237 | − pdown = ZP; | |
| 238 | − | |
| 239 | − while(1){ | |
| 240 | − if(!eqpt(pup,ZP)) | |
| 241 | − flagat(l, pup) = 0; | |
| 242 | − do{ | |
| 243 | − pup = (Point){nrand(l->width), nrand(l->height)}; | |
| 244 | − }while(!ptinrect(pup, in)); | |
| 245 | − | |
| 246 | − if(!eqpt(pdown, ZP)) | |
| 247 | − flagat(l, pup) = 0; | |
| 248 | − do{ | |
| 249 | − pdown = (Point){nrand(l->width), nrand(l->height)}; | |
| 250 | − }while(!ptinrect(pdown, in)); | |
| 251 | − | |
| 252 | − /* already upstair? */ | |
| 253 | − if(flagat(l, pdown) & Fportal) | |
| 254 | − continue; | |
| 255 | − /* too close? */ | |
| 256 | − p = subpt(pup, pdown); | |
| 257 | − if((npath = pathfind(l, pup, pdown, &path, Fblocked)) < 0) | |
| 258 | − continue; | |
| 259 | − free(path); | |
| 260 | − if(npath < sqrt(l->width * l->height)-4) | |
| 261 | − continue; | |
| 262 | − | |
| 263 | − setflagat(l, pup, Fhasfeature|Fportal); | |
| 264 | − t = tileat(l, pup); | |
| 265 | − t->feat = TUPSTAIR; | |
| 266 | − t->portal = mallocz(sizeof(Portal), 1); | |
| 267 | − strcpy(t->portal->name, "staircase upstairs"); | |
| 268 | − l->up = pup; | |
| 269 | − | |
| 270 | − setflagat(l, pdown, Fhasfeature|Fportal); | |
| 271 | − t = tileat(l, pdown); | |
| 272 | − t->feat = TDOWNSTAIR; | |
| 273 | − t->portal = mallocz(sizeof(Portal), 1); | |
| 274 | − strcpy(t->portal->name, "staircase downstairs"); | |
| 275 | − l->down = pdown; | |
| 276 | − break; | |
| 277 | − } | |
| 278 | − | |
| 279 | − enum { DIST = ORTHOCOST*10, }; | |
| 58 | + lrandstairs(l); | |
| 280 | 59 | |
| 281 | 60 | switch(type){ |
| 282 | 61 | case 0: |
| 283 | − drunken(l, TTREE, 3, 3, 3); | |
| 284 | − clear(l, pup, 1); | |
| 285 | − clear(l, pdown, 1); | |
| 286 | − several(l, &l->down, 1, "lich", 1); | |
| 287 | − several(l, &l->down, 1, "gnome wizard", 1); | |
| 288 | − break; | |
| 62 | + mkldebug(l); | |
| 289 | 63 | break; |
| 290 | 64 | case 1: |
| 291 | − space += drunken(l, TTREE, 2, 0, 0); | |
| 292 | − clear(l, pup, 1); | |
| 293 | − clear(l, pdown, 1); | |
| 294 | − do{ | |
| 295 | − p = (Point){nrand(l->width), nrand(l->height)}; | |
| 296 | − if(try++ > 10) | |
| 297 | − return -1; | |
| 298 | − }while(!ptinrect(p, in) || manhattan(p, l->down) < DIST || manhattan(p, l->up) < DIST || hasflagat(l, p, Fblocked|Fhasmonster|Fhasfeature)); | |
| 299 | − clear(l, p, 2); | |
| 300 | − several(l, &p, 1, "captain", 0); | |
| 301 | − several(l, &p, 1, "lieutenant", 1); | |
| 302 | − addspawn(l, p, "soldier", 37); | |
| 303 | − addspawn(l, p, "sergeant", 51); | |
| 304 | − addspawn(l, p, "lieutenant", 97); | |
| 305 | − addspawn(l, p, "captain", 131); | |
| 306 | − do{ | |
| 307 | − p2 = (Point){nrand(l->width), nrand(l->height)}; | |
| 308 | − if(try++ > 10) | |
| 309 | − return -1; | |
| 310 | − }while(!ptinrect(p2, in) || manhattan(p, p2) < DIST || manhattan(p, p2) > DIST*2 || manhattan(p2, l->down) < DIST || manhattan(p2, l->up) < DIST || hasflagat(l, p2, Fblocked|Fhasmonster|Fhasfeature)); | |
| 311 | − clear(l, p2, 2); | |
| 312 | − several(l, &p2, 1, "gnome king", 0); | |
| 313 | − several(l, &p2, 1, "gnome wizard", 1); | |
| 314 | − addspawn(l, p2, "gnome", 11); | |
| 315 | − addspawn(l, p2, "gnome lord", 25); | |
| 316 | − addspawn(l, p2, "gnome wizard", 73); | |
| 317 | − addspawn(l, p2, "gnome king", 131); | |
| 318 | − | |
| 319 | − /* don't start empty */ | |
| 320 | − genmonsters(l, "gnome lord", space/128); | |
| 321 | − genmonsters(l, "sergeant", space/128); | |
| 65 | + mklforest(l); | |
| 322 | 66 | break; |
| 323 | 67 | case 2: |
| 324 | − space += drunken(l, TGRAVE, 2, 4, 4); | |
| 325 | − clear(l, l->up, 2); | |
| 326 | − clear(l, l->down, 2); | |
| 327 | − do{ | |
| 328 | − p = (Point){nrand(l->width), nrand(l->height)}; | |
| 329 | − if(try++ > 10) | |
| 330 | − return -1; | |
| 331 | − }while(!ptinrect(p, in) || manhattan(p, l->down) < DIST || manhattan(p, l->up) < DIST || hasflagat(l, p, Fblocked|Fhasmonster|Fhasfeature)); | |
| 332 | − clear(l, p, 2); | |
| 333 | − addspawn(l, p, "lich", 31); | |
| 334 | − addspawn(l, p, "ghost", 5); | |
| 335 | − do{ | |
| 336 | − p2 = (Point){nrand(l->width), nrand(l->height)}; | |
| 337 | − if(try++ > 10) | |
| 338 | − return -1; | |
| 339 | − }while(!ptinrect(p2, in) || manhattan(p, p2) < DIST*3 || manhattan(p2, l->down) < DIST || manhattan(p2, l->up) < DIST || hasflagat(l, p2, Fblocked|Fhasmonster|Fhasfeature)); | |
| 340 | − clear(l, p2, 2); | |
| 341 | − several(l, &p2, 1, "gnome king", 0); | |
| 342 | − several(l, &p2, 1, "gnome wizard", 1); | |
| 343 | − addspawn(l, p2, "gnome", 11); | |
| 344 | − addspawn(l, p2, "gnome lord", 25); | |
| 345 | − addspawn(l, p2, "gnome wizard", 37); | |
| 346 | − addspawn(l, p2, "gnome king", 53); | |
| 347 | − | |
| 348 | − genmonsters(l, "ghost", space/64); | |
| 349 | − genmonsters(l, "gnome", space/64); | |
| 68 | + mklgraveyard(l); | |
| 350 | 69 | break; |
| 351 | 70 | case 3: |
| 352 | − space += drunken(l, TLAVA, 2, 1, 1); | |
| 353 | − clear(l, pup, 2); | |
| 354 | − clear(l, pdown, 2); | |
| 355 | − do{ | |
| 356 | − p = (Point){nrand(l->width), nrand(l->height)}; | |
| 357 | − }while(!ptinrect(p, in) || hasflagat(l, p, Fblocked|Fhasfeature)); | |
| 358 | − clear(l, p, 2); | |
| 359 | − addspawn(l, p, "large cat", 67); | |
| 360 | − do{ | |
| 361 | − p = (Point){nrand(l->width), nrand(l->height)}; | |
| 362 | − }while(!ptinrect(p, in) || hasflagat(l, p, Fblocked|Fhasfeature)); | |
| 363 | − clear(l, p, 2); | |
| 364 | − addspawn(l, p, "gnome lord", 27); | |
| 365 | − | |
| 366 | − genmonsters(l, "large cat", space/64); | |
| 367 | − genmonsters(l, "gnome lord", space/64); | |
| 71 | + mklvolcano(l); | |
| 368 | 72 | break; |
| 369 | 73 | } |
| 370 | 74 | |
| 371 | − l->ai = mkstate("level", nil, l, nil, levelexec, nil); | |
| 372 | − | |
| 373 | 75 | return 0; |
| 374 | 76 | } |
| 375 | 77 | |
| 376 | 78 | Level* |
| 377 | 79 | genlevel(int width, int height, int type) |
| 378 | 80 | { |
| 379 | − int x, y; | |
| 380 | 81 | Level *l; |
| 381 | 82 | |
| 382 | 83 | l = nil; |
| @@ -385,36 +86,10 @@ genlevel(int width, int height, int type) · 2 unmodified lines | ||
| 385 | 86 | if(l != nil) |
| 386 | 87 | freelevel(l); |
| 387 | 88 | |
| 388 | − l = mallocz(sizeof(Level), 1); | |
| 389 | − if(l == nil) | |
| 390 | − return nil; | |
| 391 | − | |
| 392 | − l->width = width; | |
| 393 | − l->height = height; | |
| 394 | − l->r = Rect(0, 0, width, height); | |
| 395 | − | |
| 396 | − l->tiles = mallocz(sizeof(Tile) * width * height, 1); | |
| 397 | − if(l->tiles == nil) | |
| 398 | − goto err0; | |
| 399 | − | |
| 400 | − l->flags = mallocz(sizeof(int) * width * height, 1); | |
| 401 | − if(l->flags == nil) | |
| 402 | − goto err1; | |
| 403 | − | |
| 404 | − for(x = 0; x < width; x++){ | |
| 405 | − for(y = 0; y < height; y++){ | |
| 406 | − tileat(l, Pt(x, y))->terrain = TFLOOR; | |
| 407 | − } | |
| 408 | − } | |
| 89 | + l = mklevel(width, height, TFLOOR); | |
| 409 | 90 | } while(gen(l, type) < 0); |
| 410 | 91 | |
| 411 | 92 | return l; |
| 412 | − | |
| 413 | −err1: | |
| 414 | − free(l->tiles); | |
| 415 | −err0: | |
| 416 | − free(l); | |
| 417 | − return nil; | |
| 418 | 93 | } |
| 419 | 94 | |
| 420 | 95 | void |
| @@ -0,0 +1,489 @@ | ||
| 1 | +#include <u.h> | |
| 2 | +#include <libc.h> | |
| 3 | +#include <draw.h> | |
| 4 | +#include <thread.h> | |
| 5 | + | |
| 6 | +#include "dat.h" | |
| 7 | +#include "alg.h" | |
| 8 | + | |
| 9 | +/* removes all monsters/features near p, except p itself */ | |
| 10 | +static void | |
| 11 | +lclear(Level *l, Point p, int dist) | |
| 12 | +{ | |
| 13 | + Rectangle r; | |
| 14 | + Point p2; | |
| 15 | + Tile *t; | |
| 16 | + | |
| 17 | + r = Rect(p.x-dist, p.y-dist, p.x+dist, p.y+dist); | |
| 18 | + rectclip(&r, l->r); | |
| 19 | + | |
| 20 | + for(p2.x = r.min.x; p2.x <= r.max.x; p2.x++){ | |
| 21 | + for(p2.y = r.min.y; p2.y <= r.max.y; p2.y++){ | |
| 22 | + if(eqpt(p, p2)) | |
| 23 | + continue; | |
| 24 | + t = tileat(l, p2); | |
| 25 | + t->unit = t->feat = 0; | |
| 26 | + if(t->monst){ | |
| 27 | + mfree(t->monst); | |
| 28 | + t->monst = nil; | |
| 29 | + } | |
| 30 | + flagat(l, p2) = 0; | |
| 31 | + } | |
| 32 | + } | |
| 33 | +} | |
| 34 | + | |
| 35 | +static void | |
| 36 | +lclearrect(Level *l, Rectangle r) | |
| 37 | +{ | |
| 38 | + Point p; | |
| 39 | + Tile *t; | |
| 40 | + | |
| 41 | + rectclip(&r, l->r); | |
| 42 | + | |
| 43 | + for(p.x = r.min.x; p.x <= r.max.x; p.x++){ | |
| 44 | + for(p.y = r.min.y; p.y <= r.max.y; p.y++){ | |
| 45 | + t = tileat(l, p); | |
| 46 | + t->unit = t->feat = 0; | |
| 47 | + if(t->monst != nil){ | |
| 48 | + mfree(t->monst); | |
| 49 | + t->monst = nil; | |
| 50 | + } | |
| 51 | + if(t->portal != nil){ | |
| 52 | + free(t->portal); | |
| 53 | + t->portal = nil; | |
| 54 | + } | |
| 55 | + flagat(l, p) = 0; | |
| 56 | + } | |
| 57 | + } | |
| 58 | +} | |
| 59 | + | |
| 60 | +static void | |
| 61 | +lfillrect(Level *l, Rectangle r, int type) | |
| 62 | +{ | |
| 63 | + Point p; | |
| 64 | + Tile *t; | |
| 65 | + | |
| 66 | + for(p.x = r.min.x; p.x <= r.max.x; p.x++){ | |
| 67 | + for(p.y = r.min.y; p.y <= r.max.y; p.y++){ | |
| 68 | + t = tileat(l, p); | |
| 69 | + t->feat = type; | |
| 70 | + flagat(l, p) = (Fblocked|Fhasfeature); | |
| 71 | + } | |
| 72 | + } | |
| 73 | +} | |
| 74 | + | |
| 75 | +static void | |
| 76 | +lborder(Level *l, Rectangle r, int i, int type) | |
| 77 | +{ | |
| 78 | + Rectangle tmp; | |
| 79 | + | |
| 80 | + assert(i != 0); | |
| 81 | + | |
| 82 | + if(i < 0){ | |
| 83 | + r = insetrect(r, i); | |
| 84 | + i = -i; | |
| 85 | + } | |
| 86 | + | |
| 87 | + i -= 1; | |
| 88 | + | |
| 89 | + tmp = Rect(r.min.x, r.min.y, r.max.x, r.min.y+i); | |
| 90 | + lclearrect(l, tmp); | |
| 91 | + lfillrect(l, tmp, type); | |
| 92 | + | |
| 93 | + tmp = Rect(r.min.x, r.max.y-i, r.max.x, r.max.y); | |
| 94 | + lclearrect(l, tmp); | |
| 95 | + lfillrect(l, tmp, type); | |
| 96 | + | |
| 97 | + tmp = Rect(r.min.x, r.min.y+i, r.min.x+i, r.max.y-i); | |
| 98 | + lclearrect(l, tmp); | |
| 99 | + lfillrect(l, tmp, type); | |
| 100 | + | |
| 101 | + tmp = Rect(r.max.x-i, r.min.y+i, r.max.x, r.max.y-i); | |
| 102 | + lclearrect(l, tmp); | |
| 103 | + lfillrect(l, tmp, type); | |
| 104 | +} | |
| 105 | + | |
| 106 | +/* more sbias makes it straighter. */ | |
| 107 | +static void | |
| 108 | +drunk1(Level *l, Point p, uint count, uint sbias) | |
| 109 | +{ | |
| 110 | + int cur, last; | |
| 111 | + Point next; | |
| 112 | + Rectangle clipr; | |
| 113 | + Tile *t; | |
| 114 | + | |
| 115 | + clipr = insetrect(l->r, 1); | |
| 116 | + | |
| 117 | + last = nrand(NCARDINAL); | |
| 118 | + while(count > 0){ | |
| 119 | + if(nrand(sbias+1) == 0) | |
| 120 | + cur = nrand(NCARDINAL); | |
| 121 | + else | |
| 122 | + cur = last; | |
| 123 | + next = addpt(p, cardinals[cur]); | |
| 124 | + if(!ptinrect(next, clipr)) | |
| 125 | + continue; | |
| 126 | + last = cur; | |
| 127 | + p = next; | |
| 128 | + if(!eqpt(p, l->up) && !eqpt(p, l->down) && hasflagat(l, p, Fblocked|Fhasfeature)){ | |
| 129 | + t = tileat(l, p); | |
| 130 | + t->feat = 0; | |
| 131 | + flagat(l, p) = 0; | |
| 132 | + count--; | |
| 133 | + } | |
| 134 | + } | |
| 135 | +} | |
| 136 | + | |
| 137 | +/* | |
| 138 | + * drunken performs the drunkard's walk. as s1 and | |
| 139 | + * s2 increase, the paths become more biased toward | |
| 140 | + * 'straight'. it loops until the downstairs is | |
| 141 | + * reachable from the upstairs. | |
| 142 | + */ | |
| 143 | +static int | |
| 144 | +drunken(Level *l, int type, int howmuch, int s1, int s2) | |
| 145 | +{ | |
| 146 | + int cnt, npath, redos; | |
| 147 | + Point p, *path; | |
| 148 | + Tile *t; | |
| 149 | + | |
| 150 | + cnt = ((l->width * l->height) / howmuch); //* 2; | |
| 151 | + redos = 0; | |
| 152 | + | |
| 153 | +redo: | |
| 154 | + if(redos++ > 10) | |
| 155 | + return 0; | |
| 156 | + /* fill */ | |
| 157 | + for(p.x = 0; p.x < l->width; p.x++){ | |
| 158 | + for(p.y = 0; p.y < l->height; p.y++){ | |
| 159 | + if(!hasflagat(l, p, Fblocked|Fhasfeature)){ | |
| 160 | + t = tileat(l, p); | |
| 161 | + t->feat = type; | |
| 162 | + flagat(l, p) = (Fblocked|Fhasfeature); | |
| 163 | + } | |
| 164 | + } | |
| 165 | + } | |
| 166 | + | |
| 167 | + drunk1(l, l->up, cnt/2, s1); | |
| 168 | + drunk1(l, l->down, cnt/2, s2); | |
| 169 | + | |
| 170 | + /* check reachability */ | |
| 171 | + npath = pathfind(l, l->up, l->down, &path, Fblocked); | |
| 172 | + if(npath < 0) | |
| 173 | + goto redo; | |
| 174 | + else | |
| 175 | + free(path); | |
| 176 | + | |
| 177 | + return cnt * 2; | |
| 178 | +} | |
| 179 | + | |
| 180 | +/* create a monster in the idle state */ | |
| 181 | +static Monster* | |
| 182 | +mkmons(Level *l, Point p, char *type) | |
| 183 | +{ | |
| 184 | + Monster *m; | |
| 185 | + m = mbyname(type); | |
| 186 | + if(m == nil) | |
| 187 | + sysfatal("monstbyname: %r"); | |
| 188 | + m->l = l; | |
| 189 | + m->pt = p; | |
| 190 | + /* setup ai state */ | |
| 191 | + idle(m); | |
| 192 | + return m; | |
| 193 | +} | |
| 194 | + | |
| 195 | +/* | |
| 196 | + * several is a recursive function, that spawns monsters | |
| 197 | + * centered at p[0] expaning outward according to a | |
| 198 | + * von neumann neighborhood with radius r. for example: | |
| 199 | + * several(l, p, 1, TGHOST, 2); | |
| 200 | + * spawns 13 ghosts centered at p on l. | |
| 201 | + */ | |
| 202 | +void | |
| 203 | +several(Level *l, Point *p, int count, char *type, int r) | |
| 204 | +{ | |
| 205 | + int i, j, n; | |
| 206 | + Point *neigh; | |
| 207 | + Tile *t; | |
| 208 | + for(i = 0; i < count; i++){ | |
| 209 | + if(!hasflagat(l, p[i], Fhasmonster|Fblocked)){ | |
| 210 | + t = tileat(l, p[i]); | |
| 211 | + t->monst = mkmons(l, p[i], type); | |
| 212 | + t->unit = t->monst->md->tile; | |
| 213 | + setflagat(l, p[i], Fhasmonster); | |
| 214 | + } | |
| 215 | + if(r > 0){ | |
| 216 | + neigh = neighbor(l->r, p[i], &n); | |
| 217 | + for(j = 0; j < n; j++){ | |
| 218 | + several(l, neigh, n, type, r-1); | |
| 219 | + } | |
| 220 | + free(neigh); | |
| 221 | + } | |
| 222 | + } | |
| 223 | +} | |
| 224 | + | |
| 225 | +/* genmonsters spawns count of type monsters at random places on l. */ | |
| 226 | +static void | |
| 227 | +genmonsters(Level *l, char *type, int count) | |
| 228 | +{ | |
| 229 | + int i; | |
| 230 | + Point p; | |
| 231 | + Tile *t; | |
| 232 | + | |
| 233 | + for(i = 0; i < count; i++){ | |
| 234 | + do{ | |
| 235 | + p = (Point){nrand(l->width), nrand(l->height)}; | |
| 236 | + }while(hasflagat(l, p, Fhasmonster|Fhasfeature|Fblocked)); | |
| 237 | + | |
| 238 | + t = tileat(l, p); | |
| 239 | + t->monst = mkmons(l, p, type); | |
| 240 | + t->unit = t->monst->md->tile; | |
| 241 | + setflagat(l, p, Fhasmonster); | |
| 242 | + } | |
| 243 | +} | |
| 244 | + | |
| 245 | +static void | |
| 246 | +levelexec(AIState *a) | |
| 247 | +{ | |
| 248 | + int i, j, n; | |
| 249 | + Point p, np, *neigh; | |
| 250 | + Level *l; | |
| 251 | + Spawn *sp; | |
| 252 | + Tile *t; | |
| 253 | + MonsterData *md; | |
| 254 | + Monster *m; | |
| 255 | + | |
| 256 | + l = a->aux; | |
| 257 | + | |
| 258 | + for(i = 0; i < l->nspawns; i++){ | |
| 259 | + sp = &l->spawns[i]; | |
| 260 | + if(sp->turn++ % sp->freq == 0){ | |
| 261 | + p = sp->pt; | |
| 262 | + | |
| 263 | + /* le trap */ | |
| 264 | + if(sp->istrap > 0){ | |
| 265 | + neigh = neighbor(l->r, p, &n); | |
| 266 | + for(j = 0; j < n; j++){ | |
| 267 | + np = neigh[j]; | |
| 268 | + if(hasflagat(l, np, Fhasmonster)){ | |
| 269 | + t = tileat(l, np); | |
| 270 | + m = t->monst; | |
| 271 | + md = mdbyname(sp->what); | |
| 272 | + if(md == nil){ | |
| 273 | + dbg("programming error: monster %s not found: %r", sp->what); | |
| 274 | + continue; | |
| 275 | + } | |
| 276 | + if(strcmp(m->md->name, sp->what) != 0 && abs(m->align - md->align) > 15){ | |
| 277 | + several(l, &p, 1, sp->what, sp->istrap); | |
| 278 | + } | |
| 279 | + } | |
| 280 | + } | |
| 281 | + | |
| 282 | + free(neigh); | |
| 283 | + } | |
| 284 | + several(l, &p, 1, sp->what, 0); | |
| 285 | + } | |
| 286 | + } | |
| 287 | +} | |
| 288 | + | |
| 289 | +Level* | |
| 290 | +mklevel(int width, int height, int floor) | |
| 291 | +{ | |
| 292 | + Point p; | |
| 293 | + Level *l; | |
| 294 | + | |
| 295 | + l = mallocz(sizeof(Level), 1); | |
| 296 | + if(l == nil) | |
| 297 | + return nil; | |
| 298 | + | |
| 299 | + l->width = width; | |
| 300 | + l->height = height; | |
| 301 | + l->r = Rect(0, 0, width, height); | |
| 302 | + | |
| 303 | + l->tiles = mallocz(sizeof(Tile) * width * height, 1); | |
| 304 | + if(l->tiles == nil) | |
| 305 | + goto err0; | |
| 306 | + | |
| 307 | + l->flags = mallocz(sizeof(int) * width * height, 1); | |
| 308 | + if(l->flags == nil) | |
| 309 | + goto err1; | |
| 310 | + | |
| 311 | + l->ai = mkstate("level", nil, l, nil, levelexec, nil); | |
| 312 | + if(l->ai == nil) | |
| 313 | + goto err2; | |
| 314 | + | |
| 315 | + for(p.x = 0; p.x < width; p.x++){ | |
| 316 | + for(p.y = 0; p.y < height; p.y++){ | |
| 317 | + tileat(l, p)->terrain = floor; | |
| 318 | + } | |
| 319 | + } | |
| 320 | + | |
| 321 | + return l; | |
| 322 | + | |
| 323 | +err2: | |
| 324 | + free(l->flags); | |
| 325 | +err1: | |
| 326 | + free(l->tiles); | |
| 327 | +err0: | |
| 328 | + free(l); | |
| 329 | + return nil; | |
| 330 | +} | |
| 331 | + | |
| 332 | +int | |
| 333 | +mkldebug(Level *l) | |
| 334 | +{ | |
| 335 | + drunken(l, TTREE, 3, 3, 3); | |
| 336 | + lclear(l, l->up, 1); | |
| 337 | + lclear(l, l->down, 1); | |
| 338 | + several(l, &l->down, 1, "lich", 1); | |
| 339 | + return 0; | |
| 340 | +} | |
| 341 | + | |
| 342 | +int | |
| 343 | +mklforest(Level *l) | |
| 344 | +{ | |
| 345 | + int space; | |
| 346 | + Point p, p2; | |
| 347 | + Rectangle in; | |
| 348 | + | |
| 349 | + in = insetrect(l->r, 3); | |
| 350 | + | |
| 351 | + space = drunken(l, TTREE, 2, 0, 0); | |
| 352 | + lclear(l, l->up, 1); | |
| 353 | + lclear(l, l->down, 1); | |
| 354 | + | |
| 355 | + do { | |
| 356 | + p = (Point){nrand(Dx(in))+3, nrand(Dy(in))+3}; | |
| 357 | + } while(flagat(l, p) != 0); | |
| 358 | + do { | |
| 359 | + p2 = (Point){nrand(Dx(in))+3, nrand(Dy(in))+3}; | |
| 360 | + } while(flagat(l, p2) != 0 || manhattan(p, p2) < ORTHOCOST*10 || manhattan(p, p2) > ORTHOCOST*20); | |
| 361 | + | |
| 362 | + lclear(l, p, 2); | |
| 363 | + several(l, &p, 1, "captain", 0); | |
| 364 | + several(l, &p, 1, "lieutenant", 1); | |
| 365 | + addspawn(l, p, "soldier", 57, 2); | |
| 366 | + addspawn(l, p, "sergeant", 91, 1); | |
| 367 | + addspawn(l, p, "lieutenant", 97, 1); | |
| 368 | + addspawn(l, p, "captain", 131, 1); | |
| 369 | + | |
| 370 | + lclear(l, p2, 2); | |
| 371 | + several(l, &p2, 1, "gnome king", 0); | |
| 372 | + several(l, &p2, 1, "gnome wizard", 1); | |
| 373 | + addspawn(l, p2, "gnome", 13, 1); | |
| 374 | + addspawn(l, p2, "gnome lord", 17, 2); | |
| 375 | + addspawn(l, p2, "gnome wizard", 27, 1); | |
| 376 | + addspawn(l, p2, "gnome king", 101, 1); | |
| 377 | + | |
| 378 | + /* don't start empty */ | |
| 379 | + genmonsters(l, "gnome lord", space/128); | |
| 380 | + genmonsters(l, "sergeant", space/128); | |
| 381 | + return 0; | |
| 382 | +} | |
| 383 | + | |
| 384 | +int | |
| 385 | +mklgraveyard(Level *l) | |
| 386 | +{ | |
| 387 | + int space; | |
| 388 | + Point p, p2; | |
| 389 | + Rectangle in; | |
| 390 | + | |
| 391 | + in = insetrect(l->r, 3); | |
| 392 | + | |
| 393 | + space = drunken(l, TGRAVE, 2, 4, 4); | |
| 394 | + lclear(l, l->up, 2); | |
| 395 | + lclear(l, l->down, 2); | |
| 396 | + | |
| 397 | + do { | |
| 398 | + p = (Point){nrand(Dx(in))+3, nrand(Dy(in))+3}; | |
| 399 | + } while(flagat(l, p) != 0); | |
| 400 | + do { | |
| 401 | + p2 = (Point){nrand(Dx(in))+3, nrand(Dy(in))+3}; | |
| 402 | + } while(flagat(l, p2) != 0 || manhattan(p, p2) < ORTHOCOST*15 || manhattan(p, p2) > ORTHOCOST*20); | |
| 403 | + | |
| 404 | + lclear(l, p, 2); | |
| 405 | + lclear(l, p2, 2); | |
| 406 | + | |
| 407 | + addspawn(l, p, "lich", 101, 1); | |
| 408 | + addspawn(l, p, "demilich", 157, 0); | |
| 409 | + addspawn(l, p, "ghost", 39, 2); | |
| 410 | + addspawn(l, p, "human zombie", 43, 2); | |
| 411 | + addspawn(l, p, "skeleton", 127, 1); | |
| 412 | + | |
| 413 | + several(l, &p2, 1, "gnome king", 0); | |
| 414 | + several(l, &p2, 1, "gnome wizard", 1); | |
| 415 | + addspawn(l, p2, "gnome", 23, 1); | |
| 416 | + addspawn(l, p2, "gnome lord", 25, 2); | |
| 417 | + addspawn(l, p2, "gnome wizard", 37, 2); | |
| 418 | + addspawn(l, p2, "gnome king", 53, 1); | |
| 419 | + | |
| 420 | + genmonsters(l, "ghost", space/64); | |
| 421 | + genmonsters(l, "gnome", space/64); | |
| 422 | + | |
| 423 | + return 0; | |
| 424 | +} | |
| 425 | + | |
| 426 | +int | |
| 427 | +mklvolcano(Level *l) | |
| 428 | +{ | |
| 429 | + int space; | |
| 430 | + Point p, p2; | |
| 431 | + Rectangle in; | |
| 432 | + | |
| 433 | + in = insetrect(l->r, 3); | |
| 434 | + | |
| 435 | + space = drunken(l, TLAVA, 2, 1, 1); | |
| 436 | + lclear(l, l->up, 2); | |
| 437 | + lclear(l, l->down, 2); | |
| 438 | + | |
| 439 | + do { | |
| 440 | + p = (Point){nrand(Dx(in))+3, nrand(Dy(in))+3}; | |
| 441 | + } while(flagat(l, p) != 0); | |
| 442 | + do { | |
| 443 | + p2 = (Point){nrand(Dx(in))+3, nrand(Dy(in))+3}; | |
| 444 | + } while(flagat(l, p2) != 0 || manhattan(p, p2) < ORTHOCOST*10 || manhattan(p, p2) > ORTHOCOST*20); | |
| 445 | + | |
| 446 | + lclear(l, p, 2); | |
| 447 | + lclear(l, p2, 2); | |
| 448 | + | |
| 449 | + addspawn(l, p, "large cat", 77, 0); | |
| 450 | + addspawn(l, p2, "gnome lord", 27, 2); | |
| 451 | + | |
| 452 | + genmonsters(l, "large cat", space/128); | |
| 453 | + genmonsters(l, "gnome lord", space/64); | |
| 454 | + return 0; | |
| 455 | +} | |
| 456 | + | |
| 457 | +int | |
| 458 | +mklcastle(Level *l) | |
| 459 | +{ | |
| 460 | + int n; | |
| 461 | + Point p; | |
| 462 | + Rectangle mid; | |
| 463 | + | |
| 464 | + mid = l->r; | |
| 465 | + | |
| 466 | + n = Dx(l->r)/5; | |
| 467 | + mid.min.x += n; | |
| 468 | + mid.max.x -= n; | |
| 469 | + n = Dy(l->r)/5; | |
| 470 | + mid.min.y += n; | |
| 471 | + mid.max.y -= n; | |
| 472 | + | |
| 473 | + lborder(l, mid, 1, TWALL); | |
| 474 | + lborder(l, mid, -1, TWATER); | |
| 475 | + | |
| 476 | + /* create path in */ | |
| 477 | + p = (Point){mid.min.x, mid.max.y}; | |
| 478 | + p.x += nrand(Dx(mid)-2)+2; | |
| 479 | + | |
| 480 | + lclearrect(l, Rpt(p, addpt(p, Pt(0,1)))); | |
| 481 | + | |
| 482 | + addspawn(l, addpt(mid.min, Pt(Dx(mid)/2, Dy(mid)/2)), "soldier", 21, 0); | |
| 483 | + | |
| 484 | + l->up = Pt(1,1); | |
| 485 | + l->down = Pt(4,1); | |
| 486 | + | |
| 487 | + return 0; | |
| 488 | +} | |
| 489 | + | |
| @@ -10,6 +10,7 @@ OFILES=\ · 9 unmodified lines | ||
| 10 | 10 | camera.$O\ |
| 11 | 11 | hack9.$O\ |
| 12 | 12 | level.$O\ |
| 13 | + levelgen.$O\ | |
| 13 | 14 | monst.$O\ |
| 14 | 15 | path.$O\ |
| 15 | 16 | tile.$O\ |
| @@ -342,6 +342,9 @@ maction(Monster *m, int what, Point where) · 341 unmodified lines | ||
| 342 | 342 | { |
| 343 | 343 | Tile *cur, *targ; |
| 344 | 344 | |
| 345 | + if(!ptinrect(where, m->l->r)) | |
| 346 | + return -1; | |
| 347 | + | |
| 345 | 348 | switch(what){ |
| 346 | 349 | default: |
| 347 | 350 | fprint(2, "unknown move %d\n", what); |
| @@ -15,7 +15,13 @@ monster="gnome king" · 14 unmodified lines | ||
| 15 | 15 | |
| 16 | 16 | # undead |
| 17 | 17 | monster="lich" |
| 18 | − tile=185 xpl=2 align=-50 mvr=6 hp=32 ac=3 atk=3d2 | |
| 18 | + tile=185 xpl=11 align=-50 mvr=6 hp=32 ac=0 atk=1d10 | |
| 19 | +monster="demilich" | |
| 20 | + tile=186 xpl=14 align=-50 mvr=9 hp=48 ac=-2 atk=3d4 | |
| 21 | +monster="human zombie" | |
| 22 | + tile=246 xpl=4 align=-50 mvr=6 hp=16 ac=8 atk=1d8 | |
| 23 | +monster="skeleton" | |
| 24 | + tile=250 xpl=12 align=-50 mvr=8 hp=48 ac=4 atk=2d6 | |
| 19 | 25 | monster="ghost" |
| 20 | 26 | tile=290 xpl=1 align=-50 mvr=6 hp=16 ac=-5 atk=1d4 |
| 21 | 27 | |