DEADSOFTWARE

game: disable gfx for server
[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 end;
59 function g_Map_Load(Res: String): Boolean;
60 function g_Map_GetMapInfo(Res: String): TMapInfo;
61 function g_Map_GetMapsList(WADName: String): SSArray;
62 function g_Map_Exist(Res: String): Boolean;
63 procedure g_Map_Free(freeTextures: Boolean=true);
64 procedure g_Map_Update();
66 function g_Map_PanelByGUID (aguid: Integer): TPanel; inline;
68 function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word;
69 PanelType: Word; b1x3: Boolean=false): Boolean;
70 function g_Map_CollideLiquid_Texture(X, Y: Integer; Width, Height: Word): DWORD;
72 procedure g_Map_EnableWallGUID (pguid: Integer);
73 procedure g_Map_DisableWallGUID (pguid: Integer);
74 procedure g_Map_SetLiftGUID (pguid: Integer; t: Integer);
76 // HACK!!!
77 procedure g_Map_EnableWall_XXX (ID: DWORD);
78 procedure g_Map_DisableWall_XXX (ID: DWORD);
79 procedure g_Map_SetLift_XXX (ID: DWORD; t: Integer);
81 procedure g_Map_SwitchTextureGUID (pguid: Integer; AnimLoop: Byte = 0);
83 procedure g_Map_ReAdd_DieTriggers();
84 function g_Map_IsSpecialTexture(Texture: String): Boolean;
86 function g_Map_GetPoint(PointType: Byte; var RespawnPoint: TRespawnPoint): Boolean;
87 function g_Map_GetPointCount(PointType: Byte): Word;
88 function g_Map_GetRandomPointType(): Byte;
90 function g_Map_HaveFlagPoints(): Boolean;
92 procedure g_Map_ResetFlag(Flag: Byte);
94 procedure g_Map_SaveState (st: TStream);
95 procedure g_Map_LoadState (st: TStream);
97 // returns panel or nil
98 // sets `ex` and `ey` to `x1` and `y1` when no hit was detected
99 function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
101 // returns panel or nil
102 // sets `ex` and `ey` to `x1` and `y1` when no hit was detected
103 function g_Map_traceToNearest (x0, y0, x1, y1: Integer; tag: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
105 type
106 TForEachPanelCB = function (pan: TPanel): Boolean is nested; // return `true` to stop
108 function g_Map_HasAnyPanelAtPoint (x, y: Integer; panelType: Word): Boolean;
109 function g_Map_PanelAtPoint (x, y: Integer; tagmask: Integer=-1): TPanel;
111 // trace liquid, stepping by `dx` and `dy`
112 // return last seen liquid coords, and `false` if we're started outside of the liquid
113 function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
116 // return `true` from `cb` to stop
117 function g_Map_ForEachPanel (cb: TForEachPanelCB): TPanel;
119 procedure g_Map_NetSendInterestingPanels (); // yay!
122 procedure g_Map_ProfilersBegin ();
123 procedure g_Map_ProfilersEnd ();
126 function g_Map_ParseMap (data: Pointer; dataLen: Integer): TDynRecord;
129 function g_Map_MinX (): Integer; inline;
130 function g_Map_MinY (): Integer; inline;
131 function g_Map_MaxX (): Integer; inline;
132 function g_Map_MaxY (): Integer; inline;
134 const
135 NNF_NO_NAME = 0;
136 NNF_NAME_BEFORE = 1;
137 NNF_NAME_EQUALS = 2;
138 NNF_NAME_AFTER = 3;
140 function g_Texture_NumNameFindStart(name: String): Boolean;
141 function g_Texture_NumNameFindNext(var newName: String): Byte;
143 const
144 RESPAWNPOINT_PLAYER1 = 1;
145 RESPAWNPOINT_PLAYER2 = 2;
146 RESPAWNPOINT_DM = 3;
147 RESPAWNPOINT_RED = 4;
148 RESPAWNPOINT_BLUE = 5;
150 FLAG_NONE = 0;
151 FLAG_RED = 1;
152 FLAG_BLUE = 2;
153 FLAG_DOM = 3;
155 FLAG_STATE_NONE = 0;
156 FLAG_STATE_NORMAL = 1;
157 FLAG_STATE_DROPPED = 2;
158 FLAG_STATE_CAPTURED = 3;
159 FLAG_STATE_SCORED = 4; // Äëÿ ýâåíòîâ ÷åðåç ñåòêó.
160 FLAG_STATE_RETURNED = 5; // Äëÿ ýâåíòîâ ÷åðåç ñåòêó.
162 FLAG_TIME = 720; // 20 seconds
164 SKY_STRETCH: Single = 1.5;
166 const
167 GridTagInvalid = 0;
169 (* draw order:
170 PANEL_BACK
171 PANEL_STEP
172 PANEL_WALL
173 PANEL_CLOSEDOOR
174 PANEL_ACID1
175 PANEL_ACID2
176 PANEL_WATER
177 PANEL_FORE
178 *)
179 // sorted by draw priority
180 GridTagBack = 1 shl 0; // gRenderBackgrounds
181 GridTagStep = 1 shl 1; // gSteps
182 GridTagWall = 1 shl 2; // gWalls
183 GridTagDoor = 1 shl 3; // gWalls
184 GridTagAcid1 = 1 shl 4; // gAcid1
185 GridTagAcid2 = 1 shl 5; // gAcid2
186 GridTagWater = 1 shl 6; // gWater
187 GridTagFore = 1 shl 7; // gRenderForegrounds
188 // the following are invisible
189 GridTagLift = 1 shl 8; // gLifts
190 GridTagBlockMon = 1 shl 9; // gBlockMon
192 GridTagSolid = (GridTagWall or GridTagDoor);
193 GridTagObstacle = (GridTagStep or GridTagWall or GridTagDoor);
194 GridTagLiquid = (GridTagAcid1 or GridTagAcid2 or GridTagWater);
196 GridDrawableMask = (GridTagBack or GridTagStep or GridTagWall or GridTagDoor or GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagFore);
199 type
200 TBinHeapPanelDrawCmp = class
201 public
202 class function less (const a, b: TPanel): Boolean; inline;
203 end;
205 TBinHeapPanelDraw = specialize TBinaryHeapBase<TPanel, TBinHeapPanelDrawCmp>;
207 var
208 gWalls: TPanelArray;
209 gRenderBackgrounds: TPanelArray;
210 gRenderForegrounds: TPanelArray;
211 gWater, gAcid1, gAcid2: TPanelArray;
212 gSteps: TPanelArray;
213 gLifts: TPanelArray;
214 gBlockMon: TPanelArray;
215 gFlags: array [FLAG_RED..FLAG_BLUE] of TFlag;
216 //gDOMFlags: array of TFlag;
217 gMapInfo: TMapInfo;
218 gDoorMap: array of array of DWORD;
219 gLiftMap: array of array of DWORD;
220 gWADHash: TMD5Digest;
221 gExternalResources: array of TDiskFileInfo = nil;
222 gMovingWallIds: array of Integer = nil;
224 gdbg_map_use_accel_render: Boolean = true;
225 gdbg_map_use_accel_coldet: Boolean = true;
226 profMapCollision: TProfiler = nil; //WARNING: FOR DEBUGGING ONLY!
227 gDrawPanelList: TBinHeapPanelDraw = nil; // binary heap of all walls we have to render, populated by `g_Map_CollectDrawPanels()`
229 gCurrentMap: TDynRecord = nil;
230 gCurrentMapFileName: AnsiString = ''; // so we can skip texture reloading
231 gTestMap: String = '';
234 function panelTypeToTag (panelType: Word): Integer; // returns GridTagXXX
237 type
238 TPanelGrid = specialize TBodyGridBase<TPanel>;
240 var
241 mapGrid: TPanelGrid = nil; // DO NOT USE! public for debugging only!
243 var (* private state *)
244 Textures: TLevelTextureArray = nil;
246 implementation
248 uses
249 {$IFDEF ENABLE_GFX}
250 g_gfx,
251 {$ENDIF}
252 e_input, e_log, e_res, g_items, g_console,
253 g_weapons, g_game, g_sound, e_sound, CONFIG,
254 g_options, g_triggers, g_player,
255 Math, g_monsters, g_saveload, g_language, g_netmsg,
256 sfs, xstreams, hashtable, wadreader,
257 g_res_downloader
260 const
261 FLAGRECT: TRectWH = (X:15; Y:12; Width:33; Height:52);
262 MUSIC_SIGNATURE = $4953554D; // 'MUSI'
263 FLAG_SIGNATURE = $47414C46; // 'FLAG'
266 // ////////////////////////////////////////////////////////////////////////// //
267 procedure mapWarningCB (const msg: AnsiString; line, col: Integer);
268 begin
269 if (line > 0) then
270 begin
271 e_LogWritefln('parse error at (%s,%s): %s', [line, col, msg], TMsgType.Warning);
272 end
273 else
274 begin
275 e_LogWritefln('parse error: %s', [msg], TMsgType.Warning);
276 end;
277 end;
280 // ////////////////////////////////////////////////////////////////////////// //
281 var
282 panByGUID: array of TPanel = nil;
285 // ////////////////////////////////////////////////////////////////////////// //
286 function g_Map_PanelByGUID (aguid: Integer): TPanel; inline;
287 begin
288 //if (panByGUID = nil) or (not panByGUID.get(aguid, result)) then result := nil;
289 if (aguid >= 0) and (aguid < Length(panByGUID)) then result := panByGUID[aguid] else result := nil;
290 end;
293 // return `true` from `cb` to stop
294 function g_Map_ForEachPanel (cb: TForEachPanelCB): TPanel;
295 var
296 pan: TPanel;
297 begin
298 result := nil;
299 if not assigned(cb) then exit;
300 for pan in panByGUID do
301 begin
302 if cb(pan) then begin result := pan; exit; end;
303 end;
304 end;
307 procedure g_Map_NetSendInterestingPanels ();
308 var
309 pan: TPanel;
310 begin
311 if g_Game_IsServer and g_Game_IsNet then
312 begin
313 for pan in panByGUID do
314 begin
315 if pan.gncNeedSend then MH_SEND_PanelState(pan.guid);
316 end;
317 end;
318 end;
321 // ////////////////////////////////////////////////////////////////////////// //
322 function g_Map_MinX (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridX0 else result := 0; end;
323 function g_Map_MinY (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridY0 else result := 0; end;
324 function g_Map_MaxX (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridX0+mapGrid.gridWidth-1 else result := 0; end;
325 function g_Map_MaxY (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridY0+mapGrid.gridHeight-1 else result := 0; end;
328 // ////////////////////////////////////////////////////////////////////////// //
329 var
330 dfmapdef: TDynMapDef = nil;
333 procedure loadMapDefinition ();
334 var
335 pr: TTextParser = nil;
336 st: TStream = nil;
337 WAD: TWADFile = nil;
338 begin
339 if (dfmapdef <> nil) then exit;
341 try
342 e_LogWritefln('parsing "mapdef.txt"...', []);
343 st := e_OpenResourceRO(DataDirs, 'mapdef.txt');
344 e_LogWritefln('found local "mapdef.txt"', []);
345 except
346 st := nil;
347 end;
349 if (st = nil) then
350 begin
351 WAD := TWADFile.Create();
352 if not WAD.ReadFile(GameWAD) then
353 begin
354 //raise Exception.Create('cannot load "game.wad"');
355 st := nil;
356 end
357 else
358 begin
359 st := WAD.openFileStream('mapdef.txt');
360 end;
361 end;
363 try
364 if (st = nil) then
365 begin
366 //raise Exception.Create('cannot open "mapdef.txt"');
367 e_LogWriteln('using default "mapdef.txt"...');
368 pr := TStrTextParser.Create(defaultMapDef);
369 end
370 else
371 begin
372 pr := TFileTextParser.Create(st);
373 end;
374 except on e: Exception do
375 begin
376 e_LogWritefln('something is VERY wrong here! -- ', [e.message]);
377 raise;
378 end;
379 end;
381 try
382 dfmapdef := TDynMapDef.Create(pr);
383 except
384 on e: TDynParseException do
385 raise Exception.CreateFmt('ERROR in "mapdef.txt" at (%s,%s): %s', [e.tokLine, e.tokCol, e.message]);
386 on e: Exception do
387 raise Exception.CreateFmt('ERROR in "mapdef.txt" at (%s,%s): %s', [pr.tokLine, pr.tokCol, e.message]);
388 end;
390 st.Free();
391 WAD.Free();
392 end;
395 // ////////////////////////////////////////////////////////////////////////// //
396 function g_Map_ParseMap (data: Pointer; dataLen: Integer): TDynRecord;
397 var
398 wst: TSFSMemoryChunkStream = nil;
399 begin
400 result := nil;
401 if (dataLen < 4) then exit;
403 if (dfmapdef = nil) then writeln('need to load mapdef');
404 loadMapDefinition();
405 if (dfmapdef = nil) then raise Exception.Create('internal map loader error');
407 wst := TSFSMemoryChunkStream.Create(data, dataLen);
408 try
409 result := dfmapdef.parseMap(wst);
410 except
411 on e: TDynParseException do
412 begin
413 e_LogWritefln('ERROR at (%s,%s): %s', [e.tokLine, e.tokCol, e.message]);
414 wst.Free();
415 result := nil;
416 exit;
417 end;
418 on e: Exception do
419 begin
420 e_LogWritefln('ERROR: %s', [e.message]);
421 wst.Free();
422 result := nil;
423 exit;
424 end;
425 end;
427 //e_LogWriteln('map parsed.');
428 end;
431 // ////////////////////////////////////////////////////////////////////////// //
432 var
433 NNF_PureName: String; // Èìÿ òåêñòóðû áåç öèôð â êîíöå
434 NNF_PureExt: String; // extension postfix
435 NNF_FirstNum: Integer; // ×èñëî ó íà÷àëüíîé òåêñòóðû
436 NNF_CurrentNum: Integer; // Ñëåäóþùåå ÷èñëî ó òåêñòóðû
439 function g_Texture_NumNameFindStart(name: String): Boolean;
440 var
441 i, j: Integer;
443 begin
444 Result := False;
445 NNF_PureName := '';
446 NNF_PureExt := '';
447 NNF_FirstNum := -1;
448 NNF_CurrentNum := -1;
450 for i := Length(name) downto 1 do
451 if (name[i] = '_') then // "_" - ñèìâîë íà÷àëà íîìåðíîãî ïîñòôèêñà
452 begin
453 if i = Length(name) then
454 begin // Íåò öèôð â êîíöå ñòðîêè
455 Exit;
456 end
457 else
458 begin
459 j := i + 1;
460 while (j <= Length(name)) and (name[j] <> '.') do inc(j);
461 NNF_PureName := Copy(name, 1, i);
462 NNF_PureExt := Copy(name, j);
463 name := Copy(name, i + 1, j - i - 1);
464 Break;
465 end;
466 end;
468 // Íå ïåðåâåñòè â ÷èñëî:
469 if not TryStrToInt(name, NNF_FirstNum) then
470 Exit;
472 NNF_CurrentNum := 0;
474 Result := True;
475 end;
478 function g_Texture_NumNameFindNext(var newName: String): Byte;
479 begin
480 if (NNF_PureName = '') or (NNF_CurrentNum < 0) then
481 begin
482 newName := '';
483 Result := NNF_NO_NAME;
484 Exit;
485 end;
487 newName := NNF_PureName + IntToStr(NNF_CurrentNum) + NNF_PureExt;
489 if NNF_CurrentNum < NNF_FirstNum then
490 Result := NNF_NAME_BEFORE
491 else
492 if NNF_CurrentNum > NNF_FirstNum then
493 Result := NNF_NAME_AFTER
494 else
495 Result := NNF_NAME_EQUALS;
497 Inc(NNF_CurrentNum);
498 end;
501 // ////////////////////////////////////////////////////////////////////////// //
502 function panelTypeToTag (panelType: Word): Integer;
503 begin
504 case panelType of
505 PANEL_WALL: result := GridTagWall; // gWalls
506 PANEL_OPENDOOR, PANEL_CLOSEDOOR: result := GridTagDoor; // gWalls
507 PANEL_BACK: result := GridTagBack; // gRenderBackgrounds
508 PANEL_FORE: result := GridTagFore; // gRenderForegrounds
509 PANEL_WATER: result := GridTagWater; // gWater
510 PANEL_ACID1: result := GridTagAcid1; // gAcid1
511 PANEL_ACID2: result := GridTagAcid2; // gAcid2
512 PANEL_STEP: result := GridTagStep; // gSteps
513 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: result := GridTagLift; // gLifts -- this is for all lifts
514 PANEL_BLOCKMON: result := GridTagBlockMon; // gBlockMon -- this is for all blockmons
515 else result := GridTagInvalid;
516 end;
517 end;
520 class function TBinHeapPanelDrawCmp.less (const a, b: TPanel): Boolean; inline;
521 begin
522 if (a.tag < b.tag) then begin result := true; exit; end;
523 if (a.tag > b.tag) then begin result := false; exit; end;
524 result := (a.arrIdx < b.arrIdx);
525 end;
527 var
528 TextNameHash: THashStrInt = nil; // key: texture name; value: index in `Textures`
529 BadTextNameHash: THashStrInt = nil; // set; so we won't spam with non-existing texture messages
530 RespawnPoints: array of TRespawnPoint;
531 FlagPoints: array[FLAG_RED..FLAG_BLUE] of PFlagPoint;
532 //DOMFlagPoints: Array of TFlagPoint;
535 procedure g_Map_ProfilersBegin ();
536 begin
537 if (profMapCollision = nil) then profMapCollision := TProfiler.Create('COLSOLID', g_profile_history_size);
538 if (profMapCollision <> nil) then profMapCollision.mainBegin(g_profile_collision);
539 // create sections
540 if g_profile_collision and (profMapCollision <> nil) then
541 begin
542 profMapCollision.sectionBegin('*solids');
543 profMapCollision.sectionEnd();
544 profMapCollision.sectionBegin('liquids');
545 profMapCollision.sectionEnd();
546 end;
547 end;
549 procedure g_Map_ProfilersEnd ();
550 begin
551 if (profMapCollision <> nil) then profMapCollision.mainEnd();
552 end;
555 // wall index in `gWalls` or -1
556 function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
557 var
558 ex, ey: Integer;
559 begin
560 result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, (GridTagWall or GridTagDoor));
561 if (result <> nil) then
562 begin
563 if (hitx <> nil) then hitx^ := ex;
564 if (hity <> nil) then hity^ := ey;
565 end
566 else
567 begin
568 if (hitx <> nil) then hitx^ := x1;
569 if (hity <> nil) then hity^ := y1;
570 end;
571 end;
573 // returns panel or nil
574 function g_Map_traceToNearest (x0, y0, x1, y1: Integer; tag: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
575 var
576 ex, ey: Integer;
577 begin
578 result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, tag);
579 if (result <> nil) then
580 begin
581 if (hitx <> nil) then hitx^ := ex;
582 if (hity <> nil) then hity^ := ey;
583 end
584 else
585 begin
586 if (hitx <> nil) then hitx^ := x1;
587 if (hity <> nil) then hity^ := y1;
588 end;
589 end;
592 function xxPanAtPointChecker (pan: TPanel; panelType: Word): Boolean; inline;
593 begin
594 if ((pan.tag and GridTagLift) <> 0) then
595 begin
596 // stop if the lift of the right type
597 result :=
598 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
599 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
600 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
601 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT)));
602 exit;
603 end;
604 result := true; // otherwise, stop anyway, 'cause `forEachAtPoint()` is guaranteed to call this only for correct panels
605 end;
607 function g_Map_HasAnyPanelAtPoint (x, y: Integer; panelType: Word): Boolean;
608 var
609 tagmask: Integer = 0;
610 mwit: PPanel;
611 it: TPanelGrid.Iter;
612 begin
613 result := false;
615 if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor);
616 if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater;
617 if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1;
618 if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2;
619 if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep;
620 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift;
621 if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon;
623 if (tagmask = 0) then exit;// just in case
625 if ((tagmask and GridTagLift) <> 0) then
626 begin
627 // slow
628 it := mapGrid.forEachAtPoint(x, y, tagmask);
629 for mwit in it do if (xxPanAtPointChecker(mwit^, PanelType)) then begin result := true; break; end;
630 end
631 else
632 begin
633 // fast
634 it := mapGrid.forEachAtPoint(x, y, tagmask, false, true);
635 result := (it.length <> 0); // firsthit
636 end;
637 it.release();
638 end;
641 function g_Map_PanelAtPoint (x, y: Integer; tagmask: Integer=-1): TPanel;
642 var
643 it: TPanelGrid.Iter;
644 begin
645 result := nil;
646 if (tagmask = 0) then exit;
647 it := mapGrid.forEachAtPoint(x, y, tagmask, false, true); // firsthit
648 if (it.length <> 0) then result := it.first^;
649 it.release();
650 end;
653 function g_Map_IsSpecialTexture(Texture: String): Boolean;
654 begin
655 Result := (Texture = TEXTURE_NAME_WATER) or
656 (Texture = TEXTURE_NAME_ACID1) or
657 (Texture = TEXTURE_NAME_ACID2);
658 end;
660 procedure CreateDoorMap();
661 var
662 PanelArray: Array of record
663 X, Y: Integer;
664 Width, Height: Word;
665 Active: Boolean;
666 PanelID: DWORD;
667 end;
668 a, b, c, m, i, len: Integer;
669 ok: Boolean;
670 begin
671 if gWalls = nil then
672 Exit;
674 i := 0;
675 len := 128;
676 SetLength(PanelArray, len);
678 for a := 0 to High(gWalls) do
679 if gWalls[a].Door then
680 begin
681 PanelArray[i].X := gWalls[a].X;
682 PanelArray[i].Y := gWalls[a].Y;
683 PanelArray[i].Width := gWalls[a].Width;
684 PanelArray[i].Height := gWalls[a].Height;
685 PanelArray[i].Active := True;
686 PanelArray[i].PanelID := a;
688 i := i + 1;
689 if i = len then
690 begin
691 len := len + 128;
692 SetLength(PanelArray, len);
693 end;
694 end;
696 // Íåò äâåðåé:
697 if i = 0 then
698 begin
699 PanelArray := nil;
700 Exit;
701 end;
703 SetLength(gDoorMap, 0);
705 g_Game_SetLoadingText(_lc[I_LOAD_DOOR_MAP], i-1, False);
707 for a := 0 to i-1 do
708 if PanelArray[a].Active then
709 begin
710 PanelArray[a].Active := False;
711 m := Length(gDoorMap);
712 SetLength(gDoorMap, m+1);
713 SetLength(gDoorMap[m], 1);
714 gDoorMap[m, 0] := PanelArray[a].PanelID;
715 ok := True;
717 while ok do
718 begin
719 ok := False;
721 for b := 0 to i-1 do
722 if PanelArray[b].Active then
723 for c := 0 to High(gDoorMap[m]) do
724 if {((gRenderWalls[PanelArray[b].RenderPanelID].TextureID = gRenderWalls[gDoorMap[m, c]].TextureID) or
725 gRenderWalls[PanelArray[b].RenderPanelID].Hide or gRenderWalls[gDoorMap[m, c]].Hide) and}
726 g_CollideAround(PanelArray[b].X, PanelArray[b].Y,
727 PanelArray[b].Width, PanelArray[b].Height,
728 gWalls[gDoorMap[m, c]].X,
729 gWalls[gDoorMap[m, c]].Y,
730 gWalls[gDoorMap[m, c]].Width,
731 gWalls[gDoorMap[m, c]].Height) then
732 begin
733 PanelArray[b].Active := False;
734 SetLength(gDoorMap[m],
735 Length(gDoorMap[m])+1);
736 gDoorMap[m, High(gDoorMap[m])] := PanelArray[b].PanelID;
737 ok := True;
738 Break;
739 end;
740 end;
742 g_Game_StepLoading();
743 end;
745 PanelArray := nil;
746 end;
748 procedure CreateLiftMap();
749 var
750 PanelArray: Array of record
751 X, Y: Integer;
752 Width, Height: Word;
753 Active: Boolean;
754 end;
755 a, b, c, len, i, j: Integer;
756 ok: Boolean;
757 begin
758 if gLifts = nil then
759 Exit;
761 len := Length(gLifts);
762 SetLength(PanelArray, len);
764 for a := 0 to len-1 do
765 begin
766 PanelArray[a].X := gLifts[a].X;
767 PanelArray[a].Y := gLifts[a].Y;
768 PanelArray[a].Width := gLifts[a].Width;
769 PanelArray[a].Height := gLifts[a].Height;
770 PanelArray[a].Active := True;
771 end;
773 SetLength(gLiftMap, len);
774 i := 0;
776 g_Game_SetLoadingText(_lc[I_LOAD_LIFT_MAP], len-1, False);
778 for a := 0 to len-1 do
779 if PanelArray[a].Active then
780 begin
781 PanelArray[a].Active := False;
782 SetLength(gLiftMap[i], 32);
783 j := 0;
784 gLiftMap[i, j] := a;
785 ok := True;
787 while ok do
788 begin
789 ok := False;
790 for b := 0 to len-1 do
791 if PanelArray[b].Active then
792 for c := 0 to j do
793 if g_CollideAround(PanelArray[b].X,
794 PanelArray[b].Y,
795 PanelArray[b].Width,
796 PanelArray[b].Height,
797 PanelArray[gLiftMap[i, c]].X,
798 PanelArray[gLiftMap[i, c]].Y,
799 PanelArray[gLiftMap[i, c]].Width,
800 PanelArray[gLiftMap[i, c]].Height) then
801 begin
802 PanelArray[b].Active := False;
803 j := j+1;
804 if j > High(gLiftMap[i]) then
805 SetLength(gLiftMap[i],
806 Length(gLiftMap[i])+32);
808 gLiftMap[i, j] := b;
809 ok := True;
811 Break;
812 end;
813 end;
815 SetLength(gLiftMap[i], j+1);
816 i := i+1;
818 g_Game_StepLoading();
819 end;
821 SetLength(gLiftMap, i);
823 PanelArray := nil;
824 end;
826 function CreatePanel (PanelRec: TDynRecord; AddTextures: TAddTextureArray; CurTex: Integer): Integer;
827 var
828 len: Integer;
829 panels: ^TPanelArray;
830 pan: TPanel;
831 pguid: Integer;
832 begin
833 Result := -1;
835 case PanelRec.PanelType of
836 PANEL_WALL, PANEL_OPENDOOR, PANEL_CLOSEDOOR: panels := @gWalls;
837 PANEL_BACK: panels := @gRenderBackgrounds;
838 PANEL_FORE: panels := @gRenderForegrounds;
839 PANEL_WATER: panels := @gWater;
840 PANEL_ACID1: panels := @gAcid1;
841 PANEL_ACID2: panels := @gAcid2;
842 PANEL_STEP: panels := @gSteps;
843 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: panels := @gLifts;
844 PANEL_BLOCKMON: panels := @gBlockMon;
845 else exit;
846 end;
848 len := Length(panels^);
849 SetLength(panels^, len+1);
851 pguid := Length(panByGUID);
852 SetLength(panByGUID, pguid+1); //FIXME!
853 pan := TPanel.Create(PanelRec, AddTextures, CurTex, Textures, pguid);
854 assert(pguid >= 0);
855 assert(pguid < Length(panByGUID));
856 panByGUID[pguid] := pan;
857 panels^[len] := pan;
858 pan.arrIdx := len;
859 pan.proxyId := -1;
860 pan.tag := panelTypeToTag(PanelRec.PanelType);
862 PanelRec.user['panel_guid'] := pguid;
864 //result := len;
865 result := pguid;
866 end;
869 function CreateNullTexture(RecName: String): Integer;
870 begin
871 RecName := toLowerCase1251(RecName);
872 if (TextNameHash = nil) then TextNameHash := THashStrInt.Create();
873 if TextNameHash.get(RecName, result) then exit; // i found her!
875 SetLength(Textures, Length(Textures) + 1);
876 Textures[High(Textures)].TextureName := RecName;
877 Textures[High(Textures)].FullName := '';
878 Result := High(Textures);
879 TextNameHash.put(RecName, result);
880 end;
883 function extractWadName (resourceName: string): string;
884 var
885 posN: Integer;
886 begin
887 posN := Pos(':', resourceName);
888 if posN > 0 then
889 Result:= Copy(resourceName, 0, posN-1)
890 else
891 Result := '';
892 end;
895 procedure addResToExternalResList (res: AnsiString);
896 var
897 uname: AnsiString;
898 f: Integer;
899 fi: TDiskFileInfo;
900 begin
901 if g_Game_IsClient or not g_Game_IsNet then exit;
902 if (length(res) = 0) then exit; // map wad
903 //res := extractWadName(res);
904 //if (length(res) = 0) then exit; // map wad
905 uname := toLowerCase1251(res);
906 // do not add duplicates
907 for f := 0 to High(gExternalResources) do
908 begin
909 if (gExternalResources[f].userName = uname) then exit;
910 end;
911 //writeln('***(000) addResToExternalResList: res=[', res, ']');
912 // add new resource
913 fi.userName := uname;
914 if not findFileCI(res) then exit;
915 //writeln('***(001) addResToExternalResList: res=[', res, ']');
916 fi.diskName := res;
917 if (not GetDiskFileInfo(res, fi)) then
918 begin
919 fi.tag := -1;
920 end
921 else
922 begin
923 //writeln('***(002) addResToExternalResList: res=[', res, ']');
924 fi.tag := 0; // non-zero means "cannot caclucate hash"
925 try
926 fi.hash := MD5File(fi.diskName);
927 except
928 fi.tag := -1;
929 end;
930 end;
931 //e_LogWritefln('addext: res=[%s]; uname=[%s]; diskName=[%s]', [res, fi.userName, fi.diskName]);
932 SetLength(gExternalResources, length(gExternalResources)+1);
933 gExternalResources[High(gExternalResources)] := fi;
934 end;
937 procedure compactExtResList ();
938 var
939 src, dest: Integer;
940 begin
941 src := 0;
942 dest := 0;
943 for src := 0 to High(gExternalResources) do
944 begin
945 if (gExternalResources[src].tag = 0) then
946 begin
947 // copy it
948 if (dest <> src) then gExternalResources[dest] := gExternalResources[src];
949 Inc(dest);
950 end;
951 end;
952 if (dest <> length(gExternalResources)) then SetLength(gExternalResources, dest);
953 end;
956 function GetReplacementWad (WadName: AnsiString): AnsiString;
957 begin
958 result := '';
959 if WadName <> '' then
960 begin
961 result := WadName;
962 if g_Game_IsClient then result := g_Res_FindReplacementWad(WadName);
963 if (result = WadName) then result := e_FindWad(WadDirs, result)
964 end;
965 end;
968 procedure generateExternalResourcesList (map: TDynRecord);
969 begin
970 SetLength(gExternalResources, 0);
971 addResToExternalResList(GetReplacementWad(g_ExtractWadName(map.MusicName)));
972 addResToExternalResList(GetReplacementWad(g_ExtractWadName(map.SkyName)));
973 end;
975 function CreateTexture (RecName: AnsiString; Map: String; log: Boolean): Integer;
976 var
977 HName: AnsiString;
978 WAD, WADz: TWADFile;
979 WADName, ResName: String;
980 ResData, ReszData: Pointer;
981 ResLen, ReszLen: Integer;
982 cfg: TConfig;
983 id: Integer;
984 begin
985 Result := -1;
986 HName := toLowerCase1251(RecName);
987 if (TextNameHash = nil) then
988 TextNameHash := THashStrInt.Create();
989 if TextNameHash.get(HName, Result) then
990 begin
991 // e_LogWritefln('CreateTexture: found loaded %s', [Result]);
992 end
993 else
994 begin
995 Result := -1;
996 if (BadTextNameHash = nil) or not BadTextNameHash.has(HName) then
997 begin
998 case RecName of
999 TEXTURE_NAME_WATER, TEXTURE_NAME_ACID1, TEXTURE_NAME_ACID2:
1000 begin
1001 SetLength(Textures, Length(Textures) + 1);
1002 Textures[High(Textures)].FullName := RecName;
1003 Textures[High(Textures)].TextureName := RecName;
1004 Result := High(Textures);
1005 TextNameHash.put(RecName, result);
1006 end
1007 else
1008 WADName := GetReplacementWad(g_ExtractWadName(RecName));
1009 if (WADName <> '') then
1010 addResToExternalResList(WADName);
1011 if WADName = '' then
1012 WADName := Map;
1013 ResName := g_ExtractFilePathName(RecName);
1014 WAD := TWADFile.Create();
1015 if WAD.ReadFile(WadName) then
1016 begin
1017 if WAD.GetResource(ResName, ResData, ResLen, log) then
1018 begin
1019 if IsWadData(ResData, ResLen) then
1020 begin
1021 WADz := TWADFile.Create();
1022 if WADz.ReadMemory(ResData, ResLen) then
1023 begin
1024 if WADz.GetResource('TEXT/ANIM', ReszData, ReszLen) then
1025 begin
1026 cfg := TConfig.CreateMem(ReszData, ReszLen);
1027 if cfg <> nil then
1028 begin
1029 SetLength(Textures, Length(Textures) + 1);
1030 Textures[High(Textures)].TextureName := RecName;
1031 Textures[High(Textures)].FullName := WadName + ':' + ResName;
1032 Textures[High(Textures)].FramesCount := cfg.ReadInt('', 'framecount', 0);
1033 Textures[High(Textures)].Speed := cfg.ReadInt('', 'waitcount', 0);
1034 Result := High(Textures);
1035 TextNameHash.put(HName, result);
1036 cfg.Free;
1037 end;
1038 FreeMem(ReszData);
1039 end
1040 end;
1041 WADz.Free;
1042 end
1043 else
1044 begin
1045 SetLength(Textures, Length(Textures) + 1);
1046 Textures[High(Textures)].FullName := WADName + ':' + ResName;
1047 Textures[High(Textures)].TextureName := RecName;
1048 Result := High(Textures);
1049 TextNameHash.put(HName, result);
1050 end;
1051 FreeMem(ResData);
1052 end
1053 end;
1054 WAD.Free;
1055 end
1056 end;
1057 end;
1058 if Result < 0 then
1059 begin
1060 if (BadTextNameHash = nil) then
1061 BadTextNameHash := THashStrInt.Create();
1062 if log and (not BadTextNameHash.get(HName, id)) then
1063 e_WriteLog(Format('Error loading texture %s', [RecName]), TMsgType.Warning);
1064 BadTextNameHash.put(HName, -1);
1065 end
1066 end;
1068 procedure CreateItem(Item: TDynRecord);
1069 begin
1070 if g_Game_IsClient then Exit;
1072 if (not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) and
1073 ByteBool(Item.Options and ITEM_OPTION_ONLYDM) then
1074 Exit;
1076 g_Items_Create(Item.X, Item.Y, Item.ItemType, ByteBool(Item.Options and ITEM_OPTION_FALL),
1077 gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF, GM_COOP]);
1078 end;
1080 procedure CreateArea(Area: TDynRecord);
1081 var
1082 a: Integer;
1083 begin
1084 case Area.AreaType of
1085 AREA_DMPOINT, AREA_PLAYERPOINT1, AREA_PLAYERPOINT2,
1086 AREA_REDTEAMPOINT, AREA_BLUETEAMPOINT:
1087 begin
1088 SetLength(RespawnPoints, Length(RespawnPoints)+1);
1089 with RespawnPoints[High(RespawnPoints)] do
1090 begin
1091 X := Area.X;
1092 Y := Area.Y;
1093 Direction := TDirection(Area.Direction);
1095 case Area.AreaType of
1096 AREA_DMPOINT: PointType := RESPAWNPOINT_DM;
1097 AREA_PLAYERPOINT1: PointType := RESPAWNPOINT_PLAYER1;
1098 AREA_PLAYERPOINT2: PointType := RESPAWNPOINT_PLAYER2;
1099 AREA_REDTEAMPOINT: PointType := RESPAWNPOINT_RED;
1100 AREA_BLUETEAMPOINT: PointType := RESPAWNPOINT_BLUE;
1101 end;
1102 end;
1103 end;
1105 AREA_REDFLAG, AREA_BLUEFLAG:
1106 begin
1107 if Area.AreaType = AREA_REDFLAG then a := FLAG_RED else a := FLAG_BLUE;
1109 if FlagPoints[a] <> nil then Exit;
1111 New(FlagPoints[a]);
1113 with FlagPoints[a]^ do
1114 begin
1115 X := Area.X-FLAGRECT.X;
1116 Y := Area.Y-FLAGRECT.Y;
1117 Direction := TDirection(Area.Direction);
1118 end;
1120 with gFlags[a] do
1121 begin
1122 Obj.Rect := FLAGRECT;
1123 g_Map_ResetFlag(a);
1124 end;
1125 end;
1127 AREA_DOMFLAG:
1128 begin
1129 {SetLength(DOMFlagPoints, Length(DOMFlagPoints)+1);
1130 with DOMFlagPoints[High(DOMFlagPoints)] do
1131 begin
1132 X := Area.X;
1133 Y := Area.Y;
1134 Direction := TDirection(Area.Direction);
1135 end;
1137 g_Map_CreateFlag(DOMFlagPoints[High(DOMFlagPoints)], FLAG_DOM, FLAG_STATE_NORMAL);}
1138 end;
1139 end;
1140 end;
1142 function CreateTrigger (amapIdx: Integer; Trigger: TDynRecord; atpanid, atrigpanid: Integer): Integer;
1143 var
1144 _trigger: TTrigger;
1145 tp: TPanel;
1146 begin
1147 result := -1;
1148 if g_Game_IsClient and not (Trigger.TriggerType in [TRIGGER_SOUND, TRIGGER_MUSIC]) then Exit;
1150 with _trigger do
1151 begin
1152 mapId := Trigger.id;
1153 mapIndex := amapIdx;
1154 X := Trigger.X;
1155 Y := Trigger.Y;
1156 Width := Trigger.Width;
1157 Height := Trigger.Height;
1158 Enabled := Trigger.Enabled;
1159 TexturePanelGUID := atpanid;
1160 TriggerType := Trigger.TriggerType;
1161 ActivateType := Trigger.ActivateType;
1162 Keys := Trigger.Keys;
1163 trigPanelGUID := atrigpanid;
1164 // HACK: used in TPanel.CanChangeTexture. maybe there's a better way?
1165 if TexturePanelGUID <> -1 then
1166 begin
1167 tp := g_Map_PanelByGUID(TexturePanelGUID);
1168 if (tp <> nil) then tp.hasTexTrigger := True;
1169 end;
1170 end;
1172 result := Integer(g_Triggers_Create(_trigger, Trigger));
1173 end;
1175 procedure CreateMonster(monster: TDynRecord);
1176 var
1177 a: Integer;
1178 mon: TMonster;
1179 begin
1180 if g_Game_IsClient then Exit;
1182 if (gGameSettings.GameType = GT_SINGLE)
1183 or LongBool(gGameSettings.Options and GAME_OPTION_MONSTERS) then
1184 begin
1185 mon := g_Monsters_Create(monster.MonsterType, monster.X, monster.Y, TDirection(monster.Direction));
1187 if gTriggers <> nil then
1188 begin
1189 for a := 0 to High(gTriggers) do
1190 begin
1191 if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
1192 begin
1193 //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
1194 if (gTriggers[a].trigDataRec.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
1195 end;
1196 end;
1197 end;
1199 if monster.MonsterType <> MONSTER_BARREL then Inc(gTotalMonsters);
1200 end;
1201 end;
1203 procedure g_Map_ReAdd_DieTriggers();
1205 function monsDieTrig (mon: TMonster): Boolean;
1206 var
1207 a: Integer;
1208 //tw: TStrTextWriter;
1209 begin
1210 result := false; // don't stop
1211 mon.ClearTriggers();
1212 for a := 0 to High(gTriggers) do
1213 begin
1214 if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
1215 begin
1216 //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
1218 tw := TStrTextWriter.Create();
1219 try
1220 gTriggers[a].trigData.writeTo(tw);
1221 e_LogWritefln('=== trigger #%s ==='#10'%s'#10'---', [a, tw.str]);
1222 finally
1223 tw.Free();
1224 end;
1226 if (gTriggers[a].trigDataRec.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
1227 end;
1228 end;
1229 end;
1231 begin
1232 if g_Game_IsClient then Exit;
1234 g_Mons_ForEach(monsDieTrig);
1235 end;
1237 procedure mapCreateGrid ();
1238 var
1239 mapX0: Integer = $3fffffff;
1240 mapY0: Integer = $3fffffff;
1241 mapX1: Integer = -$3fffffff;
1242 mapY1: Integer = -$3fffffff;
1244 procedure calcBoundingBox (constref panels: TPanelArray);
1245 var
1246 idx: Integer;
1247 pan: TPanel;
1248 begin
1249 for idx := 0 to High(panels) do
1250 begin
1251 pan := panels[idx];
1252 if not pan.visvalid then continue;
1253 if (pan.Width < 1) or (pan.Height < 1) then continue;
1254 if (mapX0 > pan.x0) then mapX0 := pan.x0;
1255 if (mapY0 > pan.y0) then mapY0 := pan.y0;
1256 if (mapX1 < pan.x1) then mapX1 := pan.x1;
1257 if (mapY1 < pan.y1) then mapY1 := pan.y1;
1258 end;
1259 end;
1261 procedure addPanelsToGrid (constref panels: TPanelArray);
1262 var
1263 idx: Integer;
1264 pan: TPanel;
1265 newtag: Integer;
1266 begin
1267 //tag := panelTypeToTag(tag);
1268 for idx := 0 to High(panels) do
1269 begin
1270 pan := panels[idx];
1271 if not pan.visvalid then continue;
1272 if (pan.proxyId <> -1) then
1273 begin
1274 {$IF DEFINED(D2F_DEBUG)}
1275 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);
1276 {$ENDIF}
1277 continue;
1278 end;
1279 case pan.PanelType of
1280 PANEL_WALL: newtag := GridTagWall;
1281 PANEL_OPENDOOR, PANEL_CLOSEDOOR: newtag := GridTagDoor;
1282 PANEL_BACK: newtag := GridTagBack;
1283 PANEL_FORE: newtag := GridTagFore;
1284 PANEL_WATER: newtag := GridTagWater;
1285 PANEL_ACID1: newtag := GridTagAcid1;
1286 PANEL_ACID2: newtag := GridTagAcid2;
1287 PANEL_STEP: newtag := GridTagStep;
1288 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: newtag := GridTagLift;
1289 PANEL_BLOCKMON: newtag := GridTagBlockMon;
1290 else continue; // oops
1291 end;
1292 pan.tag := newtag;
1294 pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, newtag);
1295 // "enabled" flag has meaning only for doors and walls (engine assumes it); but meh...
1296 mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
1297 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
1299 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
1300 begin
1301 e_WriteLog(Format('INSERTED wall #%d(%d) enabled (%d)', [Integer(idx), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId])]), MSG_NOTIFY);
1302 end;
1304 {$ENDIF}
1305 end;
1306 end;
1308 begin
1309 mapGrid.Free();
1310 mapGrid := nil;
1312 calcBoundingBox(gWalls);
1313 calcBoundingBox(gRenderBackgrounds);
1314 calcBoundingBox(gRenderForegrounds);
1315 calcBoundingBox(gWater);
1316 calcBoundingBox(gAcid1);
1317 calcBoundingBox(gAcid2);
1318 calcBoundingBox(gSteps);
1319 calcBoundingBox(gLifts);
1320 calcBoundingBox(gBlockMon);
1322 e_LogWritefln('map dimensions: (%d,%d)-(%d,%d); editor size:(0,0)-(%d,%d)', [mapX0, mapY0, mapX1, mapY1, gMapInfo.Width, gMapInfo.Height]);
1324 if (mapX0 > 0) then mapX0 := 0;
1325 if (mapY0 > 0) then mapY0 := 0;
1327 if (mapX1 < gMapInfo.Width-1) then mapX1 := gMapInfo.Width-1;
1328 if (mapY1 < gMapInfo.Height-1) then mapY1 := gMapInfo.Height-1;
1330 mapGrid := TPanelGrid.Create(mapX0-128, mapY0-128, mapX1-mapX0+1+128*2, mapY1-mapY0+1+128*2);
1331 //mapGrid := TPanelGrid.Create(0, 0, gMapInfo.Width, gMapInfo.Height);
1333 addPanelsToGrid(gWalls);
1334 addPanelsToGrid(gRenderBackgrounds);
1335 addPanelsToGrid(gRenderForegrounds);
1336 addPanelsToGrid(gWater);
1337 addPanelsToGrid(gAcid1);
1338 addPanelsToGrid(gAcid2);
1339 addPanelsToGrid(gSteps);
1340 addPanelsToGrid(gLifts); // it doesn't matter which LIFT type is used here
1341 addPanelsToGrid(gBlockMon);
1343 mapGrid.dumpStats();
1345 g_Mons_InitTree(mapGrid.gridX0, mapGrid.gridY0, mapGrid.gridWidth, mapGrid.gridHeight);
1346 end;
1349 function g_Map_Load(Res: String): Boolean;
1350 const
1351 DefaultMusRes = 'Standart.wad:STDMUS\MUS1';
1352 DefaultSkyRes = 'Standart.wad:STDSKY\SKY0';
1353 type
1354 PTRec = ^TTRec;
1355 TTRec = record
1356 //TexturePanel: Integer;
1357 tnum: Integer;
1358 id: Integer;
1359 trigrec: TDynRecord;
1360 // texture pane;
1361 texPanelIdx: Integer;
1362 texPanel: TDynRecord;
1363 // "action" panel
1364 actPanelIdx: Integer;
1365 actPanel: TDynRecord;
1366 end;
1367 var
1368 WAD, TestWAD: TWADFile;
1369 //mapReader: TDynRecord = nil;
1370 mapTextureList: TDynField = nil; //TTexturesRec1Array; tagInt: texture index
1371 panels: TDynField = nil; //TPanelsRec1Array;
1372 items: TDynField = nil; //TItemsRec1Array;
1373 monsters: TDynField = nil; //TMonsterRec1Array;
1374 areas: TDynField = nil; //TAreasRec1Array;
1375 triggers: TDynField = nil; //TTriggersRec1Array;
1376 b, c, k: Integer;
1377 PanelID: DWORD;
1378 AddTextures: TAddTextureArray;
1379 TriggersTable: array of TTRec;
1380 FileName, mapResName, TexName, s: AnsiString;
1381 Data: Pointer;
1382 Len: Integer;
1383 ok: Boolean;
1384 CurTex, ntn: Integer;
1385 rec, texrec: TDynRecord;
1386 pttit: PTRec;
1387 pannum, trignum, cnt, tgpid: Integer;
1388 stt: UInt64;
1389 moveSpeed{, moveStart, moveEnd}: TDFPoint;
1390 //moveActive: Boolean;
1391 pan: TPanel;
1392 mapOk: Boolean = false;
1393 usedTextures: THashStrInt = nil; // key: mapTextureList
1394 begin
1395 mapGrid.Free();
1396 mapGrid := nil;
1397 TestWAD := nil;
1398 Data := nil;
1400 //gCurrentMap.Free();
1401 //gCurrentMap := nil;
1403 panByGUID := nil;
1405 Result := False;
1406 gMapInfo.Map := Res;
1407 TriggersTable := nil;
1408 //mapReader := nil;
1410 sfsGCDisable(); // temporary disable removing of temporary volumes
1411 try
1412 // Çàãðóçêà WAD (åñëè ó íàñ íåò óæå çàãðóæåíîé êàðòû)
1413 if (gCurrentMap = nil) then
1414 begin
1415 FileName := g_ExtractWadName(Res);
1416 e_LogWritefln('Loading map WAD [%s] (res=[%s])', [FileName, Res], TMsgType.Notify);
1417 g_Game_SetLoadingText(_lc[I_LOAD_WAD_FILE], 0, False);
1419 WAD := TWADFile.Create();
1420 if not WAD.ReadFile(FileName) then
1421 begin
1422 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_WAD], [FileName]));
1423 WAD.Free();
1424 Exit;
1425 end;
1427 if gTestMap <> '' then
1428 begin
1429 s := g_ExtractWadName(gTestMap);
1430 TestWAD := TWADFile.Create();
1431 if not TestWAD.ReadFile(s) then
1432 begin
1433 g_SimpleError(Format(_lc[I_GAME_ERROR_MAP_WAD], [s]));
1434 TestWAD.Free();
1435 TestWAD := nil;
1436 end;
1437 end;
1439 if TestWAD <> nil then
1440 begin
1441 mapResName := g_ExtractFileName(gTestMap);
1442 if not TestWAD.GetMapResource(mapResName, Data, Len) then
1443 begin
1444 g_SimpleError(Format(_lc[I_GAME_ERROR_MAP_RES], [mapResName]));
1445 Data := nil;
1446 end else
1447 e_WriteLog('Using test map: '+gTestMap, TMsgType.Notify);
1448 TestWAD.Free();
1449 TestWAD := nil;
1450 end;
1452 if Data = nil then
1453 begin
1454 //k8: why loader ignores path here?
1455 mapResName := g_ExtractFileName(Res);
1456 if not WAD.GetMapResource(mapResName, Data, Len) then
1457 begin
1458 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_RES], [mapResName]));
1459 WAD.Free();
1460 Exit;
1461 end;
1462 end;
1464 WAD.Free();
1466 if (Len < 4) then
1467 begin
1468 e_LogWritefln('invalid map file: ''%s''', [mapResName]);
1469 FreeMem(Data);
1470 exit;
1471 end;
1473 // Çàãðóçêà êàðòû:
1474 e_LogWritefln('Loading map: %s', [mapResName], TMsgType.Notify);
1475 g_Game_SetLoadingText(_lc[I_LOAD_MAP], 0, False);
1477 stt := getTimeMicro();
1479 try
1480 gCurrentMap := g_Map_ParseMap(Data, Len);
1481 except
1482 gCurrentMap.Free();
1483 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]));
1484 FreeMem(Data);
1485 gCurrentMapFileName := '';
1486 Exit;
1487 end;
1489 FreeMem(Data);
1491 if (gCurrentMap = nil) then
1492 begin
1493 e_LogWritefln('invalid map file: ''%s''', [mapResName]);
1494 gCurrentMapFileName := '';
1495 exit;
1496 end;
1497 end
1498 else
1499 begin
1500 stt := getTimeMicro();
1501 end;
1503 //gCurrentMap := mapReader;
1505 generateExternalResourcesList(gCurrentMap);
1506 mapTextureList := gCurrentMap['texture'];
1507 // get all other lists here too
1508 panels := gCurrentMap['panel'];
1509 triggers := gCurrentMap['trigger'];
1510 items := gCurrentMap['item'];
1511 areas := gCurrentMap['area'];
1512 monsters := gCurrentMap['monster'];
1514 // Çàãðóçêà îïèñàíèÿ êàðòû:
1515 e_WriteLog(' Reading map info...', TMsgType.Notify);
1516 g_Game_SetLoadingText(_lc[I_LOAD_MAP_HEADER], 0, False);
1518 with gMapInfo do
1519 begin
1520 Name := gCurrentMap.MapName;
1521 Description := gCurrentMap.MapDesc;
1522 Author := gCurrentMap.MapAuthor;
1523 MusicName := gCurrentMap.MusicName;
1524 SkyName := gCurrentMap.SkyName;
1525 Height := gCurrentMap.Height;
1526 Width := gCurrentMap.Width;
1527 end;
1529 // Çàãðóçêà òåêñòóð:
1530 g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], 0, False);
1531 // Äîáàâëåíèå òåêñòóð â Textures[]:
1532 if (mapTextureList <> nil) and (mapTextureList.count > 0) then
1533 begin
1534 e_WriteLog(' Loading textures:', TMsgType.Notify);
1535 g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], mapTextureList.count-1, False);
1537 // find used textures
1538 usedTextures := THashStrInt.Create();
1539 try
1540 if (panels <> nil) and (panels.count > 0) then
1541 begin
1542 for rec in panels do
1543 begin
1544 texrec := rec.TextureRec;
1545 if (texrec <> nil) then usedTextures.put(toLowerCase1251(texrec.Resource), 42);
1546 end;
1547 end;
1549 cnt := -1;
1550 for rec in mapTextureList do
1551 begin
1552 Inc(cnt);
1553 if not usedTextures.has(toLowerCase1251(rec.Resource)) then
1554 begin
1555 rec.tagInt := -1; // just in case
1556 e_LogWritefln(' Unused texture #%d: %s', [cnt, rec.Resource]);
1557 end
1558 else
1559 begin
1560 {$IF DEFINED(D2F_DEBUG_TXLOAD)}
1561 e_LogWritefln(' Loading texture #%d: %s', [cnt, rec.Resource]);
1562 {$ENDIF}
1563 ntn := CreateTexture(rec.Resource, FileName, True);
1564 if ntn < 0 then
1565 begin
1566 if rec.Anim then
1567 g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_ANIM], [rec.Resource]))
1568 else
1569 g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_SIMPLE], [rec.Resource]));
1570 ntn := CreateNullTexture(rec.Resource)
1571 end;
1572 rec.tagInt := ntn; // remember texture number
1573 end;
1574 g_Game_StepLoading();
1575 end;
1576 finally
1577 usedTextures.Free();
1578 end;
1580 // set panel tagInt to texture index
1581 if (panels <> nil) then
1582 begin
1583 for rec in panels do
1584 begin
1585 texrec := rec.TextureRec;
1586 if (texrec = nil) then rec.tagInt := -1 else rec.tagInt := texrec.tagInt;
1587 end;
1588 end;
1589 end;
1592 // Çàãðóçêà òðèããåðîâ
1593 gTriggerClientID := 0;
1594 e_WriteLog(' Loading triggers...', TMsgType.Notify);
1595 g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS], 0, False);
1597 // Çàãðóçêà ïàíåëåé
1598 e_WriteLog(' Loading panels...', TMsgType.Notify);
1599 g_Game_SetLoadingText(_lc[I_LOAD_PANELS], 0, False);
1601 // check texture numbers for panels
1602 if (panels <> nil) and (panels.count > 0) then
1603 begin
1604 for rec in panels do
1605 begin
1606 if (rec.tagInt < 0) then
1607 begin
1608 e_WriteLog('error loading map: invalid texture index for panel', TMsgType.Fatal);
1609 result := false;
1610 gCurrentMap.Free();
1611 gCurrentMap := nil;
1612 gCurrentMapFileName := '';
1613 exit;
1614 end;
1615 end;
1616 end;
1618 // Ñîçäàíèå òàáëèöû òðèããåðîâ (ñîîòâåòñòâèå ïàíåëåé òðèããåðàì)
1619 if (triggers <> nil) and (triggers.count > 0) then
1620 begin
1621 e_WriteLog(' Setting up trigger table...', TMsgType.Notify);
1622 //SetLength(TriggersTable, triggers.count);
1623 g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS_TABLE], triggers.count-1, False);
1625 SetLength(TriggersTable, triggers.count);
1626 trignum := -1;
1627 for rec in triggers do
1628 begin
1629 Inc(trignum);
1630 pttit := @TriggersTable[trignum];
1631 pttit.trigrec := rec;
1632 // Ñìåíà òåêñòóðû (âîçìîæíî, êíîïêè)
1633 pttit.texPanelIdx := -1; // will be fixed later
1634 pttit.texPanel := rec.TexturePanelRec;
1635 // action panel
1636 pttit.actPanelIdx := -1;
1637 if (rec.trigRec <> nil) then pttit.actPanel := rec.trigRec.tgPanelRec else pttit.actPanel := nil;
1638 // set flag
1639 if (pttit.texPanel <> nil) then pttit.texPanel.userPanelTrigRef := true;
1640 if (pttit.actPanel <> nil) then pttit.actPanel.userPanelTrigRef := true;
1641 // update progress
1642 g_Game_StepLoading();
1643 end;
1644 end;
1646 // Ñîçäàåì ïàíåëè
1647 if (panels <> nil) and (panels.count > 0) then
1648 begin
1649 e_WriteLog(' Setting up trigger links...', TMsgType.Notify);
1650 g_Game_SetLoadingText(_lc[I_LOAD_LINK_TRIGGERS], panels.count-1, False);
1652 pannum := -1;
1653 for rec in panels do
1654 begin
1655 Inc(pannum);
1656 //e_LogWritefln('PANSTART: pannum=%s', [pannum]);
1657 texrec := nil;
1658 SetLength(AddTextures, 0);
1659 CurTex := -1;
1660 ok := false;
1662 if (mapTextureList <> nil) then
1663 begin
1664 texrec := rec.TextureRec;
1665 ok := (texrec <> nil);
1666 end;
1668 if ok then
1669 begin
1670 // Ñìîòðèì, ññûëàþòñÿ ëè íà ýòó ïàíåëü òðèããåðû.
1671 // Åñëè äà - òî íàäî ñîçäàòü åùå òåêñòóð
1672 ok := false;
1673 if (TriggersTable <> nil) and (mapTextureList <> nil) then
1674 begin
1675 if rec.userPanelTrigRef then
1676 begin
1677 // e_LogWritefln('trigref for panel %s', [pannum]);
1678 ok := True;
1679 end;
1680 end;
1681 end;
1683 if ok then
1684 begin
1685 // Åñòü ññûëêè òðèããåðîâ íà ýòó ïàíåëü
1686 s := texrec.Resource;
1688 // Ñïåö-òåêñòóðû çàïðåùåíû
1689 if g_Map_IsSpecialTexture(s) then
1690 begin
1691 ok := false
1692 end
1693 else
1694 begin
1695 // Îïðåäåëÿåì íàëè÷èå è ïîëîæåíèå öèôð â êîíöå ñòðîêè
1696 ok := g_Texture_NumNameFindStart(s);
1697 end;
1699 // Åñëè ok, çíà÷èò åñòü öèôðû â êîíöå.
1700 // Çàãðóæàåì òåêñòóðû ñ îñòàëüíûìè #
1701 if ok then
1702 begin
1703 k := NNF_NAME_BEFORE;
1704 // Öèêë ïî èçìåíåíèþ èìåíè òåêñòóðû
1705 while ok or (k = NNF_NAME_BEFORE) or (k = NNF_NAME_EQUALS) do
1706 begin
1707 k := g_Texture_NumNameFindNext(TexName);
1709 if (k = NNF_NAME_BEFORE) or (k = NNF_NAME_AFTER) then
1710 begin
1711 ok := CreateTexture(TexName, FileName, False) >= 0;
1713 // Îíà ñóùåñòâóåò. Çàíîñèì åå ID â ñïèñîê ïàíåëè
1714 if ok then
1715 begin
1717 for c := 0 to High(Textures) do
1718 begin
1719 if (Textures[c].TextureName = TexName) then
1720 begin
1721 SetLength(AddTextures, Length(AddTextures)+1);
1722 AddTextures[High(AddTextures)].Texture := c;
1723 break;
1724 end;
1725 end;
1727 if (TextNameHash <> nil) and TextNameHash.get(toLowerCase1251(TexName), c) then
1728 begin
1729 SetLength(AddTextures, Length(AddTextures)+1);
1730 AddTextures[High(AddTextures)].Texture := c;
1731 end;
1732 end;
1733 end
1734 else
1735 begin
1736 if k = NNF_NAME_EQUALS then
1737 begin
1738 // Çàíîñèì òåêóùóþ òåêñòóðó íà ñâîå ìåñòî
1739 SetLength(AddTextures, Length(AddTextures)+1);
1740 AddTextures[High(AddTextures)].Texture := rec.tagInt; // internal texture number, not map index
1741 CurTex := High(AddTextures);
1742 ok := true;
1743 end
1744 else // NNF_NO_NAME
1745 begin
1746 ok := false;
1747 end;
1748 end;
1749 end; // while ok...
1751 ok := true;
1752 end; // if ok - åñòü ñìåæíûå òåêñòóðû
1753 end; // if ok - ññûëàþòñÿ òðèããåðû
1755 if not ok then
1756 begin
1757 // Çàíîñèì òîëüêî òåêóùóþ òåêñòóðó
1758 SetLength(AddTextures, 1);
1759 AddTextures[0].Texture := rec.tagInt; // internal texture number, not map index
1760 CurTex := 0;
1761 end;
1763 //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);
1765 //e_LogWritefln('PANADD: pannum=%s', [pannum]);
1767 // Ñîçäàåì ïàíåëü è çàïîìèíàåì åå GUID
1768 //e_LogWritefln('new panel; tcount=%s; curtex=%s', [Length(AddTextures), CurTex]);
1769 PanelID := CreatePanel(rec, AddTextures, CurTex);
1770 //e_LogWritefln('panel #%s of type %s got guid #%s', [pannum, rec.PanelType, PanelID]);
1771 rec.userPanelId := PanelID; // remember game panel id, we'll fix triggers later
1773 // setup lifts
1774 moveSpeed := rec.moveSpeed;
1775 //moveStart := rec.moveStart;
1776 //moveEnd := rec.moveEnd;
1777 //moveActive := rec['move_active'].value;
1778 if not moveSpeed.isZero then
1779 begin
1780 SetLength(gMovingWallIds, Length(gMovingWallIds)+1);
1781 gMovingWallIds[High(gMovingWallIds)] := PanelID;
1782 //e_LogWritefln('found moving panel ''%s'' (idx=%s; id=%s)', [rec.id, pannum, PanelID]);
1783 end;
1785 //e_LogWritefln('PANEND: pannum=%s', [pannum]);
1787 g_Game_StepLoading();
1788 end;
1789 end;
1791 // ×èíèì ID'û ïàíåëåé, êîòîðûå èñïîëüçóþòñÿ â òðèããåðàõ
1792 for b := 0 to High(TriggersTable) do
1793 begin
1794 if (TriggersTable[b].texPanel <> nil) then TriggersTable[b].texPanelIdx := TriggersTable[b].texPanel.userPanelId;
1795 if (TriggersTable[b].actPanel <> nil) then TriggersTable[b].actPanelIdx := TriggersTable[b].actPanel.userPanelId;
1796 end;
1798 // create map grid, init other grids (for monsters, for example)
1799 e_WriteLog('Creating map grid', TMsgType.Notify);
1800 mapCreateGrid();
1802 // Åñëè íå LoadState, òî ñîçäàåì òðèããåðû
1803 if (triggers <> nil) and (panels <> nil) and (not gLoadGameMode) then
1804 begin
1805 e_LogWritefln(' Creating triggers (%d)...', [triggers.count]);
1806 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_TRIGGERS], 0, False);
1807 // Óêàçûâàåì òèï ïàíåëè, åñëè åñòü
1808 trignum := -1;
1809 for rec in triggers do
1810 begin
1811 Inc(trignum);
1812 tgpid := TriggersTable[trignum].actPanelIdx;
1813 //e_LogWritefln('creating trigger #%s; texpantype=%s; shotpantype=%s (%d,%d)', [trignum, b, c, TriggersTable[trignum].texPanIdx, TriggersTable[trignum].ShotPanelIdx]);
1814 TriggersTable[trignum].tnum := trignum;
1815 TriggersTable[trignum].id := CreateTrigger(trignum, rec, TriggersTable[trignum].texPanelIdx, tgpid);
1816 end;
1817 end;
1819 //FIXME: use hashtable!
1820 for pan in panByGUID do
1821 begin
1822 if (pan.endPosTrigId >= 0) and (pan.endPosTrigId < Length(TriggersTable)) then
1823 begin
1824 pan.endPosTrigId := TriggersTable[pan.endPosTrigId].id;
1825 end;
1826 if (pan.endSizeTrigId >= 0) and (pan.endSizeTrigId < Length(TriggersTable)) then
1827 begin
1828 pan.endSizeTrigId := TriggersTable[pan.endSizeTrigId].id;
1829 end;
1830 end;
1832 // Çàãðóçêà ïðåäìåòîâ
1833 e_WriteLog(' Loading items...', TMsgType.Notify);
1834 g_Game_SetLoadingText(_lc[I_LOAD_ITEMS], 0, False);
1836 // Åñëè íå LoadState, òî ñîçäàåì ïðåäìåòû
1837 if (items <> nil) and not gLoadGameMode then
1838 begin
1839 e_WriteLog(' Spawning items...', TMsgType.Notify);
1840 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_ITEMS], 0, False);
1841 for rec in items do CreateItem(rec);
1842 end;
1844 // Çàãðóçêà îáëàñòåé
1845 e_WriteLog(' Loading areas...', TMsgType.Notify);
1846 g_Game_SetLoadingText(_lc[I_LOAD_AREAS], 0, False);
1848 // Åñëè íå LoadState, òî ñîçäàåì îáëàñòè
1849 if areas <> nil then
1850 begin
1851 e_WriteLog(' Creating areas...', TMsgType.Notify);
1852 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_AREAS], 0, False);
1853 for rec in areas do CreateArea(rec);
1854 end;
1856 // Çàãðóçêà ìîíñòðîâ
1857 e_WriteLog(' Loading monsters...', TMsgType.Notify);
1858 g_Game_SetLoadingText(_lc[I_LOAD_MONSTERS], 0, False);
1860 gTotalMonsters := 0;
1862 // Åñëè íå LoadState, òî ñîçäàåì ìîíñòðîâ
1863 if (monsters <> nil) and not gLoadGameMode then
1864 begin
1865 e_WriteLog(' Spawning monsters...', TMsgType.Notify);
1866 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_MONSTERS], 0, False);
1867 for rec in monsters do CreateMonster(rec);
1868 end;
1870 //gCurrentMap := mapReader; // this will be our current map now
1871 gCurrentMapFileName := Res;
1872 //mapReader := nil;
1874 // Çàãðóçêà íåáà
1875 gMapInfo.SkyFullName := '';
1876 if (gMapInfo.SkyName <> '') then
1877 begin
1878 e_WriteLog(' Loading sky: ' + gMapInfo.SkyName, TMsgType.Notify);
1879 g_Game_SetLoadingText(_lc[I_LOAD_SKY], 0, False);
1880 gMapInfo.SkyFullName := e_GetResourcePath(WadDirs, gMapInfo.SkyName, g_ExtractWadName(Res));
1881 end;
1883 // Çàãðóçêà ìóçûêè
1884 ok := False;
1885 if gMapInfo.MusicName <> '' then
1886 begin
1887 e_WriteLog(' Loading music: ' + gMapInfo.MusicName, TMsgType.Notify);
1888 g_Game_SetLoadingText(_lc[I_LOAD_MUSIC], 0, False);
1890 s := e_GetResourcePath(WadDirs, gMapInfo.MusicName, g_ExtractWadName(Res));
1891 if g_Sound_CreateWADEx(gMapInfo.MusicName, s, True) then
1892 ok := True
1893 else
1894 g_FatalError(Format(_lc[I_GAME_ERROR_MUSIC], [s]));
1895 end;
1897 // Îñòàëüíûå óñòàíâêè
1898 CreateDoorMap();
1899 CreateLiftMap();
1901 g_Items_Init();
1902 g_Weapon_Init();
1903 g_Monsters_Init();
1905 {$IFDEF ENABLE_GFX}
1906 // Åñëè íå LoadState, òî ñîçäàåì êàðòó ñòîëêíîâåíèé:
1907 if not gLoadGameMode then g_GFX_Init();
1908 {$ENDIF}
1910 // Ñáðîñ ëîêàëüíûõ ìàññèâîâ:
1911 mapTextureList := nil;
1912 panels := nil;
1913 items := nil;
1914 areas := nil;
1915 triggers := nil;
1916 TriggersTable := nil;
1917 AddTextures := nil;
1919 // Âêëþ÷àåì ìóçûêó, åñëè ýòî íå çàãðóçêà:
1920 if ok and (not gLoadGameMode) then
1921 begin
1922 gMusic.SetByName(gMapInfo.MusicName);
1923 gMusic.Play();
1924 end
1925 else
1926 begin
1927 gMusic.SetByName('');
1928 end;
1930 stt := getTimeMicro()-stt;
1931 e_LogWritefln('map loaded in %s.%s milliseconds', [Integer(stt div 1000), Integer(stt mod 1000)]);
1932 mapOk := true;
1933 finally
1934 sfsGCEnable(); // enable releasing unused volumes
1935 //mapReader.Free();
1936 e_UnpressAllKeys; // why not?
1937 if not mapOk then
1938 begin
1939 gCurrentMap.Free();
1940 gCurrentMap := nil;
1941 gCurrentMapFileName := '';
1942 end;
1943 end;
1945 compactExtResList();
1946 e_WriteLog('Done loading map.', TMsgType.Notify);
1947 Result := True;
1948 end;
1951 function g_Map_GetMapInfo(Res: String): TMapInfo;
1952 var
1953 WAD: TWADFile;
1954 mapReader: TDynRecord;
1955 //Header: TMapHeaderRec_1;
1956 FileName: String;
1957 Data: Pointer;
1958 Len: Integer;
1959 begin
1960 FillChar(Result, SizeOf(Result), 0);
1961 FileName := g_ExtractWadName(Res);
1963 WAD := TWADFile.Create();
1964 if not WAD.ReadFile(FileName) then
1965 begin
1966 WAD.Free();
1967 Exit;
1968 end;
1970 //k8: it ignores path again
1971 if not WAD.GetMapResource(g_ExtractFileName(Res), Data, Len) then
1972 begin
1973 WAD.Free();
1974 Exit;
1975 end;
1977 WAD.Free();
1979 try
1980 mapReader := g_Map_ParseMap(Data, Len);
1981 except
1982 mapReader := nil;
1983 FreeMem(Data);
1984 exit;
1985 end;
1987 FreeMem(Data);
1989 if (mapReader = nil) then exit;
1991 if (mapReader.Width > 0) and (mapReader.Height > 0) then
1992 begin
1993 Result.Name := mapReader.MapName;
1994 Result.Description := mapReader.MapDesc;
1995 Result.Map := Res;
1996 Result.Author := mapReader.MapAuthor;
1997 Result.Height := mapReader.Height;
1998 Result.Width := mapReader.Width;
1999 end
2000 else
2001 begin
2002 g_Console_Add(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]), True);
2003 //ZeroMemory(@Header, SizeOf(Header));
2004 Result.Name := _lc[I_GAME_ERROR_MAP_SELECT];
2005 Result.Description := _lc[I_GAME_ERROR_MAP_SELECT];
2006 Result.Map := Res;
2007 Result.Author := '';
2008 Result.Height := 0;
2009 Result.Width := 0;
2010 end;
2012 mapReader.Free();
2013 end;
2015 function g_Map_GetMapsList(WADName: string): SSArray;
2016 var
2017 WAD: TWADFile;
2018 a: Integer;
2019 ResList: SSArray;
2020 begin
2021 Result := nil;
2022 WAD := TWADFile.Create();
2023 if not WAD.ReadFile(WADName) then
2024 begin
2025 WAD.Free();
2026 Exit;
2027 end;
2028 ResList := WAD.GetMapResources();
2029 if ResList <> nil then
2030 begin
2031 for a := 0 to High(ResList) do
2032 begin
2033 SetLength(Result, Length(Result)+1);
2034 Result[High(Result)] := ResList[a];
2035 end;
2036 end;
2037 WAD.Free();
2038 end;
2040 function g_Map_Exist(Res: string): Boolean;
2041 var
2042 WAD: TWADFile;
2043 FileName, mnn: string;
2044 ResList: SSArray;
2045 a: Integer;
2046 begin
2047 Result := False;
2049 FileName := addWadExtension(g_ExtractWadName(Res));
2051 WAD := TWADFile.Create;
2052 if not WAD.ReadFile(FileName) then
2053 begin
2054 WAD.Free();
2055 Exit;
2056 end;
2058 ResList := WAD.GetMapResources();
2059 WAD.Free();
2061 mnn := g_ExtractFileName(Res);
2062 if ResList <> nil then
2063 for a := 0 to High(ResList) do if StrEquCI1251(ResList[a], mnn) then
2064 begin
2065 Result := True;
2066 Exit;
2067 end;
2068 end;
2070 procedure g_Map_Free(freeTextures: Boolean=true);
2072 procedure FreePanelArray(var panels: TPanelArray);
2073 var
2074 i: Integer;
2076 begin
2077 if panels <> nil then
2078 begin
2079 for i := 0 to High(panels) do
2080 panels[i].Free();
2081 panels := nil;
2082 end;
2083 end;
2085 begin
2086 {$IFDEF ENABLE_GFX}
2087 g_GFX_Free;
2088 {$ENDIF}
2089 g_Weapon_Free();
2090 g_Items_Free();
2091 g_Triggers_Free();
2092 g_Monsters_Free();
2094 RespawnPoints := nil;
2095 if FlagPoints[FLAG_RED] <> nil then
2096 begin
2097 Dispose(FlagPoints[FLAG_RED]);
2098 FlagPoints[FLAG_RED] := nil;
2099 end;
2100 if FlagPoints[FLAG_BLUE] <> nil then
2101 begin
2102 Dispose(FlagPoints[FLAG_BLUE]);
2103 FlagPoints[FLAG_BLUE] := nil;
2104 end;
2105 //DOMFlagPoints := nil;
2107 //gDOMFlags := nil;
2109 if (Length(gCurrentMapFileName) <> 0) then
2110 begin
2111 e_LogWritefln('g_Map_Free: previous map was ''%s''...', [gCurrentMapFileName]);
2112 end
2113 else
2114 begin
2115 e_LogWritefln('g_Map_Free: no previous map.', []);
2116 end;
2118 if freeTextures then
2119 begin
2120 e_LogWritefln('g_Map_Free: clearing textures...', []);
2121 Textures := nil;
2122 TextNameHash.Free();
2123 TextNameHash := nil;
2124 BadTextNameHash.Free();
2125 BadTextNameHash := nil;
2126 gCurrentMapFileName := '';
2127 gCurrentMap.Free();
2128 gCurrentMap := nil;
2129 end;
2131 panByGUID := nil;
2133 FreePanelArray(gWalls);
2134 FreePanelArray(gRenderBackgrounds);
2135 FreePanelArray(gRenderForegrounds);
2136 FreePanelArray(gWater);
2137 FreePanelArray(gAcid1);
2138 FreePanelArray(gAcid2);
2139 FreePanelArray(gSteps);
2140 FreePanelArray(gLifts);
2141 FreePanelArray(gBlockMon);
2142 gMovingWallIds := nil;
2144 g_Game_StopAllSounds(False);
2145 gMusic.FreeSound();
2146 g_Sound_Delete(gMapInfo.MusicName);
2148 gMapInfo.Name := '';
2149 gMapInfo.Description := '';
2150 gMapInfo.MusicName := '';
2151 gMapInfo.Height := 0;
2152 gMapInfo.Width := 0;
2154 gDoorMap := nil;
2155 gLiftMap := nil;
2156 end;
2158 procedure g_Map_Update();
2159 var
2160 a, d, j: Integer;
2161 m: Word;
2162 s: String;
2163 b: Byte;
2165 procedure UpdatePanelArray(var panels: TPanelArray);
2166 var
2167 i: Integer;
2169 begin
2170 for i := 0 to High(panels) do panels[i].Update();
2171 end;
2173 begin
2174 if g_dbgpan_mplat_step then g_dbgpan_mplat_active := true;
2176 UpdatePanelArray(gWalls);
2177 UpdatePanelArray(gRenderBackgrounds);
2178 UpdatePanelArray(gRenderForegrounds);
2179 UpdatePanelArray(gWater);
2180 UpdatePanelArray(gAcid1);
2181 UpdatePanelArray(gAcid2);
2182 UpdatePanelArray(gSteps);
2184 if g_dbgpan_mplat_step then begin g_dbgpan_mplat_step := false; g_dbgpan_mplat_active := false; end;
2186 if gGameSettings.GameMode = GM_CTF then
2187 begin
2188 for a := FLAG_RED to FLAG_BLUE do
2189 begin
2190 if not (gFlags[a].State in [FLAG_STATE_NONE, FLAG_STATE_CAPTURED]) then
2191 begin
2192 with gFlags[a] do
2193 begin
2194 m := g_Obj_Move(@Obj, True, True);
2196 if gTime mod (GAME_TICK*2) <> 0 then Continue;
2198 // Ñîïðîòèâëåíèå âîçäóõà
2199 Obj.Vel.X := z_dec(Obj.Vel.X, 1);
2201 // Òàéìàóò ïîòåðÿííîãî ôëàãà, ëèáî îí âûïàë çà êàðòó
2202 if ((Count = 0) or ByteBool(m and MOVE_FALLOUT)) and g_Game_IsServer then
2203 begin
2204 g_Map_ResetFlag(a);
2205 gFlags[a].CaptureTime := 0;
2206 if a = FLAG_RED then
2207 s := _lc[I_PLAYER_FLAG_RED]
2208 else
2209 s := _lc[I_PLAYER_FLAG_BLUE];
2210 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
2212 if (((gPlayer1 <> nil) and (((gPlayer1.Team = TEAM_RED) and (a = FLAG_RED)) or ((gPlayer1.Team = TEAM_BLUE) and (a = FLAG_BLUE))))
2213 or ((gPlayer2 <> nil) and (((gPlayer2.Team = TEAM_RED) and (a = FLAG_RED)) or ((gPlayer2.Team = TEAM_BLUE) and (a = FLAG_BLUE))))) then
2214 b := 0
2215 else
2216 b := 1;
2218 if not sound_ret_flag[b].IsPlaying() then
2219 sound_ret_flag[b].Play();
2221 if g_Game_IsNet then
2222 MH_SEND_FlagEvent(FLAG_STATE_RETURNED, a, 0);
2223 Continue;
2224 end;
2226 if Count > 0 then Count -= 1;
2228 // Èãðîê áåðåò ôëàã
2229 if gPlayers <> nil then
2230 begin
2231 j := Random(Length(gPlayers)) - 1;
2232 for d := 0 to High(gPlayers) do
2233 begin
2234 Inc(j);
2235 if j > High(gPlayers) then j := 0;
2236 if gPlayers[j] <> nil then
2237 begin
2238 if gPlayers[j].alive and g_Obj_Collide(@Obj, @gPlayers[j].Obj) then
2239 begin
2240 if gPlayers[j].GetFlag(a) then Break;
2241 end;
2242 end;
2243 end;
2244 end;
2245 end;
2246 end;
2247 end;
2248 end;
2249 end;
2251 function g_Map_CollidePanelOld(X, Y: Integer; Width, Height: Word;
2252 PanelType: Word; b1x3: Boolean=false): Boolean;
2253 var
2254 a, h: Integer;
2255 begin
2256 Result := False;
2258 if WordBool(PanelType and PANEL_WALL) then
2259 if gWalls <> nil then
2260 begin
2261 h := High(gWalls);
2263 for a := 0 to h do
2264 if gWalls[a].Enabled and
2265 g_Collide(X, Y, Width, Height,
2266 gWalls[a].X, gWalls[a].Y,
2267 gWalls[a].Width, gWalls[a].Height) then
2268 begin
2269 Result := True;
2270 Exit;
2271 end;
2272 end;
2274 if WordBool(PanelType and PANEL_WATER) then
2275 if gWater <> nil then
2276 begin
2277 h := High(gWater);
2279 for a := 0 to h do
2280 if g_Collide(X, Y, Width, Height,
2281 gWater[a].X, gWater[a].Y,
2282 gWater[a].Width, gWater[a].Height) then
2283 begin
2284 Result := True;
2285 Exit;
2286 end;
2287 end;
2289 if WordBool(PanelType and PANEL_ACID1) then
2290 if gAcid1 <> nil then
2291 begin
2292 h := High(gAcid1);
2294 for a := 0 to h do
2295 if g_Collide(X, Y, Width, Height,
2296 gAcid1[a].X, gAcid1[a].Y,
2297 gAcid1[a].Width, gAcid1[a].Height) then
2298 begin
2299 Result := True;
2300 Exit;
2301 end;
2302 end;
2304 if WordBool(PanelType and PANEL_ACID2) then
2305 if gAcid2 <> nil then
2306 begin
2307 h := High(gAcid2);
2309 for a := 0 to h do
2310 if g_Collide(X, Y, Width, Height,
2311 gAcid2[a].X, gAcid2[a].Y,
2312 gAcid2[a].Width, gAcid2[a].Height) then
2313 begin
2314 Result := True;
2315 Exit;
2316 end;
2317 end;
2319 if WordBool(PanelType and PANEL_STEP) then
2320 if gSteps <> nil then
2321 begin
2322 h := High(gSteps);
2324 for a := 0 to h do
2325 if g_Collide(X, Y, Width, Height,
2326 gSteps[a].X, gSteps[a].Y,
2327 gSteps[a].Width, gSteps[a].Height) then
2328 begin
2329 Result := True;
2330 Exit;
2331 end;
2332 end;
2334 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then
2335 if gLifts <> nil then
2336 begin
2337 h := High(gLifts);
2339 for a := 0 to h do
2340 if ((WordBool(PanelType and (PANEL_LIFTUP)) and (gLifts[a].LiftType = LIFTTYPE_UP)) or
2341 (WordBool(PanelType and (PANEL_LIFTDOWN)) and (gLifts[a].LiftType = LIFTTYPE_DOWN)) or
2342 (WordBool(PanelType and (PANEL_LIFTLEFT)) and (gLifts[a].LiftType = LIFTTYPE_LEFT)) or
2343 (WordBool(PanelType and (PANEL_LIFTRIGHT)) and (gLifts[a].LiftType = LIFTTYPE_RIGHT))) and
2344 g_Collide(X, Y, Width, Height,
2345 gLifts[a].X, gLifts[a].Y,
2346 gLifts[a].Width, gLifts[a].Height) then
2347 begin
2348 Result := True;
2349 Exit;
2350 end;
2351 end;
2353 if WordBool(PanelType and PANEL_BLOCKMON) then
2354 if gBlockMon <> nil then
2355 begin
2356 h := High(gBlockMon);
2358 for a := 0 to h do
2359 if ( (not b1x3) or
2360 ((gBlockMon[a].Width + gBlockMon[a].Height) >= 64) ) and
2361 g_Collide(X, Y, Width, Height,
2362 gBlockMon[a].X, gBlockMon[a].Y,
2363 gBlockMon[a].Width, gBlockMon[a].Height) then
2364 begin
2365 Result := True;
2366 Exit;
2367 end;
2368 end;
2369 end;
2371 function g_Map_CollideLiquid_TextureOld(X, Y: Integer; Width, Height: Word): DWORD;
2372 var
2373 texid: DWORD;
2375 function checkPanels (constref panels: TPanelArray): Boolean;
2376 var
2377 a: Integer;
2378 begin
2379 result := false;
2380 if panels = nil then exit;
2381 for a := 0 to High(panels) do
2382 begin
2383 if g_Collide(X, Y, Width, Height, panels[a].X, panels[a].Y, panels[a].Width, panels[a].Height) then
2384 begin
2385 result := true;
2386 texid := panels[a].GetTextureID();
2387 exit;
2388 end;
2389 end;
2390 end;
2392 begin
2393 texid := LongWord(TEXTURE_NONE);
2394 result := texid;
2395 if not checkPanels(gWater) then
2396 if not checkPanels(gAcid1) then
2397 if not checkPanels(gAcid2) then exit;
2398 result := texid;
2399 end;
2402 function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word; PanelType: Word; b1x3: Boolean): Boolean;
2403 const
2404 SlowMask = GridTagLift or GridTagBlockMon;
2406 function checker (pan: TPanel; tag: Integer): Boolean;
2407 begin
2409 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
2410 begin
2411 result := pan.Enabled;
2412 exit;
2413 end;
2416 if ((tag and GridTagLift) <> 0) then
2417 begin
2418 result :=
2419 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
2420 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
2421 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
2422 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT))) {and
2423 g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
2424 exit;
2425 end;
2427 if ((tag and GridTagBlockMon) <> 0) then
2428 begin
2429 result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2430 exit;
2431 end;
2433 // other shit
2434 //result := g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2435 result := true; // i found her!
2436 end;
2438 var
2439 tagmask: Integer = 0;
2440 mwit: PPanel;
2441 it: TPanelGrid.Iter;
2442 pan: TPanel;
2443 begin
2444 result := false;
2445 if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor);
2446 if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater;
2447 if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1;
2448 if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2;
2449 if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep;
2450 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift;
2451 if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon;
2453 if (tagmask = 0) then exit; // just in case
2455 if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('*solids');
2456 if gdbg_map_use_accel_coldet then
2457 begin
2458 if ((tagmask and SlowMask) <> 0) then
2459 begin
2460 // slow
2461 it := mapGrid.forEachInAABB(X, Y, Width, Height, tagmask);
2462 for mwit in it do
2463 begin
2464 pan := mwit^;
2465 if ((pan.tag and GridTagLift) <> 0) then
2466 begin
2467 result :=
2468 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
2469 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
2470 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
2471 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT))) {and
2472 g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
2473 end
2474 else if ((pan.tag and GridTagBlockMon) <> 0) then
2475 begin
2476 result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2477 end
2478 else
2479 begin
2480 // other shit
2481 result := true; // i found her!
2482 end;
2483 if (result) then break;
2484 end;
2485 end
2486 else
2487 begin
2488 // fast
2489 it := mapGrid.forEachInAABB(X, Y, Width, Height, tagmask, false, true); // return first hit
2490 result := (it.length > 0);
2491 end;
2492 it.release();
2493 end
2494 else
2495 begin
2496 result := g_Map_CollidePanelOld(X, Y, Width, Height, PanelType, b1x3);
2497 end;
2498 if (profMapCollision <> nil) then profMapCollision.sectionEnd();
2499 end;
2502 // returns `true` if we need to stop
2503 function liquidChecker (pan: TPanel; var texid: DWORD; var cctype: Integer): Boolean; inline;
2504 begin
2505 result := false;
2506 //if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2)) = 0) then exit;
2507 // check priorities
2508 case cctype of
2509 0: if ((pan.tag and GridTagWater) = 0) then exit; // allowed: water
2510 1: if ((pan.tag and (GridTagWater or GridTagAcid1)) = 0) then exit; // allowed: water, acid1
2511 //2: if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2) = 0) then exit; // allowed: water, acid1, acid2
2512 end;
2513 // collision?
2514 //if not g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height) then exit;
2515 // yeah
2516 texid := pan.GetTextureID();
2517 // water? water has the highest priority, so stop right here
2518 if ((pan.tag and GridTagWater) <> 0) then begin cctype := 0; result := true; exit; end;
2519 // acid2?
2520 if ((pan.tag and GridTagAcid2) <> 0) then cctype := 2;
2521 // acid1?
2522 if ((pan.tag and GridTagAcid1) <> 0) then cctype := 1;
2523 end;
2525 function g_Map_CollideLiquid_Texture(X, Y: Integer; Width, Height: Word): DWORD;
2526 var
2527 cctype: Integer = 3; // priority: 0: water was hit, 1: acid1 was hit, 2: acid2 was hit; 3: nothing was hit
2528 mwit: PPanel;
2529 it: TPanelGrid.Iter;
2530 begin
2531 if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('liquids');
2532 if gdbg_map_use_accel_coldet then
2533 begin
2534 result := LongWord(TEXTURE_NONE);
2535 it := mapGrid.forEachInAABB(X, Y, Width, Height, (GridTagWater or GridTagAcid1 or GridTagAcid2));
2536 for mwit in it do if (liquidChecker(mwit^, result, cctype)) then break;
2537 it.release();
2538 end
2539 else
2540 begin
2541 result := g_Map_CollideLiquid_TextureOld(X, Y, Width, Height);
2542 end;
2543 if (profMapCollision <> nil) then profMapCollision.sectionEnd();
2544 end;
2547 procedure g_Map_EnableWall_XXX (ID: DWORD); begin if (ID < Length(gWalls)) then g_Map_EnableWallGUID(gWalls[ID].guid); end;
2548 procedure g_Map_DisableWall_XXX (ID: DWORD); begin if (ID < Length(gWalls)) then g_Map_DisableWallGUID(gWalls[ID].guid); end;
2549 procedure g_Map_SetLift_XXX (ID: DWORD; t: Integer); begin if (ID < Length(gLifts)) then g_Map_SetLiftGUID(gLifts[ID].guid, t); end;
2552 procedure g_Map_EnableWallGUID (pguid: Integer);
2553 var
2554 pan: TPanel;
2555 begin
2556 //pan := gWalls[ID];
2557 pan := g_Map_PanelByGUID(pguid);
2558 if (pan = nil) then exit;
2559 if pan.Enabled and mapGrid.proxyEnabled[pan.proxyId] then exit;
2561 pan.Enabled := True;
2563 {$IFDEF ENABLE_GFX}
2564 g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, true);
2565 {$ENDIF}
2567 mapGrid.proxyEnabled[pan.proxyId] := true;
2568 //if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := true
2569 //else pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, GridTagDoor);
2571 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2572 // mark platform as interesting
2573 pan.setDirty();
2575 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
2576 //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);
2577 {$ENDIF}
2578 end;
2581 procedure g_Map_DisableWallGUID (pguid: Integer);
2582 var
2583 pan: TPanel;
2584 begin
2585 //pan := gWalls[ID];
2586 pan := g_Map_PanelByGUID(pguid);
2587 if (pan = nil) then exit;
2588 if (not pan.Enabled) and (not mapGrid.proxyEnabled[pan.proxyId]) then exit;
2590 pan.Enabled := False;
2591 {$IFDEF ENABLE_GFX}
2592 g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, false);
2593 {$ENDIF}
2595 mapGrid.proxyEnabled[pan.proxyId] := false;
2596 //if (pan.proxyId >= 0) then begin mapGrid.removeBody(pan.proxyId); pan.proxyId := -1; end;
2598 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2599 // mark platform as interesting
2600 pan.setDirty();
2602 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
2603 //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);
2604 {$ENDIF}
2605 end;
2608 procedure g_Map_SwitchTextureGUID (pguid: Integer; AnimLoop: Byte = 0);
2609 var
2610 tp: TPanel;
2611 begin
2612 tp := g_Map_PanelByGUID(pguid);
2613 if (tp = nil) then exit;
2614 tp.NextTexture(AnimLoop);
2615 if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelTexture(pguid, AnimLoop);
2616 end;
2619 procedure g_Map_SetLiftGUID (pguid: Integer; t: Integer);
2620 var
2621 pan: TPanel;
2622 begin
2623 //pan := gLifts[ID];
2624 pan := g_Map_PanelByGUID(pguid);
2625 if (pan = nil) then exit;
2626 if not pan.isGLift then exit;
2628 if ({gLifts[ID]}pan.LiftType = t) then exit; //!FIXME!TRIGANY!
2630 with {gLifts[ID]} pan do
2631 begin
2632 LiftType := t;
2634 {$IFDEF ENABLE_GFX}
2635 g_Mark(X, Y, Width, Height, MARK_LIFT, false);
2636 //TODO: make separate lift tags, and change tag here
2637 case LiftType of
2638 LIFTTYPE_UP: g_Mark(X, Y, Width, Height, MARK_LIFTUP);
2639 LIFTTYPE_DOWN: g_Mark(X, Y, Width, Height, MARK_LIFTDOWN);
2640 LIFTTYPE_LEFT: g_Mark(X, Y, Width, Height, MARK_LIFTLEFT);
2641 LIFTTYPE_RIGHT: g_Mark(X, Y, Width, Height, MARK_LIFTRIGHT);
2642 end;
2643 {$ENDIF}
2645 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2646 // mark platform as interesting
2647 pan.setDirty();
2648 end;
2649 end;
2652 function g_Map_GetPoint (PointType: Byte; var RespawnPoint: TRespawnPoint): Boolean;
2653 var
2654 a: Integer;
2655 PointsArray: Array of TRespawnPoint;
2656 begin
2657 Result := False;
2658 SetLength(PointsArray, 0);
2660 if RespawnPoints = nil then
2661 Exit;
2663 for a := 0 to High(RespawnPoints) do
2664 if RespawnPoints[a].PointType = PointType then
2665 begin
2666 SetLength(PointsArray, Length(PointsArray)+1);
2667 PointsArray[High(PointsArray)] := RespawnPoints[a];
2668 end;
2670 if PointsArray = nil then
2671 Exit;
2673 RespawnPoint := PointsArray[Random(Length(PointsArray))];
2674 Result := True;
2675 end;
2677 function g_Map_GetPointCount(PointType: Byte): Word;
2678 var
2679 a: Integer;
2680 begin
2681 Result := 0;
2683 if RespawnPoints = nil then
2684 Exit;
2686 for a := 0 to High(RespawnPoints) do
2687 if RespawnPoints[a].PointType = PointType then
2688 Result := Result + 1;
2689 end;
2691 function g_Map_GetRandomPointType(): Byte;
2692 begin
2693 if RespawnPoints = nil then
2694 Result := 255
2695 else
2696 Result := RespawnPoints[Random(Length(RespawnPoints))].PointType;
2697 end;
2699 function g_Map_HaveFlagPoints(): Boolean;
2700 begin
2701 Result := (FlagPoints[FLAG_RED] <> nil) and (FlagPoints[FLAG_BLUE] <> nil);
2702 end;
2704 procedure g_Map_ResetFlag(Flag: Byte);
2705 begin
2706 with gFlags[Flag] do
2707 begin
2708 Obj.X := -1000;
2709 Obj.Y := -1000;
2710 Obj.Vel.X := 0;
2711 Obj.Vel.Y := 0;
2712 Direction := TDirection.D_LEFT;
2713 State := FLAG_STATE_NONE;
2714 if FlagPoints[Flag] <> nil then
2715 begin
2716 Obj.X := FlagPoints[Flag]^.X;
2717 Obj.Y := FlagPoints[Flag]^.Y;
2718 Direction := FlagPoints[Flag]^.Direction;
2719 State := FLAG_STATE_NORMAL;
2720 end;
2721 Count := -1;
2722 end;
2723 end;
2725 procedure g_Map_SaveState (st: TStream);
2726 var
2727 str: String;
2729 procedure savePanels ();
2730 var
2731 pan: TPanel;
2732 begin
2733 // Ñîõðàíÿåì ïàíåëè
2734 utils.writeInt(st, LongInt(Length(panByGUID)));
2735 for pan in panByGUID do pan.SaveState(st);
2736 end;
2738 procedure saveFlag (flag: PFlag);
2739 var
2740 b: Byte;
2741 begin
2742 utils.writeSign(st, 'FLAG');
2743 utils.writeInt(st, Byte(0)); // version
2744 // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
2745 utils.writeInt(st, Byte(flag^.RespawnType));
2746 // Ñîñòîÿíèå ôëàãà
2747 utils.writeInt(st, Byte(flag^.State));
2748 // Íàïðàâëåíèå ôëàãà
2749 if flag^.Direction = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT
2750 utils.writeInt(st, Byte(b));
2751 // Îáúåêò ôëàãà
2752 Obj_SaveState(st, @flag^.Obj);
2753 end;
2755 begin
2756 savePanels();
2758 // Ñîõðàíÿåì ìóçûêó
2759 utils.writeSign(st, 'MUSI');
2760 utils.writeInt(st, Byte(0));
2761 // Íàçâàíèå ìóçûêè
2762 assert(gMusic <> nil, 'g_Map_SaveState: gMusic = nil');
2763 if gMusic.NoMusic then str := '' else str := gMusic.Name;
2764 utils.writeStr(st, str);
2765 // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
2766 utils.writeInt(st, LongWord(gMusic.GetPosition()));
2767 // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
2768 utils.writeBool(st, gMusic.SpecPause);
2770 ///// Ñîõðàíÿåì êîëè÷åñòâî ìîíñòðîâ: /////
2771 utils.writeInt(st, LongInt(gTotalMonsters));
2772 ///// /////
2774 //// Ñîõðàíÿåì ôëàãè, åñëè ýòî CTF: /////
2775 if (gGameSettings.GameMode = GM_CTF) then
2776 begin
2777 // Ôëàã Êðàñíîé êîìàíäû
2778 saveFlag(@gFlags[FLAG_RED]);
2779 // Ôëàã Ñèíåé êîìàíäû
2780 saveFlag(@gFlags[FLAG_BLUE]);
2781 end;
2782 ///// /////
2784 ///// Ñîõðàíÿåì êîëè÷åñòâî ïîáåä, åñëè ýòî TDM/CTF: /////
2785 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
2786 begin
2787 // Î÷êè Êðàñíîé êîìàíäû
2788 utils.writeInt(st, SmallInt(gTeamStat[TEAM_RED].Goals));
2789 // Î÷êè Ñèíåé êîìàíäû
2790 utils.writeInt(st, SmallInt(gTeamStat[TEAM_BLUE].Goals));
2791 end;
2792 ///// /////
2793 end;
2796 procedure g_Map_LoadState (st: TStream);
2797 var
2798 dw: DWORD;
2799 str: String;
2800 boo: Boolean;
2802 procedure loadPanels ();
2803 var
2804 pan: TPanel;
2805 begin
2806 // Çàãðóæàåì ïàíåëè
2807 if (Length(panByGUID) <> utils.readLongInt(st)) then raise XStreamError.Create('invalid number of saved panels');
2808 for pan in panByGUID do
2809 begin
2810 pan.LoadState(st);
2811 if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
2812 end;
2813 end;
2815 procedure loadFlag (flag: PFlag);
2816 var
2817 b: Byte;
2818 begin
2819 // Ñèãíàòóðà ôëàãà
2820 if not utils.checkSign(st, 'FLAG') then raise XStreamError.Create('invalid flag signature');
2821 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid flag version');
2822 // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
2823 flag^.RespawnType := utils.readByte(st);
2824 // Ñîñòîÿíèå ôëàãà
2825 flag^.State := utils.readByte(st);
2826 // Íàïðàâëåíèå ôëàãà
2827 b := utils.readByte(st);
2828 if (b = 1) then flag^.Direction := TDirection.D_LEFT else flag^.Direction := TDirection.D_RIGHT; // b = 2
2829 // Îáúåêò ôëàãà
2830 Obj_LoadState(@flag^.Obj, st);
2831 end;
2833 begin
2834 if (st = nil) then exit;
2836 ///// Çàãðóæàåì ñïèñêè ïàíåëåé: /////
2837 loadPanels();
2838 ///// /////
2840 {$IFDEF ENABLE_GFX}
2841 // Îáíîâëÿåì êàðòó ñòîëêíîâåíèé è ñåòêó
2842 g_GFX_Init();
2843 {$ENDIF}
2845 //mapCreateGrid();
2847 ///// Çàãðóæàåì ìóçûêó: /////
2848 if not utils.checkSign(st, 'MUSI') then raise XStreamError.Create('invalid music signature');
2849 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid music version');
2850 // Íàçâàíèå ìóçûêè
2851 assert(gMusic <> nil, 'g_Map_LoadState: gMusic = nil');
2852 str := utils.readStr(st);
2853 // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
2854 dw := utils.readLongWord(st);
2855 // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
2856 boo := utils.readBool(st);
2857 // Çàïóñêàåì ýòó ìóçûêó
2858 gMusic.SetByName(str);
2859 gMusic.SpecPause := boo;
2860 gMusic.Play();
2861 gMusic.Pause(true);
2862 gMusic.SetPosition(dw);
2863 ///// /////
2865 ///// Çàãðóæàåì êîëè÷åñòâî ìîíñòðîâ: /////
2866 gTotalMonsters := utils.readLongInt(st);
2867 ///// /////
2869 //// Çàãðóæàåì ôëàãè, åñëè ýòî CTF: /////
2870 if (gGameSettings.GameMode = GM_CTF) then
2871 begin
2872 // Ôëàã Êðàñíîé êîìàíäû
2873 loadFlag(@gFlags[FLAG_RED]);
2874 // Ôëàã Ñèíåé êîìàíäû
2875 loadFlag(@gFlags[FLAG_BLUE]);
2876 end;
2877 ///// /////
2879 ///// Çàãðóæàåì êîëè÷åñòâî ïîáåä, åñëè ýòî TDM/CTF: /////
2880 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
2881 begin
2882 // Î÷êè Êðàñíîé êîìàíäû
2883 gTeamStat[TEAM_RED].Goals := utils.readSmallInt(st);
2884 // Î÷êè Ñèíåé êîìàíäû
2885 gTeamStat[TEAM_BLUE].Goals := utils.readSmallInt(st);
2886 end;
2887 ///// /////
2888 end;
2891 // trace liquid, stepping by `dx` and `dy`
2892 // return last seen liquid coords, and `false` if we're started outside of the liquid
2893 function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
2894 const
2895 MaskLiquid = GridTagWater or GridTagAcid1 or GridTagAcid2;
2896 begin
2897 topx := x;
2898 topy := y;
2899 // started outside of the liquid?
2900 //if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then begin result := false; exit; end;
2901 if (g_Map_PanelAtPoint(x, y, MaskLiquid) = nil) then begin result := false; exit; end;
2902 if (dx = 0) and (dy = 0) then begin result := false; exit; end; // sanity check
2903 result := true;
2904 while true do
2905 begin
2906 Inc(x, dx);
2907 Inc(y, dy);
2908 //if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then exit; // out of the water, just exit
2909 if (g_Map_PanelAtPoint(x, y, MaskLiquid) = nil) then exit; // out of the water, just exit
2910 topx := x;
2911 topy := y;
2912 end;
2913 end;
2916 begin
2917 DynWarningCB := mapWarningCB;
2918 end.