DEADSOFTWARE

render: fix animated textures
[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 Result := High(Textures);
878 TextNameHash.put(RecName, result);
879 end;
882 function extractWadName (resourceName: string): string;
883 var
884 posN: Integer;
885 begin
886 posN := Pos(':', resourceName);
887 if posN > 0 then
888 Result:= Copy(resourceName, 0, posN-1)
889 else
890 Result := '';
891 end;
894 procedure addResToExternalResList (res: AnsiString);
895 var
896 uname: AnsiString;
897 f: Integer;
898 fi: TDiskFileInfo;
899 begin
900 if g_Game_IsClient or not g_Game_IsNet then exit;
901 if (length(res) = 0) then exit; // map wad
902 //res := extractWadName(res);
903 //if (length(res) = 0) then exit; // map wad
904 uname := toLowerCase1251(res);
905 // do not add duplicates
906 for f := 0 to High(gExternalResources) do
907 begin
908 if (gExternalResources[f].userName = uname) then exit;
909 end;
910 //writeln('***(000) addResToExternalResList: res=[', res, ']');
911 // add new resource
912 fi.userName := uname;
913 if not findFileCI(res) then exit;
914 //writeln('***(001) addResToExternalResList: res=[', res, ']');
915 fi.diskName := res;
916 if (not GetDiskFileInfo(res, fi)) then
917 begin
918 fi.tag := -1;
919 end
920 else
921 begin
922 //writeln('***(002) addResToExternalResList: res=[', res, ']');
923 fi.tag := 0; // non-zero means "cannot caclucate hash"
924 try
925 fi.hash := MD5File(fi.diskName);
926 except
927 fi.tag := -1;
928 end;
929 end;
930 //e_LogWritefln('addext: res=[%s]; uname=[%s]; diskName=[%s]', [res, fi.userName, fi.diskName]);
931 SetLength(gExternalResources, length(gExternalResources)+1);
932 gExternalResources[High(gExternalResources)] := fi;
933 end;
936 procedure compactExtResList ();
937 var
938 src, dest: Integer;
939 begin
940 src := 0;
941 dest := 0;
942 for src := 0 to High(gExternalResources) do
943 begin
944 if (gExternalResources[src].tag = 0) then
945 begin
946 // copy it
947 if (dest <> src) then gExternalResources[dest] := gExternalResources[src];
948 Inc(dest);
949 end;
950 end;
951 if (dest <> length(gExternalResources)) then SetLength(gExternalResources, dest);
952 end;
955 function GetReplacementWad (WadName: AnsiString): AnsiString;
956 begin
957 result := '';
958 if WadName <> '' then
959 begin
960 result := WadName;
961 if g_Game_IsClient then result := g_Res_FindReplacementWad(WadName);
962 if (result = WadName) then result := e_FindWad(WadDirs, result)
963 end;
964 end;
967 procedure generateExternalResourcesList (map: TDynRecord);
968 begin
969 SetLength(gExternalResources, 0);
970 addResToExternalResList(GetReplacementWad(g_ExtractWadName(map.MusicName)));
971 addResToExternalResList(GetReplacementWad(g_ExtractWadName(map.SkyName)));
972 end;
974 function CreateTexture (RecName: AnsiString; Map: String; log: Boolean): Integer;
975 var
976 HName: AnsiString;
977 WAD, WADz: TWADFile;
978 WADName, ResName: String;
979 ResData, ReszData: Pointer;
980 ResLen, ReszLen: Integer;
981 cfg: TConfig;
982 id: Integer;
983 begin
984 Result := -1;
985 HName := toLowerCase1251(RecName);
986 if (TextNameHash = nil) then
987 TextNameHash := THashStrInt.Create();
988 if TextNameHash.get(HName, Result) then
989 begin
990 // e_LogWritefln('CreateTexture: found loaded %s', [Result]);
991 end
992 else
993 begin
994 Result := -1;
995 if (BadTextNameHash = nil) or not BadTextNameHash.has(HName) then
996 begin
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 Result := High(Textures);
1004 TextNameHash.put(RecName, result);
1005 end
1006 else
1007 WADName := GetReplacementWad(g_ExtractWadName(RecName));
1008 if (WADName <> '') then
1009 addResToExternalResList(WADName);
1010 if WADName = '' then
1011 WADName := Map;
1012 ResName := g_ExtractFilePathName(RecName);
1013 WAD := TWADFile.Create();
1014 if WAD.ReadFile(WadName) then
1015 begin
1016 if WAD.GetResource(ResName, ResData, ResLen, log) then
1017 begin
1018 if IsWadData(ResData, ResLen) then
1019 begin
1020 WADz := TWADFile.Create();
1021 if WADz.ReadMemory(ResData, ResLen) then
1022 begin
1023 if WADz.GetResource('TEXT/ANIM', ReszData, ReszLen) then
1024 begin
1025 cfg := TConfig.CreateMem(ReszData, ReszLen);
1026 if cfg <> nil then
1027 begin
1028 SetLength(Textures, Length(Textures) + 1);
1029 Textures[High(Textures)].TextureName := RecName;
1030 Textures[High(Textures)].FullName := WadName + ':' + ResName;
1031 Textures[High(Textures)].FramesCount := cfg.ReadInt('', 'framecount', 0);
1032 Textures[High(Textures)].Speed := cfg.ReadInt('', 'waitcount', 0);
1033 Result := High(Textures);
1034 TextNameHash.put(HName, result);
1035 cfg.Free;
1036 end;
1037 FreeMem(ReszData);
1038 end
1039 end;
1040 WADz.Free;
1041 end
1042 else
1043 begin
1044 SetLength(Textures, Length(Textures) + 1);
1045 Textures[High(Textures)].FullName := WADName + ':' + ResName;
1046 Textures[High(Textures)].TextureName := RecName;
1047 Result := High(Textures);
1048 TextNameHash.put(HName, result);
1049 end;
1050 FreeMem(ResData);
1051 end
1052 end;
1053 WAD.Free;
1054 end
1055 end;
1056 end;
1057 if Result < 0 then
1058 begin
1059 if (BadTextNameHash = nil) then
1060 BadTextNameHash := THashStrInt.Create();
1061 if log and (not BadTextNameHash.get(HName, id)) then
1062 e_WriteLog(Format('Error loading texture %s', [RecName]), TMsgType.Warning);
1063 BadTextNameHash.put(HName, -1);
1064 end
1065 end;
1067 procedure CreateItem(Item: TDynRecord);
1068 begin
1069 if g_Game_IsClient then Exit;
1071 if (not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) and
1072 ByteBool(Item.Options and ITEM_OPTION_ONLYDM) then
1073 Exit;
1075 g_Items_Create(Item.X, Item.Y, Item.ItemType, ByteBool(Item.Options and ITEM_OPTION_FALL),
1076 gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF, GM_COOP]);
1077 end;
1079 procedure CreateArea(Area: TDynRecord);
1080 var
1081 a: Integer;
1082 id: DWORD = 0;
1083 begin
1084 case Area.AreaType of
1085 AREA_DMPOINT, AREA_PLAYERPOINT1, AREA_PLAYERPOINT2,
1086 AREA_REDTEAMPOINT, AREA_BLUETEAMPOINT:
1087 begin
1088 SetLength(RespawnPoints, Length(RespawnPoints)+1);
1089 with RespawnPoints[High(RespawnPoints)] do
1090 begin
1091 X := Area.X;
1092 Y := Area.Y;
1093 Direction := TDirection(Area.Direction);
1095 case Area.AreaType of
1096 AREA_DMPOINT: PointType := RESPAWNPOINT_DM;
1097 AREA_PLAYERPOINT1: PointType := RESPAWNPOINT_PLAYER1;
1098 AREA_PLAYERPOINT2: PointType := RESPAWNPOINT_PLAYER2;
1099 AREA_REDTEAMPOINT: PointType := RESPAWNPOINT_RED;
1100 AREA_BLUETEAMPOINT: PointType := RESPAWNPOINT_BLUE;
1101 end;
1102 end;
1103 end;
1105 AREA_REDFLAG, AREA_BLUEFLAG:
1106 begin
1107 if Area.AreaType = AREA_REDFLAG then a := FLAG_RED else a := FLAG_BLUE;
1109 if FlagPoints[a] <> nil then Exit;
1111 New(FlagPoints[a]);
1113 with FlagPoints[a]^ do
1114 begin
1115 X := Area.X-FLAGRECT.X;
1116 Y := Area.Y-FLAGRECT.Y;
1117 Direction := TDirection(Area.Direction);
1118 end;
1120 with gFlags[a] do
1121 begin
1122 case a of
1123 FLAG_RED: g_Frames_Get(id, 'FRAMES_FLAG_RED');
1124 FLAG_BLUE: g_Frames_Get(id, 'FRAMES_FLAG_BLUE');
1125 end;
1127 Animation := TAnimation.Create(id, True, 8);
1128 Obj.Rect := FLAGRECT;
1130 g_Map_ResetFlag(a);
1131 end;
1132 end;
1134 AREA_DOMFLAG:
1135 begin
1136 {SetLength(DOMFlagPoints, Length(DOMFlagPoints)+1);
1137 with DOMFlagPoints[High(DOMFlagPoints)] do
1138 begin
1139 X := Area.X;
1140 Y := Area.Y;
1141 Direction := TDirection(Area.Direction);
1142 end;
1144 g_Map_CreateFlag(DOMFlagPoints[High(DOMFlagPoints)], FLAG_DOM, FLAG_STATE_NORMAL);}
1145 end;
1146 end;
1147 end;
1149 function CreateTrigger (amapIdx: Integer; Trigger: TDynRecord; atpanid, atrigpanid: Integer): Integer;
1150 var
1151 _trigger: TTrigger;
1152 tp: TPanel;
1153 begin
1154 result := -1;
1155 if g_Game_IsClient and not (Trigger.TriggerType in [TRIGGER_SOUND, TRIGGER_MUSIC]) then Exit;
1157 with _trigger do
1158 begin
1159 mapId := Trigger.id;
1160 mapIndex := amapIdx;
1161 X := Trigger.X;
1162 Y := Trigger.Y;
1163 Width := Trigger.Width;
1164 Height := Trigger.Height;
1165 Enabled := Trigger.Enabled;
1166 TexturePanelGUID := atpanid;
1167 TriggerType := Trigger.TriggerType;
1168 ActivateType := Trigger.ActivateType;
1169 Keys := Trigger.Keys;
1170 trigPanelGUID := atrigpanid;
1171 // HACK: used in TPanel.CanChangeTexture. maybe there's a better way?
1172 if TexturePanelGUID <> -1 then
1173 begin
1174 tp := g_Map_PanelByGUID(TexturePanelGUID);
1175 if (tp <> nil) then tp.hasTexTrigger := True;
1176 end;
1177 end;
1179 result := Integer(g_Triggers_Create(_trigger, Trigger));
1180 end;
1182 procedure CreateMonster(monster: TDynRecord);
1183 var
1184 a: Integer;
1185 mon: TMonster;
1186 begin
1187 if g_Game_IsClient then Exit;
1189 if (gGameSettings.GameType = GT_SINGLE)
1190 or LongBool(gGameSettings.Options and GAME_OPTION_MONSTERS) then
1191 begin
1192 mon := g_Monsters_Create(monster.MonsterType, monster.X, monster.Y, TDirection(monster.Direction));
1194 if gTriggers <> nil then
1195 begin
1196 for a := 0 to High(gTriggers) do
1197 begin
1198 if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
1199 begin
1200 //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
1201 if (gTriggers[a].trigDataRec.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
1202 end;
1203 end;
1204 end;
1206 if monster.MonsterType <> MONSTER_BARREL then Inc(gTotalMonsters);
1207 end;
1208 end;
1210 procedure g_Map_ReAdd_DieTriggers();
1212 function monsDieTrig (mon: TMonster): Boolean;
1213 var
1214 a: Integer;
1215 //tw: TStrTextWriter;
1216 begin
1217 result := false; // don't stop
1218 mon.ClearTriggers();
1219 for a := 0 to High(gTriggers) do
1220 begin
1221 if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
1222 begin
1223 //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
1225 tw := TStrTextWriter.Create();
1226 try
1227 gTriggers[a].trigData.writeTo(tw);
1228 e_LogWritefln('=== trigger #%s ==='#10'%s'#10'---', [a, tw.str]);
1229 finally
1230 tw.Free();
1231 end;
1233 if (gTriggers[a].trigDataRec.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
1234 end;
1235 end;
1236 end;
1238 begin
1239 if g_Game_IsClient then Exit;
1241 g_Mons_ForEach(monsDieTrig);
1242 end;
1244 procedure mapCreateGrid ();
1245 var
1246 mapX0: Integer = $3fffffff;
1247 mapY0: Integer = $3fffffff;
1248 mapX1: Integer = -$3fffffff;
1249 mapY1: Integer = -$3fffffff;
1251 procedure calcBoundingBox (constref panels: TPanelArray);
1252 var
1253 idx: Integer;
1254 pan: TPanel;
1255 begin
1256 for idx := 0 to High(panels) do
1257 begin
1258 pan := panels[idx];
1259 if not pan.visvalid then continue;
1260 if (pan.Width < 1) or (pan.Height < 1) then continue;
1261 if (mapX0 > pan.x0) then mapX0 := pan.x0;
1262 if (mapY0 > pan.y0) then mapY0 := pan.y0;
1263 if (mapX1 < pan.x1) then mapX1 := pan.x1;
1264 if (mapY1 < pan.y1) then mapY1 := pan.y1;
1265 end;
1266 end;
1268 procedure addPanelsToGrid (constref panels: TPanelArray);
1269 var
1270 idx: Integer;
1271 pan: TPanel;
1272 newtag: Integer;
1273 begin
1274 //tag := panelTypeToTag(tag);
1275 for idx := 0 to High(panels) do
1276 begin
1277 pan := panels[idx];
1278 if not pan.visvalid then continue;
1279 if (pan.proxyId <> -1) then
1280 begin
1281 {$IF DEFINED(D2F_DEBUG)}
1282 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);
1283 {$ENDIF}
1284 continue;
1285 end;
1286 case pan.PanelType of
1287 PANEL_WALL: newtag := GridTagWall;
1288 PANEL_OPENDOOR, PANEL_CLOSEDOOR: newtag := GridTagDoor;
1289 PANEL_BACK: newtag := GridTagBack;
1290 PANEL_FORE: newtag := GridTagFore;
1291 PANEL_WATER: newtag := GridTagWater;
1292 PANEL_ACID1: newtag := GridTagAcid1;
1293 PANEL_ACID2: newtag := GridTagAcid2;
1294 PANEL_STEP: newtag := GridTagStep;
1295 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: newtag := GridTagLift;
1296 PANEL_BLOCKMON: newtag := GridTagBlockMon;
1297 else continue; // oops
1298 end;
1299 pan.tag := newtag;
1301 pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, newtag);
1302 // "enabled" flag has meaning only for doors and walls (engine assumes it); but meh...
1303 mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
1304 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
1306 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
1307 begin
1308 e_WriteLog(Format('INSERTED wall #%d(%d) enabled (%d)', [Integer(idx), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId])]), MSG_NOTIFY);
1309 end;
1311 {$ENDIF}
1312 end;
1313 end;
1315 begin
1316 mapGrid.Free();
1317 mapGrid := nil;
1319 calcBoundingBox(gWalls);
1320 calcBoundingBox(gRenderBackgrounds);
1321 calcBoundingBox(gRenderForegrounds);
1322 calcBoundingBox(gWater);
1323 calcBoundingBox(gAcid1);
1324 calcBoundingBox(gAcid2);
1325 calcBoundingBox(gSteps);
1326 calcBoundingBox(gLifts);
1327 calcBoundingBox(gBlockMon);
1329 e_LogWritefln('map dimensions: (%d,%d)-(%d,%d); editor size:(0,0)-(%d,%d)', [mapX0, mapY0, mapX1, mapY1, gMapInfo.Width, gMapInfo.Height]);
1331 if (mapX0 > 0) then mapX0 := 0;
1332 if (mapY0 > 0) then mapY0 := 0;
1334 if (mapX1 < gMapInfo.Width-1) then mapX1 := gMapInfo.Width-1;
1335 if (mapY1 < gMapInfo.Height-1) then mapY1 := gMapInfo.Height-1;
1337 mapGrid := TPanelGrid.Create(mapX0-128, mapY0-128, mapX1-mapX0+1+128*2, mapY1-mapY0+1+128*2);
1338 //mapGrid := TPanelGrid.Create(0, 0, gMapInfo.Width, gMapInfo.Height);
1340 addPanelsToGrid(gWalls);
1341 addPanelsToGrid(gRenderBackgrounds);
1342 addPanelsToGrid(gRenderForegrounds);
1343 addPanelsToGrid(gWater);
1344 addPanelsToGrid(gAcid1);
1345 addPanelsToGrid(gAcid2);
1346 addPanelsToGrid(gSteps);
1347 addPanelsToGrid(gLifts); // it doesn't matter which LIFT type is used here
1348 addPanelsToGrid(gBlockMon);
1350 mapGrid.dumpStats();
1352 g_Mons_InitTree(mapGrid.gridX0, mapGrid.gridY0, mapGrid.gridWidth, mapGrid.gridHeight);
1353 end;
1356 function g_Map_Load(Res: String): Boolean;
1357 const
1358 DefaultMusRes = 'Standart.wad:STDMUS\MUS1';
1359 DefaultSkyRes = 'Standart.wad:STDSKY\SKY0';
1360 type
1361 PTRec = ^TTRec;
1362 TTRec = record
1363 //TexturePanel: Integer;
1364 tnum: Integer;
1365 id: Integer;
1366 trigrec: TDynRecord;
1367 // texture pane;
1368 texPanelIdx: Integer;
1369 texPanel: TDynRecord;
1370 // "action" panel
1371 actPanelIdx: Integer;
1372 actPanel: TDynRecord;
1373 end;
1374 var
1375 WAD, TestWAD: TWADFile;
1376 //mapReader: TDynRecord = nil;
1377 mapTextureList: TDynField = nil; //TTexturesRec1Array; tagInt: texture index
1378 panels: TDynField = nil; //TPanelsRec1Array;
1379 items: TDynField = nil; //TItemsRec1Array;
1380 monsters: TDynField = nil; //TMonsterRec1Array;
1381 areas: TDynField = nil; //TAreasRec1Array;
1382 triggers: TDynField = nil; //TTriggersRec1Array;
1383 b, c, k: Integer;
1384 PanelID: DWORD;
1385 AddTextures: TAddTextureArray;
1386 TriggersTable: array of TTRec;
1387 FileName, mapResName, TexName, s: AnsiString;
1388 Data: Pointer;
1389 Len: Integer;
1390 ok: Boolean;
1391 CurTex, ntn: Integer;
1392 rec, texrec: TDynRecord;
1393 pttit: PTRec;
1394 pannum, trignum, cnt, tgpid: Integer;
1395 stt: UInt64;
1396 moveSpeed{, moveStart, moveEnd}: TDFPoint;
1397 //moveActive: Boolean;
1398 pan: TPanel;
1399 mapOk: Boolean = false;
1400 usedTextures: THashStrInt = nil; // key: mapTextureList
1401 begin
1402 mapGrid.Free();
1403 mapGrid := nil;
1404 TestWAD := nil;
1405 Data := nil;
1407 //gCurrentMap.Free();
1408 //gCurrentMap := nil;
1410 panByGUID := nil;
1412 Result := False;
1413 gMapInfo.Map := Res;
1414 TriggersTable := nil;
1415 //mapReader := nil;
1417 sfsGCDisable(); // temporary disable removing of temporary volumes
1418 try
1419 // Çàãðóçêà WAD (åñëè ó íàñ íåò óæå çàãðóæåíîé êàðòû)
1420 if (gCurrentMap = nil) then
1421 begin
1422 FileName := g_ExtractWadName(Res);
1423 e_LogWritefln('Loading map WAD [%s] (res=[%s])', [FileName, Res], TMsgType.Notify);
1424 g_Game_SetLoadingText(_lc[I_LOAD_WAD_FILE], 0, False);
1426 WAD := TWADFile.Create();
1427 if not WAD.ReadFile(FileName) then
1428 begin
1429 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_WAD], [FileName]));
1430 WAD.Free();
1431 Exit;
1432 end;
1434 if gTestMap <> '' then
1435 begin
1436 s := g_ExtractWadName(gTestMap);
1437 TestWAD := TWADFile.Create();
1438 if not TestWAD.ReadFile(s) then
1439 begin
1440 g_SimpleError(Format(_lc[I_GAME_ERROR_MAP_WAD], [s]));
1441 TestWAD.Free();
1442 TestWAD := nil;
1443 end;
1444 end;
1446 if TestWAD <> nil then
1447 begin
1448 mapResName := g_ExtractFileName(gTestMap);
1449 if not TestWAD.GetMapResource(mapResName, Data, Len) then
1450 begin
1451 g_SimpleError(Format(_lc[I_GAME_ERROR_MAP_RES], [mapResName]));
1452 Data := nil;
1453 end else
1454 e_WriteLog('Using test map: '+gTestMap, TMsgType.Notify);
1455 TestWAD.Free();
1456 TestWAD := nil;
1457 end;
1459 if Data = nil then
1460 begin
1461 //k8: why loader ignores path here?
1462 mapResName := g_ExtractFileName(Res);
1463 if not WAD.GetMapResource(mapResName, Data, Len) then
1464 begin
1465 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_RES], [mapResName]));
1466 WAD.Free();
1467 Exit;
1468 end;
1469 end;
1471 WAD.Free();
1473 if (Len < 4) then
1474 begin
1475 e_LogWritefln('invalid map file: ''%s''', [mapResName]);
1476 FreeMem(Data);
1477 exit;
1478 end;
1480 // Çàãðóçêà êàðòû:
1481 e_LogWritefln('Loading map: %s', [mapResName], TMsgType.Notify);
1482 g_Game_SetLoadingText(_lc[I_LOAD_MAP], 0, False);
1484 stt := getTimeMicro();
1486 try
1487 gCurrentMap := g_Map_ParseMap(Data, Len);
1488 except
1489 gCurrentMap.Free();
1490 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]));
1491 FreeMem(Data);
1492 gCurrentMapFileName := '';
1493 Exit;
1494 end;
1496 FreeMem(Data);
1498 if (gCurrentMap = nil) then
1499 begin
1500 e_LogWritefln('invalid map file: ''%s''', [mapResName]);
1501 gCurrentMapFileName := '';
1502 exit;
1503 end;
1504 end
1505 else
1506 begin
1507 stt := getTimeMicro();
1508 end;
1510 //gCurrentMap := mapReader;
1512 generateExternalResourcesList(gCurrentMap);
1513 mapTextureList := gCurrentMap['texture'];
1514 // get all other lists here too
1515 panels := gCurrentMap['panel'];
1516 triggers := gCurrentMap['trigger'];
1517 items := gCurrentMap['item'];
1518 areas := gCurrentMap['area'];
1519 monsters := gCurrentMap['monster'];
1521 // Çàãðóçêà îïèñàíèÿ êàðòû:
1522 e_WriteLog(' Reading map info...', TMsgType.Notify);
1523 g_Game_SetLoadingText(_lc[I_LOAD_MAP_HEADER], 0, False);
1525 with gMapInfo do
1526 begin
1527 Name := gCurrentMap.MapName;
1528 Description := gCurrentMap.MapDesc;
1529 Author := gCurrentMap.MapAuthor;
1530 MusicName := gCurrentMap.MusicName;
1531 SkyName := gCurrentMap.SkyName;
1532 Height := gCurrentMap.Height;
1533 Width := gCurrentMap.Width;
1534 end;
1536 // Çàãðóçêà òåêñòóð:
1537 g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], 0, False);
1538 // Äîáàâëåíèå òåêñòóð â Textures[]:
1539 if (mapTextureList <> nil) and (mapTextureList.count > 0) then
1540 begin
1541 e_WriteLog(' Loading textures:', TMsgType.Notify);
1542 g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], mapTextureList.count-1, False);
1544 // find used textures
1545 usedTextures := THashStrInt.Create();
1546 try
1547 if (panels <> nil) and (panels.count > 0) then
1548 begin
1549 for rec in panels do
1550 begin
1551 texrec := rec.TextureRec;
1552 if (texrec <> nil) then usedTextures.put(toLowerCase1251(texrec.Resource), 42);
1553 end;
1554 end;
1556 cnt := -1;
1557 for rec in mapTextureList do
1558 begin
1559 Inc(cnt);
1560 if not usedTextures.has(toLowerCase1251(rec.Resource)) then
1561 begin
1562 rec.tagInt := -1; // just in case
1563 e_LogWritefln(' Unused texture #%d: %s', [cnt, rec.Resource]);
1564 end
1565 else
1566 begin
1567 {$IF DEFINED(D2F_DEBUG_TXLOAD)}
1568 e_LogWritefln(' Loading texture #%d: %s', [cnt, rec.Resource]);
1569 {$ENDIF}
1570 ntn := CreateTexture(rec.Resource, FileName, True);
1571 if ntn < 0 then
1572 begin
1573 if rec.Anim then
1574 g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_ANIM], [rec.Resource]))
1575 else
1576 g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_SIMPLE], [rec.Resource]));
1577 ntn := CreateNullTexture(rec.Resource)
1578 end;
1579 rec.tagInt := ntn; // remember texture number
1580 end;
1581 g_Game_StepLoading();
1582 end;
1583 finally
1584 usedTextures.Free();
1585 end;
1587 // set panel tagInt to texture index
1588 if (panels <> nil) then
1589 begin
1590 for rec in panels do
1591 begin
1592 texrec := rec.TextureRec;
1593 if (texrec = nil) then rec.tagInt := -1 else rec.tagInt := texrec.tagInt;
1594 end;
1595 end;
1596 end;
1599 // Çàãðóçêà òðèããåðîâ
1600 gTriggerClientID := 0;
1601 e_WriteLog(' Loading triggers...', TMsgType.Notify);
1602 g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS], 0, False);
1604 // Çàãðóçêà ïàíåëåé
1605 e_WriteLog(' Loading panels...', TMsgType.Notify);
1606 g_Game_SetLoadingText(_lc[I_LOAD_PANELS], 0, False);
1608 // check texture numbers for panels
1609 if (panels <> nil) and (panels.count > 0) then
1610 begin
1611 for rec in panels do
1612 begin
1613 if (rec.tagInt < 0) then
1614 begin
1615 e_WriteLog('error loading map: invalid texture index for panel', TMsgType.Fatal);
1616 result := false;
1617 gCurrentMap.Free();
1618 gCurrentMap := nil;
1619 gCurrentMapFileName := '';
1620 exit;
1621 end;
1622 end;
1623 end;
1625 // Ñîçäàíèå òàáëèöû òðèããåðîâ (ñîîòâåòñòâèå ïàíåëåé òðèããåðàì)
1626 if (triggers <> nil) and (triggers.count > 0) then
1627 begin
1628 e_WriteLog(' Setting up trigger table...', TMsgType.Notify);
1629 //SetLength(TriggersTable, triggers.count);
1630 g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS_TABLE], triggers.count-1, False);
1632 SetLength(TriggersTable, triggers.count);
1633 trignum := -1;
1634 for rec in triggers do
1635 begin
1636 Inc(trignum);
1637 pttit := @TriggersTable[trignum];
1638 pttit.trigrec := rec;
1639 // Ñìåíà òåêñòóðû (âîçìîæíî, êíîïêè)
1640 pttit.texPanelIdx := -1; // will be fixed later
1641 pttit.texPanel := rec.TexturePanelRec;
1642 // action panel
1643 pttit.actPanelIdx := -1;
1644 if (rec.trigRec <> nil) then pttit.actPanel := rec.trigRec.tgPanelRec else pttit.actPanel := nil;
1645 // set flag
1646 if (pttit.texPanel <> nil) then pttit.texPanel.userPanelTrigRef := true;
1647 if (pttit.actPanel <> nil) then pttit.actPanel.userPanelTrigRef := true;
1648 // update progress
1649 g_Game_StepLoading();
1650 end;
1651 end;
1653 // Ñîçäàåì ïàíåëè
1654 if (panels <> nil) and (panels.count > 0) then
1655 begin
1656 e_WriteLog(' Setting up trigger links...', TMsgType.Notify);
1657 g_Game_SetLoadingText(_lc[I_LOAD_LINK_TRIGGERS], panels.count-1, False);
1659 pannum := -1;
1660 for rec in panels do
1661 begin
1662 Inc(pannum);
1663 //e_LogWritefln('PANSTART: pannum=%s', [pannum]);
1664 texrec := nil;
1665 SetLength(AddTextures, 0);
1666 CurTex := -1;
1667 ok := false;
1669 if (mapTextureList <> nil) then
1670 begin
1671 texrec := rec.TextureRec;
1672 ok := (texrec <> nil);
1673 end;
1675 if ok then
1676 begin
1677 // Ñìîòðèì, ññûëàþòñÿ ëè íà ýòó ïàíåëü òðèããåðû.
1678 // Åñëè äà - òî íàäî ñîçäàòü åùå òåêñòóð
1679 ok := false;
1680 if (TriggersTable <> nil) and (mapTextureList <> nil) then
1681 begin
1682 if rec.userPanelTrigRef then
1683 begin
1684 // e_LogWritefln('trigref for panel %s', [pannum]);
1685 ok := True;
1686 end;
1687 end;
1688 end;
1690 if ok then
1691 begin
1692 // Åñòü ññûëêè òðèããåðîâ íà ýòó ïàíåëü
1693 s := texrec.Resource;
1695 // Ñïåö-òåêñòóðû çàïðåùåíû
1696 if g_Map_IsSpecialTexture(s) then
1697 begin
1698 ok := false
1699 end
1700 else
1701 begin
1702 // Îïðåäåëÿåì íàëè÷èå è ïîëîæåíèå öèôð â êîíöå ñòðîêè
1703 ok := g_Texture_NumNameFindStart(s);
1704 end;
1706 // Åñëè ok, çíà÷èò åñòü öèôðû â êîíöå.
1707 // Çàãðóæàåì òåêñòóðû ñ îñòàëüíûìè #
1708 if ok then
1709 begin
1710 k := NNF_NAME_BEFORE;
1711 // Öèêë ïî èçìåíåíèþ èìåíè òåêñòóðû
1712 while ok or (k = NNF_NAME_BEFORE) or (k = NNF_NAME_EQUALS) do
1713 begin
1714 k := g_Texture_NumNameFindNext(TexName);
1716 if (k = NNF_NAME_BEFORE) or (k = NNF_NAME_AFTER) then
1717 begin
1718 ok := CreateTexture(TexName, FileName, False) >= 0;
1720 // Îíà ñóùåñòâóåò. Çàíîñèì åå ID â ñïèñîê ïàíåëè
1721 if ok then
1722 begin
1724 for c := 0 to High(Textures) do
1725 begin
1726 if (Textures[c].TextureName = TexName) then
1727 begin
1728 SetLength(AddTextures, Length(AddTextures)+1);
1729 AddTextures[High(AddTextures)].Texture := c;
1730 break;
1731 end;
1732 end;
1734 if (TextNameHash <> nil) and TextNameHash.get(toLowerCase1251(TexName), c) then
1735 begin
1736 SetLength(AddTextures, Length(AddTextures)+1);
1737 AddTextures[High(AddTextures)].Texture := c;
1738 end;
1739 end;
1740 end
1741 else
1742 begin
1743 if k = NNF_NAME_EQUALS then
1744 begin
1745 // Çàíîñèì òåêóùóþ òåêñòóðó íà ñâîå ìåñòî
1746 SetLength(AddTextures, Length(AddTextures)+1);
1747 AddTextures[High(AddTextures)].Texture := rec.tagInt; // internal texture number, not map index
1748 CurTex := High(AddTextures);
1749 ok := true;
1750 end
1751 else // NNF_NO_NAME
1752 begin
1753 ok := false;
1754 end;
1755 end;
1756 end; // while ok...
1758 ok := true;
1759 end; // if ok - åñòü ñìåæíûå òåêñòóðû
1760 end; // if ok - ññûëàþòñÿ òðèããåðû
1762 if not ok then
1763 begin
1764 // Çàíîñèì òîëüêî òåêóùóþ òåêñòóðó
1765 SetLength(AddTextures, 1);
1766 AddTextures[0].Texture := rec.tagInt; // internal texture number, not map index
1767 CurTex := 0;
1768 end;
1770 //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);
1772 //e_LogWritefln('PANADD: pannum=%s', [pannum]);
1774 // Ñîçäàåì ïàíåëü è çàïîìèíàåì åå GUID
1775 //e_LogWritefln('new panel; tcount=%s; curtex=%s', [Length(AddTextures), CurTex]);
1776 PanelID := CreatePanel(rec, AddTextures, CurTex);
1777 //e_LogWritefln('panel #%s of type %s got guid #%s', [pannum, rec.PanelType, PanelID]);
1778 rec.userPanelId := PanelID; // remember game panel id, we'll fix triggers later
1780 // setup lifts
1781 moveSpeed := rec.moveSpeed;
1782 //moveStart := rec.moveStart;
1783 //moveEnd := rec.moveEnd;
1784 //moveActive := rec['move_active'].value;
1785 if not moveSpeed.isZero then
1786 begin
1787 SetLength(gMovingWallIds, Length(gMovingWallIds)+1);
1788 gMovingWallIds[High(gMovingWallIds)] := PanelID;
1789 //e_LogWritefln('found moving panel ''%s'' (idx=%s; id=%s)', [rec.id, pannum, PanelID]);
1790 end;
1792 //e_LogWritefln('PANEND: pannum=%s', [pannum]);
1794 g_Game_StepLoading();
1795 end;
1796 end;
1798 // ×èíèì ID'û ïàíåëåé, êîòîðûå èñïîëüçóþòñÿ â òðèããåðàõ
1799 for b := 0 to High(TriggersTable) do
1800 begin
1801 if (TriggersTable[b].texPanel <> nil) then TriggersTable[b].texPanelIdx := TriggersTable[b].texPanel.userPanelId;
1802 if (TriggersTable[b].actPanel <> nil) then TriggersTable[b].actPanelIdx := TriggersTable[b].actPanel.userPanelId;
1803 end;
1805 // create map grid, init other grids (for monsters, for example)
1806 e_WriteLog('Creating map grid', TMsgType.Notify);
1807 mapCreateGrid();
1809 // Åñëè íå LoadState, òî ñîçäàåì òðèããåðû
1810 if (triggers <> nil) and (panels <> nil) and (not gLoadGameMode) then
1811 begin
1812 e_LogWritefln(' Creating triggers (%d)...', [triggers.count]);
1813 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_TRIGGERS], 0, False);
1814 // Óêàçûâàåì òèï ïàíåëè, åñëè åñòü
1815 trignum := -1;
1816 for rec in triggers do
1817 begin
1818 Inc(trignum);
1819 tgpid := TriggersTable[trignum].actPanelIdx;
1820 //e_LogWritefln('creating trigger #%s; texpantype=%s; shotpantype=%s (%d,%d)', [trignum, b, c, TriggersTable[trignum].texPanIdx, TriggersTable[trignum].ShotPanelIdx]);
1821 TriggersTable[trignum].tnum := trignum;
1822 TriggersTable[trignum].id := CreateTrigger(trignum, rec, TriggersTable[trignum].texPanelIdx, tgpid);
1823 end;
1824 end;
1826 //FIXME: use hashtable!
1827 for pan in panByGUID do
1828 begin
1829 if (pan.endPosTrigId >= 0) and (pan.endPosTrigId < Length(TriggersTable)) then
1830 begin
1831 pan.endPosTrigId := TriggersTable[pan.endPosTrigId].id;
1832 end;
1833 if (pan.endSizeTrigId >= 0) and (pan.endSizeTrigId < Length(TriggersTable)) then
1834 begin
1835 pan.endSizeTrigId := TriggersTable[pan.endSizeTrigId].id;
1836 end;
1837 end;
1839 // Çàãðóçêà ïðåäìåòîâ
1840 e_WriteLog(' Loading items...', TMsgType.Notify);
1841 g_Game_SetLoadingText(_lc[I_LOAD_ITEMS], 0, False);
1843 // Åñëè íå LoadState, òî ñîçäàåì ïðåäìåòû
1844 if (items <> nil) and not gLoadGameMode then
1845 begin
1846 e_WriteLog(' Spawning items...', TMsgType.Notify);
1847 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_ITEMS], 0, False);
1848 for rec in items do CreateItem(rec);
1849 end;
1851 // Çàãðóçêà îáëàñòåé
1852 e_WriteLog(' Loading areas...', TMsgType.Notify);
1853 g_Game_SetLoadingText(_lc[I_LOAD_AREAS], 0, False);
1855 // Åñëè íå LoadState, òî ñîçäàåì îáëàñòè
1856 if areas <> nil then
1857 begin
1858 e_WriteLog(' Creating areas...', TMsgType.Notify);
1859 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_AREAS], 0, False);
1860 for rec in areas do CreateArea(rec);
1861 end;
1863 // Çàãðóçêà ìîíñòðîâ
1864 e_WriteLog(' Loading monsters...', TMsgType.Notify);
1865 g_Game_SetLoadingText(_lc[I_LOAD_MONSTERS], 0, False);
1867 gTotalMonsters := 0;
1869 // Åñëè íå LoadState, òî ñîçäàåì ìîíñòðîâ
1870 if (monsters <> nil) and not gLoadGameMode then
1871 begin
1872 e_WriteLog(' Spawning monsters...', TMsgType.Notify);
1873 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_MONSTERS], 0, False);
1874 for rec in monsters do CreateMonster(rec);
1875 end;
1877 //gCurrentMap := mapReader; // this will be our current map now
1878 gCurrentMapFileName := Res;
1879 //mapReader := nil;
1881 // Çàãðóçêà íåáà
1882 if (gMapInfo.SkyName <> '') then
1883 begin
1884 e_WriteLog(' Loading sky: ' + gMapInfo.SkyName, TMsgType.Notify);
1885 g_Game_SetLoadingText(_lc[I_LOAD_SKY], 0, False);
1886 s := e_GetResourcePath(WadDirs, gMapInfo.SkyName, g_ExtractWadName(Res));
1887 if g_Texture_CreateWAD(BackID, s, gTextureFilter) then
1888 g_Game_SetupScreenSize
1889 else
1890 g_FatalError(Format(_lc[I_GAME_ERROR_SKY], [s]))
1891 end;
1893 // Çàãðóçêà ìóçûêè
1894 ok := False;
1895 if gMapInfo.MusicName <> '' then
1896 begin
1897 e_WriteLog(' Loading music: ' + gMapInfo.MusicName, TMsgType.Notify);
1898 g_Game_SetLoadingText(_lc[I_LOAD_MUSIC], 0, False);
1900 s := e_GetResourcePath(WadDirs, gMapInfo.MusicName, g_ExtractWadName(Res));
1901 if g_Sound_CreateWADEx(gMapInfo.MusicName, s, True) then
1902 ok := True
1903 else
1904 g_FatalError(Format(_lc[I_GAME_ERROR_MUSIC], [s]));
1905 end;
1907 // Îñòàëüíûå óñòàíâêè
1908 CreateDoorMap();
1909 CreateLiftMap();
1911 g_Items_Init();
1912 g_Weapon_Init();
1913 g_Monsters_Init();
1915 // Åñëè íå LoadState, òî ñîçäàåì êàðòó ñòîëêíîâåíèé:
1916 if not gLoadGameMode then g_GFX_Init();
1918 // Ñáðîñ ëîêàëüíûõ ìàññèâîâ:
1919 mapTextureList := nil;
1920 panels := nil;
1921 items := nil;
1922 areas := nil;
1923 triggers := nil;
1924 TriggersTable := nil;
1925 AddTextures := nil;
1927 // Âêëþ÷àåì ìóçûêó, åñëè ýòî íå çàãðóçêà:
1928 if ok and (not gLoadGameMode) then
1929 begin
1930 gMusic.SetByName(gMapInfo.MusicName);
1931 gMusic.Play();
1932 end
1933 else
1934 begin
1935 gMusic.SetByName('');
1936 end;
1938 stt := getTimeMicro()-stt;
1939 e_LogWritefln('map loaded in %s.%s milliseconds', [Integer(stt div 1000), Integer(stt mod 1000)]);
1940 mapOk := true;
1941 finally
1942 sfsGCEnable(); // enable releasing unused volumes
1943 //mapReader.Free();
1944 e_UnpressAllKeys; // why not?
1945 if not mapOk then
1946 begin
1947 gCurrentMap.Free();
1948 gCurrentMap := nil;
1949 gCurrentMapFileName := '';
1950 end;
1951 end;
1953 compactExtResList();
1954 e_WriteLog('Done loading map.', TMsgType.Notify);
1955 Result := True;
1956 end;
1959 function g_Map_GetMapInfo(Res: String): TMapInfo;
1960 var
1961 WAD: TWADFile;
1962 mapReader: TDynRecord;
1963 //Header: TMapHeaderRec_1;
1964 FileName: String;
1965 Data: Pointer;
1966 Len: Integer;
1967 begin
1968 FillChar(Result, SizeOf(Result), 0);
1969 FileName := g_ExtractWadName(Res);
1971 WAD := TWADFile.Create();
1972 if not WAD.ReadFile(FileName) then
1973 begin
1974 WAD.Free();
1975 Exit;
1976 end;
1978 //k8: it ignores path again
1979 if not WAD.GetMapResource(g_ExtractFileName(Res), Data, Len) then
1980 begin
1981 WAD.Free();
1982 Exit;
1983 end;
1985 WAD.Free();
1987 try
1988 mapReader := g_Map_ParseMap(Data, Len);
1989 except
1990 mapReader := nil;
1991 FreeMem(Data);
1992 exit;
1993 end;
1995 FreeMem(Data);
1997 if (mapReader = nil) then exit;
1999 if (mapReader.Width > 0) and (mapReader.Height > 0) then
2000 begin
2001 Result.Name := mapReader.MapName;
2002 Result.Description := mapReader.MapDesc;
2003 Result.Map := Res;
2004 Result.Author := mapReader.MapAuthor;
2005 Result.Height := mapReader.Height;
2006 Result.Width := mapReader.Width;
2007 end
2008 else
2009 begin
2010 g_Console_Add(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]), True);
2011 //ZeroMemory(@Header, SizeOf(Header));
2012 Result.Name := _lc[I_GAME_ERROR_MAP_SELECT];
2013 Result.Description := _lc[I_GAME_ERROR_MAP_SELECT];
2014 Result.Map := Res;
2015 Result.Author := '';
2016 Result.Height := 0;
2017 Result.Width := 0;
2018 end;
2020 mapReader.Free();
2021 end;
2023 function g_Map_GetMapsList(WADName: string): SSArray;
2024 var
2025 WAD: TWADFile;
2026 a: Integer;
2027 ResList: SSArray;
2028 begin
2029 Result := nil;
2030 WAD := TWADFile.Create();
2031 if not WAD.ReadFile(WADName) then
2032 begin
2033 WAD.Free();
2034 Exit;
2035 end;
2036 ResList := WAD.GetMapResources();
2037 if ResList <> nil then
2038 begin
2039 for a := 0 to High(ResList) do
2040 begin
2041 SetLength(Result, Length(Result)+1);
2042 Result[High(Result)] := ResList[a];
2043 end;
2044 end;
2045 WAD.Free();
2046 end;
2048 function g_Map_Exist(Res: string): Boolean;
2049 var
2050 WAD: TWADFile;
2051 FileName, mnn: string;
2052 ResList: SSArray;
2053 a: Integer;
2054 begin
2055 Result := False;
2057 FileName := addWadExtension(g_ExtractWadName(Res));
2059 WAD := TWADFile.Create;
2060 if not WAD.ReadFile(FileName) then
2061 begin
2062 WAD.Free();
2063 Exit;
2064 end;
2066 ResList := WAD.GetMapResources();
2067 WAD.Free();
2069 mnn := g_ExtractFileName(Res);
2070 if ResList <> nil then
2071 for a := 0 to High(ResList) do if StrEquCI1251(ResList[a], mnn) then
2072 begin
2073 Result := True;
2074 Exit;
2075 end;
2076 end;
2078 procedure g_Map_Free(freeTextures: Boolean=true);
2080 procedure FreePanelArray(var panels: TPanelArray);
2081 var
2082 i: Integer;
2084 begin
2085 if panels <> nil then
2086 begin
2087 for i := 0 to High(panels) do
2088 panels[i].Free();
2089 panels := nil;
2090 end;
2091 end;
2093 begin
2094 g_GFX_Free();
2095 g_Weapon_Free();
2096 g_Items_Free();
2097 g_Triggers_Free();
2098 g_Monsters_Free();
2100 RespawnPoints := nil;
2101 if FlagPoints[FLAG_RED] <> nil then
2102 begin
2103 Dispose(FlagPoints[FLAG_RED]);
2104 FlagPoints[FLAG_RED] := nil;
2105 end;
2106 if FlagPoints[FLAG_BLUE] <> nil then
2107 begin
2108 Dispose(FlagPoints[FLAG_BLUE]);
2109 FlagPoints[FLAG_BLUE] := nil;
2110 end;
2111 //DOMFlagPoints := nil;
2113 //gDOMFlags := nil;
2115 if (Length(gCurrentMapFileName) <> 0) then
2116 begin
2117 e_LogWritefln('g_Map_Free: previous map was ''%s''...', [gCurrentMapFileName]);
2118 end
2119 else
2120 begin
2121 e_LogWritefln('g_Map_Free: no previous map.', []);
2122 end;
2124 if freeTextures then
2125 begin
2126 e_LogWritefln('g_Map_Free: clearing textures...', []);
2127 Textures := nil;
2128 TextNameHash.Free();
2129 TextNameHash := nil;
2130 BadTextNameHash.Free();
2131 BadTextNameHash := nil;
2132 gCurrentMapFileName := '';
2133 gCurrentMap.Free();
2134 gCurrentMap := nil;
2135 end;
2137 panByGUID := nil;
2139 FreePanelArray(gWalls);
2140 FreePanelArray(gRenderBackgrounds);
2141 FreePanelArray(gRenderForegrounds);
2142 FreePanelArray(gWater);
2143 FreePanelArray(gAcid1);
2144 FreePanelArray(gAcid2);
2145 FreePanelArray(gSteps);
2146 FreePanelArray(gLifts);
2147 FreePanelArray(gBlockMon);
2148 gMovingWallIds := nil;
2150 if BackID <> DWORD(-1) then
2151 begin
2152 gBackSize.X := 0;
2153 gBackSize.Y := 0;
2154 e_DeleteTexture(BackID);
2155 BackID := DWORD(-1);
2156 end;
2158 g_Game_StopAllSounds(False);
2159 gMusic.FreeSound();
2160 g_Sound_Delete(gMapInfo.MusicName);
2162 gMapInfo.Name := '';
2163 gMapInfo.Description := '';
2164 gMapInfo.MusicName := '';
2165 gMapInfo.Height := 0;
2166 gMapInfo.Width := 0;
2168 gDoorMap := nil;
2169 gLiftMap := nil;
2170 end;
2172 procedure g_Map_Update();
2173 var
2174 a, d, j: Integer;
2175 m: Word;
2176 s: String;
2177 b: Byte;
2179 procedure UpdatePanelArray(var panels: TPanelArray);
2180 var
2181 i: Integer;
2183 begin
2184 for i := 0 to High(panels) do panels[i].Update();
2185 end;
2187 begin
2188 if g_dbgpan_mplat_step then g_dbgpan_mplat_active := true;
2190 UpdatePanelArray(gWalls);
2191 UpdatePanelArray(gRenderBackgrounds);
2192 UpdatePanelArray(gRenderForegrounds);
2193 UpdatePanelArray(gWater);
2194 UpdatePanelArray(gAcid1);
2195 UpdatePanelArray(gAcid2);
2196 UpdatePanelArray(gSteps);
2198 if g_dbgpan_mplat_step then begin g_dbgpan_mplat_step := false; g_dbgpan_mplat_active := false; end;
2200 if gGameSettings.GameMode = GM_CTF then
2201 begin
2202 for a := FLAG_RED to FLAG_BLUE do
2203 begin
2204 if not (gFlags[a].State in [FLAG_STATE_NONE, FLAG_STATE_CAPTURED]) then
2205 begin
2206 with gFlags[a] do
2207 begin
2208 if gFlags[a].Animation <> nil then gFlags[a].Animation.Update();
2210 Obj.oldX := Obj.X;
2211 Obj.oldY := Obj.Y;
2213 m := g_Obj_Move(@Obj, True, True);
2215 NeedSend := NeedSend or (Obj.X <> Obj.oldX) or (Obj.Y <> Obj.oldY);
2217 if gTime mod (GAME_TICK*2) <> 0 then Continue;
2219 // Ñîïðîòèâëåíèå âîçäóõà
2220 Obj.Vel.X := z_dec(Obj.Vel.X, 1);
2222 // Òàéìàóò ïîòåðÿííîãî ôëàãà, ëèáî îí âûïàë çà êàðòó
2223 if ((Count = 0) or ByteBool(m and MOVE_FALLOUT)) and g_Game_IsServer then
2224 begin
2225 g_Map_ResetFlag(a);
2226 gFlags[a].CaptureTime := 0;
2227 if a = FLAG_RED then
2228 s := _lc[I_PLAYER_FLAG_RED]
2229 else
2230 s := _lc[I_PLAYER_FLAG_BLUE];
2231 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
2233 if (((gPlayer1 <> nil) and (((gPlayer1.Team = TEAM_RED) and (a = FLAG_RED)) or ((gPlayer1.Team = TEAM_BLUE) and (a = FLAG_BLUE))))
2234 or ((gPlayer2 <> nil) and (((gPlayer2.Team = TEAM_RED) and (a = FLAG_RED)) or ((gPlayer2.Team = TEAM_BLUE) and (a = FLAG_BLUE))))) then
2235 b := 0
2236 else
2237 b := 1;
2239 if not sound_ret_flag[b].IsPlaying() then
2240 sound_ret_flag[b].Play();
2242 if g_Game_IsNet then
2243 MH_SEND_FlagEvent(FLAG_STATE_RETURNED, a, 0);
2244 Continue;
2245 end;
2247 if Count > 0 then Count -= 1;
2249 // Èãðîê áåðåò ôëàã
2250 if gPlayers <> nil then
2251 begin
2252 j := Random(Length(gPlayers)) - 1;
2253 for d := 0 to High(gPlayers) do
2254 begin
2255 Inc(j);
2256 if j > High(gPlayers) then j := 0;
2257 if gPlayers[j] <> nil then
2258 begin
2259 if gPlayers[j].alive and g_Obj_Collide(@Obj, @gPlayers[j].Obj) then
2260 begin
2261 if gPlayers[j].GetFlag(a) then Break;
2262 end;
2263 end;
2264 end;
2265 end;
2266 end;
2267 end;
2268 end;
2269 end;
2270 end;
2272 function g_Map_CollidePanelOld(X, Y: Integer; Width, Height: Word;
2273 PanelType: Word; b1x3: Boolean=false): Boolean;
2274 var
2275 a, h: Integer;
2276 begin
2277 Result := False;
2279 if WordBool(PanelType and PANEL_WALL) then
2280 if gWalls <> nil then
2281 begin
2282 h := High(gWalls);
2284 for a := 0 to h do
2285 if gWalls[a].Enabled and
2286 g_Collide(X, Y, Width, Height,
2287 gWalls[a].X, gWalls[a].Y,
2288 gWalls[a].Width, gWalls[a].Height) then
2289 begin
2290 Result := True;
2291 Exit;
2292 end;
2293 end;
2295 if WordBool(PanelType and PANEL_WATER) then
2296 if gWater <> nil then
2297 begin
2298 h := High(gWater);
2300 for a := 0 to h do
2301 if g_Collide(X, Y, Width, Height,
2302 gWater[a].X, gWater[a].Y,
2303 gWater[a].Width, gWater[a].Height) then
2304 begin
2305 Result := True;
2306 Exit;
2307 end;
2308 end;
2310 if WordBool(PanelType and PANEL_ACID1) then
2311 if gAcid1 <> nil then
2312 begin
2313 h := High(gAcid1);
2315 for a := 0 to h do
2316 if g_Collide(X, Y, Width, Height,
2317 gAcid1[a].X, gAcid1[a].Y,
2318 gAcid1[a].Width, gAcid1[a].Height) then
2319 begin
2320 Result := True;
2321 Exit;
2322 end;
2323 end;
2325 if WordBool(PanelType and PANEL_ACID2) then
2326 if gAcid2 <> nil then
2327 begin
2328 h := High(gAcid2);
2330 for a := 0 to h do
2331 if g_Collide(X, Y, Width, Height,
2332 gAcid2[a].X, gAcid2[a].Y,
2333 gAcid2[a].Width, gAcid2[a].Height) then
2334 begin
2335 Result := True;
2336 Exit;
2337 end;
2338 end;
2340 if WordBool(PanelType and PANEL_STEP) then
2341 if gSteps <> nil then
2342 begin
2343 h := High(gSteps);
2345 for a := 0 to h do
2346 if g_Collide(X, Y, Width, Height,
2347 gSteps[a].X, gSteps[a].Y,
2348 gSteps[a].Width, gSteps[a].Height) then
2349 begin
2350 Result := True;
2351 Exit;
2352 end;
2353 end;
2355 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then
2356 if gLifts <> nil then
2357 begin
2358 h := High(gLifts);
2360 for a := 0 to h do
2361 if ((WordBool(PanelType and (PANEL_LIFTUP)) and (gLifts[a].LiftType = LIFTTYPE_UP)) or
2362 (WordBool(PanelType and (PANEL_LIFTDOWN)) and (gLifts[a].LiftType = LIFTTYPE_DOWN)) or
2363 (WordBool(PanelType and (PANEL_LIFTLEFT)) and (gLifts[a].LiftType = LIFTTYPE_LEFT)) or
2364 (WordBool(PanelType and (PANEL_LIFTRIGHT)) and (gLifts[a].LiftType = LIFTTYPE_RIGHT))) and
2365 g_Collide(X, Y, Width, Height,
2366 gLifts[a].X, gLifts[a].Y,
2367 gLifts[a].Width, gLifts[a].Height) then
2368 begin
2369 Result := True;
2370 Exit;
2371 end;
2372 end;
2374 if WordBool(PanelType and PANEL_BLOCKMON) then
2375 if gBlockMon <> nil then
2376 begin
2377 h := High(gBlockMon);
2379 for a := 0 to h do
2380 if ( (not b1x3) or
2381 ((gBlockMon[a].Width + gBlockMon[a].Height) >= 64) ) and
2382 g_Collide(X, Y, Width, Height,
2383 gBlockMon[a].X, gBlockMon[a].Y,
2384 gBlockMon[a].Width, gBlockMon[a].Height) then
2385 begin
2386 Result := True;
2387 Exit;
2388 end;
2389 end;
2390 end;
2392 function g_Map_CollideLiquid_TextureOld(X, Y: Integer; Width, Height: Word): DWORD;
2393 var
2394 texid: DWORD;
2396 function checkPanels (constref panels: TPanelArray): Boolean;
2397 var
2398 a: Integer;
2399 begin
2400 result := false;
2401 if panels = nil then exit;
2402 for a := 0 to High(panels) do
2403 begin
2404 if g_Collide(X, Y, Width, Height, panels[a].X, panels[a].Y, panels[a].Width, panels[a].Height) then
2405 begin
2406 result := true;
2407 texid := panels[a].GetTextureID();
2408 exit;
2409 end;
2410 end;
2411 end;
2413 begin
2414 texid := LongWord(TEXTURE_NONE);
2415 result := texid;
2416 if not checkPanels(gWater) then
2417 if not checkPanels(gAcid1) then
2418 if not checkPanels(gAcid2) then exit;
2419 result := texid;
2420 end;
2423 function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word; PanelType: Word; b1x3: Boolean): Boolean;
2424 const
2425 SlowMask = GridTagLift or GridTagBlockMon;
2427 function checker (pan: TPanel; tag: Integer): Boolean;
2428 begin
2430 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
2431 begin
2432 result := pan.Enabled;
2433 exit;
2434 end;
2437 if ((tag and GridTagLift) <> 0) then
2438 begin
2439 result :=
2440 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
2441 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
2442 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
2443 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT))) {and
2444 g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
2445 exit;
2446 end;
2448 if ((tag and GridTagBlockMon) <> 0) then
2449 begin
2450 result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2451 exit;
2452 end;
2454 // other shit
2455 //result := g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2456 result := true; // i found her!
2457 end;
2459 var
2460 tagmask: Integer = 0;
2461 mwit: PPanel;
2462 it: TPanelGrid.Iter;
2463 pan: TPanel;
2464 begin
2465 result := false;
2466 if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor);
2467 if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater;
2468 if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1;
2469 if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2;
2470 if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep;
2471 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift;
2472 if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon;
2474 if (tagmask = 0) then exit; // just in case
2476 if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('*solids');
2477 if gdbg_map_use_accel_coldet then
2478 begin
2479 if ((tagmask and SlowMask) <> 0) then
2480 begin
2481 // slow
2482 it := mapGrid.forEachInAABB(X, Y, Width, Height, tagmask);
2483 for mwit in it do
2484 begin
2485 pan := mwit^;
2486 if ((pan.tag and GridTagLift) <> 0) then
2487 begin
2488 result :=
2489 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
2490 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
2491 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
2492 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT))) {and
2493 g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
2494 end
2495 else if ((pan.tag and GridTagBlockMon) <> 0) then
2496 begin
2497 result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2498 end
2499 else
2500 begin
2501 // other shit
2502 result := true; // i found her!
2503 end;
2504 if (result) then break;
2505 end;
2506 end
2507 else
2508 begin
2509 // fast
2510 it := mapGrid.forEachInAABB(X, Y, Width, Height, tagmask, false, true); // return first hit
2511 result := (it.length > 0);
2512 end;
2513 it.release();
2514 end
2515 else
2516 begin
2517 result := g_Map_CollidePanelOld(X, Y, Width, Height, PanelType, b1x3);
2518 end;
2519 if (profMapCollision <> nil) then profMapCollision.sectionEnd();
2520 end;
2523 // returns `true` if we need to stop
2524 function liquidChecker (pan: TPanel; var texid: DWORD; var cctype: Integer): Boolean; inline;
2525 begin
2526 result := false;
2527 //if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2)) = 0) then exit;
2528 // check priorities
2529 case cctype of
2530 0: if ((pan.tag and GridTagWater) = 0) then exit; // allowed: water
2531 1: if ((pan.tag and (GridTagWater or GridTagAcid1)) = 0) then exit; // allowed: water, acid1
2532 //2: if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2) = 0) then exit; // allowed: water, acid1, acid2
2533 end;
2534 // collision?
2535 //if not g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height) then exit;
2536 // yeah
2537 texid := pan.GetTextureID();
2538 // water? water has the highest priority, so stop right here
2539 if ((pan.tag and GridTagWater) <> 0) then begin cctype := 0; result := true; exit; end;
2540 // acid2?
2541 if ((pan.tag and GridTagAcid2) <> 0) then cctype := 2;
2542 // acid1?
2543 if ((pan.tag and GridTagAcid1) <> 0) then cctype := 1;
2544 end;
2546 function g_Map_CollideLiquid_Texture(X, Y: Integer; Width, Height: Word): DWORD;
2547 var
2548 cctype: Integer = 3; // priority: 0: water was hit, 1: acid1 was hit, 2: acid2 was hit; 3: nothing was hit
2549 mwit: PPanel;
2550 it: TPanelGrid.Iter;
2551 begin
2552 if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('liquids');
2553 if gdbg_map_use_accel_coldet then
2554 begin
2555 result := LongWord(TEXTURE_NONE);
2556 it := mapGrid.forEachInAABB(X, Y, Width, Height, (GridTagWater or GridTagAcid1 or GridTagAcid2));
2557 for mwit in it do if (liquidChecker(mwit^, result, cctype)) then break;
2558 it.release();
2559 end
2560 else
2561 begin
2562 result := g_Map_CollideLiquid_TextureOld(X, Y, Width, Height);
2563 end;
2564 if (profMapCollision <> nil) then profMapCollision.sectionEnd();
2565 end;
2568 procedure g_Map_EnableWall_XXX (ID: DWORD); begin if (ID < Length(gWalls)) then g_Map_EnableWallGUID(gWalls[ID].guid); end;
2569 procedure g_Map_DisableWall_XXX (ID: DWORD); begin if (ID < Length(gWalls)) then g_Map_DisableWallGUID(gWalls[ID].guid); end;
2570 procedure g_Map_SetLift_XXX (ID: DWORD; t: Integer); begin if (ID < Length(gLifts)) then g_Map_SetLiftGUID(gLifts[ID].guid, t); end;
2573 procedure g_Map_EnableWallGUID (pguid: Integer);
2574 var
2575 pan: TPanel;
2576 begin
2577 //pan := gWalls[ID];
2578 pan := g_Map_PanelByGUID(pguid);
2579 if (pan = nil) then exit;
2580 if pan.Enabled and mapGrid.proxyEnabled[pan.proxyId] then exit;
2582 pan.Enabled := True;
2583 g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, true);
2585 mapGrid.proxyEnabled[pan.proxyId] := true;
2586 //if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := true
2587 //else pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, GridTagDoor);
2589 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2590 // mark platform as interesting
2591 pan.setDirty();
2593 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
2594 //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);
2595 {$ENDIF}
2596 end;
2599 procedure g_Map_DisableWallGUID (pguid: Integer);
2600 var
2601 pan: TPanel;
2602 begin
2603 //pan := gWalls[ID];
2604 pan := g_Map_PanelByGUID(pguid);
2605 if (pan = nil) then exit;
2606 if (not pan.Enabled) and (not mapGrid.proxyEnabled[pan.proxyId]) then exit;
2608 pan.Enabled := False;
2609 g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, false);
2611 mapGrid.proxyEnabled[pan.proxyId] := false;
2612 //if (pan.proxyId >= 0) then begin mapGrid.removeBody(pan.proxyId); pan.proxyId := -1; end;
2614 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2615 // mark platform as interesting
2616 pan.setDirty();
2618 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
2619 //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);
2620 {$ENDIF}
2621 end;
2624 procedure g_Map_SwitchTextureGUID (pguid: Integer; AnimLoop: Byte = 0);
2625 var
2626 tp: TPanel;
2627 begin
2628 tp := g_Map_PanelByGUID(pguid);
2629 if (tp = nil) then exit;
2630 tp.NextTexture(AnimLoop);
2631 if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelTexture(pguid, AnimLoop);
2632 end;
2635 procedure g_Map_SetLiftGUID (pguid: Integer; t: Integer);
2636 var
2637 pan: TPanel;
2638 begin
2639 //pan := gLifts[ID];
2640 pan := g_Map_PanelByGUID(pguid);
2641 if (pan = nil) then exit;
2642 if not pan.isGLift then exit;
2644 if ({gLifts[ID]}pan.LiftType = t) then exit; //!FIXME!TRIGANY!
2646 with {gLifts[ID]} pan do
2647 begin
2648 LiftType := t;
2650 g_Mark(X, Y, Width, Height, MARK_LIFT, false);
2651 //TODO: make separate lift tags, and change tag here
2653 case LiftType of
2654 LIFTTYPE_UP: g_Mark(X, Y, Width, Height, MARK_LIFTUP);
2655 LIFTTYPE_DOWN: g_Mark(X, Y, Width, Height, MARK_LIFTDOWN);
2656 LIFTTYPE_LEFT: g_Mark(X, Y, Width, Height, MARK_LIFTLEFT);
2657 LIFTTYPE_RIGHT: g_Mark(X, Y, Width, Height, MARK_LIFTRIGHT);
2658 end;
2660 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
2661 // mark platform as interesting
2662 pan.setDirty();
2663 end;
2664 end;
2667 function g_Map_GetPoint (PointType: Byte; var RespawnPoint: TRespawnPoint): Boolean;
2668 var
2669 a: Integer;
2670 PointsArray: Array of TRespawnPoint;
2671 begin
2672 Result := False;
2673 SetLength(PointsArray, 0);
2675 if RespawnPoints = nil then
2676 Exit;
2678 for a := 0 to High(RespawnPoints) do
2679 if RespawnPoints[a].PointType = PointType then
2680 begin
2681 SetLength(PointsArray, Length(PointsArray)+1);
2682 PointsArray[High(PointsArray)] := RespawnPoints[a];
2683 end;
2685 if PointsArray = nil then
2686 Exit;
2688 RespawnPoint := PointsArray[Random(Length(PointsArray))];
2689 Result := True;
2690 end;
2692 function g_Map_GetPointCount(PointType: Byte): Word;
2693 var
2694 a: Integer;
2695 begin
2696 Result := 0;
2698 if RespawnPoints = nil then
2699 Exit;
2701 for a := 0 to High(RespawnPoints) do
2702 if RespawnPoints[a].PointType = PointType then
2703 Result := Result + 1;
2704 end;
2706 function g_Map_GetRandomPointType(): Byte;
2707 begin
2708 if RespawnPoints = nil then
2709 Result := 255
2710 else
2711 Result := RespawnPoints[Random(Length(RespawnPoints))].PointType;
2712 end;
2714 function g_Map_HaveFlagPoints(): Boolean;
2715 begin
2716 Result := (FlagPoints[FLAG_RED] <> nil) and (FlagPoints[FLAG_BLUE] <> nil);
2717 end;
2719 procedure g_Map_ResetFlag(Flag: Byte);
2720 begin
2721 with gFlags[Flag] do
2722 begin
2723 Obj.X := -1000;
2724 Obj.Y := -1000;
2725 Obj.Vel.X := 0;
2726 Obj.Vel.Y := 0;
2727 Direction := TDirection.D_LEFT;
2728 State := FLAG_STATE_NONE;
2729 if FlagPoints[Flag] <> nil then
2730 begin
2731 Obj.X := FlagPoints[Flag]^.X;
2732 Obj.Y := FlagPoints[Flag]^.Y;
2733 Direction := FlagPoints[Flag]^.Direction;
2734 State := FLAG_STATE_NORMAL;
2735 end;
2736 Obj.oldX := Obj.X;
2737 Obj.oldY := Obj.Y;
2738 NeedSend := False; // the event will take care of this
2739 Count := -1;
2740 end;
2741 end;
2743 procedure g_Map_SaveState (st: TStream);
2744 var
2745 str: String;
2747 procedure savePanels ();
2748 var
2749 pan: TPanel;
2750 begin
2751 // Ñîõðàíÿåì ïàíåëè
2752 utils.writeInt(st, LongInt(Length(panByGUID)));
2753 for pan in panByGUID do pan.SaveState(st);
2754 end;
2756 procedure saveFlag (flag: PFlag);
2757 var
2758 b: Byte;
2759 begin
2760 utils.writeSign(st, 'FLAG');
2761 utils.writeInt(st, Byte(0)); // version
2762 // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
2763 utils.writeInt(st, Byte(flag^.RespawnType));
2764 // Ñîñòîÿíèå ôëàãà
2765 utils.writeInt(st, Byte(flag^.State));
2766 // Íàïðàâëåíèå ôëàãà
2767 if flag^.Direction = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT
2768 utils.writeInt(st, Byte(b));
2769 // Îáúåêò ôëàãà
2770 Obj_SaveState(st, @flag^.Obj);
2771 end;
2773 begin
2774 savePanels();
2776 // Ñîõðàíÿåì ìóçûêó
2777 utils.writeSign(st, 'MUSI');
2778 utils.writeInt(st, Byte(0));
2779 // Íàçâàíèå ìóçûêè
2780 assert(gMusic <> nil, 'g_Map_SaveState: gMusic = nil');
2781 if gMusic.NoMusic then str := '' else str := gMusic.Name;
2782 utils.writeStr(st, str);
2783 // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
2784 utils.writeInt(st, LongWord(gMusic.GetPosition()));
2785 // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
2786 utils.writeBool(st, gMusic.SpecPause);
2788 ///// Ñîõðàíÿåì êîëè÷åñòâî ìîíñòðîâ: /////
2789 utils.writeInt(st, LongInt(gTotalMonsters));
2790 ///// /////
2792 //// Ñîõðàíÿåì ôëàãè, åñëè ýòî CTF: /////
2793 if (gGameSettings.GameMode = GM_CTF) then
2794 begin
2795 // Ôëàã Êðàñíîé êîìàíäû
2796 saveFlag(@gFlags[FLAG_RED]);
2797 // Ôëàã Ñèíåé êîìàíäû
2798 saveFlag(@gFlags[FLAG_BLUE]);
2799 end;
2800 ///// /////
2802 ///// Ñîõðàíÿåì êîëè÷åñòâî ïîáåä, åñëè ýòî TDM/CTF: /////
2803 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
2804 begin
2805 // Î÷êè Êðàñíîé êîìàíäû
2806 utils.writeInt(st, SmallInt(gTeamStat[TEAM_RED].Score));
2807 // Î÷êè Ñèíåé êîìàíäû
2808 utils.writeInt(st, SmallInt(gTeamStat[TEAM_BLUE].Score));
2809 end;
2810 ///// /////
2811 end;
2814 procedure g_Map_LoadState (st: TStream);
2815 var
2816 dw: DWORD;
2817 str: String;
2818 boo: Boolean;
2820 procedure loadPanels ();
2821 var
2822 pan: TPanel;
2823 begin
2824 // Çàãðóæàåì ïàíåëè
2825 if (Length(panByGUID) <> utils.readLongInt(st)) then raise XStreamError.Create('invalid number of saved panels');
2826 for pan in panByGUID do
2827 begin
2828 pan.LoadState(st);
2829 if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
2830 end;
2831 end;
2833 procedure loadFlag (flag: PFlag);
2834 var
2835 b: Byte;
2836 begin
2837 // Ñèãíàòóðà ôëàãà
2838 if not utils.checkSign(st, 'FLAG') then raise XStreamError.Create('invalid flag signature');
2839 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid flag version');
2840 // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
2841 flag^.RespawnType := utils.readByte(st);
2842 // Ñîñòîÿíèå ôëàãà
2843 flag^.State := utils.readByte(st);
2844 // Íàïðàâëåíèå ôëàãà
2845 b := utils.readByte(st);
2846 if (b = 1) then flag^.Direction := TDirection.D_LEFT else flag^.Direction := TDirection.D_RIGHT; // b = 2
2847 // Îáúåêò ôëàãà
2848 Obj_LoadState(@flag^.Obj, st);
2849 end;
2851 begin
2852 if (st = nil) then exit;
2854 ///// Çàãðóæàåì ñïèñêè ïàíåëåé: /////
2855 loadPanels();
2856 ///// /////
2858 // Îáíîâëÿåì êàðòó ñòîëêíîâåíèé è ñåòêó
2859 g_GFX_Init();
2860 //mapCreateGrid();
2862 ///// Çàãðóæàåì ìóçûêó: /////
2863 if not utils.checkSign(st, 'MUSI') then raise XStreamError.Create('invalid music signature');
2864 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid music version');
2865 // Íàçâàíèå ìóçûêè
2866 assert(gMusic <> nil, 'g_Map_LoadState: gMusic = nil');
2867 str := utils.readStr(st);
2868 // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
2869 dw := utils.readLongWord(st);
2870 // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
2871 boo := utils.readBool(st);
2872 // Çàïóñêàåì ýòó ìóçûêó
2873 gMusic.SetByName(str);
2874 gMusic.SpecPause := boo;
2875 gMusic.Play();
2876 gMusic.Pause(true);
2877 gMusic.SetPosition(dw);
2878 ///// /////
2880 ///// Çàãðóæàåì êîëè÷åñòâî ìîíñòðîâ: /////
2881 gTotalMonsters := utils.readLongInt(st);
2882 ///// /////
2884 //// Çàãðóæàåì ôëàãè, åñëè ýòî CTF: /////
2885 if (gGameSettings.GameMode = GM_CTF) then
2886 begin
2887 // Ôëàã Êðàñíîé êîìàíäû
2888 loadFlag(@gFlags[FLAG_RED]);
2889 // Ôëàã Ñèíåé êîìàíäû
2890 loadFlag(@gFlags[FLAG_BLUE]);
2891 end;
2892 ///// /////
2894 ///// Çàãðóæàåì êîëè÷åñòâî ïîáåä, åñëè ýòî TDM/CTF: /////
2895 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
2896 begin
2897 // Î÷êè Êðàñíîé êîìàíäû
2898 gTeamStat[TEAM_RED].Score := utils.readSmallInt(st);
2899 // Î÷êè Ñèíåé êîìàíäû
2900 gTeamStat[TEAM_BLUE].Score := utils.readSmallInt(st);
2901 end;
2902 ///// /////
2903 end;
2906 // trace liquid, stepping by `dx` and `dy`
2907 // return last seen liquid coords, and `false` if we're started outside of the liquid
2908 function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
2909 const
2910 MaskLiquid = GridTagWater or GridTagAcid1 or GridTagAcid2;
2911 begin
2912 topx := x;
2913 topy := y;
2914 // started outside of the liquid?
2915 //if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then begin result := false; exit; end;
2916 if (g_Map_PanelAtPoint(x, y, MaskLiquid) = nil) then begin result := false; exit; end;
2917 if (dx = 0) and (dy = 0) then begin result := false; exit; end; // sanity check
2918 result := true;
2919 while true do
2920 begin
2921 Inc(x, dx);
2922 Inc(y, dy);
2923 //if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then exit; // out of the water, just exit
2924 if (g_Map_PanelAtPoint(x, y, MaskLiquid) = nil) then exit; // out of the water, just exit
2925 topx := x;
2926 topy := y;
2927 end;
2928 end;
2931 begin
2932 DynWarningCB := mapWarningCB;
2933 end.