DEADSOFTWARE

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