RingRacers/src/objects/rideroid.c

577 lines
15 KiB
C

// 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"
#include "../k_color.h"
#define NODERADIUS 260
#define NODEPULLOK 48
#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;
P_SetTarget(&p->mo->tracer, NULL);
}
// kills the rideroid and removes it from the map.
static void Obj_killRideroid(mobj_t *mo)
{
UINT8 i;
for (i = 0; i < 32; i++)
{
mobj_t *t = P_SpawnMobj(mo->x, mo->y, mo->z, MT_THOK);
t->color = SKINCOLOR_TEAL;
t->frame = FF_FULLBRIGHT;
t->destscale = 1;
t->momx = P_RandomRange(PR_EXPLOSION, -32, 32)*mapobjectscale;
t->momy = P_RandomRange(PR_EXPLOSION, -32, 32)*mapobjectscale;
t->momz = P_RandomRange(PR_EXPLOSION, -32, 32)*mapobjectscale;
}
P_RemoveMobj(mo);
}
// makes the player get off of the rideroid.
void Obj_getPlayerOffRideroid(mobj_t *mo)
{
mobj_t *pmo = mo->target;
if (pmo && !P_MobjWasRemoved(pmo))
{
player_t *p = pmo->player;
pmo->flags &= ~MF_NOGRAVITY;
if (p)
plr_resetRideroidVars(p);
mo->fuse = TICRATE/2;
mo->momx = mo->momx*2;
mo->momy = mo->momy*2;
mo->momz = 0;
mo->target = NULL;
S_StartSound(mo, sfx_ridr4);
}
}
// 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 = NULL;
UINT8 i, j;
angle_t h_an = mo->angle + ANG1*90;
if (pmo && !P_MobjWasRemoved(pmo))
{
p = pmo->player; // used to make some graphics local to save on framerate
mo->color = pmo->color;
mo->colorized = pmo->colorized;
}
// from here, we will use the following:
// extravalue1: prev x
// extravalue2: prev y
// cusval: prev z
// cvmem: prev roll angle
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)
{
if (j)
t->renderflags |= (RF_DONTDRAW & ~K_GetPlayerDontDrawFlag(p));
if (p->startboost)
t->color = K_RainbowColor(leveltime);
}
}
}
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);
mo->momx = pmo->momx;
mo->momy = pmo->momy;
mo->momz = pmo->momz;
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))
{
if (!mo->fuse)
{
mo->fuse = TICRATE/2;
}
else if (mo->fuse == 1)
{
Obj_killRideroid(mo);
}
else
{
Obj_rideroidTrail(mo);
mo->rollangle += ANG1*24;
}
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!
P_SetTarget(&p->mo->tracer, mo); // keep a reference of the rideroid in the player for convenience.
// 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 = (maxspd*12)/10; // Ring Boost: 120% max speed.
if (p->draftpower)
{
UINT8 draftperc = (p->draftpower*100 / FRACUNIT); // 0-100%
maxspd += (draftperc/5) / 100;
}
if (p->startboost)
maxspd = (maxspd*15)/10; // 150% speed
// 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, (angle_t)p->rideroidangle, p->rideroidspeed*mapobjectscale);
basemomx = p->mo->momx;
basemomy = p->mo->momy;
pmo->momx += p->rdaddmomx;
pmo->momy += p->rdaddmomy;
pmo->momz += p->rdaddmomz;
pmo->angle = (angle_t)p->rideroidangle;
p->drawangle = (angle_t)p->rideroidangle;
P_SetPlayerAngle(pmo->player, (angle_t)p->rideroidangle);
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);
// turning left/right
if (p->cmd.turning)
{
fixed_t savemomx = pmo->momx;
fixed_t savemomy = pmo->momy;
SINT8 dir = 0;
INT32 a;
if (p->cmd.turning < -400)
{
a = (INT32)(mo->angle) - ANG1*90;
P_Thrust(pmo, mo->angle - ANGLE_90, 2*mapobjectscale);
p->rideroidrollangle -= ANG1*3;
if (p->rideroidrollangle < -ANG1*25)
p->rideroidrollangle = -ANG1*25;
dir = 1;
}
else if (p->cmd.turning > 400)
{
a = (INT32)(mo->angle) + ANG1*90;
P_Thrust(pmo, mo->angle + ANGLE_90, 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;
}
if (dir != 0)
{
// save the added momentum
p->rdaddmomx = pmo->momx - basemomx;
p->rdaddmomy = pmo->momy - basemomy;
//CONS_Printf("AX1: %d, AY1: %d\n", p->rdaddmomx/mapobjectscale, p->rdaddmomy/mapobjectscale);
pmo->momx = basemomx;
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;
a = (INT32)(mo->angle) - dir*ANG1*90;
P_Thrust(pmo, (angle_t)a, RIDEROIDMAXADD*3*mapobjectscale);
xthreshold = pmo->momx - basemomx;
ythreshold = pmo->momy - basemomy;
//CONS_Printf("XT: %d (%d), YT: %d (%d)\n", xthreshold/mapobjectscale, abs(xthreshold/mapobjectscale), ythreshold/mapobjectscale, abs(ythreshold/mapobjectscale));
// clamp the momentums using the calculated thresholds.
// the fixedmul check checks if both numbers are of the same sign.
if (abs(p->rdaddmomx) > abs(xthreshold))
p->rdaddmomx = xthreshold;
if (abs(p->rdaddmomy) > abs(ythreshold))
p->rdaddmomy = ythreshold;
//CONS_Printf("AX2: %d, AY2: %d\n", p->rdaddmomx/mapobjectscale, p->rdaddmomy/mapobjectscale);
// 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;
fixed_t zpos = mo->z + 64*mapobjectscale + mapobjectscale*96*i;
ang *= ANG1; // this has to be done here or the warning prevents the compile, we don't care about overflowing here.
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 || K_isPlayerInSpecialState(&players[i]) || P_PlayerInPain(&players[i]))
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;
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");
}
}
}