DEADSOFTWARE

game: clean up TPlayer.GetRespawnPoint()
[d2df-sdl.git] / src / game / g_map.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *)
15 {$INCLUDE ../shared/a_modes.inc}
16 {$DEFINE MAP_DEBUG_ENABLED_FLAG}
17 unit g_map;
19 interface
21 uses
22 SysUtils, Classes, mempool,
23 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 end;
59 function g_Map_Load(Res: String): Boolean;
60 function g_Map_GetMapInfo(Res: String): TMapInfo;
61 function g_Map_GetMapsList(WADName: String): SSArray;
62 function g_Map_Exist(Res: String): Boolean;
63 procedure g_Map_Free(freeTextures: Boolean=true);
64 procedure g_Map_Update();
66 function g_Map_PanelByGUID (aguid: Integer): TPanel; inline;
68 procedure g_Map_DrawPanels (PanelType: Word; hasAmbient: Boolean; constref ambColor: TDFColor); // unaccelerated
69 procedure g_Map_CollectDrawPanels (x0, y0, wdt, hgt: Integer);
71 procedure g_Map_DrawBack(dx, dy: Integer);
72 function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word;
73 PanelType: Word; b1x3: Boolean=false): Boolean;
74 function g_Map_CollideLiquid_Texture(X, Y: Integer; Width, Height: Word): DWORD;
76 procedure g_Map_EnableWallGUID (pguid: Integer);
77 procedure g_Map_DisableWallGUID (pguid: Integer);
78 procedure g_Map_SetLiftGUID (pguid: Integer; t: Integer);
80 // HACK!!!
81 procedure g_Map_EnableWall_XXX (ID: DWORD);
82 procedure g_Map_DisableWall_XXX (ID: DWORD);
83 procedure g_Map_SetLift_XXX (ID: DWORD; t: Integer);
85 procedure g_Map_SwitchTextureGUID (pguid: Integer; AnimLoop: Byte = 0);
87 procedure g_Map_ReAdd_DieTriggers();
88 function g_Map_IsSpecialTexture(Texture: String): Boolean;
90 function g_Map_GetPoint(PointType: Byte; var RespawnPoint: TRespawnPoint): Boolean;
91 function g_Map_GetPointCount(PointType: Byte): Word;
92 function g_Map_GetRandomPointType(): Byte;
94 function g_Map_HaveFlagPoints(): Boolean;
96 procedure g_Map_ResetFlag(Flag: Byte);
97 procedure g_Map_DrawFlags();
99 procedure g_Map_SaveState (st: TStream);
100 procedure g_Map_LoadState (st: TStream);
102 procedure g_Map_DrawPanelShadowVolumes(lightX: Integer; lightY: Integer; radius: Integer);
104 // returns panel or nil
105 // sets `ex` and `ey` to `x1` and `y1` when no hit was detected
106 function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
108 // returns panel or nil
109 // sets `ex` and `ey` to `x1` and `y1` when no hit was detected
110 function g_Map_traceToNearest (x0, y0, x1, y1: Integer; tag: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
112 type
113 TForEachPanelCB = function (pan: TPanel): Boolean is nested; // return `true` to stop
115 function g_Map_HasAnyPanelAtPoint (x, y: Integer; panelType: Word): Boolean;
116 function g_Map_PanelAtPoint (x, y: Integer; tagmask: Integer=-1): TPanel;
118 // trace liquid, stepping by `dx` and `dy`
119 // return last seen liquid coords, and `false` if we're started outside of the liquid
120 function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
123 // return `true` from `cb` to stop
124 function g_Map_ForEachPanel (cb: TForEachPanelCB): TPanel;
126 procedure g_Map_NetSendInterestingPanels (); // yay!
129 procedure g_Map_ProfilersBegin ();
130 procedure g_Map_ProfilersEnd ();
133 function g_Map_ParseMap (data: Pointer; dataLen: Integer): TDynRecord;
136 function g_Map_MinX (): Integer; inline;
137 function g_Map_MinY (): Integer; inline;
138 function g_Map_MaxX (): Integer; inline;
139 function g_Map_MaxY (): Integer; inline;
141 const
142 NNF_NO_NAME = 0;
143 NNF_NAME_BEFORE = 1;
144 NNF_NAME_EQUALS = 2;
145 NNF_NAME_AFTER = 3;
147 function g_Texture_NumNameFindStart(name: String): Boolean;
148 function g_Texture_NumNameFindNext(var newName: String): Byte;
150 const
151 RESPAWNPOINT_PLAYER1 = 1;
152 RESPAWNPOINT_PLAYER2 = 2;
153 RESPAWNPOINT_DM = 3;
154 RESPAWNPOINT_RED = 4;
155 RESPAWNPOINT_BLUE = 5;
157 FLAG_NONE = 0;
158 FLAG_RED = 1;
159 FLAG_BLUE = 2;
160 FLAG_DOM = 3;
162 FLAG_STATE_NONE = 0;
163 FLAG_STATE_NORMAL = 1;
164 FLAG_STATE_DROPPED = 2;
165 FLAG_STATE_CAPTURED = 3;
166 FLAG_STATE_SCORED = 4; // Äëÿ ýâåíòîâ ÷åðåç ñåòêó.
167 FLAG_STATE_RETURNED = 5; // Äëÿ ýâåíòîâ ÷åðåç ñåòêó.
169 FLAG_TIME = 720; // 20 seconds
171 SKY_STRETCH: Single = 1.5;
173 const
174 GridTagInvalid = 0;
176 (* draw order:
177 PANEL_BACK
178 PANEL_STEP
179 PANEL_WALL
180 PANEL_CLOSEDOOR
181 PANEL_ACID1
182 PANEL_ACID2
183 PANEL_WATER
184 PANEL_FORE
185 *)
186 // sorted by draw priority
187 GridTagBack = 1 shl 0; // gRenderBackgrounds
188 GridTagStep = 1 shl 1; // gSteps
189 GridTagWall = 1 shl 2; // gWalls
190 GridTagDoor = 1 shl 3; // gWalls
191 GridTagAcid1 = 1 shl 4; // gAcid1
192 GridTagAcid2 = 1 shl 5; // gAcid2
193 GridTagWater = 1 shl 6; // gWater
194 GridTagFore = 1 shl 7; // gRenderForegrounds
195 // the following are invisible
196 GridTagLift = 1 shl 8; // gLifts
197 GridTagBlockMon = 1 shl 9; // gBlockMon
199 GridTagSolid = (GridTagWall or GridTagDoor);
200 GridTagObstacle = (GridTagStep or GridTagWall or GridTagDoor);
201 GridTagLiquid = (GridTagAcid1 or GridTagAcid2 or GridTagWater);
203 GridDrawableMask = (GridTagBack or GridTagStep or GridTagWall or GridTagDoor or GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagFore);
206 type
207 TBinHeapPanelDrawCmp = class
208 public
209 class function less (const a, b: TPanel): Boolean; inline;
210 end;
212 TBinHeapPanelDraw = specialize TBinaryHeapBase<TPanel, TBinHeapPanelDrawCmp>;
214 var
215 gWalls: TPanelArray;
216 gRenderBackgrounds: TPanelArray;
217 gRenderForegrounds: TPanelArray;
218 gWater, gAcid1, gAcid2: TPanelArray;
219 gSteps: TPanelArray;
220 gLifts: TPanelArray;
221 gBlockMon: TPanelArray;
222 gFlags: array [FLAG_RED..FLAG_BLUE] of TFlag;
223 //gDOMFlags: array of TFlag;
224 gMapInfo: TMapInfo;
225 gBackSize: TDFPoint;
226 gDoorMap: array of array of DWORD;
227 gLiftMap: array of array of DWORD;
228 gWADHash: TMD5Digest;
229 BackID: DWORD = DWORD(-1);
230 gExternalResources: array of TDiskFileInfo = nil;
231 gMovingWallIds: array of Integer = nil;
233 gdbg_map_use_accel_render: Boolean = true;
234 gdbg_map_use_accel_coldet: Boolean = true;
235 profMapCollision: TProfiler = nil; //WARNING: FOR DEBUGGING ONLY!
236 gDrawPanelList: TBinHeapPanelDraw = nil; // binary heap of all walls we have to render, populated by `g_Map_CollectDrawPanels()`
238 gCurrentMap: TDynRecord = nil;
239 gCurrentMapFileName: AnsiString = ''; // so we can skip texture reloading
240 gTestMap: String = '';
243 function panelTypeToTag (panelType: Word): Integer; // returns GridTagXXX
246 type
247 TPanelGrid = specialize TBodyGridBase<TPanel>;
249 var
250 mapGrid: TPanelGrid = nil; // DO NOT USE! public for debugging only!
253 implementation
255 uses
256 {$INCLUDE ../nogl/noGLuses.inc}
257 e_input, g_main, e_log, e_texture, e_res, g_items, g_gfx, g_console,
258 g_weapons, g_game, g_sound, e_sound, CONFIG,
259 g_options, g_triggers, g_player,
260 Math, g_monsters, g_saveload, g_language, g_netmsg,
261 sfs, xstreams, hashtable, wadreader,
262 ImagingTypes, Imaging, ImagingUtility,
263 ImagingGif, ImagingNetworkGraphics,
264 g_res_downloader;
266 const
267 FLAGRECT: TRectWH = (X:15; Y:12; Width:33; Height:52);
268 MUSIC_SIGNATURE = $4953554D; // 'MUSI'
269 FLAG_SIGNATURE = $47414C46; // 'FLAG'
272 // ////////////////////////////////////////////////////////////////////////// //
273 procedure mapWarningCB (const msg: AnsiString; line, col: Integer);
274 begin
275 if (line > 0) then
276 begin
277 e_LogWritefln('parse error at (%s,%s): %s', [line, col, msg], TMsgType.Warning);
278 end
279 else
280 begin
281 e_LogWritefln('parse error: %s', [msg], TMsgType.Warning);
282 end;
283 end;
286 // ////////////////////////////////////////////////////////////////////////// //
287 var
288 panByGUID: array of TPanel = nil;
291 // ////////////////////////////////////////////////////////////////////////// //
292 function g_Map_PanelByGUID (aguid: Integer): TPanel; inline;
293 begin
294 //if (panByGUID = nil) or (not panByGUID.get(aguid, result)) then result := nil;
295 if (aguid >= 0) and (aguid < Length(panByGUID)) then result := panByGUID[aguid] else result := nil;
296 end;
299 // return `true` from `cb` to stop
300 function g_Map_ForEachPanel (cb: TForEachPanelCB): TPanel;
301 var
302 pan: TPanel;
303 begin
304 result := nil;
305 if not assigned(cb) then exit;
306 for pan in panByGUID do
307 begin
308 if cb(pan) then begin result := pan; exit; end;
309 end;
310 end;
313 procedure g_Map_NetSendInterestingPanels ();
314 var
315 pan: TPanel;
316 begin
317 if g_Game_IsServer and g_Game_IsNet then
318 begin
319 for pan in panByGUID do
320 begin
321 if pan.gncNeedSend then MH_SEND_PanelState(pan.guid);
322 end;
323 end;
324 end;
327 // ////////////////////////////////////////////////////////////////////////// //
328 function g_Map_MinX (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridX0 else result := 0; end;
329 function g_Map_MinY (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridY0 else result := 0; end;
330 function g_Map_MaxX (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridX0+mapGrid.gridWidth-1 else result := 0; end;
331 function g_Map_MaxY (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridY0+mapGrid.gridHeight-1 else result := 0; end;
334 // ////////////////////////////////////////////////////////////////////////// //
335 var
336 dfmapdef: TDynMapDef = nil;
339 procedure loadMapDefinition ();
340 var
341 pr: TTextParser = nil;
342 st: TStream = nil;
343 WAD: TWADFile = nil;
344 begin
345 if (dfmapdef <> nil) then exit;
347 try
348 e_LogWritefln('parsing "mapdef.txt"...', []);
349 st := e_OpenResourceRO(DataDirs, 'mapdef.txt');
350 e_LogWritefln('found local "mapdef.txt"', []);
351 except
352 st := nil;
353 end;
355 if (st = nil) then
356 begin
357 WAD := TWADFile.Create();
358 if not WAD.ReadFile(GameWAD) then
359 begin
360 //raise Exception.Create('cannot load "game.wad"');
361 st := nil;
362 end
363 else
364 begin
365 st := WAD.openFileStream('mapdef.txt');
366 end;
367 end;
369 try
370 if (st = nil) then
371 begin
372 //raise Exception.Create('cannot open "mapdef.txt"');
373 e_LogWriteln('using default "mapdef.txt"...');
374 pr := TStrTextParser.Create(defaultMapDef);
375 end
376 else
377 begin
378 pr := TFileTextParser.Create(st);
379 end;
380 except on e: Exception do
381 begin
382 e_LogWritefln('something is VERY wrong here! -- ', [e.message]);
383 raise;
384 end;
385 end;
387 try
388 dfmapdef := TDynMapDef.Create(pr);
389 except
390 on e: TDynParseException do
391 raise Exception.CreateFmt('ERROR in "mapdef.txt" at (%s,%s): %s', [e.tokLine, e.tokCol, e.message]);
392 on e: Exception do
393 raise Exception.CreateFmt('ERROR in "mapdef.txt" at (%s,%s): %s', [pr.tokLine, pr.tokCol, e.message]);
394 end;
396 st.Free();
397 WAD.Free();
398 end;
401 // ////////////////////////////////////////////////////////////////////////// //
402 function g_Map_ParseMap (data: Pointer; dataLen: Integer): TDynRecord;
403 var
404 wst: TSFSMemoryChunkStream = nil;
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);
414 try
415 result := dfmapdef.parseMap(wst);
416 except
417 on e: TDynParseException do
418 begin
419 e_LogWritefln('ERROR at (%s,%s): %s', [e.tokLine, e.tokCol, e.message]);
420 wst.Free();
421 result := nil;
422 exit;
423 end;
424 on e: Exception do
425 begin
426 e_LogWritefln('ERROR: %s', [e.message]);
427 wst.Free();
428 result := nil;
429 exit;
430 end;
431 end;
433 //e_LogWriteln('map parsed.');
434 end;
437 // ////////////////////////////////////////////////////////////////////////// //
438 var
439 NNF_PureName: String; // Èìÿ òåêñòóðû áåç öèôð â êîíöå
440 NNF_PureExt: String; // extension postfix
441 NNF_FirstNum: Integer; // ×èñëî ó íà÷àëüíîé òåêñòóðû
442 NNF_CurrentNum: Integer; // Ñëåäóþùåå ÷èñëî ó òåêñòóðû
445 function g_Texture_NumNameFindStart(name: String): Boolean;
446 var
447 i, j: Integer;
449 begin
450 Result := False;
451 NNF_PureName := '';
452 NNF_PureExt := '';
453 NNF_FirstNum := -1;
454 NNF_CurrentNum := -1;
456 for i := Length(name) downto 1 do
457 if (name[i] = '_') then // "_" - ñèìâîë íà÷àëà íîìåðíîãî ïîñòôèêñà
458 begin
459 if i = Length(name) then
460 begin // Íåò öèôð â êîíöå ñòðîêè
461 Exit;
462 end
463 else
464 begin
465 j := i + 1;
466 while (j <= Length(name)) and (name[j] <> '.') do inc(j);
467 NNF_PureName := Copy(name, 1, i);
468 NNF_PureExt := Copy(name, j);
469 name := Copy(name, i + 1, j - i - 1);
470 Break;
471 end;
472 end;
474 // Íå ïåðåâåñòè â ÷èñëî:
475 if not TryStrToInt(name, NNF_FirstNum) then
476 Exit;
478 NNF_CurrentNum := 0;
480 Result := True;
481 end;
484 function g_Texture_NumNameFindNext(var newName: String): Byte;
485 begin
486 if (NNF_PureName = '') or (NNF_CurrentNum < 0) then
487 begin
488 newName := '';
489 Result := NNF_NO_NAME;
490 Exit;
491 end;
493 newName := NNF_PureName + IntToStr(NNF_CurrentNum) + NNF_PureExt;
495 if NNF_CurrentNum < NNF_FirstNum then
496 Result := NNF_NAME_BEFORE
497 else
498 if NNF_CurrentNum > NNF_FirstNum then
499 Result := NNF_NAME_AFTER
500 else
501 Result := NNF_NAME_EQUALS;
503 Inc(NNF_CurrentNum);
504 end;
507 // ////////////////////////////////////////////////////////////////////////// //
508 function panelTypeToTag (panelType: Word): Integer;
509 begin
510 case panelType of
511 PANEL_WALL: result := GridTagWall; // gWalls
512 PANEL_OPENDOOR, PANEL_CLOSEDOOR: result := GridTagDoor; // gWalls
513 PANEL_BACK: result := GridTagBack; // gRenderBackgrounds
514 PANEL_FORE: result := GridTagFore; // gRenderForegrounds
515 PANEL_WATER: result := GridTagWater; // gWater
516 PANEL_ACID1: result := GridTagAcid1; // gAcid1
517 PANEL_ACID2: result := GridTagAcid2; // gAcid2
518 PANEL_STEP: result := GridTagStep; // gSteps
519 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: result := GridTagLift; // gLifts -- this is for all lifts
520 PANEL_BLOCKMON: result := GridTagBlockMon; // gBlockMon -- this is for all blockmons
521 else result := GridTagInvalid;
522 end;
523 end;
526 class function TBinHeapPanelDrawCmp.less (const a, b: TPanel): Boolean; inline;
527 begin
528 if (a.tag < b.tag) then begin result := true; exit; end;
529 if (a.tag > b.tag) then begin result := false; exit; end;
530 result := (a.arrIdx < b.arrIdx);
531 end;
533 procedure dplClear ();
534 begin
535 if (gDrawPanelList = nil) then gDrawPanelList := TBinHeapPanelDraw.Create() else gDrawPanelList.clear();
536 end;
539 var
540 Textures: TLevelTextureArray = nil;
541 TextNameHash: THashStrInt = nil; // key: texture name; value: index in `Textures`
542 BadTextNameHash: THashStrInt = nil; // set; so we won't spam with non-existing texture messages
543 RespawnPoints: array of TRespawnPoint;
544 FlagPoints: array[FLAG_RED..FLAG_BLUE] of PFlagPoint;
545 //DOMFlagPoints: Array of TFlagPoint;
548 procedure g_Map_ProfilersBegin ();
549 begin
550 if (profMapCollision = nil) then profMapCollision := TProfiler.Create('COLSOLID', g_profile_history_size);
551 if (profMapCollision <> nil) then profMapCollision.mainBegin(g_profile_collision);
552 // create sections
553 if g_profile_collision and (profMapCollision <> nil) then
554 begin
555 profMapCollision.sectionBegin('*solids');
556 profMapCollision.sectionEnd();
557 profMapCollision.sectionBegin('liquids');
558 profMapCollision.sectionEnd();
559 end;
560 end;
562 procedure g_Map_ProfilersEnd ();
563 begin
564 if (profMapCollision <> nil) then profMapCollision.mainEnd();
565 end;
568 // wall index in `gWalls` or -1
569 function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
570 var
571 ex, ey: Integer;
572 begin
573 result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, (GridTagWall or GridTagDoor));
574 if (result <> nil) then
575 begin
576 if (hitx <> nil) then hitx^ := ex;
577 if (hity <> nil) then hity^ := ey;
578 end
579 else
580 begin
581 if (hitx <> nil) then hitx^ := x1;
582 if (hity <> nil) then hity^ := y1;
583 end;
584 end;
586 // returns panel or nil
587 function g_Map_traceToNearest (x0, y0, x1, y1: Integer; tag: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
588 var
589 ex, ey: Integer;
590 begin
591 result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, tag);
592 if (result <> nil) then
593 begin
594 if (hitx <> nil) then hitx^ := ex;
595 if (hity <> nil) then hity^ := ey;
596 end
597 else
598 begin
599 if (hitx <> nil) then hitx^ := x1;
600 if (hity <> nil) then hity^ := y1;
601 end;
602 end;
605 function xxPanAtPointChecker (pan: TPanel; panelType: Word): Boolean; inline;
606 begin
607 if ((pan.tag and GridTagLift) <> 0) then
608 begin
609 // stop if the lift of the right type
610 result :=
611 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
612 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
613 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
614 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT)));
615 exit;
616 end;
617 result := true; // otherwise, stop anyway, 'cause `forEachAtPoint()` is guaranteed to call this only for correct panels
618 end;
620 function g_Map_HasAnyPanelAtPoint (x, y: Integer; panelType: Word): Boolean;
621 var
622 tagmask: Integer = 0;
623 mwit: PPanel;
624 it: TPanelGrid.Iter;
625 begin
626 result := false;
628 if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor);
629 if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater;
630 if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1;
631 if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2;
632 if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep;
633 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift;
634 if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon;
636 if (tagmask = 0) then exit;// just in case
638 if ((tagmask and GridTagLift) <> 0) then
639 begin
640 // slow
641 it := mapGrid.forEachAtPoint(x, y, tagmask);
642 for mwit in it do if (xxPanAtPointChecker(mwit^, PanelType)) then begin result := true; break; end;
643 end
644 else
645 begin
646 // fast
647 it := mapGrid.forEachAtPoint(x, y, tagmask, false, true);
648 result := (it.length <> 0); // firsthit
649 end;
650 it.release();
651 end;
654 function g_Map_PanelAtPoint (x, y: Integer; tagmask: Integer=-1): TPanel;
655 var
656 it: TPanelGrid.Iter;
657 begin
658 result := nil;
659 if (tagmask = 0) then exit;
660 it := mapGrid.forEachAtPoint(x, y, tagmask, false, true); // firsthit
661 if (it.length <> 0) then result := it.first^;
662 it.release();
663 end;
666 function g_Map_IsSpecialTexture(Texture: String): Boolean;
667 begin
668 Result := (Texture = TEXTURE_NAME_WATER) or
669 (Texture = TEXTURE_NAME_ACID1) or
670 (Texture = TEXTURE_NAME_ACID2);
671 end;
673 procedure CreateDoorMap();
674 var
675 PanelArray: Array of record
676 X, Y: Integer;
677 Width, Height: Word;
678 Active: Boolean;
679 PanelID: DWORD;
680 end;
681 a, b, c, m, i, len: Integer;
682 ok: Boolean;
683 begin
684 if gWalls = nil then
685 Exit;
687 i := 0;
688 len := 128;
689 SetLength(PanelArray, len);
691 for a := 0 to High(gWalls) do
692 if gWalls[a].Door then
693 begin
694 PanelArray[i].X := gWalls[a].X;
695 PanelArray[i].Y := gWalls[a].Y;
696 PanelArray[i].Width := gWalls[a].Width;
697 PanelArray[i].Height := gWalls[a].Height;
698 PanelArray[i].Active := True;
699 PanelArray[i].PanelID := a;
701 i := i + 1;
702 if i = len then
703 begin
704 len := len + 128;
705 SetLength(PanelArray, len);
706 end;
707 end;
709 // Íåò äâåðåé:
710 if i = 0 then
711 begin
712 PanelArray := nil;
713 Exit;
714 end;
716 SetLength(gDoorMap, 0);
718 g_Game_SetLoadingText(_lc[I_LOAD_DOOR_MAP], i-1, False);
720 for a := 0 to i-1 do
721 if PanelArray[a].Active then
722 begin
723 PanelArray[a].Active := False;
724 m := Length(gDoorMap);
725 SetLength(gDoorMap, m+1);
726 SetLength(gDoorMap[m], 1);
727 gDoorMap[m, 0] := PanelArray[a].PanelID;
728 ok := True;
730 while ok do
731 begin
732 ok := False;
734 for b := 0 to i-1 do
735 if PanelArray[b].Active then
736 for c := 0 to High(gDoorMap[m]) do
737 if {((gRenderWalls[PanelArray[b].RenderPanelID].TextureID = gRenderWalls[gDoorMap[m, c]].TextureID) or
738 gRenderWalls[PanelArray[b].RenderPanelID].Hide or gRenderWalls[gDoorMap[m, c]].Hide) and}
739 g_CollideAround(PanelArray[b].X, PanelArray[b].Y,
740 PanelArray[b].Width, PanelArray[b].Height,
741 gWalls[gDoorMap[m, c]].X,
742 gWalls[gDoorMap[m, c]].Y,
743 gWalls[gDoorMap[m, c]].Width,
744 gWalls[gDoorMap[m, c]].Height) then
745 begin
746 PanelArray[b].Active := False;
747 SetLength(gDoorMap[m],
748 Length(gDoorMap[m])+1);
749 gDoorMap[m, High(gDoorMap[m])] := PanelArray[b].PanelID;
750 ok := True;
751 Break;
752 end;
753 end;
755 g_Game_StepLoading();
756 end;
758 PanelArray := nil;
759 end;
761 procedure CreateLiftMap();
762 var
763 PanelArray: Array of record
764 X, Y: Integer;
765 Width, Height: Word;
766 Active: Boolean;
767 end;
768 a, b, c, len, i, j: Integer;
769 ok: Boolean;
770 begin
771 if gLifts = nil then
772 Exit;
774 len := Length(gLifts);
775 SetLength(PanelArray, len);
777 for a := 0 to len-1 do
778 begin
779 PanelArray[a].X := gLifts[a].X;
780 PanelArray[a].Y := gLifts[a].Y;
781 PanelArray[a].Width := gLifts[a].Width;
782 PanelArray[a].Height := gLifts[a].Height;
783 PanelArray[a].Active := True;
784 end;
786 SetLength(gLiftMap, len);
787 i := 0;
789 g_Game_SetLoadingText(_lc[I_LOAD_LIFT_MAP], len-1, False);
791 for a := 0 to len-1 do
792 if PanelArray[a].Active then
793 begin
794 PanelArray[a].Active := False;
795 SetLength(gLiftMap[i], 32);
796 j := 0;
797 gLiftMap[i, j] := a;
798 ok := True;
800 while ok do
801 begin
802 ok := False;
803 for b := 0 to len-1 do
804 if PanelArray[b].Active then
805 for c := 0 to j do
806 if g_CollideAround(PanelArray[b].X,
807 PanelArray[b].Y,
808 PanelArray[b].Width,
809 PanelArray[b].Height,
810 PanelArray[gLiftMap[i, c]].X,
811 PanelArray[gLiftMap[i, c]].Y,
812 PanelArray[gLiftMap[i, c]].Width,
813 PanelArray[gLiftMap[i, c]].Height) then
814 begin
815 PanelArray[b].Active := False;
816 j := j+1;
817 if j > High(gLiftMap[i]) then
818 SetLength(gLiftMap[i],
819 Length(gLiftMap[i])+32);
821 gLiftMap[i, j] := b;
822 ok := True;
824 Break;
825 end;
826 end;
828 SetLength(gLiftMap[i], j+1);
829 i := i+1;
831 g_Game_StepLoading();
832 end;
834 SetLength(gLiftMap, i);
836 PanelArray := nil;
837 end;
839 function CreatePanel (PanelRec: TDynRecord; AddTextures: TAddTextureArray; CurTex: Integer): Integer;
840 var
841 len: Integer;
842 panels: ^TPanelArray;
843 pan: TPanel;
844 pguid: Integer;
845 begin
846 Result := -1;
848 case PanelRec.PanelType of
849 PANEL_WALL, PANEL_OPENDOOR, PANEL_CLOSEDOOR: panels := @gWalls;
850 PANEL_BACK: panels := @gRenderBackgrounds;
851 PANEL_FORE: panels := @gRenderForegrounds;
852 PANEL_WATER: panels := @gWater;
853 PANEL_ACID1: panels := @gAcid1;
854 PANEL_ACID2: panels := @gAcid2;
855 PANEL_STEP: panels := @gSteps;
856 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: panels := @gLifts;
857 PANEL_BLOCKMON: panels := @gBlockMon;
858 else exit;
859 end;
861 len := Length(panels^);
862 SetLength(panels^, len+1);
864 pguid := Length(panByGUID);
865 SetLength(panByGUID, pguid+1); //FIXME!
866 pan := TPanel.Create(PanelRec, AddTextures, CurTex, Textures, pguid);
867 assert(pguid >= 0);
868 assert(pguid < Length(panByGUID));
869 panByGUID[pguid] := pan;
870 panels^[len] := pan;
871 pan.arrIdx := len;
872 pan.proxyId := -1;
873 pan.tag := panelTypeToTag(PanelRec.PanelType);
875 PanelRec.user['panel_guid'] := pguid;
877 //result := len;
878 result := pguid;
879 end;
882 function CreateNullTexture(RecName: String): Integer;
883 begin
884 RecName := toLowerCase1251(RecName);
885 if (TextNameHash = nil) then TextNameHash := THashStrInt.Create();
886 if TextNameHash.get(RecName, result) then exit; // i found her!
888 SetLength(Textures, Length(Textures)+1);
889 result := High(Textures);
891 with Textures[High(Textures)] do
892 begin
893 TextureName := RecName;
894 Width := 1;
895 Height := 1;
896 Anim := False;
897 TextureID := LongWord(TEXTURE_NONE);
898 end;
900 TextNameHash.put(RecName, result);
901 end;
904 function extractWadName (resourceName: string): string;
905 var
906 posN: Integer;
907 begin
908 posN := Pos(':', resourceName);
909 if posN > 0 then
910 Result:= Copy(resourceName, 0, posN-1)
911 else
912 Result := '';
913 end;
916 procedure addResToExternalResList (res: AnsiString);
917 var
918 uname: AnsiString;
919 f: Integer;
920 fi: TDiskFileInfo;
921 begin
922 if g_Game_IsClient or not g_Game_IsNet then exit;
923 if (length(res) = 0) then exit; // map wad
924 //res := extractWadName(res);
925 //if (length(res) = 0) then exit; // map wad
926 uname := toLowerCase1251(res);
927 // do not add duplicates
928 for f := 0 to High(gExternalResources) do
929 begin
930 if (gExternalResources[f].userName = uname) then exit;
931 end;
932 //writeln('***(000) addResToExternalResList: res=[', res, ']');
933 // add new resource
934 fi.userName := uname;
935 if not findFileCI(res) then exit;
936 //writeln('***(001) addResToExternalResList: res=[', res, ']');
937 fi.diskName := res;
938 if (not GetDiskFileInfo(res, fi)) then
939 begin
940 fi.tag := -1;
941 end
942 else
943 begin
944 //writeln('***(002) addResToExternalResList: res=[', res, ']');
945 fi.tag := 0; // non-zero means "cannot caclucate hash"
946 try
947 fi.hash := MD5File(fi.diskName);
948 except
949 fi.tag := -1;
950 end;
951 end;
952 //e_LogWritefln('addext: res=[%s]; uname=[%s]; diskName=[%s]', [res, fi.userName, fi.diskName]);
953 SetLength(gExternalResources, length(gExternalResources)+1);
954 gExternalResources[High(gExternalResources)] := fi;
955 end;
958 procedure compactExtResList ();
959 var
960 src, dest: Integer;
961 begin
962 src := 0;
963 dest := 0;
964 for src := 0 to High(gExternalResources) do
965 begin
966 if (gExternalResources[src].tag = 0) then
967 begin
968 // copy it
969 if (dest <> src) then gExternalResources[dest] := gExternalResources[src];
970 Inc(dest);
971 end;
972 end;
973 if (dest <> length(gExternalResources)) then SetLength(gExternalResources, dest);
974 end;
977 function GetReplacementWad (WadName: AnsiString): AnsiString;
978 begin
979 result := '';
980 if WadName <> '' then
981 begin
982 result := WadName;
983 if g_Game_IsClient then result := g_Res_FindReplacementWad(WadName);
984 if (result = WadName) then result := e_FindWad(WadDirs, result)
985 end;
986 end;
989 procedure generateExternalResourcesList (map: TDynRecord);
990 begin
991 SetLength(gExternalResources, 0);
992 addResToExternalResList(GetReplacementWad(g_ExtractWadName(map.MusicName)));
993 addResToExternalResList(GetReplacementWad(g_ExtractWadName(map.SkyName)));
994 end;
997 function CreateTexture (RecName: AnsiString; Map: string; log: Boolean): Integer;
998 var
999 WAD: TWADFile;
1000 TextureData: Pointer;
1001 WADName: String;
1002 a, ResLength: Integer;
1003 begin
1004 RecName := toLowerCase1251(RecName);
1005 if (TextNameHash = nil) then TextNameHash := THashStrInt.Create();
1006 if TextNameHash.get(RecName, result) then
1007 begin
1008 // i found her!
1009 //e_LogWritefln('texture ''%s'' already loaded (%s)', [RecName, result]);
1010 exit;
1011 end;
1013 Result := -1;
1015 if (BadTextNameHash <> nil) and BadTextNameHash.has(RecName) then exit; // don't do it again and again
1018 if Textures <> nil then
1019 begin
1020 for a := 0 to High(Textures) do
1021 begin
1022 if (Textures[a].TextureName = RecName) then
1023 begin // Òåêñòóðà ñ òàêèì èìåíåì óæå åñòü
1024 e_LogWritefln('texture ''%s'' already loaded', [RecName]);
1025 Result := a;
1026 Exit;
1027 end;
1028 end;
1029 end;
1032 // Òåêñòóðû ñî ñïåöèàëüíûìè èìåíàìè (âîäà, ëàâà, êèñëîòà):
1033 if (RecName = TEXTURE_NAME_WATER) or
1034 (RecName = TEXTURE_NAME_ACID1) or
1035 (RecName = TEXTURE_NAME_ACID2) then
1036 begin
1037 SetLength(Textures, Length(Textures)+1);
1039 with Textures[High(Textures)] do
1040 begin
1041 TextureName := RecName;
1042 if (TextureName = TEXTURE_NAME_WATER) then TextureID := LongWord(TEXTURE_SPECIAL_WATER)
1043 else if (TextureName = TEXTURE_NAME_ACID1) then TextureID := LongWord(TEXTURE_SPECIAL_ACID1)
1044 else if (TextureName = TEXTURE_NAME_ACID2) then TextureID := LongWord(TEXTURE_SPECIAL_ACID2);
1046 Anim := False;
1047 end;
1049 result := High(Textures);
1050 TextNameHash.put(RecName, result);
1051 Exit;
1052 end;
1054 // Çàãðóæàåì ðåñóðñ òåêñòóðû â ïàìÿòü èç WAD'à:
1055 WADName := GetReplacementWad(g_ExtractWadName(RecName));
1056 if (WADName <> '') then addResToExternalResList(WADName);
1057 if WADName = '' then WADName := Map; //WADName := GameDir+'/wads/'+WADName else
1059 WAD := TWADFile.Create();
1060 WAD.ReadFile(WADName);
1062 //txname := RecName;
1064 if (WADName = Map) and WAD.GetResource(g_ExtractFilePathName(RecName), TextureData, ResLength) then
1065 begin
1066 FreeMem(TextureData);
1067 RecName := 'COMMON\ALIEN';
1068 end;
1071 if WAD.GetResource(g_ExtractFilePathName(RecName), TextureData, ResLength, log) then
1072 begin
1073 SetLength(Textures, Length(Textures)+1);
1074 if not e_CreateTextureMem(TextureData, ResLength, Textures[High(Textures)].TextureID) then
1075 begin
1076 e_WriteLog(Format('Error loading texture %s', [RecName]), TMsgType.Warning);
1077 SetLength(Textures, Length(Textures)-1);
1078 result := -1;
1079 Exit;
1080 end;
1081 e_GetTextureSize(Textures[High(Textures)].TextureID, @Textures[High(Textures)].Width, @Textures[High(Textures)].Height);
1082 FreeMem(TextureData);
1083 Textures[High(Textures)].TextureName := RecName;
1084 Textures[High(Textures)].Anim := False;
1086 result := High(Textures);
1087 TextNameHash.put(RecName, result);
1088 end
1089 else // Íåò òàêîãî ðåóñðñà â WAD'å
1090 begin
1091 //e_WriteLog(Format('SHIT! Error loading texture %s : %s', [RecName, g_ExtractFilePathName(RecName)]), MSG_WARNING);
1092 if (BadTextNameHash = nil) then BadTextNameHash := THashStrInt.Create();
1093 if log and (not BadTextNameHash.get(RecName, a)) then
1094 begin
1095 e_WriteLog(Format('Error loading texture %s', [RecName]), TMsgType.Warning);
1096 //e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
1097 end;
1098 BadTextNameHash.put(RecName, -1);
1099 end;
1101 WAD.Free();
1102 end;
1105 function CreateAnimTexture(RecName: String; Map: string; log: Boolean): Integer;
1106 var
1107 WAD: TWADFile;
1108 TextureWAD: PChar = nil;
1109 TextData: Pointer = nil;
1110 TextureData: Pointer = nil;
1111 cfg: TConfig = nil;
1112 WADName: String;
1113 ResLength: Integer;
1114 TextureResource: String;
1115 _width, _height, _framecount, _speed: Integer;
1116 _backanimation: Boolean;
1117 //imgfmt: string;
1118 ia: TDynImageDataArray = nil;
1119 f, c, frdelay, frloop: Integer;
1120 begin
1121 RecName := toLowerCase1251(RecName);
1122 if (TextNameHash = nil) then TextNameHash := THashStrInt.Create();
1123 if TextNameHash.get(RecName, result) then
1124 begin
1125 // i found her!
1126 //e_LogWritefln('animated texture ''%s'' already loaded (%s)', [RecName, result]);
1127 exit;
1128 end;
1130 result := -1;
1132 //e_LogWritefln('*** Loading animated texture "%s"', [RecName]);
1134 if (BadTextNameHash = nil) then BadTextNameHash := THashStrInt.Create();
1135 if BadTextNameHash.get(RecName, f) then
1136 begin
1137 //e_WriteLog(Format('no animation texture %s (don''t worry)', [RecName]), MSG_NOTIFY);
1138 exit;
1139 end;
1141 // ×èòàåì WAD-ðåñóðñ àíèì.òåêñòóðû èç WAD'à â ïàìÿòü:
1142 WADName := GetReplacementWad(g_ExtractWadName(RecName));
1143 if (WADName <> '') then addResToExternalResList(WADName);
1144 if WADName = '' then WADName := Map; //WADName := GameDir+'/wads/'+WADName else
1146 WAD := TWADFile.Create();
1147 try
1148 //if WADName <> '' then WADName := GameDir+'/wads/'+WADName else WADName := Map;
1150 WAD.ReadFile(WADName);
1152 if not WAD.GetResource(g_ExtractFilePathName(RecName), TextureWAD, ResLength, log) then
1153 begin
1154 if (BadTextNameHash = nil) then BadTextNameHash := THashStrInt.Create();
1155 if log and (not BadTextNameHash.get(RecName, f)) then
1156 begin
1157 e_WriteLog(Format('Error loading animation texture %s', [RecName]), TMsgType.Warning);
1158 //e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
1159 end;
1160 BadTextNameHash.put(RecName, -1);
1161 exit;
1162 end;
1164 {TEST
1165 if WADName = Map then
1166 begin
1167 //FreeMem(TextureWAD);
1168 if not WAD.GetResource('COMMON/animation', TextureWAD, ResLength) then Halt(1);
1169 end;
1172 WAD.FreeWAD();
1174 if ResLength < 6 then
1175 begin
1176 e_WriteLog(Format('Animated texture file "%s" too short', [RecName]), TMsgType.Warning);
1177 BadTextNameHash.put(RecName, -1);
1178 exit;
1179 end;
1181 // ýòî ïòèöà? ýòî ñàìîë¸ò?
1182 if isWadData(TextureWAD, ResLength) then
1183 begin
1184 // íåò, ýòî ñóïåðìåí!
1185 if not WAD.ReadMemory(TextureWAD, ResLength) then
1186 begin
1187 e_WriteLog(Format('Animated texture WAD file "%s" is invalid', [RecName]), TMsgType.Warning);
1188 BadTextNameHash.put(RecName, -1);
1189 exit;
1190 end;
1192 // ×èòàåì INI-ðåñóðñ àíèì. òåêñòóðû è çàïîìèíàåì åãî óñòàíîâêè:
1193 if not WAD.GetResource('TEXT/ANIM', TextData, ResLength) then
1194 begin
1195 e_WriteLog(Format('Animated texture file "%s" has invalid INI', [RecName]), TMsgType.Warning);
1196 BadTextNameHash.put(RecName, -1);
1197 exit;
1198 end;
1200 cfg := TConfig.CreateMem(TextData, ResLength);
1202 TextureResource := cfg.ReadStr('', 'resource', '');
1203 if TextureResource = '' then
1204 begin
1205 e_WriteLog(Format('Animated texture WAD file "%s" has no "resource"', [RecName]), TMsgType.Warning);
1206 BadTextNameHash.put(RecName, -1);
1207 exit;
1208 end;
1210 _width := cfg.ReadInt('', 'framewidth', 0);
1211 _height := cfg.ReadInt('', 'frameheight', 0);
1212 _framecount := cfg.ReadInt('', 'framecount', 0);
1213 _speed := cfg.ReadInt('', 'waitcount', 0);
1214 _backanimation := cfg.ReadBool('', 'backanimation', False);
1216 cfg.Free();
1217 cfg := nil;
1219 // ×èòàåì ðåñóðñ òåêñòóð (êàäðîâ) àíèì. òåêñòóðû â ïàìÿòü:
1220 if not WAD.GetResource('TEXTURES/'+TextureResource, TextureData, ResLength) then
1221 begin
1222 e_WriteLog(Format('Animated texture WAD file "%s" has no texture "%s"', [RecName, 'TEXTURES/'+TextureResource]), TMsgType.Warning);
1223 BadTextNameHash.put(RecName, -1);
1224 exit;
1225 end;
1227 WAD.Free();
1228 WAD := nil;
1230 SetLength(Textures, Length(Textures)+1);
1231 with Textures[High(Textures)] do
1232 begin
1233 // Ñîçäàåì êàäðû àíèì. òåêñòóðû èç ïàìÿòè:
1234 if g_Frames_CreateMemory(@FramesID, '', TextureData, ResLength, _width, _height, _framecount, _backanimation) then
1235 begin
1236 TextureName := RecName;
1237 Width := _width;
1238 Height := _height;
1239 Anim := True;
1240 FramesCount := _framecount;
1241 Speed := _speed;
1242 result := High(Textures);
1243 TextNameHash.put(RecName, result);
1244 end
1245 else
1246 begin
1247 if (BadTextNameHash = nil) then BadTextNameHash := THashStrInt.Create();
1248 if log and (not BadTextNameHash.get(RecName, f)) then
1249 begin
1250 e_WriteLog(Format('Error loading animation texture %s', [RecName]), TMsgType.Warning);
1251 end;
1252 BadTextNameHash.put(RecName, -1);
1253 end;
1254 end;
1255 end
1256 else
1257 begin
1258 // try animated image
1260 imgfmt := DetermineMemoryFormat(TextureWAD, ResLength);
1261 if length(imgfmt) = 0 then
1262 begin
1263 e_WriteLog(Format('Animated texture file "%s" has unknown format', [RecName]), MSG_WARNING);
1264 exit;
1265 end;
1267 GlobalMetadata.ClearMetaItems();
1268 GlobalMetadata.ClearMetaItemsForSaving();
1269 if not LoadMultiImageFromMemory(TextureWAD, ResLength, ia) then
1270 begin
1271 e_WriteLog(Format('Animated texture file "%s" cannot be loaded', [RecName]), TMsgType.Warning);
1272 BadTextNameHash.put(RecName, -1);
1273 exit;
1274 end;
1275 if length(ia) = 0 then
1276 begin
1277 e_WriteLog(Format('Animated texture file "%s" has no frames', [RecName]), TMsgType.Warning);
1278 BadTextNameHash.put(RecName, -1);
1279 exit;
1280 end;
1282 WAD.Free();
1283 WAD := nil;
1285 _width := ia[0].width;
1286 _height := ia[0].height;
1287 _framecount := length(ia);
1288 _speed := 1;
1289 _backanimation := false;
1290 frdelay := -1;
1291 frloop := -666;
1292 if GlobalMetadata.HasMetaItem(SMetaFrameDelay) then
1293 begin
1294 //writeln(' frame delay: ', GlobalMetadata.MetaItems[SMetaFrameDelay]);
1295 try
1296 f := GlobalMetadata.MetaItems[SMetaFrameDelay];
1297 frdelay := f;
1298 if f < 0 then f := 0;
1299 // rounding ;-)
1300 c := f mod 28;
1301 if c < 13 then c := 0 else c := 1;
1302 f := (f div 28)+c;
1303 if f < 1 then f := 1 else if f > 255 then f := 255;
1304 _speed := f;
1305 except
1306 end;
1307 end;
1308 if GlobalMetadata.HasMetaItem(SMetaAnimationLoops) then
1309 begin
1310 //writeln(' frame loop : ', GlobalMetadata.MetaItems[SMetaAnimationLoops]);
1311 try
1312 f := GlobalMetadata.MetaItems[SMetaAnimationLoops];
1313 frloop := f;
1314 if f <> 0 then _backanimation := true; // non-infinite looping == forth-and-back
1315 except
1316 end;
1317 end;
1318 //writeln(' creating animated texture with ', length(ia), ' frames (delay:', _speed, '; backloop:', _backanimation, ') from "', RecName, '"...');
1319 //for f := 0 to high(ia) do writeln(' frame #', f, ': ', ia[f].width, 'x', ia[f].height);
1320 f := ord(_backanimation);
1321 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);
1323 SetLength(Textures, Length(Textures)+1);
1324 // cîçäàåì êàäðû àíèì. òåêñòóðû èç êàðòèíîê
1325 if g_CreateFramesImg(ia, @Textures[High(Textures)].FramesID, '', _backanimation) then
1326 begin
1327 Textures[High(Textures)].TextureName := RecName;
1328 Textures[High(Textures)].Width := _width;
1329 Textures[High(Textures)].Height := _height;
1330 Textures[High(Textures)].Anim := True;
1331 Textures[High(Textures)].FramesCount := length(ia);
1332 Textures[High(Textures)].Speed := _speed;
1333 result := High(Textures);
1334 TextNameHash.put(RecName, result);
1335 //writeln(' CREATED!');
1336 end
1337 else
1338 begin
1339 if (BadTextNameHash = nil) then BadTextNameHash := THashStrInt.Create();
1340 if log and (not BadTextNameHash.get(RecName, f)) then
1341 begin
1342 e_WriteLog(Format('Error loading animation texture "%s" images', [RecName]), TMsgType.Warning);
1343 end;
1344 BadTextNameHash.put(RecName, -1);
1345 end;
1346 end;
1347 finally
1348 for f := 0 to High(ia) do FreeImage(ia[f]);
1349 WAD.Free();
1350 cfg.Free();
1351 if (TextureWAD <> nil) then FreeMem(TextureWAD);
1352 if (TextData <> nil) then FreeMem(TextData);
1353 if (TextureData <> nil) then FreeMem(TextureData);
1354 end;
1355 end;
1357 procedure CreateItem(Item: TDynRecord);
1358 begin
1359 if g_Game_IsClient then Exit;
1361 if (not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) and
1362 ByteBool(Item.Options and ITEM_OPTION_ONLYDM) then
1363 Exit;
1365 g_Items_Create(Item.X, Item.Y, Item.ItemType, ByteBool(Item.Options and ITEM_OPTION_FALL),
1366 gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF, GM_COOP]);
1367 end;
1369 procedure CreateArea(Area: TDynRecord);
1370 var
1371 a: Integer;
1372 id: DWORD = 0;
1373 begin
1374 case Area.AreaType of
1375 AREA_DMPOINT, AREA_PLAYERPOINT1, AREA_PLAYERPOINT2,
1376 AREA_REDTEAMPOINT, AREA_BLUETEAMPOINT:
1377 begin
1378 SetLength(RespawnPoints, Length(RespawnPoints)+1);
1379 with RespawnPoints[High(RespawnPoints)] do
1380 begin
1381 X := Area.X;
1382 Y := Area.Y;
1383 Direction := TDirection(Area.Direction);
1385 case Area.AreaType of
1386 AREA_DMPOINT: PointType := RESPAWNPOINT_DM;
1387 AREA_PLAYERPOINT1: PointType := RESPAWNPOINT_PLAYER1;
1388 AREA_PLAYERPOINT2: PointType := RESPAWNPOINT_PLAYER2;
1389 AREA_REDTEAMPOINT: PointType := RESPAWNPOINT_RED;
1390 AREA_BLUETEAMPOINT: PointType := RESPAWNPOINT_BLUE;
1391 end;
1392 end;
1393 end;
1395 AREA_REDFLAG, AREA_BLUEFLAG:
1396 begin
1397 if Area.AreaType = AREA_REDFLAG then a := FLAG_RED else a := FLAG_BLUE;
1399 if FlagPoints[a] <> nil then Exit;
1401 New(FlagPoints[a]);
1403 with FlagPoints[a]^ do
1404 begin
1405 X := Area.X-FLAGRECT.X;
1406 Y := Area.Y-FLAGRECT.Y;
1407 Direction := TDirection(Area.Direction);
1408 end;
1410 with gFlags[a] do
1411 begin
1412 case a of
1413 FLAG_RED: g_Frames_Get(id, 'FRAMES_FLAG_RED');
1414 FLAG_BLUE: g_Frames_Get(id, 'FRAMES_FLAG_BLUE');
1415 end;
1417 Animation := TAnimation.Create(id, True, 8);
1418 Obj.Rect := FLAGRECT;
1420 g_Map_ResetFlag(a);
1421 end;
1422 end;
1424 AREA_DOMFLAG:
1425 begin
1426 {SetLength(DOMFlagPoints, Length(DOMFlagPoints)+1);
1427 with DOMFlagPoints[High(DOMFlagPoints)] do
1428 begin
1429 X := Area.X;
1430 Y := Area.Y;
1431 Direction := TDirection(Area.Direction);
1432 end;
1434 g_Map_CreateFlag(DOMFlagPoints[High(DOMFlagPoints)], FLAG_DOM, FLAG_STATE_NORMAL);}
1435 end;
1436 end;
1437 end;
1439 function CreateTrigger (amapIdx: Integer; Trigger: TDynRecord; atpanid, atrigpanid: Integer): Integer;
1440 var
1441 _trigger: TTrigger;
1442 tp: TPanel;
1443 begin
1444 result := -1;
1445 if g_Game_IsClient and not (Trigger.TriggerType in [TRIGGER_SOUND, TRIGGER_MUSIC]) then Exit;
1447 with _trigger do
1448 begin
1449 mapId := Trigger.id;
1450 mapIndex := amapIdx;
1451 X := Trigger.X;
1452 Y := Trigger.Y;
1453 Width := Trigger.Width;
1454 Height := Trigger.Height;
1455 Enabled := Trigger.Enabled;
1456 TexturePanelGUID := atpanid;
1457 TriggerType := Trigger.TriggerType;
1458 ActivateType := Trigger.ActivateType;
1459 Keys := Trigger.Keys;
1460 trigPanelGUID := atrigpanid;
1461 // HACK: used in TPanel.CanChangeTexture. maybe there's a better way?
1462 if TexturePanelGUID <> -1 then
1463 begin
1464 tp := g_Map_PanelByGUID(TexturePanelGUID);
1465 if (tp <> nil) then tp.hasTexTrigger := True;
1466 end;
1467 end;
1469 result := Integer(g_Triggers_Create(_trigger, Trigger));
1470 end;
1472 procedure CreateMonster(monster: TDynRecord);
1473 var
1474 a: Integer;
1475 mon: TMonster;
1476 begin
1477 if g_Game_IsClient then Exit;
1479 if (gGameSettings.GameType = GT_SINGLE)
1480 or LongBool(gGameSettings.Options and GAME_OPTION_MONSTERS) then
1481 begin
1482 mon := g_Monsters_Create(monster.MonsterType, monster.X, monster.Y, TDirection(monster.Direction));
1484 if gTriggers <> nil then
1485 begin
1486 for a := 0 to High(gTriggers) do
1487 begin
1488 if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
1489 begin
1490 //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
1491 if (gTriggers[a].trigDataRec.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
1492 end;
1493 end;
1494 end;
1496 if monster.MonsterType <> MONSTER_BARREL then Inc(gTotalMonsters);
1497 end;
1498 end;
1500 procedure g_Map_ReAdd_DieTriggers();
1502 function monsDieTrig (mon: TMonster): Boolean;
1503 var
1504 a: Integer;
1505 //tw: TStrTextWriter;
1506 begin
1507 result := false; // don't stop
1508 mon.ClearTriggers();
1509 for a := 0 to High(gTriggers) do
1510 begin
1511 if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
1512 begin
1513 //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
1515 tw := TStrTextWriter.Create();
1516 try
1517 gTriggers[a].trigData.writeTo(tw);
1518 e_LogWritefln('=== trigger #%s ==='#10'%s'#10'---', [a, tw.str]);
1519 finally
1520 tw.Free();
1521 end;
1523 if (gTriggers[a].trigDataRec.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
1524 end;
1525 end;
1526 end;
1528 begin
1529 if g_Game_IsClient then Exit;
1531 g_Mons_ForEach(monsDieTrig);
1532 end;
1534 procedure mapCreateGrid ();
1535 var
1536 mapX0: Integer = $3fffffff;
1537 mapY0: Integer = $3fffffff;
1538 mapX1: Integer = -$3fffffff;
1539 mapY1: Integer = -$3fffffff;
1541 procedure calcBoundingBox (constref panels: TPanelArray);
1542 var
1543 idx: Integer;
1544 pan: TPanel;
1545 begin
1546 for idx := 0 to High(panels) do
1547 begin
1548 pan := panels[idx];
1549 if not pan.visvalid then continue;
1550 if (pan.Width < 1) or (pan.Height < 1) then continue;
1551 if (mapX0 > pan.x0) then mapX0 := pan.x0;
1552 if (mapY0 > pan.y0) then mapY0 := pan.y0;
1553 if (mapX1 < pan.x1) then mapX1 := pan.x1;
1554 if (mapY1 < pan.y1) then mapY1 := pan.y1;
1555 end;
1556 end;
1558 procedure addPanelsToGrid (constref panels: TPanelArray);
1559 var
1560 idx: Integer;
1561 pan: TPanel;
1562 newtag: Integer;
1563 begin
1564 //tag := panelTypeToTag(tag);
1565 for idx := 0 to High(panels) do
1566 begin
1567 pan := panels[idx];
1568 if not pan.visvalid then continue;
1569 if (pan.proxyId <> -1) then
1570 begin
1571 {$IF DEFINED(D2F_DEBUG)}
1572 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);
1573 {$ENDIF}
1574 continue;
1575 end;
1576 case pan.PanelType of
1577 PANEL_WALL: newtag := GridTagWall;
1578 PANEL_OPENDOOR, PANEL_CLOSEDOOR: newtag := GridTagDoor;
1579 PANEL_BACK: newtag := GridTagBack;
1580 PANEL_FORE: newtag := GridTagFore;
1581 PANEL_WATER: newtag := GridTagWater;
1582 PANEL_ACID1: newtag := GridTagAcid1;
1583 PANEL_ACID2: newtag := GridTagAcid2;
1584 PANEL_STEP: newtag := GridTagStep;
1585 PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: newtag := GridTagLift;
1586 PANEL_BLOCKMON: newtag := GridTagBlockMon;
1587 else continue; // oops
1588 end;
1589 pan.tag := newtag;
1591 pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, newtag);
1592 // "enabled" flag has meaning only for doors and walls (engine assumes it); but meh...
1593 mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
1594 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
1596 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
1597 begin
1598 e_WriteLog(Format('INSERTED wall #%d(%d) enabled (%d)', [Integer(idx), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId])]), MSG_NOTIFY);
1599 end;
1601 {$ENDIF}
1602 end;
1603 end;
1605 begin
1606 mapGrid.Free();
1607 mapGrid := nil;
1609 calcBoundingBox(gWalls);
1610 calcBoundingBox(gRenderBackgrounds);
1611 calcBoundingBox(gRenderForegrounds);
1612 calcBoundingBox(gWater);
1613 calcBoundingBox(gAcid1);
1614 calcBoundingBox(gAcid2);
1615 calcBoundingBox(gSteps);
1616 calcBoundingBox(gLifts);
1617 calcBoundingBox(gBlockMon);
1619 e_LogWritefln('map dimensions: (%d,%d)-(%d,%d); editor size:(0,0)-(%d,%d)', [mapX0, mapY0, mapX1, mapY1, gMapInfo.Width, gMapInfo.Height]);
1621 if (mapX0 > 0) then mapX0 := 0;
1622 if (mapY0 > 0) then mapY0 := 0;
1624 if (mapX1 < gMapInfo.Width-1) then mapX1 := gMapInfo.Width-1;
1625 if (mapY1 < gMapInfo.Height-1) then mapY1 := gMapInfo.Height-1;
1627 mapGrid := TPanelGrid.Create(mapX0-128, mapY0-128, mapX1-mapX0+1+128*2, mapY1-mapY0+1+128*2);
1628 //mapGrid := TPanelGrid.Create(0, 0, gMapInfo.Width, gMapInfo.Height);
1630 addPanelsToGrid(gWalls);
1631 addPanelsToGrid(gRenderBackgrounds);
1632 addPanelsToGrid(gRenderForegrounds);
1633 addPanelsToGrid(gWater);
1634 addPanelsToGrid(gAcid1);
1635 addPanelsToGrid(gAcid2);
1636 addPanelsToGrid(gSteps);
1637 addPanelsToGrid(gLifts); // it doesn't matter which LIFT type is used here
1638 addPanelsToGrid(gBlockMon);
1640 mapGrid.dumpStats();
1642 g_Mons_InitTree(mapGrid.gridX0, mapGrid.gridY0, mapGrid.gridWidth, mapGrid.gridHeight);
1643 end;
1646 function g_Map_Load(Res: String): Boolean;
1647 const
1648 DefaultMusRes = 'Standart.wad:STDMUS\MUS1';
1649 DefaultSkyRes = 'Standart.wad:STDSKY\SKY0';
1650 type
1651 PTRec = ^TTRec;
1652 TTRec = record
1653 //TexturePanel: Integer;
1654 tnum: Integer;
1655 id: Integer;
1656 trigrec: TDynRecord;
1657 // texture pane;
1658 texPanelIdx: Integer;
1659 texPanel: TDynRecord;
1660 // "action" panel
1661 actPanelIdx: Integer;
1662 actPanel: TDynRecord;
1663 end;
1664 var
1665 WAD, TestWAD: TWADFile;
1666 //mapReader: TDynRecord = nil;
1667 mapTextureList: TDynField = nil; //TTexturesRec1Array; tagInt: texture index
1668 panels: TDynField = nil; //TPanelsRec1Array;
1669 items: TDynField = nil; //TItemsRec1Array;
1670 monsters: TDynField = nil; //TMonsterRec1Array;
1671 areas: TDynField = nil; //TAreasRec1Array;
1672 triggers: TDynField = nil; //TTriggersRec1Array;
1673 b, c, k: Integer;
1674 PanelID: DWORD;
1675 AddTextures: TAddTextureArray;
1676 TriggersTable: array of TTRec;
1677 FileName, mapResName, TexName, s: AnsiString;
1678 Data: Pointer;
1679 Len: Integer;
1680 ok, isAnim: Boolean;
1681 CurTex, ntn: Integer;
1682 rec, texrec: TDynRecord;
1683 pttit: PTRec;
1684 pannum, trignum, cnt, tgpid: Integer;
1685 stt: UInt64;
1686 moveSpeed{, moveStart, moveEnd}: TDFPoint;
1687 //moveActive: Boolean;
1688 pan: TPanel;
1689 mapOk: Boolean = false;
1690 usedTextures: THashStrInt = nil; // key: mapTextureList
1691 begin
1692 mapGrid.Free();
1693 mapGrid := nil;
1694 TestWAD := nil;
1695 Data := nil;
1697 //gCurrentMap.Free();
1698 //gCurrentMap := nil;
1700 panByGUID := nil;
1702 Result := False;
1703 gMapInfo.Map := Res;
1704 TriggersTable := nil;
1705 //mapReader := nil;
1707 sfsGCDisable(); // temporary disable removing of temporary volumes
1708 try
1709 // Çàãðóçêà WAD (åñëè ó íàñ íåò óæå çàãðóæåíîé êàðòû)
1710 if (gCurrentMap = nil) then
1711 begin
1712 FileName := g_ExtractWadName(Res);
1713 e_LogWritefln('Loading map WAD [%s] (res=[%s])', [FileName, Res], TMsgType.Notify);
1714 g_Game_SetLoadingText(_lc[I_LOAD_WAD_FILE], 0, False);
1716 WAD := TWADFile.Create();
1717 if not WAD.ReadFile(FileName) then
1718 begin
1719 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_WAD], [FileName]));
1720 WAD.Free();
1721 Exit;
1722 end;
1724 if gTestMap <> '' then
1725 begin
1726 s := g_ExtractWadName(gTestMap);
1727 TestWAD := TWADFile.Create();
1728 if not TestWAD.ReadFile(s) then
1729 begin
1730 g_SimpleError(Format(_lc[I_GAME_ERROR_MAP_WAD], [s]));
1731 TestWAD.Free();
1732 TestWAD := nil;
1733 end;
1734 end;
1736 if TestWAD <> nil then
1737 begin
1738 mapResName := g_ExtractFileName(gTestMap);
1739 if not TestWAD.GetMapResource(mapResName, Data, Len) then
1740 begin
1741 g_SimpleError(Format(_lc[I_GAME_ERROR_MAP_RES], [mapResName]));
1742 Data := nil;
1743 end else
1744 e_WriteLog('Using test map: '+gTestMap, TMsgType.Notify);
1745 TestWAD.Free();
1746 TestWAD := nil;
1747 end;
1749 if Data = nil then
1750 begin
1751 //k8: why loader ignores path here?
1752 mapResName := g_ExtractFileName(Res);
1753 if not WAD.GetMapResource(mapResName, Data, Len) then
1754 begin
1755 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_RES], [mapResName]));
1756 WAD.Free();
1757 Exit;
1758 end;
1759 end;
1761 WAD.Free();
1763 if (Len < 4) then
1764 begin
1765 e_LogWritefln('invalid map file: ''%s''', [mapResName]);
1766 FreeMem(Data);
1767 exit;
1768 end;
1770 // Çàãðóçêà êàðòû:
1771 e_LogWritefln('Loading map: %s', [mapResName], TMsgType.Notify);
1772 g_Game_SetLoadingText(_lc[I_LOAD_MAP], 0, False);
1774 stt := getTimeMicro();
1776 try
1777 gCurrentMap := g_Map_ParseMap(Data, Len);
1778 except
1779 gCurrentMap.Free();
1780 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]));
1781 FreeMem(Data);
1782 gCurrentMapFileName := '';
1783 Exit;
1784 end;
1786 FreeMem(Data);
1788 if (gCurrentMap = nil) then
1789 begin
1790 e_LogWritefln('invalid map file: ''%s''', [mapResName]);
1791 gCurrentMapFileName := '';
1792 exit;
1793 end;
1794 end
1795 else
1796 begin
1797 stt := getTimeMicro();
1798 end;
1800 //gCurrentMap := mapReader;
1802 generateExternalResourcesList(gCurrentMap);
1803 mapTextureList := gCurrentMap['texture'];
1804 // get all other lists here too
1805 panels := gCurrentMap['panel'];
1806 triggers := gCurrentMap['trigger'];
1807 items := gCurrentMap['item'];
1808 areas := gCurrentMap['area'];
1809 monsters := gCurrentMap['monster'];
1811 // Çàãðóçêà îïèñàíèÿ êàðòû:
1812 e_WriteLog(' Reading map info...', TMsgType.Notify);
1813 g_Game_SetLoadingText(_lc[I_LOAD_MAP_HEADER], 0, False);
1815 with gMapInfo do
1816 begin
1817 Name := gCurrentMap.MapName;
1818 Description := gCurrentMap.MapDesc;
1819 Author := gCurrentMap.MapAuthor;
1820 MusicName := gCurrentMap.MusicName;
1821 SkyName := gCurrentMap.SkyName;
1822 Height := gCurrentMap.Height;
1823 Width := gCurrentMap.Width;
1824 end;
1826 // Çàãðóçêà òåêñòóð:
1827 g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], 0, False);
1828 // Äîáàâëåíèå òåêñòóð â Textures[]:
1829 if (mapTextureList <> nil) and (mapTextureList.count > 0) then
1830 begin
1831 e_WriteLog(' Loading textures:', TMsgType.Notify);
1832 g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], mapTextureList.count-1, False);
1834 // find used textures
1835 usedTextures := THashStrInt.Create();
1836 try
1837 if (panels <> nil) and (panels.count > 0) then
1838 begin
1839 for rec in panels do
1840 begin
1841 texrec := rec.TextureRec;
1842 if (texrec <> nil) then usedTextures.put(toLowerCase1251(texrec.Resource), 42);
1843 end;
1844 end;
1846 cnt := -1;
1847 for rec in mapTextureList do
1848 begin
1849 Inc(cnt);
1850 if not usedTextures.has(toLowerCase1251(rec.Resource)) then
1851 begin
1852 rec.tagInt := -1; // just in case
1853 e_LogWritefln(' Unused texture #%d: %s', [cnt, rec.Resource]);
1854 end
1855 else
1856 begin
1857 {$IF DEFINED(D2F_DEBUG_TXLOAD)}
1858 e_LogWritefln(' Loading texture #%d: %s', [cnt, rec.Resource]);
1859 {$ENDIF}
1860 //if g_Map_IsSpecialTexture(s) then e_WriteLog(' SPECIAL!', MSG_NOTIFY);
1861 if rec.Anim then
1862 begin
1863 // Àíèìèðîâàííàÿ òåêñòóðà
1864 ntn := CreateAnimTexture(rec.Resource, FileName, True);
1865 if (ntn < 0) then g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_ANIM], [rec.Resource]));
1866 end
1867 else
1868 begin
1869 // Îáû÷íàÿ òåêñòóðà
1870 ntn := CreateTexture(rec.Resource, FileName, True);
1871 if (ntn < 0) then g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_SIMPLE], [rec.Resource]));
1872 end;
1873 if (ntn < 0) then ntn := CreateNullTexture(rec.Resource);
1875 rec.tagInt := ntn; // remember texture number
1876 end;
1877 g_Game_StepLoading();
1878 end;
1879 finally
1880 usedTextures.Free();
1881 end;
1883 // set panel tagInt to texture index
1884 if (panels <> nil) then
1885 begin
1886 for rec in panels do
1887 begin
1888 texrec := rec.TextureRec;
1889 if (texrec = nil) then rec.tagInt := -1 else rec.tagInt := texrec.tagInt;
1890 end;
1891 end;
1892 end;
1895 // Çàãðóçêà òðèããåðîâ
1896 gTriggerClientID := 0;
1897 e_WriteLog(' Loading triggers...', TMsgType.Notify);
1898 g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS], 0, False);
1900 // Çàãðóçêà ïàíåëåé
1901 e_WriteLog(' Loading panels...', TMsgType.Notify);
1902 g_Game_SetLoadingText(_lc[I_LOAD_PANELS], 0, False);
1904 // check texture numbers for panels
1905 if (panels <> nil) and (panels.count > 0) then
1906 begin
1907 for rec in panels do
1908 begin
1909 if (rec.tagInt < 0) then
1910 begin
1911 e_WriteLog('error loading map: invalid texture index for panel', TMsgType.Fatal);
1912 result := false;
1913 gCurrentMap.Free();
1914 gCurrentMap := nil;
1915 gCurrentMapFileName := '';
1916 exit;
1917 end;
1918 end;
1919 end;
1921 // Ñîçäàíèå òàáëèöû òðèããåðîâ (ñîîòâåòñòâèå ïàíåëåé òðèããåðàì)
1922 if (triggers <> nil) and (triggers.count > 0) then
1923 begin
1924 e_WriteLog(' Setting up trigger table...', TMsgType.Notify);
1925 //SetLength(TriggersTable, triggers.count);
1926 g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS_TABLE], triggers.count-1, False);
1928 SetLength(TriggersTable, triggers.count);
1929 trignum := -1;
1930 for rec in triggers do
1931 begin
1932 Inc(trignum);
1933 pttit := @TriggersTable[trignum];
1934 pttit.trigrec := rec;
1935 // Ñìåíà òåêñòóðû (âîçìîæíî, êíîïêè)
1936 pttit.texPanelIdx := -1; // will be fixed later
1937 pttit.texPanel := rec.TexturePanelRec;
1938 // action panel
1939 pttit.actPanelIdx := -1;
1940 if (rec.trigRec <> nil) then pttit.actPanel := rec.trigRec.tgPanelRec else pttit.actPanel := nil;
1941 // set flag
1942 if (pttit.texPanel <> nil) then pttit.texPanel.userPanelTrigRef := true;
1943 if (pttit.actPanel <> nil) then pttit.actPanel.userPanelTrigRef := true;
1944 // update progress
1945 g_Game_StepLoading();
1946 end;
1947 end;
1949 // Ñîçäàåì ïàíåëè
1950 if (panels <> nil) and (panels.count > 0) then
1951 begin
1952 e_WriteLog(' Setting up trigger links...', TMsgType.Notify);
1953 g_Game_SetLoadingText(_lc[I_LOAD_LINK_TRIGGERS], panels.count-1, False);
1955 pannum := -1;
1956 for rec in panels do
1957 begin
1958 Inc(pannum);
1959 //e_LogWritefln('PANSTART: pannum=%s', [pannum]);
1960 texrec := nil;
1961 SetLength(AddTextures, 0);
1962 CurTex := -1;
1963 ok := false;
1965 if (mapTextureList <> nil) then
1966 begin
1967 texrec := rec.TextureRec;
1968 ok := (texrec <> nil);
1969 end;
1971 if ok then
1972 begin
1973 // Ñìîòðèì, ññûëàþòñÿ ëè íà ýòó ïàíåëü òðèããåðû.
1974 // Åñëè äà - òî íàäî ñîçäàòü åùå òåêñòóð
1975 ok := false;
1976 if (TriggersTable <> nil) and (mapTextureList <> nil) then
1977 begin
1978 if rec.userPanelTrigRef then
1979 begin
1980 // e_LogWritefln('trigref for panel %s', [pannum]);
1981 ok := True;
1982 end;
1983 end;
1984 end;
1986 if ok then
1987 begin
1988 // Åñòü ññûëêè òðèããåðîâ íà ýòó ïàíåëü
1989 s := texrec.Resource;
1991 // Ñïåö-òåêñòóðû çàïðåùåíû
1992 if g_Map_IsSpecialTexture(s) then
1993 begin
1994 ok := false
1995 end
1996 else
1997 begin
1998 // Îïðåäåëÿåì íàëè÷èå è ïîëîæåíèå öèôð â êîíöå ñòðîêè
1999 ok := g_Texture_NumNameFindStart(s);
2000 end;
2002 // Åñëè ok, çíà÷èò åñòü öèôðû â êîíöå.
2003 // Çàãðóæàåì òåêñòóðû ñ îñòàëüíûìè #
2004 if ok then
2005 begin
2006 k := NNF_NAME_BEFORE;
2007 // Öèêë ïî èçìåíåíèþ èìåíè òåêñòóðû
2008 while ok or (k = NNF_NAME_BEFORE) or (k = NNF_NAME_EQUALS) do
2009 begin
2010 k := g_Texture_NumNameFindNext(TexName);
2012 if (k = NNF_NAME_BEFORE) or (k = NNF_NAME_AFTER) then
2013 begin
2014 // Ïðîáóåì äîáàâèòü íîâóþ òåêñòóðó
2015 if texrec.Anim then
2016 begin
2017 // Íà÷àëüíàÿ - àíèìèðîâàííàÿ, èùåì àíèìèðîâàííóþ
2018 isAnim := True;
2019 //e_LogWritefln('000: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
2020 ok := CreateAnimTexture(TexName, FileName, False) >= 0;
2021 //e_LogWritefln('001: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
2022 if not ok then
2023 begin
2024 // Íåò àíèìèðîâàííîé, èùåì îáû÷íóþ
2025 isAnim := False;
2026 //e_LogWritefln('002: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
2027 ok := CreateTexture(TexName, FileName, False) >= 0;
2028 //e_LogWritefln('003: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
2029 end;
2030 end
2031 else
2032 begin
2033 // Íà÷àëüíàÿ - îáû÷íàÿ, èùåì îáû÷íóþ
2034 isAnim := False;
2035 //e_LogWritefln('004: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
2036 ok := CreateTexture(TexName, FileName, False) >= 0;
2037 //e_LogWritefln('005: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
2038 if not ok then
2039 begin
2040 // Íåò îáû÷íîé, èùåì àíèìèðîâàííóþ
2041 isAnim := True;
2042 //e_LogWritefln('006: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
2043 ok := CreateAnimTexture(TexName, FileName, False) >= 0;
2044 //e_LogWritefln('007: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
2045 end;
2046 end;
2048 // Îíà ñóùåñòâóåò. Çàíîñèì åå ID â ñïèñîê ïàíåëè
2049 if ok then
2050 begin
2052 for c := 0 to High(Textures) do
2053 begin
2054 if (Textures[c].TextureName = TexName) then
2055 begin
2056 SetLength(AddTextures, Length(AddTextures)+1);
2057 AddTextures[High(AddTextures)].Texture := c;
2058 AddTextures[High(AddTextures)].Anim := isAnim;
2059 break;
2060 end;
2061 end;
2063 if (TextNameHash <> nil) and TextNameHash.get(toLowerCase1251(TexName), c) then
2064 begin
2065 SetLength(AddTextures, Length(AddTextures)+1);
2066 AddTextures[High(AddTextures)].Texture := c;
2067 AddTextures[High(AddTextures)].Anim := isAnim;
2068 end;
2069 end;
2070 end
2071 else
2072 begin
2073 if k = NNF_NAME_EQUALS then
2074 begin
2075 // Çàíîñèì òåêóùóþ òåêñòóðó íà ñâîå ìåñòî
2076 SetLength(AddTextures, Length(AddTextures)+1);
2077 AddTextures[High(AddTextures)].Texture := rec.tagInt; // internal texture number, not map index
2078 AddTextures[High(AddTextures)].Anim := texrec.Anim;
2079 CurTex := High(AddTextures);
2080 ok := true;
2081 end
2082 else // NNF_NO_NAME
2083 begin
2084 ok := false;
2085 end;
2086 end;
2087 end; // while ok...
2089 ok := true;
2090 end; // if ok - åñòü ñìåæíûå òåêñòóðû
2091 end; // if ok - ññûëàþòñÿ òðèããåðû
2093 if not ok then
2094 begin
2095 // Çàíîñèì òîëüêî òåêóùóþ òåêñòóðó
2096 SetLength(AddTextures, 1);
2097 AddTextures[0].Texture := rec.tagInt; // internal texture number, not map index
2098 AddTextures[0].Anim := false;
2099 if (texrec <> nil) then AddTextures[0].Anim := texrec.Anim;
2100 CurTex := 0;
2101 end;
2103 //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);
2105 //e_LogWritefln('PANADD: pannum=%s', [pannum]);
2107 // Ñîçäàåì ïàíåëü è çàïîìèíàåì åå GUID
2108 //e_LogWritefln('new panel; tcount=%s; curtex=%s', [Length(AddTextures), CurTex]);
2109 PanelID := CreatePanel(rec, AddTextures, CurTex);
2110 //e_LogWritefln('panel #%s of type %s got guid #%s', [pannum, rec.PanelType, PanelID]);
2111 rec.userPanelId := PanelID; // remember game panel id, we'll fix triggers later
2113 // setup lifts
2114 moveSpeed := rec.moveSpeed;
2115 //moveStart := rec.moveStart;
2116 //moveEnd := rec.moveEnd;
2117 //moveActive := rec['move_active'].value;
2118 if not moveSpeed.isZero then
2119 begin
2120 SetLength(gMovingWallIds, Length(gMovingWallIds)+1);
2121 gMovingWallIds[High(gMovingWallIds)] := PanelID;
2122 //e_LogWritefln('found moving panel ''%s'' (idx=%s; id=%s)', [rec.id, pannum, PanelID]);
2123 end;
2125 //e_LogWritefln('PANEND: pannum=%s', [pannum]);
2127 g_Game_StepLoading();
2128 end;
2129 end;
2131 // ×èíèì ID'û ïàíåëåé, êîòîðûå èñïîëüçóþòñÿ â òðèããåðàõ
2132 for b := 0 to High(TriggersTable) do
2133 begin
2134 if (TriggersTable[b].texPanel <> nil) then TriggersTable[b].texPanelIdx := TriggersTable[b].texPanel.userPanelId;
2135 if (TriggersTable[b].actPanel <> nil) then TriggersTable[b].actPanelIdx := TriggersTable[b].actPanel.userPanelId;
2136 end;
2138 // create map grid, init other grids (for monsters, for example)
2139 e_WriteLog('Creating map grid', TMsgType.Notify);
2140 mapCreateGrid();
2142 // Åñëè íå LoadState, òî ñîçäàåì òðèããåðû
2143 if (triggers <> nil) and (panels <> nil) and (not gLoadGameMode) then
2144 begin
2145 e_LogWritefln(' Creating triggers (%d)...', [triggers.count]);
2146 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_TRIGGERS], 0, False);
2147 // Óêàçûâàåì òèï ïàíåëè, åñëè åñòü
2148 trignum := -1;
2149 for rec in triggers do
2150 begin
2151 Inc(trignum);
2152 tgpid := TriggersTable[trignum].actPanelIdx;
2153 //e_LogWritefln('creating trigger #%s; texpantype=%s; shotpantype=%s (%d,%d)', [trignum, b, c, TriggersTable[trignum].texPanIdx, TriggersTable[trignum].ShotPanelIdx]);
2154 TriggersTable[trignum].tnum := trignum;
2155 TriggersTable[trignum].id := CreateTrigger(trignum, rec, TriggersTable[trignum].texPanelIdx, tgpid);
2156 end;
2157 end;
2159 //FIXME: use hashtable!
2160 for pan in panByGUID do
2161 begin
2162 if (pan.endPosTrigId >= 0) and (pan.endPosTrigId < Length(TriggersTable)) then
2163 begin
2164 pan.endPosTrigId := TriggersTable[pan.endPosTrigId].id;
2165 end;
2166 if (pan.endSizeTrigId >= 0) and (pan.endSizeTrigId < Length(TriggersTable)) then
2167 begin
2168 pan.endSizeTrigId := TriggersTable[pan.endSizeTrigId].id;
2169 end;
2170 end;
2172 // Çàãðóçêà ïðåäìåòîâ
2173 e_WriteLog(' Loading items...', TMsgType.Notify);
2174 g_Game_SetLoadingText(_lc[I_LOAD_ITEMS], 0, False);
2176 // Åñëè íå LoadState, òî ñîçäàåì ïðåäìåòû
2177 if (items <> nil) and not gLoadGameMode then
2178 begin
2179 e_WriteLog(' Spawning items...', TMsgType.Notify);
2180 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_ITEMS], 0, False);
2181 for rec in items do CreateItem(rec);
2182 end;
2184 // Çàãðóçêà îáëàñòåé
2185 e_WriteLog(' Loading areas...', TMsgType.Notify);
2186 g_Game_SetLoadingText(_lc[I_LOAD_AREAS], 0, False);
2188 // Åñëè íå LoadState, òî ñîçäàåì îáëàñòè
2189 if areas <> nil then
2190 begin
2191 e_WriteLog(' Creating areas...', TMsgType.Notify);
2192 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_AREAS], 0, False);
2193 for rec in areas do CreateArea(rec);
2194 end;
2196 // Çàãðóçêà ìîíñòðîâ
2197 e_WriteLog(' Loading monsters...', TMsgType.Notify);
2198 g_Game_SetLoadingText(_lc[I_LOAD_MONSTERS], 0, False);
2200 gTotalMonsters := 0;
2202 // Åñëè íå LoadState, òî ñîçäàåì ìîíñòðîâ
2203 if (monsters <> nil) and not gLoadGameMode then
2204 begin
2205 e_WriteLog(' Spawning monsters...', TMsgType.Notify);
2206 g_Game_SetLoadingText(_lc[I_LOAD_CREATE_MONSTERS], 0, False);
2207 for rec in monsters do CreateMonster(rec);
2208 end;
2210 //gCurrentMap := mapReader; // this will be our current map now
2211 gCurrentMapFileName := Res;
2212 //mapReader := nil;
2214 // Çàãðóçêà íåáà
2215 if (gMapInfo.SkyName <> '') then
2216 begin
2217 e_WriteLog(' Loading sky: ' + gMapInfo.SkyName, TMsgType.Notify);
2218 g_Game_SetLoadingText(_lc[I_LOAD_SKY], 0, False);
2219 if gTextureFilter then TEXTUREFILTER := GL_LINEAR else TEXTUREFILTER := GL_NEAREST;
2220 try
2221 s := e_GetResourcePath(WadDirs, gMapInfo.SkyName, g_ExtractWadName(Res));
2222 if g_Texture_CreateWAD(BackID, s) then
2223 g_Game_SetupScreenSize
2224 else
2225 g_FatalError(Format(_lc[I_GAME_ERROR_SKY], [s]))
2226 finally
2227 TEXTUREFILTER := GL_NEAREST;
2228 end;
2229 end;
2231 // Çàãðóçêà ìóçûêè
2232 ok := False;
2233 if gMapInfo.MusicName <> '' then
2234 begin
2235 e_WriteLog(' Loading music: ' + gMapInfo.MusicName, TMsgType.Notify);
2236 g_Game_SetLoadingText(_lc[I_LOAD_MUSIC], 0, False);
2238 s := e_GetResourcePath(WadDirs, gMapInfo.MusicName, g_ExtractWadName(Res));
2239 if g_Sound_CreateWADEx(gMapInfo.MusicName, s, True) then
2240 ok := True
2241 else
2242 g_FatalError(Format(_lc[I_GAME_ERROR_MUSIC], [s]));
2243 end;
2245 // Îñòàëüíûå óñòàíâêè
2246 CreateDoorMap();
2247 CreateLiftMap();
2249 g_Items_Init();
2250 g_Weapon_Init();
2251 g_Monsters_Init();
2253 // Åñëè íå LoadState, òî ñîçäàåì êàðòó ñòîëêíîâåíèé:
2254 if not gLoadGameMode then g_GFX_Init();
2256 // Ñáðîñ ëîêàëüíûõ ìàññèâîâ:
2257 mapTextureList := nil;
2258 panels := nil;
2259 items := nil;
2260 areas := nil;
2261 triggers := nil;
2262 TriggersTable := nil;
2263 AddTextures := nil;
2265 // Âêëþ÷àåì ìóçûêó, åñëè ýòî íå çàãðóçêà:
2266 if ok and (not gLoadGameMode) then
2267 begin
2268 gMusic.SetByName(gMapInfo.MusicName);
2269 gMusic.Play();
2270 end
2271 else
2272 begin
2273 gMusic.SetByName('');
2274 end;
2276 stt := getTimeMicro()-stt;
2277 e_LogWritefln('map loaded in %s.%s milliseconds', [Integer(stt div 1000), Integer(stt mod 1000)]);
2278 mapOk := true;
2279 finally
2280 sfsGCEnable(); // enable releasing unused volumes
2281 //mapReader.Free();
2282 e_UnpressAllKeys; // why not?
2283 if not mapOk then
2284 begin
2285 gCurrentMap.Free();
2286 gCurrentMap := nil;
2287 gCurrentMapFileName := '';
2288 end;
2289 end;
2291 compactExtResList();
2292 e_WriteLog('Done loading map.', TMsgType.Notify);
2293 Result := True;
2294 end;
2297 function g_Map_GetMapInfo(Res: String): TMapInfo;
2298 var
2299 WAD: TWADFile;
2300 mapReader: TDynRecord;
2301 //Header: TMapHeaderRec_1;
2302 FileName: String;
2303 Data: Pointer;
2304 Len: Integer;
2305 begin
2306 FillChar(Result, SizeOf(Result), 0);
2307 FileName := g_ExtractWadName(Res);
2309 WAD := TWADFile.Create();
2310 if not WAD.ReadFile(FileName) then
2311 begin
2312 WAD.Free();
2313 Exit;
2314 end;
2316 //k8: it ignores path again
2317 if not WAD.GetMapResource(g_ExtractFileName(Res), Data, Len) then
2318 begin
2319 WAD.Free();
2320 Exit;
2321 end;
2323 WAD.Free();
2325 try
2326 mapReader := g_Map_ParseMap(Data, Len);
2327 except
2328 mapReader := nil;
2329 FreeMem(Data);
2330 exit;
2331 end;
2333 FreeMem(Data);
2335 if (mapReader = nil) then exit;
2337 if (mapReader.Width > 0) and (mapReader.Height > 0) then
2338 begin
2339 Result.Name := mapReader.MapName;
2340 Result.Description := mapReader.MapDesc;
2341 Result.Map := Res;
2342 Result.Author := mapReader.MapAuthor;
2343 Result.Height := mapReader.Height;
2344 Result.Width := mapReader.Width;
2345 end
2346 else
2347 begin
2348 g_Console_Add(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]), True);
2349 //ZeroMemory(@Header, SizeOf(Header));
2350 Result.Name := _lc[I_GAME_ERROR_MAP_SELECT];
2351 Result.Description := _lc[I_GAME_ERROR_MAP_SELECT];
2352 Result.Map := Res;
2353 Result.Author := '';
2354 Result.Height := 0;
2355 Result.Width := 0;
2356 end;
2358 mapReader.Free();
2359 end;
2361 function g_Map_GetMapsList(WADName: string): SSArray;
2362 var
2363 WAD: TWADFile;
2364 a: Integer;
2365 ResList: SSArray;
2366 begin
2367 Result := nil;
2368 WAD := TWADFile.Create();
2369 if not WAD.ReadFile(WADName) then
2370 begin
2371 WAD.Free();
2372 Exit;
2373 end;
2374 ResList := WAD.GetMapResources();
2375 if ResList <> nil then
2376 begin
2377 for a := 0 to High(ResList) do
2378 begin
2379 SetLength(Result, Length(Result)+1);
2380 Result[High(Result)] := ResList[a];
2381 end;
2382 end;
2383 WAD.Free();
2384 end;
2386 function g_Map_Exist(Res: string): Boolean;
2387 var
2388 WAD: TWADFile;
2389 FileName, mnn: string;
2390 ResList: SSArray;
2391 a: Integer;
2392 begin
2393 Result := False;
2395 FileName := addWadExtension(g_ExtractWadName(Res));
2397 WAD := TWADFile.Create;
2398 if not WAD.ReadFile(FileName) then
2399 begin
2400 WAD.Free();
2401 Exit;
2402 end;
2404 ResList := WAD.GetMapResources();
2405 WAD.Free();
2407 mnn := g_ExtractFileName(Res);
2408 if ResList <> nil then
2409 for a := 0 to High(ResList) do if StrEquCI1251(ResList[a], mnn) then
2410 begin
2411 Result := True;
2412 Exit;
2413 end;
2414 end;
2416 procedure g_Map_Free(freeTextures: Boolean=true);
2417 var
2418 a: Integer;
2420 procedure FreePanelArray(var panels: TPanelArray);
2421 var
2422 i: Integer;
2424 begin
2425 if panels <> nil then
2426 begin
2427 for i := 0 to High(panels) do
2428 panels[i].Free();
2429 panels := nil;
2430 end;
2431 end;
2433 begin
2434 g_GFX_Free();
2435 g_Weapon_Free();
2436 g_Items_Free();
2437 g_Triggers_Free();
2438 g_Monsters_Free();
2440 RespawnPoints := nil;
2441 if FlagPoints[FLAG_RED] <> nil then
2442 begin
2443 Dispose(FlagPoints[FLAG_RED]);
2444 FlagPoints[FLAG_RED] := nil;
2445 end;
2446 if FlagPoints[FLAG_BLUE] <> nil then
2447 begin
2448 Dispose(FlagPoints[FLAG_BLUE]);
2449 FlagPoints[FLAG_BLUE] := nil;
2450 end;
2451 //DOMFlagPoints := nil;
2453 //gDOMFlags := nil;
2455 if (Length(gCurrentMapFileName) <> 0) then
2456 begin
2457 e_LogWritefln('g_Map_Free: previous map was ''%s''...', [gCurrentMapFileName]);
2458 end
2459 else
2460 begin
2461 e_LogWritefln('g_Map_Free: no previous map.', []);
2462 end;
2464 if freeTextures then
2465 begin
2466 e_LogWritefln('g_Map_Free: clearing textures...', []);
2467 if (Textures <> nil) then
2468 begin
2469 for a := 0 to High(Textures) do
2470 begin
2471 if not g_Map_IsSpecialTexture(Textures[a].TextureName) then
2472 begin
2473 if Textures[a].Anim then
2474 begin
2475 g_Frames_DeleteByID(Textures[a].FramesID)
2476 end
2477 else
2478 begin
2479 if (Textures[a].TextureID <> LongWord(TEXTURE_NONE)) then
2480 begin
2481 e_DeleteTexture(Textures[a].TextureID);
2482 end;
2483 end;
2484 end;
2485 end;
2486 Textures := nil;
2487 end;
2488 TextNameHash.Free();
2489 TextNameHash := nil;
2490 BadTextNameHash.Free();
2491 BadTextNameHash := nil;
2492 gCurrentMapFileName := '';
2493 gCurrentMap.Free();
2494 gCurrentMap := nil;
2495 end;
2497 panByGUID := nil;
2499 FreePanelArray(gWalls);
2500 FreePanelArray(gRenderBackgrounds);
2501 FreePanelArray(gRenderForegrounds);
2502 FreePanelArray(gWater);
2503 FreePanelArray(gAcid1);
2504 FreePanelArray(gAcid2);
2505 FreePanelArray(gSteps);
2506 FreePanelArray(gLifts);
2507 FreePanelArray(gBlockMon);
2508 gMovingWallIds := nil;
2510 if BackID <> DWORD(-1) then
2511 begin
2512 gBackSize.X := 0;
2513 gBackSize.Y := 0;
2514 e_DeleteTexture(BackID);
2515 BackID := DWORD(-1);
2516 end;
2518 g_Game_StopAllSounds(False);
2519 gMusic.FreeSound();
2520 g_Sound_Delete(gMapInfo.MusicName);
2522 gMapInfo.Name := '';
2523 gMapInfo.Description := '';
2524 gMapInfo.MusicName := '';
2525 gMapInfo.Height := 0;
2526 gMapInfo.Width := 0;
2528 gDoorMap := nil;
2529 gLiftMap := nil;
2530 end;
2532 procedure g_Map_Update();
2533 var
2534 a, d, j: Integer;
2535 m: Word;
2536 s: String;
2537 b: Byte;
2539 procedure UpdatePanelArray(var panels: TPanelArray);
2540 var
2541 i: Integer;
2543 begin
2544 for i := 0 to High(panels) do panels[i].Update();
2545 end;
2547 begin
2548 if g_dbgpan_mplat_step then g_dbgpan_mplat_active := true;
2550 UpdatePanelArray(gWalls);
2551 UpdatePanelArray(gRenderBackgrounds);
2552 UpdatePanelArray(gRenderForegrounds);
2553 UpdatePanelArray(gWater);
2554 UpdatePanelArray(gAcid1);
2555 UpdatePanelArray(gAcid2);
2556 UpdatePanelArray(gSteps);
2558 if g_dbgpan_mplat_step then begin g_dbgpan_mplat_step := false; g_dbgpan_mplat_active := false; end;
2560 if gGameSettings.GameMode = GM_CTF then
2561 begin
2562 for a := FLAG_RED to FLAG_BLUE do
2563 begin
2564 if not (gFlags[a].State in [FLAG_STATE_NONE, FLAG_STATE_CAPTURED]) then
2565 begin
2566 with gFlags[a] do
2567 begin
2568 if gFlags[a].Animation <> nil then gFlags[a].Animation.Update();
2570 m := g_Obj_Move(@Obj, True, True);
2572 if gTime mod (GAME_TICK*2) <> 0 then Continue;
2574 // Ñîïðîòèâëåíèå âîçäóõà
2575 Obj.Vel.X := z_dec(Obj.Vel.X, 1);
2577 // Òàéìàóò ïîòåðÿííîãî ôëàãà, ëèáî îí âûïàë çà êàðòó
2578 if ((Count = 0) or ByteBool(m and MOVE_FALLOUT)) and g_Game_IsServer then
2579 begin
2580 g_Map_ResetFlag(a);
2581 gFlags[a].CaptureTime := 0;
2582 if a = FLAG_RED then
2583 s := _lc[I_PLAYER_FLAG_RED]
2584 else
2585 s := _lc[I_PLAYER_FLAG_BLUE];
2586 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
2588 if (((gPlayer1 <> nil) and (((gPlayer1.Team = TEAM_RED) and (a = FLAG_RED)) or ((gPlayer1.Team = TEAM_BLUE) and (a = FLAG_BLUE))))
2589 or ((gPlayer2 <> nil) and (((gPlayer2.Team = TEAM_RED) and (a = FLAG_RED)) or ((gPlayer2.Team = TEAM_BLUE) and (a = FLAG_BLUE))))) then
2590 b := 0
2591 else
2592 b := 1;
2594 if not sound_ret_flag[b].IsPlaying() then
2595 sound_ret_flag[b].Play();
2597 if g_Game_IsNet then
2598 MH_SEND_FlagEvent(FLAG_STATE_RETURNED, a, 0);
2599 Continue;
2600 end;
2602 if Count > 0 then Count -= 1;
2604 // Èãðîê áåðåò ôëàã
2605 if gPlayers <> nil then
2606 begin
2607 j := Random(Length(gPlayers)) - 1;
2608 for d := 0 to High(gPlayers) do
2609 begin
2610 Inc(j);
2611 if j > High(gPlayers) then j := 0;
2612 if gPlayers[j] <> nil then
2613 begin
2614 if gPlayers[j].alive and g_Obj_Collide(@Obj, @gPlayers[j].Obj) then
2615 begin
2616 if gPlayers[j].GetFlag(a) then Break;
2617 end;
2618 end;
2619 end;
2620 end;
2621 end;
2622 end;
2623 end;
2624 end;
2625 end;
2628 // old algo
2629 procedure g_Map_DrawPanels (PanelType: Word; hasAmbient: Boolean; constref ambColor: TDFColor);
2631 procedure DrawPanels (constref panels: TPanelArray; drawDoors: Boolean=False);
2632 var
2633 idx: Integer;
2634 begin
2635 if (panels <> nil) then
2636 begin
2637 // alas, no visible set
2638 for idx := 0 to High(panels) do
2639 begin
2640 if not (drawDoors xor panels[idx].Door) then panels[idx].Draw(hasAmbient, ambColor);
2641 end;
2642 end;
2643 end;
2645 begin
2646 case PanelType of
2647 PANEL_WALL: DrawPanels(gWalls);
2648 PANEL_CLOSEDOOR: DrawPanels(gWalls, True);
2649 PANEL_BACK: DrawPanels(gRenderBackgrounds);
2650 PANEL_FORE: DrawPanels(gRenderForegrounds);
2651 PANEL_WATER: DrawPanels(gWater);
2652 PANEL_ACID1: DrawPanels(gAcid1);
2653 PANEL_ACID2: DrawPanels(gAcid2);
2654 PANEL_STEP: DrawPanels(gSteps);
2655 end;
2656 end;
2659 // new algo
2660 procedure g_Map_CollectDrawPanels (x0, y0, wdt, hgt: Integer);
2661 var
2662 mwit: PPanel;
2663 it: TPanelGrid.Iter;
2664 begin
2665 dplClear();
2666 it := mapGrid.forEachInAABB(x0, y0, wdt, hgt, GridDrawableMask);
2667 for mwit in it do if (((mwit^.tag and GridTagDoor) <> 0) = mwit^.Door) then gDrawPanelList.insert(mwit^);
2668 it.release();
2669 // list will be rendered in `g_game.DrawPlayer()`
2670 end;
2673 procedure g_Map_DrawPanelShadowVolumes (lightX: Integer; lightY: Integer; radius: Integer);
2674 var
2675 mwit: PPanel;
2676 it: TPanelGrid.Iter;
2677 begin
2678 it := mapGrid.forEachInAABB(lightX-radius, lightY-radius, radius*2, radius*2, (GridTagWall or GridTagDoor));
2679 for mwit in it do mwit^.DrawShadowVolume(lightX, lightY, radius);
2680 it.release();
2681 end;
2684 procedure g_Map_DrawBack(dx, dy: Integer);
2685 begin
2686 if gDrawBackGround and (BackID <> DWORD(-1)) then
2687 e_DrawSize(BackID, dx, dy, 0, False, False, gBackSize.X, gBackSize.Y)
2688 else
2689 e_Clear(GL_COLOR_BUFFER_BIT, 0, 0, 0);
2690 end;
2692 function g_Map_CollidePanelOld(X, Y: Integer; Width, Height: Word;
2693 PanelType: Word; b1x3: Boolean=false): Boolean;
2694 var
2695 a, h: Integer;
2696 begin
2697 Result := False;
2699 if WordBool(PanelType and PANEL_WALL) then
2700 if gWalls <> nil then
2701 begin
2702 h := High(gWalls);
2704 for a := 0 to h do
2705 if gWalls[a].Enabled and
2706 g_Collide(X, Y, Width, Height,
2707 gWalls[a].X, gWalls[a].Y,
2708 gWalls[a].Width, gWalls[a].Height) then
2709 begin
2710 Result := True;
2711 Exit;
2712 end;
2713 end;
2715 if WordBool(PanelType and PANEL_WATER) then
2716 if gWater <> nil then
2717 begin
2718 h := High(gWater);
2720 for a := 0 to h do
2721 if g_Collide(X, Y, Width, Height,
2722 gWater[a].X, gWater[a].Y,
2723 gWater[a].Width, gWater[a].Height) then
2724 begin
2725 Result := True;
2726 Exit;
2727 end;
2728 end;
2730 if WordBool(PanelType and PANEL_ACID1) then
2731 if gAcid1 <> nil then
2732 begin
2733 h := High(gAcid1);
2735 for a := 0 to h do
2736 if g_Collide(X, Y, Width, Height,
2737 gAcid1[a].X, gAcid1[a].Y,
2738 gAcid1[a].Width, gAcid1[a].Height) then
2739 begin
2740 Result := True;
2741 Exit;
2742 end;
2743 end;
2745 if WordBool(PanelType and PANEL_ACID2) then
2746 if gAcid2 <> nil then
2747 begin
2748 h := High(gAcid2);
2750 for a := 0 to h do
2751 if g_Collide(X, Y, Width, Height,
2752 gAcid2[a].X, gAcid2[a].Y,
2753 gAcid2[a].Width, gAcid2[a].Height) then
2754 begin
2755 Result := True;
2756 Exit;
2757 end;
2758 end;
2760 if WordBool(PanelType and PANEL_STEP) then
2761 if gSteps <> nil then
2762 begin
2763 h := High(gSteps);
2765 for a := 0 to h do
2766 if g_Collide(X, Y, Width, Height,
2767 gSteps[a].X, gSteps[a].Y,
2768 gSteps[a].Width, gSteps[a].Height) then
2769 begin
2770 Result := True;
2771 Exit;
2772 end;
2773 end;
2775 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then
2776 if gLifts <> nil then
2777 begin
2778 h := High(gLifts);
2780 for a := 0 to h do
2781 if ((WordBool(PanelType and (PANEL_LIFTUP)) and (gLifts[a].LiftType = LIFTTYPE_UP)) or
2782 (WordBool(PanelType and (PANEL_LIFTDOWN)) and (gLifts[a].LiftType = LIFTTYPE_DOWN)) or
2783 (WordBool(PanelType and (PANEL_LIFTLEFT)) and (gLifts[a].LiftType = LIFTTYPE_LEFT)) or
2784 (WordBool(PanelType and (PANEL_LIFTRIGHT)) and (gLifts[a].LiftType = LIFTTYPE_RIGHT))) and
2785 g_Collide(X, Y, Width, Height,
2786 gLifts[a].X, gLifts[a].Y,
2787 gLifts[a].Width, gLifts[a].Height) then
2788 begin
2789 Result := True;
2790 Exit;
2791 end;
2792 end;
2794 if WordBool(PanelType and PANEL_BLOCKMON) then
2795 if gBlockMon <> nil then
2796 begin
2797 h := High(gBlockMon);
2799 for a := 0 to h do
2800 if ( (not b1x3) or
2801 ((gBlockMon[a].Width + gBlockMon[a].Height) >= 64) ) and
2802 g_Collide(X, Y, Width, Height,
2803 gBlockMon[a].X, gBlockMon[a].Y,
2804 gBlockMon[a].Width, gBlockMon[a].Height) then
2805 begin
2806 Result := True;
2807 Exit;
2808 end;
2809 end;
2810 end;
2812 function g_Map_CollideLiquid_TextureOld(X, Y: Integer; Width, Height: Word): DWORD;
2813 var
2814 texid: DWORD;
2816 function checkPanels (constref panels: TPanelArray): Boolean;
2817 var
2818 a: Integer;
2819 begin
2820 result := false;
2821 if panels = nil then exit;
2822 for a := 0 to High(panels) do
2823 begin
2824 if g_Collide(X, Y, Width, Height, panels[a].X, panels[a].Y, panels[a].Width, panels[a].Height) then
2825 begin
2826 result := true;
2827 texid := panels[a].GetTextureID();
2828 exit;
2829 end;
2830 end;
2831 end;
2833 begin
2834 texid := LongWord(TEXTURE_NONE);
2835 result := texid;
2836 if not checkPanels(gWater) then
2837 if not checkPanels(gAcid1) then
2838 if not checkPanels(gAcid2) then exit;
2839 result := texid;
2840 end;
2843 function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word; PanelType: Word; b1x3: Boolean): Boolean;
2844 const
2845 SlowMask = GridTagLift or GridTagBlockMon;
2847 function checker (pan: TPanel; tag: Integer): Boolean;
2848 begin
2850 if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
2851 begin
2852 result := pan.Enabled;
2853 exit;
2854 end;
2857 if ((tag and GridTagLift) <> 0) then
2858 begin
2859 result :=
2860 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
2861 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
2862 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
2863 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT))) {and
2864 g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
2865 exit;
2866 end;
2868 if ((tag and GridTagBlockMon) <> 0) then
2869 begin
2870 result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2871 exit;
2872 end;
2874 // other shit
2875 //result := g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2876 result := true; // i found her!
2877 end;
2879 var
2880 tagmask: Integer = 0;
2881 mwit: PPanel;
2882 it: TPanelGrid.Iter;
2883 pan: TPanel;
2884 begin
2885 result := false;
2886 if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor);
2887 if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater;
2888 if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1;
2889 if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2;
2890 if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep;
2891 if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift;
2892 if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon;
2894 if (tagmask = 0) then exit; // just in case
2896 if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('*solids');
2897 if gdbg_map_use_accel_coldet then
2898 begin
2899 if ((tagmask and SlowMask) <> 0) then
2900 begin
2901 // slow
2902 it := mapGrid.forEachInAABB(X, Y, Width, Height, tagmask);
2903 for mwit in it do
2904 begin
2905 pan := mwit^;
2906 if ((pan.tag and GridTagLift) <> 0) then
2907 begin
2908 result :=
2909 ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = LIFTTYPE_UP)) or
2910 (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = LIFTTYPE_DOWN)) or
2911 (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = LIFTTYPE_LEFT)) or
2912 (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = LIFTTYPE_RIGHT))) {and
2913 g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
2914 end
2915 else if ((pan.tag and GridTagBlockMon) <> 0) then
2916 begin
2917 result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
2918 end
2919 else
2920 begin
2921 // other shit
2922 result := true; // i found her!
2923 end;
2924 if (result) then break;
2925 end;
2926 end
2927 else
2928 begin
2929 // fast
2930 it := mapGrid.forEachInAABB(X, Y, Width, Height, tagmask, false, true); // return first hit
2931 result := (it.length > 0);
2932 end;
2933 it.release();
2934 end
2935 else
2936 begin
2937 result := g_Map_CollidePanelOld(X, Y, Width, Height, PanelType, b1x3);
2938 end;
2939 if (profMapCollision <> nil) then profMapCollision.sectionEnd();
2940 end;
2943 // returns `true` if we need to stop
2944 function liquidChecker (pan: TPanel; var texid: DWORD; var cctype: Integer): Boolean; inline;
2945 begin
2946 result := false;
2947 //if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2)) = 0) then exit;
2948 // check priorities
2949 case cctype of
2950 0: if ((pan.tag and GridTagWater) = 0) then exit; // allowed: water
2951 1: if ((pan.tag and (GridTagWater or GridTagAcid1)) = 0) then exit; // allowed: water, acid1
2952 //2: if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2) = 0) then exit; // allowed: water, acid1, acid2
2953 end;
2954 // collision?
2955 //if not g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height) then exit;
2956 // yeah
2957 texid := pan.GetTextureID();
2958 // water? water has the highest priority, so stop right here
2959 if ((pan.tag and GridTagWater) <> 0) then begin cctype := 0; result := true; exit; end;
2960 // acid2?
2961 if ((pan.tag and GridTagAcid2) <> 0) then cctype := 2;
2962 // acid1?
2963 if ((pan.tag and GridTagAcid1) <> 0) then cctype := 1;
2964 end;
2966 function g_Map_CollideLiquid_Texture(X, Y: Integer; Width, Height: Word): DWORD;
2967 var
2968 cctype: Integer = 3; // priority: 0: water was hit, 1: acid1 was hit, 2: acid2 was hit; 3: nothing was hit
2969 mwit: PPanel;
2970 it: TPanelGrid.Iter;
2971 begin
2972 if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('liquids');
2973 if gdbg_map_use_accel_coldet then
2974 begin
2975 result := LongWord(TEXTURE_NONE);
2976 it := mapGrid.forEachInAABB(X, Y, Width, Height, (GridTagWater or GridTagAcid1 or GridTagAcid2));
2977 for mwit in it do if (liquidChecker(mwit^, result, cctype)) then break;
2978 it.release();
2979 end
2980 else
2981 begin
2982 result := g_Map_CollideLiquid_TextureOld(X, Y, Width, Height);
2983 end;
2984 if (profMapCollision <> nil) then profMapCollision.sectionEnd();
2985 end;
2988 procedure g_Map_EnableWall_XXX (ID: DWORD); begin if (ID < Length(gWalls)) then g_Map_EnableWallGUID(gWalls[ID].guid); end;
2989 procedure g_Map_DisableWall_XXX (ID: DWORD); begin if (ID < Length(gWalls)) then g_Map_DisableWallGUID(gWalls[ID].guid); end;
2990 procedure g_Map_SetLift_XXX (ID: DWORD; t: Integer); begin if (ID < Length(gLifts)) then g_Map_SetLiftGUID(gLifts[ID].guid, t); end;
2993 procedure g_Map_EnableWallGUID (pguid: Integer);
2994 var
2995 pan: TPanel;
2996 begin
2997 //pan := gWalls[ID];
2998 pan := g_Map_PanelByGUID(pguid);
2999 if (pan = nil) then exit;
3000 if pan.Enabled and mapGrid.proxyEnabled[pan.proxyId] then exit;
3002 pan.Enabled := True;
3003 g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, true);
3005 mapGrid.proxyEnabled[pan.proxyId] := true;
3006 //if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := true
3007 //else pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, GridTagDoor);
3009 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
3010 // mark platform as interesting
3011 pan.setDirty();
3013 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
3014 //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);
3015 {$ENDIF}
3016 end;
3019 procedure g_Map_DisableWallGUID (pguid: Integer);
3020 var
3021 pan: TPanel;
3022 begin
3023 //pan := gWalls[ID];
3024 pan := g_Map_PanelByGUID(pguid);
3025 if (pan = nil) then exit;
3026 if (not pan.Enabled) and (not mapGrid.proxyEnabled[pan.proxyId]) then exit;
3028 pan.Enabled := False;
3029 g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, false);
3031 mapGrid.proxyEnabled[pan.proxyId] := false;
3032 //if (pan.proxyId >= 0) then begin mapGrid.removeBody(pan.proxyId); pan.proxyId := -1; end;
3034 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
3035 // mark platform as interesting
3036 pan.setDirty();
3038 {$IFDEF MAP_DEBUG_ENABLED_FLAG}
3039 //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);
3040 {$ENDIF}
3041 end;
3044 procedure g_Map_SwitchTextureGUID (pguid: Integer; AnimLoop: Byte = 0);
3045 var
3046 tp: TPanel;
3047 begin
3048 tp := g_Map_PanelByGUID(pguid);
3049 if (tp = nil) then exit;
3050 tp.NextTexture(AnimLoop);
3051 if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelTexture(pguid, AnimLoop);
3052 end;
3055 procedure g_Map_SetLiftGUID (pguid: Integer; t: Integer);
3056 var
3057 pan: TPanel;
3058 begin
3059 //pan := gLifts[ID];
3060 pan := g_Map_PanelByGUID(pguid);
3061 if (pan = nil) then exit;
3062 if not pan.isGLift then exit;
3064 if ({gLifts[ID]}pan.LiftType = t) then exit; //!FIXME!TRIGANY!
3066 with {gLifts[ID]} pan do
3067 begin
3068 LiftType := t;
3070 g_Mark(X, Y, Width, Height, MARK_LIFT, false);
3071 //TODO: make separate lift tags, and change tag here
3073 case LiftType of
3074 LIFTTYPE_UP: g_Mark(X, Y, Width, Height, MARK_LIFTUP);
3075 LIFTTYPE_DOWN: g_Mark(X, Y, Width, Height, MARK_LIFTDOWN);
3076 LIFTTYPE_LEFT: g_Mark(X, Y, Width, Height, MARK_LIFTLEFT);
3077 LIFTTYPE_RIGHT: g_Mark(X, Y, Width, Height, MARK_LIFTRIGHT);
3078 end;
3080 //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pguid);
3081 // mark platform as interesting
3082 pan.setDirty();
3083 end;
3084 end;
3087 function g_Map_GetPoint (PointType: Byte; var RespawnPoint: TRespawnPoint): Boolean;
3088 var
3089 a: Integer;
3090 PointsArray: Array of TRespawnPoint;
3091 begin
3092 Result := False;
3093 SetLength(PointsArray, 0);
3095 if RespawnPoints = nil then
3096 Exit;
3098 for a := 0 to High(RespawnPoints) do
3099 if RespawnPoints[a].PointType = PointType then
3100 begin
3101 SetLength(PointsArray, Length(PointsArray)+1);
3102 PointsArray[High(PointsArray)] := RespawnPoints[a];
3103 end;
3105 if PointsArray = nil then
3106 Exit;
3108 RespawnPoint := PointsArray[Random(Length(PointsArray))];
3109 Result := True;
3110 end;
3112 function g_Map_GetPointCount(PointType: Byte): Word;
3113 var
3114 a: Integer;
3115 begin
3116 Result := 0;
3118 if RespawnPoints = nil then
3119 Exit;
3121 for a := 0 to High(RespawnPoints) do
3122 if RespawnPoints[a].PointType = PointType then
3123 Result := Result + 1;
3124 end;
3126 function g_Map_GetRandomPointType(): Byte;
3127 begin
3128 if RespawnPoints = nil then
3129 Result := 255
3130 else
3131 Result := RespawnPoints[Random(Length(RespawnPoints))].PointType;
3132 end;
3134 function g_Map_HaveFlagPoints(): Boolean;
3135 begin
3136 Result := (FlagPoints[FLAG_RED] <> nil) and (FlagPoints[FLAG_BLUE] <> nil);
3137 end;
3139 procedure g_Map_ResetFlag(Flag: Byte);
3140 begin
3141 with gFlags[Flag] do
3142 begin
3143 Obj.X := -1000;
3144 Obj.Y := -1000;
3145 Obj.Vel.X := 0;
3146 Obj.Vel.Y := 0;
3147 Direction := TDirection.D_LEFT;
3148 State := FLAG_STATE_NONE;
3149 if FlagPoints[Flag] <> nil then
3150 begin
3151 Obj.X := FlagPoints[Flag]^.X;
3152 Obj.Y := FlagPoints[Flag]^.Y;
3153 Direction := FlagPoints[Flag]^.Direction;
3154 State := FLAG_STATE_NORMAL;
3155 end;
3156 Count := -1;
3157 end;
3158 end;
3160 procedure g_Map_DrawFlags();
3161 var
3162 i, dx: Integer;
3163 Mirror: TMirrorType;
3164 begin
3165 if gGameSettings.GameMode <> GM_CTF then
3166 Exit;
3168 for i := FLAG_RED to FLAG_BLUE do
3169 with gFlags[i] do
3170 if State <> FLAG_STATE_CAPTURED then
3171 begin
3172 if State = FLAG_STATE_NONE then
3173 continue;
3175 if Direction = TDirection.D_LEFT then
3176 begin
3177 Mirror := TMirrorType.Horizontal;
3178 dx := -1;
3179 end
3180 else
3181 begin
3182 Mirror := TMirrorType.None;
3183 dx := 1;
3184 end;
3186 Animation.Draw(Obj.X+dx, Obj.Y+1, Mirror);
3188 if g_debug_Frames then
3189 begin
3190 e_DrawQuad(Obj.X+Obj.Rect.X,
3191 Obj.Y+Obj.Rect.Y,
3192 Obj.X+Obj.Rect.X+Obj.Rect.Width-1,
3193 Obj.Y+Obj.Rect.Y+Obj.Rect.Height-1,
3194 0, 255, 0);
3195 end;
3196 end;
3197 end;
3200 procedure g_Map_SaveState (st: TStream);
3201 var
3202 str: String;
3204 procedure savePanels ();
3205 var
3206 pan: TPanel;
3207 begin
3208 // Ñîõðàíÿåì ïàíåëè
3209 utils.writeInt(st, LongInt(Length(panByGUID)));
3210 for pan in panByGUID do pan.SaveState(st);
3211 end;
3213 procedure saveFlag (flag: PFlag);
3214 var
3215 b: Byte;
3216 begin
3217 utils.writeSign(st, 'FLAG');
3218 utils.writeInt(st, Byte(0)); // version
3219 // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
3220 utils.writeInt(st, Byte(flag^.RespawnType));
3221 // Ñîñòîÿíèå ôëàãà
3222 utils.writeInt(st, Byte(flag^.State));
3223 // Íàïðàâëåíèå ôëàãà
3224 if flag^.Direction = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT
3225 utils.writeInt(st, Byte(b));
3226 // Îáúåêò ôëàãà
3227 Obj_SaveState(st, @flag^.Obj);
3228 end;
3230 begin
3231 savePanels();
3233 // Ñîõðàíÿåì ìóçûêó
3234 utils.writeSign(st, 'MUSI');
3235 utils.writeInt(st, Byte(0));
3236 // Íàçâàíèå ìóçûêè
3237 assert(gMusic <> nil, 'g_Map_SaveState: gMusic = nil');
3238 if gMusic.NoMusic then str := '' else str := gMusic.Name;
3239 utils.writeStr(st, str);
3240 // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
3241 utils.writeInt(st, LongWord(gMusic.GetPosition()));
3242 // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
3243 utils.writeBool(st, gMusic.SpecPause);
3245 ///// Ñîõðàíÿåì êîëè÷åñòâî ìîíñòðîâ: /////
3246 utils.writeInt(st, LongInt(gTotalMonsters));
3247 ///// /////
3249 //// Ñîõðàíÿåì ôëàãè, åñëè ýòî CTF: /////
3250 if (gGameSettings.GameMode = GM_CTF) then
3251 begin
3252 // Ôëàã Êðàñíîé êîìàíäû
3253 saveFlag(@gFlags[FLAG_RED]);
3254 // Ôëàã Ñèíåé êîìàíäû
3255 saveFlag(@gFlags[FLAG_BLUE]);
3256 end;
3257 ///// /////
3259 ///// Ñîõðàíÿåì êîëè÷åñòâî ïîáåä, åñëè ýòî TDM/CTF: /////
3260 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
3261 begin
3262 // Î÷êè Êðàñíîé êîìàíäû
3263 utils.writeInt(st, SmallInt(gTeamStat[TEAM_RED].Goals));
3264 // Î÷êè Ñèíåé êîìàíäû
3265 utils.writeInt(st, SmallInt(gTeamStat[TEAM_BLUE].Goals));
3266 end;
3267 ///// /////
3268 end;
3271 procedure g_Map_LoadState (st: TStream);
3272 var
3273 dw: DWORD;
3274 str: String;
3275 boo: Boolean;
3277 procedure loadPanels ();
3278 var
3279 pan: TPanel;
3280 begin
3281 // Çàãðóæàåì ïàíåëè
3282 if (Length(panByGUID) <> utils.readLongInt(st)) then raise XStreamError.Create('invalid number of saved panels');
3283 for pan in panByGUID do
3284 begin
3285 pan.LoadState(st);
3286 if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
3287 end;
3288 end;
3290 procedure loadFlag (flag: PFlag);
3291 var
3292 b: Byte;
3293 begin
3294 // Ñèãíàòóðà ôëàãà
3295 if not utils.checkSign(st, 'FLAG') then raise XStreamError.Create('invalid flag signature');
3296 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid flag version');
3297 // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
3298 flag^.RespawnType := utils.readByte(st);
3299 // Ñîñòîÿíèå ôëàãà
3300 flag^.State := utils.readByte(st);
3301 // Íàïðàâëåíèå ôëàãà
3302 b := utils.readByte(st);
3303 if (b = 1) then flag^.Direction := TDirection.D_LEFT else flag^.Direction := TDirection.D_RIGHT; // b = 2
3304 // Îáúåêò ôëàãà
3305 Obj_LoadState(@flag^.Obj, st);
3306 end;
3308 begin
3309 if (st = nil) then exit;
3311 ///// Çàãðóæàåì ñïèñêè ïàíåëåé: /////
3312 loadPanels();
3313 ///// /////
3315 // Îáíîâëÿåì êàðòó ñòîëêíîâåíèé è ñåòêó
3316 g_GFX_Init();
3317 //mapCreateGrid();
3319 ///// Çàãðóæàåì ìóçûêó: /////
3320 if not utils.checkSign(st, 'MUSI') then raise XStreamError.Create('invalid music signature');
3321 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid music version');
3322 // Íàçâàíèå ìóçûêè
3323 assert(gMusic <> nil, 'g_Map_LoadState: gMusic = nil');
3324 str := utils.readStr(st);
3325 // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
3326 dw := utils.readLongWord(st);
3327 // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
3328 boo := utils.readBool(st);
3329 // Çàïóñêàåì ýòó ìóçûêó
3330 gMusic.SetByName(str);
3331 gMusic.SpecPause := boo;
3332 gMusic.Play();
3333 gMusic.Pause(true);
3334 gMusic.SetPosition(dw);
3335 ///// /////
3337 ///// Çàãðóæàåì êîëè÷åñòâî ìîíñòðîâ: /////
3338 gTotalMonsters := utils.readLongInt(st);
3339 ///// /////
3341 //// Çàãðóæàåì ôëàãè, åñëè ýòî CTF: /////
3342 if (gGameSettings.GameMode = GM_CTF) then
3343 begin
3344 // Ôëàã Êðàñíîé êîìàíäû
3345 loadFlag(@gFlags[FLAG_RED]);
3346 // Ôëàã Ñèíåé êîìàíäû
3347 loadFlag(@gFlags[FLAG_BLUE]);
3348 end;
3349 ///// /////
3351 ///// Çàãðóæàåì êîëè÷åñòâî ïîáåä, åñëè ýòî TDM/CTF: /////
3352 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
3353 begin
3354 // Î÷êè Êðàñíîé êîìàíäû
3355 gTeamStat[TEAM_RED].Goals := utils.readSmallInt(st);
3356 // Î÷êè Ñèíåé êîìàíäû
3357 gTeamStat[TEAM_BLUE].Goals := utils.readSmallInt(st);
3358 end;
3359 ///// /////
3360 end;
3363 // trace liquid, stepping by `dx` and `dy`
3364 // return last seen liquid coords, and `false` if we're started outside of the liquid
3365 function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
3366 const
3367 MaskLiquid = GridTagWater or GridTagAcid1 or GridTagAcid2;
3368 begin
3369 topx := x;
3370 topy := y;
3371 // started outside of the liquid?
3372 //if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then begin result := false; exit; end;
3373 if (g_Map_PanelAtPoint(x, y, MaskLiquid) = nil) then begin result := false; exit; end;
3374 if (dx = 0) and (dy = 0) then begin result := false; exit; end; // sanity check
3375 result := true;
3376 while true do
3377 begin
3378 Inc(x, dx);
3379 Inc(y, dy);
3380 //if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then exit; // out of the water, just exit
3381 if (g_Map_PanelAtPoint(x, y, MaskLiquid) = nil) then exit; // out of the water, just exit
3382 topx := x;
3383 topy := y;
3384 end;
3385 end;
3388 begin
3389 DynWarningCB := mapWarningCB;
3390 end.