diff --git a/src/d_player.h b/src/d_player.h index 9f310f1ed..fb4208d2c 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -290,6 +290,8 @@ typedef enum #define ITEMSCALE_GROW 1 #define ITEMSCALE_SHRINK 2 +#define GARDENTOP_MAXGRINDTIME (45) + // player_t struct for all respawn variables typedef struct respawnvars_s { diff --git a/src/k_kart.c b/src/k_kart.c index 4bafa7aa0..dee43b1a0 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -2414,7 +2414,7 @@ void K_SpawnDriftBoostClipSpark(mobj_t *clip) 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), player->mo->y + (P_RandomRange(PR_DECORATION,-36,36) * player->mo->scale), @@ -2422,20 +2422,40 @@ void K_SpawnNormalSpeedLines(player_t *player) MT_FASTLINE); P_SetTarget(&fast->target, player->mo); - P_InitAngle(fast, K_MomentumAngle(player->mo)); fast->momx = 3*player->mo->momx/4; fast->momy = 3*player->mo->momy/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_SetScale(fast, 3*fast->scale/2); + P_InitAngle(fast, player->mo->angle); + 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 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) { 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) { + mobj_t *top = mo->player ? K_GetGardenTop(mo->player) : NULL; + mobj_t *fx = P_SpawnMobj(mo->x, mo->y, mo->z, MT_BUMP); + if (mo->eflags & MFE_VERTICALFLIP) fx->eflags |= MFE_VERTICALFLIP; else fx->eflags &= ~MFE_VERTICALFLIP; + 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) @@ -2723,6 +2760,10 @@ void K_KartMoveAnimation(player_t *player) drift = intsign(player->aizdriftturn); turndir = 0; } + else if (player->curshield == KSHIELD_TOP) + { + drift = -turndir; + } else if (turndir == 0 && drift == 0) { // Only try glancing if you're driving straight. @@ -3288,6 +3329,46 @@ boolean K_WaterSkip(player_t *player) 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) { // 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) { 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->scalespeed = abs(th->destscale - th->scale) / (2*TICRATE); break; + case MT_GARDENTOP: + th->movefactor = finalspeed; + break; default: break; } @@ -5472,8 +5569,11 @@ void K_DriftDustHandling(mobj_t *spawner) dust->destscale = spawner->scale * 3; dust->scalespeed = spawner->scale/12; - if (leveltime % 6 == 0) - S_StartSound(spawner, sfx_screec); + if (!spawner->player || !K_GetGardenTop(spawner->player)) + { + if (leveltime % 6 == 0) + S_StartSound(spawner, sfx_screec); + } K_MatchGenericExtraFlags(dust, spawner); @@ -5633,7 +5733,7 @@ mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, if (missile) // Shootables { - if (dir < 0 && mapthing != MT_SPB) + if (dir < 0 && mapthing != MT_SPB && mapthing != MT_GARDENTOP) { // Shoot backward 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); ponground = P_IsObjectOnGround(player->mo); - if (shield != KSHIELD_NONE && !keepshields) + if (shield != KSHIELD_NONE && shield != KSHIELD_TOP && !keepshields) { if (shield == KSHIELD_LIGHTNING) { @@ -6296,6 +6396,9 @@ void K_DropHnextList(player_t *player, boolean keepshields) orbit = false; type = MT_EGGMANITEM; break; + case MT_GARDENTOP: + Obj_GardenTopDestroy(player); + return; // intentionally do nothing case MT_ROCKETSNEAKER: case MT_SINK_SHIELD: @@ -7602,6 +7705,33 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) { 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_UpdateDraft(player); K_UpdateEngineSounds(player); // Thanks, VAda! @@ -8049,8 +8179,20 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) 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. - if ((K_GetKartButtons(player) & BT_EBRAKEMASK) != BT_EBRAKEMASK) + else if ((K_GetKartButtons(player) & BT_EBRAKEMASK) != BT_EBRAKEMASK) { player->pflags |= PF_DRIFTINPUT; } @@ -8187,7 +8329,7 @@ void K_KartResetPlayerColor(player_t *player) finalise: - if (player->curshield) + if (player->curshield && player->curshield != KSHIELD_TOP) { fullbright = true; } @@ -9245,6 +9387,7 @@ void K_KartUpdatePosition(player_t *player) fixed_t position = 1; fixed_t oldposition = player->position; fixed_t i; + INT32 realplayers = 0; if (player->spectator || !player->mo) { @@ -9259,6 +9402,8 @@ void K_KartUpdatePosition(player_t *player) if (!playeringame[i] || players[i].spectator || !players[i].mo) continue; + realplayers++; + if (gametyperules & GTR_CIRCUIT) { if (player->exiting) // End of match standings @@ -9322,6 +9467,33 @@ void K_KartUpdatePosition(player_t *player) if (oldposition != position) // Changed places? 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; } @@ -9473,6 +9645,7 @@ void K_KartEbrakeVisuals(player_t *p) 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. K_FlipFromObject(p->mo->hprev, p->mo); + p->mo->hprev->sprzoff = p->mo->sprzoff; } if (!p->spindash) @@ -10490,6 +10663,37 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } } 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: if (player->curshield != KSHIELD_BUBBLE) { diff --git a/src/k_kart.h b/src/k_kart.h index 03ecb80b3..47e7f2006 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -69,6 +69,7 @@ void K_SpawnDashDustRelease(player_t *player); void K_SpawnDriftBoostClip(player_t *player); void K_SpawnDriftBoostClipSpark(mobj_t *clip); void K_SpawnNormalSpeedLines(player_t *player); +void K_SpawnGardenTopSpeedLines(player_t *player); void K_SpawnInvincibilitySpeedLines(mobj_t *mo); void K_SpawnBumpEffect(mobj_t *mo); 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_WaterRun(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); INT16 K_GetSpindashChargeTime(player_t *player); fixed_t K_GetSpindashChargeSpeed(player_t *player); @@ -170,6 +174,7 @@ UINT8 K_GetOrbinautItemFrame(UINT8 count); boolean K_IsSPBInGame(void); void K_KartEbrakeVisuals(player_t *p); void K_HandleDirectionalInfluence(player_t *player); +fixed_t K_DefaultPlayerRadius(player_t *player); // sound stuff for lua void K_PlayAttackTaunt(mobj_t *source); diff --git a/src/k_objects.h b/src/k_objects.h index 7679db658..b5b58b800 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -8,6 +8,14 @@ void Obj_HyudoroThink(mobj_t *actor); void Obj_HyudoroCenterThink(mobj_t *actor); 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 */ void Obj_PohbeeThinker(mobj_t *pohbee); void Obj_PohbeeRemoved(mobj_t *pohbee); diff --git a/src/objects/Sourcefile b/src/objects/Sourcefile index 339175b0c..044c9b576 100644 --- a/src/objects/Sourcefile +++ b/src/objects/Sourcefile @@ -1,4 +1,5 @@ hyudoro.c +gardentop.c shrink.c item-debris.c spb.c diff --git a/src/objects/gardentop.c b/src/objects/gardentop.c new file mode 100644 index 000000000..38154636f --- /dev/null +++ b/src/objects/gardentop.c @@ -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; +} diff --git a/src/objects/orbinaut.c b/src/objects/orbinaut.c index 0c04c4b67..ac209ec16 100644 --- a/src/objects/orbinaut.c +++ b/src/objects/orbinaut.c @@ -143,6 +143,7 @@ void Obj_OrbinautThink(mobj_t *th) boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) { boolean damageitem = false; + boolean tumbleitem = false; boolean sprung = false; if ((orbinaut_selfdelay(t1) > 0 && t2->hitlag > 0) @@ -173,6 +174,11 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) return true; } + if (t1->type == MT_GARDENTOP) + { + tumbleitem = true; + } + if (t2->player) { if ((t2->player->flashing > 0 && t2->hitlag == 0) @@ -190,7 +196,8 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) else { // 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); S_StartSound(t2, sfx_s3k7b); } @@ -233,6 +240,11 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) damageitem = true; } + if (t1->type == MT_GARDENTOP) + { + damageitem = false; + } + if (damageitem) { // This Item Damage diff --git a/src/p_inter.c b/src/p_inter.c index 19f03c664..c4e186949 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2014,6 +2014,12 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da player->emeralds = 0; K_CheckEmeralds(source->player); } + + /* Drop "shield" immediately on contact. */ + if (source->player->curshield == KSHIELD_TOP) + { + Obj_GardenTopDestroy(source->player); + } } else { diff --git a/src/p_map.c b/src/p_map.c index 62baa23e6..c2b688730 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -866,6 +866,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) && (tmthing->type == MT_ORBINAUT || tmthing->type == MT_JAWZ || 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_GARDENTOP || (tmthing->type == MT_PLAYER && thing->target != tmthing))) { // 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_BANANA || thing->type == MT_EGGMANITEM || thing->type == MT_BALLHOG || thing->type == MT_SSMINE || tmthing->type == MT_LANDMINE || thing->type == MT_SINK + || thing->type == MT_GARDENTOP || (thing->type == MT_PLAYER && tmthing->target != thing))) { // 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_BANANA || tmthing->type == MT_EGGMANITEM || tmthing->type == MT_BALLHOG || tmthing->type == MT_SSMINE || tmthing->type == MT_LANDMINE || tmthing->type == MT_SINK + || tmthing->type == MT_GARDENTOP || (tmthing->type == MT_PLAYER))) { // 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_BANANA || thing->type == MT_EGGMANITEM || thing->type == MT_BALLHOG || thing->type == MT_SSMINE || tmthing->type == MT_LANDMINE || thing->type == MT_SINK + || thing->type == MT_GARDENTOP || (thing->type == MT_PLAYER))) { // see if it went over / under @@ -932,7 +936,8 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return BMIT_CONTINUE; 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 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; } 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 if (tmthing->z > thing->z + thing->height) diff --git a/src/p_mobj.c b/src/p_mobj.c index 69c5c5c5d..cfbea3185 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1734,6 +1734,7 @@ void P_XYMovement(mobj_t *mo) switch (mo->type) { case MT_ORBINAUT: // Orbinaut speed decreasing + case MT_GARDENTOP: if (mo->health > 1) { S_StartSound(mo, mo->info->attacksound); @@ -6324,6 +6325,7 @@ static boolean P_MobjDeadThink(mobj_t *mobj) return false; } /* FALLTHRU */ + case MT_GARDENTOP: case MT_ORBINAUT_SHIELD: case MT_BANANA_SHIELD: case MT_EGGMANITEM_SHIELD: @@ -7028,7 +7030,11 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } } break; - case MT_TRIPWIREBOOST: + case MT_TRIPWIREBOOST: { + mobj_t *top; + fixed_t newHeight; + fixed_t newScale; + if (!mobj->target || !mobj->target->health || !mobj->target->player || !mobj->target->player->tripwireLeniency) { @@ -7036,10 +7042,21 @@ static boolean P_MobjRegularThink(mobj_t *mobj) 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); - P_MoveOrigin(mobj, mobj->target->x, mobj->target->y, mobj->target->z + (mobj->target->height >> 1)); - mobj->destscale = mobj->target->scale; - P_SetScale(mobj, mobj->target->scale); + P_MoveOrigin(mobj, mobj->target->x, mobj->target->y, mobj->target->z + (newHeight / 2)); + mobj->destscale = newScale; + P_SetScale(mobj, newScale); if (mobj->extravalue1) { @@ -7111,6 +7128,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } } break; + } case MT_BOOSTFLAME: if (!mobj->target || !mobj->target->health) { @@ -7682,6 +7700,16 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } break; } + case MT_GARDENTOP: + { + Obj_GardenTopThink(mobj); + break; + } + case MT_GARDENTOPSPARK: + { + Obj_GardenTopSparkThink(mobj); + break; + } case MT_HYUDORO: { Obj_HyudoroThink(mobj); @@ -9727,6 +9755,7 @@ static void P_DefaultMobjShadowScale(mobj_t *thing) case MT_BUBBLESHIELD: case MT_BUBBLESHIELDTRAP: case MT_FLAMESHIELD: + case MT_GARDENTOP: thing->shadowscale = FRACUNIT; break; case MT_RING: diff --git a/src/p_user.c b/src/p_user.c index 5261b84cb..8e03c1d43 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -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) && (t_cam_rotate[num] != -42)) { diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c index e2506baa5..d3a844396 100644 --- a/src/r_patchrotation.c +++ b/src/r_patchrotation.c @@ -42,6 +42,8 @@ static angle_t R_PlayerSpriteRotation(player_t *player, player_t *viewPlayer) angle_t rollAngle = 0; + mobj_t *top = K_GetGardenTop(player); + if (player->mo->eflags & MFE_UNDERWATER) { rollAngle -= player->underwatertilt; @@ -61,6 +63,14 @@ static angle_t R_PlayerSpriteRotation(player_t *player, player_t *viewPlayer) (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; }