The Garden Top

This is all the visual effects and item logic of the Top
minus (almost all [1]) physics adjustments when a player is
riding one.

While on a Top:

- Otherwordly humming while floating

- Cannot drift [1]. Instead, holding drift begins grinding
the Top into the ground -- sparks fly out
- Sprite tilts left and right as you turn
- No wheel screech when turning too far
- Speed lines while holding drift
- Tripwire Boost effect scales to cover you AND the Top

- One hit and you lose it

- Throwing forward, getting hit or just losing it because
you were in first place for too long: the Top flies out
from under you and begins snaking like crazy
- Throwing backward also makes the Top fly out from under
you but it also thrusts you forward. The Top dies
instantly so it's not dangerous ;-)

- If you're in first for too long, it begins vibrating
- If you tumble, it dances across your entire screen

A loose top will tumble anyone it hits.
This commit is contained in:
James R 2022-09-28 00:52:55 -07:00
parent b5cdf25298
commit 5063a4acf1
12 changed files with 923 additions and 21 deletions

View file

@ -290,6 +290,8 @@ typedef enum
#define ITEMSCALE_GROW 1 #define ITEMSCALE_GROW 1
#define ITEMSCALE_SHRINK 2 #define ITEMSCALE_SHRINK 2
#define GARDENTOP_MAXGRINDTIME (45)
// player_t struct for all respawn variables // player_t struct for all respawn variables
typedef struct respawnvars_s typedef struct respawnvars_s
{ {

View file

@ -2414,7 +2414,7 @@ void K_SpawnDriftBoostClipSpark(mobj_t *clip)
spark->momy = clip->momx/2; spark->momy = clip->momx/2;
} }
void K_SpawnNormalSpeedLines(player_t *player) static void K_SpawnGenericSpeedLines(player_t *player, boolean top)
{ {
mobj_t *fast = P_SpawnMobj(player->mo->x + (P_RandomRange(PR_DECORATION,-36,36) * player->mo->scale), mobj_t *fast = P_SpawnMobj(player->mo->x + (P_RandomRange(PR_DECORATION,-36,36) * player->mo->scale),
player->mo->y + (P_RandomRange(PR_DECORATION,-36,36) * player->mo->scale), player->mo->y + (P_RandomRange(PR_DECORATION,-36,36) * player->mo->scale),
@ -2422,20 +2422,40 @@ void K_SpawnNormalSpeedLines(player_t *player)
MT_FASTLINE); MT_FASTLINE);
P_SetTarget(&fast->target, player->mo); P_SetTarget(&fast->target, player->mo);
P_InitAngle(fast, K_MomentumAngle(player->mo));
fast->momx = 3*player->mo->momx/4; fast->momx = 3*player->mo->momx/4;
fast->momy = 3*player->mo->momy/4; fast->momy = 3*player->mo->momy/4;
fast->momz = 3*P_GetMobjZMovement(player->mo)/4; fast->momz = 3*P_GetMobjZMovement(player->mo)/4;
K_MatchGenericExtraFlags(fast, player->mo); fast->z += player->mo->sprzoff;
if (player->tripwireLeniency) if (top)
{ {
fast->destscale = fast->destscale * 2; P_InitAngle(fast, player->mo->angle);
P_SetScale(fast, 3*fast->scale/2); P_SetScale(fast, (fast->destscale =
3 * fast->destscale / 2));
fast->spritexscale = 3*FRACUNIT;
}
else
{
P_InitAngle(fast, K_MomentumAngle(player->mo));
if (player->tripwireLeniency)
{
fast->destscale = fast->destscale * 2;
P_SetScale(fast, 3*fast->scale/2);
}
} }
if (player->eggmanexplode) K_MatchGenericExtraFlags(fast, player->mo);
if (top)
{
fast->color = SKINCOLOR_SUNSLAM;
fast->colorized = true;
fast->renderflags |= RF_ADD;
}
else if (player->eggmanexplode)
{ {
// Make it red when you have the eggman speed boost // Make it red when you have the eggman speed boost
fast->color = SKINCOLOR_RED; fast->color = SKINCOLOR_RED;
@ -2463,6 +2483,16 @@ void K_SpawnNormalSpeedLines(player_t *player)
} }
} }
void K_SpawnNormalSpeedLines(player_t *player)
{
K_SpawnGenericSpeedLines(player, false);
}
void K_SpawnGardenTopSpeedLines(player_t *player)
{
K_SpawnGenericSpeedLines(player, true);
}
void K_SpawnInvincibilitySpeedLines(mobj_t *mo) void K_SpawnInvincibilitySpeedLines(mobj_t *mo)
{ {
mobj_t *fast = P_SpawnMobjFromMobj(mo, mobj_t *fast = P_SpawnMobjFromMobj(mo,
@ -2561,14 +2591,21 @@ static void K_SpawnGrowShrinkParticles(mobj_t *mo, INT32 timer)
void K_SpawnBumpEffect(mobj_t *mo) void K_SpawnBumpEffect(mobj_t *mo)
{ {
mobj_t *top = mo->player ? K_GetGardenTop(mo->player) : NULL;
mobj_t *fx = P_SpawnMobj(mo->x, mo->y, mo->z, MT_BUMP); mobj_t *fx = P_SpawnMobj(mo->x, mo->y, mo->z, MT_BUMP);
if (mo->eflags & MFE_VERTICALFLIP) if (mo->eflags & MFE_VERTICALFLIP)
fx->eflags |= MFE_VERTICALFLIP; fx->eflags |= MFE_VERTICALFLIP;
else else
fx->eflags &= ~MFE_VERTICALFLIP; fx->eflags &= ~MFE_VERTICALFLIP;
fx->scale = mo->scale; fx->scale = mo->scale;
S_StartSound(mo, sfx_s3k49); if (top)
S_StartSound(mo, top->info->attacksound);
else
S_StartSound(mo, sfx_s3k49);
} }
static SINT8 K_GlanceAtPlayers(player_t *glancePlayer) static SINT8 K_GlanceAtPlayers(player_t *glancePlayer)
@ -2723,6 +2760,10 @@ void K_KartMoveAnimation(player_t *player)
drift = intsign(player->aizdriftturn); drift = intsign(player->aizdriftturn);
turndir = 0; turndir = 0;
} }
else if (player->curshield == KSHIELD_TOP)
{
drift = -turndir;
}
else if (turndir == 0 && drift == 0) else if (turndir == 0 && drift == 0)
{ {
// Only try glancing if you're driving straight. // Only try glancing if you're driving straight.
@ -3288,6 +3329,46 @@ boolean K_WaterSkip(player_t *player)
return false; return false;
} }
boolean K_IsRidingFloatingTop(player_t *player)
{
if (player->curshield != KSHIELD_TOP)
{
return false;
}
return !Obj_GardenTopPlayerIsGrinding(player);
}
boolean K_IsHoldingDownTop(player_t *player)
{
if (player->curshield != KSHIELD_TOP)
{
return false;
}
if ((K_GetKartButtons(player) & BT_DRIFT) != BT_DRIFT)
{
return false;
}
return true;
}
mobj_t *K_GetGardenTop(player_t *player)
{
if (player->curshield != KSHIELD_TOP)
{
return NULL;
}
if (player->mo == NULL)
{
return NULL;
}
return player->mo->hnext;
}
static fixed_t K_FlameShieldDashVar(INT32 val) static fixed_t K_FlameShieldDashVar(INT32 val)
{ {
// 1 second = 75% + 50% top speed // 1 second = 75% + 50% top speed
@ -4698,6 +4779,19 @@ fixed_t K_ItemScaleForPlayer(player_t *player)
} }
} }
fixed_t K_DefaultPlayerRadius(player_t *player)
{
mobj_t *top = K_GetGardenTop(player);
if (top)
{
return top->radius;
}
return FixedMul(player->mo->scale,
player->mo->info->radius);
}
static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, INT32 flags2, fixed_t speed, SINT8 dir) static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, INT32 flags2, fixed_t speed, SINT8 dir)
{ {
mobj_t *th; mobj_t *th;
@ -4812,6 +4906,9 @@ static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, I
th->destscale = th->destscale << 1; th->destscale = th->destscale << 1;
th->scalespeed = abs(th->destscale - th->scale) / (2*TICRATE); th->scalespeed = abs(th->destscale - th->scale) / (2*TICRATE);
break; break;
case MT_GARDENTOP:
th->movefactor = finalspeed;
break;
default: default:
break; break;
} }
@ -5472,8 +5569,11 @@ void K_DriftDustHandling(mobj_t *spawner)
dust->destscale = spawner->scale * 3; dust->destscale = spawner->scale * 3;
dust->scalespeed = spawner->scale/12; dust->scalespeed = spawner->scale/12;
if (leveltime % 6 == 0) if (!spawner->player || !K_GetGardenTop(spawner->player))
S_StartSound(spawner, sfx_screec); {
if (leveltime % 6 == 0)
S_StartSound(spawner, sfx_screec);
}
K_MatchGenericExtraFlags(dust, spawner); K_MatchGenericExtraFlags(dust, spawner);
@ -5633,7 +5733,7 @@ mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing,
if (missile) // Shootables if (missile) // Shootables
{ {
if (dir < 0 && mapthing != MT_SPB) if (dir < 0 && mapthing != MT_SPB && mapthing != MT_GARDENTOP)
{ {
// Shoot backward // Shoot backward
mo = K_SpawnKartMissile(player->mo, mapthing, (player->mo->angle + ANGLE_180) + angleOffset, 0, PROJSPEED, dir); mo = K_SpawnKartMissile(player->mo, mapthing, (player->mo->angle + ANGLE_180) + angleOffset, 0, PROJSPEED, dir);
@ -6244,7 +6344,7 @@ void K_DropHnextList(player_t *player, boolean keepshields)
flip = P_MobjFlip(player->mo); flip = P_MobjFlip(player->mo);
ponground = P_IsObjectOnGround(player->mo); ponground = P_IsObjectOnGround(player->mo);
if (shield != KSHIELD_NONE && !keepshields) if (shield != KSHIELD_NONE && shield != KSHIELD_TOP && !keepshields)
{ {
if (shield == KSHIELD_LIGHTNING) if (shield == KSHIELD_LIGHTNING)
{ {
@ -6296,6 +6396,9 @@ void K_DropHnextList(player_t *player, boolean keepshields)
orbit = false; orbit = false;
type = MT_EGGMANITEM; type = MT_EGGMANITEM;
break; break;
case MT_GARDENTOP:
Obj_GardenTopDestroy(player);
return;
// intentionally do nothing // intentionally do nothing
case MT_ROCKETSNEAKER: case MT_ROCKETSNEAKER:
case MT_SINK_SHIELD: case MT_SINK_SHIELD:
@ -7602,6 +7705,33 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
{ {
const boolean onground = P_IsObjectOnGround(player->mo); const boolean onground = P_IsObjectOnGround(player->mo);
/* reset sprite offsets :) */
player->mo->sprxoff = 0;
player->mo->spryoff = 0;
player->mo->sprzoff = 0;
player->mo->spritexoffset = 0;
player->mo->spriteyoffset = 0;
if (player->curshield == KSHIELD_TOP)
{
mobj_t *top = K_GetGardenTop(player);
if (top)
{
/* FIXME: I cannot figure out how offset the
player correctly in real time to pivot around
the BOTTOM of the Top. This hack plus the one
in R_PlayerSpriteRotation. */
player->mo->spritexoffset += FixedMul(
FixedDiv(top->height, top->scale),
FINESINE(top->rollangle >> ANGLETOFINESHIFT));
player->mo->sprzoff += top->sprzoff + (
P_GetMobjHead(top) -
P_GetMobjFeet(player->mo));
}
}
K_UpdateOffroad(player); K_UpdateOffroad(player);
K_UpdateDraft(player); K_UpdateDraft(player);
K_UpdateEngineSounds(player); // Thanks, VAda! K_UpdateEngineSounds(player); // Thanks, VAda!
@ -8049,8 +8179,20 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
if (cmd->buttons & BT_DRIFT) if (cmd->buttons & BT_DRIFT)
{ {
if (player->curshield == KSHIELD_TOP)
{
if (player->topdriftheld <= GARDENTOP_MAXGRINDTIME)
player->topdriftheld++;
// Squish :)
player->mo->spritexscale = 6*FRACUNIT/4;
player->mo->spriteyscale = 2*FRACUNIT/4;
if (leveltime & 1)
K_SpawnGardenTopSpeedLines(player);
}
// Only allow drifting while NOT trying to do an spindash input. // Only allow drifting while NOT trying to do an spindash input.
if ((K_GetKartButtons(player) & BT_EBRAKEMASK) != BT_EBRAKEMASK) else if ((K_GetKartButtons(player) & BT_EBRAKEMASK) != BT_EBRAKEMASK)
{ {
player->pflags |= PF_DRIFTINPUT; player->pflags |= PF_DRIFTINPUT;
} }
@ -8187,7 +8329,7 @@ void K_KartResetPlayerColor(player_t *player)
finalise: finalise:
if (player->curshield) if (player->curshield && player->curshield != KSHIELD_TOP)
{ {
fullbright = true; fullbright = true;
} }
@ -9245,6 +9387,7 @@ void K_KartUpdatePosition(player_t *player)
fixed_t position = 1; fixed_t position = 1;
fixed_t oldposition = player->position; fixed_t oldposition = player->position;
fixed_t i; fixed_t i;
INT32 realplayers = 0;
if (player->spectator || !player->mo) if (player->spectator || !player->mo)
{ {
@ -9259,6 +9402,8 @@ void K_KartUpdatePosition(player_t *player)
if (!playeringame[i] || players[i].spectator || !players[i].mo) if (!playeringame[i] || players[i].spectator || !players[i].mo)
continue; continue;
realplayers++;
if (gametyperules & GTR_CIRCUIT) if (gametyperules & GTR_CIRCUIT)
{ {
if (player->exiting) // End of match standings if (player->exiting) // End of match standings
@ -9322,6 +9467,33 @@ void K_KartUpdatePosition(player_t *player)
if (oldposition != position) // Changed places? if (oldposition != position) // Changed places?
player->positiondelay = 10; // Position number growth player->positiondelay = 10; // Position number growth
/* except in FREE PLAY */
if (player->curshield == KSHIELD_TOP &&
(gametyperules & GTR_CIRCUIT) &&
realplayers > 1)
{
/* grace period so you don't fall off INSTANTLY */
if (position == 1 && player->topinfirst < 2*TICRATE)
{
player->topinfirst++;
}
else
{
if (position == 1)
{
Obj_GardenTopThrow(player);
}
else
{
player->topinfirst = 0;
}
}
}
else
{
player->topinfirst = 0;
}
player->position = position; player->position = position;
} }
@ -9473,6 +9645,7 @@ void K_KartEbrakeVisuals(player_t *p)
p->mo->hprev->angle = p->mo->angle; p->mo->hprev->angle = p->mo->angle;
p->mo->hprev->fuse = TICRATE/2; // When we leave spindash for any reason, make sure this bubble goes away soon after. p->mo->hprev->fuse = TICRATE/2; // When we leave spindash for any reason, make sure this bubble goes away soon after.
K_FlipFromObject(p->mo->hprev, p->mo); K_FlipFromObject(p->mo->hprev, p->mo);
p->mo->hprev->sprzoff = p->mo->sprzoff;
} }
if (!p->spindash) if (!p->spindash)
@ -10490,6 +10663,37 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
} }
} }
break; break;
case KITEM_GARDENTOP:
if (ATTACK_IS_DOWN && NO_HYUDORO)
{
if (player->curshield != KSHIELD_TOP)
{
player->topinfirst = 0;
Obj_GardenTopDeploy(player->mo);
}
else
{
if (player->throwdir == -1)
{
mobj_t *top = Obj_GardenTopDestroy(player);
// Fly off the Top at high speed
P_Thrust(player->mo, K_MomentumAngle(player->mo), 80 * player->mo->scale);
P_SetObjectMomZ(player->mo, player->mo->height / 2, true);
top->momx = player->mo->momx;
top->momy = player->mo->momy;
top->momz = player->mo->momz;
}
else
{
Obj_GardenTopThrow(player);
S_StartSound(player->mo, sfx_tossed); // play only when actually thrown :^,J
K_PlayAttackTaunt(player->mo);
}
}
}
break;
case KITEM_BUBBLESHIELD: case KITEM_BUBBLESHIELD:
if (player->curshield != KSHIELD_BUBBLE) if (player->curshield != KSHIELD_BUBBLE)
{ {

View file

@ -69,6 +69,7 @@ void K_SpawnDashDustRelease(player_t *player);
void K_SpawnDriftBoostClip(player_t *player); void K_SpawnDriftBoostClip(player_t *player);
void K_SpawnDriftBoostClipSpark(mobj_t *clip); void K_SpawnDriftBoostClipSpark(mobj_t *clip);
void K_SpawnNormalSpeedLines(player_t *player); void K_SpawnNormalSpeedLines(player_t *player);
void K_SpawnGardenTopSpeedLines(player_t *player);
void K_SpawnInvincibilitySpeedLines(mobj_t *mo); void K_SpawnInvincibilitySpeedLines(mobj_t *mo);
void K_SpawnBumpEffect(mobj_t *mo); void K_SpawnBumpEffect(mobj_t *mo);
void K_KartMoveAnimation(player_t *player); void K_KartMoveAnimation(player_t *player);
@ -144,6 +145,9 @@ tripwirepass_t K_TripwirePassConditions(player_t *player);
boolean K_TripwirePass(player_t *player); boolean K_TripwirePass(player_t *player);
boolean K_WaterRun(player_t *player); boolean K_WaterRun(player_t *player);
boolean K_WaterSkip(player_t *player); boolean K_WaterSkip(player_t *player);
boolean K_IsRidingFloatingTop(player_t *player);
boolean K_IsHoldingDownTop(player_t *player);
mobj_t *K_GetGardenTop(player_t *player);
void K_ApplyTripWire(player_t *player, tripwirestate_t state); void K_ApplyTripWire(player_t *player, tripwirestate_t state);
INT16 K_GetSpindashChargeTime(player_t *player); INT16 K_GetSpindashChargeTime(player_t *player);
fixed_t K_GetSpindashChargeSpeed(player_t *player); fixed_t K_GetSpindashChargeSpeed(player_t *player);
@ -170,6 +174,7 @@ UINT8 K_GetOrbinautItemFrame(UINT8 count);
boolean K_IsSPBInGame(void); boolean K_IsSPBInGame(void);
void K_KartEbrakeVisuals(player_t *p); void K_KartEbrakeVisuals(player_t *p);
void K_HandleDirectionalInfluence(player_t *player); void K_HandleDirectionalInfluence(player_t *player);
fixed_t K_DefaultPlayerRadius(player_t *player);
// sound stuff for lua // sound stuff for lua
void K_PlayAttackTaunt(mobj_t *source); void K_PlayAttackTaunt(mobj_t *source);

View file

@ -8,6 +8,14 @@ void Obj_HyudoroThink(mobj_t *actor);
void Obj_HyudoroCenterThink(mobj_t *actor); void Obj_HyudoroCenterThink(mobj_t *actor);
void Obj_HyudoroCollide(mobj_t *special, mobj_t *toucher); void Obj_HyudoroCollide(mobj_t *special, mobj_t *toucher);
/* Garden Top */
void Obj_GardenTopDeploy(mobj_t *rider);
mobj_t *Obj_GardenTopThrow(player_t *player);
mobj_t *Obj_GardenTopDestroy(player_t *player);
void Obj_GardenTopThink(mobj_t *top);
void Obj_GardenTopSparkThink(mobj_t *spark);
boolean Obj_GardenTopPlayerIsGrinding(player_t *player);
/* Shrink */ /* Shrink */
void Obj_PohbeeThinker(mobj_t *pohbee); void Obj_PohbeeThinker(mobj_t *pohbee);
void Obj_PohbeeRemoved(mobj_t *pohbee); void Obj_PohbeeRemoved(mobj_t *pohbee);

View file

@ -1,4 +1,5 @@
hyudoro.c hyudoro.c
gardentop.c
shrink.c shrink.c
item-debris.c item-debris.c
spb.c spb.c

612
src/objects/gardentop.c Normal file
View file

@ -0,0 +1,612 @@
#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)
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
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) -= 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) += 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_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;
}
tilt(top);
P_MoveOrigin(top, rider->x, rider->y,
rider->z + K_FlipZOffset(top, rider));
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;
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);
const angle_t angle = top->angle + spark_angle(spark);
const fixed_t x = P_ReturnThrustX(top, angle, spark->scale);
const fixed_t y = P_ReturnThrustY(top, angle, spark->scale);
/* FIXME: THIS FUNCTION FUCKING SUCKS */
K_FlipFromObject(spark, top);
P_MoveOrigin(spark, top->x + x, top->y + y,
top->z + K_FlipZOffset(spark, top));
spark->angle = angle;
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)));
}
}
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);
}
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;
}
}
boolean
Obj_GardenTopPlayerIsGrinding (player_t *player)
{
mobj_t *top = K_GetGardenTop(player);
return top ? is_top_grinding(top) : false;
}

