diff --git a/src/console.c b/src/console.c index 6acf3e205..87c3a3e1e 100644 --- a/src/console.c +++ b/src/console.c @@ -1654,6 +1654,7 @@ static const char *CON_LoadingStrings[LOADED_ALLDONE+1] = "Init rendering daemon...", //LOADED_RINIT "Init audio subsystem...", //LOADED_SINITSFXCHANNELS "Cache HUD...", //LOADED_STINIT + "Test fixed-point arithmetic...", //LOADED_MATHINIT "Init ACSVM...", //LOADED_ACSINIT "Check game status...", //LOADED_DCHECKNETGAME "Now starting..." diff --git a/src/console.h b/src/console.h index d10aeacdd..fb2f5827c 100644 --- a/src/console.h +++ b/src/console.h @@ -52,6 +52,7 @@ typedef enum LOADED_RINIT, LOADED_SINITSFXCHANNELS, LOADED_STINIT, + LOADED_MATHINIT, LOADED_ACSINIT, LOADED_DCHECKNETGAME, LOADED_ALLDONE = LOADED_DCHECKNETGAME, diff --git a/src/d_main.cpp b/src/d_main.cpp index 111490711..79a6f9d80 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -1814,6 +1814,61 @@ void D_SRB2Main(void) ST_Init(); CON_SetLoadingProgress(LOADED_STINIT); + CONS_Printf("FixedSqrt of 32767 fracunits: %d; FixedSqrt64: %d\n", + FixedSqrt(0x7FFF0000), + FixedSqrt64(0x7FFF0000)); + + if (FixedSqrt(0x7FFF0000) == FixedSqrt64(0x7FFF0000)) + { + CONS_Printf("\x83" "32767: Test OK!" "\x80" "\n"); + } + else + { + CONS_Printf("\x85" "32767: Test NG!" "\x80" "\n"); + } + + CONS_Printf("FixedSqrt of 4 fracunits: %d; FixedSqrt64: %d\n", + FixedSqrt(0x40000), + FixedSqrt64(0x40000)); + + if (FixedSqrt(0x40000) == FixedSqrt64(0x40000)) + { + CONS_Printf("\x83" "4: Test OK!" "\x80" "\n"); + } + else + { + CONS_Printf("\x85" "4: Test NG!" "\x80" "\n"); + } + + // You should probably generate the weird number with RANDOM.org +#define WEIRDNUMBER 3886284 /* 59.3 fracunits; this should approximate to around 7 */ + CONS_Printf("FixedSqrt of 59.3 fracunits: %d; FixedSqrt64: %d\n", + FixedSqrt(WEIRDNUMBER), + FixedSqrt64(WEIRDNUMBER)); + + if (FixedSqrt(WEIRDNUMBER) == FixedSqrt64(WEIRDNUMBER)) + { + CONS_Printf("\x83" "59.3: Test OK!" "\x80" "\n"); + } + else + { + CONS_Printf("\x85" "59.3: Test NG!" "\x80" "\n"); + } +#undef WEIRDNUMBER + + CONS_Printf("FixedSqrt64 of 65535 fracunits: %d (not possible at 32-bit scale)\n", + FixedSqrt64(0xFFFFFFFF)); + + CONS_Printf("IntSqrt of 32767: %d; IntSqrt64: %d\n", + IntSqrt(0x7FFF), + IntSqrt64(0x7FFF)); + + CONS_Printf("IntSqrt of 4: %d; IntSqrt64: %d\n", + IntSqrt(4), + IntSqrt64(4)); + + CON_SetLoadingProgress(LOADED_MATHINIT); + CONS_Printf("ACS_Init(): Init Action Code Script VM.\n"); ACS_Init(); CON_SetLoadingProgress(LOADED_ACSINIT); diff --git a/src/k_odds.c b/src/k_odds.c index b2468f658..385a14ca4 100644 --- a/src/k_odds.c +++ b/src/k_odds.c @@ -160,6 +160,58 @@ static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = #define ENDDIST (12*ACTIVEDISTVAR) // Distance when the game stops giving you bananas +// 1/21/2025: I hate tiptoeing around the integer limit. +// This is at a smaller scale. +static UINT32 K_Dist2D(INT32 x1, INT32 y1, INT32 x2, INT32 y2) +{ + // d = √((x2 - x1)² + (y2 - y1)²) + INT32 xdiff, ydiff; + INT64 xprod, yprod; + + xdiff = (x2 - x1); + ydiff = (y2 - y1); + + xprod = ((INT64)xdiff * (INT64)xdiff); + yprod = ((INT64)ydiff * (INT64)ydiff); + + return (UINT32)(IntSqrt64(xprod + yprod)); +} + +// Basic integer distancing, to quote myself: +// "Even if you did 256 units for 1 fracunit in distancing, it'd be a better result than trying to +// deal with overflows on a system that's already being pushed to the limit by needing 65536 units +// for precision. No seriously, I don't think anyone's losing sleep over "hmmmmm, 0.0000152 or +// 0.0039?????" when most 2D game engines only give a fuck about MAYBE 0.001" +static UINT32 K_IntDistance(fixed_t curx, + fixed_t cury, + fixed_t curz, + fixed_t destx, + fixed_t desty, + fixed_t destz) +{ + return K_Dist2D(0, + curz / FRACUNIT, + K_Dist2D(curx / FRACUNIT, cury / FRACUNIT, destx / FRACUNIT, desty / FRACUNIT), + destz / FRACUNIT); +} + +// This one uses map scaling instead, use in case of loss of depth on mobjscaled maps. +static UINT32 K_IntDistanceForMap(fixed_t curx, + fixed_t cury, + fixed_t curz, + fixed_t destx, + fixed_t desty, + fixed_t destz) +{ + return K_Dist2D(0, + curz / mapobjectscale, + K_Dist2D(curx / mapobjectscale, + cury / mapobjectscale, + destx / mapobjectscale, + desty / mapobjectscale), + destz / mapobjectscale); +} + SINT8 K_ItemResultToType(SINT8 getitem) { if (getitem <= 0 || getitem >= NUMKARTRESULTS) // Sad (Fallback) @@ -839,10 +891,16 @@ INT32 K_GetRollingRouletteItem(player_t *player) return translation[(player->itemroulette % roulette_size) / 3]; } +// Legacy odds are fickle and finicky, so we exaggerate distances +// to simulate parity with pathfind odds. +#define LEGACYODDSEXAGGERATE (2*FRACUNIT/3) + UINT32 K_CalculateInitalPDIS(const player_t *player, UINT8 pingame) { UINT8 i; - UINT32 pdis = 0; + + // Hacky solution to the overflow problem: use 64-bit integers! + UINT64 pdis = 0; if (!K_UsingLegacyCheckpoints()) { @@ -969,25 +1027,15 @@ UINT32 K_CalculateInitalPDIS(const player_t *player, UINT8 pingame) secondPlayer = &players[secondIndex]; // Add the distance to the player behind you. - // Distances are divided by 4, then further divided by a fracunit - // to prevent overflow issues. - pdis += (P_AproxDistance(P_AproxDistance( - firstPlayer->mo->x/4 - secondPlayer->mo->x/4, - firstPlayer->mo->y/4 - secondPlayer->mo->y/4), - firstPlayer->mo->z/4 - secondPlayer->mo->z/4) - / FRACUNIT); - - // Scale it to mostly return to normalcy. - pdis = (pdis * 2); - - // Again, but this time base it on playercount, same form as - // the following vanilla adjustment, but much weaker since it - // stacks with it - - // MAXODDS should always match the number of players the game is designed - // around, so there shouldn't be any issues with basing this calc around that. - // Hacky solution to the overflow problem: use 64-bit integers! - pdis = (UINT32)(((UINT64)(100 + MAXODDS - min(pingame, 16)) * pdis) / 100); + // At a (relative to map) integer scale using basic distancing + // arithmetic; more accurate and less concern for overflows. + pdis += K_IntDistanceForMap( + secondPlayer->mo->x, + secondPlayer->mo->y, + secondPlayer->mo->z, + firstPlayer->mo->x, + firstPlayer->mo->y, + firstPlayer->mo->z); // Advance to next index. firstIndex = secondIndex; @@ -998,9 +1046,22 @@ UINT32 K_CalculateInitalPDIS(const player_t *player, UINT8 pingame) } } - return pdis; + // Exaggerate odds; don't you love the legacy system? :Trollic: + pdis = FixedMul64(pdis, LEGACYODDSEXAGGERATE); + + // Clamp pdis to the highest 32-bit integer. + // This is a shitty solution for overflow prevention, but it's an overflow prevention + // nonethless. + if (pdis > UINT32_MAX) + { + pdis = UINT32_MAX; + } + + return (UINT32)(pdis); } +#undef LEGACYODDSEXAGGERATE + UINT32 K_CalculatePDIS(const player_t *player, UINT8 numPlayers, boolean *spbrush) { UINT32 pdis = 0; diff --git a/src/m_fixed.c b/src/m_fixed.c index 5e5e589fb..c5b4ab03f 100644 --- a/src/m_fixed.c +++ b/src/m_fixed.c @@ -61,6 +61,45 @@ fixed_t FixedSqrt(fixed_t x) #endif } +// Shitty 64-bit duplicate so certain distancing edgecases in k_odds die +INT64 FixedSqrt64(INT64 x) +{ +#ifdef HAVE_SQRT + const float fx = FixedToFloat64(x); + float fr; +#ifdef HAVE_SQRTF + fr = sqrtf(fx); +#else + fr = (float)sqrt(fx); +#endif + return FloatToFixed64(fr); +#else + // The neglected art of Fixed Point arithmetic + // Jetro Lauha + // Seminar Presentation + // Assembly 2006, 3rd- 6th August 2006 + // (Revised: September 13, 2006) + // URL: http://jet.ro/files/The_neglected_art_of_Fixed_Point_arithmetic_20060913.pdf + register UINT64 root, remHi, remLo, testDiv, count; + root = 0; /* Clear root */ + remHi = 0; /* Clear high part of partial remainder */ + remLo = x; /* Get argument into low part of partial remainder */ + count = (31 + (FRACBITS >> 1)); /* Load loop counter */ + do + { + remHi = (remHi << 2) | (remLo >> 62); remLo <<= 2; /* get 2 bits of arg */ + root <<= 1; /* Get ready for the next bit in the root */ + testDiv = (root << 1) + 1; /* Test radical */ + if (remHi >= testDiv) + { + remHi -= testDiv; + root += 1; + } + } while (count-- != 0); + return root; +#endif +} + fixed_t FixedHypot(fixed_t x, fixed_t y) { // Moved the code from R_PointToDist2 to here, @@ -1122,6 +1161,11 @@ FUNCMATH FUNCINLINE static inline fixed_t FixedMulC(fixed_t a, fixed_t b) return (fixed_t)((((INT64)a * b)) / FRACUNIT); } +FUNCMATH FUNCINLINE static inline INT64 FixedMul64C(fixed_t a, fixed_t b) +{ + return (INt64)((((INT64)a * b)) / FRACUNIT); +} + FUNCMATH FUNCINLINE static inline fixed_t FixedDivC2(fixed_t a, fixed_t b) { INT64 ret; diff --git a/src/m_fixed.h b/src/m_fixed.h index b7722bd15..2080c396f 100644 --- a/src/m_fixed.h +++ b/src/m_fixed.h @@ -55,11 +55,21 @@ FUNCMATH FUNCINLINE static ATTRINLINE float FixedToFloat(fixed_t x) return x / (float)FRACUNIT; } +FUNCMATH FUNCINLINE static ATTRINLINE float FixedToFloat64(INT64 x) +{ + return (float)x / (float)FRACUNIT; +} + FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FloatToFixed(float f) { return (fixed_t)(f * FRACUNIT); } +FUNCMATH FUNCINLINE static ATTRINLINE INT64 FloatToFixed64(float f) +{ + return (INT64)(f * FRACUNIT); +} + // for backwards compat #define FIXED_TO_FLOAT(x) FixedToFloat(x) // (((float)(x)) / ((float)FRACUNIT)) #define FLOAT_TO_FIXED(f) FloatToFixed(f) // (fixed_t)((f) * ((float)FRACUNIT)) @@ -79,6 +89,13 @@ FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedMul(fixed_t a, fixed_t b) return (fixed_t)(((UINT64)((INT64)a * b)) >> FRACBITS); } +FUNCMATH FUNCINLINE static ATTRINLINE INT64 FixedMul64(INT64 a, INT64 b) +{ + // Need to cast to unsigned before shifting to avoid undefined behaviour + // for negative integers + return (INT64)(((UINT64)(a * b)) >> FRACBITS); +} + /** \brief The FixedDiv2 function \param a fixed_t number @@ -145,6 +162,12 @@ FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedPercentageFloat(float fpercen */ FUNCMATH fixed_t FixedSqrt(fixed_t x); +FUNCMATH INT64 FixedSqrt64(INT64 x); + +// Theoretically, we can feed this any integer value and it'll output an +// accurate square root * 256. +#define IntSqrt(x) (INT32)(FixedSqrt(x) / 256) +#define IntSqrt64(x) (INT64)(FixedSqrt64(x) / 256) /** \brief The FixedHypot function