DEADSOFTWARE

game: remove unneded render imports
[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);
200 type
201 TBinHeapPanelDrawCmp = class
202 public
203 class function less (const a, b: TPanel): Boolean; inline;
204 end;
206 TBinHeapPanelDraw = specialize TBinaryHeapBase<TPanel, TBinHeapPanelDrawCmp>;
208 var
209 gWalls: TPanelArray;
210 gRenderBackgrounds: TPanelArray;
211 gRenderForegrounds: TPanelArray;
212 gWater, gAcid1, gAcid2: TPanelArray;
213 gSteps: TPanelArray;
214 gLifts: TPanelArray;
215 gBlockMon: TPanelArray;
216 gFlags: array [FLAG_RED..FLAG_BLUE] of TFlag;
217 //gDOMFlags: array of TFlag;
218 gMapInfo: TMapInfo;
219 gDoorMap: array of array of DWORD;
220 gLiftMap: array of array of DWORD;
221 gWADHash: TMD5Digest;
222 gExternalResources: array of TDiskFileInfo = nil;
223 gMovingWallIds: array of Integer = nil;
225 gdbg_map_use_accel_render: Boolean = true;
226 gdbg_map_use_accel_coldet: Boolean = true;
227 profMapCollision: TProfiler = nil; //WARNING: FOR DEBUGGING ONLY!
228 gDrawPanelList: TBinHeapPanelDraw = nil; // binary heap of all walls we have to render, populated by `g_Map_CollectDrawPanels()`
230 gCurrentMap: TDynRecord = nil;
231 gCurrentMapFileName: AnsiString = ''; // so we can skip texture reloading
232 gTestMap: String = '';
235 function panelTypeToTag (panelType: Word): Integer; // returns GridTagXXX
238 type
239 TPanelGrid = specialize TBodyGridBase<TPanel>;
241 var
242 mapGrid: TPanelGrid = nil; // DO NOT USE! public for debugging only!
244 var (* private state *)
245 Textures: TLevelTextureArray = nil;
247 implementation
249 uses
250 e_input, e_log, e_res, g_items, g_gfx, g_console,
251 g_weapons, g_game, g_sound, e_sound, CONFIG,
252 g_options, g_triggers, g_player,
253 Math, g_monsters, g_saveload, g_language, g_netmsg,
254 sfs, xstreams, hashtable, wadreader,
255 g_res_downloader;
257 const
258 FLAGRECT: TRectWH = (X:15; Y:12; Width:33; Height:52);
259 MUSIC_SIGNATURE = $4953554D; // 'MUSI'
260 FLAG_SIGNATURE = $47414C46; // 'FLAG'
263 // ////////////////////////////////////////////////////////////////////////// //
264 procedure mapWarningCB (const msg: AnsiString; line, col: Integer);
265 begin
266 if (line > 0) then
267 begin
268 e_LogWritefln('parse error at (%s,%s): %s', [line, col, msg], TMsgType.Warning);
269 end
270 else
271 begin
272 e_LogWritefln('parse error: %s', [msg], TMsgType.Warning);
273 end;
274 end;
277 // ////////////////////////////////////////////////////////////////////////// //
278 var
279 panByGUID: array of TPanel = nil;
282 // ////////////////////////////////////////////////////////////////////////// //
283 function g_Map_PanelByGUID (aguid: Integer): TPanel; inline;
284 begin
285 //if (panByGUID = nil) or (not panByGUID.get(aguid, result)) then result := nil;
286 if (aguid >= 0) and (aguid < Length(panByGUID)) then result := panByGUID[aguid] else result := nil;
287 end;
290 // return `true` from `cb` to stop
291 function g_Map_ForEachPanel (cb: TForEachPanelCB): TPanel;
292 var
293 pan: TPanel;
294 begin
295 result := nil;
296 if not assigned(cb) then exit;
297 for pan in panByGUID do
298 begin
299 if cb(pan) then begin result := pan; exit; end;
300 end;
301 end;
304 procedure g_Map_NetSendInterestingPanels ();
305 var
306 pan: TPanel;
307 begin
308 if g_Game_IsServer and g_Game_IsNet then
309 begin
310 for pan in panByGUID do
311 begin
312 if pan.gncNeedSend then MH_SEND_PanelState(pan.guid);
313 end;
314 end;
315 end;
318 // ////////////////////////////////////////////////////////////////////////// //
319 function g_Map_MinX (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridX0 else result := 0; end;
320 function g_Map_MinY (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridY0 else result := 0; end;
321 function g_Map_MaxX (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridX0+mapGrid.gridWidth-1 else result := 0; end;
322 function g_Map_MaxY (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridY0+mapGrid.gridHeight-1 else result := 0; end;
325 // ////////////////////////////////////////////////////////////////////////// //
326 var
327 dfmapdef: TDynMapDef = nil;
330 procedure loadMapDefinition ();
331 var
332 pr: TTextParser = nil;
333 st: TStream = nil;
334 WAD: TWADFile = nil;
335 begin
336 if (dfmapdef <> nil) then exit;
338 try
339 e_LogWritefln('parsing "mapdef.txt"...', []);
340 st := e_OpenResourceRO(DataDirs, 'mapdef.txt');
341 e_LogWritefln('found local "mapdef.txt"', []);
342 except
343 st := nil;
344 end;
346 if (st = nil) then
347 begin
348 WAD := TWADFile.Create();
349 if not WAD.ReadFile(GameWAD) then
350 begin
351 //raise Exception.Create('cannot load "game.wad"');
352 st := nil;
353 end
354 else
355 begin
356 st := WAD.openFileStream('mapdef.txt');
357 end;
358 end;
360 try
361 if (st = nil) then
362 begin
363 //raise Exception.Create('cannot open "mapdef.txt"');
364 e_LogWriteln('using default "mapdef.txt"...');
365 pr := TStrTextParser.Create(defaultMapDef);
366 end
367 else
368 begin
369 pr := TFileTextParser.Create(st);
370 end;
371 except on e: Exception do
372 begin
373 e_LogWritefln('something is VERY wrong here! -- ', [e.message]);
374 raise;
375 end;
376 end;
378 try
379 dfmapdef := TDynMapDef.Create(pr);
380 except
381 on e: TDynParseException do
382 raise Exception.CreateFmt('ERROR in "mapdef.txt" at (%s,%s): %s', [e.tokLine, e.tokCol, e.message]);
383 on e: Exception do
384 raise Exception.CreateFmt('ERROR in "mapdef.txt" at (%s,%s): %s', [pr.tokLine, pr.tokCol, e.message]);
385 end;
387 st.Free();
388 WAD.Free();
389 end;
392 // ////////////////////////////////////////////////////////////////////////// //
393 function g_Map_ParseMap (data: Pointer; dataLen: Integer): TDynRecord;
394 var
395 wst: TSFSMemoryChunkStream = nil;
396 begin
397 result := nil;
398 if (dataLen < 4) then exit;
400 if (dfmapdef = nil) then writeln('need to load mapdef');
401 loadMapDefinition();
402 if (dfmapdef = nil) then raise Exception.Create('internal map loader error');
404 wst := TSFSMemoryChunkStream.Create(data, dataLen);
405 try
406 result := dfmapdef.parseMap(wst);
407 except
408 on e: TDynParseException do
409 begin
410 e_LogWritefln('ERROR at (%s,%s): %s', [e.tokLine, e.tokCol, e.message]);
411 wst.Free();
412 result := nil;
413 exit;
414 end;
415 on e: Exception do
416 begin
417 e_LogWritefln('ERROR: %s', [e.message]);
418 wst.Free();
419 result := nil;
420 exit;
421 end;
422 end;
424 //e_LogWriteln('map parsed.');
425 end;
428 // ////////////////////////////////////////////////////////////////////////// //
429 var
430 NNF_PureName: String; // Èìÿ òåêñòóðû áåç öèôð â êîíöå
431 NNF_PureExt: String; // extension postfix
432 NNF_FirstNum: Integer; // ×èñëî ó íà÷àëüíîé òåêñòóðû
433 NNF_CurrentNum: Integer; // Ñëåäóþùåå ÷èñëî ó òåêñòóðû
436 function g_Texture_NumNameFindStart(name: String): Boolean;
437 var
438 i, j: Integer;
440 begin
441 Result := False;
442 NNF_PureName := '';
443 NNF_PureExt := '';
444 NNF_FirstNum := -1;
445 NNF_CurrentNum := -1;
447 for i := Length(name) downto 1 do
448 if (name[i] = '_') then // "_" - ñèìâîë íà÷àëà íîìåðíîãî ïîñòôèêñà
449 begin
450 if i = Length(name) then
451 begin // Íåò öèôð â êîíöå ñòðîêè
452 Exit;
453 end
454 else
455 begin
456 j := i + 1;
457 while (j <= Length(name)) and (name[j] <> '.') do inc(j);
458 NNF_PureName := Copy(name, 1, i);
459 NNF_PureExt := Copy(name, j);
460 name := Copy(name, i + 1, j - i - 1);
461 Break;
462 end;
463 end;
465 // Íå ïåðåâåñòè â ÷èñëî:
466 if not TryStrToInt(name, NNF_FirstNum) then
467 Exit;
469 NNF_CurrentNum := 0;
471 Result := True;
472 end;
475 function g_Texture_NumNameFindNext(var newName: String): Byte;
476 begin
477 if (NNF_PureName = '') or (NNF_CurrentNum < 0) then
478 begin
479 newName := '';
480 Result := NNF_NO_NAME;
481 Exit;
482 end;
484 newName := NNF_PureName + IntToStr(NNF_CurrentNum) + NNF_PureExt;
486 if NNF_CurrentNum < NNF_FirstNum then
487 Result := NNF_NAME_BEFORE
488 else
489 if NNF_CurrentNum > NNF_FirstNum then
490 Result := NNF_NAME_AFTER
491 else
492 Result := NNF_NAME_EQUALS;
494 Inc(NNF_CurrentNum);
495 end;
498 // ////////////////////////////////////////////////////////////////////////// //
499 function panelTypeToTag (panelType: Word): Integer;
500 begin
501 case panelType of
502 PANEL_WALL: result := GridTagWall; // gWalls
503 PANEL_OPENDOOR, PANEL_CLOSEDOOR: result := GridTagDoor; // gWalls
504 PANEL_BACK: result := GridTagBack; // gRenderBackgrounds
505 PANEL_FORE: result := GridTagFore; // gRenderForegrounds
506 PANEL_WATER: result := GridTagWater; // gWater
507 PANEL_ACID1: result := GridTagAcid1; // gAcid1
508 PANEL_ACID2: result := GridTagAcid2; // gAcid2
509 PANEL_STEP: result := GridTagStep; // gSteps
510 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: result := GridTagLift; // gLifts -- this is for all lifts
511 PANEL_BLOCKMON: result := GridTagBlockMon; // gBlockMon -- this is for all blockmons
512 else result := GridTagInvalid;
513 end;
514 end;
517 class function TBinHeapPanelDrawCmp.less (const a, b: TPanel): Boolean; inline;
518 begin
519 if (a.tag < b.tag) then begin result := true; exit; end;
520 if (a.tag > b.tag) then begin result := false; exit; end;
521 result := (a.arrIdx < b.arrIdx);
522 end;
524 var
525 TextNameHash: THashStrInt = nil; // key: texture name; value: index in `Textures`
526 BadTextNameHash: THashStrInt = nil; // set; so we won't spam with non-existing texture messages
527 RespawnPoints: array of TRespawnPoint;
528 FlagPoints: array[FLAG_RED..FLAG_BLUE] of PFlagPoint;
529 //DOMFlagPoints: Array of TFlagPoint;
532 procedure g_Map_ProfilersBegin ();
533 begin
534 if (profMapCollision = nil) then profMapCollision := TProfiler.Create('COLSOLID', g_profile_history_size);
535 if (profMapCollision <> nil) then profMapCollision.mainBegin(g_profile_collision);
536 // create sections
537 if g_profile_collision and (profMapCollision <> nil) then
538 begin
539 profMapCollision.sectionBegin('*solids');
540 profMapCollision.sectionEnd();
541 profMapCollision.sectionBegin('liquids');
542 profMapCollision.sectionEnd();
543 end;
544 end;
546 procedure g_Map_ProfilersEnd ();
547 begin
548 if (profMapCollision <> nil) then profMapCollision.mainEnd();
549 end;
552 // wall index in `gWalls` or -1
553 function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
554 var
555 ex, ey: Integer;
556 begin
557 result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, (GridTagWall or GridTagDoor));
558 if (result <> nil) then
559 begin
560 if (hitx <> nil) then hitx^ := ex;
561 if (hity <> nil) then hity^ := ey;
562 end
563 else
564 begin
565 if (hitx <> nil) then hitx^ := x1;
566 if (hity <> nil) then hity^ := y1;
567 end;
568 end;
570 // returns panel or nil
571 function g_Map_traceToNearest (x0, y0, x1, y1: Integer; tag: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
572 var
573 ex, ey: Integer;
574 begin
575 result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, tag);
576 if (result <> nil) then
577 begin
578 if (hitx <> nil) then hitx^ := ex;
579 if (hity <> nil) then hity^ := ey;
580 end
581 else
582 begin
583 if (hitx <> nil) then hitx^ := x1;
584 if (hity <> nil) then hity^ := y1;
585 end;
586 end;
589 function xxPanAtPointChecker (pan: TPanel; panelType: Word): Boolean; inline;
590 begin
591 if ((pan.tag and GridTagLift) <> 0) then
592 begin
593 // stop if the lift of the right type
594 result :=
595 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
596 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
597 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
598 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT)));
599 exit;
600 end;
601 result := true; // otherwise, stop anyway, 'cause `forEachAtPoint()` is guaranteed to call this only for correct panels
602 end;
604 function g_Map_HasAnyPanelAtPoint (x, y: Integer; panelType: Word): Boolean;
605 var
606 tagmask: Integer = 0;
607 mwit: PPanel;
608 it: TPanelGrid.Iter;
609 begin
610 result := false;
612 if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor);
613 if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater;
614 if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1;
615 if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2;
616 if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep;
617 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift;
618 if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon;
620 if (tagmask = 0) then exit;// just in case
622 if ((tagmask and GridTagLift) <> 0) then
623 begin
624 // slow
625 it := mapGrid.forEachAtPoint(x, y, tagmask);
626 for mwit in it do if (xxPanAtPointChecker(mwit^, PanelType)) then begin result := true; break; end;
627 end
628 else
629 begin
630 // fast
631 it := mapGrid.forEachAtPoint(x, y, tagmask, false, true);
632 result := (it.length <> 0); // firsthit
633 end;
634 it.release();
635 end;
638 function g_Map_PanelAtPoint (x, y: Integer; tagmask: Integer=-1): TPanel;
639 var
640 it: TPanelGrid.Iter;
641 begin
642 result := nil;
643 if (tagmask = 0) then exit;
644 it := mapGrid.forEachAtPoint(x, y, tagmask, false, true); // firsthit
645 if (it.length <> 0) then result := it.first^;
646 it.release();
647 end;
650 function g_Map_IsSpecialTexture(Texture: String): Boolean;
651 begin
652 Result := (Texture = TEXTURE_NAME_WATER) or
653 (Texture = TEXTURE_NAME_ACID1) or
654 (Texture = TEXTURE_NAME_ACID2);
655 end;
657 procedure CreateDoorMap();
658 var
659 PanelArray: Array of record
660 X, Y: Integer;
661 Width, Height: Word;
662 Active: Boolean;
663 PanelID: DWORD;
664 end;
665 a, b, c, m, i, len: Integer;
666 ok: Boolean;
667 begin
668 if gWalls = nil then
669 Exit;
671 i := 0;
672 len := 128;
673 SetLength(PanelArray, len);
675 for a := 0 to High(gWalls) do
676 if gWalls[a].Door then
677 begin
678 PanelArray[i].X := gWalls[a].X;
679 PanelArray[i].Y := gWalls[a].Y;
680 PanelArray[i].Width := gWalls[a].Width;
681 PanelArray[i].Height := gWalls[a].Height;
682 PanelArray[i].Active := True;
683 PanelArray[i].PanelID := a;
685 i := i + 1;
686 if i = len then
687 begin
688 len := len + 128;
689 SetLength(PanelArray, len);
690 end;
691 end;
693 // Íåò äâåðåé:
694 if i = 0 then
695 begin
696 PanelArray := nil;
697 Exit;
698 end;
700 SetLength(gDoorMap, 0);
702 g_Game_SetLoadingText(_lc[I_LOAD_DOOR_MAP], i-1, False);
704 for a := 0 to i-1 do
705 if PanelArray[a].Active then
706 begin
707 PanelArray[a].Active := False;
708 m := Length(gDoorMap);
709 SetLength(gDoorMap, m+1);
710 SetLength(gDoorMap[m], 1);
711 gDoorMap[m, 0] := PanelArray[a].PanelID;
712 ok := True;
714 while ok do
715 begin
716 ok := False;
718 for b := 0 to i-1 do
719 if PanelArray[b].Active then
720 for c := 0 to High(gDoorMap[m]) do
721 if {((gRenderWalls[PanelArray[b].RenderPanelID].TextureID = gRenderWalls[gDoorMap[m, c]].TextureID) or
722 gRenderWalls[PanelArray[b].RenderPanelID].Hide or gRenderWalls[gDoorMap[m, c]].Hide) and}
723 g_CollideAround(PanelArray[b].X, PanelArray[b].Y,
724 PanelArray[b].Width, PanelArray[b].Height,
725 gWalls[gDoorMap[m, c]].X,
726 gWalls[gDoorMap[m, c]].Y,
727 gWalls[gDoorMap[m, c]].Width,
728 gWalls[gDoorMap[m, c]].Height) then
729 begin
730 PanelArray[b].Active := False;
731 SetLength(gDoorMap[m],
732 Length(gDoorMap[m])+1);
733 gDoorMap[m, High(gDoorMap[m])] := PanelArray[b].PanelID;
734 ok := True;
735 Break;
736 end;
737 end;
739 g_Game_StepLoading();
740 end;
742 PanelArray := nil;
743 end;
745 procedure CreateLiftMap();
746 var
747 PanelArray: Array of record
748 X, Y: Integer;
749 Width, Height: Word;
750 Active: Boolean;
751 end;
752 a, b, c, len, i, j: Integer;
753 ok: Boolean;
754 begin
755 if gLifts = nil then
756 Exit;
758 len := Length(gLifts);
759 SetLength(PanelArray, len);
761 for a := 0 to len-1 do
762 begin
763 PanelArray[a].X := gLifts[a].X;
764 PanelArray[a].Y := gLifts[a].Y;
765 PanelArray[a].Width := gLifts[a].Width;
766 PanelArray[a].Height := gLifts[a].Height;
767 PanelArray[a].Active := True;
768 end;
770 SetLength(gLiftMap, len);
771 i := 0;
773 g_Game_SetLoadingText(_lc[I_LOAD_LIFT_MAP], len-1, False);
775 for a := 0 to len-1 do
776 if PanelArray[a].Active then
777 begin
778 PanelArray[a].Active := False;
779 SetLength(gLiftMap[i], 32);
780 j := 0;
781 gLiftMap[i, j] := a;
782 ok := True;
784 while ok do
785 begin
786 ok := False;
787 for b := 0 to len-1 do
788 if PanelArray[b].Active then
789 for c := 0 to j do
790 if g_CollideAround(PanelArray[b].X,
791 PanelArray[b].Y,
792 PanelArray[b].Width,
793 PanelArray[b].Height,
794 PanelArray[gLiftMap[i, c]].X,
795 PanelArray[gLiftMap[i, c]].Y,
796 PanelArray[gLiftMap[i, c]].Width,
797 PanelArray[gLiftMap[i, c]].Height) then
798 begin
799 PanelArray[b].Active := False;
800 j := j+1;
801 if j > High(gLiftMap[i]) then
802 SetLength(gLiftMap[i],
803 Length(gLiftMap[i])+32);
805 gLiftMap[i, j] := b;
806 ok := True;
808 Break;
809 end;
810 end;
812 SetLength(gLiftMap[i], j+1);
813 i := i+1;
815 g_Game_StepLoading();
816 end;
818 SetLength(gLiftMap, i);
820 PanelArray := nil;
821 end;
823 function CreatePanel (PanelRec: TDynRecord; AddTextures: TAddTextureArray; CurTex: Integer): Integer;
824 var
825 len: Integer;
826 panels: ^TPanelArray;
827 pan: TPanel;
828 pguid: Integer;
829 begin
830 Result := -1;
832 case PanelRec.PanelType of
833 PANEL_WALL, PANEL_OPENDOOR, PANEL_CLOSEDOOR: panels := @gWalls;
834 PANEL_BACK: panels := @gRenderBackgrounds;
835 PANEL_FORE: panels := @gRenderForegrounds;
836 PANEL_WATER: panels := @gWater;
837 PANEL_ACID1: panels := @gAcid1;
838 PANEL_ACID2: panels := @gAcid2;
839 PANEL_STEP: panels := @gSteps;
840 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: panels := @gLifts;
841 PANEL_BLOCKMON: panels := @gBlockMon;
842 else exit;
843 end;
845 len := Length(panels^);
846 SetLength(panels^, len+1);
848 pguid := Length(panByGUID);
849 SetLength(panByGUID, pguid+1); //FIXME!
850 pan := TPanel.Create(PanelRec, AddTextures, CurTex, Textures, pguid);
851 assert(pguid >= 0);
852 assert(pguid < Length(panByGUID));
853 panByGUID[pguid] := pan;
854 panels^[len] := pan;
855 pan.arrIdx := len;
856 pan.proxyId := -1;
857 pan.tag := panelTypeToTag(PanelRec.PanelType);
859 PanelRec.user['panel_guid'] := pguid;
861 //result := len;
862 result := pguid;
863 end;
866 function CreateNullTexture(RecName: String): Integer;
867 begin
868 RecName := toLowerCase1251(RecName);
869 if (TextNameHash = nil) then TextNameHash := THashStrInt.Create();
870 if TextNameHash.get(RecName, result) then exit; // i found her!
872 SetLength(Textures, Length(Textures) + 1);
873 Textures[High(Textures)].TextureName := RecName;
874 Textures[High(Textures)].FullName := '';
875 Result := High(Textures);
876 TextNameHash.put(RecName, result);
877 end;
880 function extractWadName (resourceName: string): string;
881 var
882 posN: Integer;
883 begin
884 posN := Pos(':', resourceName);
885 if posN > 0 then
886 Result:= Copy(resourceName, 0, posN-1)
887 else
888 Result := '';
889 end;
892 procedure addResToExternalResList (res: AnsiString);
893 var
894 uname: AnsiString;
895 f: Integer;
896 fi: TDiskFileInfo;
897 begin
898 if g_Game_IsClient or not g_Game_IsNet then exit;
899 if (length(res) = 0) then exit; // map wad
900 //res := extractWadName(res);
901 //if (length(res) = 0) then exit; // map wad
902 uname := toLowerCase1251(res);
903 // do not add duplicates
904 for f := 0 to High(gExternalResources) do
905 begin
906 if (gExternalResources[f].userName = uname) then exit;
907 end;
908 //writeln('***(000) addResToExternalResList: res=[', res, ']');
909 // add new resource
910 fi.userName := uname;
911 if not findFileCI(res) then exit;
912 //writeln('***(001) addResToExternalResList: res=[', res, ']');
913 fi.diskName := res;
914 if (not GetDiskFileInfo(res, fi)) then
915 begin
916 fi.tag := -1;
917 end
918 else
919 begin
920 //writeln('***(002) addResToExternalResList: res=[', res, ']');
921 fi.tag := 0; // non-zero means "cannot caclucate hash"
922 try
923 fi.hash := MD5File(fi.diskName);
924 except
925 fi.tag := -1;
926 end;
927 end;
928 //e_LogWritefln('addext: res=[%s]; uname=[%s]; diskName=[%s]', [res, fi.userName, fi.diskName]);
929 SetLength(gExternalResources, length(gExternalResources)+1);
930 gExternalResources[High(gExternalResources)] := fi;
931 end;
934 procedure compactExtResList ();
935 var
936 src, dest: Integer;
937 begin
938 src := 0;
939 dest := 0;
940 for src := 0 to High(gExternalResources) do
941 begin
942 if (gExternalResources[src].tag = 0) then
943 begin
944 // copy it
945 if (dest <> src) then gExternalResources[dest] := gExternalResources[src];
946 Inc(dest);
947 end;
948 end;
949 if (dest <> length(gExternalResources)) then SetLength(gExternalResources, dest);
950 end;
953 function GetReplacementWad (WadName: AnsiString): AnsiString;
954 begin
955 result := '';
956 if WadName <> '' then
957 begin
958 result := WadName;
959 if g_Game_IsClient then result := g_Res_FindReplacementWad(WadName);
960 if (result = WadName) then result := e_FindWad(WadDirs, result)
961 end;
962 end;
965 procedure generateExternalResourcesList (map: TDynRecord);
966 begin
967 SetLength(gExternalResources, 0);
968 addResToExternalResList(GetReplacementWad(g_ExtractWadName(map.MusicName)));
969 addResToExternalResList(GetReplacementWad(g_ExtractWadName(map.SkyName)));
970 end;
972 function CreateTexture (RecName: AnsiString; Map: String; log: Boolean): Integer;
973 var
974 HName: AnsiString;
975 WAD, WADz: TWADFile;
976 WADName, ResName: String;
977 ResData, ReszData: Pointer;
978 ResLen, ReszLen: Integer;
979 cfg: TConfig;
980 id: Integer;
981 begin
982 Result := -1;
983 HName := toLowerCase1251(RecName);
984 if (TextNameHash = nil) then
985 TextNameHash := THashStrInt.Create();
986 if TextNameHash.get(HName, Result) then
987 begin
988 // e_LogWritefln('CreateTexture: found loaded %s', [Result]);
989 end
990 else
991 begin
992 Result := -1;
993 if (BadTextNameHash = nil) or not BadTextNameHash.has(HName) then
994 begin
995 case RecName of
996 TEXTURE_NAME_WATER, TEXTURE_NAME_ACID1, TEXTURE_NAME_ACID2:
997 begin
998 SetLength(Textures, Length(Textures) + 1);
999 Textures[High(Textures)].FullName := RecName;
1000 Textures[High(Textures)].TextureName := RecName;
1001 Result := High(Textures);
1002 TextNameHash.put(RecName, result);
1003 end
1004 else
1005 WADName := GetReplacementWad(g_ExtractWadName(RecName));
1006 if (WADName <> '') then
1007 addResToExternalResList(WADName);
1008 if WADName = '' then
1009 WADName := Map;
1010 ResName := g_ExtractFilePathName(RecName);
1011 WAD := TWADFile.Create();
1012 if WAD.ReadFile(WadName) then
1013 begin
1014 if WAD.GetResource(ResName, ResData, ResLen, log) then
1015 begin
1016 if IsWadData(ResData, ResLen) then
1017 begin
1018 WADz := TWADFile.Create();
1019 if WADz.ReadMemory(ResData, ResLen) then
1020 begin
1021 if WADz.GetResource('TEXT/ANIM', ReszData, ReszLen) then
1022 begin
1023 cfg := TConfig.CreateMem(ReszData, ReszLen);
1024 if cfg <> nil then
1025 begin
1026 SetLength(Textures, Length(Textures) + 1);
1027 Textures[High(Textures)].TextureName := RecName;
1028 Textures[High(Textures)].FullName := WadName + ':' + ResName;
1029 Textures[High(Textures)].FramesCount := cfg.ReadInt('', 'framecount', 0);
1030 Textures[High(Textures)].Speed := cfg.ReadInt('', 'waitcount', 0);
1031 Result := High(Textures);
1032 TextNameHash.put(HName, result);
1033 cfg.Free;
1034 end;
1035 FreeMem(ReszData);
1036 end
1037 end;
1038 WADz.Free;
1039 end
1040 else
1041 begin
1042 SetLength(Textures, Length(Textures) + 1);
1043 Textures[High(Textures)].FullName := WADName + ':' + ResName;
1044 Textures[High(Textures)].TextureName := RecName;
1045 Result := High(Textures);
1046 TextNameHash.put(HName, result);
1047 end;
1048 FreeMem(ResData);
1049 end
1050 end;
1051 WAD.Free;
1052 end
1053 end;
1054 end;
1055 if Result < 0 then
1056 begin
1057 if (BadTextNameHash = nil) then
1058 BadTextNameHash := THashStrInt.Create();
1059 if log and (not BadTextNameHash.get(HName, id)) then
1060 e_WriteLog(Format('Error loading texture %s', [RecName]), TMsgType.Warning);
1061 BadTextNameHash.put(HName, -1);
1062 end
1063 end;
1065 procedure CreateItem(Item: TDynRecord);
1066 begin
1067 if g_Game_IsClient then Exit;
1069 if (not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) and
1070 ByteBool(Item.Options and ITEM_OPTION_ONLYDM) then
1071 Exit;
1073 g_Items_Create(Item.X, Item.Y, Item.ItemType, ByteBool(Item.Options and ITEM_OPTION_FALL),
1074 gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF, GM_COOP]);
1075 end;
1077 procedure CreateArea(Area: TDynRecord);
1078 var
1079 a: Integer;
1080 begin
1081 case Area.AreaType of
1082 AREA_DMPOINT, AREA_PLAYERPOINT1, AREA_PLAYERPOINT2,
1083 AREA_REDTEAMPOINT, AREA_BLUETEAMPOINT:
1084 begin
1085 SetLength(RespawnPoints, Length(RespawnPoints)+1);
1086 with RespawnPoints[High(RespawnPoints)] do
1087 begin
1088 X := Area.X;
1089 Y := Area.Y;
1090 Direction := TDirection(Area.Direction);
1092 case Area.AreaType of
1093 AREA_DMPOINT: PointType := RESPAWNPOINT_DM;
1094 AREA_PLAYERPOINT1: PointType := RESPAWNPOINT_PLAYER1;
1095 AREA_PLAYERPOINT2: PointType := RESPAWNPOINT_PLAYER2;
1096 AREA_REDTEAMPOINT: PointType := RESPAWNPOINT_RED;
1097 AREA_BLUETEAMPOINT: PointType := RESPAWNPOINT_BLUE;
1098 end;
1099 end;
1100 end;
1102 AREA_REDFLAG, AREA_BLUEFLAG:
1103 begin
1104 if Area.AreaType = AREA_REDFLAG then a := FLAG_RED else a := FLAG_BLUE;
1106 if FlagPoints[a] <> nil then Exit;
1108 New(FlagPoints[a]);
1110 with FlagPoints[a]^ do
1111 begin
1112 X := Area.X-FLAGRECT.X;
1113 Y := Area.Y-FLAGRECT.Y;
1114 Direction := TDirection(Area.Direction);
1115 end;
1117 with gFlags[a] do
1118 begin
1119 Obj.Rect := FLAGRECT;
1120 g_Map_ResetFlag(a);
1121 end;
1122 end;
1124 AREA_DOMFLAG:
1125 begin
1126 {SetLength(DOMFlagPoints, Length(DOMFlagPoints)+1);
1127 with DOMFlagPoints[High(DOMFlagPoints)] do
1128 begin
1129 X := Area.X;
1130 Y := Area.Y;
1131 Direction := TDirection(Area.Direction);
1132 end;
1134 g_Map_CreateFlag(DOMFlagPoints[High(DOMFlagPoints)], FLAG_DOM, FLAG_STATE_NORMAL);}
1135 end;
1136 end;
1137 end;
1139 function CreateTrigger (amapIdx: Integer; Trigger: TDynRecord; atpanid, atrigpanid: Integer): Integer;
1140 var
1141 _trigger: TTrigger;
1142 tp: TPanel;
1143 begin
1144 result := -1;
1145 if g_Game_IsClient and not (Trigger.TriggerType in [TRIGGER_SOUND, TRIGGER_MUSIC]) then Exit;
1147 with _trigger do
1148 begin
1149 mapId := Trigger.id;
1150 mapIndex := amapIdx;
1151 X := Trigger.X;
1152 Y := Trigger.Y;
1153 Width := Trigger.Width;
1154 Height := Trigger.Height;
1155 Enabled := Trigger.Enabled;
1156 TexturePanelGUID := atpanid;
1157 TriggerType := Trigger.TriggerType;
1158 ActivateType := Trigger.ActivateType;
1159 Keys := Trigger.Keys;
1160 trigPanelGUID := atrigpanid;
1161 // HACK: used in TPanel.CanChangeTexture. maybe there's a better way?
1162 if TexturePanelGUID <> -1 then
1163 begin
1164 tp := g_Map_PanelByGUID(TexturePanelGUID);
1165 if (tp <> nil) then tp.hasTexTrigger := True;
1166 end;
1167 end;
1169 result := Integer(g_Triggers_Create(_trigger, Trigger));
1170 end;
1172 procedure CreateMonster(monster: TDynRecord);
1173 var
1174 a: Integer;
1175 mon: TMonster;
1176 begin
1177 if g_Game_IsClient then Exit;
1179 if (gGameSettings.GameType = GT_SINGLE)
1180 or LongBool(gGameSettings.Options and GAME_OPTION_MONSTERS) then
1181 begin
1182 mon := g_Monsters_Create(monster.MonsterType, monster.X, monster.Y, TDirection(monster.Direction));
1184 if gTriggers <> nil then
1185 begin
1186 for a := 0 to High(gTriggers) do
1187 begin
1188 if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
1189 begin
1190 //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
1191 if (gTriggers[a].trigDataRec.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
1192 end;
1193 end;
1194 end;
1196 if monster.MonsterType <> MONSTER_BARREL then Inc(gTotalMonsters);
1197 end;
1198 end;
1200 procedure g_Map_ReAdd_DieTriggers();
1202 function monsDieTrig (mon: TMonster): Boolean;
1203 var
1204 a: Integer;
1205 //tw: TStrTextWriter;
1206 begin
1207 result := false; // don't stop
1208 mon.ClearTriggers();
1209 for a := 0 to High(gTriggers) do
1210 begin
1211 if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
1212 begin
1213 //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
1215 tw := TStrTextWriter.Create();
1216 try
1217 gTriggers[a].trigData.writeTo(tw);
1218 e_LogWritefln('=== trigger #%s ==='#10'%s'#10'---', [a, tw.str]);
1219 finally
1220 tw.Free();
1221 end;
1223 if (gTriggers[a].trigDataRec.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
1224 end;
1225 end;
1226 end;
1228 begin
1229 if g_Game_IsClient then Exit;
1231 g_Mons_ForEach(monsDieTrig);
1232 end;
1234 procedure mapCreateGrid ();
1235 var
1236 mapX0: Integer = $3fffffff;
1237 mapY0: Integer = $3fffffff;
1238 mapX1: Integer = -$3fffffff;
1239 mapY1: Integer = -$3fffffff;
1241 procedure calcBoundingBox (constref panels: TPanelArray);
1242 var
1243 idx: Integer;
1244 pan: TPanel;
1245 begin
1246 for idx := 0 to High(panels) do
1247 begin
1248 pan := panels[idx];
1249 if not pan.visvalid then continue;
1250 if (pan.Width < 1) or (pan.Height < 1) then continue;
1251 if (mapX0 > pan.x0) then mapX0 := pan.x0;
1252 if (mapY0 > pan.y0) then mapY0 := pan.y0;
1253 if (mapX1 < pan.x1) then mapX1 := pan.x1;
1254 if (mapY1 < pan.y1) then mapY1 := pan.y1;
1255 end;
1256 end;
1258 procedure addPanelsToGrid (constref panels: TPanelArray);
1259 var
1260 idx: Integer;
1261 pan: TPanel;
1262 newtag: Integer;
1263 begin
1264 //tag := panelTypeToTag(tag);
1265 for idx := 0 to High(panels) do
1266 begin
1267 pan := panels[idx];
1268 if not pan.visvalid then continue;
1269 if (pan.proxyId <> -1) then
1270 begin
1271 {$IF DEFINED(D2F_DEBUG)}
1272 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);
1273 {$ENDIF}
1274 continue;
1275 end;
1276 case pan.PanelType of
1277 PANEL_WALL: newtag := GridTagWall;
1278 PANEL_OPENDOOR, PANEL_CLOSEDOOR: newtag := GridTagDoor;
1279 PANEL_BACK: newtag := GridTagBack;
1280 PANEL_FORE: newtag := GridTagFore;
1281 PANEL_WATER: newtag := GridTagWater;
1282 PANEL_ACID1: newtag := GridTagAcid1;
1283 PANEL_ACID2: newtag := GridTagAcid2;
1284 PANEL_STEP: newtag := GridTagStep;
1285 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: newtag := GridTagLift;
1286 PANEL_BLOCKMON: newtag := GridTagBlockMon;
1287 else continue; // oops
1288 end;
1289 pan.tag := newtag;
1291 pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, newtag);
1292 // "enabled" flag has meaning only for doors and walls (engine assumes it); but meh...
1293 mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
1294 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
1296 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
1297 begin
1298 e_WriteLog(Format('INSERTED wall #%d(%d) enabled (%d)', [Integer(idx), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId])]), MSG_NOTIFY);
1299 end;
1301 {$ENDIF}
1302 end;
1303 end;
1305 begin
1306 mapGrid.Free();
1307 mapGrid := nil;
1309 calcBoundingBox(gWalls);
1310 calcBoundingBox(gRenderBackgrounds);
1311 calcBoundingBox(gRenderForegrounds);
1312 calcBoundingBox(gWater);
1313 calcBoundingBox(gAcid1);
1314 calcBoundingBox(gAcid2);
1315 calcBoundingBox(gSteps);
1316 calcBoundingBox(gLifts);
1317 calcBoundingBox(gBlockMon);
1319 e_LogWritefln('map dimensions: (%d,%d)-(%d,%d); editor size:(0,0)-(%d,%d)', [mapX0, mapY0, mapX1, mapY1, gMapInfo.Width, gMapInfo.Height]);
1321 if (mapX0 > 0) then mapX0 := 0;
1322 if (mapY0 > 0) then mapY0 := 0;
1324 if (mapX1 < gMapInfo.Width-1) then mapX1 := gMapInfo.Width-1;
1325 if (mapY1 < gMapInfo.Height-1) then mapY1 := gMapInfo.Height-1;
1327 mapGrid := TPanelGrid.Create(mapX0-128, mapY0-128, mapX1-mapX0+1+128*2, mapY1-mapY0+1+128*2);
1328 //mapGrid := TPanelGrid.Create(0, 0, gMapInfo.Width, gMapInfo.Height);
1330 addPanelsToGrid(gWalls);
1331 addPanelsToGrid(gRenderBackgrounds);
1332 addPanelsToGrid(gRenderForegrounds);
1333 addPanelsToGrid(gWater);
1334 addPanelsToGrid(gAcid1);
1335 addPanelsToGrid(gAcid2);
1336 addPanelsToGrid(gSteps);
1337 addPanelsToGrid(gLifts); // it doesn't matter which LIFT type is used here
1338 addPanelsToGrid(gBlockMon);
1340 mapGrid.dumpStats();
1342 g_Mons_InitTree(mapGrid.gridX0, mapGrid.gridY0, mapGrid.gridWidth, mapGrid.gridHeight);
1343 end;
1346 function g_Map_Load(Res: String): Boolean;
1347 const
1348 DefaultMusRes = 'Standart.wad:STDMUS\MUS1';
1349 DefaultSkyRes = 'Standart.wad:STDSKY\SKY0';
1350 type
1351 PTRec = ^TTRec;
1352 TTRec = record
1353 //TexturePanel: Integer;
1354 tnum: Integer;
1355 id: Integer;
1356 trigrec: TDynRecord;
1357 // texture pane;
1358 texPanelIdx: Integer;
1359 texPanel: TDynRecord;
1360 // "action" panel
1361 actPanelIdx: Integer;
1362 actPanel: TDynRecord;
1363 end;
1364 var
1365 WAD, TestWAD: TWADFile;
1366 //mapReader: TDynRecord = nil;
1367 mapTextureList: TDynField = nil; //TTexturesRec1Array; tagInt: texture index
1368 panels: TDynField = nil; //TPanelsRec1Array;
1369 items: TDynField = nil; //TItemsRec1Array;
1370 monsters: TDynField = nil; //TMonsterRec1Array;
1371 areas: TDynField = nil; //TAreasRec1Array;
1372 triggers: TDynField = nil; //TTriggersRec1Array;
1373 b, c, k: Integer;
1374 PanelID: DWORD;
1375 AddTextures: TAddTextureArray;
1376 TriggersTable: array of TTRec;
1377 FileName, mapResName, TexName, s: AnsiString;
1378 Data: Pointer;
1379 Len: Integer;
1380 ok: Boolean;
1381 CurTex, ntn: Integer;
1382 rec, texrec: TDynRecord;
1383 pttit: PTRec;
1384 pannum, trignum, cnt, tgpid: Integer;
1385 stt: UInt64;
1386 moveSpeed{, moveStart, moveEnd}: TDFPoint;
1387 //moveActive: Boolean;
1388 pan: TPanel;
1389 mapOk: Boolean = false;
1390 usedTextures: THashStrInt = nil; // key: mapTextureList
1391 begin
1392 mapGrid.Free();
1393 mapGrid := nil;
1394 TestWAD := nil;
1395 Data := nil;
1397 //gCurrentMap.Free();
1398 //gCurrentMap := nil;
1400 panByGUID := nil;
1402 Result := False;
1403 gMapInfo.Map := Res;
1404 TriggersTable := nil;
1405 //mapReader := nil;
1407 sfsGCDisable(); // temporary disable removing of temporary volumes
1408 try
1409 // Çàãðóçêà WAD (åñëè ó íàñ íåò óæå çàãðóæåíîé êàðòû)
1410 if (gCurrentMap = nil) then
1411 begin
1412 FileName := g_ExtractWadName(Res);
1413 e_LogWritefln('Loading map WAD [%s] (res=[%s])', [FileName, Res], TMsgType.Notify);
1414 g_Game_SetLoadingText(_lc[I_LOAD_WAD_FILE], 0, False);
1416 WAD := TWADFile.Create();
1417 if not WAD.ReadFile(FileName) then
1418 begin
1419 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_WAD], [FileName]));
1420 WAD.Free();
1421 Exit;
1422 end;
1424 if gTestMap <> '' then
1425 begin
1426 s := g_ExtractWadName(gTestMap);
1427 TestWAD := TWADFile.Create();
1428 if not TestWAD.ReadFile(s) then
1429 begin
1430 g_SimpleError(Format(_lc[I_GAME_ERROR_MAP_WAD], [s]));
1431 TestWAD.Free();
1432 TestWAD := nil;
1433 end;
1434 end;
1436 if TestWAD <> nil then
1437 begin
1438 mapResName := g_ExtractFileName(gTestMap);
1439 if not TestWAD.GetMapResource(mapResName, Data, Len) then
1440 begin
1441 g_SimpleError(Format(_lc[I_GAME_ERROR_MAP_RES], [mapResName]));
1442 Data := nil;
1443 end else
1444 e_WriteLog('Using test map: '+gTestMap, TMsgType.Notify);
1445 TestWAD.Free();
1446 TestWAD := nil;
1447 end;
1449 if Data = nil then
1450 begin
1451 //k8: why loader ignores path here?
1452 mapResName := g_ExtractFileName(Res);
1453 if not WAD.GetMapResource(mapResName, Data, Len) then
1454 begin
1455 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_RES], [mapResName]));
1456 WAD.Free();
1457 Exit;
1458 end;
1459 end;
1461 WAD.Free();
1463 if (Len < 4) then
1464 begin
1465 e_LogWritefln('invalid map file: ''%s''', [mapResName]);
1466 FreeMem(Data);
1467 exit;
1468 end;
1470 // Çàãðóçêà êàðòû:
1471 e_LogWritefln('Loading map: %s', [mapResName], TMsgType.Notify);
1472 g_Game_SetLoadingText(_lc[I_LOAD_MAP], 0, False);
1474 stt := getTimeMicro();
1476 try
1477 gCurrentMap := g_Map_ParseMap(Data, Len);
1478 except
1479 gCurrentMap.Free();
1480 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]));
1481 FreeMem(Data);
1482 gCurrentMapFileName := '';
1483 Exit;
1484 end;
1486 FreeMem(Data);
1488 if (gCurrentMap = nil) then
1489 begin
1490 e_LogWritefln('invalid map file: ''%s''', [mapResName]);
1491 gCurrentMapFileName := '';
1492 exit;
1493 end;
1494 end
1495 else
1496 begin
1497 stt := getTimeMicro();
1498 end;
1500 //gCurrentMap := mapReader;
1502 generateExternalResourcesList(gCurrentMap);
1503 mapTextureList := gCurrentMap['texture'];
1504 // get all other lists here too
1505 panels := gCurrentMap['panel'];
1506 triggers := gCurrentMap['trigger'];
1507 items := gCurrentMap['item'];
1508 areas := gCurrentMap['area'];
1509 monsters := gCurrentMap['monster'];
1511 // Çàãðóçêà îïèñàíèÿ êàðòû:
1512 e_WriteLog(' Reading map info...', TMsgType.Notify);
1513 g_Game_SetLoadingText(_lc[I_LOAD_MAP_HEADER], 0, False);
1515 with gMapInfo do
1516 begin
1517 Name := gCurrentMap.MapName;
1518 Description := gCurrentMap.MapDesc;
1519 Author := gCurrentMap.MapAuthor;
1520 MusicName := gCurrentMap.MusicName;
1521 SkyName := gCurrentMap.SkyName;
1522 Height := gCurrentMap.Height;
1523 Width := gCurrentMap.Width;
1524 end;
1526 // Çàãðóçêà òåêñòóð:
1527 g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], 0, False);
1528 // Äîáàâëåíèå òåêñòóð â Textures[]:
1529 if (mapTextureList <> nil) and (mapTextureList.count > 0) then
1530 begin
1531 e_WriteLog(' Loading textures:', TMsgType.Notify);
1532 g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], mapTextureList.count-1, False);
1534 // find used textures
1535 usedTextures := THashStrInt.Create();
1536 try
1537 if (panels <> nil) and (panels.count > 0) then
1538 begin
1539 for rec in panels do
1540 begin
1541 texrec := rec.TextureRec;
1542 if (texrec <> nil) then usedTextures.put(toLowerCase1251(texrec.Resource), 42);
1543 end;
1544 end;
1546 cnt := -1;
1547 for rec in mapTextureList do
1548 begin
1549 Inc(cnt);
1550 if not usedTextures.has(toLowerCase1251(rec.Resource)) then
1551 begin
1552 rec.tagInt := -1; // just in case
1553 e_LogWritefln(' Unused texture #%d: %s', [cnt, rec.Resource]);
1554 end
1555 else
1556 begin
1557 {$IF DEFINED(D2F_DEBUG_TXLOAD)}
1558 e_LogWritefln(' Loading texture #%d: %s', [cnt, rec.Resource]);
1559 {$ENDIF}
1560 ntn := CreateTexture(rec.Resource, FileName, True);
1561 if ntn < 0 then
1562 begin
1563 if rec.Anim then
1564 g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_ANIM], [rec.Resource]))
1565 else
1566 g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_SIMPLE], [rec.Resource]));
1567 ntn := CreateNullTexture(rec.Resource)
1568 end;
1569 rec.tagInt := ntn; // remember texture number
1570 end;
1571 g_Game_StepLoading();
1572 end;
1573 finally
1574 usedTextures.Free();
1575 end;
1577 // set panel tagInt to texture index
1578 if (panels <> nil) then
1579 begin
1580 for rec in panels do
1581 begin
1582 texrec := rec.TextureRec;
1583 if (texrec = nil) then rec.tagInt := -1 else rec.tagInt := texrec.tagInt;
1584 end;
1585 end;
1586 end;
1589 // Çàãðóçêà òðèããåðîâ
1590 gTriggerClientID := 0;
1591 e_WriteLog(' Loading triggers...', TMsgType.Notify);
1592 g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS], 0, False);
1594 // Çàãðóçêà ïàíåëåé
1595 e_WriteLog(' Loading panels...', TMsgType.Notify);
1596 g_Game_SetLoadingText(_lc[I_LOAD_PANELS], 0, False);
1598 // check texture numbers for panels
1599 if (panels <> nil) and (panels.count > 0) then
1600 begin
1601 for rec in panels do
1602 begin
1603 if (rec.tagInt < 0) then
1604 begin
1605 e_WriteLog('error loading map: invalid texture index for panel', TMsgType.Fatal);
1606 result := false;
1607 gCurrentMap.Free();
1608 gCurrentMap := nil;
1609 gCurrentMapFileName := '';
1610 exit;
1611 end;
1612 end;
1613 end;
1615 // Ñîçäàíèå òàáëèöû òðèããåðîâ (ñîîòâåòñòâèå ïàíåëåé òðèããåðàì)
1616 if (triggers <> nil) and (triggers.count > 0) then
1617 begin
1618 e_WriteLog(' Setting up trigger table...', TMsgType.Notify);
1619 //SetLength(TriggersTable, triggers.count);
1620 g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS_TABLE], triggers.count-1, False);
1622 SetLength(TriggersTable, triggers.count);
1623 trignum := -1;
1624 for rec in triggers do
1625 begin
1626 Inc(trignum);
1627 pttit := @TriggersTable[trignum];
1628 pttit.trigrec := rec;
1629 // Ñìåíà òåêñòóðû (âîçìîæíî, êíîïêè)
1630 pttit.texPanelIdx := -1; // will be fixed later
1631 pttit.texPanel := rec.TexturePanelRec;
1632 // action panel
1633 pttit.actPanelIdx := -1;
1634 if (rec.trigRec <> nil) then pttit.actPanel := rec.trigRec.tgPanelRec else pttit.actPanel := nil;
1635 // set flag
1636 if (pttit.texPanel <> nil) then pttit.texPanel.userPanelTrigRef := true;
1637 if (pttit.actPanel <> nil) then pttit.actPanel.userPanelTrigRef := true;
1638 // update progress
1639 g_Game_StepLoading();
1640 end;
1641 end;
1643 // Ñîçäàåì ïàíåëè
1644 if (panels <> nil) and (panels.count > 0) then
1645 begin
1646 e_WriteLog(' Setting up trigger links...', TMsgType.Notify);
1647 g_Game_SetLoadingText(_lc[I_LOAD_LINK_TRIGGERS], panels.count-1, False);
1649 pannum := -1;
1650 for rec in panels do
1651 begin
1652 Inc(pannum);
1653 //e_LogWritefln('PANSTART: pannum=%s', [pannum]);
1654 texrec := nil;
1655 SetLength(AddTextures, 0);
1656 CurTex := -1;
1657 ok := false;
1659 if (mapTextureList <> nil) then
1660 begin
1661 texrec := rec.TextureRec;
1662 ok := (texrec <> nil);
1663 end;
1665 if ok then
1666 begin
1667 // Ñìîòðèì, ññûëàþòñÿ ëè íà ýòó ïàíåëü òðèããåðû.
1668 // Åñëè äà - òî íàäî ñîçäàòü åùå òåêñòóð
1669 ok := false;
1670 if (TriggersTable <> nil) and (mapTextureList <> nil) then
1671 begin
1672 if rec.userPanelTrigRef then
1673 begin
1674 // e_LogWritefln('trigref for panel %s', [pannum]);
1675 ok := True;
1676 end;
1677 end;
1678 end;
1680 if ok then
1681 begin
1682 // Åñòü ññûëêè òðèããåðîâ íà ýòó ïàíåëü
1683 s := texrec.Resource;
1685 // Ñïåö-òåêñòóðû çàïðåùåíû
1686 if g_Map_IsSpecialTexture(s) then
1687 begin
1688 ok := false
1689 end
1690 else
1691 begin
1692 // Îïðåäåëÿåì íàëè÷èå è ïîëîæåíèå öèôð â êîíöå ñòðîêè
1693 ok := g_Texture_NumNameFindStart(s);
1694 end;
1696 // Åñëè ok, çíà÷èò åñòü öèôðû â êîíöå.
1697 // Çàãðóæàåì òåêñòóðû ñ îñòàëüíûìè #
1698 if ok then
1699 begin
1700 k := NNF_NAME_BEFORE;
1701 // Öèêë ïî èçìåíåíèþ èìåíè òåêñòóðû
1702 while ok or (k = NNF_NAME_BEFORE) or (k = NNF_NAME_EQUALS) do
1703 begin
1704 k := g_Texture_NumNameFindNext(TexName);
1706 if (k = NNF_NAME_BEFORE) or (k = NNF_NAME_AFTER) then
1707 begin
1708 ok := CreateTexture(TexName, FileName, False) >= 0;
1710 // Îíà ñóùåñòâóåò. Çàíîñèì åå ID â ñïèñîê ïàíåëè
1711 if ok then
1712 begin
1714 for c := 0 to High(Textures) do
1715 begin
1716 if (Textures[c].TextureName = TexName) then
1717 begin
1718 SetLength(AddTextures, Length(AddTextures)+1);
1719 AddTextures[High(AddTextures)].Texture := c;
1720 break;
1721 end;
1722 end;
1724 if (TextNameHash <> nil) and TextNameHash.get(toLowerCase1251(TexName), c) then
1725 begin
1726 SetLength(AddTextures, Length(AddTextures)+1);
1727 AddTextures[High(AddTextures)].Texture := c;
1728 end;
1729 end;
1730 end
1731 else
1732 begin
1733 if k = NNF_NAME_EQUALS then
1734 begin
1735 // Çàíîñèì òåêóùóþ òåêñòóðó íà ñâîå ìåñòî
1736 SetLength(AddTextures, Length(AddTextures)+1);
1737 AddTextures[High(AddTextures)].Texture := rec.tagInt; // internal texture number, not map index
1738 CurTex := High(AddTextures);
1739 ok := true;
1740 end
1741 else // NNF_NO_NAME
1742 begin
1743 ok := false;
1744 end;
1745 end;
1746 end; // while ok...
1748 ok := true;
1749 end; // if ok - åñòü ñìåæíûå òåêñòóðû
1750 end; // if ok - ññûëàþòñÿ òðèããåðû
1752 if not ok then
1753 begin
1754 // Çàíîñèì òîëüêî òåêóùóþ òåêñòóðó
1755 SetLength(AddTextures, 1);
1756 AddTextures[0].Texture := rec.tagInt; // internal texture number, not map index
1757 CurTex := 0;
1758 end;
1760 //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);
1762 //e_LogWritefln('PANADD: pannum=%s', [pannum]);
1764 // Ñîçäàåì ïàíåëü è çàïîìèíàåì åå GUID
1765 //e_LogWritefln('new panel; tcount=%s; curtex=%s', [Length(AddTextures), CurTex]);
1766 PanelID := CreatePanel(rec, AddTextures, CurTex);
1767 //e_LogWritefln('panel #%s of type %s got guid #%s', [pannum, rec.PanelType, PanelID]);
1768 rec.userPanelId := PanelID; // remember game panel id, we'll fix triggers later
1770 // setup lifts
1771 moveSpeed := rec.moveSpeed;
1772 //moveStart := rec.moveStart;
1773 //moveEnd := rec.moveEnd;
1774 //moveActive := rec['move_active'].value;
1775 if not moveSpeed.isZero then
1776 begin
1777 SetLength(gMovingWallIds, Length(gMovingWallIds)+1);
1778 gMovingWallIds[High(gMovingWallIds)] := PanelID;
1779 //e_LogWritefln('found moving panel ''%s'' (idx=%s; id=%s)', [rec.id, pannum, PanelID]);
1780 end;
1782 //e_LogWritefln('PANEND: pannum=%s', [pannum]);
1784 g_Game_StepLoading();
1785 end;
1786 end;
1788 // ×èíèì ID'û ïàíåëåé, êîòîðûå èñïîëüçóþòñÿ â òðèããåðàõ
1789 for b := 0 to High(TriggersTable) do
1790 begin
1791 if (TriggersTable[b].texPanel <> nil) then TriggersTable[b].texPanelIdx := TriggersTable[b].texPanel.userPanelId;
1792 if (TriggersTable[b].actPanel <> nil) then TriggersTable[b].actPanelIdx := TriggersTable[b].actPanel.userPanelId;
1793 end;
1795 // create map grid, init other grids (for monsters, for example)
1796 e_WriteLog('Creating map grid', TMsgType.Notify);
1797 mapCreateGrid();
1799 // Åñëè íå LoadState, òî ñîçäàåì òðèããåðû
1800 if (triggers <> nil) and (panels <> nil) and (not gLoadGameMode) then
1801 begin
1802 e_LogWritefln(' Creating triggers (%d)...', [triggers.count]);
1803 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_TRIGGERS], 0, False);
1804 // Óêàçûâàåì òèï ïàíåëè, åñëè åñòü
1805 trignum := -1;
1806 for rec in triggers do
1807 begin
1808 Inc(trignum);
1809 tgpid := TriggersTable[trignum].actPanelIdx;
1810 //e_LogWritefln('creating trigger #%s; texpantype=%s; shotpantype=%s (%d,%d)', [trignum, b, c, TriggersTable[trignum].texPanIdx, TriggersTable[trignum].ShotPanelIdx]);
1811 TriggersTable[trignum].tnum := trignum;
1812 TriggersTable[trignum].id := CreateTrigger(trignum, rec, TriggersTable[trignum].texPanelIdx, tgpid);
1813 end;
1814 end;
1816 //FIXME: use hashtable!
1817 for pan in panByGUID do
1818 begin
1819 if (pan.endPosTrigId >= 0) and (pan.endPosTrigId < Length(TriggersTable)) then
1820 begin
1821 pan.endPosTrigId := TriggersTable[pan.endPosTrigId].id;
1822 end;
1823 if (pan.endSizeTrigId >= 0) and (pan.endSizeTrigId < Length(TriggersTable)) then
1824 begin
1825 pan.endSizeTrigId := TriggersTable[pan.endSizeTrigId].id;
1826 end;
1827 end;
1829 // Çàãðóçêà ïðåäìåòîâ
1830 e_WriteLog(' Loading items...', TMsgType.Notify);
1831 g_Game_SetLoadingText(_lc[I_LOAD_ITEMS], 0, False);
1833 // Åñëè íå LoadState, òî ñîçäàåì ïðåäìåòû
1834 if (items <> nil) and not gLoadGameMode then
1835 begin
1836 e_WriteLog(' Spawning items...', TMsgType.Notify);
1837 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_ITEMS], 0, False);
1838 for rec in items do CreateItem(rec);
1839 end;
1841 // Çàãðóçêà îáëàñòåé
1842 e_WriteLog(' Loading areas...', TMsgType.Notify);
1843 g_Game_SetLoadingText(_lc[I_LOAD_AREAS], 0, False);
1845 // Åñëè íå LoadState, òî ñîçäàåì îáëàñòè
1846 if areas <> nil then
1847 begin
1848 e_WriteLog(' Creating areas...', TMsgType.Notify);
1849 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_AREAS], 0, False);
1850 for rec in areas do CreateArea(rec);
1851 end;
1853 // Çàãðóçêà ìîíñòðîâ
1854 e_WriteLog(' Loading monsters...', TMsgType.Notify);
1855 g_Game_SetLoadingText(_lc[I_LOAD_MONSTERS], 0, False);
1857 gTotalMonsters := 0;
1859 // Åñëè íå LoadState, òî ñîçäàåì ìîíñòðîâ
1860 if (monsters <> nil) and not gLoadGameMode then
1861 begin
1862 e_WriteLog(' Spawning monsters...', TMsgType.Notify);
1863 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_MONSTERS], 0, False);
1864 for rec in monsters do CreateMonster(rec);
1865 end;
1867 //gCurrentMap := mapReader; // this will be our current map now
1868 gCurrentMapFileName := Res;
1869 //mapReader := nil;
1871 // Çàãðóçêà íåáà
1872 gMapInfo.SkyFullName := '';
1873 if (gMapInfo.SkyName <> '') then
1874 begin
1875 e_WriteLog(' Loading sky: ' + gMapInfo.SkyName, TMsgType.Notify);
1876 g_Game_SetLoadingText(_lc[I_LOAD_SKY], 0, False);
1877 gMapInfo.SkyFullName := e_GetResourcePath(WadDirs, gMapInfo.SkyName, g_ExtractWadName(Res));
1878 end;
1880 // Çàãðóçêà ìóçûêè
1881 ok := False;
1882 if gMapInfo.MusicName <> '' then
1883 begin
1884 e_WriteLog(' Loading music: ' + gMapInfo.MusicName, TMsgType.Notify);
1885 g_Game_SetLoadingText(_lc[I_LOAD_MUSIC], 0, False);
1887 s := e_GetResourcePath(WadDirs, gMapInfo.MusicName, g_ExtractWadName(Res));
1888 if g_Sound_CreateWADEx(gMapInfo.MusicName, s, True) then
1889 ok := True
1890 else
1891 g_FatalError(Format(_lc[I_GAME_ERROR_MUSIC], [s]));
1892 end;
1894 // Îñòàëüíûå óñòàíâêè
1895 CreateDoorMap();
1896 CreateLiftMap();
1898 g_Items_Init();
1899 g_Weapon_Init();
1900 g_Monsters_Init();
1902 // Åñëè íå LoadState, òî ñîçäàåì êàðòó ñòîëêíîâåíèé:
1903 if not gLoadGameMode then g_GFX_Init();
1905 // Ñáðîñ ëîêàëüíûõ ìàññèâîâ:
1906 mapTextureList := nil;
1907 panels := nil;
1908 items := nil;
1909 areas := nil;
1910 triggers := nil;
1911 TriggersTable := nil;
1912 AddTextures := nil;
1914 // Âêëþ÷àåì ìóçûêó, åñëè ýòî íå çàãðóçêà:
1915 if ok and (not gLoadGameMode) then
1916 begin
1917 gMusic.SetByName(gMapInfo.MusicName);
1918 gMusic.Play();
1919 end
1920 else
1921 begin
1922 gMusic.SetByName('');
1923 end;
1925 stt := getTimeMicro()-stt;
1926 e_LogWritefln('map loaded in %s.%s milliseconds', [Integer(stt div 1000), Integer(stt mod 1000)]);
1927 mapOk := true;
1928 finally
1929 sfsGCEnable(); // enable releasing unused volumes
1930 //mapReader.Free();
1931 e_UnpressAllKeys; // why not?
1932 if not mapOk then
1933 begin
1934 gCurrentMap.Free();
1935 gCurrentMap := nil;
1936 gCurrentMapFileName := '';
1937 end;
1938 end;
1940 compactExtResList();
1941 e_WriteLog('Done loading map.', TMsgType.Notify);
1942 Result := True;
1943 end;
1946 function g_Map_GetMapInfo(Res: String): TMapInfo;
1947 var
1948 WAD: TWADFile;
1949 mapReader: TDynRecord;
1950 //Header: TMapHeaderRec_1;
1951 FileName: String;
1952 Data: Pointer;
1953 Len: Integer;
1954 begin
1955 FillChar(Result, SizeOf(Result), 0);
1956 FileName := g_ExtractWadName(Res);
1958 WAD := TWADFile.Create();
1959 if not WAD.ReadFile(FileName) then
1960 begin
1961 WAD.Free();
1962 Exit;
1963 end;
1965 //k8: it ignores path again
1966 if not WAD.GetMapResource(g_ExtractFileName(Res), Data, Len) then
1967 begin
1968 WAD.Free();
1969 Exit;
1970 end;
1972 WAD.Free();
1974 try
1975 mapReader := g_Map_ParseMap(Data, Len);
1976 except
1977 mapReader := nil;
1978 FreeMem(Data);
1979 exit;
1980 end;
1982 FreeMem(Data);
1984 if (mapReader = nil) then exit;
1986 if (mapReader.Width > 0) and (mapReader.Height > 0) then
1987 begin
1988 Result.Name := mapReader.MapName;
1989 Result.Description := mapReader.MapDesc;
1990 Result.Map := Res;
1991 Result.Author := mapReader.MapAuthor;
1992 Result.Height := mapReader.Height;
1993 Result.Width := mapReader.Width;
1994 end
1995 else
1996 begin
1997 g_Console_Add(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]), True);
1998 //ZeroMemory(@Header, SizeOf(Header));
1999 Result.Name := _lc[I_GAME_ERROR_MAP_SELECT];
2000 Result.Description := _lc[I_GAME_ERROR_MAP_SELECT];
2001 Result.Map := Res;
2002 Result.Author := '';
2003 Result.Height := 0;
2004 Result.Width := 0;
2005 end;
2007 mapReader.Free();
2008 end;
2010 function g_Map_GetMapsList(WADName: string): SSArray;
2011 var
2012 WAD: TWADFile;
2013 a: Integer;
2014 ResList: SSArray;
2015 begin
2016 Result := nil;
2017 WAD := TWADFile.Create();
2018 if not WAD.ReadFile(WADName) then
2019 begin
2020 WAD.Free();
2021 Exit;
2022 end;
2023 ResList := WAD.GetMapResources();
2024 if ResList <> nil then
2025 begin
2026 for a := 0 to High(ResList) do
2027 begin
2028 SetLength(Result, Length(Result)+1);
2029 Result[High(Result)] := ResList[a];
2030 end;
2031 end;
2032 WAD.Free();
2033 end;
2035 function g_Map_Exist(Res: string): Boolean;
2036 var
2037 WAD: TWADFile;
2038 FileName, mnn: string;
2039 ResList: SSArray;
2040 a: Integer;
2041 begin
2042 Result := False;
2044 FileName := addWadExtension(g_ExtractWadName(Res));
2046 WAD := TWADFile.Create;
2047 if not WAD.ReadFile(FileName) then
2048 begin
2049 WAD.Free();
2050 Exit;
2051 end;
2053 ResList := WAD.GetMapResources();
2054 WAD.Free();
2056 mnn := g_ExtractFileName(Res);
2057 if ResList <> nil then
2058 for a := 0 to High(ResList) do if StrEquCI1251(ResList[a], mnn) then
2059 begin
2060 Result := True;
2061 Exit;
2062 end;
2063 end;
2065 procedure g_Map_Free(freeTextures: Boolean=true);
2067 procedure FreePanelArray(var panels: TPanelArray);
2068 var
2069 i: Integer;
2071 begin
2072 if panels <> nil then
2073 begin
2074 for i := 0 to High(panels) do
2075 panels[i].Free();
2076 panels := nil;
2077 end;
2078 end;
2080 begin
2081 g_GFX_Free();
2082 g_Weapon_Free();
2083 g_Items_Free();
2084 g_Triggers_Free();
2085 g_Monsters_Free();
2087 RespawnPoints := nil;
2088 if FlagPoints[FLAG_RED] <> nil then
2089 begin
2090 Dispose(FlagPoints[FLAG_RED]);
2091 FlagPoints[FLAG_RED] := nil;
2092 end;
2093 if FlagPoints[FLAG_BLUE] <> nil then
2094 begin
2095 Dispose(FlagPoints[FLAG_BLUE]);
2096 FlagPoints[FLAG_BLUE] := nil;
2097 end;
2098 //DOMFlagPoints := nil;
2100 //gDOMFlags := nil;
2102 if (Length(gCurrentMapFileName) <> 0) then
2103 begin
2104 e_LogWritefln('g_Map_Free: previous map was ''%s''...', [gCurrentMapFileName]);
2105 end
2106 else
2107 begin
2108 e_LogWritefln('g_Map_Free: no previous map.', []);
2109 end;
2111 if freeTextures then
2112 begin
2113 e_LogWritefln('g_Map_Free: clearing textures...', []);
2114 Textures := nil;
2115 TextNameHash.Free();
2116 TextNameHash := nil;
2117 BadTextNameHash.Free();
2118 BadTextNameHash := nil;
2119 gCurrentMapFileName := '';
2120 gCurrentMap.Free();
2121 gCurrentMap := nil;
2122 end;
2124 panByGUID := nil;
2126 FreePanelArray(gWalls);
2127 FreePanelArray(gRenderBackgrounds);
2128 FreePanelArray(gRenderForegrounds);
2129 FreePanelArray(gWater);
2130 FreePanelArray(gAcid1);
2131 FreePanelArray(gAcid2);
2132 FreePanelArray(gSteps);
2133 FreePanelArray(gLifts);
2134 FreePanelArray(gBlockMon);
2135 gMovingWallIds := nil;
2137 g_Game_StopAllSounds(False);
2138 gMusic.FreeSound();
2139 g_Sound_Delete(gMapInfo.MusicName);
2141 gMapInfo.Name := '';
2142 gMapInfo.Description := '';
2143 gMapInfo.MusicName := '';
2144 gMapInfo.Height := 0;
2145 gMapInfo.Width := 0;
2147 gDoorMap := nil;
2148 gLiftMap := nil;
2149 end;
2151 procedure g_Map_Update();
2152 var
2153 a, d, j: Integer;
2154 m: Word;
2155 s: String;
2156 b: Byte;
2158 procedure UpdatePanelArray(var panels: TPanelArray);
2159 var
2160 i: Integer;
2162 begin
2163 for i := 0 to High(panels) do panels[i].Update();
2164 end;
2166 begin
2167 if g_dbgpan_mplat_step then g_dbgpan_mplat_active := true;
2169 UpdatePanelArray(gWalls);
2170 UpdatePanelArray(gRenderBackgrounds);
2171 UpdatePanelArray(gRenderForegrounds);
2172 UpdatePanelArray(gWater);
2173 UpdatePanelArray(gAcid1);
2174 UpdatePanelArray(gAcid2);
2175 UpdatePanelArray(gSteps);
2177 if g_dbgpan_mplat_step then begin g_dbgpan_mplat_step := false; g_dbgpan_mplat_active := false; end;
2179 if gGameSettings.GameMode = GM_CTF then
2180 begin
2181 for a := FLAG_RED to FLAG_BLUE do
2182 begin
2183 if not (gFlags[a].State in [FLAG_STATE_NONE, FLAG_STATE_CAPTURED]) then
2184 begin
2185 with gFlags[a] do
2186 begin
2187 Obj.oldX := Obj.X;
2188 Obj.oldY := Obj.Y;
2190 m := g_Obj_Move(@Obj, True, True);
2192 NeedSend := NeedSend or (Obj.X <> Obj.oldX) or (Obj.Y <> Obj.oldY);
2194 if gTime mod (GAME_TICK*2) <> 0 then Continue;
2196 // Ñîïðîòèâëåíèå âîçäóõà
2197 Obj.Vel.X := z_dec(Obj.Vel.X, 1);
2199 // Òàéìàóò ïîòåðÿííîãî ôëàãà, ëèáî îí âûïàë çà êàðòó
2200 if ((Count = 0) or ByteBool(m and MOVE_FALLOUT)) and g_Game_IsServer then
2201 begin
2202 g_Map_ResetFlag(a);
2203 gFlags[a].CaptureTime := 0;
2204 if a = FLAG_RED then
2205 s := _lc[I_PLAYER_FLAG_RED]
2206 else
2207 s := _lc[I_PLAYER_FLAG_BLUE];
2208 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
2210 if (((gPlayer1 <> nil) and (((gPlayer1.Team = TEAM_RED) and (a = FLAG_RED)) or ((gPlayer1.Team = TEAM_BLUE) and (a = FLAG_BLUE))))
2211 or ((gPlayer2 <> nil) and (((gPlayer2.Team = TEAM_RED) and (a = FLAG_RED)) or ((gPlayer2.Team = TEAM_BLUE) and (a = FLAG_BLUE))))) then
2212 b := 0
2213 else
2214 b := 1;
2216 if not sound_ret_flag[b].IsPlaying() then
2217 sound_ret_flag[b].Play();
2219 if g_Game_IsNet then
2220 MH_SEND_FlagEvent(FLAG_STATE_RETURNED, a, 0);
2221 Continue;
2222 end;
2224 if Count > 0 then Count -= 1;
2226 // Èãðîê áåðåò ôëàã
2227 if gPlayers <> nil then
2228 begin
2229 j := Random(Length(gPlayers)) - 1;
2230 for d := 0 to High(gPlayers) do
2231 begin
2232 Inc(j);
2233 if j > High(gPlayers) then j := 0;
2234 if gPlayers[j] <> nil then
2235 begin
2236 if gPlayers[j].alive and g_Obj_Collide(@Obj, @gPlayers[j].Obj) then
2237 begin
2238 if gPlayers[j].GetFlag(a) then Break;
2239 end;
2240 end;
2241 end;
2242 end;
2243 end;
2244 end;
2245 end;
2246 end;
2247 end;
2249 function g_Map_CollidePanelOld(X, Y: Integer; Width, Height: Word;
2250 PanelType: Word; b1x3: Boolean=false): Boolean;
2251 var
2252 a, h: Integer;
2253 begin
2254 Result := False;
2256 if WordBool(PanelType and PANEL_WALL) then
2257 if gWalls <> nil then
2258 begin
2259 h := High(gWalls);
2261 for a := 0 to h do
2262 if gWalls[a].Enabled and
2263 g_Collide(X, Y, Width, Height,
2264 gWalls[a].X, gWalls[a].Y,
2265 gWalls[a].Width, gWalls[a].Height) then
2266 begin
2267 Result := True;
2268 Exit;
2269 end;
2270 end;
2272 if WordBool(PanelType and PANEL_WATER) then
2273 if gWater <> nil then
2274 begin
2275 h := High(gWater);
2277 for a := 0 to h do
2278 if g_Collide(X, Y, Width, Height,
2279 gWater[a].X, gWater[a].Y,
2280 gWater[a].Width, gWater[a].Height) then
2281 begin
2282 Result := True;
2283 Exit;
2284 end;
2285 end;
2287 if WordBool(PanelType and PANEL_ACID1) then
2288 if gAcid1 <> nil then
2289 begin
2290 h := High(gAcid1);
2292 for a := 0 to h do
2293 if g_Collide(X, Y, Width, Height,
2294 gAcid1[a].X, gAcid1[a].Y,
2295 gAcid1[a].Width, gAcid1[a].Height) then
2296 begin
2297 Result := True;
2298 Exit;
2299 end;
2300 end;
2302 if WordBool(PanelType and PANEL_ACID2) then
2303 if gAcid2 <> nil then
2304 begin
2305 h := High(gAcid2);
2307 for a := 0 to h do
2308 if g_Collide(X, Y, Width, Height,
2309 gAcid2[a].X, gAcid2[a].Y,
2310 gAcid2[a].Width, gAcid2[a].Height) then
2311 begin
2312 Result := True;
2313 Exit;
2314 end;
2315 end;
2317 if WordBool(PanelType and PANEL_STEP) then
2318 if gSteps <> nil then
2319 begin
2320 h := High(gSteps);
2322 for a := 0 to h do
2323 if g_Collide(X, Y, Width, Height,
2324 gSteps[a].X, gSteps[a].Y,
2325 gSteps[a].Width, gSteps[a].Height) then
2326 begin
2327 Result := True;
2328 Exit;
2329 end;
2330 end;
2332 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then
2333 if gLifts <> nil then
2334 begin
2335 h := High(gLifts);
2337 for a := 0 to h do
2338 if ((WordBool(PanelType and (PANEL_LIFTUP)) and (gLifts[a].LiftType = LIFTTYPE_UP)) or
2339 (WordBool(PanelType and (PANEL_LIFTDOWN)) and (gLifts[a].LiftType = LIFTTYPE_DOWN)) or
2340 (WordBool(PanelType and (PANEL_LIFTLEFT)) and (gLifts[a].LiftType = LIFTTYPE_LEFT)) or
2341 (WordBool(PanelType and (PANEL_LIFTRIGHT)) and (gLifts[a].LiftType = LIFTTYPE_RIGHT))) and
2342 g_Collide(X, Y, Width, Height,
2343 gLifts[a].X, gLifts[a].Y,
2344 gLifts[a].Width, gLifts[a].Height) then
2345 begin
2346 Result := True;
2347 Exit;
2348 end;
2349 end;
2351 if WordBool(PanelType and PANEL_BLOCKMON) then
2352 if gBlockMon <> nil then
2353 begin
2354 h := High(gBlockMon);
2356 for a := 0 to h do
2357 if ( (not b1x3) or
2358 ((gBlockMon[a].Width + gBlockMon[a].Height) >= 64) ) and
2359 g_Collide(X, Y, Width, Height,
2360 gBlockMon[a].X, gBlockMon[a].Y,
2361 gBlockMon[a].Width, gBlockMon[a].Height) then
2362 begin
2363 Result := True;
2364 Exit;
2365 end;
2366 end;
2367 end;
2369 function g_Map_CollideLiquid_TextureOld(X, Y: Integer; Width, Height: Word): DWORD;
2370 var
2371 texid: DWORD;
2373 function checkPanels (constref panels: TPanelArray): Boolean;
2374 var
2375 a: Integer;
2376 begin
2377 result := false;
2378 if panels = nil then exit;
2379 for a := 0 to High(panels) do
2380 begin
2381 if g_Collide(X, Y, Width, Height, panels[a].X, panels[a].Y, panels[a].Width, panels[a].Height) then
2382 begin
2383 result := true;
2384 texid := panels[a].GetTextureID();
2385 exit;
2386 end;
2387 end;
2388 end;
2390 begin
2391 texid := LongWord(TEXTURE_NONE);
2392 result := texid;
2393 if not checkPanels(gWater) then
2394 if not checkPanels(gAcid1) then
2395 if not checkPanels(gAcid2) then exit;
2396 result := texid;
2397 end;
2400 function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word; PanelType: Word; b1x3: Boolean): Boolean;
2401 const
2402 SlowMask = GridTagLift or GridTagBlockMon;
2404 function checker (pan: TPanel; tag: Integer): Boolean;
2405 begin
2407 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
2408 begin
2409 result := pan.Enabled;
2410 exit;
2411 end;
2414 if ((tag and GridTagLift) <> 0) then
2415 begin
2416 result :=
2417 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
2418 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
2419 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
2420 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT))) {and
2421 g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
2422 exit;
2423 end;
2425 if ((tag and GridTagBlockMon) <> 0) then
2426 begin
2427 result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2428 exit;
2429 end;
2431 // other shit
2432 //result := g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2433 result := true; // i found her!
2434 end;
2436 var
2437 tagmask: Integer = 0;
2438 mwit: PPanel;
2439 it: TPanelGrid.Iter;
2440 pan: TPanel;
2441 begin
2442 result := false;
2443 if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor);
2444 if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater;
2445 if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1;
2446 if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2;
2447 if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep;
2448 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift;
2449 if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon;
2451 if (tagmask = 0) then exit; // just in case
2453 if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('*solids');
2454 if gdbg_map_use_accel_coldet then
2455 begin
2456 if ((tagmask and SlowMask) <> 0) then
2457 begin
2458 // slow
2459 it := mapGrid.forEachInAABB(X, Y, Width, Height, tagmask);
2460 for mwit in it do
2461 begin
2462 pan := mwit^;
2463 if ((pan.tag and GridTagLift) <> 0) then
2464 begin
2465 result :=
2466 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
2467 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
2468 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
2469 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT))) {and
2470 g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
2471 end
2472 else if ((pan.tag and GridTagBlockMon) <> 0) then
2473 begin
2474 result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2475 end
2476 else
2477 begin
2478 // other shit
2479 result := true; // i found her!
2480 end;
2481 if (result) then break;
2482 end;
2483 end
2484 else
2485 begin
2486 // fast
2487 it := mapGrid.forEachInAABB(X, Y, Width, Height, tagmask, false, true); // return first hit
2488 result := (it.length > 0);
2489 end;
2490 it.release();
2491 end
2492 else
2493 begin
2494 result := g_Map_CollidePanelOld(X, Y, Width, Height, PanelType, b1x3);
2495 end;
2496 if (profMapCollision <> nil) then profMapCollision.sectionEnd();
2497 end;
2500 // returns `true` if we need to stop
2501 function liquidChecker (pan: TPanel; var texid: DWORD; var cctype: Integer): Boolean; inline;
2502 begin
2503 result := false;
2504 //if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2)) = 0) then exit;
2505 // check priorities
2506 case cctype of
2507 0: if ((pan.tag and GridTagWater) = 0) then exit; // allowed: water
2508 1: if ((pan.tag and (GridTagWater or GridTagAcid1)) = 0) then exit; // allowed: water, acid1
2509 //2: if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2) = 0) then exit; // allowed: water, acid1, acid2
2510 end;
2511 // collision?
2512 //if not g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height) then exit;
2513 // yeah
2514 texid := pan.GetTextureID();
2515 // water? water has the highest priority, so stop right here
2516 if ((pan.tag and GridTagWater) <> 0) then begin cctype := 0; result := true; exit; end;
2517 // acid2?
2518 if ((pan.tag and GridTagAcid2) <> 0) then cctype := 2;
2519 // acid1?
2520 if ((pan.tag and GridTagAcid1) <> 0) then cctype := 1;
2521 end;
2523 function g_Map_CollideLiquid_Texture(X, Y: Integer; Width, Height: Word): DWORD;
2524 var
2525 cctype: Integer = 3; // priority: 0: water was hit, 1: acid1 was hit, 2: acid2 was hit; 3: nothing was hit
2526 mwit: PPanel;
2527 it: TPanelGrid.Iter;
2528 begin
2529 if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('liquids');
2530 if gdbg_map_use_accel_coldet then
2531 begin
2532 result := LongWord(TEXTURE_NONE);
2533 it := mapGrid.forEachInAABB(X, Y, Width, Height, (GridTagWater or GridTagAcid1 or GridTagAcid2));
2534 for mwit in it do if (liquidChecker(mwit^, result, cctype)) then break;
2535 it.release();
2536 end
2537 else
2538 begin
2539 result := g_Map_CollideLiquid_TextureOld(X, Y, Width, Height);
2540 end;
2541 if (profMapCollision <> nil) then profMapCollision.sectionEnd();
2542 end;
2545 procedure g_Map_EnableWall_XXX (ID: DWORD); begin if (ID < Length(gWalls)) then g_Map_EnableWallGUID(gWalls[ID].guid); end;
2546 procedure g_Map_DisableWall_XXX (ID: DWORD); begin if (ID < Length(gWalls)) then g_Map_DisableWallGUID(gWalls[ID].guid); end;
2547 procedure g_Map_SetLift_XXX (ID: DWORD; t: Integer); begin if (ID < Length(gLifts)) then g_Map_SetLiftGUID(gLifts[ID].guid, t); end;
2550 procedure g_Map_EnableWallGUID (pguid: Integer);
2551 var
2552 pan: TPanel;
2553 begin
2554 //pan := gWalls[ID];
2555 pan := g_Map_PanelByGUID(pguid);
2556 if (pan = nil) then exit;
2557 if pan.Enabled and mapGrid.proxyEnabled[pan.proxyId] then exit;
2559 pan.Enabled := True;
2560 g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, true);
2562 mapGrid.proxyEnabled[pan.proxyId] := true;
2563 //if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := true
2564 //else pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, GridTagDoor);
2566 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2567 // mark platform as interesting
2568 pan.setDirty();
2570 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
2571 //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);
2572 {$ENDIF}
2573 end;
2576 procedure g_Map_DisableWallGUID (pguid: Integer);
2577 var
2578 pan: TPanel;
2579 begin
2580 //pan := gWalls[ID];
2581 pan := g_Map_PanelByGUID(pguid);
2582 if (pan = nil) then exit;
2583 if (not pan.Enabled) and (not mapGrid.proxyEnabled[pan.proxyId]) then exit;
2585 pan.Enabled := False;
2586 g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, false);
2588 mapGrid.proxyEnabled[pan.proxyId] := false;
2589 //if (pan.proxyId >= 0) then begin mapGrid.removeBody(pan.proxyId); pan.proxyId := -1; end;
2591 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2592 // mark platform as interesting
2593 pan.setDirty();
2595 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
2596 //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);
2597 {$ENDIF}
2598 end;
2601 procedure g_Map_SwitchTextureGUID (pguid: Integer; AnimLoop: Byte = 0);
2602 var
2603 tp: TPanel;
2604 begin
2605 tp := g_Map_PanelByGUID(pguid);
2606 if (tp = nil) then exit;
2607 tp.NextTexture(AnimLoop);
2608 if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelTexture(pguid, AnimLoop);
2609 end;
2612 procedure g_Map_SetLiftGUID (pguid: Integer; t: Integer);
2613 var
2614 pan: TPanel;
2615 begin
2616 //pan := gLifts[ID];
2617 pan := g_Map_PanelByGUID(pguid);
2618 if (pan = nil) then exit;
2619 if not pan.isGLift then exit;
2621 if ({gLifts[ID]}pan.LiftType = t) then exit; //!FIXME!TRIGANY!
2623 with {gLifts[ID]} pan do
2624 begin
2625 LiftType := t;
2627 g_Mark(X, Y, Width, Height, MARK_LIFT, false);
2628 //TODO: make separate lift tags, and change tag here
2630 case LiftType of
2631 LIFTTYPE_UP: g_Mark(X, Y, Width, Height, MARK_LIFTUP);
2632 LIFTTYPE_DOWN: g_Mark(X, Y, Width, Height, MARK_LIFTDOWN);
2633 LIFTTYPE_LEFT: g_Mark(X, Y, Width, Height, MARK_LIFTLEFT);
2634 LIFTTYPE_RIGHT: g_Mark(X, Y, Width, Height, MARK_LIFTRIGHT);
2635 end;
2637 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2638 // mark platform as interesting
2639 pan.setDirty();
2640 end;
2641 end;
2644 function g_Map_GetPoint (PointType: Byte; var RespawnPoint: TRespawnPoint): Boolean;
2645 var
2646 a: Integer;
2647 PointsArray: Array of TRespawnPoint;
2648 begin
2649 Result := False;
2650 SetLength(PointsArray, 0);
2652 if RespawnPoints = nil then
2653 Exit;
2655 for a := 0 to High(RespawnPoints) do
2656 if RespawnPoints[a].PointType = PointType then
2657 begin
2658 SetLength(PointsArray, Length(PointsArray)+1);
2659 PointsArray[High(PointsArray)] := RespawnPoints[a];
2660 end;
2662 if PointsArray = nil then
2663 Exit;
2665 RespawnPoint := PointsArray[Random(Length(PointsArray))];
2666 Result := True;
2667 end;
2669 function g_Map_GetPointCount(PointType: Byte): Word;
2670 var
2671 a: Integer;
2672 begin
2673 Result := 0;
2675 if RespawnPoints = nil then
2676 Exit;
2678 for a := 0 to High(RespawnPoints) do
2679 if RespawnPoints[a].PointType = PointType then
2680 Result := Result + 1;
2681 end;
2683 function g_Map_GetRandomPointType(): Byte;
2684 begin
2685 if RespawnPoints = nil then
2686 Result := 255
2687 else
2688 Result := RespawnPoints[Random(Length(RespawnPoints))].PointType;
2689 end;
2691 function g_Map_HaveFlagPoints(): Boolean;
2692 begin
2693 Result := (FlagPoints[FLAG_RED] <> nil) and (FlagPoints[FLAG_BLUE] <> nil);
2694 end;
2696 procedure g_Map_ResetFlag(Flag: Byte);
2697 begin
2698 with gFlags[Flag] do
2699 begin
2700 Obj.X := -1000;
2701 Obj.Y := -1000;
2702 Obj.Vel.X := 0;
2703 Obj.Vel.Y := 0;
2704 Direction := TDirection.D_LEFT;
2705 State := FLAG_STATE_NONE;
2706 if FlagPoints[Flag] <> nil then
2707 begin
2708 Obj.X := FlagPoints[Flag]^.X;
2709 Obj.Y := FlagPoints[Flag]^.Y;
2710 Direction := FlagPoints[Flag]^.Direction;
2711 State := FLAG_STATE_NORMAL;
2712 end;
2713 Obj.oldX := Obj.X;
2714 Obj.oldY := Obj.Y;
2715 NeedSend := False; // the event will take care of this
2716 Count := -1;
2717 end;
2718 end;
2720 procedure g_Map_SaveState (st: TStream);
2721 var
2722 str: String;
2724 procedure savePanels ();
2725 var
2726 pan: TPanel;
2727 begin
2728 // Ñîõðàíÿåì ïàíåëè
2729 utils.writeInt(st, LongInt(Length(panByGUID)));
2730 for pan in panByGUID do pan.SaveState(st);
2731 end;
2733 procedure saveFlag (flag: PFlag);
2734 var
2735 b: Byte;
2736 begin
2737 utils.writeSign(st, 'FLAG');
2738 utils.writeInt(st, Byte(0)); // version
2739 // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
2740 utils.writeInt(st, Byte(flag^.RespawnType));
2741 // Ñîñòîÿíèå ôëàãà
2742 utils.writeInt(st, Byte(flag^.State));
2743 // Íàïðàâëåíèå ôëàãà
2744 if flag^.Direction = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT
2745 utils.writeInt(st, Byte(b));
2746 // Îáúåêò ôëàãà
2747 Obj_SaveState(st, @flag^.Obj);
2748 end;
2750 begin
2751 savePanels();
2753 // Ñîõðàíÿåì ìóçûêó
2754 utils.writeSign(st, 'MUSI');
2755 utils.writeInt(st, Byte(0));
2756 // Íàçâàíèå ìóçûêè
2757 assert(gMusic <> nil, 'g_Map_SaveState: gMusic = nil');
2758 if gMusic.NoMusic then str := '' else str := gMusic.Name;
2759 utils.writeStr(st, str);
2760 // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
2761 utils.writeInt(st, LongWord(gMusic.GetPosition()));
2762 // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
2763 utils.writeBool(st, gMusic.SpecPause);
2765 ///// Ñîõðàíÿåì êîëè÷åñòâî ìîíñòðîâ: /////
2766 utils.writeInt(st, LongInt(gTotalMonsters));
2767 ///// /////
2769 //// Ñîõðàíÿåì ôëàãè, åñëè ýòî CTF: /////
2770 if (gGameSettings.GameMode = GM_CTF) then
2771 begin
2772 // Ôëàã Êðàñíîé êîìàíäû
2773 saveFlag(@gFlags[FLAG_RED]);
2774 // Ôëàã Ñèíåé êîìàíäû
2775 saveFlag(@gFlags[FLAG_BLUE]);
2776 end;
2777 ///// /////
2779 ///// Ñîõðàíÿåì êîëè÷åñòâî ïîáåä, åñëè ýòî TDM/CTF: /////
2780 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
2781 begin
2782 // Î÷êè Êðàñíîé êîìàíäû
2783 utils.writeInt(st, SmallInt(gTeamStat[TEAM_RED].Score));
2784 // Î÷êè Ñèíåé êîìàíäû
2785 utils.writeInt(st, SmallInt(gTeamStat[TEAM_BLUE].Score));
2786 end;
2787 ///// /////
2788 end;
2791 procedure g_Map_LoadState (st: TStream);
2792 var
2793 dw: DWORD;
2794 str: String;
2795 boo: Boolean;
2797 procedure loadPanels ();
2798 var
2799 pan: TPanel;
2800 begin
2801 // Çàãðóæàåì ïàíåëè
2802 if (Length(panByGUID) <> utils.readLongInt(st)) then raise XStreamError.Create('invalid number of saved panels');
2803 for pan in panByGUID do
2804 begin
2805 pan.LoadState(st);
2806 if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
2807 end;
2808 end;
2810 procedure loadFlag (flag: PFlag);
2811 var
2812 b: Byte;
2813 begin
2814 // Ñèãíàòóðà ôëàãà
2815 if not utils.checkSign(st, 'FLAG') then raise XStreamError.Create('invalid flag signature');
2816 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid flag version');
2817 // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
2818 flag^.RespawnType := utils.readByte(st);
2819 // Ñîñòîÿíèå ôëàãà
2820 flag^.State := utils.readByte(st);
2821 // Íàïðàâëåíèå ôëàãà
2822 b := utils.readByte(st);
2823 if (b = 1) then flag^.Direction := TDirection.D_LEFT else flag^.Direction := TDirection.D_RIGHT; // b = 2
2824 // Îáúåêò ôëàãà
2825 Obj_LoadState(@flag^.Obj, st);
2826 end;
2828 begin
2829 if (st = nil) then exit;
2831 ///// Çàãðóæàåì ñïèñêè ïàíåëåé: /////
2832 loadPanels();
2833 ///// /////
2835 // Îáíîâëÿåì êàðòó ñòîëêíîâåíèé è ñåòêó
2836 g_GFX_Init();
2837 //mapCreateGrid();
2839 ///// Çàãðóæàåì ìóçûêó: /////
2840 if not utils.checkSign(st, 'MUSI') then raise XStreamError.Create('invalid music signature');
2841 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid music version');
2842 // Íàçâàíèå ìóçûêè
2843 assert(gMusic <> nil, 'g_Map_LoadState: gMusic = nil');
2844 str := utils.readStr(st);
2845 // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
2846 dw := utils.readLongWord(st);
2847 // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
2848 boo := utils.readBool(st);
2849 // Çàïóñêàåì ýòó ìóçûêó
2850 gMusic.SetByName(str);
2851 gMusic.SpecPause := boo;
2852 gMusic.Play();
2853 gMusic.Pause(true);
2854 gMusic.SetPosition(dw);
2855 ///// /////
2857 ///// Çàãðóæàåì êîëè÷åñòâî ìîíñòðîâ: /////
2858 gTotalMonsters := utils.readLongInt(st);
2859 ///// /////
2861 //// Çàãðóæàåì ôëàãè, åñëè ýòî CTF: /////
2862 if (gGameSettings.GameMode = GM_CTF) then
2863 begin
2864 // Ôëàã Êðàñíîé êîìàíäû
2865 loadFlag(@gFlags[FLAG_RED]);
2866 // Ôëàã Ñèíåé êîìàíäû
2867 loadFlag(@gFlags[FLAG_BLUE]);
2868 end;
2869 ///// /////
2871 ///// Çàãðóæàåì êîëè÷åñòâî ïîáåä, åñëè ýòî TDM/CTF: /////
2872 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
2873 begin
2874 // Î÷êè Êðàñíîé êîìàíäû
2875 gTeamStat[TEAM_RED].Score := utils.readSmallInt(st);
2876 // Î÷êè Ñèíåé êîìàíäû
2877 gTeamStat[TEAM_BLUE].Score := utils.readSmallInt(st);
2878 end;
2879 ///// /////
2880 end;
2883 // trace liquid, stepping by `dx` and `dy`
2884 // return last seen liquid coords, and `false` if we're started outside of the liquid
2885 function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
2886 const
2887 MaskLiquid = GridTagWater or GridTagAcid1 or GridTagAcid2;
2888 begin
2889 topx := x;
2890 topy := y;
2891 // started outside of the liquid?
2892 //if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then begin result := false; exit; end;
2893 if (g_Map_PanelAtPoint(x, y, MaskLiquid) = nil) then begin result := false; exit; end;
2894 if (dx = 0) and (dy = 0) then begin result := false; exit; end; // sanity check
2895 result := true;
2896 while true do
2897 begin
2898 Inc(x, dx);
2899 Inc(y, dy);
2900 //if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then exit; // out of the water, just exit
2901 if (g_Map_PanelAtPoint(x, y, MaskLiquid) = nil) then exit; // out of the water, just exit
2902 topx := x;
2903 topy := y;
2904 end;
2905 end;
2908 begin
2909 DynWarningCB := mapWarningCB;
2910 end.