From 36deb8d4595b015f77465c8eaee2bc75e9308d99 Mon Sep 17 00:00:00 2001 From: Ketmar Dark Date: Thu, 24 Aug 2017 05:23:12 +0300 Subject: [PATCH] slightly better water particles --- src/game/g_gfx.pas | 248 ++++++++++++++++++++++---------- src/game/g_grid.pas | 342 +++++++++++++++++++++++++++++++++++++++++--- src/game/g_map.pas | 13 +- 3 files changed, 511 insertions(+), 92 deletions(-) diff --git a/src/game/g_gfx.pas b/src/game/g_gfx.pas index ab89576..bbdc432 100644 --- a/src/game/g_gfx.pas +++ b/src/game/g_gfx.pas @@ -87,6 +87,8 @@ type offsetX, offsetY: ShortInt; // for bubbles liquidTopY: Integer; // don't float higher than this + // for water + stickDX: Integer; //k8: sorry, i have to emulate virtual methods this way, 'cause i haet `Object` @@ -690,71 +692,48 @@ begin end; -// ////////////////////////////////////////////////////////////////////////// // -procedure TParticle.thinkerBubble (); -var - h: Integer; - dY: SmallInt; - b: Integer; - s: ShortInt; -begin - h := gMapInfo.Height; - - dY := Round(VelY); - - if dY <> 0 then - begin - if dY > 0 then - s := 1 - else - s := -1; - - for b := 1 to Abs(dY) do - begin - if (Y+s >= h) or (Y+s <= 0) then begin die(); break; end; - - (* - if not isLiquidAt(X, Y+s) {ByteBool(gCollideMap[Y+s, X] and MARK_LIQUID)} then - begin // Óæå íå æèäêîñòü - State := STATE_FREE; - Break; - end; - *) - // we traced liquid before, so don't bother checking - if (Y+s <= liquidTopY) then begin die(); break; end; - - Y := Y+s; - end; - end; - - if VelY > -4 then - VelY := VelY + AccelY; - - Time := Time + 1; -end; - - // ////////////////////////////////////////////////////////////////////////// // procedure TParticle.thinkerWater (); var - w, h: Integer; dX, dY: SmallInt; + {$IF not DEFINED(D2F_NEW_SPARK_THINKER)} + w, h: Integer; b: Integer; s: ShortInt; + {$ELSE} + pan: TPanel; + ex, ey: Integer; + {$ENDIF} begin + {$IF not DEFINED(D2F_NEW_SPARK_THINKER)} w := gMapInfo.Width; h := gMapInfo.Height; + {$ENDIF} + //TODO: trace wall end when water becomes stick if (State = STATE_STICK) and (Random(30) = 15) then begin // Ñòåêàåò/îòëèïàåò VelY := 0.5; AccelY := 0.15; + {$IF not DEFINED(D2F_NEW_SPARK_THINKER)} if (not isBlockedAt(X-1, Y) {ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)}) and (not isBlockedAt(X+1, Y) {ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED)}) then State := STATE_NORMAL; + {$ELSE} + if (stickDX = 0) then + begin + // no walls around, drop + State := STATE_NORMAL; + end + else + begin + if (g_Map_PanelAtPoint(X+stickDX, Y, (GridTagWall or GridTagDoor or GridTagStep)) = nil) then State := STATE_NORMAL; + end; + {$ENDIF} exit; end; + {$IF not DEFINED(D2F_NEW_SPARK_THINKER)} if not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then begin if isLiftUpAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then @@ -779,11 +758,37 @@ begin AccelY := 0.15; end; end; + {$ELSE} + pan := g_Map_PanelAtPoint(X, Y, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagLift)); + if (pan <> nil) then + begin + if ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end; + if ((pan.PanelType and PANEL_LIFTUP) <> 0) then + begin + if (VelY > -4-Random(3)) then VelY -= 0.8; + if (Abs(VelX) > 0.1) then VelX -= VelX/10.0; + VelX += (Random-Random)*0.2; + AccelY := 0.15; + end; + if ((pan.PanelType and PANEL_LIFTLEFT) <> 0) then + begin + if (VelX > -8-Random(3)) then VelX -= 0.8; + AccelY := 0.15; + end; + if ((pan.PanelType and PANEL_LIFTRIGHT) <> 0) then + begin + if (VelX < 8+Random(3)) then VelX += 0.8; + AccelY := 0.15; + end; + end; + {$ENDIF} dX := Round(VelX); dY := Round(VelY); + {$IF not DEFINED(D2F_NEW_SPARK_THINKER)} if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then + begin if (State <> STATE_STICK) and (not isBlockedAt(X, Y-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and (not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and @@ -793,24 +798,88 @@ begin AccelY := 0.5; State := STATE_NORMAL; end; - - if dX <> 0 then + end; + {$ELSE} + if (State <> STATE_STICK) and (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then begin - if dX > 0 then - s := 1 - else - s := -1; + // Âèñèò â âîçäóõå - êàïàåò + if (nil = g_Map_traceToNearest(X, Y-1, X, Y+1, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey)) then + begin + VelY := 0.8; + AccelY := 0.5; + State := STATE_NORMAL; + end; + end; + {$ENDIF} + {$IF DEFINED(D2F_NEW_SPARK_THINKER)} + // horizontal + if (dX <> 0) then + begin + pan := g_Map_traceToNearest(X, Y, X+dX, Y, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey); + X := ex; + // free to ride? + if (pan <> nil) then + begin + // nope + if (dY > 0) and ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end; + // Ñòåíà/äâåðü? + if ((pan.tag and (GridTagWall or GridTagDoor or GridTagStep)) <> 0) then + begin + VelX := 0; + VelY := 0; + AccelX := 0; + AccelY := 0; + State := STATE_STICK; + if (dX > 0) then stickDX := 1 else stickDX := -1; + end; + end; + if (X < 0) or (X >= gMapInfo.Width) then begin die(); exit; end; + end; + // vertical + if (dY <> 0) then + begin + pan := g_Map_traceToNearest(X, Y, X, Y+dY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey); + Y := ey; + // free to ride? + if (pan <> nil) then + begin + // nope + if (dY > 0) and ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end; + // Ñòåíà/äâåðü? + if ((pan.tag and (GridTagWall or GridTagDoor or GridTagStep)) <> 0) then + begin + VelX := 0; + VelY := 0; + AccelX := 0; + AccelY := 0; + if (dY > 0) and (State <> STATE_STICK) then + begin + State := STATE_NORMAL; + end + else + begin + State := STATE_STICK; + if (g_Map_PanelAtPoint(X-1, Y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := -1 + else if (g_Map_PanelAtPoint(X+1, Y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := 1 + else stickDX := 0; + end; + end; + end; + if (Y < 0) or (Y >= gMapInfo.Height) then begin die(); exit; end; + end; + {$ELSE} + // horizontal + if (dX <> 0) then + begin + if (dX > 0) then s := 1 else s := -1; for b := 1 to Abs(dX) do begin - // Ñáîêó ãðàíèöà? + // Ñáîêó ãðàíèöà? if (X+s >= w) or (X+s <= 0) then begin die(); break;end; - //c := gCollideMap[Y, X+s]; - // Ñáîêó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò? if isLiquidAt(X+s, Y) {ByteBool(c and MARK_LIQUID)} and (dY > 0) then begin die(); break; end; - if isBlockedAt(X+s, Y) {ByteBool(c and MARK_BLOCKED)} then begin // Ñòåíà/äâåðü VelX := 0; @@ -820,47 +889,80 @@ begin State := STATE_STICK; Break; end; - X := X+s; end; end; - - if dY <> 0 then + // vertical + if (dY <> 0) then begin - if dY > 0 then - s := 1 - else - s := -1; - + if (dY > 0) then s := 1 else s := -1; for b := 1 to Abs(dY) do begin // Ñíèçó/ñâåðõó ãðàíèöà if (Y+s >= h) or (Y+s <= 0) then begin die(); break; end; - //c := gCollideMap[Y+s, X]; - // Ñíèçó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò if isLiquidAt(X, Y+s) {ByteBool(c and MARK_LIQUID)} and (dY > 0) then begin die(); break; end; - if isBlockedAt(X, Y+s) {ByteBool(c and MARK_BLOCKED)} then begin // Ñòåíà/äâåðü VelX := 0; VelY := 0; AccelX := 0; AccelY := 0; - if (s > 0) and (State <> STATE_STICK) then - State := STATE_NORMAL - else - State := STATE_STICK; + if (s > 0) and (State <> STATE_STICK) then State := STATE_NORMAL else State := STATE_STICK; + break; + end; + Y := Y+s; + end; + end; + {$ENDIF} + + VelX += AccelX; + VelY += AccelY; + + Time += 1; +end; + + +// ////////////////////////////////////////////////////////////////////////// // +procedure TParticle.thinkerBubble (); +var + h: Integer; + dY: SmallInt; + b: Integer; + s: ShortInt; +begin + h := gMapInfo.Height; + + dY := Round(VelY); + + if dY <> 0 then + begin + if dY > 0 then + s := 1 + else + s := -1; + + for b := 1 to Abs(dY) do + begin + if (Y+s >= h) or (Y+s <= 0) then begin die(); break; end; + + (* + if not isLiquidAt(X, Y+s) {ByteBool(gCollideMap[Y+s, X] and MARK_LIQUID)} then + begin // Óæå íå æèäêîñòü + State := STATE_FREE; Break; end; + *) + // we traced liquid before, so don't bother checking + if (Y+s <= liquidTopY) then begin die(); break; end; Y := Y+s; end; end; - VelX := VelX + AccelX; - VelY := VelY + AccelY; + if VelY > -4 then + VelY := VelY + AccelY; Time := Time + 1; end; @@ -1286,7 +1388,7 @@ begin // trace liquid, so we'll know where it ends; do it in 8px steps for speed // tracer will return `false` if we started outside of the liquid - if not g_Map_TraceLiquid(X, Y, 0, -8, liquidx, liquidTopY) then continue; + if not g_Map_TraceLiquidNonPrecise(X, Y, 0, -8, liquidx, liquidTopY) then continue; VelX := 0; VelY := -1-Random; diff --git a/src/game/g_grid.pas b/src/game/g_grid.pas index 3ff9230..2afca2f 100644 --- a/src/game/g_grid.pas +++ b/src/game/g_grid.pas @@ -146,10 +146,14 @@ type // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it) // cb with `(nil)` will be called before processing new tile // no callback: return object of the nearest hit or nil + // if `inverted` is true, trace will register bodies *exluding* tagmask //WARNING: don't change tags in callbacks here! function traceRay (const x0, y0, x1, y1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP; overload; function traceRay (out ex, ey: Integer; const ax0, ay0, ax1, ay1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP; + //function traceRayWhileIn (const x0, y0, x1, y1: Integer; tagmask: Integer=-1): ITP; overload; + //function traceRayWhileIn (out ex, ey: Integer; const ax0, ay0, ax1, ay1: Integer; tagmask: Integer=-1): ITP; + //WARNING: don't modify grid while any query is in progress (no checks are made!) // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it) // trace line along the grid, calling `cb` for all objects in passed cells, in no particular order @@ -1291,23 +1295,7 @@ begin result := forEachAtPoint(ax0, ay0, nil, tagmask, @ptag); if (result <> nil) then begin - if assigned(cb) then - begin - if cb(result, ptag, ax0, ay0, ax0, ay0) then - begin - ex := ax0; - ey := ay0; - end - else - begin - result := nil; - end; - end - else - begin - ex := ax0; - ey := ay0; - end; + if assigned(cb) and not cb(result, ptag, ax0, ay0, ax0, ay0) then result := Default(ITP); end; exit; end; @@ -1880,4 +1868,324 @@ begin end; +// ////////////////////////////////////////////////////////////////////////// // +(* +function TBodyGridBase.traceRayWhileIn (const x0, y0, x1, y1: Integer; tagmask: Integer=-1): ITP; overload; +var + ex, ey: Integer; +begin + result := traceRayWhileIn(ex, ey, x0, y0, x1, y1, tagmask); +end; + + +// FUCKIN' PASTA! +function TBodyGridBase.traceRayWhileIn (out ex, ey: Integer; const ax0, ay0, ax1, ay1: Integer; tagmask: Integer=-1): ITP; +const + tsize = mTileSize; +var + wx0, wy0, wx1, wy1: Integer; // window coordinates + stx, sty: Integer; // "steps" for x and y axes + dsx, dsy: Integer; // "lengthes" for x and y axes + dx2, dy2: Integer; // "double lengthes" for x and y axes + xd, yd: Integer; // current coord + e: Integer; // "error" (as in bresenham algo) + rem: Integer; + term: Integer; + xptr, yptr: PInteger; + xfixed: Boolean; + temp: Integer; + prevx, prevy: Integer; + lastDistSq: Integer; + ccidx, curci: Integer; + hasUntried: Boolean; + lastGA: Integer = -1; + ga, x, y: Integer; + lastObj: ITP; + wasHit: Boolean = false; + gw, gh, minx, miny, maxx, maxy: Integer; + cc: PGridCell; + px: PBodyProxyRec; + lq: LongWord; + f, ptag, distSq: Integer; + x0, y0, x1, y1: Integer; + inx, iny: Integer; +begin + result := Default(ITP); + lastObj := Default(ITP); + tagmask := tagmask and TagFullMask; + ex := ax1; // why not? + ey := ay1; // why not? + if (tagmask = 0) then exit; + + if (ax0 = ax1) and (ay0 = ay1) then exit; // doesn't matter + + // we should start inside + if (forEachAtPoint(ax0, ay0, nil, tagmask, @ptag) = nil) then + begin + ex := ax0; // why not? + ey := ay0; // why not? + exit; + end; + + lastDistSq := distanceSq(ax0, ay0, ax1, ay1)+1; + + gw := mWidth; + gh := mHeight; + minx := mMinX; + miny := mMinY; + maxx := gw*tsize-1; + maxy := gh*tsize-1; + + x0 := ax0; + y0 := ay0; + x1 := ax1; + y1 := ay1; + + // offset query coords to (0,0)-based + Dec(x0, minx); + Dec(y0, miny); + Dec(x1, minx); + Dec(y1, miny); + + // clip rectange + wx0 := 0; + wy0 := 0; + wx1 := maxx; + wy1 := maxy; + + // horizontal setup + if (x0 < x1) then + begin + // from left to right + if (x0 > wx1) or (x1 < wx0) then exit; // out of screen + stx := 1; // going right + end + else + begin + // from right to left + if (x1 > wx1) or (x0 < wx0) then exit; // out of screen + stx := -1; // going left + x0 := -x0; + x1 := -x1; + wx0 := -wx0; + wx1 := -wx1; + swapInt(wx0, wx1); + end; + + // vertical setup + if (y0 < y1) then + begin + // from top to bottom + if (y0 > wy1) or (y1 < wy0) then exit; // out of screen + sty := 1; // going down + end + else + begin + // from bottom to top + if (y1 > wy1) or (y0 < wy0) then exit; // out of screen + sty := -1; // going up + y0 := -y0; + y1 := -y1; + wy0 := -wy0; + wy1 := -wy1; + swapInt(wy0, wy1); + end; + + dsx := x1-x0; + dsy := y1-y0; + + if (dsx < dsy) then + begin + xptr := @yd; + yptr := @xd; + swapInt(x0, y0); + swapInt(x1, y1); + swapInt(dsx, dsy); + swapInt(wx0, wy0); + swapInt(wx1, wy1); + swapInt(stx, sty); + end + else + begin + xptr := @xd; + yptr := @yd; + end; + + dx2 := 2*dsx; + dy2 := 2*dsy; + xd := x0; + yd := y0; + e := 2*dsy-dsx; + term := x1; + + xfixed := false; + if (y0 < wy0) then + begin + // clip at top + temp := dx2*(wy0-y0)-dsx; + xd += temp div dy2; + rem := temp mod dy2; + if (xd > wx1) then exit; // x is moved out of clipping rect, nothing to do + if (xd+1 >= wx0) then + begin + yd := wy0; + e -= rem+dsx; + if (rem > 0) then begin Inc(xd); e += dy2; end; + xfixed := true; + end; + end; + + if (not xfixed) and (x0 < wx0) then + begin + // clip at left + temp := dy2*(wx0-x0); + yd += temp div dx2; + rem := temp mod dx2; + if (yd > wy1) or (yd = wy1) and (rem >= dsx) then exit; + xd := wx0; + e += rem; + if (rem >= dsx) then begin Inc(yd); e -= dx2; end; + end; + + if (y1 > wy1) then + begin + // clip at bottom + temp := dx2*(wy1-y0)+dsx; + term := x0+temp div dy2; + rem := temp mod dy2; + if (rem = 0) then Dec(term); + end; + + if (term > wx1) then term := wx1; // clip at right + + Inc(term); // draw last point + //if (term = xd) then exit; // this is the only point, get out of here + + if (sty = -1) then yd := -yd; + if (stx = -1) then begin xd := -xd; term := -term; end; + dx2 -= dy2; + + // first move, to skip starting point + // DON'T DO THIS! loop will take care of that + if (xd = term) then + begin + result := forEachAtPoint(ax0, ay0, nil, tagmask, @ptag); + if (result <> nil) and ((ptag and tagmask) <> 0) then result := nil; + exit; + end; + + prevx := xptr^+minx; + prevy := yptr^+miny; + + // increase query counter + Inc(mLastQuery); + if (mLastQuery = 0) then + begin + // just in case of overflow + mLastQuery := 1; + for f := 0 to High(mProxies) do mProxies[f].mQueryMark := 0; + end; + lq := mLastQuery; + + ccidx := -1; + // draw it; can omit checks + while (xd <> term) do + begin + // check cell(s) + // new tile? + ga := (yptr^ div tsize)*gw+(xptr^ div tsize); + if (ga <> lastGA) then + begin + // yes + lastGA := ga; + ccidx := mGrid[lastGA]; + // no objects in cell == exit + if (ccidx = -1) then exit; + end; + // has something to process in this tile? + if (ccidx <> -1) then + begin + // process cell + curci := ccidx; + // convert coords to map (to avoid ajdusting coords inside the loop) + x := xptr^+minx; + y := yptr^+miny; + wasHit := false; + // process cell list + while (curci <> -1) do + begin + cc := @mCells[curci]; + for f := 0 to GridCellBucketSize-1 do + begin + if (cc.bodies[f] = -1) then break; + px := @mProxies[cc.bodies[f]]; + ptag := px.mTag; + if ((ptag and TagDisabled) = 0) and (px.mQueryMark <> lq) then + begin +function lineAABBIntersects (x0, y0, x1, y1: Integer; bx, by, bw, bh: Integer; out inx, iny: Integer): Boolean; + // can we process this proxy? + if (x >= px.mX) and (y >= px.mY) and (x < px.mX+px.mWidth) and (y < px.mY+px.mHeight) then + begin + px.mQueryMark := lq; // mark as processed + if ((ptag and tagmask) = 0) then + begin + result := px.mObj; + ex := x; + ey := y; + exit; + end; + // march out of the panel/cell + while (xd <> term) do + begin + if (e >= 0) then begin yd += sty; e -= dx2; end else e += dy2; + xd += stx; + // new cell? + ga := (yptr^ div tsize)*gw+(xptr^ div tsize); + if (ga <> lastGA) then break; + // out of panel? + if not ((x >= px.mX) and (y >= px.mY) and (x < px.mX+px.mWidth) and (y < px.mY+px.mHeight)) then break; + end; + end; + end; + end; + // next cell + curci := cc.next; + end; + // still has something interesting in this cell? + if not hasUntried then + begin + // nope, don't process this cell anymore; signal cell completion + ccidx := -1; + if assigned(cb) then + begin + if cb(nil, 0, x, y, prevx, prevy) then begin result := lastObj; exit; end; + end + else if wasHit then + begin + result := lastObj; + exit; + end; + end; + end; + //putPixel(xptr^, yptr^); + // move coords + prevx := xptr^+minx; + prevy := yptr^+miny; + if (e >= 0) then begin yd += sty; e -= dx2; end else e += dy2; + xd += stx; + end; + // we can travel less than one cell + if wasHit and not assigned(cb) then + begin + result := lastObj; + end + else + begin + ex := ax1; // why not? + ey := ay1; // why not? + end; +end; +*) + + end. diff --git a/src/game/g_map.pas b/src/game/g_map.pas index 2c2a6b5..f0419bf 100644 --- a/src/game/g_map.pas +++ b/src/game/g_map.pas @@ -104,10 +104,11 @@ 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_TraceLiquid (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean; +function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean; procedure g_Map_ProfilersBegin (); @@ -376,6 +377,14 @@ begin 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 @@ -2873,7 +2882,7 @@ 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_TraceLiquid (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean; +function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean; const MaskLiquid = GridTagWater or GridTagAcid1 or GridTagAcid2; begin -- 2.29.2