diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 898fcc789..8bd399a1a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -48,6 +48,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 p_floor.c p_inter.c p_lights.c + p_loop.c p_map.c p_maputl.c p_mobj.c diff --git a/src/d_player.h b/src/d_player.h index 12cab4a9a..298c65485 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -374,6 +374,16 @@ struct itemroulette_t boolean eggman; }; +// player_t struct for loop state +typedef struct { + fixed_t radius; + fixed_t revolution, min_revolution, max_revolution; + angle_t yaw; + vector3_t origin; + vector2_t shift; + boolean flip; +} sonicloopvars_t; + // ======================================================================== // PLAYER STRUCTURE // ======================================================================== @@ -668,6 +678,8 @@ struct player_t #ifdef HWRENDER fixed_t fovadd; // adjust FOV for hw rendering #endif + + sonicloopvars_t loop; }; #ifdef __cplusplus diff --git a/src/deh_tables.c b/src/deh_tables.c index 07c69f687..ab5e7959c 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -5664,6 +5664,9 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_SPECIAL_UFO", "MT_SPECIAL_UFO_PIECE", + + "MT_LOOPENDPOINT", + "MT_LOOPCENTERPOINT", }; const char *const MOBJFLAG_LIST[] = { diff --git a/src/info.c b/src/info.c index 4807869e6..d0509e731 100644 --- a/src/info.c +++ b/src/info.c @@ -29311,6 +29311,60 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_DONTENCOREMAP|MF_NOSQUISH, // flags S_NULL // raisestate }, + + { // MT_LOOPENDPOINT + 2020, // doomednum + S_INVISIBLE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + MAXRADIUS, // radius + 2*MAXRADIUS, // height + 0, // display offset + 100, // mass + 1, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + + { // MT_LOOPCENTERPOINT + 2021, // doomednum + S_INVISIBLE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 48*FRACUNIT, // radius + 32*FRACUNIT, // height + 0, // display offset + 100, // mass + 1, // damage + sfx_None, // activesound + MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, }; skincolor_t skincolors[MAXSKINCOLORS] = { diff --git a/src/info.h b/src/info.h index 1bac859b1..02e194cb6 100644 --- a/src/info.h +++ b/src/info.h @@ -6736,6 +6736,9 @@ typedef enum mobj_type MT_SPECIAL_UFO, MT_SPECIAL_UFO_PIECE, + MT_LOOPENDPOINT, + MT_LOOPCENTERPOINT, + MT_FIRSTFREESLOT, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, NUMMOBJTYPES diff --git a/src/k_objects.h b/src/k_objects.h index 05debb889..922764c94 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -2,6 +2,8 @@ #ifndef k_objects_H #define k_objects_H +#include "taglist.h" + #ifdef __cplusplus extern "C" { #endif @@ -92,6 +94,13 @@ boolean Obj_ItemSpotIsAvailable(const mobj_t *spot); void Obj_ItemSpotAssignMonitor(mobj_t *spot, mobj_t *monitor); void Obj_ItemSpotUpdate(mobj_t *spot); +/* Loops */ +mobj_t *Obj_FindLoopCenter(const mtag_t tag); +void Obj_InitLoopEndpoint(mobj_t *end, mobj_t *anchor); +void Obj_InitLoopCenter(mobj_t *center); +void Obj_LinkLoopAnchor(mobj_t *anchor, mobj_t *center, UINT8 type); +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..dd9ea7c3b --- /dev/null +++ b/src/objects/loops.c @@ -0,0 +1,281 @@ +// 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 void +crisscross +( mobj_t * anchor, + mobj_t ** target_p, + mobj_t ** other_p) +{ + P_SetTarget(target_p, anchor); + + if (!P_MobjWasRemoved(*other_p)) + { + P_SetTarget(&anchor_other(anchor), *other_p); + P_SetTarget(&anchor_other(*other_p), anchor); + } +} + +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); + } +} + +mobj_t * +Obj_FindLoopCenter (const mtag_t tag) +{ + INT32 i; + + TAG_ITER_THINGS(tag, i) + { + mapthing_t *mt = &mapthings[i]; + + if (mt->type == mobjinfo[MT_LOOPCENTERPOINT].doomednum) + { + return mt->mobj; + } + } + + return NULL; +} + +void +Obj_InitLoopEndpoint +( mobj_t * end, + mobj_t * anchor) +{ + P_SetTarget(&end_anchor(end), anchor); +} + +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_LinkLoopAnchor +( mobj_t * anchor, + mobj_t * center, + UINT8 type) +{ + P_SetTarget(&anchor_center(anchor), center); + + anchor_type(anchor) = type; + + if (!P_MobjWasRemoved(center)) + { + switch (type) + { + case TMLOOP_ALPHA: + crisscross(anchor, + ¢er_alpha(center), + ¢er_beta(center)); + break; + + case TMLOOP_BETA: + crisscross(anchor, + ¢er_beta(center), + ¢er_alpha(center)); + break; + } + } +} + +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 09f6349d6..3d69eec25 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_local.h b/src/p_local.h index bf847fbca..12362a548 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -193,6 +193,10 @@ boolean P_AutoPause(void); void P_ElementalFire(player_t *player, boolean cropcircle); void P_SpawnSkidDust(player_t *player, fixed_t radius, boolean sound); +void P_HaltPlayerOrbit(player_t *player); +void P_ExitPlayerOrbit(player_t *player); +boolean P_PlayerOrbit(player_t *player); + void P_MovePlayer(player_t *player); void P_PlayerThink(player_t *player); void P_PlayerAfterThink(player_t *player); diff --git a/src/p_loop.c b/src/p_loop.c new file mode 100644 index 000000000..e2d838f31 --- /dev/null +++ b/src/p_loop.c @@ -0,0 +1,180 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// 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 p_loop.c +/// \brief Sonic loop physics + +#include "doomdef.h" +#include "d_player.h" +#include "k_kart.h" +#include "p_local.h" +#include "p_setup.h" +#include "p_slopes.h" +#include "r_main.h" + +static inline angle_t +get_pitch (fixed_t revolution) +{ + return FixedAngle((revolution & FRACMASK) * 360); +} + +static inline fixed_t +get_shift_curve (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); +} + +void P_HaltPlayerOrbit(player_t *player) +{ + // see P_PlayerOrbit + player->mo->flags &= ~(MF_NOCLIPHEIGHT); + + player->loop.radius = 0; +} + +void P_ExitPlayerOrbit(player_t *player) +{ + sonicloopvars_t *s = &player->loop; + + angle_t pitch = get_pitch(s->revolution); + angle_t yaw = s->yaw; + + fixed_t co, si; + fixed_t speed; + + if (s->radius < 0) + { + pitch += ANGLE_180; + } + + co = FCOS(pitch); + si = FSIN(pitch); + + speed = FixedMul(co, player->speed); + + P_InstaThrust(player->mo, yaw, speed); + + player->mo->momz = FixedMul(si, player->speed); + + if (speed < 0) + { + yaw += ANGLE_180; + } + + // excludes only extremely vertical angles + if (abs(co) * 4 > abs(si)) + { + P_SetPlayerAngle(player, yaw); + } + + if (s->flip) + { + player->mo->eflags ^= MFE_VERTICALFLIP; + player->mo->flags2 ^= MF2_OBJECTFLIP; + + P_SetPitchRoll(player->mo, + pitch + ANGLE_180, s->yaw); + } + + // tiregrease gives less friction, extends momentum + player->tiregrease = TICRATE; + + P_HaltPlayerOrbit(player); +} + +boolean P_PlayerOrbit(player_t *player) +{ + sonicloopvars_t *s = &player->loop; + + angle_t pitch; + + fixed_t xy, z; + fixed_t xs, ys; + + fixed_t step, th, left; + + fixed_t grav; + + if (s->radius == 0) + { + return false; + } + + grav = abs(P_GetMobjGravity(player->mo)); + + // Lose speed on the way up. revolution = 0.5 always + // points straight up. + if (abs(s->revolution & FRACMASK) < FRACUNIT/2) + { + player->speed -= grav; + } + else + { + player->speed += 4 * grav; + } + + pitch = get_pitch(s->revolution); + + xy = FixedMul(abs(s->radius), FSIN(pitch)); + z = FixedMul(abs(s->radius), -(FCOS(pitch))); + + th = get_shift_curve(s); + + xs = FixedMul(s->shift.x, th); + ys = FixedMul(s->shift.y, th); + + xs += FixedMul(xy, FCOS(s->yaw)); + ys += FixedMul(xy, FSIN(s->yaw)); + + P_MoveOrigin(player->mo, + s->origin.x + xs, + s->origin.y + ys, + s->origin.z + z); + + // Match rollangle to revolution + P_SetPitchRoll(player->mo, + s->radius < 0 ? (ANGLE_180 + pitch) : pitch, + s->yaw); + + // circumfrence = (2r)PI + step = FixedDiv(player->speed, + FixedMul(s->radius, M_TAU_FIXED)); + + left = (s->max_revolution - s->revolution); + + if (abs(left) < abs(step)) + { + P_ExitPlayerOrbit(player); + + return false; + } + + // If player slows down by too much, throw them out of + // the loop in a tumble. + if (player->speed < player->mo->scale) + { + P_HaltPlayerOrbit(player); + K_StumblePlayer(player); + + return false; + } + + s->revolution += step; + + // We need to not clip the ground. It sucks but setting + // this flag is the only way to do that. + player->mo->flags |= (MF_NOCLIPHEIGHT); + + return true; +} diff --git a/src/p_map.c b/src/p_map.c index 1f7051c10..8c5751d2a 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -2674,6 +2674,22 @@ 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, @@ -2734,7 +2750,7 @@ increment_move // copy into the spechitint buffer from spechit spechitint_copyinto(); - if (!(thing->flags & MF_NOCLIP)) + if (P_UsingStepUp(thing)) { // All things are affected by their scale. fixed_t maxstep = P_GetThingStepUp(thing, tryx, tryy); diff --git a/src/p_mobj.c b/src/p_mobj.c index b9fa47a36..232812765 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -3979,7 +3979,8 @@ static void P_CheckFloatbobPlatforms(mobj_t *mobj) static void P_SquishThink(mobj_t *mobj) { if (!(mobj->flags & MF_NOSQUISH) && - !(mobj->eflags & MFE_SLOPELAUNCHED)) + !(mobj->eflags & MFE_SLOPELAUNCHED) && + !(mobj->player && mobj->player->loop.radius != 0)) { K_Squish(mobj); } @@ -4002,7 +4003,8 @@ static void P_PlayerMobjThinker(mobj_t *mobj) // Zoom tube if ((mobj->player->carry == CR_ZOOMTUBE && mobj->tracer && !P_MobjWasRemoved(mobj->tracer)) - || mobj->player->respawn.state == RESPAWNST_MOVE) + || mobj->player->respawn.state == RESPAWNST_MOVE + || mobj->player->loop.radius != 0) { P_HitSpecialLines(mobj, mobj->x, mobj->y, mobj->momx, mobj->momy); P_UnsetThingPosition(mobj); @@ -13325,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; } @@ -13545,6 +13552,11 @@ static void P_SpawnItemRow(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 numi angle_t angle = FixedAngle(fixedangle << FRACBITS); angle_t fineangle = (angle >> ANGLETOFINESHIFT) & FINEMASK; + boolean isloopend = (mthing->type == mobjinfo[MT_LOOPENDPOINT].doomednum); + mobj_t *loopanchor; + + boolean inclusive = isloopend; + for (r = 0; r < numitemtypes; r++) { dummything = *mthing; @@ -13563,6 +13575,21 @@ static void P_SpawnItemRow(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 numi } z = P_GetMobjSpawnHeight(itemtypes[0], x, y, z, 0, mthing->options & MTF_OBJECTFLIP, mthing->scale); + if (isloopend) + { + const fixed_t length = (numitems - 1) * horizontalspacing / 2; + + mobj_t *loopcenter = Obj_FindLoopCenter(Tag_FGet(&mthing->tags)); + + // Spawn the anchor at the middle point of the line + loopanchor = P_SpawnMobjFromMapThing(&dummything, + x + FixedMul(length, FINECOSINE(fineangle)), + y + FixedMul(length, FINESINE(fineangle)), + z, MT_LOOPCENTERPOINT); + + Obj_LinkLoopAnchor(loopanchor, loopcenter, mthing->args[0]); + } + for (r = 0; r < numitems; r++) { mobjtype_t itemtype = itemtypes[r % numitemtypes]; @@ -13570,15 +13597,24 @@ static void P_SpawnItemRow(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 numi continue; dummything.type = mobjinfo[itemtype].doomednum; + if (inclusive) + mobj = P_SpawnMobjFromMapThing(&dummything, x, y, z, itemtype); + x += FixedMul(horizontalspacing, FINECOSINE(fineangle)); y += FixedMul(horizontalspacing, FINESINE(fineangle)); z += (mthing->options & MTF_OBJECTFLIP) ? -verticalspacing : verticalspacing; - mobj = P_SpawnMobjFromMapThing(&dummything, x, y, z, itemtype); + if (!inclusive) + mobj = P_SpawnMobjFromMapThing(&dummything, x, y, z, itemtype); if (!mobj) continue; + if (isloopend) + { + Obj_InitLoopEndpoint(mobj, loopanchor); + } + mobj->spawnpoint = NULL; } } diff --git a/src/p_saveg.c b/src/p_saveg.c index 55fac75f5..fc9d7daf4 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -465,6 +465,19 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT32(save->p, players[i].itemRoulette.tics); WRITEUINT32(save->p, players[i].itemRoulette.elapsed); WRITEUINT8(save->p, players[i].itemRoulette.eggman); + + // sonicloopsvars_t + WRITEFIXED(save->p, players[i].loop.radius); + WRITEFIXED(save->p, players[i].loop.revolution); + WRITEFIXED(save->p, players[i].loop.min_revolution); + WRITEFIXED(save->p, players[i].loop.max_revolution); + WRITEANGLE(save->p, players[i].loop.yaw); + 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.shift.x); + WRITEFIXED(save->p, players[i].loop.shift.y); + WRITEUINT8(save->p, players[i].loop.flip); } } @@ -834,6 +847,19 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].itemRoulette.elapsed = (tic_t)READUINT32(save->p); players[i].itemRoulette.eggman = (boolean)READUINT8(save->p); + // sonicloopsvars_t + players[i].loop.radius = READFIXED(save->p); + players[i].loop.revolution = READFIXED(save->p); + players[i].loop.min_revolution = READFIXED(save->p); + players[i].loop.max_revolution = READFIXED(save->p); + players[i].loop.yaw = READANGLE(save->p); + 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.shift.x = READFIXED(save->p); + players[i].loop.shift.y = READFIXED(save->p); + players[i].loop.flip = READUINT8(save->p); + //players[i].viewheight = P_GetPlayerViewHeight(players[i]); // scale cannot be factored in at this point } } diff --git a/src/p_setup.c b/src/p_setup.c index 249354d70..4175ec384 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -677,11 +677,26 @@ void P_ReloadRings(void) } } +static int cmp_loopends(const void *a, const void *b) +{ + const mapthing_t + *mt1 = *(const mapthing_t*const*)a, + *mt2 = *(const mapthing_t*const*)b; + + // weighted sorting; tag takes precedence over type + return + intsign(Tag_FGet(&mt1->tags) - Tag_FGet(&mt2->tags)) * 2 + + intsign(mt1->args[0] - mt2->args[0]); +} + static void P_SpawnMapThings(boolean spawnemblems) { size_t i; mapthing_t *mt; + mapthing_t **loopends; + size_t num_loopends = 0; + // Spawn axis points first so they are at the front of the list for fast searching. for (i = 0, mt = mapthings; i < nummapthings; i++, mt++) { @@ -690,14 +705,22 @@ static void P_SpawnMapThings(boolean spawnemblems) case 1700: // MT_AXIS case 1701: // MT_AXISTRANSFER case 1702: // MT_AXISTRANSFERLINE + case 2021: // MT_LOOPCENTERPOINT mt->mobj = NULL; P_SpawnMapThing(mt); break; + case 2020: // MT_LOOPENDPOINT + num_loopends++; + break; default: break; } } + Z_Malloc(num_loopends * sizeof *loopends, PU_STATIC, + &loopends); + num_loopends = 0; + for (i = 0, mt = mapthings; i < nummapthings; i++, mt++) { switch (mt->type) @@ -705,6 +728,7 @@ static void P_SpawnMapThings(boolean spawnemblems) case 1700: // MT_AXIS case 1701: // MT_AXISTRANSFER case 1702: // MT_AXISTRANSFERLINE + case 2021: // MT_LOOPCENTERPOINT continue; // These were already spawned } @@ -716,6 +740,13 @@ static void P_SpawnMapThings(boolean spawnemblems) mt->mobj = NULL; + if (mt->type == mobjinfo[MT_LOOPENDPOINT].doomednum) + { + loopends[num_loopends] = mt; + num_loopends++; + continue; + } + if (mt->type >= 600 && mt->type <= 611) // item patterns P_SpawnItemPattern(mt); else if (mt->type == 1713) // hoops @@ -723,6 +754,25 @@ static void P_SpawnMapThings(boolean spawnemblems) else // Everything else P_SpawnMapThing(mt); } + + qsort(loopends, num_loopends, sizeof *loopends, + cmp_loopends); + + for (i = 1; i < num_loopends; ++i) + { + mapthing_t + *mt1 = loopends[i - 1], + *mt2 = loopends[i]; + + if (Tag_FGet(&mt1->tags) == Tag_FGet(&mt2->tags) && + mt1->args[0] == mt2->args[0]) + { + P_SpawnItemLine(mt1, mt2); + i++; + } + } + + Z_Free(loopends); } // Experimental groovy write function! @@ -3931,6 +3981,8 @@ static void P_AddBinaryMapTags(void) case 292: case 294: case 780: + case 2020: // MT_LOOPENDPOINT + case 2021: // MT_LOOPCENTERPOINT Tag_FSet(&mapthings[i].tags, mapthings[i].extrainfo); break; default: @@ -6736,6 +6788,17 @@ static void P_ConvertBinaryThingTypes(void) mapthings[i].args[2] |= TMICF_INVERTSIZE; } break; + case 2020: // MT_LOOPENDPOINT + { + mapthings[i].args[0] = + mapthings[i].options & MTF_AMBUSH ? + TMLOOP_BETA : TMLOOP_ALPHA; + break; + } + case 2021: // MT_LOOPCENTERPOINT + mapthings[i].args[0] = (mapthings[i].options & MTF_AMBUSH) == MTF_AMBUSH; + mapthings[i].args[1] = mapthings[i].angle; + break; case 2050: // MT_DUELBOMB mapthings[i].args[1] = !!(mapthings[i].options & MTF_AMBUSH); break; diff --git a/src/p_spec.h b/src/p_spec.h index 6ccb328cd..908046936 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -513,6 +513,12 @@ typedef enum TMBOT_FORCEDIR = 1<<2, } textmapbotcontroller_t; +typedef enum +{ + TMLOOP_ALPHA = 0, + TMLOOP_BETA = 1, +} textmaploopendpointtype_t; + // GETSECSPECIAL (specialval, section) // // Pulls out the special # from a particular section. diff --git a/src/p_user.c b/src/p_user.c index 223af8f47..7826d4071 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4227,6 +4227,11 @@ void P_PlayerThink(player_t *player) P_DoZoomTube(player); player->rmomx = player->rmomy = 0; } + else if (player->loop.radius != 0) + { + P_PlayerOrbit(player); + player->rmomx = player->rmomy = 0; + } else { // Move around.