diff --git a/src/d_player.h b/src/d_player.h index f4beaf95c..57b33bcf4 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -471,6 +471,7 @@ typedef struct { fixed_t revolution, min_revolution, max_revolution; angle_t yaw; vector3_t origin; + vector2_t origin_shift; vector2_t shift; boolean flip; } sonicloopvars_t; diff --git a/src/k_kart.c b/src/k_kart.c index 8ea9d92ab..b65553acc 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -7847,6 +7847,15 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->mo->sprzoff += player->cameraOffset; } + if (player->loop.radius) + { + // Offset sprite Z position so wheels touch top of + // hitbox when rotated 180 degrees. + // TODO: this should be generalized for pitch/roll + angle_t pitch = FixedAngle(player->loop.revolution * 360) / 2; + player->mo->sprzoff += FixedMul(player->mo->height, FSIN(pitch)); + } + K_UpdateOffroad(player); K_UpdateDraft(player); K_UpdateEngineSounds(player); // Thanks, VAda! @@ -10502,21 +10511,7 @@ static void K_KartSpindash(player_t *player) P_InstaThrust(player->mo, player->mo->angle, thrust); } - if (!player->tiregrease) - { - UINT8 i; - for (i = 0; i < 2; i++) - { - mobj_t *grease; - grease = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_TIREGREASE); - P_SetTarget(&grease->target, player->mo); - grease->angle = K_MomentumAngle(player->mo); - grease->extravalue1 = i; - K_ReduceVFX(grease, player); - } - } - - player->tiregrease = 2*TICRATE; + K_SetTireGrease(player, 2*TICRATE); player->spindash = 0; S_ReducedVFXSound(player->mo, sfx_s23c, player); @@ -12470,4 +12465,23 @@ boolean K_Cooperative(void) return false; } +void K_SetTireGrease(player_t *player, tic_t tics) +{ + if (!player->tiregrease) + { + UINT8 i; + for (i = 0; i < 2; i++) + { + mobj_t *grease; + grease = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_TIREGREASE); + P_SetTarget(&grease->target, player->mo); + grease->angle = K_MomentumAngle(player->mo); + grease->extravalue1 = i; + K_ReduceVFX(grease, player); + } + } + + player->tiregrease = tics; +} + //} diff --git a/src/k_kart.h b/src/k_kart.h index af773c953..7d0379760 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -235,6 +235,8 @@ boolean K_Cooperative(void); // lat: used for when the player is in some weird state where it wouldn't be wise for it to be overwritten by another object that does similarly wacky shit. boolean K_isPlayerInSpecialState(player_t *p); +void K_SetTireGrease(player_t *player, tic_t tics); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index db3656d9e..1e99c2f0f 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -12,7 +12,7 @@ target_sources(SRB2SDL2 PRIVATE ufo.c monitor.c item-spot.c - loops.c + loops.cpp drop-target.c ring-shooter.c audience.c diff --git a/src/objects/loops.c b/src/objects/loops.cpp similarity index 79% rename from src/objects/loops.c rename to src/objects/loops.cpp index 21d67c12f..c54103f9d 100644 --- a/src/objects/loops.c +++ b/src/objects/loops.cpp @@ -10,6 +10,8 @@ /// \file loop-endpoint.c /// \brief Sonic loops, start and end points +#include + #include "../doomdef.h" #include "../k_kart.h" #include "../taglist.h" @@ -149,6 +151,81 @@ get_binary_direction } } +static std::optional +intersect +( const mobj_t * anchor, + const mobj_t * toucher) +{ + struct Line + { + angle_t a; + vector2_t o; + + angle_t k = AbsAngle(a); + + Line(vector2_t o_, angle_t a_) : a(a_), o(o_) {} + + bool vertical() const { return k == ANGLE_90; } + + fixed_t m() const + { + // tangent table is offset 90 degrees + return FTAN(a - ANGLE_90); + } + + fixed_t b() const + { + return o.y - FixedMul(o.x, m()); + } + + fixed_t y(fixed_t x) const + { + return FixedMul(m(), x) + b(); + } + }; + + if (toucher->momx == 0 && toucher->momy == 0) + { + // undefined angle + return {}; + } + + Line a({toucher->x, toucher->y}, + R_PointToAngle2(0, 0, toucher->momx, toucher->momy)); + + Line b({anchor->x, anchor->y}, anchor->angle + ANGLE_90); + + if (a.k == b.k) + { + // parallel lines do not intersect + return {}; + } + + vector2_t v; + + auto v_intersect = [&v](Line &a, Line &b) + { + if (a.vertical()) + { + return false; + } + + v.x = b.o.x; + v.y = a.y(v.x); + + return true; + }; + + if (!v_intersect(a, b) && !v_intersect(b, a)) + { + // untested! + v.x = FixedDiv(a.b() - b.b(), b.m() - a.m()); + v.y = a.y(v.x); + } + + return v; +} + mobj_t * Obj_FindLoopCenter (const mtag_t tag) { @@ -226,8 +303,8 @@ Obj_LoopEndpointCollide fixed_t radius; /* predict movement for a smooth transition */ - const fixed_t px = toucher->x + toucher->momx; - const fixed_t py = toucher->y + toucher->momy; + const fixed_t px = toucher->x + 2 * toucher->momx; + const fixed_t py = toucher->y + 2 * toucher->momy; SINT8 flip; @@ -251,6 +328,11 @@ Obj_LoopEndpointCollide if (!P_MobjWasRemoved(anchor_other(anchor))) { set_shiftxy(player, anchor); + + vector2_t i = intersect(anchor, toucher) + .value_or(vector2_t {px, py}); + + s->origin_shift = {i.x - px, i.y - py}; } flip = get_binary_direction(pitch, toucher); @@ -276,4 +358,7 @@ Obj_LoopEndpointCollide /* cancel the effects of K_Squish */ toucher->spritexscale = FRACUNIT; toucher->spriteyscale = FRACUNIT; + + toucher->momx = 0; + toucher->momy = 0; } diff --git a/src/p_loop.c b/src/p_loop.c index e2d838f31..bc1454a5d 100644 --- a/src/p_loop.c +++ b/src/p_loop.c @@ -24,15 +24,11 @@ get_pitch (fixed_t revolution) } static inline fixed_t -get_shift_curve (const sonicloopvars_t *s) +normal_revolution (const sonicloopvars_t *s) { - const angle_t th = get_pitch(FixedDiv( - s->revolution - s->min_revolution, - s->max_revolution - s->min_revolution)); - - // XY shift is transformed on wave scale; less movement - // at start and end of rotation, more halfway. - return FSIN((th / 2) - ANGLE_90); + return FixedDiv( + s->revolution - s->min_revolution, + s->max_revolution - s->min_revolution); } void P_HaltPlayerOrbit(player_t *player) @@ -88,7 +84,7 @@ void P_ExitPlayerOrbit(player_t *player) } // tiregrease gives less friction, extends momentum - player->tiregrease = TICRATE; + K_SetTireGrease(player, 3*TICRATE); P_HaltPlayerOrbit(player); } @@ -98,8 +94,9 @@ boolean P_PlayerOrbit(player_t *player) sonicloopvars_t *s = &player->loop; angle_t pitch; + angle_t pitch_normal; - fixed_t xy, z; + fixed_t r, xy, z; fixed_t xs, ys; fixed_t step, th, left; @@ -125,15 +122,29 @@ boolean P_PlayerOrbit(player_t *player) } pitch = get_pitch(s->revolution); + pitch_normal = get_pitch(normal_revolution(s) / 2); - xy = FixedMul(abs(s->radius), FSIN(pitch)); - z = FixedMul(abs(s->radius), -(FCOS(pitch))); + r = abs(s->radius) - + FixedMul(player->mo->radius, abs(FSIN(pitch))); - th = get_shift_curve(s); + xy = FixedMul(r, FSIN(pitch)); + + z = FixedMul(abs(s->radius), -(FCOS(pitch))) - + FixedMul(player->mo->height, FSIN(pitch / 2)); + + // XY shift is transformed on wave scale; less movement + // at start and end of rotation, more halfway. + th = FSIN(pitch_normal - ANGLE_90); xs = FixedMul(s->shift.x, th); ys = FixedMul(s->shift.y, th); + // Interpolate 0-1 over entire rotation. + th = FSIN(pitch_normal / 2); + + xs += FixedMul(s->origin_shift.x, th); + ys += FixedMul(s->origin_shift.y, th); + xs += FixedMul(xy, FCOS(s->yaw)); ys += FixedMul(xy, FSIN(s->yaw)); @@ -153,13 +164,18 @@ boolean P_PlayerOrbit(player_t *player) left = (s->max_revolution - s->revolution); - if (abs(left) < abs(step)) + if (left == 0) { P_ExitPlayerOrbit(player); return false; } + if (abs(left) < abs(step)) + { + step = left; + } + // If player slows down by too much, throw them out of // the loop in a tumble. if (player->speed < player->mo->scale) diff --git a/src/p_map.c b/src/p_map.c index dd23deae7..9bd4a141a 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -323,25 +323,7 @@ P_DoSpringEx object->player->springstars = max(abs(vertispeed), horizspeed) / FRACUNIT / 2; object->player->springcolor = starcolor; - // Less friction when hitting springs - if (!object->player->tiregrease) - { - UINT8 i; - for (i = 0; i < 2; i++) - { - mobj_t *grease; - grease = P_SpawnMobj(object->x, object->y, object->z, MT_TIREGREASE); - P_SetTarget(&grease->target, object); - grease->angle = K_MomentumAngle(object); - grease->extravalue1 = i; - K_ReduceVFX(grease, object->player); - } - } - - if (object->player->tiregrease < greasetics) - { - object->player->tiregrease = greasetics; - } + K_SetTireGrease(object->player, max(object->player->tiregrease, greasetics)); } } diff --git a/src/p_saveg.c b/src/p_saveg.c index 685c7491a..dd8714cb1 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -674,6 +674,8 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEFIXED(save->p, players[i].loop.origin.x); WRITEFIXED(save->p, players[i].loop.origin.y); WRITEFIXED(save->p, players[i].loop.origin.z); + WRITEFIXED(save->p, players[i].loop.origin_shift.x); + WRITEFIXED(save->p, players[i].loop.origin_shift.y); WRITEFIXED(save->p, players[i].loop.shift.x); WRITEFIXED(save->p, players[i].loop.shift.y); WRITEUINT8(save->p, players[i].loop.flip); @@ -1196,6 +1198,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].loop.origin.x = READFIXED(save->p); players[i].loop.origin.y = READFIXED(save->p); players[i].loop.origin.z = READFIXED(save->p); + players[i].loop.origin_shift.x = READFIXED(save->p); + players[i].loop.origin_shift.y = READFIXED(save->p); players[i].loop.shift.x = READFIXED(save->p); players[i].loop.shift.y = READFIXED(save->p); players[i].loop.flip = READUINT8(save->p); diff --git a/src/p_user.c b/src/p_user.c index ed8f1a215..17f04c76f 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -3289,7 +3289,15 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall dist -= FixedMul(11*dist/16, player->karthud[khud_boostcam]); } - speed = P_AproxDistance(P_AproxDistance(mo->momx, mo->momy), mo->momz / 16); + if (player->loop.radius) + { + speed = player->speed; + } + else + { + speed = P_AproxDistance(P_AproxDistance(mo->momx, mo->momy), mo->momz / 16); + } + lag = FRACUNIT - ((FixedDiv(speed, speedthreshold) - FRACUNIT) * 2); if (lag > FRACUNIT) @@ -4265,7 +4273,8 @@ void P_PlayerThink(player_t *player) else if (player->loop.radius != 0) { P_PlayerOrbit(player); - player->rmomx = player->rmomy = 0; + player->rmomx = player->mo->momx; + player->rmomy = player->mo->momy; } else { diff --git a/src/tables.h b/src/tables.h index 1a1da82d3..61812cb2e 100644 --- a/src/tables.h +++ b/src/tables.h @@ -139,6 +139,7 @@ void FM_Rotate(matrix_t *dest, angle_t angle, fixed_t x, fixed_t y, fixed_t z); // FSIN(ANGLE_90) = FRACUNIT #define FSIN(n) FINESINE(ANGLETOFINE(n)) #define FCOS(n) FINECOSINE(ANGLETOFINE(n)) +#define FTAN(n) FINETANGENT(((n) >> ANGLETOFINESHIFT) & ((FINEANGLES / 2) - 1)) #ifdef __cplusplus } // extern "C"