1 module d2dmap
is aliced
;
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
];
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;
39 foreach (int dy
; 0..wt
.height
) {
40 foreach (int dx
; 0..wt
.width
) {
41 img
.putPixel(x
+dx
, y
, d2dpal
[*s
++]);
48 // ////////////////////////////////////////////////////////////////////////// //
49 public final class LevelMap
{
51 private import std
.stdio
: File
;
53 enum MapVersion
= 2; // óÁÍÁÑ ÐÏÓÌÅÄÎÑÑ ×ÅÒÓÉÑ ËÁÒÔÙ
54 public enum MapSize
= 100;
61 TILE_DOORC
= 2, // closed door
62 TILE_DOORO
= 3, // opened door
67 TILE_MBLOCK
= 8, // just blocks monsters
83 MB_MUSIC
, // usually 8 bytes
84 MB_SKY
, // ushort: [1..3]
98 static struct MapThing
{
102 DeathMatch
= 0x0010, // ÐÏÑ×ÌÑÅÔÓÑ ÔÏÌØËÏ × DeathMatch'Å
105 short x
, y
; // ËÏÏÒÄÉÎÁÔÙ
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
119 ubyte tm
; // ÄÏÌÖÎÏ ÂÙÔØ 0
120 ubyte a
, b
; // ÏÂÙÞÎÏ - ËÏÏÒÄÉÎÁÔÙ/8 Ä×ÅÒÉ
121 ushort c
; // ÎÅ ÉÓÐÏÌØÚÕÅÔÓÑ (×ÒÏÄÅ ÂÙ)
122 ubyte flags
; // ÆÌÁÇÉ (SW_*)
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
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);
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);
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));
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") {
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
);
210 foreach (Texture tex
; texgl
) if (tex
!is null) tex
.clear
;
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
]));
228 bool getThingPos (ushort id
, int* x
=null, int* y
=null, ushort* flags
=null) {
229 foreach (ref th
; things
[]) {
231 if (x
!is null) *x
= th
.x
;
232 if (y
!is null) *y
= th
.y
;
233 if (flags
!is null) *flags
= th
.flags
;
237 if (x
!is null) *x
= 0;
238 if (y
!is null) *y
= 0;
239 if (flags
!is null) *flags
= 0;
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
;
252 width
= height
= MapSize
;
254 while (width
> 0 && isEmpty
!"col"(width
-1, 0)) --width
;
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
) {
266 scope(failure
) clear
;
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");
274 foreach (ref a
; tiles
[]) a
= new ubyte[](MapSize
*MapSize
);
275 char[$] skyname
= "sprites/sky/rsky1.vga";
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
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
);
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
];
297 if (bsize
!= data
.length
) throw new Exception("invalid tile data size");
298 st
.rawReadExact(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
++];
309 int count
= pkdata
[spos
++];
310 count |
= pkdata
[spos
++]<<8;
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];
321 wallnames
.length
= 0;
323 //wallnames[] = null;
324 //walltypes.length = 0;
325 while (bsize
>= 8+1) {
327 st
.rawReadExact(texname
[]);
328 auto type
= st
.readNum
!ubyte();
331 foreach (char ch
; texname
) {
333 if (ch
>= 'A' && ch
<= 'Z') ch
+= 32;
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]);
348 if (bsize
!= 0) throw new Exception("invalid texture chunk size");
349 debug { conwriteln(wallnames
.length
, " textures loaded"); }
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");
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();
377 if (bsize
!= 0) throw new Exception("invalid thing chunk size");
380 auto pkdata
= new ubyte[](bsize
);
381 st
.rawReadExact(pkdata
[]);
388 foreach (immutable idx
, string name
; wallnames
) {
389 if (name
.length
== 0 || name
[0] == '_') {
393 textures
~= new D2DImage("tilegfx/"~name
~".vga");
396 assert(textures
.length
== wallnames
.length
);
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
); }