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