Merge pull request 'Make bots drift' (#47) from Anonimus/blankart:dobotsdrift into blankart-dev

Reviewed-on: https://codeberg.org/NepDisk/blankart/pulls/47
This commit is contained in:
NepDisk 2025-04-27 02:43:45 +00:00
commit b3922d1316
12 changed files with 373 additions and 6 deletions

View file

@ -5214,6 +5214,10 @@ thingtypes
title = "Waypoint Anchor";
sprite = "WAY2A0";
angletext = "ID";
flags1text = "[1] Powerslide";
flags4text = "[4] Drift left";
flags8text = "[8] Drift right";
parametertext = "End drift";
fixedrotation = 1;
}
2004

View file

@ -5374,6 +5374,20 @@ udmf
8 = "Finish line";
}
}
arg3
{
title = "Drifting";
type = 11;
enum
{
0 = "No action";
1 = "Left powerslide";
2 = "Left drift";
3 = "Right powerslide";
4 = "Right drift";
5 = "End drift";
}
}
}
2004

View file

@ -402,6 +402,17 @@ typedef enum
// for kickstartaccel
#define ACCEL_KICKSTART (TICRATE)
// Minimum percentage for a (non-auto) drift to begin.
#define BOTDRIFTPERCENT (10)
// Minimum turning percentage for an auto drift to begin.
#define DRIFTSTARTPCT (45)
#define DRIFTTICMUL (43690) // FRACUNIT * 0.66
#define BOTDRIFTTICS (FixedMul(TICRATE, DRIFTTICMUL))
#define MAXDRIFTSKILL (FRACUNIT/2)
typedef enum
{
BOT_STYLE_NORMAL,
@ -411,6 +422,13 @@ typedef enum
BOT_STYLE__MAX
} botStyle_e;
typedef enum {
DRIFTSTATE_AUTO=0,
DRIFTSTATE_ACTIVE,
DRIFTSTATE_ENDING,
NUMDRIFTSTATES
} botdrift_t;
// player_t struct for all bot variables
struct botvars_t
{
@ -429,6 +447,14 @@ struct botvars_t
SINT8 turnconfirm; // Confirm turn direction
UINT32 respawnconfirm; // Confirm when respawn is needed.
// Drift-relevant data below:
fixed_t driftskill; // The bot's "skill" at drifts.
// Determines how soon a bot starts to drift.
INT32 driftstate; // Drifting state
INT32 driftamt; // Turning severity for a drift
SINT8 driftturn; // Drifting turn direction
tic_t drifttime; // Time spent drifting
boolean powersliding; // Are we powersliding?
};
struct sonicloopcamvars_t

View file

@ -2617,6 +2617,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
p->botvars.rubberband = FRACUNIT;
p->botvars.controller = UINT16_MAX;
p->botvars.driftskill =
FixedMul(MAXDRIFTSKILL, K_BotDetermineDriftSkill(p));
p->botvars.driftstate = DRIFTSTATE_AUTO;
if (follower)
P_RemoveMobj(follower);

View file

