really compiles! ;-)
[dd2d.git] / mapexport / d2dmap.d
blobc003f182d31ec991c95eba83ce84ec1e8f3f15e7
1 module d2dmap is aliced;
2 private:
4 import console;
5 import wadarc;
7 import iv.stream;
8 import iv.encoding;
11 // ////////////////////////////////////////////////////////////////////////// //
12 public final class LevelMap {
13 private:
14 private import std.stdio : File;
16 enum MapVersion = 2; // óÁÍÁÑ ÐÏÓÌÅÄÎÑÑ ×ÅÒÓÉÑ ËÁÒÔÙ
17 public enum MapSize = 100;
19 public:
20 // tile type
21 enum : ubyte {
22 TILE_EMPTY = 0,
23 TILE_WALL = 1,
24 TILE_DOORC = 2, // closed door
25 TILE_DOORO = 3, // opened door
26 TILE_STEP = 4,
27 TILE_WATER = 5,
28 TILE_ACID1 = 6,
29 TILE_ACID2 = 7,
30 TILE_MBLOCK = 8, // just blocks monsters
31 TILE_LIFTU = 9,
32 TILE_LIFTD = 10,
34 TILE_ACTTRAP = 255,
37 enum : short {
38 MB_COMMENT = -1,
39 MB_END = 0,
40 MB_WALLNAMES,
41 MB_BACK,
42 MB_WTYPE,
43 MB_FRONT,
44 MB_THING,
45 MB_SWITCH,
46 MB_MUSIC, // usually 8 bytes
47 MB_SKY, // ushort: [1..3]
48 MB_SWITCH2,
51 enum {
52 SW_PL_PRESS = 1<<0,
53 SW_MN_PRESS = 1<<1,
54 SW_PL_NEAR = 1<<2,
55 SW_MN_NEAR = 1<<3,
56 SW_KEY_R = 1<<4,
57 SW_KEY_G = 1<<5,
58 SW_KEY_B = 1<<6,
61 enum {
62 SW_NONE,
63 SW_EXIT,
64 SW_EXITS,
65 SW_OPENDOOR,
66 SW_SHUTDOOR,
67 SW_SHUTTRAP,
68 SW_DOOR,
69 SW_DOOR5,
70 SW_PRESS,
71 SW_TELE,
72 SW_SECRET,
73 SW_LIFTUP,
74 SW_LIFTDOWN,
75 SW_TRAP,
76 SW_LIFT,
77 SW_WINGAME = 64,
80 static struct MapThing {
81 // thing flags
82 enum : ushort {
83 DirRight = 0x0001,
84 DeathMatch = 0x0010, // ÐÏÑ×ÌÑÅÔÓÑ ÔÏÌØËÏ × DeathMatch'Å
87 short x, y; // ËÏÏÒÄÉÎÁÔÙ
88 ushort type; // ÔÉÐ
89 ushort flags; // ÆÌÁÇÉ
91 @property const pure nothrow @safe @nogc {
92 bool left () => ((flags&DirRight) == 0);
93 bool right () => ((flags&DirRight) != 0);
94 bool dmonly () => ((flags&DeathMatch) != 0);
98 static struct MapSwitch {
99 ubyte x, y; // ËÏÏÒÄÉÎÁÔÙ/8
100 ubyte type; // ÔÉÐ
101 ubyte tm; // ÄÏÌÖÎÏ ÂÙÔØ 0
102 ubyte a, b; // ÏÂÙÞÎÏ - ËÏÏÒÄÉÎÁÔÙ/8 Ä×ÅÒÉ
103 ushort c; // ÎÅ ÉÓÐÏÌØÚÕÅÔÓÑ (×ÒÏÄÅ ÂÙ)
104 ubyte flags; // ÆÌÁÇÉ (SW_*)
107 public:
108 enum { Type, Front, Back, Water, Lava, Acid, LightMask } // tile types, Front+: megatexture types
109 ubyte[][3] tiles; // Type, Front, Back
110 string[] wallnames; // "_water_0": water, "_water_1": acid, "_water_2": lava
111 int width, height;
112 string skytex;
114 MapThing[] things;
115 MapSwitch[] switches;
117 this (string fname) { load(fname); }
118 this (File fl) { load(fl); }
120 void clear () {
121 tiles[] = null;
122 wallnames = null;
123 skytex = null;
124 things = null;
125 switches = null;
126 width = height = 0;
129 void dump (int idx) {
130 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))); }
131 foreach (immutable y; 0..MapSize) {
132 foreach (immutable x; 0..MapSize) {
133 conwrite(to62(tiles[idx][y*MapSize+x]));
135 conwriteln;
139 // true: found
140 bool getThingPos (ushort id, int* x=null, int* y=null, ushort* flags=null) {
141 foreach (ref th; things[]) {
142 if (th.type == id) {
143 if (x !is null) *x = th.x;
144 if (y !is null) *y = th.y;
145 if (flags !is null) *flags = th.flags;
146 return true;
149 if (x !is null) *x = 0;
150 if (y !is null) *y = 0;
151 if (flags !is null) *flags = 0;
152 return false;
155 private:
156 void calcMapSize () {
157 bool isEmpty(string dir) (int x, int y) if (dir == "col" || dir == "row") {
158 while (x < MapSize && y < MapSize) {
159 if (tiles[0][y*MapSize+x] || tiles[1][y*MapSize+x] || tiles[2][y*MapSize+x]) return false;
160 static if (dir == "row") ++x; else ++y;
162 return true;
164 width = height = MapSize;
165 // fix width
166 while (width > 0 && isEmpty!"col"(width-1, 0)) --width;
167 // fix height
168 while (height > 0 && isEmpty!"row"(0, height-1)) --height;
171 void load (string fname) {
172 import std.stdio : File;
173 load(openFile(fname));
176 void load(ST) (auto ref ST st) if (isReadableStream!ST) {
177 clear();
178 scope(failure) clear;
180 char[8] sign;
181 st.rawReadExact(sign[]);
182 if (sign != "Doom2D\x1a\x00") throw new Exception("invalid map signature");
183 if (st.readNum!ushort() != MapVersion) throw new Exception("invalid map version");
185 // load map blocks
186 foreach (ref a; tiles[]) a = new ubyte[](MapSize*MapSize);
187 char[$] skyname = "sprites/sky/rsky1.vga";
188 for (;;) {
189 auto btype = st.readNum!ushort();
190 if (btype == MB_END) break; // no more blocks
191 auto bsubtype = st.readNum!ushort();
192 auto bsize = st.readNum!uint();
193 if (bsize == 0) continue; // skip this block, it has no data (wtf?!)
194 // various tile types
195 switch (btype) {
196 case MB_SKY:
197 if (bsize != 2) throw new Exception("invalid sky data size");
198 ushort num = st.readNum!ushort();
199 if (num >= 1 && num <= 3) skyname[$-5] = cast(char)('0'+num);
200 break;
201 case MB_BACK:
202 case MB_FRONT:
203 case MB_WTYPE:
204 if (bsubtype > 1) throw new Exception("unknown tile block subtype");
205 int idx = (btype == MB_BACK ? Back : (btype == MB_FRONT ? Front : Type));
206 //ubyte[MapSize*MapSize] data = 0;
207 auto data = tiles[idx];
208 if (bsubtype == 0) {
209 if (bsize != data.length) throw new Exception("invalid tile data size");
210 st.rawReadExact(data[]);
211 } else {
212 // unpack RLE data
213 auto pkdata = new ubyte[](bsize);
214 st.rawReadExact(pkdata[]);
215 int spos = 0, opos = 0;
216 while (spos < pkdata.length) {
217 ubyte b = pkdata[spos++];
218 if (b != 255) {
219 data[opos++] = b;
220 } else {
221 int count = pkdata[spos++];
222 count |= pkdata[spos++]<<8;
223 b = pkdata[spos++];
224 while (count-- > 0) data[opos++] = b;
227 assert(opos == data.length);
229 // copy unpacked data
230 //foreach (immutable y; 0..MapSize) tiles[idx][y*MapSize] = data[y*MapSize..(y+1)*MapSize];
231 break;
232 case MB_WALLNAMES:
233 wallnames.length = 0;
234 wallnames ~= null;
235 //wallnames[] = null;
236 while (bsize >= 8+1) {
237 char[8] texname = 0;
238 st.rawReadExact(texname[]);
239 auto type = st.readNum!ubyte();
240 //char[] tn;
241 string tns;
242 foreach (char ch; texname) {
243 if (ch == 0) break;
244 if (ch >= 'A' && ch <= 'Z') ch += 32;
245 ch = dos2koi8(ch);
246 tns ~= ch;
247 //tn = texname[0..idx+1];
249 import std.uni : toLower;
250 wallnames ~= koi8lotranslit(tns).toLower;
251 bsize -= 8+1;
253 if (bsize != 0) throw new Exception("invalid texture chunk size");
254 debug { conwriteln(wallnames.length, " textures loaded"); }
255 break;
256 case MB_THING:
257 while (bsize >= 8) {
258 bsize -= 8;
259 MapThing t = void;
260 t.x = st.readNum!short();
261 t.y = st.readNum!short();
262 t.type = st.readNum!ushort();
263 t.flags = st.readNum!ushort();
264 if (t.type != 0) things ~= t;
266 if (bsize != 0) throw new Exception("invalid thing chunk size");
267 break;
268 case MB_SWITCH2:
269 while (bsize >= 9) {
270 bsize -= 9;
271 MapSwitch sw = void;
272 sw.x = st.readNum!ubyte();
273 sw.y = st.readNum!ubyte();
274 sw.type = st.readNum!ubyte();
275 sw.tm = st.readNum!ubyte();
276 sw.a = st.readNum!ubyte();
277 sw.b = st.readNum!ubyte();
278 sw.c = st.readNum!ushort();
279 sw.flags = st.readNum!ubyte();
280 switches ~= sw;
282 if (bsize != 0) throw new Exception("invalid thing chunk size");
283 break;
284 default:
285 auto pkdata = new ubyte[](bsize);
286 st.rawReadExact(pkdata[]);
287 break;
290 calcMapSize();
291 skytex = skyname.idup;
292 debug { conwriteln(width, "x", height); }