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
;
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
];
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;
87 foreach (int dy
; 0..wt
.height
) {
88 foreach (int dx
; 0..wt
.width
) {
89 img
.putPixel(x
+dx
, y
, d2dpal
[*s
++]);
97 static ubyte clampByte (ubyte b
, int delta
) {
99 return cast(ubyte)(delta
< 0 ?
0 : delta
> 255 ?
255 : delta
);
103 // ////////////////////////////////////////////////////////////////////////// //
104 public final class LevelMap
{
106 enum MapVersion
= 2; // óÁÍÁÑ ÐÏÓÌÅÄÎÑÑ ×ÅÒÓÉÑ ËÁÒÔÙ
107 public enum MapSize
= 100;
114 TILE_DOORC
= 2, // closed door
115 TILE_DOORO
= 3, // opened door
120 TILE_MBLOCK
= 8, // just blocks monsters
136 MB_MUSIC
, // usually 8 bytes
137 MB_SKY
, // ushort: [1..3]
151 static struct MapThing
{
155 DeathMatch
= 0x0010, // ÐÏÑ×ÌÑÅÔÓÑ ÔÏÌØËÏ × DeathMatch'Å
158 short x
, y
; // ËÏÏÒÄÉÎÁÔÙ
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
172 ubyte tm
; // ÄÏÌÖÎÏ ÂÙÔØ 0
173 ubyte a
, b
; // ÏÂÙÞÎÏ - ËÏÏÒÄÉÎÁÔÙ/8 Ä×ÅÒÉ
174 ushort c
; // ÎÅ ÉÓÐÏÌØÚÕÅÔÓÑ (×ÒÏÄÅ ÂÙ)
175 ubyte flags
; // ÆÌÁÇÉ (SW_*)
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"
185 Texture
[MapTexturesMax
] texgl
;
186 TrueColorImage
[MapTexturesMax
] teximgs
;
191 MapSwitch
[] switches
;
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;
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);
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
264 img
.putPixelTile(x
*TileSize
, y
*TileSize
, wclr
);
265 // if this is top one, make some light border
266 if (!hasLiquidAt(x
, y
-1)) {
268 auto wcc
= wclr
.lighten(0.6);
270 foreach (int dx
; 0..TileSize
) img
.putPixel(x
*TileSize
+dx
, y
*TileSize
+0, wcc
);
271 wcc
= wclr
.lighten(0.4);
273 foreach (int dx
; 0..TileSize
) img
.putPixel(x
*TileSize
+dx
, y
*TileSize
+1, wcc
);
274 wcc
= wclr
.lighten(0.2);
276 foreach (int dx
; 0..TileSize
) img
.putPixel(x
*TileSize
+dx
, y
*TileSize
+2, wcc
);
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") {
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
);
299 texgl
[type
].setFromImage(img
, 0, 0);
309 foreach (Texture tex
; texgl
) if (tex
!is null) tex
.clear
;
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
]));
327 bool getThingPos (ushort id
, int* x
=null, int* y
=null, ushort* flags
=null) {
328 foreach (ref th
; things
[]) {
330 if (x
!is null) *x
= th
.x
;
331 if (y
!is null) *y
= th
.y
;
332 if (flags
!is null) *flags
= th
.flags
;
336 if (x
!is null) *x
= 0;
337 if (y
!is null) *y
= 0;
338 if (flags
!is null) *flags
= 0;
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;
352 width = height = MapSize;
354 while (width > 0 && isEmpty!"col"(width-1, 0)) --width;
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
) {
367 scope(failure
) clear
;
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");
375 foreach (ref a
; tiles
[]) a
= new ubyte[](MapSize
*MapSize
);
376 char[$] skyname
= "sprites/sky/rsky1.vga";
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
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
);
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
];
398 if (bsize
!= data
.length
) throw new Exception("invalid tile data size");
399 st
.rawReadExact(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
++];
410 int count
= pkdata
[spos
++];
411 count |
= pkdata
[spos
++]<<8;
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];
422 wallnames
.length
= 0;
424 //wallnames[] = null;
425 //walltypes.length = 0;
426 while (bsize
>= 8+1) {
428 st
.rawReadExact(texname
[]);
429 auto type
= st
.readNum
!ubyte();
432 foreach (char ch
; texname
) {
434 if (ch
>= 'A' && ch
<= 'Z') ch
+= 32;
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]);
451 if (bsize
!= 0) throw new Exception("invalid texture chunk size");
452 debug { conwriteln(wallnames
.length
, " textures loaded"); }
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");
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();
480 if (bsize
!= 0) throw new Exception("invalid thing chunk size");
483 auto pkdata
= new ubyte[](bsize
);
484 st
.rawReadExact(pkdata
[]);
491 foreach (immutable idx
, string name
; wallnames
) {
492 if (name
.length
== 0 || name
[0] == '_') {
496 textures
~= new D2DImage("tilegfx/"~name
~".vga");
499 assert(textures
.length
== wallnames
.length
);
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
); }