@ -1035,6 +1035,147 @@ static void K_DrawPredictionDebug(botprediction_t *predict, const player_t *play
}
}
/*--------------------------------------------------
fixed_t K_BotDetermineDriftSkill(player_t *player)
Calculates drift skill for a player based on stats.
Input Arguments:-
player - Player to get drift skill for.
Return:-
Calculated drift skill.
--------------------------------------------------*/
fixed_t K_BotDetermineDriftSkill(player_t *player)
{
return ((FRACUNIT * (player->kartspeed + player->kartweight)) / 18);
}
static void K_WaypointGetDirectionVector(waypoint_t *wp1, waypoint_t *wp2, vector3_t *a_o)
{
vector3_t v1, v2;
v1.x = wp1->mobj->x;
v1.y = wp1->mobj->y;
v1.x = wp1->mobj->z;
v2.x = wp2->mobj->x;
v2.y = wp2->mobj->y;
v2.x = wp2->mobj->z;
FV3_SubEx(&v1,&v2,a_o);
FV3_Normalize(a_o);
}
/*--------------------------------------------------
static INT32 K_BotStartDrift(player_t* player)
Begins and ends "forced" drifts on a per-waypoint basis.
Input Arguments:-
player - Player to begin the drift for.
Return:-
Override value for turn amount.
--------------------------------------------------*/
static INT32 K_BotStartDrift(player_t* player)
{
// Handle DRIFTING towards waypoints!
boolean shouldDrift;
fixed_t botDriftSpeed, distToNext;
INT32 driftsetting, turnamt;
if ((!(player->currentwaypoint)) ||
(!(player->currentwaypoint->driftsettings)))
{
// No waypoints, nothing we can do here.
return 0;
}
turnamt = 0;
driftsetting = player->currentwaypoint->driftsettings;
botDriftSpeed = FixedMul(K_GetKartSpeed(player, false, false),
FixedPercentage(BOTDRIFTPERCENT));
if (((driftsetting)) &&
(driftsetting <= DRIFT_END))
{
shouldDrift = false;
// Randomly decide to drift based on our skill at drifting,
// and how fast we're moving.
fixed_t driftpotential = P_RandomKey(MAXDRIFTSKILL);
if ((driftpotential <= player->botvars.driftskill) &&
(botDriftSpeed <= player->speed))
{
{
// Get our distance from the waypoint.
distToNext = R_PointToDist2(
0,
player->mo->z,
R_PointToDist2(player->mo->x,
player->mo->y,
player->currentwaypoint->mobj->x,
player->currentwaypoint->mobj->y),
player->currentwaypoint->mobj->z);
// If we're within distance, start drifting!
if (distToNext < player->currentwaypoint->mobj->radius)
{
shouldDrift = true;
turnamt = KART_FULLTURN;
}
}
}
if (shouldDrift)
{
// Start our drift based on the waypoint's drift settings.
switch (driftsetting)
{
case DRIFT_PWRSLIDE_L:
player->botvars.driftstate = DRIFTSTATE_ACTIVE;
player->botvars.driftturn = -1;
player->botvars.powersliding = true;
break;
case DRIFT_LEFT:
player->botvars.driftstate = DRIFTSTATE_ACTIVE;
player->botvars.driftturn = -1;
player->botvars.powersliding = false;
break;
case DRIFT_PWRSLIDE_R:
player->botvars.driftstate = DRIFTSTATE_ACTIVE;
player->botvars.driftturn = 1;
player->botvars.powersliding = true;
turnamt *= -1;
break;
case DRIFT_RIGHT:
player->botvars.driftstate = DRIFTSTATE_ACTIVE;
player->botvars.driftturn = 1;
player->botvars.powersliding = false;
turnamt *= -1;
break;
case DRIFT_END:
if (player->botvars.driftstate)
{
player->botvars.driftstate = DRIFTSTATE_ENDING;
player->botvars.driftturn = 0;
player->botvars.powersliding = false;
turnamt = INT32_MAX; // Tells the game not to modify turning.
}
break;
case DRIFT_NONE:
default:
turnamt = 0;
break;
}
}
}
return turnamt;
}
/*--------------------------------------------------
static INT32 K_HandleBotTrack(const player_t *player, ticcmd_t *cmd, botprediction_t *predict)
@ -1053,7 +1194,7 @@ static INT32 K_HandleBotTrack(player_t *player, ticcmd_t *cmd, botprediction_t *
ZoneScoped;
// Handle steering towards waypoints!
INT32 turnamt = 0;
INT32 turnamt = 0, driftamt = 0;
SINT8 turnsign = 0;
angle_t moveangle;
INT32 anglediff;
@ -1080,6 +1221,9 @@ static INT32 K_HandleBotTrack(player_t *player, ticcmd_t *cmd, botprediction_t *
// Wrong way!
cmd->forwardmove = -MAXPLMOVE;
cmd->buttons |= BT_BRAKE;
// Why would we need to drift?
cmd->buttons &= ~(BT_DRIFT);
}
else
{
@ -1097,6 +1241,13 @@ static INT32 K_HandleBotTrack(player_t *player, ticcmd_t *cmd, botprediction_t *
realrad = playerwidth;
}
// Nonesensical and hacky. Figure out a better way eventually.
fixed_t turnpower =
FRACUNIT -
FixedMul(
FRACUNIT,
FixedDiv(std::max<angle_t>(0, ANGLE_90 - anglediff), ANGLE_90));
// Become more precise based on how hard you need to turn
// This makes predictions into turns a little nicer
// Facing 90 degrees away from the predicted point gives you 0 radius
@ -1130,6 +1281,33 @@ static INT32 K_HandleBotTrack(player_t *player, ticcmd_t *cmd, botprediction_t *
// Going the right way, don't turn at all.
turnamt = 0;
}
// Start or continue a drift.
if (player->botvars.drifttime)
{
// Confirm our drift angle.
if ((player->botvars.driftturn)
&& (player->botvars.drifttime < 4))
{
turnamt = KART_FULLTURN * -player->botvars.driftturn;
}
cmd->buttons |= BT_DRIFT;
}
else if ((turnamt) && (player->botvars.driftstate == DRIFTSTATE_AUTO) &&
(turnpower > FixedPercentage(DRIFTSTARTPCT)))
{
// TODO: Figure out a drift prediction system.
}
else if ((player->botvars.driftamt < 2) && (player->botvars.driftstate))
{
cmd->buttons |= BT_DRIFT;
if (player->botvars.driftamt != INT32_MAX)
{
turnamt = driftamt;
}
}
}
return turnamt;
@ -1567,6 +1745,40 @@ void K_UpdateBotGameplayVars(player_t *player)
player->botvars.respawnconfirm = 0;
}
else
{
// Figure out if we need to drift.
// Drift-ending waypoints will kill the drift timer,
// so no need to worry about doing that ourselves.
player->botvars.driftamt = K_BotStartDrift(player);
if (player->botvars.drifttime)
{
// Continue the drift until we go over the threshold.
// Only increment when we're finishing our drift, or
// just starting it!
if (((player->botvars.driftstate == DRIFTSTATE_ENDING) ||
(player->botvars.driftstate == DRIFTSTATE_AUTO)) ||
(player->botvars.drifttime < 4))
{
player->botvars.drifttime++;
}
if (player->botvars.drifttime > BOTDRIFTTICS + 3)
{
player->botvars.drifttime = 0;
player->botvars.driftstate = DRIFTSTATE_AUTO;
}
}
else if ((player->botvars.driftamt) && (player->botvars.driftstate))
{
if (player->botvars.driftamt != INT32_MAX)
{
// Ready to drift!
player->botvars.drifttime++;
}
}
}
K_UpdateBotGameplayVarsItemUsage(player);
}

View file

@ -376,6 +376,20 @@ void K_UpdateBotGameplayVars(player_t *player);
void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt);
/*--------------------------------------------------
fixed_t K_BotDetermineDriftSkill(player_t *player)
Calculates drift skill for a player based on stats.
Input Arguments:-
player - Player to get drift skill for.
Return:-
Calculated drift skill.
--------------------------------------------------*/
fixed_t K_BotDetermineDriftSkill(player_t *player);
#ifdef __cplusplus
} // extern "C"

