diff --git a/src/d_player.h b/src/d_player.h index 132ec37d8..62c30d5f5 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -169,6 +169,7 @@ typedef enum CR_ZOOMTUBE, CR_DASHRING, CR_TRAPBUBBLE, + CR_MUSHROOMHILLPOLE, } carrytype_t; // carry /* diff --git a/src/deh_tables.c b/src/deh_tables.c index 1f7a499c9..21e950abc 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -3125,6 +3125,8 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_ANCIENTGEAR", "S_ANCIENTGEAR_PART", + + "S_MHPOLE", }; // RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1", @@ -4044,6 +4046,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_ANCIENTGEAR", "MT_ANCIENTGEAR_PART", + + "MT_MHPOLE", }; const char *const MOBJFLAG_LIST[] = { @@ -4778,6 +4782,7 @@ struct int_const_s const INT_CONST[] = { {"CR_ZOOMTUBE",CR_ZOOMTUBE}, {"CR_DASHRING",CR_DASHRING}, {"CR_TRAPBUBBLE",CR_TRAPBUBBLE}, + {"CR_MUSHROOMHILLPOLE", CR_MUSHROOMHILLPOLE}, // Character flags (skinflags_t) {"SF_MACHINE",SF_MACHINE}, diff --git a/src/info.c b/src/info.c index 381efaf67..dca797a0e 100644 --- a/src/info.c +++ b/src/info.c @@ -815,6 +815,8 @@ char sprnames[NUMSPRITES + 1][5] = "GEAR", + "MHPL", + // Pulley "HCCH", "HCHK", @@ -3719,6 +3721,8 @@ state_t states[NUMSTATES] = {SPR_GEAR, 0, -1, {NULL}, 0, 0, S_NULL}, // S_ANCIENTGEAR {SPR_GEAR, FF_PAPERSPRITE|1, -1, {NULL}, 0, 0, S_NULL}, // S_ANCIENTGEAR_PART + + {SPR_MHPL, 0, -1, {NULL}, 0, 0, S_NULL}, // S_MHPOLE }; mobjinfo_t mobjinfo[NUMMOBJTYPES] = @@ -22938,6 +22942,32 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = MF_SCENERY|MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP|MF_NOSQUISH|MF_DRAWFROMFARAWAY, // flags S_NULL // raisestate }, + { // MT_MHPOLE + 3850, // doomednum + S_MHPOLE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_s3k4a, // seesound + 15, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 8*FRACUNIT, // speed + 20*FRACUNIT, // radius + 128*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_s3kb6, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, }; diff --git a/src/info.h b/src/info.h index c7fec5cad..fc27b881c 100644 --- a/src/info.h +++ b/src/info.h @@ -1352,6 +1352,8 @@ typedef enum sprite SPR_GEAR, + SPR_MHPL, + // Pulley SPR_HCCH, SPR_HCHK, @@ -4203,6 +4205,8 @@ typedef enum state S_ANCIENTGEAR, S_ANCIENTGEAR_PART, + S_MHPOLE, + S_FIRSTFREESLOT, S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1, NUMSTATES @@ -5145,6 +5149,8 @@ typedef enum mobj_type MT_ANCIENTGEAR, MT_ANCIENTGEAR_PART, + MT_MHPOLE, + MT_FIRSTFREESLOT, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, NUMMOBJTYPES diff --git a/src/k_kart.c b/src/k_kart.c index 35c0da86c..e94c612e9 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -9694,6 +9694,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) K_UpdateEngineSounds(player); // Thanks, VAda! Obj_DashRingPlayerThink(player); + Obj_MushroomHillPolePlayerThink(player); // update boost angle if not spun out if (!player->spinouttimer && !player->wipeoutslow) diff --git a/src/k_objects.h b/src/k_objects.h index b7719a3ab..3ddbc5ab2 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -501,6 +501,10 @@ void Obj_AncientGearLevelInit(void); player_t *Obj_GetAncientGearCollectingPlayer(void); boolean Obj_AllAncientGearsCollected(void); +void Obj_MushroomHillPolePlayerThink(player_t *player); +void Obj_MushroomHillPoleTouch(mobj_t *pole, mobj_t *toucher); +void Obj_MushroomHillPoleFuse(mobj_t *pole); + #ifdef __cplusplus } // extern "C" diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index a5c069005..1af0f0839 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -69,6 +69,7 @@ target_sources(SRB2SDL2 PRIVATE bail.c toxomister.cpp ancient-gear.c + mushroom-hill-pole.c ) add_subdirectory(versus) diff --git a/src/objects/mushroom-hill-pole.c b/src/objects/mushroom-hill-pole.c new file mode 100644 index 000000000..d5b9fba28 --- /dev/null +++ b/src/objects/mushroom-hill-pole.c @@ -0,0 +1,127 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Lachlan "Lach" Wright +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file mushroom-hill-pole.c +/// \brief Mushroom Hill Pole object code. + +#include "../p_local.h" +#include "../k_objects.h" +#include "../s_sound.h" +#include "../k_kart.h" +#include "../m_easing.h" +#include "../r_main.h" + +// where 0 = fully facing angle, FRACUNIT = fully momentum angle +#define MOMENTUM_ANGLE_PROPORTION (FRACUNIT/2) +#define MOMENTUM_MULTIPLIER (FRACUNIT * 5 / 2) +#define MINIMUM_SPIN_DEGREES (360 + 180) + +static const fixed_t PI = 355 * FRACUNIT / 113; + +static void MushroomHillPolePlayerThink(player_t *player) +{ + mobj_t *mo = player->mo; + mobj_t *pole = mo->tracer; + fixed_t radius, circumference; + angle_t deltaAngle; + + radius = mo->radius + pole->radius; // distance from the pole to spin from + circumference = 2 * FixedMul(PI, radius); + deltaAngle = FixedAngle(FixedMul(FixedDiv(pole->movefactor, circumference), 360 * FRACUNIT)); // change in angle for this tic + + if ((pole->movecount -= AngleFixed(deltaAngle)) > 0) // continue spinning as long as we have degrees remaining + { + deltaAngle *= pole->extravalue1; + pole->movedir += deltaAngle; + + P_InstaThrust(mo, pole->movedir + pole->extravalue1 * ANGLE_90, pole->movefactor); // force the player's momentum so effects like drift sparks look cool + P_MoveOrigin(mo, + pole->x + P_ReturnThrustX(NULL, pole->movedir, radius) - mo->momx, + pole->y + P_ReturnThrustY(NULL, pole->movedir, radius) - mo->momy, + mo->z + ); + P_SetPlayerAngle(player, player->angleturn + deltaAngle); + } + else // RELEASE!!!!!!!!!! + { + P_SetTarget(&pole->target, NULL); + P_SetTarget(&pole->tracer, mo); // track this player so that they can't interact with this pole again... + pole->fuse = pole->reactiontime; /// ...for this many tics + + P_SetPlayerAngle(player, (angle_t)pole->extravalue2); // restore camera angle + P_InstaThrust(mo, pole->angle, FixedMul(pole->movefactor, MOMENTUM_MULTIPLIER)); // launch at launch angle + S_StartSound(mo, pole->info->activesound); + P_SetTarget(&mo->tracer, NULL); + player->carry = CR_NONE; + } +} + +void Obj_MushroomHillPolePlayerThink(player_t *player) +{ + // not carried by a pole? + if (player->carry != CR_MUSHROOMHILLPOLE) + { + return; + } + + // pole mysteriously vanquished by eldritch addon setups? + if (P_MobjWasRemoved(player->mo->tracer)) + { + P_SetTarget(&player->mo->tracer, NULL); + player->carry = CR_NONE; + return; + } + + MushroomHillPolePlayerThink(player); +} + +void Obj_MushroomHillPoleTouch(mobj_t *pole, mobj_t *toucher) +{ + player_t *player = toucher->player; + angle_t momentumAngle; + + if ( + player->carry != CR_NONE // player is already being carried by something else + || pole->tracer == toucher // pole just launched this player + || ( + !P_MobjWasRemoved(pole->target) + && pole->target->player + && pole->target->player->carry == CR_MUSHROOMHILLPOLE + ) // pole is already occupied by a player + ) + { + return; + } + + momentumAngle = K_MomentumAngle(toucher); + + P_SetTarget(&pole->target, toucher); + pole->movefactor = max(FixedHypot(toucher->momx, toucher->momy), FixedMul(pole->info->speed, pole->scale)); // speed at which to spin around the pole + pole->movedir = R_PointToAngle2(pole->x, pole->y, toucher->x, toucher->y); // angle at which to project the player from the pole + pole->angle = toucher->angle + Easing_Linear(MOMENTUM_ANGLE_PROPORTION, 0, (INT32)(momentumAngle - toucher->angle)); // final launch angle + pole->extravalue1 = (pole->movedir - momentumAngle < ANGLE_180) ? -1 : 1; // direction to spin around the pole + pole->extravalue2 = (INT32)player->angleturn; // player's old angle, to restore upon launch + pole->movecount = AngleFixed(pole->extravalue1 * (pole->angle - pole->movedir) - ANGLE_90); // fixed-scale number of degrees to spin around the pole + + while (pole->movecount < MINIMUM_SPIN_DEGREES * FRACUNIT) + { + pole->movecount += 360 * FRACUNIT; + } + + P_SetTarget(&toucher->tracer, pole); + player->carry = CR_MUSHROOMHILLPOLE; + S_StartSound(toucher, pole->info->seesound); + + MushroomHillPolePlayerThink(player); // start spinning immediately +} + +void Obj_MushroomHillPoleFuse(mobj_t *pole) +{ + P_SetTarget(&pole->tracer, NULL); +} diff --git a/src/p_inter.c b/src/p_inter.c index 1d580e371..ac8438298 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1141,6 +1141,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) Obj_AncientGearTouch(special, toucher); return; + case MT_MHPOLE: + Obj_MushroomHillPoleTouch(special, toucher); + return; + default: // SOC or script pickup P_SetTarget(&special->target, toucher); break; diff --git a/src/p_mobj.c b/src/p_mobj.c index a5d42fe2d..1ac671d73 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -10635,6 +10635,11 @@ static boolean P_FuseThink(mobj_t *mobj) Obj_SneakerPanelSpawnerFuse(mobj); break; } + case MT_MHPOLE: + { + Obj_MushroomHillPoleFuse(mobj); + break; + } case MT_PLAYER: break; // don't remove default: @@ -11191,6 +11196,8 @@ fixed_t P_GetMobjDefaultScale(mobj_t *mobj) return 2*FRACUNIT; case MT_ANCIENTGEAR: return 3*FRACUNIT/2; + case MT_MHPOLE: + return 4*FRACUNIT; default: break; } diff --git a/src/p_user.c b/src/p_user.c index 353645ae1..6eb62efda 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -3417,7 +3417,10 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall lookback = K_GetKartButtons(player) & BT_LOOKBACK; camspeed = cv_cam_speed[num].value; - camstill = cv_cam_still[num].value || player->seasaw; // RR: seasaws lock the camera so that it isn't disorienting. + camstill = cv_cam_still[num].value + || player->seasaw // RR: seasaws lock the camera so that it isn't disorienting. + || player->carry == CR_MUSHROOMHILLPOLE + ; camrotate = cv_cam_rotate[num].value; camdist = FixedMul(cv_cam_dist[num].value, cameraScale); camheight = FixedMul(cv_cam_height[num].value, cameraScale);