View file

@ -143,6 +143,7 @@ void Obj_OrbinautThink(mobj_t *th)
boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2)
{ {
boolean damageitem = false; boolean damageitem = false;
boolean tumbleitem = false;
boolean sprung = false; boolean sprung = false;
if ((orbinaut_selfdelay(t1) > 0 && t2->hitlag > 0) if ((orbinaut_selfdelay(t1) > 0 && t2->hitlag > 0)
@ -173,6 +174,11 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2)
return true; return true;
} }
if (t1->type == MT_GARDENTOP)
{
tumbleitem = true;
}
if (t2->player) if (t2->player)
{ {
if ((t2->player->flashing > 0 && t2->hitlag == 0) if ((t2->player->flashing > 0 && t2->hitlag == 0)
@ -190,7 +196,8 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2)
else else
{ {
// Player Damage // Player Damage
P_DamageMobj(t2, t1, t1->target, 1, DMG_WIPEOUT|DMG_WOMBO); P_DamageMobj(t2, t1, t1->target, 1, DMG_WOMBO |
(tumbleitem ? DMG_TUMBLE : DMG_WIPEOUT));
K_KartBouncing(t2, t1); K_KartBouncing(t2, t1);
S_StartSound(t2, sfx_s3k7b); S_StartSound(t2, sfx_s3k7b);
} }
@ -233,6 +240,11 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2)
damageitem = true; damageitem = true;
} }
if (t1->type == MT_GARDENTOP)
{
damageitem = false;
}
if (damageitem) if (damageitem)
{ {
// This Item Damage // This Item Damage

View file

@ -2014,6 +2014,12 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
player->emeralds = 0; player->emeralds = 0;
K_CheckEmeralds(source->player); K_CheckEmeralds(source->player);
} }
/* Drop "shield" immediately on contact. */
if (source->player->curshield == KSHIELD_TOP)
{
Obj_GardenTopDestroy(source->player);
}
} }
else else
{ {

View file

@ -866,6 +866,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
&& (tmthing->type == MT_ORBINAUT || tmthing->type == MT_JAWZ && (tmthing->type == MT_ORBINAUT || tmthing->type == MT_JAWZ
|| tmthing->type == MT_BANANA || tmthing->type == MT_EGGMANITEM || tmthing->type == MT_BALLHOG || tmthing->type == MT_BANANA || tmthing->type == MT_EGGMANITEM || tmthing->type == MT_BALLHOG
|| tmthing->type == MT_SSMINE || tmthing->type == MT_LANDMINE || tmthing->type == MT_SINK || tmthing->type == MT_SSMINE || tmthing->type == MT_LANDMINE || tmthing->type == MT_SINK
|| tmthing->type == MT_GARDENTOP
|| (tmthing->type == MT_PLAYER && thing->target != tmthing))) || (tmthing->type == MT_PLAYER && thing->target != tmthing)))
{ {
// see if it went over / under // see if it went over / under
@ -881,6 +882,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
&& (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ && (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ
|| thing->type == MT_BANANA || thing->type == MT_EGGMANITEM || thing->type == MT_BALLHOG || thing->type == MT_BANANA || thing->type == MT_EGGMANITEM || thing->type == MT_BALLHOG
|| thing->type == MT_SSMINE || tmthing->type == MT_LANDMINE || thing->type == MT_SINK || thing->type == MT_SSMINE || tmthing->type == MT_LANDMINE || thing->type == MT_SINK
|| thing->type == MT_GARDENTOP
|| (thing->type == MT_PLAYER && tmthing->target != thing))) || (thing->type == MT_PLAYER && tmthing->target != thing)))
{ {
// see if it went over / under // see if it went over / under
@ -901,6 +903,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
&& (tmthing->type == MT_ORBINAUT || tmthing->type == MT_JAWZ && (tmthing->type == MT_ORBINAUT || tmthing->type == MT_JAWZ
|| tmthing->type == MT_BANANA || tmthing->type == MT_EGGMANITEM || tmthing->type == MT_BALLHOG || tmthing->type == MT_BANANA || tmthing->type == MT_EGGMANITEM || tmthing->type == MT_BALLHOG
|| tmthing->type == MT_SSMINE || tmthing->type == MT_LANDMINE || tmthing->type == MT_SINK || tmthing->type == MT_SSMINE || tmthing->type == MT_LANDMINE || tmthing->type == MT_SINK
|| tmthing->type == MT_GARDENTOP
|| (tmthing->type == MT_PLAYER))) || (tmthing->type == MT_PLAYER)))
{ {
// see if it went over / under // see if it went over / under
@ -915,6 +918,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
&& (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ && (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ
|| thing->type == MT_BANANA || thing->type == MT_EGGMANITEM || thing->type == MT_BALLHOG || thing->type == MT_BANANA || thing->type == MT_EGGMANITEM || thing->type == MT_BALLHOG
|| thing->type == MT_SSMINE || tmthing->type == MT_LANDMINE || thing->type == MT_SINK || thing->type == MT_SSMINE || tmthing->type == MT_LANDMINE || thing->type == MT_SINK
|| thing->type == MT_GARDENTOP
|| (thing->type == MT_PLAYER))) || (thing->type == MT_PLAYER)))
{ {
// see if it went over / under // see if it went over / under
@ -932,7 +936,8 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
return BMIT_CONTINUE; return BMIT_CONTINUE;
if (tmthing->type == MT_ORBINAUT || tmthing->type == MT_JAWZ if (tmthing->type == MT_ORBINAUT || tmthing->type == MT_JAWZ
|| tmthing->type == MT_ORBINAUT_SHIELD || tmthing->type == MT_JAWZ_SHIELD) || tmthing->type == MT_ORBINAUT_SHIELD || tmthing->type == MT_JAWZ_SHIELD
|| tmthing->type == MT_GARDENTOP)
{ {
// see if it went over / under // see if it went over / under
if (tmthing->z > thing->z + thing->height) if (tmthing->z > thing->z + thing->height)
@ -943,7 +948,8 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
return Obj_OrbinautJawzCollide(tmthing, thing) ? BMIT_CONTINUE : BMIT_ABORT; return Obj_OrbinautJawzCollide(tmthing, thing) ? BMIT_CONTINUE : BMIT_ABORT;
} }
else if (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ else if (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ
|| thing->type == MT_ORBINAUT_SHIELD || thing->type == MT_JAWZ_SHIELD) || thing->type == MT_ORBINAUT_SHIELD || thing->type == MT_JAWZ_SHIELD
|| thing->type == MT_GARDENTOP)
{ {
// see if it went over / under // see if it went over / under
if (tmthing->z > thing->z + thing->height) if (tmthing->z > thing->z + thing->height)

View file

@ -1734,6 +1734,7 @@ void P_XYMovement(mobj_t *mo)
switch (mo->type) switch (mo->type)
{ {
case MT_ORBINAUT: // Orbinaut speed decreasing case MT_ORBINAUT: // Orbinaut speed decreasing
case MT_GARDENTOP:
if (mo->health > 1) if (mo->health > 1)
{ {
S_StartSound(mo, mo->info->attacksound); S_StartSound(mo, mo->info->attacksound);
@ -6324,6 +6325,7 @@ static boolean P_MobjDeadThink(mobj_t *mobj)
return false; return false;
} }
/* FALLTHRU */ /* FALLTHRU */
case MT_GARDENTOP:
case MT_ORBINAUT_SHIELD: case MT_ORBINAUT_SHIELD:
case MT_BANANA_SHIELD: case MT_BANANA_SHIELD:
case MT_EGGMANITEM_SHIELD: case MT_EGGMANITEM_SHIELD:
@ -7028,7 +7030,11 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
} }
} }
break; break;
case MT_TRIPWIREBOOST: case MT_TRIPWIREBOOST: {
mobj_t *top;
fixed_t newHeight;
fixed_t newScale;
if (!mobj->target || !mobj->target->health if (!mobj->target || !mobj->target->health
|| !mobj->target->player || !mobj->target->player->tripwireLeniency) || !mobj->target->player || !mobj->target->player->tripwireLeniency)
{ {
@ -7036,10 +7042,21 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
return false; return false;
} }
newHeight = mobj->target->height;
newScale = mobj->target->scale;
top = K_GetGardenTop(mobj->target->player);
if (top)
{
newHeight += 5 * top->height / 4;
newScale = FixedMul(newScale, FixedDiv(newHeight / 2, mobj->target->height));
}
mobj->angle = K_MomentumAngle(mobj->target); mobj->angle = K_MomentumAngle(mobj->target);
P_MoveOrigin(mobj, mobj->target->x, mobj->target->y, mobj->target->z + (mobj->target->height >> 1)); P_MoveOrigin(mobj, mobj->target->x, mobj->target->y, mobj->target->z + (newHeight / 2));
mobj->destscale = mobj->target->scale; mobj->destscale = newScale;
P_SetScale(mobj, mobj->target->scale); P_SetScale(mobj, newScale);
if (mobj->extravalue1) if (mobj->extravalue1)
{ {
@ -7111,6 +7128,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
} }
} }
break; break;
}
case MT_BOOSTFLAME: case MT_BOOSTFLAME:
if (!mobj->target || !mobj->target->health) if (!mobj->target || !mobj->target->health)
{ {
@ -7682,6 +7700,16 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
} }
break; break;
} }
case MT_GARDENTOP:
{
Obj_GardenTopThink(mobj);
break;
}
case MT_GARDENTOPSPARK:
{
Obj_GardenTopSparkThink(mobj);
break;
}
case MT_HYUDORO: case MT_HYUDORO:
{ {
Obj_HyudoroThink(mobj); Obj_HyudoroThink(mobj);
@ -9727,6 +9755,7 @@ static void P_DefaultMobjShadowScale(mobj_t *thing)
case MT_BUBBLESHIELD: case MT_BUBBLESHIELD:
case MT_BUBBLESHIELDTRAP: case MT_BUBBLESHIELDTRAP:
case MT_FLAMESHIELD: case MT_FLAMESHIELD:
case MT_GARDENTOP:
thing->shadowscale = FRACUNIT; thing->shadowscale = FRACUNIT;
break; break;
case MT_RING: case MT_RING:

View file

@ -3263,6 +3263,13 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
} }
} }
/* The Top is Big Large so zoom out */
if (player->curshield == KSHIELD_TOP)
{
camdist += 40 * mapobjectscale;
camheight += 40 * mapobjectscale;
}
if (!resetcalled && (leveltime >= introtime && timeover != 2) if (!resetcalled && (leveltime >= introtime && timeover != 2)
&& (t_cam_rotate[num] != -42)) && (t_cam_rotate[num] != -42))
{ {

View file

@ -42,6 +42,8 @@ static angle_t R_PlayerSpriteRotation(player_t *player, player_t *viewPlayer)
angle_t rollAngle = 0; angle_t rollAngle = 0;
mobj_t *top = K_GetGardenTop(player);
if (player->mo->eflags & MFE_UNDERWATER) if (player->mo->eflags & MFE_UNDERWATER)
{ {
rollAngle -= player->underwatertilt; rollAngle -= player->underwatertilt;
@ -61,6 +63,14 @@ static angle_t R_PlayerSpriteRotation(player_t *player, player_t *viewPlayer)
(17 / player->stairjank)); (17 / player->stairjank));
} }
if (top)
{
/* FIXME: why does it not look right at more acute
angles without this? There's a related hack to
spritexoffset in K_KartPlayerThink. */
rollAngle += 3 * (INT32)top->rollangle / 2;
}
return rollAngle; return rollAngle;
} }