DEADSOFTWARE

9a51d3b66ef9202f389ca80c2ddd553fb5b52163
[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, g_textures,
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;
156 FLAG_STATE_NONE = 0;
157 FLAG_STATE_NORMAL = 1;
158 FLAG_STATE_DROPPED = 2;
159 FLAG_STATE_CAPTURED = 3;
160 FLAG_STATE_SCORED = 4; // Äëÿ ýâåíòîâ ÷åðåç ñåòêó.
161 FLAG_STATE_RETURNED = 5; // Äëÿ ýâåíòîâ ÷åðåç ñåòêó.
163 FLAG_TIME = 720; // 20 seconds
165 SKY_STRETCH: Single = 1.5;
167 const
168 GridTagInvalid = 0;
170 (* draw order:
171 PANEL_BACK
172 PANEL_STEP
173 PANEL_WALL
174 PANEL_CLOSEDOOR
175 PANEL_ACID1
176 PANEL_ACID2
177 PANEL_WATER
178 PANEL_FORE
179 *)
180 // sorted by draw priority
181 GridTagBack = 1 shl 0; // gRenderBackgrounds
182 GridTagStep = 1 shl 1; // gSteps
183 GridTagWall = 1 shl 2; // gWalls
184 GridTagDoor = 1 shl 3; // gWalls
185 GridTagAcid1 = 1 shl 4; // gAcid1
186 GridTagAcid2 = 1 shl 5; // gAcid2
187 GridTagWater = 1 shl 6; // gWater
188 GridTagFore = 1 shl 7; // gRenderForegrounds
189 // the following are invisible
190 GridTagLift = 1 shl 8; // gLifts
191 GridTagBlockMon = 1 shl 9; // gBlockMon
193 GridTagSolid = (GridTagWall or GridTagDoor);
194 GridTagObstacle = (GridTagStep or GridTagWall or GridTagDoor);
195 GridTagLiquid = (GridTagAcid1 or GridTagAcid2 or GridTagWater);
197 GridDrawableMask = (GridTagBack or GridTagStep or GridTagWall or GridTagDoor or GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagFore);
199 var
200 gWalls: TPanelArray;
201 gRenderBackgrounds: TPanelArray;
202 gRenderForegrounds: TPanelArray;
203 gWater, gAcid1, gAcid2: TPanelArray;
204 gSteps: TPanelArray;
205 gLifts: TPanelArray;
206 gBlockMon: TPanelArray;
207 gFlags: array [FLAG_RED..FLAG_BLUE] of TFlag;
208 //gDOMFlags: array of TFlag;
209 gMapInfo: TMapInfo;
210 gDoorMap: array of array of DWORD;
211 gLiftMap: array of array of DWORD;
212 gWADHash: TMD5Digest;
213 gExternalResources: array of TDiskFileInfo = nil;
214 gMovingWallIds: array of Integer = nil;
216 gdbg_map_use_accel_render: Boolean = true;
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,
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, WADz: TWADFile;
962 WADName, ResName: String;
963 ResData, ReszData: Pointer;
964 ResLen, ReszLen: Integer;
965 cfg: TConfig;
966 id: Integer;
967 begin
968 Result := -1;
969 HName := toLowerCase1251(RecName);
970 if (TextNameHash = nil) then
971 TextNameHash := THashStrInt.Create();
972 if TextNameHash.get(HName, Result) then
973 begin
974 // e_LogWritefln('CreateTexture: found loaded %s', [Result]);
975 end
976 else
977 begin
978 Result := -1;
979 if (BadTextNameHash = nil) or not BadTextNameHash.has(HName) then
980 begin
981 case RecName of
982 TEXTURE_NAME_WATER, TEXTURE_NAME_ACID1, TEXTURE_NAME_ACID2:
983 begin
984 SetLength(Textures, Length(Textures) + 1);
985 Textures[High(Textures)].FullName := RecName;
986 Textures[High(Textures)].TextureName := RecName;
987 Result := High(Textures);
988 TextNameHash.put(RecName, result);
989 end
990 else
991 WADName := GetReplacementWad(g_ExtractWadName(RecName));
992 if (WADName <> '') then
993 addResToExternalResList(WADName);
994 if WADName = '' then
995 WADName := Map;
996 ResName := g_ExtractFilePathName(RecName);
997 WAD := TWADFile.Create();
998 if WAD.ReadFile(WadName) then
999 begin
1000 if WAD.GetResource(ResName, ResData, ResLen, log) then
1001 begin
1002 if IsWadData(ResData, ResLen) then
1003 begin
1004 WADz := TWADFile.Create();
1005 if WADz.ReadMemory(ResData, ResLen) then
1006 begin
1007 if WADz.GetResource('TEXT/ANIM', ReszData, ReszLen) then
1008 begin
1009 cfg := TConfig.CreateMem(ReszData, ReszLen);
1010 if cfg <> nil then
1011 begin
1012 SetLength(Textures, Length(Textures) + 1);
1013 Textures[High(Textures)].TextureName := RecName;
1014 Textures[High(Textures)].FullName := WadName + ':' + ResName;
1015 Textures[High(Textures)].FramesCount := cfg.ReadInt('', 'framecount', 0);
1016 Textures[High(Textures)].Speed := cfg.ReadInt('', 'waitcount', 0);
1017 Result := High(Textures);
1018 TextNameHash.put(HName, result);
1019 cfg.Free;
1020 end;
1021 FreeMem(ReszData);
1022 end
1023 end;
1024 WADz.Free;
1025 end
1026 else
1027 begin
1028 SetLength(Textures, Length(Textures) + 1);
1029 Textures[High(Textures)].FullName := WADName + ':' + ResName;
1030 Textures[High(Textures)].TextureName := RecName;
1031 Result := High(Textures);
1032 TextNameHash.put(HName, result);
1033 end;
1034 FreeMem(ResData);
1035 end
1036 end;
1037 WAD.Free;
1038 end
1039 end;
1040 end;
1041 if Result < 0 then
1042 begin
1043 if (BadTextNameHash = nil) then
1044 BadTextNameHash := THashStrInt.Create();
1045 if log and (not BadTextNameHash.get(HName, id)) then
1046 e_WriteLog(Format('Error loading texture %s', [RecName]), TMsgType.Warning);
1047 BadTextNameHash.put(HName, -1);
1048 end
1049 end;
1051 procedure CreateItem(Item: TDynRecord);
1052 begin
1053 if g_Game_IsClient then Exit;
1055 if (not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) and
1056 ByteBool(Item.Options and ITEM_OPTION_ONLYDM) then
1057 Exit;
1059 g_Items_Create(Item.X, Item.Y, Item.ItemType, ByteBool(Item.Options and ITEM_OPTION_FALL),
1060 gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF, GM_COOP]);
1061 end;
1063 procedure CreateArea(Area: TDynRecord);
1064 var
1065 a: Integer;
1066 begin
1067 case Area.AreaType of
1068 AREA_DMPOINT, AREA_PLAYERPOINT1, AREA_PLAYERPOINT2,
1069 AREA_REDTEAMPOINT, AREA_BLUETEAMPOINT:
1070 begin
1071 SetLength(RespawnPoints, Length(RespawnPoints)+1);
1072 with RespawnPoints[High(RespawnPoints)] do
1073 begin
1074 X := Area.X;
1075 Y := Area.Y;
1076 Direction := TDirection(Area.Direction);
1078 case Area.AreaType of
1079 AREA_DMPOINT: PointType := RESPAWNPOINT_DM;
1080 AREA_PLAYERPOINT1: PointType := RESPAWNPOINT_PLAYER1;
1081 AREA_PLAYERPOINT2: PointType := RESPAWNPOINT_PLAYER2;
1082 AREA_REDTEAMPOINT: PointType := RESPAWNPOINT_RED;
1083 AREA_BLUETEAMPOINT: PointType := RESPAWNPOINT_BLUE;
1084 end;
1085 end;
1086 end;
1088 AREA_REDFLAG, AREA_BLUEFLAG:
1089 begin
1090 if Area.AreaType = AREA_REDFLAG then a := FLAG_RED else a := FLAG_BLUE;
1092 if FlagPoints[a] <> nil then Exit;
1094 New(FlagPoints[a]);
1096 with FlagPoints[a]^ do
1097 begin
1098 X := Area.X-FLAGRECT.X;
1099 Y := Area.Y-FLAGRECT.Y;
1100 Direction := TDirection(Area.Direction);
1101 end;
1103 with gFlags[a] do
1104 begin
1105 Obj.Rect := FLAGRECT;
1106 g_Map_ResetFlag(a);
1107 end;
1108 end;
1110 AREA_DOMFLAG:
1111 begin
1112 {SetLength(DOMFlagPoints, Length(DOMFlagPoints)+1);
1113 with DOMFlagPoints[High(DOMFlagPoints)] do
1114 begin
1115 X := Area.X;
1116 Y := Area.Y;
1117 Direction := TDirection(Area.Direction);
1118 end;
1120 g_Map_CreateFlag(DOMFlagPoints[High(DOMFlagPoints)], FLAG_DOM, FLAG_STATE_NORMAL);}
1121 end;
1122 end;
1123 end;
1125 function CreateTrigger (amapIdx: Integer; Trigger: TDynRecord; atpanid, atrigpanid: Integer): Integer;
1126 var
1127 _trigger: TTrigger;
1128 tp: TPanel;
1129 begin
1130 result := -1;
1131 if g_Game_IsClient and not (Trigger.TriggerType in [TRIGGER_SOUND, TRIGGER_MUSIC]) then Exit;
1133 with _trigger do
1134 begin
1135 mapId := Trigger.id;
1136 mapIndex := amapIdx;
1137 X := Trigger.X;
1138 Y := Trigger.Y;
1139 Width := Trigger.Width;
1140 Height := Trigger.Height;
1141 Enabled := Trigger.Enabled;
1142 TexturePanelGUID := atpanid;
1143 TriggerType := Trigger.TriggerType;
1144 ActivateType := Trigger.ActivateType;
1145 Keys := Trigger.Keys;
1146 trigPanelGUID := atrigpanid;
1147 // HACK: used in TPanel.CanChangeTexture. maybe there's a better way?
1148 if TexturePanelGUID <> -1 then
1149 begin
1150 tp := g_Map_PanelByGUID(TexturePanelGUID);
1151 if (tp <> nil) then tp.hasTexTrigger := True;
1152 end;
1153 end;
1155 result := Integer(g_Triggers_Create(_trigger, Trigger));
1156 end;
1158 procedure CreateMonster(monster: TDynRecord);
1159 var
1160 a: Integer;
1161 mon: TMonster;
1162 begin
1163 if g_Game_IsClient then Exit;
1165 if (gGameSettings.GameType = GT_SINGLE)
1166 or LongBool(gGameSettings.Options and GAME_OPTION_MONSTERS) then
1167 begin
1168 mon := g_Monsters_Create(monster.MonsterType, monster.X, monster.Y, TDirection(monster.Direction));
1170 if gTriggers <> nil then
1171 begin
1172 for a := 0 to High(gTriggers) do
1173 begin
1174 if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
1175 begin
1176 //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
1177 if (gTriggers[a].trigDataRec.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
1178 end;
1179 end;
1180 end;
1182 if monster.MonsterType <> MONSTER_BARREL then Inc(gTotalMonsters);
1183 end;
1184 end;
1186 procedure g_Map_ReAdd_DieTriggers();
1188 function monsDieTrig (mon: TMonster): Boolean;
1189 var
1190 a: Integer;
1191 //tw: TStrTextWriter;
1192 begin
1193 result := false; // don't stop
1194 mon.ClearTriggers();
1195 for a := 0 to High(gTriggers) do
1196 begin
1197 if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
1198 begin
1199 //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
1201 tw := TStrTextWriter.Create();
1202 try
1203 gTriggers[a].trigData.writeTo(tw);
1204 e_LogWritefln('=== trigger #%s ==='#10'%s'#10'---', [a, tw.str]);
1205 finally
1206 tw.Free();
1207 end;
1209 if (gTriggers[a].trigDataRec.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
1210 end;
1211 end;
1212 end;
1214 begin
1215 if g_Game_IsClient then Exit;
1217 g_Mons_ForEach(monsDieTrig);
1218 end;
1220 procedure mapCreateGrid ();
1221 var
1222 mapX0: Integer = $3fffffff;
1223 mapY0: Integer = $3fffffff;
1224 mapX1: Integer = -$3fffffff;
1225 mapY1: Integer = -$3fffffff;
1227 procedure calcBoundingBox (constref panels: TPanelArray);
1228 var
1229 idx: Integer;
1230 pan: TPanel;
1231 begin
1232 for idx := 0 to High(panels) do
1233 begin
1234 pan := panels[idx];
1235 if not pan.visvalid then continue;
1236 if (pan.Width < 1) or (pan.Height < 1) then continue;
1237 if (mapX0 > pan.x0) then mapX0 := pan.x0;
1238 if (mapY0 > pan.y0) then mapY0 := pan.y0;
1239 if (mapX1 < pan.x1) then mapX1 := pan.x1;
1240 if (mapY1 < pan.y1) then mapY1 := pan.y1;
1241 end;
1242 end;
1244 procedure addPanelsToGrid (constref panels: TPanelArray);
1245 var
1246 idx: Integer;
1247 pan: TPanel;
1248 newtag: Integer;
1249 begin
1250 //tag := panelTypeToTag(tag);
1251 for idx := 0 to High(panels) do
1252 begin
1253 pan := panels[idx];
1254 if not pan.visvalid then continue;
1255 if (pan.proxyId <> -1) then
1256 begin
1257 {$IF DEFINED(D2F_DEBUG)}
1258 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);
1259 {$ENDIF}
1260 continue;
1261 end;
1262 case pan.PanelType of
1263 PANEL_WALL: newtag := GridTagWall;
1264 PANEL_OPENDOOR, PANEL_CLOSEDOOR: newtag := GridTagDoor;
1265 PANEL_BACK: newtag := GridTagBack;
1266 PANEL_FORE: newtag := GridTagFore;
1267 PANEL_WATER: newtag := GridTagWater;
1268 PANEL_ACID1: newtag := GridTagAcid1;
1269 PANEL_ACID2: newtag := GridTagAcid2;
1270 PANEL_STEP: newtag := GridTagStep;
1271 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: newtag := GridTagLift;
1272 PANEL_BLOCKMON: newtag := GridTagBlockMon;
1273 else continue; // oops
1274 end;
1275 pan.tag := newtag;
1277 pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, newtag);
1278 // "enabled" flag has meaning only for doors and walls (engine assumes it); but meh...
1279 mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
1280 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
1282 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
1283 begin
1284 e_WriteLog(Format('INSERTED wall #%d(%d) enabled (%d)', [Integer(idx), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId])]), MSG_NOTIFY);
1285 end;
1287 {$ENDIF}
1288 end;
1289 end;
1291 begin
1292 mapGrid.Free();
1293 mapGrid := nil;
1295 calcBoundingBox(gWalls);
1296 calcBoundingBox(gRenderBackgrounds);
1297 calcBoundingBox(gRenderForegrounds);
1298 calcBoundingBox(gWater);
1299 calcBoundingBox(gAcid1);
1300 calcBoundingBox(gAcid2);
1301 calcBoundingBox(gSteps);
1302 calcBoundingBox(gLifts);
1303 calcBoundingBox(gBlockMon);
1305 e_LogWritefln('map dimensions: (%d,%d)-(%d,%d); editor size:(0,0)-(%d,%d)', [mapX0, mapY0, mapX1, mapY1, gMapInfo.Width, gMapInfo.Height]);
1307 if (mapX0 > 0) then mapX0 := 0;
1308 if (mapY0 > 0) then mapY0 := 0;
1310 if (mapX1 < gMapInfo.Width-1) then mapX1 := gMapInfo.Width-1;
1311 if (mapY1 < gMapInfo.Height-1) then mapY1 := gMapInfo.Height-1;
1313 mapGrid := TPanelGrid.Create(mapX0-128, mapY0-128, mapX1-mapX0+1+128*2, mapY1-mapY0+1+128*2);
1314 //mapGrid := TPanelGrid.Create(0, 0, gMapInfo.Width, gMapInfo.Height);
1316 addPanelsToGrid(gWalls);
1317 addPanelsToGrid(gRenderBackgrounds);
1318 addPanelsToGrid(gRenderForegrounds);
1319 addPanelsToGrid(gWater);
1320 addPanelsToGrid(gAcid1);
1321 addPanelsToGrid(gAcid2);
1322 addPanelsToGrid(gSteps);
1323 addPanelsToGrid(gLifts); // it doesn't matter which LIFT type is used here
1324 addPanelsToGrid(gBlockMon);
1326 mapGrid.dumpStats();
1328 g_Mons_InitTree(mapGrid.gridX0, mapGrid.gridY0, mapGrid.gridWidth, mapGrid.gridHeight);
1329 end;
1332 function g_Map_Load(Res: String): Boolean;
1333 const
1334 DefaultMusRes = 'Standart.wad:STDMUS\MUS1';
1335 DefaultSkyRes = 'Standart.wad:STDSKY\SKY0';
1336 type
1337 PTRec = ^TTRec;
1338 TTRec = record
1339 //TexturePanel: Integer;
1340 tnum: Integer;
1341 id: Integer;
1342 trigrec: TDynRecord;
1343 // texture pane;
1344 texPanelIdx: Integer;
1345 texPanel: TDynRecord;
1346 // "action" panel
1347 actPanelIdx: Integer;
1348 actPanel: TDynRecord;
1349 end;
1350 var
1351 WAD, TestWAD: TWADFile;
1352 //mapReader: TDynRecord = nil;
1353 mapTextureList: TDynField = nil; //TTexturesRec1Array; tagInt: texture index
1354 panels: TDynField = nil; //TPanelsRec1Array;
1355 items: TDynField = nil; //TItemsRec1Array;
1356 monsters: TDynField = nil; //TMonsterRec1Array;
1357 areas: TDynField = nil; //TAreasRec1Array;
1358 triggers: TDynField = nil; //TTriggersRec1Array;
1359 b, c, k: Integer;
1360 PanelID: DWORD;
1361 AddTextures: TAddTextureArray;
1362 TriggersTable: array of TTRec;
1363 FileName, mapResName, TexName, s: AnsiString;
1364 Data: Pointer;
1365 Len: Integer;
1366 ok: Boolean;
1367 CurTex, ntn: Integer;
1368 rec, texrec: TDynRecord;
1369 pttit: PTRec;
1370 pannum, trignum, cnt, tgpid: Integer;
1371 stt: UInt64;
1372 moveSpeed{, moveStart, moveEnd}: TDFPoint;
1373 //moveActive: Boolean;
1374 pan: TPanel;
1375 mapOk: Boolean = false;
1376 usedTextures: THashStrInt = nil; // key: mapTextureList
1377 begin
1378 mapGrid.Free();
1379 mapGrid := nil;
1380 TestWAD := nil;
1381 Data := nil;
1383 //gCurrentMap.Free();
1384 //gCurrentMap := nil;
1386 panByGUID := nil;
1388 Result := False;
1389 gMapInfo.Map := Res;
1390 TriggersTable := nil;
1391 //mapReader := nil;
1393 sfsGCDisable(); // temporary disable removing of temporary volumes
1394 try
1395 // Çàãðóçêà WAD (åñëè ó íàñ íåò óæå çàãðóæåíîé êàðòû)
1396 if (gCurrentMap = nil) then
1397 begin
1398 FileName := g_ExtractWadName(Res);
1399 e_LogWritefln('Loading map WAD [%s] (res=[%s])', [FileName, Res], TMsgType.Notify);
1400 g_Game_SetLoadingText(_lc[I_LOAD_WAD_FILE], 0, False);
1402 WAD := TWADFile.Create();
1403 if not WAD.ReadFile(FileName) then
1404 begin
1405 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_WAD], [FileName]));
1406 WAD.Free();
1407 Exit;
1408 end;
1410 if gTestMap <> '' then
1411 begin
1412 s := g_ExtractWadName(gTestMap);
1413 TestWAD := TWADFile.Create();
1414 if not TestWAD.ReadFile(s) then
1415 begin
1416 g_SimpleError(Format(_lc[I_GAME_ERROR_MAP_WAD], [s]));
1417 TestWAD.Free();
1418 TestWAD := nil;
1419 end;
1420 end;
1422 if TestWAD <> nil then
1423 begin
1424 mapResName := g_ExtractFileName(gTestMap);
1425 if not TestWAD.GetMapResource(mapResName, Data, Len) then
1426 begin
1427 g_SimpleError(Format(_lc[I_GAME_ERROR_MAP_RES], [mapResName]));
1428 Data := nil;
1429 end else
1430 e_WriteLog('Using test map: '+gTestMap, TMsgType.Notify);
1431 TestWAD.Free();
1432 TestWAD := nil;
1433 end;
1435 if Data = nil then
1436 begin
1437 //k8: why loader ignores path here?
1438 mapResName := g_ExtractFileName(Res);
1439 if not WAD.GetMapResource(mapResName, Data, Len) then
1440 begin
1441 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_RES], [mapResName]));
1442 WAD.Free();
1443 Exit;
1444 end;
1445 end;
1447 WAD.Free();
1449 if (Len < 4) then
1450 begin
1451 e_LogWritefln('invalid map file: ''%s''', [mapResName]);
1452 FreeMem(Data);
1453 exit;
1454 end;
1456 // Çàãðóçêà êàðòû:
1457 e_LogWritefln('Loading map: %s', [mapResName], TMsgType.Notify);
1458 g_Game_SetLoadingText(_lc[I_LOAD_MAP], 0, False);
1460 stt := getTimeMicro();
1462 try
1463 gCurrentMap := g_Map_ParseMap(Data, Len);
1464 except
1465 gCurrentMap.Free();
1466 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]));
1467 FreeMem(Data);
1468 gCurrentMapFileName := '';
1469 Exit;
1470 end;
1472 FreeMem(Data);
1474 if (gCurrentMap = nil) then
1475 begin
1476 e_LogWritefln('invalid map file: ''%s''', [mapResName]);
1477 gCurrentMapFileName := '';
1478 exit;
1479 end;
1480 end
1481 else
1482 begin
1483 stt := getTimeMicro();
1484 end;
1486 //gCurrentMap := mapReader;
1488 generateExternalResourcesList(gCurrentMap);
1489 mapTextureList := gCurrentMap['texture'];
1490 // get all other lists here too
1491 panels := gCurrentMap['panel'];
1492 triggers := gCurrentMap['trigger'];
1493 items := gCurrentMap['item'];
1494 areas := gCurrentMap['area'];
1495 monsters := gCurrentMap['monster'];
1497 // Çàãðóçêà îïèñàíèÿ êàðòû:
1498 e_WriteLog(' Reading map info...', TMsgType.Notify);
1499 g_Game_SetLoadingText(_lc[I_LOAD_MAP_HEADER], 0, False);
1501 with gMapInfo do
1502 begin
1503 Name := gCurrentMap.MapName;
1504 Description := gCurrentMap.MapDesc;
1505 Author := gCurrentMap.MapAuthor;
1506 MusicName := gCurrentMap.MusicName;
1507 SkyName := gCurrentMap.SkyName;
1508 Height := gCurrentMap.Height;
1509 Width := gCurrentMap.Width;
1510 end;
1512 // Çàãðóçêà òåêñòóð:
1513 g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], 0, False);
1514 // Äîáàâëåíèå òåêñòóð â Textures[]:
1515 if (mapTextureList <> nil) and (mapTextureList.count > 0) then
1516 begin
1517 e_WriteLog(' Loading textures:', TMsgType.Notify);
1518 g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], mapTextureList.count-1, False);
1520 // find used textures
1521 usedTextures := THashStrInt.Create();
1522 try
1523 if (panels <> nil) and (panels.count > 0) then
1524 begin
1525 for rec in panels do
1526 begin
1527 texrec := rec.TextureRec;
1528 if (texrec <> nil) then usedTextures.put(toLowerCase1251(texrec.Resource), 42);
1529 end;
1530 end;
1532 cnt := -1;
1533 for rec in mapTextureList do
1534 begin
1535 Inc(cnt);
1536 if not usedTextures.has(toLowerCase1251(rec.Resource)) then
1537 begin
1538 rec.tagInt := -1; // just in case
1539 e_LogWritefln(' Unused texture #%d: %s', [cnt, rec.Resource]);
1540 end
1541 else
1542 begin
1543 {$IF DEFINED(D2F_DEBUG_TXLOAD)}
1544 e_LogWritefln(' Loading texture #%d: %s', [cnt, rec.Resource]);
1545 {$ENDIF}
1546 ntn := CreateTexture(rec.Resource, FileName, True);
1547 if ntn < 0 then
1548 begin
1549 if rec.Anim then
1550 g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_ANIM], [rec.Resource]))
1551 else
1552 g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_SIMPLE], [rec.Resource]));
1553 ntn := CreateNullTexture(rec.Resource)
1554 end;
1555 rec.tagInt := ntn; // remember texture number
1556 end;
1557 g_Game_StepLoading();
1558 end;
1559 finally
1560 usedTextures.Free();
1561 end;
1563 // set panel tagInt to texture index
1564 if (panels <> nil) then
1565 begin
1566 for rec in panels do
1567 begin
1568 texrec := rec.TextureRec;
1569 if (texrec = nil) then rec.tagInt := -1 else rec.tagInt := texrec.tagInt;
1570 end;
1571 end;
1572 end;
1575 // Çàãðóçêà òðèããåðîâ
1576 gTriggerClientID := 0;
1577 e_WriteLog(' Loading triggers...', TMsgType.Notify);
1578 g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS], 0, False);
1580 // Çàãðóçêà ïàíåëåé
1581 e_WriteLog(' Loading panels...', TMsgType.Notify);
1582 g_Game_SetLoadingText(_lc[I_LOAD_PANELS], 0, False);
1584 // check texture numbers for panels
1585 if (panels <> nil) and (panels.count > 0) then
1586 begin
1587 for rec in panels do
1588 begin
1589 if (rec.tagInt < 0) then
1590 begin
1591 e_WriteLog('error loading map: invalid texture index for panel', TMsgType.Fatal);
1592 result := false;
1593 gCurrentMap.Free();
1594 gCurrentMap := nil;
1595 gCurrentMapFileName := '';
1596 exit;
1597 end;
1598 end;
1599 end;
1601 // Ñîçäàíèå òàáëèöû òðèããåðîâ (ñîîòâåòñòâèå ïàíåëåé òðèããåðàì)
1602 if (triggers <> nil) and (triggers.count > 0) then
1603 begin
1604 e_WriteLog(' Setting up trigger table...', TMsgType.Notify);
1605 //SetLength(TriggersTable, triggers.count);
1606 g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS_TABLE], triggers.count-1, False);
1608 SetLength(TriggersTable, triggers.count);
1609 trignum := -1;
1610 for rec in triggers do
1611 begin
1612 Inc(trignum);
1613 pttit := @TriggersTable[trignum];
1614 pttit.trigrec := rec;
1615 // Ñìåíà òåêñòóðû (âîçìîæíî, êíîïêè)
1616 pttit.texPanelIdx := -1; // will be fixed later
1617 pttit.texPanel := rec.TexturePanelRec;
1618 // action panel
1619 pttit.actPanelIdx := -1;
1620 if (rec.trigRec <> nil) then pttit.actPanel := rec.trigRec.tgPanelRec else pttit.actPanel := nil;
1621 // set flag
1622 if (pttit.texPanel <> nil) then pttit.texPanel.userPanelTrigRef := true;
1623 if (pttit.actPanel <> nil) then pttit.actPanel.userPanelTrigRef := true;
1624 // update progress
1625 g_Game_StepLoading();
1626 end;
1627 end;
1629 // Ñîçäàåì ïàíåëè
1630 if (panels <> nil) and (panels.count > 0) then
1631 begin
1632 e_WriteLog(' Setting up trigger links...', TMsgType.Notify);
1633 g_Game_SetLoadingText(_lc[I_LOAD_LINK_TRIGGERS], panels.count-1, False);
1635 pannum := -1;
1636 for rec in panels do
1637 begin
1638 Inc(pannum);
1639 //e_LogWritefln('PANSTART: pannum=%s', [pannum]);
1640 texrec := nil;
1641 SetLength(AddTextures, 0);
1642 CurTex := -1;
1643 ok := false;
1645 if (mapTextureList <> nil) then
1646 begin
1647 texrec := rec.TextureRec;
1648 ok := (texrec <> nil);
1649 end;
1651 if ok then
1652 begin
1653 // Ñìîòðèì, ññûëàþòñÿ ëè íà ýòó ïàíåëü òðèããåðû.
1654 // Åñëè äà - òî íàäî ñîçäàòü åùå òåêñòóð
1655 ok := false;
1656 if (TriggersTable <> nil) and (mapTextureList <> nil) then
1657 begin
1658 if rec.userPanelTrigRef then
1659 begin
1660 // e_LogWritefln('trigref for panel %s', [pannum]);
1661 ok := True;
1662 end;
1663 end;
1664 end;
1666 if ok then
1667 begin
1668 // Åñòü ññûëêè òðèããåðîâ íà ýòó ïàíåëü
1669 s := texrec.Resource;
1671 // Ñïåö-òåêñòóðû çàïðåùåíû
1672 if g_Map_IsSpecialTexture(s) then
1673 begin
1674 ok := false
1675 end
1676 else
1677 begin
1678 // Îïðåäåëÿåì íàëè÷èå è ïîëîæåíèå öèôð â êîíöå ñòðîêè
1679 ok := g_Texture_NumNameFindStart(s);
1680 end;
1682 // Åñëè ok, çíà÷èò åñòü öèôðû â êîíöå.
1683 // Çàãðóæàåì òåêñòóðû ñ îñòàëüíûìè #
1684 if ok then
1685 begin
1686 k := NNF_NAME_BEFORE;
1687 // Öèêë ïî èçìåíåíèþ èìåíè òåêñòóðû
1688 while ok or (k = NNF_NAME_BEFORE) or (k = NNF_NAME_EQUALS) do
1689 begin
1690 k := g_Texture_NumNameFindNext(TexName);
1692 if (k = NNF_NAME_BEFORE) or (k = NNF_NAME_AFTER) then
1693 begin
1694 ok := CreateTexture(TexName, FileName, False) >= 0;
1696 // Îíà ñóùåñòâóåò. Çàíîñèì åå ID â ñïèñîê ïàíåëè
1697 if ok then
1698 begin
1700 for c := 0 to High(Textures) do
1701 begin
1702 if (Textures[c].TextureName = TexName) then
1703 begin
1704 SetLength(AddTextures, Length(AddTextures)+1);
1705 AddTextures[High(AddTextures)].Texture := c;
1706 break;
1707 end;
1708 end;
1710 if (TextNameHash <> nil) and TextNameHash.get(toLowerCase1251(TexName), c) then
1711 begin
1712 SetLength(AddTextures, Length(AddTextures)+1);
1713 AddTextures[High(AddTextures)].Texture := c;
1714 end;
1715 end;
1716 end
1717 else
1718 begin
1719 if k = NNF_NAME_EQUALS then
1720 begin
1721 // Çàíîñèì òåêóùóþ òåêñòóðó íà ñâîå ìåñòî
1722 SetLength(AddTextures, Length(AddTextures)+1);
1723 AddTextures[High(AddTextures)].Texture := rec.tagInt; // internal texture number, not map index
1724 CurTex := High(AddTextures);
1725 ok := true;
1726 end
1727 else // NNF_NO_NAME
1728 begin
1729 ok := false;
1730 end;
1731 end;
1732 end; // while ok...
1734 ok := true;
1735 end; // if ok - åñòü ñìåæíûå òåêñòóðû
1736 end; // if ok - ññûëàþòñÿ òðèããåðû
1738 if not ok then
1739 begin
1740 // Çàíîñèì òîëüêî òåêóùóþ òåêñòóðó
1741 SetLength(AddTextures, 1);
1742 AddTextures[0].Texture := rec.tagInt; // internal texture number, not map index
1743 CurTex := 0;
1744 end;
1746 //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);
1748 //e_LogWritefln('PANADD: pannum=%s', [pannum]);
1750 // Ñîçäàåì ïàíåëü è çàïîìèíàåì åå GUID
1751 //e_LogWritefln('new panel; tcount=%s; curtex=%s', [Length(AddTextures), CurTex]);
1752 PanelID := CreatePanel(rec, AddTextures, CurTex);
1753 //e_LogWritefln('panel #%s of type %s got guid #%s', [pannum, rec.PanelType, PanelID]);
1754 rec.userPanelId := PanelID; // remember game panel id, we'll fix triggers later
1756 // setup lifts
1757 moveSpeed := rec.moveSpeed;
1758 //moveStart := rec.moveStart;
1759 //moveEnd := rec.moveEnd;
1760 //moveActive := rec['move_active'].value;
1761 if not moveSpeed.isZero then
1762 begin
1763 SetLength(gMovingWallIds, Length(gMovingWallIds)+1);
1764 gMovingWallIds[High(gMovingWallIds)] := PanelID;
1765 //e_LogWritefln('found moving panel ''%s'' (idx=%s; id=%s)', [rec.id, pannum, PanelID]);
1766 end;
1768 //e_LogWritefln('PANEND: pannum=%s', [pannum]);
1770 g_Game_StepLoading();
1771 end;
1772 end;
1774 // ×èíèì ID'û ïàíåëåé, êîòîðûå èñïîëüçóþòñÿ â òðèããåðàõ
1775 for b := 0 to High(TriggersTable) do
1776 begin
1777 if (TriggersTable[b].texPanel <> nil) then TriggersTable[b].texPanelIdx := TriggersTable[b].texPanel.userPanelId;
1778 if (TriggersTable[b].actPanel <> nil) then TriggersTable[b].actPanelIdx := TriggersTable[b].actPanel.userPanelId;
1779 end;
1781 // create map grid, init other grids (for monsters, for example)
1782 e_WriteLog('Creating map grid', TMsgType.Notify);
1783 mapCreateGrid();
1785 // Åñëè íå LoadState, òî ñîçäàåì òðèããåðû
1786 if (triggers <> nil) and (panels <> nil) and (not gLoadGameMode) then
1787 begin
1788 e_LogWritefln(' Creating triggers (%d)...', [triggers.count]);
1789 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_TRIGGERS], 0, False);
1790 // Óêàçûâàåì òèï ïàíåëè, åñëè åñòü
1791 trignum := -1;
1792 for rec in triggers do
1793 begin
1794 Inc(trignum);
1795 tgpid := TriggersTable[trignum].actPanelIdx;
1796 //e_LogWritefln('creating trigger #%s; texpantype=%s; shotpantype=%s (%d,%d)', [trignum, b, c, TriggersTable[trignum].texPanIdx, TriggersTable[trignum].ShotPanelIdx]);
1797 TriggersTable[trignum].tnum := trignum;
1798 TriggersTable[trignum].id := CreateTrigger(trignum, rec, TriggersTable[trignum].texPanelIdx, tgpid);
1799 end;
1800 end;
1802 //FIXME: use hashtable!
1803 for pan in panByGUID do
1804 begin
1805 if (pan.endPosTrigId >= 0) and (pan.endPosTrigId < Length(TriggersTable)) then
1806 begin
1807 pan.endPosTrigId := TriggersTable[pan.endPosTrigId].id;
1808 end;
1809 if (pan.endSizeTrigId >= 0) and (pan.endSizeTrigId < Length(TriggersTable)) then
1810 begin
1811 pan.endSizeTrigId := TriggersTable[pan.endSizeTrigId].id;
1812 end;
1813 end;
1815 // Çàãðóçêà ïðåäìåòîâ
1816 e_WriteLog(' Loading items...', TMsgType.Notify);
1817 g_Game_SetLoadingText(_lc[I_LOAD_ITEMS], 0, False);
1819 // Åñëè íå LoadState, òî ñîçäàåì ïðåäìåòû
1820 if (items <> nil) and not gLoadGameMode then
1821 begin
1822 e_WriteLog(' Spawning items...', TMsgType.Notify);
1823 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_ITEMS], 0, False);
1824 for rec in items do CreateItem(rec);
1825 end;
1827 // Çàãðóçêà îáëàñòåé
1828 e_WriteLog(' Loading areas...', TMsgType.Notify);
1829 g_Game_SetLoadingText(_lc[I_LOAD_AREAS], 0, False);
1831 // Åñëè íå LoadState, òî ñîçäàåì îáëàñòè
1832 if areas <> nil then
1833 begin
1834 e_WriteLog(' Creating areas...', TMsgType.Notify);
1835 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_AREAS], 0, False);
1836 for rec in areas do CreateArea(rec);
1837 end;
1839 // Çàãðóçêà ìîíñòðîâ
1840 e_WriteLog(' Loading monsters...', TMsgType.Notify);
1841 g_Game_SetLoadingText(_lc[I_LOAD_MONSTERS], 0, False);
1843 gTotalMonsters := 0;
1845 // Åñëè íå LoadState, òî ñîçäàåì ìîíñòðîâ
1846 if (monsters <> nil) and not gLoadGameMode then
1847 begin
1848 e_WriteLog(' Spawning monsters...', TMsgType.Notify);
1849 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_MONSTERS], 0, False);
1850 for rec in monsters do CreateMonster(rec);
1851 end;
1853 //gCurrentMap := mapReader; // this will be our current map now
1854 gCurrentMapFileName := Res;
1855 //mapReader := nil;
1857 // Çàãðóçêà íåáà
1858 gMapInfo.SkyFullName := '';
1859 if (gMapInfo.SkyName <> '') then
1860 begin
1861 e_WriteLog(' Loading sky: ' + gMapInfo.SkyName, TMsgType.Notify);
1862 g_Game_SetLoadingText(_lc[I_LOAD_SKY], 0, False);
1863 gMapInfo.SkyFullName := e_GetResourcePath(WadDirs, gMapInfo.SkyName, g_ExtractWadName(Res));
1864 end;
1866 // Çàãðóçêà ìóçûêè
1867 ok := False;
1868 if gMapInfo.MusicName <> '' then
1869 begin
1870 e_WriteLog(' Loading music: ' + gMapInfo.MusicName, TMsgType.Notify);
1871 g_Game_SetLoadingText(_lc[I_LOAD_MUSIC], 0, False);
1873 s := e_GetResourcePath(WadDirs, gMapInfo.MusicName, g_ExtractWadName(Res));
1874 if g_Sound_CreateWADEx(gMapInfo.MusicName, s, True) then
1875 ok := True
1876 else
1877 g_FatalError(Format(_lc[I_GAME_ERROR_MUSIC], [s]));
1878 end;
1880 // Îñòàëüíûå óñòàíâêè
1881 CreateDoorMap();
1882 CreateLiftMap();
1884 g_Items_Init();
1885 g_Weapon_Init();
1886 g_Monsters_Init();
1888 {$IFDEF ENABLE_GFX}
1889 // Åñëè íå LoadState, òî ñîçäàåì êàðòó ñòîëêíîâåíèé:
1890 if not gLoadGameMode then g_GFX_Init();
1891 {$ENDIF}
1893 // Ñáðîñ ëîêàëüíûõ ìàññèâîâ:
1894 mapTextureList := nil;
1895 panels := nil;
1896 items := nil;
1897 areas := nil;
1898 triggers := nil;
1899 TriggersTable := nil;
1900 AddTextures := nil;
1902 // Âêëþ÷àåì ìóçûêó, åñëè ýòî íå çàãðóçêà:
1903 if ok and (not gLoadGameMode) then
1904 begin
1905 gMusic.SetByName(gMapInfo.MusicName);
1906 gMusic.Play();
1907 end
1908 else
1909 begin
1910 gMusic.SetByName('');
1911 end;
1913 stt := getTimeMicro()-stt;
1914 e_LogWritefln('map loaded in %s.%s milliseconds', [Integer(stt div 1000), Integer(stt mod 1000)]);
1915 mapOk := true;
1916 finally
1917 sfsGCEnable(); // enable releasing unused volumes
1918 //mapReader.Free();
1919 e_UnpressAllKeys; // why not?
1920 if not mapOk then
1921 begin
1922 gCurrentMap.Free();
1923 gCurrentMap := nil;
1924 gCurrentMapFileName := '';
1925 end;
1926 end;
1928 compactExtResList();
1929 e_WriteLog('Done loading map.', TMsgType.Notify);
1930 Result := True;
1931 end;
1934 function g_Map_GetMapInfo(Res: String): TMapInfo;
1935 var
1936 WAD: TWADFile;
1937 mapReader: TDynRecord;
1938 //Header: TMapHeaderRec_1;
1939 FileName: String;
1940 Data: Pointer;
1941 Len: Integer;
1942 begin
1943 FillChar(Result, SizeOf(Result), 0);
1944 FileName := g_ExtractWadName(Res);
1946 WAD := TWADFile.Create();
1947 if not WAD.ReadFile(FileName) then
1948 begin
1949 WAD.Free();
1950 Exit;
1951 end;
1953 //k8: it ignores path again
1954 if not WAD.GetMapResource(g_ExtractFileName(Res), Data, Len) then
1955 begin
1956 WAD.Free();
1957 Exit;
1958 end;
1960 WAD.Free();
1962 try
1963 mapReader := g_Map_ParseMap(Data, Len);
1964 except
1965 mapReader := nil;
1966 FreeMem(Data);
1967 exit;
1968 end;
1970 FreeMem(Data);
1972 if (mapReader = nil) then exit;
1974 if (mapReader.Width > 0) and (mapReader.Height > 0) then
1975 begin
1976 Result.Name := mapReader.MapName;
1977 Result.Description := mapReader.MapDesc;
1978 Result.Map := Res;
1979 Result.Author := mapReader.MapAuthor;
1980 Result.Height := mapReader.Height;
1981 Result.Width := mapReader.Width;
1982 end
1983 else
1984 begin
1985 g_Console_Add(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]), True);
1986 //ZeroMemory(@Header, SizeOf(Header));
1987 Result.Name := _lc[I_GAME_ERROR_MAP_SELECT];
1988 Result.Description := _lc[I_GAME_ERROR_MAP_SELECT];
1989 Result.Map := Res;
1990 Result.Author := '';
1991 Result.Height := 0;
1992 Result.Width := 0;
1993 end;
1995 mapReader.Free();
1996 end;
1998 function g_Map_GetMapsList(WADName: string): SSArray;
1999 var
2000 WAD: TWADFile;
2001 a: Integer;
2002 ResList: SSArray;
2003 begin
2004 Result := nil;
2005 WAD := TWADFile.Create();
2006 if not WAD.ReadFile(WADName) then
2007 begin
2008 WAD.Free();
2009 Exit;
2010 end;
2011 ResList := WAD.GetMapResources();
2012 if ResList <> nil then
2013 begin
2014 for a := 0 to High(ResList) do
2015 begin
2016 SetLength(Result, Length(Result)+1);
2017 Result[High(Result)] := ResList[a];
2018 end;
2019 end;
2020 WAD.Free();
2021 end;
2023 function g_Map_Exist(Res: string): Boolean;
2024 var
2025 WAD: TWADFile;
2026 FileName, mnn: string;
2027 ResList: SSArray;
2028 a: Integer;
2029 begin
2030 Result := False;
2032 FileName := addWadExtension(g_ExtractWadName(Res));
2034 WAD := TWADFile.Create;
2035 if not WAD.ReadFile(FileName) then
2036 begin
2037 WAD.Free();
2038 Exit;
2039 end;
2041 ResList := WAD.GetMapResources();
2042 WAD.Free();
2044 mnn := g_ExtractFileName(Res);
2045 if ResList <> nil then
2046 for a := 0 to High(ResList) do if StrEquCI1251(ResList[a], mnn) then
2047 begin
2048 Result := True;
2049 Exit;
2050 end;
2051 end;
2053 procedure g_Map_Free(freeTextures: Boolean=true);
2055 procedure FreePanelArray(var panels: TPanelArray);
2056 var
2057 i: Integer;
2059 begin
2060 if panels <> nil then
2061 begin
2062 for i := 0 to High(panels) do
2063 panels[i].Free();
2064 panels := nil;
2065 end;
2066 end;
2068 begin
2069 {$IFDEF ENABLE_GFX}
2070 g_GFX_Free;
2071 {$ENDIF}
2072 g_Weapon_Free();
2073 g_Items_Free();
2074 g_Triggers_Free();
2075 g_Monsters_Free();
2077 RespawnPoints := nil;
2078 if FlagPoints[FLAG_RED] <> nil then
2079 begin
2080 Dispose(FlagPoints[FLAG_RED]);
2081 FlagPoints[FLAG_RED] := nil;
2082 end;
2083 if FlagPoints[FLAG_BLUE] <> nil then
2084 begin
2085 Dispose(FlagPoints[FLAG_BLUE]);
2086 FlagPoints[FLAG_BLUE] := nil;
2087 end;
2088 //DOMFlagPoints := nil;
2090 //gDOMFlags := nil;
2092 if (Length(gCurrentMapFileName) <> 0) then
2093 begin
2094 e_LogWritefln('g_Map_Free: previous map was ''%s''...', [gCurrentMapFileName]);
2095 end
2096 else
2097 begin
2098 e_LogWritefln('g_Map_Free: no previous map.', []);
2099 end;
2101 if freeTextures then
2102 begin
2103 e_LogWritefln('g_Map_Free: clearing textures...', []);
2104 Textures := nil;
2105 TextNameHash.Free();
2106 TextNameHash := nil;
2107 BadTextNameHash.Free();
2108 BadTextNameHash := nil;
2109 gCurrentMapFileName := '';
2110 gCurrentMap.Free();
2111 gCurrentMap := nil;
2112 end;
2114 panByGUID := nil;
2116 FreePanelArray(gWalls);
2117 FreePanelArray(gRenderBackgrounds);
2118 FreePanelArray(gRenderForegrounds);
2119 FreePanelArray(gWater);
2120 FreePanelArray(gAcid1);
2121 FreePanelArray(gAcid2);
2122 FreePanelArray(gSteps);
2123 FreePanelArray(gLifts);
2124 FreePanelArray(gBlockMon);
2125 gMovingWallIds := nil;
2127 g_Game_StopAllSounds(False);
2128 gMusic.FreeSound();
2129 g_Sound_Delete(gMapInfo.MusicName);
2131 gMapInfo.Name := '';
2132 gMapInfo.Description := '';
2133 gMapInfo.MusicName := '';
2134 gMapInfo.Height := 0;
2135 gMapInfo.Width := 0;
2137 gDoorMap := nil;
2138 gLiftMap := nil;
2139 end;
2141 procedure g_Map_Update();
2142 var
2143 a, d, j: Integer;
2144 m: Word;
2145 s: String;
2146 b: Byte;
2148 procedure UpdatePanelArray(var panels: TPanelArray);
2149 var
2150 i: Integer;
2152 begin
2153 for i := 0 to High(panels) do panels[i].Update();
2154 end;
2156 begin
2157 if g_dbgpan_mplat_step then g_dbgpan_mplat_active := true;
2159 UpdatePanelArray(gWalls);
2160 UpdatePanelArray(gRenderBackgrounds);
2161 UpdatePanelArray(gRenderForegrounds);
2162 UpdatePanelArray(gWater);
2163 UpdatePanelArray(gAcid1);
2164 UpdatePanelArray(gAcid2);
2165 UpdatePanelArray(gSteps);
2167 if g_dbgpan_mplat_step then begin g_dbgpan_mplat_step := false; g_dbgpan_mplat_active := false; end;
2169 if gGameSettings.GameMode = GM_CTF then
2170 begin
2171 for a := FLAG_RED to FLAG_BLUE do
2172 begin
2173 if not (gFlags[a].State in [FLAG_STATE_NONE, FLAG_STATE_CAPTURED]) then
2174 begin
2175 with gFlags[a] do
2176 begin
2177 Obj.oldX := Obj.X;
2178 Obj.oldY := Obj.Y;
2180 m := g_Obj_Move(@Obj, True, True);
2182 NeedSend := NeedSend or (Obj.X <> Obj.oldX) or (Obj.Y <> Obj.oldY);
2184 if gTime mod (GAME_TICK*2) <> 0 then Continue;
2186 // Ñîïðîòèâëåíèå âîçäóõà
2187 Obj.Vel.X := z_dec(Obj.Vel.X, 1);
2189 // Òàéìàóò ïîòåðÿííîãî ôëàãà, ëèáî îí âûïàë çà êàðòó
2190 if ((Count = 0) or ByteBool(m and MOVE_FALLOUT)) and g_Game_IsServer then
2191 begin
2192 g_Map_ResetFlag(a);
2193 gFlags[a].CaptureTime := 0;
2194 if a = FLAG_RED then
2195 s := _lc[I_PLAYER_FLAG_RED]
2196 else
2197 s := _lc[I_PLAYER_FLAG_BLUE];
2198 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
2200 if (((gPlayer1 <> nil) and (((gPlayer1.Team = TEAM_RED) and (a = FLAG_RED)) or ((gPlayer1.Team = TEAM_BLUE) and (a = FLAG_BLUE))))
2201 or ((gPlayer2 <> nil) and (((gPlayer2.Team = TEAM_RED) and (a = FLAG_RED)) or ((gPlayer2.Team = TEAM_BLUE) and (a = FLAG_BLUE))))) then
2202 b := 0
2203 else
2204 b := 1;
2206 if not sound_ret_flag[b].IsPlaying() then
2207 sound_ret_flag[b].Play();
2209 if g_Game_IsNet then
2210 MH_SEND_FlagEvent(FLAG_STATE_RETURNED, a, 0);
2211 Continue;
2212 end;
2214 if Count > 0 then Count -= 1;
2216 // Èãðîê áåðåò ôëàã
2217 if gPlayers <> nil then
2218 begin
2219 j := Random(Length(gPlayers)) - 1;
2220 for d := 0 to High(gPlayers) do
2221 begin
2222 Inc(j);
2223 if j > High(gPlayers) then j := 0;
2224 if gPlayers[j] <> nil then
2225 begin
2226 if gPlayers[j].alive and g_Obj_Collide(@Obj, @gPlayers[j].Obj) then
2227 begin
2228 if gPlayers[j].GetFlag(a) then Break;
2229 end;
2230 end;
2231 end;
2232 end;
2233 end;
2234 end;
2235 end;
2236 end;
2237 end;
2239 function g_Map_CollidePanelOld(X, Y: Integer; Width, Height: Word;
2240 PanelType: Word; b1x3: Boolean=false): Boolean;
2241 var
2242 a, h: Integer;
2243 begin
2244 Result := False;
2246 if WordBool(PanelType and PANEL_WALL) then
2247 if gWalls <> nil then
2248 begin
2249 h := High(gWalls);
2251 for a := 0 to h do
2252 if gWalls[a].Enabled and
2253 g_Collide(X, Y, Width, Height,
2254 gWalls[a].X, gWalls[a].Y,
2255 gWalls[a].Width, gWalls[a].Height) then
2256 begin
2257 Result := True;
2258 Exit;
2259 end;
2260 end;
2262 if WordBool(PanelType and PANEL_WATER) then
2263 if gWater <> nil then
2264 begin
2265 h := High(gWater);
2267 for a := 0 to h do
2268 if g_Collide(X, Y, Width, Height,
2269 gWater[a].X, gWater[a].Y,
2270 gWater[a].Width, gWater[a].Height) then
2271 begin
2272 Result := True;
2273 Exit;
2274 end;
2275 end;
2277 if WordBool(PanelType and PANEL_ACID1) then
2278 if gAcid1 <> nil then
2279 begin
2280 h := High(gAcid1);
2282 for a := 0 to h do
2283 if g_Collide(X, Y, Width, Height,
2284 gAcid1[a].X, gAcid1[a].Y,
2285 gAcid1[a].Width, gAcid1[a].Height) then
2286 begin
2287 Result := True;
2288 Exit;
2289 end;
2290 end;
2292 if WordBool(PanelType and PANEL_ACID2) then
2293 if gAcid2 <> nil then
2294 begin
2295 h := High(gAcid2);
2297 for a := 0 to h do
2298 if g_Collide(X, Y, Width, Height,
2299 gAcid2[a].X, gAcid2[a].Y,
2300 gAcid2[a].Width, gAcid2[a].Height) then
2301 begin
2302 Result := True;
2303 Exit;
2304 end;
2305 end;
2307 if WordBool(PanelType and PANEL_STEP) then
2308 if gSteps <> nil then
2309 begin
2310 h := High(gSteps);
2312 for a := 0 to h do
2313 if g_Collide(X, Y, Width, Height,
2314 gSteps[a].X, gSteps[a].Y,
2315 gSteps[a].Width, gSteps[a].Height) then
2316 begin
2317 Result := True;
2318 Exit;
2319 end;
2320 end;
2322 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then
2323 if gLifts <> nil then
2324 begin
2325 h := High(gLifts);
2327 for a := 0 to h do
2328 if ((WordBool(PanelType and (PANEL_LIFTUP)) and (gLifts[a].LiftType = LIFTTYPE_UP)) or
2329 (WordBool(PanelType and (PANEL_LIFTDOWN)) and (gLifts[a].LiftType = LIFTTYPE_DOWN)) or
2330 (WordBool(PanelType and (PANEL_LIFTLEFT)) and (gLifts[a].LiftType = LIFTTYPE_LEFT)) or
2331 (WordBool(PanelType and (PANEL_LIFTRIGHT)) and (gLifts[a].LiftType = LIFTTYPE_RIGHT))) and
2332 g_Collide(X, Y, Width, Height,
2333 gLifts[a].X, gLifts[a].Y,
2334 gLifts[a].Width, gLifts[a].Height) then
2335 begin
2336 Result := True;
2337 Exit;
2338 end;
2339 end;
2341 if WordBool(PanelType and PANEL_BLOCKMON) then
2342 if gBlockMon <> nil then
2343 begin
2344 h := High(gBlockMon);
2346 for a := 0 to h do
2347 if ( (not b1x3) or
2348 ((gBlockMon[a].Width + gBlockMon[a].Height) >= 64) ) and
2349 g_Collide(X, Y, Width, Height,
2350 gBlockMon[a].X, gBlockMon[a].Y,
2351 gBlockMon[a].Width, gBlockMon[a].Height) then
2352 begin
2353 Result := True;
2354 Exit;
2355 end;
2356 end;
2357 end;
2359 function g_Map_CollideLiquid_TextureOld(X, Y: Integer; Width, Height: Word): DWORD;
2360 var
2361 texid: DWORD;
2363 function checkPanels (constref panels: TPanelArray): Boolean;
2364 var
2365 a: Integer;
2366 begin
2367 result := false;
2368 if panels = nil then exit;
2369 for a := 0 to High(panels) do
2370 begin
2371 if g_Collide(X, Y, Width, Height, panels[a].X, panels[a].Y, panels[a].Width, panels[a].Height) then
2372 begin
2373 result := true;
2374 texid := panels[a].GetTextureID();
2375 exit;
2376 end;
2377 end;
2378 end;
2380 begin
2381 texid := LongWord(TEXTURE_NONE);
2382 result := texid;
2383 if not checkPanels(gWater) then
2384 if not checkPanels(gAcid1) then
2385 if not checkPanels(gAcid2) then exit;
2386 result := texid;
2387 end;
2390 function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word; PanelType: Word; b1x3: Boolean): Boolean;
2391 const
2392 SlowMask = GridTagLift or GridTagBlockMon;
2394 function checker (pan: TPanel; tag: Integer): Boolean;
2395 begin
2397 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
2398 begin
2399 result := pan.Enabled;
2400 exit;
2401 end;
2404 if ((tag and GridTagLift) <> 0) then
2405 begin
2406 result :=
2407 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
2408 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
2409 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
2410 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT))) {and
2411 g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
2412 exit;
2413 end;
2415 if ((tag and GridTagBlockMon) <> 0) then
2416 begin
2417 result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2418 exit;
2419 end;
2421 // other shit
2422 //result := g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2423 result := true; // i found her!
2424 end;
2426 var
2427 tagmask: Integer = 0;
2428 mwit: PPanel;
2429 it: TPanelGrid.Iter;
2430 pan: TPanel;
2431 begin
2432 result := false;
2433 if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor);
2434 if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater;
2435 if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1;
2436 if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2;
2437 if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep;
2438 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift;
2439 if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon;
2441 if (tagmask = 0) then exit; // just in case
2443 if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('*solids');
2444 if gdbg_map_use_accel_coldet then
2445 begin
2446 if ((tagmask and SlowMask) <> 0) then
2447 begin
2448 // slow
2449 it := mapGrid.forEachInAABB(X, Y, Width, Height, tagmask);
2450 for mwit in it do
2451 begin
2452 pan := mwit^;
2453 if ((pan.tag and GridTagLift) <> 0) then
2454 begin
2455 result :=
2456 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
2457 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
2458 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
2459 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT))) {and
2460 g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
2461 end
2462 else if ((pan.tag and GridTagBlockMon) <> 0) then
2463 begin
2464 result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2465 end
2466 else
2467 begin
2468 // other shit
2469 result := true; // i found her!
2470 end;
2471 if (result) then break;
2472 end;
2473 end
2474 else
2475 begin
2476 // fast
2477 it := mapGrid.forEachInAABB(X, Y, Width, Height, tagmask, false, true); // return first hit
2478 result := (it.length > 0);
2479 end;
2480 it.release();
2481 end
2482 else
2483 begin
2484 result := g_Map_CollidePanelOld(X, Y, Width, Height, PanelType, b1x3);
2485 end;
2486 if (profMapCollision <> nil) then profMapCollision.sectionEnd();
2487 end;
2490 // returns `true` if we need to stop
2491 function liquidChecker (pan: TPanel; var texid: DWORD; var cctype: Integer): Boolean; inline;
2492 begin
2493 result := false;
2494 //if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2)) = 0) then exit;
2495 // check priorities
2496 case cctype of
2497 0: if ((pan.tag and GridTagWater) = 0) then exit; // allowed: water
2498 1: if ((pan.tag and (GridTagWater or GridTagAcid1)) = 0) then exit; // allowed: water, acid1
2499 //2: if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2) = 0) then exit; // allowed: water, acid1, acid2
2500 end;
2501 // collision?
2502 //if not g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height) then exit;
2503 // yeah
2504 texid := pan.GetTextureID();
2505 // water? water has the highest priority, so stop right here
2506 if ((pan.tag and GridTagWater) <> 0) then begin cctype := 0; result := true; exit; end;
2507 // acid2?
2508 if ((pan.tag and GridTagAcid2) <> 0) then cctype := 2;
2509 // acid1?
2510 if ((pan.tag and GridTagAcid1) <> 0) then cctype := 1;
2511 end;
2513 function g_Map_CollideLiquid_Texture(X, Y: Integer; Width, Height: Word): DWORD;
2514 var
2515 cctype: Integer = 3; // priority: 0: water was hit, 1: acid1 was hit, 2: acid2 was hit; 3: nothing was hit
2516 mwit: PPanel;
2517 it: TPanelGrid.Iter;
2518 begin
2519 if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('liquids');
2520 if gdbg_map_use_accel_coldet then
2521 begin
2522 result := LongWord(TEXTURE_NONE);
2523 it := mapGrid.forEachInAABB(X, Y, Width, Height, (GridTagWater or GridTagAcid1 or GridTagAcid2));
2524 for mwit in it do if (liquidChecker(mwit^, result, cctype)) then break;
2525 it.release();
2526 end
2527 else
2528 begin
2529 result := g_Map_CollideLiquid_TextureOld(X, Y, Width, Height);
2530 end;
2531 if (profMapCollision <> nil) then profMapCollision.sectionEnd();
2532 end;
2535 procedure g_Map_EnableWall_XXX (ID: DWORD); begin if (ID < Length(gWalls)) then g_Map_EnableWallGUID(gWalls[ID].guid); end;
2536 procedure g_Map_DisableWall_XXX (ID: DWORD); begin if (ID < Length(gWalls)) then g_Map_DisableWallGUID(gWalls[ID].guid); end;
2537 procedure g_Map_SetLift_XXX (ID: DWORD; t: Integer); begin if (ID < Length(gLifts)) then g_Map_SetLiftGUID(gLifts[ID].guid, t); end;
2540 procedure g_Map_EnableWallGUID (pguid: Integer);
2541 var
2542 pan: TPanel;
2543 begin
2544 //pan := gWalls[ID];
2545 pan := g_Map_PanelByGUID(pguid);
2546 if (pan = nil) then exit;
2547 if pan.Enabled and mapGrid.proxyEnabled[pan.proxyId] then exit;
2549 pan.Enabled := True;
2551 {$IFDEF ENABLE_GFX}
2552 g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, true);
2553 {$ENDIF}
2555 mapGrid.proxyEnabled[pan.proxyId] := true;
2556 //if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := true
2557 //else pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, GridTagDoor);
2559 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2560 // mark platform as interesting
2561 pan.setDirty();
2563 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
2564 //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);
2565 {$ENDIF}
2566 end;
2569 procedure g_Map_DisableWallGUID (pguid: Integer);
2570 var
2571 pan: TPanel;
2572 begin
2573 //pan := gWalls[ID];
2574 pan := g_Map_PanelByGUID(pguid);
2575 if (pan = nil) then exit;
2576 if (not pan.Enabled) and (not mapGrid.proxyEnabled[pan.proxyId]) then exit;
2578 pan.Enabled := False;
2579 {$IFDEF ENABLE_GFX}
2580 g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, false);
2581 {$ENDIF}
2583 mapGrid.proxyEnabled[pan.proxyId] := false;
2584 //if (pan.proxyId >= 0) then begin mapGrid.removeBody(pan.proxyId); pan.proxyId := -1; end;
2586 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2587 // mark platform as interesting
2588 pan.setDirty();
2590 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
2591 //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);
2592 {$ENDIF}
2593 end;
2596 procedure g_Map_SwitchTextureGUID (pguid: Integer; AnimLoop: Byte = 0);
2597 var
2598 tp: TPanel;
2599 begin
2600 tp := g_Map_PanelByGUID(pguid);
2601 if (tp = nil) then exit;
2602 tp.NextTexture(AnimLoop);
2603 if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelTexture(pguid, AnimLoop);
2604 end;
2607 procedure g_Map_SetLiftGUID (pguid: Integer; t: Integer);
2608 var
2609 pan: TPanel;
2610 begin
2611 //pan := gLifts[ID];
2612 pan := g_Map_PanelByGUID(pguid);
2613 if (pan = nil) then exit;
2614 if not pan.isGLift then exit;
2616 if ({gLifts[ID]}pan.LiftType = t) then exit; //!FIXME!TRIGANY!
2618 with {gLifts[ID]} pan do
2619 begin
2620 LiftType := t;
2622 {$IFDEF ENABLE_GFX}
2623 g_Mark(X, Y, Width, Height, MARK_LIFT, false);
2624 //TODO: make separate lift tags, and change tag here
2625 case LiftType of
2626 LIFTTYPE_UP: g_Mark(X, Y, Width, Height, MARK_LIFTUP);
2627 LIFTTYPE_DOWN: g_Mark(X, Y, Width, Height, MARK_LIFTDOWN);
2628 LIFTTYPE_LEFT: g_Mark(X, Y, Width, Height, MARK_LIFTLEFT);
2629 LIFTTYPE_RIGHT: g_Mark(X, Y, Width, Height, MARK_LIFTRIGHT);
2630 end;
2631 {$ENDIF}
2633 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2634 // mark platform as interesting
2635 pan.setDirty();
2636 end;
2637 end;
2640 function g_Map_GetPoint (PointType: Byte; var RespawnPoint: TRespawnPoint): Boolean;
2641 var
2642 a: Integer;
2643 PointsArray: Array of TRespawnPoint;
2644 begin
2645 Result := False;
2646 SetLength(PointsArray, 0);
2648 if RespawnPoints = nil then
2649 Exit;
2651 for a := 0 to High(RespawnPoints) do
2652 if RespawnPoints[a].PointType = PointType then
2653 begin
2654 SetLength(PointsArray, Length(PointsArray)+1);
2655 PointsArray[High(PointsArray)] := RespawnPoints[a];
2656 end;
2658 if PointsArray = nil then
2659 Exit;
2661 RespawnPoint := PointsArray[Random(Length(PointsArray))];
2662 Result := True;
2663 end;
2665 function g_Map_GetPointCount(PointType: Byte): Word;
2666 var
2667 a: Integer;
2668 begin
2669 Result := 0;
2671 if RespawnPoints = nil then
2672 Exit;
2674 for a := 0 to High(RespawnPoints) do
2675 if RespawnPoints[a].PointType = PointType then
2676 Result := Result + 1;
2677 end;
2679 function g_Map_GetRandomPointType(): Byte;
2680 begin
2681 if RespawnPoints = nil then
2682 Result := 255
2683 else
2684 Result := RespawnPoints[Random(Length(RespawnPoints))].PointType;
2685 end;
2687 function g_Map_HaveFlagPoints(): Boolean;
2688 begin
2689 Result := (FlagPoints[FLAG_RED] <> nil) and (FlagPoints[FLAG_BLUE] <> nil);
2690 end;
2692 procedure g_Map_ResetFlag(Flag: Byte);
2693 begin
2694 with gFlags[Flag] do
2695 begin
2696 Obj.X := -1000;
2697 Obj.Y := -1000;
2698 Obj.Vel.X := 0;
2699 Obj.Vel.Y := 0;
2700 Direction := TDirection.D_LEFT;
2701 State := FLAG_STATE_NONE;
2702 if FlagPoints[Flag] <> nil then
2703 begin
2704 Obj.X := FlagPoints[Flag]^.X;
2705 Obj.Y := FlagPoints[Flag]^.Y;
2706 Direction := FlagPoints[Flag]^.Direction;
2707 State := FLAG_STATE_NORMAL;
2708 end;
2709 Obj.oldX := Obj.X;
2710 Obj.oldY := Obj.Y;
2711 NeedSend := False; // the event will take care of this
2712 Count := -1;
2713 end;
2714 end;
2716 procedure g_Map_SaveState (st: TStream);
2717 var
2718 str: String;
2720 procedure savePanels ();
2721 var
2722 pan: TPanel;
2723 begin
2724 // Ñîõðàíÿåì ïàíåëè
2725 utils.writeInt(st, LongInt(Length(panByGUID)));
2726 for pan in panByGUID do pan.SaveState(st);
2727 end;
2729 procedure saveFlag (flag: PFlag);
2730 var
2731 b: Byte;
2732 begin
2733 utils.writeSign(st, 'FLAG');
2734 utils.writeInt(st, Byte(0)); // version
2735 // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
2736 utils.writeInt(st, Byte(flag^.RespawnType));
2737 // Ñîñòîÿíèå ôëàãà
2738 utils.writeInt(st, Byte(flag^.State));
2739 // Íàïðàâëåíèå ôëàãà
2740 if flag^.Direction = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT
2741 utils.writeInt(st, Byte(b));
2742 // Îáúåêò ôëàãà
2743 Obj_SaveState(st, @flag^.Obj);
2744 end;
2746 begin
2747 savePanels();
2749 // Ñîõðàíÿåì ìóçûêó
2750 utils.writeSign(st, 'MUSI');
2751 utils.writeInt(st, Byte(0));
2752 // Íàçâàíèå ìóçûêè
2753 assert(gMusic <> nil, 'g_Map_SaveState: gMusic = nil');
2754 if gMusic.NoMusic then str := '' else str := gMusic.Name;
2755 utils.writeStr(st, str);
2756 // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
2757 utils.writeInt(st, LongWord(gMusic.GetPosition()));
2758 // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
2759 utils.writeBool(st, gMusic.SpecPause);
2761 ///// Ñîõðàíÿåì êîëè÷åñòâî ìîíñòðîâ: /////
2762 utils.writeInt(st, LongInt(gTotalMonsters));
2763 ///// /////
2765 //// Ñîõðàíÿåì ôëàãè, åñëè ýòî CTF: /////
2766 if (gGameSettings.GameMode = GM_CTF) then
2767 begin
2768 // Ôëàã Êðàñíîé êîìàíäû
2769 saveFlag(@gFlags[FLAG_RED]);
2770 // Ôëàã Ñèíåé êîìàíäû
2771 saveFlag(@gFlags[FLAG_BLUE]);
2772 end;
2773 ///// /////
2775 ///// Ñîõðàíÿåì êîëè÷åñòâî ïîáåä, åñëè ýòî TDM/CTF: /////
2776 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
2777 begin
2778 // Î÷êè Êðàñíîé êîìàíäû
2779 utils.writeInt(st, SmallInt(gTeamStat[TEAM_RED].Score));
2780 // Î÷êè Ñèíåé êîìàíäû
2781 utils.writeInt(st, SmallInt(gTeamStat[TEAM_BLUE].Score));
2782 end;
2783 ///// /////
2784 end;
2787 procedure g_Map_LoadState (st: TStream);
2788 var
2789 dw: DWORD;
2790 str: String;
2791 boo: Boolean;
2793 procedure loadPanels ();
2794 var
2795 pan: TPanel;
2796 begin
2797 // Çàãðóæàåì ïàíåëè
2798 if (Length(panByGUID) <> utils.readLongInt(st)) then raise XStreamError.Create('invalid number of saved panels');
2799 for pan in panByGUID do
2800 begin
2801 pan.LoadState(st);
2802 if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
2803 end;
2804 end;
2806 procedure loadFlag (flag: PFlag);
2807 var
2808 b: Byte;
2809 begin
2810 // Ñèãíàòóðà ôëàãà
2811 if not utils.checkSign(st, 'FLAG') then raise XStreamError.Create('invalid flag signature');
2812 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid flag version');
2813 // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
2814 flag^.RespawnType := utils.readByte(st);
2815 // Ñîñòîÿíèå ôëàãà
2816 flag^.State := utils.readByte(st);
2817 // Íàïðàâëåíèå ôëàãà
2818 b := utils.readByte(st);
2819 if (b = 1) then flag^.Direction := TDirection.D_LEFT else flag^.Direction := TDirection.D_RIGHT; // b = 2
2820 // Îáúåêò ôëàãà
2821 Obj_LoadState(@flag^.Obj, st);
2822 end;
2824 begin
2825 if (st = nil) then exit;
2827 ///// Çàãðóæàåì ñïèñêè ïàíåëåé: /////
2828 loadPanels();
2829 ///// /////
2831 {$IFDEF ENABLE_GFX}
2832 // Îáíîâëÿåì êàðòó ñòîëêíîâåíèé è ñåòêó
2833 g_GFX_Init();
2834 {$ENDIF}
2836 //mapCreateGrid();
2838 ///// Çàãðóæàåì ìóçûêó: /////
2839 if not utils.checkSign(st, 'MUSI') then raise XStreamError.Create('invalid music signature');
2840 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid music version');
2841 // Íàçâàíèå ìóçûêè
2842 assert(gMusic <> nil, 'g_Map_LoadState: gMusic = nil');
2843 str := utils.readStr(st);
2844 // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
2845 dw := utils.readLongWord(st);
2846 // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
2847 boo := utils.readBool(st);
2848 // Çàïóñêàåì ýòó ìóçûêó
2849 gMusic.SetByName(str);
2850 gMusic.SpecPause := boo;
2851 gMusic.Play();
2852 gMusic.Pause(true);
2853 gMusic.SetPosition(dw);
2854 ///// /////
2856 ///// Çàãðóæàåì êîëè÷åñòâî ìîíñòðîâ: /////
2857 gTotalMonsters := utils.readLongInt(st);
2858 ///// /////
2860 //// Çàãðóæàåì ôëàãè, åñëè ýòî CTF: /////
2861 if (gGameSettings.GameMode = GM_CTF) then
2862 begin
2863 // Ôëàã Êðàñíîé êîìàíäû
2864 loadFlag(@gFlags[FLAG_RED]);
2865 // Ôëàã Ñèíåé êîìàíäû
2866 loadFlag(@gFlags[FLAG_BLUE]);
2867 end;
2868 ///// /////
2870 ///// Çàãðóæàåì êîëè÷åñòâî ïîáåä, åñëè ýòî TDM/CTF: /////
2871 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
2872 begin
2873 // Î÷êè Êðàñíîé êîìàíäû
2874 gTeamStat[TEAM_RED].Score := utils.readSmallInt(st);
2875 // Î÷êè Ñèíåé êîìàíäû
2876 gTeamStat[TEAM_BLUE].Score := utils.readSmallInt(st);
2877 end;
2878 ///// /////
2879 end;
2882 // trace liquid, stepping by `dx` and `dy`
2883 // return last seen liquid coords, and `false` if we're started outside of the liquid
2884 function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
2885 const
2886 MaskLiquid = GridTagWater or GridTagAcid1 or GridTagAcid2;
2887 begin
2888 topx := x;
2889 topy := y;
2890 // started outside of the liquid?
2891 //if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then begin result := false; exit; end;
2892 if (g_Map_PanelAtPoint(x, y, MaskLiquid) = nil) then begin result := false; exit; end;
2893 if (dx = 0) and (dy = 0) then begin result := false; exit; end; // sanity check
2894 result := true;
2895 while true do
2896 begin
2897 Inc(x, dx);
2898 Inc(y, dy);
2899 //if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then exit; // out of the water, just exit
2900 if (g_Map_PanelAtPoint(x, y, MaskLiquid) = nil) then exit; // out of the water, just exit
2901 topx := x;
2902 topy := y;
2903 end;
2904 end;
2907 begin
2908 DynWarningCB := mapWarningCB;
2909 end.