RingRacers/src/objects/gardentop.c
Sally Coolatta abde576c58 Enforce const in bot ticcmds
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.
2023-12-22 23:28:08 -05:00

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