#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; } 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 (player_t *player) { mobj_t *top = K_GetGardenTop(player); return top ? is_top_grinding(top) : false; }