1 module d2dmap
is aliced
;
12 // ////////////////////////////////////////////////////////////////////////// //
13 __gshared Color
[256] d2dpal
;
16 public void loadPalette () {
17 auto fl
= openFile("playpal.pal");
18 foreach (immutable idx
; 0..256) {
19 ubyte r
= cast(ubyte)(fl
.readNum
!ubyte()*4);
20 ubyte g
= cast(ubyte)(fl
.readNum
!ubyte()*4);
21 ubyte b
= cast(ubyte)(fl
.readNum
!ubyte()*4);
27 // color 0 is transparent
32 // ////////////////////////////////////////////////////////////////////////// //
33 public class WallTexture
{
34 private import std
.stdio
: File
;
42 auto fl
= openFile(name
);
46 Color
opIndex (usize y
, usize x
) { pragma(inline
, true); return (x
< width
&& y
< height ? d2dpal
.ptr
[data
.ptr
[y
*width
+x
]] : Color(0, 0, 0, 0)); }
48 TrueColorImage
asTCImage () {
49 auto img
= new TrueColorImage(width
, height
);
50 auto cols
= img
.imageData
.colors
.ptr
;
51 foreach (int y
; 0..height
) {
52 foreach (int x
; 0..width
) {
53 ubyte c
= data
.ptr
[y
*width
+x
];
55 *cols
= Color(0, 0, 0, 0); // transparent
67 width
= fi
.readNum
!ushort();
68 height
= fi
.readNum
!ushort();
69 if (width
< 1) assert(0);
70 if (height
< 1) assert(0);
71 sx
= fi
.readNum
!short();
72 sy
= fi
.readNum
!short();
73 data
= new ubyte[width
*height
];
74 fi
.rawReadExact(data
[]);
79 // ////////////////////////////////////////////////////////////////////////// //
80 private Color
getPixel (TrueColorImage img
, int x
, int y
) {
81 if (x
>= 0 && y
>= 0 && x
< img
.width
&& y
< img
.height
) {
82 return img
.imageData
.colors
.ptr
[y
*img
.width
+x
];
84 return Color(0, 0, 0, 0);
89 private void putPixel (TrueColorImage img
, int x
, int y
, Color clr
) {
90 if (x
>= 0 && y
>= 0 && x
< img
.width
&& y
< img
.height
&& clr
.a
!= 0) {
91 img
.imageData
.colors
.ptr
[y
*img
.width
+x
] = clr
;
96 private void putTile (TrueColorImage img
, int x
, int y
, WallTexture wt
) {
97 if (wt
is null) return;
100 auto s
= wt
.data
.ptr
;
101 foreach (int dy
; 0..wt
.height
) {
102 foreach (int dx
; 0..wt
.width
) {
103 img
.putPixel(x
+dx
, y
, d2dpal
[*s
++]);
110 // ////////////////////////////////////////////////////////////////////////// //
136 // ////////////////////////////////////////////////////////////////////////// //
137 public final class LevelMap
{
139 private import std
.stdio
: File
;
141 enum MapVersion
= 2; // óÁÍÁÑ ÐÏÓÌÅÄÎÑÑ ×ÅÒÓÉÑ ËÁÒÔÙ
149 TILE_DOORC
= 2, // closed door
150 TILE_DOORO
= 3, // opened door
155 TILE_MBLOCK
= 8, // just blocks monsters
171 MB_MUSIC
, // 8 bytes, usually
172 MB_SKY
, // ushort: [1..3]
187 static struct MapThing
{
188 short x
, y
; // ËÏÏÒÄÉÎÁÔÙ
190 ushort flags
; // ÆÌÁÇÉ
193 static struct MapSwitch
{
194 ubyte x
, y
; // ËÏÏÒÄÉÎÁÔÙ/8
196 ubyte tm
; // ÄÏÌÖÎÏ ÂÙÔØ 0
197 ubyte a
, b
; // ÏÂÙÞÎÏ - ËÏÏÒÄÉÎÁÔÙ/8 Ä×ÅÒÉ
198 ushort c
; // ÎÅ ÉÓÐÏÌØÚÕÅÔÓÑ (×ÒÏÄÅ ÂÙ)
199 ubyte flags
; // ÆÌÁÇÉ
205 DeathMatch
= 0x10, // ÐÏÑ×ÌÑÅÔÓÑ ÔÏÌØËÏ × DeathMatch'Å
209 enum { Type
, Front
, Back
, Water
, Lava
, Acid
, LightMask
} // tile types, Front+: megatexture types
211 string
[] wallnames
; // "_water_0": water, "_water_1": acid, "_water_2": lava
212 ubyte[] walltypes
; // 0: solid; 1: non-solid
213 WallTexture
[] textures
;
218 this (string fname
) { load(fname
); }
219 this (File fl
) { load(fl
); }
221 // build OpenGL "megatextures" with the whole level on it
222 void oglBuildMega () {
223 auto img
= new TrueColorImage(width
*8, height
*8);
224 foreach (immutable type
; Front
..LightMask
+1) {
225 img
.imageData
.colors
[] = Color(0, 0, 0, 0);
227 foreach (int y
; 0..(height
*8+skytex
.height
-1)/skytex
.height
) {
228 foreach (int x
; 0..(width
*8+skytex
.width
-1)/skytex
.width
) {
229 img
.putTile(x
*skytex
.width
, y
*skytex
.height
, skytex
);
233 foreach (int y
; 0..height
) {
234 foreach (int x
; 0..width
) {
235 if (type
== LightMask
) {
236 // in the lightmask texture, we should have only occluders' pixels
237 auto tt
= tiles
.ptr
[Type
].ptr
[y
].ptr
[x
];
238 if (tt
== TILE_WALL || tt
== TILE_DOORC
) {
239 img
.putTile(x
*8, y
*8, textures
.ptr
[tiles
.ptr
[Back
].ptr
[y
].ptr
[x
]]);
240 img
.putTile(x
*8, y
*8, textures
.ptr
[tiles
.ptr
[Front
].ptr
[y
].ptr
[x
]]);
241 } /*else if (tt != TILE_LIFTU && tt != TILE_LIFTD) {
242 img.putTile(x*8, y*8, textures.ptr[tiles.ptr[Front].ptr[y].ptr[x]]);
244 } else if (type
>= Water
) {
245 auto tt
= tiles
.ptr
[Front
].ptr
[y
].ptr
[x
];
246 if (type
== Water
) tt
= (wallnames
[tt
] == "_water_0" ?
1 : 0);
247 else if (type
== Acid
) tt
= (wallnames
[tt
] == "_water_1" ?
2 : 0);
248 else if (type
== Lava
) tt
= (wallnames
[tt
] == "_water_2" ?
3 : 0);
250 tt
= tiles
.ptr
[Back
].ptr
[y
].ptr
[x
];
251 img
.putTile(x
*8, y
*8, textures
.ptr
[tt
]);
252 foreach (int dy
; 0..8) {
253 foreach (int dx
; 0..8) {
254 //img.putPixel(x*8+dx, y*8+dy, Color((tt == 3 ? 128 : 0), (tt == 2 ? 128 : 0), (tt == 1 ? 128 : 0), 128));
255 auto c
= img
.getPixel(x
*8+dx
, y
*8+dy
);
256 if (c
.a
== 0) img
.putPixel(x
*8+dx
, y
*8+dy
, Color(0, 0, 0, 255));
261 auto tf
= tiles
.ptr
[Front
].ptr
[y
].ptr
[x
];
262 auto tb
= tiles
.ptr
[Back
].ptr
[y
].ptr
[x
];
263 auto tt
= tiles
.ptr
[type
].ptr
[y
].ptr
[x
];
264 if (wallnames
[tf
] == "_water_0" || wallnames
[tf
] == "_water_1" || wallnames
[tf
] == "_water_2" ||
265 wallnames
[tb
] == "_water_0" || wallnames
[tb
] == "_water_1" || wallnames
[tb
] == "_water_2") {
267 img
.putTile(x
*8, y
*8, textures
.ptr
[tt
]);
272 texgl
[type
] = new Texture(img
, Texture
.Option
.Nearest
, Texture
.Option
.Clamp
);
281 foreach (Texture tex
; texgl
) if (tex
!is null) tex
.clear
;
286 void dump (int idx
) {
287 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))); }
288 foreach (immutable y
; 0..MapSize
) {
289 foreach (immutable x
; 0..MapSize
) {
290 conwrite(to62(tiles
[idx
][y
][x
]));
297 void calcMapSize () {
298 bool isEmpty(string dir
) (int x
, int y
) if (dir
== "col" || dir
== "row") {
299 while (x
< MapSize
&& y
< MapSize
) {
300 if (tiles
[0][y
][x
] || tiles
[1][y
][x
] || tiles
[2][y
][x
]) return false;
301 static if (dir
== "row") ++x
; else ++y
;
305 width
= height
= MapSize
;
307 while (width
> 0 && isEmpty
!"col"(width
-1, 0)) --width
;
309 while (height
> 0 && isEmpty
!"row"(0, height
-1)) --height
;
312 void load (string fname
) {
313 import std
.stdio
: File
;
314 load(openFile(fname
));
317 void load(ST
) (auto ref ST st
) if (isReadableStream
!ST
) {
319 scope(failure
) clear
;
322 st
.rawReadExact(sign
[]);
323 if (sign
!= "Doom2D\x1a\x00") throw new Exception("invalid map signature");
324 if (st
.readNum
!ushort() != MapVersion
) throw new Exception("invalid map version");
327 tiles
= new ubyte[][][](3, MapSize
, MapSize
);
329 auto btype
= st
.readNum
!ushort();
330 if (btype
== 0) break; // no more blocks
331 auto bsubtype
= st
.readNum
!ushort();
332 auto bsize
= st
.readNum
!uint();
333 if (bsize
== 0) continue; // skip this block, it has no data (wtf?!)
334 // various tile types
335 if (btype
== MB_BACK || btype
== MB_FRONT || btype
== MB_WTYPE
) {
336 if (bsubtype
> 1) throw new Exception("unknown tile block subtype");
337 int idx
= (btype
== MB_BACK ? Back
: (btype
== MB_FRONT ? Front
: Type
));
338 ubyte[MapSize
*MapSize
] data
= 0;
340 st
.rawReadExact(data
[]);
343 auto pkdata
= new ubyte[](bsize
);
344 st
.rawReadExact(pkdata
[]);
345 int spos
= 0, opos
= 0;
346 while (spos
< pkdata
.length
) {
347 ubyte b
= pkdata
[spos
++];
351 int count
= pkdata
[spos
++];
352 count |
= pkdata
[spos
++]<<8;
354 while (count
-- > 0) data
[opos
++] = b
;
357 assert(opos
== data
.length
);
359 // copy unpacked data
360 foreach (immutable y
; 0..MapSize
) {
361 tiles
[idx
][y
][] = data
[y
*MapSize
..(y
+1)*MapSize
];
363 } else if (btype
== MB_WALLNAMES
) {
364 wallnames
.length
= 0;
366 //wallnames[] = null;
367 //walltypes.length = 0;
368 while (bsize
>= 8+1) {
370 st
.rawReadExact(texname
[]);
371 auto type
= st
.readNum
!ubyte();
373 foreach (immutable idx
, ref char ch
; texname
) {
375 if (ch
>= 'A' && ch
<= 'Z') ch
+= 32;
376 tn
= texname
[0..idx
+1];
378 wallnames
~= tn
.idup
;
379 //debug { conwriteln(wallnames.length-1, " : ", wallnames[$-1]); }
380 //if (wallnames[$-1][$-1] == '_') wallnames[$-1] ~= "1";
381 //wallnames[$-1] ~= ".vga";
382 //conwriteln(wallnames[$-1]);
386 if (bsize
!= 0) throw new Exception("invalid texture chunk size");
387 debug { conwriteln(wallnames
.length
, " textures loaded"); }
389 auto pkdata
= new ubyte[](bsize
);
390 st
.rawReadExact(pkdata
[]);
396 foreach (immutable idx
, string name
; wallnames
) {
397 if (name
.length
== 0 || name
[0] == '_') {
401 textures
~= new WallTexture("tilegfx/"~name
~".vga");
404 assert(textures
.length
== wallnames
.length
);
406 foreach (immutable y
; 0..height
) {
407 foreach (immutable x
; 0..width
) {
408 if (tiles
[Front
][y
][x
] >= textures
.length
) tiles
[Front
][y
][x
] = 0;
409 if (tiles
[Back
][y
][x
] >= textures
.length
) tiles
[Back
][y
][x
] = 0;
412 skytex
= new WallTexture("sprites/rsky1.vga");
413 skytex
.sx
= skytex
.sy
= 0;
414 debug { conwriteln(width
, "x", height
); }
419 // ////////////////////////////////////////////////////////////////////////// //
445 MN__FIRST
= MN_DEMON
,
446 MN_TYPES_MAX
= MN__LAST
-MN__FIRST
,
453 struct MonsterStats
{
461 int minp
; // min_pn? minimal pain to became re-angry?
466 const monster_stats_t mnsz[MN_TYPES_MAX+1] = {
467 //rad ht life pain rv jv slop min_pn
468 {.r= 0, .h= 0, .l= 0, .mp= 0, .rv=0, .jv= 0, .sp= 0, .minp= 0}, // none
469 {.r=15, .h=28, .l= 60, .mp=20, .rv=7, .jv=10, .sp= 0, .minp=10}, // demon
470 {.r=10, .h=28, .l= 25, .mp=15, .rv=3, .jv=10, .sp=30, .minp= 0}, // imp
471 {.r=10, .h=28, .l= 15, .mp=10, .rv=3, .jv=10, .sp=30, .minp= 0}, // zombie
472 {.r=10, .h=28, .l= 20, .mp=10, .rv=3, .jv=10, .sp=30, .minp= 0}, // sergeant
473 {.r=20, .h=55, .l=500, .mp=70, .rv=5, .jv=10, .sp= 0, .minp=50}, // cyberdemon
474 {.r=12, .h=28, .l= 60, .mp=20, .rv=3, .jv=10, .sp=30, .minp=10}, // chaingunner
475 {.r=12, .h=32, .l=150, .mp=40, .rv=3, .jv=10, .sp= 0, .minp=30}, // baron of hell
476 {.r=12, .h=32, .l= 75, .mp=40, .rv=3, .jv=10, .sp= 0, .minp=30}, // hell knight
477 {.r=15, .h=28, .l=100, .mp=10, .rv=4, .jv= 4, .sp= 0, .minp= 0}, // cacodemon
478 {.r= 8, .h=18, .l= 60, .mp=10, .rv=4, .jv= 4, .sp= 0, .minp= 0}, // lost soul
479 {.r=15, .h=28, .l=100, .mp=10, .rv=4, .jv= 4, .sp= 0, .minp= 0}, // pain elemental
480 {.r=64, .h=50, .l=500, .mp=70, .rv=4, .jv=10, .sp= 0, .minp=50}, // spider mastermind
481 {.r=25, .h=27, .l=150, .mp=20, .rv=4, .jv=10, .sp= 0, .minp= 0}, // arachnotron
482 {.r=18, .h=30, .l=200, .mp=40, .rv=3, .jv= 7, .sp= 0, .minp=20}, // mancubus
483 {.r=17, .h=36, .l=200, .mp=40, .rv=6, .jv=11, .sp= 0, .minp=20}, // revenant
484 {.r=17, .h=36, .l=150, .mp=30, .rv=7, .jv=12, .sp= 0, .minp=10}, // archvile
485 {.r= 5, .h= 5, .l= 35, .mp=20, .rv=14, .jv= 6, .sp= 0, .minp=10}, // fish
486 {.r= 5, .h=17, .l= 20, .mp= 0, .rv=7, .jv= 6, .sp= 0, .minp= 0}, // barrel
487 {.r=17, .h=38, .l= 20, .mp=40, .rv=3, .jv= 6, .sp= 0, .minp=20}, // robot
488 {.r= 8, .h=26, .l=400, .mp=70, .rv=8, .jv=10, .sp=30, .minp=50}, // man