diff --git a/extras/blanbinary/BlanKartBinary-Config.cfg b/extras/blanbinary/BlanKartBinary-Config.cfg index 2e3782b28..e23530ffd 100644 --- a/extras/blanbinary/BlanKartBinary-Config.cfg +++ b/extras/blanbinary/BlanKartBinary-Config.cfg @@ -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 diff --git a/extras/blanudmf/Includes/BlanKart_things.cfg b/extras/blanudmf/Includes/BlanKart_things.cfg index 56a7edf69..2b0662aa8 100644 --- a/extras/blanudmf/Includes/BlanKart_things.cfg +++ b/extras/blanudmf/Includes/BlanKart_things.cfg @@ -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 diff --git a/src/d_player.h b/src/d_player.h index 1a56efb59..7b5e2b5a3 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -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 diff --git a/src/g_game.c b/src/g_game.c index 09001f2a6..a7a5d98c1 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -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); diff --git a/src/k_bot.cpp b/src/k_bot.cpp index 798ab6764..c96b4a708 100644 --- a/src/k_bot.cpp +++ b/src/k_bot.cpp @@ -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(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); } diff --git a/src/k_bot.h b/src/k_bot.h index 9230a2182..2f56c569a 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -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" diff --git a/src/k_waypoint.cpp b/src/k_waypoint.cpp index 7337286e5..b30b913e7 100644 --- a/src/k_waypoint.cpp +++ b/src/k_waypoint.cpp @@ -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 diff --git a/src/k_waypoint.h b/src/k_waypoint.h index c41305fe1..f0e1474a7 100644 --- a/src/k_waypoint.h +++ b/src/k_waypoint.h @@ -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; }; diff --git a/src/m_fixed.h b/src/m_fixed.h index c8fb01b88..1c147c401 100644 --- a/src/m_fixed.h +++ b/src/m_fixed.h @@ -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 diff --git a/src/p_map.c b/src/p_map.c index 877b274d3..2933a8dd3 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -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; diff --git a/src/p_mobj.c b/src/p_mobj.c index 62f824e46..9f381fd3a 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -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 diff --git a/src/p_saveg.c b/src/p_saveg.c index bd6c00b3b..305ba5a57 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -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);