DEADSOFTWARE

game: disable gfx for server
[d2df-sdl.git] / src / game / g_map.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *)
15 {$INCLUDE ../shared/a_modes.inc}
16 {$DEFINE MAP_DEBUG_ENABLED_FLAG}
17 unit g_map;
19 interface
21 uses
22 SysUtils, Classes, mempool,
23 g_base, g_basic, MAPDEF, g_textures,
24 g_phys, utils, g_panel, g_grid, md5, binheap, xprofiler, xparser, xdynrec;
26 type
27 TMapInfo = record
28 Map: String;
29 Name: String;
30 Description: String;
31 Author: String;
32 MusicName: String;
33 SkyName: String;
34 SkyFullName: String; // used by render
35 Height: Word;
36 Width: Word;
37 end;
39 PRespawnPoint = ^TRespawnPoint;
40 TRespawnPoint = record
41 X, Y: Integer;
42 Direction: TDirection;
43 PointType: Byte;
44 end;
46 PFlagPoint = ^TFlagPoint;
47 TFlagPoint = TRespawnPoint;
49 PFlag = ^TFlag;
50 TFlag = record
51 Obj: TObj;
52 RespawnType: Byte;
53 State: Byte;
54 Count: Integer;
55 CaptureTime: LongWord;
56 Direction: TDirection;
57 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 {$IFDEF ENABLE_GFX}
251 g_gfx,
252 {$ENDIF}
253 e_input, e_log, e_res, g_items, g_console,
254 g_weapons, g_game, g_sound, e_sound, CONFIG,
255 g_options, g_triggers, g_player,
256 Math, g_monsters, g_saveload, g_language, g_netmsg,
257 sfs, xstreams, hashtable, wadreader,
258 g_res_downloader
261 const
262 FLAGRECT: TRectWH = (X:15; Y:12; Width:33; Height:52);
263 MUSIC_SIGNATURE = $4953554D; // 'MUSI'
264 FLAG_SIGNATURE = $47414C46; // 'FLAG'
267 // ////////////////////////////////////////////////////////////////////////// //
268 procedure mapWarningCB (const msg: AnsiString; line, col: Integer);
269 begin
270 if (line > 0) then
271 begin
272 e_LogWritefln('parse error at (%s,%s): %s', [line, col, msg], TMsgType.Warning);
273 end
274 else
275 begin
276 e_LogWritefln('parse error: %s', [msg], TMsgType.Warning);
277 end;
278 end;
281 // ////////////////////////////////////////////////////////////////////////// //
282 var
283 panByGUID: array of TPanel = nil;
286 // ////////////////////////////////////////////////////////////////////////// //
287 function g_Map_PanelByGUID (aguid: Integer): TPanel; inline;
288 begin
289 //if (panByGUID = nil) or (not panByGUID.get(aguid, result)) then result := nil;
290 if (aguid >= 0) and (aguid < Length(panByGUID)) then result := panByGUID[aguid] else result := nil;
291 end;
294 // return `true` from `cb` to stop
295 function g_Map_ForEachPanel (cb: TForEachPanelCB): TPanel;
296 var
297 pan: TPanel;
298 begin
299 result := nil;
300 if not assigned(cb) then exit;
301 for pan in panByGUID do
302 begin
303 if cb(pan) then begin result := pan; exit; end;
304 end;
305 end;
308 procedure g_Map_NetSendInterestingPanels ();
309 var
310 pan: TPanel;
311 begin
312 if g_Game_IsServer and g_Game_IsNet then
313 begin
314 for pan in panByGUID do
315 begin
316 if pan.gncNeedSend then MH_SEND_PanelState(pan.guid);
317 end;
318 end;
319 end;
322 // ////////////////////////////////////////////////////////////////////////// //
323 function g_Map_MinX (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridX0 else result := 0; end;
324 function g_Map_MinY (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridY0 else result := 0; end;
325 function g_Map_MaxX (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridX0+mapGrid.gridWidth-1 else result := 0; end;
326 function g_Map_MaxY (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridY0+mapGrid.gridHeight-1 else result := 0; end;
329 // ////////////////////////////////////////////////////////////////////////// //
330 var
331 dfmapdef: TDynMapDef = nil;
334 procedure loadMapDefinition ();
335 var
336 pr: TTextParser = nil;
337 st: TStream = nil;
338 WAD: TWADFile = nil;
339 begin
340 if (dfmapdef <> nil) then exit;
342 try
343 e_LogWritefln('parsing "mapdef.txt"...', []);
344 st := e_OpenResourceRO(DataDirs, 'mapdef.txt');
345 e_LogWritefln('found local "mapdef.txt"', []);
346 except
347 st := nil;
348 end;
350 if (st = nil) then
351 begin
352 WAD := TWADFile.Create();
353 if not WAD.ReadFile(GameWAD) then
354 begin
355 //raise Exception.Create('cannot load "game.wad"');
356 st := nil;
357 end
358 else
359 begin
360 st := WAD.openFileStream('mapdef.txt');
361 end;
362 end;
364 try
365 if (st = nil) then
366 begin
367 //raise Exception.Create('cannot open "mapdef.txt"');
368 e_LogWriteln('using default "mapdef.txt"...');
369 pr := TStrTextParser.Create(defaultMapDef);
370 end
371 else
372 begin
373 pr := TFileTextParser.Create(st);
374 end;
375 except on e: Exception do
376 begin
377 e_LogWritefln('something is VERY wrong here! -- ', [e.message]);
378 raise;
379 end;
380 end;
382 try
383 dfmapdef := TDynMapDef.Create(pr);
384 except
385 on e: TDynParseException do
386 raise Exception.CreateFmt('ERROR in "mapdef.txt" at (%s,%s): %s', [e.tokLine, e.tokCol, e.message]);
387 on e: Exception do
388 raise Exception.CreateFmt('ERROR in "mapdef.txt" at (%s,%s): %s', [pr.tokLine, pr.tokCol, e.message]);
389 end;
391 st.Free();
392 WAD.Free();
393 end;
396 // ////////////////////////////////////////////////////////////////////////// //
397 function g_Map_ParseMap (data: Pointer; dataLen: Integer): TDynRecord;
398 var
399 wst: TSFSMemoryChunkStream = nil;
400 begin
401 result := nil;
402 if (dataLen < 4) then exit;
404 if (dfmapdef = nil) then writeln('need to load mapdef');
405 loadMapDefinition();
406 if (dfmapdef = nil) then raise Exception.Create('internal map loader error');
408 wst := TSFSMemoryChunkStream.Create(data, dataLen);
409 try
410 result := dfmapdef.parseMap(wst);
411 except
412 on e: TDynParseException do
413 begin
414 e_LogWritefln('ERROR at (%s,%s): %s', [e.tokLine, e.tokCol, e.message]);
415 wst.Free();
416 result := nil;
417 exit;
418 end;
419 on e: Exception do
420 begin
421 e_LogWritefln('ERROR: %s', [e.message]);
422 wst.Free();
423 result := nil;
424 exit;
425 end;
426 end;
428 //e_LogWriteln('map parsed.');
429 end;
432 // ////////////////////////////////////////////////////////////////////////// //
433 var
434 NNF_PureName: String; // Èìÿ òåêñòóðû áåç öèôð â êîíöå
435 NNF_PureExt: String; // extension postfix
436 NNF_FirstNum: Integer; // ×èñëî ó íà÷àëüíîé òåêñòóðû
437 NNF_CurrentNum: Integer; // Ñëåäóþùåå ÷èñëî ó òåêñòóðû
440 function g_Texture_NumNameFindStart(name: String): Boolean;
441 var
442 i, j: Integer;
444 begin
445 Result := False;
446 NNF_PureName := '';
447 NNF_PureExt := '';
448 NNF_FirstNum := -1;
449 NNF_CurrentNum := -1;
451 for i := Length(name) downto 1 do
452 if (name[i] = '_') then // "_" - ñèìâîë íà÷àëà íîìåðíîãî ïîñòôèêñà
453 begin
454 if i = Length(name) then
455 begin // Íåò öèôð â êîíöå ñòðîêè
456 Exit;
457 end
458 else
459 begin
460 j := i + 1;
461 while (j <= Length(name)) and (name[j] <> '.') do inc(j);
462 NNF_PureName := Copy(name, 1, i);
463 NNF_PureExt := Copy(name, j);
464 name := Copy(name, i + 1, j - i - 1);
465 Break;
466 end;
467 end;
469 // Íå ïåðåâåñòè â ÷èñëî:
470 if not TryStrToInt(name, NNF_FirstNum) then
471 Exit;
473 NNF_CurrentNum := 0;
475 Result := True;
476 end;
479 function g_Texture_NumNameFindNext(var newName: String): Byte;
480 begin
481 if (NNF_PureName = '') or (NNF_CurrentNum < 0) then
482 begin
483 newName := '';
484 Result := NNF_NO_NAME;
485 Exit;
486 end;
488 newName := NNF_PureName + IntToStr(NNF_CurrentNum) + NNF_PureExt;
490 if NNF_CurrentNum < NNF_FirstNum then
491 Result := NNF_NAME_BEFORE
492 else
493 if NNF_CurrentNum > NNF_FirstNum then
494 Result := NNF_NAME_AFTER
495 else
496 Result := NNF_NAME_EQUALS;
498 Inc(NNF_CurrentNum);
499 end;
502 // ////////////////////////////////////////////////////////////////////////// //
503 function panelTypeToTag (panelType: Word): Integer;
504 begin
505 case panelType of
506 PANEL_WALL: result := GridTagWall; // gWalls
507 PANEL_OPENDOOR, PANEL_CLOSEDOOR: result := GridTagDoor; // gWalls
508 PANEL_BACK: result := GridTagBack; // gRenderBackgrounds
509 PANEL_FORE: result := GridTagFore; // gRenderForegrounds
510 PANEL_WATER: result := GridTagWater; // gWater
511 PANEL_ACID1: result := GridTagAcid1; // gAcid1
512 PANEL_ACID2: result := GridTagAcid2; // gAcid2
513 PANEL_STEP: result := GridTagStep; // gSteps
514 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: result := GridTagLift; // gLifts -- this is for all lifts
515 PANEL_BLOCKMON: result := GridTagBlockMon; // gBlockMon -- this is for all blockmons
516 else result := GridTagInvalid;
517 end;
518 end;
521 class function TBinHeapPanelDrawCmp.less (const a, b: TPanel): Boolean; inline;
522 begin
523 if (a.tag < b.tag) then begin result := true; exit; end;
524 if (a.tag > b.tag) then begin result := false; exit; end;
525 result := (a.arrIdx < b.arrIdx);
526 end;
528 var
529 TextNameHash: THashStrInt = nil; // key: texture name; value: index in `Textures`
530 BadTextNameHash: THashStrInt = nil; // set; so we won't spam with non-existing texture messages
531 RespawnPoints: array of TRespawnPoint;
532 FlagPoints: array[FLAG_RED..FLAG_BLUE] of PFlagPoint;
533 //DOMFlagPoints: Array of TFlagPoint;
536 procedure g_Map_ProfilersBegin ();
537 begin
538 if (profMapCollision = nil) then profMapCollision := TProfiler.Create('COLSOLID', g_profile_history_size);
539 if (profMapCollision <> nil) then profMapCollision.mainBegin(g_profile_collision);
540 // create sections
541 if g_profile_collision and (profMapCollision <> nil) then
542 begin
543 profMapCollision.sectionBegin('*solids');
544 profMapCollision.sectionEnd();
545 profMapCollision.sectionBegin('liquids');
546 profMapCollision.sectionEnd();
547 end;
548 end;
550 procedure g_Map_ProfilersEnd ();
551 begin
552 if (profMapCollision <> nil) then profMapCollision.mainEnd();
553 end;
556 // wall index in `gWalls` or -1
557 function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
558 var
559 ex, ey: Integer;
560 begin
561 result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, (GridTagWall or GridTagDoor));
562 if (result <> nil) then
563 begin
564 if (hitx <> nil) then hitx^ := ex;
565 if (hity <> nil) then hity^ := ey;
566 end
567 else
568 begin
569 if (hitx <> nil) then hitx^ := x1;
570 if (hity <> nil) then hity^ := y1;
571 end;
572 end;
574 // returns panel or nil
575 function g_Map_traceToNearest (x0, y0, x1, y1: Integer; tag: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
576 var
577 ex, ey: Integer;
578 begin
579 result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, tag);
580 if (result <> nil) then
581 begin
582 if (hitx <> nil) then hitx^ := ex;
583 if (hity <> nil) then hity^ := ey;
584 end
585 else
586 begin
587 if (hitx <> nil) then hitx^ := x1;
588 if (hity <> nil) then hity^ := y1;
589 end;
590 end;
593 function xxPanAtPointChecker (pan: TPanel; panelType: Word): Boolean; inline;
594 begin
595 if ((pan.tag and GridTagLift) <> 0) then
596 begin
597 // stop if the lift of the right type
598 result :=
599 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
600 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
601 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
602 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT)));
603 exit;
604 end;
605 result := true; // otherwise, stop anyway, 'cause `forEachAtPoint()` is guaranteed to call this only for correct panels
606 end;
608 function g_Map_HasAnyPanelAtPoint (x, y: Integer; panelType: Word): Boolean;
609 var
610 tagmask: Integer = 0;
611 mwit: PPanel;
612 it: TPanelGrid.Iter;
613 begin
614 result := false;
616 if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor);
617 if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater;
618 if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1;
619 if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2;
620 if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep;
621 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift;
622 if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon;
624 if (tagmask = 0) then exit;// just in case
626 if ((tagmask and GridTagLift) <> 0) then
627 begin
628 // slow
629 it := mapGrid.forEachAtPoint(x, y, tagmask);
630 for mwit in it do if (xxPanAtPointChecker(mwit^, PanelType)) then begin result := true; break; end;
631 end
632 else
633 begin
634 // fast
635 it := mapGrid.forEachAtPoint(x, y, tagmask, false, true);
636 result := (it.length <> 0); // firsthit
637 end;
638 it.release();
639 end;
642 function g_Map_PanelAtPoint (x, y: Integer; tagmask: Integer=-1): TPanel;
643 var
644 it: TPanelGrid.Iter;
645 begin
646 result := nil;
647 if (tagmask = 0) then exit;
648 it := mapGrid.forEachAtPoint(x, y, tagmask, false, true); // firsthit
649 if (it.length <> 0) then result := it.first^;
650 it.release();
651 end;
654 function g_Map_IsSpecialTexture(Texture: String): Boolean;
655 begin
656 Result := (Texture = TEXTURE_NAME_WATER) or
657 (Texture = TEXTURE_NAME_ACID1) or
658 (Texture = TEXTURE_NAME_ACID2);
659 end;
661 procedure CreateDoorMap();
662 var
663 PanelArray: Array of record
664 X, Y: Integer;
665 Width, Height: Word;
666 Active: Boolean;
667 PanelID: DWORD;
668 end;
669 a, b, c, m, i, len: Integer;
670 ok: Boolean;
671 begin
672 if gWalls = nil then
673 Exit;
675 i := 0;
676 len := 128;
677 SetLength(PanelArray, len);
679 for a := 0 to High(gWalls) do
680 if gWalls[a].Door then
681 begin
682 PanelArray[i].X := gWalls[a].X;
683 PanelArray[i].Y := gWalls[a].Y;
684 PanelArray[i].Width := gWalls[a].Width;
685 PanelArray[i].Height := gWalls[a].Height;
686 PanelArray[i].Active := True;
687 PanelArray[i].PanelID := a;
689 i := i + 1;
690 if i = len then
691 begin
692 len := len + 128;
693 SetLength(PanelArray, len);
694 end;
695 end;
697 // Íåò äâåðåé:
698 if i = 0 then
699 begin
700 PanelArray := nil;
701 Exit;
702 end;
704 SetLength(gDoorMap, 0);
706 g_Game_SetLoadingText(_lc[I_LOAD_DOOR_MAP], i-1, False);
708 for a := 0 to i-1 do
709 if PanelArray[a].Active then
710 begin
711 PanelArray[a].Active := False;
712 m := Length(gDoorMap);
713 SetLength(gDoorMap, m+1);
714 SetLength(gDoorMap[m], 1);
715 gDoorMap[m, 0] := PanelArray[a].PanelID;
716 ok := True;
718 while ok do
719 begin
720 ok := False;
722 for b := 0 to i-1 do
723 if PanelArray[b].Active then
724 for c := 0 to High(gDoorMap[m]) do
725 if {((gRenderWalls[PanelArray[b].RenderPanelID].TextureID = gRenderWalls[gDoorMap[m, c]].TextureID) or
726 gRenderWalls[PanelArray[b].RenderPanelID].Hide or gRenderWalls[gDoorMap[m, c]].Hide) and}
727 g_CollideAround(PanelArray[b].X, PanelArray[b].Y,
728 PanelArray[b].Width, PanelArray[b].Height,
729 gWalls[gDoorMap[m, c]].X,
730 gWalls[gDoorMap[m, c]].Y,
731 gWalls[gDoorMap[m, c]].Width,
732 gWalls[gDoorMap[m, c]].Height) then
733 begin
734 PanelArray[b].Active := False;
735 SetLength(gDoorMap[m],
736 Length(gDoorMap[m])+1);
737 gDoorMap[m, High(gDoorMap[m])] := PanelArray[b].PanelID;
738 ok := True;
739 Break;
740 end;
741 end;
743 g_Game_StepLoading();
744 end;
746 PanelArray := nil;
747 end;
749 procedure CreateLiftMap();
750 var
751 PanelArray: Array of record
752 X, Y: Integer;
753 Width, Height: Word;
754 Active: Boolean;
755 end;
756 a, b, c, len, i, j: Integer;
757 ok: Boolean;
758 begin
759 if gLifts = nil then
760 Exit;
762 len := Length(gLifts);
763 SetLength(PanelArray, len);
765 for a := 0 to len-1 do
766 begin
767 PanelArray[a].X := gLifts[a].X;
768 PanelArray[a].Y := gLifts[a].Y;
769 PanelArray[a].Width := gLifts[a].Width;
770 PanelArray[a].Height := gLifts[a].Height;
771 PanelArray[a].Active := True;
772 end;
774 SetLength(gLiftMap, len);
775 i := 0;
777 g_Game_SetLoadingText(_lc[I_LOAD_LIFT_MAP], len-1, False);
779 for a := 0 to len-1 do
780 if PanelArray[a].Active then
781 begin
782 PanelArray[a].Active := False;
783 SetLength(gLiftMap[i], 32);
784 j := 0;
785 gLiftMap[i, j] := a;
786 ok := True;
788 while ok do
789 begin
790 ok := False;
791 for b := 0 to len-1 do
792 if PanelArray[b].Active then
793 for c := 0 to j do
794 if g_CollideAround(PanelArray[b].X,
795 PanelArray[b].Y,
796 PanelArray[b].Width,
797 PanelArray[b].Height,
798 PanelArray[gLiftMap[i, c]].X,
799 PanelArray[gLiftMap[i, c]].Y,
800 PanelArray[gLiftMap[i, c]].Width,
801 PanelArray[gLiftMap[i, c]].Height) then
802 begin
803 PanelArray[b].Active := False;
804 j := j+1;
805 if j > High(gLiftMap[i]) then
806 SetLength(gLiftMap[i],
807 Length(gLiftMap[i])+32);
809 gLiftMap[i, j] := b;
810 ok := True;
812 Break;
813 end;
814 end;
816 SetLength(gLiftMap[i], j+1);
817 i := i+1;
819 g_Game_StepLoading();
820 end;
822 SetLength(gLiftMap, i);
824 PanelArray := nil;
825 end;
827 function CreatePanel (PanelRec: TDynRecord; AddTextures: TAddTextureArray; CurTex: Integer): Integer;
828 var
829 len: Integer;
830 panels: ^TPanelArray;
831 pan: TPanel;
832 pguid: Integer;
833 begin
834 Result := -1;
836 case PanelRec.PanelType of
837 PANEL_WALL, PANEL_OPENDOOR, PANEL_CLOSEDOOR: panels := @gWalls;
838 PANEL_BACK: panels := @gRenderBackgrounds;
839 PANEL_FORE: panels := @gRenderForegrounds;
840 PANEL_WATER: panels := @gWater;
841 PANEL_ACID1: panels := @gAcid1;
842 PANEL_ACID2: panels := @gAcid2;
843 PANEL_STEP: panels := @gSteps;
844 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: panels := @gLifts;
845 PANEL_BLOCKMON: panels := @gBlockMon;
846 else exit;
847 end;
849 len := Length(panels^);
850 SetLength(panels^, len+1);
852 pguid := Length(panByGUID);
853 SetLength(panByGUID, pguid+1); //FIXME!
854 pan := TPanel.Create(PanelRec, AddTextures, CurTex, Textures, pguid);
855 assert(pguid >= 0);
856 assert(pguid < Length(panByGUID));
857 panByGUID[pguid] := pan;
858 panels^[len] := pan;
859 pan.arrIdx := len;
860 pan.proxyId := -1;
861 pan.tag := panelTypeToTag(PanelRec.PanelType);
863 PanelRec.user['panel_guid'] := pguid;
865 //result := len;
866 result := pguid;
867 end;
870 function CreateNullTexture(RecName: String): Integer;
871 begin
872 RecName := toLowerCase1251(RecName);
873 if (TextNameHash = nil) then TextNameHash := THashStrInt.Create();
874 if TextNameHash.get(RecName, result) then exit; // i found her!
876 SetLength(Textures, Length(Textures) + 1);
877 Textures[High(Textures)].TextureName := RecName;
878 Textures[High(Textures)].FullName := '';
879 Result := High(Textures);
880 TextNameHash.put(RecName, result);
881 end;
884 function extractWadName (resourceName: string): string;
885 var
886 posN: Integer;
887 begin
888 posN := Pos(':', resourceName);
889 if posN > 0 then
890 Result:= Copy(resourceName, 0, posN-1)
891 else
892 Result := '';
893 end;
896 procedure addResToExternalResList (res: AnsiString);
897 var
898 uname: AnsiString;
899 f: Integer;
900 fi: TDiskFileInfo;
901 begin
902 if g_Game_IsClient or not g_Game_IsNet then exit;
903 if (length(res) = 0) then exit; // map wad
904 //res := extractWadName(res);
905 //if (length(res) = 0) then exit; // map wad
906 uname := toLowerCase1251(res);
907 // do not add duplicates
908 for f := 0 to High(gExternalResources) do
909 begin
910 if (gExternalResources[f].userName = uname) then exit;
911 end;
912 //writeln('***(000) addResToExternalResList: res=[', res, ']');
913 // add new resource
914 fi.userName := uname;
915 if not findFileCI(res) then exit;
916 //writeln('***(001) addResToExternalResList: res=[', res, ']');
917 fi.diskName := res;
918 if (not GetDiskFileInfo(res, fi)) then
919 begin
920 fi.tag := -1;
921 end
922 else
923 begin
924 //writeln('***(002) addResToExternalResList: res=[', res, ']');
925 fi.tag := 0; // non-zero means "cannot caclucate hash"
926 try
927 fi.hash := MD5File(fi.diskName);
928 except
929 fi.tag := -1;
930 end;
931 end;
932 //e_LogWritefln('addext: res=[%s]; uname=[%s]; diskName=[%s]', [res, fi.userName, fi.diskName]);
933 SetLength(gExternalResources, length(gExternalResources)+1);
934 gExternalResources[High(gExternalResources)] := fi;
935 end;
938 procedure compactExtResList ();
939 var
940 src, dest: Integer;
941 begin
942 src := 0;
943 dest := 0;
944 for src := 0 to High(gExternalResources) do
945 begin
946 if (gExternalResources[src].tag = 0) then
947 begin
948 // copy it
949 if (dest <> src) then gExternalResources[dest] := gExternalResources[src];
950 Inc(dest);
951 end;
952 end;
953 if (dest <> length(gExternalResources)) then SetLength(gExternalResources, dest);
954 end;
957 function GetReplacementWad (WadName: AnsiString): AnsiString;
958 begin
959 result := '';
960 if WadName <> '' then
961 begin
962 result := WadName;
963 if g_Game_IsClient then result := g_Res_FindReplacementWad(WadName);
964 if (result = WadName) then result := e_FindWad(WadDirs, result)
965 end;
966 end;
969 procedure generateExternalResourcesList (map: TDynRecord);
970 begin
971 SetLength(gExternalResources, 0);
972 addResToExternalResList(GetReplacementWad(g_ExtractWadName(map.MusicName)));
973 addResToExternalResList(GetReplacementWad(g_ExtractWadName(map.SkyName)));
974 end;
976 function CreateTexture (RecName: AnsiString; Map: String; log: Boolean): Integer;
977 var
978 HName: AnsiString;
979 WAD, WADz: TWADFile;
980 WADName, ResName: String;
981 ResData, ReszData: Pointer;
982 ResLen, ReszLen: Integer;
983 cfg: TConfig;
984 id: Integer;
985 begin
986 Result := -1;
987 HName := toLowerCase1251(RecName);
988 if (TextNameHash = nil) then
989 TextNameHash := THashStrInt.Create();
990 if TextNameHash.get(HName, Result) then
991 begin
992 // e_LogWritefln('CreateTexture: found loaded %s', [Result]);
993 end
994 else
995 begin
996 Result := -1;
997 if (BadTextNameHash = nil) or not BadTextNameHash.has(HName) then
998 begin
999 case RecName of
1000 TEXTURE_NAME_WATER, TEXTURE_NAME_ACID1, TEXTURE_NAME_ACID2:
1001 begin
1002 SetLength(Textures, Length(Textures) + 1);
1003 Textures[High(Textures)].FullName := RecName;
1004 Textures[High(Textures)].TextureName := RecName;
1005 Result := High(Textures);
1006 TextNameHash.put(RecName, result);
1007 end
1008 else
1009 WADName := GetReplacementWad(g_ExtractWadName(RecName));
1010 if (WADName <> '') then
1011 addResToExternalResList(WADName);
1012 if WADName = '' then
1013 WADName := Map;
1014 ResName := g_ExtractFilePathName(RecName);
1015 WAD := TWADFile.Create();
1016 if WAD.ReadFile(WadName) then
1017 begin
1018 if WAD.GetResource(ResName, ResData, ResLen, log) then
1019 begin
1020 if IsWadData(ResData, ResLen) then
1021 begin
1022 WADz := TWADFile.Create();
1023 if WADz.ReadMemory(ResData, ResLen) then
1024 begin
1025 if WADz.GetResource('TEXT/ANIM', ReszData, ReszLen) then
1026 begin
1027 cfg := TConfig.CreateMem(ReszData, ReszLen);
1028 if cfg <> nil then
1029 begin
1030 SetLength(Textures, Length(Textures) + 1);
1031 Textures[High(Textures)].TextureName := RecName;
1032 Textures[High(Textures)].FullName := WadName + ':' + ResName;
1033 Textures[High(Textures)].FramesCount := cfg.ReadInt('', 'framecount', 0);
1034 Textures[High(Textures)].Speed := cfg.ReadInt('', 'waitcount', 0);
1035 Result := High(Textures);
1036 TextNameHash.put(HName, result);
1037 cfg.Free;
1038 end;
1039 FreeMem(ReszData);
1040 end
1041 end;
1042 WADz.Free;
1043 end
1044 else
1045 begin
1046 SetLength(Textures, Length(Textures) + 1);
1047 Textures[High(Textures)].FullName := WADName + ':' + ResName;
1048 Textures[High(Textures)].TextureName := RecName;
1049 Result := High(Textures);
1050 TextNameHash.put(HName, result);
1051 end;
1052 FreeMem(ResData);
1053 end
1054 end;
1055 WAD.Free;
1056 end
1057 end;
1058 end;
1059 if Result < 0 then
1060 begin
1061 if (BadTextNameHash = nil) then
1062 BadTextNameHash := THashStrInt.Create();
1063 if log and (not BadTextNameHash.get(HName, id)) then
1064 e_WriteLog(Format('Error loading texture %s', [RecName]), TMsgType.Warning);
1065 BadTextNameHash.put(HName, -1);
1066 end
1067 end;
1069 procedure CreateItem(Item: TDynRecord);
1070 begin
1071 if g_Game_IsClient then Exit;
1073 if (not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) and
1074 ByteBool(Item.Options and ITEM_OPTION_ONLYDM) then
1075 Exit;
1077 g_Items_Create(Item.X, Item.Y, Item.ItemType, ByteBool(Item.Options and ITEM_OPTION_FALL),
1078 gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF, GM_COOP]);
1079 end;
1081 procedure CreateArea(Area: TDynRecord);
1082 var
1083 a: Integer;
1084 begin
1085 case Area.AreaType of
1086 AREA_DMPOINT, AREA_PLAYERPOINT1, AREA_PLAYERPOINT2,
1087 AREA_REDTEAMPOINT, AREA_BLUETEAMPOINT:
1088 begin
1089 SetLength(RespawnPoints, Length(RespawnPoints)+1);
1090 with RespawnPoints[High(RespawnPoints)] do
1091 begin
1092 X := Area.X;
1093 Y := Area.Y;
1094 Direction := TDirection(Area.Direction);
1096 case Area.AreaType of
1097 AREA_DMPOINT: PointType := RESPAWNPOINT_DM;
1098 AREA_PLAYERPOINT1: PointType := RESPAWNPOINT_PLAYER1;
1099 AREA_PLAYERPOINT2: PointType := RESPAWNPOINT_PLAYER2;
1100 AREA_REDTEAMPOINT: PointType := RESPAWNPOINT_RED;
1101 AREA_BLUETEAMPOINT: PointType := RESPAWNPOINT_BLUE;
1102 end;
1103 end;
1104 end;
1106 AREA_REDFLAG, AREA_BLUEFLAG:
1107 begin
1108 if Area.AreaType = AREA_REDFLAG then a := FLAG_RED else a := FLAG_BLUE;
1110 if FlagPoints[a] <> nil then Exit;
1112 New(FlagPoints[a]);
1114 with FlagPoints[a]^ do
1115 begin
1116 X := Area.X-FLAGRECT.X;
1117 Y := Area.Y-FLAGRECT.Y;
1118 Direction := TDirection(Area.Direction);
1119 end;
1121 with gFlags[a] do
1122 begin
1123 Obj.Rect := FLAGRECT;
1124 g_Map_ResetFlag(a);
1125 end;
1126 end;
1128 AREA_DOMFLAG:
1129 begin
1130 {SetLength(DOMFlagPoints, Length(DOMFlagPoints)+1);
1131 with DOMFlagPoints[High(DOMFlagPoints)] do
1132 begin
1133 X := Area.X;
1134 Y := Area.Y;
1135 Direction := TDirection(Area.Direction);
1136 end;
1138 g_Map_CreateFlag(DOMFlagPoints[High(DOMFlagPoints)], FLAG_DOM, FLAG_STATE_NORMAL);}
1139 end;
1140 end;
1141 end;
1143 function CreateTrigger (amapIdx: Integer; Trigger: TDynRecord; atpanid, atrigpanid: Integer): Integer;
1144 var
1145 _trigger: TTrigger;
1146 tp: TPanel;
1147 begin
1148 result := -1;
1149 if g_Game_IsClient and not (Trigger.TriggerType in [TRIGGER_SOUND, TRIGGER_MUSIC]) then Exit;
1151 with _trigger do
1152 begin
1153 mapId := Trigger.id;
1154 mapIndex := amapIdx;
1155 X := Trigger.X;
1156 Y := Trigger.Y;
1157 Width := Trigger.Width;
1158 Height := Trigger.Height;
1159 Enabled := Trigger.Enabled;
1160 TexturePanelGUID := atpanid;
1161 TriggerType := Trigger.TriggerType;
1162 ActivateType := Trigger.ActivateType;
1163 Keys := Trigger.Keys;
1164 trigPanelGUID := atrigpanid;
1165 // HACK: used in TPanel.CanChangeTexture. maybe there's a better way?
1166 if TexturePanelGUID <> -1 then
1167 begin
1168 tp := g_Map_PanelByGUID(TexturePanelGUID);
1169 if (tp <> nil) then tp.hasTexTrigger := True;
1170 end;
1171 end;
1173 result := Integer(g_Triggers_Create(_trigger, Trigger));
1174 end;
1176 procedure CreateMonster(monster: TDynRecord);
1177 var
1178 a: Integer;
1179 mon: TMonster;
1180 begin
1181 if g_Game_IsClient then Exit;
1183 if (gGameSettings.GameType = GT_SINGLE)
1184 or LongBool(gGameSettings.Options and GAME_OPTION_MONSTERS) then
1185 begin
1186 mon := g_Monsters_Create(monster.MonsterType, monster.X, monster.Y, TDirection(monster.Direction));
1188 if gTriggers <> nil then
1189 begin
1190 for a := 0 to High(gTriggers) do
1191 begin
1192 if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
1193 begin
1194 //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
1195 if (gTriggers[a].trigDataRec.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
1196 end;
1197 end;
1198 end;
1200 if monster.MonsterType <> MONSTER_BARREL then Inc(gTotalMonsters);
1201 end;
1202 end;
1204 procedure g_Map_ReAdd_DieTriggers();
1206 function monsDieTrig (mon: TMonster): Boolean;
1207 var
1208 a: Integer;
1209 //tw: TStrTextWriter;
1210 begin
1211 result := false; // don't stop
1212 mon.ClearTriggers();
1213 for a := 0 to High(gTriggers) do
1214 begin
1215 if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
1216 begin
1217 //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
1219 tw := TStrTextWriter.Create();
1220 try
1221 gTriggers[a].trigData.writeTo(tw);
1222 e_LogWritefln('=== trigger #%s ==='#10'%s'#10'---', [a, tw.str]);
1223 finally
1224 tw.Free();
1225 end;
1227 if (gTriggers[a].trigDataRec.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
1228 end;
1229 end;
1230 end;
1232 begin
1233 if g_Game_IsClient then Exit;
1235 g_Mons_ForEach(monsDieTrig);
1236 end;
1238 procedure mapCreateGrid ();
1239 var
1240 mapX0: Integer = $3fffffff;
1241 mapY0: Integer = $3fffffff;
1242 mapX1: Integer = -$3fffffff;
1243 mapY1: Integer = -$3fffffff;
1245 procedure calcBoundingBox (constref panels: TPanelArray);
1246 var
1247 idx: Integer;
1248 pan: TPanel;
1249 begin
1250 for idx := 0 to High(panels) do
1251 begin
1252 pan := panels[idx];
1253 if not pan.visvalid then continue;
1254 if (pan.Width < 1) or (pan.Height < 1) then continue;
1255 if (mapX0 > pan.x0) then mapX0 := pan.x0;
1256 if (mapY0 > pan.y0) then mapY0 := pan.y0;
1257 if (mapX1 < pan.x1) then mapX1 := pan.x1;
1258 if (mapY1 < pan.y1) then mapY1 := pan.y1;
1259 end;
1260 end;
1262 procedure addPanelsToGrid (constref panels: TPanelArray);
1263 var
1264 idx: Integer;
1265 pan: TPanel;
1266 newtag: Integer;
1267 begin
1268 //tag := panelTypeToTag(tag);
1269 for idx := 0 to High(panels) do
1270 begin
1271 pan := panels[idx];
1272 if not pan.visvalid then continue;
1273 if (pan.proxyId <> -1) then
1274 begin
1275 {$IF DEFINED(D2F_DEBUG)}
1276 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);
1277 {$ENDIF}
1278 continue;
1279 end;
1280 case pan.PanelType of
1281 PANEL_WALL: newtag := GridTagWall;
1282 PANEL_OPENDOOR, PANEL_CLOSEDOOR: newtag := GridTagDoor;
1283 PANEL_BACK: newtag := GridTagBack;
1284 PANEL_FORE: newtag := GridTagFore;
1285 PANEL_WATER: newtag := GridTagWater;
1286 PANEL_ACID1: newtag := GridTagAcid1;
1287 PANEL_ACID2: newtag := GridTagAcid2;
1288 PANEL_STEP: newtag := GridTagStep;
1289 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: newtag := GridTagLift;
1290 PANEL_BLOCKMON: newtag := GridTagBlockMon;
1291 else continue; // oops
1292 end;
1293 pan.tag := newtag;
1295 pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, newtag);
1296 // "enabled" flag has meaning only for doors and walls (engine assumes it); but meh...
1297 mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
1298 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
1300 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
1301 begin
1302 e_WriteLog(Format('INSERTED wall #%d(%d) enabled (%d)', [Integer(idx), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId])]), MSG_NOTIFY);
1303 end;
1305 {$ENDIF}
1306 end;
1307 end;
1309 begin
1310 mapGrid.Free();
1311 mapGrid := nil;
1313 calcBoundingBox(gWalls);
1314 calcBoundingBox(gRenderBackgrounds);
1315 calcBoundingBox(gRenderForegrounds);
1316 calcBoundingBox(gWater);
1317 calcBoundingBox(gAcid1);
1318 calcBoundingBox(gAcid2);
1319 calcBoundingBox(gSteps);
1320 calcBoundingBox(gLifts);
1321 calcBoundingBox(gBlockMon);
1323 e_LogWritefln('map dimensions: (%d,%d)-(%d,%d); editor size:(0,0)-(%d,%d)', [mapX0, mapY0, mapX1, mapY1, gMapInfo.Width, gMapInfo.Height]);
1325 if (mapX0 > 0) then mapX0 := 0;
1326 if (mapY0 > 0) then mapY0 := 0;
1328 if (mapX1 < gMapInfo.Width-1) then mapX1 := gMapInfo.Width-1;
1329 if (mapY1 < gMapInfo.Height-1) then mapY1 := gMapInfo.Height-1;
1331 mapGrid := TPanelGrid.Create(mapX0-128, mapY0-128, mapX1-mapX0+1+128*2, mapY1-mapY0+1+128*2);
1332 //mapGrid := TPanelGrid.Create(0, 0, gMapInfo.Width, gMapInfo.Height);
1334 addPanelsToGrid(gWalls);
1335 addPanelsToGrid(gRenderBackgrounds);
1336 addPanelsToGrid(gRenderForegrounds);
1337 addPanelsToGrid(gWater);
1338 addPanelsToGrid(gAcid1);
1339 addPanelsToGrid(gAcid2);
1340 addPanelsToGrid(gSteps);
1341 addPanelsToGrid(gLifts); // it doesn't matter which LIFT type is used here
1342 addPanelsToGrid(gBlockMon);
1344 mapGrid.dumpStats();
1346 g_Mons_InitTree(mapGrid.gridX0, mapGrid.gridY0, mapGrid.gridWidth, mapGrid.gridHeight);
1347 end;
1350 function g_Map_Load(Res: String): Boolean;
1351 const
1352 DefaultMusRes = 'Standart.wad:STDMUS\MUS1';
1353 DefaultSkyRes = 'Standart.wad:STDSKY\SKY0';
1354 type
1355 PTRec = ^TTRec;
1356 TTRec = record
1357 //TexturePanel: Integer;
1358 tnum: Integer;
1359 id: Integer;
1360 trigrec: TDynRecord;
1361 // texture pane;
1362 texPanelIdx: Integer;
1363 texPanel: TDynRecord;
1364 // "action" panel
1365 actPanelIdx: Integer;
1366 actPanel: TDynRecord;
1367 end;
1368 var
1369 WAD, TestWAD: TWADFile;
1370 //mapReader: TDynRecord = nil;
1371 mapTextureList: TDynField = nil; //TTexturesRec1Array; tagInt: texture index
1372 panels: TDynField = nil; //TPanelsRec1Array;
1373 items: TDynField = nil; //TItemsRec1Array;
1374 monsters: TDynField = nil; //TMonsterRec1Array;
1375 areas: TDynField = nil; //TAreasRec1Array;
1376 triggers: TDynField = nil; //TTriggersRec1Array;
1377 b, c, k: Integer;
1378 PanelID: DWORD;
1379 AddTextures: TAddTextureArray;
1380 TriggersTable: array of TTRec;
1381 FileName, mapResName, TexName, s: AnsiString;
1382 Data: Pointer;
1383 Len: Integer;
1384 ok: Boolean;
1385 CurTex, ntn: Integer;
1386 rec, texrec: TDynRecord;
1387 pttit: PTRec;
1388 pannum, trignum, cnt, tgpid: Integer;
1389 stt: UInt64;
1390 moveSpeed{, moveStart, moveEnd}: TDFPoint;
1391 //moveActive: Boolean;
1392 pan: TPanel;
1393 mapOk: Boolean = false;
1394 usedTextures: THashStrInt = nil; // key: mapTextureList
1395 begin
1396 mapGrid.Free();
1397 mapGrid := nil;
1398 TestWAD := nil;
1399 Data := nil;
1401 //gCurrentMap.Free();
1402 //gCurrentMap := nil;
1404 panByGUID := nil;
1406 Result := False;
1407 gMapInfo.Map := Res;
1408 TriggersTable := nil;
1409 //mapReader := nil;
1411 sfsGCDisable(); // temporary disable removing of temporary volumes
1412 try
1413 // Çàãðóçêà WAD (åñëè ó íàñ íåò óæå çàãðóæåíîé êàðòû)
1414 if (gCurrentMap = nil) then
1415 begin
1416 FileName := g_ExtractWadName(Res);
1417 e_LogWritefln('Loading map WAD [%s] (res=[%s])', [FileName, Res], TMsgType.Notify);
1418 g_Game_SetLoadingText(_lc[I_LOAD_WAD_FILE], 0, False);
1420 WAD := TWADFile.Create();
1421 if not WAD.ReadFile(FileName) then
1422 begin
1423 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_WAD], [FileName]));
1424 WAD.Free();
1425 Exit;
1426 end;
1428 if gTestMap <> '' then
1429 begin
1430 s := g_ExtractWadName(gTestMap);
1431 TestWAD := TWADFile.Create();
1432 if not TestWAD.ReadFile(s) then
1433 begin
1434 g_SimpleError(Format(_lc[I_GAME_ERROR_MAP_WAD], [s]));
1435 TestWAD.Free();
1436 TestWAD := nil;
1437 end;
1438 end;
1440 if TestWAD <> nil then
1441 begin
1442 mapResName := g_ExtractFileName(gTestMap);
1443 if not TestWAD.GetMapResource(mapResName, Data, Len) then
1444 begin
1445 g_SimpleError(Format(_lc[I_GAME_ERROR_MAP_RES], [mapResName]));
1446 Data := nil;
1447 end else
1448 e_WriteLog('Using test map: '+gTestMap, TMsgType.Notify);
1449 TestWAD.Free();
1450 TestWAD := nil;
1451 end;
1453 if Data = nil then
1454 begin
1455 //k8: why loader ignores path here?
1456 mapResName := g_ExtractFileName(Res);
1457 if not WAD.GetMapResource(mapResName, Data, Len) then
1458 begin
1459 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_RES], [mapResName]));
1460 WAD.Free();
1461 Exit;
1462 end;
1463 end;
1465 WAD.Free();
1467 if (Len < 4) then
1468 begin
1469 e_LogWritefln('invalid map file: ''%s''', [mapResName]);
1470 FreeMem(Data);
1471 exit;
1472 end;
1474 // Çàãðóçêà êàðòû:
1475 e_LogWritefln('Loading map: %s', [mapResName], TMsgType.Notify);
1476 g_Game_SetLoadingText(_lc[I_LOAD_MAP], 0, False);
1478 stt := getTimeMicro();
1480 try
1481 gCurrentMap := g_Map_ParseMap(Data, Len);
1482 except
1483 gCurrentMap.Free();
1484 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]));
1485 FreeMem(Data);
1486 gCurrentMapFileName := '';
1487 Exit;
1488 end;
1490 FreeMem(Data);
1492 if (gCurrentMap = nil) then
1493 begin
1494 e_LogWritefln('invalid map file: ''%s''', [mapResName]);
1495 gCurrentMapFileName := '';
1496 exit;
1497 end;
1498 end
1499 else
1500 begin
1501 stt := getTimeMicro();
1502 end;
1504 //gCurrentMap := mapReader;
1506 generateExternalResourcesList(gCurrentMap);
1507 mapTextureList := gCurrentMap['texture'];
1508 // get all other lists here too
1509 panels := gCurrentMap['panel'];
1510 triggers := gCurrentMap['trigger'];
1511 items := gCurrentMap['item'];
1512 areas := gCurrentMap['area'];
1513 monsters := gCurrentMap['monster'];
1515 // Çàãðóçêà îïèñàíèÿ êàðòû:
1516 e_WriteLog(' Reading map info...', TMsgType.Notify);
1517 g_Game_SetLoadingText(_lc[I_LOAD_MAP_HEADER], 0, False);
1519 with gMapInfo do
1520 begin
1521 Name := gCurrentMap.MapName;
1522 Description := gCurrentMap.MapDesc;
1523 Author := gCurrentMap.MapAuthor;
1524 MusicName := gCurrentMap.MusicName;
1525 SkyName := gCurrentMap.SkyName;
1526 Height := gCurrentMap.Height;
1527 Width := gCurrentMap.Width;
1528 end;
1530 // Çàãðóçêà òåêñòóð:
1531 g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], 0, False);
1532 // Äîáàâëåíèå òåêñòóð â Textures[]:
1533 if (mapTextureList <> nil) and (mapTextureList.count > 0) then
1534 begin
1535 e_WriteLog(' Loading textures:', TMsgType.Notify);
1536 g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], mapTextureList.count-1, False);
1538 // find used textures
1539 usedTextures := THashStrInt.Create();
1540 try
1541 if (panels <> nil) and (panels.count > 0) then
1542 begin
1543 for rec in panels do
1544 begin
1545 texrec := rec.TextureRec;
1546 if (texrec <> nil) then usedTextures.put(toLowerCase1251(texrec.Resource), 42);
1547 end;
1548 end;
1550 cnt := -1;
1551 for rec in mapTextureList do
1552 begin
1553 Inc(cnt);
1554 if not usedTextures.has(toLowerCase1251(rec.Resource)) then
1555 begin
1556 rec.tagInt := -1; // just in case
1557 e_LogWritefln(' Unused texture #%d: %s', [cnt, rec.Resource]);
1558 end
1559 else
1560 begin
1561 {$IF DEFINED(D2F_DEBUG_TXLOAD)}
1562 e_LogWritefln(' Loading texture #%d: %s', [cnt, rec.Resource]);
1563 {$ENDIF}
1564 ntn := CreateTexture(rec.Resource, FileName, True);
1565 if ntn < 0 then
1566 begin
1567 if rec.Anim then
1568 g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_ANIM], [rec.Resource]))
1569 else
1570 g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_SIMPLE], [rec.Resource]));
1571 ntn := CreateNullTexture(rec.Resource)
1572 end;
1573 rec.tagInt := ntn; // remember texture number
1574 end;
1575 g_Game_StepLoading();
1576 end;
1577 finally
1578 usedTextures.Free();
1579 end;
1581 // set panel tagInt to texture index
1582 if (panels <> nil) then
1583 begin
1584 for rec in panels do
1585 begin
1586 texrec := rec.TextureRec;
1587 if (texrec = nil) then rec.tagInt := -1 else rec.tagInt := texrec.tagInt;
1588 end;
1589 end;
1590 end;
1593 // Çàãðóçêà òðèããåðîâ
1594 gTriggerClientID := 0;
1595 e_WriteLog(' Loading triggers...', TMsgType.Notify);
1596 g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS], 0, False);
1598 // Çàãðóçêà ïàíåëåé
1599 e_WriteLog(' Loading panels...', TMsgType.Notify);
1600 g_Game_SetLoadingText(_lc[I_LOAD_PANELS], 0, False);
1602 // check texture numbers for panels
1603 if (panels <> nil) and (panels.count > 0) then
1604 begin
1605 for rec in panels do
1606 begin
1607 if (rec.tagInt < 0) then
1608 begin
1609 e_WriteLog('error loading map: invalid texture index for panel', TMsgType.Fatal);
1610 result := false;
1611 gCurrentMap.Free();
1612 gCurrentMap := nil;
1613 gCurrentMapFileName := '';
1614 exit;
1615 end;
1616 end;
1617 end;
1619 // Ñîçäàíèå òàáëèöû òðèããåðîâ (ñîîòâåòñòâèå ïàíåëåé òðèããåðàì)
1620 if (triggers <> nil) and (triggers.count > 0) then
1621 begin
1622 e_WriteLog(' Setting up trigger table...', TMsgType.Notify);
1623 //SetLength(TriggersTable, triggers.count);
1624 g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS_TABLE], triggers.count-1, False);
1626 SetLength(TriggersTable, triggers.count);
1627 trignum := -1;
1628 for rec in triggers do
1629 begin
1630 Inc(trignum);
1631 pttit := @TriggersTable[trignum];
1632 pttit.trigrec := rec;
1633 // Ñìåíà òåêñòóðû (âîçìîæíî, êíîïêè)
1634 pttit.texPanelIdx := -1; // will be fixed later
1635 pttit.texPanel := rec.TexturePanelRec;
1636 // action panel
1637 pttit.actPanelIdx := -1;
1638 if (rec.trigRec <> nil) then pttit.actPanel := rec.trigRec.tgPanelRec else pttit.actPanel := nil;
1639 // set flag
1640 if (pttit.texPanel <> nil) then pttit.texPanel.userPanelTrigRef := true;
1641 if (pttit.actPanel <> nil) then pttit.actPanel.userPanelTrigRef := true;
1642 // update progress
1643 g_Game_StepLoading();
1644 end;
1645 end;
1647 // Ñîçäàåì ïàíåëè
1648 if (panels <> nil) and (panels.count > 0) then
1649 begin
1650 e_WriteLog(' Setting up trigger links...', TMsgType.Notify);
1651 g_Game_SetLoadingText(_lc[I_LOAD_LINK_TRIGGERS], panels.count-1, False);
1653 pannum := -1;
1654 for rec in panels do
1655 begin
1656 Inc(pannum);
1657 //e_LogWritefln('PANSTART: pannum=%s', [pannum]);
1658 texrec := nil;
1659 SetLength(AddTextures, 0);
1660 CurTex := -1;
1661 ok := false;
1663 if (mapTextureList <> nil) then
1664 begin
1665 texrec := rec.TextureRec;
1666 ok := (texrec <> nil);
1667 end;
1669 if ok then
1670 begin
1671 // Ñìîòðèì, ññûëàþòñÿ ëè íà ýòó ïàíåëü òðèããåðû.
1672 // Åñëè äà - òî íàäî ñîçäàòü åùå òåêñòóð
1673 ok := false;
1674 if (TriggersTable <> nil) and (mapTextureList <> nil) then
1675 begin
1676 if rec.userPanelTrigRef then
1677 begin
1678 // e_LogWritefln('trigref for panel %s', [pannum]);
1679 ok := True;
1680 end;
1681 end;
1682 end;
1684 if ok then
1685 begin
1686 // Åñòü ññûëêè òðèããåðîâ íà ýòó ïàíåëü
1687 s := texrec.Resource;
1689 // Ñïåö-òåêñòóðû çàïðåùåíû
1690 if g_Map_IsSpecialTexture(s) then
1691 begin
1692 ok := false
1693 end
1694 else
1695 begin
1696 // Îïðåäåëÿåì íàëè÷èå è ïîëîæåíèå öèôð â êîíöå ñòðîêè
1697 ok := g_Texture_NumNameFindStart(s);
1698 end;
1700 // Åñëè ok, çíà÷èò åñòü öèôðû â êîíöå.
1701 // Çàãðóæàåì òåêñòóðû ñ îñòàëüíûìè #
1702 if ok then
1703 begin
1704 k := NNF_NAME_BEFORE;
1705 // Öèêë ïî èçìåíåíèþ èìåíè òåêñòóðû
1706 while ok or (k = NNF_NAME_BEFORE) or (k = NNF_NAME_EQUALS) do
1707 begin
1708 k := g_Texture_NumNameFindNext(TexName);
1710 if (k = NNF_NAME_BEFORE) or (k = NNF_NAME_AFTER) then
1711 begin
1712 ok := CreateTexture(TexName, FileName, False) >= 0;
1714 // Îíà ñóùåñòâóåò. Çàíîñèì åå ID â ñïèñîê ïàíåëè
1715 if ok then
1716 begin
1718 for c := 0 to High(Textures) do
1719 begin
1720 if (Textures[c].TextureName = TexName) then
1721 begin
1722 SetLength(AddTextures, Length(AddTextures)+1);
1723 AddTextures[High(AddTextures)].Texture := c;
1724 break;
1725 end;
1726 end;
1728 if (TextNameHash <> nil) and TextNameHash.get(toLowerCase1251(TexName), c) then
1729 begin
1730 SetLength(AddTextures, Length(AddTextures)+1);
1731 AddTextures[High(AddTextures)].Texture := c;
1732 end;
1733 end;
1734 end
1735 else
1736 begin
1737 if k = NNF_NAME_EQUALS then
1738 begin
1739 // Çàíîñèì òåêóùóþ òåêñòóðó íà ñâîå ìåñòî
1740 SetLength(AddTextures, Length(AddTextures)+1);
1741 AddTextures[High(AddTextures)].Texture := rec.tagInt; // internal texture number, not map index
1742 CurTex := High(AddTextures);
1743 ok := true;
1744 end
1745 else // NNF_NO_NAME
1746 begin
1747 ok := false;
1748 end;
1749 end;
1750 end; // while ok...
1752 ok := true;
1753 end; // if ok - åñòü ñìåæíûå òåêñòóðû
1754 end; // if ok - ññûëàþòñÿ òðèããåðû
1756 if not ok then
1757 begin
1758 // Çàíîñèì òîëüêî òåêóùóþ òåêñòóðó
1759 SetLength(AddTextures, 1);
1760 AddTextures[0].Texture := rec.tagInt; // internal texture number, not map index
1761 CurTex := 0;
1762 end;
1764 //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);
1766 //e_LogWritefln('PANADD: pannum=%s', [pannum]);
1768 // Ñîçäàåì ïàíåëü è çàïîìèíàåì åå GUID
1769 //e_LogWritefln('new panel; tcount=%s; curtex=%s', [Length(AddTextures), CurTex]);
1770 PanelID := CreatePanel(rec, AddTextures, CurTex);
1771 //e_LogWritefln('panel #%s of type %s got guid #%s', [pannum, rec.PanelType, PanelID]);
1772 rec.userPanelId := PanelID; // remember game panel id, we'll fix triggers later
1774 // setup lifts
1775 moveSpeed := rec.moveSpeed;
1776 //moveStart := rec.moveStart;
1777 //moveEnd := rec.moveEnd;
1778 //moveActive := rec['move_active'].value;
1779 if not moveSpeed.isZero then
1780 begin
1781 SetLength(gMovingWallIds, Length(gMovingWallIds)+1);
1782 gMovingWallIds[High(gMovingWallIds)] := PanelID;
1783 //e_LogWritefln('found moving panel ''%s'' (idx=%s; id=%s)', [rec.id, pannum, PanelID]);
1784 end;
1786 //e_LogWritefln('PANEND: pannum=%s', [pannum]);
1788 g_Game_StepLoading();
1789 end;
1790 end;
1792 // ×èíèì ID'û ïàíåëåé, êîòîðûå èñïîëüçóþòñÿ â òðèããåðàõ
1793 for b := 0 to High(TriggersTable) do
1794 begin
1795 if (TriggersTable[b].texPanel <> nil) then TriggersTable[b].texPanelIdx := TriggersTable[b].texPanel.userPanelId;
1796 if (TriggersTable[b].actPanel <> nil) then TriggersTable[b].actPanelIdx := TriggersTable[b].actPanel.userPanelId;
1797 end;
1799 // create map grid, init other grids (for monsters, for example)
1800 e_WriteLog('Creating map grid', TMsgType.Notify);
1801 mapCreateGrid();
1803 // Åñëè íå LoadState, òî ñîçäàåì òðèããåðû
1804 if (triggers <> nil) and (panels <> nil) and (not gLoadGameMode) then
1805 begin
1806 e_LogWritefln(' Creating triggers (%d)...', [triggers.count]);
1807 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_TRIGGERS], 0, False);
1808 // Óêàçûâàåì òèï ïàíåëè, åñëè åñòü
1809 trignum := -1;
1810 for rec in triggers do
1811 begin
1812 Inc(trignum);
1813 tgpid := TriggersTable[trignum].actPanelIdx;
1814 //e_LogWritefln('creating trigger #%s; texpantype=%s; shotpantype=%s (%d,%d)', [trignum, b, c, TriggersTable[trignum].texPanIdx, TriggersTable[trignum].ShotPanelIdx]);
1815 TriggersTable[trignum].tnum := trignum;
1816 TriggersTable[trignum].id := CreateTrigger(trignum, rec, TriggersTable[trignum].texPanelIdx, tgpid);
1817 end;
1818 end;
1820 //FIXME: use hashtable!
1821 for pan in panByGUID do
1822 begin
1823 if (pan.endPosTrigId >= 0) and (pan.endPosTrigId < Length(TriggersTable)) then
1824 begin
1825 pan.endPosTrigId := TriggersTable[pan.endPosTrigId].id;
1826 end;
1827 if (pan.endSizeTrigId >= 0) and (pan.endSizeTrigId < Length(TriggersTable)) then
1828 begin
1829 pan.endSizeTrigId := TriggersTable[pan.endSizeTrigId].id;
1830 end;
1831 end;
1833 // Çàãðóçêà ïðåäìåòîâ
1834 e_WriteLog(' Loading items...', TMsgType.Notify);
1835 g_Game_SetLoadingText(_lc[I_LOAD_ITEMS], 0, False);
1837 // Åñëè íå LoadState, òî ñîçäàåì ïðåäìåòû
1838 if (items <> nil) and not gLoadGameMode then
1839 begin
1840 e_WriteLog(' Spawning items...', TMsgType.Notify);
1841 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_ITEMS], 0, False);
1842 for rec in items do CreateItem(rec);
1843 end;
1845 // Çàãðóçêà îáëàñòåé
1846 e_WriteLog(' Loading areas...', TMsgType.Notify);
1847 g_Game_SetLoadingText(_lc[I_LOAD_AREAS], 0, False);
1849 // Åñëè íå LoadState, òî ñîçäàåì îáëàñòè
1850 if areas <> nil then
1851 begin
1852 e_WriteLog(' Creating areas...', TMsgType.Notify);
1853 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_AREAS], 0, False);
1854 for rec in areas do CreateArea(rec);
1855 end;
1857 // Çàãðóçêà ìîíñòðîâ
1858 e_WriteLog(' Loading monsters...', TMsgType.Notify);
1859 g_Game_SetLoadingText(_lc[I_LOAD_MONSTERS], 0, False);
1861 gTotalMonsters := 0;
1863 // Åñëè íå LoadState, òî ñîçäàåì ìîíñòðîâ
1864 if (monsters <> nil) and not gLoadGameMode then
1865 begin
1866 e_WriteLog(' Spawning monsters...', TMsgType.Notify);
1867 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_MONSTERS], 0, False);
1868 for rec in monsters do CreateMonster(rec);
1869 end;
1871 //gCurrentMap := mapReader; // this will be our current map now
1872 gCurrentMapFileName := Res;
1873 //mapReader := nil;
1875 // Çàãðóçêà íåáà
1876 gMapInfo.SkyFullName := '';
1877 if (gMapInfo.SkyName <> '') then
1878 begin
1879 e_WriteLog(' Loading sky: ' + gMapInfo.SkyName, TMsgType.Notify);
1880 g_Game_SetLoadingText(_lc[I_LOAD_SKY], 0, False);
1881 gMapInfo.SkyFullName := e_GetResourcePath(WadDirs, gMapInfo.SkyName, g_ExtractWadName(Res));
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 {$IFDEF ENABLE_GFX}
1907 // Åñëè íå LoadState, òî ñîçäàåì êàðòó ñòîëêíîâåíèé:
1908 if not gLoadGameMode then g_GFX_Init();
1909 {$ENDIF}
1911 // Ñáðîñ ëîêàëüíûõ ìàññèâîâ:
1912 mapTextureList := nil;
1913 panels := nil;
1914 items := nil;
1915 areas := nil;
1916 triggers := nil;
1917 TriggersTable := nil;
1918 AddTextures := nil;
1920 // Âêëþ÷àåì ìóçûêó, åñëè ýòî íå çàãðóçêà:
1921 if ok and (not gLoadGameMode) then
1922 begin
1923 gMusic.SetByName(gMapInfo.MusicName);
1924 gMusic.Play();
1925 end
1926 else
1927 begin
1928 gMusic.SetByName('');
1929 end;
1931 stt := getTimeMicro()-stt;
1932 e_LogWritefln('map loaded in %s.%s milliseconds', [Integer(stt div 1000), Integer(stt mod 1000)]);
1933 mapOk := true;
1934 finally
1935 sfsGCEnable(); // enable releasing unused volumes
1936 //mapReader.Free();
1937 e_UnpressAllKeys; // why not?
1938 if not mapOk then
1939 begin
1940 gCurrentMap.Free();
1941 gCurrentMap := nil;
1942 gCurrentMapFileName := '';
1943 end;
1944 end;
1946 compactExtResList();
1947 e_WriteLog('Done loading map.', TMsgType.Notify);
1948 Result := True;
1949 end;
1952 function g_Map_GetMapInfo(Res: String): TMapInfo;
1953 var
1954 WAD: TWADFile;
1955 mapReader: TDynRecord;
1956 //Header: TMapHeaderRec_1;
1957 FileName: String;
1958 Data: Pointer;
1959 Len: Integer;
1960 begin
1961 FillChar(Result, SizeOf(Result), 0);
1962 FileName := g_ExtractWadName(Res);
1964 WAD := TWADFile.Create();
1965 if not WAD.ReadFile(FileName) then
1966 begin
1967 WAD.Free();
1968 Exit;
1969 end;
1971 //k8: it ignores path again
1972 if not WAD.GetMapResource(g_ExtractFileName(Res), Data, Len) then
1973 begin
1974 WAD.Free();
1975 Exit;
1976 end;
1978 WAD.Free();
1980 try
1981 mapReader := g_Map_ParseMap(Data, Len);
1982 except
1983 mapReader := nil;
1984 FreeMem(Data);
1985 exit;
1986 end;
1988 FreeMem(Data);
1990 if (mapReader = nil) then exit;
1992 if (mapReader.Width > 0) and (mapReader.Height > 0) then
1993 begin
1994 Result.Name := mapReader.MapName;
1995 Result.Description := mapReader.MapDesc;
1996 Result.Map := Res;
1997 Result.Author := mapReader.MapAuthor;
1998 Result.Height := mapReader.Height;
1999 Result.Width := mapReader.Width;
2000 end
2001 else
2002 begin
2003 g_Console_Add(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]), True);
2004 //ZeroMemory(@Header, SizeOf(Header));
2005 Result.Name := _lc[I_GAME_ERROR_MAP_SELECT];
2006 Result.Description := _lc[I_GAME_ERROR_MAP_SELECT];
2007 Result.Map := Res;
2008 Result.Author := '';
2009 Result.Height := 0;
2010 Result.Width := 0;
2011 end;
2013 mapReader.Free();
2014 end;
2016 function g_Map_GetMapsList(WADName: string): SSArray;
2017 var
2018 WAD: TWADFile;
2019 a: Integer;
2020 ResList: SSArray;
2021 begin
2022 Result := nil;
2023 WAD := TWADFile.Create();
2024 if not WAD.ReadFile(WADName) then
2025 begin
2026 WAD.Free();
2027 Exit;
2028 end;
2029 ResList := WAD.GetMapResources();
2030 if ResList <> nil then
2031 begin
2032 for a := 0 to High(ResList) do
2033 begin
2034 SetLength(Result, Length(Result)+1);
2035 Result[High(Result)] := ResList[a];
2036 end;
2037 end;
2038 WAD.Free();
2039 end;
2041 function g_Map_Exist(Res: string): Boolean;
2042 var
2043 WAD: TWADFile;
2044 FileName, mnn: string;
2045 ResList: SSArray;
2046 a: Integer;
2047 begin
2048 Result := False;
2050 FileName := addWadExtension(g_ExtractWadName(Res));
2052 WAD := TWADFile.Create;
2053 if not WAD.ReadFile(FileName) then
2054 begin
2055 WAD.Free();
2056 Exit;
2057 end;
2059 ResList := WAD.GetMapResources();
2060 WAD.Free();
2062 mnn := g_ExtractFileName(Res);
2063 if ResList <> nil then
2064 for a := 0 to High(ResList) do if StrEquCI1251(ResList[a], mnn) then
2065 begin
2066 Result := True;
2067 Exit;
2068 end;
2069 end;
2071 procedure g_Map_Free(freeTextures: Boolean=true);
2073 procedure FreePanelArray(var panels: TPanelArray);
2074 var
2075 i: Integer;
2077 begin
2078 if panels <> nil then
2079 begin
2080 for i := 0 to High(panels) do
2081 panels[i].Free();
2082 panels := nil;
2083 end;
2084 end;
2086 begin
2087 {$IFDEF ENABLE_GFX}
2088 g_GFX_Free;
2089 {$ENDIF}
2090 g_Weapon_Free();
2091 g_Items_Free();
2092 g_Triggers_Free();
2093 g_Monsters_Free();
2095 RespawnPoints := nil;
2096 if FlagPoints[FLAG_RED] <> nil then
2097 begin
2098 Dispose(FlagPoints[FLAG_RED]);
2099 FlagPoints[FLAG_RED] := nil;
2100 end;
2101 if FlagPoints[FLAG_BLUE] <> nil then
2102 begin
2103 Dispose(FlagPoints[FLAG_BLUE]);
2104 FlagPoints[FLAG_BLUE] := nil;
2105 end;
2106 //DOMFlagPoints := nil;
2108 //gDOMFlags := nil;
2110 if (Length(gCurrentMapFileName) <> 0) then
2111 begin
2112 e_LogWritefln('g_Map_Free: previous map was ''%s''...', [gCurrentMapFileName]);
2113 end
2114 else
2115 begin
2116 e_LogWritefln('g_Map_Free: no previous map.', []);
2117 end;
2119 if freeTextures then
2120 begin
2121 e_LogWritefln('g_Map_Free: clearing textures...', []);
2122 Textures := nil;
2123 TextNameHash.Free();
2124 TextNameHash := nil;
2125 BadTextNameHash.Free();
2126 BadTextNameHash := nil;
2127 gCurrentMapFileName := '';
2128 gCurrentMap.Free();
2129 gCurrentMap := nil;
2130 end;
2132 panByGUID := nil;
2134 FreePanelArray(gWalls);
2135 FreePanelArray(gRenderBackgrounds);
2136 FreePanelArray(gRenderForegrounds);
2137 FreePanelArray(gWater);
2138 FreePanelArray(gAcid1);
2139 FreePanelArray(gAcid2);
2140 FreePanelArray(gSteps);
2141 FreePanelArray(gLifts);
2142 FreePanelArray(gBlockMon);
2143 gMovingWallIds := nil;
2145 g_Game_StopAllSounds(False);
2146 gMusic.FreeSound();
2147 g_Sound_Delete(gMapInfo.MusicName);
2149 gMapInfo.Name := '';
2150 gMapInfo.Description := '';
2151 gMapInfo.MusicName := '';
2152 gMapInfo.Height := 0;
2153 gMapInfo.Width := 0;
2155 gDoorMap := nil;
2156 gLiftMap := nil;
2157 end;
2159 procedure g_Map_Update();
2160 var
2161 a, d, j: Integer;
2162 m: Word;
2163 s: String;
2164 b: Byte;
2166 procedure UpdatePanelArray(var panels: TPanelArray);
2167 var
2168 i: Integer;
2170 begin
2171 for i := 0 to High(panels) do panels[i].Update();
2172 end;
2174 begin
2175 if g_dbgpan_mplat_step then g_dbgpan_mplat_active := true;
2177 UpdatePanelArray(gWalls);
2178 UpdatePanelArray(gRenderBackgrounds);
2179 UpdatePanelArray(gRenderForegrounds);
2180 UpdatePanelArray(gWater);
2181 UpdatePanelArray(gAcid1);
2182 UpdatePanelArray(gAcid2);
2183 UpdatePanelArray(gSteps);
2185 if g_dbgpan_mplat_step then begin g_dbgpan_mplat_step := false; g_dbgpan_mplat_active := false; end;
2187 if gGameSettings.GameMode = GM_CTF then
2188 begin
2189 for a := FLAG_RED to FLAG_BLUE do
2190 begin
2191 if not (gFlags[a].State in [FLAG_STATE_NONE, FLAG_STATE_CAPTURED]) then
2192 begin
2193 with gFlags[a] do
2194 begin
2195 Obj.oldX := Obj.X;
2196 Obj.oldY := Obj.Y;
2198 m := g_Obj_Move(@Obj, True, True);
2200 NeedSend := NeedSend or (Obj.X <> Obj.oldX) or (Obj.Y <> Obj.oldY);
2202 if gTime mod (GAME_TICK*2) <> 0 then Continue;
2204 // Ñîïðîòèâëåíèå âîçäóõà
2205 Obj.Vel.X := z_dec(Obj.Vel.X, 1);
2207 // Òàéìàóò ïîòåðÿííîãî ôëàãà, ëèáî îí âûïàë çà êàðòó
2208 if ((Count = 0) or ByteBool(m and MOVE_FALLOUT)) and g_Game_IsServer then
2209 begin
2210 g_Map_ResetFlag(a);
2211 gFlags[a].CaptureTime := 0;
2212 if a = FLAG_RED then
2213 s := _lc[I_PLAYER_FLAG_RED]
2214 else
2215 s := _lc[I_PLAYER_FLAG_BLUE];
2216 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
2218 if (((gPlayer1 <> nil) and (((gPlayer1.Team = TEAM_RED) and (a = FLAG_RED)) or ((gPlayer1.Team = TEAM_BLUE) and (a = FLAG_BLUE))))
2219 or ((gPlayer2 <> nil) and (((gPlayer2.Team = TEAM_RED) and (a = FLAG_RED)) or ((gPlayer2.Team = TEAM_BLUE) and (a = FLAG_BLUE))))) then
2220 b := 0
2221 else
2222 b := 1;
2224 if not sound_ret_flag[b].IsPlaying() then
2225 sound_ret_flag[b].Play();
2227 if g_Game_IsNet then
2228 MH_SEND_FlagEvent(FLAG_STATE_RETURNED, a, 0);
2229 Continue;
2230 end;
2232 if Count > 0 then Count -= 1;
2234 // Èãðîê áåðåò ôëàã
2235 if gPlayers <> nil then
2236 begin
2237 j := Random(Length(gPlayers)) - 1;
2238 for d := 0 to High(gPlayers) do
2239 begin
2240 Inc(j);
2241 if j > High(gPlayers) then j := 0;
2242 if gPlayers[j] <> nil then
2243 begin
2244 if gPlayers[j].alive and g_Obj_Collide(@Obj, @gPlayers[j].Obj) then
2245 begin
2246 if gPlayers[j].GetFlag(a) then Break;
2247 end;
2248 end;
2249 end;
2250 end;
2251 end;
2252 end;
2253 end;
2254 end;
2255 end;
2257 function g_Map_CollidePanelOld(X, Y: Integer; Width, Height: Word;
2258 PanelType: Word; b1x3: Boolean=false): Boolean;
2259 var
2260 a, h: Integer;
2261 begin
2262 Result := False;
2264 if WordBool(PanelType and PANEL_WALL) then
2265 if gWalls <> nil then
2266 begin
2267 h := High(gWalls);
2269 for a := 0 to h do
2270 if gWalls[a].Enabled and
2271 g_Collide(X, Y, Width, Height,
2272 gWalls[a].X, gWalls[a].Y,
2273 gWalls[a].Width, gWalls[a].Height) then
2274 begin
2275 Result := True;
2276 Exit;
2277 end;
2278 end;
2280 if WordBool(PanelType and PANEL_WATER) then
2281 if gWater <> nil then
2282 begin
2283 h := High(gWater);
2285 for a := 0 to h do
2286 if g_Collide(X, Y, Width, Height,
2287 gWater[a].X, gWater[a].Y,
2288 gWater[a].Width, gWater[a].Height) then
2289 begin
2290 Result := True;
2291 Exit;
2292 end;
2293 end;
2295 if WordBool(PanelType and PANEL_ACID1) then
2296 if gAcid1 <> nil then
2297 begin
2298 h := High(gAcid1);
2300 for a := 0 to h do
2301 if g_Collide(X, Y, Width, Height,
2302 gAcid1[a].X, gAcid1[a].Y,
2303 gAcid1[a].Width, gAcid1[a].Height) then
2304 begin
2305 Result := True;
2306 Exit;
2307 end;
2308 end;
2310 if WordBool(PanelType and PANEL_ACID2) then
2311 if gAcid2 <> nil then
2312 begin
2313 h := High(gAcid2);
2315 for a := 0 to h do
2316 if g_Collide(X, Y, Width, Height,
2317 gAcid2[a].X, gAcid2[a].Y,
2318 gAcid2[a].Width, gAcid2[a].Height) then
2319 begin
2320 Result := True;
2321 Exit;
2322 end;
2323 end;
2325 if WordBool(PanelType and PANEL_STEP) then
2326 if gSteps <> nil then
2327 begin
2328 h := High(gSteps);
2330 for a := 0 to h do
2331 if g_Collide(X, Y, Width, Height,
2332 gSteps[a].X, gSteps[a].Y,
2333 gSteps[a].Width, gSteps[a].Height) then
2334 begin
2335 Result := True;
2336 Exit;
2337 end;
2338 end;
2340 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then
2341 if gLifts <> nil then
2342 begin
2343 h := High(gLifts);
2345 for a := 0 to h do
2346 if ((WordBool(PanelType and (PANEL_LIFTUP)) and (gLifts[a].LiftType = LIFTTYPE_UP)) or
2347 (WordBool(PanelType and (PANEL_LIFTDOWN)) and (gLifts[a].LiftType = LIFTTYPE_DOWN)) or
2348 (WordBool(PanelType and (PANEL_LIFTLEFT)) and (gLifts[a].LiftType = LIFTTYPE_LEFT)) or
2349 (WordBool(PanelType and (PANEL_LIFTRIGHT)) and (gLifts[a].LiftType = LIFTTYPE_RIGHT))) and
2350 g_Collide(X, Y, Width, Height,
2351 gLifts[a].X, gLifts[a].Y,
2352 gLifts[a].Width, gLifts[a].Height) then
2353 begin
2354 Result := True;
2355 Exit;
2356 end;
2357 end;
2359 if WordBool(PanelType and PANEL_BLOCKMON) then
2360 if gBlockMon <> nil then
2361 begin
2362 h := High(gBlockMon);
2364 for a := 0 to h do
2365 if ( (not b1x3) or
2366 ((gBlockMon[a].Width + gBlockMon[a].Height) >= 64) ) and
2367 g_Collide(X, Y, Width, Height,
2368 gBlockMon[a].X, gBlockMon[a].Y,
2369 gBlockMon[a].Width, gBlockMon[a].Height) then
2370 begin
2371 Result := True;
2372 Exit;
2373 end;
2374 end;
2375 end;
2377 function g_Map_CollideLiquid_TextureOld(X, Y: Integer; Width, Height: Word): DWORD;
2378 var
2379 texid: DWORD;
2381 function checkPanels (constref panels: TPanelArray): Boolean;
2382 var
2383 a: Integer;
2384 begin
2385 result := false;
2386 if panels = nil then exit;
2387 for a := 0 to High(panels) do
2388 begin
2389 if g_Collide(X, Y, Width, Height, panels[a].X, panels[a].Y, panels[a].Width, panels[a].Height) then
2390 begin
2391 result := true;
2392 texid := panels[a].GetTextureID();
2393 exit;
2394 end;
2395 end;
2396 end;
2398 begin
2399 texid := LongWord(TEXTURE_NONE);
2400 result := texid;
2401 if not checkPanels(gWater) then
2402 if not checkPanels(gAcid1) then
2403 if not checkPanels(gAcid2) then exit;
2404 result := texid;
2405 end;
2408 function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word; PanelType: Word; b1x3: Boolean): Boolean;
2409 const
2410 SlowMask = GridTagLift or GridTagBlockMon;
2412 function checker (pan: TPanel; tag: Integer): Boolean;
2413 begin
2415 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
2416 begin
2417 result := pan.Enabled;
2418 exit;
2419 end;
2422 if ((tag and GridTagLift) <> 0) then
2423 begin
2424 result :=
2425 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
2426 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
2427 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
2428 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT))) {and
2429 g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
2430 exit;
2431 end;
2433 if ((tag and GridTagBlockMon) <> 0) then
2434 begin
2435 result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2436 exit;
2437 end;
2439 // other shit
2440 //result := g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2441 result := true; // i found her!
2442 end;
2444 var
2445 tagmask: Integer = 0;
2446 mwit: PPanel;
2447 it: TPanelGrid.Iter;
2448 pan: TPanel;
2449 begin
2450 result := false;
2451 if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor);
2452 if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater;
2453 if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1;
2454 if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2;
2455 if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep;
2456 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift;
2457 if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon;
2459 if (tagmask = 0) then exit; // just in case
2461 if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('*solids');
2462 if gdbg_map_use_accel_coldet then
2463 begin
2464 if ((tagmask and SlowMask) <> 0) then
2465 begin
2466 // slow
2467 it := mapGrid.forEachInAABB(X, Y, Width, Height, tagmask);
2468 for mwit in it do
2469 begin
2470 pan := mwit^;
2471 if ((pan.tag and GridTagLift) <> 0) then
2472 begin
2473 result :=
2474 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
2475 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
2476 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
2477 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT))) {and
2478 g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
2479 end
2480 else if ((pan.tag and GridTagBlockMon) <> 0) then
2481 begin
2482 result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2483 end
2484 else
2485 begin
2486 // other shit
2487 result := true; // i found her!
2488 end;
2489 if (result) then break;
2490 end;
2491 end
2492 else
2493 begin
2494 // fast
2495 it := mapGrid.forEachInAABB(X, Y, Width, Height, tagmask, false, true); // return first hit
2496 result := (it.length > 0);
2497 end;
2498 it.release();
2499 end
2500 else
2501 begin
2502 result := g_Map_CollidePanelOld(X, Y, Width, Height, PanelType, b1x3);
2503 end;
2504 if (profMapCollision <> nil) then profMapCollision.sectionEnd();
2505 end;
2508 // returns `true` if we need to stop
2509 function liquidChecker (pan: TPanel; var texid: DWORD; var cctype: Integer): Boolean; inline;
2510 begin
2511 result := false;
2512 //if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2)) = 0) then exit;
2513 // check priorities
2514 case cctype of
2515 0: if ((pan.tag and GridTagWater) = 0) then exit; // allowed: water
2516 1: if ((pan.tag and (GridTagWater or GridTagAcid1)) = 0) then exit; // allowed: water, acid1
2517 //2: if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2) = 0) then exit; // allowed: water, acid1, acid2
2518 end;
2519 // collision?
2520 //if not g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height) then exit;
2521 // yeah
2522 texid := pan.GetTextureID();
2523 // water? water has the highest priority, so stop right here
2524 if ((pan.tag and GridTagWater) <> 0) then begin cctype := 0; result := true; exit; end;
2525 // acid2?
2526 if ((pan.tag and GridTagAcid2) <> 0) then cctype := 2;
2527 // acid1?
2528 if ((pan.tag and GridTagAcid1) <> 0) then cctype := 1;
2529 end;
2531 function g_Map_CollideLiquid_Texture(X, Y: Integer; Width, Height: Word): DWORD;
2532 var
2533 cctype: Integer = 3; // priority: 0: water was hit, 1: acid1 was hit, 2: acid2 was hit; 3: nothing was hit
2534 mwit: PPanel;
2535 it: TPanelGrid.Iter;
2536 begin
2537 if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('liquids');
2538 if gdbg_map_use_accel_coldet then
2539 begin
2540 result := LongWord(TEXTURE_NONE);
2541 it := mapGrid.forEachInAABB(X, Y, Width, Height, (GridTagWater or GridTagAcid1 or GridTagAcid2));
2542 for mwit in it do if (liquidChecker(mwit^, result, cctype)) then break;
2543 it.release();
2544 end
2545 else
2546 begin
2547 result := g_Map_CollideLiquid_TextureOld(X, Y, Width, Height);
2548 end;
2549 if (profMapCollision <> nil) then profMapCollision.sectionEnd();
2550 end;
2553 procedure g_Map_EnableWall_XXX (ID: DWORD); begin if (ID < Length(gWalls)) then g_Map_EnableWallGUID(gWalls[ID].guid); end;
2554 procedure g_Map_DisableWall_XXX (ID: DWORD); begin if (ID < Length(gWalls)) then g_Map_DisableWallGUID(gWalls[ID].guid); end;
2555 procedure g_Map_SetLift_XXX (ID: DWORD; t: Integer); begin if (ID < Length(gLifts)) then g_Map_SetLiftGUID(gLifts[ID].guid, t); end;
2558 procedure g_Map_EnableWallGUID (pguid: Integer);
2559 var
2560 pan: TPanel;
2561 begin
2562 //pan := gWalls[ID];
2563 pan := g_Map_PanelByGUID(pguid);
2564 if (pan = nil) then exit;
2565 if pan.Enabled and mapGrid.proxyEnabled[pan.proxyId] then exit;
2567 pan.Enabled := True;
2569 {$IFDEF ENABLE_GFX}
2570 g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, true);
2571 {$ENDIF}
2573 mapGrid.proxyEnabled[pan.proxyId] := true;
2574 //if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := true
2575 //else pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, GridTagDoor);
2577 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2578 // mark platform as interesting
2579 pan.setDirty();
2581 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
2582 //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);
2583 {$ENDIF}
2584 end;
2587 procedure g_Map_DisableWallGUID (pguid: Integer);
2588 var
2589 pan: TPanel;
2590 begin
2591 //pan := gWalls[ID];
2592 pan := g_Map_PanelByGUID(pguid);
2593 if (pan = nil) then exit;
2594 if (not pan.Enabled) and (not mapGrid.proxyEnabled[pan.proxyId]) then exit;
2596 pan.Enabled := False;
2597 {$IFDEF ENABLE_GFX}
2598 g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, false);
2599 {$ENDIF}
2601 mapGrid.proxyEnabled[pan.proxyId] := false;
2602 //if (pan.proxyId >= 0) then begin mapGrid.removeBody(pan.proxyId); pan.proxyId := -1; end;
2604 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2605 // mark platform as interesting
2606 pan.setDirty();
2608 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
2609 //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);
2610 {$ENDIF}
2611 end;
2614 procedure g_Map_SwitchTextureGUID (pguid: Integer; AnimLoop: Byte = 0);
2615 var
2616 tp: TPanel;
2617 begin
2618 tp := g_Map_PanelByGUID(pguid);
2619 if (tp = nil) then exit;
2620 tp.NextTexture(AnimLoop);
2621 if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelTexture(pguid, AnimLoop);
2622 end;
2625 procedure g_Map_SetLiftGUID (pguid: Integer; t: Integer);
2626 var
2627 pan: TPanel;
2628 begin
2629 //pan := gLifts[ID];
2630 pan := g_Map_PanelByGUID(pguid);
2631 if (pan = nil) then exit;
2632 if not pan.isGLift then exit;
2634 if ({gLifts[ID]}pan.LiftType = t) then exit; //!FIXME!TRIGANY!
2636 with {gLifts[ID]} pan do
2637 begin
2638 LiftType := t;
2640 {$IFDEF ENABLE_GFX}
2641 g_Mark(X, Y, Width, Height, MARK_LIFT, false);
2642 //TODO: make separate lift tags, and change tag here
2643 case LiftType of
2644 LIFTTYPE_UP: g_Mark(X, Y, Width, Height, MARK_LIFTUP);
2645 LIFTTYPE_DOWN: g_Mark(X, Y, Width, Height, MARK_LIFTDOWN);
2646 LIFTTYPE_LEFT: g_Mark(X, Y, Width, Height, MARK_LIFTLEFT);
2647 LIFTTYPE_RIGHT: g_Mark(X, Y, Width, Height, MARK_LIFTRIGHT);
2648 end;
2649 {$ENDIF}
2651 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2652 // mark platform as interesting
2653 pan.setDirty();
2654 end;
2655 end;
2658 function g_Map_GetPoint (PointType: Byte; var RespawnPoint: TRespawnPoint): Boolean;
2659 var
2660 a: Integer;
2661 PointsArray: Array of TRespawnPoint;
2662 begin
2663 Result := False;
2664 SetLength(PointsArray, 0);
2666 if RespawnPoints = nil then
2667 Exit;
2669 for a := 0 to High(RespawnPoints) do
2670 if RespawnPoints[a].PointType = PointType then
2671 begin
2672 SetLength(PointsArray, Length(PointsArray)+1);
2673 PointsArray[High(PointsArray)] := RespawnPoints[a];
2674 end;
2676 if PointsArray = nil then
2677 Exit;
2679 RespawnPoint := PointsArray[Random(Length(PointsArray))];
2680 Result := True;
2681 end;
2683 function g_Map_GetPointCount(PointType: Byte): Word;
2684 var
2685 a: Integer;
2686 begin
2687 Result := 0;
2689 if RespawnPoints = nil then
2690 Exit;
2692 for a := 0 to High(RespawnPoints) do
2693 if RespawnPoints[a].PointType = PointType then
2694 Result := Result + 1;
2695 end;
2697 function g_Map_GetRandomPointType(): Byte;
2698 begin
2699 if RespawnPoints = nil then
2700 Result := 255
2701 else
2702 Result := RespawnPoints[Random(Length(RespawnPoints))].PointType;
2703 end;
2705 function g_Map_HaveFlagPoints(): Boolean;
2706 begin
2707 Result := (FlagPoints[FLAG_RED] <> nil) and (FlagPoints[FLAG_BLUE] <> nil);
2708 end;
2710 procedure g_Map_ResetFlag(Flag: Byte);
2711 begin
2712 with gFlags[Flag] do
2713 begin
2714 Obj.X := -1000;
2715 Obj.Y := -1000;
2716 Obj.Vel.X := 0;
2717 Obj.Vel.Y := 0;
2718 Direction := TDirection.D_LEFT;
2719 State := FLAG_STATE_NONE;
2720 if FlagPoints[Flag] <> nil then
2721 begin
2722 Obj.X := FlagPoints[Flag]^.X;
2723 Obj.Y := FlagPoints[Flag]^.Y;
2724 Direction := FlagPoints[Flag]^.Direction;
2725 State := FLAG_STATE_NORMAL;
2726 end;
2727 Obj.oldX := Obj.X;
2728 Obj.oldY := Obj.Y;
2729 NeedSend := False; // the event will take care of this
2730 Count := -1;
2731 end;
2732 end;
2734 procedure g_Map_SaveState (st: TStream);
2735 var
2736 str: String;
2738 procedure savePanels ();
2739 var
2740 pan: TPanel;
2741 begin
2742 // Ñîõðàíÿåì ïàíåëè
2743 utils.writeInt(st, LongInt(Length(panByGUID)));
2744 for pan in panByGUID do pan.SaveState(st);
2745 end;
2747 procedure saveFlag (flag: PFlag);
2748 var
2749 b: Byte;
2750 begin
2751 utils.writeSign(st, 'FLAG');
2752 utils.writeInt(st, Byte(0)); // version
2753 // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
2754 utils.writeInt(st, Byte(flag^.RespawnType));
2755 // Ñîñòîÿíèå ôëàãà
2756 utils.writeInt(st, Byte(flag^.State));
2757 // Íàïðàâëåíèå ôëàãà
2758 if flag^.Direction = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT
2759 utils.writeInt(st, Byte(b));
2760 // Îáúåêò ôëàãà
2761 Obj_SaveState(st, @flag^.Obj);
2762 end;
2764 begin
2765 savePanels();
2767 // Ñîõðàíÿåì ìóçûêó
2768 utils.writeSign(st, 'MUSI');
2769 utils.writeInt(st, Byte(0));
2770 // Íàçâàíèå ìóçûêè
2771 assert(gMusic <> nil, 'g_Map_SaveState: gMusic = nil');
2772 if gMusic.NoMusic then str := '' else str := gMusic.Name;
2773 utils.writeStr(st, str);
2774 // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
2775 utils.writeInt(st, LongWord(gMusic.GetPosition()));
2776 // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
2777 utils.writeBool(st, gMusic.SpecPause);
2779 ///// Ñîõðàíÿåì êîëè÷åñòâî ìîíñòðîâ: /////
2780 utils.writeInt(st, LongInt(gTotalMonsters));
2781 ///// /////
2783 //// Ñîõðàíÿåì ôëàãè, åñëè ýòî CTF: /////
2784 if (gGameSettings.GameMode = GM_CTF) then
2785 begin
2786 // Ôëàã Êðàñíîé êîìàíäû
2787 saveFlag(@gFlags[FLAG_RED]);
2788 // Ôëàã Ñèíåé êîìàíäû
2789 saveFlag(@gFlags[FLAG_BLUE]);
2790 end;
2791 ///// /////
2793 ///// Ñîõðàíÿåì êîëè÷åñòâî ïîáåä, åñëè ýòî TDM/CTF: /////
2794 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
2795 begin
2796 // Î÷êè Êðàñíîé êîìàíäû
2797 utils.writeInt(st, SmallInt(gTeamStat[TEAM_RED].Score));
2798 // Î÷êè Ñèíåé êîìàíäû
2799 utils.writeInt(st, SmallInt(gTeamStat[TEAM_BLUE].Score));
2800 end;
2801 ///// /////
2802 end;
2805 procedure g_Map_LoadState (st: TStream);
2806 var
2807 dw: DWORD;
2808 str: String;
2809 boo: Boolean;
2811 procedure loadPanels ();
2812 var
2813 pan: TPanel;
2814 begin
2815 // Çàãðóæàåì ïàíåëè
2816 if (Length(panByGUID) <> utils.readLongInt(st)) then raise XStreamError.Create('invalid number of saved panels');
2817 for pan in panByGUID do
2818 begin
2819 pan.LoadState(st);
2820 if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
2821 end;
2822 end;
2824 procedure loadFlag (flag: PFlag);
2825 var
2826 b: Byte;
2827 begin
2828 // Ñèãíàòóðà ôëàãà
2829 if not utils.checkSign(st, 'FLAG') then raise XStreamError.Create('invalid flag signature');
2830 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid flag version');
2831 // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
2832 flag^.RespawnType := utils.readByte(st);
2833 // Ñîñòîÿíèå ôëàãà
2834 flag^.State := utils.readByte(st);
2835 // Íàïðàâëåíèå ôëàãà
2836 b := utils.readByte(st);
2837 if (b = 1) then flag^.Direction := TDirection.D_LEFT else flag^.Direction := TDirection.D_RIGHT; // b = 2
2838 // Îáúåêò ôëàãà
2839 Obj_LoadState(@flag^.Obj, st);
2840 end;
2842 begin
2843 if (st = nil) then exit;
2845 ///// Çàãðóæàåì ñïèñêè ïàíåëåé: /////
2846 loadPanels();
2847 ///// /////
2849 {$IFDEF ENABLE_GFX}
2850 // Îáíîâëÿåì êàðòó ñòîëêíîâåíèé è ñåòêó
2851 g_GFX_Init();
2852 {$ENDIF}
2854 //mapCreateGrid();
2856 ///// Çàãðóæàåì ìóçûêó: /////
2857 if not utils.checkSign(st, 'MUSI') then raise XStreamError.Create('invalid music signature');
2858 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid music version');
2859 // Íàçâàíèå ìóçûêè
2860 assert(gMusic <> nil, 'g_Map_LoadState: gMusic = nil');
2861 str := utils.readStr(st);
2862 // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
2863 dw := utils.readLongWord(st);
2864 // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
2865 boo := utils.readBool(st);
2866 // Çàïóñêàåì ýòó ìóçûêó
2867 gMusic.SetByName(str);
2868 gMusic.SpecPause := boo;
2869 gMusic.Play();
2870 gMusic.Pause(true);
2871 gMusic.SetPosition(dw);
2872 ///// /////
2874 ///// Çàãðóæàåì êîëè÷åñòâî ìîíñòðîâ: /////
2875 gTotalMonsters := utils.readLongInt(st);
2876 ///// /////
2878 //// Çàãðóæàåì ôëàãè, åñëè ýòî CTF: /////
2879 if (gGameSettings.GameMode = GM_CTF) then
2880 begin
2881 // Ôëàã Êðàñíîé êîìàíäû
2882 loadFlag(@gFlags[FLAG_RED]);
2883 // Ôëàã Ñèíåé êîìàíäû
2884 loadFlag(@gFlags[FLAG_BLUE]);
2885 end;
2886 ///// /////
2888 ///// Çàãðóæàåì êîëè÷åñòâî ïîáåä, åñëè ýòî TDM/CTF: /////
2889 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
2890 begin
2891 // Î÷êè Êðàñíîé êîìàíäû
2892 gTeamStat[TEAM_RED].Score := utils.readSmallInt(st);
2893 // Î÷êè Ñèíåé êîìàíäû
2894 gTeamStat[TEAM_BLUE].Score := utils.readSmallInt(st);
2895 end;
2896 ///// /////
2897 end;
2900 // trace liquid, stepping by `dx` and `dy`
2901 // return last seen liquid coords, and `false` if we're started outside of the liquid
2902 function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
2903 const
2904 MaskLiquid = GridTagWater or GridTagAcid1 or GridTagAcid2;
2905 begin
2906 topx := x;
2907 topy := y;
2908 // started outside of the liquid?
2909 //if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then begin result := false; exit; end;
2910 if (g_Map_PanelAtPoint(x, y, MaskLiquid) = nil) then begin result := false; exit; end;
2911 if (dx = 0) and (dy = 0) then begin result := false; exit; end; // sanity check
2912 result := true;
2913 while true do
2914 begin
2915 Inc(x, dx);
2916 Inc(y, dy);
2917 //if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then exit; // out of the water, just exit
2918 if (g_Map_PanelAtPoint(x, y, MaskLiquid) = nil) then exit; // out of the water, just exit
2919 topx := x;
2920 topy := y;
2921 end;
2922 end;
2925 begin
2926 DynWarningCB := mapWarningCB;
2927 end.