blankart/src/k_cluster.cpp
2025-12-21 21:41:13 -05:00

397 lines
12 KiB
C++

#include "k_cluster.hpp"
#include <vector>
#include "d_player.h"
#include "doomdef.h"
#include "doomstat.h"
#include "g_game.h"
#include "hu_stuff.h"
#include "k_kart.h"
#include "m_fixed.h"
#include "r_main.h"
static fixed_t K_PlayerDistance3D(player_t* source, player_t* destination)
{
if ((!source->mo) || (!destination->mo))
return INT32_MAX; // Return a garbage value.
return K_Distance3D(source->mo->x,
source->mo->y,
source->mo->z,
destination->mo->x,
destination->mo->y,
destination->mo->z);
}
extern "C"
{
player_t* closesttocluster;
fixed_t K_Distance3D(fixed_t x1, fixed_t y1, fixed_t z1, fixed_t x2, fixed_t y2, fixed_t z2)
{
fixed_t dist_xy = R_PointToDist2(x1, y1, x2, y2);
return R_PointToDist2(0, z1, dist_xy, z2);
}
UINT32 K_GetDTFDifference(player_t* source, player_t* destination)
{
if ((!source->mo) || (!destination->mo))
return INT16_MAX; // Return a garbage value.
if (destination->distancetofinish > source->distancetofinish)
return (destination->distancetofinish - source->distancetofinish);
return (source->distancetofinish - destination->distancetofinish);
}
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<UINT8>(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;
N = 0;
vector3_t neighborvector;
std::vector<player_t*> clusternodes;
if (!sourcePlayer->mo)
return 0;
meanx = meany = meanz = 0;
INT32 i;
UINT8 pingame = 0;
// To hell with this; scan for ourselves and pre-emptively flag ourselves.
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
continue;
if (&players[i] == sourcePlayer)
{
clusterplayer[i] = true;
}
if (players[i].spectator)
continue; // spectator
if (!players[i].mo)
continue;
pingame++;
}
for (i = 0; i < MAXPLAYERS; i++)
{
player_t* player;
if (!playeringame[i])
continue;
player = &players[i];
if (player == sourcePlayer)
{
// Ignore ourselves.
continue;
}
if (clusterplayer[i])
continue; // Ignore players we've scanned already.
if (player->spectator)
continue; // spectator
if (!player->mo)
continue;
if (cluster_filter)
{
if (!cluster_filter(player, filterval))
continue;
}
// Scan all points in the database
fixed_t dist = K_PlayerDistance3D(sourcePlayer, player);
/*CONS_Printf("%s: checking %f against epsilon value %f\n",
player_names[i],
FIXED_TO_FLOAT(dist), FIXED_TO_FLOAT(eps)
);*/
if (dist <= eps)
{
clusterplayer[i] = true;
// CONS_Printf("%s is in this cluster\n", player_names[i]);
if (nodevec)
static_cast<std::vector<player_t*>*>(nodevec)->push_back(player);
// Scan our neighbor for any players close to them.
K_CountNeighboringPlayersByDistance(
player, eps, cluster_filter, filterval, &neighborvector, nodevec);
}
}
clusternodes = *static_cast<std::vector<player_t*>*>(nodevec);
N = clusternodes.size();
if (N != 0)
{
// Return the average center point of this cluster.
for (i = 0; i < N; i++)
{
if (!clusternodes[i])
continue;
if (!clusternodes[i]->mo)
continue;
meanx += clusternodes[i]->mo->x / FRACUNIT;
meany += clusternodes[i]->mo->y / FRACUNIT;
meanz += clusternodes[i]->mo->z / FRACUNIT;
}
meanx /= N;
meany /= N;
meanz /= N;
// CONS_Printf("mean x: %lld, mean y: %lld, mean z: %lld\n", meanx, meany, meanz);
out->x = (fixed_t)(meanx) << FRACBITS;
out->y = (fixed_t)(meany) << FRACBITS;
out->z = (fixed_t)(meanz) << FRACBITS;
// Get the player closest to the cluster.
fixed_t disttocluster, bestdist;
bestdist = INT32_MAX;
for (i = 0; i < N; i++)
{
if (!clusternodes[i])
continue;
if (!clusternodes[i]->mo)
continue;
disttocluster = K_Distance3D(clusternodes[i]->mo->x,
clusternodes[i]->mo->y,
clusternodes[i]->mo->z,
out->x,
out->y,
out->z);
if (disttocluster < bestdist)
{
closesttocluster = clusternodes[i];
bestdist = disttocluster;
}
}
}
else
{
out->z = out->y = out->x = 0;
}
return N;
}
static std::vector<player_t*> dtf_vec;
static boolean cleardtf = true;
static UINT32 K_GetU32Diff(UINT32 a, UINT32 b)
{
return (b > a) ? (b - a) : (a - b);
}
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;
}
if (!sourcePlayer->mo)
return 0;
mean = 0;
INT32 i;
for (i = 0; i < MAXPLAYERS; i++)
{
player_t* player;
if (!playeringame[i])
continue;
player = &players[i];
if (player == sourcePlayer)
continue; // Ignore ourselves.
if (clusterplayer[i])
continue; // Ignore players we've scanned already.
if (player->spectator)
continue; // spectator
if (!player->mo)
continue;
if (cluster_filter)
{
if (!cluster_filter(player, filterval))
continue;
}
// Scan all points in the database
if ((fixed_t)K_GetDTFDifference(sourcePlayer, player) <= eps)
{
// CONS_Printf("%d is less than %d, adding to nodes vector\n",
// K_GetDTFDifference(sourcePlayer, player), eps);
clusterplayer[i] = true;
if (nodevec)
{
// Add to the result.
static_cast<std::vector<player_t*>*>(nodevec)->push_back(player);
}
// Scan our neighbor for any players close to them.
K_CountNeighboringPlayersByDTFEx(
player, eps, cluster_filter, filterval, &neighborvector, nodevec);
}
}
N = static_cast<std::vector<player_t*>*>(nodevec)->size();
if (N != 0)
{
// Return the average center point of this cluster.
for (i = 0; i < N; i++)
{
if (!static_cast<std::vector<player_t*>*>(nodevec)->at(i))
continue;
if (!static_cast<std::vector<player_t*>*>(nodevec)->at(i)->mo)
continue;
mean += static_cast<std::vector<player_t*>*>(nodevec)->at(i)->distancetofinish;
}
mean /= N;
out->x = (fixed_t)(mean);
out->y = 0;
out->z = 0;
// Get the player closest to the cluster.
UINT32 disttocluster, bestdist;
bestdist = UINT32_MAX;
for (i = 0; i < N; i++)
{
disttocluster = K_GetU32Diff(
static_cast<std::vector<player_t*>*>(nodevec)->at(i)->distancetofinish, mean);
if (disttocluster < bestdist)
{
closesttocluster = static_cast<std::vector<player_t*>*>(nodevec)->at(i);
bestdist = disttocluster;
}
}
}
else
{
out->z = out->y = out->x = 0;
}
return N;
}
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<player_t*> dummy = {0};
dummy.clear();
INT32 result = K_CountNeighboringPlayersByDistance(
sourcePlayer, eps, cluster_filter, filterval, out, &dummy);
return result;
}
}