DEADSOFTWARE

do not write empty blocks
[mp2df.git] / mp2df.pas
1 (* Copyright (C) SovietPony
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *)
16 {$MODE OBJFPC}
17 {$MODESWITCH ANSISTRINGS+}
18 {$MODESWITCH AUTODEREF+}
19 {$MODESWITCH CLASSICPROCVARS+}
20 {$MODESWITCH DEFAULTPARAMETERS+}
21 {$MODESWITCH DUPLICATELOCALS-}
22 {$MODESWITCH EXCEPTIONS+}
23 {$MODESWITCH HINTDIRECTIVE+}
24 {$MODESWITCH NESTEDCOMMENTS+}
25 {$MODESWITCH NESTEDPROCVARS+}
26 {$MODESWITCH OBJPAS+}
27 {$MODESWITCH OUT+}
28 {$MODESWITCH PCHARTOSTRING+}
29 {$MODESWITCH POINTERTOPROCVAR+}
30 {$MODESWITCH REPEATFORWARD+}
31 {$MODESWITCH RESULT+}
32 {$MODESWITCH UNICODESTRINGS-}
34 {$ASSERTIONS ON}
35 {$COPERATORS ON}
36 {$EXTENDEDSYNTAX ON}
38 program mp2df;
40 uses Classes, SysUtils, Math, SDL, SDL_image;
42 const
43 (* MP Tiles *)
44 MP_WALL = 0;
45 MP_STEP = 1;
46 MP_BACK = 2;
47 MP_FORE = 3;
48 MP_WATER = 4;
49 MP_ACID1 = 5;
50 MP_ACID2 = 6;
51 MP_LIFTUP = 7;
52 MP_LIFTDOWN = 8;
53 MP_LIFTLEFT = 9;
54 MP_LIFTRIGHT = 10;
56 (* MP Items *)
57 MP_MEDKIT_SMALL = 11;
58 MP_MEDKIT_LARGE = 12;
59 MP_ARMOR_GREEN = 13;
60 MP_ARMOR_BLUE = 14;
61 MP_SPHERE_BLUE = 15;
62 MP_SPHERE_WHITE = 16;
63 MP_INVUL = 17;
64 MP_JETPACK = 18;
65 MP_MEDKIT_BLACK = 19;
66 MP_AMMO_BACKPACK = 20;
67 MP_AMMO_BULLETS = 21;
68 MP_AMMO_BULLETS_BOX = 22;
69 MP_AMMO_SHELLS = 23;
70 MP_AMMO_SHELLS_BOX = 24;
71 MP_BOTTLE = 25;
72 MP_HELMET = 26;
73 MP_AMMO_ROCKET = 27;
74 MP_AMMO_ROCKET_BOX = 28;
75 MP_AMMO_CELL = 29;
76 MP_AMMO_CELL_BIG = 30;
77 MP_WEAPON_SHOTGUN1 = 31;
78 MP_WEAPON_SHOTGUN2 = 32;
79 MP_WEAPON_CHAINGUN = 33;
80 MP_WEAPON_SAW = 34;
81 MP_WEAPON_ROCKETLAUNCHER = 35;
82 MP_WEAPON_PLASMA = 36;
83 MP_WEAPON_BFG = 37;
84 MP_WEAPON_SUPERPULEMET = 38;
85 // areas and triggers here!
86 MP_INVIS = 49;
87 MP_SUIT = 50;
89 (* MP Areas *)
90 MP_DMPOINT = 39;
91 MP_REDTEAMPOINT = 40;
92 MP_BLUETEAMPOINT = 41;
93 MP_REDFLAG = 42;
94 MP_BLUEFLAG = 43;
96 (* MP Trigger activation *)
97 MP_COLLIDE = 44;
98 MP_PRESS = 45;
99 MP_SHOT = 46;
100 MP_START = 47;
101 MP_NOACT = 48;
103 (* MP Trigger *)
104 MP_CLOSEDOOR = 0;
105 MP_OPENDOOR = 1;
106 MP_DOOR = 2;
107 MP_RANDOM = 3;
108 MP_EXTENDER = 4;
109 MP_SWITCH = 5;
110 MP_DAMAGE = 6;
111 MP_TELEPORT = 7;
112 MP_EXIT = 8;
114 const
115 PANEL_NONE = 0;
116 PANEL_WALL = 1;
117 PANEL_BACK = 2;
118 PANEL_FORE = 4;
119 PANEL_WATER = 8;
120 PANEL_ACID1 = 16;
121 PANEL_ACID2 = 32;
122 PANEL_STEP = 64;
123 PANEL_LIFTUP = 128;
124 PANEL_LIFTDOWN = 256;
125 PANEL_OPENDOOR = 512;
126 PANEL_CLOSEDOOR = 1024;
127 PANEL_BLOCKMON = 2048;
128 PANEL_LIFTLEFT = 4096;
129 PANEL_LIFTRIGHT = 8192;
131 PANEL_FLAG_BLENDING = 1;
132 PANEL_FLAG_HIDE = 2;
133 PANEL_FLAG_WATERTEXTURES = 4;
135 ITEM_NONE = 0;
136 ITEM_MEDKIT_SMALL = 1;
137 ITEM_MEDKIT_LARGE = 2;
138 ITEM_MEDKIT_BLACK = 3;
139 ITEM_ARMOR_GREEN = 4;
140 ITEM_ARMOR_BLUE = 5;
141 ITEM_SPHERE_BLUE = 6;
142 ITEM_SPHERE_WHITE = 7;
143 ITEM_SUIT = 8;
144 ITEM_OXYGEN = 9;
145 ITEM_INVUL = 10;
146 ITEM_WEAPON_SAW = 11;
147 ITEM_WEAPON_SHOTGUN1 = 12;
148 ITEM_WEAPON_SHOTGUN2 = 13;
149 ITEM_WEAPON_CHAINGUN = 14;
150 ITEM_WEAPON_ROCKETLAUNCHER = 15;
151 ITEM_WEAPON_PLASMA = 16;
152 ITEM_WEAPON_BFG = 17;
153 ITEM_WEAPON_SUPERPULEMET = 18;
154 ITEM_AMMO_BULLETS = 19;
155 ITEM_AMMO_BULLETS_BOX = 20;
156 ITEM_AMMO_SHELLS = 21;
157 ITEM_AMMO_SHELLS_BOX = 22;
158 ITEM_AMMO_ROCKET = 23;
159 ITEM_AMMO_ROCKET_BOX = 24;
160 ITEM_AMMO_CELL = 25;
161 ITEM_AMMO_CELL_BIG = 26;
162 ITEM_AMMO_BACKPACK = 27;
163 ITEM_KEY_RED = 28;
164 ITEM_KEY_GREEN = 29;
165 ITEM_KEY_BLUE = 30;
166 ITEM_WEAPON_KASTET = 31;
167 ITEM_WEAPON_PISTOL = 32;
168 ITEM_BOTTLE = 33;
169 ITEM_HELMET = 34;
170 ITEM_JETPACK = 35;
171 ITEM_INVIS = 36;
172 ITEM_WEAPON_FLAMETHROWER = 37;
173 ITEM_AMMO_FUELCAN = 38;
175 ITEM_OPTION_ONLYDM = 1;
176 ITEM_OPTION_FALL = 2;
178 AREA_NONE = 0;
179 AREA_PLAYERPOINT1 = 1;
180 AREA_PLAYERPOINT2 = 2;
181 AREA_DMPOINT = 3;
182 AREA_REDFLAG = 4;
183 AREA_BLUEFLAG = 5;
184 AREA_DOMFLAG = 6;
185 AREA_REDTEAMPOINT = 7;
186 AREA_BLUETEAMPOINT = 8;
188 TRIGGER_NONE = 0;
189 TRIGGER_EXIT = 1;
190 TRIGGER_TELEPORT = 2;
191 TRIGGER_OPENDOOR = 3;
192 TRIGGER_CLOSEDOOR = 4;
193 TRIGGER_DOOR = 5;
194 TRIGGER_DOOR5 = 6;
195 TRIGGER_CLOSETRAP = 7;
196 TRIGGER_TRAP = 8;
197 TRIGGER_PRESS = 9;
198 TRIGGER_SECRET = 10;
199 TRIGGER_LIFTUP = 11;
200 TRIGGER_LIFTDOWN = 12;
201 TRIGGER_LIFT = 13;
202 TRIGGER_TEXTURE = 14;
203 TRIGGER_ON = 15;
204 TRIGGER_OFF = 16;
205 TRIGGER_ONOFF = 17;
206 TRIGGER_SOUND = 18;
207 TRIGGER_SPAWNMONSTER = 19;
208 TRIGGER_SPAWNITEM = 20;
209 TRIGGER_MUSIC = 21;
210 TRIGGER_PUSH = 22;
211 TRIGGER_SCORE = 23;
212 TRIGGER_MESSAGE = 24;
213 TRIGGER_DAMAGE = 25;
214 TRIGGER_HEALTH = 26;
215 TRIGGER_SHOT = 27;
216 TRIGGER_EFFECT = 28;
217 TRIGGER_MAX = 28;
219 TRIGGER_SHOT_PISTOL = 0;
220 TRIGGER_SHOT_BULLET = 1;
221 TRIGGER_SHOT_SHOTGUN = 2;
222 TRIGGER_SHOT_SSG = 3;
223 TRIGGER_SHOT_IMP = 4;
224 TRIGGER_SHOT_PLASMA = 5;
225 TRIGGER_SHOT_SPIDER = 6;
226 TRIGGER_SHOT_CACO = 7;
227 TRIGGER_SHOT_BARON = 8;
228 TRIGGER_SHOT_MANCUB = 9;
229 TRIGGER_SHOT_REV = 10;
230 TRIGGER_SHOT_ROCKET = 11;
231 TRIGGER_SHOT_BFG = 12;
232 TRIGGER_SHOT_EXPL = 13;
233 TRIGGER_SHOT_BFGEXPL = 14;
234 TRIGGER_SHOT_FLAME = 15;
235 TRIGGER_SHOT_MAX = 15;
237 TRIGGER_SHOT_TARGET_NONE = 0;
238 TRIGGER_SHOT_TARGET_MON = 1;
239 TRIGGER_SHOT_TARGET_PLR = 2;
240 TRIGGER_SHOT_TARGET_RED = 3;
241 TRIGGER_SHOT_TARGET_BLUE = 4;
242 TRIGGER_SHOT_TARGET_MONPLR = 5;
243 TRIGGER_SHOT_TARGET_PLRMON = 6;
245 TRIGGER_SHOT_AIM_DEFAULT = 0;
246 TRIGGER_SHOT_AIM_ALLMAP = 1;
247 TRIGGER_SHOT_AIM_TRACE = 2;
248 TRIGGER_SHOT_AIM_TRACEALL = 3;
250 TRIGGER_EFFECT_PARTICLE = 0;
251 TRIGGER_EFFECT_ANIMATION = 1;
253 TRIGGER_EFFECT_SLIQUID = 0;
254 TRIGGER_EFFECT_LLIQUID = 1;
255 TRIGGER_EFFECT_DLIQUID = 2;
256 TRIGGER_EFFECT_BLOOD = 3;
257 TRIGGER_EFFECT_SPARK = 4;
258 TRIGGER_EFFECT_BUBBLE = 5;
259 TRIGGER_EFFECT_MAX = 5;
261 TRIGGER_EFFECT_POS_CENTER = 0;
262 TRIGGER_EFFECT_POS_AREA = 1;
264 ACTIVATE_PLAYERCOLLIDE = 1;
265 ACTIVATE_MONSTERCOLLIDE = 2;
266 ACTIVATE_PLAYERPRESS = 4;
267 ACTIVATE_MONSTERPRESS = 8;
268 ACTIVATE_SHOT = 16;
269 ACTIVATE_NOMONSTER = 32;
270 ACTIVATE_CUSTOM = 255;
272 KEY_RED = 1;
273 KEY_GREEN = 2;
274 KEY_BLUE = 4;
275 KEY_REDTEAM = 8;
276 KEY_BLUETEAM = 16;
278 TEXTURE_SPECIAL_WATER = DWORD(-1);
279 TEXTURE_SPECIAL_ACID1 = DWORD(-2);
280 TEXTURE_SPECIAL_ACID2 = DWORD(-3);
281 TEXTURE_NONE = DWORD(-4);
283 mp2df_wall: array [MP_WALL..MP_LIFTRIGHT] of Integer = (
284 PANEL_WALL, PANEL_STEP, PANEL_BACK, PANEL_FORE, PANEL_WATER,
285 PANEL_ACID1, PANEL_ACID2, PANEL_LIFTUP, PANEL_LIFTDOWN,
286 PANEL_LIFTLEFT, PANEL_LIFTRIGHT
287 );
289 mp2df_item: array [MP_MEDKIT_SMALL..MP_SUIT] of Byte = (
290 ITEM_MEDKIT_SMALL, ITEM_MEDKIT_LARGE, ITEM_ARMOR_GREEN, ITEM_ARMOR_BLUE,
291 ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_INVUL, ITEM_JETPACK,
292 ITEM_MEDKIT_BLACK, ITEM_AMMO_BACKPACK, ITEM_AMMO_BULLETS, ITEM_AMMO_BULLETS_BOX,
293 ITEM_AMMO_SHELLS, ITEM_AMMO_SHELLS_BOX, ITEM_BOTTLE, ITEM_HELMET,
294 ITEM_AMMO_ROCKET, ITEM_AMMO_ROCKET_BOX, ITEM_AMMO_CELL, ITEM_AMMO_CELL_BIG,
295 ITEM_WEAPON_SHOTGUN1, ITEM_WEAPON_SHOTGUN2, ITEM_WEAPON_CHAINGUN, ITEM_WEAPON_SAW,
296 ITEM_WEAPON_ROCKETLAUNCHER, ITEM_WEAPON_PLASMA, ITEM_WEAPON_BFG, ITEM_WEAPON_SUPERPULEMET,
297 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // unused here
298 ITEM_INVIS, ITEM_SUIT
299 );
301 mp2df_area: array [MP_DMPOINT..MP_BLUEFLAG] of Byte = (
302 AREA_DMPOINT, AREA_REDTEAMPOINT, AREA_BLUETEAMPOINT,
303 AREA_REDFLAG, AREA_BLUEFLAG
304 );
306 mp2df_act: array [MP_COLLIDE..MP_NOACT] of Byte = (
307 ACTIVATE_PLAYERCOLLIDE, ACTIVATE_PLAYERPRESS, ACTIVATE_SHOT,
308 ACTIVATE_NOMONSTER or ACTIVATE_MONSTERCOLLIDE, 0
309 );
311 mp2df_trig: array [MP_CLOSEDOOR..MP_EXIT] of Byte = (
312 TRIGGER_CLOSETRAP, TRIGGER_OPENDOOR, TRIGGER_DOOR,
313 TRIGGER_PRESS, TRIGGER_PRESS, // random, extender
314 TRIGGER_ONOFF, TRIGGER_DAMAGE, TRIGGER_TELEPORT, TRIGGER_EXIT
315 );
317 type
318 Tile = record
319 t, s, x, y: Integer;
320 sx, sy: Single;
321 x0, y0, x1, y1: Integer;
322 // ------- //
323 door: Boolean; // door
324 switch: Boolean; // switchable door
325 id: Integer; // door panel id
326 did: Integer; // switchable door id
327 end;
329 Texture = record
330 name: AnsiString;
331 w, h: Integer;
332 id: Integer;
333 flags: Integer;
334 end;
336 var
337 name, desc, music, sky: AnsiString;
338 width, height: Integer;
339 textures: array of Texture;
340 tiles: array of Tile;
341 havePanels: Boolean;
342 haveItems: Boolean;
343 haveAreas: Boolean;
344 haveTriggers: Boolean;
346 (* --------- Reader --------- *)
348 procedure ReadMap (fname: AnsiString);
349 var f: TextFile; i, n: Integer;
351 procedure ReadInt (var i: Integer);
352 var s: AnsiString;
353 begin
354 ReadLn(f, s);
355 i := StrToInt(s);
356 end;
358 procedure ReadFloat (var i: Single);
359 var s: AnsiString;
360 begin
361 ReadLn(f, s);
362 i := StrToFloat(s);
363 end;
365 begin
366 Assign(f, fname);
367 Reset(f);
368 ReadLn(f, name);
369 ReadLn(f, desc);
370 ReadInt(width);
371 ReadInt(height);
372 ReadLn(f, music);
373 ReadLn(f, sky);
374 ReadInt(n);
375 SetLength(textures, n);
376 for i := 1 to n - 1 do
377 begin
378 ReadLn(f, textures[i].name);
379 end;
380 i := 0;
381 while eof(f) = false do
382 begin
383 i := Length(tiles);
384 SetLength(tiles, i + 1);
385 with tiles[i] do
386 begin
387 ReadInt(t);
388 ReadInt(s);
389 ReadInt(x);
390 ReadInt(y);
391 if (t >= 44) and (t <= 48) then
392 begin
393 ReadFloat(sx);
394 ReadFloat(sy);
395 if s <> 8 then
396 begin
397 ReadInt(x0);
398 ReadInt(y0);
399 if s <> 7 then
400 begin
401 ReadInt(x1);
402 ReadInt(y1);
403 end
404 end
405 end
406 end
407 end;
408 Close(f);
409 end;
411 (* --------- Analyse --------- *)
413 function isCollide (x0, y0, w0, h0, x1, y1, w1, h1: Integer): Boolean;
414 var xx0, yy0, xx1, yy1: Integer;
415 begin
416 xx0 := x0 + w0 - 1;
417 yy0 := y0 + h0 - 1;
418 xx1 := x1 + w1 - 1;
419 yy1 := y1 + h1 - 1;
420 result := (xx0 >= x1) and (x0 <= xx1) and (yy0 >= y1) and (y0 <= yy1)
421 end;
423 procedure Analyse;
424 var i, j, t, x, y, w, h, xx, yy, ww, hh: Integer; n: AnsiString; s: PSDL_Surface;
425 begin
426 if sky = '*NO_BACKGROUND' then sky := '';
427 if music = '*NO_MUSIC' then music := '';
428 textures[0].w := 16;
429 textures[0].h := 16;
430 textures[0].id := 0;
431 textures[0].flags := PANEL_FLAG_HIDE;
432 for i := 1 to High(textures) do
433 begin
434 n := StringReplace(textures[i].name, '\', '/', [rfReplaceAll]);
435 s := IMG_Load(PChar(n));
436 if s <> nil then
437 begin
438 textures[i].w := s.w;
439 textures[i].h := s.h;
440 SDL_FreeSurface(s);
441 end
442 else
443 begin
444 textures[i].w := 16;
445 textures[i].h := 16;
446 WriteLn('warning: texture ', n, ' not loaded: ', IMG_GetError())
447 end;
448 textures[i].id := i - 1;
449 textures[i].flags := 0;
450 end;
451 for i := 0 to High(tiles) do
452 begin
453 tiles[i].id := -1;
454 case tiles[i].t of
455 MP_WALL..MP_LIFTRIGHT: havePanels := true;
456 MP_MEDKIT_SMALL..MP_WEAPON_SUPERPULEMET, MP_INVIS, MP_SUIT: haveItems := true;
457 MP_DMPOINT..MP_BLUEFLAG: haveAreas := true;
458 MP_COLLIDE..MP_NOACT:
459 begin
460 haveTriggers := true;
461 t := tiles[i].s;
462 if (t in [MP_CLOSEDOOR..MP_DOOR]) or (t >= 101) and (t <= 400) or (t >= 501) and (t <= 800) then
463 begin
464 x := tiles[i].x0;
465 y := tiles[i].y0;
466 w := tiles[i].x1 - tiles[i].x0;
467 h := tiles[i].y1 - tiles[i].y0;
468 for j := 0 to High(tiles) do
469 begin
470 if tiles[j].t = MP_WALL then
471 begin
472 xx := tiles[j].x;
473 yy := tiles[j].y;
474 ww := textures[tiles[j].s].w;
475 hh := textures[tiles[j].s].h;
476 if isCollide(x, y, w, h, xx, yy, ww, hh) then
477 begin
478 tiles[j].door := tiles[j].t = MP_WALL;
479 if tiles[j].door and ((t = MP_DOOR) or ((t >= 101) and (t <= 400))) then
480 tiles[j].switch := true
481 end
482 end
483 end
484 end
485 end
486 else assert(false)
487 end;
488 end;
489 j := 0;
490 for i := 0 to High(tiles) do
491 begin
492 tiles[i].did := -1;
493 if tiles[i].switch then
494 begin
495 tiles[i].did := j;
496 Inc(j);
497 end
498 end;
499 // TODO merge tiles to panels, but this can be done by df editor so...
500 end;
502 (* --------- Writer --------- *)
504 var
505 wrCount: Integer;
506 wrFixup: Integer;
507 wrTag: Integer;
508 wrPanels: Integer;
509 wrTriggers: Integer;
510 wrX: Integer;
512 procedure WriteBytes (f: TFileStream; x: array of Byte);
513 begin
514 f.Write(x, Length(x));
515 Inc(wrCount, Length(x))
516 end;
518 procedure WriteChars(f: TFileStream; x: array of Char);
519 var i: Integer;
520 begin
521 for i := 0 to High(x) do
522 begin
523 WriteBytes(f, [Ord(x[i])])
524 end
525 end;
527 procedure WriteAnsiString (f: TFileStream; s: AnsiString; maxlen: Integer);
528 var i, len: Integer;
529 begin
530 Assert(f <> nil);
531 Assert(maxlen >= 0);
532 len := min(Length(s), maxlen);
533 i := 1;
534 while i <= len do
535 begin
536 WriteChars(f, [s[i]]);
537 Inc(i)
538 end;
539 while i <= maxlen do
540 begin
541 WriteChars(f, [#0]);
542 Inc(i)
543 end;
544 end;
546 procedure WriteInt (f: TFileStream; x: Integer);
547 var a, b, c, d: Integer;
548 begin
549 a := x and $FF;
550 b := x >> 8 and $FF;
551 c := x >> 16 and $FF;
552 d := x >> 24 and $FF;
553 WriteBytes(f, [a, b, c, d])
554 end;
556 procedure WriteInt16 (f: TFileStream; x: Integer);
557 var a, b: Integer;
558 begin
559 a := x and $FF;
560 b := x >> 8 and $FF;
561 WriteBytes(f, [a, b])
562 end;
564 procedure BeginBlock (f: TFileStream; t: Byte);
565 begin
566 Assert(wrFixup = 0);
567 WriteBytes(f, [t]);
568 WriteInt(f, 0);
569 wrFixup := f.Position;
570 WriteInt(f, -1);
571 wrCount := 0
572 end;
574 procedure EndBlock(f: TFileStream);
575 var pos: Integer;
576 begin
577 pos := f.Position;
578 f.Position := wrFixup;
579 WriteInt(f, wrCount);
580 f.Position := pos;
581 wrCount := 0;
582 wrFixup := 0
583 end;
585 procedure WriteHeader (f: TFileStream; name, author, desc, mus, sky: AnsiString; w, h: Integer);
586 begin
587 BeginBlock(f, 7);
588 WriteAnsiString(f, name, 32);
589 WriteAnsiString(f, author, 32);
590 WriteAnsiString(f, desc, 256);
591 WriteAnsiString(f, mus, 64);
592 WriteAnsiString(f, sky, 64);
593 WriteInt16(f, w);
594 WriteInt16(f, h);
595 EndBlock(f)
596 end;
598 procedure WriteTexture (f: TFileStream; name: AnsiString; anim: Byte);
599 begin
600 WriteAnsiString(f, name, 64);
601 WriteBytes(f, [anim]);
602 end;
604 procedure WritePanel (f: TFileStream; x, y, w, h, tex, typ, alpha, flags: Integer);
605 begin
606 if (tex < 0) or (tex > High(textures)) then
607 begin
608 WriteLn('warning: panel ', wrPanels, ' [', x, 'x', y, ':', w, 'x', h, ':typ=', typ, '] have invalid texture id ', tex);
609 tex := 0
610 end;
611 WriteInt(f, x);
612 WriteInt(f, y);
613 WriteInt16(f, w);
614 WriteInt16(f, h);
615 WriteInt16(f, tex);
616 WriteInt16(f, typ);
617 WriteBytes(f, [alpha, flags]);
618 Inc(wrPanels)
619 end;
621 procedure WriteItem (f: TFileStream; x, y: Integer; typ, flags: Byte);
622 begin
623 WriteInt(f, x);
624 WriteInt(f, y);
625 WriteBytes(f, [typ, flags]);
626 end;
628 procedure WriteArea (f: TFileStream; x, y: Integer; typ, dir: Byte);
629 begin
630 WriteInt(f, x);
631 WriteInt(f, y);
632 WriteBytes(f, [typ, dir]);
633 end;
635 procedure WriteEnd (f: TFileStream);
636 begin
637 BeginBlock(f, 0);
638 EndBlock(f)
639 end;
641 function BoolToInt (x: Boolean): Integer;
642 begin
643 if x then result := 1 else result := 0
644 end;
646 procedure BeginTrigger (f: TFileStream; x, y, w, h, enabled, texpan, typ, act, keys: Integer);
647 begin
648 assert(f <> nil);
649 assert(typ in [TRIGGER_EXIT..TRIGGER_MAX]);
650 assert(wrTag = 0);
651 WriteInt(f, x);
652 WriteInt(f, y);
653 WriteInt16(f, w);
654 WriteInt16(f, h);
655 WriteBytes(f, [enabled]);
656 WriteInt(f, texpan);
657 WriteBytes(f, [typ, act, keys]);
658 wrTag := typ
659 end;
661 procedure EndTrigger (f: TFileStream);
662 var i: Integer;
663 begin
664 assert(f <> nil);
665 for i := wrCount mod 148 to 148 - 1 do
666 begin
667 WriteBytes(f, [0])
668 end;
669 assert(wrCount mod 148 = 0);
670 Inc(wrTriggers);
671 wrTag := 0
672 end;
674 procedure ExitTrigger (f: TFileStream; map: AnsiString);
675 begin
676 assert(f <> nil);
677 assert(wrTag = TRIGGER_EXIT);
678 WriteAnsiString(f, map, 16);
679 wrTag := 0
680 end;
682 procedure TeleportTrigger (f: TFileStream; x, y: Integer; d2d, silent: Boolean; dir: Integer);
683 begin
684 assert(f <> nil);
685 assert(wrTag = TRIGGER_TELEPORT);
686 WriteInt(f, x);
687 WriteInt(f, y);
688 WriteBytes(f, [BoolToInt(d2d), BoolToInt(silent), dir]);
689 wrTag := 0
690 end;
692 procedure DoorTrigger (f: TFileStream; panelid: Integer; nosound, d2d: Boolean);
693 begin
694 assert(f <> nil);
695 assert(wrTag in [TRIGGER_OPENDOOR..TRIGGER_TRAP, TRIGGER_LIFTUP..TRIGGER_LIFT]);
696 WriteInt(f, panelid);
697 WriteBytes(f, [BoolToInt(nosound), BoolToInt(d2d)]);
698 wrTag := 0
699 end;
701 procedure SwitchTrigger (f: TFileStream; x, y, w, h, wait, count, monsterid: Integer; random: Boolean);
702 begin
703 assert(f <> nil);
704 assert(wrTag in [TRIGGER_PRESS, TRIGGER_ON..TRIGGER_ONOFF]);
705 WriteInt(f, x);
706 WriteInt(f, y);
707 WriteInt16(f, w);
708 WriteInt16(f, h);
709 WriteInt16(f, wait);
710 WriteInt16(f, count);
711 WriteInt(f, monsterid);
712 WriteBytes(f, [BoolToInt(random)]);
713 wrTag := 0
714 end;
716 procedure DamageTrigger (f: TFileStream; damage, interval, typ: Integer);
717 begin
718 assert(f <> nil);
719 assert(wrTag = TRIGGER_DAMAGE);
720 WriteInt16(f, damage);
721 WriteInt16(f, interval);
722 WriteBytes(f, [typ]);
723 wrTag := 0
724 end;
726 function SecToTick (sec: Integer): Integer;
727 begin
728 result := sec * 1000 div 28;
729 end;
731 procedure WriteMap (fname: AnsiString);
732 var f: TFileStream; i, j, typ, wait: Integer; t: Tile;
733 begin
734 f := TFileStream.Create(fname, fmCreate);
735 WriteLn('magic...');
736 WriteBytes(f, [Ord('M'), Ord('A'), Ord('P'), 1]);
737 WriteLn('header...');
738 WriteHeader(f, name, '', desc, music, sky, width, height);
739 if High(textures) > 0 then
740 begin
741 WriteLn('textures...');
742 BeginBlock(f, 1);
743 for i := 1 to High(textures) do
744 WriteTexture(f, ':' + textures[i].name, 0);
745 EndBlock(f);
746 end;
747 if havePanels then
748 begin
749 WriteLn('panels...');
750 BeginBlock(f, 2);
751 for i := 0 to High(tiles) do
752 begin
753 t := tiles[i];
754 if t.t in [MP_WALL..MP_LIFTRIGHT] then
755 begin
756 tiles[i].id := wrPanels;
757 if t.door then
758 WritePanel(f, t.x, t.y, textures[t.s].w, textures[t.s].h, textures[t.s].id, PANEL_CLOSEDOOR, 0, textures[t.s].flags)
759 else if t.t in [MP_LIFTUP..MP_LIFTRIGHT] then
760 WritePanel(f, t.x, t.y, 16, 16, 0, mp2df_wall[t.t], 0, PANEL_FLAG_HIDE)
761 else if t.t in [MP_WATER..MP_ACID2] then
762 WritePanel(f, t.x, t.y, textures[t.s].w, textures[t.s].h, textures[t.s].id, mp2df_wall[t.t], 0, textures[t.s].flags or PANEL_FLAG_WATERTEXTURES)
763 else
764 WritePanel(f, t.x, t.y, textures[t.s].w, textures[t.s].h, textures[t.s].id, mp2df_wall[t.t], 0, textures[t.s].flags)
765 end
766 end;
767 EndBlock(f)
768 end;
769 if haveItems then
770 begin
771 WriteLn('items...');
772 BeginBlock(f, 3);
773 for i := 0 to High(tiles) do
774 begin
775 t := tiles[i];
776 if t.t in [MP_MEDKIT_SMALL..MP_WEAPON_SUPERPULEMET, MP_INVIS, MP_SUIT] then
777 WriteItem(f, t.x, t.y, mp2df_item[t.t], 0)
778 end;
779 EndBlock(f)
780 end;
781 if haveAreas then
782 begin
783 WriteLn('areas...');
784 BeginBlock(f, 4);
785 for i := 0 to High(tiles) do
786 begin
787 t := tiles[i];
788 case t.t of
789 MP_DMPOINT..MP_BLUETEAMPOINT: WriteArea(f, t.x, t.y + 12, mp2df_area[t.t], 0);
790 MP_REDFLAG, MP_BLUEFLAG: WriteArea(f, t.x + 8, t.y - 4, mp2df_area[t.t], 1);
791 end
792 end;
793 EndBlock(f)
794 end;
795 if haveTriggers then
796 begin
797 WriteLn('triggers...');
798 BeginBlock(f, 6);
799 for i := 0 to High(tiles) do
800 begin
801 t := tiles[i];
802 if t.t in [MP_COLLIDE..MP_NOACT] then
803 begin
804 case t.s of
805 MP_EXIT:
806 begin
807 BeginTrigger(f, t.x, t.y, Round(16 * t.sx), Round(16 * t.sy), 1, -1, TRIGGER_EXIT, mp2df_act[t.t], 0);
808 ExitTrigger(f, '');
809 EndTrigger(f)
810 end;
811 MP_TELEPORT:
812 begin
813 BeginTrigger(f, t.x, t.y, Round(16 * t.sx), Round(16 * t.sy), 1, -1, TRIGGER_TELEPORT, mp2df_act[t.t], 0);
814 TeleportTrigger(f, t.x0, t.y0 + 32, true, false, 0);
815 EndTrigger(f)
816 end;
817 MP_CLOSEDOOR, MP_OPENDOOR:
818 begin
819 for j := 0 to High(tiles) do
820 begin
821 if tiles[j].door and isCollide(t.x0, t.y0, t.x1 - t.x0, t.y1 - t.y0, tiles[j].x, tiles[j].y, textures[tiles[j].s].w, textures[tiles[j].s].h) then
822 begin
823 if tiles[j].switch then
824 begin
825 // sync with MP_DOOR
826 BeginTrigger(f, t.x, t.y, Round(16 * t.sx), Round(16 * t.sy), 1, -1, TRIGGER_PRESS, mp2df_act[t.t], 0);
827 SwitchTrigger(f, tiles[j].did * 32, IfThen(t.s = MP_CLOSEDOOR, -16, -32), 32, 16, 0, 1, 0, false);
828 EndTrigger(f)
829 end
830 else
831 begin
832 BeginTrigger(f, t.x, t.y, Round(16 * t.sx), Round(16 * t.sy), 1, -1, mp2df_trig[t.s], mp2df_act[t.t], 0);
833 DoorTrigger(f, tiles[j].id, false, false);
834 EndTrigger(f)
835 end
836 end
837 end
838 end;
839 MP_DOOR, 101..400:
840 begin
841 wait := IfThen(t.s = MP_DOOR, 0, t.s - 100);
842 // button: activate sequence in not locked
843 BeginTrigger(f, t.x, t.y, Round(16 * t.sx), Round(16 * t.sy), 1, -1, TRIGGER_PRESS, mp2df_act[t.t], 0);
844 SwitchTrigger(f, wrX, -80, 32, 16, 0, 1, 0, false);
845 EndTrigger(f);
846 // start: activate group + timer
847 BeginTrigger(f, wrX, -80, 16, 16, 1, -1, TRIGGER_PRESS, 0, 0);
848 SwitchTrigger(f, wrX, -64, 32, 32, 0, 1, 0, false);
849 EndTrigger(f);
850 // start: lock start
851 BeginTrigger(f, wrX + 16, -80, 16, 16, 1, -1, TRIGGER_OFF, 0, 0);
852 SwitchTrigger(f, wrX, -80, 16, 16, 0, 1, 0, false);
853 EndTrigger(f);
854 for j := 0 to High(tiles) do
855 begin
856 if tiles[j].switch and isCollide(t.x0, t.y0, t.x1 - t.x0, t.y1 - t.y0, tiles[j].x, tiles[j].y, textures[tiles[j].s].w, textures[tiles[j].s].h) then
857 begin
858 // group: activate both branches
859 BeginTrigger(f, wrX, -64, 16, 16, 1, -1, TRIGGER_PRESS, 0, 0);
860 SwitchTrigger(f, tiles[j].did * 32, -32, 32, 32, 0, 1, 0, false);
861 EndTrigger(f)
862 end
863 end;
864 // timer: wait & activate group
865 BeginTrigger(f, wrX, -48, 16, 16, 1, -1, TRIGGER_PRESS, 0, 0);
866 SwitchTrigger(f, wrX, -64, 32, 16, SecToTick(wait), 1, 0, false);
867 EndTrigger(f);
868 // timer: wait & unlock start
869 BeginTrigger(f, wrX + 16, -48, 16, 16, 1, -1, TRIGGER_ON, 0, 0);
870 SwitchTrigger(f, wrX, -80, 16, 16, SecToTick(wait), 1, 0, false);
871 EndTrigger(f);
872 Inc(wrX, 32)
873 end;
874 MP_EXTENDER, MP_RANDOM, MP_SWITCH, 501..800:
875 begin
876 wait := IfThen(t.s in [MP_EXTENDER, MP_RANDOM, MP_SWITCH], 0, t.s - 500);
877 typ := IfThen(t.s in [MP_EXTENDER, MP_RANDOM, MP_SWITCH], mp2df_trig[t.s], TRIGGER_PRESS);
878 BeginTrigger(f, t.x, t.y, Round(16 * t.sx), Round(16 * t.sy), 1, -1, typ, mp2df_act[t.t], 0);
879 SwitchTrigger(f, t.x0, t.y0, t.x1 - t.x0, t.y1 - t.y0, SecToTick(wait), 1, 0, t.s = MP_RANDOM);
880 EndTrigger(f)
881 end;
882 MP_DAMAGE:
883 begin
884 if (t.x = t.x0) and (t.y = t.y0) and (16 * t.sx = t.x1 - t.x0) and (16 * t.sy = t.y1 - t.y0) then
885 begin
886 BeginTrigger(f, t.x, t.y, t.x1 - t.x0, t.y1 - t.y0, 1, -1, TRIGGER_DAMAGE, mp2df_act[t.t], 0);
887 DamageTrigger(f, 666, 0, 0);
888 EndTrigger(f)
889 end
890 else
891 begin
892 // TODO find non intersectable location for activation
893 BeginTrigger(f, t.x, t.y, Round(16 * t.sx), Round(16 * t.sy), 1, -1, TRIGGER_PRESS, mp2df_act[t.t], 0);
894 SwitchTrigger(f, t.x0, t.y0, 16, 16, 0, 1, 0, false);
895 EndTrigger(f);
896 BeginTrigger(f, t.x0, t.y0, t.x1 - t.x0, t.y1 - t.y0, 1, -1, TRIGGER_DAMAGE, 0, 0);
897 DamageTrigger(f, 999, 0, 0);
898 EndTrigger(f);
899 for j := 0 to High(tiles) do
900 if (j <> i) and isCollide(t.x0, t.y0, 16, 16, tiles[j].x0, tiles[j].y0, tiles[j].x1 - tiles[j].x0, tiles[j].x1 - tiles[j].y0) then
901 WriteLn('waring: trigger damage may activate triggers at ', t.x0, 'x', t.y0)
902 end
903 end
904 else WriteLn('warning: unknown MP trigger ', t.s)
905 end
906 end
907 end;
908 // MP_DOOR intermediate triggers
909 for i := 0 to High(tiles) do
910 begin
911 if tiles[i].switch then
912 begin
913 // invert state
914 BeginTrigger(f, tiles[i].did * 32 + 16, -32, 16, 32, 1, -1, TRIGGER_ONOFF, 0, 0);
915 SwitchTrigger(f, tiles[i].did * 32, -32, 16, 32, 0, 1, 0, false);
916 EndTrigger(f);
917 // open trap
918 BeginTrigger(f, tiles[i].did * 32, -32, 16, 16, 1, -1, TRIGGER_OPENDOOR, 0, 0);
919 DoorTrigger(f, tiles[i].id, false, false);
920 EndTrigger(f);
921 // close trap
922 BeginTrigger(f, tiles[i].did * 32, -16, 16, 16, 0, -1, TRIGGER_CLOSETRAP, 0, 0);
923 DoorTrigger(f, tiles[i].id, false, false);
924 EndTrigger(f)
925 end
926 end;
927 EndBlock(f)
928 end;
929 WriteEnd(f);
930 f.Free;
931 end;
933 (* --------- Init --------- *)
935 var
936 inputFile: AnsiString;
937 outputFile: AnsiString = 'MAP01';
938 listTextures: Boolean = false;
940 procedure PrintTextureList;
941 var i: Integer; n: AnsiString;
942 begin
943 for i := 1 to High(textures) do
944 begin
945 n := StringReplace(textures[i].name, '\', '/', [rfReplaceAll]);
946 WriteLn(n)
947 end
948 end;
950 procedure Help;
951 begin
952 WriteLn('Usage: mp2df [OPTION] FILE.dlv [OUTPUT]');
953 WriteLn('Options:');
954 WriteLn(' -l list textures used on map and exit');
955 WriteLn(' -h show this help');
956 Halt(0)
957 end;
959 procedure ParseArgs;
960 var i, n: Integer; done: Boolean; str: AnsiString;
961 begin
962 i := 1; done := false;
963 while (i <= ParamCount) and (not done) do
964 begin
965 str := ParamStr(i);
966 done := (Length(str) = 0) or (str[1] <> '-');
967 if not done then
968 begin
969 case str of
970 '-l': listTextures := true;
971 '-h', '-?': Help;
972 else
973 WriteLn('mp2df: unknown argument ', str);
974 Halt(1)
975 end;
976 Inc(i)
977 end
978 end;
979 n := ParamCount - i + 1;
980 if n = 1 then
981 begin
982 inputFile := ParamStr(i);
983 end
984 else if (n = 2) and (not listTextures) then
985 begin
986 inputFile := ParamStr(i);
987 outputFile := ParamStr(i + 1);
988 end
989 else
990 begin
991 WriteLn('mp2df: you may specify input file and output file only, use -h to know more');
992 Halt(1)
993 end;
994 if inputFile = '' then
995 begin
996 WriteLn('mp2df: empty input file path');
997 Halt(1);
998 end;
999 if outputFile = '' then
1000 begin
1001 WriteLn('mp2df: empty output file path');
1002 Halt(1);
1003 end
1004 end;
1006 begin
1007 ParseArgs;
1008 ReadMap(inputFile);
1009 if listTextures then
1010 begin
1011 PrintTextureList
1012 end
1013 else
1014 begin
1015 Analyse;
1016 WriteMap(outputFile)
1017 end;
1018 Halt(0)
1019 end.