diff --git a/src/d_netcmd.c b/src/d_netcmd.c index a3e9cab44..58a17204b 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -362,7 +362,6 @@ consvar_t cv_joyscale[MAXSPLITSCREENPLAYERS] = { //Alam: Dummy for save #endif // SRB2kart -consvar_t cv_superring = CVAR_INIT ("superring", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_sneaker = CVAR_INIT ("sneaker", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_rocketsneaker = CVAR_INIT ("rocketsneaker", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_invincibility = CVAR_INIT ("invincibility", "On", CV_NETVAR, CV_OnOff, NULL); @@ -372,7 +371,6 @@ consvar_t cv_orbinaut = CVAR_INIT ("orbinaut", "On", CV_NETVAR, CV_OnOff, consvar_t cv_jawz = CVAR_INIT ("jawz", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_mine = CVAR_INIT ("mine", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_landmine = CVAR_INIT ("landmine", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_droptarget = CVAR_INIT ("droptarget", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_ballhog = CVAR_INIT ("ballhog", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_selfpropelledbomb = CVAR_INIT ("selfpropelledbomb", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_grow = CVAR_INIT ("grow", "On", CV_NETVAR, CV_OnOff, NULL); @@ -382,7 +380,10 @@ consvar_t cv_bubbleshield = CVAR_INIT ("bubbleshield", "On", CV_NETVAR, CV_O consvar_t cv_flameshield = CVAR_INIT ("flameshield", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_hyudoro = CVAR_INIT ("hyudoro", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_pogospring = CVAR_INIT ("pogospring", "On", CV_NETVAR, CV_OnOff, NULL); +consvar_t cv_superring = CVAR_INIT ("superring", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_kitchensink = CVAR_INIT ("kitchensink", "On", CV_NETVAR, CV_OnOff, NULL); +consvar_t cv_droptarget = CVAR_INIT ("droptarget", "On", CV_NETVAR, CV_OnOff, NULL); +consvar_t cv_gardentop = CVAR_INIT ("gardentop", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_dualsneaker = CVAR_INIT ("dualsneaker", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_triplesneaker = CVAR_INIT ("triplesneaker", "On", CV_NETVAR, CV_OnOff, NULL); diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 6851163cb..55c806b4c 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -72,14 +72,38 @@ extern consvar_t cv_pause; extern consvar_t cv_restrictskinchange, cv_allowteamchange, cv_maxplayers, cv_respawntime; // SRB2kart items -extern consvar_t cv_superring, cv_sneaker, cv_rocketsneaker, cv_invincibility, cv_banana; -extern consvar_t cv_eggmanmonitor, cv_orbinaut, cv_jawz, cv_mine, cv_landmine, cv_droptarget; -extern consvar_t cv_ballhog, cv_selfpropelledbomb, cv_grow, cv_shrink; -extern consvar_t cv_lightningshield, cv_bubbleshield, cv_flameshield; -extern consvar_t cv_hyudoro, cv_pogospring, cv_kitchensink; +extern consvar_t + cv_sneaker, + cv_rocketsneaker, + cv_invincibility, + cv_banana, + cv_eggmanmonitor, + cv_orbinaut, + cv_jawz, + cv_mine, + cv_landmine, + cv_ballhog, + cv_selfpropelledbomb, + cv_grow, + cv_shrink, + cv_lightningshield, + cv_bubbleshield, + cv_flameshield, + cv_hyudoro, + cv_pogospring, + cv_superring, + cv_kitchensink, + cv_droptarget, + cv_gardentop; -extern consvar_t cv_dualsneaker, cv_triplesneaker, cv_triplebanana, cv_decabanana; -extern consvar_t cv_tripleorbinaut, cv_quadorbinaut, cv_dualjawz; +extern consvar_t + cv_dualsneaker, + cv_triplesneaker, + cv_triplebanana, + cv_decabanana, + cv_tripleorbinaut, + cv_quadorbinaut, + cv_dualjawz; extern consvar_t cv_kartminimap; extern consvar_t cv_kartcheck; diff --git a/src/d_player.h b/src/d_player.h index f66493a4e..930803b3f 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -159,7 +159,8 @@ Run this macro, then #undef FOREACH afterward FOREACH (POGOSPRING, 18),\ FOREACH (SUPERRING, 19),\ FOREACH (KITCHENSINK, 20),\ - FOREACH (DROPTARGET, 21) + FOREACH (DROPTARGET, 21),\ + FOREACH (GARDENTOP, 22) typedef enum { @@ -187,6 +188,7 @@ typedef enum KSHIELD_LIGHTNING = 1, KSHIELD_BUBBLE = 2, KSHIELD_FLAME = 3, + KSHIELD_TOP = 4, NUMKARTSHIELDS } kartshields_t; @@ -288,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 { @@ -598,6 +602,8 @@ typedef struct player_s UINT8 kickstartaccel; UINT8 stairjank; + UINT8 topdriftheld; + UINT8 topinfirst; UINT8 shrinkLaserDelay; diff --git a/src/deh_tables.c b/src/deh_tables.c index be283371e..391353393 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -3740,6 +3740,14 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_FLAMESHIELDLINE3", "S_FLAMESHIELDFLASH", + // Marble Garden Zone Spinning Top + "S_GARDENTOP_FLOATING", + "S_GARDENTOP_SINKING1", + "S_GARDENTOP_SINKING2", + "S_GARDENTOP_SINKING3", + "S_GARDENTOP_DEAD", + "S_GARDENTOPSPARK", + // Caked-Up Booty-Sheet Ghost "S_HYUDORO", @@ -5346,6 +5354,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_FLAMESHIELDUNDERLAY", "MT_FLAMESHIELDPAPER", "MT_BUBBLESHIELDTRAP", + "MT_GARDENTOP", + "MT_GARDENTOPSPARK", "MT_HYUDORO", "MT_HYUDORO_CENTER", @@ -6668,6 +6678,7 @@ struct int_const_s const INT_CONST[] = { {"KSHIELD_LIGHTNING",KSHIELD_LIGHTNING}, {"KSHIELD_BUBBLE",KSHIELD_BUBBLE}, {"KSHIELD_FLAME",KSHIELD_FLAME}, + {"KSHIELD_TOP",KSHIELD_TOP}, {"NUMKARTSHIELDS",NUMKARTSHIELDS}, // kartspinoutflags_t diff --git a/src/info.c b/src/info.c index a5fe03bf2..42abd5b2e 100644 --- a/src/info.c +++ b/src/info.c @@ -4320,6 +4320,13 @@ state_t states[NUMSTATES] = {SPR_FLML, FF_FULLBRIGHT|FF_PAPERSPRITE|FF_ANIMATE|14, 7, {NULL}, 6, 1, S_NULL}, // S_FLAMESHIELDLINE3 {SPR_FLMF, FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_FLAMESHIELDFLASH + {SPR_GTOP, FF_ANIMATE, -1, {NULL}, 5, 1, S_NULL}, // S_GARDENTOP_FLOATING + {SPR_GTOP, 0, 1, {NULL}, 5, 1, S_GARDENTOP_SINKING2}, // S_GARDENTOP_SINKING1 + {SPR_GTOP, 2, 1, {NULL}, 5, 1, S_GARDENTOP_SINKING3}, // S_GARDENTOP_SINKING2 + {SPR_GTOP, 4, 1, {NULL}, 5, 1, S_GARDENTOP_SINKING1}, // S_GARDENTOP_SINKING3 + {SPR_GTOP, FF_ANIMATE, 100, {A_Scream}, 5, 1, S_NULL}, // S_GARDENTOP_DEAD + {SPR_BDRF, FF_FULLBRIGHT|FF_PAPERSPRITE|FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 5, 2, S_NULL}, // S_GARDENTOPSPARK + {SPR_HYUU, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_HYUDORO {SPR_GRWP, FF_FULLBRIGHT|FF_ANIMATE, 13, {NULL}, 7, 1, S_NULL}, // S_GROW_PARTICLE @@ -24065,6 +24072,60 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_GARDENTOP + -1, // doomednum + S_GARDENTOP_FLOATING, // spawnstate + 8, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 4, // reactiontime + sfx_s3k8b, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_GARDENTOP_DEAD, // deathstate + S_NULL, // xdeathstate + sfx_s3k7a, // deathsound + 40*FRACUNIT, // speed + 30*FRACUNIT, // radius + 68*FRACUNIT, // height + -1, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOCLIPTHING|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + + { // MT_GARDENTOPSPARK + -1, // doomednum + S_GARDENTOPSPARK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // 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, // speed + 8*FRACUNIT, // radius + 8*FRACUNIT, // height + 1, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP|MF_NOSQUISH, // flags + S_NULL // raisestate + }, + { // MT_HYUDORO -1, // doomednum S_HYUDORO, // spawnstate diff --git a/src/info.h b/src/info.h index 29e2695bf..48850f5d0 100644 --- a/src/info.h +++ b/src/info.h @@ -4750,6 +4750,14 @@ typedef enum state S_FLAMESHIELDLINE3, S_FLAMESHIELDFLASH, + // Marble Garden Zone Spinning Top + S_GARDENTOP_FLOATING, + S_GARDENTOP_SINKING1, + S_GARDENTOP_SINKING2, + S_GARDENTOP_SINKING3, + S_GARDENTOP_DEAD, + S_GARDENTOPSPARK, + // Caked-Up Booty-Sheet Ghost S_HYUDORO, @@ -6392,6 +6400,8 @@ typedef enum mobj_type MT_FLAMESHIELDUNDERLAY, MT_FLAMESHIELDPAPER, MT_BUBBLESHIELDTRAP, + MT_GARDENTOP, + MT_GARDENTOPSPARK, MT_HYUDORO, MT_HYUDORO_CENTER, diff --git a/src/k_collide.c b/src/k_collide.c index e4a536403..978993339 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -66,17 +66,22 @@ boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2) if (t1->type == MT_BANANA && t1->health > 1) S_StartSound(t2, sfx_bsnipe); + damageitem = true; + if (t2->player->flamedash && t2->player->itemtype == KITEM_FLAMESHIELD) { // Melt item S_StartSound(t2, sfx_s3k43); } + else if (K_IsRidingFloatingTop(t2->player)) + { + // Float over silly banana + damageitem = false; + } else { P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL|DMG_WOMBO); } - - damageitem = true; } else if (t2->type == MT_BANANA || t2->type == MT_BANANA_SHIELD || t2->type == MT_ORBINAUT || t2->type == MT_ORBINAUT_SHIELD @@ -774,11 +779,13 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) // Clash instead of damage if both parties have any of these conditions t1Condition = (K_IsBigger(t1, t2) == true) || (t1->player->invincibilitytimer > 0) - || (t1->player->flamedash > 0 && t1->player->itemtype == KITEM_FLAMESHIELD); + || (t1->player->flamedash > 0 && t1->player->itemtype == KITEM_FLAMESHIELD) + || (t1->player->curshield == KSHIELD_TOP && !K_IsHoldingDownTop(t1->player)); t2Condition = (K_IsBigger(t2, t1) == true) || (t2->player->invincibilitytimer > 0) - || (t2->player->flamedash > 0 && t2->player->itemtype == KITEM_FLAMESHIELD); + || (t2->player->flamedash > 0 && t2->player->itemtype == KITEM_FLAMESHIELD) + || (t2->player->curshield == KSHIELD_TOP && !K_IsHoldingDownTop(t2->player)); if (t1Condition == true && t2Condition == true) { diff --git a/src/k_hud.c b/src/k_hud.c index 14e526dd2..6b4703d2b 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -111,7 +111,7 @@ static patch_t *kp_itemtimer[2]; static patch_t *kp_itemmulsticker[2]; static patch_t *kp_itemx; -static patch_t *kp_superring[2]; +static patch_t *kp_sadface[2]; static patch_t *kp_sneaker[2]; static patch_t *kp_rocketsneaker[2]; static patch_t *kp_invincibility[13]; @@ -121,7 +121,6 @@ static patch_t *kp_orbinaut[5]; static patch_t *kp_jawz[2]; static patch_t *kp_mine[2]; static patch_t *kp_landmine[2]; -static patch_t *kp_droptarget[2]; static patch_t *kp_ballhog[2]; static patch_t *kp_selfpropelledbomb[2]; static patch_t *kp_grow[2]; @@ -131,8 +130,10 @@ static patch_t *kp_bubbleshield[2]; static patch_t *kp_flameshield[2]; static patch_t *kp_hyudoro[2]; static patch_t *kp_pogospring[2]; +static patch_t *kp_superring[2]; static patch_t *kp_kitchensink[2]; -static patch_t *kp_sadface[2]; +static patch_t *kp_droptarget[2]; +static patch_t *kp_gardentop[2]; static patch_t *kp_check[6]; @@ -390,7 +391,7 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_itemmulsticker[0], "K_ITMUL"); HU_UpdatePatch(&kp_itemx, "K_ITX"); - HU_UpdatePatch(&kp_superring[0], "K_ITRING"); + HU_UpdatePatch(&kp_sadface[0], "K_ITSAD"); HU_UpdatePatch(&kp_sneaker[0], "K_ITSHOE"); HU_UpdatePatch(&kp_rocketsneaker[0], "K_ITRSHE"); @@ -411,7 +412,6 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_jawz[0], "K_ITJAWZ"); HU_UpdatePatch(&kp_mine[0], "K_ITMINE"); HU_UpdatePatch(&kp_landmine[0], "K_ITLNDM"); - HU_UpdatePatch(&kp_droptarget[0], "K_ITDTRG"); HU_UpdatePatch(&kp_ballhog[0], "K_ITBHOG"); HU_UpdatePatch(&kp_selfpropelledbomb[0], "K_ITSPB"); HU_UpdatePatch(&kp_grow[0], "K_ITGROW"); @@ -421,8 +421,10 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_flameshield[0], "K_ITFLMS"); HU_UpdatePatch(&kp_hyudoro[0], "K_ITHYUD"); HU_UpdatePatch(&kp_pogospring[0], "K_ITPOGO"); + HU_UpdatePatch(&kp_superring[0], "K_ITRING"); HU_UpdatePatch(&kp_kitchensink[0], "K_ITSINK"); - HU_UpdatePatch(&kp_sadface[0], "K_ITSAD"); + HU_UpdatePatch(&kp_droptarget[0], "K_ITDTRG"); + HU_UpdatePatch(&kp_gardentop[0], "K_ITGTOP"); sprintf(buffer, "FSMFGxxx"); for (i = 0; i < 104; i++) @@ -447,7 +449,7 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_itemtimer[1], "K_ISIMER"); HU_UpdatePatch(&kp_itemmulsticker[1], "K_ISMUL"); - HU_UpdatePatch(&kp_superring[1], "K_ISRING"); + HU_UpdatePatch(&kp_sadface[1], "K_ISSAD"); HU_UpdatePatch(&kp_sneaker[1], "K_ISSHOE"); HU_UpdatePatch(&kp_rocketsneaker[1], "K_ISRSHE"); sprintf(buffer, "K_ISINVx"); @@ -462,7 +464,6 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_jawz[1], "K_ISJAWZ"); HU_UpdatePatch(&kp_mine[1], "K_ISMINE"); HU_UpdatePatch(&kp_landmine[1], "K_ISLNDM"); - HU_UpdatePatch(&kp_droptarget[1], "K_ISDTRG"); HU_UpdatePatch(&kp_ballhog[1], "K_ISBHOG"); HU_UpdatePatch(&kp_selfpropelledbomb[1], "K_ISSPB"); HU_UpdatePatch(&kp_grow[1], "K_ISGROW"); @@ -472,8 +473,10 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_flameshield[1], "K_ISFLMS"); HU_UpdatePatch(&kp_hyudoro[1], "K_ISHYUD"); HU_UpdatePatch(&kp_pogospring[1], "K_ISPOGO"); + HU_UpdatePatch(&kp_superring[1], "K_ISRING"); HU_UpdatePatch(&kp_kitchensink[1], "K_ISSINK"); - HU_UpdatePatch(&kp_sadface[1], "K_ISSAD"); + HU_UpdatePatch(&kp_droptarget[1], "K_ISDTRG"); + HU_UpdatePatch(&kp_gardentop[1], "K_ISGTOP"); sprintf(buffer, "FSMFSxxx"); for (i = 0; i < 104; i++) @@ -662,8 +665,6 @@ const char *K_GetItemPatch(UINT8 item, boolean tiny) return (tiny ? "K_ISMINE" : "K_ITMINE"); case KITEM_LANDMINE: return (tiny ? "K_ISLNDM" : "K_ITLNDM"); - case KITEM_DROPTARGET: - return (tiny ? "K_ISDTRG" : "K_ITDTRG"); case KITEM_BALLHOG: return (tiny ? "K_ISBHOG" : "K_ITBHOG"); case KITEM_SPB: @@ -686,6 +687,10 @@ const char *K_GetItemPatch(UINT8 item, boolean tiny) return (tiny ? "K_ISRING" : "K_ITRING"); case KITEM_KITCHENSINK: return (tiny ? "K_ISSINK" : "K_ITSINK"); + case KITEM_DROPTARGET: + return (tiny ? "K_ISDTRG" : "K_ITDTRG"); + case KITEM_GARDENTOP: + return (tiny ? "K_ISGTOP" : "K_ITGTOP"); case KRITEM_TRIPLEORBINAUT: return (tiny ? "K_ISORBN" : "K_ITORB3"); case KRITEM_QUADORBINAUT: @@ -721,6 +726,7 @@ static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset) kp_superring, kp_kitchensink, kp_droptarget, + kp_gardentop, }; if (item == KITEM_SAD || (item > KITEM_NONE && item < NUMKARTITEMS)) @@ -4456,6 +4462,7 @@ static void K_drawDistributionDebugger(void) kp_superring[1], kp_kitchensink[1], kp_droptarget[1], + kp_gardentop[1], kp_sneaker[1], kp_sneaker[1], diff --git a/src/k_kart.c b/src/k_kart.c index a10386980..c7559d558 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -218,7 +218,6 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_jawz); CV_RegisterVar(&cv_mine); CV_RegisterVar(&cv_landmine); - CV_RegisterVar(&cv_droptarget); CV_RegisterVar(&cv_ballhog); CV_RegisterVar(&cv_selfpropelledbomb); CV_RegisterVar(&cv_grow); @@ -230,6 +229,8 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_pogospring); CV_RegisterVar(&cv_superring); CV_RegisterVar(&cv_kitchensink); + CV_RegisterVar(&cv_droptarget); + CV_RegisterVar(&cv_gardentop); CV_RegisterVar(&cv_dualsneaker); CV_RegisterVar(&cv_triplesneaker); @@ -336,6 +337,7 @@ consvar_t *KartItemCVars[NUMKARTRESULTS-1] = &cv_superring, &cv_kitchensink, &cv_droptarget, + &cv_gardentop, &cv_dualsneaker, &cv_triplesneaker, &cv_triplebanana, @@ -350,68 +352,70 @@ consvar_t *KartItemCVars[NUMKARTRESULTS-1] = // Less ugly 2D arrays static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = { - //P-Odds 0 1 2 3 4 5 6 7 - /*Sneaker*/ { 0, 0, 2, 4, 6, 0, 0, 0 }, // Sneaker - /*Rocket Sneaker*/ { 0, 0, 0, 0, 0, 2, 4, 6 }, // Rocket Sneaker - /*Invincibility*/ { 0, 0, 0, 0, 3, 4, 5, 7 }, // Invincibility - /*Banana*/ { 2, 3, 1, 0, 0, 0, 0, 0 }, // Banana - /*Eggman Monitor*/ { 1, 2, 0, 0, 0, 0, 0, 0 }, // Eggman Monitor - /*Orbinaut*/ { 5, 5, 2, 2, 0, 0, 0, 0 }, // Orbinaut - /*Jawz*/ { 0, 4, 2, 1, 0, 0, 0, 0 }, // Jawz - /*Mine*/ { 0, 3, 3, 1, 0, 0, 0, 0 }, // Mine - /*Land Mine*/ { 3, 0, 0, 0, 0, 0, 0, 0 }, // Land Mine - /*Ballhog*/ { 0, 0, 2, 2, 0, 0, 0, 0 }, // Ballhog - /*Self-Propelled Bomb*/ { 0, 0, 0, 0, 0, 0, 0, 0 }, // Self-Propelled Bomb - /*Grow*/ { 0, 0, 0, 1, 2, 3, 0, 0 }, // Grow - /*Shrink*/ { 0, 0, 0, 0, 0, 1, 3, 2 }, // Shrink - /*Lightning Shield*/ { 1, 0, 0, 0, 0, 0, 0, 0 }, // Lightning Shield - /*Bubble Shield*/ { 0, 1, 2, 1, 0, 0, 0, 0 }, // Bubble Shield - /*Flame Shield*/ { 0, 0, 0, 0, 0, 1, 3, 5 }, // Flame Shield - /*Hyudoro*/ { 3, 0, 0, 0, 0, 0, 0, 0 }, // Hyudoro - /*Pogo Spring*/ { 0, 0, 0, 0, 0, 0, 0, 0 }, // Pogo Spring - /*Super Ring*/ { 2, 1, 1, 0, 0, 0, 0, 0 }, // Super Ring - /*Kitchen Sink*/ { 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink - /*Drop Target*/ { 3, 0, 0, 0, 0, 0, 0, 0 }, // Drop Target - /*Sneaker x2*/ { 0, 0, 2, 2, 2, 0, 0, 0 }, // Sneaker x2 - /*Sneaker x3*/ { 0, 0, 0, 1, 6, 9, 5, 0 }, // Sneaker x3 - /*Banana x3*/ { 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3 - /*Banana x10*/ { 0, 0, 0, 1, 0, 0, 0, 0 }, // Banana x10 - /*Orbinaut x3*/ { 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3 - /*Orbinaut x4*/ { 0, 0, 0, 2, 0, 0, 0, 0 }, // Orbinaut x4 - /*Jawz x2*/ { 0, 0, 1, 2, 1, 0, 0, 0 } // Jawz x2 + //B C D E F G H I + { 0, 0, 2, 3, 4, 0, 0, 0 }, // Sneaker + { 0, 0, 0, 0, 0, 3, 4, 5 }, // Rocket Sneaker + { 0, 0, 0, 0, 2, 5, 5, 7 }, // Invincibility + { 2, 3, 1, 0, 0, 0, 0, 0 }, // Banana + { 1, 2, 0, 0, 0, 0, 0, 0 }, // Eggman Monitor + { 5, 5, 2, 2, 0, 0, 0, 0 }, // Orbinaut + { 0, 4, 2, 1, 0, 0, 0, 0 }, // Jawz + { 0, 3, 3, 1, 0, 0, 0, 0 }, // Mine + { 3, 0, 0, 0, 0, 0, 0, 0 }, // Land Mine + { 0, 0, 2, 2, 0, 0, 0, 0 }, // Ballhog + { 0, 0, 0, 0, 0, 2, 4, 0 }, // Self-Propelled Bomb + { 0, 0, 0, 0, 2, 5, 0, 0 }, // Grow + { 0, 0, 0, 0, 0, 2, 4, 2 }, // Shrink + { 1, 0, 0, 0, 0, 0, 0, 0 }, // Lightning Shield + { 0, 1, 2, 1, 0, 0, 0, 0 }, // Bubble Shield + { 0, 0, 0, 0, 0, 1, 3, 5 }, // Flame Shield + { 3, 0, 0, 0, 0, 0, 0, 0 }, // Hyudoro + { 0, 0, 0, 0, 0, 0, 0, 0 }, // Pogo Spring + { 2, 1, 1, 0, 0, 0, 0, 0 }, // Super Ring + { 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink + { 3, 0, 0, 0, 0, 0, 0, 0 }, // Drop Target + { 0, 0, 0, 3, 5, 0, 0, 0 }, // Garden Top + { 0, 0, 2, 2, 2, 0, 0, 0 }, // Sneaker x2 + { 0, 0, 0, 0, 4, 4, 4, 0 }, // Sneaker x3 + { 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3 + { 0, 0, 0, 1, 0, 0, 0, 0 }, // Banana x10 + { 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3 + { 0, 0, 0, 2, 0, 0, 0, 0 }, // Orbinaut x4 + { 0, 0, 1, 2, 1, 0, 0, 0 } // Jawz x2 }; static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = { - //P-Odds 0 1 - /*Sneaker*/ { 2, 1 }, // Sneaker - /*Rocket Sneaker*/ { 0, 0 }, // Rocket Sneaker - /*Invincibility*/ { 4, 1 }, // Invincibility - /*Banana*/ { 0, 0 }, // Banana - /*Eggman Monitor*/ { 1, 0 }, // Eggman Monitor - /*Orbinaut*/ { 8, 0 }, // Orbinaut - /*Jawz*/ { 8, 1 }, // Jawz - /*Mine*/ { 6, 1 }, // Mine - /*Land Mine*/ { 2, 0 }, // Land Mine - /*Ballhog*/ { 2, 1 }, // Ballhog - /*Self-Propelled Bomb*/ { 0, 0 }, // Self-Propelled Bomb - /*Grow*/ { 2, 1 }, // Grow - /*Shrink*/ { 0, 0 }, // Shrink - /*Lightning Shield*/ { 4, 0 }, // Lightning Shield - /*Bubble Shield*/ { 1, 0 }, // Bubble Shield - /*Flame Shield*/ { 1, 0 }, // Flame Shield - /*Hyudoro*/ { 2, 0 }, // Hyudoro - /*Pogo Spring*/ { 3, 0 }, // Pogo Spring - /*Super Ring*/ { 0, 0 }, // Super Ring - /*Kitchen Sink*/ { 0, 0 }, // Kitchen Sink - /*Drop Target*/ { 2, 0 }, // Drop Target - /*Sneaker x2*/ { 0, 0 }, // Sneaker x2 - /*Sneaker x3*/ { 0, 1 }, // Sneaker x3 - /*Banana x3*/ { 0, 0 }, // Banana x3 - /*Banana x10*/ { 1, 1 }, // Banana x10 - /*Orbinaut x3*/ { 2, 0 }, // Orbinaut x3 - /*Orbinaut x4*/ { 1, 1 }, // Orbinaut x4 - /*Jawz x2*/ { 5, 1 } // Jawz x2 + //K L + { 2, 1 }, // Sneaker + { 0, 0 }, // Rocket Sneaker + { 4, 1 }, // Invincibility + { 0, 0 }, // Banana + { 1, 0 }, // Eggman Monitor + { 8, 0 }, // Orbinaut + { 8, 1 }, // Jawz + { 6, 1 }, // Mine + { 2, 0 }, // Land Mine + { 2, 1 }, // Ballhog + { 0, 0 }, // Self-Propelled Bomb + { 2, 1 }, // Grow + { 0, 0 }, // Shrink + { 4, 0 }, // Lightning Shield + { 1, 0 }, // Bubble Shield + { 1, 0 }, // Flame Shield + { 2, 0 }, // Hyudoro + { 3, 0 }, // Pogo Spring + { 0, 0 }, // Super Ring + { 0, 0 }, // Kitchen Sink + { 2, 0 }, // Drop Target + { 4, 0 }, // Garden Top + { 0, 0 }, // Sneaker x2 + { 0, 1 }, // Sneaker x3 + { 0, 0 }, // Banana x3 + { 1, 1 }, // Banana x10 + { 2, 0 }, // Orbinaut x3 + { 1, 1 }, // Orbinaut x4 + { 5, 1 } // Jawz x2 }; #define DISTVAR (2048) // Magic number distance for use with item roulette tiers @@ -442,6 +446,7 @@ INT32 K_GetShieldFromItem(INT32 item) case KITEM_LIGHTNINGSHIELD: return KSHIELD_LIGHTNING; case KITEM_BUBBLESHIELD: return KSHIELD_BUBBLE; case KITEM_FLAMESHIELD: return KSHIELD_FLAME; + case KITEM_GARDENTOP: return KSHIELD_TOP; default: return KSHIELD_NONE; } } @@ -715,10 +720,20 @@ INT32 K_KartGetItemOdds( if (players[i].exiting) pexiting++; - if (shieldtype != KSHIELD_NONE && shieldtype == K_GetShieldFromItem(players[i].itemtype)) + switch (shieldtype) { - // Don't allow more than one of each shield type at a time - return 0; + case KSHIELD_NONE: + /* Marble Garden Top is not REALLY + a Sonic 3 shield */ + case KSHIELD_TOP: + break; + + default: + if (shieldtype == K_GetShieldFromItem(players[i].itemtype)) + { + // Don't allow more than one of each shield type at a time + return 0; + } } if (players[i].position == 1) @@ -1359,7 +1374,13 @@ static fixed_t K_PlayerWeight(mobj_t *mobj, mobj_t *against) if (!mobj->player) return weight; - if (against && !P_MobjWasRemoved(against) && against->player + if (against && (against->type == MT_GARDENTOP || (against->player && against->player->curshield == KSHIELD_TOP))) + { + /* Players bumping into a Top get zero weight -- the + Top rider is immovable. */ + weight = 0; + } + else if (against && !P_MobjWasRemoved(against) && against->player && ((!P_PlayerInPain(against->player) && P_PlayerInPain(mobj->player)) // You're hurt || (against->player->itemtype == KITEM_BUBBLESHIELD && mobj->player->itemtype != KITEM_BUBBLESHIELD))) // They have a Bubble Shield { @@ -1947,6 +1968,18 @@ static void K_DrawDraftCombiring(player_t *player, player_t *victim, fixed_t cur #undef CHAOTIXBANDLEN } +static boolean K_HasInfiniteTether(player_t *player) +{ + switch (player->curshield) + { + case KSHIELD_LIGHTNING: + case KSHIELD_TOP: + return true; + } + + return false; +} + /** \brief Updates the player's drafting values once per frame \param player player object passed from K_KartPlayerThink @@ -1961,7 +1994,7 @@ static void K_UpdateDraft(player_t *player) UINT8 leniency; UINT8 i; - if (player->itemtype == KITEM_LIGHTNINGSHIELD) + if (K_HasInfiniteTether(player)) { // Lightning Shield gets infinite draft distance as its (other) passive effect. draftdistance = 0; @@ -2399,7 +2432,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), @@ -2407,20 +2440,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; @@ -2448,6 +2501,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, @@ -2546,14 +2609,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) @@ -2708,6 +2778,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. @@ -3211,6 +3285,8 @@ boolean K_ApplyOffroad(player_t *player) { if (player->invincibilitytimer || player->hyudorotimer || player->sneakertimer) return false; + if (K_IsRidingFloatingTop(player)) + return false; return true; } @@ -3218,6 +3294,8 @@ boolean K_SlopeResistance(player_t *player) { if (player->invincibilitytimer || player->sneakertimer || player->tiregrease || player->flamedash) return true; + if (player->curshield == KSHIELD_TOP) + return true; return false; } @@ -3281,6 +3359,11 @@ boolean K_WaterRun(mobj_t *mobj) return false; } + if (mobj->player->curshield == KSHIELD_TOP) + { + return K_IsHoldingDownTop(mobj->player) == false; + } + if (mobj->player->invincibilitytimer || mobj->player->sneakertimer || mobj->player->tiregrease @@ -3311,6 +3394,16 @@ boolean K_WaterSkip(mobj_t *mobj) switch (mobj->type) { case MT_PLAYER: + { + if (mobj->player != NULL && mobj->player->curshield == KSHIELD_TOP) + { + // Don't allow + return false; + } + // Allow + break; + } + case MT_ORBINAUT: case MT_JAWZ: case MT_BALLHOG: @@ -3486,6 +3579,46 @@ void K_SpawnWaterRunParticles(mobj_t *mobj) } } +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 @@ -3648,7 +3781,7 @@ static void K_GetKartBoostPower(player_t *player) draftspeed *= 2; } - if (player->itemtype == KITEM_LIGHTNINGSHIELD) + if (K_HasInfiniteTether(player)) { // infinite tether draftspeed *= 2; @@ -3770,6 +3903,10 @@ fixed_t K_GetKartAccel(player_t *player) if (gametype == GT_BATTLE && player->bumpers <= 0) k_accel *= 2; + // Marble Garden Top gets 800% accel + if (player->curshield == KSHIELD_TOP) + k_accel *= 8; + return FixedMul(k_accel, (FRACUNIT + player->accelboost) / 4); } @@ -3859,17 +3996,35 @@ SINT8 K_GetForwardMove(player_t *player) forwardmove = MAXPLMOVE; } + if (player->curshield == KSHIELD_TOP) + { + if (forwardmove < 0 || + (K_GetKartButtons(player) & BT_DRIFT)) + { + forwardmove = 0; + } + else + { + forwardmove = MAXPLMOVE; + } + } + return forwardmove; } fixed_t K_GetNewSpeed(player_t *player) { const fixed_t accelmax = 4000; - const fixed_t p_speed = K_GetKartSpeed(player, true, true); + fixed_t p_speed = K_GetKartSpeed(player, true, true); fixed_t p_accel = K_GetKartAccel(player); fixed_t newspeed, oldspeed, finalspeed; + if (player->curshield == KSHIELD_TOP) + { + p_speed = 11 * p_speed / 10; + } + if (K_PlayerUsesBotMovement(player) == true && player->botvars.rubberband > 0) { // Acceleration is tied to top speed... @@ -4896,6 +5051,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; @@ -5010,6 +5178,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; } @@ -5670,8 +5841,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); @@ -5831,7 +6005,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); @@ -6442,7 +6616,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) { @@ -6494,6 +6668,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: @@ -7800,6 +7977,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! @@ -8244,8 +8448,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; } @@ -8382,7 +8598,7 @@ void K_KartResetPlayerColor(player_t *player) finalise: - if (player->curshield) + if (player->curshield && player->curshield != KSHIELD_TOP) { fullbright = true; } @@ -9027,13 +9243,25 @@ INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue) if ((currentSpeed <= 0) // Not moving && ((K_GetKartButtons(player) & BT_EBRAKEMASK) != BT_EBRAKEMASK) // Not e-braking && (player->respawn.state == RESPAWNST_NONE) // Not respawning + && (player->curshield != KSHIELD_TOP) // Not riding a Top && (P_IsObjectOnGround(player->mo) == true)) // On the ground { return 0; } p_maxspeed = K_GetKartSpeed(player, false, true); - p_speed = min(currentSpeed, (p_maxspeed * 2)); + + if (player->curshield == KSHIELD_TOP) + { + // Do not downscale turning speed with faster + // movement speed; behaves as if turning in place. + p_speed = 0; + } + else + { + p_speed = min(currentSpeed, (p_maxspeed * 2)); + } + weightadjust = FixedDiv((p_maxspeed * 3) - p_speed, (p_maxspeed * 3) + (player->kartweight * FRACUNIT)); if (K_PlayerUsesBotMovement(player)) @@ -9060,7 +9288,9 @@ INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue) turnfixed = FixedMul(turnfixed, FRACUNIT + player->handleboost); } - if (player->mo->eflags & MFE_UNDERWATER) + if (player->curshield == KSHIELD_TOP) + ; + else if (player->mo->eflags & MFE_UNDERWATER) { fixed_t div = min(FRACUNIT + K_GetUnderwaterStrafeMul(player), 2*FRACUNIT); turnfixed = FixedDiv(turnfixed, div); @@ -9448,6 +9678,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) { @@ -9462,6 +9693,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 @@ -9525,6 +9758,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; } @@ -9676,6 +9936,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) @@ -10043,6 +10304,11 @@ void K_AdjustPlayerFriction(player_t *player) player->mo->friction += ((FRACUNIT - prevfriction) / greasetics) * player->tiregrease; } + if (player->curshield == KSHIELD_TOP) + { + player->mo->friction += 1024; + } + /* if (K_PlayerEBrake(player) == true) { @@ -10693,6 +10959,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 * mapobjectscale); + P_SetObjectMomZ(player->mo, player->mo->info->height / 8, 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 117422ffe..7973a236c 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); @@ -146,6 +147,9 @@ boolean K_MovingHorizontally(mobj_t *mobj); boolean K_WaterRun(mobj_t *mobj); boolean K_WaterSkip(mobj_t *mobj); void K_SpawnWaterRunParticles(mobj_t *mobj); +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); @@ -172,6 +176,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..3d55e03d1 --- /dev/null +++ b/src/objects/gardentop.c @@ -0,0 +1,614 @@ +#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) = 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_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 70ea44edd..36c50f2bb 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) @@ -2842,6 +2848,11 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff) thing->terrain = NULL; } + if (thing->player && K_IsRidingFloatingTop(thing->player)) + { + stairjank = false; + } + /* FIXME: slope step down (even up) has some false positives, so just ignore them entirely. */ if (stairjank && !oldslope && !thing->standingslope && diff --git a/src/p_mobj.c b/src/p_mobj.c index e6894f349..29dfecf78 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1133,7 +1133,11 @@ fixed_t P_GetMobjGravity(mobj_t *mo) gravityadd = FixedMul(TUMBLEGRAVITY, gravityadd); } - if (mo->player->fastfall != 0) + if (K_IsHoldingDownTop(mo->player)) + { + gravityadd = (5*gravityadd)/2; + } + else if (mo->player->fastfall != 0) { // Fast falling gravityadd *= 4; @@ -1734,6 +1738,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); @@ -6444,6 +6449,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: @@ -7152,7 +7158,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) { @@ -7160,10 +7170,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) { @@ -7235,6 +7256,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } } break; + } case MT_BOOSTFLAME: if (!mobj->target || !mobj->target->health) { @@ -7806,6 +7828,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); @@ -9851,6 +9883,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_saveg.c b/src/p_saveg.c index dd77337b9..859f19976 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -378,6 +378,8 @@ static void P_NetArchivePlayers(void) WRITEUINT8(save_p, players[i].kickstartaccel); WRITEUINT8(save_p, players[i].stairjank); + WRITEUINT8(save_p, players[i].topdriftheld); + WRITEUINT8(save_p, players[i].topinfirst); WRITEUINT8(save_p, players[i].shrinkLaserDelay); @@ -674,6 +676,8 @@ static void P_NetUnArchivePlayers(void) players[i].kickstartaccel = READUINT8(save_p); players[i].stairjank = READUINT8(save_p); + players[i].topdriftheld = READUINT8(save_p); + players[i].topinfirst = READUINT8(save_p); players[i].shrinkLaserDelay = READUINT8(save_p); diff --git a/src/p_spec.c b/src/p_spec.c index cd5d922fb..3934e56c9 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -4416,7 +4416,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers switch (special) { case 1: // Damage (Generic) - if (roversector || P_MobjReadyToTrigger(player->mo, sector)) + if (!K_IsRidingFloatingTop(player) && (roversector || P_MobjReadyToTrigger(player->mo, sector))) P_DamageMobj(player->mo, NULL, NULL, 1, DMG_NORMAL); break; case 2: // Damage (Water) // SRB2kart - These three damage types are now offroad sectors @@ -4424,7 +4424,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers case 4: // Damage (Electrical) break; case 5: // Spikes - if (roversector || P_MobjReadyToTrigger(player->mo, sector)) + if (!K_IsRidingFloatingTop(player) && (roversector || P_MobjReadyToTrigger(player->mo, sector))) P_DamageMobj(player->mo, NULL, NULL, 1, DMG_NORMAL); break; case 6: // Death Pit (Camera Mod) diff --git a/src/p_tick.c b/src/p_tick.c index 708ed6ca8..ad83abca6 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -44,6 +44,14 @@ INT32 P_AltFlip(INT32 n, tic_t tics) return leveltime % (2 * tics) < tics ? n : -(n); } +// Please read p_tick.h +INT32 P_LerpFlip(INT32 n, tic_t tics) +{ + const tic_t w = 2 * tics; + + return P_AltFlip(((leveltime % w) - tics) * n, w); +} + // // THINKERS // All thinkers should be allocated by Z_Calloc diff --git a/src/p_tick.h b/src/p_tick.h index 7b10a5f28..f892942ea 100644 --- a/src/p_tick.h +++ b/src/p_tick.h @@ -35,4 +35,12 @@ mobj_t *P_SetTarget(mobj_t **mo, mobj_t *target); // killough 11/98 INT32 P_AltFlip(INT32 value, tic_t tics); #define P_RandomFlip(value) P_AltFlip(value, 1) +// Multiply value back and forth between -(tics) and +(tics). +// Example output P_ModulateFlip(2, 2): +// Tic: 0 1 2 3 4 5 6 7 8 +// Val: -4 -2 0 2 4 2 0 -2 -4 +// A half cycle (one direction) takes 2 * tics. +// A full cycle takes 4 * tics. +INT32 P_LerpFlip(INT32 value, tic_t tics); + #endif diff --git a/src/p_user.c b/src/p_user.c index 8334db3dd..62d5e327c 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1801,7 +1801,7 @@ static void P_3dMovement(player_t *player) // Get the old momentum; this will be needed at the end of the function! -SH oldMagnitude = R_PointToDist2(player->mo->momx - player->cmomx, player->mo->momy - player->cmomy, 0, 0); - if (player->stairjank > 8 && leveltime & 3) + if ((player->stairjank > 8 && leveltime & 3) || K_IsRidingFloatingTop(player)) { movepushangle = K_MomentumAngle(player->mo); } @@ -1884,6 +1884,7 @@ static void P_3dMovement(player_t *player) if (player->mo->movefactor != FRACUNIT) // Friction-scaled acceleration... movepushforward = FixedMul(movepushforward, player->mo->movefactor); + if (player->curshield != KSHIELD_TOP) { INT32 a = K_GetUnderwaterTurnAdjust(player); INT32 adj = 0; @@ -1967,6 +1968,24 @@ static void P_3dMovement(player_t *player) player->mo->momx += totalthrust.x; player->mo->momy += totalthrust.y; + // Releasing a drift while on the Top translates all your + // momentum (and even then some) into whichever direction + // you're facing + if (onground && player->curshield == KSHIELD_TOP && (K_GetKartButtons(player) & BT_DRIFT) != BT_DRIFT && (player->oldcmd.buttons & BT_DRIFT)) + { + const fixed_t gmin = FRACUNIT/4; + const fixed_t gmax = 5*FRACUNIT/2; + + const fixed_t grindfactor = (gmax - gmin) / GARDENTOP_MAXGRINDTIME; + const fixed_t grindscale = gmin + (player->topdriftheld * grindfactor); + + const fixed_t speed = R_PointToDist2(0, 0, player->mo->momx, player->mo->momy); + + P_InstaThrust(player->mo, player->mo->angle, FixedMul(speed, grindscale)); + + player->topdriftheld = 0;/* reset after release */ + } + if (!onground) { const fixed_t airspeedcap = (50*mapobjectscale); @@ -3169,6 +3188,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; }