sound: allow to completelly disable sound
[d2df-sdl.git] / src / game / g_map.pas
blob09e4e5c67c8636cc2f9ed17eef2c1382a56af7e4
1 (* Copyright (C) Doom 2D: Forever Developers
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.
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.
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/>.
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 e_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 procedure g_Map_DrawPanels (PanelType: Word; hasAmbient: Boolean; constref ambColor: TDFColor); // unaccelerated
70 procedure g_Map_CollectDrawPanels (x0, y0, wdt, hgt: Integer);
72 procedure g_Map_DrawBack(dx, dy: Integer);
73 function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word;
74 PanelType: Word; b1x3: Boolean=false): Boolean;
75 function g_Map_CollideLiquid_Texture(X, Y: Integer; Width, Height: Word): DWORD;
77 procedure g_Map_EnableWallGUID (pguid: Integer);
78 procedure g_Map_DisableWallGUID (pguid: Integer);
79 procedure g_Map_SetLiftGUID (pguid: Integer; t: Integer);
81 // HACK!!!
82 procedure g_Map_EnableWall_XXX (ID: DWORD);
83 procedure g_Map_DisableWall_XXX (ID: DWORD);
84 procedure g_Map_SetLift_XXX (ID: DWORD; t: Integer);
86 procedure g_Map_SwitchTextureGUID (pguid: Integer; AnimLoop: Byte = 0);
88 procedure g_Map_ReAdd_DieTriggers();
89 function g_Map_IsSpecialTexture(Texture: String): Boolean;
91 function g_Map_GetPoint(PointType: Byte; var RespawnPoint: TRespawnPoint): Boolean;
92 function g_Map_GetPointCount(PointType: Byte): Word;
93 function g_Map_GetRandomPointType(): Byte;
95 function g_Map_HaveFlagPoints(): Boolean;
97 procedure g_Map_ResetFlag(Flag: Byte);
98 procedure g_Map_DrawFlags();
100 procedure g_Map_SaveState (st: TStream);
101 procedure g_Map_LoadState (st: TStream);
103 procedure g_Map_DrawPanelShadowVolumes(lightX: Integer; lightY: Integer; radius: Integer);
105 // returns panel or nil
106 // sets `ex` and `ey` to `x1` and `y1` when no hit was detected
107 function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
109 // returns panel or nil
110 // sets `ex` and `ey` to `x1` and `y1` when no hit was detected
111 function g_Map_traceToNearest (x0, y0, x1, y1: Integer; tag: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
113 type
114 TForEachPanelCB = function (pan: TPanel): Boolean is nested; // return `true` to stop
116 function g_Map_HasAnyPanelAtPoint (x, y: Integer; panelType: Word): Boolean;
117 function g_Map_PanelAtPoint (x, y: Integer; tagmask: Integer=-1): TPanel;
119 // trace liquid, stepping by `dx` and `dy`
120 // return last seen liquid coords, and `false` if we're started outside of the liquid
121 function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
124 // return `true` from `cb` to stop
125 function g_Map_ForEachPanel (cb: TForEachPanelCB): TPanel;
127 procedure g_Map_NetSendInterestingPanels (); // yay!
130 procedure g_Map_ProfilersBegin ();
131 procedure g_Map_ProfilersEnd ();
134 function g_Map_ParseMap (data: Pointer; dataLen: Integer): TDynRecord;
137 function g_Map_MinX (): Integer; inline;
138 function g_Map_MinY (): Integer; inline;
139 function g_Map_MaxX (): Integer; inline;
140 function g_Map_MaxY (): Integer; inline;
142 const
143 NNF_NO_NAME = 0;
144 NNF_NAME_BEFORE = 1;
145 NNF_NAME_EQUALS = 2;
146 NNF_NAME_AFTER = 3;
148 function g_Texture_NumNameFindStart(name: String): Boolean;
149 function g_Texture_NumNameFindNext(var newName: String): Byte;
151 const
152 RESPAWNPOINT_PLAYER1 = 1;
153 RESPAWNPOINT_PLAYER2 = 2;
154 RESPAWNPOINT_DM = 3;
155 RESPAWNPOINT_RED = 4;
156 RESPAWNPOINT_BLUE = 5;
158 FLAG_NONE = 0;
159 FLAG_RED = 1;
160 FLAG_BLUE = 2;
161 FLAG_DOM = 3;
163 FLAG_STATE_NONE = 0;
164 FLAG_STATE_NORMAL = 1;
165 FLAG_STATE_DROPPED = 2;
166 FLAG_STATE_CAPTURED = 3;
167 FLAG_STATE_SCORED = 4; // Äëÿ ýâåíòîâ ÷åðåç ñåòêó.
168 FLAG_STATE_RETURNED = 5; // Äëÿ ýâåíòîâ ÷åðåç ñåòêó.
170 FLAG_TIME = 720; // 20 seconds
172 SKY_STRETCH: Single = 1.5;
174 const
175 GridTagInvalid = 0;
177 (* draw order:
178 PANEL_BACK
179 PANEL_STEP
180 PANEL_WALL
181 PANEL_CLOSEDOOR
182 PANEL_ACID1
183 PANEL_ACID2
184 PANEL_WATER
185 PANEL_FORE
187 // sorted by draw priority
188 GridTagBack = 1 shl 0; // gRenderBackgrounds
189 GridTagStep = 1 shl 1; // gSteps
190 GridTagWall = 1 shl 2; // gWalls
191 GridTagDoor = 1 shl 3; // gWalls
192 GridTagAcid1 = 1 shl 4; // gAcid1
193 GridTagAcid2 = 1 shl 5; // gAcid2
194 GridTagWater = 1 shl 6; // gWater
195 GridTagFore = 1 shl 7; // gRenderForegrounds
196 // the following are invisible
197 GridTagLift = 1 shl 8; // gLifts
198 GridTagBlockMon = 1 shl 9; // gBlockMon
200 GridTagSolid = (GridTagWall or GridTagDoor);
201 GridTagObstacle = (GridTagStep or GridTagWall or GridTagDoor);
202 GridTagLiquid = (GridTagAcid1 or GridTagAcid2 or GridTagWater);
204 GridDrawableMask = (GridTagBack or GridTagStep or GridTagWall or GridTagDoor or GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagFore);
207 type
208 TBinHeapPanelDrawCmp = class
209 public
210 class function less (const a, b: TPanel): Boolean; inline;
211 end;
213 TBinHeapPanelDraw = specialize TBinaryHeapBase<TPanel, TBinHeapPanelDrawCmp>;
216 gWalls: TPanelArray;
217 gRenderBackgrounds: TPanelArray;
218 gRenderForegrounds: TPanelArray;
219 gWater, gAcid1, gAcid2: TPanelArray;
220 gSteps: TPanelArray;
221 gLifts: TPanelArray;
222 gBlockMon: TPanelArray;
223 gFlags: array [FLAG_RED..FLAG_BLUE] of TFlag;
224 //gDOMFlags: array of TFlag;
225 gMapInfo: TMapInfo;
226 gBackSize: TDFPoint;
227 gDoorMap: array of array of DWORD;
228 gLiftMap: array of array of DWORD;
229 gWADHash: TMD5Digest;
230 BackID: DWORD = DWORD(-1);
231 gExternalResources: array of TDiskFileInfo;
232 gMovingWallIds: array of Integer;
234 gdbg_map_use_accel_render: Boolean = True;
235 gdbg_map_use_accel_coldet: Boolean = True;
236 profMapCollision: TProfiler; //WARNING: FOR DEBUGGING ONLY!
237 gDrawPanelList: TBinHeapPanelDraw; // binary heap of all walls we have to render, populated by `g_Map_CollectDrawPanels()`
239 gCurrentMap: TDynRecord;
240 gCurrentMapFileName: AnsiString; // so we can skip texture reloading
241 gTestMap: String;
244 function panelTypeToTag (panelType: Word): Integer; // returns GridTagXXX
247 type
248 TPanelGrid = specialize TBodyGridBase<TPanel>;
251 mapGrid: TPanelGrid; // DO NOT USE! public for debugging only!
254 implementation
256 uses
257 {$INCLUDE ../nogl/noGLuses.inc}
258 {$IFDEF ENABLE_SOUND}
259 g_sound, e_sound,
260 {$ENDIF}
261 e_input, g_main, e_log, e_texture, e_res, g_items, g_gfx, g_console,
262 g_weapons, g_game, CONFIG,
263 g_options, g_triggers, g_player,
264 Math, g_monsters, g_saveload, g_language, g_netmsg,
265 sfs, xstreams, hashtable, wadreader,
266 ImagingTypes, Imaging, ImagingUtility,
267 ImagingGif, ImagingNetworkGraphics,
268 g_res_downloader;
270 const
271 FLAGRECT: TRectWH = (X:15; Y:12; Width:33; Height:52);
272 MUSIC_SIGNATURE = $4953554D; // 'MUSI'
273 FLAG_SIGNATURE = $47414C46; // 'FLAG'
276 // ////////////////////////////////////////////////////////////////////////// //
277 procedure mapWarningCB (const msg: AnsiString; line, col: Integer);
278 begin
279 if (line > 0) then
280 begin
281 e_LogWritefln('parse error at (%s,%s): %s', [line, col, msg], TMsgType.Warning);
283 else
284 begin
285 e_LogWritefln('parse error: %s', [msg], TMsgType.Warning);
286 end;
287 end;
290 // ////////////////////////////////////////////////////////////////////////// //
292 panByGUID: array of TPanel;
295 // ////////////////////////////////////////////////////////////////////////// //
296 function g_Map_PanelByGUID (aguid: Integer): TPanel; inline;
297 begin
298 //if (panByGUID = nil) or (not panByGUID.get(aguid, result)) then result := nil;
299 if (aguid >= 0) and (aguid < Length(panByGUID)) then result := panByGUID[aguid] else result := nil;
300 end;
303 // return `true` from `cb` to stop
304 function g_Map_ForEachPanel (cb: TForEachPanelCB): TPanel;
306 pan: TPanel;
307 begin
308 result := nil;
309 if not assigned(cb) then exit;
310 for pan in panByGUID do
311 begin
312 if cb(pan) then begin result := pan; exit; end;
313 end;
314 end;
317 procedure g_Map_NetSendInterestingPanels ();
319 pan: TPanel;
320 begin
321 if g_Game_IsServer and g_Game_IsNet then
322 begin
323 for pan in panByGUID do
324 begin
325 if pan.gncNeedSend then MH_SEND_PanelState(pan.guid);
326 end;
327 end;
328 end;
331 // ////////////////////////////////////////////////////////////////////////// //
332 function g_Map_MinX (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridX0 else result := 0; end;
333 function g_Map_MinY (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridY0 else result := 0; end;
334 function g_Map_MaxX (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridX0+mapGrid.gridWidth-1 else result := 0; end;
335 function g_Map_MaxY (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridY0+mapGrid.gridHeight-1 else result := 0; end;
338 // ////////////////////////////////////////////////////////////////////////// //
340 dfmapdef: TDynMapDef;
343 procedure loadMapDefinition ();
345 WAD: TWADFile = nil;
346 st: TStream = nil;
347 pr: TTextParser = nil;
348 begin
349 if (dfmapdef <> nil) then exit;
352 e_LogWritefln('parsing "mapdef.txt"...', []);
353 st := e_OpenResourceRO(DataDirs, 'mapdef.txt');
354 e_LogWritefln('found local "mapdef.txt"', []);
355 except
356 st := nil;
357 end;
359 if st = nil then
360 begin
361 WAD := TWADFile.Create();
362 if WAD.ReadFile(GameWAD)
363 then st := WAD.openFileStream('mapdef.txt')
364 else st := nil;
365 end;
369 if st <> nil then
370 pr := TFileTextParser.Create(st)
371 else
372 begin
373 //raise Exception.Create('cannot open "mapdef.txt"');
374 e_LogWriteln('using default "mapdef.txt"...');
375 pr := TStrTextParser.Create(defaultMapDef);
376 end;
377 except
378 on E: Exception do
379 begin
380 e_LogWritefln('something is VERY wrong here! -- ', [E.message]);
381 Raise;
382 end;
383 end;
386 dfmapdef := TDynMapDef.Create(pr);
387 except
388 on e: TDynParseException do
389 Raise Exception.CreateFmt('ERROR in "mapdef.txt" at (%s,%s): %s', [e.tokLine, e.tokCol, e.message]);
390 on e: Exception do
391 Raise Exception.CreateFmt('ERROR in "mapdef.txt" at (%s,%s): %s', [pr.tokLine, pr.tokCol, e.message]);
392 end;
393 finally
394 pr.Free();
395 st.Free();
396 WAD.Free();
397 end;
398 end;
401 // ////////////////////////////////////////////////////////////////////////// //
402 function g_Map_ParseMap (data: Pointer; dataLen: Integer): TDynRecord;
404 wst: TSFSMemoryChunkStream;
405 begin
406 Result := nil;
407 if (dataLen < 4) then exit;
409 if (dfmapdef = nil) then WriteLn('need to load mapdef');
410 loadMapDefinition();
411 if (dfmapdef = nil) then raise Exception.Create('internal map loader error');
413 wst := TSFSMemoryChunkStream.Create(data, dataLen);
415 Result := dfmapdef.parseMap(wst);
416 except
417 on e: TDynParseException do
418 e_LogWritefln('ERROR at (%s,%s): %s', [e.tokLine, e.tokCol, e.message]);
419 on e: Exception do
420 e_LogWritefln('ERROR: %s', [e.message]);
421 end;
423 wst.Destroy();
424 end;
427 // ////////////////////////////////////////////////////////////////////////// //
429 NNF_PureName: String; // Èìÿ òåêñòóðû áåç öèôð â êîíöå
430 NNF_PureExt: String; // extension postfix
431 NNF_FirstNum: Integer; // ×èñëî ó íà÷àëüíîé òåêñòóðû
432 NNF_CurrentNum: Integer; // Ñëåäóþùåå ÷èñëî ó òåêñòóðû
435 function g_Texture_NumNameFindStart(name: String): Boolean;
437 i, j: Integer;
439 begin
440 Result := False;
441 NNF_PureName := '';
442 NNF_PureExt := '';
443 NNF_FirstNum := -1;
444 NNF_CurrentNum := -1;
446 for i := Length(name) downto 1 do
447 if (name[i] = '_') then // "_" - ñèìâîë íà÷àëà íîìåðíîãî ïîñòôèêñà
448 begin
449 if i = Length(name) then
450 begin // Íåò öèôð â êîíöå ñòðîêè
451 Exit;
453 else
454 begin
455 j := i + 1;
456 while (j <= Length(name)) and (name[j] <> '.') do inc(j);
457 NNF_PureName := Copy(name, 1, i);
458 NNF_PureExt := Copy(name, j);
459 name := Copy(name, i + 1, j - i - 1);
460 Break;
461 end;
462 end;
464 // Íå ïåðåâåñòè â ÷èñëî:
465 if not TryStrToInt(name, NNF_FirstNum) then
466 Exit;
468 NNF_CurrentNum := 0;
470 Result := True;
471 end;
474 function g_Texture_NumNameFindNext(var newName: String): Byte;
475 begin
476 if (NNF_PureName = '') or (NNF_CurrentNum < 0) then
477 begin
478 newName := '';
479 Result := NNF_NO_NAME;
480 Exit;
481 end;
483 newName := NNF_PureName + IntToStr(NNF_CurrentNum) + NNF_PureExt;
485 if NNF_CurrentNum < NNF_FirstNum then
486 Result := NNF_NAME_BEFORE
487 else
488 if NNF_CurrentNum > NNF_FirstNum then
489 Result := NNF_NAME_AFTER
490 else
491 Result := NNF_NAME_EQUALS;
493 Inc(NNF_CurrentNum);
494 end;
497 // ////////////////////////////////////////////////////////////////////////// //
498 function panelTypeToTag (panelType: Word): Integer;
499 begin
500 case panelType of
501 PANEL_WALL: result := GridTagWall; // gWalls
502 PANEL_OPENDOOR, PANEL_CLOSEDOOR: result := GridTagDoor; // gWalls
503 PANEL_BACK: result := GridTagBack; // gRenderBackgrounds
504 PANEL_FORE: result := GridTagFore; // gRenderForegrounds
505 PANEL_WATER: result := GridTagWater; // gWater
506 PANEL_ACID1: result := GridTagAcid1; // gAcid1
507 PANEL_ACID2: result := GridTagAcid2; // gAcid2
508 PANEL_STEP: result := GridTagStep; // gSteps
509 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: result := GridTagLift; // gLifts -- this is for all lifts
510 PANEL_BLOCKMON: result := GridTagBlockMon; // gBlockMon -- this is for all blockmons
511 else result := GridTagInvalid;
512 end;
513 end;
516 class function TBinHeapPanelDrawCmp.less (const a, b: TPanel): Boolean; inline;
517 begin
518 if (a.tag < b.tag) then begin result := true; exit; end;
519 if (a.tag > b.tag) then begin result := false; exit; end;
520 result := (a.arrIdx < b.arrIdx);
521 end;
523 procedure dplClear ();
524 begin
525 if (gDrawPanelList = nil) then gDrawPanelList := TBinHeapPanelDraw.Create() else gDrawPanelList.clear();
526 end;
530 Textures: TLevelTextureArray;
531 TextNameHash: THashStrInt; // key: texture name; value: index in `Textures`
532 BadTextNameHash: THashStrInt; // set; so we won't spam with non-existing texture messages
533 RespawnPoints: array of TRespawnPoint;
534 FlagPoints: array[FLAG_RED..FLAG_BLUE] of PFlagPoint;
535 //DOMFlagPoints: Array of TFlagPoint;
538 procedure g_Map_ProfilersBegin ();
539 begin
540 if (profMapCollision = nil) then profMapCollision := TProfiler.Create('COLSOLID', g_profile_history_size);
541 if (profMapCollision <> nil) then profMapCollision.mainBegin(g_profile_collision);
542 // create sections
543 if g_profile_collision and (profMapCollision <> nil) then
544 begin
545 profMapCollision.sectionBegin('*solids');
546 profMapCollision.sectionEnd();
547 profMapCollision.sectionBegin('liquids');
548 profMapCollision.sectionEnd();
549 end;
550 end;
552 procedure g_Map_ProfilersEnd ();
553 begin
554 if (profMapCollision <> nil) then profMapCollision.mainEnd();
555 end;
558 // wall index in `gWalls` or -1
559 function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
561 ex, ey: Integer;
562 begin
563 result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, (GridTagWall or GridTagDoor));
564 if (result <> nil) then
565 begin
566 if (hitx <> nil) then hitx^ := ex;
567 if (hity <> nil) then hity^ := ey;
569 else
570 begin
571 if (hitx <> nil) then hitx^ := x1;
572 if (hity <> nil) then hity^ := y1;
573 end;
574 end;
576 // returns panel or nil
577 function g_Map_traceToNearest (x0, y0, x1, y1: Integer; tag: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
579 ex, ey: Integer;
580 begin
581 result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, tag);
582 if (result <> nil) then
583 begin
584 if (hitx <> nil) then hitx^ := ex;
585 if (hity <> nil) then hity^ := ey;
587 else
588 begin
589 if (hitx <> nil) then hitx^ := x1;
590 if (hity <> nil) then hity^ := y1;
591 end;
592 end;
595 function xxPanAtPointChecker (pan: TPanel; panelType: Word): Boolean; inline;
596 begin
597 if ((pan.tag and GridTagLift) <> 0) then
598 begin
599 // stop if the lift of the right type
600 result :=
601 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
602 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
603 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
604 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT)));
605 exit;
606 end;
607 result := true; // otherwise, stop anyway, 'cause `forEachAtPoint()` is guaranteed to call this only for correct panels
608 end;
610 function g_Map_HasAnyPanelAtPoint (x, y: Integer; panelType: Word): Boolean;
612 tagmask: Integer = 0;
613 mwit: PPanel;
614 it: TPanelGrid.Iter;
615 begin
616 result := false;
618 if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor);
619 if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater;
620 if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1;
621 if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2;
622 if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep;
623 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift;
624 if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon;
626 if (tagmask = 0) then exit;// just in case
628 if ((tagmask and GridTagLift) <> 0) then
629 begin
630 // slow
631 it := mapGrid.forEachAtPoint(x, y, tagmask);
632 for mwit in it do if (xxPanAtPointChecker(mwit^, PanelType)) then begin result := true; break; end;
634 else
635 begin
636 // fast
637 it := mapGrid.forEachAtPoint(x, y, tagmask, false, true);
638 result := (it.length <> 0); // firsthit
639 end;
640 it.release();
641 end;
644 function g_Map_PanelAtPoint (x, y: Integer; tagmask: Integer=-1): TPanel;
646 it: TPanelGrid.Iter;
647 begin
648 result := nil;
649 if (tagmask = 0) then exit;
650 it := mapGrid.forEachAtPoint(x, y, tagmask, false, true); // firsthit
651 if (it.length <> 0) then result := it.first^;
652 it.release();
653 end;
656 function g_Map_IsSpecialTexture(Texture: String): Boolean;
657 begin
658 Result := (Texture = TEXTURE_NAME_WATER) or
659 (Texture = TEXTURE_NAME_ACID1) or
660 (Texture = TEXTURE_NAME_ACID2);
661 end;
663 procedure CreateDoorMap();
665 PanelArray: Array of record
666 X, Y: Integer;
667 Width, Height: Word;
668 Active: Boolean;
669 PanelID: DWORD;
670 end;
671 a, b, c, m, i, len: Integer;
672 ok: Boolean;
673 begin
674 if gWalls = nil then
675 Exit;
677 i := 0;
678 len := 128;
679 SetLength(PanelArray, len);
681 for a := 0 to High(gWalls) do
682 if gWalls[a].Door then
683 begin
684 PanelArray[i].X := gWalls[a].X;
685 PanelArray[i].Y := gWalls[a].Y;
686 PanelArray[i].Width := gWalls[a].Width;
687 PanelArray[i].Height := gWalls[a].Height;
688 PanelArray[i].Active := True;
689 PanelArray[i].PanelID := a;
691 i := i + 1;
692 if i = len then
693 begin
694 len := len + 128;
695 SetLength(PanelArray, len);
696 end;
697 end;
699 // Íåò äâåðåé:
700 if i = 0 then
701 begin
702 PanelArray := nil;
703 Exit;
704 end;
706 SetLength(gDoorMap, 0);
708 g_Game_SetLoadingText(_lc[I_LOAD_DOOR_MAP], i-1, False);
710 for a := 0 to i-1 do
711 if PanelArray[a].Active then
712 begin
713 PanelArray[a].Active := False;
714 m := Length(gDoorMap);
715 SetLength(gDoorMap, m+1);
716 SetLength(gDoorMap[m], 1);
717 gDoorMap[m, 0] := PanelArray[a].PanelID;
718 ok := True;
720 while ok do
721 begin
722 ok := False;
724 for b := 0 to i-1 do
725 if PanelArray[b].Active then
726 for c := 0 to High(gDoorMap[m]) do
727 if {((gRenderWalls[PanelArray[b].RenderPanelID].TextureID = gRenderWalls[gDoorMap[m, c]].TextureID) or
728 gRenderWalls[PanelArray[b].RenderPanelID].Hide or gRenderWalls[gDoorMap[m, c]].Hide) and}
729 g_CollideAround(PanelArray[b].X, PanelArray[b].Y,
730 PanelArray[b].Width, PanelArray[b].Height,
731 gWalls[gDoorMap[m, c]].X,
732 gWalls[gDoorMap[m, c]].Y,
733 gWalls[gDoorMap[m, c]].Width,
734 gWalls[gDoorMap[m, c]].Height) then
735 begin
736 PanelArray[b].Active := False;
737 SetLength(gDoorMap[m],
738 Length(gDoorMap[m])+1);
739 gDoorMap[m, High(gDoorMap[m])] := PanelArray[b].PanelID;
740 ok := True;
741 Break;
742 end;
743 end;
745 g_Game_StepLoading();
746 end;
748 PanelArray := nil;
749 end;
751 procedure CreateLiftMap();
753 PanelArray: Array of record
754 X, Y: Integer;
755 Width, Height: Word;
756 Active: Boolean;
757 end;
758 a, b, c, len, i, j: Integer;
759 ok: Boolean;
760 begin
761 if gLifts = nil then
762 Exit;
764 len := Length(gLifts);
765 SetLength(PanelArray, len);
767 for a := 0 to len-1 do
768 begin
769 PanelArray[a].X := gLifts[a].X;
770 PanelArray[a].Y := gLifts[a].Y;
771 PanelArray[a].Width := gLifts[a].Width;
772 PanelArray[a].Height := gLifts[a].Height;
773 PanelArray[a].Active := True;
774 end;
776 SetLength(gLiftMap, len);
777 i := 0;
779 g_Game_SetLoadingText(_lc[I_LOAD_LIFT_MAP], len-1, False);
781 for a := 0 to len-1 do
782 if PanelArray[a].Active then
783 begin
784 PanelArray[a].Active := False;
785 SetLength(gLiftMap[i], 32);
786 j := 0;
787 gLiftMap[i, j] := a;
788 ok := True;
790 while ok do
791 begin
792 ok := False;
793 for b := 0 to len-1 do
794 if PanelArray[b].Active then
795 for c := 0 to j do
796 if g_CollideAround(PanelArray[b].X,
797 PanelArray[b].Y,
798 PanelArray[b].Width,
799 PanelArray[b].Height,
800 PanelArray[gLiftMap[i, c]].X,
801 PanelArray[gLiftMap[i, c]].Y,
802 PanelArray[gLiftMap[i, c]].Width,
803 PanelArray[gLiftMap[i, c]].Height) then
804 begin
805 PanelArray[b].Active := False;
806 j := j+1;
807 if j > High(gLiftMap[i]) then
808 SetLength(gLiftMap[i],
809 Length(gLiftMap[i])+32);
811 gLiftMap[i, j] := b;
812 ok := True;
814 Break;
815 end;
816 end;
818 SetLength(gLiftMap[i], j+1);
819 i := i+1;
821 g_Game_StepLoading();
822 end;
824 SetLength(gLiftMap, i);
826 PanelArray := nil;
827 end;
829 function CreatePanel (PanelRec: TDynRecord; AddTextures: TAddTextureArray; CurTex: Integer): Integer;
831 len: Integer;
832 panels: ^TPanelArray;
833 pan: TPanel;
834 pguid: Integer;
835 begin
836 Result := -1;
838 case PanelRec.PanelType of
839 PANEL_WALL, PANEL_OPENDOOR, PANEL_CLOSEDOOR: panels := @gWalls;
840 PANEL_BACK: panels := @gRenderBackgrounds;
841 PANEL_FORE: panels := @gRenderForegrounds;
842 PANEL_WATER: panels := @gWater;
843 PANEL_ACID1: panels := @gAcid1;
844 PANEL_ACID2: panels := @gAcid2;
845 PANEL_STEP: panels := @gSteps;
846 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: panels := @gLifts;
847 PANEL_BLOCKMON: panels := @gBlockMon;
848 else exit;
849 end;
851 len := Length(panels^);
852 SetLength(panels^, len+1);
854 pguid := Length(panByGUID);
855 SetLength(panByGUID, pguid+1); //FIXME!
856 pan := TPanel.Create(PanelRec, AddTextures, CurTex, Textures, pguid);
857 assert(pguid >= 0);
858 assert(pguid < Length(panByGUID));
859 panByGUID[pguid] := pan;
860 panels^[len] := pan;
861 pan.arrIdx := len;
862 pan.proxyId := -1;
863 pan.tag := panelTypeToTag(PanelRec.PanelType);
865 PanelRec.user['panel_guid'] := pguid;
867 //result := len;
868 result := pguid;
869 end;
872 function CreateNullTexture(RecName: String): Integer;
873 begin
874 RecName := toLowerCase1251(RecName);
875 if (TextNameHash = nil) then TextNameHash := THashStrInt.Create();
876 if TextNameHash.get(RecName, result) then exit; // i found her!
878 SetLength(Textures, Length(Textures)+1);
879 result := High(Textures);
881 with Textures[High(Textures)] do
882 begin
883 TextureName := RecName;
884 Width := 1;
885 Height := 1;
886 Anim := False;
887 TextureID := LongWord(TEXTURE_NONE);
888 end;
890 TextNameHash.put(RecName, result);
891 end;
894 function extractWadName (resourceName: string): string;
896 posN: Integer;
897 begin
898 posN := Pos(':', resourceName);
899 if posN > 0 then
900 Result:= Copy(resourceName, 0, posN-1)
901 else
902 Result := '';
903 end;
906 procedure addResToExternalResList (res: AnsiString);
908 uname: AnsiString;
909 f: Integer;
910 fi: TDiskFileInfo;
911 begin
912 if g_Game_IsClient or not g_Game_IsNet then exit;
913 if (length(res) = 0) then exit; // map wad
914 //res := extractWadName(res);
915 //if (length(res) = 0) then exit; // map wad
916 uname := toLowerCase1251(res);
917 // do not add duplicates
918 for f := 0 to High(gExternalResources) do
919 begin
920 if (gExternalResources[f].userName = uname) then exit;
921 end;
922 //writeln('***(000) addResToExternalResList: res=[', res, ']');
923 // add new resource
924 fi.userName := uname;
925 if not findFileCI(res) then exit;
926 //writeln('***(001) addResToExternalResList: res=[', res, ']');
927 fi.diskName := res;
928 if (not GetDiskFileInfo(res, fi)) then
929 begin
930 fi.tag := -1;
932 else
933 begin
934 //writeln('***(002) addResToExternalResList: res=[', res, ']');
935 fi.tag := 0; // non-zero means "cannot caclucate hash"
937 fi.hash := MD5File(fi.diskName);
938 except
939 fi.tag := -1;
940 end;
941 end;
942 //e_LogWritefln('addext: res=[%s]; uname=[%s]; diskName=[%s]', [res, fi.userName, fi.diskName]);
943 SetLength(gExternalResources, length(gExternalResources)+1);
944 gExternalResources[High(gExternalResources)] := fi;
945 end;
948 procedure compactExtResList ();
950 src, dest: Integer;
951 begin
952 src := 0;
953 dest := 0;
954 for src := 0 to High(gExternalResources) do
955 begin
956 if (gExternalResources[src].tag = 0) then
957 begin
958 // copy it
959 if (dest <> src) then gExternalResources[dest] := gExternalResources[src];
960 Inc(dest);
961 end;
962 end;
963 if (dest <> length(gExternalResources)) then SetLength(gExternalResources, dest);
964 end;
967 function GetReplacementWad (WadName: AnsiString): AnsiString;
968 begin
969 result := '';
970 if WadName <> '' then
971 begin
972 result := WadName;
973 if g_Game_IsClient then result := g_Res_FindReplacementWad(WadName);
974 if (result = WadName) then result := e_FindWad(WadDirs, result)
975 end;
976 end;
979 procedure generateExternalResourcesList (map: TDynRecord);
980 begin
981 SetLength(gExternalResources, 0);
982 addResToExternalResList(GetReplacementWad(g_ExtractWadName(map.MusicName)));
983 addResToExternalResList(GetReplacementWad(g_ExtractWadName(map.SkyName)));
984 end;
987 function CreateTexture (RecName: AnsiString; Map: string; log: Boolean): Integer;
989 WAD: TWADFile;
990 TextureData: Pointer;
991 WADName: String;
992 a, ResLength: Integer;
993 begin
994 RecName := toLowerCase1251(RecName);
995 if (TextNameHash = nil) then TextNameHash := THashStrInt.Create();
996 if TextNameHash.get(RecName, result) then
997 begin
998 // i found her!
999 //e_LogWritefln('texture ''%s'' already loaded (%s)', [RecName, result]);
1000 exit;
1001 end;
1003 Result := -1;
1005 if (BadTextNameHash <> nil) and BadTextNameHash.has(RecName) then exit; // don't do it again and again
1008 if Textures <> nil then
1009 begin
1010 for a := 0 to High(Textures) do
1011 begin
1012 if (Textures[a].TextureName = RecName) then
1013 begin // Òåêñòóðà ñ òàêèì èìåíåì óæå åñòü
1014 e_LogWritefln('texture ''%s'' already loaded', [RecName]);
1015 Result := a;
1016 Exit;
1017 end;
1018 end;
1019 end;
1022 // Òåêñòóðû ñî ñïåöèàëüíûìè èìåíàìè (âîäà, ëàâà, êèñëîòà):
1023 if (RecName = TEXTURE_NAME_WATER) or
1024 (RecName = TEXTURE_NAME_ACID1) or
1025 (RecName = TEXTURE_NAME_ACID2) then
1026 begin
1027 SetLength(Textures, Length(Textures)+1);
1029 with Textures[High(Textures)] do
1030 begin
1031 TextureName := RecName;
1032 if (TextureName = TEXTURE_NAME_WATER) then TextureID := LongWord(TEXTURE_SPECIAL_WATER)
1033 else if (TextureName = TEXTURE_NAME_ACID1) then TextureID := LongWord(TEXTURE_SPECIAL_ACID1)
1034 else if (TextureName = TEXTURE_NAME_ACID2) then TextureID := LongWord(TEXTURE_SPECIAL_ACID2);
1036 Anim := False;
1037 end;
1039 result := High(Textures);
1040 TextNameHash.put(RecName, result);
1041 Exit;
1042 end;
1044 // Çàãðóæàåì ðåñóðñ òåêñòóðû â ïàìÿòü èç WAD'à:
1045 WADName := GetReplacementWad(g_ExtractWadName(RecName));
1046 if (WADName <> '') then addResToExternalResList(WADName);
1047 if WADName = '' then WADName := Map; //WADName := GameDir+'/wads/'+WADName else
1049 WAD := TWADFile.Create();
1050 WAD.ReadFile(WADName);
1052 //txname := RecName;
1054 if (WADName = Map) and WAD.GetResource(g_ExtractFilePathName(RecName), TextureData, ResLength) then
1055 begin
1056 FreeMem(TextureData);
1057 RecName := 'COMMON\ALIEN';
1058 end;
1061 if WAD.GetResource(g_ExtractFilePathName(RecName), TextureData, ResLength, log) then
1062 begin
1063 SetLength(Textures, Length(Textures)+1);
1064 if not e_CreateTextureMem(TextureData, ResLength, Textures[High(Textures)].TextureID) then
1065 begin
1066 e_WriteLog(Format('Error loading texture %s', [RecName]), TMsgType.Warning);
1067 SetLength(Textures, Length(Textures)-1);
1068 result := -1;
1069 Exit;
1070 end;
1071 e_GetTextureSize(Textures[High(Textures)].TextureID, @Textures[High(Textures)].Width, @Textures[High(Textures)].Height);
1072 FreeMem(TextureData);
1073 Textures[High(Textures)].TextureName := RecName;
1074 Textures[High(Textures)].Anim := False;
1076 result := High(Textures);
1077 TextNameHash.put(RecName, result);
1079 else // Íåò òàêîãî ðåóñðñà â WAD'å
1080 begin
1081 //e_WriteLog(Format('SHIT! Error loading texture %s : %s', [RecName, g_ExtractFilePathName(RecName)]), MSG_WARNING);
1082 if (BadTextNameHash = nil) then BadTextNameHash := THashStrInt.Create();
1083 if log and (not BadTextNameHash.get(RecName, a)) then
1084 begin
1085 e_WriteLog(Format('Error loading texture %s', [RecName]), TMsgType.Warning);
1086 //e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
1087 end;
1088 BadTextNameHash.put(RecName, -1);
1089 end;
1091 WAD.Free();
1092 end;
1095 function CreateAnimTexture(RecName: String; Map: string; log: Boolean): Integer;
1097 WAD: TWADFile;
1098 TextureWAD: PChar = nil;
1099 TextData: Pointer = nil;
1100 TextureData: Pointer = nil;
1101 cfg: TConfig = nil;
1102 WADName: String;
1103 ResLength: Integer;
1104 TextureResource: String;
1105 _width, _height, _framecount, _speed: Integer;
1106 _backanimation: Boolean;
1107 //imgfmt: string;
1108 ia: TDynImageDataArray = nil;
1109 f, c, frdelay, frloop: Integer;
1110 begin
1111 RecName := toLowerCase1251(RecName);
1112 if (TextNameHash = nil) then TextNameHash := THashStrInt.Create();
1113 if TextNameHash.get(RecName, result) then
1114 begin
1115 // i found her!
1116 //e_LogWritefln('animated texture ''%s'' already loaded (%s)', [RecName, result]);
1117 exit;
1118 end;
1120 result := -1;
1122 //e_LogWritefln('*** Loading animated texture "%s"', [RecName]);
1124 if (BadTextNameHash = nil) then BadTextNameHash := THashStrInt.Create();
1125 if BadTextNameHash.get(RecName, f) then
1126 begin
1127 //e_WriteLog(Format('no animation texture %s (don''t worry)', [RecName]), MSG_NOTIFY);
1128 exit;
1129 end;
1131 // ×èòàåì WAD-ðåñóðñ àíèì.òåêñòóðû èç WAD'à â ïàìÿòü:
1132 WADName := GetReplacementWad(g_ExtractWadName(RecName));
1133 if (WADName <> '') then addResToExternalResList(WADName);
1134 if WADName = '' then WADName := Map; //WADName := GameDir+'/wads/'+WADName else
1136 WAD := TWADFile.Create();
1138 //if WADName <> '' then WADName := GameDir+'/wads/'+WADName else WADName := Map;
1140 WAD.ReadFile(WADName);
1142 if not WAD.GetResource(g_ExtractFilePathName(RecName), TextureWAD, ResLength, log) then
1143 begin
1144 if (BadTextNameHash = nil) then BadTextNameHash := THashStrInt.Create();
1145 if log and (not BadTextNameHash.get(RecName, f)) then
1146 begin
1147 e_WriteLog(Format('Error loading animation texture %s', [RecName]), TMsgType.Warning);
1148 //e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
1149 end;
1150 BadTextNameHash.put(RecName, -1);
1151 exit;
1152 end;
1154 {TEST
1155 if WADName = Map then
1156 begin
1157 //FreeMem(TextureWAD);
1158 if not WAD.GetResource('COMMON/animation', TextureWAD, ResLength) then Halt(1);
1159 end;
1162 WAD.FreeWAD();
1164 if ResLength < 6 then
1165 begin
1166 e_WriteLog(Format('Animated texture file "%s" too short', [RecName]), TMsgType.Warning);
1167 BadTextNameHash.put(RecName, -1);
1168 exit;
1169 end;
1171 // ýòî ïòèöà? ýòî ñàìîë¸ò?
1172 if isWadData(TextureWAD, ResLength) then
1173 begin
1174 // íåò, ýòî ñóïåðìåí!
1175 if not WAD.ReadMemory(TextureWAD, ResLength) then
1176 begin
1177 e_WriteLog(Format('Animated texture WAD file "%s" is invalid', [RecName]), TMsgType.Warning);
1178 BadTextNameHash.put(RecName, -1);
1179 exit;
1180 end;
1182 // ×èòàåì INI-ðåñóðñ àíèì. òåêñòóðû è çàïîìèíàåì åãî óñòàíîâêè:
1183 if not WAD.GetResource('TEXT/ANIM', TextData, ResLength) then
1184 begin
1185 e_WriteLog(Format('Animated texture file "%s" has invalid INI', [RecName]), TMsgType.Warning);
1186 BadTextNameHash.put(RecName, -1);
1187 exit;
1188 end;
1190 cfg := TConfig.CreateMem(TextData, ResLength);
1192 TextureResource := cfg.ReadStr('', 'resource', '');
1193 if TextureResource = '' then
1194 begin
1195 e_WriteLog(Format('Animated texture WAD file "%s" has no "resource"', [RecName]), TMsgType.Warning);
1196 BadTextNameHash.put(RecName, -1);
1197 exit;
1198 end;
1200 _width := cfg.ReadInt('', 'framewidth', 0);
1201 _height := cfg.ReadInt('', 'frameheight', 0);
1202 _framecount := cfg.ReadInt('', 'framecount', 0);
1203 _speed := cfg.ReadInt('', 'waitcount', 0);
1204 _backanimation := cfg.ReadBool('', 'backanimation', False);
1206 cfg.Free();
1207 cfg := nil;
1209 // ×èòàåì ðåñóðñ òåêñòóð (êàäðîâ) àíèì. òåêñòóðû â ïàìÿòü:
1210 if not WAD.GetResource('TEXTURES/'+TextureResource, TextureData, ResLength) then
1211 begin
1212 e_WriteLog(Format('Animated texture WAD file "%s" has no texture "%s"', [RecName, 'TEXTURES/'+TextureResource]), TMsgType.Warning);
1213 BadTextNameHash.put(RecName, -1);
1214 exit;
1215 end;
1217 WAD.Free();
1218 WAD := nil;
1220 SetLength(Textures, Length(Textures)+1);
1221 with Textures[High(Textures)] do
1222 begin
1223 // Ñîçäàåì êàäðû àíèì. òåêñòóðû èç ïàìÿòè:
1224 if g_Frames_CreateMemory(@FramesID, '', TextureData, ResLength, _width, _height, _framecount, _backanimation) then
1225 begin
1226 TextureName := RecName;
1227 Width := _width;
1228 Height := _height;
1229 Anim := True;
1230 FramesCount := _framecount;
1231 Speed := _speed;
1232 result := High(Textures);
1233 TextNameHash.put(RecName, result);
1235 else
1236 begin
1237 if (BadTextNameHash = nil) then BadTextNameHash := THashStrInt.Create();
1238 if log and (not BadTextNameHash.get(RecName, f)) then
1239 begin
1240 e_WriteLog(Format('Error loading animation texture %s', [RecName]), TMsgType.Warning);
1241 end;
1242 BadTextNameHash.put(RecName, -1);
1243 end;
1244 end;
1246 else
1247 begin
1248 // try animated image
1250 imgfmt := DetermineMemoryFormat(TextureWAD, ResLength);
1251 if length(imgfmt) = 0 then
1252 begin
1253 e_WriteLog(Format('Animated texture file "%s" has unknown format', [RecName]), MSG_WARNING);
1254 exit;
1255 end;
1257 GlobalMetadata.ClearMetaItems();
1258 GlobalMetadata.ClearMetaItemsForSaving();
1259 if not LoadMultiImageFromMemory(TextureWAD, ResLength, ia) then
1260 begin
1261 e_WriteLog(Format('Animated texture file "%s" cannot be loaded', [RecName]), TMsgType.Warning);
1262 BadTextNameHash.put(RecName, -1);
1263 exit;
1264 end;
1265 if length(ia) = 0 then
1266 begin
1267 e_WriteLog(Format('Animated texture file "%s" has no frames', [RecName]), TMsgType.Warning);
1268 BadTextNameHash.put(RecName, -1);
1269 exit;
1270 end;
1272 WAD.Free();
1273 WAD := nil;
1275 _width := ia[0].width;
1276 _height := ia[0].height;
1277 _framecount := length(ia);
1278 _speed := 1;
1279 _backanimation := false;
1280 frdelay := -1;
1281 frloop := -666;
1282 if GlobalMetadata.HasMetaItem(SMetaFrameDelay) then
1283 begin
1284 //writeln(' frame delay: ', GlobalMetadata.MetaItems[SMetaFrameDelay]);
1286 f := GlobalMetadata.MetaItems[SMetaFrameDelay];
1287 frdelay := f;
1288 if f < 0 then f := 0;
1289 // rounding ;-)
1290 c := f mod 28;
1291 if c < 13 then c := 0 else c := 1;
1292 f := (f div 28)+c;
1293 if f < 1 then f := 1 else if f > 255 then f := 255;
1294 _speed := f;
1295 except
1296 end;
1297 end;
1298 if GlobalMetadata.HasMetaItem(SMetaAnimationLoops) then
1299 begin
1300 //writeln(' frame loop : ', GlobalMetadata.MetaItems[SMetaAnimationLoops]);
1302 f := GlobalMetadata.MetaItems[SMetaAnimationLoops];
1303 frloop := f;
1304 if f <> 0 then _backanimation := true; // non-infinite looping == forth-and-back
1305 except
1306 end;
1307 end;
1308 //writeln(' creating animated texture with ', length(ia), ' frames (delay:', _speed, '; backloop:', _backanimation, ') from "', RecName, '"...');
1309 //for f := 0 to high(ia) do writeln(' frame #', f, ': ', ia[f].width, 'x', ia[f].height);
1310 f := ord(_backanimation);
1311 e_WriteLog(Format('Animated texture file "%s": %d frames (delay:%d; back:%d; frdelay:%d; frloop:%d), %dx%d', [RecName, length(ia), _speed, f, frdelay, frloop, _width, _height]), TMsgType.Notify);
1313 SetLength(Textures, Length(Textures)+1);
1314 // cîçäàåì êàäðû àíèì. òåêñòóðû èç êàðòèíîê
1315 if g_CreateFramesImg(ia, @Textures[High(Textures)].FramesID, '', _backanimation) then
1316 begin
1317 Textures[High(Textures)].TextureName := RecName;
1318 Textures[High(Textures)].Width := _width;
1319 Textures[High(Textures)].Height := _height;
1320 Textures[High(Textures)].Anim := True;
1321 Textures[High(Textures)].FramesCount := length(ia);
1322 Textures[High(Textures)].Speed := _speed;
1323 result := High(Textures);
1324 TextNameHash.put(RecName, result);
1325 //writeln(' CREATED!');
1327 else
1328 begin
1329 if (BadTextNameHash = nil) then BadTextNameHash := THashStrInt.Create();
1330 if log and (not BadTextNameHash.get(RecName, f)) then
1331 begin
1332 e_WriteLog(Format('Error loading animation texture "%s" images', [RecName]), TMsgType.Warning);
1333 end;
1334 BadTextNameHash.put(RecName, -1);
1335 end;
1336 end;
1337 finally
1338 for f := 0 to High(ia) do FreeImage(ia[f]);
1339 WAD.Free();
1340 cfg.Free();
1341 if (TextureWAD <> nil) then FreeMem(TextureWAD);
1342 if (TextData <> nil) then FreeMem(TextData);
1343 if (TextureData <> nil) then FreeMem(TextureData);
1344 end;
1345 end;
1347 procedure CreateItem(Item: TDynRecord);
1348 begin
1349 if g_Game_IsClient then Exit;
1351 if (not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) and
1352 ByteBool(Item.Options and ITEM_OPTION_ONLYDM) then
1353 Exit;
1355 g_Items_Create(Item.X, Item.Y, Item.ItemType, ByteBool(Item.Options and ITEM_OPTION_FALL),
1356 gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF, GM_COOP]);
1357 end;
1359 procedure CreateArea(Area: TDynRecord);
1361 a: Integer;
1362 id: DWORD = 0;
1363 begin
1364 case Area.AreaType of
1365 AREA_DMPOINT, AREA_PLAYERPOINT1, AREA_PLAYERPOINT2,
1366 AREA_REDTEAMPOINT, AREA_BLUETEAMPOINT:
1367 begin
1368 SetLength(RespawnPoints, Length(RespawnPoints)+1);
1369 with RespawnPoints[High(RespawnPoints)] do
1370 begin
1371 X := Area.X;
1372 Y := Area.Y;
1373 Direction := TDirection(Area.Direction);
1375 case Area.AreaType of
1376 AREA_DMPOINT: PointType := RESPAWNPOINT_DM;
1377 AREA_PLAYERPOINT1: PointType := RESPAWNPOINT_PLAYER1;
1378 AREA_PLAYERPOINT2: PointType := RESPAWNPOINT_PLAYER2;
1379 AREA_REDTEAMPOINT: PointType := RESPAWNPOINT_RED;
1380 AREA_BLUETEAMPOINT: PointType := RESPAWNPOINT_BLUE;
1381 end;
1382 end;
1383 end;
1385 AREA_REDFLAG, AREA_BLUEFLAG:
1386 begin
1387 if Area.AreaType = AREA_REDFLAG
1388 then a := FLAG_RED
1389 else a := FLAG_BLUE;
1391 if FlagPoints[a] <> nil then Exit;
1393 New(FlagPoints[a]);
1395 with FlagPoints[a]^ do
1396 begin
1397 X := Area.X-FLAGRECT.X;
1398 Y := Area.Y-FLAGRECT.Y;
1399 Direction := TDirection(Area.Direction);
1400 end;
1402 with gFlags[a] do
1403 begin
1404 case a of
1405 FLAG_RED: g_Frames_Get(id, 'FRAMES_FLAG_RED');
1406 FLAG_BLUE: g_Frames_Get(id, 'FRAMES_FLAG_BLUE');
1407 end;
1409 Animation := TAnimation.Create(id, True, 8);
1410 Obj.Rect := FLAGRECT;
1412 g_Map_ResetFlag(a);
1413 end;
1414 end;
1416 AREA_DOMFLAG:
1417 begin
1418 {SetLength(DOMFlagPoints, Length(DOMFlagPoints)+1);
1419 with DOMFlagPoints[High(DOMFlagPoints)] do
1420 begin
1421 X := Area.X;
1422 Y := Area.Y;
1423 Direction := TDirection(Area.Direction);
1424 end;
1426 g_Map_CreateFlag(DOMFlagPoints[High(DOMFlagPoints)], FLAG_DOM, FLAG_STATE_NORMAL);}
1427 end;
1428 end;
1429 end;
1431 function CreateTrigger (amapIdx: Integer; Trigger: TDynRecord; atpanid, atrigpanid: Integer): Integer;
1433 _trigger: TTrigger;
1434 tp: TPanel;
1435 begin
1436 result := -1;
1437 if g_Game_IsClient and not (Trigger.TriggerType in [TRIGGER_SOUND, TRIGGER_MUSIC]) then Exit;
1439 with _trigger do
1440 begin
1441 mapId := Trigger.id;
1442 mapIndex := amapIdx;
1443 X := Trigger.X;
1444 Y := Trigger.Y;
1445 Width := Trigger.Width;
1446 Height := Trigger.Height;
1447 Enabled := Trigger.Enabled;
1448 TexturePanelGUID := atpanid;
1449 TriggerType := Trigger.TriggerType;
1450 ActivateType := Trigger.ActivateType;
1451 Keys := Trigger.Keys;
1452 trigPanelGUID := atrigpanid;
1453 // HACK: used in TPanel.CanChangeTexture. maybe there's a better way?
1454 if TexturePanelGUID <> -1 then
1455 begin
1456 tp := g_Map_PanelByGUID(TexturePanelGUID);
1457 if (tp <> nil) then tp.hasTexTrigger := True;
1458 end;
1459 end;
1461 result := Integer(g_Triggers_Create(_trigger, Trigger));
1462 end;
1464 procedure CreateMonster(monster: TDynRecord);
1466 a: Integer;
1467 mon: TMonster;
1468 begin
1469 if g_Game_IsClient then Exit;
1471 if (gGameSettings.GameType = GT_SINGLE)
1472 or (TGameOption.MONSTERS in gGameSettings.Options) then
1473 begin
1474 mon := g_Monsters_Create(monster.MonsterType, monster.X, monster.Y, TDirection(monster.Direction));
1476 if gTriggers <> nil then
1477 begin
1478 for a := 0 to High(gTriggers) do
1479 begin
1480 if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
1481 begin
1482 //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
1483 if (gTriggers[a].trigDataRec.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
1484 end;
1485 end;
1486 end;
1488 if monster.MonsterType <> MONSTER_BARREL then Inc(gTotalMonsters);
1489 end;
1490 end;
1492 procedure g_Map_ReAdd_DieTriggers();
1494 function monsDieTrig (mon: TMonster): Boolean;
1496 a: Integer;
1497 //tw: TStrTextWriter;
1498 begin
1499 result := false; // don't stop
1500 mon.ClearTriggers();
1501 for a := 0 to High(gTriggers) do
1502 begin
1503 if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
1504 begin
1505 //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
1507 tw := TStrTextWriter.Create();
1509 gTriggers[a].trigData.writeTo(tw);
1510 e_LogWritefln('=== trigger #%s ==='#10'%s'#10'---', [a, tw.str]);
1511 finally
1512 tw.Free();
1513 end;
1515 if (gTriggers[a].trigDataRec.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
1516 end;
1517 end;
1518 end;
1520 begin
1521 if g_Game_IsClient then Exit;
1523 g_Mons_ForEach(monsDieTrig);
1524 end;
1526 procedure mapCreateGrid ();
1528 mapX0: Integer = $3fffffff;
1529 mapY0: Integer = $3fffffff;
1530 mapX1: Integer = -$3fffffff;
1531 mapY1: Integer = -$3fffffff;
1533 procedure calcBoundingBox (constref panels: TPanelArray);
1535 idx: Integer;
1536 pan: TPanel;
1537 begin
1538 for idx := 0 to High(panels) do
1539 begin
1540 pan := panels[idx];
1541 if not pan.visvalid then continue;
1542 if (pan.Width < 1) or (pan.Height < 1) then continue;
1543 if (mapX0 > pan.x0) then mapX0 := pan.x0;
1544 if (mapY0 > pan.y0) then mapY0 := pan.y0;
1545 if (mapX1 < pan.x1) then mapX1 := pan.x1;
1546 if (mapY1 < pan.y1) then mapY1 := pan.y1;
1547 end;
1548 end;
1550 procedure addPanelsToGrid (constref panels: TPanelArray);
1552 idx: Integer;
1553 pan: TPanel;
1554 newtag: Integer;
1555 begin
1556 //tag := panelTypeToTag(tag);
1557 for idx := 0 to High(panels) do
1558 begin
1559 pan := panels[idx];
1560 if not pan.visvalid then continue;
1561 if (pan.proxyId <> -1) then
1562 begin
1563 {$IF DEFINED(D2F_DEBUG)}
1564 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);
1565 {$ENDIF}
1566 continue;
1567 end;
1568 case pan.PanelType of
1569 PANEL_WALL: newtag := GridTagWall;
1570 PANEL_OPENDOOR, PANEL_CLOSEDOOR: newtag := GridTagDoor;
1571 PANEL_BACK: newtag := GridTagBack;
1572 PANEL_FORE: newtag := GridTagFore;
1573 PANEL_WATER: newtag := GridTagWater;
1574 PANEL_ACID1: newtag := GridTagAcid1;
1575 PANEL_ACID2: newtag := GridTagAcid2;
1576 PANEL_STEP: newtag := GridTagStep;
1577 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: newtag := GridTagLift;
1578 PANEL_BLOCKMON: newtag := GridTagBlockMon;
1579 else continue; // oops
1580 end;
1581 pan.tag := newtag;
1583 pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, newtag);
1584 // "enabled" flag has meaning only for doors and walls (engine assumes it); but meh...
1585 mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
1586 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
1588 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
1589 begin
1590 e_WriteLog(Format('INSERTED wall #%d(%d) enabled (%d)', [Integer(idx), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId])]), MSG_NOTIFY);
1591 end;
1593 {$ENDIF}
1594 end;
1595 end;
1597 begin
1598 FreeAndNil(mapGrid); // BD: do we really need it here anymore if it's now also in g_Map_Free()?
1600 calcBoundingBox(gWalls);
1601 calcBoundingBox(gRenderBackgrounds);
1602 calcBoundingBox(gRenderForegrounds);
1603 calcBoundingBox(gWater);
1604 calcBoundingBox(gAcid1);
1605 calcBoundingBox(gAcid2);
1606 calcBoundingBox(gSteps);
1607 calcBoundingBox(gLifts);
1608 calcBoundingBox(gBlockMon);
1610 e_LogWritefln('map dimensions: (%d,%d)-(%d,%d); editor size:(0,0)-(%d,%d)', [mapX0, mapY0, mapX1, mapY1, gMapInfo.Width, gMapInfo.Height]);
1612 if (mapX0 > 0) then mapX0 := 0;
1613 if (mapY0 > 0) then mapY0 := 0;
1615 if (mapX1 < gMapInfo.Width-1) then mapX1 := gMapInfo.Width-1;
1616 if (mapY1 < gMapInfo.Height-1) then mapY1 := gMapInfo.Height-1;
1618 mapGrid := TPanelGrid.Create(mapX0-128, mapY0-128, mapX1-mapX0+1+128*2, mapY1-mapY0+1+128*2);
1619 //mapGrid := TPanelGrid.Create(0, 0, gMapInfo.Width, gMapInfo.Height);
1621 addPanelsToGrid(gWalls);
1622 addPanelsToGrid(gRenderBackgrounds);
1623 addPanelsToGrid(gRenderForegrounds);
1624 addPanelsToGrid(gWater);
1625 addPanelsToGrid(gAcid1);
1626 addPanelsToGrid(gAcid2);
1627 addPanelsToGrid(gSteps);
1628 addPanelsToGrid(gLifts); // it doesn't matter which LIFT type is used here
1629 addPanelsToGrid(gBlockMon);
1631 mapGrid.dumpStats();
1633 g_Mons_InitTree(mapGrid.gridX0, mapGrid.gridY0, mapGrid.gridWidth, mapGrid.gridHeight);
1634 end;
1637 function g_Map_Load(Res: String): Boolean;
1638 const
1639 DefaultMusRes = 'Standart.wad:STDMUS\MUS1';
1640 DefaultSkyRes = 'Standart.wad:STDSKY\SKY0';
1641 type
1642 PTRec = ^TTRec;
1643 TTRec = record
1644 //TexturePanel: Integer;
1645 tnum: Integer;
1646 id: Integer;
1647 trigrec: TDynRecord;
1648 // texture pane;
1649 texPanelIdx: Integer;
1650 texPanel: TDynRecord;
1651 // "action" panel
1652 actPanelIdx: Integer;
1653 actPanel: TDynRecord;
1654 end;
1656 WAD, TestWAD: TWADFile;
1657 //mapReader: TDynRecord = nil;
1658 mapTextureList: TDynField = nil; //TTexturesRec1Array; tagInt: texture index
1659 panels: TDynField = nil; //TPanelsRec1Array;
1660 items: TDynField = nil; //TItemsRec1Array;
1661 monsters: TDynField = nil; //TMonsterRec1Array;
1662 areas: TDynField = nil; //TAreasRec1Array;
1663 triggers: TDynField = nil; //TTriggersRec1Array;
1664 b, c, k: Integer;
1665 PanelID: DWORD;
1666 AddTextures: TAddTextureArray;
1667 TriggersTable: array of TTRec;
1668 FileName, mapResName, TexName, s: AnsiString;
1669 Data: Pointer;
1670 Len: Integer;
1671 ok, isAnim: Boolean;
1672 CurTex, ntn: Integer;
1673 rec, texrec: TDynRecord;
1674 pttit: PTRec;
1675 pannum, trignum, cnt, tgpid: Integer;
1676 stt: UInt64;
1677 moveSpeed{, moveStart, moveEnd}: TDFPoint;
1678 //moveActive: Boolean;
1679 pan: TPanel;
1680 mapOk: Boolean = false;
1681 usedTextures: THashStrInt = nil; // key: mapTextureList
1682 begin
1683 mapGrid.Free();
1684 mapGrid := nil;
1685 TestWAD := nil;
1686 Data := nil;
1688 //gCurrentMap.Free();
1689 //gCurrentMap := nil;
1691 panByGUID := nil;
1693 Result := False;
1694 gMapInfo.Map := Res;
1695 TriggersTable := nil;
1696 //mapReader := nil;
1698 sfsGCDisable(); // temporary disable removing of temporary volumes
1700 // Çàãðóçêà WAD (åñëè ó íàñ íåò óæå çàãðóæåíîé êàðòû)
1701 if (gCurrentMap = nil) then
1702 begin
1703 FileName := g_ExtractWadName(Res);
1704 e_LogWritefln('Loading map WAD [%s] (res=[%s])', [FileName, Res], TMsgType.Notify);
1705 g_Game_SetLoadingText(_lc[I_LOAD_WAD_FILE], 0, False);
1707 WAD := TWADFile.Create();
1708 if not WAD.ReadFile(FileName) then
1709 begin
1710 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_WAD], [FileName]));
1711 WAD.Free();
1712 Exit;
1713 end;
1715 if gTestMap <> '' then
1716 begin
1717 s := g_ExtractWadName(gTestMap);
1718 TestWAD := TWADFile.Create();
1719 if not TestWAD.ReadFile(s) then
1720 begin
1721 g_SimpleError(Format(_lc[I_GAME_ERROR_MAP_WAD], [s]));
1722 TestWAD.Free();
1723 TestWAD := nil;
1724 end;
1725 end;
1727 if TestWAD <> nil then
1728 begin
1729 mapResName := g_ExtractFileName(gTestMap);
1730 if not TestWAD.GetMapResource(mapResName, Data, Len) then
1731 begin
1732 g_SimpleError(Format(_lc[I_GAME_ERROR_MAP_RES], [mapResName]));
1733 Data := nil;
1734 end else
1735 e_WriteLog('Using test map: '+gTestMap, TMsgType.Notify);
1736 TestWAD.Free();
1737 TestWAD := nil;
1738 end;
1740 if Data = nil then
1741 begin
1742 //k8: why loader ignores path here?
1743 mapResName := g_ExtractFileName(Res);
1744 if not WAD.GetMapResource(mapResName, Data, Len) then
1745 begin
1746 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_RES], [mapResName]));
1747 WAD.Free();
1748 Exit;
1749 end;
1750 end;
1752 WAD.Free();
1754 if (Len < 4) then
1755 begin
1756 e_LogWritefln('invalid map file: ''%s''', [mapResName]);
1757 FreeMem(Data);
1758 exit;
1759 end;
1761 // Çàãðóçêà êàðòû:
1762 e_LogWritefln('Loading map: %s', [mapResName], TMsgType.Notify);
1763 g_Game_SetLoadingText(_lc[I_LOAD_MAP], 0, False);
1765 stt := getTimeMicro();
1768 gCurrentMap := g_Map_ParseMap(Data, Len);
1769 except
1770 gCurrentMap.Free();
1771 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]));
1772 FreeMem(Data);
1773 gCurrentMapFileName := '';
1774 Exit;
1775 end;
1777 FreeMem(Data);
1779 if (gCurrentMap = nil) then
1780 begin
1781 e_LogWritefln('invalid map file: ''%s''', [mapResName]);
1782 gCurrentMapFileName := '';
1783 exit;
1784 end;
1786 else
1787 begin
1788 stt := getTimeMicro();
1789 end;
1791 //gCurrentMap := mapReader;
1793 generateExternalResourcesList(gCurrentMap);
1794 mapTextureList := gCurrentMap['texture'];
1795 // get all other lists here too
1796 panels := gCurrentMap['panel'];
1797 triggers := gCurrentMap['trigger'];
1798 items := gCurrentMap['item'];
1799 areas := gCurrentMap['area'];
1800 monsters := gCurrentMap['monster'];
1802 // Çàãðóçêà îïèñàíèÿ êàðòû:
1803 e_WriteLog(' Reading map info...', TMsgType.Notify);
1804 g_Game_SetLoadingText(_lc[I_LOAD_MAP_HEADER], 0, False);
1806 with gMapInfo do
1807 begin
1808 Name := gCurrentMap.MapName;
1809 Description := gCurrentMap.MapDesc;
1810 Author := gCurrentMap.MapAuthor;
1811 MusicName := gCurrentMap.MusicName;
1812 SkyName := gCurrentMap.SkyName;
1813 Height := gCurrentMap.Height;
1814 Width := gCurrentMap.Width;
1815 end;
1817 // Çàãðóçêà òåêñòóð:
1818 g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], 0, False);
1819 // Äîáàâëåíèå òåêñòóð â Textures[]:
1820 if (mapTextureList <> nil) and (mapTextureList.count > 0) then
1821 begin
1822 e_WriteLog(' Loading textures:', TMsgType.Notify);
1823 g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], mapTextureList.count-1, False);
1825 // find used textures
1826 usedTextures := THashStrInt.Create();
1828 if (panels <> nil) and (panels.count > 0) then
1829 begin
1830 for rec in panels do
1831 begin
1832 texrec := rec.TextureRec;
1833 if (texrec <> nil) then usedTextures.put(toLowerCase1251(texrec.Resource), 42);
1834 end;
1835 end;
1837 cnt := -1;
1838 for rec in mapTextureList do
1839 begin
1840 cnt += 1;
1841 if not usedTextures.has(toLowerCase1251(rec.Resource)) then
1842 begin
1843 rec.tagInt := -1; // just in case
1844 e_LogWritefln(' Unused texture #%d: %s', [cnt, rec.Resource]);
1846 else
1847 begin
1848 {$IF DEFINED(D2F_DEBUG_TXLOAD)}
1849 e_LogWritefln(' Loading texture #%d: %s', [cnt, rec.Resource]);
1850 {$ENDIF}
1851 //if g_Map_IsSpecialTexture(s) then e_WriteLog(' SPECIAL!', MSG_NOTIFY);
1852 // TODO: Unify the texture reader - static textures are a special case of dynamic ones, just with only one frame.
1853 if rec.Anim then
1854 begin
1855 // Àíèìèðîâàííàÿ òåêñòóðà
1856 ntn := CreateAnimTexture(rec.Resource, FileName, True);
1857 if (ntn < 0) then
1858 begin
1859 // FIXME: I think, CreateAnimTexture() will load static textures too, just as animated ones with one frame.
1860 ntn := CreateTexture(rec.Resource, FileName, False);
1861 if (ntn < 0) then
1862 g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_ANIM], [rec.Resource]))
1863 else
1864 begin
1865 rec.user['animated'] := False;
1866 e_LogWritefln(' wrong (outdated?) anim flag hint - texture #%d is actually static: %s', [cnt, rec.Resource]);
1867 end;
1868 end;
1870 else
1871 begin
1872 // Îáû÷íàÿ òåêñòóðà
1873 ntn := CreateTexture(rec.Resource, FileName, True);
1874 if (ntn < 0) then
1875 begin
1876 ntn := CreateAnimTexture(rec.Resource, FileName, False);
1877 if (ntn < 0) then
1878 g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_SIMPLE], [rec.Resource]))
1879 else
1880 begin
1881 rec.user['animated'] := True;
1882 e_LogWritefln(' wrong (outdated?) anim flag hint - texture #%d is actually animated: %s', [cnt, rec.Resource]);
1883 end;
1884 end;
1885 end;
1886 if (ntn < 0) then ntn := CreateNullTexture(rec.Resource);
1888 rec.tagInt := ntn; // remember texture number
1889 end;
1890 g_Game_StepLoading();
1891 end;
1892 finally
1893 usedTextures.Free();
1894 end;
1896 // set panel tagInt to texture index
1897 if (panels <> nil) then
1898 begin
1899 for rec in panels do
1900 begin
1901 texrec := rec.TextureRec;
1902 if (texrec = nil) then rec.tagInt := -1 else rec.tagInt := texrec.tagInt;
1903 end;
1904 end;
1905 end;
1908 // Çàãðóçêà òðèããåðîâ
1909 gTriggerClientID := 0;
1910 e_WriteLog(' Loading triggers...', TMsgType.Notify);
1911 g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS], 0, False);
1913 // Çàãðóçêà ïàíåëåé
1914 e_WriteLog(' Loading panels...', TMsgType.Notify);
1915 g_Game_SetLoadingText(_lc[I_LOAD_PANELS], 0, False);
1917 // check texture numbers for panels
1918 if (panels <> nil) and (panels.count > 0) then
1919 begin
1920 for rec in panels do
1921 begin
1922 if (rec.tagInt < 0) then
1923 begin
1924 e_WriteLog('error loading map: invalid texture index for panel', TMsgType.Fatal);
1925 result := false;
1926 gCurrentMap.Free();
1927 gCurrentMap := nil;
1928 gCurrentMapFileName := '';
1929 exit;
1930 end;
1931 end;
1932 end;
1934 // Ñîçäàíèå òàáëèöû òðèããåðîâ (ñîîòâåòñòâèå ïàíåëåé òðèããåðàì)
1935 if (triggers <> nil) and (triggers.count > 0) then
1936 begin
1937 e_WriteLog(' Setting up trigger table...', TMsgType.Notify);
1938 //SetLength(TriggersTable, triggers.count);
1939 g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS_TABLE], triggers.count-1, False);
1941 SetLength(TriggersTable, triggers.count);
1942 trignum := -1;
1943 for rec in triggers do
1944 begin
1945 Inc(trignum);
1946 pttit := @TriggersTable[trignum];
1947 pttit.trigrec := rec;
1948 // Ñìåíà òåêñòóðû (âîçìîæíî, êíîïêè)
1949 pttit.texPanelIdx := -1; // will be fixed later
1950 pttit.texPanel := rec.TexturePanelRec;
1951 // action panel
1952 pttit.actPanelIdx := -1;
1953 if (rec.trigRec <> nil) then pttit.actPanel := rec.trigRec.tgPanelRec else pttit.actPanel := nil;
1954 // set flag
1955 if (pttit.texPanel <> nil) then pttit.texPanel.userPanelTrigRef := true;
1956 if (pttit.actPanel <> nil) then pttit.actPanel.userPanelTrigRef := true;
1957 // update progress
1958 g_Game_StepLoading();
1959 end;
1960 end;
1962 // Ñîçäàåì ïàíåëè
1963 if (panels <> nil) and (panels.count > 0) then
1964 begin
1965 e_WriteLog(' Setting up trigger links...', TMsgType.Notify);
1966 g_Game_SetLoadingText(_lc[I_LOAD_LINK_TRIGGERS], panels.count-1, False);
1968 pannum := -1;
1969 for rec in panels do
1970 begin
1971 Inc(pannum);
1972 //e_LogWritefln('PANSTART: pannum=%s', [pannum]);
1973 texrec := nil;
1974 SetLength(AddTextures, 0);
1975 CurTex := -1;
1976 ok := false;
1978 if (mapTextureList <> nil) then
1979 begin
1980 texrec := rec.TextureRec;
1981 ok := (texrec <> nil);
1982 end;
1984 if ok then
1985 begin
1986 // Ñìîòðèì, ññûëàþòñÿ ëè íà ýòó ïàíåëü òðèããåðû.
1987 // Åñëè äà - òî íàäî ñîçäàòü åùå òåêñòóð
1988 ok := false;
1989 if (TriggersTable <> nil) and (mapTextureList <> nil) then
1990 begin
1991 if rec.userPanelTrigRef then
1992 begin
1993 // e_LogWritefln('trigref for panel %s', [pannum]);
1994 ok := True;
1995 end;
1996 end;
1997 end;
1999 if ok then
2000 begin
2001 // Åñòü ññûëêè òðèããåðîâ íà ýòó ïàíåëü
2002 s := texrec.Resource;
2004 // Ñïåö-òåêñòóðû çàïðåùåíû
2005 if g_Map_IsSpecialTexture(s) then
2006 begin
2007 ok := false
2009 else
2010 begin
2011 // Îïðåäåëÿåì íàëè÷èå è ïîëîæåíèå öèôð â êîíöå ñòðîêè
2012 ok := g_Texture_NumNameFindStart(s);
2013 end;
2015 // Åñëè ok, çíà÷èò åñòü öèôðû â êîíöå.
2016 // Çàãðóæàåì òåêñòóðû ñ îñòàëüíûìè #
2017 if ok then
2018 begin
2019 k := NNF_NAME_BEFORE;
2020 // Öèêë ïî èçìåíåíèþ èìåíè òåêñòóðû
2021 while ok or (k = NNF_NAME_BEFORE) or (k = NNF_NAME_EQUALS) do
2022 begin
2023 k := g_Texture_NumNameFindNext(TexName);
2025 if (k = NNF_NAME_BEFORE) or (k = NNF_NAME_AFTER) then
2026 begin
2027 // Ïðîáóåì äîáàâèòü íîâóþ òåêñòóðó
2028 if texrec.Anim then
2029 begin
2030 // Íà÷àëüíàÿ - àíèìèðîâàííàÿ, èùåì àíèìèðîâàííóþ
2031 isAnim := True;
2032 //e_LogWritefln('000: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
2033 ok := CreateAnimTexture(TexName, FileName, False) >= 0;
2034 //e_LogWritefln('001: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
2035 if not ok then
2036 begin
2037 // Íåò àíèìèðîâàííîé, èùåì îáû÷íóþ
2038 isAnim := False;
2039 //e_LogWritefln('002: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
2040 ok := CreateTexture(TexName, FileName, False) >= 0;
2041 //e_LogWritefln('003: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
2042 end;
2044 else
2045 begin
2046 // Íà÷àëüíàÿ - îáû÷íàÿ, èùåì îáû÷íóþ
2047 isAnim := False;
2048 //e_LogWritefln('004: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
2049 ok := CreateTexture(TexName, FileName, False) >= 0;
2050 //e_LogWritefln('005: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
2051 if not ok then
2052 begin
2053 // Íåò îáû÷íîé, èùåì àíèìèðîâàííóþ
2054 isAnim := True;
2055 //e_LogWritefln('006: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
2056 ok := CreateAnimTexture(TexName, FileName, False) >= 0;
2057 //e_LogWritefln('007: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
2058 end;
2059 end;
2061 // Îíà ñóùåñòâóåò. Çàíîñèì åå ID â ñïèñîê ïàíåëè
2062 if ok then
2063 begin
2065 for c := 0 to High(Textures) do
2066 begin
2067 if (Textures[c].TextureName = TexName) then
2068 begin
2069 SetLength(AddTextures, Length(AddTextures)+1);
2070 AddTextures[High(AddTextures)].Texture := c;
2071 AddTextures[High(AddTextures)].Anim := isAnim;
2072 break;
2073 end;
2074 end;
2076 if (TextNameHash <> nil) and TextNameHash.get(toLowerCase1251(TexName), c) then
2077 begin
2078 SetLength(AddTextures, Length(AddTextures)+1);
2079 AddTextures[High(AddTextures)].Texture := c;
2080 AddTextures[High(AddTextures)].Anim := isAnim;
2081 end;
2082 end;
2084 else
2085 begin
2086 if k = NNF_NAME_EQUALS then
2087 begin
2088 // Çàíîñèì òåêóùóþ òåêñòóðó íà ñâîå ìåñòî
2089 SetLength(AddTextures, Length(AddTextures)+1);
2090 AddTextures[High(AddTextures)].Texture := rec.tagInt; // internal texture number, not map index
2091 AddTextures[High(AddTextures)].Anim := texrec.Anim;
2092 CurTex := High(AddTextures);
2093 ok := true;
2095 else // NNF_NO_NAME
2096 begin
2097 ok := false;
2098 end;
2099 end;
2100 end; // while ok...
2102 ok := true;
2103 end; // if ok - åñòü ñìåæíûå òåêñòóðû
2104 end; // if ok - ññûëàþòñÿ òðèããåðû
2106 if not ok then
2107 begin
2108 // Çàíîñèì òîëüêî òåêóùóþ òåêñòóðó
2109 SetLength(AddTextures, 1);
2110 AddTextures[0].Texture := rec.tagInt; // internal texture number, not map index
2111 AddTextures[0].Anim := false;
2112 if (texrec <> nil) then AddTextures[0].Anim := texrec.Anim;
2113 CurTex := 0;
2114 end;
2116 //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);
2118 //e_LogWritefln('PANADD: pannum=%s', [pannum]);
2120 // Ñîçäàåì ïàíåëü è çàïîìèíàåì åå GUID
2121 //e_LogWritefln('new panel; tcount=%s; curtex=%s', [Length(AddTextures), CurTex]);
2122 PanelID := CreatePanel(rec, AddTextures, CurTex);
2123 //e_LogWritefln('panel #%s of type %s got guid #%s', [pannum, rec.PanelType, PanelID]);
2124 rec.userPanelId := PanelID; // remember game panel id, we'll fix triggers later
2126 // setup lifts
2127 moveSpeed := rec.moveSpeed;
2128 //moveStart := rec.moveStart;
2129 //moveEnd := rec.moveEnd;
2130 //moveActive := rec['move_active'].value;
2131 if not moveSpeed.isZero then
2132 begin
2133 SetLength(gMovingWallIds, Length(gMovingWallIds)+1);
2134 gMovingWallIds[High(gMovingWallIds)] := PanelID;
2135 //e_LogWritefln('found moving panel ''%s'' (idx=%s; id=%s)', [rec.id, pannum, PanelID]);
2136 end;
2138 //e_LogWritefln('PANEND: pannum=%s', [pannum]);
2140 g_Game_StepLoading();
2141 end;
2142 end;
2144 // ×èíèì ID'û ïàíåëåé, êîòîðûå èñïîëüçóþòñÿ â òðèããåðàõ
2145 for b := 0 to High(TriggersTable) do
2146 begin
2147 if (TriggersTable[b].texPanel <> nil) then TriggersTable[b].texPanelIdx := TriggersTable[b].texPanel.userPanelId;
2148 if (TriggersTable[b].actPanel <> nil) then TriggersTable[b].actPanelIdx := TriggersTable[b].actPanel.userPanelId;
2149 end;
2151 // create map grid, init other grids (for monsters, for example)
2152 e_WriteLog('Creating map grid', TMsgType.Notify);
2153 mapCreateGrid();
2155 // Åñëè íå LoadState, òî ñîçäàåì òðèããåðû
2156 if (triggers <> nil) and (panels <> nil) and (not gLoadGameMode) then
2157 begin
2158 e_LogWritefln(' Creating triggers (%d)...', [triggers.count]);
2159 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_TRIGGERS], 0, False);
2160 // Óêàçûâàåì òèï ïàíåëè, åñëè åñòü
2161 trignum := -1;
2162 for rec in triggers do
2163 begin
2164 Inc(trignum);
2165 tgpid := TriggersTable[trignum].actPanelIdx;
2166 //e_LogWritefln('creating trigger #%s; texpantype=%s; shotpantype=%s (%d,%d)', [trignum, b, c, TriggersTable[trignum].texPanIdx, TriggersTable[trignum].ShotPanelIdx]);
2167 TriggersTable[trignum].tnum := trignum;
2168 TriggersTable[trignum].id := CreateTrigger(trignum, rec, TriggersTable[trignum].texPanelIdx, tgpid);
2169 end;
2170 end;
2172 //FIXME: use hashtable!
2173 for pan in panByGUID do
2174 begin
2175 if (pan.endPosTrigId >= 0) and (pan.endPosTrigId < Length(TriggersTable)) then
2176 begin
2177 pan.endPosTrigId := TriggersTable[pan.endPosTrigId].id;
2178 end;
2179 if (pan.endSizeTrigId >= 0) and (pan.endSizeTrigId < Length(TriggersTable)) then
2180 begin
2181 pan.endSizeTrigId := TriggersTable[pan.endSizeTrigId].id;
2182 end;
2183 end;
2185 // Çàãðóçêà ïðåäìåòîâ
2186 e_WriteLog(' Loading items...', TMsgType.Notify);
2187 g_Game_SetLoadingText(_lc[I_LOAD_ITEMS], 0, False);
2189 // Åñëè íå LoadState, òî ñîçäàåì ïðåäìåòû
2190 if (items <> nil) and not gLoadGameMode then
2191 begin
2192 e_WriteLog(' Spawning items...', TMsgType.Notify);
2193 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_ITEMS], 0, False);
2194 for rec in items do CreateItem(rec);
2195 end;
2197 // Çàãðóçêà îáëàñòåé
2198 e_WriteLog(' Loading areas...', TMsgType.Notify);
2199 g_Game_SetLoadingText(_lc[I_LOAD_AREAS], 0, False);
2201 // Åñëè íå LoadState, òî ñîçäàåì îáëàñòè
2202 if areas <> nil then
2203 begin
2204 e_WriteLog(' Creating areas...', TMsgType.Notify);
2205 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_AREAS], 0, False);
2206 for rec in areas do CreateArea(rec);
2207 end;
2209 // Çàãðóçêà ìîíñòðîâ
2210 e_WriteLog(' Loading monsters...', TMsgType.Notify);
2211 g_Game_SetLoadingText(_lc[I_LOAD_MONSTERS], 0, False);
2213 gTotalMonsters := 0;
2215 // Åñëè íå LoadState, òî ñîçäàåì ìîíñòðîâ
2216 if (monsters <> nil) and not gLoadGameMode then
2217 begin
2218 e_WriteLog(' Spawning monsters...', TMsgType.Notify);
2219 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_MONSTERS], 0, False);
2220 for rec in monsters do CreateMonster(rec);
2221 end;
2223 //gCurrentMap := mapReader; // this will be our current map now
2224 gCurrentMapFileName := Res;
2225 //mapReader := nil;
2227 // Çàãðóçêà íåáà
2228 if (gMapInfo.SkyName <> '') then
2229 begin
2230 e_WriteLog(' Loading sky: ' + gMapInfo.SkyName, TMsgType.Notify);
2231 g_Game_SetLoadingText(_lc[I_LOAD_SKY], 0, False);
2232 if gTextureFilter then TEXTUREFILTER := GL_LINEAR else TEXTUREFILTER := GL_NEAREST;
2234 s := e_GetResourcePath(WadDirs, gMapInfo.SkyName, g_ExtractWadName(Res));
2235 if g_Texture_CreateWAD(BackID, s) then
2236 g_Game_SetupScreenSize
2237 else
2238 g_FatalError(Format(_lc[I_GAME_ERROR_SKY], [s]))
2239 finally
2240 TEXTUREFILTER := GL_NEAREST;
2241 end;
2242 end;
2244 {$IFDEF ENABLE_SOUND}
2245 // Çàãðóçêà ìóçûêè
2246 ok := False;
2247 if gMapInfo.MusicName <> '' then
2248 begin
2249 e_WriteLog(' Loading music: ' + gMapInfo.MusicName, TMsgType.Notify);
2250 g_Game_SetLoadingText(_lc[I_LOAD_MUSIC], 0, False);
2252 s := e_GetResourcePath(WadDirs, gMapInfo.MusicName, g_ExtractWadName(Res));
2253 if g_Sound_CreateWADEx(gMapInfo.MusicName, s, True) then
2254 ok := True
2255 else
2256 g_FatalError(Format(_lc[I_GAME_ERROR_MUSIC], [s]));
2257 end;
2258 {$ENDIF}
2260 // Îñòàëüíûå óñòàíâêè
2261 CreateDoorMap();
2262 CreateLiftMap();
2264 g_Items_Init();
2265 g_Weapon_Init();
2266 g_Monsters_Init();
2268 // Åñëè íå LoadState, òî ñîçäàåì êàðòó ñòîëêíîâåíèé:
2269 if not gLoadGameMode then g_GFX_Init();
2271 // Ñáðîñ ëîêàëüíûõ ìàññèâîâ:
2272 mapTextureList := nil;
2273 panels := nil;
2274 items := nil;
2275 areas := nil;
2276 triggers := nil;
2277 TriggersTable := nil;
2278 AddTextures := nil;
2280 {$IFDEF ENABLE_SOUND}
2281 // Âêëþ÷àåì ìóçûêó, åñëè ýòî íå çàãðóçêà:
2282 if ok and (not gLoadGameMode) then
2283 begin
2284 gMusic.SetByName(gMapInfo.MusicName);
2285 gMusic.Play();
2287 else
2288 begin
2289 gMusic.SetByName('');
2290 end;
2291 {$ELSE}
2292 gMusicName := gMapInfo.MusicName;
2293 gMusicPlay := True;
2294 gMusicPos := 0;
2295 gMusicPause := False;
2296 {$ENDIF}
2298 stt := getTimeMicro()-stt;
2299 e_LogWritefln('map loaded in %s.%s milliseconds', [Integer(stt div 1000), Integer(stt mod 1000)]);
2300 mapOk := true;
2301 finally
2302 sfsGCEnable(); // enable releasing unused volumes
2303 //mapReader.Free();
2304 e_UnpressAllKeys; // why not?
2305 if not mapOk then
2306 begin
2307 gCurrentMap.Free();
2308 gCurrentMap := nil;
2309 gCurrentMapFileName := '';
2310 end;
2311 end;
2313 compactExtResList();
2314 e_WriteLog('Done loading map.', TMsgType.Notify);
2315 Result := True;
2316 end;
2319 function g_Map_GetMapInfo(Res: String): TMapInfo;
2321 WAD: TWADFile;
2322 mapReader: TDynRecord;
2323 //Header: TMapHeaderRec_1;
2324 FileName: String;
2325 Data: Pointer;
2326 Len: Integer;
2327 begin
2328 FillChar(Result, SizeOf(Result), 0);
2329 FileName := g_ExtractWadName(Res);
2331 WAD := TWADFile.Create();
2332 if not WAD.ReadFile(FileName) then
2333 begin
2334 WAD.Free();
2335 Exit;
2336 end;
2338 //k8: it ignores path again
2339 if not WAD.GetMapResource(g_ExtractFileName(Res), Data, Len) then
2340 begin
2341 WAD.Free();
2342 Exit;
2343 end;
2345 WAD.Free();
2348 mapReader := g_Map_ParseMap(Data, Len);
2349 except
2350 mapReader := nil;
2351 FreeMem(Data);
2352 exit;
2353 end;
2355 FreeMem(Data);
2357 if (mapReader = nil) then exit;
2359 if (mapReader.Width > 0) and (mapReader.Height > 0) then
2360 begin
2361 Result.Name := mapReader.MapName;
2362 Result.Description := mapReader.MapDesc;
2363 Result.Map := Res;
2364 Result.Author := mapReader.MapAuthor;
2365 Result.Height := mapReader.Height;
2366 Result.Width := mapReader.Width;
2368 else
2369 begin
2370 g_Console_Add(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]), True);
2371 //ZeroMemory(@Header, SizeOf(Header));
2372 Result.Name := _lc[I_GAME_ERROR_MAP_SELECT];
2373 Result.Description := _lc[I_GAME_ERROR_MAP_SELECT];
2374 Result.Map := Res;
2375 Result.Author := '';
2376 Result.Height := 0;
2377 Result.Width := 0;
2378 end;
2380 mapReader.Free();
2381 end;
2383 function g_Map_GetMapsList(WADName: string): SSArray;
2385 WAD: TWADFile;
2386 a: Integer;
2387 ResList: SSArray;
2388 begin
2389 Result := nil;
2390 WAD := TWADFile.Create();
2391 if not WAD.ReadFile(WADName) then
2392 begin
2393 WAD.Destroy();
2394 Exit;
2395 end;
2396 ResList := WAD.GetMapResources();
2397 if ResList <> nil then
2398 begin
2399 for a := 0 to High(ResList) do
2400 begin
2401 SetLength(Result, Length(Result)+1);
2402 Result[High(Result)] := ResList[a];
2403 end;
2404 end;
2405 WAD.Destroy();
2406 end;
2408 function g_Map_Exist(Res: string): Boolean;
2410 WAD: TWADFile;
2411 FileName, mnn: string;
2412 ResList: SSArray;
2413 a: Integer;
2414 begin
2415 Result := False;
2417 FileName := addWadExtension(g_ExtractWadName(Res));
2419 WAD := TWADFile.Create;
2420 if not WAD.ReadFile(FileName) then
2421 begin
2422 WAD.Free();
2423 Exit;
2424 end;
2426 ResList := WAD.GetMapResources();
2427 WAD.Free();
2429 mnn := g_ExtractFileName(Res);
2430 if ResList <> nil then
2431 for a := 0 to High(ResList) do if StrEquCI1251(ResList[a], mnn) then
2432 begin
2433 Result := True;
2434 Exit;
2435 end;
2436 end;
2438 procedure g_Map_Free(freeTextures: Boolean=true);
2440 a: Integer;
2442 procedure FreePanelArray(var panels: TPanelArray);
2444 i: Integer;
2446 begin
2447 if panels <> nil then
2448 begin
2449 for i := 0 to High(panels) do
2450 panels[i].Free();
2451 panels := nil;
2452 end;
2453 end;
2455 begin
2456 FreeAndNil(mapGrid);
2457 FreeAndNil(gDrawPanelList);
2458 framePool.kill(); // in fact, this just frees up its actual background buffer
2460 g_GFX_Free();
2461 g_Weapon_Free();
2462 g_Items_Free();
2463 g_Triggers_Free();
2464 g_Monsters_Free();
2466 RespawnPoints := nil;
2467 if FlagPoints[FLAG_RED] <> nil then
2468 begin
2469 Dispose(FlagPoints[FLAG_RED]);
2470 FlagPoints[FLAG_RED] := nil;
2471 end;
2472 if FlagPoints[FLAG_BLUE] <> nil then
2473 begin
2474 Dispose(FlagPoints[FLAG_BLUE]);
2475 FlagPoints[FLAG_BLUE] := nil;
2476 end;
2477 FreeAndNil(gFlags[FLAG_RED].Animation);
2478 FreeAndNil(gFlags[FLAG_BLUE].Animation);
2479 //DOMFlagPoints := nil;
2481 //gDOMFlags := nil;
2483 if gCurrentMapFileName <> ''
2484 then e_LogWritefln('g_Map_Free: previous map was ''%s''...', [gCurrentMapFileName])
2485 else e_LogWritefln('g_Map_Free: no previous map.', []);
2487 if freeTextures then
2488 begin
2489 e_LogWritefln('g_Map_Free: clearing textures...', []);
2491 for a := 0 to High(Textures) do
2492 begin
2493 if not g_Map_IsSpecialTexture(Textures[a].TextureName) then
2494 begin
2495 if Textures[a].Anim then
2496 begin
2497 g_Frames_DeleteByID(Textures[a].FramesID)
2499 else
2500 begin
2501 if (Textures[a].TextureID <> LongWord(TEXTURE_NONE)) then
2502 begin
2503 e_DeleteTexture(Textures[a].TextureID);
2504 end;
2505 end;
2506 end;
2507 end;
2508 Textures := nil;
2510 FreeAndNil(TextNameHash);
2511 FreeAndNil(BadTextNameHash);
2512 gCurrentMapFileName := '';
2513 FreeAndNil(gCurrentMap);
2514 end;
2516 panByGUID := nil;
2518 FreePanelArray(gWalls);
2519 FreePanelArray(gRenderBackgrounds);
2520 FreePanelArray(gRenderForegrounds);
2521 FreePanelArray(gWater);
2522 FreePanelArray(gAcid1);
2523 FreePanelArray(gAcid2);
2524 FreePanelArray(gSteps);
2525 FreePanelArray(gLifts);
2526 FreePanelArray(gBlockMon);
2527 gMovingWallIds := nil;
2529 if BackID <> DWORD(-1) then
2530 begin
2531 gBackSize.X := 0;
2532 gBackSize.Y := 0;
2533 e_DeleteTexture(BackID);
2534 BackID := DWORD(-1);
2535 end;
2537 {$IFDEF ENABLE_SOUND}
2538 g_Game_StopAllSounds(False);
2539 gMusic.FreeSound();
2540 g_Sound_Delete(gMapInfo.MusicName);
2541 {$ELSE}
2542 gMusicName := '';
2543 gMusicPlay := False;
2544 gMusicPos := 0;
2545 gMusicPause := False;
2546 {$ENDIF}
2548 gMapInfo.Name := '';
2549 gMapInfo.Description := '';
2550 gMapInfo.MusicName := '';
2551 gMapInfo.Height := 0;
2552 gMapInfo.Width := 0;
2554 gDoorMap := nil;
2555 gLiftMap := nil;
2556 end;
2558 procedure g_Map_Update();
2560 a, d, j: Integer;
2561 m: Word;
2562 s: String;
2563 b: Byte;
2565 procedure UpdatePanelArray(var panels: TPanelArray);
2567 i: Integer;
2569 begin
2570 for i := 0 to High(panels) do panels[i].Update();
2571 end;
2573 begin
2574 if g_dbgpan_mplat_step then g_dbgpan_mplat_active := true;
2576 UpdatePanelArray(gWalls);
2577 UpdatePanelArray(gRenderBackgrounds);
2578 UpdatePanelArray(gRenderForegrounds);
2579 UpdatePanelArray(gWater);
2580 UpdatePanelArray(gAcid1);
2581 UpdatePanelArray(gAcid2);
2582 UpdatePanelArray(gSteps);
2584 if g_dbgpan_mplat_step then begin g_dbgpan_mplat_step := false; g_dbgpan_mplat_active := false; end;
2586 if gGameSettings.GameMode = GM_CTF then
2587 begin
2588 for a := FLAG_RED to FLAG_BLUE do
2589 begin
2590 if not (gFlags[a].State in [FLAG_STATE_NONE, FLAG_STATE_CAPTURED]) then
2591 begin
2592 with gFlags[a] do
2593 begin
2594 if gFlags[a].Animation <> nil then gFlags[a].Animation.Update();
2596 Obj.oldX := Obj.X;
2597 Obj.oldY := Obj.Y;
2599 m := g_Obj_Move(@Obj, True, True);
2601 NeedSend := NeedSend or (Obj.X <> Obj.oldX) or (Obj.Y <> Obj.oldY);
2603 if gTime mod (GAME_TICK*2) <> 0 then Continue;
2605 // Ñîïðîòèâëåíèå âîçäóõà
2606 Obj.Vel.X := z_dec(Obj.Vel.X, 1);
2608 // Òàéìàóò ïîòåðÿííîãî ôëàãà, ëèáî îí âûïàë çà êàðòó
2609 if ((Count = 0) or ByteBool(m and MOVE_FALLOUT)) and g_Game_IsServer then
2610 begin
2611 g_Map_ResetFlag(a);
2612 gFlags[a].CaptureTime := 0;
2613 if a = FLAG_RED then
2614 s := _lc[I_PLAYER_FLAG_RED]
2615 else
2616 s := _lc[I_PLAYER_FLAG_BLUE];
2617 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
2619 {$IFDEF ENABLE_SOUND}
2620 if (((gPlayer1 <> nil) and (((gPlayer1.Team = TEAM_RED) and (a = FLAG_RED)) or ((gPlayer1.Team = TEAM_BLUE) and (a = FLAG_BLUE))))
2621 or ((gPlayer2 <> nil) and (((gPlayer2.Team = TEAM_RED) and (a = FLAG_RED)) or ((gPlayer2.Team = TEAM_BLUE) and (a = FLAG_BLUE))))) then
2622 b := 0
2623 else
2624 b := 1;
2626 if not sound_ret_flag[b].IsPlaying() then
2627 sound_ret_flag[b].Play();
2628 {$ENDIF}
2630 if g_Game_IsNet then
2631 MH_SEND_FlagEvent(FLAG_STATE_RETURNED, a, 0);
2632 Continue;
2633 end;
2635 if Count > 0 then Count -= 1;
2637 // Èãðîê áåðåò ôëàã
2638 if gPlayers <> nil then
2639 begin
2640 j := Random(Length(gPlayers)) - 1;
2641 for d := 0 to High(gPlayers) do
2642 begin
2643 Inc(j);
2644 if j > High(gPlayers) then j := 0;
2645 if gPlayers[j] <> nil then
2646 begin
2647 if gPlayers[j].alive and g_Obj_Collide(@Obj, @gPlayers[j].Obj) then
2648 begin
2649 if gPlayers[j].GetFlag(a) then Break;
2650 end;
2651 end;
2652 end;
2653 end;
2654 end;
2655 end;
2656 end;
2657 end;
2658 end;
2661 // old algo
2662 procedure g_Map_DrawPanels (PanelType: Word; hasAmbient: Boolean; constref ambColor: TDFColor);
2664 procedure DrawPanels (constref panels: TPanelArray; drawDoors: Boolean=False);
2666 idx: Integer;
2667 begin
2668 if (panels <> nil) then
2669 begin
2670 // alas, no visible set
2671 for idx := 0 to High(panels) do
2672 begin
2673 if not (drawDoors xor panels[idx].Door) then panels[idx].Draw(hasAmbient, ambColor);
2674 end;
2675 end;
2676 end;
2678 begin
2679 case PanelType of
2680 PANEL_WALL: DrawPanels(gWalls);
2681 PANEL_CLOSEDOOR: DrawPanels(gWalls, True);
2682 PANEL_BACK: DrawPanels(gRenderBackgrounds);
2683 PANEL_FORE: DrawPanels(gRenderForegrounds);
2684 PANEL_WATER: DrawPanels(gWater);
2685 PANEL_ACID1: DrawPanels(gAcid1);
2686 PANEL_ACID2: DrawPanels(gAcid2);
2687 PANEL_STEP: DrawPanels(gSteps);
2688 end;
2689 end;
2692 // new algo
2693 procedure g_Map_CollectDrawPanels (x0, y0, wdt, hgt: Integer);
2695 mwit: PPanel;
2696 it: TPanelGrid.Iter;
2697 begin
2698 dplClear();
2699 it := mapGrid.forEachInAABB(x0, y0, wdt, hgt, GridDrawableMask);
2700 for mwit in it do
2701 if ((mwit^.tag and GridTagDoor) <> 0) = mwit^.Door then
2702 gDrawPanelList.insert(mwit^);
2703 it.release();
2704 // list will be rendered in `g_game.DrawPlayer()`
2705 end;
2708 procedure g_Map_DrawPanelShadowVolumes (lightX: Integer; lightY: Integer; radius: Integer);
2710 mwit: PPanel;
2711 it: TPanelGrid.Iter;
2712 begin
2713 it := mapGrid.forEachInAABB(lightX-radius, lightY-radius, radius*2, radius*2, (GridTagWall or GridTagDoor));
2714 for mwit in it do mwit^.DrawShadowVolume(lightX, lightY, radius);
2715 it.release();
2716 end;
2719 procedure g_Map_DrawBack(dx, dy: Integer);
2720 begin
2721 if gDrawBackGround and (BackID <> DWORD(-1)) then
2722 e_DrawSize(BackID, dx, dy, 0, False, False, gBackSize.X, gBackSize.Y)
2723 else
2724 e_Clear(GL_COLOR_BUFFER_BIT, 0, 0, 0);
2725 end;
2727 function g_Map_CollidePanelOld(X, Y: Integer; Width, Height: Word;
2728 PanelType: Word; b1x3: Boolean=false): Boolean;
2730 a, h: Integer;
2731 begin
2732 Result := False;
2734 if WordBool(PanelType and PANEL_WALL) then
2735 if gWalls <> nil then
2736 begin
2737 h := High(gWalls);
2739 for a := 0 to h do
2740 if gWalls[a].Enabled and
2741 g_Collide(X, Y, Width, Height,
2742 gWalls[a].X, gWalls[a].Y,
2743 gWalls[a].Width, gWalls[a].Height) then
2744 begin
2745 Result := True;
2746 Exit;
2747 end;
2748 end;
2750 if WordBool(PanelType and PANEL_WATER) then
2751 if gWater <> nil then
2752 begin
2753 h := High(gWater);
2755 for a := 0 to h do
2756 if g_Collide(X, Y, Width, Height,
2757 gWater[a].X, gWater[a].Y,
2758 gWater[a].Width, gWater[a].Height) then
2759 begin
2760 Result := True;
2761 Exit;
2762 end;
2763 end;
2765 if WordBool(PanelType and PANEL_ACID1) then
2766 if gAcid1 <> nil then
2767 begin
2768 h := High(gAcid1);
2770 for a := 0 to h do
2771 if g_Collide(X, Y, Width, Height,
2772 gAcid1[a].X, gAcid1[a].Y,
2773 gAcid1[a].Width, gAcid1[a].Height) then
2774 begin
2775 Result := True;
2776 Exit;
2777 end;
2778 end;
2780 if WordBool(PanelType and PANEL_ACID2) then
2781 if gAcid2 <> nil then
2782 begin
2783 h := High(gAcid2);
2785 for a := 0 to h do
2786 if g_Collide(X, Y, Width, Height,
2787 gAcid2[a].X, gAcid2[a].Y,
2788 gAcid2[a].Width, gAcid2[a].Height) then
2789 begin
2790 Result := True;
2791 Exit;
2792 end;
2793 end;
2795 if WordBool(PanelType and PANEL_STEP) then
2796 if gSteps <> nil then
2797 begin
2798 h := High(gSteps);
2800 for a := 0 to h do
2801 if g_Collide(X, Y, Width, Height,
2802 gSteps[a].X, gSteps[a].Y,
2803 gSteps[a].Width, gSteps[a].Height) then
2804 begin
2805 Result := True;
2806 Exit;
2807 end;
2808 end;
2810 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then
2811 if gLifts <> nil then
2812 begin
2813 h := High(gLifts);
2815 for a := 0 to h do
2816 if ((WordBool(PanelType and (PANEL_LIFTUP)) and (gLifts[a].LiftType = LIFTTYPE_UP)) or
2817 (WordBool(PanelType and (PANEL_LIFTDOWN)) and (gLifts[a].LiftType = LIFTTYPE_DOWN)) or
2818 (WordBool(PanelType and (PANEL_LIFTLEFT)) and (gLifts[a].LiftType = LIFTTYPE_LEFT)) or
2819 (WordBool(PanelType and (PANEL_LIFTRIGHT)) and (gLifts[a].LiftType = LIFTTYPE_RIGHT))) and
2820 g_Collide(X, Y, Width, Height,
2821 gLifts[a].X, gLifts[a].Y,
2822 gLifts[a].Width, gLifts[a].Height) then
2823 begin
2824 Result := True;
2825 Exit;
2826 end;
2827 end;
2829 if WordBool(PanelType and PANEL_BLOCKMON) then
2830 if gBlockMon <> nil then
2831 begin
2832 h := High(gBlockMon);
2834 for a := 0 to h do
2835 if ( (not b1x3) or
2836 ((gBlockMon[a].Width + gBlockMon[a].Height) >= 64) ) and
2837 g_Collide(X, Y, Width, Height,
2838 gBlockMon[a].X, gBlockMon[a].Y,
2839 gBlockMon[a].Width, gBlockMon[a].Height) then
2840 begin
2841 Result := True;
2842 Exit;
2843 end;
2844 end;
2845 end;
2847 function g_Map_CollideLiquid_TextureOld(X, Y: Integer; Width, Height: Word): DWORD;
2849 texid: DWORD;
2851 function checkPanels (constref panels: TPanelArray): Boolean;
2853 a: Integer;
2854 begin
2855 result := false;
2856 if panels = nil then exit;
2857 for a := 0 to High(panels) do
2858 begin
2859 if g_Collide(X, Y, Width, Height, panels[a].X, panels[a].Y, panels[a].Width, panels[a].Height) then
2860 begin
2861 result := true;
2862 texid := panels[a].GetTextureID();
2863 exit;
2864 end;
2865 end;
2866 end;
2868 begin
2869 texid := LongWord(TEXTURE_NONE);
2870 result := texid;
2871 if not checkPanels(gWater) then
2872 if not checkPanels(gAcid1) then
2873 if not checkPanels(gAcid2) then exit;
2874 result := texid;
2875 end;
2878 function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word; PanelType: Word; b1x3: Boolean): Boolean;
2879 const
2880 SlowMask = GridTagLift or GridTagBlockMon;
2882 function checker (pan: TPanel; tag: Integer): Boolean;
2883 begin
2885 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
2886 begin
2887 result := pan.Enabled;
2888 exit;
2889 end;
2892 if ((tag and GridTagLift) <> 0) then
2893 begin
2894 result :=
2895 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
2896 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
2897 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
2898 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT))) {and
2899 g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
2900 exit;
2901 end;
2903 if ((tag and GridTagBlockMon) <> 0) then
2904 begin
2905 result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2906 exit;
2907 end;
2909 // other shit
2910 //result := g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2911 result := true; // i found her!
2912 end;
2915 tagmask: Integer = 0;
2916 mwit: PPanel;
2917 it: TPanelGrid.Iter;
2918 pan: TPanel;
2919 begin
2920 result := false;
2921 if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor);
2922 if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater;
2923 if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1;
2924 if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2;
2925 if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep;
2926 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift;
2927 if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon;
2929 if (tagmask = 0) then exit; // just in case
2931 if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('*solids');
2932 if gdbg_map_use_accel_coldet then
2933 begin
2934 if ((tagmask and SlowMask) <> 0) then
2935 begin
2936 // slow
2937 it := mapGrid.forEachInAABB(X, Y, Width, Height, tagmask);
2938 for mwit in it do
2939 begin
2940 pan := mwit^;
2941 if ((pan.tag and GridTagLift) <> 0) then
2942 begin
2943 result :=
2944 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
2945 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
2946 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
2947 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT))) {and
2948 g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
2950 else if ((pan.tag and GridTagBlockMon) <> 0) then
2951 begin
2952 result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2954 else
2955 begin
2956 // other shit
2957 result := true; // i found her!
2958 end;
2959 if (result) then break;
2960 end;
2962 else
2963 begin
2964 // fast
2965 it := mapGrid.forEachInAABB(X, Y, Width, Height, tagmask, false, true); // return first hit
2966 result := (it.length > 0);
2967 end;
2968 it.release();
2970 else
2971 begin
2972 result := g_Map_CollidePanelOld(X, Y, Width, Height, PanelType, b1x3);
2973 end;
2974 if (profMapCollision <> nil) then profMapCollision.sectionEnd();
2975 end;
2978 // returns `true` if we need to stop
2979 function liquidChecker (pan: TPanel; var texid: DWORD; var cctype: Integer): Boolean; inline;
2980 begin
2981 result := false;
2982 //if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2)) = 0) then exit;
2983 // check priorities
2984 case cctype of
2985 0: if ((pan.tag and GridTagWater) = 0) then exit; // allowed: water
2986 1: if ((pan.tag and (GridTagWater or GridTagAcid1)) = 0) then exit; // allowed: water, acid1
2987 //2: if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2) = 0) then exit; // allowed: water, acid1, acid2
2988 end;
2989 // collision?
2990 //if not g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height) then exit;
2991 // yeah
2992 texid := pan.GetTextureID();
2993 // water? water has the highest priority, so stop right here
2994 if ((pan.tag and GridTagWater) <> 0) then begin cctype := 0; result := true; exit; end;
2995 // acid2?
2996 if ((pan.tag and GridTagAcid2) <> 0) then cctype := 2;
2997 // acid1?
2998 if ((pan.tag and GridTagAcid1) <> 0) then cctype := 1;
2999 end;
3001 function g_Map_CollideLiquid_Texture(X, Y: Integer; Width, Height: Word): DWORD;
3003 cctype: Integer = 3; // priority: 0: water was hit, 1: acid1 was hit, 2: acid2 was hit; 3: nothing was hit
3004 mwit: PPanel;
3005 it: TPanelGrid.Iter;
3006 begin
3007 if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('liquids');
3008 if gdbg_map_use_accel_coldet then
3009 begin
3010 result := LongWord(TEXTURE_NONE);
3011 it := mapGrid.forEachInAABB(X, Y, Width, Height, (GridTagWater or GridTagAcid1 or GridTagAcid2));
3012 for mwit in it do if (liquidChecker(mwit^, result, cctype)) then break;
3013 it.release();
3015 else
3016 begin
3017 result := g_Map_CollideLiquid_TextureOld(X, Y, Width, Height);
3018 end;
3019 if (profMapCollision <> nil) then profMapCollision.sectionEnd();
3020 end;
3023 procedure g_Map_EnableWall_XXX (ID: DWORD); begin if (ID < Length(gWalls)) then g_Map_EnableWallGUID(gWalls[ID].guid); end;
3024 procedure g_Map_DisableWall_XXX (ID: DWORD); begin if (ID < Length(gWalls)) then g_Map_DisableWallGUID(gWalls[ID].guid); end;
3025 procedure g_Map_SetLift_XXX (ID: DWORD; t: Integer); begin if (ID < Length(gLifts)) then g_Map_SetLiftGUID(gLifts[ID].guid, t); end;
3028 procedure g_Map_EnableWallGUID (pguid: Integer);
3030 pan: TPanel;
3031 begin
3032 //pan := gWalls[ID];
3033 pan := g_Map_PanelByGUID(pguid);
3034 if (pan = nil) then exit;
3035 if pan.Enabled and mapGrid.proxyEnabled[pan.proxyId] then exit;
3037 pan.Enabled := True;
3038 g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, true);
3040 mapGrid.proxyEnabled[pan.proxyId] := true;
3041 //if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := true
3042 //else pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, GridTagDoor);
3044 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
3045 // mark platform as interesting
3046 pan.setDirty();
3048 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
3049 //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);
3050 {$ENDIF}
3051 end;
3054 procedure g_Map_DisableWallGUID (pguid: Integer);
3056 pan: TPanel;
3057 begin
3058 //pan := gWalls[ID];
3059 pan := g_Map_PanelByGUID(pguid);
3060 if (pan = nil) then exit;
3061 if (not pan.Enabled) and (not mapGrid.proxyEnabled[pan.proxyId]) then exit;
3063 pan.Enabled := False;
3064 g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, false);
3066 mapGrid.proxyEnabled[pan.proxyId] := false;
3067 //if (pan.proxyId >= 0) then begin mapGrid.removeBody(pan.proxyId); pan.proxyId := -1; end;
3069 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
3070 // mark platform as interesting
3071 pan.setDirty();
3073 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
3074 //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);
3075 {$ENDIF}
3076 end;
3079 procedure g_Map_SwitchTextureGUID (pguid: Integer; AnimLoop: Byte = 0);
3081 tp: TPanel;
3082 begin
3083 tp := g_Map_PanelByGUID(pguid);
3084 if (tp = nil) then exit;
3085 tp.NextTexture(AnimLoop);
3086 if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelTexture(pguid, AnimLoop);
3087 end;
3090 procedure g_Map_SetLiftGUID (pguid: Integer; t: Integer);
3092 pan: TPanel;
3093 begin
3094 //pan := gLifts[ID];
3095 pan := g_Map_PanelByGUID(pguid);
3096 if (pan = nil) then exit;
3097 if not pan.isGLift then exit;
3099 if ({gLifts[ID]}pan.LiftType = t) then exit; //!FIXME!TRIGANY!
3101 with {gLifts[ID]} pan do
3102 begin
3103 LiftType := t;
3105 g_Mark(X, Y, Width, Height, MARK_LIFT, false);
3106 //TODO: make separate lift tags, and change tag here
3108 case LiftType of
3109 LIFTTYPE_UP: g_Mark(X, Y, Width, Height, MARK_LIFTUP);
3110 LIFTTYPE_DOWN: g_Mark(X, Y, Width, Height, MARK_LIFTDOWN);
3111 LIFTTYPE_LEFT: g_Mark(X, Y, Width, Height, MARK_LIFTLEFT);
3112 LIFTTYPE_RIGHT: g_Mark(X, Y, Width, Height, MARK_LIFTRIGHT);
3113 end;
3115 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
3116 // mark platform as interesting
3117 pan.setDirty();
3118 end;
3119 end;
3122 function g_Map_GetPoint (PointType: Byte; var RespawnPoint: TRespawnPoint): Boolean;
3124 a: Integer;
3125 PointsArray: Array of TRespawnPoint;
3126 begin
3127 Result := False;
3128 SetLength(PointsArray, 0);
3130 if RespawnPoints = nil then
3131 Exit;
3133 for a := 0 to High(RespawnPoints) do
3134 if RespawnPoints[a].PointType = PointType then
3135 begin
3136 SetLength(PointsArray, Length(PointsArray)+1);
3137 PointsArray[High(PointsArray)] := RespawnPoints[a];
3138 end;
3140 if PointsArray = nil then
3141 Exit;
3143 RespawnPoint := PointsArray[Random(Length(PointsArray))];
3144 Result := True;
3145 end;
3147 function g_Map_GetPointCount(PointType: Byte): Word;
3149 a: Integer;
3150 begin
3151 Result := 0;
3153 if RespawnPoints = nil then
3154 Exit;
3156 for a := 0 to High(RespawnPoints) do
3157 if RespawnPoints[a].PointType = PointType then
3158 Result := Result + 1;
3159 end;
3161 function g_Map_GetRandomPointType(): Byte;
3162 begin
3163 if RespawnPoints = nil then
3164 Result := 255
3165 else
3166 Result := RespawnPoints[Random(Length(RespawnPoints))].PointType;
3167 end;
3169 function g_Map_HaveFlagPoints(): Boolean;
3170 begin
3171 Result := (FlagPoints[FLAG_RED] <> nil) and (FlagPoints[FLAG_BLUE] <> nil);
3172 end;
3174 procedure g_Map_ResetFlag(Flag: Byte);
3175 begin
3176 with gFlags[Flag] do
3177 begin
3178 Obj.X := -1000;
3179 Obj.Y := -1000;
3180 Obj.Vel.X := 0;
3181 Obj.Vel.Y := 0;
3182 Direction := TDirection.D_LEFT;
3183 State := FLAG_STATE_NONE;
3184 if FlagPoints[Flag] <> nil then
3185 begin
3186 Obj.X := FlagPoints[Flag]^.X;
3187 Obj.Y := FlagPoints[Flag]^.Y;
3188 Direction := FlagPoints[Flag]^.Direction;
3189 State := FLAG_STATE_NORMAL;
3190 end;
3191 Obj.oldX := Obj.X;
3192 Obj.oldY := Obj.Y;
3193 NeedSend := False; // the event will take care of this
3194 Count := -1;
3195 end;
3196 end;
3198 procedure g_Map_DrawFlags();
3200 i, dx: Integer;
3201 tx, ty: Integer;
3202 Mirror: TMirrorType;
3203 begin
3204 if gGameSettings.GameMode <> GM_CTF then
3205 Exit;
3207 for i := FLAG_RED to FLAG_BLUE do
3208 with gFlags[i] do
3209 if State <> FLAG_STATE_CAPTURED then
3210 begin
3211 if State = FLAG_STATE_NONE then
3212 continue;
3214 Obj.lerp(gLerpFactor, tx, ty);
3216 if Direction = TDirection.D_LEFT then
3217 begin
3218 Mirror := TMirrorType.Horizontal;
3219 dx := -1;
3221 else
3222 begin
3223 Mirror := TMirrorType.None;
3224 dx := 1;
3225 end;
3227 Animation.Draw(tx+dx, ty+1, Mirror);
3229 if g_debug_Frames then
3230 begin
3231 e_DrawQuad(Obj.X+Obj.Rect.X,
3232 Obj.Y+Obj.Rect.Y,
3233 Obj.X+Obj.Rect.X+Obj.Rect.Width-1,
3234 Obj.Y+Obj.Rect.Y+Obj.Rect.Height-1,
3235 0, 255, 0);
3236 end;
3237 end;
3238 end;
3241 procedure g_Map_SaveState (st: TStream);
3243 pan: TPanel;
3245 procedure saveFlag (flag: PFlag);
3246 begin
3247 utils.writeSign(st, 'FLAG');
3248 st.WriteByte(0); // version
3250 st.WriteByte(flag^.RespawnType); // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
3251 st.WriteByte(flag^.State); // Ñîñòîÿíèå ôëàãà
3253 // Íàïðàâëåíèå ôëàãà
3254 if flag^.Direction = TDirection.D_LEFT
3255 then st.WriteByte(1)
3256 else st.WriteByte(2); // D_RIGHT
3258 // Îáúåêò ôëàãà
3259 Obj_SaveState(st, @flag^.Obj);
3260 end;
3262 begin
3263 // Ñîõðàíÿåì ïàíåëè
3264 st.WriteDWordLE(Length(panByGUID));
3265 for pan in panByGUID do pan.SaveState(st);
3267 // Ñîõðàíÿåì ìóçûêó
3268 utils.writeSign(st, 'MUSI');
3269 st.WriteByte(0);
3271 // Íàçâàíèå ìóçûêè
3272 {$IFDEF ENABLE_SOUND}
3273 Assert(gMusic <> nil, 'g_Map_SaveState: gMusic = nil');
3274 if gMusic.NoMusic
3275 then utils.writeStr(st, '')
3276 else utils.writeStr(st, gMusic.Name);
3278 st.WriteDWordLE(gMusic.GetPosition()); // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
3279 st.WriteBool(gMusic.SpecPause); // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
3280 {$ELSE}
3281 utils.writeStr(st, gMusicName);
3283 st.WriteDWordLE(gMusicPos);
3284 st.WriteBool(gMusicPause);
3285 {$ENDIF}
3287 st.WriteInt32LE(gTotalMonsters); // Ñîõðàíÿåì êîëè÷åñòâî ìîíñòðîâ:
3289 // Ñîõðàíÿåì ôëàãè, åñëè ýòî CTF:
3290 if gGameSettings.GameMode = GM_CTF then
3291 begin
3292 saveFlag(@gFlags[FLAG_RED]); // Ôëàã Êðàñíîé êîìàíäû
3293 saveFlag(@gFlags[FLAG_BLUE]); // Ôëàã Ñèíåé êîìàíäû
3294 end;
3296 // Ñîõðàíÿåì êîëè÷åñòâî ïîáåä, åñëè ýòî TDM/CTF:
3297 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
3298 begin
3299 st.WriteInt16LE(gTeamStat[TEAM_RED].Score); // Î÷êè Êðàñíîé êîìàíäû
3300 st.WriteInt16LE(gTeamStat[TEAM_BLUE].Score); // Î÷êè Ñèíåé êîìàíäû
3301 end;
3302 end;
3305 procedure g_Map_LoadState (st: TStream);
3307 dw: DWORD;
3308 str: String;
3309 boo: Boolean;
3310 pan: TPanel;
3312 procedure loadFlag (flag: PFlag);
3313 begin
3314 // Ñèãíàòóðà ôëàãà
3315 if not utils.checkSign(st, 'FLAG') then
3316 Raise XStreamError.Create('invalid flag signature');
3317 if st.ReadByte() <> 0 then
3318 Raise XStreamError.Create('invalid flag version');
3320 flag^.RespawnType := st.ReadByte(); // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
3321 flag^.State := st.ReadByte(); // Ñîñòîÿíèå ôëàãà
3323 // Íàïðàâëåíèå ôëàãà
3324 if st.ReadByte() = 1
3325 then flag^.Direction := TDirection.D_LEFT
3326 else flag^.Direction := TDirection.D_RIGHT; // b = 2
3328 // Îáúåêò ôëàãà
3329 Obj_LoadState(@flag^.Obj, st);
3330 end;
3332 begin
3333 if st = nil then Exit;
3335 // Çàãðóæàåì ñïèñêè ïàíåëåé:
3336 if st.ReadDWordLE() <> Length(panByGUID) then
3337 Raise XStreamError.Create('invalid number of saved panels');
3338 for pan in panByGUID do
3339 begin
3340 pan.LoadState(st);
3341 if pan.proxyId >= 0 then
3342 mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
3343 end;
3345 // Îáíîâëÿåì êàðòó ñòîëêíîâåíèé è ñåòêó
3346 g_GFX_Init();
3347 //mapCreateGrid();
3349 ///// Çàãðóæàåì ìóçûêó: /////
3350 if not utils.checkSign(st, 'MUSI') then
3351 Raise XStreamError.Create('invalid music signature');
3352 if st.ReadByte() <> 0 then
3353 Raise XStreamError.Create('invalid music version');
3355 {$IFDEF ENABLE_SOUND}
3356 Assert(gMusic <> nil, 'g_Map_LoadState: gMusic = nil');
3357 {$ENDIF}
3358 str := utils.readStr(st); // Íàçâàíèå ìóçûêè
3359 dw := st.ReadDWordLE(); // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
3360 boo := st.ReadBool(); // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
3362 // Çàïóñêàåì ýòó ìóçûêó
3363 {$IFDEF ENABLE_SOUND}
3364 gMusic.SetByName(str);
3365 gMusic.SpecPause := boo;
3366 gMusic.Play();
3367 gMusic.Pause(true);
3368 gMusic.SetPosition(dw);
3369 {$ELSE}
3370 gMusicName := str;
3371 gMusicPlay := True;
3372 gMusicPos := dw;
3373 gMusicPause := boo;
3374 {$ENDIF}
3375 ///// /////
3377 gTotalMonsters := st.ReadInt32LE(); // Çàãðóæàåì êîëè÷åñòâî ìîíñòðîâ:
3379 //// Çàãðóæàåì ôëàãè, åñëè ýòî CTF: /////
3380 if (gGameSettings.GameMode = GM_CTF) then
3381 begin
3382 loadFlag(@gFlags[FLAG_RED]); // Ôëàã Êðàñíîé êîìàíäû
3383 loadFlag(@gFlags[FLAG_BLUE]); // Ôëàã Ñèíåé êîìàíäû
3384 end;
3386 ///// Çàãðóæàåì êîëè÷åñòâî ïîáåä, åñëè ýòî TDM/CTF: /////
3387 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
3388 begin
3389 gTeamStat[TEAM_RED].Score := st.ReadInt16LE(); // Î÷êè Êðàñíîé êîìàíäû
3390 gTeamStat[TEAM_BLUE].Score := st.ReadInt16LE(); // Î÷êè Ñèíåé êîìàíäû
3391 end;
3392 end;
3395 // trace liquid, stepping by `dx` and `dy`
3396 // return last seen liquid coords, and `false` if we're started outside of the liquid
3397 function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
3398 const
3399 MaskLiquid = GridTagWater or GridTagAcid1 or GridTagAcid2;
3400 begin
3401 topx := x;
3402 topy := y;
3403 // started outside of the liquid?
3404 //if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then begin result := false; exit; end;
3405 if (g_Map_PanelAtPoint(x, y, MaskLiquid) = nil) then begin result := false; exit; end;
3406 if (dx = 0) and (dy = 0) then begin result := false; exit; end; // sanity check
3407 result := true;
3408 while true do
3409 begin
3410 Inc(x, dx);
3411 Inc(y, dy);
3412 //if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then exit; // out of the water, just exit
3413 if (g_Map_PanelAtPoint(x, y, MaskLiquid) = nil) then exit; // out of the water, just exit
3414 topx := x;
3415 topy := y;
3416 end;
3417 end;
3419 initialization
3420 DynWarningCB := mapWarningCB;
3422 finalization
3423 profMapCollision.Free();
3424 dfmapdef.Free();
3426 end.