DEADSOFTWARE

render: hide panel textures into render
[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 Animation: TAnimation;
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 gBackSize: TDFPoint;
220 gDoorMap: array of array of DWORD;
221 gLiftMap: array of array of DWORD;
222 gWADHash: TMD5Digest;
223 BackID: DWORD = DWORD(-1);
224 gExternalResources: array of TDiskFileInfo = nil;
225 gMovingWallIds: array of Integer = nil;
227 gdbg_map_use_accel_render: Boolean = true;
228 gdbg_map_use_accel_coldet: Boolean = true;
229 profMapCollision: TProfiler = nil; //WARNING: FOR DEBUGGING ONLY!
230 gDrawPanelList: TBinHeapPanelDraw = nil; // binary heap of all walls we have to render, populated by `g_Map_CollectDrawPanels()`
232 gCurrentMap: TDynRecord = nil;
233 gCurrentMapFileName: AnsiString = ''; // so we can skip texture reloading
234 gTestMap: String = '';
237 function panelTypeToTag (panelType: Word): Integer; // returns GridTagXXX
240 type
241 TPanelGrid = specialize TBodyGridBase<TPanel>;
243 var
244 mapGrid: TPanelGrid = nil; // DO NOT USE! public for debugging only!
246 var (* private state *)
247 Textures: TLevelTextureArray = nil;
249 implementation
251 uses
252 e_input, e_log, e_res, g_items, g_gfx, g_console,
253 g_weapons, g_game, g_sound, e_sound, CONFIG,
254 g_options, g_triggers, g_player, r_textures, r_animations,
255 Math, g_monsters, g_saveload, g_language, g_netmsg,
256 sfs, xstreams, hashtable, wadreader,
257 g_res_downloader;
259 const
260 FLAGRECT: TRectWH = (X:15; Y:12; Width:33; Height:52);
261 MUSIC_SIGNATURE = $4953554D; // 'MUSI'
262 FLAG_SIGNATURE = $47414C46; // 'FLAG'
265 // ////////////////////////////////////////////////////////////////////////// //
266 procedure mapWarningCB (const msg: AnsiString; line, col: Integer);
267 begin
268 if (line > 0) then
269 begin
270 e_LogWritefln('parse error at (%s,%s): %s', [line, col, msg], TMsgType.Warning);
271 end
272 else
273 begin
274 e_LogWritefln('parse error: %s', [msg], TMsgType.Warning);
275 end;
276 end;
279 // ////////////////////////////////////////////////////////////////////////// //
280 var
281 panByGUID: array of TPanel = nil;
284 // ////////////////////////////////////////////////////////////////////////// //
285 function g_Map_PanelByGUID (aguid: Integer): TPanel; inline;
286 begin
287 //if (panByGUID = nil) or (not panByGUID.get(aguid, result)) then result := nil;
288 if (aguid >= 0) and (aguid < Length(panByGUID)) then result := panByGUID[aguid] else result := nil;
289 end;
292 // return `true` from `cb` to stop
293 function g_Map_ForEachPanel (cb: TForEachPanelCB): TPanel;
294 var
295 pan: TPanel;
296 begin
297 result := nil;
298 if not assigned(cb) then exit;
299 for pan in panByGUID do
300 begin
301 if cb(pan) then begin result := pan; exit; end;
302 end;
303 end;
306 procedure g_Map_NetSendInterestingPanels ();
307 var
308 pan: TPanel;
309 begin
310 if g_Game_IsServer and g_Game_IsNet then
311 begin
312 for pan in panByGUID do
313 begin
314 if pan.gncNeedSend then MH_SEND_PanelState(pan.guid);
315 end;
316 end;
317 end;
320 // ////////////////////////////////////////////////////////////////////////// //
321 function g_Map_MinX (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridX0 else result := 0; end;
322 function g_Map_MinY (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridY0 else result := 0; end;
323 function g_Map_MaxX (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridX0+mapGrid.gridWidth-1 else result := 0; end;
324 function g_Map_MaxY (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridY0+mapGrid.gridHeight-1 else result := 0; end;
327 // ////////////////////////////////////////////////////////////////////////// //
328 var
329 dfmapdef: TDynMapDef = nil;
332 procedure loadMapDefinition ();
333 var
334 pr: TTextParser = nil;
335 st: TStream = nil;
336 WAD: TWADFile = nil;
337 begin
338 if (dfmapdef <> nil) then exit;
340 try
341 e_LogWritefln('parsing "mapdef.txt"...', []);
342 st := e_OpenResourceRO(DataDirs, 'mapdef.txt');
343 e_LogWritefln('found local "mapdef.txt"', []);
344 except
345 st := nil;
346 end;
348 if (st = nil) then
349 begin
350 WAD := TWADFile.Create();
351 if not WAD.ReadFile(GameWAD) then
352 begin
353 //raise Exception.Create('cannot load "game.wad"');
354 st := nil;
355 end
356 else
357 begin
358 st := WAD.openFileStream('mapdef.txt');
359 end;
360 end;
362 try
363 if (st = nil) then
364 begin
365 //raise Exception.Create('cannot open "mapdef.txt"');
366 e_LogWriteln('using default "mapdef.txt"...');
367 pr := TStrTextParser.Create(defaultMapDef);
368 end
369 else
370 begin
371 pr := TFileTextParser.Create(st);
372 end;
373 except on e: Exception do
374 begin
375 e_LogWritefln('something is VERY wrong here! -- ', [e.message]);
376 raise;
377 end;
378 end;
380 try
381 dfmapdef := TDynMapDef.Create(pr);
382 except
383 on e: TDynParseException do
384 raise Exception.CreateFmt('ERROR in "mapdef.txt" at (%s,%s): %s', [e.tokLine, e.tokCol, e.message]);
385 on e: Exception do
386 raise Exception.CreateFmt('ERROR in "mapdef.txt" at (%s,%s): %s', [pr.tokLine, pr.tokCol, e.message]);
387 end;
389 st.Free();
390 WAD.Free();
391 end;
394 // ////////////////////////////////////////////////////////////////////////// //
395 function g_Map_ParseMap (data: Pointer; dataLen: Integer): TDynRecord;
396 var
397 wst: TSFSMemoryChunkStream = nil;
398 begin
399 result := nil;
400 if (dataLen < 4) then exit;
402 if (dfmapdef = nil) then writeln('need to load mapdef');
403 loadMapDefinition();
404 if (dfmapdef = nil) then raise Exception.Create('internal map loader error');
406 wst := TSFSMemoryChunkStream.Create(data, dataLen);
407 try
408 result := dfmapdef.parseMap(wst);
409 except
410 on e: TDynParseException do
411 begin
412 e_LogWritefln('ERROR at (%s,%s): %s', [e.tokLine, e.tokCol, e.message]);
413 wst.Free();
414 result := nil;
415 exit;
416 end;
417 on e: Exception do
418 begin
419 e_LogWritefln('ERROR: %s', [e.message]);
420 wst.Free();
421 result := nil;
422 exit;
423 end;
424 end;
426 //e_LogWriteln('map parsed.');
427 end;
430 // ////////////////////////////////////////////////////////////////////////// //
431 var
432 NNF_PureName: String; // Èìÿ òåêñòóðû áåç öèôð â êîíöå
433 NNF_PureExt: String; // extension postfix
434 NNF_FirstNum: Integer; // ×èñëî ó íà÷àëüíîé òåêñòóðû
435 NNF_CurrentNum: Integer; // Ñëåäóþùåå ÷èñëî ó òåêñòóðû
438 function g_Texture_NumNameFindStart(name: String): Boolean;
439 var
440 i, j: Integer;
442 begin
443 Result := False;
444 NNF_PureName := '';
445 NNF_PureExt := '';
446 NNF_FirstNum := -1;
447 NNF_CurrentNum := -1;
449 for i := Length(name) downto 1 do
450 if (name[i] = '_') then // "_" - ñèìâîë íà÷àëà íîìåðíîãî ïîñòôèêñà
451 begin
452 if i = Length(name) then
453 begin // Íåò öèôð â êîíöå ñòðîêè
454 Exit;
455 end
456 else
457 begin
458 j := i + 1;
459 while (j <= Length(name)) and (name[j] <> '.') do inc(j);
460 NNF_PureName := Copy(name, 1, i);
461 NNF_PureExt := Copy(name, j);
462 name := Copy(name, i + 1, j - i - 1);
463 Break;
464 end;
465 end;
467 // Íå ïåðåâåñòè â ÷èñëî:
468 if not TryStrToInt(name, NNF_FirstNum) then
469 Exit;
471 NNF_CurrentNum := 0;
473 Result := True;
474 end;
477 function g_Texture_NumNameFindNext(var newName: String): Byte;
478 begin
479 if (NNF_PureName = '') or (NNF_CurrentNum < 0) then
480 begin
481 newName := '';
482 Result := NNF_NO_NAME;
483 Exit;
484 end;
486 newName := NNF_PureName + IntToStr(NNF_CurrentNum) + NNF_PureExt;
488 if NNF_CurrentNum < NNF_FirstNum then
489 Result := NNF_NAME_BEFORE
490 else
491 if NNF_CurrentNum > NNF_FirstNum then
492 Result := NNF_NAME_AFTER
493 else
494 Result := NNF_NAME_EQUALS;
496 Inc(NNF_CurrentNum);
497 end;
500 // ////////////////////////////////////////////////////////////////////////// //
501 function panelTypeToTag (panelType: Word): Integer;
502 begin
503 case panelType of
504 PANEL_WALL: result := GridTagWall; // gWalls
505 PANEL_OPENDOOR, PANEL_CLOSEDOOR: result := GridTagDoor; // gWalls
506 PANEL_BACK: result := GridTagBack; // gRenderBackgrounds
507 PANEL_FORE: result := GridTagFore; // gRenderForegrounds
508 PANEL_WATER: result := GridTagWater; // gWater
509 PANEL_ACID1: result := GridTagAcid1; // gAcid1
510 PANEL_ACID2: result := GridTagAcid2; // gAcid2
511 PANEL_STEP: result := GridTagStep; // gSteps
512 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: result := GridTagLift; // gLifts -- this is for all lifts
513 PANEL_BLOCKMON: result := GridTagBlockMon; // gBlockMon -- this is for all blockmons
514 else result := GridTagInvalid;
515 end;
516 end;
519 class function TBinHeapPanelDrawCmp.less (const a, b: TPanel): Boolean; inline;
520 begin
521 if (a.tag < b.tag) then begin result := true; exit; end;
522 if (a.tag > b.tag) then begin result := false; exit; end;
523 result := (a.arrIdx < b.arrIdx);
524 end;
526 var
527 TextNameHash: THashStrInt = nil; // key: texture name; value: index in `Textures`
528 BadTextNameHash: THashStrInt = nil; // set; so we won't spam with non-existing texture messages
529 RespawnPoints: array of TRespawnPoint;
530 FlagPoints: array[FLAG_RED..FLAG_BLUE] of PFlagPoint;
531 //DOMFlagPoints: Array of TFlagPoint;
534 procedure g_Map_ProfilersBegin ();
535 begin
536 if (profMapCollision = nil) then profMapCollision := TProfiler.Create('COLSOLID', g_profile_history_size);
537 if (profMapCollision <> nil) then profMapCollision.mainBegin(g_profile_collision);
538 // create sections
539 if g_profile_collision and (profMapCollision <> nil) then
540 begin
541 profMapCollision.sectionBegin('*solids');
542 profMapCollision.sectionEnd();
543 profMapCollision.sectionBegin('liquids');
544 profMapCollision.sectionEnd();
545 end;
546 end;
548 procedure g_Map_ProfilersEnd ();
549 begin
550 if (profMapCollision <> nil) then profMapCollision.mainEnd();
551 end;
554 // wall index in `gWalls` or -1
555 function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
556 var
557 ex, ey: Integer;
558 begin
559 result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, (GridTagWall or GridTagDoor));
560 if (result <> nil) then
561 begin
562 if (hitx <> nil) then hitx^ := ex;
563 if (hity <> nil) then hity^ := ey;
564 end
565 else
566 begin
567 if (hitx <> nil) then hitx^ := x1;
568 if (hity <> nil) then hity^ := y1;
569 end;
570 end;
572 // returns panel or nil
573 function g_Map_traceToNearest (x0, y0, x1, y1: Integer; tag: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
574 var
575 ex, ey: Integer;
576 begin
577 result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, tag);
578 if (result <> nil) then
579 begin
580 if (hitx <> nil) then hitx^ := ex;
581 if (hity <> nil) then hity^ := ey;
582 end
583 else
584 begin
585 if (hitx <> nil) then hitx^ := x1;
586 if (hity <> nil) then hity^ := y1;
587 end;
588 end;
591 function xxPanAtPointChecker (pan: TPanel; panelType: Word): Boolean; inline;
592 begin
593 if ((pan.tag and GridTagLift) <> 0) then
594 begin
595 // stop if the lift of the right type
596 result :=
597 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
598 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
599 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
600 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT)));
601 exit;
602 end;
603 result := true; // otherwise, stop anyway, 'cause `forEachAtPoint()` is guaranteed to call this only for correct panels
604 end;
606 function g_Map_HasAnyPanelAtPoint (x, y: Integer; panelType: Word): Boolean;
607 var
608 tagmask: Integer = 0;
609 mwit: PPanel;
610 it: TPanelGrid.Iter;
611 begin
612 result := false;
614 if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor);
615 if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater;
616 if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1;
617 if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2;
618 if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep;
619 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift;
620 if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon;
622 if (tagmask = 0) then exit;// just in case
624 if ((tagmask and GridTagLift) <> 0) then
625 begin
626 // slow
627 it := mapGrid.forEachAtPoint(x, y, tagmask);
628 for mwit in it do if (xxPanAtPointChecker(mwit^, PanelType)) then begin result := true; break; end;
629 end
630 else
631 begin
632 // fast
633 it := mapGrid.forEachAtPoint(x, y, tagmask, false, true);
634 result := (it.length <> 0); // firsthit
635 end;
636 it.release();
637 end;
640 function g_Map_PanelAtPoint (x, y: Integer; tagmask: Integer=-1): TPanel;
641 var
642 it: TPanelGrid.Iter;
643 begin
644 result := nil;
645 if (tagmask = 0) then exit;
646 it := mapGrid.forEachAtPoint(x, y, tagmask, false, true); // firsthit
647 if (it.length <> 0) then result := it.first^;
648 it.release();
649 end;
652 function g_Map_IsSpecialTexture(Texture: String): Boolean;
653 begin
654 Result := (Texture = TEXTURE_NAME_WATER) or
655 (Texture = TEXTURE_NAME_ACID1) or
656 (Texture = TEXTURE_NAME_ACID2);
657 end;
659 procedure CreateDoorMap();
660 var
661 PanelArray: Array of record
662 X, Y: Integer;
663 Width, Height: Word;
664 Active: Boolean;
665 PanelID: DWORD;
666 end;
667 a, b, c, m, i, len: Integer;
668 ok: Boolean;
669 begin
670 if gWalls = nil then
671 Exit;
673 i := 0;
674 len := 128;
675 SetLength(PanelArray, len);
677 for a := 0 to High(gWalls) do
678 if gWalls[a].Door then
679 begin
680 PanelArray[i].X := gWalls[a].X;
681 PanelArray[i].Y := gWalls[a].Y;
682 PanelArray[i].Width := gWalls[a].Width;
683 PanelArray[i].Height := gWalls[a].Height;
684 PanelArray[i].Active := True;
685 PanelArray[i].PanelID := a;
687 i := i + 1;
688 if i = len then
689 begin
690 len := len + 128;
691 SetLength(PanelArray, len);
692 end;
693 end;
695 // Íåò äâåðåé:
696 if i = 0 then
697 begin
698 PanelArray := nil;
699 Exit;
700 end;
702 SetLength(gDoorMap, 0);
704 g_Game_SetLoadingText(_lc[I_LOAD_DOOR_MAP], i-1, False);
706 for a := 0 to i-1 do
707 if PanelArray[a].Active then
708 begin
709 PanelArray[a].Active := False;
710 m := Length(gDoorMap);
711 SetLength(gDoorMap, m+1);
712 SetLength(gDoorMap[m], 1);
713 gDoorMap[m, 0] := PanelArray[a].PanelID;
714 ok := True;
716 while ok do
717 begin
718 ok := False;
720 for b := 0 to i-1 do
721 if PanelArray[b].Active then
722 for c := 0 to High(gDoorMap[m]) do
723 if {((gRenderWalls[PanelArray[b].RenderPanelID].TextureID = gRenderWalls[gDoorMap[m, c]].TextureID) or
724 gRenderWalls[PanelArray[b].RenderPanelID].Hide or gRenderWalls[gDoorMap[m, c]].Hide) and}
725 g_CollideAround(PanelArray[b].X, PanelArray[b].Y,
726 PanelArray[b].Width, PanelArray[b].Height,
727 gWalls[gDoorMap[m, c]].X,
728 gWalls[gDoorMap[m, c]].Y,
729 gWalls[gDoorMap[m, c]].Width,
730 gWalls[gDoorMap[m, c]].Height) then
731 begin
732 PanelArray[b].Active := False;
733 SetLength(gDoorMap[m],
734 Length(gDoorMap[m])+1);
735 gDoorMap[m, High(gDoorMap[m])] := PanelArray[b].PanelID;
736 ok := True;
737 Break;
738 end;
739 end;
741 g_Game_StepLoading();
742 end;
744 PanelArray := nil;
745 end;
747 procedure CreateLiftMap();
748 var
749 PanelArray: Array of record
750 X, Y: Integer;
751 Width, Height: Word;
752 Active: Boolean;
753 end;
754 a, b, c, len, i, j: Integer;
755 ok: Boolean;
756 begin
757 if gLifts = nil then
758 Exit;
760 len := Length(gLifts);
761 SetLength(PanelArray, len);
763 for a := 0 to len-1 do
764 begin
765 PanelArray[a].X := gLifts[a].X;
766 PanelArray[a].Y := gLifts[a].Y;
767 PanelArray[a].Width := gLifts[a].Width;
768 PanelArray[a].Height := gLifts[a].Height;
769 PanelArray[a].Active := True;
770 end;
772 SetLength(gLiftMap, len);
773 i := 0;
775 g_Game_SetLoadingText(_lc[I_LOAD_LIFT_MAP], len-1, False);
777 for a := 0 to len-1 do
778 if PanelArray[a].Active then
779 begin
780 PanelArray[a].Active := False;
781 SetLength(gLiftMap[i], 32);
782 j := 0;
783 gLiftMap[i, j] := a;
784 ok := True;
786 while ok do
787 begin
788 ok := False;
789 for b := 0 to len-1 do
790 if PanelArray[b].Active then
791 for c := 0 to j do
792 if g_CollideAround(PanelArray[b].X,
793 PanelArray[b].Y,
794 PanelArray[b].Width,
795 PanelArray[b].Height,
796 PanelArray[gLiftMap[i, c]].X,
797 PanelArray[gLiftMap[i, c]].Y,
798 PanelArray[gLiftMap[i, c]].Width,
799 PanelArray[gLiftMap[i, c]].Height) then
800 begin
801 PanelArray[b].Active := False;
802 j := j+1;
803 if j > High(gLiftMap[i]) then
804 SetLength(gLiftMap[i],
805 Length(gLiftMap[i])+32);
807 gLiftMap[i, j] := b;
808 ok := True;
810 Break;
811 end;
812 end;
814 SetLength(gLiftMap[i], j+1);
815 i := i+1;
817 g_Game_StepLoading();
818 end;
820 SetLength(gLiftMap, i);
822 PanelArray := nil;
823 end;
825 function CreatePanel (PanelRec: TDynRecord; AddTextures: TAddTextureArray; CurTex: Integer): Integer;
826 var
827 len: Integer;
828 panels: ^TPanelArray;
829 pan: TPanel;
830 pguid: Integer;
831 begin
832 Result := -1;
834 case PanelRec.PanelType of
835 PANEL_WALL, PANEL_OPENDOOR, PANEL_CLOSEDOOR: panels := @gWalls;
836 PANEL_BACK: panels := @gRenderBackgrounds;
837 PANEL_FORE: panels := @gRenderForegrounds;
838 PANEL_WATER: panels := @gWater;
839 PANEL_ACID1: panels := @gAcid1;
840 PANEL_ACID2: panels := @gAcid2;
841 PANEL_STEP: panels := @gSteps;
842 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: panels := @gLifts;
843 PANEL_BLOCKMON: panels := @gBlockMon;
844 else exit;
845 end;
847 len := Length(panels^);
848 SetLength(panels^, len+1);
850 pguid := Length(panByGUID);
851 SetLength(panByGUID, pguid+1); //FIXME!
852 pan := TPanel.Create(PanelRec, AddTextures, CurTex, Textures, pguid);
853 assert(pguid >= 0);
854 assert(pguid < Length(panByGUID));
855 panByGUID[pguid] := pan;
856 panels^[len] := pan;
857 pan.arrIdx := len;
858 pan.proxyId := -1;
859 pan.tag := panelTypeToTag(PanelRec.PanelType);
861 PanelRec.user['panel_guid'] := pguid;
863 //result := len;
864 result := pguid;
865 end;
868 function CreateNullTexture(RecName: String): Integer;
869 begin
870 RecName := toLowerCase1251(RecName);
871 if (TextNameHash = nil) then TextNameHash := THashStrInt.Create();
872 if TextNameHash.get(RecName, result) then exit; // i found her!
874 SetLength(Textures, Length(Textures) + 1);
875 Textures[High(Textures)].TextureName := RecName;
876 Textures[High(Textures)].FullName := '';
877 Textures[High(Textures)].Anim := False;
878 Result := High(Textures);
879 TextNameHash.put(RecName, result);
880 end;
883 function extractWadName (resourceName: string): string;
884 var
885 posN: Integer;
886 begin
887 posN := Pos(':', resourceName);
888 if posN > 0 then
889 Result:= Copy(resourceName, 0, posN-1)
890 else
891 Result := '';
892 end;
895 procedure addResToExternalResList (res: AnsiString);
896 var
897 uname: AnsiString;
898 f: Integer;
899 fi: TDiskFileInfo;
900 begin
901 if g_Game_IsClient or not g_Game_IsNet then exit;
902 if (length(res) = 0) then exit; // map wad
903 //res := extractWadName(res);
904 //if (length(res) = 0) then exit; // map wad
905 uname := toLowerCase1251(res);
906 // do not add duplicates
907 for f := 0 to High(gExternalResources) do
908 begin
909 if (gExternalResources[f].userName = uname) then exit;
910 end;
911 //writeln('***(000) addResToExternalResList: res=[', res, ']');
912 // add new resource
913 fi.userName := uname;
914 if not findFileCI(res) then exit;
915 //writeln('***(001) addResToExternalResList: res=[', res, ']');
916 fi.diskName := res;
917 if (not GetDiskFileInfo(res, fi)) then
918 begin
919 fi.tag := -1;
920 end
921 else
922 begin
923 //writeln('***(002) addResToExternalResList: res=[', res, ']');
924 fi.tag := 0; // non-zero means "cannot caclucate hash"
925 try
926 fi.hash := MD5File(fi.diskName);
927 except
928 fi.tag := -1;
929 end;
930 end;
931 //e_LogWritefln('addext: res=[%s]; uname=[%s]; diskName=[%s]', [res, fi.userName, fi.diskName]);
932 SetLength(gExternalResources, length(gExternalResources)+1);
933 gExternalResources[High(gExternalResources)] := fi;
934 end;
937 procedure compactExtResList ();
938 var
939 src, dest: Integer;
940 begin
941 src := 0;
942 dest := 0;
943 for src := 0 to High(gExternalResources) do
944 begin
945 if (gExternalResources[src].tag = 0) then
946 begin
947 // copy it
948 if (dest <> src) then gExternalResources[dest] := gExternalResources[src];
949 Inc(dest);
950 end;
951 end;
952 if (dest <> length(gExternalResources)) then SetLength(gExternalResources, dest);
953 end;
956 function GetReplacementWad (WadName: AnsiString): AnsiString;
957 begin
958 result := '';
959 if WadName <> '' then
960 begin
961 result := WadName;
962 if g_Game_IsClient then result := g_Res_FindReplacementWad(WadName);
963 if (result = WadName) then result := e_FindWad(WadDirs, result)
964 end;
965 end;
968 procedure generateExternalResourcesList (map: TDynRecord);
969 begin
970 SetLength(gExternalResources, 0);
971 addResToExternalResList(GetReplacementWad(g_ExtractWadName(map.MusicName)));
972 addResToExternalResList(GetReplacementWad(g_ExtractWadName(map.SkyName)));
973 end;
976 function CreateTexture (RecName: AnsiString; Map: string; log: Boolean): Integer;
977 var
978 WAD: TWADFile;
979 WADName, ResName: String;
980 ResData: Pointer;
981 ResLen: Integer;
982 begin
983 RecName := toLowerCase1251(RecName);
984 if (TextNameHash = nil) then TextNameHash := THashStrInt.Create();
985 if TextNameHash.get(RecName, result) then
986 begin
987 // i found her!
988 //e_LogWritefln('texture ''%s'' already loaded (%s)', [RecName, result]);
989 exit;
990 end;
992 Result := -1;
994 if (BadTextNameHash <> nil) and BadTextNameHash.has(RecName) then
995 exit; // don't do it again and again
997 case RecName of
998 TEXTURE_NAME_WATER, TEXTURE_NAME_ACID1, TEXTURE_NAME_ACID2:
999 begin
1000 SetLength(Textures, Length(Textures) + 1);
1001 Textures[High(Textures)].FullName := RecName;
1002 Textures[High(Textures)].TextureName := RecName;
1003 Textures[High(Textures)].Anim := False;
1004 Result := High(Textures);
1005 TextNameHash.put(RecName, result);
1006 end
1007 else
1008 WADName := GetReplacementWad(g_ExtractWadName(RecName));
1009 if (WADName <> '') then
1010 addResToExternalResList(WADName);
1011 if WADName = '' then
1012 WADName := Map;
1013 ResName := g_ExtractFilePathName(RecName);
1015 // !!! we have a better way to check that resource exists?
1016 WAD := TWADFile.Create();
1017 if WAD.ReadFile(WadName) and WAD.GetResource(ResName, ResData, ResLen, log) then
1018 begin
1019 FreeMem(ResData);
1020 SetLength(Textures, Length(Textures) + 1);
1021 Textures[High(Textures)].FullName := WADName + ':' + ResName;
1022 Textures[High(Textures)].TextureName := RecName;
1023 Textures[High(Textures)].Anim := False;
1024 Result := High(Textures);
1025 TextNameHash.put(RecName, result);
1026 end;
1027 WAD.Free;
1028 end;
1029 end;
1032 function CreateAnimTexture(RecName: String; Map: string; log: Boolean): Integer;
1033 var
1034 WAD: TWADFile;
1035 TextureWAD: PChar = nil;
1036 TextData: Pointer = nil;
1037 cfg: TConfig = nil;
1038 WADName: String;
1039 ResLength: Integer;
1040 f: Integer;
1041 begin
1042 RecName := toLowerCase1251(RecName);
1043 if (TextNameHash = nil) then TextNameHash := THashStrInt.Create();
1044 if TextNameHash.get(RecName, result) then
1045 begin
1046 // i found her!
1047 //e_LogWritefln('animated texture ''%s'' already loaded (%s)', [RecName, result]);
1048 exit;
1049 end;
1051 result := -1;
1053 //e_LogWritefln('*** Loading animated texture "%s"', [RecName]);
1055 if (BadTextNameHash = nil) then BadTextNameHash := THashStrInt.Create();
1056 if BadTextNameHash.get(RecName, f) then
1057 begin
1058 //e_WriteLog(Format('no animation texture %s (don''t worry)', [RecName]), MSG_NOTIFY);
1059 exit;
1060 end;
1062 // ×èòàåì WAD-ðåñóðñ àíèì.òåêñòóðû èç WAD'à â ïàìÿòü:
1063 WADName := GetReplacementWad(g_ExtractWadName(RecName));
1064 if (WADName <> '') then addResToExternalResList(WADName);
1065 if WADName = '' then WADName := Map; //WADName := GameDir+'/wads/'+WADName else
1067 WAD := TWADFile.Create();
1068 try
1069 WAD.ReadFile(WADName);
1070 if not WAD.GetResource(g_ExtractFilePathName(RecName), TextureWAD, ResLength, log) then
1071 begin
1072 if (BadTextNameHash = nil) then BadTextNameHash := THashStrInt.Create();
1073 if log and (not BadTextNameHash.get(RecName, f)) then
1074 begin
1075 e_WriteLog(Format('Error loading animation texture %s', [RecName]), TMsgType.Warning);
1076 //e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
1077 end;
1078 BadTextNameHash.put(RecName, -1);
1079 exit;
1080 end;
1081 WAD.FreeWAD();
1083 // íåò, ýòî ñóïåðìåí!
1084 if not WAD.ReadMemory(TextureWAD, ResLength) then
1085 begin
1086 e_WriteLog(Format('Animated texture WAD file "%s" is invalid', [RecName]), TMsgType.Warning);
1087 BadTextNameHash.put(RecName, -1);
1088 exit;
1089 end;
1091 // ×èòàåì INI-ðåñóðñ àíèì. òåêñòóðû è çàïîìèíàåì åãî óñòàíîâêè:
1092 if not WAD.GetResource('TEXT/ANIM', TextData, ResLength) then
1093 begin
1094 e_WriteLog(Format('Animated texture file "%s" has invalid INI', [RecName]), TMsgType.Warning);
1095 BadTextNameHash.put(RecName, -1);
1096 exit;
1097 end;
1099 WAD.Free();
1100 WAD := nil;
1102 cfg := TConfig.CreateMem(TextData, ResLength);
1103 if cfg <> nil then
1104 begin
1105 SetLength(Textures, Length(Textures) + 1);
1106 Textures[High(Textures)].TextureName := RecName;
1107 Textures[High(Textures)].FullName := WadName + ':' + g_ExtractFilePathName(RecName);
1108 Textures[High(Textures)].Anim := True;
1109 Textures[High(Textures)].FramesCount := cfg.ReadInt('', 'framecount', 0);
1110 Textures[High(Textures)].Speed := cfg.ReadInt('', 'waitcount', 0);
1111 result := High(Textures);
1112 TextNameHash.put(RecName, result);
1113 cfg.Free();
1114 cfg := nil;
1115 end;
1116 finally
1117 WAD.Free();
1118 cfg.Free();
1119 if (TextureWAD <> nil) then FreeMem(TextureWAD);
1120 if (TextData <> nil) then FreeMem(TextData);
1121 end;
1122 end;
1124 procedure CreateItem(Item: TDynRecord);
1125 begin
1126 if g_Game_IsClient then Exit;
1128 if (not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) and
1129 ByteBool(Item.Options and ITEM_OPTION_ONLYDM) then
1130 Exit;
1132 g_Items_Create(Item.X, Item.Y, Item.ItemType, ByteBool(Item.Options and ITEM_OPTION_FALL),
1133 gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF, GM_COOP]);
1134 end;
1136 procedure CreateArea(Area: TDynRecord);
1137 var
1138 a: Integer;
1139 id: DWORD = 0;
1140 begin
1141 case Area.AreaType of
1142 AREA_DMPOINT, AREA_PLAYERPOINT1, AREA_PLAYERPOINT2,
1143 AREA_REDTEAMPOINT, AREA_BLUETEAMPOINT:
1144 begin
1145 SetLength(RespawnPoints, Length(RespawnPoints)+1);
1146 with RespawnPoints[High(RespawnPoints)] do
1147 begin
1148 X := Area.X;
1149 Y := Area.Y;
1150 Direction := TDirection(Area.Direction);
1152 case Area.AreaType of
1153 AREA_DMPOINT: PointType := RESPAWNPOINT_DM;
1154 AREA_PLAYERPOINT1: PointType := RESPAWNPOINT_PLAYER1;
1155 AREA_PLAYERPOINT2: PointType := RESPAWNPOINT_PLAYER2;
1156 AREA_REDTEAMPOINT: PointType := RESPAWNPOINT_RED;
1157 AREA_BLUETEAMPOINT: PointType := RESPAWNPOINT_BLUE;
1158 end;
1159 end;
1160 end;
1162 AREA_REDFLAG, AREA_BLUEFLAG:
1163 begin
1164 if Area.AreaType = AREA_REDFLAG then a := FLAG_RED else a := FLAG_BLUE;
1166 if FlagPoints[a] <> nil then Exit;
1168 New(FlagPoints[a]);
1170 with FlagPoints[a]^ do
1171 begin
1172 X := Area.X-FLAGRECT.X;
1173 Y := Area.Y-FLAGRECT.Y;
1174 Direction := TDirection(Area.Direction);
1175 end;
1177 with gFlags[a] do
1178 begin
1179 case a of
1180 FLAG_RED: g_Frames_Get(id, 'FRAMES_FLAG_RED');
1181 FLAG_BLUE: g_Frames_Get(id, 'FRAMES_FLAG_BLUE');
1182 end;
1184 Animation := TAnimation.Create(id, True, 8);
1185 Obj.Rect := FLAGRECT;
1187 g_Map_ResetFlag(a);
1188 end;
1189 end;
1191 AREA_DOMFLAG:
1192 begin
1193 {SetLength(DOMFlagPoints, Length(DOMFlagPoints)+1);
1194 with DOMFlagPoints[High(DOMFlagPoints)] do
1195 begin
1196 X := Area.X;
1197 Y := Area.Y;
1198 Direction := TDirection(Area.Direction);
1199 end;
1201 g_Map_CreateFlag(DOMFlagPoints[High(DOMFlagPoints)], FLAG_DOM, FLAG_STATE_NORMAL);}
1202 end;
1203 end;
1204 end;
1206 function CreateTrigger (amapIdx: Integer; Trigger: TDynRecord; atpanid, atrigpanid: Integer): Integer;
1207 var
1208 _trigger: TTrigger;
1209 tp: TPanel;
1210 begin
1211 result := -1;
1212 if g_Game_IsClient and not (Trigger.TriggerType in [TRIGGER_SOUND, TRIGGER_MUSIC]) then Exit;
1214 with _trigger do
1215 begin
1216 mapId := Trigger.id;
1217 mapIndex := amapIdx;
1218 X := Trigger.X;
1219 Y := Trigger.Y;
1220 Width := Trigger.Width;
1221 Height := Trigger.Height;
1222 Enabled := Trigger.Enabled;
1223 TexturePanelGUID := atpanid;
1224 TriggerType := Trigger.TriggerType;
1225 ActivateType := Trigger.ActivateType;
1226 Keys := Trigger.Keys;
1227 trigPanelGUID := atrigpanid;
1228 // HACK: used in TPanel.CanChangeTexture. maybe there's a better way?
1229 if TexturePanelGUID <> -1 then
1230 begin
1231 tp := g_Map_PanelByGUID(TexturePanelGUID);
1232 if (tp <> nil) then tp.hasTexTrigger := True;
1233 end;
1234 end;
1236 result := Integer(g_Triggers_Create(_trigger, Trigger));
1237 end;
1239 procedure CreateMonster(monster: TDynRecord);
1240 var
1241 a: Integer;
1242 mon: TMonster;
1243 begin
1244 if g_Game_IsClient then Exit;
1246 if (gGameSettings.GameType = GT_SINGLE)
1247 or LongBool(gGameSettings.Options and GAME_OPTION_MONSTERS) then
1248 begin
1249 mon := g_Monsters_Create(monster.MonsterType, monster.X, monster.Y, TDirection(monster.Direction));
1251 if gTriggers <> nil then
1252 begin
1253 for a := 0 to High(gTriggers) do
1254 begin
1255 if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
1256 begin
1257 //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
1258 if (gTriggers[a].trigDataRec.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
1259 end;
1260 end;
1261 end;
1263 if monster.MonsterType <> MONSTER_BARREL then Inc(gTotalMonsters);
1264 end;
1265 end;
1267 procedure g_Map_ReAdd_DieTriggers();
1269 function monsDieTrig (mon: TMonster): Boolean;
1270 var
1271 a: Integer;
1272 //tw: TStrTextWriter;
1273 begin
1274 result := false; // don't stop
1275 mon.ClearTriggers();
1276 for a := 0 to High(gTriggers) do
1277 begin
1278 if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
1279 begin
1280 //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
1282 tw := TStrTextWriter.Create();
1283 try
1284 gTriggers[a].trigData.writeTo(tw);
1285 e_LogWritefln('=== trigger #%s ==='#10'%s'#10'---', [a, tw.str]);
1286 finally
1287 tw.Free();
1288 end;
1290 if (gTriggers[a].trigDataRec.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
1291 end;
1292 end;
1293 end;
1295 begin
1296 if g_Game_IsClient then Exit;
1298 g_Mons_ForEach(monsDieTrig);
1299 end;
1301 procedure mapCreateGrid ();
1302 var
1303 mapX0: Integer = $3fffffff;
1304 mapY0: Integer = $3fffffff;
1305 mapX1: Integer = -$3fffffff;
1306 mapY1: Integer = -$3fffffff;
1308 procedure calcBoundingBox (constref panels: TPanelArray);
1309 var
1310 idx: Integer;
1311 pan: TPanel;
1312 begin
1313 for idx := 0 to High(panels) do
1314 begin
1315 pan := panels[idx];
1316 if not pan.visvalid then continue;
1317 if (pan.Width < 1) or (pan.Height < 1) then continue;
1318 if (mapX0 > pan.x0) then mapX0 := pan.x0;
1319 if (mapY0 > pan.y0) then mapY0 := pan.y0;
1320 if (mapX1 < pan.x1) then mapX1 := pan.x1;
1321 if (mapY1 < pan.y1) then mapY1 := pan.y1;
1322 end;
1323 end;
1325 procedure addPanelsToGrid (constref panels: TPanelArray);
1326 var
1327 idx: Integer;
1328 pan: TPanel;
1329 newtag: Integer;
1330 begin
1331 //tag := panelTypeToTag(tag);
1332 for idx := 0 to High(panels) do
1333 begin
1334 pan := panels[idx];
1335 if not pan.visvalid then continue;
1336 if (pan.proxyId <> -1) then
1337 begin
1338 {$IF DEFINED(D2F_DEBUG)}
1339 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);
1340 {$ENDIF}
1341 continue;
1342 end;
1343 case pan.PanelType of
1344 PANEL_WALL: newtag := GridTagWall;
1345 PANEL_OPENDOOR, PANEL_CLOSEDOOR: newtag := GridTagDoor;
1346 PANEL_BACK: newtag := GridTagBack;
1347 PANEL_FORE: newtag := GridTagFore;
1348 PANEL_WATER: newtag := GridTagWater;
1349 PANEL_ACID1: newtag := GridTagAcid1;
1350 PANEL_ACID2: newtag := GridTagAcid2;
1351 PANEL_STEP: newtag := GridTagStep;
1352 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: newtag := GridTagLift;
1353 PANEL_BLOCKMON: newtag := GridTagBlockMon;
1354 else continue; // oops
1355 end;
1356 pan.tag := newtag;
1358 pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, newtag);
1359 // "enabled" flag has meaning only for doors and walls (engine assumes it); but meh...
1360 mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
1361 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
1363 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
1364 begin
1365 e_WriteLog(Format('INSERTED wall #%d(%d) enabled (%d)', [Integer(idx), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId])]), MSG_NOTIFY);
1366 end;
1368 {$ENDIF}
1369 end;
1370 end;
1372 begin
1373 mapGrid.Free();
1374 mapGrid := nil;
1376 calcBoundingBox(gWalls);
1377 calcBoundingBox(gRenderBackgrounds);
1378 calcBoundingBox(gRenderForegrounds);
1379 calcBoundingBox(gWater);
1380 calcBoundingBox(gAcid1);
1381 calcBoundingBox(gAcid2);
1382 calcBoundingBox(gSteps);
1383 calcBoundingBox(gLifts);
1384 calcBoundingBox(gBlockMon);
1386 e_LogWritefln('map dimensions: (%d,%d)-(%d,%d); editor size:(0,0)-(%d,%d)', [mapX0, mapY0, mapX1, mapY1, gMapInfo.Width, gMapInfo.Height]);
1388 if (mapX0 > 0) then mapX0 := 0;
1389 if (mapY0 > 0) then mapY0 := 0;
1391 if (mapX1 < gMapInfo.Width-1) then mapX1 := gMapInfo.Width-1;
1392 if (mapY1 < gMapInfo.Height-1) then mapY1 := gMapInfo.Height-1;
1394 mapGrid := TPanelGrid.Create(mapX0-128, mapY0-128, mapX1-mapX0+1+128*2, mapY1-mapY0+1+128*2);
1395 //mapGrid := TPanelGrid.Create(0, 0, gMapInfo.Width, gMapInfo.Height);
1397 addPanelsToGrid(gWalls);
1398 addPanelsToGrid(gRenderBackgrounds);
1399 addPanelsToGrid(gRenderForegrounds);
1400 addPanelsToGrid(gWater);
1401 addPanelsToGrid(gAcid1);
1402 addPanelsToGrid(gAcid2);
1403 addPanelsToGrid(gSteps);
1404 addPanelsToGrid(gLifts); // it doesn't matter which LIFT type is used here
1405 addPanelsToGrid(gBlockMon);
1407 mapGrid.dumpStats();
1409 g_Mons_InitTree(mapGrid.gridX0, mapGrid.gridY0, mapGrid.gridWidth, mapGrid.gridHeight);
1410 end;
1413 function g_Map_Load(Res: String): Boolean;
1414 const
1415 DefaultMusRes = 'Standart.wad:STDMUS\MUS1';
1416 DefaultSkyRes = 'Standart.wad:STDSKY\SKY0';
1417 type
1418 PTRec = ^TTRec;
1419 TTRec = record
1420 //TexturePanel: Integer;
1421 tnum: Integer;
1422 id: Integer;
1423 trigrec: TDynRecord;
1424 // texture pane;
1425 texPanelIdx: Integer;
1426 texPanel: TDynRecord;
1427 // "action" panel
1428 actPanelIdx: Integer;
1429 actPanel: TDynRecord;
1430 end;
1431 var
1432 WAD, TestWAD: TWADFile;
1433 //mapReader: TDynRecord = nil;
1434 mapTextureList: TDynField = nil; //TTexturesRec1Array; tagInt: texture index
1435 panels: TDynField = nil; //TPanelsRec1Array;
1436 items: TDynField = nil; //TItemsRec1Array;
1437 monsters: TDynField = nil; //TMonsterRec1Array;
1438 areas: TDynField = nil; //TAreasRec1Array;
1439 triggers: TDynField = nil; //TTriggersRec1Array;
1440 b, c, k: Integer;
1441 PanelID: DWORD;
1442 AddTextures: TAddTextureArray;
1443 TriggersTable: array of TTRec;
1444 FileName, mapResName, TexName, s: AnsiString;
1445 Data: Pointer;
1446 Len: Integer;
1447 ok, isAnim: Boolean;
1448 CurTex, ntn: Integer;
1449 rec, texrec: TDynRecord;
1450 pttit: PTRec;
1451 pannum, trignum, cnt, tgpid: Integer;
1452 stt: UInt64;
1453 moveSpeed{, moveStart, moveEnd}: TDFPoint;
1454 //moveActive: Boolean;
1455 pan: TPanel;
1456 mapOk: Boolean = false;
1457 usedTextures: THashStrInt = nil; // key: mapTextureList
1458 begin
1459 mapGrid.Free();
1460 mapGrid := nil;
1461 TestWAD := nil;
1462 Data := nil;
1464 //gCurrentMap.Free();
1465 //gCurrentMap := nil;
1467 panByGUID := nil;
1469 Result := False;
1470 gMapInfo.Map := Res;
1471 TriggersTable := nil;
1472 //mapReader := nil;
1474 sfsGCDisable(); // temporary disable removing of temporary volumes
1475 try
1476 // Çàãðóçêà WAD (åñëè ó íàñ íåò óæå çàãðóæåíîé êàðòû)
1477 if (gCurrentMap = nil) then
1478 begin
1479 FileName := g_ExtractWadName(Res);
1480 e_LogWritefln('Loading map WAD [%s] (res=[%s])', [FileName, Res], TMsgType.Notify);
1481 g_Game_SetLoadingText(_lc[I_LOAD_WAD_FILE], 0, False);
1483 WAD := TWADFile.Create();
1484 if not WAD.ReadFile(FileName) then
1485 begin
1486 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_WAD], [FileName]));
1487 WAD.Free();
1488 Exit;
1489 end;
1491 if gTestMap <> '' then
1492 begin
1493 s := g_ExtractWadName(gTestMap);
1494 TestWAD := TWADFile.Create();
1495 if not TestWAD.ReadFile(s) then
1496 begin
1497 g_SimpleError(Format(_lc[I_GAME_ERROR_MAP_WAD], [s]));
1498 TestWAD.Free();
1499 TestWAD := nil;
1500 end;
1501 end;
1503 if TestWAD <> nil then
1504 begin
1505 mapResName := g_ExtractFileName(gTestMap);
1506 if not TestWAD.GetMapResource(mapResName, Data, Len) then
1507 begin
1508 g_SimpleError(Format(_lc[I_GAME_ERROR_MAP_RES], [mapResName]));
1509 Data := nil;
1510 end else
1511 e_WriteLog('Using test map: '+gTestMap, TMsgType.Notify);
1512 TestWAD.Free();
1513 TestWAD := nil;
1514 end;
1516 if Data = nil then
1517 begin
1518 //k8: why loader ignores path here?
1519 mapResName := g_ExtractFileName(Res);
1520 if not WAD.GetMapResource(mapResName, Data, Len) then
1521 begin
1522 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_RES], [mapResName]));
1523 WAD.Free();
1524 Exit;
1525 end;
1526 end;
1528 WAD.Free();
1530 if (Len < 4) then
1531 begin
1532 e_LogWritefln('invalid map file: ''%s''', [mapResName]);
1533 FreeMem(Data);
1534 exit;
1535 end;
1537 // Çàãðóçêà êàðòû:
1538 e_LogWritefln('Loading map: %s', [mapResName], TMsgType.Notify);
1539 g_Game_SetLoadingText(_lc[I_LOAD_MAP], 0, False);
1541 stt := getTimeMicro();
1543 try
1544 gCurrentMap := g_Map_ParseMap(Data, Len);
1545 except
1546 gCurrentMap.Free();
1547 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]));
1548 FreeMem(Data);
1549 gCurrentMapFileName := '';
1550 Exit;
1551 end;
1553 FreeMem(Data);
1555 if (gCurrentMap = nil) then
1556 begin
1557 e_LogWritefln('invalid map file: ''%s''', [mapResName]);
1558 gCurrentMapFileName := '';
1559 exit;
1560 end;
1561 end
1562 else
1563 begin
1564 stt := getTimeMicro();
1565 end;
1567 //gCurrentMap := mapReader;
1569 generateExternalResourcesList(gCurrentMap);
1570 mapTextureList := gCurrentMap['texture'];
1571 // get all other lists here too
1572 panels := gCurrentMap['panel'];
1573 triggers := gCurrentMap['trigger'];
1574 items := gCurrentMap['item'];
1575 areas := gCurrentMap['area'];
1576 monsters := gCurrentMap['monster'];
1578 // Çàãðóçêà îïèñàíèÿ êàðòû:
1579 e_WriteLog(' Reading map info...', TMsgType.Notify);
1580 g_Game_SetLoadingText(_lc[I_LOAD_MAP_HEADER], 0, False);
1582 with gMapInfo do
1583 begin
1584 Name := gCurrentMap.MapName;
1585 Description := gCurrentMap.MapDesc;
1586 Author := gCurrentMap.MapAuthor;
1587 MusicName := gCurrentMap.MusicName;
1588 SkyName := gCurrentMap.SkyName;
1589 Height := gCurrentMap.Height;
1590 Width := gCurrentMap.Width;
1591 end;
1593 // Çàãðóçêà òåêñòóð:
1594 g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], 0, False);
1595 // Äîáàâëåíèå òåêñòóð â Textures[]:
1596 if (mapTextureList <> nil) and (mapTextureList.count > 0) then
1597 begin
1598 e_WriteLog(' Loading textures:', TMsgType.Notify);
1599 g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], mapTextureList.count-1, False);
1601 // find used textures
1602 usedTextures := THashStrInt.Create();
1603 try
1604 if (panels <> nil) and (panels.count > 0) then
1605 begin
1606 for rec in panels do
1607 begin
1608 texrec := rec.TextureRec;
1609 if (texrec <> nil) then usedTextures.put(toLowerCase1251(texrec.Resource), 42);
1610 end;
1611 end;
1613 cnt := -1;
1614 for rec in mapTextureList do
1615 begin
1616 Inc(cnt);
1617 if not usedTextures.has(toLowerCase1251(rec.Resource)) then
1618 begin
1619 rec.tagInt := -1; // just in case
1620 e_LogWritefln(' Unused texture #%d: %s', [cnt, rec.Resource]);
1621 end
1622 else
1623 begin
1624 {$IF DEFINED(D2F_DEBUG_TXLOAD)}
1625 e_LogWritefln(' Loading texture #%d: %s', [cnt, rec.Resource]);
1626 {$ENDIF}
1627 //if g_Map_IsSpecialTexture(s) then e_WriteLog(' SPECIAL!', MSG_NOTIFY);
1628 if rec.Anim then
1629 begin
1630 // Àíèìèðîâàííàÿ òåêñòóðà
1631 ntn := CreateAnimTexture(rec.Resource, FileName, True);
1632 if (ntn < 0) then g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_ANIM], [rec.Resource]));
1633 end
1634 else
1635 begin
1636 // Îáû÷íàÿ òåêñòóðà
1637 ntn := CreateTexture(rec.Resource, FileName, True);
1638 if (ntn < 0) then g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_SIMPLE], [rec.Resource]));
1639 end;
1640 if (ntn < 0) then ntn := CreateNullTexture(rec.Resource);
1642 rec.tagInt := ntn; // remember texture number
1643 end;
1644 g_Game_StepLoading();
1645 end;
1646 finally
1647 usedTextures.Free();
1648 end;
1650 // set panel tagInt to texture index
1651 if (panels <> nil) then
1652 begin
1653 for rec in panels do
1654 begin
1655 texrec := rec.TextureRec;
1656 if (texrec = nil) then rec.tagInt := -1 else rec.tagInt := texrec.tagInt;
1657 end;
1658 end;
1659 end;
1662 // Çàãðóçêà òðèããåðîâ
1663 gTriggerClientID := 0;
1664 e_WriteLog(' Loading triggers...', TMsgType.Notify);
1665 g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS], 0, False);
1667 // Çàãðóçêà ïàíåëåé
1668 e_WriteLog(' Loading panels...', TMsgType.Notify);
1669 g_Game_SetLoadingText(_lc[I_LOAD_PANELS], 0, False);
1671 // check texture numbers for panels
1672 if (panels <> nil) and (panels.count > 0) then
1673 begin
1674 for rec in panels do
1675 begin
1676 if (rec.tagInt < 0) then
1677 begin
1678 e_WriteLog('error loading map: invalid texture index for panel', TMsgType.Fatal);
1679 result := false;
1680 gCurrentMap.Free();
1681 gCurrentMap := nil;
1682 gCurrentMapFileName := '';
1683 exit;
1684 end;
1685 end;
1686 end;
1688 // Ñîçäàíèå òàáëèöû òðèããåðîâ (ñîîòâåòñòâèå ïàíåëåé òðèããåðàì)
1689 if (triggers <> nil) and (triggers.count > 0) then
1690 begin
1691 e_WriteLog(' Setting up trigger table...', TMsgType.Notify);
1692 //SetLength(TriggersTable, triggers.count);
1693 g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS_TABLE], triggers.count-1, False);
1695 SetLength(TriggersTable, triggers.count);
1696 trignum := -1;
1697 for rec in triggers do
1698 begin
1699 Inc(trignum);
1700 pttit := @TriggersTable[trignum];
1701 pttit.trigrec := rec;
1702 // Ñìåíà òåêñòóðû (âîçìîæíî, êíîïêè)
1703 pttit.texPanelIdx := -1; // will be fixed later
1704 pttit.texPanel := rec.TexturePanelRec;
1705 // action panel
1706 pttit.actPanelIdx := -1;
1707 if (rec.trigRec <> nil) then pttit.actPanel := rec.trigRec.tgPanelRec else pttit.actPanel := nil;
1708 // set flag
1709 if (pttit.texPanel <> nil) then pttit.texPanel.userPanelTrigRef := true;
1710 if (pttit.actPanel <> nil) then pttit.actPanel.userPanelTrigRef := true;
1711 // update progress
1712 g_Game_StepLoading();
1713 end;
1714 end;
1716 // Ñîçäàåì ïàíåëè
1717 if (panels <> nil) and (panels.count > 0) then
1718 begin
1719 e_WriteLog(' Setting up trigger links...', TMsgType.Notify);
1720 g_Game_SetLoadingText(_lc[I_LOAD_LINK_TRIGGERS], panels.count-1, False);
1722 pannum := -1;
1723 for rec in panels do
1724 begin
1725 Inc(pannum);
1726 //e_LogWritefln('PANSTART: pannum=%s', [pannum]);
1727 texrec := nil;
1728 SetLength(AddTextures, 0);
1729 CurTex := -1;
1730 ok := false;
1732 if (mapTextureList <> nil) then
1733 begin
1734 texrec := rec.TextureRec;
1735 ok := (texrec <> nil);
1736 end;
1738 if ok then
1739 begin
1740 // Ñìîòðèì, ññûëàþòñÿ ëè íà ýòó ïàíåëü òðèããåðû.
1741 // Åñëè äà - òî íàäî ñîçäàòü åùå òåêñòóð
1742 ok := false;
1743 if (TriggersTable <> nil) and (mapTextureList <> nil) then
1744 begin
1745 if rec.userPanelTrigRef then
1746 begin
1747 // e_LogWritefln('trigref for panel %s', [pannum]);
1748 ok := True;
1749 end;
1750 end;
1751 end;
1753 if ok then
1754 begin
1755 // Åñòü ññûëêè òðèããåðîâ íà ýòó ïàíåëü
1756 s := texrec.Resource;
1758 // Ñïåö-òåêñòóðû çàïðåùåíû
1759 if g_Map_IsSpecialTexture(s) then
1760 begin
1761 ok := false
1762 end
1763 else
1764 begin
1765 // Îïðåäåëÿåì íàëè÷èå è ïîëîæåíèå öèôð â êîíöå ñòðîêè
1766 ok := g_Texture_NumNameFindStart(s);
1767 end;
1769 // Åñëè ok, çíà÷èò åñòü öèôðû â êîíöå.
1770 // Çàãðóæàåì òåêñòóðû ñ îñòàëüíûìè #
1771 if ok then
1772 begin
1773 k := NNF_NAME_BEFORE;
1774 // Öèêë ïî èçìåíåíèþ èìåíè òåêñòóðû
1775 while ok or (k = NNF_NAME_BEFORE) or (k = NNF_NAME_EQUALS) do
1776 begin
1777 k := g_Texture_NumNameFindNext(TexName);
1779 if (k = NNF_NAME_BEFORE) or (k = NNF_NAME_AFTER) then
1780 begin
1781 // Ïðîáóåì äîáàâèòü íîâóþ òåêñòóðó
1782 if texrec.Anim then
1783 begin
1784 // Íà÷àëüíàÿ - àíèìèðîâàííàÿ, èùåì àíèìèðîâàííóþ
1785 isAnim := True;
1786 //e_LogWritefln('000: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
1787 ok := CreateAnimTexture(TexName, FileName, False) >= 0;
1788 //e_LogWritefln('001: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
1789 if not ok then
1790 begin
1791 // Íåò àíèìèðîâàííîé, èùåì îáû÷íóþ
1792 isAnim := False;
1793 //e_LogWritefln('002: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
1794 ok := CreateTexture(TexName, FileName, False) >= 0;
1795 //e_LogWritefln('003: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
1796 end;
1797 end
1798 else
1799 begin
1800 // Íà÷àëüíàÿ - îáû÷íàÿ, èùåì îáû÷íóþ
1801 isAnim := False;
1802 //e_LogWritefln('004: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
1803 ok := CreateTexture(TexName, FileName, False) >= 0;
1804 //e_LogWritefln('005: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
1805 if not ok then
1806 begin
1807 // Íåò îáû÷íîé, èùåì àíèìèðîâàííóþ
1808 isAnim := True;
1809 //e_LogWritefln('006: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
1810 ok := CreateAnimTexture(TexName, FileName, False) >= 0;
1811 //e_LogWritefln('007: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
1812 end;
1813 end;
1815 // Îíà ñóùåñòâóåò. Çàíîñèì åå ID â ñïèñîê ïàíåëè
1816 if ok then
1817 begin
1819 for c := 0 to High(Textures) do
1820 begin
1821 if (Textures[c].TextureName = TexName) then
1822 begin
1823 SetLength(AddTextures, Length(AddTextures)+1);
1824 AddTextures[High(AddTextures)].Texture := c;
1825 AddTextures[High(AddTextures)].Anim := isAnim;
1826 break;
1827 end;
1828 end;
1830 if (TextNameHash <> nil) and TextNameHash.get(toLowerCase1251(TexName), c) then
1831 begin
1832 SetLength(AddTextures, Length(AddTextures)+1);
1833 AddTextures[High(AddTextures)].Texture := c;
1834 AddTextures[High(AddTextures)].Anim := isAnim;
1835 end;
1836 end;
1837 end
1838 else
1839 begin
1840 if k = NNF_NAME_EQUALS then
1841 begin
1842 // Çàíîñèì òåêóùóþ òåêñòóðó íà ñâîå ìåñòî
1843 SetLength(AddTextures, Length(AddTextures)+1);
1844 AddTextures[High(AddTextures)].Texture := rec.tagInt; // internal texture number, not map index
1845 AddTextures[High(AddTextures)].Anim := texrec.Anim;
1846 CurTex := High(AddTextures);
1847 ok := true;
1848 end
1849 else // NNF_NO_NAME
1850 begin
1851 ok := false;
1852 end;
1853 end;
1854 end; // while ok...
1856 ok := true;
1857 end; // if ok - åñòü ñìåæíûå òåêñòóðû
1858 end; // if ok - ññûëàþòñÿ òðèããåðû
1860 if not ok then
1861 begin
1862 // Çàíîñèì òîëüêî òåêóùóþ òåêñòóðó
1863 SetLength(AddTextures, 1);
1864 AddTextures[0].Texture := rec.tagInt; // internal texture number, not map index
1865 AddTextures[0].Anim := false;
1866 if (texrec <> nil) then AddTextures[0].Anim := texrec.Anim;
1867 CurTex := 0;
1868 end;
1870 //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);
1872 //e_LogWritefln('PANADD: pannum=%s', [pannum]);
1874 // Ñîçäàåì ïàíåëü è çàïîìèíàåì åå GUID
1875 //e_LogWritefln('new panel; tcount=%s; curtex=%s', [Length(AddTextures), CurTex]);
1876 PanelID := CreatePanel(rec, AddTextures, CurTex);
1877 //e_LogWritefln('panel #%s of type %s got guid #%s', [pannum, rec.PanelType, PanelID]);
1878 rec.userPanelId := PanelID; // remember game panel id, we'll fix triggers later
1880 // setup lifts
1881 moveSpeed := rec.moveSpeed;
1882 //moveStart := rec.moveStart;
1883 //moveEnd := rec.moveEnd;
1884 //moveActive := rec['move_active'].value;
1885 if not moveSpeed.isZero then
1886 begin
1887 SetLength(gMovingWallIds, Length(gMovingWallIds)+1);
1888 gMovingWallIds[High(gMovingWallIds)] := PanelID;
1889 //e_LogWritefln('found moving panel ''%s'' (idx=%s; id=%s)', [rec.id, pannum, PanelID]);
1890 end;
1892 //e_LogWritefln('PANEND: pannum=%s', [pannum]);
1894 g_Game_StepLoading();
1895 end;
1896 end;
1898 // ×èíèì ID'û ïàíåëåé, êîòîðûå èñïîëüçóþòñÿ â òðèããåðàõ
1899 for b := 0 to High(TriggersTable) do
1900 begin
1901 if (TriggersTable[b].texPanel <> nil) then TriggersTable[b].texPanelIdx := TriggersTable[b].texPanel.userPanelId;
1902 if (TriggersTable[b].actPanel <> nil) then TriggersTable[b].actPanelIdx := TriggersTable[b].actPanel.userPanelId;
1903 end;
1905 // create map grid, init other grids (for monsters, for example)
1906 e_WriteLog('Creating map grid', TMsgType.Notify);
1907 mapCreateGrid();
1909 // Åñëè íå LoadState, òî ñîçäàåì òðèããåðû
1910 if (triggers <> nil) and (panels <> nil) and (not gLoadGameMode) then
1911 begin
1912 e_LogWritefln(' Creating triggers (%d)...', [triggers.count]);
1913 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_TRIGGERS], 0, False);
1914 // Óêàçûâàåì òèï ïàíåëè, åñëè åñòü
1915 trignum := -1;
1916 for rec in triggers do
1917 begin
1918 Inc(trignum);
1919 tgpid := TriggersTable[trignum].actPanelIdx;
1920 //e_LogWritefln('creating trigger #%s; texpantype=%s; shotpantype=%s (%d,%d)', [trignum, b, c, TriggersTable[trignum].texPanIdx, TriggersTable[trignum].ShotPanelIdx]);
1921 TriggersTable[trignum].tnum := trignum;
1922 TriggersTable[trignum].id := CreateTrigger(trignum, rec, TriggersTable[trignum].texPanelIdx, tgpid);
1923 end;
1924 end;
1926 //FIXME: use hashtable!
1927 for pan in panByGUID do
1928 begin
1929 if (pan.endPosTrigId >= 0) and (pan.endPosTrigId < Length(TriggersTable)) then
1930 begin
1931 pan.endPosTrigId := TriggersTable[pan.endPosTrigId].id;
1932 end;
1933 if (pan.endSizeTrigId >= 0) and (pan.endSizeTrigId < Length(TriggersTable)) then
1934 begin
1935 pan.endSizeTrigId := TriggersTable[pan.endSizeTrigId].id;
1936 end;
1937 end;
1939 // Çàãðóçêà ïðåäìåòîâ
1940 e_WriteLog(' Loading items...', TMsgType.Notify);
1941 g_Game_SetLoadingText(_lc[I_LOAD_ITEMS], 0, False);
1943 // Åñëè íå LoadState, òî ñîçäàåì ïðåäìåòû
1944 if (items <> nil) and not gLoadGameMode then
1945 begin
1946 e_WriteLog(' Spawning items...', TMsgType.Notify);
1947 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_ITEMS], 0, False);
1948 for rec in items do CreateItem(rec);
1949 end;
1951 // Çàãðóçêà îáëàñòåé
1952 e_WriteLog(' Loading areas...', TMsgType.Notify);
1953 g_Game_SetLoadingText(_lc[I_LOAD_AREAS], 0, False);
1955 // Åñëè íå LoadState, òî ñîçäàåì îáëàñòè
1956 if areas <> nil then
1957 begin
1958 e_WriteLog(' Creating areas...', TMsgType.Notify);
1959 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_AREAS], 0, False);
1960 for rec in areas do CreateArea(rec);
1961 end;
1963 // Çàãðóçêà ìîíñòðîâ
1964 e_WriteLog(' Loading monsters...', TMsgType.Notify);
1965 g_Game_SetLoadingText(_lc[I_LOAD_MONSTERS], 0, False);
1967 gTotalMonsters := 0;
1969 // Åñëè íå LoadState, òî ñîçäàåì ìîíñòðîâ
1970 if (monsters <> nil) and not gLoadGameMode then
1971 begin
1972 e_WriteLog(' Spawning monsters...', TMsgType.Notify);
1973 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_MONSTERS], 0, False);
1974 for rec in monsters do CreateMonster(rec);
1975 end;
1977 //gCurrentMap := mapReader; // this will be our current map now
1978 gCurrentMapFileName := Res;
1979 //mapReader := nil;
1981 // Çàãðóçêà íåáà
1982 if (gMapInfo.SkyName <> '') then
1983 begin
1984 e_WriteLog(' Loading sky: ' + gMapInfo.SkyName, TMsgType.Notify);
1985 g_Game_SetLoadingText(_lc[I_LOAD_SKY], 0, False);
1986 s := e_GetResourcePath(WadDirs, gMapInfo.SkyName, g_ExtractWadName(Res));
1987 if g_Texture_CreateWAD(BackID, s, gTextureFilter) then
1988 g_Game_SetupScreenSize
1989 else
1990 g_FatalError(Format(_lc[I_GAME_ERROR_SKY], [s]))
1991 end;
1993 // Çàãðóçêà ìóçûêè
1994 ok := False;
1995 if gMapInfo.MusicName <> '' then
1996 begin
1997 e_WriteLog(' Loading music: ' + gMapInfo.MusicName, TMsgType.Notify);
1998 g_Game_SetLoadingText(_lc[I_LOAD_MUSIC], 0, False);
2000 s := e_GetResourcePath(WadDirs, gMapInfo.MusicName, g_ExtractWadName(Res));
2001 if g_Sound_CreateWADEx(gMapInfo.MusicName, s, True) then
2002 ok := True
2003 else
2004 g_FatalError(Format(_lc[I_GAME_ERROR_MUSIC], [s]));
2005 end;
2007 // Îñòàëüíûå óñòàíâêè
2008 CreateDoorMap();
2009 CreateLiftMap();
2011 g_Items_Init();
2012 g_Weapon_Init();
2013 g_Monsters_Init();
2015 // Åñëè íå LoadState, òî ñîçäàåì êàðòó ñòîëêíîâåíèé:
2016 if not gLoadGameMode then g_GFX_Init();
2018 // Ñáðîñ ëîêàëüíûõ ìàññèâîâ:
2019 mapTextureList := nil;
2020 panels := nil;
2021 items := nil;
2022 areas := nil;
2023 triggers := nil;
2024 TriggersTable := nil;
2025 AddTextures := nil;
2027 // Âêëþ÷àåì ìóçûêó, åñëè ýòî íå çàãðóçêà:
2028 if ok and (not gLoadGameMode) then
2029 begin
2030 gMusic.SetByName(gMapInfo.MusicName);
2031 gMusic.Play();
2032 end
2033 else
2034 begin
2035 gMusic.SetByName('');
2036 end;
2038 stt := getTimeMicro()-stt;
2039 e_LogWritefln('map loaded in %s.%s milliseconds', [Integer(stt div 1000), Integer(stt mod 1000)]);
2040 mapOk := true;
2041 finally
2042 sfsGCEnable(); // enable releasing unused volumes
2043 //mapReader.Free();
2044 e_UnpressAllKeys; // why not?
2045 if not mapOk then
2046 begin
2047 gCurrentMap.Free();
2048 gCurrentMap := nil;
2049 gCurrentMapFileName := '';
2050 end;
2051 end;
2053 compactExtResList();
2054 e_WriteLog('Done loading map.', TMsgType.Notify);
2055 Result := True;
2056 end;
2059 function g_Map_GetMapInfo(Res: String): TMapInfo;
2060 var
2061 WAD: TWADFile;
2062 mapReader: TDynRecord;
2063 //Header: TMapHeaderRec_1;
2064 FileName: String;
2065 Data: Pointer;
2066 Len: Integer;
2067 begin
2068 FillChar(Result, SizeOf(Result), 0);
2069 FileName := g_ExtractWadName(Res);
2071 WAD := TWADFile.Create();
2072 if not WAD.ReadFile(FileName) then
2073 begin
2074 WAD.Free();
2075 Exit;
2076 end;
2078 //k8: it ignores path again
2079 if not WAD.GetMapResource(g_ExtractFileName(Res), Data, Len) then
2080 begin
2081 WAD.Free();
2082 Exit;
2083 end;
2085 WAD.Free();
2087 try
2088 mapReader := g_Map_ParseMap(Data, Len);
2089 except
2090 mapReader := nil;
2091 FreeMem(Data);
2092 exit;
2093 end;
2095 FreeMem(Data);
2097 if (mapReader = nil) then exit;
2099 if (mapReader.Width > 0) and (mapReader.Height > 0) then
2100 begin
2101 Result.Name := mapReader.MapName;
2102 Result.Description := mapReader.MapDesc;
2103 Result.Map := Res;
2104 Result.Author := mapReader.MapAuthor;
2105 Result.Height := mapReader.Height;
2106 Result.Width := mapReader.Width;
2107 end
2108 else
2109 begin
2110 g_Console_Add(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]), True);
2111 //ZeroMemory(@Header, SizeOf(Header));
2112 Result.Name := _lc[I_GAME_ERROR_MAP_SELECT];
2113 Result.Description := _lc[I_GAME_ERROR_MAP_SELECT];
2114 Result.Map := Res;
2115 Result.Author := '';
2116 Result.Height := 0;
2117 Result.Width := 0;
2118 end;
2120 mapReader.Free();
2121 end;
2123 function g_Map_GetMapsList(WADName: string): SSArray;
2124 var
2125 WAD: TWADFile;
2126 a: Integer;
2127 ResList: SSArray;
2128 begin
2129 Result := nil;
2130 WAD := TWADFile.Create();
2131 if not WAD.ReadFile(WADName) then
2132 begin
2133 WAD.Free();
2134 Exit;
2135 end;
2136 ResList := WAD.GetMapResources();
2137 if ResList <> nil then
2138 begin
2139 for a := 0 to High(ResList) do
2140 begin
2141 SetLength(Result, Length(Result)+1);
2142 Result[High(Result)] := ResList[a];
2143 end;
2144 end;
2145 WAD.Free();
2146 end;
2148 function g_Map_Exist(Res: string): Boolean;
2149 var
2150 WAD: TWADFile;
2151 FileName, mnn: string;
2152 ResList: SSArray;
2153 a: Integer;
2154 begin
2155 Result := False;
2157 FileName := addWadExtension(g_ExtractWadName(Res));
2159 WAD := TWADFile.Create;
2160 if not WAD.ReadFile(FileName) then
2161 begin
2162 WAD.Free();
2163 Exit;
2164 end;
2166 ResList := WAD.GetMapResources();
2167 WAD.Free();
2169 mnn := g_ExtractFileName(Res);
2170 if ResList <> nil then
2171 for a := 0 to High(ResList) do if StrEquCI1251(ResList[a], mnn) then
2172 begin
2173 Result := True;
2174 Exit;
2175 end;
2176 end;
2178 procedure g_Map_Free(freeTextures: Boolean=true);
2180 procedure FreePanelArray(var panels: TPanelArray);
2181 var
2182 i: Integer;
2184 begin
2185 if panels <> nil then
2186 begin
2187 for i := 0 to High(panels) do
2188 panels[i].Free();
2189 panels := nil;
2190 end;
2191 end;
2193 begin
2194 g_GFX_Free();
2195 g_Weapon_Free();
2196 g_Items_Free();
2197 g_Triggers_Free();
2198 g_Monsters_Free();
2200 RespawnPoints := nil;
2201 if FlagPoints[FLAG_RED] <> nil then
2202 begin
2203 Dispose(FlagPoints[FLAG_RED]);
2204 FlagPoints[FLAG_RED] := nil;
2205 end;
2206 if FlagPoints[FLAG_BLUE] <> nil then
2207 begin
2208 Dispose(FlagPoints[FLAG_BLUE]);
2209 FlagPoints[FLAG_BLUE] := nil;
2210 end;
2211 //DOMFlagPoints := nil;
2213 //gDOMFlags := nil;
2215 if (Length(gCurrentMapFileName) <> 0) then
2216 begin
2217 e_LogWritefln('g_Map_Free: previous map was ''%s''...', [gCurrentMapFileName]);
2218 end
2219 else
2220 begin
2221 e_LogWritefln('g_Map_Free: no previous map.', []);
2222 end;
2224 if freeTextures then
2225 begin
2226 e_LogWritefln('g_Map_Free: clearing textures...', []);
2227 Textures := nil;
2228 TextNameHash.Free();
2229 TextNameHash := nil;
2230 BadTextNameHash.Free();
2231 BadTextNameHash := nil;
2232 gCurrentMapFileName := '';
2233 gCurrentMap.Free();
2234 gCurrentMap := nil;
2235 end;
2237 panByGUID := nil;
2239 FreePanelArray(gWalls);
2240 FreePanelArray(gRenderBackgrounds);
2241 FreePanelArray(gRenderForegrounds);
2242 FreePanelArray(gWater);
2243 FreePanelArray(gAcid1);
2244 FreePanelArray(gAcid2);
2245 FreePanelArray(gSteps);
2246 FreePanelArray(gLifts);
2247 FreePanelArray(gBlockMon);
2248 gMovingWallIds := nil;
2250 if BackID <> DWORD(-1) then
2251 begin
2252 gBackSize.X := 0;
2253 gBackSize.Y := 0;
2254 e_DeleteTexture(BackID);
2255 BackID := DWORD(-1);
2256 end;
2258 g_Game_StopAllSounds(False);
2259 gMusic.FreeSound();
2260 g_Sound_Delete(gMapInfo.MusicName);
2262 gMapInfo.Name := '';
2263 gMapInfo.Description := '';
2264 gMapInfo.MusicName := '';
2265 gMapInfo.Height := 0;
2266 gMapInfo.Width := 0;
2268 gDoorMap := nil;
2269 gLiftMap := nil;
2270 end;
2272 procedure g_Map_Update();
2273 var
2274 a, d, j: Integer;
2275 m: Word;
2276 s: String;
2277 b: Byte;
2279 procedure UpdatePanelArray(var panels: TPanelArray);
2280 var
2281 i: Integer;
2283 begin
2284 for i := 0 to High(panels) do panels[i].Update();
2285 end;
2287 begin
2288 if g_dbgpan_mplat_step then g_dbgpan_mplat_active := true;
2290 UpdatePanelArray(gWalls);
2291 UpdatePanelArray(gRenderBackgrounds);
2292 UpdatePanelArray(gRenderForegrounds);
2293 UpdatePanelArray(gWater);
2294 UpdatePanelArray(gAcid1);
2295 UpdatePanelArray(gAcid2);
2296 UpdatePanelArray(gSteps);
2298 if g_dbgpan_mplat_step then begin g_dbgpan_mplat_step := false; g_dbgpan_mplat_active := false; end;
2300 if gGameSettings.GameMode = GM_CTF then
2301 begin
2302 for a := FLAG_RED to FLAG_BLUE do
2303 begin
2304 if not (gFlags[a].State in [FLAG_STATE_NONE, FLAG_STATE_CAPTURED]) then
2305 begin
2306 with gFlags[a] do
2307 begin
2308 if gFlags[a].Animation <> nil then gFlags[a].Animation.Update();
2310 Obj.oldX := Obj.X;
2311 Obj.oldY := Obj.Y;
2313 m := g_Obj_Move(@Obj, True, True);
2315 NeedSend := NeedSend or (Obj.X <> Obj.oldX) or (Obj.Y <> Obj.oldY);
2317 if gTime mod (GAME_TICK*2) <> 0 then Continue;
2319 // Ñîïðîòèâëåíèå âîçäóõà
2320 Obj.Vel.X := z_dec(Obj.Vel.X, 1);
2322 // Òàéìàóò ïîòåðÿííîãî ôëàãà, ëèáî îí âûïàë çà êàðòó
2323 if ((Count = 0) or ByteBool(m and MOVE_FALLOUT)) and g_Game_IsServer then
2324 begin
2325 g_Map_ResetFlag(a);
2326 gFlags[a].CaptureTime := 0;
2327 if a = FLAG_RED then
2328 s := _lc[I_PLAYER_FLAG_RED]
2329 else
2330 s := _lc[I_PLAYER_FLAG_BLUE];
2331 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
2333 if (((gPlayer1 <> nil) and (((gPlayer1.Team = TEAM_RED) and (a = FLAG_RED)) or ((gPlayer1.Team = TEAM_BLUE) and (a = FLAG_BLUE))))
2334 or ((gPlayer2 <> nil) and (((gPlayer2.Team = TEAM_RED) and (a = FLAG_RED)) or ((gPlayer2.Team = TEAM_BLUE) and (a = FLAG_BLUE))))) then
2335 b := 0
2336 else
2337 b := 1;
2339 if not sound_ret_flag[b].IsPlaying() then
2340 sound_ret_flag[b].Play();
2342 if g_Game_IsNet then
2343 MH_SEND_FlagEvent(FLAG_STATE_RETURNED, a, 0);
2344 Continue;
2345 end;
2347 if Count > 0 then Count -= 1;
2349 // Èãðîê áåðåò ôëàã
2350 if gPlayers <> nil then
2351 begin
2352 j := Random(Length(gPlayers)) - 1;
2353 for d := 0 to High(gPlayers) do
2354 begin
2355 Inc(j);
2356 if j > High(gPlayers) then j := 0;
2357 if gPlayers[j] <> nil then
2358 begin
2359 if gPlayers[j].alive and g_Obj_Collide(@Obj, @gPlayers[j].Obj) then
2360 begin
2361 if gPlayers[j].GetFlag(a) then Break;
2362 end;
2363 end;
2364 end;
2365 end;
2366 end;
2367 end;
2368 end;
2369 end;
2370 end;
2372 function g_Map_CollidePanelOld(X, Y: Integer; Width, Height: Word;
2373 PanelType: Word; b1x3: Boolean=false): Boolean;
2374 var
2375 a, h: Integer;
2376 begin
2377 Result := False;
2379 if WordBool(PanelType and PANEL_WALL) then
2380 if gWalls <> nil then
2381 begin
2382 h := High(gWalls);
2384 for a := 0 to h do
2385 if gWalls[a].Enabled and
2386 g_Collide(X, Y, Width, Height,
2387 gWalls[a].X, gWalls[a].Y,
2388 gWalls[a].Width, gWalls[a].Height) then
2389 begin
2390 Result := True;
2391 Exit;
2392 end;
2393 end;
2395 if WordBool(PanelType and PANEL_WATER) then
2396 if gWater <> nil then
2397 begin
2398 h := High(gWater);
2400 for a := 0 to h do
2401 if g_Collide(X, Y, Width, Height,
2402 gWater[a].X, gWater[a].Y,
2403 gWater[a].Width, gWater[a].Height) then
2404 begin
2405 Result := True;
2406 Exit;
2407 end;
2408 end;
2410 if WordBool(PanelType and PANEL_ACID1) then
2411 if gAcid1 <> nil then
2412 begin
2413 h := High(gAcid1);
2415 for a := 0 to h do
2416 if g_Collide(X, Y, Width, Height,
2417 gAcid1[a].X, gAcid1[a].Y,
2418 gAcid1[a].Width, gAcid1[a].Height) then
2419 begin
2420 Result := True;
2421 Exit;
2422 end;
2423 end;
2425 if WordBool(PanelType and PANEL_ACID2) then
2426 if gAcid2 <> nil then
2427 begin
2428 h := High(gAcid2);
2430 for a := 0 to h do
2431 if g_Collide(X, Y, Width, Height,
2432 gAcid2[a].X, gAcid2[a].Y,
2433 gAcid2[a].Width, gAcid2[a].Height) then
2434 begin
2435 Result := True;
2436 Exit;
2437 end;
2438 end;
2440 if WordBool(PanelType and PANEL_STEP) then
2441 if gSteps <> nil then
2442 begin
2443 h := High(gSteps);
2445 for a := 0 to h do
2446 if g_Collide(X, Y, Width, Height,
2447 gSteps[a].X, gSteps[a].Y,
2448 gSteps[a].Width, gSteps[a].Height) then
2449 begin
2450 Result := True;
2451 Exit;
2452 end;
2453 end;
2455 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then
2456 if gLifts <> nil then
2457 begin
2458 h := High(gLifts);
2460 for a := 0 to h do
2461 if ((WordBool(PanelType and (PANEL_LIFTUP)) and (gLifts[a].LiftType = LIFTTYPE_UP)) or
2462 (WordBool(PanelType and (PANEL_LIFTDOWN)) and (gLifts[a].LiftType = LIFTTYPE_DOWN)) or
2463 (WordBool(PanelType and (PANEL_LIFTLEFT)) and (gLifts[a].LiftType = LIFTTYPE_LEFT)) or
2464 (WordBool(PanelType and (PANEL_LIFTRIGHT)) and (gLifts[a].LiftType = LIFTTYPE_RIGHT))) and
2465 g_Collide(X, Y, Width, Height,
2466 gLifts[a].X, gLifts[a].Y,
2467 gLifts[a].Width, gLifts[a].Height) then
2468 begin
2469 Result := True;
2470 Exit;
2471 end;
2472 end;
2474 if WordBool(PanelType and PANEL_BLOCKMON) then
2475 if gBlockMon <> nil then
2476 begin
2477 h := High(gBlockMon);
2479 for a := 0 to h do
2480 if ( (not b1x3) or
2481 ((gBlockMon[a].Width + gBlockMon[a].Height) >= 64) ) and
2482 g_Collide(X, Y, Width, Height,
2483 gBlockMon[a].X, gBlockMon[a].Y,
2484 gBlockMon[a].Width, gBlockMon[a].Height) then
2485 begin
2486 Result := True;
2487 Exit;
2488 end;
2489 end;
2490 end;
2492 function g_Map_CollideLiquid_TextureOld(X, Y: Integer; Width, Height: Word): DWORD;
2493 var
2494 texid: DWORD;
2496 function checkPanels (constref panels: TPanelArray): Boolean;
2497 var
2498 a: Integer;
2499 begin
2500 result := false;
2501 if panels = nil then exit;
2502 for a := 0 to High(panels) do
2503 begin
2504 if g_Collide(X, Y, Width, Height, panels[a].X, panels[a].Y, panels[a].Width, panels[a].Height) then
2505 begin
2506 result := true;
2507 texid := panels[a].GetTextureID();
2508 exit;
2509 end;
2510 end;
2511 end;
2513 begin
2514 texid := LongWord(TEXTURE_NONE);
2515 result := texid;
2516 if not checkPanels(gWater) then
2517 if not checkPanels(gAcid1) then
2518 if not checkPanels(gAcid2) then exit;
2519 result := texid;
2520 end;
2523 function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word; PanelType: Word; b1x3: Boolean): Boolean;
2524 const
2525 SlowMask = GridTagLift or GridTagBlockMon;
2527 function checker (pan: TPanel; tag: Integer): Boolean;
2528 begin
2530 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
2531 begin
2532 result := pan.Enabled;
2533 exit;
2534 end;
2537 if ((tag and GridTagLift) <> 0) then
2538 begin
2539 result :=
2540 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
2541 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
2542 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
2543 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT))) {and
2544 g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
2545 exit;
2546 end;
2548 if ((tag and GridTagBlockMon) <> 0) then
2549 begin
2550 result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2551 exit;
2552 end;
2554 // other shit
2555 //result := g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2556 result := true; // i found her!
2557 end;
2559 var
2560 tagmask: Integer = 0;
2561 mwit: PPanel;
2562 it: TPanelGrid.Iter;
2563 pan: TPanel;
2564 begin
2565 result := false;
2566 if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor);
2567 if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater;
2568 if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1;
2569 if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2;
2570 if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep;
2571 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift;
2572 if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon;
2574 if (tagmask = 0) then exit; // just in case
2576 if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('*solids');
2577 if gdbg_map_use_accel_coldet then
2578 begin
2579 if ((tagmask and SlowMask) <> 0) then
2580 begin
2581 // slow
2582 it := mapGrid.forEachInAABB(X, Y, Width, Height, tagmask);
2583 for mwit in it do
2584 begin
2585 pan := mwit^;
2586 if ((pan.tag and GridTagLift) <> 0) then
2587 begin
2588 result :=
2589 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
2590 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
2591 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
2592 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT))) {and
2593 g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
2594 end
2595 else if ((pan.tag and GridTagBlockMon) <> 0) then
2596 begin
2597 result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2598 end
2599 else
2600 begin
2601 // other shit
2602 result := true; // i found her!
2603 end;
2604 if (result) then break;
2605 end;
2606 end
2607 else
2608 begin
2609 // fast
2610 it := mapGrid.forEachInAABB(X, Y, Width, Height, tagmask, false, true); // return first hit
2611 result := (it.length > 0);
2612 end;
2613 it.release();
2614 end
2615 else
2616 begin
2617 result := g_Map_CollidePanelOld(X, Y, Width, Height, PanelType, b1x3);
2618 end;
2619 if (profMapCollision <> nil) then profMapCollision.sectionEnd();
2620 end;
2623 // returns `true` if we need to stop
2624 function liquidChecker (pan: TPanel; var texid: DWORD; var cctype: Integer): Boolean; inline;
2625 begin
2626 result := false;
2627 //if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2)) = 0) then exit;
2628 // check priorities
2629 case cctype of
2630 0: if ((pan.tag and GridTagWater) = 0) then exit; // allowed: water
2631 1: if ((pan.tag and (GridTagWater or GridTagAcid1)) = 0) then exit; // allowed: water, acid1
2632 //2: if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2) = 0) then exit; // allowed: water, acid1, acid2
2633 end;
2634 // collision?
2635 //if not g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height) then exit;
2636 // yeah
2637 texid := pan.GetTextureID();
2638 // water? water has the highest priority, so stop right here
2639 if ((pan.tag and GridTagWater) <> 0) then begin cctype := 0; result := true; exit; end;
2640 // acid2?
2641 if ((pan.tag and GridTagAcid2) <> 0) then cctype := 2;
2642 // acid1?
2643 if ((pan.tag and GridTagAcid1) <> 0) then cctype := 1;
2644 end;
2646 function g_Map_CollideLiquid_Texture(X, Y: Integer; Width, Height: Word): DWORD;
2647 var
2648 cctype: Integer = 3; // priority: 0: water was hit, 1: acid1 was hit, 2: acid2 was hit; 3: nothing was hit
2649 mwit: PPanel;
2650 it: TPanelGrid.Iter;
2651 begin
2652 if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('liquids');
2653 if gdbg_map_use_accel_coldet then
2654 begin
2655 result := LongWord(TEXTURE_NONE);
2656 it := mapGrid.forEachInAABB(X, Y, Width, Height, (GridTagWater or GridTagAcid1 or GridTagAcid2));
2657 for mwit in it do if (liquidChecker(mwit^, result, cctype)) then break;
2658 it.release();
2659 end
2660 else
2661 begin
2662 result := g_Map_CollideLiquid_TextureOld(X, Y, Width, Height);
2663 end;
2664 if (profMapCollision <> nil) then profMapCollision.sectionEnd();
2665 end;
2668 procedure g_Map_EnableWall_XXX (ID: DWORD); begin if (ID < Length(gWalls)) then g_Map_EnableWallGUID(gWalls[ID].guid); end;
2669 procedure g_Map_DisableWall_XXX (ID: DWORD); begin if (ID < Length(gWalls)) then g_Map_DisableWallGUID(gWalls[ID].guid); end;
2670 procedure g_Map_SetLift_XXX (ID: DWORD; t: Integer); begin if (ID < Length(gLifts)) then g_Map_SetLiftGUID(gLifts[ID].guid, t); end;
2673 procedure g_Map_EnableWallGUID (pguid: Integer);
2674 var
2675 pan: TPanel;
2676 begin
2677 //pan := gWalls[ID];
2678 pan := g_Map_PanelByGUID(pguid);
2679 if (pan = nil) then exit;
2680 if pan.Enabled and mapGrid.proxyEnabled[pan.proxyId] then exit;
2682 pan.Enabled := True;
2683 g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, true);
2685 mapGrid.proxyEnabled[pan.proxyId] := true;
2686 //if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := true
2687 //else pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, GridTagDoor);
2689 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2690 // mark platform as interesting
2691 pan.setDirty();
2693 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
2694 //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);
2695 {$ENDIF}
2696 end;
2699 procedure g_Map_DisableWallGUID (pguid: Integer);
2700 var
2701 pan: TPanel;
2702 begin
2703 //pan := gWalls[ID];
2704 pan := g_Map_PanelByGUID(pguid);
2705 if (pan = nil) then exit;
2706 if (not pan.Enabled) and (not mapGrid.proxyEnabled[pan.proxyId]) then exit;
2708 pan.Enabled := False;
2709 g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, false);
2711 mapGrid.proxyEnabled[pan.proxyId] := false;
2712 //if (pan.proxyId >= 0) then begin mapGrid.removeBody(pan.proxyId); pan.proxyId := -1; end;
2714 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2715 // mark platform as interesting
2716 pan.setDirty();
2718 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
2719 //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);
2720 {$ENDIF}
2721 end;
2724 procedure g_Map_SwitchTextureGUID (pguid: Integer; AnimLoop: Byte = 0);
2725 var
2726 tp: TPanel;
2727 begin
2728 tp := g_Map_PanelByGUID(pguid);
2729 if (tp = nil) then exit;
2730 tp.NextTexture(AnimLoop);
2731 if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelTexture(pguid, AnimLoop);
2732 end;
2735 procedure g_Map_SetLiftGUID (pguid: Integer; t: Integer);
2736 var
2737 pan: TPanel;
2738 begin
2739 //pan := gLifts[ID];
2740 pan := g_Map_PanelByGUID(pguid);
2741 if (pan = nil) then exit;
2742 if not pan.isGLift then exit;
2744 if ({gLifts[ID]}pan.LiftType = t) then exit; //!FIXME!TRIGANY!
2746 with {gLifts[ID]} pan do
2747 begin
2748 LiftType := t;
2750 g_Mark(X, Y, Width, Height, MARK_LIFT, false);
2751 //TODO: make separate lift tags, and change tag here
2753 case LiftType of
2754 LIFTTYPE_UP: g_Mark(X, Y, Width, Height, MARK_LIFTUP);
2755 LIFTTYPE_DOWN: g_Mark(X, Y, Width, Height, MARK_LIFTDOWN);
2756 LIFTTYPE_LEFT: g_Mark(X, Y, Width, Height, MARK_LIFTLEFT);
2757 LIFTTYPE_RIGHT: g_Mark(X, Y, Width, Height, MARK_LIFTRIGHT);
2758 end;
2760 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2761 // mark platform as interesting
2762 pan.setDirty();
2763 end;
2764 end;
2767 function g_Map_GetPoint (PointType: Byte; var RespawnPoint: TRespawnPoint): Boolean;
2768 var
2769 a: Integer;
2770 PointsArray: Array of TRespawnPoint;
2771 begin
2772 Result := False;
2773 SetLength(PointsArray, 0);
2775 if RespawnPoints = nil then
2776 Exit;
2778 for a := 0 to High(RespawnPoints) do
2779 if RespawnPoints[a].PointType = PointType then
2780 begin
2781 SetLength(PointsArray, Length(PointsArray)+1);
2782 PointsArray[High(PointsArray)] := RespawnPoints[a];
2783 end;
2785 if PointsArray = nil then
2786 Exit;
2788 RespawnPoint := PointsArray[Random(Length(PointsArray))];
2789 Result := True;
2790 end;
2792 function g_Map_GetPointCount(PointType: Byte): Word;
2793 var
2794 a: Integer;
2795 begin
2796 Result := 0;
2798 if RespawnPoints = nil then
2799 Exit;
2801 for a := 0 to High(RespawnPoints) do
2802 if RespawnPoints[a].PointType = PointType then
2803 Result := Result + 1;
2804 end;
2806 function g_Map_GetRandomPointType(): Byte;
2807 begin
2808 if RespawnPoints = nil then
2809 Result := 255
2810 else
2811 Result := RespawnPoints[Random(Length(RespawnPoints))].PointType;
2812 end;
2814 function g_Map_HaveFlagPoints(): Boolean;
2815 begin
2816 Result := (FlagPoints[FLAG_RED] <> nil) and (FlagPoints[FLAG_BLUE] <> nil);
2817 end;
2819 procedure g_Map_ResetFlag(Flag: Byte);
2820 begin
2821 with gFlags[Flag] do
2822 begin
2823 Obj.X := -1000;
2824 Obj.Y := -1000;
2825 Obj.Vel.X := 0;
2826 Obj.Vel.Y := 0;
2827 Direction := TDirection.D_LEFT;
2828 State := FLAG_STATE_NONE;
2829 if FlagPoints[Flag] <> nil then
2830 begin
2831 Obj.X := FlagPoints[Flag]^.X;
2832 Obj.Y := FlagPoints[Flag]^.Y;
2833 Direction := FlagPoints[Flag]^.Direction;
2834 State := FLAG_STATE_NORMAL;
2835 end;
2836 Obj.oldX := Obj.X;
2837 Obj.oldY := Obj.Y;
2838 NeedSend := False; // the event will take care of this
2839 Count := -1;
2840 end;
2841 end;
2843 procedure g_Map_SaveState (st: TStream);
2844 var
2845 str: String;
2847 procedure savePanels ();
2848 var
2849 pan: TPanel;
2850 begin
2851 // Ñîõðàíÿåì ïàíåëè
2852 utils.writeInt(st, LongInt(Length(panByGUID)));
2853 for pan in panByGUID do pan.SaveState(st);
2854 end;
2856 procedure saveFlag (flag: PFlag);
2857 var
2858 b: Byte;
2859 begin
2860 utils.writeSign(st, 'FLAG');
2861 utils.writeInt(st, Byte(0)); // version
2862 // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
2863 utils.writeInt(st, Byte(flag^.RespawnType));
2864 // Ñîñòîÿíèå ôëàãà
2865 utils.writeInt(st, Byte(flag^.State));
2866 // Íàïðàâëåíèå ôëàãà
2867 if flag^.Direction = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT
2868 utils.writeInt(st, Byte(b));
2869 // Îáúåêò ôëàãà
2870 Obj_SaveState(st, @flag^.Obj);
2871 end;
2873 begin
2874 savePanels();
2876 // Ñîõðàíÿåì ìóçûêó
2877 utils.writeSign(st, 'MUSI');
2878 utils.writeInt(st, Byte(0));
2879 // Íàçâàíèå ìóçûêè
2880 assert(gMusic <> nil, 'g_Map_SaveState: gMusic = nil');
2881 if gMusic.NoMusic then str := '' else str := gMusic.Name;
2882 utils.writeStr(st, str);
2883 // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
2884 utils.writeInt(st, LongWord(gMusic.GetPosition()));
2885 // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
2886 utils.writeBool(st, gMusic.SpecPause);
2888 ///// Ñîõðàíÿåì êîëè÷åñòâî ìîíñòðîâ: /////
2889 utils.writeInt(st, LongInt(gTotalMonsters));
2890 ///// /////
2892 //// Ñîõðàíÿåì ôëàãè, åñëè ýòî CTF: /////
2893 if (gGameSettings.GameMode = GM_CTF) then
2894 begin
2895 // Ôëàã Êðàñíîé êîìàíäû
2896 saveFlag(@gFlags[FLAG_RED]);
2897 // Ôëàã Ñèíåé êîìàíäû
2898 saveFlag(@gFlags[FLAG_BLUE]);
2899 end;
2900 ///// /////
2902 ///// Ñîõðàíÿåì êîëè÷åñòâî ïîáåä, åñëè ýòî TDM/CTF: /////
2903 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
2904 begin
2905 // Î÷êè Êðàñíîé êîìàíäû
2906 utils.writeInt(st, SmallInt(gTeamStat[TEAM_RED].Score));
2907 // Î÷êè Ñèíåé êîìàíäû
2908 utils.writeInt(st, SmallInt(gTeamStat[TEAM_BLUE].Score));
2909 end;
2910 ///// /////
2911 end;
2914 procedure g_Map_LoadState (st: TStream);
2915 var
2916 dw: DWORD;
2917 str: String;
2918 boo: Boolean;
2920 procedure loadPanels ();
2921 var
2922 pan: TPanel;
2923 begin
2924 // Çàãðóæàåì ïàíåëè
2925 if (Length(panByGUID) <> utils.readLongInt(st)) then raise XStreamError.Create('invalid number of saved panels');
2926 for pan in panByGUID do
2927 begin
2928 pan.LoadState(st);
2929 if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
2930 end;
2931 end;
2933 procedure loadFlag (flag: PFlag);
2934 var
2935 b: Byte;
2936 begin
2937 // Ñèãíàòóðà ôëàãà
2938 if not utils.checkSign(st, 'FLAG') then raise XStreamError.Create('invalid flag signature');
2939 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid flag version');
2940 // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
2941 flag^.RespawnType := utils.readByte(st);
2942 // Ñîñòîÿíèå ôëàãà
2943 flag^.State := utils.readByte(st);
2944 // Íàïðàâëåíèå ôëàãà
2945 b := utils.readByte(st);
2946 if (b = 1) then flag^.Direction := TDirection.D_LEFT else flag^.Direction := TDirection.D_RIGHT; // b = 2
2947 // Îáúåêò ôëàãà
2948 Obj_LoadState(@flag^.Obj, st);
2949 end;
2951 begin
2952 if (st = nil) then exit;
2954 ///// Çàãðóæàåì ñïèñêè ïàíåëåé: /////
2955 loadPanels();
2956 ///// /////
2958 // Îáíîâëÿåì êàðòó ñòîëêíîâåíèé è ñåòêó
2959 g_GFX_Init();
2960 //mapCreateGrid();
2962 ///// Çàãðóæàåì ìóçûêó: /////
2963 if not utils.checkSign(st, 'MUSI') then raise XStreamError.Create('invalid music signature');
2964 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid music version');
2965 // Íàçâàíèå ìóçûêè
2966 assert(gMusic <> nil, 'g_Map_LoadState: gMusic = nil');
2967 str := utils.readStr(st);
2968 // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
2969 dw := utils.readLongWord(st);
2970 // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
2971 boo := utils.readBool(st);
2972 // Çàïóñêàåì ýòó ìóçûêó
2973 gMusic.SetByName(str);
2974 gMusic.SpecPause := boo;
2975 gMusic.Play();
2976 gMusic.Pause(true);
2977 gMusic.SetPosition(dw);
2978 ///// /////
2980 ///// Çàãðóæàåì êîëè÷åñòâî ìîíñòðîâ: /////
2981 gTotalMonsters := utils.readLongInt(st);
2982 ///// /////
2984 //// Çàãðóæàåì ôëàãè, åñëè ýòî CTF: /////
2985 if (gGameSettings.GameMode = GM_CTF) then
2986 begin
2987 // Ôëàã Êðàñíîé êîìàíäû
2988 loadFlag(@gFlags[FLAG_RED]);
2989 // Ôëàã Ñèíåé êîìàíäû
2990 loadFlag(@gFlags[FLAG_BLUE]);
2991 end;
2992 ///// /////
2994 ///// Çàãðóæàåì êîëè÷åñòâî ïîáåä, åñëè ýòî TDM/CTF: /////
2995 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
2996 begin
2997 // Î÷êè Êðàñíîé êîìàíäû
2998 gTeamStat[TEAM_RED].Score := utils.readSmallInt(st);
2999 // Î÷êè Ñèíåé êîìàíäû
3000 gTeamStat[TEAM_BLUE].Score := utils.readSmallInt(st);
3001 end;
3002 ///// /////
3003 end;
3006 // trace liquid, stepping by `dx` and `dy`
3007 // return last seen liquid coords, and `false` if we're started outside of the liquid
3008 function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
3009 const
3010 MaskLiquid = GridTagWater or GridTagAcid1 or GridTagAcid2;
3011 begin
3012 topx := x;
3013 topy := y;
3014 // started outside of the liquid?
3015 //if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then begin result := false; exit; end;
3016 if (g_Map_PanelAtPoint(x, y, MaskLiquid) = nil) then begin result := false; exit; end;
3017 if (dx = 0) and (dy = 0) then begin result := false; exit; end; // sanity check
3018 result := true;
3019 while true do
3020 begin
3021 Inc(x, dx);
3022 Inc(y, dy);
3023 //if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then exit; // out of the water, just exit
3024 if (g_Map_PanelAtPoint(x, y, MaskLiquid) = nil) then exit; // out of the water, just exit
3025 topx := x;
3026 topy := y;
3027 end;
3028 end;
3031 begin
3032 DynWarningCB := mapWarningCB;
3033 end.