X-Git-Url: http://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fgame%2Fg_map.pas;h=f0419bf49cf4314ad47ee168b1eb3fd78c87ad8a;hb=36deb8d4595b015f77465c8eaee2bc75e9308d99;hp=c9851ee57bb6ec63e8822a8aeae444c19f4442eb;hpb=e27a7539a5cd40fb3a15c6daef0e18817b7c9bd8;p=d2df-sdl.git diff --git a/src/game/g_map.pas b/src/game/g_map.pas index c9851ee..f0419bf 100644 --- a/src/game/g_map.pas +++ b/src/game/g_map.pas @@ -14,13 +14,14 @@ * along with this program. If not, see . *) {$INCLUDE ../shared/a_modes.inc} +{$DEFINE MAP_DEBUG_ENABLED_FLAG} unit g_map; interface uses e_graphics, g_basic, MAPSTRUCT, g_textures, Classes, - g_phys, wadreader, BinEditor, g_panel, g_grid, g_sap, md5, xprofiler; + g_phys, wadreader, BinEditor, g_panel, g_grid, md5, binheap, xprofiler; type TMapInfo = record @@ -62,11 +63,12 @@ function g_Map_Exist(Res: String): Boolean; procedure g_Map_Free(); procedure g_Map_Update(); -procedure g_Map_DrawPanels(x0, y0, wdt, hgt: Integer; PanelType: Word); +procedure g_Map_DrawPanels (PanelType: Word); // unaccelerated +procedure g_Map_CollectDrawPanels (x0, y0, wdt, hgt: Integer); procedure g_Map_DrawBack(dx, dy: Integer); function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word; - PanelType: Word; b1x3: Boolean): Boolean; + PanelType: Word; b1x3: Boolean=false): Boolean; function g_Map_CollideLiquid_Texture(X, Y: Integer; Width, Height: Word): DWORD; procedure g_Map_EnableWall(ID: DWORD); procedure g_Map_DisableWall(ID: DWORD); @@ -90,9 +92,29 @@ procedure g_Map_LoadState(Var Mem: TBinMemoryReader); procedure g_Map_DrawPanelShadowVolumes(lightX: Integer; lightY: Integer; radius: Integer); +// returns panel or nil +// sets `ex` and `ey` to `x1` and `y1` when no hit was detected +function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel; + +// returns panel or nil +// sets `ex` and `ey` to `x1` and `y1` when no hit was detected +function g_Map_traceToNearest (x0, y0, x1, y1: Integer; tag: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel; + +type + TForEachPanelCB = function (pan: TPanel): Boolean; // return `true` to stop + +function g_Map_HasAnyPanelAtPoint (x, y: Integer; panelType: Word): Boolean; +function g_Map_PanelAtPoint (x, y: Integer; tagmask: Integer=-1): TPanel; + +// trace liquid, stepping by `dx` and `dy` +// return last seen liquid coords, and `false` if we're started outside of the liquid +function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean; + + procedure g_Map_ProfilersBegin (); procedure g_Map_ProfilersEnd (); + const RESPAWNPOINT_PLAYER1 = 1; RESPAWNPOINT_PLAYER2 = 2; @@ -116,6 +138,35 @@ const SKY_STRETCH: Single = 1.5; +const + GridTagInvalid = 0; + + (* draw order: + PANEL_BACK + PANEL_STEP + PANEL_WALL + PANEL_CLOSEDOOR + PANEL_ACID1 + PANEL_ACID2 + PANEL_WATER + PANEL_FORE + *) + // sorted by draw priority + GridTagBack = 1 shl 0; + GridTagStep = 1 shl 1; + GridTagWall = 1 shl 2; + GridTagDoor = 1 shl 3; + GridTagAcid1 = 1 shl 4; + GridTagAcid2 = 1 shl 5; + GridTagWater = 1 shl 6; + GridTagFore = 1 shl 7; + // the following are invisible + GridTagLift = 1 shl 8; + GridTagBlockMon = 1 shl 9; + + GridDrawableMask = (GridTagBack or GridTagStep or GridTagWall or GridTagDoor or GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagFore); + + var gWalls: TPanelArray; gRenderBackgrounds: TPanelArray; @@ -134,10 +185,21 @@ var BackID: DWORD = DWORD(-1); gExternalResources: TStringList; - gdbg_map_use_grid_render: Boolean = true; - gdbg_map_use_grid_coldet: Boolean = true; - gdbg_map_use_sap: Boolean = false; + gdbg_map_use_accel_render: Boolean = true; + gdbg_map_use_accel_coldet: Boolean = true; profMapCollision: TProfiler = nil; //WARNING: FOR DEBUGGING ONLY! + gDrawPanelList: TBinaryHeapObj = nil; // binary heap of all walls we have to render, populated by `g_Map_CollectDrawPanels()` + + +function panelTypeToTag (panelType: Word): Integer; // returns GridTagXXX + + +type + TPanelGrid = specialize TBodyGridBase; + +var + mapGrid: TPanelGrid = nil; + implementation @@ -146,7 +208,7 @@ uses GL, GLExt, g_weapons, g_game, g_sound, e_sound, CONFIG, g_options, MAPREADER, g_triggers, g_player, MAPDEF, Math, g_monsters, g_saveload, g_language, g_netmsg, - utils, sfs, binheap, + utils, sfs, ImagingTypes, Imaging, ImagingUtility, ImagingGif, ImagingNetworkGraphics; @@ -155,22 +217,12 @@ const MUSIC_SIGNATURE = $4953554D; // 'MUSI' FLAG_SIGNATURE = $47414C46; // 'FLAG' - GridTagInvalid = -1; - GridTagWallDoor = 0; - GridTagBack = 1; - GridTagFore = 2; - GridTagWater = 3; - GridTagAcid1 = 4; - GridTagAcid2 = 5; - GridTagStep = 6; - GridTagLift = 7; - GridTagBlockMon = 8; - function panelTypeToTag (panelType: Word): Integer; begin case panelType of - PANEL_WALL, PANEL_OPENDOOR, PANEL_CLOSEDOOR: result := GridTagWallDoor; // gWalls + PANEL_WALL: result := GridTagWall; // gWalls + PANEL_OPENDOOR, PANEL_CLOSEDOOR: result := GridTagDoor; // gWalls PANEL_BACK: result := GridTagBack; // gRenderBackgrounds PANEL_FORE: result := GridTagFore; // gRenderForegrounds PANEL_WATER: result := GridTagWater; // gWater @@ -184,6 +236,23 @@ begin end; +function dplLess (a, b: TObject): Boolean; +var + pa, pb: TPanel; +begin + pa := TPanel(a); + pb := TPanel(b); + if (pa.tag < pb.tag) then begin result := true; exit; end; + if (pa.tag > pb.tag) then begin result := false; exit; end; + result := (pa.arrIdx < pb.arrIdx); +end; + +procedure dplClear (); +begin + if (gDrawPanelList = nil) then gDrawPanelList := TBinaryHeapObj.Create(@dplLess) else gDrawPanelList.clear(); +end; + + type TPanelID = record PWhere: ^TPanelArray; @@ -196,20 +265,18 @@ var RespawnPoints: Array of TRespawnPoint; FlagPoints: Array [FLAG_RED..FLAG_BLUE] of PFlagPoint; //DOMFlagPoints: Array of TFlagPoint; - gMapGrid: TBodyGrid = nil; - gMapSAP: TSweepAndPrune = nil; procedure g_Map_ProfilersBegin (); begin - if (profMapCollision = nil) then profMapCollision := TProfiler.Create('MAP COLLISION', g_profile_history_size); + if (profMapCollision = nil) then profMapCollision := TProfiler.Create('COLSOLID', g_profile_history_size); profMapCollision.mainBegin(g_profile_collision); // create sections if g_profile_collision then begin - profMapCollision.sectionBegin('wall coldet'); + profMapCollision.sectionBegin('*solids'); profMapCollision.sectionEnd(); - profMapCollision.sectionBegin('liquid coldet'); + profMapCollision.sectionBegin('liquids'); profMapCollision.sectionEnd(); end; end; @@ -220,6 +287,104 @@ begin end; +// wall index in `gWalls` or -1 +function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel; +var + ex, ey: Integer; +begin + result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, nil, (GridTagWall or GridTagDoor)); + if (result <> nil) then + begin + if (hitx <> nil) then hitx^ := ex; + if (hity <> nil) then hity^ := ey; + end + else + begin + if (hitx <> nil) then hitx^ := x1; + if (hity <> nil) then hity^ := y1; + end; +end; + +// returns panel or nil +function g_Map_traceToNearest (x0, y0, x1, y1: Integer; tag: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel; +var + ex, ey: Integer; +begin + result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, nil, tag); + if (result <> nil) then + begin + if (hitx <> nil) then hitx^ := ex; + if (hity <> nil) then hity^ := ey; + end + else + begin + if (hitx <> nil) then hitx^ := x1; + if (hity <> nil) then hity^ := y1; + end; +end; + + +function g_Map_HasAnyPanelAtPoint (x, y: Integer; panelType: Word): Boolean; + + function checker (pan: TPanel; tag: Integer): Boolean; + begin + { + if ((tag and (GridTagWall or GridTagDoor)) <> 0) then + begin + result := pan.Enabled; // stop if wall is enabled + exit; + end; + } + + if ((tag and GridTagLift) <> 0) then + begin + // stop if the lift of the right type + result := + ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = 0)) or + (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = 1)) or + (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = 2)) or + (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = 3))); + exit; + end; + + result := true; // otherwise, stop anyway, 'cause `forEachAtPoint()` is guaranteed to call this only for correct panels + end; + +var + tagmask: Integer = 0; +begin + result := false; + + if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor); + if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater; + if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1; + if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2; + if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep; + if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift; + if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon; + + if (tagmask = 0) then exit;// just in case + if ((tagmask and GridTagLift) <> 0) then + begin + // slow + result := (mapGrid.forEachAtPoint(x, y, checker, tagmask) <> nil); + end + else + begin + // fast + result := (mapGrid.forEachAtPoint(x, y, nil, tagmask) <> nil); + end; +end; + + +function g_Map_PanelAtPoint (x, y: Integer; tagmask: Integer=-1): TPanel; +begin + result := nil; + if (tagmask = 0) then exit; + result := mapGrid.forEachAtPoint(x, y, nil, tagmask); +end; + + function g_Map_IsSpecialTexture(Texture: String): Boolean; begin Result := (Texture = TEXTURE_NAME_WATER) or @@ -428,7 +593,9 @@ begin SetLength(panels^, len + 1); panels^[len] := TPanel.Create(PanelRec, AddTextures, CurTex, Textures); - panels^[len].ArrIdx := len; + panels^[len].arrIdx := len; + panels^[len].proxyId := -1; + panels^[len].tag := panelTypeToTag(PanelRec.PanelType); if sav then panels^[len].SaveIt := True; @@ -779,7 +946,7 @@ end; procedure CreateArea(Area: TAreaRec_1); var a: Integer; - id: DWORD; + id: DWORD = 0; begin case Area.AreaType of AREA_DMPOINT, AREA_PLAYERPOINT1, AREA_PLAYERPOINT2, @@ -873,45 +1040,52 @@ end; procedure CreateMonster(monster: TMonsterRec_1); var - a, i: Integer; + a: Integer; + mon: TMonster; begin if g_Game_IsClient then Exit; if (gGameSettings.GameType = GT_SINGLE) or LongBool(gGameSettings.Options and GAME_OPTION_MONSTERS) then begin - i := g_Monsters_Create(monster.MonsterType, monster.X, monster.Y, - TDirection(monster.Direction)); + mon := g_Monsters_Create(monster.MonsterType, monster.X, monster.Y, TDirection(monster.Direction)); if gTriggers <> nil then + begin for a := 0 to High(gTriggers) do - if gTriggers[a].TriggerType in [TRIGGER_PRESS, - TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then - if (gTriggers[a].Data.MonsterID-1) = gMonsters[i].StartID then - gMonsters[i].AddTrigger(a); + begin + if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then + begin + if (gTriggers[a].Data.MonsterID-1) = mon.StartID then mon.AddTrigger(a); + end; + end; + end; - if monster.MonsterType <> MONSTER_BARREL then - Inc(gTotalMonsters); + if monster.MonsterType <> MONSTER_BARREL then Inc(gTotalMonsters); end; end; procedure g_Map_ReAdd_DieTriggers(); -var - i, a: Integer; -begin - if g_Game_IsClient then Exit; - for i := 0 to High(gMonsters) do - if gMonsters[i] <> nil then + function monsDieTrig (mon: TMonster): Boolean; + var + a: Integer; + begin + result := false; // don't stop + mon.ClearTriggers(); + for a := 0 to High(gTriggers) do + begin + if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then begin - gMonsters[i].ClearTriggers(); - - for a := 0 to High(gTriggers) do - if gTriggers[a].TriggerType in [TRIGGER_PRESS, - TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then - if (gTriggers[a].Data.MonsterID-1) = gMonsters[i].StartID then - gMonsters[i].AddTrigger(a); + if (gTriggers[a].Data.MonsterID-1) = mon.StartID then mon.AddTrigger(a); end; + end; + end; + +begin + if g_Game_IsClient then Exit; + + g_Mons_ForEach(monsDieTrig); end; function extractWadName(resourceName: string): string; @@ -957,6 +1131,7 @@ begin addResToExternalResList(mapHeader.SkyName); end; + procedure mapCreateGrid (); var mapX0: Integer = $3fffffff; @@ -964,71 +1139,110 @@ var mapX1: Integer = -$3fffffff; mapY1: Integer = -$3fffffff; - procedure fixMinMax (var panels: TPanelArray); + procedure calcBoundingBox (constref panels: TPanelArray); var idx: Integer; + pan: TPanel; begin for idx := 0 to High(panels) do begin - if (panels[idx].Width < 1) or (panels[idx].Height < 1) then continue; - if mapX0 > panels[idx].X then mapX0 := panels[idx].X; - if mapY0 > panels[idx].Y then mapY0 := panels[idx].Y; - if mapX1 < panels[idx].X+panels[idx].Width-1 then mapX1 := panels[idx].X+panels[idx].Width-1; - if mapY1 < panels[idx].Y+panels[idx].Height-1 then mapY1 := panels[idx].Y+panels[idx].Height-1; + pan := panels[idx]; + if not pan.visvalid then continue; + if (pan.Width < 1) or (pan.Height < 1) then continue; + if (mapX0 > pan.x0) then mapX0 := pan.x0; + if (mapY0 > pan.y0) then mapY0 := pan.y0; + if (mapX1 < pan.x1) then mapX1 := pan.x1; + if (mapY1 < pan.y1) then mapY1 := pan.y1; end; end; - procedure addPanelsToGrid (var panels: TPanelArray; tag: Integer); + procedure addPanelsToGrid (constref panels: TPanelArray); var idx: Integer; + pan: TPanel; + newtag: Integer; begin - tag := panelTypeToTag(tag); - for idx := High(panels) downto 0 do + //tag := panelTypeToTag(tag); + for idx := 0 to High(panels) do begin - gMapGrid.insertBody(panels[idx], panels[idx].X, panels[idx].Y, panels[idx].Width, panels[idx].Height, tag); - gMapSAP.insertBody(panels[idx], panels[idx].X, panels[idx].Y, panels[idx].Width, panels[idx].Height, tag); + pan := panels[idx]; + if not pan.visvalid then continue; + if (pan.proxyId <> -1) then + begin + {$IF DEFINED(D2F_DEBUG)} + e_WriteLog(Format('DUPLICATE wall #%d(%d) enabled (%d); type:%08x', [Integer(idx), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId]), pan.PanelType]), MSG_NOTIFY); + {$ENDIF} + continue; + end; + case pan.PanelType of + PANEL_WALL: newtag := GridTagWall; + PANEL_OPENDOOR, PANEL_CLOSEDOOR: newtag := GridTagDoor; + PANEL_BACK: newtag := GridTagBack; + PANEL_FORE: newtag := GridTagFore; + PANEL_WATER: newtag := GridTagWater; + PANEL_ACID1: newtag := GridTagAcid1; + PANEL_ACID2: newtag := GridTagAcid2; + PANEL_STEP: newtag := GridTagStep; + PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: newtag := GridTagLift; + PANEL_BLOCKMON: newtag := GridTagBlockMon; + else continue; // oops + end; + pan.tag := newtag; + + pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, newtag); + // "enabled" flag has meaning only for doors and walls (engine assumes it); but meh... + mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled; + {$IFDEF MAP_DEBUG_ENABLED_FLAG} + { + if ((tag and (GridTagWall or GridTagDoor)) <> 0) then + begin + e_WriteLog(Format('INSERTED wall #%d(%d) enabled (%d)', [Integer(idx), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId])]), MSG_NOTIFY); + end; + } + {$ENDIF} end; end; begin - gMapGrid.Free(); - gMapGrid := nil; - gMapSAP.Free(); - gMapSAP := nil; - - fixMinMax(gWalls); - fixMinMax(gRenderBackgrounds); - fixMinMax(gRenderForegrounds); - fixMinMax(gWater); - fixMinMax(gAcid1); - fixMinMax(gAcid2); - fixMinMax(gSteps); - fixMinMax(gLifts); - fixMinMax(gBlockMon); - - if (mapX0 < 0) or (mapY0 < 0) then - begin - e_WriteLog(Format('funny map dimensions: (%d,%d)-(%d,%d)', [mapX0, mapY0, mapX1, mapY1]), MSG_WARNING); - //raise Exception.Create('we are fucked'); - end; - - gMapGrid := TBodyGrid.Create(mapX0, mapY0, mapX1-mapX0+1, mapY1-mapY0+1); - gMapSAP := TSweepAndPrune.Create(); - - addPanelsToGrid(gWalls, PANEL_WALL); // and PANEL_CLOSEDOOR - addPanelsToGrid(gRenderBackgrounds, PANEL_BACK); - addPanelsToGrid(gRenderForegrounds, PANEL_FORE); - addPanelsToGrid(gWater, PANEL_WATER); - addPanelsToGrid(gAcid1, PANEL_ACID1); - addPanelsToGrid(gAcid2, PANEL_ACID2); - addPanelsToGrid(gSteps, PANEL_STEP); - addPanelsToGrid(gLifts, PANEL_LIFTUP); // it doesn't matter which LIFT type is used here - addPanelsToGrid(gBlockMon, PANEL_BLOCKMON); - - gMapGrid.dumpStats(); - gMapSAP.dumpStats(); + mapGrid.Free(); + mapGrid := nil; + + calcBoundingBox(gWalls); + calcBoundingBox(gRenderBackgrounds); + calcBoundingBox(gRenderForegrounds); + calcBoundingBox(gWater); + calcBoundingBox(gAcid1); + calcBoundingBox(gAcid2); + calcBoundingBox(gSteps); + calcBoundingBox(gLifts); + calcBoundingBox(gBlockMon); + + e_WriteLog(Format('map dimensions: (%d,%d)-(%d,%d); editor size:(0,0)-(%d,%d)', [mapX0, mapY0, mapX1, mapY1, gMapInfo.Width, gMapInfo.Height]), MSG_WARNING); + + if (mapX0 > 0) then mapX0 := 0; + if (mapY0 > 0) then mapY0 := 0; + + if (mapX1 < gMapInfo.Width-1) then mapX1 := gMapInfo.Width-1; + if (mapY1 < gMapInfo.Height-1) then mapY1 := gMapInfo.Height-1; + + mapGrid := TPanelGrid.Create(mapX0-128, mapY0-128, mapX1-mapX0+1+128*2, mapY1-mapY0+1+128*2); + + addPanelsToGrid(gWalls); + addPanelsToGrid(gRenderBackgrounds); + addPanelsToGrid(gRenderForegrounds); + addPanelsToGrid(gWater); + addPanelsToGrid(gAcid1); + addPanelsToGrid(gAcid2); + addPanelsToGrid(gSteps); + addPanelsToGrid(gLifts); // it doesn't matter which LIFT type is used here + addPanelsToGrid(gBlockMon); + + mapGrid.dumpStats(); + + g_Mons_InitTree(mapGrid.gridX0, mapGrid.gridY0, mapGrid.gridWidth, mapGrid.gridHeight); end; + function g_Map_Load(Res: String): Boolean; const DefaultMusRes = 'Standart.wad:STDMUS\MUS1'; @@ -1061,10 +1275,10 @@ var CurTex, ntn: Integer; begin - gMapGrid.Free(); - gMapGrid := nil; - gMapSAP.Free(); - gMapSAP := nil; + mapGrid.Free(); + mapGrid := nil; + //mapTree.Free(); + //mapTree := nil; Result := False; gMapInfo.Map := Res; @@ -1116,6 +1330,22 @@ begin _textures := MapReader.GetTextures(); _texnummap := nil; + // Çàãðóçêà îïèñàíèÿ êàðòû: + e_WriteLog(' Reading map info...', MSG_NOTIFY); + g_Game_SetLoadingText(_lc[I_LOAD_MAP_HEADER], 0, False); + Header := MapReader.GetMapHeader(); + + with gMapInfo do + begin + Name := Header.MapName; + Description := Header.MapDescription; + Author := Header.MapAuthor; + MusicName := Header.MusicName; + SkyName := Header.SkyName; + Height := Header.Height; + Width := Header.Width; + end; + // Äîáàâëåíèå òåêñòóð â Textures[]: if _textures <> nil then begin @@ -1371,6 +1601,10 @@ begin end; end; + // create map grid, init other grids (for monsters, for example) + e_WriteLog('Creating map grid', MSG_NOTIFY); + mapCreateGrid(); + // Åñëè íå LoadState, òî ñîçäàåì òðèããåðû: if (triggers <> nil) and not gLoadGameMode then begin @@ -1436,24 +1670,8 @@ begin CreateMonster(monsters[a]); end; - // Çàãðóçêà îïèñàíèÿ êàðòû: - e_WriteLog(' Reading map info...', MSG_NOTIFY); - g_Game_SetLoadingText(_lc[I_LOAD_MAP_HEADER], 0, False); - Header := MapReader.GetMapHeader(); - MapReader.Free(); - with gMapInfo do - begin - Name := Header.MapName; - Description := Header.MapDescription; - Author := Header.MapAuthor; - MusicName := Header.MusicName; - SkyName := Header.SkyName; - Height := Header.Height; - Width := Header.Width; - end; - // Çàãðóçêà íåáà: if gMapInfo.SkyName <> '' then begin @@ -1532,9 +1750,6 @@ begin sfsGCEnable(); // enable releasing unused volumes end; - e_WriteLog('Creating map grid', MSG_NOTIFY); - mapCreateGrid(); - e_WriteLog('Done loading map.', MSG_NOTIFY); Result := True; end; @@ -1819,13 +2034,14 @@ begin end; -procedure g_Map_DrawPanelsOld(PanelType: Word); +// old algo +procedure g_Map_DrawPanels (PanelType: Word); - procedure DrawPanels (stp: Integer; var panels: TPanelArray; drawDoors: Boolean=False); + procedure DrawPanels (constref panels: TPanelArray; drawDoors: Boolean=False); var idx: Integer; begin - if (panels <> nil) and (stp >= 0) and (stp <= 6) then + if (panels <> nil) then begin // alas, no visible set for idx := 0 to High(panels) do @@ -1837,148 +2053,60 @@ procedure g_Map_DrawPanelsOld(PanelType: Word); begin case PanelType of - PANEL_WALL: DrawPanels(0, gWalls); - PANEL_CLOSEDOOR: DrawPanels(0, gWalls, True); - PANEL_BACK: DrawPanels(1, gRenderBackgrounds); - PANEL_FORE: DrawPanels(2, gRenderForegrounds); - PANEL_WATER: DrawPanels(3, gWater); - PANEL_ACID1: DrawPanels(4, gAcid1); - PANEL_ACID2: DrawPanels(5, gAcid2); - PANEL_STEP: DrawPanels(6, gSteps); + PANEL_WALL: DrawPanels(gWalls); + PANEL_CLOSEDOOR: DrawPanels(gWalls, True); + PANEL_BACK: DrawPanels(gRenderBackgrounds); + PANEL_FORE: DrawPanels(gRenderForegrounds); + PANEL_WATER: DrawPanels(gWater); + PANEL_ACID1: DrawPanels(gAcid1); + PANEL_ACID2: DrawPanels(gAcid2); + PANEL_STEP: DrawPanels(gSteps); end; end; -var - gDrawPanelList: TBinaryHeapObj = nil; +// new algo +procedure g_Map_CollectDrawPanels (x0, y0, wdt, hgt: Integer); -function dplLess (a, b: TObject): Boolean; -begin - result := ((a as TPanel).ArrIdx < (b as TPanel).ArrIdx); -end; - -procedure dplClear (); -begin - if gDrawPanelList = nil then gDrawPanelList := TBinaryHeapObj.Create(@dplLess) else gDrawPanelList.clear(); -end; - -procedure dplAddPanel (pan: TPanel); -begin - if pan = nil then exit; - gDrawPanelList.insert(pan); -end; - - -procedure g_Map_DrawPanels(x0, y0, wdt, hgt: Integer; PanelType: Word); -var - ptag: Integer; - - function checker (obj: TObject; tag: Integer): Boolean; - var - pan: TPanel; + function checker (pan: TPanel; tag: Integer): Boolean; begin result := false; // don't stop, ever - if (tag <> ptag) then exit; - //e_WriteLog(Format(' *body: tag:%d; qtag:%d', [tag, PanelType]), MSG_NOTIFY); - - if obj = nil then begin e_WriteLog(Format(' !bodyFUUUUU0: tag:%d; qtag:%d', [tag, PanelType]), MSG_NOTIFY); exit; end; - if not (obj is TPanel) then begin e_WriteLog(Format(' !bodyFUUUUU1: tag:%d; qtag:%d', [tag, PanelType]), MSG_NOTIFY); exit; end; - //pan := (obj as TPanel); - //e_WriteLog(Format(' !body: (%d,%d)-(%dx%d) tag:%d; qtag:%d', [pan.X, pan.Y, pan.Width, pan.Height, tag, PanelType]), MSG_NOTIFY); - - pan := (obj as TPanel); - if (PanelType = PANEL_CLOSEDOOR) then begin if not pan.Door then exit; end else begin if pan.Door then exit; end; - //e_WriteLog(Format(' body hit: (%d,%d)-(%dx%d) tag: %d; qtag:%d', [pan.X, pan.Y, pan.Width, pan.Height, tag, PanelType]), MSG_NOTIFY); - dplAddPanel(pan); - end; - - procedure DrawPanels (stp: Integer; var panels: TPanelArray; drawDoors: Boolean=False); - var - idx: Integer; - pan: TPanel; - begin - if (panels <> nil) and (stp >= 0) and (stp <= 6) then - begin - // alas, no visible set - for idx := 0 to High(panels) do - begin - if not (drawDoors xor panels[idx].Door) then - begin - pan := panels[idx]; - { - if (pan.Width < 1) or (pan.Height < 1) then continue; - if (pan.X+pan.Width <= x0) or (pan.Y+pan.Height <= y0) then continue; - if (pan.X >= x0+wdt) or (pan.Y >= y0+hgt) then continue; - } - pan.Draw(); - //e_WriteLog(Format(' *body hit: (%d,%d)-(%dx%d) tag: %d; qtag:%d', [pan.X, pan.Y, pan.Width, pan.Height, PanelType, PanelType]), MSG_NOTIFY); - end; - end; - end; + if ((tag and GridTagDoor) <> 0) <> pan.Door then exit; + gDrawPanelList.insert(pan); end; begin - //g_Map_DrawPanelsOld(PanelType); exit; - //e_WriteLog('==================', MSG_NOTIFY); - //e_WriteLog(Format('***QQQ: qtag:%d', [PanelType]), MSG_NOTIFY); dplClear(); - ptag := panelTypeToTag(PanelType); + //tagmask := panelTypeToTag(PanelType); - if gdbg_map_use_grid_render then + {if gdbg_map_use_tree_draw then begin - if gdbg_map_use_sap then - begin - gMapSAP.forEachInAABB(x0, y0, wdt, hgt, checker); - end - else - begin - gMapGrid.forEachInAABB(x0, y0, wdt, hgt, checker); - end; - // sort and draw the list (we need to sort it, or rendering is fucked) - while gDrawPanelList.count > 0 do - begin - (gDrawPanelList.front() as TPanel).Draw(); - gDrawPanelList.popFront(); - end; + mapTree.aabbQuery(x0, y0, wdt, hgt, checker, (GridTagBack or GridTagStep or GridTagWall or GridTagDoor or GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagFore)); end - else + else} begin - //e_WriteLog(Format('+++QQQ: qtag:%d', [PanelType]), MSG_NOTIFY); - case PanelType of - PANEL_WALL: DrawPanels(0, gWalls); - PANEL_CLOSEDOOR: DrawPanels(0, gWalls, True); - PANEL_BACK: DrawPanels(1, gRenderBackgrounds); - PANEL_FORE: DrawPanels(2, gRenderForegrounds); - PANEL_WATER: DrawPanels(3, gWater); - PANEL_ACID1: DrawPanels(4, gAcid1); - PANEL_ACID2: DrawPanels(5, gAcid2); - PANEL_STEP: DrawPanels(6, gSteps); - end; + mapGrid.forEachInAABB(x0, y0, wdt, hgt, checker, GridDrawableMask); end; - - //e_WriteLog('==================', MSG_NOTIFY); + // list will be rendered in `g_game.DrawPlayer()` end; procedure g_Map_DrawPanelShadowVolumes(lightX: Integer; lightY: Integer; radius: Integer); - function checker (obj: TObject; tag: Integer): Boolean; - var - pan: TPanel; + + function checker (pan: TPanel; tag: Integer): Boolean; begin result := false; // don't stop, ever - if (tag <> GridTagWallDoor) then exit; // only walls - pan := (obj as TPanel); pan.DrawShadowVolume(lightX, lightY, radius); end; begin - if gdbg_map_use_sap then + {if gdbg_map_use_tree_draw then begin - gMapSAP.forEachInAABB(lightX-radius, lightY-radius, radius*2, radius*2, checker); + mapTree.aabbQuery(lightX-radius, lightY-radius, radius*2, radius*2, checker, (GridTagWall or GridTagDoor)); end - else + else} begin - gMapGrid.forEachInAABB(lightX-radius, lightY-radius, radius*2, radius*2, checker); + mapGrid.forEachInAABB(lightX-radius, lightY-radius, radius*2, radius*2, checker, (GridTagWall or GridTagDoor)); end; end; @@ -1992,7 +2120,7 @@ begin end; function g_Map_CollidePanelOld(X, Y: Integer; Width, Height: Word; - PanelType: Word; b1x3: Boolean): Boolean; + PanelType: Word; b1x3: Boolean=false): Boolean; var a, h: Integer; begin @@ -2115,7 +2243,7 @@ function g_Map_CollideLiquid_TextureOld(X, Y: Integer; Width, Height: Word): DWO var texid: DWORD; - function checkPanels (var panels: TPanelArray): Boolean; + function checkPanels (constref panels: TPanelArray): Boolean; var a: Integer; begin @@ -2143,215 +2271,197 @@ end; function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word; PanelType: Word; b1x3: Boolean): Boolean; - - function checker (obj: TObject; tag: Integer): Boolean; - var - pan: TPanel; - a: Integer; +const + SlowMask = GridTagLift or GridTagBlockMon; + function checker (pan: TPanel; tag: Integer): Boolean; begin - result := false; // don't stop, ever - - //e_WriteLog(Format(' *body: tag:%d; qtag:%d', [tag, PanelType]), MSG_NOTIFY); - - if obj = nil then - begin - e_WriteLog(Format(' !bodyFUUUUU0: tag:%d; qtag:%d', [tag, PanelType]), MSG_NOTIFY); - end; - if not (obj is TPanel) then + { + if ((tag and (GridTagWall or GridTagDoor)) <> 0) then begin - e_WriteLog(Format(' !bodyFUUUUU1: tag:%d; qtag:%d', [tag, PanelType]), MSG_NOTIFY); + result := pan.Enabled; exit; end; + } - pan := (obj as TPanel); - a := pan.ArrIdx; - - if WordBool(PanelType and PANEL_WALL) and (tag = GridTagWallDoor) then - begin - if gWalls[a].Enabled and g_Collide(X, Y, Width, Height, gWalls[a].X, gWalls[a].Y, gWalls[a].Width, gWalls[a].Height) then - begin - result := true; - exit; - end; - end; - - if WordBool(PanelType and PANEL_WATER) and (tag = GridTagWater) then - begin - if g_Collide(X, Y, Width, Height, gWater[a].X, gWater[a].Y, gWater[a].Width, gWater[a].Height) then - begin - result := True; - exit; - end; - end; - - if WordBool(PanelType and PANEL_ACID1) and (tag = GridTagAcid1) then - begin - if g_Collide(X, Y, Width, Height, gAcid1[a].X, gAcid1[a].Y, gAcid1[a].Width, gAcid1[a].Height) then - begin - result := True; - exit; - end; - end; - - if WordBool(PanelType and PANEL_ACID2) and (tag = GridTagAcid2) then + if ((tag and GridTagLift) <> 0) then begin - if g_Collide(X, Y, Width, Height, gAcid2[a].X, gAcid2[a].Y, gAcid2[a].Width, gAcid2[a].Height) then - begin - result := True; - exit; - end; + result := + ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = 0)) or + (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = 1)) or + (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = 2)) or + (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = 3))) {and + g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)}; + exit; end; - if WordBool(PanelType and PANEL_STEP) and (tag = GridTagStep) then + if ((tag and GridTagBlockMon) <> 0) then begin - if g_Collide(X, Y, Width, Height, gSteps[a].X, gSteps[a].Y, gSteps[a].Width, gSteps[a].Height) then - begin - result := True; - exit; - end; + result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height); + exit; end; - if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) and (tag = GridTagLift) then - begin - if ((WordBool(PanelType and (PANEL_LIFTUP)) and (gLifts[a].LiftType = 0)) or - (WordBool(PanelType and (PANEL_LIFTDOWN)) and (gLifts[a].LiftType = 1)) or - (WordBool(PanelType and (PANEL_LIFTLEFT)) and (gLifts[a].LiftType = 2)) or - (WordBool(PanelType and (PANEL_LIFTRIGHT)) and (gLifts[a].LiftType = 3))) and - g_Collide(X, Y, Width, Height, gLifts[a].X, gLifts[a].Y, gLifts[a].Width, gLifts[a].Height) then - begin - result := true; - exit; - end; - end; + // other shit + //result := g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height); + result := true; // i found her! + end; - if WordBool(PanelType and PANEL_BLOCKMON) and (tag = GridTagBlockMon) then +var + tagmask: Integer = 0; +begin + if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor); + if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater; + if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1; + if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2; + if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep; + if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift; + if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon; + + if (tagmask = 0) then begin result := false; exit; end; // just in case + + if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('*solids'); + if gdbg_map_use_accel_coldet then + begin + {if gdbg_map_use_tree_coldet then begin - if ((not b1x3) or ((gBlockMon[a].Width + gBlockMon[a].Height) >= 64)) and - g_Collide(X, Y, Width, Height, gBlockMon[a].X, gBlockMon[a].Y, gBlockMon[a].Width, gBlockMon[a].Height) then + //e_WriteLog(Format('coldet query: x=%d; y=%d; w=%d; h=%d', [X, Y, Width, Height]), MSG_NOTIFY); + result := (mapTree.aabbQuery(X, Y, Width, Height, checker, tagmask) <> nil); + if (gdbg_map_dump_coldet_tree_queries) and (mapTree.nodesVisited <> 0) then begin - result := True; - exit; + //e_WriteLog(Format('map collision: %d nodes visited (%d deep)', [mapTree.nodesVisited, mapTree.nodesDeepVisited]), MSG_NOTIFY); + g_Console_Add(Format('map collision: %d nodes visited (%d deep)', [mapTree.nodesVisited, mapTree.nodesDeepVisited])); end; - end; - end; - -begin - //TODO: detailed profile - if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('wall coldet'); - try - if gdbg_map_use_grid_coldet then + end + else} begin - if gdbg_map_use_sap then + if (Width = 1) and (Height = 1) then begin - gMapSAP.forEachInAABB(X, Y, Width, Height, checker); + if ((tagmask and SlowMask) <> 0) then + begin + // slow + result := (mapGrid.forEachAtPoint(X, Y, checker, tagmask) <> nil); + end + else + begin + // fast + result := (mapGrid.forEachAtPoint(X, Y, nil, tagmask) <> nil); + end; end else begin - result := gMapGrid.forEachInAABB(X, Y, Width, Height, checker); + if ((tagmask and SlowMask) <> 0) then + begin + // slow + result := (mapGrid.forEachInAABB(X, Y, Width, Height, checker, tagmask) <> nil); + end + else + begin + // fast + result := (mapGrid.forEachInAABB(X, Y, Width, Height, nil, tagmask) <> nil); + end; end; - end - else - begin - result := g_Map_CollidePanelOld(X, Y, Width, Height, PanelType, b1x3); end; - finally - if (profMapCollision <> nil) then profMapCollision.sectionEnd(); + end + else + begin + result := g_Map_CollidePanelOld(X, Y, Width, Height, PanelType, b1x3); end; + if (profMapCollision <> nil) then profMapCollision.sectionEnd(); end; function g_Map_CollideLiquid_Texture(X, Y: Integer; Width, Height: Word): DWORD; var - cctype: Integer = 3; // priority: 0: water, 1: acid1, 2: acid2; 3: others (nothing) + cctype: Integer = 3; // priority: 0: water was hit, 1: acid1 was hit, 2: acid2 was hit; 3: nothing was hit texid: DWORD; // slightly different from the old code, but meh... - function checker (obj: TObject; tag: Integer): Boolean; - var - pan: TPanel; - a: Integer; + function checker (pan: TPanel; tag: Integer): Boolean; begin result := false; // don't stop, ever - pan := (obj as TPanel); - a := pan.ArrIdx; - // water - if (tag = GridTagWater) then - begin - if g_Collide(X, Y, Width, Height, gWater[a].X, gWater[a].Y, gWater[a].Width, gWater[a].Height) then - begin - result := true; // water has highest priority, so stop right here - texid := gWater[a].GetTextureID(); - exit; - end; - end; - // acid1 - if (cctype > 1) and (tag = GridTagAcid1) then - begin - if g_Collide(X, Y, Width, Height, gAcid1[a].X, gAcid1[a].Y, gAcid1[a].Width, gAcid1[a].Height) then - begin - cctype := 1; - texid := gAcid1[a].GetTextureID(); - exit; - end; - end; - // acid2 - if (cctype > 2) and (tag = GridTagAcid2) then - begin - if g_Collide(X, Y, Width, Height, gAcid2[a].X, gAcid2[a].Y, gAcid2[a].Width, gAcid2[a].Height) then - begin - cctype := 2; - texid := gAcid2[a].GetTextureID(); - exit; - end; + //if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2)) = 0) then exit; + // check priorities + case cctype of + 0: if ((tag and GridTagWater) = 0) then exit; // allowed: water + 1: if ((tag and (GridTagWater or GridTagAcid1)) = 0) then exit; // allowed: water, acid1 + //2: if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2) = 0) then exit; // allowed: water, acid1, acid2 end; + // collision? + //if not g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height) then exit; + // yeah + texid := pan.GetTextureID(); + // water? water has the highest priority, so stop right here + if ((tag and GridTagWater) <> 0) then begin cctype := 0; result := true; exit; end; + // acid2? + if ((tag and GridTagAcid2) <> 0) then cctype := 2; + // acid1? + if ((tag and GridTagAcid1) <> 0) then cctype := 1; end; begin - //TODO: detailed profile? - if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('liquid coldet'); - try - if gdbg_map_use_grid_coldet then + if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('liquids'); + if gdbg_map_use_accel_coldet then + begin + texid := TEXTURE_NONE; + {if gdbg_map_use_tree_coldet then + begin + mapTree.aabbQuery(X, Y, Width, Height, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2)); + end + else} begin - texid := TEXTURE_NONE; - if gdbg_map_use_sap then + if (Width = 1) and (Height = 1) then begin - gMapSAP.forEachInAABB(X, Y, Width, Height, checker); + mapGrid.forEachAtPoint(X, Y, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2)); end else begin - gMapGrid.forEachInAABB(X, Y, Width, Height, checker); + mapGrid.forEachInAABB(X, Y, Width, Height, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2)); end; - result := texid; - end - else - begin - result := g_Map_CollideLiquid_TextureOld(X, Y, Width, Height); end; - finally - if (profMapCollision <> nil) then profMapCollision.sectionEnd(); + result := texid; + end + else + begin + result := g_Map_CollideLiquid_TextureOld(X, Y, Width, Height); end; + if (profMapCollision <> nil) then profMapCollision.sectionEnd(); end; + procedure g_Map_EnableWall(ID: DWORD); +var + pan: TPanel; begin - with gWalls[ID] do - begin - Enabled := True; - g_Mark(X, Y, Width, Height, MARK_DOOR, True); + pan := gWalls[ID]; + pan.Enabled := True; + g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, True); - if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(PanelType, ID); - end; + mapGrid.proxyEnabled[pan.proxyId] := true; + //if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := true + //else pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, GridTagDoor); + + if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(gWalls[ID].PanelType, ID); + + {$IFDEF MAP_DEBUG_ENABLED_FLAG} + //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); + {$ENDIF} end; procedure g_Map_DisableWall(ID: DWORD); +var + pan: TPanel; begin - with gWalls[ID] do - begin - Enabled := False; - g_Mark(X, Y, Width, Height, MARK_DOOR, False); + pan := gWalls[ID]; + pan.Enabled := False; + g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, False); - if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(PanelType, ID); - end; + mapGrid.proxyEnabled[pan.proxyId] := false; + //if (pan.proxyId >= 0) then begin mapGrid.removeBody(pan.proxyId); pan.proxyId := -1; end; + + if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pan.PanelType, ID); + + {$IFDEF MAP_DEBUG_ENABLED_FLAG} + //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); + {$ENDIF} end; procedure g_Map_SwitchTexture(PanelType: Word; ID: DWORD; AnimLoop: Byte = 0); @@ -2392,6 +2502,7 @@ begin LiftType := t; g_Mark(X, Y, Width, Height, MARK_LIFT, False); + //TODO: make separate lift tags, and change tag here if LiftType = 0 then g_Mark(X, Y, Width, Height, MARK_LIFTUP, True) @@ -2768,4 +2879,28 @@ begin Result := Addr(Arr[PanelByID[PanelID].PArrID]); end; + +// trace liquid, stepping by `dx` and `dy` +// return last seen liquid coords, and `false` if we're started outside of the liquid +function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean; +const + MaskLiquid = GridTagWater or GridTagAcid1 or GridTagAcid2; +begin + topx := x; + topy := y; + // started outside of the liquid? + if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then begin result := false; exit; end; + if (dx = 0) and (dy = 0) then begin result := false; exit; end; // sanity check + result := true; + while true do + begin + Inc(x, dx); + Inc(y, dy); + if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then exit; // out of the water, just exit + topx := x; + topy := y; + end; +end; + + end.