some mapmod code
[dd2d.git] / d2dmap.d
blobc6be786378c310345a2b8e61ce6efdce5d572486
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; // 0: solid; 1: non-solid
179 D2DImage[] textures;
180 int width, height;
181 Texture[MapTexturesMax] texgl;
182 D2DImage skytex;
183 Texture skytexgl;
185 MapThing[] things;
186 MapSwitch[] switches;
188 this (string fname) { load(fname); }
189 this (File fl) { load(fl); }
191 bool hasLiquidAt (int x, int y) {
192 if (x < 0 || y < 0 || x >= MapSize || y >= MapSize) return false;
193 auto tt = tiles.ptr[Front].ptr[y*MapSize+x];
194 return (wallnames[tt] == "_water_0" || wallnames[tt] == "_water_1" || wallnames[tt] == "_water_2");
197 //FIXME: recreate textures cleanly
198 void clearMegaTextures () {
199 foreach (Texture tex; texgl) if (tex !is null) tex.clear;
200 texgl[] = null;
203 // build OpenGL "megatextures" with the whole level on it
204 void oglBuildMega () {
205 if (skytexgl is null) skytexgl = new Texture(skytex.asTCImage, Texture.Option.Linear, Texture.Option.Repeat);
206 auto img = new TrueColorImage(width*8, height*8);
207 foreach (immutable type; Front..LightMask+1) {
208 img.imageData.colors[] = Color(0, 0, 0, 0);
209 if (type == Back) {
211 foreach (int y; 0..(height*8+skytex.height-1)/skytex.height) {
212 foreach (int x; 0..(width*8+skytex.width-1)/skytex.width) {
213 img.putTile(x*skytex.width, y*skytex.height, skytex);
218 foreach (int y; 0..height) {
219 foreach (int x; 0..width) {
220 if (type == LightMask) {
221 // in the lightmask texture, we should have only occluders' pixels
222 auto tt = tiles.ptr[Type].ptr[y*MapSize+x];
223 if (tt == TILE_WALL || tt == TILE_DOORC) {
224 img.putTile(x*8, y*8, textures.ptr[tiles.ptr[Back].ptr[y*MapSize+x]]);
225 img.putTile(x*8, y*8, textures.ptr[tiles.ptr[Front].ptr[y*MapSize+x]]);
226 } /*else if (tt != TILE_LIFTU && tt != TILE_LIFTD) {
227 img.putTile(x*8, y*8, textures.ptr[tiles.ptr[Front].ptr[y*MapSize+x]]);
229 } else if (type == AllLiquids) {
230 // texture with liquid background, for distortion
231 auto tt = tiles.ptr[Front].ptr[y*MapSize+x];
232 if (wallnames[tt] == "_water_0" || wallnames[tt] == "_water_1" || wallnames[tt] == "_water_2") {
233 tt = tiles.ptr[Back].ptr[y*MapSize+x];
234 img.putTile(x*8, y*8, textures.ptr[tt]);
235 foreach (int dy; 0..8) {
236 foreach (int dx; 0..8) {
237 //img.putPixel(x*8+dx, y*8+dy, Color((tt == 3 ? 128 : 0), (tt == 2 ? 128 : 0), (tt == 1 ? 128 : 0), 128));
238 auto c = img.getPixel(x*8+dx, y*8+dy);
239 if (c.a == 0) img.putPixel(x*8+dx, y*8+dy, Color(0, 0, 0, 255));
243 } else if (type == LiquidMask) {
244 // texture with liquid colors, will be blended on top of the level
245 auto tt = tiles.ptr[Front].ptr[y*MapSize+x];
246 auto wclr = Color(0, 0, 0, 0);
247 if (wallnames[tt] == "_water_0") wclr = Color(0, 0, 100, 128); // water
248 else if (wallnames[tt] == "_water_1") wclr = Color(24, 140, 0, 128); // acid
249 else if (wallnames[tt] == "_water_2") wclr = Color(160, 0, 0, 128); // lava
250 if (wclr.a) {
251 img.putPixelTile(x*8, y*8, wclr);
252 // if this is top one, make some light border
253 if (!hasLiquidAt(x, y-1)) {
254 // border
255 auto wcc = wclr.lighten(0.6);
256 wcc.a = wclr.a;
257 foreach (int dx; 0..8) img.putPixel(x*8+dx, y*8+0, wcc);
258 wcc = wclr.lighten(0.4);
259 wcc.a = wclr.a;
260 foreach (int dx; 0..8) img.putPixel(x*8+dx, y*8+1, wcc);
261 wcc = wclr.lighten(0.2);
262 wcc.a = wclr.a;
263 foreach (int dx; 0..8) img.putPixel(x*8+dx, y*8+2, wcc);
266 } else {
267 auto tf = tiles.ptr[Front].ptr[y*MapSize+x];
268 auto tb = tiles.ptr[Back].ptr[y*MapSize+x];
269 auto tt = tiles.ptr[type].ptr[y*MapSize+x];
270 if (wallnames[tf] == "_water_0" || wallnames[tf] == "_water_1" || wallnames[tf] == "_water_2" ||
271 wallnames[tb] == "_water_0" || wallnames[tb] == "_water_1" || wallnames[tb] == "_water_2") {
272 } else {
273 img.putTile(x*8, y*8, textures.ptr[tt]);
279 import std.string : format;
280 import arsd.png : writePng;
281 writePng("zpng%02s.png".format(type), img);
283 texgl[type] = new Texture(img, Texture.Option.Nearest, Texture.Option.Clamp);
287 void clear () {
288 tiles[] = null;
289 wallnames = null;
290 walltypes = null;
291 textures = null;
292 foreach (Texture tex; texgl) if (tex !is null) tex.clear;
293 texgl[] = null;
294 skytex = null;
295 things = null;
296 switches = null;
299 void dump (int idx) {
300 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))); }
301 foreach (immutable y; 0..MapSize) {
302 foreach (immutable x; 0..MapSize) {
303 conwrite(to62(tiles[idx][y*MapSize+x]));
305 conwriteln;
309 // true: found
310 bool getThingPos (ushort id, int* x=null, int* y=null, ushort* flags=null) {
311 foreach (ref th; things[]) {
312 if (th.type == id) {
313 if (x !is null) *x = th.x;
314 if (y !is null) *y = th.y;
315 if (flags !is null) *flags = th.flags;
316 return true;
319 if (x !is null) *x = 0;
320 if (y !is null) *y = 0;
321 if (flags !is null) *flags = 0;
322 return false;
325 private:
326 void calcMapSize () {
327 bool isEmpty(string dir) (int x, int y) if (dir == "col" || dir == "row") {
328 while (x < MapSize && y < MapSize) {
329 if (tiles[0][y*MapSize+x] || tiles[1][y*MapSize+x] || tiles[2][y*MapSize+x]) return false;
330 static if (dir == "row") ++x; else ++y;
332 return true;
334 width = height = MapSize;
335 // fix width
336 while (width > 0 && isEmpty!"col"(width-1, 0)) --width;
337 // fix height
338 while (height > 0 && isEmpty!"row"(0, height-1)) --height;
341 void load (string fname) {
342 import std.stdio : File;
343 load(openFile(fname));
346 void load(ST) (auto ref ST st) if (isReadableStream!ST) {
347 clear();
348 scope(failure) clear;
350 char[8] sign;
351 st.rawReadExact(sign[]);
352 if (sign != "Doom2D\x1a\x00") throw new Exception("invalid map signature");
353 if (st.readNum!ushort() != MapVersion) throw new Exception("invalid map version");
355 // load map blocks
356 foreach (ref a; tiles[]) a = new ubyte[](MapSize*MapSize);
357 char[$] skyname = "sprites/sky/rsky1.vga";
358 for (;;) {
359 auto btype = st.readNum!ushort();
360 if (btype == MB_END) break; // no more blocks
361 auto bsubtype = st.readNum!ushort();
362 auto bsize = st.readNum!uint();
363 if (bsize == 0) continue; // skip this block, it has no data (wtf?!)
364 // various tile types
365 switch (btype) {
366 case MB_SKY:
367 if (bsize != 2) throw new Exception("invalid sky data size");
368 ushort num = st.readNum!ushort();
369 if (num >= 1 && num <= 3) skyname[$-5] = cast(char)('0'+num);
370 break;
371 case MB_BACK:
372 case MB_FRONT:
373 case MB_WTYPE:
374 if (bsubtype > 1) throw new Exception("unknown tile block subtype");
375 int idx = (btype == MB_BACK ? Back : (btype == MB_FRONT ? Front : Type));
376 //ubyte[MapSize*MapSize] data = 0;
377 auto data = tiles[idx];
378 if (bsubtype == 0) {
379 if (bsize != data.length) throw new Exception("invalid tile data size");
380 st.rawReadExact(data[]);
381 } else {
382 // unpack RLE data
383 auto pkdata = new ubyte[](bsize);
384 st.rawReadExact(pkdata[]);
385 int spos = 0, opos = 0;
386 while (spos < pkdata.length) {
387 ubyte b = pkdata[spos++];
388 if (b != 255) {
389 data[opos++] = b;
390 } else {
391 int count = pkdata[spos++];
392 count |= pkdata[spos++]<<8;
393 b = pkdata[spos++];
394 while (count-- > 0) data[opos++] = b;
397 assert(opos == data.length);
399 // copy unpacked data
400 //foreach (immutable y; 0..MapSize) tiles[idx][y*MapSize] = data[y*MapSize..(y+1)*MapSize];
401 break;
402 case MB_WALLNAMES:
403 wallnames.length = 0;
404 wallnames ~= null;
405 //wallnames[] = null;
406 //walltypes.length = 0;
407 while (bsize >= 8+1) {
408 char[8] texname = 0;
409 st.rawReadExact(texname[]);
410 auto type = st.readNum!ubyte();
411 //char[] tn;
412 string tns;
413 foreach (char ch; texname) {
414 if (ch == 0) break;
415 if (ch >= 'A' && ch <= 'Z') ch += 32;
416 ch = dos2koi8(ch);
417 tns ~= ch;
418 //tn = texname[0..idx+1];
420 import std.uni : toLower;
421 wallnames ~= koi8lotranslit(tns).toLower;
422 //wallnames ~= recodeToKOI8(recode(tn.idup, "utf-8", "cp866").toLower, "utf-8");
423 //debug { conwriteln(wallnames.length-1, " : ", wallnames[$-1]); }
424 //if (wallnames[$-1][$-1] == '_') wallnames[$-1] ~= "1";
425 //wallnames[$-1] ~= ".vga";
426 //conwriteln(wallnames[$-1]);
427 //walltypes ~= type;
428 bsize -= 8+1;
430 if (bsize != 0) throw new Exception("invalid texture chunk size");
431 debug { conwriteln(wallnames.length, " textures loaded"); }
432 break;
433 case MB_THING:
434 while (bsize >= 8) {
435 bsize -= 8;
436 MapThing t = void;
437 t.x = st.readNum!short();
438 t.y = st.readNum!short();
439 t.type = st.readNum!ushort();
440 t.flags = st.readNum!ushort();
441 if (t.type != 0) things ~= t;
443 if (bsize != 0) throw new Exception("invalid thing chunk size");
444 break;
445 case MB_SWITCH2:
446 while (bsize >= 9) {
447 bsize -= 9;
448 MapSwitch sw = void;
449 sw.x = st.readNum!ubyte();
450 sw.y = st.readNum!ubyte();
451 sw.type = st.readNum!ubyte();
452 sw.tm = st.readNum!ubyte();
453 sw.a = st.readNum!ubyte();
454 sw.b = st.readNum!ubyte();
455 sw.c = st.readNum!ushort();
456 sw.flags = st.readNum!ubyte();
457 switches ~= sw;
459 if (bsize != 0) throw new Exception("invalid thing chunk size");
460 break;
461 default:
462 auto pkdata = new ubyte[](bsize);
463 st.rawReadExact(pkdata[]);
464 break;
467 calcMapSize();
468 // load textures
469 textures.length = 0;
470 foreach (immutable idx, string name; wallnames) {
471 if (name.length == 0 || name[0] == '_') {
472 textures ~= null;
473 continue;
474 } else {
475 textures ~= new D2DImage("tilegfx/"~name~".vga");
478 assert(textures.length == wallnames.length);
479 // fix tiles
480 foreach (immutable y; 0..height) {
481 foreach (immutable x; 0..width) {
482 if (tiles[Front][y*MapSize+x] >= textures.length) tiles[Front][y*MapSize+x] = 0;
483 if (tiles[Back][y*MapSize+x] >= textures.length) tiles[Back][y*MapSize+x] = 0;
486 skytex = new D2DImage(skyname.idup); //"sprites/rsky1.vga");
487 //skytex = new D2DImage("sprites/rsky3.vga");
488 skytex.sx = skytex.sy = 0;
489 debug { conwriteln(width, "x", height); }