View file

@ -2198,6 +2198,12 @@ static waypoint_t *K_SetupWaypoint(mobj_t *const mobj)
thiswaypoint->onaline = K_GetWaypointIsOnLine(thiswaypoint);
}
// Set drift settings.
if (mobj->watertop)
{
thiswaypoint->driftsettings = mobj->watertop;
}
if (thiswaypoint->numnextwaypoints > 0)
{
waypoint_t *nextwaypoint = NULL;
@ -2959,12 +2965,23 @@ static boolean K_AnchorWaypointRadius(
{
if (anchor->spawnpoint->angle == waypointmobj->spawnpoint->angle)
{
waypointmobj->radius = R_PointToDist2(
waypointmobj->x, waypointmobj->y,
anchor->x, anchor->y);
// Let's keep drift parameters and radius modifications separate.
if (anchor->extravalue1)
{
waypointmobj->watertop = anchor->extravalue1;
waypointmobj->spawnpoint->args[3] = waypointmobj->watertop;
}
else
{
waypointmobj->radius = R_PointToDist2(
waypointmobj->x, waypointmobj->y,
anchor->x, anchor->y);
// Keep changes for -writetextmap
waypointmobj->spawnpoint->args[1] = waypointmobj->radius >> FRACBITS;
}
// Keep changes for -writetextmap
waypointmobj->spawnpoint->args[1] = waypointmobj->radius >> FRACBITS;
return true;
}
else

View file

@ -24,6 +24,16 @@ extern "C" {
#define DEFAULT_WAYPOINT_RADIUS (384)
// "Powerslide" drifts in this context enable sliptiding.
typedef enum {
DRIFT_NONE=0,
DRIFT_PWRSLIDE_L,
DRIFT_LEFT,
DRIFT_PWRSLIDE_R,
DRIFT_RIGHT,
DRIFT_END
} driftSetting_e;
struct waypoint_t
{
mobj_t *mobj;
@ -34,6 +44,7 @@ struct waypoint_t
UINT32 *prevwaypointdistances;
size_t numnextwaypoints;
size_t numprevwaypoints;
UINT32 driftsettings;
};

View file

@ -122,6 +122,19 @@ FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedDiv(fixed_t a, fixed_t b)
return FixedDiv2(a, b);
}
/*!
\brief Converts a percentage into a fixed number.
*/
FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedPercentage(INT32 percent)
{
return FixedDiv((percent * FRACUNIT), 100 * FRACUNIT);
}
FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedPercentageFloat(float fpercent)
{
return FixedDiv(FloatToFixed(fpercent), 100 * FRACUNIT);
}
/** \brief The FixedSqrt function
\param x fixed_t number

View file

@ -3716,6 +3716,12 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result)
mo->player->driftcharge = 0;
}
// Regardless of bumpspark, tell bots to stop drifting if they bonk a wall.
mo->player->botvars.drifttime = 0;
mo->player->botvars.driftstate = DRIFTSTATE_AUTO;
mo->player->botvars.driftturn = 0;
mo->player->botvars.powersliding = false;
if (!cv_kartbumpspring.value || modeattacking != ATTACKING_NONE)
{
mo->player->pogospring = 0;

View file

@ -3110,6 +3110,11 @@ void P_MobjCheckWater(mobj_t *mobj)
fixed_t top2 = P_GetSectorCeilingZAt(sector, mobj->x, mobj->y);
fixed_t bot2 = P_GetSectorFloorZAt(sector, mobj->x, mobj->y);
if (mobj->type == MT_WAYPOINT)
{
CONS_Printf("P_MobjCheckWater waypoint call (oh boy...)\n");
}
// Default if no water exists.
mobj->watertop = mobj->waterbottom = mobj->z - 1000*FRACUNIT;
@ -3408,6 +3413,11 @@ static void P_SceneryCheckWater(mobj_t *mobj)
{
sector_t *sector;
if (mobj->type == MT_WAYPOINT)
{
CONS_Printf("P_SceneryCheckWater waypoint call (oh boy...)\n");
}
// Default if no water exists.
mobj->watertop = mobj->waterbottom = mobj->z - 1000*FRACUNIT;
@ -13083,7 +13093,9 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
// lastlook is used for indicating the waypoint is a shortcut
// extravalue1 is used for indicating the waypoint is disabled
// extravalue2 is used for indicating the waypoint is the finishline
// watertop is the waypoint's drift settings
mobj->threshold = mthing->args[0];
mobj->movecount = tag;
if (mthing->args[2] & TMWPF_DISABLED)
{
@ -13121,11 +13133,36 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
mobj->extravalue2 = 0;
}
mobj->watertop = mthing->args[3];
// Sryder 2018-12-7: Grabbed this from the old MT_BOSS3WAYPOINT section so they'll be in the waypointcap instead
P_SetTarget(&mobj->tracer, waypointcap);
P_SetTarget(&waypointcap, mobj);
break;
}
case MT_WAYPOINT_ANCHOR:
{
// Get the drift parameters from the anchor's spawnpoint.
// Drift left
mobj->extravalue1 = ((mthing->options & MTF_OBJECTSPECIAL) >> 1);
if (mthing->options & MTF_AMBUSH)
{
// Drift right
mobj->extravalue1 = ((mthing->options & MTF_AMBUSH) >> 1);
}
// Powerslide offset
mobj->extravalue1 = max(0, min(4, mobj->extravalue1 - (mthing->options & 1)));
if (mthing->extrainfo)
{
// If the parameter is set at all, this anchor ends the drift.
mobj->extravalue1 = 5;
}
break;
}
case MT_BOTHINT:
{
// Change size

View file

@ -376,6 +376,11 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT32(save->p, players[i].botvars.itemconfirm);
WRITESINT8(save->p, players[i].botvars.turnconfirm);
WRITEUINT32(save->p, players[i].botvars.respawnconfirm);
WRITEFIXED(save->p, players[i].botvars.driftskill);
WRITEINT32(save->p, players[i].botvars.driftstate);
WRITESINT8(save->p, players[i].botvars.driftturn);
WRITEUINT32(save->p, players[i].botvars.drifttime);
WRITEUINT8(save->p, players[i].botvars.powersliding);
WRITEFIXED(save->p, players[i].outrun);
WRITEUINT8(save->p, players[i].outruntime);
@ -694,6 +699,11 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].botvars.itemconfirm = READUINT32(save->p);
players[i].botvars.turnconfirm = READSINT8(save->p);
players[i].botvars.respawnconfirm = READUINT32(save->p);
players[i].botvars.driftskill = READFIXED(save->p);
players[i].botvars.driftstate = READINT32(save->p);
players[i].botvars.driftturn = READSINT8(save->p);
players[i].botvars.drifttime = READUINT32(save->p);
players[i].botvars.powersliding = (boolean)READUINT8(save->p);
players[i].outrun = READFIXED(save->p);
players[i].outruntime = READUINT8(save->p);