diff --git a/src/deh_tables.c b/src/deh_tables.c index f4112f614..0749e1665 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4669,6 +4669,9 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_CHECKPOINT_SPARK9", "S_CHECKPOINT_SPARK10", "S_CHECKPOINT_SPARK11", + + "S_RIDEROID", + "S_RIDEROID_ICON", }; // RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1", @@ -5821,6 +5824,9 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_CHECKPOINT_END", "MT_SCRIPT_THING", + + "MT_RIDEROID", + "MT_RIDEROIDNODE", }; const char *const MOBJFLAG_LIST[] = { diff --git a/src/info.c b/src/info.c index cd5337c81..291169514 100644 --- a/src/info.c +++ b/src/info.c @@ -30439,7 +30439,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 0, // mass 0, // damage sfx_None, // activesound - MF_NOGRAVITY, // flags + MF_NOGRAVITY|MF_NOCLIP, // flags S_NULL // raisestate }, diff --git a/src/k_objects.h b/src/k_objects.h index 7050ddff4..83c704b1f 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -225,6 +225,13 @@ boolean Obj_GetCheckpointRespawnPosition(const mobj_t *checkpoint, vector3_t *re angle_t Obj_GetCheckpointRespawnAngle(const mobj_t *checkpoint); void Obj_ActivateCheckpointInstantly(mobj_t* mobj); +/* Rideroid / Rideroid Node */ +void Obj_RideroidThink(mobj_t *mo); + +void Obj_RideroidNodeSpawn(mobj_t *mo); +void Obj_RideroidNodeThink(mobj_t *mo); + + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index c3b01f4a0..366a0a0f7 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -29,4 +29,5 @@ target_sources(SRB2SDL2 PRIVATE sneaker-panel.c emerald.c checkpoint.cpp + rideroid.c ) diff --git a/src/objects/rideroid.c b/src/objects/rideroid.c new file mode 100644 index 000000000..405630f83 --- /dev/null +++ b/src/objects/rideroid.c @@ -0,0 +1,498 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2022 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2022 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 rideroid.c +/// \brief Rideroid / Rideroid Node object code. This also has the player behaviour code to be used in k_kart. + +#include "../doomdef.h" +#include "../doomstat.h" +#include "../info.h" +#include "../k_kart.h" +#include "../k_objects.h" +#include "../m_random.h" +#include "../p_local.h" +#include "../r_main.h" +#include "../s_sound.h" +#include "../g_game.h" +#include "../z_zone.h" +#include "../k_waypoint.h" +#include "../k_respawn.h" +#include "../k_collide.h" + +#define NODERADIUS 260 +#define NODEPULLOK 16 +#define NODEROTSPEED ANG1 + +#define RIDEROIDSPEED 80 +#define RIDEROIDMAXADD 8 + + +// static functions that only really get used here... +static void plr_undoRespawn(player_t *p) +{ + p->respawn.state = 0; + p->respawn.timer = 0; +} + +static void plr_resetRideroidVars(player_t *p) +{ + p->rdnodepull = false; + p->rideroid = false; + p->rideroidangle = 0; + p->rideroidspeed = 0; + p->rideroidrollangle = 0; + + p->rdaddmomx = 0; + p->rdaddmomy = 0; + p->rdaddmomz = 0; +} + +// kills the rideroid and removes it from the map. +static void Obj_killRideroid(mobj_t *mo) +{ + P_RemoveMobj(mo); +} + +// this assumes mo->target and mo->target->player is valid. +// if it's not, uuuh well too bad. +static void Obj_getPlayerOffRideroid(mobj_t *mo) +{ + mobj_t *pmo = mo->target; + player_t *p = pmo->player; + + pmo->flags &= ~MF_NOGRAVITY; + plr_resetRideroidVars(p); + + if (!P_MobjWasRemoved(mo)) + mo->fuse = TICRATE/2; +} + +// this assumes mo->target and mo->target->player is valid. +// if it's not, uuuh well too bad. +static void Obj_explodeRideroid(mobj_t *mo) +{ + mobj_t *pmo = mo->target; + + Obj_getPlayerOffRideroid(mo); + K_SpawnMineExplosion(pmo, pmo->color, 3); + S_StartSound(pmo, sfx_s3k4e); + Obj_killRideroid(mo); + + // @TODO: quake. + +} + +// used to create a smooth trail. +static fixed_t Obj_rideroidLerp(INT32 start, INT32 finish, INT32 percent) +{ + return start + FixedMul(finish-start, FRACUNIT-percent); +} + +static void Obj_rideroidTrail(mobj_t *mo) +{ + mobj_t *pmo = mo->target; + player_t *p = pmo->player; // used to make some graphics local to save on framerate + + UINT8 i, j; + + angle_t h_an = mo->angle + ANG1*90; + // from here, we will use the following: + // extravalue1: prev x + // extravalue2: prev y + // cusval: prev z + // cvmem: prev roll angle + + mo->color = pmo->color; + mo->colorized = pmo->colorized; + + for (j = 0; j < 9; j++) + { + for (i = 0; i < 2; i++) + { + INT32 percent = FRACUNIT * (10-j)/10; + angle_t roll = (angle_t)Obj_rideroidLerp((angle_t)mo->cvmem, mo->rollangle, percent); + fixed_t x = (fixed_t)Obj_rideroidLerp((fixed_t)mo->extravalue1, mo->x, percent); + fixed_t y = (fixed_t)Obj_rideroidLerp((fixed_t)mo->extravalue2, mo->y, percent); + fixed_t z = (fixed_t)Obj_rideroidLerp((fixed_t)mo->cusval, mo->z, percent); + + angle_t v_an = i ? (roll+ANG1*90) : (roll-ANG1*90); + + fixed_t pos = FixedMul(mo->scale, FINESINE(v_an>>ANGLETOFINESHIFT)*60); + fixed_t tx = x+FixedMul(FINECOSINE(h_an>>ANGLETOFINESHIFT), pos); + fixed_t ty = y+FixedMul(FINESINE(h_an>>ANGLETOFINESHIFT), pos); + fixed_t tz = z+FixedMul(FINECOSINE(v_an>>ANGLETOFINESHIFT)*60, mo->scale); + + mobj_t *t = P_SpawnMobj(tx, ty, tz, MT_THOK); + t->color = SKINCOLOR_TEAL; + t->frame = FF_FULLBRIGHT|FF_TRANS50; + // 120 is no magic number, the base scale speed is mapobjectscale/12 + P_SetScale(t, max(1, mapobjectscale*5/6 - ((10-j)*mapobjectscale/120))); + t->destscale = 1; + + if (p != &players[consoleplayer] && j) + t->renderflags |= RF_DONTDRAW; + + } + } + + mo->extravalue1 = (INT32)mo->x; + mo->extravalue2 = (INT32)mo->y; + mo->cusval = (INT32)mo->z; + mo->cvmem = (INT32)mo->rollangle; +} + + +static void Obj_updateRideroidPos(mobj_t *mo) +{ + mobj_t *pmo = mo->target; + + fixed_t x = pmo->x + 2*FINECOSINE(pmo->angle>>ANGLETOFINESHIFT); + fixed_t y = pmo->y + 2*FINESINE(pmo->angle>>ANGLETOFINESHIFT); + + P_MoveOrigin(mo, x, y, pmo->z - 10*mapobjectscale); + Obj_rideroidTrail(mo); +} + +// handles the rideroid and the player attached to it. +void Obj_RideroidThink(mobj_t *mo) +{ + player_t *p; + mobj_t *pmo = mo->target; + + fixed_t basemomx; + fixed_t basemomy; + fixed_t xthreshold; + fixed_t ythreshold; + + + // speed values... + fixed_t maxspd = RIDEROIDSPEED*mapobjectscale; + + if (!pmo || P_MobjWasRemoved(pmo)) + { + Obj_killRideroid(mo); + return; + } + + // if we're here, our player should still exist which is kinda crazy! + p = pmo->player; + + // pulling towards the node, AKA towards where the rideroid is, which just so happens to be us right now. + if (p->rdnodepull) + { + pmo->momx = (mo->x - pmo->x)/6; + pmo->momy = (mo->y - pmo->y)/6; + pmo->momz = (mo->z - pmo->z)/6; + + //CONS_Printf("%d\n", R_PointToDist2(mo->x, mo->y, pmo->x, pmo->y)/FRACUNIT); + + if (R_PointToDist2(mo->x, mo->y, pmo->x, pmo->y) < NODEPULLOK*mapobjectscale) + { + p->rideroid = true; + p->rdnodepull = false; + + //S_StartSound(pmo, sfx_ridr2); + } + + return; + } + + // if we're here, we made it to the rideroid and we can use it, or something like that! + + // calculate the maximum speed we can move at. + // the values are a little arbitrary but they work for how little use these have. + + if (p->ringboost) + maxspd *= 12/10; // Ring Boost: 120% max speed. + + if (p->draftpower) + { + UINT8 draftperc = (p->draftpower*100 / FRACUNIT); // 0-100% + maxspd += (draftperc/5) / 100; + } + + // increase speed as we go unless we're turning harshly. + if (p->rideroidspeed*mapobjectscale < maxspd) + { + if (abs(p->cmd.turning < 400)) + p->rideroidspeed += (p->ringboost ? 2 : 1); // acceleration is also higher with a ring boost. + } + else + p->rideroidspeed -= 1; + + + // sounds + + mo->movecount++; // we use this as a timer for sounds and whatnot. + + //if (mo->movecount == 1 || !(mo->movecount%TICRATE)) + //S_StartSound(mo, sfx_ridr3); + + + // aaaaand the actual gameplay and shit... wooooo + pmo->angle = mo->angle; + pmo->flags |= MF_NOGRAVITY; + + // do not let the player touch the ground + // @TODO: check all 4 corners of the player and use P_GetZAt to account for slopes if pmo->standslope isn't NULL. + // right now it's not important as LV doesn't mix rdr and slopes but if somehow i manage to pull through w this shit it'll need to be done + if (pmo->eflags & MFE_VERTICALFLIP) + { + fixed_t minz = pmo->ceilingz - 2*mapobjectscale; + if (pmo->z > minz) + pmo->z = minz; + } + else + { + fixed_t minz = pmo->floorz + 2*mapobjectscale; + if (pmo->z < minz) + pmo->z = minz; + } + + + // if we hit a wall or get hit, get off of the rideroid. + if (pmo->eflags & MFE_JUSTBOUNCEDWALL || P_PlayerInPain(p)) + { + Obj_explodeRideroid(mo); + return; + } + + // now actual movement: + + // first, do the movement for this frame + P_InstaThrust(pmo, mo->angle, p->rideroidspeed*mapobjectscale); + pmo->momx += p->rdaddmomx; + pmo->momy += p->rdaddmomy; + pmo->momz += p->rdaddmomz; + pmo->angle = mo->angle; + p->drawangle = mo->angle; + P_SetPlayerAngle(pmo->player, mo->angle); + pmo->rollangle = p->rideroidrollangle; + mo->rollangle = p->rideroidrollangle; + pmo->pitch = 0; + + // update the rideroid object (me) to be below the target player + Obj_updateRideroidPos(mo); + + + // now compute all the shit for the *next* frame! + basemomx = p->rideroidspeed*FINECOSINE(mo->angle >> ANGLETOFINESHIFT); + basemomy = p->rideroidspeed*FINESINE(mo->angle >> ANGLETOFINESHIFT); + + // turning left/right + if (p->cmd.turning) + { + fixed_t savemomx = pmo->momx; + fixed_t savemomy = pmo->momy; + UINT8 dir = 0; + + if (p->cmd.turning < -400) + { + P_Thrust(pmo, mo->angle - 90*ANG1, 2*mapobjectscale); + p->rideroidrollangle -= ANG1*3; + + if (p->rideroidrollangle < -ANG1*25) + p->rideroidrollangle = -ANG1*25; + + dir = 1; + + } + else if (p->cmd.turning > 400) + { + P_Thrust(pmo, mo->angle + 90*ANG1, 2*mapobjectscale); + p->rideroidrollangle += ANG1*3; + + if (p->rideroidrollangle > ANG1*25) + p->rideroidrollangle = ANG1*25; + + dir = -1; + } + + if (dir != 0 && leveltime & 1 && p->rideroidspeed > RIDEROIDSPEED/2) + { + p->rideroidspeed -= 1; + } + + // save the added momentum + p->rdaddmomx = pmo->momx - basemomx; + p->rdaddmomy = pmo->momy - basemomy; + + //CONS_Printf("CURR: %d, %d\n", pmo->momx/mapobjectscale, pmo->momy/mapobjectscale); + //CONS_Printf("BASE: %d, %d\n", basemomx/mapobjectscale, basemomy/mapobjectscale); + //CONS_Printf("ADD: %d, %d\n", p->rdaddmomx/mapobjectscale, p->rdaddmomy/mapobjectscale); + + // find out how much addmomx and addmomy we can actually get. + // we do this by misusing P_Thrust to calc our values then immediately cancelling it. + basemomx = pmo->momx; + basemomy = pmo->momy; + P_Thrust(pmo, mo->angle - ANG1*90, RIDEROIDMAXADD*6*mapobjectscale); + xthreshold = pmo->momx - basemomx; + ythreshold = pmo->momy - basemomy; + + // clamp the momentums using the calculated thresholds. + p->rdaddmomx = max(p->rdaddmomx, -abs(xthreshold)); + p->rdaddmomy = max(p->rdaddmomy, -abs(ythreshold)); + p->rdaddmomx = min(p->rdaddmomx, abs(xthreshold)); + p->rdaddmomy = min(p->rdaddmomy, abs(ythreshold)); + + // now cancel it. + pmo->momx = savemomx; + pmo->momy = savemomy; + //CONS_Printf("NEWCURR: %d, %d\n", pmo->momx/mapobjectscale, pmo->momy/mapobjectscale); + + } + else // not turning + { + // for some reason doing *= 9/10 causes it to get set to 0 instantly? so it's done like this. + p->rdaddmomx = (p->rdaddmomx*9)/10; + p->rdaddmomy = (p->rdaddmomy*9)/10; + p->rideroidrollangle /= 2; + } + + // and now, going up/down + + if (p->cmd.throwdir > 0) + { + // if we were going the opposite direction, this helps us change our height very easily. + if (p->rdaddmomz < 0) + p->rdaddmomz /= 2; + + p->rdaddmomz = min(RIDEROIDMAXADD*mapobjectscale/7, p->rdaddmomz + mapobjectscale/16); + + if (p->rideroidspeed > RIDEROIDSPEED/2 + && abs(p->cmd.turning) > 400 + && leveltime & 1) + p->rideroidspeed -= 1; + + } + else if (p->cmd.throwdir < 0) + { + // if we were going the opposite direction, this helps us change our height very easily. + if (p->rdaddmomz > 0) + p->rdaddmomz /= 2; + + p->rdaddmomz = max(-RIDEROIDMAXADD*mapobjectscale/7, p->rdaddmomz - mapobjectscale/16); + + if (p->rideroidspeed > RIDEROIDSPEED/2 + && abs(p->cmd.turning) > 400 + && leveltime & 1) + p->rideroidspeed -= 1; + } + else + p->rdaddmomz = (p->rdaddmomz*6)/10; + +} + +// transposed lua code. +// the lua used to continuously P_SpawnMobj the letters which was fine for the intended use case in the original LV iteration. +// however the LV remake spams a lot of these rideroid nodes close to each other which created a huge overhead whether or not they were being displayed. +// so now it's more optimized and only spawns things once. + +void Obj_RideroidNodeSpawn(mobj_t *mo) +{ + + fixed_t radius = NODERADIUS*mapobjectscale; // radius for the text to rotate at. + mobj_t *ptr = mo; + UINT8 i; + UINT8 j; + + + // make it bigger. + P_SetScale(mo, mo->scale*3); + + // spawn the letter things. + for (i = 0; i < 2; i++) + { + + angle_t ang = mo->angle + (i)*180*ANG1; + fixed_t zpos = mo->z + 64*mapobjectscale + mapobjectscale*96*i; + + for (j = 0; j < 7; j++) + { + fixed_t xpos = mo->x + FixedMul(radius, FINECOSINE(ang>>ANGLETOFINESHIFT)); + fixed_t ypos = mo->y + FixedMul(radius, FINESINE(ang>>ANGLETOFINESHIFT)); + + mobj_t *let = P_SpawnMobj(xpos, ypos, zpos, MT_THOK); + let->sprite = SPR_RDRL; + let->frame = j|FF_FULLBRIGHT|FF_PAPERSPRITE; + let->fuse = -1; + let->tics = -1; + let->angle = ang + ANG1*90; + let->scale = 2*mapobjectscale; + + // set letter in previous thing's hnext, this will let us loop em easily in the looping thinker. + P_SetTarget(&ptr->hnext, let); + + // set the ptr to the last letter spawned. + ptr = let; + + ang += ANG1*8; + } + } +} + +void Obj_RideroidNodeThink(mobj_t *mo) +{ + fixed_t radius = NODERADIUS*mapobjectscale; // radius for the text to rotate at. + mobj_t *ptr = mo->hnext; + mobj_t *pmo; + UINT8 i; + + mo->angle -= NODEROTSPEED; // continuously rotate. + + while (ptr && !P_MobjWasRemoved(ptr)) + { + // get the new position, move us here, and move on to the next object in line. + angle_t newang = ptr->angle - NODEROTSPEED; + fixed_t newxpos = mo->x + FixedMul(radius, FINECOSINE((newang - ANG1*90)>>ANGLETOFINESHIFT)); + fixed_t newypos = mo->y + FixedMul(radius, FINESINE((newang - ANG1*90)>>ANGLETOFINESHIFT)); + + P_MoveOrigin(ptr, newxpos, newypos, ptr->z); + ptr->angle = newang; + + ptr = ptr->hnext; + } + + // check for players coming near us. + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator || players[i].rideroid || players[i].rdnodepull) + continue; + + pmo = players[i].mo; + //CONS_Printf("rd: %d\n", players[i].rideroid); + + if (R_PointToDist2(mo->x, mo->y, pmo->x, pmo->y) < NODERADIUS*mapobjectscale + && pmo->z + pmo->height >= mo->z + && pmo->z <= mo->z + 512*mapobjectscale) + { + + mobj_t *rd; + + plr_undoRespawn(&players[i]); + plr_resetRideroidVars(&players[i]); + + players[i].rdnodepull = true; + players[i].rideroidangle = mo->spawnpoint->angle*ANG1; // reminder that mo->angle changes, so we use the spawnpoint angle. + players[i].rideroidspeed = RIDEROIDSPEED/8; + + P_SetTarget(&pmo->tracer, mo); + + // spawn the rideroid. + rd = P_SpawnMobj(mo->x, mo->y, mo->z, MT_RIDEROID); + rd->angle = players[i].rideroidangle; + P_SetTarget(&rd->target, pmo); + + //S_StartSound(rd, sfx_ridr1); + + //CONS_Printf("rd pull\n"); + + } + } +} diff --git a/src/p_mobj.c b/src/p_mobj.c index 4f1b97657..a17e1eef7 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9633,6 +9633,20 @@ static boolean P_MobjRegularThink(mobj_t *mobj) case MT_RAINBOWDASHRING: Obj_RainbowDashRingThink(mobj); break; + + case MT_RIDEROID: + Obj_RideroidThink(mobj); + if (P_MobjWasRemoved(mobj)) + { + return false; + } + + break; + + case MT_RIDEROIDNODE: + Obj_RideroidNodeThink(mobj); + break; + default: // check mobj against possible water content, before movement code P_MobjCheckWater(mobj); @@ -11043,6 +11057,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) case MT_RAINBOWDASHRING: Obj_RainbowDashRingSpawn(mobj); break; + case MT_RIDEROIDNODE: + Obj_RideroidNodeSpawn(mobj); + break; case MT_SNEAKERPANEL: Obj_SneakerPanelSpawn(mobj); break;