From c758a8ad27aa21946082b69085ee852570f0d67c Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 17 Oct 2023 01:22:47 -0700 Subject: [PATCH 01/25] Try to correctly interpolate chase camera viewpoint as angle changes --- src/r_fps.c | 10 ++++++---- src/r_fps.h | 1 + src/r_main.cpp | 11 +++++++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/r_fps.c b/src/r_fps.c index 1418b2a22..61c3eb192 100644 --- a/src/r_fps.c +++ b/src/r_fps.c @@ -140,10 +140,6 @@ void R_InterpolateView(fixed_t frac) prevview = newview; } - viewx = R_LerpFixed(prevview->x, newview->x, frac); - viewy = R_LerpFixed(prevview->y, newview->y, frac); - viewz = R_LerpFixed(prevview->z, newview->z, frac); - viewangle = R_LerpAngle(prevview->angle, newview->angle, frac); aimingangle = R_LerpAngle(prevview->aim, newview->aim, frac); viewroll = R_LerpAngle(prevview->roll, newview->roll, frac); @@ -151,6 +147,12 @@ void R_InterpolateView(fixed_t frac) viewsin = FINESINE(viewangle>>ANGLETOFINESHIFT); viewcos = FINECOSINE(viewangle>>ANGLETOFINESHIFT); + fixed_t zoom = R_LerpFixed(prevview->zoom, newview->zoom, frac); + + viewx = R_LerpFixed(prevview->x, newview->x, frac) - FixedMul(viewcos, zoom); + viewy = R_LerpFixed(prevview->y, newview->y, frac) - FixedMul(viewsin, zoom); + viewz = R_LerpFixed(prevview->z, newview->z, frac); + viewplayer = newview->player; viewsector = R_PointInSubsector(viewx, viewy)->sector; diff --git a/src/r_fps.h b/src/r_fps.h index 969c427fa..f2df84cd9 100644 --- a/src/r_fps.h +++ b/src/r_fps.h @@ -48,6 +48,7 @@ struct viewvars_t { fixed_t x; fixed_t y; fixed_t z; + fixed_t zoom; boolean sky; sector_t *sector; player_t *player; diff --git a/src/r_main.cpp b/src/r_main.cpp index 7277c2ace..e05c88fe3 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -1244,6 +1244,7 @@ void R_SetupFrame(int s) newview->x = r_viewmobj->x; newview->y = r_viewmobj->y; newview->z = r_viewmobj->z; + newview->zoom = 0; R_SetupCommonFrame(player, r_viewmobj->subsector); } @@ -1252,9 +1253,13 @@ void R_SetupFrame(int s) { r_viewmobj = NULL; - newview->x = thiscam->x; - newview->y = thiscam->y; + fixed_t x = player->mo ? player->mo->x : thiscam->x; + fixed_t y = player->mo ? player->mo->y : thiscam->y; + + newview->x = x; + newview->y = y; newview->z = thiscam->z + (thiscam->height>>1); + newview->zoom = FixedHypot(thiscam->x - x, thiscam->y - y); R_SetupCommonFrame(player, thiscam->subsector); } @@ -1267,6 +1272,7 @@ void R_SetupFrame(int s) newview->x = r_viewmobj->x; newview->y = r_viewmobj->y; newview->z = player->viewz; + newview->zoom = 0; R_SetupCommonFrame(player, r_viewmobj->subsector); } @@ -1297,6 +1303,7 @@ void R_SkyboxFrame(int s) newview->x = r_viewmobj->x; newview->y = r_viewmobj->y; newview->z = r_viewmobj->z; // 26/04/17: use actual Z position instead of spawnpoint angle! + newview->zoom = 0; if (mapheaderinfo[gamemap-1]) { From 0e57da56b0f0752cebbaff8ad3139ca8cee337c8 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 15 Oct 2023 23:33:43 -0700 Subject: [PATCH 02/25] Loop camera It zooms out, pans to the side and toward the player. Loop Center thing: - arg2: zoom-out speed in tics (zooms out when entering the loop) - arg3: zoom-in speed in tics (zooms in when exiting the loop) - arg4: zoom-out distance in fracunits (multiply by 65536) - arg5: angle to pan to the side of the loop in degrees fracunits (multiply by 65536) - This will be flipped depending on where the camera was facing before entering the loop. - arg6: panning speed in degrees fracunits (multiply by 65536) - arg7: panning acceleration in tics (camera gradually pans to side of loop) - arg8: panning deceleration in tics (camera gradually pans back to normal) --- src/d_player.h | 10 +++++++ src/objects/loops.cpp | 12 +++++++++ src/p_loop.c | 1 + src/p_saveg.c | 22 ++++++++++++++++ src/p_user.c | 61 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 106 insertions(+) diff --git a/src/d_player.h b/src/d_player.h index 1b9744efb..fff4eab9c 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -465,6 +465,15 @@ typedef enum BOT_ITEM_PR__MAX } botItemPriority_e; +typedef struct { + tic_t enter_tic, exit_tic; + tic_t zoom_in_speed, zoom_out_speed; + fixed_t dist; + angle_t pan; + fixed_t pan_speed; // in degrees + tic_t pan_accel, pan_back; +} sonicloopcamvars_t; + // player_t struct for loop state typedef struct { fixed_t radius; @@ -474,6 +483,7 @@ typedef struct { vector2_t origin_shift; vector2_t shift; boolean flip; + sonicloopcamvars_t camera; } sonicloopvars_t; // player_t struct for power-ups diff --git a/src/objects/loops.cpp b/src/objects/loops.cpp index c54103f9d..9d2262848 100644 --- a/src/objects/loops.cpp +++ b/src/objects/loops.cpp @@ -295,6 +295,7 @@ Obj_LoopEndpointCollide { player_t *player = toucher->player; sonicloopvars_t *s = &player->loop; + sonicloopcamvars_t *cam = &s->camera; mobj_t *anchor = end_anchor(end); mobj_t *center = anchor ? anchor_center(anchor) : NULL; @@ -352,6 +353,17 @@ Obj_LoopEndpointCollide s->flip = center_has_flip(center); + cam->enter_tic = leveltime; + cam->exit_tic = INFTICS; + + cam->zoom_out_speed = center->thing_args[2]; + cam->zoom_in_speed = center->thing_args[3]; + cam->dist = center->thing_args[4] * FRACUNIT; + cam->pan = FixedAngle(center->thing_args[5] * FRACUNIT); + cam->pan_speed = center->thing_args[6] * FRACUNIT; + cam->pan_accel = center->thing_args[7]; + cam->pan_back = center->thing_args[8]; + player->speed = 3 * (player->speed + toucher->momz) / 2; diff --git a/src/p_loop.c b/src/p_loop.c index bc1454a5d..67ba60f02 100644 --- a/src/p_loop.c +++ b/src/p_loop.c @@ -37,6 +37,7 @@ void P_HaltPlayerOrbit(player_t *player) player->mo->flags &= ~(MF_NOCLIPHEIGHT); player->loop.radius = 0; + player->loop.camera.exit_tic = leveltime; } void P_ExitPlayerOrbit(player_t *player) diff --git a/src/p_saveg.c b/src/p_saveg.c index 4a8fd3027..097057ef7 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -682,6 +682,17 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEFIXED(save->p, players[i].loop.shift.y); WRITEUINT8(save->p, players[i].loop.flip); + // sonicloopcamvars_t + WRITEUINT32(save->p, players[i].loop.camera.enter_tic); + WRITEUINT32(save->p, players[i].loop.camera.exit_tic); + WRITEUINT32(save->p, players[i].loop.camera.zoom_in_speed); + WRITEUINT32(save->p, players[i].loop.camera.zoom_out_speed); + WRITEFIXED(save->p, players[i].loop.camera.dist); + WRITEANGLE(save->p, players[i].loop.camera.pan); + WRITEFIXED(save->p, players[i].loop.camera.pan_speed); + WRITEUINT32(save->p, players[i].loop.camera.pan_accel); + WRITEUINT32(save->p, players[i].loop.camera.pan_back); + // ACS has read access to this, so it has to be net-communicated. // It is the ONLY roundcondition that is sent over the wire and I'd like it to stay that way. WRITEUINT32(save->p, players[i].roundconditions.unlocktriggers); @@ -1208,6 +1219,17 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].loop.shift.y = READFIXED(save->p); players[i].loop.flip = READUINT8(save->p); + // sonicloopcamvars_t + players[i].loop.camera.enter_tic = READUINT32(save->p); + players[i].loop.camera.exit_tic = READUINT32(save->p); + players[i].loop.camera.zoom_in_speed = READUINT32(save->p); + players[i].loop.camera.zoom_out_speed = READUINT32(save->p); + players[i].loop.camera.dist = READFIXED(save->p); + players[i].loop.camera.pan = READANGLE(save->p); + players[i].loop.camera.pan_speed = READFIXED(save->p); + players[i].loop.camera.pan_accel = READUINT32(save->p); + players[i].loop.camera.pan_back = READUINT32(save->p); + // ACS has read access to this, so it has to be net-communicated. // It is the ONLY roundcondition that is sent over the wire and I'd like it to stay that way. players[i].roundconditions.unlocktriggers = READUINT32(save->p); diff --git a/src/p_user.c b/src/p_user.c index 17f04c76f..070cfcee8 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -3072,6 +3072,10 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall fixed_t scaleDiff; fixed_t cameraScale = mapobjectscale; + sonicloopcamvars_t *loop = &player->loop.camera; + tic_t loop_out = leveltime - loop->enter_tic; + tic_t loop_in = max(leveltime, loop->exit_tic) - loop->exit_tic; + thiscam->old_x = thiscam->x; thiscam->old_y = thiscam->y; thiscam->old_z = thiscam->z; @@ -3196,6 +3200,58 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall camdist = FixedMul(cv_cam_dist[num].value, cameraScale); camheight = FixedMul(cv_cam_height[num].value, cameraScale); + if (loop_in < loop->zoom_in_speed) + { + fixed_t f = loop_out < loop->zoom_out_speed + ? (loop_out * FRACUNIT) / loop->zoom_out_speed + : FRACUNIT - ((loop_in * FRACUNIT) / loop->zoom_in_speed); + + camspeed -= FixedMul(f, camspeed - (FRACUNIT/10)); + camdist += FixedMul(f, loop->dist); + } + + if (loop_in < max(loop->pan_back, 1)) + { + fixed_t f = (loop_in * FRACUNIT) / max(loop->pan_back, 1); + + fixed_t dx = mo->x - thiscam->x; + fixed_t dy = mo->y - thiscam->y; + + angle_t th = R_PointToAngle2(0, 0, dx, dy); + fixed_t d = AngleFixed(focusangle - th); + + if (d > 180*FRACUNIT) + { + d -= (360*FRACUNIT); + } + + focusangle = th + FixedAngle(FixedMul(f, d)); + + if (loop_in == 0) + { + focusaiming = R_PointToAngle2(0, thiscam->z, FixedHypot(dx, dy), mo->z); + } + } + + if (loop_in == 0) + { + tic_t accel = max(loop->pan_accel, 1); + fixed_t f = (min(loop_out, accel) * FRACUNIT) / accel; + + INT32 turn = AngleDeltaSigned(focusangle, player->loop.yaw - loop->pan); + INT32 turnspeed = FixedAngle(FixedMul(f, loop->pan_speed)); + + if (turn > turnspeed) + { + if (turn < ANGLE_90) + { + turnspeed = -(turnspeed); + } + + focusangle += turnspeed; + } + } + if (timeover) { const INT32 timeovercam = max(0, min(180, (player->karthud[khud_timeovercam] - 2*TICRATE)*15)); @@ -3878,6 +3934,11 @@ DoABarrelRoll (player_t *player) fixed_t smoothing; + if (player->loop.radius) + { + return; + } + if (player->respawn.state != RESPAWNST_NONE) { player->tilt = 0; From 44245a18d0405e829983c844579a0cde15ff0760 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 17 Oct 2023 20:19:05 -0700 Subject: [PATCH 03/25] Default loop camera settings if camera distance is not set --- src/objects/loops.cpp | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/objects/loops.cpp b/src/objects/loops.cpp index 9d2262848..930fc6cd3 100644 --- a/src/objects/loops.cpp +++ b/src/objects/loops.cpp @@ -356,13 +356,26 @@ Obj_LoopEndpointCollide cam->enter_tic = leveltime; cam->exit_tic = INFTICS; - cam->zoom_out_speed = center->thing_args[2]; - cam->zoom_in_speed = center->thing_args[3]; - cam->dist = center->thing_args[4] * FRACUNIT; - cam->pan = FixedAngle(center->thing_args[5] * FRACUNIT); - cam->pan_speed = center->thing_args[6] * FRACUNIT; - cam->pan_accel = center->thing_args[7]; - cam->pan_back = center->thing_args[8]; + if (center->thing_args[4]) // is camera distance set? + { + cam->zoom_out_speed = center->thing_args[2]; + cam->zoom_in_speed = center->thing_args[3]; + cam->dist = center->thing_args[4] * FRACUNIT; + cam->pan = FixedAngle(center->thing_args[5] * FRACUNIT); + cam->pan_speed = center->thing_args[6] * FRACUNIT; + cam->pan_accel = center->thing_args[7]; + cam->pan_back = center->thing_args[8]; + } + else + { + cam->zoom_out_speed = 20; + cam->zoom_in_speed = 60; + cam->dist = radius; + cam->pan = ANGLE_22h; + cam->pan_speed = 6*FRACUNIT; + cam->pan_accel = 10; + cam->pan_back = 40; + } player->speed = 3 * (player->speed + toucher->momz) / 2; From b7892fd5d11c935e17687843987df3024267f9cd Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Fri, 27 Oct 2023 22:37:56 -0700 Subject: [PATCH 04/25] HACK: Lift all inputs when wipes execute --- src/f_wipe.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/f_wipe.cpp b/src/f_wipe.cpp index 6a8587586..9ecb41c32 100644 --- a/src/f_wipe.cpp +++ b/src/f_wipe.cpp @@ -494,6 +494,11 @@ void F_RunWipe(UINT8 wipemode, UINT8 wipetype, boolean drawMenu, const char *col WipeInAction = true; wipe_scr = screens[0]; + // FIXME: Wipes SUCK and drop input events for some reason, causing stuck gamepad inputs. + // It's better to ignore an intentional hold than to turn a tap into a phantom hold. + // (If you're removing this, remove the one after the inner loop too!) + memset(gamekeydown, 0, sizeof(gamekeydown)); + // lastwipetic should either be 0 or the tic we last wiped // on for fade-to-black for (;;) @@ -581,6 +586,11 @@ void F_RunWipe(UINT8 wipemode, UINT8 wipetype, boolean drawMenu, const char *col WipeInAction = false; + // FIXME: Wipes SUCK and drop input events for some reason, causing stuck gamepad inputs. + // It's better to ignore an intentional hold than to turn a tap into a phantom hold. + // (If you're removing this, remove the one before the inner loop too!) + memset(gamekeydown, 0, sizeof(gamekeydown)); + if (fcolor) { Z_Free(fcolor); From 7dba0c9aba8164516a52d6e1d2b5f21bb9bcce3d Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Fri, 27 Oct 2023 23:31:08 -0700 Subject: [PATCH 05/25] Don't do gameplay controller vibration after finishing --- src/p_tick.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/p_tick.c b/src/p_tick.c index 1ad1c8dd2..feb14be86 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -708,6 +708,9 @@ static inline void P_DeviceRumbleTick(void) if (player->mo == NULL) continue; + if (player->exiting) + continue; + if ((player->mo->eflags & MFE_DAMAGEHITLAG) && player->mo->hitlag) { low = high = 65536 / 2; From 4db18b122515219e9e505fd94622894b8f53d9de Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Fri, 3 Nov 2023 22:51:14 -0700 Subject: [PATCH 06/25] Easier ballhog tapfire --- src/d_player.h | 1 + src/k_kart.c | 15 +++++++++++++-- src/lua_playerlib.c | 4 ++++ src/p_saveg.c | 2 ++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index cc176671c..460d7e4b0 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -716,6 +716,7 @@ struct player_t UINT8 flamelength; // Flame Shield dash meter, number of segments UINT16 ballhogcharge; // Ballhog charge up -- the higher this value, the more projectiles + boolean ballhogtap; // Ballhog released during charge: used to allow semirapid tapfire UINT16 hyudorotimer; // Duration of the Hyudoro offroad effect itself SINT8 stealingtimer; // if >0 you are stealing, if <0 you are being stolen from diff --git a/src/k_kart.c b/src/k_kart.c index 8f6337a08..0ab773fd7 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -11493,8 +11493,19 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { INT32 ballhogmax = (player->itemamount) * BALLHOGINCREMENT; - if ((cmd->buttons & BT_ATTACK) && (player->pflags & PF_HOLDREADY) - && (player->ballhogcharge < ballhogmax)) + // This construct looks a little goofy, but we're basically just + // trying to prevent rapid taps from restarting a charge, while + // still allowing quick tapfire. + // (The player still has to pace their shots like this, it's not + // semi-auto, but that's probably kind of okay.) + if (player->ballhogcharge && !(cmd->buttons & BT_ATTACK)) + player->ballhogtap = true; + + if (player->ballhogcharge == 0) + player->ballhogtap = false; + + boolean realcharge = (cmd->buttons & BT_ATTACK) && (player->pflags & PF_HOLDREADY) && (player->ballhogcharge < ballhogmax); + if ((realcharge && !player->ballhogtap) || (player->ballhogtap && player->ballhogcharge < BALLHOGINCREMENT)) { player->ballhogcharge++; if (player->ballhogcharge % BALLHOGINCREMENT == 0) diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 6fe1e325d..145c26d4b 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -381,6 +381,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->flamelength); else if (fastcmp(field,"ballhogcharge")) lua_pushinteger(L, plr->ballhogcharge); + else if (fastcmp(field,"ballhogtap")) + lua_pushinteger(L, plr->ballhogtap); else if (fastcmp(field,"hyudorotimer")) lua_pushinteger(L, plr->hyudorotimer); else if (fastcmp(field,"stealingtimer")) @@ -863,6 +865,8 @@ static int player_set(lua_State *L) plr->flamelength = luaL_checkinteger(L, 3); else if (fastcmp(field,"ballhogcharge")) plr->ballhogcharge = luaL_checkinteger(L, 3); + else if (fastcmp(field,"ballhogtap")) + plr->ballhogtap = luaL_checkinteger(L, 3); else if (fastcmp(field,"hyudorotimer")) plr->hyudorotimer = luaL_checkinteger(L, 3); else if (fastcmp(field,"stealingtimer")) diff --git a/src/p_saveg.c b/src/p_saveg.c index f6f8880dc..3e34860f8 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -478,6 +478,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].flamelength); WRITEUINT16(save->p, players[i].ballhogcharge); + WRITEUINT8(save->p, players[i].ballhogtap); WRITEUINT16(save->p, players[i].hyudorotimer); WRITESINT8(save->p, players[i].stealingtimer); @@ -992,6 +993,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].flamelength = READUINT8(save->p); players[i].ballhogcharge = READUINT16(save->p); + players[i].ballhogtap = READUINT8(save->p); players[i].hyudorotimer = READUINT16(save->p); players[i].stealingtimer = READSINT8(save->p); From 1667ca591e54328a769c41d1cb55fb031ec11507 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sat, 4 Nov 2023 23:22:03 -0700 Subject: [PATCH 07/25] Don't failsafe when twerking --- src/k_kart.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 8f6337a08..2e1798a71 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -10786,7 +10786,9 @@ static void K_AirFailsafe(player_t *player) if (leveltime < introtime) return; - if ((K_GetKartButtons(player) & BT_ACCELERATE) || K_GetForwardMove(player) != 0) + UINT8 buttons = K_GetKartButtons(player); + + if (((buttons & BT_ACCELERATE) && !(buttons & BT_BRAKE)) || K_GetForwardMove(player) != 0) { // Queue up later player->pflags |= PF_AIRFAILSAFE; From 7ba7e2b076f9a121027c9e38a3cb45097bee206a Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sat, 4 Nov 2023 23:42:26 -0700 Subject: [PATCH 08/25] Prevent failsafe briefly after taking damage --- src/d_player.h | 2 ++ src/k_kart.c | 6 +++++- src/lua_playerlib.c | 4 ++++ src/p_inter.c | 1 + src/p_saveg.c | 4 ++++ 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/d_player.h b/src/d_player.h index cc176671c..8eba378ff 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -902,6 +902,8 @@ struct player_t UINT8 instaWhipChargeLockout; UINT8 guardCooldown; + UINT8 preventfailsafe; // Set when taking damage to prevent cheesing eggboxes + UINT8 handtimer; angle_t besthanddirection; diff --git a/src/k_kart.c b/src/k_kart.c index 2e1798a71..b95fd2e78 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -8243,6 +8243,9 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->invincibilitytimer && onground == true) player->invincibilitytimer--; + if (player->preventfailsafe) + player->preventfailsafe--; + if ((player->respawn.state == RESPAWNST_NONE) && player->growshrinktimer != 0) { if (player->growshrinktimer > 0 && onground == true) @@ -10776,7 +10779,8 @@ static void K_AirFailsafe(player_t *player) const fixed_t thrustSpeed = 6*player->mo->scale; // 10*player->mo->scale if (player->speed > maxSpeed // Above the max speed that you're allowed to use this technique. - || player->respawn.state != RESPAWNST_NONE) // Respawning, you don't need this AND drop dash :V + || player->respawn.state != RESPAWNST_NONE // Respawning, you don't need this AND drop dash :V + || player->preventfailsafe) // You just got hit or interacted with something committal, no mashing for distance { player->pflags &= ~PF_AIRFAILSAFE; return; diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 6fe1e325d..7cd2ef130 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -337,6 +337,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->instaWhipCooldown); else if (fastcmp(field,"guardCooldown")) lua_pushinteger(L, plr->guardCooldown); + else if (fastcmp(field,"preventfailsafe")) + lua_pushinteger(L, plr->preventfailsafe); /* else if (fastcmp(field,"itemroulette")) lua_pushinteger(L, plr->itemroulette); @@ -819,6 +821,8 @@ static int player_set(lua_State *L) plr->instaWhipCharge = luaL_checkinteger(L, 3); else if (fastcmp(field,"guardCooldown")) plr->guardCooldown = luaL_checkinteger(L, 3); + else if (fastcmp(field,"preventfailsafe")) + plr->preventfailsafe = luaL_checkinteger(L, 3); /* else if (fastcmp(field,"itemroulette")) plr->itemroulette = luaL_checkinteger(L, 3); diff --git a/src/p_inter.c b/src/p_inter.c index 61b3403b0..029a7f317 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2996,6 +2996,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da player->fastfall = 0; player->ringboost = 0; player->glanceDir = 0; + player->preventfailsafe = TICRATE*3; player->pflags &= ~PF_GAINAX; if (player->spectator == false && !(player->charflags & SF_IRONMAN)) diff --git a/src/p_saveg.c b/src/p_saveg.c index f6f8880dc..8c7b90b20 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -547,6 +547,8 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].instaWhipCooldown); WRITEUINT8(save->p, players[i].guardCooldown); + WRITEUINT8(save->p, players[i].preventfailsafe); + WRITEUINT8(save->p, players[i].handtimer); WRITEANGLE(save->p, players[i].besthanddirection); @@ -1061,6 +1063,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].instaWhipCooldown = READUINT8(save->p); players[i].guardCooldown = READUINT8(save->p); + players[i].preventfailsafe = READUINT8(save->p); + players[i].handtimer = READUINT8(save->p); players[i].besthanddirection = READANGLE(save->p); From 06777082167fc1427ee812c28948c10080b74e03 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sun, 5 Nov 2023 04:30:45 -0700 Subject: [PATCH 09/25] Measure frameskip timing before sleeping --- src/d_main.cpp | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index 7df05d12f..048367629 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -973,20 +973,9 @@ void D_SRB2Loop(void) // Fully completed frame made. finishprecise = I_GetPreciseTime(); - if (!singletics) - { - INT64 elapsed = (INT64)(finishprecise - enterprecise); - // in the case of "match refresh rate" + vsync, don't sleep at all - const boolean vsync_with_match_refresh = cv_vidwait.value && cv_fpscap.value == 0; - - if (elapsed > 0 && (INT64)capbudget > elapsed && !vsync_with_match_refresh) - { - I_SleepDuration(capbudget - (finishprecise - enterprecise)); - } - } - // Capture the time once more to get the real delta time. - finishprecise = I_GetPreciseTime(); + // Use the time before sleep for frameskip calculations: + // post-sleep time is literally being intentionally wasted deltasecs = (double)((INT64)(finishprecise - enterprecise)) / I_GetPrecisePrecision(); deltatics = deltasecs * NEWTICRATE; @@ -1006,6 +995,23 @@ void D_SRB2Loop(void) { frameskip = 0; } + + if (!singletics) + { + INT64 elapsed = (INT64)(finishprecise - enterprecise); + + // in the case of "match refresh rate" + vsync, don't sleep at all + const boolean vsync_with_match_refresh = cv_vidwait.value && cv_fpscap.value == 0; + + if (elapsed > 0 && (INT64)capbudget > elapsed && !vsync_with_match_refresh) + { + I_SleepDuration(capbudget - (finishprecise - enterprecise)); + } + } + // Capture the time once more to get the real delta time. + finishprecise = I_GetPreciseTime(); + deltasecs = (double)((INT64)(finishprecise - enterprecise)) / I_GetPrecisePrecision(); + deltatics = deltasecs * NEWTICRATE; } } From 38c096a5cd360afea708f3a32f947488a3b2a8ab Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sun, 5 Nov 2023 04:40:44 -0700 Subject: [PATCH 10/25] Clean up failsafe queue condition --- src/k_kart.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index b95fd2e78..41b45e75f 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -10792,9 +10792,10 @@ static void K_AirFailsafe(player_t *player) UINT8 buttons = K_GetKartButtons(player); - if (((buttons & BT_ACCELERATE) && !(buttons & BT_BRAKE)) || K_GetForwardMove(player) != 0) + // Accel inputs queue air-failsafe for when they're released, + // as long as they're not part of a fastfall attempt. + if ((buttons & (BT_ACCELERATE|BT_BRAKE)) == BT_ACCELERATE || K_GetForwardMove(player) != 0) { - // Queue up later player->pflags |= PF_AIRFAILSAFE; return; } From 208601e38d8f18850748557482e31e5ff317b650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20Alh=C3=A4ll?= Date: Sun, 5 Nov 2023 13:52:23 +0100 Subject: [PATCH 11/25] Cache and reuse removed mobjs when spawning mobjs --- src/d_think.h | 1 + src/p_local.h | 1 + src/p_mobj.c | 17 +++++++++++++++-- src/p_setup.c | 1 + src/p_tick.c | 12 +++++++++++- 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/d_think.h b/src/d_think.h index d833f4ca0..29b67718e 100644 --- a/src/d_think.h +++ b/src/d_think.h @@ -55,6 +55,7 @@ struct thinker_t // killough 11/98: count of how many other objects reference // this one using pointers. Used for garbage collection. INT32 references; + boolean cachable; #ifdef PARANOIA INT32 debug_mobjtype; diff --git a/src/p_local.h b/src/p_local.h index fccc0287f..0e0a3fb23 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -77,6 +77,7 @@ typedef enum NUM_THINKERLISTS } thinklistnum_t; /**< Thinker lists. */ extern thinker_t thlist[]; +extern mobj_t *mobjcache; void P_InitThinkers(void); void P_AddThinker(const thinklistnum_t n, thinker_t *thinker); diff --git a/src/p_mobj.c b/src/p_mobj.c index 1ada630c8..f0baa74e1 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -63,6 +63,8 @@ mobj_t *waypointcap = NULL; // general purpose. mobj_t *trackercap = NULL; +mobj_t *mobjcache = NULL; + void P_InitCachedActions(void) { actioncachehead.prev = actioncachehead.next = &actioncachehead; @@ -10951,7 +10953,16 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) type = MT_RAY; } - mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL); + if (mobjcache != NULL) + { + mobj = mobjcache; + mobjcache = mobjcache->hnext; + memset(mobj, 0, sizeof(*mobj)); + } + else + { + mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL); + } // this is officially a mobj, declared as soon as possible. mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker; @@ -11859,7 +11870,9 @@ void P_RemoveMobj(mobj_t *mobj) INT32 prevreferences; if (!mobj->thinker.references) { - Z_Free(mobj); // No refrrences? Can be removed immediately! :D + // no references, dump it directly in the mobj cache + mobj->hnext = mobjcache; + mobjcache = mobj; return; } diff --git a/src/p_setup.c b/src/p_setup.c index 4ba02f340..0bc686292 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -8363,6 +8363,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) Patch_FreeTag(PU_PATCH_LOWPRIORITY); Patch_FreeTag(PU_PATCH_ROTATED); Z_FreeTags(PU_LEVEL, PU_PURGELEVEL - 1); + mobjcache = NULL; R_InitializeLevelInterpolators(); diff --git a/src/p_tick.c b/src/p_tick.c index 1ad1c8dd2..167a1c160 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -310,6 +310,7 @@ void P_AddThinker(const thinklistnum_t n, thinker_t *thinker) thlist[n].prev = thinker; thinker->references = 0; // killough 11/98: init reference counter to 0 + thinker->cachable = n == THINK_MOBJ; #ifdef PARANOIA thinker->debug_mobjtype = MT_NULL; @@ -427,7 +428,16 @@ void P_UnlinkThinker(thinker_t *thinker) I_Assert(thinker->references == 0); (next->prev = thinker->prev)->next = next; - Z_Free(thinker); + if (thinker->cachable) + { + // put cachable thinkers in the mobj cache, so we can avoid allocations + ((mobj_t *)thinker)->hnext = mobjcache; + mobjcache = (mobj_t *)thinker; + } + else + { + Z_Free(thinker); + } } // From 06bc06869c7b96970fc2e8165065d960beb31873 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 9 Nov 2023 17:02:26 -0700 Subject: [PATCH 12/25] Lite Steer profile option, itemstate pflags -> player.itemflags --- src/cvars.cpp | 8 ++ src/d_netcmd.c | 7 ++ src/d_player.h | 18 +++-- src/deh_tables.c | 9 +-- src/g_build_ticcmd.cpp | 2 +- src/g_demo.c | 8 ++ src/g_game.c | 4 +- src/g_game.h | 1 + src/k_botitem.c | 24 +++--- src/k_botsearch.c | 4 +- src/k_collide.cpp | 2 +- src/k_hud.c | 2 +- src/k_kart.c | 90 +++++++++++----------- src/k_menu.h | 1 + src/k_profiles.c | 15 ++++ src/k_profiles.h | 5 +- src/k_roulette.c | 2 +- src/lua_playerlib.c | 4 + src/menus/options-profiles-1.c | 2 + src/menus/options-profiles-edit-controls.c | 5 ++ src/p_inter.c | 10 +-- src/p_mobj.c | 2 +- src/p_saveg.c | 6 ++ 23 files changed, 148 insertions(+), 83 deletions(-) diff --git a/src/cvars.cpp b/src/cvars.cpp index 4b0897dcf..d41c9e61d 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -905,6 +905,7 @@ void Dummymenuplayer_OnChange(void); consvar_t cv_dummymenuplayer = MenuDummy("dummymenuplayer", "P1").onchange(Dummymenuplayer_OnChange).values({{0, "NOPE"}, {1, "P1"}, {2, "P2"}, {3, "P3"}, {4, "P4"}}); consvar_t cv_dummyprofileautoroulette = MenuDummy("dummyprofileautoroulette", "Off").on_off(); +consvar_t cv_dummyprofilelitesteer = MenuDummy("dummyprofilelitesteer", "On").on_off(); consvar_t cv_dummyprofilekickstart = MenuDummy("dummyprofilekickstart", "Off").on_off(); consvar_t cv_dummyprofilename = MenuDummy("dummyprofilename", ""); consvar_t cv_dummyprofileplayername = MenuDummy("dummyprofileplayername", ""); @@ -1011,6 +1012,13 @@ consvar_t cv_autoroulette[MAXSPLITSCREENPLAYERS] = { Player("autoroulette4", "Off").on_off().onchange(weaponPrefChange4), }; +consvar_t cv_litesteer[MAXSPLITSCREENPLAYERS] = { + Player("litesteer", "On").on_off().onchange(weaponPrefChange), + Player("litesteer2", "On").on_off().onchange(weaponPrefChange2), + Player("litesteer3", "On").on_off().onchange(weaponPrefChange3), + Player("litesteer4", "On").on_off().onchange(weaponPrefChange4), +}; + consvar_t cv_cam_dist[MAXSPLITSCREENPLAYERS] = { Player("cam_dist", "190").floating_point(), Player("cam2_dist", "190").floating_point(), diff --git a/src/d_netcmd.c b/src/d_netcmd.c index b51f1408a..63a6f37a9 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1229,6 +1229,7 @@ enum { WP_KICKSTARTACCEL = 1<<0, WP_SHRINKME = 1<<1, WP_AUTOROULETTE = 1<<2, + WP_LITESTEER = 1<<3, }; void WeaponPref_Send(UINT8 ssplayer) @@ -1241,6 +1242,9 @@ void WeaponPref_Send(UINT8 ssplayer) if (cv_autoroulette[ssplayer].value) prefs |= WP_AUTOROULETTE; + if (cv_litesteer[ssplayer].value) + prefs |= WP_LITESTEER; + if (cv_shrinkme[ssplayer].value) prefs |= WP_SHRINKME; @@ -1259,6 +1263,9 @@ void WeaponPref_Save(UINT8 **cp, INT32 playernum) if (player->pflags & PF_AUTOROULETTE) prefs |= WP_AUTOROULETTE; + if (player->pflags & PF_LITESTEER) + prefs |= WP_LITESTEER; + if (player->pflags & PF_SHRINKME) prefs |= WP_SHRINKME; diff --git a/src/d_player.h b/src/d_player.h index b9ca4d532..aae92b311 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -64,6 +64,14 @@ typedef enum PST_REBORN } playerstate_t; +typedef enum +{ + IF_USERINGS = 1, // Have to be not holding the item button to change from using rings to using items (or vice versa) - prevents weirdness + IF_ITEMOUT = 1<<1, // Are you holding an item out? + IF_EGGMANOUT = 1<<2, // Eggman mark held, separate from IF_ITEMOUT so it doesn't stop you from getting items + IF_HOLDREADY = 1<<3, // Hold button-style item is ready to activate +} itemflags_t; + // // Player internal flags // @@ -95,11 +103,9 @@ typedef enum PF_RINGLOCK = 1<<13, // Prevent picking up rings while SPB is locked on - // The following four flags are mutually exclusive, although they can also all be off at the same time. If we ever run out of pflags, eventually turn them into a seperate five(+) mode UINT8..? - PF_USERINGS = 1<<14, // Have to be not holding the item button to change from using rings to using items (or vice versa) - prevents weirdness - PF_ITEMOUT = 1<<15, // Are you holding an item out? - PF_EGGMANOUT = 1<<16, // Eggman mark held, separate from PF_ITEMOUT so it doesn't stop you from getting items - PF_HOLDREADY = 1<<17, // Hold button-style item is ready to activate + PF_LITESTEER = 1<<14, // Hold Down to shallow turn (digital only) + + //15-17 free, was previously itemflags stuff PF_DRIFTINPUT = 1<<18, // Drifting! PF_GETSPARKS = 1<<19, // Can get sparks @@ -915,6 +921,8 @@ struct player_t UINT8 ringboxdelay; // Delay until Ring Box auto-activates UINT8 ringboxaward; // Where did we stop? + UINT8 itemflags; // holds IS_flags (see itemstate_t) + fixed_t outrun; // Milky Way road effect uint8_t public_key[PUBKEYLENGTH]; diff --git a/src/deh_tables.c b/src/deh_tables.c index 3023b70eb..3bf8c289b 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -6093,11 +6093,10 @@ const char *const PLAYERFLAG_LIST[] = { "RINGLOCK", // Prevent picking up rings while SPB is locked on - // The following four flags are mutually exclusive, although they can also all be off at the same time. If we ever run out of pflags, eventually turn them into a seperate five(+) mode UINT8..? - "USERINGS", // Have to be not holding the item button to change from using rings to using items (or vice versa) - prevents weirdness - "ITEMOUT", // Are you holding an item out? - "EGGMANOUT", // Eggman mark held, separate from PF_ITEMOUT so it doesn't stop you from getting items - "HOLDREADY", // Hold button-style item is ready to activate + "LITESTEER", // Shallow digital turn with DOWN + "UNUSED", + "UNUSED", + "UNUSED", "DRIFTINPUT", // Drifting! "GETSPARKS", // Can get sparks diff --git a/src/g_build_ticcmd.cpp b/src/g_build_ticcmd.cpp index 81d431a12..6dca175da 100644 --- a/src/g_build_ticcmd.cpp +++ b/src/g_build_ticcmd.cpp @@ -380,7 +380,7 @@ class TiccmdBuilder // ugly with the current abstractions, though, and there's a fortunate trick here: // if you can input full strength turns on both axes, either you're using a fucking // square gate, or you're not on an analog device. - if (joystickvector.yaxis >= JOYAXISRANGE && abs(cmd->turning) == KART_FULLTURN) // >= beacuse some analog devices can go past JOYAXISRANGE (?!) + if (cv_litesteer[ssplayer - 1].value && joystickvector.yaxis >= JOYAXISRANGE && abs(cmd->turning) == KART_FULLTURN) // >= beacuse some analog devices can go past JOYAXISRANGE (?!) cmd->turning /= 2; } diff --git a/src/g_demo.c b/src/g_demo.c index d039ddfdd..26c4e078b 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -127,6 +127,7 @@ demoghost *ghosts = NULL; #define DEMO_SHRINKME 0x04 #define DEMO_BOT 0x08 #define DEMO_AUTOROULETTE 0x10 +#define DEMO_LITESTEER 0x20 // For demos #define ZT_FWD 0x0001 @@ -2480,6 +2481,8 @@ void G_BeginRecording(void) i |= DEMO_KICKSTART; if (player->pflags & PF_AUTOROULETTE) i |= DEMO_AUTOROULETTE; + if (player->pflags & PF_LITESTEER) + i |= DEMO_LITESTEER; if (player->pflags & PF_SHRINKME) i |= DEMO_SHRINKME; if (player->bot == true) @@ -3447,6 +3450,11 @@ void G_DoPlayDemo(const char *defdemoname) else players[p].pflags &= ~PF_AUTOROULETTE; + if (flags & DEMO_LITESTEER) + players[p].pflags |= PF_LITESTEER; + else + players[p].pflags &= ~PF_LITESTEER; + if (flags & DEMO_SHRINKME) players[p].pflags |= PF_SHRINKME; else diff --git a/src/g_game.c b/src/g_game.c index b42085fc9..af58cfb47 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2127,7 +2127,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) totalring = players[player].totalring; xtralife = players[player].xtralife; - pflags = (players[player].pflags & (PF_WANTSTOJOIN|PF_KICKSTARTACCEL|PF_SHRINKME|PF_SHRINKACTIVE|PF_AUTOROULETTE)); + pflags = (players[player].pflags & (PF_WANTSTOJOIN|PF_KICKSTARTACCEL|PF_SHRINKME|PF_SHRINKACTIVE|PF_AUTOROULETTE|PF_LITESTEER)); // SRB2kart memcpy(&itemRoulette, &players[player].itemRoulette, sizeof (itemRoulette)); @@ -2175,7 +2175,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) } else { - if (players[player].pflags & PF_ITEMOUT) + if (players[player].itemflags & IF_ITEMOUT) { itemtype = 0; itemamount = 0; diff --git a/src/g_game.h b/src/g_game.h index fc747ae7b..9464a6e8a 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -99,6 +99,7 @@ extern consvar_t cv_pauseifunfocused; extern consvar_t cv_kickstartaccel[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_autoroulette[MAXSPLITSCREENPLAYERS]; +extern consvar_t cv_litesteer[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_shrinkme[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_deadzone[MAXSPLITSCREENPLAYERS]; diff --git a/src/k_botitem.c b/src/k_botitem.c index c483afdb5..ee9fe577e 100644 --- a/src/k_botitem.c +++ b/src/k_botitem.c @@ -440,7 +440,7 @@ static boolean K_BotRevealsGenericTrap(player_t *player, INT16 turnamt, boolean --------------------------------------------------*/ static void K_BotItemGenericTrapShield(player_t *player, ticcmd_t *cmd, INT16 turnamt, boolean mine) { - if (player->pflags & PF_ITEMOUT) + if (player->itemflags & IF_ITEMOUT) { return; } @@ -465,7 +465,7 @@ static void K_BotItemGenericTrapShield(player_t *player, ticcmd_t *cmd, INT16 tu --------------------------------------------------*/ static void K_BotItemGenericOrbitShield(player_t *player, ticcmd_t *cmd) { - if (player->pflags & PF_ITEMOUT) + if (player->itemflags & IF_ITEMOUT) { return; } @@ -811,7 +811,7 @@ static boolean K_BotRevealsEggbox(player_t *player) --------------------------------------------------*/ static void K_BotItemEggmanShield(player_t *player, ticcmd_t *cmd) { - if (player->pflags & PF_EGGMANOUT) + if (player->itemflags & IF_EGGMANOUT) { return; } @@ -1221,7 +1221,7 @@ static void K_BotItemBubble(player_t *player, ticcmd_t *cmd) hold = true; } - if (hold && (player->pflags & PF_HOLDREADY)) + if (hold && (player->itemflags & IF_HOLDREADY)) { cmd->buttons |= BT_ATTACK; } @@ -1245,7 +1245,7 @@ static void K_BotItemFlame(player_t *player, ticcmd_t *cmd) { player->botvars.itemconfirm--; } - else if (player->pflags & PF_HOLDREADY) + else if (player->itemflags & IF_HOLDREADY) { INT32 flamemax = player->flamelength; @@ -1532,7 +1532,7 @@ static void K_BotItemRouletteMash(player_t *player, ticcmd_t *cmd) --------------------------------------------------*/ void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt) { - if (player->pflags & PF_USERINGS) + if (player->itemflags & IF_USERINGS) { if (player->rings > 0) { @@ -1567,7 +1567,7 @@ void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt) { K_BotItemEggmanExplosion(player, cmd); } - else if (player->pflags & PF_EGGMANOUT) + else if (player->itemflags & IF_EGGMANOUT) { K_BotItemEggman(player, cmd); } @@ -1604,7 +1604,7 @@ void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt) K_BotItemSneaker(player, cmd); break; case KITEM_BANANA: - if (!(player->pflags & PF_ITEMOUT)) + if (!(player->itemflags & IF_ITEMOUT)) { K_BotItemGenericTrapShield(player, cmd, turnamt, false); } @@ -1617,7 +1617,7 @@ void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt) K_BotItemEggmanShield(player, cmd); break; case KITEM_ORBINAUT: - if (!(player->pflags & PF_ITEMOUT)) + if (!(player->itemflags & IF_ITEMOUT)) { K_BotItemGenericOrbitShield(player, cmd); } @@ -1627,7 +1627,7 @@ void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt) } break; case KITEM_JAWZ: - if (!(player->pflags & PF_ITEMOUT)) + if (!(player->itemflags & IF_ITEMOUT)) { K_BotItemGenericOrbitShield(player, cmd); } @@ -1637,7 +1637,7 @@ void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt) } break; case KITEM_MINE: - if (!(player->pflags & PF_ITEMOUT)) + if (!(player->itemflags & IF_ITEMOUT)) { K_BotItemGenericTrapShield(player, cmd, turnamt, true); } @@ -1654,7 +1654,7 @@ void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt) K_BotItemBallhog(player, cmd); break; case KITEM_DROPTARGET: - if (!(player->pflags & PF_ITEMOUT)) + if (!(player->itemflags & IF_ITEMOUT)) { K_BotItemGenericTrapShield(player, cmd, turnamt, false); } diff --git a/src/k_botsearch.c b/src/k_botsearch.c index bdc7ecf62..8d0fc1bc7 100644 --- a/src/k_botsearch.c +++ b/src/k_botsearch.c @@ -577,8 +577,8 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) } // Has held item shield else if (K_PlayerAttackSteer(thing, side, 20, - (thing->player->pflags & (PF_ITEMOUT|PF_EGGMANOUT)), - (g_nudgeSearch.botmo->player->pflags & (PF_ITEMOUT|PF_EGGMANOUT)) + (thing->player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT)), + (g_nudgeSearch.botmo->player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT)) )) { break; diff --git a/src/k_collide.cpp b/src/k_collide.cpp index 87a4a6862..0b78db526 100644 --- a/src/k_collide.cpp +++ b/src/k_collide.cpp @@ -190,7 +190,7 @@ boolean K_EggItemCollide(mobj_t *t1, mobj_t *t2) if (t1->target->hnext == t1) { P_SetTarget(&t1->target->hnext, NULL); - t1->target->player->pflags &= ~PF_EGGMANOUT; + t1->target->player->itemflags &= ~IF_EGGMANOUT; } } diff --git a/src/k_hud.c b/src/k_hud.c index ab26ae388..4f9d94729 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1453,7 +1453,7 @@ static void K_drawKartItem(void) break; } - if ((stplyr->pflags & PF_ITEMOUT) && !(leveltime & 1)) + if ((stplyr->itemflags & IF_ITEMOUT) && !(leveltime & 1)) localpatch[1] = kp_nodraw; } diff --git a/src/k_kart.c b/src/k_kart.c index 99483e230..64a2d7472 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6629,11 +6629,11 @@ void K_DropHnextList(player_t *player) player->bananadrag = 0; - if (player->pflags & PF_EGGMANOUT) + if (player->itemflags & IF_EGGMANOUT) { - player->pflags &= ~PF_EGGMANOUT; + player->itemflags &= ~IF_EGGMANOUT; } - else if ((player->pflags & PF_ITEMOUT) + else if ((player->itemflags & IF_ITEMOUT) && (dropall || (--player->itemamount <= 0))) { player->itemamount = 0; @@ -7026,11 +7026,11 @@ static void K_MoveHeldObjects(player_t *player) { player->bananadrag = 0; - if (player->pflags & PF_EGGMANOUT) + if (player->itemflags & IF_EGGMANOUT) { - player->pflags &= ~PF_EGGMANOUT; + player->itemflags &= ~IF_EGGMANOUT; } - else if (player->pflags & PF_ITEMOUT) + else if (player->itemflags & IF_ITEMOUT) { player->itemamount = 0; K_UnsetItemOut(player); @@ -7045,11 +7045,11 @@ static void K_MoveHeldObjects(player_t *player) P_SetTarget(&player->mo->hnext, NULL); player->bananadrag = 0; - if (player->pflags & PF_EGGMANOUT) + if (player->itemflags & IF_EGGMANOUT) { - player->pflags &= ~PF_EGGMANOUT; + player->itemflags &= ~IF_EGGMANOUT; } - else if (player->pflags & PF_ITEMOUT) + else if (player->itemflags & IF_ITEMOUT) { player->itemamount = 0; K_UnsetItemOut(player); @@ -8033,7 +8033,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) } if (player->itemtype == KITEM_NONE) - player->pflags &= ~PF_HOLDREADY; + player->itemflags &= ~IF_HOLDREADY; // DKR style camera for boosting if (player->karthud[khud_boostcam] != 0 || player->karthud[khud_destboostcam] != 0) @@ -8378,7 +8378,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) S_StopSoundByID(player->mo, sfx_wchrg2); } - if (player->itemamount || player->respawn.state != RESPAWNST_NONE || player->pflags & (PF_ITEMOUT|PF_EGGMANOUT) || player->rocketsneakertimer || player->ringboxdelay) + if (player->itemamount || player->respawn.state != RESPAWNST_NONE || player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT) || player->rocketsneakertimer || player->ringboxdelay) player->instaWhipCharge = 0; if (player->tiregrease) @@ -8707,7 +8707,7 @@ void K_KartPlayerAfterThink(player_t *player) K_MoveHeldObjects(player); // Jawz reticule (seeking) - if (player->itemtype == KITEM_JAWZ && (player->pflags & PF_ITEMOUT)) + if (player->itemtype == KITEM_JAWZ && (player->itemflags & IF_ITEMOUT)) { const INT32 lastTargID = player->lastjawztarget; mobj_t *lastTarg = NULL; @@ -10178,7 +10178,7 @@ void K_StripItems(player_t *player) K_DropKitchenSink(player); player->itemtype = KITEM_NONE; player->itemamount = 0; - player->pflags &= ~(PF_ITEMOUT|PF_EGGMANOUT); + player->itemflags &= ~(IF_ITEMOUT|IF_EGGMANOUT); if (player->itemRoulette.eggman == false) { @@ -11021,7 +11021,7 @@ static void K_trickPanelTimingVisual(player_t *player, fixed_t momz) void K_SetItemOut(player_t *player) { - player->pflags |= PF_ITEMOUT; + player->itemflags |= IF_ITEMOUT; if (player->mo->scale >= FixedMul(GROW_PHYSICS_SCALE, mapobjectscale)) { @@ -11039,7 +11039,7 @@ void K_SetItemOut(player_t *player) void K_UnsetItemOut(player_t *player) { - player->pflags &= ~PF_ITEMOUT; + player->itemflags &= ~IF_ITEMOUT; player->itemscale = ITEMSCALE_NORMAL; player->bananadrag = 0; } @@ -11051,7 +11051,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { ticcmd_t *cmd = &player->cmd; boolean ATTACK_IS_DOWN = ((cmd->buttons & BT_ATTACK) && !(player->oldcmd.buttons & BT_ATTACK) && (player->respawn.state == RESPAWNST_NONE)); - boolean HOLDING_ITEM = (player->pflags & (PF_ITEMOUT|PF_EGGMANOUT)); + boolean HOLDING_ITEM = (player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT)); boolean NO_HYUDORO = (player->stealingtimer == 0); if (!player->exiting) @@ -11077,9 +11077,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground) || player->itemRoulette.active == true || player->rocketsneakertimer || player->eggmanexplode)) - player->pflags |= PF_USERINGS; + player->itemflags |= IF_USERINGS; else - player->pflags &= ~PF_USERINGS; + player->itemflags &= ~IF_USERINGS; } if (player->ringboxdelay) @@ -11120,7 +11120,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->instaWhipCooldown = 0; } - if (leveltime < starttime || player->pflags & (PF_ITEMOUT|PF_EGGMANOUT) || player->rocketsneakertimer || player->instaWhipCooldown) + if (leveltime < starttime || player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT) || player->rocketsneakertimer || player->instaWhipCooldown) { chargingwhip = false; player->instaWhipCharge = 0; @@ -11194,7 +11194,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) // First, the really specific, finicky items that function without the item being directly in your item slot. { // Ring boosting - if (player->pflags & PF_USERINGS) + if (player->itemflags & IF_USERINGS) { if ((cmd->buttons & BT_ATTACK) && !player->ringdelay && player->rings > 0) { @@ -11218,13 +11218,13 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->eggmanexplode = 1; } // Eggman Monitor throwing - else if (player->pflags & PF_EGGMANOUT) + else if (player->itemflags & IF_EGGMANOUT) { if (ATTACK_IS_DOWN) { K_ThrowKartItem(player, false, MT_EGGMANITEM, -1, 0, 0); K_PlayAttackTaunt(player->mo); - player->pflags &= ~PF_EGGMANOUT; + player->itemflags &= ~IF_EGGMANOUT; K_UpdateHnextList(player, true); } } @@ -11328,7 +11328,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) prev = mo; } } - else if (ATTACK_IS_DOWN && (player->pflags & PF_ITEMOUT)) // Banana x3 thrown + else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Banana x3 thrown { K_ThrowKartItem(player, false, MT_BANANA, -1, 0, 0); K_PlayAttackTaunt(player->mo); @@ -11341,7 +11341,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { mobj_t *mo; player->itemamount--; - player->pflags |= PF_EGGMANOUT; + player->itemflags |= IF_EGGMANOUT; S_StartSound(player->mo, sfx_s254); mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM_SHIELD); if (mo) @@ -11391,7 +11391,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) prev = mo; } } - else if (ATTACK_IS_DOWN && (player->pflags & PF_ITEMOUT)) // Orbinaut x3 thrown + else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Orbinaut x3 thrown { K_ThrowKartItem(player, true, MT_ORBINAUT, 1, 0, 0); K_PlayAttackTaunt(player->mo); @@ -11432,7 +11432,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) prev = mo; } } - else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->pflags & PF_ITEMOUT)) // Jawz thrown + else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Jawz thrown { K_ThrowKartItem(player, true, MT_JAWZ, 1, 0, 0); K_PlayAttackTaunt(player->mo); @@ -11458,12 +11458,12 @@ void K_MoveKartPlayer(player_t *player, boolean onground) P_SetTarget(&player->mo->hnext, mo); } } - else if (ATTACK_IS_DOWN && (player->pflags & PF_ITEMOUT)) + else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) { K_ThrowKartItem(player, false, MT_SSMINE, 1, 1, 0); K_PlayAttackTaunt(player->mo); player->itemamount--; - player->pflags &= ~PF_ITEMOUT; + player->itemflags &= ~IF_ITEMOUT; K_UpdateHnextList(player, true); } break; @@ -11493,12 +11493,12 @@ void K_MoveKartPlayer(player_t *player, boolean onground) P_SetTarget(&player->mo->hnext, mo); } } - else if (ATTACK_IS_DOWN && (player->pflags & PF_ITEMOUT)) + else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) { K_ThrowKartItem(player, (player->throwdir > 0), MT_DROPTARGET, -1, 0, 0); K_PlayAttackTaunt(player->mo); player->itemamount--; - player->pflags &= ~PF_ITEMOUT; + player->itemflags &= ~IF_ITEMOUT; K_UpdateHnextList(player, true); } break; @@ -11518,7 +11518,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (player->ballhogcharge == 0) player->ballhogtap = false; - boolean realcharge = (cmd->buttons & BT_ATTACK) && (player->pflags & PF_HOLDREADY) && (player->ballhogcharge < ballhogmax); + boolean realcharge = (cmd->buttons & BT_ATTACK) && (player->itemflags & IT_HOLDREADY) && (player->ballhogcharge < ballhogmax); if ((realcharge && !player->ballhogtap) || (player->ballhogtap && player->ballhogcharge < BALLHOGINCREMENT)) { player->ballhogcharge++; @@ -11541,11 +11541,11 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { if (cmd->buttons & BT_ATTACK) { - player->pflags &= ~PF_HOLDREADY; + player->itemflags &= ~IF_HOLDREADY; } else { - player->pflags |= PF_HOLDREADY; + player->itemflags |= IF_HOLDREADY; } if (player->ballhogcharge > 0) @@ -11581,7 +11581,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } player->ballhogcharge = 0; - player->pflags &= ~PF_HOLDREADY; + player->itemflags &= ~IF_HOLDREADY; } } } @@ -11715,7 +11715,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (!HOLDING_ITEM && NO_HYUDORO) { - if ((cmd->buttons & BT_ATTACK) && (player->pflags & PF_HOLDREADY)) + if ((cmd->buttons & BT_ATTACK) && (player->itemflags & IF_HOLDREADY)) { if (player->bubbleblowup == 0) S_StartSound(player->mo, sfx_s3k75); @@ -11735,7 +11735,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) K_PlayAttackTaunt(player->mo); player->bubbleblowup = 0; player->bubblecool = 0; - player->pflags &= ~PF_HOLDREADY; + player->itemflags &= ~IF_HOLDREADY; player->itemamount--; } } @@ -11748,9 +11748,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->bubbleblowup--; if (player->bubblecool) - player->pflags &= ~PF_HOLDREADY; + player->itemflags &= ~IF_HOLDREADY; else - player->pflags |= PF_HOLDREADY; + player->itemflags |= IF_HOLDREADY; } } break; @@ -11774,7 +11774,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) flamemax = player->flamelength + TICRATE; // TICRATE leniency period, but we block most effects at flamelength 0 down below - if ((cmd->buttons & BT_ATTACK) && (player->pflags & PF_HOLDREADY)) + if ((cmd->buttons & BT_ATTACK) && (player->itemflags & IF_HOLDREADY)) { const INT32 incr = (gametyperules & GTR_CLOSERPLAYERS) ? 4 : 2; player->flamemeter += incr; @@ -11816,13 +11816,13 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->flamemeter = 0; player->flamelength = 0; - player->pflags &= ~PF_HOLDREADY; + player->itemflags &= ~IF_HOLDREADY; player->itemamount--; } } else { - player->pflags |= PF_HOLDREADY; + player->itemflags |= IF_HOLDREADY; if (!(gametyperules & GTR_CLOSERPLAYERS) || leveltime % 6 == 0) { @@ -11892,12 +11892,12 @@ void K_MoveKartPlayer(player_t *player, boolean onground) P_SetTarget(&player->mo->hnext, mo); } } - else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->pflags & PF_ITEMOUT)) // Sink thrown + else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Sink thrown { K_ThrowKartItem(player, false, MT_SINK, 1, 2, 0); K_PlayAttackTaunt(player->mo); player->itemamount--; - player->pflags &= ~PF_ITEMOUT; + player->itemflags &= ~IF_ITEMOUT; K_UpdateHnextList(player, true); } break; @@ -11932,7 +11932,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) // No more! if (!player->itemamount) { - player->pflags &= ~PF_ITEMOUT; + player->itemflags &= ~IF_ITEMOUT; player->itemtype = KITEM_NONE; } @@ -12027,7 +12027,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->trickpanel = 0; K_trickPanelTimingVisual(player, momz); // fail trick visual P_SetPlayerMobjState(player->mo, S_KART_SPINOUT); - if (player->pflags & (PF_ITEMOUT|PF_EGGMANOUT)) + if (player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT)) { //K_PopPlayerShield(player); // shield is just being yeeted, don't pop K_DropHnextList(player); diff --git a/src/k_menu.h b/src/k_menu.h index c02f29d50..f4144ac39 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -979,6 +979,7 @@ extern consvar_t cv_dummyprofilename; extern consvar_t cv_dummyprofileplayername; extern consvar_t cv_dummyprofilekickstart; extern consvar_t cv_dummyprofileautoroulette; +extern consvar_t cv_dummyprofilelitesteer; extern consvar_t cv_dummyprofilerumble; void M_ResetOptions(void); diff --git a/src/k_profiles.c b/src/k_profiles.c index f1ff15210..cd2491731 100644 --- a/src/k_profiles.c +++ b/src/k_profiles.c @@ -72,6 +72,7 @@ profile_t* PR_MakeProfile( new->followercolor = fcol; new->kickstartaccel = false; new->autoroulette = false; + new->litesteer = true; // Copy from gamecontrol directly as we'll be setting controls up directly in the profile. memcpy(new->controls, controlarray, sizeof(new->controls)); @@ -89,6 +90,7 @@ profile_t* PR_MakeProfileFromPlayer(const char *prname, const char *pname, const // Player bound cvars: new->kickstartaccel = cv_kickstartaccel[pnum].value; new->autoroulette = cv_autoroulette[pnum].value; + new->litesteer = cv_litesteer[pnum].value; new->rumble = cv_rumble[pnum].value; return new; @@ -276,6 +278,7 @@ void PR_SaveProfiles(void) // Consvars. WRITEUINT8(save.p, profilesList[i]->kickstartaccel); WRITEUINT8(save.p, profilesList[i]->autoroulette); + WRITEUINT8(save.p, profilesList[i]->litesteer); WRITEUINT8(save.p, profilesList[i]->rumble); // Controls. @@ -425,6 +428,17 @@ void PR_LoadProfiles(void) profilesList[i]->autoroulette = (boolean)READUINT8(save.p); } + // 7->8, add litesteer + if (version < 8) + { + profilesList[i]->litesteer = true; + + } + else + { + profilesList[i]->litesteer = (boolean)READUINT8(save.p); + } + if (version < 4) { profilesList[i]->rumble = true; @@ -478,6 +492,7 @@ static void PR_ApplyProfile_Settings(profile_t *p, UINT8 playernum) // toggles CV_StealthSetValue(&cv_kickstartaccel[playernum], p->kickstartaccel); CV_StealthSetValue(&cv_autoroulette[playernum], p->autoroulette); + CV_StealthSetValue(&cv_litesteer[playernum], p->litesteer); // set controls... memcpy(&gamecontrol[playernum], p->controls, sizeof(gamecontroldefault)); diff --git a/src/k_profiles.h b/src/k_profiles.h index 3b941500f..e920fe9be 100644 --- a/src/k_profiles.h +++ b/src/k_profiles.h @@ -31,7 +31,7 @@ extern "C" { #define SKINNAMESIZE 16 #define PROFILENAMELEN 6 -#define PROFILEVER 7 +#define PROFILEVER 8 #define MAXPROFILES 16 #define PROFILESFILE "ringprofiles.prf" #define PROFILE_GUEST 0 @@ -74,7 +74,8 @@ struct profile_t // Player-specific consvars. // @TODO: List all of those boolean kickstartaccel; // cv_kickstartaccel - boolean autoroulette; // cv_autoroulette + boolean autoroulette; // cv_autoroulette + boolean litesteer; // cv_litesteer boolean rumble; // cv_rumble // Finally, control data itself diff --git a/src/k_roulette.c b/src/k_roulette.c index 1b378aeae..cfc57fdbb 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1622,7 +1622,7 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd) // If the roulette finishes or the player presses BT_ATTACK, stop the roulette and calculate the item. // I'm returning via the exact opposite, however, to forgo having another bracket embed. Same result either way, I think. // Finally, if you get past this check, now you can actually start calculating what item you get. - if (confirmItem == true && (player->pflags & (PF_ITEMOUT|PF_EGGMANOUT|PF_USERINGS)) == 0) + if (confirmItem == true && (player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT|IF_USERINGS)) == 0) { if (roulette->eggman == true) { diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 5cba9fa62..0f6b7a485 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -259,6 +259,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->ringboxdelay); else if (fastcmp(field,"ringboxaward")) lua_pushinteger(L, plr->ringboxaward); + else if (fastcmp(field,"itemflags")) + lua_pushinteger(L, plr->itemflags); else if (fastcmp(field,"drift")) lua_pushinteger(L, plr->drift); else if (fastcmp(field,"driftcharge")) @@ -745,6 +747,8 @@ static int player_set(lua_State *L) plr->ringboxdelay = luaL_checkinteger(L, 3); else if (fastcmp(field,"ringboxaward")) plr->ringboxaward = luaL_checkinteger(L, 3); + else if (fastcmp(field,"itemflags")) + plr->itemflags = luaL_checkinteger(L, 3); else if (fastcmp(field,"drift")) plr->drift = luaL_checkinteger(L, 3); else if (fastcmp(field,"driftcharge")) diff --git a/src/menus/options-profiles-1.c b/src/menus/options-profiles-1.c index ee9f3a40d..bb194a2ee 100644 --- a/src/menus/options-profiles-1.c +++ b/src/menus/options-profiles-1.c @@ -89,6 +89,7 @@ static void M_StartEditProfile(INT32 c) CV_StealthSet(&cv_dummyprofileplayername, optionsmenu.profile->playername); CV_StealthSetValue(&cv_dummyprofilekickstart, optionsmenu.profile->kickstartaccel); CV_StealthSetValue(&cv_dummyprofileautoroulette, optionsmenu.profile->autoroulette); + CV_StealthSetValue(&cv_dummyprofilelitesteer, optionsmenu.profile->litesteer); CV_StealthSetValue(&cv_dummyprofilerumble, optionsmenu.profile->rumble); } else @@ -97,6 +98,7 @@ static void M_StartEditProfile(INT32 c) CV_StealthSet(&cv_dummyprofileplayername, ""); CV_StealthSetValue(&cv_dummyprofilekickstart, 0); // off CV_StealthSetValue(&cv_dummyprofileautoroulette, 0); // off + CV_StealthSetValue(&cv_dummyprofilelitesteer, 1); // on CV_StealthSetValue(&cv_dummyprofilerumble, 1); // on } diff --git a/src/menus/options-profiles-edit-controls.c b/src/menus/options-profiles-edit-controls.c index dc648abd6..0b7e8dcf6 100644 --- a/src/menus/options-profiles-edit-controls.c +++ b/src/menus/options-profiles-edit-controls.c @@ -94,6 +94,9 @@ menuitem_t OPTIONS_ProfileControls[] = { {IT_CONTROL | IT_CVAR, "AUTO ROULETTE", "Item roulette auto-stops on a random result.", NULL, {.cvar = &cv_dummyprofileautoroulette}, 0, 0}, + {IT_CONTROL | IT_CVAR, "LITE STEER", "Hold DOWN on d-pad/keyboard for shallow turns.", + NULL, {.cvar = &cv_dummyprofilelitesteer}, 0, 0}, + {IT_HEADER, "EXTRA", "", NULL, {NULL}, 0, 0}, @@ -191,6 +194,7 @@ static void M_ProfileControlSaveResponse(INT32 choice) // Save the profile optionsmenu.profile->kickstartaccel = cv_dummyprofilekickstart.value; optionsmenu.profile->autoroulette = cv_dummyprofileautoroulette.value; + optionsmenu.profile->litesteer = cv_dummyprofilelitesteer.value; optionsmenu.profile->rumble = cv_dummyprofilerumble.value; memcpy(&optionsmenu.profile->controls, optionsmenu.tempcontrols, sizeof(gamecontroldefault)); @@ -201,6 +205,7 @@ static void M_ProfileControlSaveResponse(INT32 choice) memcpy(&gamecontrol[belongsto], optionsmenu.tempcontrols, sizeof(gamecontroldefault)); CV_SetValue(&cv_kickstartaccel[belongsto], cv_dummyprofilekickstart.value); CV_SetValue(&cv_autoroulette[belongsto], cv_dummyprofileautoroulette.value); + CV_SetValue(&cv_litesteer[belongsto], cv_dummyprofilelitesteer.value); CV_SetValue(&cv_rumble[belongsto], cv_dummyprofilerumble.value); } diff --git a/src/p_inter.c b/src/p_inter.c index e5feda38a..266cba2e2 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -153,7 +153,7 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon) if (player->itemRoulette.active == true || player->ringboxdelay > 0 || (weapon != 3 && player->itemamount) - || (player->pflags & PF_ITEMOUT)) + || (player->itemflags & IF_ITEMOUT)) return false; if (weapon == 3 && K_GetShieldFromItem(player->itemtype) != KSHIELD_NONE) @@ -1577,10 +1577,10 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget // I wish I knew a better way to do this if (target->target && target->target->player && target->target->player->mo) { - if ((target->target->player->pflags & PF_EGGMANOUT) && target->type == MT_EGGMANITEM_SHIELD) - target->target->player->pflags &= ~PF_EGGMANOUT; + if ((target->target->player->itemflags & IF_EGGMANOUT) && target->type == MT_EGGMANITEM_SHIELD) + target->target->player->itemflags &= ~IF_EGGMANOUT; - if (target->target->player->pflags & PF_ITEMOUT) + if (target->target->player->itemflags & IF_ITEMOUT) { if ((target->type == MT_BANANA_SHIELD && target->target->player->itemtype == KITEM_BANANA) // trail items || (target->type == MT_SSMINE_SHIELD && target->target->player->itemtype == KITEM_MINE) @@ -1608,7 +1608,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget } if (!target->target->player->itemamount) - target->target->player->pflags &= ~PF_ITEMOUT; + target->target->player->itemflags &= ~IF_ITEMOUT; if (target->target->hnext == target) P_SetTarget(&target->target->hnext, NULL); diff --git a/src/p_mobj.c b/src/p_mobj.c index f0baa74e1..5e74fc9db 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6471,7 +6471,7 @@ static void P_MobjSceneryThink(mobj_t *mobj) break; } - if (mobj->target->player->pflags & PF_ITEMOUT) + if (mobj->target->player->itemflags & IF_ITEMOUT) { if (leveltime & 1) mobj->tracer->renderflags &= ~RF_DONTDRAW; diff --git a/src/p_saveg.c b/src/p_saveg.c index 0b0aeab9b..e36464ad9 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -559,6 +559,9 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].ringboxdelay); WRITEUINT8(save->p, players[i].ringboxaward); + + WRITEUINT8(save->p, players[i].itemflags); + WRITEFIXED(save->p, players[i].outrun); WRITEUINT8(save->p, players[i].rideroid); @@ -1076,6 +1079,9 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].ringboxdelay = READUINT8(save->p); players[i].ringboxaward = READUINT8(save->p); + + players[i].itemflags = READUINT8(save->p); + players[i].outrun = READFIXED(save->p); players[i].rideroid = (boolean)READUINT8(save->p); From 0b51bc91f949a34573b9c8216aa939703a7dc14b Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 9 Nov 2023 17:07:04 -0700 Subject: [PATCH 13/25] Fix comment referring to itemflags by wrong name --- src/d_player.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d_player.h b/src/d_player.h index aae92b311..ff6dfc23b 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -921,7 +921,7 @@ struct player_t UINT8 ringboxdelay; // Delay until Ring Box auto-activates UINT8 ringboxaward; // Where did we stop? - UINT8 itemflags; // holds IS_flags (see itemstate_t) + UINT8 itemflags; // holds IF_ flags (see itemflags_t) fixed_t outrun; // Milky Way road effect From 81e7ec6aa30675e223059ee5c74192e8a9ea2be7 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 9 Nov 2023 17:30:19 -0700 Subject: [PATCH 14/25] itemflag tyop --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 64a2d7472..a56320b54 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -11518,7 +11518,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (player->ballhogcharge == 0) player->ballhogtap = false; - boolean realcharge = (cmd->buttons & BT_ATTACK) && (player->itemflags & IT_HOLDREADY) && (player->ballhogcharge < ballhogmax); + boolean realcharge = (cmd->buttons & BT_ATTACK) && (player->itemflags & IF_HOLDREADY) && (player->ballhogcharge < ballhogmax); if ((realcharge && !player->ballhogtap) || (player->ballhogtap && player->ballhogcharge < BALLHOGINCREMENT)) { player->ballhogcharge++; From 37f23842295a01631b99f16d85186796d5622aa8 Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 9 Nov 2023 22:46:57 -0800 Subject: [PATCH 15/25] Add srb2::math, fixed-point, vector, line and slope formula classes - srb2::math::Fixed - Operator overloads for FixedMul and FixedDiv - Implicit conversion between fixed_t, Fixed and floating-point types - srb2::math::Vec2 - Template to any type - Operator overloads for arithmetic operations - Convertible between different types - srb2::math::LineSegment - Template to any type - Holds two Vec2 instances - Sorting methods and vertical/horizontal test - srb2::math::LineEquation - Slope formula from LineSegment - y method to find y from x - Intersect algorithm - Fixed-point specialization to avoid overflows - srb2::math::LineEquationX - Inherits LineEquation - x method to find x from y --- src/math/fixed.hpp | 107 +++++++++++++++++++++++++++++++++++++ src/math/line_equation.hpp | 102 +++++++++++++++++++++++++++++++++++ src/math/line_segment.hpp | 43 +++++++++++++++ src/math/traits.hpp | 34 ++++++++++++ src/math/vec.hpp | 84 +++++++++++++++++++++++++++++ 5 files changed, 370 insertions(+) create mode 100644 src/math/fixed.hpp create mode 100644 src/math/line_equation.hpp create mode 100644 src/math/line_segment.hpp create mode 100644 src/math/traits.hpp create mode 100644 src/math/vec.hpp diff --git a/src/math/fixed.hpp b/src/math/fixed.hpp new file mode 100644 index 000000000..0e5cae4c1 --- /dev/null +++ b/src/math/fixed.hpp @@ -0,0 +1,107 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef math_fixed_hpp +#define math_fixed_hpp + +#include + +#include "traits.hpp" + +#include "../m_fixed.h" + +namespace srb2::math +{ + +struct Fixed +{ + static Fixed copysign(fixed_t x, fixed_t y) { return (x < 0) != (y < 0) ? -x : x; } + static Fixed hypot(fixed_t x, fixed_t y) { return FixedHypot(x, y); } + + constexpr Fixed() : val_(0) {} + constexpr Fixed(fixed_t val) : val_(val) {} + + template , bool> = true> + Fixed(T val) : val_(FloatToFixed(val)) {} + + Fixed(const Fixed& b) = default; + Fixed& operator=(const Fixed& b) = default; + + fixed_t value() const { return val_; } + int sign() const { return val_ < 0 ? -1 : 1; } + + operator fixed_t() const { return val_; } + explicit operator float() const { return FixedToFloat(val_); } + + Fixed& operator+=(const Fixed& b) + { + val_ += b.val_; + return *this; + } + + Fixed& operator-=(const Fixed& b) + { + val_ -= b.val_; + return *this; + } + + Fixed& operator*=(const Fixed& b) + { + val_ = FixedMul(val_, b.val_); + return *this; + } + + Fixed& operator/=(const Fixed& b) + { + val_ = FixedDiv(val_, b.val_); + return *this; + } + + Fixed operator-() const { return -val_; } + +#define X(op) \ + template \ + Fixed operator op(const T& b) const { return val_ op b; } \ + Fixed operator op(const Fixed& b) const \ + { \ + Fixed f{val_};\ + f op##= b;\ + return f;\ + } \ + template \ + Fixed& operator op##=(const T& b) \ + { \ + val_ op##= b; \ + return *this; \ + } + + X(+) + X(-) + X(*) + X(/) + +#undef X + +private: + fixed_t val_; +}; + +template <> +struct Traits +{ + static constexpr Fixed kZero = 0; + static constexpr Fixed kUnit = FRACUNIT; + + static constexpr auto copysign = Fixed::copysign; + static constexpr auto hypot = Fixed::hypot; +}; + +}; // namespace srb2::math + +#endif/*math_fixed_hpp*/ diff --git a/src/math/line_equation.hpp b/src/math/line_equation.hpp new file mode 100644 index 000000000..ec4b69d52 --- /dev/null +++ b/src/math/line_equation.hpp @@ -0,0 +1,102 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef math_line_equation_hpp +#define math_line_equation_hpp + +#include "fixed.hpp" +#include "line_segment.hpp" +#include "vec.hpp" + +namespace srb2::math +{ + +template +struct LineEquation +{ + using vec2 = Vec2; + using line_segment = LineSegment; + + // Fixed-point: shift value by this amount during + // multiplications and divisions to avoid overflows. + static constexpr std::enable_if_t, fixed_t> kF = 1024; // fixed_t, not Fixed + + LineEquation() {} + LineEquation(const vec2& p, const vec2& d) : d_(d), m_(d.y / d.x), b_(p.y - (p.x * m())) {} + LineEquation(const line_segment& l) : LineEquation(l.a, l.b - l.a) {} + + const vec2& d() const { return d_; } + T m() const { return m_; } + T b() const { return b_; } + T y(T x) const { return (m() * x) + b(); } + + vec2 intersect(const LineEquation& q) const + { + T x = (b() - q.b()) / (q.m() - m()); + return {x, y(x)}; + } + +protected: + vec2 d_{}; + T m_{}, b_{}; +}; + +template <> +inline LineEquation::LineEquation(const vec2& p, const vec2& d) : + d_(d), m_((d.y / d.x) / kF), b_((p.y / kF) - (p.x * m_)) +{ +} + +template <> +inline Fixed LineEquation::m() const +{ + return m_ * kF; +} + +template <> +inline Fixed LineEquation::b() const +{ + return b_ * kF; +} + +template <> +inline Fixed LineEquation::y(Fixed x) const +{ + return ((m_ * x) + b_) * kF; +} + +template <> +inline LineEquation::vec2 LineEquation::intersect(const LineEquation& q) const +{ + Fixed x = ((b_ - q.b_) / ((q.m_ - m_) * kF)) * kF; + return {x, y(x)}; +} + +template +struct LineEquationX : LineEquation +{ + T x(T y) const { return (y - LineEquation::b()) / LineEquation::m(); } +}; + +template <> +struct LineEquationX : LineEquation +{ + LineEquationX() {} + LineEquationX(const vec2& p, const vec2& d) : LineEquation(p, d), w_((d.x / d.y) / kF), a_((p.x / kF) - (p.y * w_)) {} + LineEquationX(const line_segment& l) : LineEquationX(l.a, l.b - l.a) {} + + Fixed x(Fixed y) const { return ((w_ * y) + a_) * kF; } + +protected: + Fixed w_{}, a_{}; +}; + +}; // namespace srb2::math + +#endif/*math_line_equation_hpp*/ diff --git a/src/math/line_segment.hpp b/src/math/line_segment.hpp new file mode 100644 index 000000000..0094acf60 --- /dev/null +++ b/src/math/line_segment.hpp @@ -0,0 +1,43 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef math_line_segment_hpp +#define math_line_segment_hpp + +#include +#include + +#include "vec.hpp" + +namespace srb2::math +{ + +template +struct LineSegment +{ + using vec2 = Vec2; + using view = std::pair; + + vec2 a, b; + + LineSegment(vec2 a_, vec2 b_) : a(a_), b(b_) {} + + template + LineSegment(const LineSegment& b) : LineSegment(b.a, b.b) {} + + bool horizontal() const { return a.y == b.y; } + bool vertical() const { return a.x == b.x; } + + view by_x() const { return std::minmax(a, b, [](auto& a, auto& b) { return a.x < b.x; }); } + view by_y() const { return std::minmax(a, b, [](auto& a, auto& b) { return a.y < b.y; }); } +}; + +}; // namespace srb2 + +#endif/*math_line_segment_hpp*/ diff --git a/src/math/traits.hpp b/src/math/traits.hpp new file mode 100644 index 000000000..fbb6aaa5b --- /dev/null +++ b/src/math/traits.hpp @@ -0,0 +1,34 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef math_traits_hpp +#define math_traits_hpp + +#include +#include + +namespace srb2::math +{ + +template +struct Traits; + +template +struct Traits>> +{ + static constexpr T kZero = 0.0; + static constexpr T kUnit = 1.0; + + static T copysign(T x, T y) { return std::copysign(x, y); } + static T hypot(T x, T y) { return std::hypot(x, y); } +}; + +}; // namespace srb2::math + +#endif/*math_traits_hpp*/ diff --git a/src/math/vec.hpp b/src/math/vec.hpp new file mode 100644 index 000000000..670a3677c --- /dev/null +++ b/src/math/vec.hpp @@ -0,0 +1,84 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef math_vec_hpp +#define math_vec_hpp + +#include + +#include "traits.hpp" + +namespace srb2::math +{ + +template +struct Vec2 +{ + T x, y; + + Vec2() : x{}, y{} {} + Vec2(T x_, T y_) : x(x_), y(y_) {} + Vec2(T z) : x(z), y(z) {} + + template + Vec2(const Vec2& b) : Vec2(b.x, b.y) {} + + T magnitude() const { return Traits::hypot(x, y); } + Vec2 normal() const { return {-y, x}; } + +#define X(op) \ + Vec2& operator op##=(const Vec2& b) \ + { \ + x op##= b.x; \ + y op##= b.y; \ + return *this; \ + } \ + Vec2 operator op(const Vec2& b) const { return Vec2(x op b.x, y op b.y); } \ + + X(+) + X(-) + X(*) + X(/) + +#undef X + + Vec2 operator-() const { return Vec2(-x, -y); } +}; + +template +struct is_vec2 : std::false_type {}; + +template +struct is_vec2> : std::true_type {}; + +template +inline constexpr bool is_vec2_v = is_vec2::value; + +#define X(op) \ + template , bool> = true> \ + Vec2 operator op(const T& a, const Vec2& b) \ + { \ + return Vec2 {a} op Vec2 {b}; \ + } \ + template , bool> = true> \ + Vec2 operator op(const Vec2& a, const U& b) \ + { \ + return Vec2 {a} op Vec2 {b}; \ + } \ + +X(+) +X(-) +X(*) +X(/) + +#undef X + +}; // namespace srb2::math + +#endif/*math_vec_hpp*/ From 5a62a07e54b1838f05f1e45288b525aecffba8fe Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 9 Nov 2023 23:53:42 -0800 Subject: [PATCH 16/25] Add srb2::sweep, AABB line sweep algorithms --- src/CMakeLists.txt | 1 + src/p_sweep.cpp | 271 +++++++++++++++++++++++++++++++++++++++++++++ src/p_sweep.hpp | 131 ++++++++++++++++++++++ 3 files changed, 403 insertions(+) create mode 100644 src/p_sweep.cpp create mode 100644 src/p_sweep.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8cfba2d4c..4f088dae7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -64,6 +64,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 p_tick.c p_user.c p_slopes.c + p_sweep.cpp tables.c r_bsp.cpp r_data.c diff --git a/src/p_sweep.cpp b/src/p_sweep.cpp new file mode 100644 index 000000000..d4e9d22ef --- /dev/null +++ b/src/p_sweep.cpp @@ -0,0 +1,271 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include +#include + +#include "p_sweep.hpp" + +using namespace srb2::math; +using namespace srb2::sweep; + +Result SlopeAABBvsLine::vs_slope(const line_segment& l) const +{ + auto [a, b] = l.by_x(); // left, right + LineEquation ql{l}; + unit ls = copysign(kUnit, ql.m()); + + auto hit = [&](const vec2& k, unit xr, unit x, const vec2& n) -> Contact + { + std::optional k2; + + if (l.horizontal()) + { + // Horizontal line: create second contact point on opposite corner. + // TODO: avoid duplicate point + k2 = vec2(std::clamp(x + xr, a.x, b.x), k.y); + } + + return {time(x), n, k, k2}; + }; + + auto slide = [&](const vec2& k, const vec2& s) -> std::optional + { + vec2 kf = k * s; + vec2 r = r_ * s; + vec2 p = k - r; + + // Slide vertically along AABB left/right edge. + unit f = q_.y(p.x) * s.y; + + if (f - r_ > kf.y) + { + // Out of bounds detection. + // This should never slide in front. + // If it does, there was never a hit. + return {}; + } + + if (f + r_ < kf.y) + { + // Slid behind contact point. + // Try sliding horizontally along AABB top/bottom + // edge. + + if (q_.m() == kZero) + { + // Sweep is horizontal. + // It is impossible to slide against a line's + // end by the X axis because the line segment + // lies on that axis. + return {}; + } + + p.x = q_.x(p.y); + f = p.x * s.x; + + if (f - r_ > kf.x) + { + // Slid beyond contact point. + return {}; + } + + if (f + r_ < kf.x) + { + // Out of bounds detection. + // This should never slide behind. + // If it does, there was never a hit. + return {}; + } + + return hit(k, r.x, p.x, {kZero, -s.y}); + } + + return hit(k, r.x, p.x, {-s.x, kZero}); + }; + + // xrs.x = x radius + // xrs.y = x sign + auto bind = [&](const vec2& k, const vec2& xrs, unit ns) -> std::optional + { + if (k.x < a.x) + { + return slide(a, {xrs.y, ls}); + } + + if (k.x > b.x) + { + return slide(b, {xrs.y, -ls}); + } + + return hit(k, xrs.x, k.x + xrs.x, normal(l) * ns); + }; + + if (ql.m() == q_.m()) + { + // Parallel lines can only cross at the ends. + vec2 s{kUnit, ls}; + return order(slide(a, s), slide(b, -s), ds_.x); + } + + vec2 i = ql.intersect(q_); + + // Compare slopes to determine if ray is moving upward or + // downward into line. + // For a positive line, AABB top left corner hits the + // line first if the ray is moving upward. + // Swap diagonal corners to bottom right if moving + // downward. + unit ys = q_.m() * ds_.x < ql.m() * ds_.x ? -kUnit : kUnit; + unit yr = r_ * ys; + + // Swap left/right corners if line is negative. + unit xr = yr * ls; + + // Intersection as if ray were offset -r, +r. + vec2 v = [&] + { + unit y = (q_.m() * xr) + yr; + unit x = y / (ql.m() - q_.m()); + return vec2 {x, (x * q_.m()) + y}; + }(); + + // Find the intersection along diagonally oppposing AABB + // corners. + vec2 xrs{xr, ds_.x}; + return {bind(i + v, xrs, -ys), bind(i - v, -xrs, -ys)}; +} + +// TODO: Comments. Bitch. +Result SlopeAABBvsLine::vs_vertical(const line_segment& l) const +{ + auto [a, b] = l.by_y(); // bottom, top + + auto hit = [&](const vec2& p, std::optional q, unit x, const vec2& n) -> Contact { return {time(x), n, p, q}; }; + + auto bind = [&](const vec2& k, const vec2& a, const vec2& b, const vec2& s, auto limit) -> std::optional + { + vec2 r = r_ * s; + vec2 af = a * s; + unit kyf = k.y * s.y; + + if (kyf + r_ < af.y) + { + if (q_.m() == kZero) + { + return {}; + } + + unit x = q_.x(a.y - r.y); + + if ((x * s.x) - r_ > af.x) + { + return {}; + } + + return hit(a, {}, x, {kZero, -s.y}); + } + + // TODO: avoid duplicate point + vec2 k2{k.x, limit(k.y - r.y, a.y)}; + unit byf = b.y * s.y; + vec2 n{-s.x, kZero}; + + if (kyf + r_ > byf) + { + if (kyf - r_ > byf) + { + return {}; + } + + return hit(b, k2, k.x - r.x, n); + } + + return hit(vec2(k.x, k.y + r.y), k2, k.x - r.x, n); + }; + + vec2 i{a.x, q_.y(a.x)}; + vec2 v{kZero, q_.m() * r_ * ds_.x * ds_.y}; + vec2 s = ds_ * ds_.y; + + // Damn you, template overloads! + auto min = [](unit x, unit y) { return std::min(x, y); }; + auto max = [](unit x, unit y) { return std::max(x, y); }; + + return order(bind(i - v, a, b, s, max), bind(i + v, b, a, -s, min), ds_.y); +} + +Result VerticalAABBvsLine::vs_slope(const line_segment& l) const +{ + auto [a, b] = l.by_x(); // left, right + LineEquation ql{l}; + + auto hit = [&](const vec2& k, unit xr, unit y, const vec2& n) -> Contact + { + std::optional k2; + + if (l.horizontal()) + { + // Horizontal line: create second contact point on opposite corner. + // TODO: avoid duplicate point + k2 = vec2(std::clamp(x_ + xr, a.x, b.x), k.y); + } + + return {time(y), n, k, k2}; + }; + + auto bind = [&](const vec2& a, const vec2& b, const vec2& s) -> std::optional + { + vec2 r = r_ * s; + unit xf = x_ * s.x; + + if (xf - r_ > b.x * s.x) + { + return {}; + } + + unit axf = a.x * s.x; + + if (xf - r_ < axf) + { + if (xf + r_ < axf) + { + return {}; + } + + return hit(a, r.x, a.y - r.y, {kZero, -s.y}); + } + + vec2 i{x_, ql.y(x_)}; + vec2 v{r.x, ql.m() * r.x}; + vec2 k = i - v; + return hit(k, r.x, k.y - r.y, normal(l) * -s.y); + }; + + unit mys = copysign(kUnit, ql.m() * ds_.y); + vec2 s{kUnit, ds_.y * mys}; + return order(bind(a, b, s), bind(b, a, -s), mys); +} + +Result VerticalAABBvsLine::vs_vertical(const line_segment& l) const +{ + // Box does not overlap Y plane. + if (x_ + r_ < l.a.x || x_ - r_ > l.a.x) + { + return {}; + } + + auto [a, b] = l.by_y(); // bottom, top + + auto hit = [&](const vec2& k, unit yr) -> Contact { return {time(k.y + yr), {kZero, -ds_.y}, k}; }; + + // Offset away from line ends. + // Contacts are opposite when swept downward. + return order(hit(a, -r_), hit(b, r_), ds_.y); +} diff --git a/src/p_sweep.hpp b/src/p_sweep.hpp new file mode 100644 index 000000000..68c6ac359 --- /dev/null +++ b/src/p_sweep.hpp @@ -0,0 +1,131 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef p_sweep_hpp +#define p_sweep_hpp + +#include +#include + +#include "math/fixed.hpp" +#include "math/line_equation.hpp" +#include "math/line_segment.hpp" +#include "math/vec.hpp" + +namespace srb2::sweep +{ + +using unit = math::Fixed; +using vec2 = math::Vec2; +using line_segment = math::LineSegment; + +struct Contact +{ + unit z; // time + vec2 n; // normal TODO REMOVE duplicate for each contact + vec2 p; // contact point 1 + std::optional q; // AABBvsLine: contact point 2 +}; + +struct Result +{ + std::optional hit, exit; // TODO result itself should be optional, not each contact +}; + +namespace detail +{ + +template +struct BaseAABBvsLine : protected srb2::math::Traits +{ +public: + Result operator()(const line_segment& l) const + { + auto derived = static_cast(this); + return l.vertical() ? derived->vs_vertical(l) : derived->vs_slope(l); + } + +protected: + unit r_; // AABB radius + vec2 ds_; // sweep direction signs + + BaseAABBvsLine(unit r, const vec2& d, unit pz, unit dz) : + r_(r), ds_(copysign(kUnit, d.x), copysign(kUnit, d.y)), t_(pz, dz) {} + + unit time(unit x) const { return (x - t_.x) / t_.y; } + + static Result order(std::optional&& t1, std::optional&& t2, unit s) + { + return s > kZero ? Result {t1, t2} : Result {t2, t1}; + } + + static vec2 normal(const vec2& v) + { + // Normalize vector so that x is positive -- normal always points up. + return v.normal() * (copysign(kUnit, v.x) / v.magnitude()); + } + + static vec2 normal(const line_segment& l) { return normal(l.b - l.a); } + +private: + vec2 t_; // origin and length for calculating time +}; + +}; // namespace detail + +// Sweep can be represented as y = mx + b +struct SlopeAABBvsLine : detail::BaseAABBvsLine +{ + SlopeAABBvsLine(unit r, const line_segment& l) : SlopeAABBvsLine(r, l.a, l.b - l.a) {} + + Result vs_slope(const line_segment& l) const; + Result vs_vertical(const line_segment& l) const; + +private: + math::LineEquationX q_; + + SlopeAABBvsLine(unit r, const vec2& p, const vec2& d) : BaseAABBvsLine(r, d, p.x, d.x), q_(p, d) {} +}; + +// Sweep is vertical +struct VerticalAABBvsLine : detail::BaseAABBvsLine +{ + VerticalAABBvsLine(unit r, const line_segment& l) : VerticalAABBvsLine(r, l.a, l.b - l.a) {} + + Result vs_slope(const line_segment& l) const; + Result vs_vertical(const line_segment& l) const; + +private: + unit x_; + + VerticalAABBvsLine(unit r, const vec2& p, const vec2& d) : BaseAABBvsLine(r, d, p.y, d.y), x_(p.x) {} +}; + +struct AABBvsLine +{ + AABBvsLine(unit r, const line_segment& l) : + var_(l.vertical() ? var_t {VerticalAABBvsLine(r, l)} : var_t {SlopeAABBvsLine(r, l)}) + { + } + + Result operator()(const line_segment& l) const + { + Result rs; + std::visit([&](auto& sweeper) { rs = sweeper(l); }, var_); + return rs; + } + +private: + using var_t = std::variant; + var_t var_; +}; + +}; // namespace srb2::sweep + +#endif/*p_sweep_hpp*/ From 7861d51a7f39848d9478739cbd396fba4637ae82 Mon Sep 17 00:00:00 2001 From: James R Date: Fri, 10 Nov 2023 00:02:32 -0800 Subject: [PATCH 17/25] P_TryMove: sweep collided lines to find nearest normal --- src/CMakeLists.txt | 1 + src/cvars.cpp | 1 + src/p_local.h | 18 ++++- src/p_map.c | 190 +++++++++++++++++++++++++++++++++------------ src/p_mobj.c | 4 +- src/p_test.cpp | 78 +++++++++++++++++++ 6 files changed, 238 insertions(+), 54 deletions(-) create mode 100644 src/p_test.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4f088dae7..a1f3d29e6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -65,6 +65,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 p_user.c p_slopes.c p_sweep.cpp + p_test.cpp tables.c r_bsp.cpp r_data.c diff --git a/src/cvars.cpp b/src/cvars.cpp index 4b0897dcf..9336becb2 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -806,6 +806,7 @@ consvar_t cv_numlaps = OnlineCheat("numlaps", "Map default").values(numlaps_cons consvar_t cv_restrictskinchange = OnlineCheat("restrictskinchange", "Yes").yes_no().description("Don't let players change their skin in the middle of gameplay"); consvar_t cv_spbtest = OnlineCheat("spbtest", "Off").on_off().description("SPB can never target a player"); +consvar_t cv_showgremlins = OnlineCheat("showgremlins", "No").yes_no().description("Show line collision errors"); consvar_t cv_timescale = OnlineCheat(cvlist_timer)("timescale", "1.0").floating_point().min_max(FRACUNIT/20, 20*FRACUNIT).description("Overclock or slow down the game"); consvar_t cv_ufo_follow = OnlineCheat("ufo_follow", "0").min_max(0, MAXPLAYERS).description("Make UFO Catcher folow this player"); consvar_t cv_ufo_health = OnlineCheat("ufo_health", "-1").min_max(-1, 100).description("Override UFO Catcher health -- applied at spawn or when value is changed"); diff --git a/src/p_local.h b/src/p_local.h index 0e0a3fb23..045a59472 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -387,9 +387,16 @@ struct tm_t // so missiles don't explode against sky hack walls line_t *ceilingline; - // set by PIT_CheckLine() for any line that stopped the PIT_CheckLine() - // that is, for any line which is 'solid' - line_t *blockingline; + // P_CheckPosition: this position blocks movement + boolean blocking; + + // P_CheckPosition: set this before each call to + // P_CheckPosition to enable a line sweep on collided + // lines + boolean sweep; + + // sweep: max step up at tm.x, tm.y + fixed_t maxstep; }; extern tm_t tm; @@ -415,6 +422,7 @@ struct TryMoveResult_t boolean success; line_t *line; mobj_t *mo; + vector2_t normal; }; boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y, TryMoveResult_t *result); @@ -422,6 +430,10 @@ boolean P_CheckMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff, T boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff, TryMoveResult_t *result); boolean P_SceneryTryMove(mobj_t *thing, fixed_t x, fixed_t y, TryMoveResult_t *result); +void P_TestLine(line_t *ld); +void P_ClearTestLines(void); +line_t *P_SweepTestLines(fixed_t ax, fixed_t ay, fixed_t bx, fixed_t by, fixed_t r, vector2_t *return_normal); + boolean P_IsLineBlocking(const line_t *ld, const mobj_t *thing); boolean P_IsLineTripWire(const line_t *ld); boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam); diff --git a/src/p_map.c b/src/p_map.c index 79b9ba59e..683dd3c5d 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -1770,7 +1770,6 @@ static BlockItReturn_t PIT_CheckCameraLine(line_t *ld) // could be crossed in either order. // this line is out of the if so upper and lower textures can be hit by a splat - tm.blockingline = ld; if (!ld->backsector) // one sided line { if (P_PointOnLineSide(mapcampointer->x, mapcampointer->y, ld)) @@ -1841,6 +1840,22 @@ boolean P_IsLineTripWire(const line_t *ld) return ld->tripwire; } +static boolean P_UsingStepUp(mobj_t *thing) +{ + if (thing->flags & MF_NOCLIP) + { + return false; + } + + // orbits have no collision + if (thing->player && thing->player->loop.radius) + { + return false; + } + + return true; +} + // // PIT_CheckLine // Adjusts tm.floorz and tm.ceilingz as lines are contacted @@ -1898,14 +1913,20 @@ static BlockItReturn_t PIT_CheckLine(line_t *ld) // could be crossed in either order. // this line is out of the if so upper and lower textures can be hit by a splat - tm.blockingline = ld; { - UINT8 shouldCollide = LUA_HookMobjLineCollide(tm.thing, tm.blockingline); // checks hook for thing's type + UINT8 shouldCollide = LUA_HookMobjLineCollide(tm.thing, ld); // checks hook for thing's type if (P_MobjWasRemoved(tm.thing)) return BMIT_CONTINUE; // one of them was removed??? if (shouldCollide == 1) - return BMIT_ABORT; // force collide + { + if (tm.sweep) + { + P_TestLine(ld); + } + tm.blocking = true; // force collide + return BMIT_CONTINUE; + } else if (shouldCollide == 2) return BMIT_CONTINUE; // force no collide } @@ -1914,15 +1935,55 @@ static BlockItReturn_t PIT_CheckLine(line_t *ld) { if (P_PointOnLineSide(tm.thing->x, tm.thing->y, ld)) return BMIT_CONTINUE; // don't hit the back side - return BMIT_ABORT; + + if (tm.sweep) + { + P_TestLine(ld); + } + tm.blocking = true; + return BMIT_CONTINUE; } if (P_IsLineBlocking(ld, tm.thing)) - return BMIT_ABORT; + { + if (tm.sweep) + { + P_TestLine(ld); + } + tm.blocking = true; + return BMIT_CONTINUE; + } // set openrange, opentop, openbottom P_LineOpening(ld, tm.thing, &open); + if (tm.sweep && P_UsingStepUp(tm.thing)) + { + // copied from P_TryMove + // TODO: refactor this into one place + if (open.range < tm.thing->height) + { + P_TestLine(ld); + } + else if (tm.maxstep > 0) + { + if (tm.thing->z < open.floor) + { + if (open.floorstep > tm.maxstep) + { + P_TestLine(ld); + } + } + else if (open.ceiling < tm.thing->z + tm.thing->height) + { + if (open.ceilingstep > tm.maxstep) + { + P_TestLine(ld); + } + } + } + } + // adjust floor / ceiling heights if (open.ceiling < tm.ceilingz) { @@ -2042,7 +2103,8 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y, TryMoveResult_t *re tm.bbox[BOXLEFT] = x - tm.thing->radius; newsubsec = R_PointInSubsector(x, y); - tm.ceilingline = tm.blockingline = NULL; + tm.ceilingline = NULL; + tm.blocking = false; // The base floor / ceiling is from the subsector // that contains the point. @@ -2314,23 +2376,33 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y, TryMoveResult_t *re validcount++; + P_ClearTestLines(); + // check lines for (bx = xl; bx <= xh; bx++) { for (by = yl; by <= yh; by++) { - if (!P_BlockLinesIterator(bx, by, PIT_CheckLine)) - { - blockval = false; - } + P_BlockLinesIterator(bx, by, PIT_CheckLine); } } + if (tm.blocking) + { + blockval = false; + } + if (result != NULL) { - result->line = tm.blockingline; + result->line = NULL; result->mo = tm.hitthing; } + else + { + P_ClearTestLines(); + } + + tm.sweep = false; return blockval; } @@ -2379,7 +2451,7 @@ boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam) tm.bbox[BOXLEFT] = x - thiscam->radius; newsubsec = R_PointInSubsector(x, y); - tm.ceilingline = tm.blockingline = NULL; + tm.ceilingline = NULL; mapcampointer = thiscam; @@ -2753,22 +2825,6 @@ fixed_t P_GetThingStepUp(mobj_t *thing, fixed_t destX, fixed_t destY) return maxstep; } -static boolean P_UsingStepUp(mobj_t *thing) -{ - if (thing->flags & MF_NOCLIP) - { - return false; - } - - // orbits have no collision - if (thing->player && thing->player->loop.radius) - { - return false; - } - - return true; -} - static boolean increment_move ( mobj_t * thing, @@ -2821,7 +2877,29 @@ increment_move tryy = y; } - if (!P_CheckPosition(thing, tryx, tryy, result)) + if (P_UsingStepUp(thing)) + { + tm.maxstep = P_GetThingStepUp(thing, tryx, tryy); + } + + if (result) + { + tm.sweep = true; + } + + boolean move_ok = P_CheckPosition(thing, tryx, tryy, result); + + if (P_MobjWasRemoved(thing)) + { + return false; + } + + if (result) + { + result->line = P_SweepTestLines(thing->x, thing->y, x, y, thing->radius, &result->normal); + } + + if (!move_ok) { return false; // solid wall or thing } @@ -3466,30 +3544,27 @@ static void P_HitSlideLine(line_t *ld) // // HitBounceLine, for players // -static void P_PlayerHitBounceLine(line_t *ld) +static void P_PlayerHitBounceLine(line_t *ld, vector2_t* normal) { - INT32 side; - angle_t lineangle; fixed_t movelen; fixed_t x, y; - side = P_PointOnLineSide(slidemo->x, slidemo->y, ld); - lineangle = ld->angle - ANGLE_90; - - if (side == 1) - lineangle += ANGLE_180; - - lineangle >>= ANGLETOFINESHIFT; - movelen = P_AproxDistance(tmxmove, tmymove); if (slidemo->player && movelen < (15*mapobjectscale)) movelen = (15*mapobjectscale); - x = FixedMul(movelen, FINECOSINE(lineangle)); - y = FixedMul(movelen, FINESINE(lineangle)); + if (!ld) + { + angle_t th = R_PointToAngle2(0, 0, tmxmove, tmymove); + normal->x = -FCOS(th); + normal->y = -FSIN(th); + } - if (P_IsLineTripWire(ld)) + x = FixedMul(movelen, normal->x); + y = FixedMul(movelen, normal->y); + + if (ld && P_IsLineTripWire(ld)) { tmxmove = x * 4; tmymove = y * 4; @@ -3958,6 +4033,8 @@ papercollision: static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result) { + extern consvar_t cv_showgremlins; + fixed_t mmomx = 0, mmomy = 0; fixed_t oldmomx = mo->momx, oldmomy = mo->momy; @@ -3982,8 +4059,23 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result) slidemo = mo; bestslideline = result->line; - if (bestslideline == NULL) - return; + if (bestslideline == NULL && cv_showgremlins.value) + { + // debug + mobj_t*x = P_SpawnMobj(mo->x, mo->y, mo->z, MT_THOK); + x->frame = FF_FULLBRIGHT | FF_ADD; + x->renderflags = RF_ALWAYSONTOP; + x->color = SKINCOLOR_RED; + + CONS_Printf( + "GREMLIN: leveltime=%u x=%f y=%f z=%f angle=%f\n", + leveltime, + FixedToFloat(mo->x), + FixedToFloat(mo->y), + FixedToFloat(mo->z), + AngleToFloat(R_PointToAngle2(0, 0, oldmomx, oldmomy)) + ); + } if (mo->eflags & MFE_JUSTBOUNCEDWALL) // Stronger push-out { @@ -3996,7 +4088,7 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result) tmymove = FixedMul(mmomy, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3))); } - if (P_IsLineTripWire(bestslideline)) + if (bestslideline && P_IsLineTripWire(bestslideline)) { // TRIPWIRE CANNOT BE MADE NONBOUNCY K_ApplyTripWire(mo->player, TRIPSTATE_BLOCKED); @@ -4014,7 +4106,7 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result) K_SpawnBumpEffect(mo); } - P_PlayerHitBounceLine(bestslideline); + P_PlayerHitBounceLine(bestslideline, &result->normal); mo->eflags |= MFE_JUSTBOUNCEDWALL; mo->momx = tmxmove; @@ -4022,7 +4114,7 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result) mo->player->cmomx = tmxmove; mo->player->cmomy = tmymove; - if (!P_IsLineTripWire(bestslideline)) + if (!bestslideline || !P_IsLineTripWire(bestslideline)) { if (!P_TryMove(mo, mo->x + tmxmove, mo->y + tmymove, true, NULL)) { diff --git a/src/p_mobj.c b/src/p_mobj.c index f0baa74e1..447592037 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1671,7 +1671,7 @@ void P_XYMovement(mobj_t *mo) // blocked move moved = false; - if (LUA_HookMobjMoveBlocked(mo, tm.hitthing, tm.blockingline)) + if (LUA_HookMobjMoveBlocked(mo, tm.hitthing, result.line)) { if (P_MobjWasRemoved(mo)) return; @@ -1679,7 +1679,7 @@ void P_XYMovement(mobj_t *mo) else if (P_MobjWasRemoved(mo)) return; - P_PushSpecialLine(tm.blockingline, mo); + P_PushSpecialLine(result.line, mo); if (mo->flags & MF_MISSILE) { diff --git a/src/p_test.cpp b/src/p_test.cpp new file mode 100644 index 000000000..e362f33a5 --- /dev/null +++ b/src/p_test.cpp @@ -0,0 +1,78 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include +#include + +#include "math/fixed.hpp" +#include "p_sweep.hpp" + +#include "p_local.h" + +namespace +{ + +std::vector g_lines; + +}; + +void P_TestLine(line_t* ld) +{ + g_lines.emplace_back(ld); +} + +line_t* P_SweepTestLines(fixed_t ax, fixed_t ay, fixed_t bx, fixed_t by, fixed_t r, vector2_t* return_normal) +{ + using namespace srb2::math; + using namespace srb2::sweep; + + struct Collision + { + unit z; + vec2 normal; + line_t* ld; + + bool operator<(const Collision& b) const { return z < b.z; } + }; + + std::optional collision; + + LineSegment l{{ax, ay}, {bx, by}}; + AABBvsLine sweep{r, l}; + + for (line_t* ld : g_lines) + { + LineSegment ls{{ld->v1->x, ld->v1->y}, {ld->v2->x, ld->v2->y}}; + Result rs = sweep(ls); + if (rs.hit) + { + if (!collision || rs.hit->z < collision->z) + { + collision = {rs.hit->z, rs.hit->n, ld}; + } + } + } + + g_lines.clear(); + + if (!collision) + { + return nullptr; + } + + return_normal->x = Fixed {collision->normal.x}; + return_normal->y = Fixed {collision->normal.y}; + + return collision->ld; +} + +void P_ClearTestLines(void) +{ + g_lines.clear(); +} From 77af33d182f97df1d27e890ceb96e2b2c80cdd87 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Fri, 10 Nov 2023 02:58:05 -0700 Subject: [PATCH 18/25] More idiomatic unused pflags --- src/deh_tables.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index 3bf8c289b..72141e85a 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -6094,9 +6094,9 @@ const char *const PLAYERFLAG_LIST[] = { "RINGLOCK", // Prevent picking up rings while SPB is locked on "LITESTEER", // Shallow digital turn with DOWN - "UNUSED", - "UNUSED", - "UNUSED", + "\x01", // Free + "\x01", // Free + "\x01", // Free "DRIFTINPUT", // Drifting! "GETSPARKS", // Can get sparks From 90902997ba3a7026265217ff1d73441f22f35069 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Fri, 10 Nov 2023 16:33:50 -0700 Subject: [PATCH 19/25] Unset RF_DONTDRAW during controllable non-flashing states --- src/p_user.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/p_user.c b/src/p_user.c index 419e78ab6..ced510540 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4329,12 +4329,12 @@ void P_PlayerThink(player_t *player) } } + boolean deathcontrolled = (player->respawn.state != RESPAWNST_NONE && player->respawn.truedeath == true) + || (player->pflags & PF_NOCONTEST) || (player->karmadelay); + boolean powercontrolled = (player->hyudorotimer) || (player->growshrinktimer > 0); + // Flash player after being hit. - if (!(player->hyudorotimer // SRB2kart - fixes Hyudoro not flashing when it should. - || player->growshrinktimer > 0 // Grow doesn't flash either. - || (player->respawn.state != RESPAWNST_NONE && player->respawn.truedeath == true) // Respawn timer (for drop dash effect) - || (player->pflags & PF_NOCONTEST) // NO CONTEST explosion - || player->karmadelay)) + if (!deathcontrolled && !powercontrolled) { if (player->flashing > 1 && player->flashing < K_GetKartFlashing(player) && (leveltime & 1)) @@ -4342,6 +4342,10 @@ void P_PlayerThink(player_t *player) else player->mo->renderflags &= ~RF_DONTDRAW; } + else if (!deathcontrolled) + { + player->mo->renderflags &= ~RF_DONTDRAW; + } if (player->stairjank > 0) { From ff131335d4d1157a42dfd4c55d224196315dc396 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Fri, 10 Nov 2023 16:47:52 -0700 Subject: [PATCH 20/25] Don't force player sounds to full volume when exiting --- src/s_sound.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/s_sound.c b/src/s_sound.c index c0bec85fc..d5ce60424 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -426,6 +426,7 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume) for (i = 0; i <= r_splitscreen; i++) { player_t *player = &players[displayplayers[i]]; + boolean camaway = false; memset(&listener[i], 0, sizeof (listener[i])); listenmobj[i] = NULL; @@ -442,9 +443,11 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume) else { listenmobj[i] = player->mo; + if (player->exiting) + camaway = true; } - if (origin && origin == listenmobj[i] && !camera[i].freecam) + if (origin && origin == listenmobj[i] && !camera[i].freecam && !camaway) { itsUs = true; } From e3fe3ed54ab3dbfa04ad59428fd4ab3f0ed38ed4 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Fri, 10 Nov 2023 17:08:39 -0700 Subject: [PATCH 21/25] Handle sound falloff for exiting player sounds --- src/s_sound.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/s_sound.c b/src/s_sound.c index d5ce60424..f744b1598 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -766,6 +766,9 @@ void S_UpdateSounds(void) if (c->origin != listenmobj[i]) continue; + if (listenmobj[i]->player && listenmobj[i]->player->exiting) + continue; + itsUs = true; } From 4997c4c0ed2ac9ae5be455c3df53c0d7c8bfa129 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Fri, 10 Nov 2023 17:28:54 -0700 Subject: [PATCH 22/25] Add K_IsPlayingDisplayPlayer for HUD sounds --- src/k_kart.c | 6 ++++++ src/k_kart.h | 2 ++ src/k_roulette.c | 6 +++--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 99483e230..bf6f731d7 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -12630,4 +12630,10 @@ void K_SetTireGrease(player_t *player, tic_t tics) player->tiregrease = tics; } +// somewhat sensible check for HUD sounds in a post-bot-takeover world +boolean K_IsPlayingDisplayPlayer(player_t *player) +{ + return P_IsDisplayPlayer(player) && (!player->exiting); +} + //} diff --git a/src/k_kart.h b/src/k_kart.h index 7a4424f08..4ddd8b1c3 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -243,6 +243,8 @@ boolean K_isPlayerInSpecialState(player_t *p); void K_SetTireGrease(player_t *player, tic_t tics); +boolean K_IsPlayingDisplayPlayer(player_t *player); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_roulette.c b/src/k_roulette.c index 1b378aeae..d01678d9c 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1633,7 +1633,7 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd) //player->karthud[khud_itemblinkmode] = 1; //player->karthud[khud_rouletteoffset] = K_GetRouletteOffset(roulette, FRACUNIT); - if (P_IsDisplayPlayer(player)) + if (K_IsPlayingDisplayPlayer(player)) { S_StartSound(NULL, sfx_itrole); } @@ -1680,7 +1680,7 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd) player->karthud[khud_itemblinkmode] = 0; player->karthud[khud_rouletteoffset] = K_GetRouletteOffset(roulette, FRACUNIT); - if (P_IsDisplayPlayer(player)) + if (K_IsPlayingDisplayPlayer(player)) { if (roulette->ringbox) { @@ -1714,7 +1714,7 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd) // This makes the roulette produce the random noises. roulette->sound = (roulette->sound + 1) % 8; - if (P_IsDisplayPlayer(player)) + if (K_IsPlayingDisplayPlayer(player)) { if (roulette->ringbox) S_StartSound(NULL, sfx_s240); From ec4ea384c30ee4bfeb60100953f320d46864530b Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 4 Nov 2023 13:28:21 +0000 Subject: [PATCH 23/25] Obj_AudienceInit: Clean up handling - Don't require a mapthing to select a random Follower from the Audience list - Don't print an error message if the mapheader Audience list is empty. This is probably intentional. --- src/objects/audience.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/objects/audience.c b/src/objects/audience.c index f1c1a0131..b3d844184 100644 --- a/src/objects/audience.c +++ b/src/objects/audience.c @@ -29,6 +29,7 @@ Obj_AudienceInit mapthing_t *mthing, INT32 followerpick) { + const boolean ourchoiceofvisuals = (followerpick < 0 || followerpick > numfollowers); INT16 *reflist = NULL; INT16 tempreflist[MAXHEADERFOLLOWERS]; UINT8 numref = 0; @@ -36,9 +37,9 @@ Obj_AudienceInit audience_mainstate(mobj) = S_NULL; // Pick follower - if (mthing != NULL) + if (ourchoiceofvisuals == true) { - if (mthing->thing_stringargs[0] != NULL) + if (mthing != NULL && mthing->thing_stringargs[0] != NULL) { // From mapthing char *stringcopy = Z_StrDup(mthing->thing_stringargs[0]); @@ -56,11 +57,23 @@ Obj_AudienceInit *c = ' '; } - if ((tempreflist[numref++] = K_FollowerAvailable(tok)) == -1) + if ((tempreflist[numref] = K_FollowerAvailable(tok)) == -1) + { CONS_Alert(CONS_WARNING, "Mapthing %s: Follower \"%s\" is invalid!\n", sizeu1(mthing-mapthings), tok); + } + else + numref++; + tok = strtok(NULL, " ,"); } + if (!numref) + { + // This is the one thing a user should definitely be told about. + CONS_Alert(CONS_WARNING, "Mapthing %s: Follower audience has no valid followers to pick from!\n", sizeu1(mthing-mapthings)); + // DO NOT RETURN HERE + } + Z_Free(stringcopy); reflist = tempreflist; @@ -81,8 +94,8 @@ Obj_AudienceInit if (!numref || !reflist) { - // This is the one thing a user should definitely be told about. - CONS_Alert(CONS_WARNING, "Mapthing %s: Follower audience has no valid followers to pick from!\n", sizeu1(mthing-mapthings)); + // Clean up after ourselves. + P_RemoveMobj(mobj); return; } @@ -137,11 +150,11 @@ Obj_AudienceInit } // Handle colors - if (mthing != NULL) + if (ourchoiceofvisuals == true) { UINT16 colorpick = SKINCOLOR_NONE; - if (mthing->thing_stringargs[1] != NULL) + if (mthing != NULL && mthing->thing_stringargs[1] != NULL) { if (!stricmp("Random", mthing->thing_stringargs[1])) { From 9e49f761b0a6be78348cc89936a72439ca00d08b Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 4 Nov 2023 13:32:08 +0000 Subject: [PATCH 24/25] Prison Egg polish: Spawn three Followers on hit - Selected from the mapheader's Audience list - Since most Prison Break maps don't have Audience lists set, you'll be seeing a lot of Flicky/Motobuddy/Chao for now - Yes, these will disappear in deathpits like in Sky Chase - Intentionally did not do this for the CD Special Stage UFO --- src/k_objects.h | 2 +- src/objects/audience.c | 27 ++++++++++++++------ src/p_inter.c | 56 ++++++++++++++++++++++++++++++++++++++++++ src/p_mobj.c | 6 +++-- 4 files changed, 80 insertions(+), 11 deletions(-) diff --git a/src/k_objects.h b/src/k_objects.h index c88bf3320..bad973f62 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -136,7 +136,7 @@ void Obj_UpdateRingShooterFace(mobj_t *part); /* Follower Audience */ void Obj_AudienceInit(mobj_t * mobj, mapthing_t *mthing, INT32 followerpick); -void Obj_AudienceThink(mobj_t * mobj, boolean focusonplayer); +void Obj_AudienceThink(mobj_t * mobj, boolean focusonplayer, boolean checkdeathpit); /* Random Item Boxes */ void Obj_RandomItemVisuals(mobj_t *mobj); diff --git a/src/objects/audience.c b/src/objects/audience.c index b3d844184..b6d211132 100644 --- a/src/objects/audience.c +++ b/src/objects/audience.c @@ -202,8 +202,11 @@ Obj_AudienceInit void Obj_AudienceThink ( mobj_t * mobj, - boolean focusonplayer) + boolean focusonplayer, + boolean checkdeathpit) { + boolean landed = false; + if (audience_mainstate(mobj) == S_NULL) { // Uninitialised, don't do anything funny. @@ -328,15 +331,23 @@ Obj_AudienceThink } else if (mobj->flags2 & MF2_OBJECTFLIP) { - if (mobj->z + mobj->height >= mobj->ceilingz) - { - mobj->momz = -audience_bobamp(mobj); - P_SetMobjState(mobj, audience_mainstate(mobj)); - } + landed = (mobj->z + mobj->height >= mobj->ceilingz); } - else if (mobj->z <= mobj->floorz) + else { - mobj->momz = audience_bobamp(mobj); + landed = (mobj->z <= mobj->floorz); + } + + if (landed == true) + { + if (checkdeathpit && P_CheckDeathPitCollide(mobj)) + { + P_RemoveMobj(mobj); + return; + } + + mobj->momx = mobj->momy = 0; + mobj->momz = P_MobjFlip(mobj)*audience_bobamp(mobj); P_SetMobjState(mobj, audience_mainstate(mobj)); } } diff --git a/src/p_inter.c b/src/p_inter.c index 266cba2e2..f90848f66 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2103,6 +2103,62 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget cur = cur->hnext; } + // Spawn three Followers (if possible) + if (mapheaderinfo[gamemap-1]->numFollowers) + { + dir = FixedAngle(P_RandomKey(PR_RANDOMAUDIENCE, 360)*FRACUNIT); + + const fixed_t launchmomentum = 11 * mapobjectscale; + const fixed_t jaggedness = 4; + angle_t launchangle; + UINT8 i; + for (i = 0; i < 3; i++, dir += ANGLE_120) + { + cur = P_SpawnMobj( + target->x, target->y, + target->z + target->height/2, + MT_RANDOMAUDIENCE + ); + + // We check if you have some horrible Lua + if (P_MobjWasRemoved(cur)) + break; + + Obj_AudienceInit(cur, NULL, -1); + + // We check again if the list is invalid + if (P_MobjWasRemoved(cur)) + break; + + // For Followers, this makes them + // MF2_AMBUSH - try to always look at the nearest player + // MF2_DONTRESPAWN - Check for death pits + cur->flags2 |= (MF2_AMBUSH|MF2_DONTRESPAWN); + + cur->hitlag = target->hitlag; + + P_SetScale(cur, cur->destscale/TICRATE); + cur->scalespeed = cur->destscale/TICRATE; + cur->z -= cur->height/2; + + // flags are NOT from the target - just in case it's just been placed on the ceiling as a gimmick + cur->flags2 |= (source->flags2 & MF2_OBJECTFLIP); + cur->eflags |= (source->eflags & MFE_VERTICALFLIP); + + launchangle = FixedAngle(P_RandomRange(PR_RANDOMAUDIENCE, 60/jaggedness, 80/jaggedness) * jaggedness*FRACUNIT); + + cur->momz = P_MobjFlip(target) // THIS one uses target! + * P_ReturnThrustY(cur, launchangle, launchmomentum); + + cur->angle = dir; + + P_InstaThrust( + cur, cur->angle, + P_ReturnThrustX(cur, launchangle, launchmomentum) + ); + } + } + S_StartSound(target, sfx_mbs60); P_AddBrokenPrison(target, inflictor, source); diff --git a/src/p_mobj.c b/src/p_mobj.c index f58006a0d..839e37cfc 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7270,7 +7270,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) if (mobj->flags2 & MF2_STRONGBOX) { - Obj_AudienceThink(mobj, true); + Obj_AudienceThink(mobj, true, false); if (P_MobjWasRemoved(mobj)) return false; } @@ -10808,7 +10808,9 @@ void P_SceneryThinker(mobj_t *mobj) if (mobj->type == MT_RANDOMAUDIENCE) { - Obj_AudienceThink(mobj, !!(mobj->flags2 & MF2_AMBUSH)); + Obj_AudienceThink(mobj, !!(mobj->flags2 & MF2_AMBUSH), !!(mobj->flags2 & MF2_DONTRESPAWN)); + if (P_MobjWasRemoved(mobj)) + return; } } From 3805a4d5f0c4425f24c865105a2880cfbf994a7e Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 12 Nov 2023 23:51:44 +0000 Subject: [PATCH 25/25] Adjust Prison followers based on feedback... - Exist for 1.5 seconds before disappearing - Flickers away in the last half second - Twice as many spawned, at half the size - Different arc - Severely reduced gravity - Don't jump if they hypothetically touch the floor --- src/objects/audience.c | 6 ++++++ src/p_inter.c | 21 +++++++++++++-------- src/p_mobj.c | 4 ++++ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/objects/audience.c b/src/objects/audience.c index b6d211132..b5814ae16 100644 --- a/src/objects/audience.c +++ b/src/objects/audience.c @@ -207,6 +207,12 @@ Obj_AudienceThink { boolean landed = false; + if (mobj->fuse && mobj->fuse < (TICRATE/2)) + { + mobj->renderflags ^= RF_DONTDRAW; + return; // no jumping when you hit the floor, your gravity is weird + } + if (audience_mainstate(mobj) == S_NULL) { // Uninitialised, don't do anything funny. diff --git a/src/p_inter.c b/src/p_inter.c index f90848f66..f510cf2fa 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2108,11 +2108,11 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget { dir = FixedAngle(P_RandomKey(PR_RANDOMAUDIENCE, 360)*FRACUNIT); - const fixed_t launchmomentum = 11 * mapobjectscale; + const fixed_t launchmomentum = 7 * mapobjectscale; const fixed_t jaggedness = 4; angle_t launchangle; UINT8 i; - for (i = 0; i < 3; i++, dir += ANGLE_120) + for (i = 0; i < 6; i++, dir += ANG60) { cur = P_SpawnMobj( target->x, target->y, @@ -2130,13 +2130,9 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget if (P_MobjWasRemoved(cur)) break; - // For Followers, this makes them - // MF2_AMBUSH - try to always look at the nearest player - // MF2_DONTRESPAWN - Check for death pits - cur->flags2 |= (MF2_AMBUSH|MF2_DONTRESPAWN); - cur->hitlag = target->hitlag; + cur->destscale /= 2; P_SetScale(cur, cur->destscale/TICRATE); cur->scalespeed = cur->destscale/TICRATE; cur->z -= cur->height/2; @@ -2145,7 +2141,13 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget cur->flags2 |= (source->flags2 & MF2_OBJECTFLIP); cur->eflags |= (source->eflags & MFE_VERTICALFLIP); - launchangle = FixedAngle(P_RandomRange(PR_RANDOMAUDIENCE, 60/jaggedness, 80/jaggedness) * jaggedness*FRACUNIT); + launchangle = FixedAngle( + ( + ( + P_RandomRange(PR_RANDOMAUDIENCE, 12/jaggedness, 24/jaggedness) * jaggedness + ) + (i & 1)*16 + ) * FRACUNIT + ); cur->momz = P_MobjFlip(target) // THIS one uses target! * P_ReturnThrustY(cur, launchangle, launchmomentum); @@ -2156,6 +2158,9 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget cur, cur->angle, P_ReturnThrustX(cur, launchangle, launchmomentum) ); + + cur->fuse = (3*TICRATE)/2; + cur->flags |= MF_NOCLIPHEIGHT; } } diff --git a/src/p_mobj.c b/src/p_mobj.c index 839e37cfc..da1b58d8e 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1278,6 +1278,10 @@ fixed_t P_GetMobjGravity(mobj_t *mo) } break; } + case MT_RANDOMAUDIENCE: + if (mo->fuse) + gravityadd /= 10; + break; default: break; }