Rideroid: first pass

This commit is contained in:
Lat 2023-09-18 16:54:16 +02:00
parent 56d8ae67ae
commit aef958d7ed
6 changed files with 530 additions and 1 deletions

View file

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

View file

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

View file

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

View file

@ -29,4 +29,5 @@ target_sources(SRB2SDL2 PRIVATE
sneaker-panel.c
emerald.c
checkpoint.cpp
rideroid.c
)

498
src/objects/rideroid.c Normal file
View file

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

View file

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