Merge branch 'loops' into 'master'

Sonic Loops

See merge request KartKrew/Kart!991
This commit is contained in:
Oni 2023-03-01 18:28:56 +00:00
commit 07f2b5cc5d
17 changed files with 708 additions and 4 deletions

View file

@ -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

View file

@ -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

View file

@ -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[] = {

View file

@ -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] = {

View file

@ -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

View file

@ -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

View file

@ -12,4 +12,5 @@ target_sources(SRB2SDL2 PRIVATE
ufo.c
monitor.c
item-spot.c
loops.c
)

281
src/objects/loops.c Normal file
View file

@ -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,
&center_alpha(center),
&center_beta(center));
break;
case TMLOOP_BETA:
crisscross(anchor,
&center_beta(center),
&center_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;
}

View file

@ -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;

View file

@ -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);

180
src/p_loop.c Normal file
View file

@ -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;
}

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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
}
}

View file

@ -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;

View file

@ -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.

View file

@ -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.