diff --git a/src/deh_soc.c b/src/deh_soc.c index d6661933a..adb44c61e 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -3819,6 +3819,7 @@ void readfollower(MYFILE *f) s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); // Ready the default variables for followers. We will overwrite them as we go! We won't set the name or states RIGHT HERE as this is handled down instead. + followers[numfollowers].mode = FOLLOWERMODE_FLOAT; followers[numfollowers].scale = FRACUNIT; followers[numfollowers].bubblescale = 0; // No bubble by default followers[numfollowers].atangle = FixedAngle(230 * FRACUNIT); @@ -3865,6 +3866,18 @@ void readfollower(MYFILE *f) strcpy(followers[numfollowers].name, word2); nameset = true; } + else if (fastcmp(word, "MODE")) + { + if (word2) + strupr(word2); + + if (fastcmp(word2, "FLOAT") || fastcmp(word2, "DEFAULT")) + followers[numfollowers].mode = FOLLOWERMODE_FLOAT; + else if (fastcmp(word2, "GROUND")) + followers[numfollowers].mode = FOLLOWERMODE_GROUND; + else + deh_warning("Follower %d: unknown follower mode '%s'", numfollowers, word2); + } else if (fastcmp(word, "DEFAULTCOLOR")) { followers[numfollowers].defaultcolor = get_number(word2); @@ -3997,11 +4010,18 @@ void readfollower(MYFILE *f) // fallbacks for variables // Print a warning if the variable is on a weird value and set it back to the minimum available if that's the case. + + if (followers[numfollowers].mode < FOLLOWERMODE_FLOAT || followers[numfollowers].mode >= FOLLOWERMODE__MAX) + { + followers[numfollowers].mode = FOLLOWERMODE_FLOAT; + deh_warning("Follower '%s': Value for 'mode' should be between %d and %d.", dname, FOLLOWERMODE_FLOAT, FOLLOWERMODE__MAX-1); + } + #define FALLBACK(field, field2, threshold, set) \ if ((signed)followers[numfollowers].field < threshold) \ { \ followers[numfollowers].field = set; \ - deh_warning("Follower '%s': Value for '%s' is too low! Minimum should be %d. Value was overwritten to %d.", dname, field2, set, set); \ + deh_warning("Follower '%s': Value for '%s' is too low! Minimum should be %d. Value was overwritten to %d.", dname, field2, threshold, set); \ } \ FALLBACK(dist, "DIST", 0, 0); @@ -4016,6 +4036,8 @@ if ((signed)followers[numfollowers].field < threshold) \ FALLBACK(scale, "SCALE", 1, 1); // No null/negative scale FALLBACK(bubblescale, "BUBBLESCALE", 0, 0); // No negative scale +#undef FALLBACK + // Special case for color I suppose if (followers[numfollowers].defaultcolor > (unsigned)(numskincolors-1)) { @@ -4023,8 +4045,6 @@ if ((signed)followers[numfollowers].field < threshold) \ deh_warning("Follower \'%s\': Value for 'color' should be between 1 and %d.\n", dname, numskincolors-1); } -#undef FALLBACK - // also check if we forgot states. If we did, we will set any missing state to the follower's idlestate. // Print a warning in case we don't have a fallback and set the state to S_INVISIBLE (rather than S_NULL) if unavailable. diff --git a/src/k_botitem.c b/src/k_botitem.c index 465c45adb..48ed6283c 100644 --- a/src/k_botitem.c +++ b/src/k_botitem.c @@ -1308,7 +1308,7 @@ void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt) K_BotItemGenericOrbitShield(player, cmd); } else if (player->position != 1) // Hold onto orbiting items when in 1st :) - /* FALL-THRU */ + /* FALLTHRU */ case KITEM_BALLHOG: { K_BotItemOrbinaut(player, cmd); diff --git a/src/k_follower.c b/src/k_follower.c index 532adbe24..76a2d9dd7 100644 --- a/src/k_follower.c +++ b/src/k_follower.c @@ -7,6 +7,7 @@ #include "doomdef.h" #include "g_game.h" #include "g_demo.h" +#include "r_main.h" #include "r_skins.h" #include "p_local.h" #include "p_mobj.h" @@ -185,7 +186,9 @@ void K_HandleFollower(player_t *player) follower_t fl; angle_t an; fixed_t zoffs; + fixed_t ourheight; fixed_t sx, sy, sz, deltaz; + fixed_t fh = INT32_MIN, ch = INT32_MAX; UINT16 color; fixed_t bubble; // bubble scale (0 if no bubble) @@ -235,17 +238,39 @@ void K_HandleFollower(player_t *player) deltaz = (player->mo->z - player->mo->old_z); // for the z coordinate, don't be a doof like Steel and forget that MFE_VERTICALFLIP exists :P - sz = player->mo->z + player->mo->momz + FixedMul(player->mo->scale, zoffs) * P_MobjFlip(player->mo); + sz = player->mo->z + player->mo->momz + FixedMul(player->mo->scale, zoffs * P_MobjFlip(player->mo)); + ourheight = FixedMul(fl.height, player->mo->scale); if (player->mo->eflags & MFE_VERTICALFLIP) { - sz += FixedMul(fl.height, player->mo->scale); + sz += ourheight; } - // finally, add a cool floating effect to the z height. - // not stolen from k_kart I swear!! + fh = player->mo->floorz; + ch = player->mo->ceilingz - ourheight; + + switch (fl.mode) { - fixed_t sine = FixedMul(fl.bobamp, FINESINE(((FixedMul(4 * M_TAU_FIXED, fl.bobspeed) * leveltime) >> ANGLETOFINESHIFT) & FINEMASK)); - sz += FixedMul(player->mo->scale, sine) * P_MobjFlip(player->mo); + case FOLLOWERMODE_GROUND: + { + if (player->mo->eflags & MFE_VERTICALFLIP) + { + sz = ch; + } + else + { + sz = fh; + } + break; + } + case FOLLOWERMODE_FLOAT: + default: + { + // finally, add a cool floating effect to the z height. + // not stolen from k_kart I swear!! + fixed_t sine = FixedMul(fl.bobamp, FINESINE(((FixedMul(4 * M_TAU_FIXED, fl.bobspeed) * leveltime) >> ANGLETOFINESHIFT) & FINEMASK)); + sz += FixedMul(player->mo->scale, sine) * P_MobjFlip(player->mo); + break; + } } // Set follower colour @@ -317,11 +342,39 @@ void K_HandleFollower(player_t *player) } // move the follower next to us (yes, this is really basic maths but it looks pretty damn clean in practice)! - // 02/09/2021: cast lag to int32 otherwise funny things happen since it was changed to uint32 in the struct player->follower->momx = FixedDiv(sx - player->follower->x, fl.horzlag); player->follower->momy = FixedDiv(sy - player->follower->y, fl.horzlag); + player->follower->z += FixedDiv(deltaz, fl.vertlag); - player->follower->momz = FixedDiv(sz - player->follower->z, fl.vertlag); + + if (fl.mode == FOLLOWERMODE_GROUND) + { + sector_t *sec = R_PointInSubsector(sx, sy)->sector; + + fh = min(fh, P_GetFloorZ(player->follower, sec, sx, sy, NULL)); + ch = max(ch, P_GetCeilingZ(player->follower, sec, sx, sy, NULL) - ourheight); + + if (P_IsObjectOnGround(player->mo) == false) + { + // In the air, match their momentum. + player->follower->momz = player->mo->momz; + } + else + { + fixed_t fg = P_GetMobjGravity(player->mo); + fixed_t fz = P_GetMobjZMovement(player->follower); + + player->follower->momz = fz; + + // Player is on the ground ... try to get the follower + // back to the ground also if it is above it. + player->follower->momz += FixedDiv(fg * 6, fl.vertlag); // Scaled against the default value of vertlag + } + } + else + { + player->follower->momz = FixedDiv(sz - player->follower->z, fl.vertlag); + } if (player->mo->colorized) { @@ -347,7 +400,15 @@ void K_HandleFollower(player_t *player) } // if we're moving let's make the angle the direction we're moving towards. This is to avoid drifting / reverse looking awkward. - destAngle = K_MomentumAngle(player->follower); + if (FixedHypot(player->follower->momx, player->follower->momy) >= player->mo->scale) + { + destAngle = R_PointToAngle2(0, 0, player->follower->momx, player->follower->momy); + } + else + { + // Face the player's angle when standing still. + destAngle = player->mo->angle; + } // Sal: Turn the follower around when looking backwards. if ( player->cmd.buttons & BT_LOOKBACK ) @@ -363,6 +424,35 @@ void K_HandleFollower(player_t *player) player->follower->angle += FixedDiv(angleDiff, fl.anglelag); } + // Ground follower slope rotation + if (fl.mode == FOLLOWERMODE_GROUND) + { + if (player->follower->z <= fh) + { + player->follower->z = fh; + if (player->follower->momz < 0) + { + player->follower->momz = 0; + } + } + else if (player->follower->z >= ch) + { + player->follower->z = ch; + if (player->follower->momz > 0) + { + player->follower->momz = 0; + } + } + + K_CalculateBananaSlope( + player->follower, + player->follower->x, player->follower->y, player->follower->z, + player->follower->radius, ourheight, + (player->mo->eflags & MFE_VERTICALFLIP), + false + ); + } + // Finally, if the follower has bubbles, move them, set their scale, etc.... // This is what I meant earlier by it being easier, now we can just use this weird lil loop to get the job done! diff --git a/src/k_follower.h b/src/k_follower.h index b39eae9e6..27e070814 100644 --- a/src/k_follower.h +++ b/src/k_follower.h @@ -22,6 +22,13 @@ extern CV_PossibleValue_t Followercolor_cons_t[]; // follower colours table, not a duplicate because of the "Match" option. +typedef enum +{ + FOLLOWERMODE_FLOAT, // Default behavior, floats in the position you set it to. + FOLLOWERMODE_GROUND, // Snaps to the ground & rotates with slopes. + FOLLOWERMODE__MAX +} followermode_t; + // // We'll define these here because they're really just a mobj that'll follow some rules behind a player // @@ -31,6 +38,7 @@ typedef struct follower_s char name[SKINNAMESIZE+1]; // Name. This is used for the menus. We'll just follow the same rules as skins for this. skincolornum_t defaultcolor; // default color for menus. + followermode_t mode; // Follower behavior modifier. fixed_t scale; // Scale relative to the player's. fixed_t bubblescale; // Bubble scale relative to the player scale. If not set, no bubble will spawn (default) diff --git a/src/k_kart.c b/src/k_kart.c index 42adaf268..200e6b734 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -3347,7 +3347,7 @@ fixed_t K_3dKartMovement(player_t *player) angle_t K_MomentumAngle(mobj_t *mo) { - if (mo->momx || mo->momy) + if (FixedHypot(mo->momx, mo->momy) >= mo->scale) { return R_PointToAngle2(0, 0, mo->momx, mo->momy); } @@ -6244,7 +6244,7 @@ static fixed_t K_BananaSlopeZ(pslope_t *slope, fixed_t x, fixed_t y, fixed_t z, return P_GetZAt(slope, testx, testy, z); } -static void K_CalculateBananaSlope(mobj_t *mobj, fixed_t x, fixed_t y, fixed_t z, fixed_t radius, fixed_t height, boolean flip, boolean player) +void K_CalculateBananaSlope(mobj_t *mobj, fixed_t x, fixed_t y, fixed_t z, fixed_t radius, fixed_t height, boolean flip, boolean player) { fixed_t newz; sector_t *sec; diff --git a/src/k_kart.h b/src/k_kart.h index 20c8cd33f..1e45e0637 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -94,6 +94,7 @@ void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source); void K_UpdateHnextList(player_t *player, boolean clean); void K_DropHnextList(player_t *player, boolean keepshields); void K_RepairOrbitChain(mobj_t *orbit); +void K_CalculateBananaSlope(mobj_t *mobj, fixed_t x, fixed_t y, fixed_t z, fixed_t radius, fixed_t height, boolean flip, boolean player); player_t *K_FindJawzTarget(mobj_t *actor, player_t *source); INT32 K_GetKartRingPower(player_t *player, boolean boosted); void K_UpdateDistanceFromFinishLine(player_t *const player);