texture atlas fix: it seems to work now
[dd2d.git] / d2dmap.d
blob7dd26a34bc3383e703ed6ed27c09b43d6de2b702
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;
34 // ////////////////////////////////////////////////////////////////////////// //
35 Color getPixel (TrueColorImage img, int x, int y) {
36 if (x >= 0 && y >= 0 && x < img.width && y < img.height) {
37 return img.imageData.colors.ptr[y*img.width+x];
38 } else {
39 return Color(0, 0, 0, 0);
44 void putPixel (TrueColorImage img, int x, int y, Color clr) {
45 if (x >= 0 && y >= 0 && x < img.width && y < img.height && clr.a != 0) {
46 img.imageData.colors.ptr[y*img.width+x] = clr;
51 void fixPixelA (TrueColorImage img, int x, int y, ubyte a) {
52 if (x >= 0 && y >= 0 && x < img.width && y < img.height) {
53 img.imageData.colors.ptr[y*img.width+x].a = a;
58 void fixPixelTileA (TrueColorImage img, int x, int y, ubyte a) {
59 foreach (int dy; 0..8) {
60 foreach (int dx; 0..8) {
61 fixPixelA(img, x+dx, y+dy, a);
67 void putPixelTile (TrueColorImage img, int x, int y, Color clr) {
68 foreach (int dy; 0..8) {
69 foreach (int dx; 0..8) {
70 putPixel(img, x+dx, y+dy, clr);
76 void putTile (TrueColorImage img, int x, int y, D2DImage wt) {
77 if (wt is null) return;
78 x -= wt.sx;
79 y -= wt.sy;
80 auto s = wt.data.ptr;
81 foreach (int dy; 0..wt.height) {
82 foreach (int dx; 0..wt.width) {
83 img.putPixel(x+dx, y, d2dpal[*s++]);
85 ++y;
90 // temp
91 static ubyte clampByte (ubyte b, int delta) {
92 delta += b;
93 return cast(ubyte)(delta < 0 ? 0 : delta > 255 ? 255 : delta);
97 // ////////////////////////////////////////////////////////////////////////// //
98 public final class LevelMap {
99 private:
100 private import std.stdio : File;
102 enum MapVersion = 2; // óÁÍÁÑ ÐÏÓÌÅÄÎÑÑ ×ÅÒÓÉÑ ËÁÒÔÙ
103 public enum MapSize = 100;
105 public:
106 // tile type
107 enum : ubyte {
108 TILE_EMPTY = 0,
109 TILE_WALL = 1,
110 TILE_DOORC = 2, // closed door
111 TILE_DOORO = 3, // opened door
112 TILE_STEP = 4,
113 TILE_WATER = 5,
114 TILE_ACID1 = 6,
115 TILE_ACID2 = 7,
116 TILE_MBLOCK = 8, // just blocks monsters
117 TILE_LIFTU = 9,
118 TILE_LIFTD = 10,
120 TILE_ACTTRAP = 255,
123 enum : short {
124 MB_COMMENT = -1,
125 MB_END = 0,
126 MB_WALLNAMES,
127 MB_BACK,
128 MB_WTYPE,
129 MB_FRONT,
130 MB_THING,
131 MB_SWITCH,
132 MB_MUSIC, // usually 8 bytes
133 MB_SKY, // ushort: [1..3]
134 MB_SWITCH2,
137 enum {
138 SW_PL_PRESS = 1<<0,
139 SW_MN_PRESS = 1<<1,
140 SW_PL_NEAR = 1<<2,
141 SW_MN_NEAR = 1<<3,
142 SW_KEY_R = 1<<4,
143 SW_KEY_G = 1<<5,
144 SW_KEY_B = 1<<6,
147 static struct MapThing {
148 // thing flags
149 enum : ushort {
150 DirRight = 0x0001,
151 DeathMatch = 0x0010, // ÐÏÑ×ÌÑÅÔÓÑ ÔÏÌØËÏ × DeathMatch'Å
154 short x, y; // ËÏÏÒÄÉÎÁÔÙ
155 ushort type; // ÔÉÐ
156 ushort flags; // ÆÌÁÇÉ
158 @property const pure nothrow @safe @nogc {
159 bool left () => ((flags&DirRight) == 0);
160 bool right () => ((flags&DirRight) != 0);
161 bool dmonly () => ((flags&DeathMatch) != 0);
165 static struct MapSwitch {
166 ubyte x, y; // ËÏÏÒÄÉÎÁÔÙ/8
167 ubyte type; // ÔÉÐ
168 ubyte tm; // ÄÏÌÖÎÏ ÂÙÔØ 0
169 ubyte a, b; // ÏÂÙÞÎÏ - ËÏÏÒÄÉÎÁÔÙ/8 Ä×ÅÒÉ
170 ushort c; // ÎÅ ÉÓÐÏÌØÚÕÅÔÓÑ (×ÒÏÄÅ ÂÙ)
171 ubyte flags; // ÆÌÁÇÉ (SW_*)
174 public:
175 enum { Type, Front, Back, AllLiquids, LiquidMask, LightMask, MapTexturesMax } // tile types, Front+: megatexture types
176 ubyte[][3] tiles; // Type, Front, Back
177 string[] wallnames; // "_water_0": water, "_water_1": acid, "_water_2": lava
178 ubyte[] walltypes; // bit0: solid/non-solid(0x01); bit1: 0x02 if "vtrap01"
179 D2DImage[] textures;
180 int width, height;
181 Texture[MapTexturesMax] texgl;
182 TrueColorImage[MapTexturesMax] teximgs;
183 D2DImage skytex;
184 Texture skytexgl;
186 MapThing[] things;
187 MapSwitch[] switches;
189 this (string fname) { load(fname); }
190 this (File fl) { load(fl); }
192 bool hasLiquidAt (int x, int y) {
193 if (x < 0 || y < 0 || x >= MapSize || y >= MapSize) return false;
194 auto tt = tiles.ptr[Front].ptr[y*MapSize+x];
195 return (wallnames[tt] == "_water_0" || wallnames[tt] == "_water_1" || wallnames[tt] == "_water_2");
198 //FIXME: recreate textures cleanly
199 void clearMegaTextures () {
200 //foreach (Texture tex; texgl) if (tex !is null) tex.clear;
201 //texgl[] = null;
204 // build OpenGL "megatextures" with the whole level on it
205 void oglBuildMega (uint buildMask=0xffff_ffffu) {
206 if (skytexgl is null) skytexgl = new Texture(skytex.asTCImage, Texture.Option.Linear, Texture.Option.Repeat);
207 //auto img = new TrueColorImage(width*8, height*8);
208 foreach (immutable type; Front..LightMask+1) {
209 if (teximgs[type] is null) {
210 buildMask |= 1<<type;
211 teximgs[type] = new TrueColorImage(width*8, height*8);
213 if ((buildMask&(1<<type)) == 0) continue;
214 auto img = teximgs[type];
215 img.imageData.colors[] = Color(0, 0, 0, 0);
216 if (type == Back) {
218 foreach (int y; 0..(height*8+skytex.height-1)/skytex.height) {
219 foreach (int x; 0..(width*8+skytex.width-1)/skytex.width) {
220 img.putTile(x*skytex.width, y*skytex.height, skytex);
225 foreach (int y; 0..height) {
226 foreach (int x; 0..width) {
227 if (type == LightMask) {
228 // in the lightmask texture, we should have only occluders' pixels
229 auto tt = tiles.ptr[Type].ptr[y*MapSize+x];
230 if (tt == TILE_WALL || tt == TILE_DOORC) {
231 img.putTile(x*8, y*8, textures.ptr[tiles.ptr[Back].ptr[y*MapSize+x]]);
232 img.putTile(x*8, y*8, textures.ptr[tiles.ptr[Front].ptr[y*MapSize+x]]);
233 } /*else if (tt != TILE_LIFTU && tt != TILE_LIFTD) {
234 img.putTile(x*8, y*8, textures.ptr[tiles.ptr[Front].ptr[y*MapSize+x]]);
236 } else if (type == AllLiquids) {
237 // texture with liquid background, for distortion
238 auto tt = tiles.ptr[Front].ptr[y*MapSize+x];
239 if (wallnames[tt] == "_water_0" || wallnames[tt] == "_water_1" || wallnames[tt] == "_water_2") {
240 tt = tiles.ptr[Back].ptr[y*MapSize+x];
241 img.putTile(x*8, y*8, textures.ptr[tt]);
242 foreach (int dy; 0..8) {
243 foreach (int dx; 0..8) {
244 //img.putPixel(x*8+dx, y*8+dy, Color((tt == 3 ? 128 : 0), (tt == 2 ? 128 : 0), (tt == 1 ? 128 : 0), 128));
245 auto c = img.getPixel(x*8+dx, y*8+dy);
246 if (c.a == 0) img.putPixel(x*8+dx, y*8+dy, Color(0, 0, 0, 255));
250 } else if (type == LiquidMask) {
251 // texture with liquid colors, will be blended on top of the level
252 auto tt = tiles.ptr[Front].ptr[y*MapSize+x];
253 auto wclr = Color(0, 0, 0, 0);
254 if (wallnames[tt] == "_water_0") wclr = Color(0, 0, 100, 128); // water
255 else if (wallnames[tt] == "_water_1") wclr = Color(24, 140, 0, 128); // acid
256 else if (wallnames[tt] == "_water_2") wclr = Color(160, 0, 0, 128); // lava
257 if (wclr.a) {
258 img.putPixelTile(x*8, y*8, wclr);
259 // if this is top one, make some light border
260 if (!hasLiquidAt(x, y-1)) {
261 // border
262 auto wcc = wclr.lighten(0.6);
263 wcc.a = wclr.a;
264 foreach (int dx; 0..8) img.putPixel(x*8+dx, y*8+0, wcc);
265 wcc = wclr.lighten(0.4);
266 wcc.a = wclr.a;
267 foreach (int dx; 0..8) img.putPixel(x*8+dx, y*8+1, wcc);
268 wcc = wclr.lighten(0.2);
269 wcc.a = wclr.a;
270 foreach (int dx; 0..8) img.putPixel(x*8+dx, y*8+2, wcc);
273 } else {
274 auto tf = tiles.ptr[Front].ptr[y*MapSize+x];
275 auto tb = tiles.ptr[Back].ptr[y*MapSize+x];
276 auto tt = tiles.ptr[type].ptr[y*MapSize+x];
277 if (wallnames[tf] == "_water_0" || wallnames[tf] == "_water_1" || wallnames[tf] == "_water_2" ||
278 wallnames[tb] == "_water_0" || wallnames[tb] == "_water_1" || wallnames[tb] == "_water_2") {
279 } else {
280 img.putTile(x*8, y*8, textures.ptr[tt]);
286 import std.string : format;
287 import arsd.png : writePng;
288 writePng("zpng%02s.png".format(type), img);
290 if (texgl[type] is null) {
291 texgl[type] = new Texture(img, Texture.Option.Nearest, Texture.Option.Clamp);
292 } else {
293 texgl[type].setFromImage(img, 0, 0);
298 void clear () {
299 tiles[] = null;
300 wallnames = null;
301 walltypes = null;
302 textures = null;
303 foreach (Texture tex; texgl) if (tex !is null) tex.clear;
304 texgl[] = null;
305 skytex = null;
306 things = null;
307 switches = null;
310 void dump (int idx) {
311 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))); }
312 foreach (immutable y; 0..MapSize) {
313 foreach (immutable x; 0..MapSize) {
314 conwrite(to62(tiles[idx][y*MapSize+x]));
316 conwriteln;
320 // true: found
321 bool getThingPos (ushort id, int* x=null, int* y=null, ushort* flags=null) {
322 foreach (ref th; things[]) {
323 if (th.type == id) {
324 if (x !is null) *x = th.x;
325 if (y !is null) *y = th.y;
326 if (flags !is null) *flags = th.flags;
327 return true;
330 if (x !is null) *x = 0;
331 if (y !is null) *y = 0;
332 if (flags !is null) *flags = 0;
333 return false;
336 private:
337 void calcMapSize () {
338 bool isEmpty(string dir) (int x, int y) if (dir == "col" || dir == "row") {
339 while (x < MapSize && y < MapSize) {
340 if (tiles[0][y*MapSize+x] || tiles[1][y*MapSize+x] || tiles[2][y*MapSize+x]) return false;
341 static if (dir == "row") ++x; else ++y;
343 return true;
345 width = height = MapSize;
346 // fix width
347 while (width > 0 && isEmpty!"col"(width-1, 0)) --width;
348 // fix height
349 while (height > 0 && isEmpty!"row"(0, height-1)) --height;
352 void load (string fname) {
353 import std.stdio : File;
354 load(openFile(fname));
357 void load(ST) (auto ref ST st) if (isReadableStream!ST) {
358 clear();
359 scope(failure) clear;
361 char[8] sign;
362 st.rawReadExact(sign[]);
363 if (sign != "Doom2D\x1a\x00") throw new Exception("invalid map signature");
364 if (st.readNum!ushort() != MapVersion) throw new Exception("invalid map version");
366 // load map blocks
367 foreach (ref a; tiles[]) a = new ubyte[](MapSize*MapSize);
368 char[$] skyname = "sprites/sky/rsky1.vga";
369 for (;;) {
370 auto btype = st.readNum!ushort();
371 if (btype == MB_END) break; // no more blocks
372 auto bsubtype = st.readNum!ushort();
373 auto bsize = st.readNum!uint();
374 if (bsize == 0) continue; // skip this block, it has no data (wtf?!)
375 // various tile types
376 switch (btype) {
377 case MB_SKY:
378 if (bsize != 2) throw new Exception("invalid sky data size");
379 ushort num = st.readNum!ushort();
380 if (num >= 1 && num <= 3) skyname[$-5] = cast(char)('0'+num);
381 break;
382 case MB_BACK:
383 case MB_FRONT:
384 case MB_WTYPE:
385 if (bsubtype > 1) throw new Exception("unknown tile block subtype");
386 int idx = (btype == MB_BACK ? Back : (btype == MB_FRONT ? Front : Type));
387 //ubyte[MapSize*MapSize] data = 0;
388 auto data = tiles[idx];
389 if (bsubtype == 0) {
390 if (bsize != data.length) throw new Exception("invalid tile data size");
391 st.rawReadExact(data[]);
392 } else {
393 // unpack RLE data
394 auto pkdata = new ubyte[](bsize);
395 st.rawReadExact(pkdata[]);
396 int spos = 0, opos = 0;
397 while (spos < pkdata.length) {
398 ubyte b = pkdata[spos++];
399 if (b != 255) {
400 data[opos++] = b;
401 } else {
402 int count = pkdata[spos++];
403 count |= pkdata[spos++]<<8;
404 b = pkdata[spos++];
405 while (count-- > 0) data[opos++] = b;
408 assert(opos == data.length);
410 // copy unpacked data
411 //foreach (immutable y; 0..MapSize) tiles[idx][y*MapSize] = data[y*MapSize..(y+1)*MapSize];
412 break;
413 case MB_WALLNAMES:
414 wallnames.length = 0;
415 wallnames ~= null;
416 //wallnames[] = null;
417 //walltypes.length = 0;
418 while (bsize >= 8+1) {
419 char[8] texname = 0;
420 st.rawReadExact(texname[]);
421 auto type = st.readNum!ubyte();
422 //char[] tn;
423 string tns;
424 foreach (char ch; texname) {
425 if (ch == 0) break;
426 if (ch >= 'A' && ch <= 'Z') ch += 32;
427 ch = dos2koi8(ch);
428 tns ~= ch;
429 //tn = texname[0..idx+1];
431 import std.uni : toLower;
432 wallnames ~= koi8lotranslit(tns).toLower;
433 type = (type ? 1 : 0);
434 if (wallnames[$-1] == "vtrap01") type |= 0x02;
435 //wallnames ~= recodeToKOI8(recode(tn.idup, "utf-8", "cp866").toLower, "utf-8");
436 //debug { conwriteln(wallnames.length-1, " : ", wallnames[$-1]); }
437 //if (wallnames[$-1][$-1] == '_') wallnames[$-1] ~= "1";
438 //wallnames[$-1] ~= ".vga";
439 //conwriteln(wallnames[$-1]);
440 walltypes ~= type;
441 bsize -= 8+1;
443 if (bsize != 0) throw new Exception("invalid texture chunk size");
444 debug { conwriteln(wallnames.length, " textures loaded"); }
445 break;
446 case MB_THING:
447 while (bsize >= 8) {
448 bsize -= 8;
449 MapThing t = void;
450 t.x = st.readNum!short();
451 t.y = st.readNum!short();
452 t.type = st.readNum!ushort();
453 t.flags = st.readNum!ushort();
454 if (t.type != 0) things ~= t;
456 if (bsize != 0) throw new Exception("invalid thing chunk size");
457 break;
458 case MB_SWITCH2:
459 while (bsize >= 9) {
460 bsize -= 9;
461 MapSwitch sw = void;
462 sw.x = st.readNum!ubyte();
463 sw.y = st.readNum!ubyte();
464 sw.type = st.readNum!ubyte();
465 sw.tm = st.readNum!ubyte();
466 sw.a = st.readNum!ubyte();
467 sw.b = st.readNum!ubyte();
468 sw.c = st.readNum!ushort();
469 sw.flags = st.readNum!ubyte();
470 switches ~= sw;
472 if (bsize != 0) throw new Exception("invalid thing chunk size");
473 break;
474 default:
475 auto pkdata = new ubyte[](bsize);
476 st.rawReadExact(pkdata[]);
477 break;
480 calcMapSize();
481 // load textures
482 textures.length = 0;
483 foreach (immutable idx, string name; wallnames) {
484 if (name.length == 0 || name[0] == '_') {
485 textures ~= null;
486 continue;
487 } else {
488 textures ~= new D2DImage("tilegfx/"~name~".vga");
491 assert(textures.length == wallnames.length);
492 // fix tiles
493 foreach (immutable y; 0..height) {
494 foreach (immutable x; 0..width) {
495 if (tiles[Front][y*MapSize+x] >= textures.length) tiles[Front][y*MapSize+x] = 0;
496 if (tiles[Back][y*MapSize+x] >= textures.length) tiles[Back][y*MapSize+x] = 0;
499 skytex = new D2DImage(skyname.idup); //"sprites/rsky1.vga");
500 //skytex = new D2DImage("sprites/rsky3.vga");
501 skytex.sx = skytex.sy = 0;
502 debug { conwriteln(width, "x", height); }