mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-12-07 16:42:45 +00:00
There were a few remaining cases of bot ticcmd generation editing player structures directly. Fix all of this and make as much of it pass const player pointers so this physically can't be allowed to happen ever again. Appears to improve bot sync in netgames & demos bot support, but I have not tested extensively.
689 lines
12 KiB
C
689 lines
12 KiB
C
#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_local.h"
|
|
#include "../s_sound.h"
|
|
|
|
// TODO: separate from this file
|
|
static fixed_t K_FlipZOffset(mobj_t *us, mobj_t *them)
|
|
{
|
|
fixed_t z = 0;
|
|
|
|
if (them->eflags & MFE_VERTICALFLIP)
|
|
z += them->height;
|
|
|
|
if (us->eflags & MFE_VERTICALFLIP)
|
|
z -= us->height;
|
|
|
|
return z;
|
|
}
|
|
|
|
#define SPARKCOLOR SKINCOLOR_ROBIN
|
|
|
|
enum {
|
|
TOP_ANCHORED,
|
|
TOP_LOOSE,
|
|
};
|
|
|
|
#define topsfx_floating sfx_s3k7d
|
|
#define topsfx_grinding sfx_s3k79
|
|
#define topsfx_lift sfx_s3ka0
|
|
|
|
#define rider_top(o) ((o)->hnext)
|
|
|
|
#define top_mode(o) ((o)->extravalue1)
|
|
#define top_float(o) ((o)->lastlook)
|
|
#define top_sound(o) ((o)->extravalue2)
|
|
#define top_soundtic(o) ((o)->movecount)
|
|
|
|
/* TOP_ANCHORED */
|
|
#define top_rider(o) ((o)->tracer)
|
|
|
|
/* TOP_LOOSE */
|
|
#define top_waveangle(o) ((o)->movedir)
|
|
/* wavepause will take mobjinfo reactiontime automatically */
|
|
#define top_wavepause(o) ((o)->reactiontime)
|
|
|
|
#define spark_top(o) ((o)->target)
|
|
#define spark_angle(o) ((o)->movedir)
|
|
|
|
enum {
|
|
ARROW_OVERHEAD,
|
|
ARROW_IN_FRONT,
|
|
};
|
|
|
|
#define arrow_top(o) ((o)->target)
|
|
|
|
static inline player_t *
|
|
get_rider_player (mobj_t *rider)
|
|
{
|
|
return rider ? rider->player : NULL;
|
|
}
|
|
|
|
static inline player_t *
|
|
get_top_rider_player (mobj_t *top)
|
|
{
|
|
return get_rider_player(top_rider(top));
|
|
}
|
|
|
|
static inline boolean
|
|
is_top_grind_input (mobj_t *top)
|
|
{
|
|
player_t *player = get_top_rider_player(top);
|
|
|
|
return player && K_IsHoldingDownTop(player);
|
|
}
|
|
|
|
static inline boolean
|
|
is_top_grinding (mobj_t *top)
|
|
{
|
|
if (top_float(top) > 0)
|
|
return false;
|
|
|
|
if (!P_IsObjectOnGround(top))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline fixed_t
|
|
grind_spark_base_scale (player_t *player)
|
|
{
|
|
return FRACUNIT/2 +
|
|
player->topdriftheld * FRACUNIT
|
|
/ GARDENTOP_MAXGRINDTIME;
|
|
}
|
|
|
|
static inline INT32
|
|
get_player_steer_tilt
|
|
( player_t * player,
|
|
INT32 stages)
|
|
{
|
|
return player->steering
|
|
* stages
|
|
|
|
// 1 degree for a full turn
|
|
/ KART_FULLTURN
|
|
* ANG1
|
|
|
|
// stages is for fractions of a full turn, divide to
|
|
// get a fraction of a degree
|
|
/ stages
|
|
|
|
// angle is inverted in reverse gravity
|
|
* P_MobjFlip(player->mo);
|
|
}
|
|
|
|
static inline fixed_t
|
|
goofy_shake (fixed_t n)
|
|
{
|
|
return P_RandomRange(PR_DECORATION, -1, 1) * n;
|
|
}
|
|
|
|
static inline void
|
|
init_top
|
|
( mobj_t * top,
|
|
INT32 mode)
|
|
{
|
|
top_mode(top) = mode;
|
|
top_float(top) = 0;
|
|
top_sound(top) = sfx_None;
|
|
top_waveangle(top) = 0;
|
|
}
|
|
|
|
static void
|
|
spawn_spark
|
|
( mobj_t * top,
|
|
angle_t angle)
|
|
{
|
|
mobj_t *spark = P_SpawnMobjFromMobj(
|
|
top, 0, 0, 0, MT_GARDENTOPSPARK);
|
|
|
|
P_SetTarget(&spark_top(spark), top);
|
|
|
|
spark_angle(spark) = angle;
|
|
|
|
spark->color = SPARKCOLOR;
|
|
spark->spriteyscale = 3*FRACUNIT/4;
|
|
}
|
|
|
|
static void
|
|
spawn_spark_circle
|
|
( mobj_t * top,
|
|
UINT8 n)
|
|
{
|
|
const angle_t a = ANGLE_MAX / n;
|
|
|
|
UINT8 i;
|
|
|
|
for (i = 0; i < n; ++i)
|
|
{
|
|
spawn_spark(top, i * a);
|
|
}
|
|
}
|
|
|
|
static void
|
|
spawn_grind_spark (mobj_t *top)
|
|
{
|
|
mobj_t *rider = top_rider(top);
|
|
mobj_t *spark;
|
|
|
|
player_t *player = NULL;
|
|
|
|
fixed_t x = 0;
|
|
fixed_t y = 0;
|
|
|
|
angle_t angle = top->angle;
|
|
|
|
if (rider)
|
|
{
|
|
const fixed_t speed = -20 * top->scale;
|
|
|
|
angle = K_MomentumAngle(rider);
|
|
|
|
x = P_ReturnThrustX(rider, angle, speed);
|
|
y = P_ReturnThrustY(rider, angle, speed);
|
|
|
|
player = get_rider_player(rider);
|
|
}
|
|
|
|
spark = P_SpawnMobjFromMobj(
|
|
top, x, y, 0, MT_DRIFTSPARK);
|
|
|
|
spark->momx = x;
|
|
spark->momy = y;
|
|
|
|
P_SetMobjState(spark, S_DRIFTSPARK_A1);
|
|
|
|
spark->angle = angle;
|
|
spark->color = SPARKCOLOR;
|
|
|
|
if (player)
|
|
{
|
|
spark->destscale = FixedMul(spark->destscale,
|
|
grind_spark_base_scale(player));
|
|
|
|
P_SetScale(spark, spark->destscale);
|
|
}
|
|
}
|
|
|
|
static void
|
|
spawn_arrow (mobj_t *top)
|
|
{
|
|
mobj_t *arrow = P_SpawnMobjFromMobj(
|
|
top, 0, 0, 0, MT_GARDENTOPARROW);
|
|
|
|
P_SetTarget(&arrow_top(arrow), top);
|
|
|
|
P_SetScale(arrow,
|
|
(arrow->destscale = 3 * arrow->scale / 4));
|
|
}
|
|
|
|
static void
|
|
loop_sfx
|
|
( mobj_t * top,
|
|
sfxenum_t sfx)
|
|
{
|
|
switch (sfx)
|
|
{
|
|
case topsfx_floating:
|
|
if (S_SoundPlaying(top, sfx))
|
|
{
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case topsfx_grinding:
|
|
if ((sfxenum_t)top_sound(top) != sfx)
|
|
{
|
|
top_soundtic(top) = leveltime;
|
|
}
|
|
|
|
/* FIXME: could this sound just be looped
|
|
normally? :face_holding_back_tears: */
|
|
if ((leveltime - top_soundtic(top)) % 28 > 0)
|
|
{
|
|
return;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
S_StartSound(top, sfx);
|
|
}
|
|
|
|
static void
|
|
modulate (mobj_t *top)
|
|
{
|
|
const fixed_t max_hover = top->height / 4;
|
|
const fixed_t hover_step = max_hover / 4;
|
|
|
|
sfxenum_t ambience = sfx_None;
|
|
|
|
if (is_top_grind_input(top))
|
|
{
|
|
if (top_float(top) == max_hover)
|
|
{
|
|
P_SetMobjState(top, S_GARDENTOP_SINKING1);
|
|
}
|
|
|
|
if (top_float(top) > 0)
|
|
{
|
|
top_float(top) = max(0,
|
|
top_float(top) - hover_step);
|
|
}
|
|
else if (P_IsObjectOnGround(top))
|
|
{
|
|
spawn_grind_spark(top);
|
|
ambience = topsfx_grinding;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (top_float(top) == 0)
|
|
{
|
|
P_SetMobjState(top, S_GARDENTOP_FLOATING);
|
|
|
|
S_StopSoundByID(top, topsfx_grinding);
|
|
S_StartSound(top, topsfx_lift);
|
|
}
|
|
|
|
if (top_float(top) < max_hover)
|
|
{
|
|
top_float(top) = min(max_hover,
|
|
top_float(top) + hover_step);
|
|
}
|
|
else
|
|
{
|
|
ambience = topsfx_floating;
|
|
}
|
|
}
|
|
|
|
top->sprzoff = top_float(top) * P_MobjFlip(top);
|
|
|
|
if (ambience)
|
|
{
|
|
loop_sfx(top, ambience);
|
|
}
|
|
|
|
top_sound(top) = ambience;
|
|
}
|
|
|
|
static void
|
|
tilt (mobj_t *top)
|
|
{
|
|
player_t *player = get_top_rider_player(top);
|
|
|
|
INT32 tilt = top->rollangle;
|
|
|
|
if (is_top_grind_input(top))
|
|
{
|
|
const angle_t tiltmax = ANGLE_22h;
|
|
|
|
tilt += get_player_steer_tilt(player, 4);
|
|
|
|
if (abs(tilt) > tiltmax)
|
|
{
|
|
tilt = intsign(tilt) * tiltmax;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const angle_t decay = ANG1 * 2;
|
|
|
|
if (abs(tilt) > decay)
|
|
{
|
|
tilt -= intsign(tilt) * decay;
|
|
}
|
|
else
|
|
{
|
|
tilt = 0;
|
|
}
|
|
}
|
|
|
|
top->rollangle = tilt;
|
|
|
|
/* Vibrate left and right if you're about to lose it. */
|
|
if (player && player->topinfirst)
|
|
{
|
|
top->spritexoffset = P_LerpFlip(32*FRACUNIT, 1);
|
|
}
|
|
else
|
|
{
|
|
top->spritexoffset = 0;
|
|
}
|
|
|
|
/* Go ABSOLUTELY NUTS if the player is tumbling... */
|
|
if (player && player->tumbleBounces > 0)
|
|
{
|
|
const fixed_t yofs = 48 * FRACUNIT;
|
|
const fixed_t ofs3d = 24 * top->scale;
|
|
|
|
/* spriteyoffset scales, e.g. with K_Squish */
|
|
top->spriteyoffset = FixedDiv(
|
|
goofy_shake(yofs), top->spriteyscale);
|
|
|
|
top->sprxoff = goofy_shake(ofs3d);
|
|
top->spryoff = goofy_shake(ofs3d);
|
|
}
|
|
else
|
|
{
|
|
top->spriteyoffset = 0;
|
|
top->sprxoff = 0;
|
|
top->spryoff = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
anchor
|
|
( mobj_t * us,
|
|
mobj_t * them,
|
|
angle_t angle,
|
|
fixed_t radius)
|
|
{
|
|
const fixed_t x = P_ReturnThrustX(us, angle, radius);
|
|
const fixed_t y = P_ReturnThrustY(us, angle, radius);
|
|
|
|
/* FIXME: THIS FUNCTION FUCKING SUCKS */
|
|
K_FlipFromObject(us, them);
|
|
|
|
P_MoveOrigin(us, them->x + x, them->y + y,
|
|
them->z + K_FlipZOffset(us, them));
|
|
|
|
us->angle = angle;
|
|
}
|
|
|
|
static void
|
|
anchor_top (mobj_t *top)
|
|
{
|
|
mobj_t *rider = top_rider(top);
|
|
player_t *player = get_rider_player(rider);
|
|
|
|
if (player && player->curshield != KSHIELD_TOP)
|
|
{
|
|
P_RemoveMobj(top);
|
|
return;
|
|
}
|
|
|
|
/* Rider lost track of this object. */
|
|
if (rider_top(rider) != top)
|
|
{
|
|
P_RemoveMobj(top);
|
|
return;
|
|
}
|
|
|
|
tilt(top);
|
|
|
|
anchor(top, rider, rider->angle, 0);
|
|
|
|
K_GenericExtraFlagsNoZAdjust(top, rider);
|
|
|
|
/* Copying the Z momentum lets the Top squash and stretch
|
|
as it falls with the player. Don't copy the X/Y
|
|
momentum because then it would always get slightly
|
|
ahead of the player. */
|
|
top->momx = 0;
|
|
top->momy = 0;
|
|
top->momz = rider->momz;
|
|
|
|
/* The Z momentum can put the Top slightly ahead of the
|
|
player in that axis too. It looks cool if the Top
|
|
falls below you but not if it bounces up. */
|
|
if (top->momz * P_MobjFlip(top) > 0)
|
|
{
|
|
top->momz = 0;
|
|
}
|
|
|
|
/* match rider's slope tilt */
|
|
top->pitch = rider->pitch;
|
|
top->roll = rider->roll;
|
|
}
|
|
|
|
static void
|
|
loose_think (mobj_t *top)
|
|
{
|
|
const fixed_t thrustamount = top->movefactor / 2;
|
|
const angle_t momangle = K_MomentumAngle(top);
|
|
|
|
angle_t ang = top->angle;
|
|
|
|
mobj_t *ghost = P_SpawnGhostMobj(top);
|
|
ghost->colorized = true; // already has color!
|
|
|
|
if (AngleDelta(ang, momangle) > ANGLE_90)
|
|
{
|
|
top->angle = momangle;
|
|
}
|
|
|
|
if (top_wavepause(top))
|
|
{
|
|
top_wavepause(top)--;
|
|
}
|
|
else
|
|
{
|
|
/* oscillate between +90 and -90 degrees */
|
|
ang += AbsAngle(top_waveangle(top)) - ANGLE_90;
|
|
}
|
|
|
|
P_InstaThrust(top, top->angle, thrustamount);
|
|
P_Thrust(top, ang, thrustamount);
|
|
|
|
//top_waveangle(top) = (angle_t)top_waveangle(top) + ANG10;
|
|
top_waveangle(top) += ANG10;
|
|
|
|
/* intangibility grace period */
|
|
if (top->threshold > 0)
|
|
{
|
|
top->threshold--;
|
|
}
|
|
}
|
|
|
|
static void
|
|
anchor_spark (mobj_t *spark)
|
|
{
|
|
mobj_t *top = spark_top(spark);
|
|
mobj_t *rider = top_rider(top);
|
|
player_t *player = get_rider_player(rider);
|
|
|
|
anchor(spark, top,
|
|
(top->angle + spark_angle(spark)), spark->scale);
|
|
|
|
if (player)
|
|
{
|
|
const fixed_t topspeed =
|
|
K_GetKartSpeed(player, false, false);
|
|
|
|
const fixed_t speed = FixedHypot(
|
|
rider->momx, rider->momy);
|
|
|
|
P_SetScale(spark, FixedMul(top->scale, FRACUNIT/2 +
|
|
FixedDiv(speed / 2, topspeed)));
|
|
}
|
|
}
|
|
|
|
static void
|
|
anchor_arrow_overhead (mobj_t *arrow)
|
|
{
|
|
mobj_t *top = arrow_top(arrow);
|
|
mobj_t *rider = top_rider(top);
|
|
|
|
const fixed_t height =
|
|
top->height + rider->height + (3 * arrow->height / 4);
|
|
|
|
arrow->sprzoff = top->sprzoff +
|
|
(height * P_MobjFlip(arrow));
|
|
|
|
anchor(arrow, top, rider->angle + ANGLE_180, 0);
|
|
}
|
|
|
|
void
|
|
Obj_GardenTopDeploy (mobj_t *rider)
|
|
{
|
|
player_t *player = rider->player;
|
|
|
|
mobj_t *top = P_SpawnMobjFromMobj(
|
|
rider, 0, 0, 0, MT_GARDENTOP);
|
|
|
|
init_top(top, TOP_ANCHORED);
|
|
|
|
top->flags |= MF_NOCLIPHEIGHT;
|
|
|
|
/* only the player's shadow needs to be rendered */
|
|
top->shadowscale = 0;
|
|
|
|
P_SetTarget(&top_rider(top), rider);
|
|
P_SetTarget(&rider_top(rider), top);
|
|
|
|
if (player)
|
|
{
|
|
player->curshield = KSHIELD_TOP;
|
|
rider->radius = K_DefaultPlayerRadius(player);
|
|
}
|
|
|
|
spawn_spark_circle(top, 6);
|
|
|
|
spawn_arrow(top);
|
|
}
|
|
|
|
mobj_t *
|
|
Obj_GardenTopThrow (player_t *player)
|
|
{
|
|
mobj_t *top = K_GetGardenTop(player);
|
|
|
|
if (top)
|
|
{
|
|
const fixed_t oldfloat = top_float(top);
|
|
const fixed_t height = top->height;
|
|
|
|
K_UpdateHnextList(player, true);
|
|
|
|
/* Sucks that another one needs to be spawned but
|
|
this way, the throwing function can be used. */
|
|
top = K_ThrowKartItem(
|
|
player, true, MT_GARDENTOP, 1, 0, 0);
|
|
|
|
init_top(top, TOP_LOOSE);
|
|
|
|
top_float(top) = oldfloat;
|
|
top_waveangle(top) = 0;
|
|
|
|
/* prevents it from hitting us on its way out */
|
|
top->threshold = 20;
|
|
|
|
/* ensure it's tangible */
|
|
top->flags &= ~(MF_NOCLIPTHING);
|
|
|
|
/* Put player PHYSICALLY on top. While riding the
|
|
Top, player collision was used and the player
|
|
technically remained on the ground. Now they
|
|
should fall off. */
|
|
P_SetOrigin(player->mo, player->mo->x, player->mo->y,
|
|
player->mo->z + height * P_MobjFlip(player->mo));
|
|
}
|
|
|
|
if (player->itemamount > 0)
|
|
player->itemamount--;
|
|
|
|
if (player->itemamount <= 0)
|
|
player->itemtype = KITEM_NONE;
|
|
|
|
player->curshield = KSHIELD_NONE;
|
|
|
|
player->mo->radius = K_DefaultPlayerRadius(player);
|
|
|
|
return top;
|
|
}
|
|
|
|
mobj_t *
|
|
Obj_GardenTopDestroy (player_t *player)
|
|
{
|
|
mobj_t *top = Obj_GardenTopThrow(player);
|
|
|
|
if (top)
|
|
{
|
|
/* kill kill kill die die die */
|
|
P_KillMobj(top, NULL, NULL, DMG_NORMAL);
|
|
}
|
|
|
|
return top;
|
|
}
|
|
|
|
void
|
|
Obj_GardenTopThink (mobj_t *top)
|
|
{
|
|
modulate(top);
|
|
|
|
switch (top_mode(top))
|
|
{
|
|
case TOP_ANCHORED:
|
|
if (top_rider(top))
|
|
{
|
|
anchor_top(top);
|
|
}
|
|
break;
|
|
|
|
case TOP_LOOSE:
|
|
loose_think(top);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
Obj_GardenTopSparkThink (mobj_t *spark)
|
|
{
|
|
mobj_t *top = spark_top(spark);
|
|
|
|
if (!top)
|
|
{
|
|
P_RemoveMobj(spark);
|
|
return;
|
|
}
|
|
|
|
anchor_spark(spark);
|
|
|
|
if (is_top_grinding(top))
|
|
{
|
|
spark->renderflags ^= RF_DONTDRAW;
|
|
}
|
|
else
|
|
{
|
|
spark->renderflags |= RF_DONTDRAW;
|
|
}
|
|
}
|
|
|
|
void
|
|
Obj_GardenTopArrowThink (mobj_t *arrow)
|
|
{
|
|
mobj_t *top = arrow_top(arrow);
|
|
mobj_t *rider = top ? top_rider(top) : NULL;
|
|
|
|
if (!rider)
|
|
{
|
|
P_RemoveMobj(arrow);
|
|
return;
|
|
}
|
|
|
|
anchor_arrow_overhead(arrow);
|
|
|
|
if (rider->player)
|
|
{
|
|
// Don't show for other players
|
|
arrow->renderflags =
|
|
(arrow->renderflags & ~(RF_DONTDRAW)) |
|
|
(RF_DONTDRAW & ~(K_GetPlayerDontDrawFlag(rider->player)));
|
|
}
|
|
}
|
|
|
|
boolean
|
|
Obj_GardenTopPlayerIsGrinding (const player_t *player)
|
|
{
|
|
mobj_t *top = K_GetGardenTop(player);
|
|
|
|
return top ? is_top_grinding(top) : false;
|
|
}
|