DEADSOFTWARE

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