light shader now takes into account map size in pixels
[dd2d.git] / d2dmap.d
blob5c0e1d357acdb64cd4ab2857568036fe957f20ce
1 module d2dmap is aliced;
2 private:
4 import arsd.color;
5 import iv.stream;
7 import glutils;
8 import console;
9 import wadarc;
11 import d2dgfx;
12 //import d2dtpl;
14 import iv.encoding;
17 // ////////////////////////////////////////////////////////////////////////// //
18 Color getPixel (TrueColorImage img, int x, int y) {
19 if (x >= 0 && y >= 0 && x < img.width && y < img.height) {
20 return img.imageData.colors.ptr[y*img.width+x];
21 } else {
22 return Color(0, 0, 0, 0);
27 void putPixel (TrueColorImage img, int x, int y, Color clr) {
28 if (x >= 0 && y >= 0 && x < img.width && y < img.height && clr.a != 0) {
29 img.imageData.colors.ptr[y*img.width+x] = clr;
34 void putTile (TrueColorImage img, int x, int y, D2DImage wt) {
35 if (wt is null) return;
36 x -= wt.sx;
37 y -= wt.sy;
38 auto s = wt.data.ptr;
39 foreach (int dy; 0..wt.height) {
40 foreach (int dx; 0..wt.width) {
41 img.putPixel(x+dx, y, d2dpal[*s++]);
43 ++y;
48 // ////////////////////////////////////////////////////////////////////////// //
49 public final class LevelMap {
50 private:
51 private import std.stdio : File;
53 enum MapVersion = 2; // óÁÍÁÑ ÐÏÓÌÅÄÎÑÑ ×ÅÒÓÉÑ ËÁÒÔÙ
54 public enum MapSize = 100;
56 public:
57 // tile type
58 enum : ubyte {
59 TILE_EMPTY = 0,
60 TILE_WALL = 1,
61 TILE_DOORC = 2, // closed door
62 TILE_DOORO = 3, // opened door
63 TILE_STEP = 4,
64 TILE_WATER = 5,
65 TILE_ACID1 = 6,
66 TILE_ACID2 = 7,
67 TILE_MBLOCK = 8, // just blocks monsters
68 TILE_LIFTU = 9,
69 TILE_LIFTD = 10,
71 TILE_ACTTRAP = 255,
74 enum : short {
75 MB_COMMENT = -1,
76 MB_END = 0,
77 MB_WALLNAMES,
78 MB_BACK,
79 MB_WTYPE,
80 MB_FRONT,
81 MB_THING,
82 MB_SWITCH,
83 MB_MUSIC, // usually 8 bytes
84 MB_SKY, // ushort: [1..3]
85 MB_SWITCH2,
88 enum {
89 SW_PL_PRESS = 1<<0,
90 SW_MN_PRESS = 1<<1,
91 SW_PL_NEAR = 1<<2,
92 SW_MN_NEAR = 1<<3,
93 SW_KEY_R = 1<<4,
94 SW_KEY_G = 1<<5,
95 SW_KEY_B = 1<<6,
98 static struct MapThing {
99 // thing flags
100 enum : ushort {
101 DirRight = 0x0001,
102 DeathMatch = 0x0010, // ÐÏÑ×ÌÑÅÔÓÑ ÔÏÌØËÏ × DeathMatch'Å
105 short x, y; // ËÏÏÒÄÉÎÁÔÙ
106 ushort type; // ÔÉÐ
107 ushort flags; // ÆÌÁÇÉ
109 @property const pure nothrow @safe @nogc {
110 bool left () => ((flags&DirRight) == 0);
111 bool right () => ((flags&DirRight) != 0);
112 bool dmonly () => ((flags&DeathMatch) != 0);
116 static struct MapSwitch {
117 ubyte x, y; // ËÏÏÒÄÉÎÁÔÙ/8
118 ubyte type; // ÔÉÐ
119 ubyte tm; // ÄÏÌÖÎÏ ÂÙÔØ 0
120 ubyte a, b; // ÏÂÙÞÎÏ - ËÏÏÒÄÉÎÁÔÙ/8 Ä×ÅÒÉ
121 ushort c; // ÎÅ ÉÓÐÏÌØÚÕÅÔÓÑ (×ÒÏÄÅ ÂÙ)
122 ubyte flags; // ÆÌÁÇÉ (SW_*)
125 public:
126 enum { Type, Front, Back, Water, Lava, Acid, LightMask } // tile types, Front+: megatexture types
127 ubyte[][3] tiles; // Type, Front, Back
128 string[] wallnames; // "_water_0": water, "_water_1": acid, "_water_2": lava
129 ubyte[] walltypes; // 0: solid; 1: non-solid
130 D2DImage[] textures;
131 int width, height;
132 Texture[7] texgl;
133 D2DImage skytex;
134 Texture skytexgl;
136 MapThing[] things;
137 MapSwitch[] switches;
139 this (string fname) { load(fname); }
140 this (File fl) { load(fl); }
142 // build OpenGL "megatextures" with the whole level on it
143 void oglBuildMega () {
144 skytexgl = new Texture(skytex.asTCImage, Texture.Option.Linear, Texture.Option.Repeat);
145 auto img = new TrueColorImage(width*8, height*8);
146 foreach (immutable type; Front..LightMask+1) {
147 img.imageData.colors[] = Color(0, 0, 0, 0);
148 if (type == Back) {
150 foreach (int y; 0..(height*8+skytex.height-1)/skytex.height) {
151 foreach (int x; 0..(width*8+skytex.width-1)/skytex.width) {
152 img.putTile(x*skytex.width, y*skytex.height, skytex);
157 foreach (int y; 0..height) {
158 foreach (int x; 0..width) {
159 if (type == LightMask) {
160 // in the lightmask texture, we should have only occluders' pixels
161 auto tt = tiles.ptr[Type].ptr[y*MapSize+x];
162 if (tt == TILE_WALL || tt == TILE_DOORC) {
163 img.putTile(x*8, y*8, textures.ptr[tiles.ptr[Back].ptr[y*MapSize+x]]);
164 img.putTile(x*8, y*8, textures.ptr[tiles.ptr[Front].ptr[y*MapSize+x]]);
165 } /*else if (tt != TILE_LIFTU && tt != TILE_LIFTD) {
166 img.putTile(x*8, y*8, textures.ptr[tiles.ptr[Front].ptr[y*MapSize+x]]);
168 } else if (type >= Water) {
169 auto tt = tiles.ptr[Front].ptr[y*MapSize+x];
170 if (type == Water) tt = (wallnames[tt] == "_water_0" ? 1 : 0);
171 else if (type == Acid) tt = (wallnames[tt] == "_water_1" ? 2 : 0);
172 else if (type == Lava) tt = (wallnames[tt] == "_water_2" ? 3 : 0);
173 if (tt) {
174 tt = tiles.ptr[Back].ptr[y*MapSize+x];
175 img.putTile(x*8, y*8, textures.ptr[tt]);
176 foreach (int dy; 0..8) {
177 foreach (int dx; 0..8) {
178 //img.putPixel(x*8+dx, y*8+dy, Color((tt == 3 ? 128 : 0), (tt == 2 ? 128 : 0), (tt == 1 ? 128 : 0), 128));
179 auto c = img.getPixel(x*8+dx, y*8+dy);
180 if (c.a == 0) img.putPixel(x*8+dx, y*8+dy, Color(0, 0, 0, 255));
184 } else {
185 auto tf = tiles.ptr[Front].ptr[y*MapSize+x];
186 auto tb = tiles.ptr[Back].ptr[y*MapSize+x];
187 auto tt = tiles.ptr[type].ptr[y*MapSize+x];
188 if (wallnames[tf] == "_water_0" || wallnames[tf] == "_water_1" || wallnames[tf] == "_water_2" ||
189 wallnames[tb] == "_water_0" || wallnames[tb] == "_water_1" || wallnames[tb] == "_water_2") {
190 } else {
191 img.putTile(x*8, y*8, textures.ptr[tt]);
197 import std.string : format;
198 import arsd.png : writePng;
199 writePng("zpng%02s.png".format(type), img);
201 texgl[type] = new Texture(img, Texture.Option.Nearest, Texture.Option.Clamp);
205 void clear () {
206 tiles[] = null;
207 wallnames = null;
208 walltypes = null;
209 textures = null;
210 foreach (Texture tex; texgl) if (tex !is null) tex.clear;
211 texgl[] = null;
212 skytex = null;
213 things = null;
214 switches = null;
217 void dump (int idx) {
218 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))); }
219 foreach (immutable y; 0..MapSize) {
220 foreach (immutable x; 0..MapSize) {
221 conwrite(to62(tiles[idx][y*MapSize+x]));
223 conwriteln;
227 // true: found
228 bool getThingPos (ushort id, int* x=null, int* y=null, ushort* flags=null) {
229 foreach (ref th; things[]) {
230 if (th.type == id) {
231 if (x !is null) *x = th.x;
232 if (y !is null) *y = th.y;
233 if (flags !is null) *flags = th.flags;
234 return true;
237 if (x !is null) *x = 0;
238 if (y !is null) *y = 0;
239 if (flags !is null) *flags = 0;
240 return false;
243 private:
244 void calcMapSize () {
245 bool isEmpty(string dir) (int x, int y) if (dir == "col" || dir == "row") {
246 while (x < MapSize && y < MapSize) {
247 if (tiles[0][y*MapSize+x] || tiles[1][y*MapSize+x] || tiles[2][y*MapSize+x]) return false;
248 static if (dir == "row") ++x; else ++y;
250 return true;
252 width = height = MapSize;
253 // fix width
254 while (width > 0 && isEmpty!"col"(width-1, 0)) --width;
255 // fix height
256 while (height > 0 && isEmpty!"row"(0, height-1)) --height;
259 void load (string fname) {
260 import std.stdio : File;
261 load(openFile(fname));
264 void load(ST) (auto ref ST st) if (isReadableStream!ST) {
265 clear();
266 scope(failure) clear;
268 char[8] sign;
269 st.rawReadExact(sign[]);
270 if (sign != "Doom2D\x1a\x00") throw new Exception("invalid map signature");
271 if (st.readNum!ushort() != MapVersion) throw new Exception("invalid map version");
273 // load map blocks
274 foreach (ref a; tiles[]) a = new ubyte[](MapSize*MapSize);
275 char[$] skyname = "sprites/sky/rsky1.vga";
276 for (;;) {
277 auto btype = st.readNum!ushort();
278 if (btype == MB_END) break; // no more blocks
279 auto bsubtype = st.readNum!ushort();
280 auto bsize = st.readNum!uint();
281 if (bsize == 0) continue; // skip this block, it has no data (wtf?!)
282 // various tile types
283 switch (btype) {
284 case MB_SKY:
285 if (bsize != 2) throw new Exception("invalid sky data size");
286 ushort num = st.readNum!ushort();
287 if (num >= 1 && num <= 3) skyname[$-5] = cast(char)('0'+num);
288 break;
289 case MB_BACK:
290 case MB_FRONT:
291 case MB_WTYPE:
292 if (bsubtype > 1) throw new Exception("unknown tile block subtype");
293 int idx = (btype == MB_BACK ? Back : (btype == MB_FRONT ? Front : Type));
294 //ubyte[MapSize*MapSize] data = 0;
295 auto data = tiles[idx];
296 if (bsubtype == 0) {
297 if (bsize != data.length) throw new Exception("invalid tile data size");
298 st.rawReadExact(data[]);
299 } else {
300 // unpack RLE data
301 auto pkdata = new ubyte[](bsize);
302 st.rawReadExact(pkdata[]);
303 int spos = 0, opos = 0;
304 while (spos < pkdata.length) {
305 ubyte b = pkdata[spos++];
306 if (b != 255) {
307 data[opos++] = b;
308 } else {
309 int count = pkdata[spos++];
310 count |= pkdata[spos++]<<8;
311 b = pkdata[spos++];
312 while (count-- > 0) data[opos++] = b;
315 assert(opos == data.length);
317 // copy unpacked data
318 //foreach (immutable y; 0..MapSize) tiles[idx][y*MapSize] = data[y*MapSize..(y+1)*MapSize];
319 break;
320 case MB_WALLNAMES:
321 wallnames.length = 0;
322 wallnames ~= null;
323 //wallnames[] = null;
324 //walltypes.length = 0;
325 while (bsize >= 8+1) {
326 char[8] texname = 0;
327 st.rawReadExact(texname[]);
328 auto type = st.readNum!ubyte();
329 //char[] tn;
330 string tns;
331 foreach (char ch; texname) {
332 if (ch == 0) break;
333 if (ch >= 'A' && ch <= 'Z') ch += 32;
334 ch = dos2koi8(ch);
335 tns ~= ch;
336 //tn = texname[0..idx+1];
338 import std.uni : toLower;
339 wallnames ~= koi8lotranslit(tns).toLower;
340 //wallnames ~= recodeToKOI8(recode(tn.idup, "utf-8", "cp866").toLower, "utf-8");
341 //debug { conwriteln(wallnames.length-1, " : ", wallnames[$-1]); }
342 //if (wallnames[$-1][$-1] == '_') wallnames[$-1] ~= "1";
343 //wallnames[$-1] ~= ".vga";
344 //conwriteln(wallnames[$-1]);
345 //walltypes ~= type;
346 bsize -= 8+1;
348 if (bsize != 0) throw new Exception("invalid texture chunk size");
349 debug { conwriteln(wallnames.length, " textures loaded"); }
350 break;
351 case MB_THING:
352 while (bsize >= 8) {
353 bsize -= 8;
354 MapThing t = void;
355 t.x = st.readNum!short();
356 t.y = st.readNum!short();
357 t.type = st.readNum!ushort();
358 t.flags = st.readNum!ushort();
359 if (t.type != 0) things ~= t;
361 if (bsize != 0) throw new Exception("invalid thing chunk size");
362 break;
363 case MB_SWITCH2:
364 while (bsize >= 9) {
365 bsize -= 9;
366 MapSwitch sw = void;
367 sw.x = st.readNum!ubyte();
368 sw.y = st.readNum!ubyte();
369 sw.type = st.readNum!ubyte();
370 sw.tm = st.readNum!ubyte();
371 sw.a = st.readNum!ubyte();
372 sw.b = st.readNum!ubyte();
373 sw.c = st.readNum!ushort();
374 sw.flags = st.readNum!ubyte();
375 switches ~= sw;
377 if (bsize != 0) throw new Exception("invalid thing chunk size");
378 break;
379 default:
380 auto pkdata = new ubyte[](bsize);
381 st.rawReadExact(pkdata[]);
382 break;
385 calcMapSize();
386 // load textures
387 textures.length = 0;
388 foreach (immutable idx, string name; wallnames) {
389 if (name.length == 0 || name[0] == '_') {
390 textures ~= null;
391 continue;
392 } else {
393 textures ~= new D2DImage("tilegfx/"~name~".vga");
396 assert(textures.length == wallnames.length);
397 // fix tiles
398 foreach (immutable y; 0..height) {
399 foreach (immutable x; 0..width) {
400 if (tiles[Front][y*MapSize+x] >= textures.length) tiles[Front][y*MapSize+x] = 0;
401 if (tiles[Back][y*MapSize+x] >= textures.length) tiles[Back][y*MapSize+x] = 0;
404 skytex = new D2DImage(skyname.idup); //"sprites/rsky1.vga");
405 //skytex = new D2DImage("sprites/rsky3.vga");
406 skytex.sx = skytex.sy = 0;
407 debug { conwriteln(width, "x", height); }