diff --git a/src/k_objects.h b/src/k_objects.h index 05debb889..830c9e769 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -92,6 +92,10 @@ boolean Obj_ItemSpotIsAvailable(const mobj_t *spot); void Obj_ItemSpotAssignMonitor(mobj_t *spot, mobj_t *monitor); void Obj_ItemSpotUpdate(mobj_t *spot); +/* Loops */ +void Obj_InitLoopCenter(mobj_t *center); +void Obj_LoopEndpointCollide(mobj_t *special, mobj_t *toucher); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 440f700de..ca8680205 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -12,4 +12,5 @@ target_sources(SRB2SDL2 PRIVATE ufo.c monitor.c item-spot.c + loops.c ) diff --git a/src/objects/loops.c b/src/objects/loops.c new file mode 100644 index 000000000..a8838e3df --- /dev/null +++ b/src/objects/loops.c @@ -0,0 +1,211 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James R. +// Copyright (C) 2023 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file loop-endpoint.c +/// \brief Sonic loops, start and end points + +#include "../doomdef.h" +#include "../k_kart.h" +#include "../taglist.h" +#include "../p_local.h" +#include "../p_setup.h" +#include "../p_spec.h" +#include "../r_main.h" +#include "../k_objects.h" + +#define end_anchor(o) ((o)->target) + +#define center_max_revolution(o) ((o)->threshold) +#define center_alpha(o) ((o)->target) +#define center_beta(o) ((o)->tracer) + +static inline boolean +center_has_flip (const mobj_t *center) +{ + return (center->flags2 & MF2_AMBUSH) == MF2_AMBUSH; +} + +static inline void +center_set_flip +( mobj_t * center, + boolean mode) +{ + center->flags2 = (center->flags2 & ~(MF2_AMBUSH)) | + ((mode != false) * MF2_AMBUSH); +} + +#define anchor_center(o) ((o)->target) +#define anchor_other(o) ((o)->tracer) +#define anchor_type(o) ((o)->reactiontime) + +static void +set_shiftxy +( player_t * player, + const mobj_t * a) +{ + const mobj_t *b = anchor_other(a); + + const fixed_t dx = (b->x - a->x); + const fixed_t dy = (b->y - a->y); + + const angle_t th = + (R_PointToAngle2(0, 0, dx, dy) - a->angle); + + const fixed_t adj = FixedMul( + abs(FCOS(AbsAngle(th - ANGLE_90))), + FixedHypot(dx, dy)) / 2; + + vector2_t *xy = &player->loop.shift; + + xy->x = FixedMul(FSIN(a->angle), adj); + xy->y = FixedMul(FCOS(a->angle), adj); +} + +static void +measure_clock +( const mobj_t * center, + const mobj_t * anchor, + angle_t * pitch, + fixed_t * radius) +{ + const fixed_t dx = (anchor->x - center->x); + const fixed_t dy = (anchor->y - center->y); + const fixed_t dz = (anchor->z - center->z); + + /* Translate the anchor point to be along a center line. + This makes the horizontal position one dimensional + relative to the center point. */ + const fixed_t xy = ( + FixedMul(dx, FCOS(anchor->angle)) + + FixedMul(dy, FSIN(anchor->angle))); + + /* The 3d position of the anchor point is then reduced to + two axes and can be measured as an angle. */ + *pitch = R_PointToAngle2(0, 0, xy, dz) + ANGLE_90; + *radius = FixedHypot(xy, dz); +} + +static boolean +moving_toward_gate +( const player_t * player, + const mobj_t * anchor, + angle_t pitch) +{ + const fixed_t + x = player->mo->momx, + y = player->mo->momy, + z = player->mo->momz, + + zx = FixedMul(FCOS(anchor->angle), z), + zy = FixedMul(FSIN(anchor->angle), z), + + co = abs(FCOS(pitch)), + si = abs(FSIN(pitch)), + + dx = FixedMul(co, x) + FixedMul(si, zx), + dy = FixedMul(co, y) + FixedMul(si, zy); + + return AngleDelta(anchor->angle, + R_PointToAngle2(0, 0, dx, dy)) < ANG60; +} + +static SINT8 +get_binary_direction +( angle_t pitch, + mobj_t * toucher) +{ + const fixed_t si = FSIN(pitch); + + if (abs(si) < abs(FCOS(pitch))) + { + // pitch = 0 points downward so offset 90 degrees + // clockwise so 180 occurs at horizon + return ((pitch + ANGLE_90) < ANGLE_180) ? 1 : -(1); + } + else + { + return intsign(si) * P_MobjFlip(toucher); + } +} + +void +Obj_InitLoopCenter (mobj_t *center) +{ + const mapthing_t *mt = center->spawnpoint; + + center_max_revolution(center) = mt->args[1] * FRACUNIT / 360; + center_set_flip(center, mt->args[0]); +} + +void +Obj_LoopEndpointCollide +( mobj_t * end, + mobj_t * toucher) +{ + player_t *player = toucher->player; + sonicloopvars_t *s = &player->loop; + + mobj_t *anchor = end_anchor(end); + mobj_t *center = anchor ? anchor_center(anchor) : NULL; + + angle_t pitch; + 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; + + SINT8 flip; + + if (P_MobjWasRemoved(center)) + { + return; + } + + if (player->loop.radius != 0) + { + return; + } + + measure_clock(center, anchor, &pitch, &radius); + + if (!moving_toward_gate(player, anchor, pitch)) + { + return; + } + + if (!P_MobjWasRemoved(anchor_other(anchor))) + { + set_shiftxy(player, anchor); + } + + flip = get_binary_direction(pitch, toucher); + + s->yaw = anchor->angle; + + s->origin.x = center->x - (anchor->x - px); + s->origin.y = center->y - (anchor->y - py); + s->origin.z = center->z; + + s->radius = radius * flip; + s->revolution = AngleFixed(pitch) / 360; + + s->min_revolution = s->revolution; + s->max_revolution = s->revolution + + center_max_revolution(center) * flip; + + s->flip = center_has_flip(center); + + player->speed = + 3 * (player->speed + toucher->momz) / 2; + + /* cancel the effects of K_Squish */ + toucher->spritexscale = FRACUNIT; + toucher->spriteyscale = FRACUNIT; +} diff --git a/src/p_inter.c b/src/p_inter.c index 6e053a405..73b018e92 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -601,6 +601,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } return; + case MT_LOOPENDPOINT: + Obj_LoopEndpointCollide(special, toucher); + return; + default: // SOC or script pickup P_SetTarget(&special->target, toucher); break; diff --git a/src/p_mobj.c b/src/p_mobj.c index 1fb36e854..2d128f0cd 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -13327,6 +13327,11 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean mobj->reactiontime++; break; } + case MT_LOOPCENTERPOINT: + { + Obj_InitLoopCenter(mobj); + break; + } default: break; }