DEADSOFTWARE

gl: fix panel drawing
[d2df-sdl.git] / src / game / g_map.pas
1 (* Copyright (C) Doom 2D: Forever Developers
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 *)
15 {$INCLUDE ../shared/a_modes.inc}
16 {$DEFINE MAP_DEBUG_ENABLED_FLAG}
17 unit g_map;
19 interface
21 uses
22 SysUtils, Classes, mempool,
23 g_base, g_basic, MAPDEF,
24 g_phys, utils, g_panel, g_grid, md5, binheap, xprofiler, xparser, xdynrec;
26 type
27 TMapInfo = record
28 Map: String;
29 Name: String;
30 Description: String;
31 Author: String;
32 MusicName: String;
33 SkyName: String;
34 SkyFullName: String; // used by render
35 Height: Word;
36 Width: Word;
37 end;
39 PRespawnPoint = ^TRespawnPoint;
40 TRespawnPoint = record
41 X, Y: Integer;
42 Direction: TDirection;
43 PointType: Byte;
44 end;
46 PFlagPoint = ^TFlagPoint;
47 TFlagPoint = TRespawnPoint;
49 PFlag = ^TFlag;
50 TFlag = record
51 Obj: TObj;
52 RespawnType: Byte;
53 State: Byte;
54 Count: Integer;
55 CaptureTime: LongWord;
56 Direction: TDirection;
57 NeedSend: Boolean;
58 end;
60 function g_Map_Load(Res: String): Boolean;
61 function g_Map_GetMapInfo(Res: String): TMapInfo;
62 function g_Map_GetMapsList(WADName: String): SSArray;
63 function g_Map_Exist(Res: String): Boolean;
64 procedure g_Map_Free(freeTextures: Boolean=true);
65 procedure g_Map_Update();
67 function g_Map_PanelByGUID (aguid: Integer): TPanel; inline;
69 function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word;
70 PanelType: Word; b1x3: Boolean=false): Boolean;
71 function g_Map_CollideLiquid_Texture(X, Y: Integer; Width, Height: Word): DWORD;
73 procedure g_Map_EnableWallGUID (pguid: Integer);
74 procedure g_Map_DisableWallGUID (pguid: Integer);
75 procedure g_Map_SetLiftGUID (pguid: Integer; t: Integer);
77 // HACK!!!
78 procedure g_Map_EnableWall_XXX (ID: DWORD);
79 procedure g_Map_DisableWall_XXX (ID: DWORD);
80 procedure g_Map_SetLift_XXX (ID: DWORD; t: Integer);
82 procedure g_Map_SwitchTextureGUID (pguid: Integer; AnimLoop: Byte = 0);
84 procedure g_Map_ReAdd_DieTriggers();
85 function g_Map_IsSpecialTexture(Texture: String): Boolean;
87 function g_Map_GetPoint(PointType: Byte; var RespawnPoint: TRespawnPoint): Boolean;
88 function g_Map_GetPointCount(PointType: Byte): Word;
89 function g_Map_GetRandomPointType(): Byte;
91 function g_Map_HaveFlagPoints(): Boolean;
93 procedure g_Map_ResetFlag(Flag: Byte);
95 procedure g_Map_SaveState (st: TStream);
96 procedure g_Map_LoadState (st: TStream);
98 // returns panel or nil
99 // sets `ex` and `ey` to `x1` and `y1` when no hit was detected
100 function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
102 // returns panel or nil
103 // sets `ex` and `ey` to `x1` and `y1` when no hit was detected
104 function g_Map_traceToNearest (x0, y0, x1, y1: Integer; tag: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
106 type
107 TForEachPanelCB = function (pan: TPanel): Boolean is nested; // return `true` to stop
109 function g_Map_HasAnyPanelAtPoint (x, y: Integer; panelType: Word): Boolean;
110 function g_Map_PanelAtPoint (x, y: Integer; tagmask: Integer=-1): TPanel;
112 // trace liquid, stepping by `dx` and `dy`
113 // return last seen liquid coords, and `false` if we're started outside of the liquid
114 function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
117 // return `true` from `cb` to stop
118 function g_Map_ForEachPanel (cb: TForEachPanelCB): TPanel;
120 procedure g_Map_NetSendInterestingPanels (); // yay!
123 procedure g_Map_ProfilersBegin ();
124 procedure g_Map_ProfilersEnd ();
127 function g_Map_ParseMap (data: Pointer; dataLen: Integer): TDynRecord;
130 function g_Map_MinX (): Integer; inline;
131 function g_Map_MinY (): Integer; inline;
132 function g_Map_MaxX (): Integer; inline;
133 function g_Map_MaxY (): Integer; inline;
135 const
136 NNF_NO_NAME = 0;
137 NNF_NAME_BEFORE = 1;
138 NNF_NAME_EQUALS = 2;
139 NNF_NAME_AFTER = 3;
141 function g_Texture_NumNameFindStart(name: String): Boolean;
142 function g_Texture_NumNameFindNext(var newName: String): Byte;
144 const
145 RESPAWNPOINT_PLAYER1 = 1;
146 RESPAWNPOINT_PLAYER2 = 2;
147 RESPAWNPOINT_DM = 3;
148 RESPAWNPOINT_RED = 4;
149 RESPAWNPOINT_BLUE = 5;
151 FLAG_NONE = 0;
152 FLAG_RED = 1;
153 FLAG_BLUE = 2;
154 FLAG_DOM = 3;
155 FLAG_LAST = FLAG_DOM;
157 FLAG_STATE_NONE = 0;
158 FLAG_STATE_NORMAL = 1;
159 FLAG_STATE_DROPPED = 2;
160 FLAG_STATE_CAPTURED = 3;
161 FLAG_STATE_SCORED = 4; // Äëÿ ýâåíòîâ ÷åðåç ñåòêó.
162 FLAG_STATE_RETURNED = 5; // Äëÿ ýâåíòîâ ÷åðåç ñåòêó.
164 FLAG_TIME = 720; // 20 seconds
166 SKY_STRETCH: Single = 1.5;
168 const
169 GridTagInvalid = 0;
171 (* draw order:
172 PANEL_BACK
173 PANEL_STEP
174 PANEL_WALL
175 PANEL_CLOSEDOOR
176 PANEL_ACID1
177 PANEL_ACID2
178 PANEL_WATER
179 PANEL_FORE
180 *)
181 // sorted by draw priority
182 GridTagBack = 1 shl 0; // gRenderBackgrounds
183 GridTagStep = 1 shl 1; // gSteps
184 GridTagWall = 1 shl 2; // gWalls
185 GridTagDoor = 1 shl 3; // gWalls
186 GridTagAcid1 = 1 shl 4; // gAcid1
187 GridTagAcid2 = 1 shl 5; // gAcid2
188 GridTagWater = 1 shl 6; // gWater
189 GridTagFore = 1 shl 7; // gRenderForegrounds
190 // the following are invisible
191 GridTagLift = 1 shl 8; // gLifts
192 GridTagBlockMon = 1 shl 9; // gBlockMon
194 GridTagSolid = (GridTagWall or GridTagDoor);
195 GridTagObstacle = (GridTagStep or GridTagWall or GridTagDoor);
196 GridTagLiquid = (GridTagAcid1 or GridTagAcid2 or GridTagWater);
198 GridDrawableMask = (GridTagBack or GridTagStep or GridTagWall or GridTagDoor or GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagFore);
200 var
201 gWalls: TPanelArray;
202 gRenderBackgrounds: TPanelArray;
203 gRenderForegrounds: TPanelArray;
204 gWater, gAcid1, gAcid2: TPanelArray;
205 gSteps: TPanelArray;
206 gLifts: TPanelArray;
207 gBlockMon: TPanelArray;
208 gFlags: array [FLAG_RED..FLAG_BLUE] of TFlag;
209 //gDOMFlags: array of TFlag;
210 gMapInfo: TMapInfo;
211 gDoorMap: array of array of DWORD;
212 gLiftMap: array of array of DWORD;
213 gWADHash: TMD5Digest;
214 gExternalResources: array of TDiskFileInfo = nil;
215 gMovingWallIds: array of Integer = nil;
217 gdbg_map_use_accel_coldet: Boolean = true;
218 profMapCollision: TProfiler = nil; //WARNING: FOR DEBUGGING ONLY!
220 gCurrentMap: TDynRecord = nil;
221 gCurrentMapFileName: AnsiString = ''; // so we can skip texture reloading
222 gTestMap: String = '';
225 function panelTypeToTag (panelType: Word): Integer; // returns GridTagXXX
228 type
229 TPanelGrid = specialize TBodyGridBase<TPanel>;
231 var
232 mapGrid: TPanelGrid = nil; // DO NOT USE! public for debugging only!
234 var (* private state *)
235 Textures: TLevelTextureArray = nil;
237 implementation
239 uses
240 {$IFDEF ENABLE_GFX}
241 g_gfx,
242 {$ENDIF}
243 e_input, e_log, e_res, g_items, g_console,
244 g_weapons, g_game, g_sound, e_sound, CONFIG,
245 g_options, g_triggers, g_player, g_window,
246 Math, g_monsters, g_saveload, g_language, g_netmsg,
247 sfs, xstreams, hashtable, wadreader,
248 g_res_downloader
251 const
252 FLAGRECT: TRectWH = (X:15; Y:12; Width:33; Height:52);
253 MUSIC_SIGNATURE = $4953554D; // 'MUSI'
254 FLAG_SIGNATURE = $47414C46; // 'FLAG'
257 // ////////////////////////////////////////////////////////////////////////// //
258 procedure mapWarningCB (const msg: AnsiString; line, col: Integer);
259 begin
260 if (line > 0) then
261 begin
262 e_LogWritefln('parse error at (%s,%s): %s', [line, col, msg], TMsgType.Warning);
263 end
264 else
265 begin
266 e_LogWritefln('parse error: %s', [msg], TMsgType.Warning);
267 end;
268 end;
271 // ////////////////////////////////////////////////////////////////////////// //
272 var
273 panByGUID: array of TPanel = nil;
276 // ////////////////////////////////////////////////////////////////////////// //
277 function g_Map_PanelByGUID (aguid: Integer): TPanel; inline;
278 begin
279 //if (panByGUID = nil) or (not panByGUID.get(aguid, result)) then result := nil;
280 if (aguid >= 0) and (aguid < Length(panByGUID)) then result := panByGUID[aguid] else result := nil;
281 end;
284 // return `true` from `cb` to stop
285 function g_Map_ForEachPanel (cb: TForEachPanelCB): TPanel;
286 var
287 pan: TPanel;
288 begin
289 result := nil;
290 if not assigned(cb) then exit;
291 for pan in panByGUID do
292 begin
293 if cb(pan) then begin result := pan; exit; end;
294 end;
295 end;
298 procedure g_Map_NetSendInterestingPanels ();
299 var
300 pan: TPanel;
301 begin
302 if g_Game_IsServer and g_Game_IsNet then
303 begin
304 for pan in panByGUID do
305 begin
306 if pan.gncNeedSend then MH_SEND_PanelState(pan.guid);
307 end;
308 end;
309 end;
312 // ////////////////////////////////////////////////////////////////////////// //
313 function g_Map_MinX (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridX0 else result := 0; end;
314 function g_Map_MinY (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridY0 else result := 0; end;
315 function g_Map_MaxX (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridX0+mapGrid.gridWidth-1 else result := 0; end;
316 function g_Map_MaxY (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridY0+mapGrid.gridHeight-1 else result := 0; end;
319 // ////////////////////////////////////////////////////////////////////////// //
320 var
321 dfmapdef: TDynMapDef = nil;
324 procedure loadMapDefinition ();
325 var
326 pr: TTextParser = nil;
327 st: TStream = nil;
328 WAD: TWADFile = nil;
329 begin
330 if (dfmapdef <> nil) then exit;
332 try
333 e_LogWritefln('parsing "mapdef.txt"...', []);
334 st := e_OpenResourceRO(DataDirs, 'mapdef.txt');
335 e_LogWritefln('found local "mapdef.txt"', []);
336 except
337 st := nil;
338 end;
340 if (st = nil) then
341 begin
342 WAD := TWADFile.Create();
343 if not WAD.ReadFile(GameWAD) then
344 begin
345 //raise Exception.Create('cannot load "game.wad"');
346 st := nil;
347 end
348 else
349 begin
350 st := WAD.openFileStream('mapdef.txt');
351 end;
352 end;
354 try
355 if (st = nil) then
356 begin
357 //raise Exception.Create('cannot open "mapdef.txt"');
358 e_LogWriteln('using default "mapdef.txt"...');
359 pr := TStrTextParser.Create(defaultMapDef);
360 end
361 else
362 begin
363 pr := TFileTextParser.Create(st);
364 end;
365 except on e: Exception do
366 begin
367 e_LogWritefln('something is VERY wrong here! -- ', [e.message]);
368 raise;
369 end;
370 end;
372 try
373 dfmapdef := TDynMapDef.Create(pr);
374 except
375 on e: TDynParseException do
376 raise Exception.CreateFmt('ERROR in "mapdef.txt" at (%s,%s): %s', [e.tokLine, e.tokCol, e.message]);
377 on e: Exception do
378 raise Exception.CreateFmt('ERROR in "mapdef.txt" at (%s,%s): %s', [pr.tokLine, pr.tokCol, e.message]);
379 end;
381 st.Free();
382 WAD.Free();
383 end;
386 // ////////////////////////////////////////////////////////////////////////// //
387 function g_Map_ParseMap (data: Pointer; dataLen: Integer): TDynRecord;
388 var
389 wst: TSFSMemoryChunkStream = nil;
390 begin
391 result := nil;
392 if (dataLen < 4) then exit;
394 if (dfmapdef = nil) then writeln('need to load mapdef');
395 loadMapDefinition();
396 if (dfmapdef = nil) then raise Exception.Create('internal map loader error');
398 wst := TSFSMemoryChunkStream.Create(data, dataLen);
399 try
400 result := dfmapdef.parseMap(wst);
401 except
402 on e: TDynParseException do
403 begin
404 e_LogWritefln('ERROR at (%s,%s): %s', [e.tokLine, e.tokCol, e.message]);
405 wst.Free();
406 result := nil;
407 exit;
408 end;
409 on e: Exception do
410 begin
411 e_LogWritefln('ERROR: %s', [e.message]);
412 wst.Free();
413 result := nil;
414 exit;
415 end;
416 end;
418 //e_LogWriteln('map parsed.');
419 end;
422 // ////////////////////////////////////////////////////////////////////////// //
423 var
424 NNF_PureName: String; // Èìÿ òåêñòóðû áåç öèôð â êîíöå
425 NNF_PureExt: String; // extension postfix
426 NNF_FirstNum: Integer; // ×èñëî ó íà÷àëüíîé òåêñòóðû
427 NNF_CurrentNum: Integer; // Ñëåäóþùåå ÷èñëî ó òåêñòóðû
430 function g_Texture_NumNameFindStart(name: String): Boolean;
431 var
432 i, j: Integer;
434 begin
435 Result := False;
436 NNF_PureName := '';
437 NNF_PureExt := '';
438 NNF_FirstNum := -1;
439 NNF_CurrentNum := -1;
441 for i := Length(name) downto 1 do
442 if (name[i] = '_') then // "_" - ñèìâîë íà÷àëà íîìåðíîãî ïîñòôèêñà
443 begin
444 if i = Length(name) then
445 begin // Íåò öèôð â êîíöå ñòðîêè
446 Exit;
447 end
448 else
449 begin
450 j := i + 1;
451 while (j <= Length(name)) and (name[j] <> '.') do inc(j);
452 NNF_PureName := Copy(name, 1, i);
453 NNF_PureExt := Copy(name, j);
454 name := Copy(name, i + 1, j - i - 1);
455 Break;
456 end;
457 end;
459 // Íå ïåðåâåñòè â ÷èñëî:
460 if not TryStrToInt(name, NNF_FirstNum) then
461 Exit;
463 NNF_CurrentNum := 0;
465 Result := True;
466 end;
469 function g_Texture_NumNameFindNext(var newName: String): Byte;
470 begin
471 if (NNF_PureName = '') or (NNF_CurrentNum < 0) then
472 begin
473 newName := '';
474 Result := NNF_NO_NAME;
475 Exit;
476 end;
478 newName := NNF_PureName + IntToStr(NNF_CurrentNum) + NNF_PureExt;
480 if NNF_CurrentNum < NNF_FirstNum then
481 Result := NNF_NAME_BEFORE
482 else
483 if NNF_CurrentNum > NNF_FirstNum then
484 Result := NNF_NAME_AFTER
485 else
486 Result := NNF_NAME_EQUALS;
488 Inc(NNF_CurrentNum);
489 end;
492 // ////////////////////////////////////////////////////////////////////////// //
493 function panelTypeToTag (panelType: Word): Integer;
494 begin
495 case panelType of
496 PANEL_WALL: result := GridTagWall; // gWalls
497 PANEL_OPENDOOR, PANEL_CLOSEDOOR: result := GridTagDoor; // gWalls
498 PANEL_BACK: result := GridTagBack; // gRenderBackgrounds
499 PANEL_FORE: result := GridTagFore; // gRenderForegrounds
500 PANEL_WATER: result := GridTagWater; // gWater
501 PANEL_ACID1: result := GridTagAcid1; // gAcid1
502 PANEL_ACID2: result := GridTagAcid2; // gAcid2
503 PANEL_STEP: result := GridTagStep; // gSteps
504 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: result := GridTagLift; // gLifts -- this is for all lifts
505 PANEL_BLOCKMON: result := GridTagBlockMon; // gBlockMon -- this is for all blockmons
506 else result := GridTagInvalid;
507 end;
508 end;
510 var
511 TextNameHash: THashStrInt = nil; // key: texture name; value: index in `Textures`
512 BadTextNameHash: THashStrInt = nil; // set; so we won't spam with non-existing texture messages
513 RespawnPoints: array of TRespawnPoint;
514 FlagPoints: array[FLAG_RED..FLAG_BLUE] of PFlagPoint;
515 //DOMFlagPoints: Array of TFlagPoint;
518 procedure g_Map_ProfilersBegin ();
519 begin
520 if (profMapCollision = nil) then profMapCollision := TProfiler.Create('COLSOLID', g_profile_history_size);
521 if (profMapCollision <> nil) then profMapCollision.mainBegin(g_profile_collision);
522 // create sections
523 if g_profile_collision and (profMapCollision <> nil) then
524 begin
525 profMapCollision.sectionBegin('*solids');
526 profMapCollision.sectionEnd();
527 profMapCollision.sectionBegin('liquids');
528 profMapCollision.sectionEnd();
529 end;
530 end;
532 procedure g_Map_ProfilersEnd ();
533 begin
534 if (profMapCollision <> nil) then profMapCollision.mainEnd();
535 end;
538 // wall index in `gWalls` or -1
539 function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
540 var
541 ex, ey: Integer;
542 begin
543 result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, (GridTagWall or GridTagDoor));
544 if (result <> nil) then
545 begin
546 if (hitx <> nil) then hitx^ := ex;
547 if (hity <> nil) then hity^ := ey;
548 end
549 else
550 begin
551 if (hitx <> nil) then hitx^ := x1;
552 if (hity <> nil) then hity^ := y1;
553 end;
554 end;
556 // returns panel or nil
557 function g_Map_traceToNearest (x0, y0, x1, y1: Integer; tag: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
558 var
559 ex, ey: Integer;
560 begin
561 result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, tag);
562 if (result <> nil) then
563 begin
564 if (hitx <> nil) then hitx^ := ex;
565 if (hity <> nil) then hity^ := ey;
566 end
567 else
568 begin
569 if (hitx <> nil) then hitx^ := x1;
570 if (hity <> nil) then hity^ := y1;
571 end;
572 end;
575 function xxPanAtPointChecker (pan: TPanel; panelType: Word): Boolean; inline;
576 begin
577 if ((pan.tag and GridTagLift) <> 0) then
578 begin
579 // stop if the lift of the right type
580 result :=
581 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
582 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
583 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
584 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT)));
585 exit;
586 end;
587 result := true; // otherwise, stop anyway, 'cause `forEachAtPoint()` is guaranteed to call this only for correct panels
588 end;
590 function g_Map_HasAnyPanelAtPoint (x, y: Integer; panelType: Word): Boolean;
591 var
592 tagmask: Integer = 0;
593 mwit: PPanel;
594 it: TPanelGrid.Iter;
595 begin
596 result := false;
598 if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor);
599 if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater;
600 if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1;
601 if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2;
602 if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep;
603 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift;
604 if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon;
606 if (tagmask = 0) then exit;// just in case
608 if ((tagmask and GridTagLift) <> 0) then
609 begin
610 // slow
611 it := mapGrid.forEachAtPoint(x, y, tagmask);
612 for mwit in it do if (xxPanAtPointChecker(mwit^, PanelType)) then begin result := true; break; end;
613 end
614 else
615 begin
616 // fast
617 it := mapGrid.forEachAtPoint(x, y, tagmask, false, true);
618 result := (it.length <> 0); // firsthit
619 end;
620 it.release();
621 end;
624 function g_Map_PanelAtPoint (x, y: Integer; tagmask: Integer=-1): TPanel;
625 var
626 it: TPanelGrid.Iter;
627 begin
628 result := nil;
629 if (tagmask = 0) then exit;
630 it := mapGrid.forEachAtPoint(x, y, tagmask, false, true); // firsthit
631 if (it.length <> 0) then result := it.first^;
632 it.release();
633 end;
636 function g_Map_IsSpecialTexture(Texture: String): Boolean;
637 begin
638 Result := (Texture = TEXTURE_NAME_WATER) or
639 (Texture = TEXTURE_NAME_ACID1) or
640 (Texture = TEXTURE_NAME_ACID2);
641 end;
643 procedure CreateDoorMap();
644 var
645 PanelArray: Array of record
646 X, Y: Integer;
647 Width, Height: Word;
648 Active: Boolean;
649 PanelID: DWORD;
650 end;
651 a, b, c, m, i, len: Integer;
652 ok: Boolean;
653 begin
654 if gWalls = nil then
655 Exit;
657 i := 0;
658 len := 128;
659 SetLength(PanelArray, len);
661 for a := 0 to High(gWalls) do
662 if gWalls[a].Door then
663 begin
664 PanelArray[i].X := gWalls[a].X;
665 PanelArray[i].Y := gWalls[a].Y;
666 PanelArray[i].Width := gWalls[a].Width;
667 PanelArray[i].Height := gWalls[a].Height;
668 PanelArray[i].Active := True;
669 PanelArray[i].PanelID := a;
671 i := i + 1;
672 if i = len then
673 begin
674 len := len + 128;
675 SetLength(PanelArray, len);
676 end;
677 end;
679 // Íåò äâåðåé:
680 if i = 0 then
681 begin
682 PanelArray := nil;
683 Exit;
684 end;
686 SetLength(gDoorMap, 0);
688 g_Game_SetLoadingText(_lc[I_LOAD_DOOR_MAP], i-1, False);
690 for a := 0 to i-1 do
691 if PanelArray[a].Active then
692 begin
693 PanelArray[a].Active := False;
694 m := Length(gDoorMap);
695 SetLength(gDoorMap, m+1);
696 SetLength(gDoorMap[m], 1);
697 gDoorMap[m, 0] := PanelArray[a].PanelID;
698 ok := True;
700 while ok do
701 begin
702 ok := False;
704 for b := 0 to i-1 do
705 if PanelArray[b].Active then
706 for c := 0 to High(gDoorMap[m]) do
707 if {((gRenderWalls[PanelArray[b].RenderPanelID].TextureID = gRenderWalls[gDoorMap[m, c]].TextureID) or
708 gRenderWalls[PanelArray[b].RenderPanelID].Hide or gRenderWalls[gDoorMap[m, c]].Hide) and}
709 g_CollideAround(PanelArray[b].X, PanelArray[b].Y,
710 PanelArray[b].Width, PanelArray[b].Height,
711 gWalls[gDoorMap[m, c]].X,
712 gWalls[gDoorMap[m, c]].Y,
713 gWalls[gDoorMap[m, c]].Width,
714 gWalls[gDoorMap[m, c]].Height) then
715 begin
716 PanelArray[b].Active := False;
717 SetLength(gDoorMap[m],
718 Length(gDoorMap[m])+1);
719 gDoorMap[m, High(gDoorMap[m])] := PanelArray[b].PanelID;
720 ok := True;
721 Break;
722 end;
723 end;
725 g_Game_StepLoading();
726 end;
728 PanelArray := nil;
729 end;
731 procedure CreateLiftMap();
732 var
733 PanelArray: Array of record
734 X, Y: Integer;
735 Width, Height: Word;
736 Active: Boolean;
737 end;
738 a, b, c, len, i, j: Integer;
739 ok: Boolean;
740 begin
741 if gLifts = nil then
742 Exit;
744 len := Length(gLifts);
745 SetLength(PanelArray, len);
747 for a := 0 to len-1 do
748 begin
749 PanelArray[a].X := gLifts[a].X;
750 PanelArray[a].Y := gLifts[a].Y;
751 PanelArray[a].Width := gLifts[a].Width;
752 PanelArray[a].Height := gLifts[a].Height;
753 PanelArray[a].Active := True;
754 end;
756 SetLength(gLiftMap, len);
757 i := 0;
759 g_Game_SetLoadingText(_lc[I_LOAD_LIFT_MAP], len-1, False);
761 for a := 0 to len-1 do
762 if PanelArray[a].Active then
763 begin
764 PanelArray[a].Active := False;
765 SetLength(gLiftMap[i], 32);
766 j := 0;
767 gLiftMap[i, j] := a;
768 ok := True;
770 while ok do
771 begin
772 ok := False;
773 for b := 0 to len-1 do
774 if PanelArray[b].Active then
775 for c := 0 to j do
776 if g_CollideAround(PanelArray[b].X,
777 PanelArray[b].Y,
778 PanelArray[b].Width,
779 PanelArray[b].Height,
780 PanelArray[gLiftMap[i, c]].X,
781 PanelArray[gLiftMap[i, c]].Y,
782 PanelArray[gLiftMap[i, c]].Width,
783 PanelArray[gLiftMap[i, c]].Height) then
784 begin
785 PanelArray[b].Active := False;
786 j := j+1;
787 if j > High(gLiftMap[i]) then
788 SetLength(gLiftMap[i],
789 Length(gLiftMap[i])+32);
791 gLiftMap[i, j] := b;
792 ok := True;
794 Break;
795 end;
796 end;
798 SetLength(gLiftMap[i], j+1);
799 i := i+1;
801 g_Game_StepLoading();
802 end;
804 SetLength(gLiftMap, i);
806 PanelArray := nil;
807 end;
809 function CreatePanel (PanelRec: TDynRecord; AddTextures: TAddTextureArray; CurTex: Integer): Integer;
810 var
811 len: Integer;
812 panels: ^TPanelArray;
813 pan: TPanel;
814 pguid: Integer;
815 begin
816 Result := -1;
818 case PanelRec.PanelType of
819 PANEL_WALL, PANEL_OPENDOOR, PANEL_CLOSEDOOR: panels := @gWalls;
820 PANEL_BACK: panels := @gRenderBackgrounds;
821 PANEL_FORE: panels := @gRenderForegrounds;
822 PANEL_WATER: panels := @gWater;
823 PANEL_ACID1: panels := @gAcid1;
824 PANEL_ACID2: panels := @gAcid2;
825 PANEL_STEP: panels := @gSteps;
826 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: panels := @gLifts;
827 PANEL_BLOCKMON: panels := @gBlockMon;
828 else exit;
829 end;
831 len := Length(panels^);
832 SetLength(panels^, len+1);
834 pguid := Length(panByGUID);
835 SetLength(panByGUID, pguid+1); //FIXME!
836 pan := TPanel.Create(PanelRec, AddTextures, CurTex, Textures, pguid);
837 assert(pguid >= 0);
838 assert(pguid < Length(panByGUID));
839 panByGUID[pguid] := pan;
840 panels^[len] := pan;
841 pan.arrIdx := len;
842 pan.proxyId := -1;
843 pan.tag := panelTypeToTag(PanelRec.PanelType);
845 PanelRec.user['panel_guid'] := pguid;
847 //result := len;
848 result := pguid;
849 end;
852 function CreateNullTexture(RecName: String): Integer;
853 begin
854 RecName := toLowerCase1251(RecName);
855 if (TextNameHash = nil) then TextNameHash := THashStrInt.Create();
856 if TextNameHash.get(RecName, result) then exit; // i found her!
858 SetLength(Textures, Length(Textures) + 1);
859 Textures[High(Textures)].TextureName := RecName;
860 Textures[High(Textures)].FullName := '';
861 Result := High(Textures);
862 TextNameHash.put(RecName, result);
863 end;
866 function extractWadName (resourceName: string): string;
867 var
868 posN: Integer;
869 begin
870 posN := Pos(':', resourceName);
871 if posN > 0 then
872 Result:= Copy(resourceName, 0, posN-1)
873 else
874 Result := '';
875 end;
878 procedure addResToExternalResList (res: AnsiString);
879 var
880 uname: AnsiString;
881 f: Integer;
882 fi: TDiskFileInfo;
883 begin
884 if g_Game_IsClient or not g_Game_IsNet then exit;
885 if (length(res) = 0) then exit; // map wad
886 //res := extractWadName(res);
887 //if (length(res) = 0) then exit; // map wad
888 uname := toLowerCase1251(res);
889 // do not add duplicates
890 for f := 0 to High(gExternalResources) do
891 begin
892 if (gExternalResources[f].userName = uname) then exit;
893 end;
894 //writeln('***(000) addResToExternalResList: res=[', res, ']');
895 // add new resource
896 fi.userName := uname;
897 if not findFileCI(res) then exit;
898 //writeln('***(001) addResToExternalResList: res=[', res, ']');
899 fi.diskName := res;
900 if (not GetDiskFileInfo(res, fi)) then
901 begin
902 fi.tag := -1;
903 end
904 else
905 begin
906 //writeln('***(002) addResToExternalResList: res=[', res, ']');
907 fi.tag := 0; // non-zero means "cannot caclucate hash"
908 try
909 fi.hash := MD5File(fi.diskName);
910 except
911 fi.tag := -1;
912 end;
913 end;
914 //e_LogWritefln('addext: res=[%s]; uname=[%s]; diskName=[%s]', [res, fi.userName, fi.diskName]);
915 SetLength(gExternalResources, length(gExternalResources)+1);
916 gExternalResources[High(gExternalResources)] := fi;
917 end;
920 procedure compactExtResList ();
921 var
922 src, dest: Integer;
923 begin
924 src := 0;
925 dest := 0;
926 for src := 0 to High(gExternalResources) do
927 begin
928 if (gExternalResources[src].tag = 0) then
929 begin
930 // copy it
931 if (dest <> src) then gExternalResources[dest] := gExternalResources[src];
932 Inc(dest);
933 end;
934 end;
935 if (dest <> length(gExternalResources)) then SetLength(gExternalResources, dest);
936 end;
939 function GetReplacementWad (WadName: AnsiString): AnsiString;
940 begin
941 result := '';
942 if WadName <> '' then
943 begin
944 result := WadName;
945 if g_Game_IsClient then result := g_Res_FindReplacementWad(WadName);
946 if (result = WadName) then result := e_FindWad(WadDirs, result)
947 end;
948 end;
951 procedure generateExternalResourcesList (map: TDynRecord);
952 begin
953 SetLength(gExternalResources, 0);
954 addResToExternalResList(GetReplacementWad(g_ExtractWadName(map.MusicName)));
955 addResToExternalResList(GetReplacementWad(g_ExtractWadName(map.SkyName)));
956 end;
958 function CreateTexture (RecName: AnsiString; Map: String; log: Boolean): Integer;
959 var
960 HName: AnsiString;
961 WAD: TWADFile;
962 WADName, ResName: String;
963 ResData: Pointer;
964 ResLen: Integer;
965 id: Integer;
966 begin
967 Result := -1;
968 HName := toLowerCase1251(RecName);
969 if (TextNameHash = nil) then
970 TextNameHash := THashStrInt.Create();
971 if TextNameHash.get(HName, Result) then
972 begin
973 // e_LogWritefln('CreateTexture: found loaded %s', [Result]);
974 end
975 else
976 begin
977 Result := -1;
978 if (BadTextNameHash = nil) or not BadTextNameHash.has(HName) then
979 begin
980 case RecName of
981 TEXTURE_NAME_WATER, TEXTURE_NAME_ACID1, TEXTURE_NAME_ACID2:
982 begin
983 SetLength(Textures, Length(Textures) + 1);
984 Textures[High(Textures)].FullName := RecName;
985 Textures[High(Textures)].TextureName := RecName;
986 Result := High(Textures);
987 TextNameHash.put(RecName, result);
988 end
989 else
990 WADName := GetReplacementWad(g_ExtractWadName(RecName));
991 if (WADName <> '') then
992 addResToExternalResList(WADName);
993 if WADName = '' then
994 WADName := Map;
995 ResName := g_ExtractFilePathName(RecName);
996 WAD := TWADFile.Create();
997 if WAD.ReadFile(WadName) then
998 begin
999 if WAD.GetResource(ResName, ResData, ResLen, log) then
1000 begin
1001 SetLength(Textures, Length(Textures) + 1);
1002 Textures[High(Textures)].FullName := WADName + ':' + ResName;
1003 Textures[High(Textures)].TextureName := RecName;
1004 Result := High(Textures);
1005 TextNameHash.put(HName, result);
1006 FreeMem(ResData);
1007 end;
1008 end;
1009 WAD.Free;
1010 end
1011 end;
1012 end;
1013 if Result < 0 then
1014 begin
1015 if (BadTextNameHash = nil) then
1016 BadTextNameHash := THashStrInt.Create();
1017 if log and (not BadTextNameHash.get(HName, id)) then
1018 e_WriteLog(Format('Error loading texture %s', [RecName]), TMsgType.Warning);
1019 BadTextNameHash.put(HName, -1);
1020 end
1021 end;
1023 procedure CreateItem(Item: TDynRecord);
1024 begin
1025 if g_Game_IsClient then Exit;
1027 if (not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) and
1028 ByteBool(Item.Options and ITEM_OPTION_ONLYDM) then
1029 Exit;
1031 g_Items_Create(Item.X, Item.Y, Item.ItemType, ByteBool(Item.Options and ITEM_OPTION_FALL),
1032 gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF, GM_COOP]);
1033 end;
1035 procedure CreateArea(Area: TDynRecord);
1036 var
1037 a: Integer;
1038 begin
1039 case Area.AreaType of
1040 AREA_DMPOINT, AREA_PLAYERPOINT1, AREA_PLAYERPOINT2,
1041 AREA_REDTEAMPOINT, AREA_BLUETEAMPOINT:
1042 begin
1043 SetLength(RespawnPoints, Length(RespawnPoints)+1);
1044 with RespawnPoints[High(RespawnPoints)] do
1045 begin
1046 X := Area.X;
1047 Y := Area.Y;
1048 Direction := TDirection(Area.Direction);
1050 case Area.AreaType of
1051 AREA_DMPOINT: PointType := RESPAWNPOINT_DM;
1052 AREA_PLAYERPOINT1: PointType := RESPAWNPOINT_PLAYER1;
1053 AREA_PLAYERPOINT2: PointType := RESPAWNPOINT_PLAYER2;
1054 AREA_REDTEAMPOINT: PointType := RESPAWNPOINT_RED;
1055 AREA_BLUETEAMPOINT: PointType := RESPAWNPOINT_BLUE;
1056 end;
1057 end;
1058 end;
1060 AREA_REDFLAG, AREA_BLUEFLAG:
1061 begin
1062 if Area.AreaType = AREA_REDFLAG then a := FLAG_RED else a := FLAG_BLUE;
1064 if FlagPoints[a] <> nil then Exit;
1066 New(FlagPoints[a]);
1068 with FlagPoints[a]^ do
1069 begin
1070 X := Area.X-FLAGRECT.X;
1071 Y := Area.Y-FLAGRECT.Y;
1072 Direction := TDirection(Area.Direction);
1073 end;
1075 with gFlags[a] do
1076 begin
1077 Obj.Rect := FLAGRECT;
1078 g_Map_ResetFlag(a);
1079 end;
1080 end;
1082 AREA_DOMFLAG:
1083 begin
1084 {SetLength(DOMFlagPoints, Length(DOMFlagPoints)+1);
1085 with DOMFlagPoints[High(DOMFlagPoints)] do
1086 begin
1087 X := Area.X;
1088 Y := Area.Y;
1089 Direction := TDirection(Area.Direction);
1090 end;
1092 g_Map_CreateFlag(DOMFlagPoints[High(DOMFlagPoints)], FLAG_DOM, FLAG_STATE_NORMAL);}
1093 end;
1094 end;
1095 end;
1097 function CreateTrigger (amapIdx: Integer; Trigger: TDynRecord; atpanid, atrigpanid: Integer): Integer;
1098 var
1099 _trigger: TTrigger;
1100 tp: TPanel;
1101 begin
1102 result := -1;
1103 if g_Game_IsClient and not (Trigger.TriggerType in [TRIGGER_SOUND, TRIGGER_MUSIC]) then Exit;
1105 with _trigger do
1106 begin
1107 mapId := Trigger.id;
1108 mapIndex := amapIdx;
1109 X := Trigger.X;
1110 Y := Trigger.Y;
1111 Width := Trigger.Width;
1112 Height := Trigger.Height;
1113 Enabled := Trigger.Enabled;
1114 TexturePanelGUID := atpanid;
1115 TriggerType := Trigger.TriggerType;
1116 ActivateType := Trigger.ActivateType;
1117 Keys := Trigger.Keys;
1118 trigPanelGUID := atrigpanid;
1119 // HACK: used in TPanel.CanChangeTexture. maybe there's a better way?
1120 if TexturePanelGUID <> -1 then
1121 begin
1122 tp := g_Map_PanelByGUID(TexturePanelGUID);
1123 if (tp <> nil) then tp.hasTexTrigger := True;
1124 end;
1125 end;
1127 result := Integer(g_Triggers_Create(_trigger, Trigger));
1128 end;
1130 procedure CreateMonster(monster: TDynRecord);
1131 var
1132 a: Integer;
1133 mon: TMonster;
1134 begin
1135 if g_Game_IsClient then Exit;
1137 if (gGameSettings.GameType = GT_SINGLE)
1138 or LongBool(gGameSettings.Options and GAME_OPTION_MONSTERS) then
1139 begin
1140 mon := g_Monsters_Create(monster.MonsterType, monster.X, monster.Y, TDirection(monster.Direction));
1142 if gTriggers <> nil then
1143 begin
1144 for a := 0 to High(gTriggers) do
1145 begin
1146 if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
1147 begin
1148 //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
1149 if (gTriggers[a].trigDataRec.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
1150 end;
1151 end;
1152 end;
1154 if monster.MonsterType <> MONSTER_BARREL then Inc(gTotalMonsters);
1155 end;
1156 end;
1158 procedure g_Map_ReAdd_DieTriggers();
1160 function monsDieTrig (mon: TMonster): Boolean;
1161 var
1162 a: Integer;
1163 //tw: TStrTextWriter;
1164 begin
1165 result := false; // don't stop
1166 mon.ClearTriggers();
1167 for a := 0 to High(gTriggers) do
1168 begin
1169 if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
1170 begin
1171 //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
1173 tw := TStrTextWriter.Create();
1174 try
1175 gTriggers[a].trigData.writeTo(tw);
1176 e_LogWritefln('=== trigger #%s ==='#10'%s'#10'---', [a, tw.str]);
1177 finally
1178 tw.Free();
1179 end;
1181 if (gTriggers[a].trigDataRec.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
1182 end;
1183 end;
1184 end;
1186 begin
1187 if g_Game_IsClient then Exit;
1189 g_Mons_ForEach(monsDieTrig);
1190 end;
1192 procedure mapCreateGrid ();
1193 var
1194 mapX0: Integer = $3fffffff;
1195 mapY0: Integer = $3fffffff;
1196 mapX1: Integer = -$3fffffff;
1197 mapY1: Integer = -$3fffffff;
1199 procedure calcBoundingBox (constref panels: TPanelArray);
1200 var
1201 idx: Integer;
1202 pan: TPanel;
1203 begin
1204 for idx := 0 to High(panels) do
1205 begin
1206 pan := panels[idx];
1207 if not pan.visvalid then continue;
1208 if (pan.Width < 1) or (pan.Height < 1) then continue;
1209 if (mapX0 > pan.x0) then mapX0 := pan.x0;
1210 if (mapY0 > pan.y0) then mapY0 := pan.y0;
1211 if (mapX1 < pan.x1) then mapX1 := pan.x1;
1212 if (mapY1 < pan.y1) then mapY1 := pan.y1;
1213 end;
1214 end;
1216 procedure addPanelsToGrid (constref panels: TPanelArray);
1217 var
1218 idx: Integer;
1219 pan: TPanel;
1220 newtag: Integer;
1221 begin
1222 //tag := panelTypeToTag(tag);
1223 for idx := 0 to High(panels) do
1224 begin
1225 pan := panels[idx];
1226 if not pan.visvalid then continue;
1227 if (pan.proxyId <> -1) then
1228 begin
1229 {$IF DEFINED(D2F_DEBUG)}
1230 e_WriteLog(Format('DUPLICATE wall #%d(%d) enabled (%d); type:%08x', [Integer(idx), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId]), pan.PanelType]), TMsgType.Notify);
1231 {$ENDIF}
1232 continue;
1233 end;
1234 case pan.PanelType of
1235 PANEL_WALL: newtag := GridTagWall;
1236 PANEL_OPENDOOR, PANEL_CLOSEDOOR: newtag := GridTagDoor;
1237 PANEL_BACK: newtag := GridTagBack;
1238 PANEL_FORE: newtag := GridTagFore;
1239 PANEL_WATER: newtag := GridTagWater;
1240 PANEL_ACID1: newtag := GridTagAcid1;
1241 PANEL_ACID2: newtag := GridTagAcid2;
1242 PANEL_STEP: newtag := GridTagStep;
1243 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: newtag := GridTagLift;
1244 PANEL_BLOCKMON: newtag := GridTagBlockMon;
1245 else continue; // oops
1246 end;
1247 pan.tag := newtag;
1249 pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, newtag);
1250 // "enabled" flag has meaning only for doors and walls (engine assumes it); but meh...
1251 mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
1252 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
1254 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
1255 begin
1256 e_WriteLog(Format('INSERTED wall #%d(%d) enabled (%d)', [Integer(idx), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId])]), MSG_NOTIFY);
1257 end;
1259 {$ENDIF}
1260 end;
1261 end;
1263 begin
1264 mapGrid.Free();
1265 mapGrid := nil;
1267 calcBoundingBox(gWalls);
1268 calcBoundingBox(gRenderBackgrounds);
1269 calcBoundingBox(gRenderForegrounds);
1270 calcBoundingBox(gWater);
1271 calcBoundingBox(gAcid1);
1272 calcBoundingBox(gAcid2);
1273 calcBoundingBox(gSteps);
1274 calcBoundingBox(gLifts);
1275 calcBoundingBox(gBlockMon);
1277 e_LogWritefln('map dimensions: (%d,%d)-(%d,%d); editor size:(0,0)-(%d,%d)', [mapX0, mapY0, mapX1, mapY1, gMapInfo.Width, gMapInfo.Height]);
1279 if (mapX0 > 0) then mapX0 := 0;
1280 if (mapY0 > 0) then mapY0 := 0;
1282 if (mapX1 < gMapInfo.Width-1) then mapX1 := gMapInfo.Width-1;
1283 if (mapY1 < gMapInfo.Height-1) then mapY1 := gMapInfo.Height-1;
1285 mapGrid := TPanelGrid.Create(mapX0-128, mapY0-128, mapX1-mapX0+1+128*2, mapY1-mapY0+1+128*2);
1286 //mapGrid := TPanelGrid.Create(0, 0, gMapInfo.Width, gMapInfo.Height);
1288 addPanelsToGrid(gWalls);
1289 addPanelsToGrid(gRenderBackgrounds);
1290 addPanelsToGrid(gRenderForegrounds);
1291 addPanelsToGrid(gWater);
1292 addPanelsToGrid(gAcid1);
1293 addPanelsToGrid(gAcid2);
1294 addPanelsToGrid(gSteps);
1295 addPanelsToGrid(gLifts); // it doesn't matter which LIFT type is used here
1296 addPanelsToGrid(gBlockMon);
1298 mapGrid.dumpStats();
1300 g_Mons_InitTree(mapGrid.gridX0, mapGrid.gridY0, mapGrid.gridWidth, mapGrid.gridHeight);
1301 end;
1304 function g_Map_Load(Res: String): Boolean;
1305 const
1306 DefaultMusRes = 'Standart.wad:STDMUS\MUS1';
1307 DefaultSkyRes = 'Standart.wad:STDSKY\SKY0';
1308 type
1309 PTRec = ^TTRec;
1310 TTRec = record
1311 //TexturePanel: Integer;
1312 tnum: Integer;
1313 id: Integer;
1314 trigrec: TDynRecord;
1315 // texture pane;
1316 texPanelIdx: Integer;
1317 texPanel: TDynRecord;
1318 // "action" panel
1319 actPanelIdx: Integer;
1320 actPanel: TDynRecord;
1321 end;
1322 var
1323 WAD, TestWAD: TWADFile;
1324 //mapReader: TDynRecord = nil;
1325 mapTextureList: TDynField = nil; //TTexturesRec1Array; tagInt: texture index
1326 panels: TDynField = nil; //TPanelsRec1Array;
1327 items: TDynField = nil; //TItemsRec1Array;
1328 monsters: TDynField = nil; //TMonsterRec1Array;
1329 areas: TDynField = nil; //TAreasRec1Array;
1330 triggers: TDynField = nil; //TTriggersRec1Array;
1331 b, c, k: Integer;
1332 PanelID: DWORD;
1333 AddTextures: TAddTextureArray;
1334 TriggersTable: array of TTRec;
1335 FileName, mapResName, TexName, s: AnsiString;
1336 Data: Pointer;
1337 Len: Integer;
1338 ok: Boolean;
1339 CurTex, ntn: Integer;
1340 rec, texrec: TDynRecord;
1341 pttit: PTRec;
1342 pannum, trignum, cnt, tgpid: Integer;
1343 stt: UInt64;
1344 moveSpeed{, moveStart, moveEnd}: TDFPoint;
1345 //moveActive: Boolean;
1346 pan: TPanel;
1347 mapOk: Boolean = false;
1348 usedTextures: THashStrInt = nil; // key: mapTextureList
1349 begin
1350 mapGrid.Free();
1351 mapGrid := nil;
1352 TestWAD := nil;
1353 Data := nil;
1355 //gCurrentMap.Free();
1356 //gCurrentMap := nil;
1358 panByGUID := nil;
1360 Result := False;
1361 gMapInfo.Map := Res;
1362 TriggersTable := nil;
1363 //mapReader := nil;
1365 sfsGCDisable(); // temporary disable removing of temporary volumes
1366 try
1367 // Çàãðóçêà WAD (åñëè ó íàñ íåò óæå çàãðóæåíîé êàðòû)
1368 if (gCurrentMap = nil) then
1369 begin
1370 FileName := g_ExtractWadName(Res);
1371 e_LogWritefln('Loading map WAD [%s] (res=[%s])', [FileName, Res], TMsgType.Notify);
1372 g_Game_SetLoadingText(_lc[I_LOAD_WAD_FILE], 0, False);
1374 WAD := TWADFile.Create();
1375 if not WAD.ReadFile(FileName) then
1376 begin
1377 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_WAD], [FileName]));
1378 WAD.Free();
1379 Exit;
1380 end;
1382 if gTestMap <> '' then
1383 begin
1384 s := g_ExtractWadName(gTestMap);
1385 TestWAD := TWADFile.Create();
1386 if not TestWAD.ReadFile(s) then
1387 begin
1388 g_SimpleError(Format(_lc[I_GAME_ERROR_MAP_WAD], [s]));
1389 TestWAD.Free();
1390 TestWAD := nil;
1391 end;
1392 end;
1394 if TestWAD <> nil then
1395 begin
1396 mapResName := g_ExtractFileName(gTestMap);
1397 if not TestWAD.GetMapResource(mapResName, Data, Len) then
1398 begin
1399 g_SimpleError(Format(_lc[I_GAME_ERROR_MAP_RES], [mapResName]));
1400 Data := nil;
1401 end else
1402 e_WriteLog('Using test map: '+gTestMap, TMsgType.Notify);
1403 TestWAD.Free();
1404 TestWAD := nil;
1405 end;
1407 if Data = nil then
1408 begin
1409 //k8: why loader ignores path here?
1410 mapResName := g_ExtractFileName(Res);
1411 if not WAD.GetMapResource(mapResName, Data, Len) then
1412 begin
1413 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_RES], [mapResName]));
1414 WAD.Free();
1415 Exit;
1416 end;
1417 end;
1419 WAD.Free();
1421 if (Len < 4) then
1422 begin
1423 e_LogWritefln('invalid map file: ''%s''', [mapResName]);
1424 FreeMem(Data);
1425 exit;
1426 end;
1428 // Çàãðóçêà êàðòû:
1429 e_LogWritefln('Loading map: %s', [mapResName], TMsgType.Notify);
1430 g_Game_SetLoadingText(_lc[I_LOAD_MAP], 0, False);
1432 stt := getTimeMicro();
1434 try
1435 gCurrentMap := g_Map_ParseMap(Data, Len);
1436 except
1437 gCurrentMap.Free();
1438 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]));
1439 FreeMem(Data);
1440 gCurrentMapFileName := '';
1441 Exit;
1442 end;
1444 FreeMem(Data);
1446 if (gCurrentMap = nil) then
1447 begin
1448 e_LogWritefln('invalid map file: ''%s''', [mapResName]);
1449 gCurrentMapFileName := '';
1450 exit;
1451 end;
1452 end
1453 else
1454 begin
1455 stt := getTimeMicro();
1456 end;
1458 //gCurrentMap := mapReader;
1460 generateExternalResourcesList(gCurrentMap);
1461 mapTextureList := gCurrentMap['texture'];
1462 // get all other lists here too
1463 panels := gCurrentMap['panel'];
1464 triggers := gCurrentMap['trigger'];
1465 items := gCurrentMap['item'];
1466 areas := gCurrentMap['area'];
1467 monsters := gCurrentMap['monster'];
1469 // Çàãðóçêà îïèñàíèÿ êàðòû:
1470 e_WriteLog(' Reading map info...', TMsgType.Notify);
1471 g_Game_SetLoadingText(_lc[I_LOAD_MAP_HEADER], 0, False);
1473 with gMapInfo do
1474 begin
1475 Name := gCurrentMap.MapName;
1476 Description := gCurrentMap.MapDesc;
1477 Author := gCurrentMap.MapAuthor;
1478 MusicName := gCurrentMap.MusicName;
1479 SkyName := gCurrentMap.SkyName;
1480 Height := gCurrentMap.Height;
1481 Width := gCurrentMap.Width;
1482 end;
1484 // Çàãðóçêà òåêñòóð:
1485 g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], 0, False);
1486 // Äîáàâëåíèå òåêñòóð â Textures[]:
1487 if (mapTextureList <> nil) and (mapTextureList.count > 0) then
1488 begin
1489 e_WriteLog(' Loading textures:', TMsgType.Notify);
1490 g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], mapTextureList.count-1, False);
1492 // find used textures
1493 usedTextures := THashStrInt.Create();
1494 try
1495 if (panels <> nil) and (panels.count > 0) then
1496 begin
1497 for rec in panels do
1498 begin
1499 texrec := rec.TextureRec;
1500 if (texrec <> nil) then usedTextures.put(toLowerCase1251(texrec.Resource), 42);
1501 end;
1502 end;
1504 cnt := -1;
1505 for rec in mapTextureList do
1506 begin
1507 Inc(cnt);
1508 if not usedTextures.has(toLowerCase1251(rec.Resource)) then
1509 begin
1510 rec.tagInt := -1; // just in case
1511 e_LogWritefln(' Unused texture #%d: %s', [cnt, rec.Resource]);
1512 end
1513 else
1514 begin
1515 {$IF DEFINED(D2F_DEBUG_TXLOAD)}
1516 e_LogWritefln(' Loading texture #%d: %s', [cnt, rec.Resource]);
1517 {$ENDIF}
1518 ntn := CreateTexture(rec.Resource, FileName, True);
1519 if ntn < 0 then
1520 begin
1521 if rec.Anim then
1522 g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_ANIM], [rec.Resource]))
1523 else
1524 g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_SIMPLE], [rec.Resource]));
1525 ntn := CreateNullTexture(rec.Resource)
1526 end;
1527 rec.tagInt := ntn; // remember texture number
1528 end;
1529 g_Game_StepLoading();
1530 end;
1531 finally
1532 usedTextures.Free();
1533 end;
1535 // set panel tagInt to texture index
1536 if (panels <> nil) then
1537 begin
1538 for rec in panels do
1539 begin
1540 texrec := rec.TextureRec;
1541 if (texrec = nil) then rec.tagInt := -1 else rec.tagInt := texrec.tagInt;
1542 end;
1543 end;
1544 end;
1547 // Çàãðóçêà òðèããåðîâ
1548 gTriggerClientID := 0;
1549 e_WriteLog(' Loading triggers...', TMsgType.Notify);
1550 g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS], 0, False);
1552 // Çàãðóçêà ïàíåëåé
1553 e_WriteLog(' Loading panels...', TMsgType.Notify);
1554 g_Game_SetLoadingText(_lc[I_LOAD_PANELS], 0, False);
1556 // check texture numbers for panels
1557 if (panels <> nil) and (panels.count > 0) then
1558 begin
1559 for rec in panels do
1560 begin
1561 if (rec.tagInt < 0) then
1562 begin
1563 e_WriteLog('error loading map: invalid texture index for panel', TMsgType.Fatal);
1564 result := false;
1565 gCurrentMap.Free();
1566 gCurrentMap := nil;
1567 gCurrentMapFileName := '';
1568 exit;
1569 end;
1570 end;
1571 end;
1573 // Ñîçäàíèå òàáëèöû òðèããåðîâ (ñîîòâåòñòâèå ïàíåëåé òðèããåðàì)
1574 if (triggers <> nil) and (triggers.count > 0) then
1575 begin
1576 e_WriteLog(' Setting up trigger table...', TMsgType.Notify);
1577 //SetLength(TriggersTable, triggers.count);
1578 g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS_TABLE], triggers.count-1, False);
1580 SetLength(TriggersTable, triggers.count);
1581 trignum := -1;
1582 for rec in triggers do
1583 begin
1584 Inc(trignum);
1585 pttit := @TriggersTable[trignum];
1586 pttit.trigrec := rec;
1587 // Ñìåíà òåêñòóðû (âîçìîæíî, êíîïêè)
1588 pttit.texPanelIdx := -1; // will be fixed later
1589 pttit.texPanel := rec.TexturePanelRec;
1590 // action panel
1591 pttit.actPanelIdx := -1;
1592 if (rec.trigRec <> nil) then pttit.actPanel := rec.trigRec.tgPanelRec else pttit.actPanel := nil;
1593 // set flag
1594 if (pttit.texPanel <> nil) then pttit.texPanel.userPanelTrigRef := true;
1595 if (pttit.actPanel <> nil) then pttit.actPanel.userPanelTrigRef := true;
1596 // update progress
1597 g_Game_StepLoading();
1598 end;
1599 end;
1601 // Ñîçäàåì ïàíåëè
1602 if (panels <> nil) and (panels.count > 0) then
1603 begin
1604 e_WriteLog(' Setting up trigger links...', TMsgType.Notify);
1605 g_Game_SetLoadingText(_lc[I_LOAD_LINK_TRIGGERS], panels.count-1, False);
1607 pannum := -1;
1608 for rec in panels do
1609 begin
1610 Inc(pannum);
1611 //e_LogWritefln('PANSTART: pannum=%s', [pannum]);
1612 texrec := nil;
1613 SetLength(AddTextures, 0);
1614 CurTex := -1;
1615 ok := false;
1617 if (mapTextureList <> nil) then
1618 begin
1619 texrec := rec.TextureRec;
1620 ok := (texrec <> nil);
1621 end;
1623 if ok then
1624 begin
1625 // Ñìîòðèì, ññûëàþòñÿ ëè íà ýòó ïàíåëü òðèããåðû.
1626 // Åñëè äà - òî íàäî ñîçäàòü åùå òåêñòóð
1627 ok := false;
1628 if (TriggersTable <> nil) and (mapTextureList <> nil) then
1629 begin
1630 if rec.userPanelTrigRef then
1631 begin
1632 // e_LogWritefln('trigref for panel %s', [pannum]);
1633 ok := True;
1634 end;
1635 end;
1636 end;
1638 if ok then
1639 begin
1640 // Åñòü ññûëêè òðèããåðîâ íà ýòó ïàíåëü
1641 s := texrec.Resource;
1643 // Ñïåö-òåêñòóðû çàïðåùåíû
1644 if g_Map_IsSpecialTexture(s) then
1645 begin
1646 ok := false
1647 end
1648 else
1649 begin
1650 // Îïðåäåëÿåì íàëè÷èå è ïîëîæåíèå öèôð â êîíöå ñòðîêè
1651 ok := g_Texture_NumNameFindStart(s);
1652 end;
1654 // Åñëè ok, çíà÷èò åñòü öèôðû â êîíöå.
1655 // Çàãðóæàåì òåêñòóðû ñ îñòàëüíûìè #
1656 if ok then
1657 begin
1658 k := NNF_NAME_BEFORE;
1659 // Öèêë ïî èçìåíåíèþ èìåíè òåêñòóðû
1660 while ok or (k = NNF_NAME_BEFORE) or (k = NNF_NAME_EQUALS) do
1661 begin
1662 k := g_Texture_NumNameFindNext(TexName);
1664 if (k = NNF_NAME_BEFORE) or (k = NNF_NAME_AFTER) then
1665 begin
1666 ok := CreateTexture(TexName, FileName, False) >= 0;
1668 // Îíà ñóùåñòâóåò. Çàíîñèì åå ID â ñïèñîê ïàíåëè
1669 if ok then
1670 begin
1672 for c := 0 to High(Textures) do
1673 begin
1674 if (Textures[c].TextureName = TexName) then
1675 begin
1676 SetLength(AddTextures, Length(AddTextures)+1);
1677 AddTextures[High(AddTextures)].Texture := c;
1678 break;
1679 end;
1680 end;
1682 if (TextNameHash <> nil) and TextNameHash.get(toLowerCase1251(TexName), c) then
1683 begin
1684 SetLength(AddTextures, Length(AddTextures)+1);
1685 AddTextures[High(AddTextures)].Texture := c;
1686 end;
1687 end;
1688 end
1689 else
1690 begin
1691 if k = NNF_NAME_EQUALS then
1692 begin
1693 // Çàíîñèì òåêóùóþ òåêñòóðó íà ñâîå ìåñòî
1694 SetLength(AddTextures, Length(AddTextures)+1);
1695 AddTextures[High(AddTextures)].Texture := rec.tagInt; // internal texture number, not map index
1696 CurTex := High(AddTextures);
1697 ok := true;
1698 end
1699 else // NNF_NO_NAME
1700 begin
1701 ok := false;
1702 end;
1703 end;
1704 end; // while ok...
1706 ok := true;
1707 end; // if ok - åñòü ñìåæíûå òåêñòóðû
1708 end; // if ok - ññûëàþòñÿ òðèããåðû
1710 if not ok then
1711 begin
1712 // Çàíîñèì òîëüêî òåêóùóþ òåêñòóðó
1713 SetLength(AddTextures, 1);
1714 AddTextures[0].Texture := rec.tagInt; // internal texture number, not map index
1715 CurTex := 0;
1716 end;
1718 //e_WriteLog(Format('panel #%d: TextureNum=%d; ht=%d; ht1=%d; atl=%d', [a, panels[a].TextureNum, High(mapTextureList), High(Textures), High(AddTextures)]), MSG_NOTIFY);
1720 //e_LogWritefln('PANADD: pannum=%s', [pannum]);
1722 // Ñîçäàåì ïàíåëü è çàïîìèíàåì åå GUID
1723 //e_LogWritefln('new panel; tcount=%s; curtex=%s', [Length(AddTextures), CurTex]);
1724 PanelID := CreatePanel(rec, AddTextures, CurTex);
1725 //e_LogWritefln('panel #%s of type %s got guid #%s', [pannum, rec.PanelType, PanelID]);
1726 rec.userPanelId := PanelID; // remember game panel id, we'll fix triggers later
1728 // setup lifts
1729 moveSpeed := rec.moveSpeed;
1730 //moveStart := rec.moveStart;
1731 //moveEnd := rec.moveEnd;
1732 //moveActive := rec['move_active'].value;
1733 if not moveSpeed.isZero then
1734 begin
1735 SetLength(gMovingWallIds, Length(gMovingWallIds)+1);
1736 gMovingWallIds[High(gMovingWallIds)] := PanelID;
1737 //e_LogWritefln('found moving panel ''%s'' (idx=%s; id=%s)', [rec.id, pannum, PanelID]);
1738 end;
1740 //e_LogWritefln('PANEND: pannum=%s', [pannum]);
1742 g_Game_StepLoading();
1743 end;
1744 end;
1746 // ×èíèì ID'û ïàíåëåé, êîòîðûå èñïîëüçóþòñÿ â òðèããåðàõ
1747 for b := 0 to High(TriggersTable) do
1748 begin
1749 if (TriggersTable[b].texPanel <> nil) then TriggersTable[b].texPanelIdx := TriggersTable[b].texPanel.userPanelId;
1750 if (TriggersTable[b].actPanel <> nil) then TriggersTable[b].actPanelIdx := TriggersTable[b].actPanel.userPanelId;
1751 end;
1753 // create map grid, init other grids (for monsters, for example)
1754 e_WriteLog('Creating map grid', TMsgType.Notify);
1755 mapCreateGrid();
1757 // Åñëè íå LoadState, òî ñîçäàåì òðèããåðû
1758 if (triggers <> nil) and (panels <> nil) and (not gLoadGameMode) then
1759 begin
1760 e_LogWritefln(' Creating triggers (%d)...', [triggers.count]);
1761 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_TRIGGERS], 0, False);
1762 // Óêàçûâàåì òèï ïàíåëè, åñëè åñòü
1763 trignum := -1;
1764 for rec in triggers do
1765 begin
1766 Inc(trignum);
1767 tgpid := TriggersTable[trignum].actPanelIdx;
1768 //e_LogWritefln('creating trigger #%s; texpantype=%s; shotpantype=%s (%d,%d)', [trignum, b, c, TriggersTable[trignum].texPanIdx, TriggersTable[trignum].ShotPanelIdx]);
1769 TriggersTable[trignum].tnum := trignum;
1770 TriggersTable[trignum].id := CreateTrigger(trignum, rec, TriggersTable[trignum].texPanelIdx, tgpid);
1771 end;
1772 end;
1774 //FIXME: use hashtable!
1775 for pan in panByGUID do
1776 begin
1777 if (pan.endPosTrigId >= 0) and (pan.endPosTrigId < Length(TriggersTable)) then
1778 begin
1779 pan.endPosTrigId := TriggersTable[pan.endPosTrigId].id;
1780 end;
1781 if (pan.endSizeTrigId >= 0) and (pan.endSizeTrigId < Length(TriggersTable)) then
1782 begin
1783 pan.endSizeTrigId := TriggersTable[pan.endSizeTrigId].id;
1784 end;
1785 end;
1787 // Çàãðóçêà ïðåäìåòîâ
1788 e_WriteLog(' Loading items...', TMsgType.Notify);
1789 g_Game_SetLoadingText(_lc[I_LOAD_ITEMS], 0, False);
1791 // Åñëè íå LoadState, òî ñîçäàåì ïðåäìåòû
1792 if (items <> nil) and not gLoadGameMode then
1793 begin
1794 e_WriteLog(' Spawning items...', TMsgType.Notify);
1795 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_ITEMS], 0, False);
1796 for rec in items do CreateItem(rec);
1797 end;
1799 // Çàãðóçêà îáëàñòåé
1800 e_WriteLog(' Loading areas...', TMsgType.Notify);
1801 g_Game_SetLoadingText(_lc[I_LOAD_AREAS], 0, False);
1803 // Åñëè íå LoadState, òî ñîçäàåì îáëàñòè
1804 if areas <> nil then
1805 begin
1806 e_WriteLog(' Creating areas...', TMsgType.Notify);
1807 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_AREAS], 0, False);
1808 for rec in areas do CreateArea(rec);
1809 end;
1811 // Çàãðóçêà ìîíñòðîâ
1812 e_WriteLog(' Loading monsters...', TMsgType.Notify);
1813 g_Game_SetLoadingText(_lc[I_LOAD_MONSTERS], 0, False);
1815 gTotalMonsters := 0;
1817 // Åñëè íå LoadState, òî ñîçäàåì ìîíñòðîâ
1818 if (monsters <> nil) and not gLoadGameMode then
1819 begin
1820 e_WriteLog(' Spawning monsters...', TMsgType.Notify);
1821 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_MONSTERS], 0, False);
1822 for rec in monsters do CreateMonster(rec);
1823 end;
1825 //gCurrentMap := mapReader; // this will be our current map now
1826 gCurrentMapFileName := Res;
1827 //mapReader := nil;
1829 // Çàãðóçêà íåáà
1830 gMapInfo.SkyFullName := '';
1831 if (gMapInfo.SkyName <> '') then
1832 begin
1833 e_WriteLog(' Loading sky: ' + gMapInfo.SkyName, TMsgType.Notify);
1834 g_Game_SetLoadingText(_lc[I_LOAD_SKY], 0, False);
1835 gMapInfo.SkyFullName := e_GetResourcePath(WadDirs, gMapInfo.SkyName, g_ExtractWadName(Res));
1836 end;
1838 // Çàãðóçêà ìóçûêè
1839 ok := False;
1840 if gMapInfo.MusicName <> '' then
1841 begin
1842 e_WriteLog(' Loading music: ' + gMapInfo.MusicName, TMsgType.Notify);
1843 g_Game_SetLoadingText(_lc[I_LOAD_MUSIC], 0, False);
1845 s := e_GetResourcePath(WadDirs, gMapInfo.MusicName, g_ExtractWadName(Res));
1846 if g_Sound_CreateWADEx(gMapInfo.MusicName, s, True) then
1847 ok := True
1848 else
1849 g_FatalError(Format(_lc[I_GAME_ERROR_MUSIC], [s]));
1850 end;
1852 // Îñòàëüíûå óñòàíâêè
1853 CreateDoorMap();
1854 CreateLiftMap();
1856 g_Items_Init();
1857 g_Weapon_Init();
1858 g_Monsters_Init();
1860 {$IFDEF ENABLE_GFX}
1861 // Åñëè íå LoadState, òî ñîçäàåì êàðòó ñòîëêíîâåíèé:
1862 if not gLoadGameMode then g_GFX_Init();
1863 {$ENDIF}
1865 // Ñáðîñ ëîêàëüíûõ ìàññèâîâ:
1866 mapTextureList := nil;
1867 panels := nil;
1868 items := nil;
1869 areas := nil;
1870 triggers := nil;
1871 TriggersTable := nil;
1872 AddTextures := nil;
1874 // Âêëþ÷àåì ìóçûêó, åñëè ýòî íå çàãðóçêà:
1875 if ok and (not gLoadGameMode) then
1876 begin
1877 gMusic.SetByName(gMapInfo.MusicName);
1878 gMusic.Play();
1879 end
1880 else
1881 begin
1882 gMusic.SetByName('');
1883 end;
1885 stt := getTimeMicro()-stt;
1886 e_LogWritefln('map loaded in %s.%s milliseconds', [Integer(stt div 1000), Integer(stt mod 1000)]);
1887 mapOk := true;
1888 finally
1889 sfsGCEnable(); // enable releasing unused volumes
1890 //mapReader.Free();
1891 e_UnpressAllKeys; // why not?
1892 if not mapOk then
1893 begin
1894 gCurrentMap.Free();
1895 gCurrentMap := nil;
1896 gCurrentMapFileName := '';
1897 end;
1898 end;
1900 compactExtResList();
1901 e_WriteLog('Done loading map.', TMsgType.Notify);
1902 Result := True;
1903 end;
1906 function g_Map_GetMapInfo(Res: String): TMapInfo;
1907 var
1908 WAD: TWADFile;
1909 mapReader: TDynRecord;
1910 //Header: TMapHeaderRec_1;
1911 FileName: String;
1912 Data: Pointer;
1913 Len: Integer;
1914 begin
1915 FillChar(Result, SizeOf(Result), 0);
1916 FileName := g_ExtractWadName(Res);
1918 WAD := TWADFile.Create();
1919 if not WAD.ReadFile(FileName) then
1920 begin
1921 WAD.Free();
1922 Exit;
1923 end;
1925 //k8: it ignores path again
1926 if not WAD.GetMapResource(g_ExtractFileName(Res), Data, Len) then
1927 begin
1928 WAD.Free();
1929 Exit;
1930 end;
1932 WAD.Free();
1934 try
1935 mapReader := g_Map_ParseMap(Data, Len);
1936 except
1937 mapReader := nil;
1938 FreeMem(Data);
1939 exit;
1940 end;
1942 FreeMem(Data);
1944 if (mapReader = nil) then exit;
1946 if (mapReader.Width > 0) and (mapReader.Height > 0) then
1947 begin
1948 Result.Name := mapReader.MapName;
1949 Result.Description := mapReader.MapDesc;
1950 Result.Map := Res;
1951 Result.Author := mapReader.MapAuthor;
1952 Result.Height := mapReader.Height;
1953 Result.Width := mapReader.Width;
1954 end
1955 else
1956 begin
1957 g_Console_Add(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]), True);
1958 //ZeroMemory(@Header, SizeOf(Header));
1959 Result.Name := _lc[I_GAME_ERROR_MAP_SELECT];
1960 Result.Description := _lc[I_GAME_ERROR_MAP_SELECT];
1961 Result.Map := Res;
1962 Result.Author := '';
1963 Result.Height := 0;
1964 Result.Width := 0;
1965 end;
1967 mapReader.Free();
1968 end;
1970 function g_Map_GetMapsList(WADName: string): SSArray;
1971 var
1972 WAD: TWADFile;
1973 a: Integer;
1974 ResList: SSArray;
1975 begin
1976 Result := nil;
1977 WAD := TWADFile.Create();
1978 if not WAD.ReadFile(WADName) then
1979 begin
1980 WAD.Free();
1981 Exit;
1982 end;
1983 ResList := WAD.GetMapResources();
1984 if ResList <> nil then
1985 begin
1986 for a := 0 to High(ResList) do
1987 begin
1988 SetLength(Result, Length(Result)+1);
1989 Result[High(Result)] := ResList[a];
1990 end;
1991 end;
1992 WAD.Free();
1993 end;
1995 function g_Map_Exist(Res: string): Boolean;
1996 var
1997 WAD: TWADFile;
1998 FileName, mnn: string;
1999 ResList: SSArray;
2000 a: Integer;
2001 begin
2002 Result := False;
2004 FileName := addWadExtension(g_ExtractWadName(Res));
2006 WAD := TWADFile.Create;
2007 if not WAD.ReadFile(FileName) then
2008 begin
2009 WAD.Free();
2010 Exit;
2011 end;
2013 ResList := WAD.GetMapResources();
2014 WAD.Free();
2016 mnn := g_ExtractFileName(Res);
2017 if ResList <> nil then
2018 for a := 0 to High(ResList) do if StrEquCI1251(ResList[a], mnn) then
2019 begin
2020 Result := True;
2021 Exit;
2022 end;
2023 end;
2025 procedure g_Map_Free(freeTextures: Boolean=true);
2027 procedure FreePanelArray(var panels: TPanelArray);
2028 var
2029 i: Integer;
2031 begin
2032 if panels <> nil then
2033 begin
2034 for i := 0 to High(panels) do
2035 panels[i].Free();
2036 panels := nil;
2037 end;
2038 end;
2040 begin
2041 {$IFDEF ENABLE_GFX}
2042 g_GFX_Free;
2043 {$ENDIF}
2044 g_Weapon_Free();
2045 g_Items_Free();
2046 g_Triggers_Free();
2047 g_Monsters_Free();
2049 RespawnPoints := nil;
2050 if FlagPoints[FLAG_RED] <> nil then
2051 begin
2052 Dispose(FlagPoints[FLAG_RED]);
2053 FlagPoints[FLAG_RED] := nil;
2054 end;
2055 if FlagPoints[FLAG_BLUE] <> nil then
2056 begin
2057 Dispose(FlagPoints[FLAG_BLUE]);
2058 FlagPoints[FLAG_BLUE] := nil;
2059 end;
2060 //DOMFlagPoints := nil;
2062 //gDOMFlags := nil;
2064 if (Length(gCurrentMapFileName) <> 0) then
2065 begin
2066 e_LogWritefln('g_Map_Free: previous map was ''%s''...', [gCurrentMapFileName]);
2067 end
2068 else
2069 begin
2070 e_LogWritefln('g_Map_Free: no previous map.', []);
2071 end;
2073 if freeTextures then
2074 begin
2075 e_LogWritefln('g_Map_Free: clearing textures...', []);
2076 Textures := nil;
2077 TextNameHash.Free();
2078 TextNameHash := nil;
2079 BadTextNameHash.Free();
2080 BadTextNameHash := nil;
2081 gCurrentMapFileName := '';
2082 gCurrentMap.Free();
2083 gCurrentMap := nil;
2084 end;
2086 panByGUID := nil;
2088 FreePanelArray(gWalls);
2089 FreePanelArray(gRenderBackgrounds);
2090 FreePanelArray(gRenderForegrounds);
2091 FreePanelArray(gWater);
2092 FreePanelArray(gAcid1);
2093 FreePanelArray(gAcid2);
2094 FreePanelArray(gSteps);
2095 FreePanelArray(gLifts);
2096 FreePanelArray(gBlockMon);
2097 gMovingWallIds := nil;
2099 g_Game_StopAllSounds(False);
2100 gMusic.FreeSound();
2101 g_Sound_Delete(gMapInfo.MusicName);
2103 gMapInfo.Name := '';
2104 gMapInfo.Description := '';
2105 gMapInfo.MusicName := '';
2106 gMapInfo.Height := 0;
2107 gMapInfo.Width := 0;
2109 gDoorMap := nil;
2110 gLiftMap := nil;
2111 end;
2113 procedure g_Map_Update();
2114 var
2115 a, d, j: Integer;
2116 m: Word;
2117 s: String;
2118 b: Byte;
2120 procedure UpdatePanelArray(var panels: TPanelArray);
2121 var
2122 i: Integer;
2124 begin
2125 for i := 0 to High(panels) do panels[i].Update();
2126 end;
2128 begin
2129 if g_dbgpan_mplat_step then g_dbgpan_mplat_active := true;
2131 UpdatePanelArray(gWalls);
2132 UpdatePanelArray(gRenderBackgrounds);
2133 UpdatePanelArray(gRenderForegrounds);
2134 UpdatePanelArray(gWater);
2135 UpdatePanelArray(gAcid1);
2136 UpdatePanelArray(gAcid2);
2137 UpdatePanelArray(gSteps);
2139 if g_dbgpan_mplat_step then begin g_dbgpan_mplat_step := false; g_dbgpan_mplat_active := false; end;
2141 if gGameSettings.GameMode = GM_CTF then
2142 begin
2143 for a := FLAG_RED to FLAG_BLUE do
2144 begin
2145 if not (gFlags[a].State in [FLAG_STATE_NONE, FLAG_STATE_CAPTURED]) then
2146 begin
2147 with gFlags[a] do
2148 begin
2149 Obj.oldX := Obj.X;
2150 Obj.oldY := Obj.Y;
2152 m := g_Obj_Move(@Obj, True, True);
2154 NeedSend := NeedSend or (Obj.X <> Obj.oldX) or (Obj.Y <> Obj.oldY);
2156 if gTime mod (GAME_TICK*2) <> 0 then Continue;
2158 // Ñîïðîòèâëåíèå âîçäóõà
2159 Obj.Vel.X := z_dec(Obj.Vel.X, 1);
2161 // Òàéìàóò ïîòåðÿííîãî ôëàãà, ëèáî îí âûïàë çà êàðòó
2162 if ((Count = 0) or ByteBool(m and MOVE_FALLOUT)) and g_Game_IsServer then
2163 begin
2164 g_Map_ResetFlag(a);
2165 gFlags[a].CaptureTime := 0;
2166 if a = FLAG_RED then
2167 s := _lc[I_PLAYER_FLAG_RED]
2168 else
2169 s := _lc[I_PLAYER_FLAG_BLUE];
2170 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
2172 if (((gPlayer1 <> nil) and (((gPlayer1.Team = TEAM_RED) and (a = FLAG_RED)) or ((gPlayer1.Team = TEAM_BLUE) and (a = FLAG_BLUE))))
2173 or ((gPlayer2 <> nil) and (((gPlayer2.Team = TEAM_RED) and (a = FLAG_RED)) or ((gPlayer2.Team = TEAM_BLUE) and (a = FLAG_BLUE))))) then
2174 b := 0
2175 else
2176 b := 1;
2178 if not sound_ret_flag[b].IsPlaying() then
2179 sound_ret_flag[b].Play();
2181 if g_Game_IsNet then
2182 MH_SEND_FlagEvent(FLAG_STATE_RETURNED, a, 0);
2183 Continue;
2184 end;
2186 if Count > 0 then Count -= 1;
2188 // Èãðîê áåðåò ôëàã
2189 if gPlayers <> nil then
2190 begin
2191 j := Random(Length(gPlayers)) - 1;
2192 for d := 0 to High(gPlayers) do
2193 begin
2194 Inc(j);
2195 if j > High(gPlayers) then j := 0;
2196 if gPlayers[j] <> nil then
2197 begin
2198 if gPlayers[j].alive and g_Obj_Collide(@Obj, @gPlayers[j].Obj) then
2199 begin
2200 if gPlayers[j].GetFlag(a) then Break;
2201 end;
2202 end;
2203 end;
2204 end;
2205 end;
2206 end;
2207 end;
2208 end;
2209 end;
2211 function g_Map_CollidePanelOld(X, Y: Integer; Width, Height: Word;
2212 PanelType: Word; b1x3: Boolean=false): Boolean;
2213 var
2214 a, h: Integer;
2215 begin
2216 Result := False;
2218 if WordBool(PanelType and PANEL_WALL) then
2219 if gWalls <> nil then
2220 begin
2221 h := High(gWalls);
2223 for a := 0 to h do
2224 if gWalls[a].Enabled and
2225 g_Collide(X, Y, Width, Height,
2226 gWalls[a].X, gWalls[a].Y,
2227 gWalls[a].Width, gWalls[a].Height) then
2228 begin
2229 Result := True;
2230 Exit;
2231 end;
2232 end;
2234 if WordBool(PanelType and PANEL_WATER) then
2235 if gWater <> nil then
2236 begin
2237 h := High(gWater);
2239 for a := 0 to h do
2240 if g_Collide(X, Y, Width, Height,
2241 gWater[a].X, gWater[a].Y,
2242 gWater[a].Width, gWater[a].Height) then
2243 begin
2244 Result := True;
2245 Exit;
2246 end;
2247 end;
2249 if WordBool(PanelType and PANEL_ACID1) then
2250 if gAcid1 <> nil then
2251 begin
2252 h := High(gAcid1);
2254 for a := 0 to h do
2255 if g_Collide(X, Y, Width, Height,
2256 gAcid1[a].X, gAcid1[a].Y,
2257 gAcid1[a].Width, gAcid1[a].Height) then
2258 begin
2259 Result := True;
2260 Exit;
2261 end;
2262 end;
2264 if WordBool(PanelType and PANEL_ACID2) then
2265 if gAcid2 <> nil then
2266 begin
2267 h := High(gAcid2);
2269 for a := 0 to h do
2270 if g_Collide(X, Y, Width, Height,
2271 gAcid2[a].X, gAcid2[a].Y,
2272 gAcid2[a].Width, gAcid2[a].Height) then
2273 begin
2274 Result := True;
2275 Exit;
2276 end;
2277 end;
2279 if WordBool(PanelType and PANEL_STEP) then
2280 if gSteps <> nil then
2281 begin
2282 h := High(gSteps);
2284 for a := 0 to h do
2285 if g_Collide(X, Y, Width, Height,
2286 gSteps[a].X, gSteps[a].Y,
2287 gSteps[a].Width, gSteps[a].Height) then
2288 begin
2289 Result := True;
2290 Exit;
2291 end;
2292 end;
2294 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then
2295 if gLifts <> nil then
2296 begin
2297 h := High(gLifts);
2299 for a := 0 to h do
2300 if ((WordBool(PanelType and (PANEL_LIFTUP)) and (gLifts[a].LiftType = LIFTTYPE_UP)) or
2301 (WordBool(PanelType and (PANEL_LIFTDOWN)) and (gLifts[a].LiftType = LIFTTYPE_DOWN)) or
2302 (WordBool(PanelType and (PANEL_LIFTLEFT)) and (gLifts[a].LiftType = LIFTTYPE_LEFT)) or
2303 (WordBool(PanelType and (PANEL_LIFTRIGHT)) and (gLifts[a].LiftType = LIFTTYPE_RIGHT))) and
2304 g_Collide(X, Y, Width, Height,
2305 gLifts[a].X, gLifts[a].Y,
2306 gLifts[a].Width, gLifts[a].Height) then
2307 begin
2308 Result := True;
2309 Exit;
2310 end;
2311 end;
2313 if WordBool(PanelType and PANEL_BLOCKMON) then
2314 if gBlockMon <> nil then
2315 begin
2316 h := High(gBlockMon);
2318 for a := 0 to h do
2319 if ( (not b1x3) or
2320 ((gBlockMon[a].Width + gBlockMon[a].Height) >= 64) ) and
2321 g_Collide(X, Y, Width, Height,
2322 gBlockMon[a].X, gBlockMon[a].Y,
2323 gBlockMon[a].Width, gBlockMon[a].Height) then
2324 begin
2325 Result := True;
2326 Exit;
2327 end;
2328 end;
2329 end;
2331 function g_Map_CollideLiquid_TextureOld(X, Y: Integer; Width, Height: Word): DWORD;
2332 var
2333 texid: DWORD;
2335 function checkPanels (constref panels: TPanelArray): Boolean;
2336 var
2337 a: Integer;
2338 begin
2339 result := false;
2340 if panels = nil then exit;
2341 for a := 0 to High(panels) do
2342 begin
2343 if g_Collide(X, Y, Width, Height, panels[a].X, panels[a].Y, panels[a].Width, panels[a].Height) then
2344 begin
2345 result := true;
2346 texid := panels[a].GetTextureID();
2347 exit;
2348 end;
2349 end;
2350 end;
2352 begin
2353 texid := LongWord(TEXTURE_NONE);
2354 result := texid;
2355 if not checkPanels(gWater) then
2356 if not checkPanels(gAcid1) then
2357 if not checkPanels(gAcid2) then exit;
2358 result := texid;
2359 end;
2362 function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word; PanelType: Word; b1x3: Boolean): Boolean;
2363 const
2364 SlowMask = GridTagLift or GridTagBlockMon;
2366 function checker (pan: TPanel; tag: Integer): Boolean;
2367 begin
2369 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
2370 begin
2371 result := pan.Enabled;
2372 exit;
2373 end;
2376 if ((tag and GridTagLift) <> 0) then
2377 begin
2378 result :=
2379 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
2380 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
2381 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
2382 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT))) {and
2383 g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
2384 exit;
2385 end;
2387 if ((tag and GridTagBlockMon) <> 0) then
2388 begin
2389 result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2390 exit;
2391 end;
2393 // other shit
2394 //result := g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2395 result := true; // i found her!
2396 end;
2398 var
2399 tagmask: Integer = 0;
2400 mwit: PPanel;
2401 it: TPanelGrid.Iter;
2402 pan: TPanel;
2403 begin
2404 result := false;
2405 if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor);
2406 if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater;
2407 if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1;
2408 if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2;
2409 if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep;
2410 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift;
2411 if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon;
2413 if (tagmask = 0) then exit; // just in case
2415 if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('*solids');
2416 if gdbg_map_use_accel_coldet then
2417 begin
2418 if ((tagmask and SlowMask) <> 0) then
2419 begin
2420 // slow
2421 it := mapGrid.forEachInAABB(X, Y, Width, Height, tagmask);
2422 for mwit in it do
2423 begin
2424 pan := mwit^;
2425 if ((pan.tag and GridTagLift) <> 0) then
2426 begin
2427 result :=
2428 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
2429 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
2430 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
2431 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT))) {and
2432 g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
2433 end
2434 else if ((pan.tag and GridTagBlockMon) <> 0) then
2435 begin
2436 result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2437 end
2438 else
2439 begin
2440 // other shit
2441 result := true; // i found her!
2442 end;
2443 if (result) then break;
2444 end;
2445 end
2446 else
2447 begin
2448 // fast
2449 it := mapGrid.forEachInAABB(X, Y, Width, Height, tagmask, false, true); // return first hit
2450 result := (it.length > 0);
2451 end;
2452 it.release();
2453 end
2454 else
2455 begin
2456 result := g_Map_CollidePanelOld(X, Y, Width, Height, PanelType, b1x3);
2457 end;
2458 if (profMapCollision <> nil) then profMapCollision.sectionEnd();
2459 end;
2462 // returns `true` if we need to stop
2463 function liquidChecker (pan: TPanel; var texid: DWORD; var cctype: Integer): Boolean; inline;
2464 begin
2465 result := false;
2466 //if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2)) = 0) then exit;
2467 // check priorities
2468 case cctype of
2469 0: if ((pan.tag and GridTagWater) = 0) then exit; // allowed: water
2470 1: if ((pan.tag and (GridTagWater or GridTagAcid1)) = 0) then exit; // allowed: water, acid1
2471 //2: if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2) = 0) then exit; // allowed: water, acid1, acid2
2472 end;
2473 // collision?
2474 //if not g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height) then exit;
2475 // yeah
2476 texid := pan.GetTextureID();
2477 // water? water has the highest priority, so stop right here
2478 if ((pan.tag and GridTagWater) <> 0) then begin cctype := 0; result := true; exit; end;
2479 // acid2?
2480 if ((pan.tag and GridTagAcid2) <> 0) then cctype := 2;
2481 // acid1?
2482 if ((pan.tag and GridTagAcid1) <> 0) then cctype := 1;
2483 end;
2485 function g_Map_CollideLiquid_Texture(X, Y: Integer; Width, Height: Word): DWORD;
2486 var
2487 cctype: Integer = 3; // priority: 0: water was hit, 1: acid1 was hit, 2: acid2 was hit; 3: nothing was hit
2488 mwit: PPanel;
2489 it: TPanelGrid.Iter;
2490 begin
2491 if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('liquids');
2492 if gdbg_map_use_accel_coldet then
2493 begin
2494 result := LongWord(TEXTURE_NONE);
2495 it := mapGrid.forEachInAABB(X, Y, Width, Height, (GridTagWater or GridTagAcid1 or GridTagAcid2));
2496 for mwit in it do if (liquidChecker(mwit^, result, cctype)) then break;
2497 it.release();
2498 end
2499 else
2500 begin
2501 result := g_Map_CollideLiquid_TextureOld(X, Y, Width, Height);
2502 end;
2503 if (profMapCollision <> nil) then profMapCollision.sectionEnd();
2504 end;
2507 procedure g_Map_EnableWall_XXX (ID: DWORD); begin if (ID < Length(gWalls)) then g_Map_EnableWallGUID(gWalls[ID].guid); end;
2508 procedure g_Map_DisableWall_XXX (ID: DWORD); begin if (ID < Length(gWalls)) then g_Map_DisableWallGUID(gWalls[ID].guid); end;
2509 procedure g_Map_SetLift_XXX (ID: DWORD; t: Integer); begin if (ID < Length(gLifts)) then g_Map_SetLiftGUID(gLifts[ID].guid, t); end;
2512 procedure g_Map_EnableWallGUID (pguid: Integer);
2513 var
2514 pan: TPanel;
2515 begin
2516 //pan := gWalls[ID];
2517 pan := g_Map_PanelByGUID(pguid);
2518 if (pan = nil) then exit;
2519 if pan.Enabled and mapGrid.proxyEnabled[pan.proxyId] then exit;
2521 pan.Enabled := True;
2523 {$IFDEF ENABLE_GFX}
2524 g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, true);
2525 {$ENDIF}
2527 mapGrid.proxyEnabled[pan.proxyId] := true;
2528 //if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := true
2529 //else pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, GridTagDoor);
2531 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2532 // mark platform as interesting
2533 pan.setDirty();
2535 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
2536 //e_WriteLog(Format('ENABLE: wall #%d(%d) enabled (%d) (%d,%d)-(%d,%d)', [Integer(ID), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId]), pan.x, pan.y, pan.width, pan.height]), MSG_NOTIFY);
2537 {$ENDIF}
2538 end;
2541 procedure g_Map_DisableWallGUID (pguid: Integer);
2542 var
2543 pan: TPanel;
2544 begin
2545 //pan := gWalls[ID];
2546 pan := g_Map_PanelByGUID(pguid);
2547 if (pan = nil) then exit;
2548 if (not pan.Enabled) and (not mapGrid.proxyEnabled[pan.proxyId]) then exit;
2550 pan.Enabled := False;
2551 {$IFDEF ENABLE_GFX}
2552 g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, false);
2553 {$ENDIF}
2555 mapGrid.proxyEnabled[pan.proxyId] := false;
2556 //if (pan.proxyId >= 0) then begin mapGrid.removeBody(pan.proxyId); pan.proxyId := -1; end;
2558 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2559 // mark platform as interesting
2560 pan.setDirty();
2562 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
2563 //e_WriteLog(Format('DISABLE: wall #%d(%d) disabled (%d) (%d,%d)-(%d,%d)', [Integer(ID), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId]), pan.x, pan.y, pan.width, pan.height]), MSG_NOTIFY);
2564 {$ENDIF}
2565 end;
2568 procedure g_Map_SwitchTextureGUID (pguid: Integer; AnimLoop: Byte = 0);
2569 var
2570 tp: TPanel;
2571 begin
2572 tp := g_Map_PanelByGUID(pguid);
2573 if (tp = nil) then exit;
2574 tp.NextTexture(AnimLoop);
2575 if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelTexture(pguid);
2576 end;
2579 procedure g_Map_SetLiftGUID (pguid: Integer; t: Integer);
2580 var
2581 pan: TPanel;
2582 begin
2583 //pan := gLifts[ID];
2584 pan := g_Map_PanelByGUID(pguid);
2585 if (pan = nil) then exit;
2586 if not pan.isGLift then exit;
2588 if ({gLifts[ID]}pan.LiftType = t) then exit; //!FIXME!TRIGANY!
2590 with {gLifts[ID]} pan do
2591 begin
2592 LiftType := t;
2594 {$IFDEF ENABLE_GFX}
2595 g_Mark(X, Y, Width, Height, MARK_LIFT, false);
2596 //TODO: make separate lift tags, and change tag here
2597 case LiftType of
2598 LIFTTYPE_UP: g_Mark(X, Y, Width, Height, MARK_LIFTUP);
2599 LIFTTYPE_DOWN: g_Mark(X, Y, Width, Height, MARK_LIFTDOWN);
2600 LIFTTYPE_LEFT: g_Mark(X, Y, Width, Height, MARK_LIFTLEFT);
2601 LIFTTYPE_RIGHT: g_Mark(X, Y, Width, Height, MARK_LIFTRIGHT);
2602 end;
2603 {$ENDIF}
2605 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2606 // mark platform as interesting
2607 pan.setDirty();
2608 end;
2609 end;
2612 function g_Map_GetPoint (PointType: Byte; var RespawnPoint: TRespawnPoint): Boolean;
2613 var
2614 a: Integer;
2615 PointsArray: Array of TRespawnPoint;
2616 begin
2617 Result := False;
2618 SetLength(PointsArray, 0);
2620 if RespawnPoints = nil then
2621 Exit;
2623 for a := 0 to High(RespawnPoints) do
2624 if RespawnPoints[a].PointType = PointType then
2625 begin
2626 SetLength(PointsArray, Length(PointsArray)+1);
2627 PointsArray[High(PointsArray)] := RespawnPoints[a];
2628 end;
2630 if PointsArray = nil then
2631 Exit;
2633 RespawnPoint := PointsArray[Random(Length(PointsArray))];
2634 Result := True;
2635 end;
2637 function g_Map_GetPointCount(PointType: Byte): Word;
2638 var
2639 a: Integer;
2640 begin
2641 Result := 0;
2643 if RespawnPoints = nil then
2644 Exit;
2646 for a := 0 to High(RespawnPoints) do
2647 if RespawnPoints[a].PointType = PointType then
2648 Result := Result + 1;
2649 end;
2651 function g_Map_GetRandomPointType(): Byte;
2652 begin
2653 if RespawnPoints = nil then
2654 Result := 255
2655 else
2656 Result := RespawnPoints[Random(Length(RespawnPoints))].PointType;
2657 end;
2659 function g_Map_HaveFlagPoints(): Boolean;
2660 begin
2661 Result := (FlagPoints[FLAG_RED] <> nil) and (FlagPoints[FLAG_BLUE] <> nil);
2662 end;
2664 procedure g_Map_ResetFlag(Flag: Byte);
2665 begin
2666 with gFlags[Flag] do
2667 begin
2668 Obj.X := -1000;
2669 Obj.Y := -1000;
2670 Obj.Vel.X := 0;
2671 Obj.Vel.Y := 0;
2672 Direction := TDirection.D_LEFT;
2673 State := FLAG_STATE_NONE;
2674 if FlagPoints[Flag] <> nil then
2675 begin
2676 Obj.X := FlagPoints[Flag]^.X;
2677 Obj.Y := FlagPoints[Flag]^.Y;
2678 Direction := FlagPoints[Flag]^.Direction;
2679 State := FLAG_STATE_NORMAL;
2680 end;
2681 Obj.oldX := Obj.X;
2682 Obj.oldY := Obj.Y;
2683 NeedSend := False; // the event will take care of this
2684 Count := -1;
2685 end;
2686 end;
2688 procedure g_Map_SaveState (st: TStream);
2689 var
2690 str: String;
2692 procedure savePanels ();
2693 var
2694 pan: TPanel;
2695 begin
2696 // Ñîõðàíÿåì ïàíåëè
2697 utils.writeInt(st, LongInt(Length(panByGUID)));
2698 for pan in panByGUID do pan.SaveState(st);
2699 end;
2701 procedure saveFlag (flag: PFlag);
2702 var
2703 b: Byte;
2704 begin
2705 utils.writeSign(st, 'FLAG');
2706 utils.writeInt(st, Byte(0)); // version
2707 // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
2708 utils.writeInt(st, Byte(flag^.RespawnType));
2709 // Ñîñòîÿíèå ôëàãà
2710 utils.writeInt(st, Byte(flag^.State));
2711 // Íàïðàâëåíèå ôëàãà
2712 if flag^.Direction = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT
2713 utils.writeInt(st, Byte(b));
2714 // Îáúåêò ôëàãà
2715 Obj_SaveState(st, @flag^.Obj);
2716 end;
2718 begin
2719 savePanels();
2721 // Ñîõðàíÿåì ìóçûêó
2722 utils.writeSign(st, 'MUSI');
2723 utils.writeInt(st, Byte(0));
2724 // Íàçâàíèå ìóçûêè
2725 assert(gMusic <> nil, 'g_Map_SaveState: gMusic = nil');
2726 if gMusic.NoMusic then str := '' else str := gMusic.Name;
2727 utils.writeStr(st, str);
2728 // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
2729 utils.writeInt(st, LongWord(gMusic.GetPosition()));
2730 // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
2731 utils.writeBool(st, gMusic.SpecPause);
2733 ///// Ñîõðàíÿåì êîëè÷åñòâî ìîíñòðîâ: /////
2734 utils.writeInt(st, LongInt(gTotalMonsters));
2735 ///// /////
2737 //// Ñîõðàíÿåì ôëàãè, åñëè ýòî CTF: /////
2738 if (gGameSettings.GameMode = GM_CTF) then
2739 begin
2740 // Ôëàã Êðàñíîé êîìàíäû
2741 saveFlag(@gFlags[FLAG_RED]);
2742 // Ôëàã Ñèíåé êîìàíäû
2743 saveFlag(@gFlags[FLAG_BLUE]);
2744 end;
2745 ///// /////
2747 ///// Ñîõðàíÿåì êîëè÷åñòâî ïîáåä, åñëè ýòî TDM/CTF: /////
2748 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
2749 begin
2750 // Î÷êè Êðàñíîé êîìàíäû
2751 utils.writeInt(st, SmallInt(gTeamStat[TEAM_RED].Score));
2752 // Î÷êè Ñèíåé êîìàíäû
2753 utils.writeInt(st, SmallInt(gTeamStat[TEAM_BLUE].Score));
2754 end;
2755 ///// /////
2756 end;
2759 procedure g_Map_LoadState (st: TStream);
2760 var
2761 dw: DWORD;
2762 str: String;
2763 boo: Boolean;
2765 procedure loadPanels ();
2766 var
2767 pan: TPanel;
2768 begin
2769 // Çàãðóæàåì ïàíåëè
2770 if (Length(panByGUID) <> utils.readLongInt(st)) then raise XStreamError.Create('invalid number of saved panels');
2771 for pan in panByGUID do
2772 begin
2773 pan.LoadState(st);
2774 if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
2775 end;
2776 end;
2778 procedure loadFlag (flag: PFlag);
2779 var
2780 b: Byte;
2781 begin
2782 // Ñèãíàòóðà ôëàãà
2783 if not utils.checkSign(st, 'FLAG') then raise XStreamError.Create('invalid flag signature');
2784 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid flag version');
2785 // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
2786 flag^.RespawnType := utils.readByte(st);
2787 // Ñîñòîÿíèå ôëàãà
2788 flag^.State := utils.readByte(st);
2789 // Íàïðàâëåíèå ôëàãà
2790 b := utils.readByte(st);
2791 if (b = 1) then flag^.Direction := TDirection.D_LEFT else flag^.Direction := TDirection.D_RIGHT; // b = 2
2792 // Îáúåêò ôëàãà
2793 Obj_LoadState(@flag^.Obj, st);
2794 end;
2796 begin
2797 if (st = nil) then exit;
2799 ///// Çàãðóæàåì ñïèñêè ïàíåëåé: /////
2800 loadPanels();
2801 ///// /////
2803 {$IFDEF ENABLE_GFX}
2804 // Îáíîâëÿåì êàðòó ñòîëêíîâåíèé è ñåòêó
2805 g_GFX_Init();
2806 {$ENDIF}
2808 //mapCreateGrid();
2810 ///// Çàãðóæàåì ìóçûêó: /////
2811 if not utils.checkSign(st, 'MUSI') then raise XStreamError.Create('invalid music signature');
2812 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid music version');
2813 // Íàçâàíèå ìóçûêè
2814 assert(gMusic <> nil, 'g_Map_LoadState: gMusic = nil');
2815 str := utils.readStr(st);
2816 // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
2817 dw := utils.readLongWord(st);
2818 // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
2819 boo := utils.readBool(st);
2820 // Çàïóñêàåì ýòó ìóçûêó
2821 gMusic.SetByName(str);
2822 gMusic.SpecPause := boo;
2823 gMusic.Play();
2824 gMusic.Pause(true);
2825 gMusic.SetPosition(dw);
2826 ///// /////
2828 ///// Çàãðóæàåì êîëè÷åñòâî ìîíñòðîâ: /////
2829 gTotalMonsters := utils.readLongInt(st);
2830 ///// /////
2832 //// Çàãðóæàåì ôëàãè, åñëè ýòî CTF: /////
2833 if (gGameSettings.GameMode = GM_CTF) then
2834 begin
2835 // Ôëàã Êðàñíîé êîìàíäû
2836 loadFlag(@gFlags[FLAG_RED]);
2837 // Ôëàã Ñèíåé êîìàíäû
2838 loadFlag(@gFlags[FLAG_BLUE]);
2839 end;
2840 ///// /////
2842 ///// Çàãðóæàåì êîëè÷åñòâî ïîáåä, åñëè ýòî TDM/CTF: /////
2843 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
2844 begin
2845 // Î÷êè Êðàñíîé êîìàíäû
2846 gTeamStat[TEAM_RED].Score := utils.readSmallInt(st);
2847 // Î÷êè Ñèíåé êîìàíäû
2848 gTeamStat[TEAM_BLUE].Score := utils.readSmallInt(st);
2849 end;
2850 ///// /////
2851 end;
2854 // trace liquid, stepping by `dx` and `dy`
2855 // return last seen liquid coords, and `false` if we're started outside of the liquid
2856 function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
2857 const
2858 MaskLiquid = GridTagWater or GridTagAcid1 or GridTagAcid2;
2859 begin
2860 topx := x;
2861 topy := y;
2862 // started outside of the liquid?
2863 //if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then begin result := false; exit; end;
2864 if (g_Map_PanelAtPoint(x, y, MaskLiquid) = nil) then begin result := false; exit; end;
2865 if (dx = 0) and (dy = 0) then begin result := false; exit; end; // sanity check
2866 result := true;
2867 while true do
2868 begin
2869 Inc(x, dx);
2870 Inc(y, dy);
2871 //if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then exit; // out of the water, just exit
2872 if (g_Map_PanelAtPoint(x, y, MaskLiquid) = nil) then exit; // out of the water, just exit
2873 topx := x;
2874 topy := y;
2875 end;
2876 end;
2879 begin
2880 DynWarningCB := mapWarningCB;
2881 end.