more hud api
[dd2d.git] / d2dmap.d
blob724cc8b560520aa7a9a99a5ee887f924b65b45b3
1 /* DooM2D: Midnight on the Firing Line
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module d2dmap is aliced;
19 private:
21 import arsd.color;
22 import iv.stream;
24 import glutils;
25 import console;
26 import wadarc;
28 import d2dgfx;
29 //import d2dtpl;
31 //import iv.encoding;
33 import iv.vfs.koi8;
36 // ////////////////////////////////////////////////////////////////////////// //
37 public enum TileSize = 8;
40 // ////////////////////////////////////////////////////////////////////////// //
41 Color getPixel (TrueColorImage img, int x, int y) {
42 if (x >= 0 && y >= 0 && x < img.width && y < img.height) {
43 return img.imageData.colors.ptr[y*img.width+x];
44 } else {
45 return Color(0, 0, 0, 0);
50 void putPixel (TrueColorImage img, int x, int y, Color clr) {
51 if (x >= 0 && y >= 0 && x < img.width && y < img.height && clr.a != 0) {
52 img.imageData.colors.ptr[y*img.width+x] = clr;
57 void fixPixelA (TrueColorImage img, int x, int y, ubyte a) {
58 if (x >= 0 && y >= 0 && x < img.width && y < img.height) {
59 img.imageData.colors.ptr[y*img.width+x].a = a;
64 void fixPixelTileA (TrueColorImage img, int x, int y, ubyte a) {
65 foreach (int dy; 0..TileSize) {
66 foreach (int dx; 0..TileSize) {
67 fixPixelA(img, x+dx, y+dy, a);
73 void putPixelTile (TrueColorImage img, int x, int y, Color clr) {
74 foreach (int dy; 0..TileSize) {
75 foreach (int dx; 0..TileSize) {
76 putPixel(img, x+dx, y+dy, clr);
82 void putTile (TrueColorImage img, int x, int y, D2DImage wt) {
83 if (wt is null) return;
84 x -= wt.sx;
85 y -= wt.sy;
86 auto s = wt.data.ptr;
87 foreach (int dy; 0..wt.height) {
88 foreach (int dx; 0..wt.width) {
89 img.putPixel(x+dx, y, d2dpal[*s++]);
91 ++y;
96 // temp
97 static ubyte clampByte (ubyte b, int delta) {
98 delta += b;
99 return cast(ubyte)(delta < 0 ? 0 : delta > 255 ? 255 : delta);
103 // ////////////////////////////////////////////////////////////////////////// //
104 public final class LevelMap {
105 private:
106 enum MapVersion = 2; // óÁÍÁÑ ÐÏÓÌÅÄÎÑÑ ×ÅÒÓÉÑ ËÁÒÔÙ
107 public enum MapSize = 100;
109 public:
110 // tile type
111 enum : ubyte {
112 TILE_EMPTY = 0,
113 TILE_WALL = 1,
114 TILE_DOORC = 2, // closed door
115 TILE_DOORO = 3, // opened door
116 TILE_STEP = 4,
117 TILE_WATER = 5,
118 TILE_ACID1 = 6,
119 TILE_ACID2 = 7,
120 TILE_MBLOCK = 8, // just blocks monsters
121 TILE_LIFTU = 9,
122 TILE_LIFTD = 10,
124 TILE_ACTTRAP = 255,
127 enum : short {
128 MB_COMMENT = -1,
129 MB_END = 0,
130 MB_WALLNAMES,
131 MB_BACK,
132 MB_WTYPE,
133 MB_FRONT,
134 MB_THING,
135 MB_SWITCH,
136 MB_MUSIC, // usually 8 bytes
137 MB_SKY, // ushort: [1..3]
138 MB_SWITCH2,
141 enum {
142 SW_PL_PRESS = 1<<0,
143 SW_MN_PRESS = 1<<1,
144 SW_PL_NEAR = 1<<2,
145 SW_MN_NEAR = 1<<3,
146 SW_KEY_R = 1<<4,
147 SW_KEY_G = 1<<5,
148 SW_KEY_B = 1<<6,
151 static struct MapThing {
152 // thing flags
153 enum : ushort {
154 DirRight = 0x0001,
155 DeathMatch = 0x0010, // ÐÏÑ×ÌÑÅÔÓÑ ÔÏÌØËÏ × DeathMatch'Å
158 short x, y; // ËÏÏÒÄÉÎÁÔÙ
159 ushort type; // ÔÉÐ
160 ushort flags; // ÆÌÁÇÉ
162 @property const pure nothrow @safe @nogc {
163 bool left () => ((flags&DirRight) == 0);
164 bool right () => ((flags&DirRight) != 0);
165 bool dmonly () => ((flags&DeathMatch) != 0);
169 static struct MapSwitch {
170 ubyte x, y; // ËÏÏÒÄÉÎÁÔÙ/8
171 ubyte type; // ÔÉÐ
172 ubyte tm; // ÄÏÌÖÎÏ ÂÙÔØ 0
173 ubyte a, b; // ÏÂÙÞÎÏ - ËÏÏÒÄÉÎÁÔÙ/8 Ä×ÅÒÉ
174 ushort c; // ÎÅ ÉÓÐÏÌØÚÕÅÔÓÑ (×ÒÏÄÅ ÂÙ)
175 ubyte flags; // ÆÌÁÇÉ (SW_*)
178 public:
179 enum { Type, Front, Back, AllLiquids, LiquidMask, LightMask, MapTexturesMax } // tile types, Front+: megatexture types
180 ubyte[][3] tiles; // Type, Front, Back
181 string[] wallnames; // "_water_0": water, "_water_1": acid, "_water_2": lava
182 ubyte[] walltypes; // bit0: solid/non-solid(0x01); bit1: 0x02 if "vtrap01"
183 D2DImage[] textures;
184 int width, height;
185 Texture[MapTexturesMax] texgl;
186 TrueColorImage[MapTexturesMax] teximgs;
187 D2DImage skytex;
188 Texture skytexgl;
190 MapThing[] things;
191 MapSwitch[] switches;
193 this () {}
195 this (string fname) { load(fname); }
196 this(ST) (auto ref ST fl) { load(fl); }
198 bool hasLiquidAt (int x, int y) {
199 if (x < 0 || y < 0 || x >= MapSize || y >= MapSize) return false;
200 auto tt = tiles.ptr[Front].ptr[y*MapSize+x];
201 return (wallnames[tt] == "_water_0" || wallnames[tt] == "_water_1" || wallnames[tt] == "_water_2");
204 //FIXME: recreate textures cleanly
205 void clearMegaTextures () {
206 //foreach (Texture tex; texgl) if (tex !is null) tex.clear;
207 //texgl[] = null;
210 // build OpenGL "megatextures" with the whole level on it
211 void oglBuildMega (uint buildMask=0xffff_ffffu) {
212 if (skytexgl is null) skytexgl = new Texture(skytex.asTCImage, Texture.Option.Linear, Texture.Option.Repeat);
213 //auto img = new TrueColorImage(width*TileSize, height*TileSize);
214 foreach (immutable type; Front..LightMask+1) {
215 if (teximgs[type] is null) {
216 buildMask |= 1<<type;
217 teximgs[type] = new TrueColorImage(width*TileSize, height*TileSize);
219 if ((buildMask&(1<<type)) == 0) continue;
220 auto img = teximgs[type];
221 img.imageData.colors[] = Color(0, 0, 0, 0);
222 if (type == Back) {
224 foreach (int y; 0..(height*TileSize+skytex.height-1)/skytex.height) {
225 foreach (int x; 0..(width*TileSize+skytex.width-1)/skytex.width) {
226 img.putTile(x*skytex.width, y*skytex.height, skytex);
231 foreach (int y; 0..height) {
232 foreach (int x; 0..width) {
233 if (type == LightMask) {
234 // in the lightmask texture, we should have only occluders' pixels
235 auto tt = tiles.ptr[Type].ptr[y*MapSize+x];
236 if ((tt&0x80) == 0 && (tt == TILE_WALL || tt == TILE_DOORC)) {
237 img.putTile(x*TileSize, y*TileSize, textures.ptr[tiles.ptr[Back].ptr[y*MapSize+x]]);
238 img.putTile(x*TileSize, y*TileSize, textures.ptr[tiles.ptr[Front].ptr[y*MapSize+x]]);
239 } /*else if (tt != TILE_LIFTU && tt != TILE_LIFTD) {
240 img.putTile(x*TileSize, y*TileSize, textures.ptr[tiles.ptr[Front].ptr[y*MapSize+x]]);
242 } else if (type == AllLiquids) {
243 // texture with liquid background, for distortion
244 auto tt = tiles.ptr[Front].ptr[y*MapSize+x];
245 if (wallnames[tt] == "_water_0" || wallnames[tt] == "_water_1" || wallnames[tt] == "_water_2") {
246 tt = tiles.ptr[Back].ptr[y*MapSize+x];
247 img.putTile(x*TileSize, y*TileSize, textures.ptr[tt]);
248 foreach (int dy; 0..TileSize) {
249 foreach (int dx; 0..TileSize) {
250 //img.putPixel(x*TileSize+dx, y*TileSize+dy, Color((tt == 3 ? 128 : 0), (tt == 2 ? 128 : 0), (tt == 1 ? 128 : 0), 128));
251 auto c = img.getPixel(x*TileSize+dx, y*TileSize+dy);
252 if (c.a == 0) img.putPixel(x*TileSize+dx, y*TileSize+dy, Color(0, 0, 0, 255));
256 } else if (type == LiquidMask) {
257 // texture with liquid colors, will be blended on top of the level
258 auto tt = tiles.ptr[Front].ptr[y*MapSize+x];
259 auto wclr = Color(0, 0, 0, 0);
260 if (wallnames[tt] == "_water_0") wclr = Color(0, 0, 100, 128); // water
261 else if (wallnames[tt] == "_water_1") wclr = Color(24, 140, 0, 128); // acid
262 else if (wallnames[tt] == "_water_2") wclr = Color(160, 0, 0, 128); // lava
263 if (wclr.a) {
264 img.putPixelTile(x*TileSize, y*TileSize, wclr);
265 // if this is top one, make some light border
266 if (!hasLiquidAt(x, y-1)) {
267 // border
268 auto wcc = wclr.lighten(0.6);
269 wcc.a = wclr.a;
270 foreach (int dx; 0..TileSize) img.putPixel(x*TileSize+dx, y*TileSize+0, wcc);
271 wcc = wclr.lighten(0.4);
272 wcc.a = wclr.a;
273 foreach (int dx; 0..TileSize) img.putPixel(x*TileSize+dx, y*TileSize+1, wcc);
274 wcc = wclr.lighten(0.2);
275 wcc.a = wclr.a;
276 foreach (int dx; 0..TileSize) img.putPixel(x*TileSize+dx, y*TileSize+2, wcc);
279 } else {
280 auto tf = tiles.ptr[Front].ptr[y*MapSize+x];
281 auto tb = tiles.ptr[Back].ptr[y*MapSize+x];
282 auto tt = tiles.ptr[type].ptr[y*MapSize+x];
283 if (wallnames[tf] == "_water_0" || wallnames[tf] == "_water_1" || wallnames[tf] == "_water_2" ||
284 wallnames[tb] == "_water_0" || wallnames[tb] == "_water_1" || wallnames[tb] == "_water_2") {
285 } else {
286 img.putTile(x*TileSize, y*TileSize, textures.ptr[tt]);
292 import std.string : format;
293 import arsd.png : writePng;
294 writePng("zpng%02s.png".format(type), img);
296 if (texgl[type] is null) {
297 texgl[type] = new Texture(img, (type == LightMask ? Texture.Option./*Linear*/Nearest : Texture.Option.Nearest), Texture.Option.Clamp);
298 } else {
299 texgl[type].setFromImage(img, 0, 0);
304 void clear () {
305 tiles[] = null;
306 wallnames = null;
307 walltypes = null;
308 textures = null;
309 foreach (Texture tex; texgl) if (tex !is null) tex.clear;
310 texgl[] = null;
311 skytex = null;
312 things = null;
313 switches = null;
316 void dump (int idx) {
317 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))); }
318 foreach (immutable y; 0..MapSize) {
319 foreach (immutable x; 0..MapSize) {
320 conwrite(to62(tiles[idx][y*MapSize+x]));
322 conwriteln;
326 // true: found
327 bool getThingPos (ushort id, int* x=null, int* y=null, ushort* flags=null) {
328 foreach (ref th; things[]) {
329 if (th.type == id) {
330 if (x !is null) *x = th.x;
331 if (y !is null) *y = th.y;
332 if (flags !is null) *flags = th.flags;
333 return true;
336 if (x !is null) *x = 0;
337 if (y !is null) *y = 0;
338 if (flags !is null) *flags = 0;
339 return false;
342 private:
343 void calcMapSize () {
345 bool isEmpty(string dir) (int x, int y) if (dir == "col" || dir == "row") {
346 while (x < MapSize && y < MapSize) {
347 if (tiles[0][y*MapSize+x] || tiles[1][y*MapSize+x] || tiles[2][y*MapSize+x]) return false;
348 static if (dir == "row") ++x; else ++y;
350 return true;
352 width = height = MapSize;
353 // fix width
354 while (width > 0 && isEmpty!"col"(width-1, 0)) --width;
355 // fix height
356 while (height > 0 && isEmpty!"row"(0, height-1)) --height;
358 width = height = MapSize;
361 void load (string fname) {
362 load(openFile(fname));
365 void load(ST) (auto ref ST st) if (isReadableStream!ST) {
366 clear();
367 scope(failure) clear;
369 char[8] sign;
370 st.rawReadExact(sign[]);
371 if (sign != "Doom2D\x1a\x00") throw new Exception("invalid map signature");
372 if (st.readNum!ushort() != MapVersion) throw new Exception("invalid map version");
374 // load map blocks
375 foreach (ref a; tiles[]) a = new ubyte[](MapSize*MapSize);
376 char[$] skyname = "sprites/sky/rsky1.vga";
377 for (;;) {
378 auto btype = st.readNum!ushort();
379 if (btype == MB_END) break; // no more blocks
380 auto bsubtype = st.readNum!ushort();
381 auto bsize = st.readNum!uint();
382 if (bsize == 0) continue; // skip this block, it has no data (wtf?!)
383 // various tile types
384 switch (btype) {
385 case MB_SKY:
386 if (bsize != 2) throw new Exception("invalid sky data size");
387 ushort num = st.readNum!ushort();
388 if (num >= 1 && num <= 3) skyname[$-5] = cast(char)('0'+num);
389 break;
390 case MB_BACK:
391 case MB_FRONT:
392 case MB_WTYPE:
393 if (bsubtype > 1) throw new Exception("unknown tile block subtype");
394 int idx = (btype == MB_BACK ? Back : (btype == MB_FRONT ? Front : Type));
395 //ubyte[MapSize*MapSize] data = 0;
396 auto data = tiles[idx];
397 if (bsubtype == 0) {
398 if (bsize != data.length) throw new Exception("invalid tile data size");
399 st.rawReadExact(data[]);
400 } else {
401 // unpack RLE data
402 auto pkdata = new ubyte[](bsize);
403 st.rawReadExact(pkdata[]);
404 int spos = 0, opos = 0;
405 while (spos < pkdata.length) {
406 ubyte b = pkdata[spos++];
407 if (b != 255) {
408 data[opos++] = b;
409 } else {
410 int count = pkdata[spos++];
411 count |= pkdata[spos++]<<8;
412 b = pkdata[spos++];
413 while (count-- > 0) data[opos++] = b;
416 assert(opos == data.length);
418 // copy unpacked data
419 //foreach (immutable y; 0..MapSize) tiles[idx][y*MapSize] = data[y*MapSize..(y+1)*MapSize];
420 break;
421 case MB_WALLNAMES:
422 wallnames.length = 0;
423 wallnames ~= null;
424 //wallnames[] = null;
425 //walltypes.length = 0;
426 while (bsize >= 8+1) {
427 char[8] texname = 0;
428 st.rawReadExact(texname[]);
429 auto type = st.readNum!ubyte();
430 //char[] tn;
431 string tns;
432 foreach (char ch; texname) {
433 if (ch == 0) break;
434 if (ch >= 'A' && ch <= 'Z') ch += 32;
435 ch = dos2koi8(ch);
436 tns ~= ch;
437 //tn = texname[0..idx+1];
439 import std.uni : toLower;
440 wallnames ~= koi8lotranslit(tns).toLower;
441 type = (type ? 1 : 0);
442 if (wallnames[$-1] == "vtrap01") type |= 0x02;
443 //wallnames ~= recodeToKOI8(recode(tn.idup, "utf-8", "cp866").toLower, "utf-8");
444 //debug { conwriteln(wallnames.length-1, " : ", wallnames[$-1]); }
445 //if (wallnames[$-1][$-1] == '_') wallnames[$-1] ~= "1";
446 //wallnames[$-1] ~= ".vga";
447 //conwriteln(wallnames[$-1]);
448 walltypes ~= type;
449 bsize -= 8+1;
451 if (bsize != 0) throw new Exception("invalid texture chunk size");
452 debug { conwriteln(wallnames.length, " textures loaded"); }
453 break;
454 case MB_THING:
455 while (bsize >= 8) {
456 bsize -= 8;
457 MapThing t = void;
458 t.x = st.readNum!short();
459 t.y = st.readNum!short();
460 t.type = st.readNum!ushort();
461 t.flags = st.readNum!ushort();
462 if (t.type != 0) things ~= t;
464 if (bsize != 0) throw new Exception("invalid thing chunk size");
465 break;
466 case MB_SWITCH2:
467 while (bsize >= 9) {
468 bsize -= 9;
469 MapSwitch sw = void;
470 sw.x = st.readNum!ubyte();
471 sw.y = st.readNum!ubyte();
472 sw.type = st.readNum!ubyte();
473 sw.tm = st.readNum!ubyte();
474 sw.a = st.readNum!ubyte();
475 sw.b = st.readNum!ubyte();
476 sw.c = st.readNum!ushort();
477 sw.flags = st.readNum!ubyte();
478 switches ~= sw;
480 if (bsize != 0) throw new Exception("invalid thing chunk size");
481 break;
482 default:
483 auto pkdata = new ubyte[](bsize);
484 st.rawReadExact(pkdata[]);
485 break;
488 calcMapSize();
489 // load textures
490 textures.length = 0;
491 foreach (immutable idx, string name; wallnames) {
492 if (name.length == 0 || name[0] == '_') {
493 textures ~= null;
494 continue;
495 } else {
496 textures ~= new D2DImage("tilegfx/"~name~".vga");
499 assert(textures.length == wallnames.length);
500 // fix tiles
501 foreach (immutable y; 0..height) {
502 foreach (immutable x; 0..width) {
503 if (tiles[Front][y*MapSize+x] >= textures.length) tiles[Front][y*MapSize+x] = 0;
504 if (tiles[Back][y*MapSize+x] >= textures.length) tiles[Back][y*MapSize+x] = 0;
507 skytex = new D2DImage(skyname.idup); //"sprites/rsky1.vga");
508 //skytex = new D2DImage("sprites/rsky3.vga");
509 skytex.sx = skytex.sy = 0;
510 debug { conwriteln(width, "x", height); }