DEADSOFTWARE

update readme
[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;
342 (* --------- Reader --------- *)
344 procedure ReadMap (fname: AnsiString);
345 var f: TextFile; i, n: Integer;
347 procedure ReadInt (var i: Integer);
348 var s: AnsiString;
349 begin
350 ReadLn(f, s);
351 i := StrToInt(s);
352 end;
354 procedure ReadFloat (var i: Single);
355 var s: AnsiString;
356 begin
357 ReadLn(f, s);
358 i := StrToFloat(s);
359 end;
361 begin
362 Assign(f, fname);
363 Reset(f);
364 ReadLn(f, name);
365 ReadLn(f, desc);
366 ReadInt(width);
367 ReadInt(height);
368 ReadLn(f, music);
369 ReadLn(f, sky);
370 ReadInt(n);
371 SetLength(textures, n);
372 for i := 1 to n - 1 do
373 begin
374 ReadLn(f, textures[i].name);
375 end;
376 i := 0;
377 while eof(f) = false do
378 begin
379 i := Length(tiles);
380 SetLength(tiles, i + 1);
381 with tiles[i] do
382 begin
383 ReadInt(t);
384 ReadInt(s);
385 ReadInt(x);
386 ReadInt(y);
387 if (t >= 44) and (t <= 48) then
388 begin
389 ReadFloat(sx);
390 ReadFloat(sy);
391 if s <> 8 then
392 begin
393 ReadInt(x0);
394 ReadInt(y0);
395 if s <> 7 then
396 begin
397 ReadInt(x1);
398 ReadInt(y1);
399 end
400 end
401 end
402 end
403 end;
404 Close(f);
405 end;
407 (* --------- Analyse --------- *)
409 function isCollide (x0, y0, w0, h0, x1, y1, w1, h1: Integer): Boolean;
410 var xx0, yy0, xx1, yy1: Integer;
411 begin
412 xx0 := x0 + w0 - 1;
413 yy0 := y0 + h0 - 1;
414 xx1 := x1 + w1 - 1;
415 yy1 := y1 + h1 - 1;
416 result := (xx0 >= x1) and (x0 <= xx1) and (yy0 >= y1) and (y0 <= yy1)
417 end;
419 procedure Analyse;
420 var i, j, t, x, y, w, h, xx, yy, ww, hh: Integer; n: AnsiString; s: PSDL_Surface;
421 begin
422 if sky = '*NO_BACKGROUND' then sky := '';
423 if music = '*NO_MUSIC' then music := '';
424 textures[0].w := 16;
425 textures[0].h := 16;
426 textures[0].id := 0;
427 textures[0].flags := PANEL_FLAG_HIDE;
428 for i := 1 to High(textures) do
429 begin
430 n := StringReplace(textures[i].name, '\', '/', [rfReplaceAll]);
431 s := IMG_Load(PChar(n));
432 if s <> nil then
433 begin
434 textures[i].w := s.w;
435 textures[i].h := s.h;
436 SDL_FreeSurface(s);
437 end
438 else
439 begin
440 textures[i].w := 16;
441 textures[i].h := 16;
442 WriteLn('warning: texture ', n, ' not loaded: ', IMG_GetError())
443 end;
444 textures[i].id := i - 1;
445 textures[i].flags := 0;
446 end;
447 for i := 0 to High(tiles) do
448 begin
449 tiles[i].id := -1;
450 if tiles[i].t in [MP_COLLIDE..MP_NOACT] then
451 begin
452 t := tiles[i].s;
453 if (t in [MP_CLOSEDOOR..MP_DOOR]) or (t >= 101) and (t <= 400) or (t >= 501) and (t <= 800) then
454 begin
455 x := tiles[i].x0;
456 y := tiles[i].y0;
457 w := tiles[i].x1 - tiles[i].x0;
458 h := tiles[i].y1 - tiles[i].y0;
459 for j := 0 to High(tiles) do
460 begin
461 if tiles[j].t = MP_WALL then
462 begin
463 xx := tiles[j].x;
464 yy := tiles[j].y;
465 ww := textures[tiles[j].s].w;
466 hh := textures[tiles[j].s].h;
467 if isCollide(x, y, w, h, xx, yy, ww, hh) then
468 begin
469 tiles[j].door := tiles[j].t = MP_WALL;
470 if tiles[j].door and ((t = MP_DOOR) or ((t >= 101) and (t <= 400))) then
471 tiles[j].switch := true;
472 end
473 end
474 end
475 end
476 end
477 end;
478 j := 0;
479 for i := 0 to High(tiles) do
480 begin
481 tiles[i].did := -1;
482 if tiles[i].switch then
483 begin
484 tiles[i].did := j;
485 Inc(j);
486 end
487 end;
488 // TODO merge tiles to panels, but this can be done by df editor so...
489 end;
491 (* --------- Writer --------- *)
493 var
494 wrCount: Integer;
495 wrFixup: Integer;
496 wrTag: Integer;
497 wrPanels: Integer;
498 wrTriggers: Integer;
499 wrX: Integer;
501 procedure WriteBytes (f: TFileStream; x: array of Byte);
502 begin
503 f.Write(x, Length(x));
504 Inc(wrCount, Length(x))
505 end;
507 procedure WriteChars(f: TFileStream; x: array of Char);
508 var i: Integer;
509 begin
510 for i := 0 to High(x) do
511 begin
512 WriteBytes(f, [Ord(x[i])])
513 end
514 end;
516 procedure WriteAnsiString (f: TFileStream; s: AnsiString; maxlen: Integer);
517 var i, len: Integer;
518 begin
519 Assert(f <> nil);
520 Assert(maxlen >= 0);
521 len := min(Length(s), maxlen);
522 i := 1;
523 while i <= len do
524 begin
525 WriteChars(f, [s[i]]);
526 Inc(i)
527 end;
528 while i <= maxlen do
529 begin
530 WriteChars(f, [#0]);
531 Inc(i)
532 end;
533 end;
535 procedure WriteInt (f: TFileStream; x: Integer);
536 var a, b, c, d: Integer;
537 begin
538 a := x and $FF;
539 b := x >> 8 and $FF;
540 c := x >> 16 and $FF;
541 d := x >> 24 and $FF;
542 WriteBytes(f, [a, b, c, d])
543 end;
545 procedure WriteInt16 (f: TFileStream; x: Integer);
546 var a, b: Integer;
547 begin
548 a := x and $FF;
549 b := x >> 8 and $FF;
550 WriteBytes(f, [a, b])
551 end;
553 procedure BeginBlock (f: TFileStream; t: Byte);
554 begin
555 Assert(wrFixup = 0);
556 WriteBytes(f, [t]);
557 WriteInt(f, 0);
558 wrFixup := f.Position;
559 WriteInt(f, -1);
560 wrCount := 0
561 end;
563 procedure EndBlock(f: TFileStream);
564 var pos: Integer;
565 begin
566 pos := f.Position;
567 f.Position := wrFixup;
568 WriteInt(f, wrCount);
569 f.Position := pos;
570 wrCount := 0;
571 wrFixup := 0
572 end;
574 procedure WriteHeader (f: TFileStream; name, author, desc, mus, sky: AnsiString; w, h: Integer);
575 begin
576 BeginBlock(f, 7);
577 WriteAnsiString(f, name, 32);
578 WriteAnsiString(f, author, 32);
579 WriteAnsiString(f, desc, 256);
580 WriteAnsiString(f, mus, 64);
581 WriteAnsiString(f, sky, 64);
582 WriteInt16(f, w);
583 WriteInt16(f, h);
584 EndBlock(f)
585 end;
587 procedure WriteTexture (f: TFileStream; name: AnsiString; anim: Byte);
588 begin
589 WriteAnsiString(f, name, 64);
590 WriteBytes(f, [anim]);
591 end;
593 procedure WritePanel (f: TFileStream; x, y, w, h, tex, typ, alpha, flags: Integer);
594 begin
595 if (tex < 0) or (tex > High(textures)) then
596 begin
597 WriteLn('warning: panel ', wrPanels, ' [', x, 'x', y, ':', w, 'x', h, ':typ=', typ, '] have invalid texture id ', tex);
598 tex := 0
599 end;
600 WriteInt(f, x);
601 WriteInt(f, y);
602 WriteInt16(f, w);
603 WriteInt16(f, h);
604 WriteInt16(f, tex);
605 WriteInt16(f, typ);
606 WriteBytes(f, [alpha, flags]);
607 Inc(wrPanels)
608 end;
610 procedure WriteItem (f: TFileStream; x, y: Integer; typ, flags: Byte);
611 begin
612 WriteInt(f, x);
613 WriteInt(f, y);
614 WriteBytes(f, [typ, flags]);
615 end;
617 procedure WriteArea (f: TFileStream; x, y: Integer; typ, dir: Byte);
618 begin
619 WriteInt(f, x);
620 WriteInt(f, y);
621 WriteBytes(f, [typ, dir]);
622 end;
624 procedure WriteEnd (f: TFileStream);
625 begin
626 BeginBlock(f, 0);
627 EndBlock(f)
628 end;
630 function BoolToInt (x: Boolean): Integer;
631 begin
632 if x then result := 1 else result := 0
633 end;
635 procedure BeginTrigger (f: TFileStream; x, y, w, h, enabled, texpan, typ, act, keys: Integer);
636 begin
637 assert(f <> nil);
638 assert(typ in [TRIGGER_EXIT..TRIGGER_MAX]);
639 assert(wrTag = 0);
640 WriteInt(f, x);
641 WriteInt(f, y);
642 WriteInt16(f, w);
643 WriteInt16(f, h);
644 WriteBytes(f, [enabled]);
645 WriteInt(f, texpan);
646 WriteBytes(f, [typ, act, keys]);
647 wrTag := typ
648 end;
650 procedure EndTrigger (f: TFileStream);
651 var i: Integer;
652 begin
653 assert(f <> nil);
654 for i := wrCount mod 148 to 148 - 1 do
655 begin
656 WriteBytes(f, [0])
657 end;
658 assert(wrCount mod 148 = 0);
659 Inc(wrTriggers);
660 wrTag := 0
661 end;
663 procedure ExitTrigger (f: TFileStream; map: AnsiString);
664 begin
665 assert(f <> nil);
666 assert(wrTag = TRIGGER_EXIT);
667 WriteAnsiString(f, map, 16);
668 wrTag := 0
669 end;
671 procedure TeleportTrigger (f: TFileStream; x, y: Integer; d2d, silent: Boolean; dir: Integer);
672 begin
673 assert(f <> nil);
674 assert(wrTag = TRIGGER_TELEPORT);
675 WriteInt(f, x);
676 WriteInt(f, y);
677 WriteBytes(f, [BoolToInt(d2d), BoolToInt(silent), dir]);
678 wrTag := 0
679 end;
681 procedure DoorTrigger (f: TFileStream; panelid: Integer; nosound, d2d: Boolean);
682 begin
683 assert(f <> nil);
684 assert(wrTag in [TRIGGER_OPENDOOR..TRIGGER_TRAP, TRIGGER_LIFTUP..TRIGGER_LIFT]);
685 WriteInt(f, panelid);
686 WriteBytes(f, [BoolToInt(nosound), BoolToInt(d2d)]);
687 wrTag := 0
688 end;
690 procedure SwitchTrigger (f: TFileStream; x, y, w, h, wait, count, monsterid: Integer; random: Boolean);
691 begin
692 assert(f <> nil);
693 assert(wrTag in [TRIGGER_PRESS, TRIGGER_ON..TRIGGER_ONOFF]);
694 WriteInt(f, x);
695 WriteInt(f, y);
696 WriteInt16(f, w);
697 WriteInt16(f, h);
698 WriteInt16(f, wait);
699 WriteInt16(f, count);
700 WriteInt(f, monsterid);
701 WriteBytes(f, [BoolToInt(random)]);
702 wrTag := 0
703 end;
705 procedure DamageTrigger (f: TFileStream; damage, interval, typ: Integer);
706 begin
707 assert(f <> nil);
708 assert(wrTag = TRIGGER_DAMAGE);
709 WriteInt16(f, damage);
710 WriteInt16(f, interval);
711 WriteBytes(f, [typ]);
712 wrTag := 0
713 end;
715 function SecToTick (sec: Integer): Integer;
716 begin
717 result := sec * 1000 div 28;
718 end;
720 procedure WriteMap (fname: AnsiString);
721 var f: TFileStream; i, j, typ, wait: Integer; t: Tile;
722 begin
723 f := TFileStream.Create(fname, fmCreate);
724 WriteLn('magic...');
725 WriteBytes(f, [Ord('M'), Ord('A'), Ord('P'), 1]);
726 WriteLn('header...');
727 WriteHeader(f, name, '', desc, music, sky, width, height);
728 WriteLn('textures...');
729 BeginBlock(f, 1);
730 for i := 1 to High(textures) do
731 begin
732 WriteTexture(f, ':' + textures[i].name, 0)
733 end;
734 EndBlock(f);
735 WriteLn('panels...');
736 BeginBlock(f, 2);
737 for i := 0 to High(tiles) do
738 begin
739 t := tiles[i];
740 if t.t in [MP_WALL..MP_LIFTRIGHT] then
741 begin
742 tiles[i].id := wrPanels;
743 if t.door then
744 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)
745 else if t.t in [MP_LIFTUP..MP_LIFTRIGHT] then
746 WritePanel(f, t.x, t.y, 16, 16, 0, mp2df_wall[t.t], 0, PANEL_FLAG_HIDE)
747 else if t.t in [MP_WATER..MP_ACID2] then
748 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)
749 else
750 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)
751 end
752 end;
753 EndBlock(f);
754 WriteLn('items...');
755 BeginBlock(f, 3);
756 for i := 0 to High(tiles) do
757 begin
758 t := tiles[i];
759 if t.t in [MP_MEDKIT_SMALL..MP_WEAPON_SUPERPULEMET, MP_INVIS, MP_SUIT] then
760 WriteItem(f, t.x, t.y, mp2df_item[t.t], 0)
761 end;
762 EndBlock(f);
763 WriteLn('areas...');
764 BeginBlock(f, 4);
765 for i := 0 to High(tiles) do
766 begin
767 t := tiles[i];
768 case t.t of
769 MP_DMPOINT..MP_BLUETEAMPOINT: WriteArea(f, t.x, t.y + 12, mp2df_area[t.t], 0);
770 MP_REDFLAG, MP_BLUEFLAG: WriteArea(f, t.x + 8, t.y - 4, mp2df_area[t.t], 1);
771 end
772 end;
773 EndBlock(f);
774 WriteLn('triggers...');
775 BeginBlock(f, 6);
776 for i := 0 to High(tiles) do
777 begin
778 t := tiles[i];
779 if t.t in [MP_COLLIDE..MP_NOACT] then
780 begin
781 case t.s of
782 MP_EXIT:
783 begin
784 BeginTrigger(f, t.x, t.y, Round(16 * t.sx), Round(16 * t.sy), 1, -1, TRIGGER_EXIT, mp2df_act[t.t], 0);
785 ExitTrigger(f, '');
786 EndTrigger(f)
787 end;
788 MP_TELEPORT:
789 begin
790 BeginTrigger(f, t.x, t.y, Round(16 * t.sx), Round(16 * t.sy), 1, -1, TRIGGER_TELEPORT, mp2df_act[t.t], 0);
791 TeleportTrigger(f, t.x0, t.y0 + 32, true, false, 0);
792 EndTrigger(f)
793 end;
794 MP_CLOSEDOOR, MP_OPENDOOR:
795 begin
796 for j := 0 to High(tiles) do
797 begin
798 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
799 begin
800 if tiles[j].switch then
801 begin
802 // sync with MP_DOOR
803 BeginTrigger(f, t.x, t.y, Round(16 * t.sx), Round(16 * t.sy), 1, -1, TRIGGER_PRESS, mp2df_act[t.t], 0);
804 SwitchTrigger(f, tiles[j].did * 32, IfThen(t.s = MP_CLOSEDOOR, -16, -32), 32, 16, 0, 1, 0, false);
805 EndTrigger(f)
806 end
807 else
808 begin
809 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);
810 DoorTrigger(f, tiles[j].id, false, false);
811 EndTrigger(f);
812 end
813 end
814 end
815 end;
816 MP_DOOR, 101..400:
817 begin
818 wait := IfThen(t.s = MP_DOOR, 0, t.s - 100);
819 // button: activate sequence in not locked
820 BeginTrigger(f, t.x, t.y, Round(16 * t.sx), Round(16 * t.sy), 1, -1, TRIGGER_PRESS, mp2df_act[t.t], 0);
821 SwitchTrigger(f, wrX, -80, 32, 16, 0, 1, 0, false);
822 EndTrigger(f);
823 // start: activate group + timer
824 BeginTrigger(f, wrX, -80, 16, 16, 1, -1, TRIGGER_PRESS, 0, 0);
825 SwitchTrigger(f, wrX, -64, 32, 32, 0, 1, 0, false);
826 EndTrigger(f);
827 // start: lock start
828 BeginTrigger(f, wrX + 16, -80, 16, 16, 1, -1, TRIGGER_OFF, 0, 0);
829 SwitchTrigger(f, wrX, -80, 16, 16, 0, 1, 0, false);
830 EndTrigger(f);
831 for j := 0 to High(tiles) do
832 begin
833 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
834 begin
835 // group: activate both branches
836 BeginTrigger(f, wrX, -64, 16, 16, 1, -1, TRIGGER_PRESS, 0, 0);
837 SwitchTrigger(f, tiles[j].did * 32, -32, 32, 32, 0, 1, 0, false);
838 EndTrigger(f);
839 end
840 end;
841 // timer: wait & activate group
842 BeginTrigger(f, wrX, -48, 16, 16, 1, -1, TRIGGER_PRESS, 0, 0);
843 SwitchTrigger(f, wrX, -64, 32, 16, SecToTick(wait), 1, 0, false);
844 EndTrigger(f);
845 // timer: wait & unlock start
846 BeginTrigger(f, wrX + 16, -48, 16, 16, 1, -1, TRIGGER_ON, 0, 0);
847 SwitchTrigger(f, wrX, -80, 16, 16, SecToTick(wait), 1, 0, false);
848 EndTrigger(f);
849 Inc(wrX, 32);
850 end;
851 MP_EXTENDER, MP_RANDOM, MP_SWITCH, 501..800:
852 begin
853 wait := IfThen(t.s in [MP_EXTENDER, MP_RANDOM, MP_SWITCH], 0, t.s - 500);
854 typ := IfThen(t.s in [MP_EXTENDER, MP_RANDOM, MP_SWITCH], mp2df_trig[t.s], TRIGGER_PRESS);
855 BeginTrigger(f, t.x, t.y, Round(16 * t.sx), Round(16 * t.sy), 1, -1, typ, mp2df_act[t.t], 0);
856 SwitchTrigger(f, t.x0, t.y0, t.x1 - t.x0, t.y1 - t.y0, SecToTick(wait), 1, 0, t.s = MP_RANDOM);
857 EndTrigger(f)
858 end;
859 MP_DAMAGE:
860 begin
861 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
862 begin
863 BeginTrigger(f, t.x, t.y, t.x1 - t.x0, t.y1 - t.y0, 1, -1, TRIGGER_DAMAGE, mp2df_act[t.t], 0);
864 DamageTrigger(f, 666, 0, 0);
865 EndTrigger(f)
866 end
867 else
868 begin
869 // TODO find non intersectable location for activation
870 BeginTrigger(f, t.x, t.y, Round(16 * t.sx), Round(16 * t.sy), 1, -1, TRIGGER_PRESS, mp2df_act[t.t], 0);
871 SwitchTrigger(f, t.x0, t.y0, 16, 16, 0, 1, 0, false);
872 EndTrigger(f);
873 BeginTrigger(f, t.x0, t.y0, t.x1 - t.x0, t.y1 - t.y0, 1, -1, TRIGGER_DAMAGE, 0, 0);
874 DamageTrigger(f, 999, 0, 0);
875 EndTrigger(f);
876 for j := 0 to High(tiles) do
877 begin
878 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
879 begin
880 WriteLn('waring: trigger damage may activate triggers at ', t.x0, 'x', t.y0);
881 end
882 end
883 end
884 end;
885 else WriteLn('warning: unknown MP trigger ', t.s)
886 end
887 end
888 end;
889 // MP_DOOR intermediate triggers
890 for i := 0 to High(tiles) do
891 begin
892 if tiles[i].switch then
893 begin
894 // invert state
895 BeginTrigger(f, tiles[i].did * 32 + 16, -32, 16, 32, 1, -1, TRIGGER_ONOFF, 0, 0);
896 SwitchTrigger(f, tiles[i].did * 32, -32, 16, 32, 0, 1, 0, false);
897 EndTrigger(f);
898 // open trap
899 BeginTrigger(f, tiles[i].did * 32, -32, 16, 16, 1, -1, TRIGGER_OPENDOOR, 0, 0);
900 DoorTrigger(f, tiles[i].id, false, false);
901 EndTrigger(f);
902 // close trap
903 BeginTrigger(f, tiles[i].did * 32, -16, 16, 16, 0, -1, TRIGGER_CLOSETRAP, 0, 0);
904 DoorTrigger(f, tiles[i].id, false, false);
905 EndTrigger(f);
906 end
907 end;
908 EndBlock(f);
909 WriteEnd(f);
910 f.Free;
911 end;
913 (* --------- Init --------- *)
915 var
916 inputFile: AnsiString;
917 outputFile: AnsiString = 'MAP01';
918 listTextures: Boolean = false;
920 procedure PrintTextureList;
921 var i: Integer; n: AnsiString;
922 begin
923 for i := 1 to High(textures) do
924 begin
925 n := StringReplace(textures[i].name, '\', '/', [rfReplaceAll]);
926 WriteLn(n)
927 end
928 end;
930 procedure Help;
931 begin
932 WriteLn('Usage: mp2df [OPTION] FILE.dlv [OUTPUT]');
933 WriteLn('Options:');
934 WriteLn(' -l list textures used on map and exit');
935 WriteLn(' -h show this help');
936 Halt(0)
937 end;
939 procedure ParseArgs;
940 var i, n: Integer; done: Boolean; str: AnsiString;
941 begin
942 i := 1; done := false;
943 while (i <= ParamCount) and (not done) do
944 begin
945 str := ParamStr(i);
946 done := (Length(str) = 0) or (str[1] <> '-');
947 if not done then
948 begin
949 case str of
950 '-l': listTextures := true;
951 '-h', '-?': Help;
952 else
953 WriteLn('mp2df: unknown argument ', str);
954 Halt(1)
955 end;
956 Inc(i)
957 end
958 end;
959 n := ParamCount - i + 1;
960 if n = 1 then
961 begin
962 inputFile := ParamStr(i);
963 end
964 else if (n = 2) and (not listTextures) then
965 begin
966 inputFile := ParamStr(i);
967 outputFile := ParamStr(i + 1);
968 end
969 else
970 begin
971 WriteLn('mp2df: you may specify input file and output file only, use -h to know more');
972 Halt(1)
973 end;
974 if inputFile = '' then
975 begin
976 WriteLn('mp2df: empty input file path');
977 Halt(1);
978 end;
979 if outputFile = '' then
980 begin
981 WriteLn('mp2df: empty output file path');
982 Halt(1);
983 end
984 end;
986 begin
987 ParseArgs;
988 ReadMap(inputFile);
989 if listTextures then
990 begin
991 PrintTextureList
992 end
993 else
994 begin
995 Analyse;
996 WriteMap(outputFile)
997 end;
998 Halt(0)
999 end.