diff --git a/src/p_user.c b/src/p_user.c index b3c605159..f95d5afc1 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2112,6 +2112,78 @@ static void P_3dMovement(player_t *player) } } +// For turning correction in P_UpdatePlayerAngle. +// Given a range of possible steering inputs, finds a steering input that corresponds to the desired angle change. +static INT16 P_FindClosestTurningForAngle(player_t *player, INT32 targetAngle, INT16 lowBound, INT16 highBound) +{ + INT16 newBound; + INT16 preferred = lowBound; + int attempts = 0; + + // Only works if our low bound is actually our low bound. + if (highBound < lowBound) + { + INT16 tmp = lowBound; + lowBound = highBound; + highBound = tmp; + } + + // Slightly frumpy binary search for the ideal turning input. + // We do this instead of reversing K_GetKartTurnValue so that future handling changes are automatically accounted for. + + while (attempts++ < 20) // Practical calls of this function search maximum 10 times, this is solely for safety. + { + // These need to be treated as signed, or situations where boundaries straddle 0 are a mess. + INT32 lowAngle = K_GetKartTurnValue(player, lowBound) << TICCMD_REDUCE; + INT32 highAngle = K_GetKartTurnValue(player, highBound) << TICCMD_REDUCE; + + // EXIT CONDITION 1: Hopeless search, target angle isn't between boundaries at all. + if (lowAngle >= targetAngle) + return lowBound; + if (highAngle <= targetAngle) + return highBound; + + // Test the middle of our steering range, so we can see which side is more promising. + newBound = (lowBound + highBound) / 2; + + // EXIT CONDITION 2: Boundaries converged and we're all out of precision. + if (newBound == lowBound || newBound == highBound) + break; + + INT32 newAngle = K_GetKartTurnValue(player, newBound) << TICCMD_REDUCE; + + angle_t lowError = abs(targetAngle - lowAngle); + angle_t highError = abs(targetAngle - highAngle); + angle_t newError = abs(targetAngle - newAngle); + + // CONS_Printf("steering %d / %d / %d - angle %d / %d / %d - TA %d - error %d / %d / %d\n", lowBound, newBound, highBound, lowAngle, newAngle, highAngle, targetAngle, lowError, newError, highError); + + // EXIT CONDITION 3: We got lucky! + if (lowError == 0) + return lowBound; + if (newError == 0) + return newBound; + if (highError == 0) + return highBound; + + // If not, store the best estimate... + if (lowError <= newError && lowError <= highError) + preferred = lowBound; + if (highError <= newError && highError <= lowError) + preferred = highBound; + if (newError <= lowError && newError <= highError) + preferred = newBound; + + // ....and adjust the bounds for another run. + if (lowAngle <= targetAngle && targetAngle <= newAngle) + highBound = newBound; + else + lowBound = newBound; + } + + return preferred; +} + // // P_UpdatePlayerAngle // @@ -2148,6 +2220,13 @@ static void P_UpdatePlayerAngle(player_t *player) angle_t targetAngle = (player->cmd.angle) << TICCMD_REDUCE; angle_t targetDelta = targetAngle - (player->mo->angle); + // Corrections via fake turn go through easing. + // That means undoing them takes the same amount of time as doing them. + // This can lead to oscillating death spiral states on a multi-tic correction, as we swing past the target angle. + // So before we go into death-spirals, if our predicton is _almost_ right... + angle_t leniency = (2*ANG1/3) * min(player->cmd.latency, 6); + // Don't force another turning tic, just give them the desired angle! + if (targetDelta == angleChange || player->pflags & PF_DRIFTEND || (maxTurnRight == 0 && maxTurnLeft == 0)) { // We are where we need to be. @@ -2158,25 +2237,15 @@ static void P_UpdatePlayerAngle(player_t *player) // so we momentarily ignore the camera angle and let the server trust our inputs instead. // That way, even if you're steering blind, you get the intended "kick-out" effect. } - else if (targetDelta >= ANGLE_180 && maxTurnLeft >= targetDelta) // Overshot, so just fudge it. + else { - angleChange = targetDelta; - player->steering = targetsteering; - } - else if (targetDelta <= ANGLE_180 && maxTurnRight <= targetDelta) // Overshot, so just fudge it. - { - angleChange = targetDelta; - player->steering = targetsteering; - } - else if (targetDelta >= ANGLE_180 && maxTurnLeft < targetDelta) // Undershot, slam the stick. - { - angleChange = maxTurnLeft; - player->steering = steeringLeft; - } - else if (targetDelta <= ANGLE_180 && maxTurnRight > targetDelta) // Undershot, slam the stick. - { - angleChange = maxTurnRight; - player->steering = steeringRight; + // We're off. Try to legally steer the player towards their camera. + player->steering = P_FindClosestTurningForAngle(player, targetDelta, steeringLeft, steeringRight); + angleChange = K_GetKartTurnValue(player, player->steering) << TICCMD_REDUCE; + + // And if the resulting steering input is close enough, snap them exactly. + if (min(targetDelta - angleChange, angleChange - targetDelta) <= leniency) + angleChange = targetDelta; } } else @@ -2213,6 +2282,7 @@ static void P_UpdatePlayerAngle(player_t *player) } } + // // P_SpectatorMovement // diff --git a/src/r_things.c b/src/r_things.c index dc73628d8..92f9148ee 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -1065,6 +1065,25 @@ static void R_DrawVisSprite(vissprite_t *vis) pwidth = patch->width; #endif + if (vis->x1test && vis->x2test) + { + INT32 x1test = vis->x1test; + INT32 x2test = vis->x2test; + + if (x1test < 0) + x1test = 0; + + if (x2test >= vid.width) + x2test = vid.width-1; + + const INT32 t = (vis->startfrac + (vis->xiscale * (x2test - x1test))) >> FRACBITS; + + if (x1test <= x2test && (t < 0 || t >= pwidth)) + { + CONS_Printf("THE GAME WOULD HAVE CRASHED, %d (old) vs %d (new)\n", (x2test - x1test), (vis->x2 - vis->x1)); + } + } + // Non-paper drawing loop for (dc_x = vis->x1; dc_x <= vis->x2; dc_x++, frac += vis->xiscale, sprtopscreen += vis->shear.tan) { @@ -1452,6 +1471,9 @@ static void R_ProjectDropShadow( shadow->x1 = x1 < portalclipstart ? portalclipstart : x1; shadow->x2 = x2 >= portalclipend ? portalclipend-1 : x2; + shadow->x1test = 0; + shadow->x2test = 0; + shadow->xscale = FixedMul(xscale, shadowxscale); //SoM: 4/17/2000 shadow->scale = FixedMul(yscale, shadowyscale); shadow->thingscale = interp.scale; @@ -1598,6 +1620,9 @@ static void R_ProjectBoundingBox(mobj_t *thing, vissprite_t *vis) box->sortscale = yscale; box->dispoffset = 0; } + + box->x1test = 0; + box->x2test = 0; } // @@ -1616,6 +1641,7 @@ static void R_ProjectSprite(mobj_t *thing) fixed_t sort_x = 0, sort_y = 0, sort_z; INT32 x1, x2; + INT32 x1test = 0, x2test = 0; spritedef_t *sprdef; spriteframe_t *sprframe; @@ -2001,18 +2027,35 @@ static void R_ProjectSprite(mobj_t *thing) scalestep = 0; yscale = sortscale; tx += offset; - x1 = (centerxfrac + FixedMul(tx,xscale))>>FRACBITS; + //x1 = (centerxfrac + FixedMul(tx,xscale))>>FRACBITS; + x1 = centerx + (FixedMul(tx,xscale) / FRACUNIT); + + x1test = (centerxfrac + FixedMul(tx,xscale))>>FRACBITS; + + if (x1test > viewwidth) + x1test = 0; // off the right side? if (x1 > viewwidth) return; tx += offset2; - x2 = ((centerxfrac + FixedMul(tx,xscale))>>FRACBITS); x2--; + //x2 = ((centerxfrac + FixedMul(tx,xscale))>>FRACBITS); x2--; + x2 = (centerx + (FixedMul(tx,xscale) / FRACUNIT)) - 1; + + x2test = ((centerxfrac + FixedMul(tx,xscale))>>FRACBITS) - 1; + + if (x2test < 0) + x2test = 0; // off the left side if (x2 < 0) return; + +#if 0 + if ((x2 - x1) != (x2test - x1test)) + CONS_Printf("[%d] %d != %d\n", objectsdrawn, x2 - x1, x2test - x1test); +#endif } // Adjust the sort scale if needed @@ -2101,6 +2144,12 @@ static void R_ProjectSprite(mobj_t *thing) if (x2 < portalclipstart || x1 >= portalclipend) return; + if (x2test < portalclipstart || x1test >= portalclipend) + { + x1test = 0; + x2test = 0; + } + if (P_PointOnLineSide(interp.x, interp.y, portalclipline) != 0) return; } @@ -2269,6 +2318,9 @@ static void R_ProjectSprite(mobj_t *thing) vis->x1 = x1 < portalclipstart ? portalclipstart : x1; vis->x2 = x2 >= portalclipend ? portalclipend-1 : x2; + vis->x1test = x1test < portalclipstart ? portalclipstart : x1test; + vis->x2test = x2test >= portalclipend ? portalclipend-1 : x2test; + vis->sector = thing->subsector->sector; vis->szt = (INT16)((centeryfrac - FixedMul(vis->gzt - viewz, sortscale))>>FRACBITS); vis->sz = (INT16)((centeryfrac - FixedMul(vis->gz - viewz, sortscale))>>FRACBITS); @@ -2290,6 +2342,12 @@ static void R_ProjectSprite(mobj_t *thing) if (shadowdraw || shadoweffects) { + if (x1test && x2test) + { + vis->xiscaletest = (patch->width<width<shear.offset = vis->x1-x1; @@ -2303,6 +2361,7 @@ static void R_ProjectSprite(mobj_t *thing) { vis->startfrac = spr_width-1; vis->xiscale = -iscale; + vis->xiscaletest = -vis->xiscaletest; } else { @@ -2310,10 +2369,13 @@ static void R_ProjectSprite(mobj_t *thing) vis->xiscale = iscale; } + vis->startfractest = vis->startfrac; + if (vis->x1 > x1) { vis->startfrac += FixedDiv(vis->xiscale, this_scale) * (vis->x1 - x1); vis->scale += FixedMul(scalestep, spriteyscale) * (vis->x1 - x1); + vis->startfractest += FixedDiv(vis->xiscaletest, this_scale) * (vis->x1test - x1test); } vis->transmap = R_GetBlendTable(blendmode, trans); @@ -2525,6 +2587,9 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing) vis->x1 = x1 < portalclipstart ? portalclipstart : x1; vis->x2 = x2 >= portalclipend ? portalclipend-1 : x2; + vis->x1test = 0; + vis->x2test = 0; + vis->xscale = xscale; //SoM: 4/17/2000 vis->sector = thing->subsector->sector; vis->szt = (INT16)((centeryfrac - FixedMul(vis->gzt - viewz, yscale))>>FRACBITS); diff --git a/src/r_things.h b/src/r_things.h index 4ea4b8342..2d82b4381 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -161,6 +161,9 @@ struct vissprite_t mobj_t *mobj; // for easy access INT32 x1, x2; + INT32 x1test, x2test; + fixed_t xiscaletest; + fixed_t startfractest; fixed_t gx, gy; // for line side calculation fixed_t gz, gzt; // global bottom/top for silhouette clipping and sorting with 3D floors