console logger; template loader
[dd2d.git] / d2dmap.d
blob2b8196a113c1d291447bee5c042e58b555370a6f
1 module d2dmap is aliced;
2 private:
4 import arsd.color;
5 import iv.stream;
7 import glutils;
8 import console;
9 import wadarc;
12 // ////////////////////////////////////////////////////////////////////////// //
13 __gshared Color[256] d2dpal;
16 public void loadPalette () {
17 auto fl = openFile("playpal.pal");
18 foreach (immutable idx; 0..256) {
19 ubyte r = cast(ubyte)(fl.readNum!ubyte()*4);
20 ubyte g = cast(ubyte)(fl.readNum!ubyte()*4);
21 ubyte b = cast(ubyte)(fl.readNum!ubyte()*4);
22 d2dpal[idx].r = r;
23 d2dpal[idx].g = g;
24 d2dpal[idx].b = b;
25 d2dpal[idx].a = 255;
27 // color 0 is transparent
28 d2dpal[0].asUint = 0;
32 // ////////////////////////////////////////////////////////////////////////// //
33 public class WallTexture {
34 private import std.stdio : File;
36 public:
37 int sx, sy;
38 int width, height;
39 ubyte[] data;
41 this (string name) {
42 auto fl = openFile(name);
43 load(fl);
46 Color opIndex (usize y, usize x) { pragma(inline, true); return (x < width && y < height ? d2dpal.ptr[data.ptr[y*width+x]] : Color(0, 0, 0, 0)); }
48 TrueColorImage asTCImage () {
49 auto img = new TrueColorImage(width, height);
50 auto cols = img.imageData.colors.ptr;
51 foreach (int y; 0..height) {
52 foreach (int x; 0..width) {
53 ubyte c = data.ptr[y*width+x];
54 if (c == 0) {
55 *cols = Color(0, 0, 0, 0); // transparent
56 } else {
57 *cols = d2dpal[c];
59 ++cols;
62 return img;
65 private:
66 void load (File fi) {
67 width = fi.readNum!ushort();
68 height = fi.readNum!ushort();
69 if (width < 1) assert(0);
70 if (height < 1) assert(0);
71 sx = fi.readNum!short();
72 sy = fi.readNum!short();
73 data = new ubyte[width*height];
74 fi.rawReadExact(data[]);
79 // ////////////////////////////////////////////////////////////////////////// //
80 private Color getPixel (TrueColorImage img, int x, int y) {
81 if (x >= 0 && y >= 0 && x < img.width && y < img.height) {
82 return img.imageData.colors.ptr[y*img.width+x];
83 } else {
84 return Color(0, 0, 0, 0);
89 private void putPixel (TrueColorImage img, int x, int y, Color clr) {
90 if (x >= 0 && y >= 0 && x < img.width && y < img.height && clr.a != 0) {
91 img.imageData.colors.ptr[y*img.width+x] = clr;
96 private void putTile (TrueColorImage img, int x, int y, WallTexture wt) {
97 if (wt is null) return;
98 x -= wt.sx;
99 y -= wt.sy;
100 auto s = wt.data.ptr;
101 foreach (int dy; 0..wt.height) {
102 foreach (int dx; 0..wt.width) {
103 img.putPixel(x+dx, y, d2dpal[*s++]);
105 ++y;
110 // ////////////////////////////////////////////////////////////////////////// //
111 enum MType {
112 Nobody,
113 Demon,
114 Imp,
115 Zombie,
116 Sergeant,
117 Cyberdemon,
118 Chaingunner,
119 BaronOfHell,
120 HellKnight,
121 Cacodemon,
122 LostSoul,
123 PainElemental,
124 SpiderMastermind,
125 Arachnotron,
126 Mancubus,
127 Revenant,
128 Archvile,
129 Fish,
130 Barrel,
131 Robot,
132 Man,
136 // ////////////////////////////////////////////////////////////////////////// //
137 public final class LevelMap {
138 private:
139 private import std.stdio : File;
141 enum MapVersion = 2; // óÁÍÁÑ ÐÏÓÌÅÄÎÑÑ ×ÅÒÓÉÑ ËÁÒÔÙ
142 enum MapSize = 100;
144 public:
145 // tile type
146 enum : ubyte {
147 TILE_EMPTY = 0,
148 TILE_WALL = 1,
149 TILE_DOORC = 2, // closed door
150 TILE_DOORO = 3, // opened door
151 TILE_STEP = 4,
152 TILE_WATER = 5,
153 TILE_ACID1 = 6,
154 TILE_ACID2 = 7,
155 TILE_MBLOCK = 8, // just blocks monsters
156 TILE_LIFTU = 9,
157 TILE_LIFTD = 10,
159 TILE_ACTTRAP = 255,
162 enum : short {
163 MB_COMMENT = -1,
164 MB_END = 0,
165 MB_WALLNAMES,
166 MB_BACK,
167 MB_WTYPE,
168 MB_FRONT,
169 MB_THING,
170 MB_SWITCH,
171 MB_MUSIC, // 8 bytes, usually
172 MB_SKY, // ushort: [1..3]
173 MB_SWITCH2,
174 MB__UNKNOWN,
177 enum {
178 SW_PL_PRESS = 1<<0,
179 SW_MN_PRESS = 1<<1,
180 SW_PL_NEAR = 1<<2,
181 SW_MN_NEAR = 1<<3,
182 SW_KEY_R = 1<<4,
183 SW_KEY_G = 1<<5,
184 SW_KEY_B = 1<<6,
187 static struct MapThing {
188 short x, y; // ËÏÏÒÄÉÎÁÔÙ
189 ushort type; // ÔÉÐ
190 ushort flags; // ÆÌÁÇÉ
193 static struct MapSwitch {
194 ubyte x, y; // ËÏÏÒÄÉÎÁÔÙ/8
195 ubyte type; // ÔÉÐ
196 ubyte tm; // ÄÏÌÖÎÏ ÂÙÔØ 0
197 ubyte a, b; // ÏÂÙÞÎÏ - ËÏÏÒÄÉÎÁÔÙ/8 Ä×ÅÒÉ
198 ushort c; // ÎÅ ÉÓÐÏÌØÚÕÅÔÓÑ (×ÒÏÄÅ ÂÙ)
199 ubyte flags; // ÆÌÁÇÉ
202 // thing flags
203 enum : ubyte {
204 DirRight = 0x01,
205 DeathMatch = 0x10, // ÐÏÑ×ÌÑÅÔÓÑ ÔÏÌØËÏ × DeathMatch'Å
208 public:
209 enum { Type, Front, Back, Water, Lava, Acid, LightMask } // tile types, Front+: megatexture types
210 ubyte[][][] tiles;
211 string[] wallnames; // "_water_0": water, "_water_1": acid, "_water_2": lava
212 ubyte[] walltypes; // 0: solid; 1: non-solid
213 WallTexture[] textures;
214 int width, height;
215 Texture[7] texgl;
216 WallTexture skytex;
218 this (string fname) { load(fname); }
219 this (File fl) { load(fl); }
221 // build OpenGL "megatextures" with the whole level on it
222 void oglBuildMega () {
223 auto img = new TrueColorImage(width*8, height*8);
224 foreach (immutable type; Front..LightMask+1) {
225 img.imageData.colors[] = Color(0, 0, 0, 0);
226 if (type == Back) {
227 foreach (int y; 0..(height*8+skytex.height-1)/skytex.height) {
228 foreach (int x; 0..(width*8+skytex.width-1)/skytex.width) {
229 img.putTile(x*skytex.width, y*skytex.height, skytex);
233 foreach (int y; 0..height) {
234 foreach (int x; 0..width) {
235 if (type == LightMask) {
236 // in the lightmask texture, we should have only occluders' pixels
237 auto tt = tiles.ptr[Type].ptr[y].ptr[x];
238 if (tt == TILE_WALL || tt == TILE_DOORC) {
239 img.putTile(x*8, y*8, textures.ptr[tiles.ptr[Back].ptr[y].ptr[x]]);
240 img.putTile(x*8, y*8, textures.ptr[tiles.ptr[Front].ptr[y].ptr[x]]);
241 } /*else if (tt != TILE_LIFTU && tt != TILE_LIFTD) {
242 img.putTile(x*8, y*8, textures.ptr[tiles.ptr[Front].ptr[y].ptr[x]]);
244 } else if (type >= Water) {
245 auto tt = tiles.ptr[Front].ptr[y].ptr[x];
246 if (type == Water) tt = (wallnames[tt] == "_water_0" ? 1 : 0);
247 else if (type == Acid) tt = (wallnames[tt] == "_water_1" ? 2 : 0);
248 else if (type == Lava) tt = (wallnames[tt] == "_water_2" ? 3 : 0);
249 if (tt) {
250 tt = tiles.ptr[Back].ptr[y].ptr[x];
251 img.putTile(x*8, y*8, textures.ptr[tt]);
252 foreach (int dy; 0..8) {
253 foreach (int dx; 0..8) {
254 //img.putPixel(x*8+dx, y*8+dy, Color((tt == 3 ? 128 : 0), (tt == 2 ? 128 : 0), (tt == 1 ? 128 : 0), 128));
255 auto c = img.getPixel(x*8+dx, y*8+dy);
256 if (c.a == 0) img.putPixel(x*8+dx, y*8+dy, Color(0, 0, 0, 255));
260 } else {
261 auto tf = tiles.ptr[Front].ptr[y].ptr[x];
262 auto tb = tiles.ptr[Back].ptr[y].ptr[x];
263 auto tt = tiles.ptr[type].ptr[y].ptr[x];
264 if (wallnames[tf] == "_water_0" || wallnames[tf] == "_water_1" || wallnames[tf] == "_water_2" ||
265 wallnames[tb] == "_water_0" || wallnames[tb] == "_water_1" || wallnames[tb] == "_water_2") {
266 } else {
267 img.putTile(x*8, y*8, textures.ptr[tt]);
272 texgl[type] = new Texture(img, Texture.Option.Nearest, Texture.Option.Clamp);
276 void clear () {
277 tiles = null;
278 wallnames = null;
279 walltypes = null;
280 textures = null;
281 foreach (Texture tex; texgl) if (tex !is null) tex.clear;
282 texgl[] = null;
283 skytex = null;
286 void dump (int idx) {
287 static char to62 (ubyte b) { pragma(inline, true); return cast(char)(b < 10 ? '0'+b : (b-10 < 26 ? 'A'+(b-10) : 'a'+(b-10-26))); }
288 foreach (immutable y; 0..MapSize) {
289 foreach (immutable x; 0..MapSize) {
290 conwrite(to62(tiles[idx][y][x]));
292 conwriteln;
296 private:
297 void calcMapSize () {
298 bool isEmpty(string dir) (int x, int y) if (dir == "col" || dir == "row") {
299 while (x < MapSize && y < MapSize) {
300 if (tiles[0][y][x] || tiles[1][y][x] || tiles[2][y][x]) return false;
301 static if (dir == "row") ++x; else ++y;
303 return true;
305 width = height = MapSize;
306 // fix width
307 while (width > 0 && isEmpty!"col"(width-1, 0)) --width;
308 // fix height
309 while (height > 0 && isEmpty!"row"(0, height-1)) --height;
312 void load (string fname) {
313 import std.stdio : File;
314 load(openFile(fname));
317 void load(ST) (auto ref ST st) if (isReadableStream!ST) {
318 clear();
319 scope(failure) clear;
321 char[8] sign;
322 st.rawReadExact(sign[]);
323 if (sign != "Doom2D\x1a\x00") throw new Exception("invalid map signature");
324 if (st.readNum!ushort() != MapVersion) throw new Exception("invalid map version");
326 // load map blocks
327 tiles = new ubyte[][][](3, MapSize, MapSize);
328 for (;;) {
329 auto btype = st.readNum!ushort();
330 if (btype == 0) break; // no more blocks
331 auto bsubtype = st.readNum!ushort();
332 auto bsize = st.readNum!uint();
333 if (bsize == 0) continue; // skip this block, it has no data (wtf?!)
334 // various tile types
335 if (btype == MB_BACK || btype == MB_FRONT || btype == MB_WTYPE) {
336 if (bsubtype > 1) throw new Exception("unknown tile block subtype");
337 int idx = (btype == MB_BACK ? Back : (btype == MB_FRONT ? Front : Type));
338 ubyte[MapSize*MapSize] data = 0;
339 if (bsubtype == 0) {
340 st.rawReadExact(data[]);
341 } else {
342 // unpack RLE data
343 auto pkdata = new ubyte[](bsize);
344 st.rawReadExact(pkdata[]);
345 int spos = 0, opos = 0;
346 while (spos < pkdata.length) {
347 ubyte b = pkdata[spos++];
348 if (b != 255) {
349 data[opos++] = b;
350 } else {
351 int count = pkdata[spos++];
352 count |= pkdata[spos++]<<8;
353 b = pkdata[spos++];
354 while (count-- > 0) data[opos++] = b;
357 assert(opos == data.length);
359 // copy unpacked data
360 foreach (immutable y; 0..MapSize) {
361 tiles[idx][y][] = data[y*MapSize..(y+1)*MapSize];
363 } else if (btype == MB_WALLNAMES) {
364 wallnames.length = 0;
365 wallnames ~= null;
366 //wallnames[] = null;
367 //walltypes.length = 0;
368 while (bsize >= 8+1) {
369 char[8] texname = 0;
370 st.rawReadExact(texname[]);
371 auto type = st.readNum!ubyte();
372 char[] tn;
373 foreach (immutable idx, ref char ch; texname) {
374 if (ch == 0) break;
375 if (ch >= 'A' && ch <= 'Z') ch += 32;
376 tn = texname[0..idx+1];
378 wallnames ~= tn.idup;
379 //debug { conwriteln(wallnames.length-1, " : ", wallnames[$-1]); }
380 //if (wallnames[$-1][$-1] == '_') wallnames[$-1] ~= "1";
381 //wallnames[$-1] ~= ".vga";
382 //conwriteln(wallnames[$-1]);
383 //walltypes ~= type;
384 bsize -= 8+1;
386 if (bsize != 0) throw new Exception("invalid texture chunk size");
387 debug { conwriteln(wallnames.length, " textures loaded"); }
388 } else {
389 auto pkdata = new ubyte[](bsize);
390 st.rawReadExact(pkdata[]);
393 calcMapSize();
394 // load textures
395 textures.length = 0;
396 foreach (immutable idx, string name; wallnames) {
397 if (name.length == 0 || name[0] == '_') {
398 textures ~= null;
399 continue;
400 } else {
401 textures ~= new WallTexture("tilegfx/"~name~".vga");
404 assert(textures.length == wallnames.length);
405 // fix tiles
406 foreach (immutable y; 0..height) {
407 foreach (immutable x; 0..width) {
408 if (tiles[Front][y][x] >= textures.length) tiles[Front][y][x] = 0;
409 if (tiles[Back][y][x] >= textures.length) tiles[Back][y][x] = 0;
412 skytex = new WallTexture("sprites/rsky1.vga");
413 skytex.sx = skytex.sy = 0;
414 debug { conwriteln(width, "x", height); }
419 // ////////////////////////////////////////////////////////////////////////// //
420 enum {
421 MN_NONE,
423 MN_DEMON,
424 MN_IMP,
425 MN_ZOMBY,
426 MN_SERG,
427 MN_CYBER,
428 MN_CGUN,
429 MN_BARON,
430 MN_KNIGHT,
431 MN_CACO,
432 MN_SOUL,
433 MN_PAIN,
434 MN_SPIDER,
435 MN_BSP,
436 MN_MANCUB,
437 MN_SKEL,
438 MN_VILE,
439 MN_FISH,
440 MN_BARREL,
441 MN_ROBO,
442 MN_MAN,
444 MN__LAST,
445 MN__FIRST = MN_DEMON,
446 MN_TYPES_MAX = MN__LAST-MN__FIRST,
448 MN_PL_DEAD = 100,
449 MN_PL_MESS,
453 struct MonsterStats {
454 int r; // radius
455 int h; // ht (???)
456 int l; // life
457 int mp; // pain
458 int rv; // rv?
459 int jv; // jv?
460 int sp; // slop?
461 int minp; // min_pn? minimal pain to became re-angry?
466 const monster_stats_t mnsz[MN_TYPES_MAX+1] = {
467 //rad ht life pain rv jv slop min_pn
468 {.r= 0, .h= 0, .l= 0, .mp= 0, .rv=0, .jv= 0, .sp= 0, .minp= 0}, // none
469 {.r=15, .h=28, .l= 60, .mp=20, .rv=7, .jv=10, .sp= 0, .minp=10}, // demon
470 {.r=10, .h=28, .l= 25, .mp=15, .rv=3, .jv=10, .sp=30, .minp= 0}, // imp
471 {.r=10, .h=28, .l= 15, .mp=10, .rv=3, .jv=10, .sp=30, .minp= 0}, // zombie
472 {.r=10, .h=28, .l= 20, .mp=10, .rv=3, .jv=10, .sp=30, .minp= 0}, // sergeant
473 {.r=20, .h=55, .l=500, .mp=70, .rv=5, .jv=10, .sp= 0, .minp=50}, // cyberdemon
474 {.r=12, .h=28, .l= 60, .mp=20, .rv=3, .jv=10, .sp=30, .minp=10}, // chaingunner
475 {.r=12, .h=32, .l=150, .mp=40, .rv=3, .jv=10, .sp= 0, .minp=30}, // baron of hell
476 {.r=12, .h=32, .l= 75, .mp=40, .rv=3, .jv=10, .sp= 0, .minp=30}, // hell knight
477 {.r=15, .h=28, .l=100, .mp=10, .rv=4, .jv= 4, .sp= 0, .minp= 0}, // cacodemon
478 {.r= 8, .h=18, .l= 60, .mp=10, .rv=4, .jv= 4, .sp= 0, .minp= 0}, // lost soul
479 {.r=15, .h=28, .l=100, .mp=10, .rv=4, .jv= 4, .sp= 0, .minp= 0}, // pain elemental
480 {.r=64, .h=50, .l=500, .mp=70, .rv=4, .jv=10, .sp= 0, .minp=50}, // spider mastermind
481 {.r=25, .h=27, .l=150, .mp=20, .rv=4, .jv=10, .sp= 0, .minp= 0}, // arachnotron
482 {.r=18, .h=30, .l=200, .mp=40, .rv=3, .jv= 7, .sp= 0, .minp=20}, // mancubus
483 {.r=17, .h=36, .l=200, .mp=40, .rv=6, .jv=11, .sp= 0, .minp=20}, // revenant
484 {.r=17, .h=36, .l=150, .mp=30, .rv=7, .jv=12, .sp= 0, .minp=10}, // archvile
485 {.r= 5, .h= 5, .l= 35, .mp=20, .rv=14, .jv= 6, .sp= 0, .minp=10}, // fish
486 {.r= 5, .h=17, .l= 20, .mp= 0, .rv=7, .jv= 6, .sp= 0, .minp= 0}, // barrel
487 {.r=17, .h=38, .l= 20, .mp=40, .rv=3, .jv= 6, .sp= 0, .minp=20}, // robot
488 {.r= 8, .h=26, .l=400, .mp=70, .rv=8, .jv=10, .sp=30, .minp=50}, // man