From 75bdd14cef072768f8eee9ed1b25590dc6a2202c Mon Sep 17 00:00:00 2001 From: yamamama Date: Sat, 13 Dec 2025 12:26:31 -0500 Subject: [PATCH] Refactor cluster filtering Double-clustering still needs to be done --- src/k_cluster.cpp | 107 +++++++++++++++++++++++++++++++++++----------- src/k_cluster.hpp | 13 ++++-- src/k_items.c | 2 +- src/k_kart.c | 6 +-- 4 files changed, 97 insertions(+), 31 deletions(-) diff --git a/src/k_cluster.cpp b/src/k_cluster.cpp index a24751e65..ce4f4c29e 100644 --- a/src/k_cluster.cpp +++ b/src/k_cluster.cpp @@ -39,7 +39,50 @@ static UINT32 K_GetDTFDifference(player_t *source, player_t *destination) extern "C" { player_t *closesttocluster; -INT32 K_CountNeighboringPlayersByDistance(player_t *sourcePlayer, fixed_t eps, vector3_t *out, void *nodevec) +boolean K_ClusterFilter_NoFilter(player_t *player, UINT32 fval) +{ + // Ignores literally every parameter and returns true. + return true; +} + +// Store our last memory of the cluster player (because we're clearing it out for the second cluster). +static boolean clusterplayer_memory[MAXPLAYERS]; + +// Filters players based on various factors, such as playercount and position. +// fval = pingame +boolean K_ClusterFilter_BaseFilter(player_t *player, UINT32 fval) +{ + UINT8 pingame = static_cast(fval & 0xFF); + + if (player->position >= pingame) + { + return false; // Ignore last place. *They* need help the most! + } + + if (pingame > 6) + { + // FIXME: Double-cluster algorithm. + // Can't do it from within this function, or we're going to have too much overhead. + // However: at above 18 players, we shouldn't even *consider* double-clustering. + if (!K_IsPlayerLosing(player)) + { + return false; // Ignore winning players to prevent sandbagging. + } + } + else if (pingame > 4) + { + if (player->position <= 1) + { + return false; // Ignore the frontrunner. + } + } + + // Nothing to filter. + + return true; +} + +INT32 K_CountNeighboringPlayersByDistance(player_t *sourcePlayer, fixed_t eps, playerfilter_f *cluster_filter, UINT32 filterval, vector3_t *out, void *nodevec) { INT64 meanx, meany, meanz; INT32 N; @@ -99,11 +142,11 @@ INT32 K_CountNeighboringPlayersByDistance(player_t *sourcePlayer, fixed_t eps, v if (!player->mo) continue; - if (!K_IsPlayerLosing(player)) - continue; // Ignore winning players to prevent sandbagging. - - if (player->position >= pingame) - continue; // Ignore last place. *They* need help the most! + if (cluster_filter) + { + if (!cluster_filter(player, filterval)) + continue; + } // Scan all points in the database fixed_t dist = K_PlayerDistance3D(sourcePlayer, player); @@ -121,7 +164,7 @@ INT32 K_CountNeighboringPlayersByDistance(player_t *sourcePlayer, fixed_t eps, v static_cast *>(nodevec)->push_back(player); // Scan our neighbor for any players close to them. - K_CountNeighboringPlayersByDistance(player, eps, &neighborvector, nodevec); + K_CountNeighboringPlayersByDistance(player, eps, cluster_filter, filterval, &neighborvector, nodevec); } } @@ -191,17 +234,22 @@ static UINT32 K_GetU32Diff(UINT32 a, UINT32 b) return (b > a) ? (b - a) : (a - b); } -INT32 K_CountNeighboringPlayersByDTF(player_t *sourcePlayer, fixed_t eps, vector3_t *out) +INT32 K_CountNeighboringPlayersByDTFEx(player_t *sourcePlayer, fixed_t eps, playerfilter_f *cluster_filter, UINT32 filterval, vector3_t *out, void *nodevec) { INT64 mean; INT32 N = 0; vector3_t neighborvector; + if (!nodevec) + { + // Nice goddamn try. + return 0; + } + + std::vector node_dtf = *static_cast *>(nodevec); + if (!sourcePlayer->mo) return 0; - - if (cleardtf) - dtf_vec.clear(); mean = 0; @@ -235,31 +283,34 @@ INT32 K_CountNeighboringPlayersByDTF(player_t *sourcePlayer, fixed_t eps, vector { //CONS_Printf("%d is less than %d, adding to nodes vector\n", K_GetDTFDifference(sourcePlayer, player), eps); clusterplayer[i] = true; - dtf_vec.push_back(player); // Add to result - cleardtf = false; + if (nodevec) + { + // Add to the result. + static_cast *>(nodevec)->push_back(player); + } // Scan our neighbor for any players close to them. - K_CountNeighboringPlayersByDTF(player, eps, &neighborvector); - - cleardtf = true; + K_CountNeighboringPlayersByDTFEx(player, eps, cluster_filter, filterval, &neighborvector, nodevec); } } - N = dtf_vec.size(); + N = node_dtf.size(); + + CONS_Printf("node count: %d\n", N); if (N != 0) { // Return the average center point of this cluster. for (i = 0; i < N; i++) { - if (!dtf_vec[i]) + if (!node_dtf[i]) continue; - if (!dtf_vec[i]->mo) + if (!node_dtf[i]->mo) continue; - mean += dtf_vec[i]->distancetofinish; + mean += node_dtf[i]->distancetofinish; } mean /= N; @@ -272,11 +323,11 @@ INT32 K_CountNeighboringPlayersByDTF(player_t *sourcePlayer, fixed_t eps, vector bestdist = UINT32_MAX; for (i = 0; i < N; i++) { - disttocluster = K_GetU32Diff(dtf_vec[i]->distancetofinish, mean); + disttocluster = K_GetU32Diff(node_dtf[i]->distancetofinish, mean); if (disttocluster < bestdist) { - closesttocluster = dtf_vec[i]; + closesttocluster = node_dtf[i]; bestdist = disttocluster; } } @@ -289,14 +340,22 @@ INT32 K_CountNeighboringPlayersByDTF(player_t *sourcePlayer, fixed_t eps, vector return N; } -INT32 K_CountNeighboringPlayers(player_t *sourcePlayer, fixed_t eps, vector3_t *out) +INT32 K_CountNeighboringPlayersByDTF(player_t *sourcePlayer, fixed_t eps, playerfilter_f *cluster_filter, UINT32 filterval, vector3_t *out) +{ + if (cleardtf) + dtf_vec.clear(); + + return K_CountNeighboringPlayersByDTFEx(sourcePlayer, eps, cluster_filter, filterval, out, &dtf_vec); +} + +INT32 K_CountNeighboringPlayers(player_t *sourcePlayer, fixed_t eps, playerfilter_f *cluster_filter, UINT32 filterval, vector3_t *out) { // Dummy vector so the game doesn't SIGSEGV. // Turns out, it's also necessary for this system not to be placebo! std::vector dummy = {0}; dummy.clear(); - INT32 result = K_CountNeighboringPlayersByDistance(sourcePlayer, eps, out, &dummy); + INT32 result = K_CountNeighboringPlayersByDistance(sourcePlayer, eps, cluster_filter, filterval, out, &dummy); return result; } diff --git a/src/k_cluster.hpp b/src/k_cluster.hpp index 874cac269..33befe025 100644 --- a/src/k_cluster.hpp +++ b/src/k_cluster.hpp @@ -7,11 +7,18 @@ extern "C" { #endif +typedef boolean (playerfilter_f)(player_t *player, UINT32 filter_value); extern player_t *closesttocluster; -INT32 K_CountNeighboringPlayersByDistance(player_t *sourcePlayer, fixed_t eps, vector3_t *out, void *nodevec); -INT32 K_CountNeighboringPlayersByDTF(player_t *sourcePlayer, fixed_t eps, vector3_t *out); -INT32 K_CountNeighboringPlayers(player_t *sourcePlayer, fixed_t eps, vector3_t *out); +// Literally allows any player to pass through. +boolean K_ClusterFilter_NoFilter(player_t *player, UINT32 fval); + +boolean K_ClusterFilter_BaseFilter(player_t *player, UINT32 fval); + +INT32 K_CountNeighboringPlayersByDistance(player_t *sourcePlayer, fixed_t eps, playerfilter_f *cluster_filter, UINT32 filterval, vector3_t *out, void *nodevec); +INT32 K_CountNeighboringPlayersByDTFEx(player_t *sourcePlayer, fixed_t eps, playerfilter_f *cluster_filter, UINT32 filterval, vector3_t *out, void *nodevec); +INT32 K_CountNeighboringPlayersByDTF(player_t *sourcePlayer, fixed_t eps, playerfilter_f *cluster_filter, UINT32 filterval, vector3_t *out); +INT32 K_CountNeighboringPlayers(player_t *sourcePlayer, fixed_t eps, playerfilter_f *cluster_filter, UINT32 filterval, vector3_t *out); #ifdef __cplusplus } diff --git a/src/k_items.c b/src/k_items.c index 749deb2e5..30b83a4aa 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -1591,7 +1591,7 @@ static void K_DoGrowShrink(player_t *player, boolean shrinking) if (shrinking) { // Find neighbors - INT32 n = K_CountNeighboringPlayers(player, ALTSHRINK_EPSILON, &(vector3_t){}); + INT32 n = K_CountNeighboringPlayers(player, ALTSHRINK_EPSILON, &K_ClusterFilter_NoFilter, 0, &(vector3_t){}); // For every neighbor, add some iframes for a clean breakaway. UINT32 iframes = BASE_IFRAMES + n * NEIGHBOR_IFRAMES; diff --git a/src/k_kart.c b/src/k_kart.c index c186281be..a0b31beff 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -9519,7 +9519,7 @@ static UINT32 K_UpdateDistanceFromCluster(player_t *player) player->invincibilitybottleneck = (UINT16)(-1); // No bottlenecking! return 0; } - else if ((pingame > 3) && (pingame < MINCLUSTERPLAYERS)) + else if ((pingame == 3)) { // "Second place" in this case is actually second to last. // In very small lobbies, it's harder for players to cluster together. @@ -9854,7 +9854,7 @@ static UINT32 K_UndoMapScaling(UINT32 distance) // Based on DBSCAN, so it "chain-scans" neighboring players as well for a more accurate result. UINT32 clusterid = 0; -static vector3_t *K_FindPlayerCluster(fixed_t eps, INT32 (*func)(player_t *, fixed_t, vector3_t *), vector3_t *out) +static vector3_t *K_FindPlayerCluster(fixed_t eps, INT32 (*func)(player_t *, fixed_t, playerfilter_f *, UINT32, vector3_t *), vector3_t *out) { INT32 bestdensity = 0; // Cluster counter vector3_t tempclusterpoint; @@ -9882,7 +9882,7 @@ static vector3_t *K_FindPlayerCluster(fixed_t eps, INT32 (*func)(player_t *, fix continue; // Find neighbors - INT32 N = func(player, eps, &tempclusterpoint); + INT32 N = func(player, eps, &K_ClusterFilter_BaseFilter, 0, &tempclusterpoint); // Double-remove the clusterplayer flag. Bad hack, I know... clusterplayer[i] = false;