diff --git a/src/Sourcefile b/src/Sourcefile index 05c35b7a3..7d9d18c75 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -122,6 +122,7 @@ k_bheap.c k_bot.cpp k_botitem.cpp k_botsearch.cpp +k_cluster.cpp k_grandprix.c k_boss.c k_hud.c diff --git a/src/command.h b/src/command.h index c8b90f0a1..dc99f4a2f 100644 --- a/src/command.h +++ b/src/command.h @@ -186,6 +186,9 @@ extern CV_PossibleValue_t CV_Natural[]; #define KARTGP_MASTER 4 // Not a speed setting, gives hard speed with maxed out bots #define KARTGP_NIGHTMARE 5 // Not a speed setting, gives expert speed with maxed out bots extern CV_PossibleValue_t kartspeed_cons_t[]; +// Invincibility types. +#define KARTINVIN_LEGACY 0 +#define KARTINVIN_ALTERN 1 extern consvar_t cv_execversion; diff --git a/src/d_main.cpp b/src/d_main.cpp index 50ba77d9e..3ba1db0d4 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -86,7 +86,7 @@ #define ASSET_HASH_TEXTURES_KART 0xb4211b2f32b6a291 #define ASSET_HASH_CHARS_KART 0x1e68a3e01aa5c68b #define ASSET_HASH_MAPS_KART 0x38558ed00da41ce9 -#define ASSET_HASH_MAIN_PK3 0x9a6188063fcdcc93 +#define ASSET_HASH_MAIN_PK3 0x7fbb80904dbc97d7 #define ASSET_HASH_MAPPATCH_PK3 0x93a9213b2b2ba260 #define ASSET_HASH_BONUSCHARS_KART 0x60e6f13d822a7461 #ifdef USE_PATCH_FILE diff --git a/src/d_netcmd.c b/src/d_netcmd.c index d22cea64b..5e869201a 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -154,6 +154,7 @@ static void KartStacking_OnChange(void); static void KartChaining_OnChange(void); static void KartSlipdash_OnChange(void); static void KartItemBreaker_OnChange(void); +static void KartInvinType_OnChange(void); static void Schedule_OnChange(void); @@ -462,8 +463,10 @@ consvar_t cv_kartstacking_sneaker_accelboost = CVAR_INIT ("vanillaboost_sneaker_ consvar_t cv_kartstacking_sneaker_maxgrade = CVAR_INIT ("vanillaboost_sneaker_maxgrade", "3", CV_NETVAR|CV_CHEAT, CV_Natural, NULL); consvar_t cv_kartstacking_sneaker_stackable = CVAR_INIT ("vanillaboost_sneaker_stackable", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_kartstacking_invincibility_speedboost = CVAR_INIT ("vanillaboost_invincibility_speedboost", "0.375", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); -consvar_t cv_kartstacking_invincibility_accelboost = CVAR_INIT ("vanillaboost_invincibility_accelboost", "3.0", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); +consvar_t cv_kartstacking_invincibility_legacyspeedboost = CVAR_INIT ("vanillaboost_invincibility_legacyspeedboost", "0.375", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); +consvar_t cv_kartstacking_invincibility_legacyaccelboost = CVAR_INIT ("vanillaboost_invincibility_legacyaccelboost", "3.0", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); +consvar_t cv_kartstacking_invincibility_alternatespeedboost = CVAR_INIT ("vanillaboost_invincibility_alternatespeedboost", "0.68", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); +consvar_t cv_kartstacking_invincibility_alternateaccelboost = CVAR_INIT ("vanillaboost_invincibility_alternateaccelboost", "3.0", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); consvar_t cv_kartstacking_invincibility_stackable = CVAR_INIT ("vanillaboost_invincibility_stackable", "Off", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_kartstacking_grow_speedboost = CVAR_INIT ("vanillaboost_grow_speedboost", "0.2", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); @@ -503,6 +506,19 @@ consvar_t cv_kartbumpspring = CVAR_INIT ("kartbumpspring", "No", CV_NETVAR, CV_Y consvar_t cv_kartslipdash = CVAR_INIT ("kartslipdash", "No", CV_NETVAR|CV_CALL|CV_NOINIT, CV_YesNo, KartSlipdash_OnChange); +// Invincibility modifiers +static CV_PossibleValue_t invintype_cons_t[] = {{0, "Legacy"}, {1, "Alternative"}, {0, NULL}}; +consvar_t cv_kartinvintype = CVAR_INIT ("kartinvintype", "Legacy", CV_NETVAR|CV_CALL, invintype_cons_t, KartInvinType_OnChange); + +// How far the player must be from the cluster to begin frequently rolling Invincibility. +static CV_PossibleValue_t invindist_cons_t[] = {{1, "MIN"}, {32000, "MAX"}, {0, NULL}}; +consvar_t cv_kartinvindist = CVAR_INIT ("kartinvindist", "8400", CV_NETVAR|CV_CHEAT, invindist_cons_t, NULL); + +consvar_t cv_kartinvindistmul = CVAR_INIT ("kartinvindistmul", "0.54", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); + +consvar_t cv_kartinvin_maxtime = CVAR_INIT ("kartinvin_maxtime", "35.0", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); +consvar_t cv_kartinvin_midtime = CVAR_INIT ("kartinvin_midtime", "23.333", CV_NETVAR|CV_CHEAT|CV_FLOAT, CV_Unsigned, NULL); + static CV_PossibleValue_t kartdebugitem_cons_t[] = { #define FOREACH( name, n ) { n, #name } @@ -527,6 +543,7 @@ static CV_PossibleValue_t kartdebugwaypoint_cons_t[] = {{0, "Off"}, {1, "Forward consvar_t cv_kartdebugwaypoints = CVAR_INIT ("kartdebugwaypoints", "Off", CV_NETVAR|CV_CHEAT, kartdebugwaypoint_cons_t, NULL); consvar_t cv_kartdebuglap = CVAR_INIT ("kartdebuglap", "Off", CV_NETVAR|CV_CHEAT, kartdebugwaypoint_cons_t, NULL); consvar_t cv_kartdebugbot = CVAR_INIT ("kartdebugbot", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); +consvar_t cv_kartdebugcluster = CVAR_INIT ("kartdebugcluster", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); consvar_t cv_kartdebugcheckpoint = CVAR_INIT ("kartdebugcheckpoint", "Off", 0, CV_OnOff, NULL); consvar_t cv_kartdebugnodes = CVAR_INIT ("kartdebugnodes", "Off", 0, CV_OnOff, NULL); @@ -7311,6 +7328,24 @@ static void KartItemBreaker_OnChange(void) } } +static void KartInvinType_OnChange(void) +{ + if (K_CanChangeRules() == false) + { + return; + } + + if (leveltime < starttime) + { + CONS_Printf(M_GetText("Invincibility type has been changed to \"%s\".\n"), cv_kartinvintype.string); + invintype = (UINT8)cv_kartinvintype.value; + } + else + { + CONS_Printf(M_GetText("Invincibility type will be changed to \"%s\" next round.\n"), cv_kartinvintype.string); + } +} + static void Schedule_OnChange(void) { size_t i; diff --git a/src/d_netcmd.h b/src/d_netcmd.h index e5228922c..b6b5a367f 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -138,8 +138,10 @@ extern consvar_t cv_kartstacking_sneaker_accelboost; extern consvar_t cv_kartstacking_sneaker_maxgrade; extern consvar_t cv_kartstacking_sneaker_stackable; -extern consvar_t cv_kartstacking_invincibility_speedboost; -extern consvar_t cv_kartstacking_invincibility_accelboost; +extern consvar_t cv_kartstacking_invincibility_legacyspeedboost; +extern consvar_t cv_kartstacking_invincibility_legacyaccelboost; +extern consvar_t cv_kartstacking_invincibility_alternatespeedboost; +extern consvar_t cv_kartstacking_invincibility_alternateaccelboost; extern consvar_t cv_kartstacking_invincibility_stackable; extern consvar_t cv_kartstacking_flame_speedval; @@ -171,6 +173,11 @@ extern consvar_t cv_kartpurpledrift; extern consvar_t cv_kartbumpspark; extern consvar_t cv_kartbumpspring; extern consvar_t cv_kartslipdash; +extern consvar_t cv_kartinvintype; +extern consvar_t cv_kartinvindist; +extern consvar_t cv_kartinvindistmul; +extern consvar_t cv_kartinvin_maxtime; +extern consvar_t cv_kartinvin_midtime; extern consvar_t cv_encorevotes; @@ -179,7 +186,7 @@ extern consvar_t cv_votetime; extern consvar_t cv_kartdebugitem, cv_kartdebugamount, cv_kartdebugdistribution, cv_kartdebughuddrop; extern consvar_t cv_kartdebugshrink; extern consvar_t cv_kartdebugcheckpoint, cv_kartdebugnodes, cv_kartdebugcolorize, cv_kartdebugdirector; -extern consvar_t cv_kartdebugwaypoints, cv_kartdebuglap, cv_kartdebugbot; +extern consvar_t cv_kartdebugwaypoints, cv_kartdebuglap, cv_kartdebugbot, cv_kartdebugcluster; extern consvar_t cv_itemfinder; diff --git a/src/d_player.h b/src/d_player.h index df70e2dd1..84bd491a3 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -110,6 +110,8 @@ typedef enum PF_SHRINKME = 1<<25, // "Shrink me" cheat preference PF_SHRINKACTIVE = 1<<26, // "Shrink me" cheat is in effect. (Can't be disabled mid-race) + PF_JUSTFLIPPED = 1<<27, // Just got flipped over, handle the bump interaction. + // up to 1<<29 is free PF_ATTACKDOWN = 1<<30, // For lua compat, don't use! PF_SLIDING = 1<<31, // For lua compat, don't use! @@ -605,6 +607,7 @@ struct player_t UINT8 positiondelay; // Used for position number, so it can grow when passing/being passed UINT32 distancetofinish; UINT32 distancetofinishprev; + UINT32 distancefromcluster; waypoint_t *currentwaypoint; waypoint_t *nextwaypoint; UINT16 bigwaypointgap; @@ -619,6 +622,10 @@ struct player_t UINT16 flashing; UINT16 spinouttimer; // Spin-out from a banana peel or oil slick (was "pw_bananacam") UINT8 spinouttype; // Determines the mode of spinout/wipeout, see kartspinoutflags_t + + UINT16 flipovertimer; // Flipped over by a player using Invincibility. + angle_t flipoverangle; // Movement angle for a flipped-over player. + UINT8 instashield; // Instashield no-damage animation timer UINT8 wipeoutslow; // Timer before you slowdown when getting wiped out UINT8 justbumped; // Prevent players from endlessly bumping into each other @@ -700,11 +707,16 @@ struct player_t UINT8 boostcharge; // Charge during race start - INT16 growshrinktimer; // > 0 = Big, < 0 = small - INT16 growcancel; // Duration of grow canceling - INT16 squishedtimer; // Duration of being squished - UINT16 rocketsneakertimer; // Rocket Sneaker duration timer - UINT16 invincibilitytimer; // Invincibility timer + INT16 growshrinktimer; // > 0 = Big, < 0 = small + INT16 growcancel; // Duration of grow canceling + INT16 squishedtimer; // Duration of being squished + + UINT16 rocketsneakertimer; // Rocket Sneaker duration timer + + UINT16 invincibilitytimer; // Invincibility timer + UINT16 maxinvincibilitytime; // (Alternate) Initial time for Invincibility, used for the item bar. + UINT16 invincibilitybottleneck; // (Alternate) Prevents breakaways by gradienting towards a heavier decrement. + INT16 invincibilitycancel; // (Alternate) Duration of Invincibility canceling. UINT8 eggmanexplode; // Fake item recieved, explode in a few seconds SINT8 eggmanblame; // (-1 to 15) - Fake item recieved, who set this fake diff --git a/src/deh_tables.c b/src/deh_tables.c index 49278ee83..a1c24acf6 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1039,6 +1039,7 @@ struct int_const_s const INT_CONST[] = { {"DMG_SQUISH",DMG_SQUISH}, {"DMG_VOLTAGE",DMG_VOLTAGE}, {"DMG_KARMA",DMG_KARMA}, + {"DMG_FLIPOVER",DMG_FLIPOVER}, //// Death types {"DMG_INSTAKILL",DMG_INSTAKILL}, {"DMG_DEATHPIT",DMG_DEATHPIT}, diff --git a/src/doomstat.h b/src/doomstat.h index fde731033..a1fdd1338 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -664,6 +664,7 @@ extern INT32 cheats; // SRB2kart extern UINT8 numlaps; extern UINT8 gamespeed; +extern UINT8 invintype; extern boolean franticitems; extern boolean encoremode, prevencoremode; extern boolean comeback; diff --git a/src/f_finale.c b/src/f_finale.c index d5da44d9f..05021f8f7 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -914,7 +914,11 @@ static const char *blancredits[] = { "\1Support Programming", "\"hayaunderscore\" aka \"DeltaKaynx\"", "\"WumboSpasm\"", - "\"Anonimous\"", + "\"Anonimus\"", + "", + "\1Item Programming", + "\"NepDisk\"", + "\"Anonimus\"", "", "\1External Programming", "\"Hanicef\"", @@ -929,11 +933,25 @@ static const char *blancredits[] = { "\"JugadorXEI\"", "\"Kimberly\"", "", + "\1Item Design", + "\"NepDisk\"", + "\"Anonimus\"", + "\"Denny\" aka \"shephoron\"", + "", + "\1Design Support", + "\"Rim Jobless\"", + "\"luna\"", + "\"Denny\" aka \"shephoron\"", + "", "\1New Graphics Creation", "\"Spee\"", "\"Jin\"", "\"NepDisk\"", "", + "\1Debug", + "\"Rim Jobless\"", + "\"luna\"", + "", "\1Special Thanks", "\"Sunflower\" aka \"AnimeSonic\"", "Sunflower's Garden", diff --git a/src/g_game.c b/src/g_game.c index 8d789da9a..2b7fc1daf 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -282,6 +282,7 @@ INT32 cheats; //for multiplayer cheat commands // Cvars that we don't want changed mid-game UINT8 numlaps; // Removed from Cvar hell UINT8 gamespeed; // Game's current speed (or difficulty, or cc, or etc); 0 for easy, 1 for normal, 2 for hard +UINT8 invintype; // How Invincibility functions. 0 for Legacy/Vanilla, 1 for Alternative. boolean encoremode = false; // Encore Mode currently enabled? boolean prevencoremode; boolean franticitems; // Frantic items currently enabled? diff --git a/src/info/sounds.h b/src/info/sounds.h index 25d8694aa..97f198731 100644 --- a/src/info/sounds.h +++ b/src/info/sounds.h @@ -786,6 +786,9 @@ _(kc6c) _(kc6d) _(kc6e) +// MKDS sounds +_(mdse8) + // SRB2kart _(slip) _(screec) diff --git a/src/k_cluster.cpp b/src/k_cluster.cpp new file mode 100644 index 000000000..aef54033d --- /dev/null +++ b/src/k_cluster.cpp @@ -0,0 +1,263 @@ +#include "doomdef.h" +#include "doomstat.h" +#include "hu_stuff.h" +#include "d_player.h" +#include "m_fixed.h" +#include "k_kart.h" +#include "r_main.h" +#include "g_game.h" + +#include "k_cluster.hpp" +#include + +static fixed_t K_Distance3D(fixed_t x1, fixed_t y1, fixed_t z1, fixed_t x2, fixed_t y2, fixed_t z2) +{ + fixed_t dist_xy = R_PointToDist2(x1,y1,x2,y2); + + return R_PointToDist2(0,z1,dist_xy,z2); +} + +static fixed_t K_PlayerDistance3D(player_t *source, player_t *destination) +{ + if ((!source->mo) || (!destination->mo)) + return INT32_MAX; // Return a garbage value. + + return K_Distance3D(source->mo->x,source->mo->y,source->mo->z,destination->mo->x,destination->mo->y,destination->mo->z); +} + +static UINT32 K_GetDTFDifference(player_t *source, player_t *destination) +{ + if ((!source->mo) || (!destination->mo)) + return INT16_MAX; // Return a garbage value. + + if (destination->distancetofinish > source->distancetofinish) + return (destination->distancetofinish - source->distancetofinish); + + return (source->distancetofinish - destination->distancetofinish); +} + +extern "C" { +player_t *closesttocluster; + +INT32 K_CountNeighboringPlayersByDistance(player_t *sourcePlayer, fixed_t eps, vector3_t *out, void *nodevec) +{ + INT64 meanx, meany, meanz; + INT32 N; + N = 0; + vector3_t neighborvector; + std::vectorclusternodes; + std::vector *realvec; + + if (nodevec != nullptr) + realvec = static_cast *>(nodevec); + + if (!sourcePlayer->mo) + return 0; + + meanx = meany = meanz = 0; + + INT32 i; + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *player; + + if (!playeringame[i]) + continue; + + player = &players[i]; + + if (player == sourcePlayer) + continue; // Ignore ourselves. + + if (clusterplayer[i]) + continue; // Ignore players we've scanned already. + + if (player->spectator) + continue; // spectator + + if (!player->mo) + continue; + + if (!K_IsPlayerLosing(player)) + continue; // Ignore winning players to prevent sandbagging. + + // Scan all points in the database + if (K_PlayerDistance3D(sourcePlayer, player) <= eps) + { + clusterplayer[i] = true; + clusternodes.push_back(player); + + if (realvec) + realvec->push_back(player); + + // Scan our neighbor for any players close to them. + K_CountNeighboringPlayersByDistance(player, eps, &neighborvector, &clusternodes); + } + } + + N = clusternodes.size(); + + if (N != 0) + { + // Return the average center point of this cluster. + for (i = 0; i < N; i++) + { + if (!clusternodes[i]) + continue; + + if (!clusternodes[i]->mo) + continue; + + meanx += clusternodes[i]->mo->x / FRACUNIT; + meany += clusternodes[i]->mo->y / FRACUNIT; + meanz += clusternodes[i]->mo->z / FRACUNIT; + } + + meanx /= N; + meany /= N; + meanz /= N; + + //CONS_Printf("mean x: %lld, mean y: %lld, mean z: %lld\n", meanx, meany, meanz); + + out->x = (fixed_t)(meanx) << FRACBITS; + out->y = (fixed_t)(meany) << FRACBITS; + out->z = (fixed_t)(meanz) << FRACBITS; + + // Get the player closest to the cluster. + fixed_t disttocluster, bestdist; + bestdist = INT32_MAX; + for (i = 0; i < N; i++) + { + disttocluster = K_Distance3D(clusternodes[i]->mo->x,clusternodes[i]->mo->y,clusternodes[i]->mo->z,out->x,out->y,out->z); + + if (disttocluster < bestdist) + { + closesttocluster = clusternodes[i]; + bestdist = disttocluster; + } + } + } + else + { + out->z = out->y = out->x = 0; + } + + return N; +} + +static std::vector dtf_vec; +static boolean cleardtf = true; + +static UINT32 K_GetU32Diff(UINT32 a, UINT32 b) +{ + return (b > a) ? (b - a) : (a - b); +} + +INT32 K_CountNeighboringPlayersByDTF(player_t *sourcePlayer, fixed_t eps, vector3_t *out) +{ + INT64 mean; + INT32 N = 0; + vector3_t neighborvector; + + if (!sourcePlayer->mo) + return 0; + + if (cleardtf) + dtf_vec.clear(); + + mean = 0; + + INT32 i; + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *player; + + if (!playeringame[i]) + continue; + + player = &players[i]; + + if (player == sourcePlayer) + continue; // Ignore ourselves. + + if (clusterplayer[i]) + continue; // Ignore players we've scanned already. + + if (player->spectator) + continue; // spectator + + if (!player->mo) + continue; + + if (!K_IsPlayerLosing(player)) + continue; // Ignore winning players to prevent sandbagging. + + // Scan all points in the database + if ((fixed_t)K_GetDTFDifference(sourcePlayer, player) <= eps) + { + //CONS_Printf("%d is less than %d, adding to nodes vector\n", K_GetDTFDifference(sourcePlayer, player), eps); + clusterplayer[i] = true; + dtf_vec.push_back(player); // Add to result + + cleardtf = false; + + // Scan our neighbor for any players close to them. + K_CountNeighboringPlayersByDTF(player, eps, &neighborvector); + + cleardtf = true; + } + } + + N = dtf_vec.size(); + + if (N != 0) + { + // Return the average center point of this cluster. + for (i = 0; i < N; i++) + { + if (!dtf_vec[i]) + continue; + + if (!dtf_vec[i]->mo) + continue; + + mean += dtf_vec[i]->distancetofinish; + } + mean /= N; + + out->x = (fixed_t)(mean); + out->y = 0; + out->z = 0; + + // Get the player closest to the cluster. + UINT32 disttocluster, bestdist; + bestdist = UINT32_MAX; + for (i = 0; i < N; i++) + { + disttocluster = K_GetU32Diff(dtf_vec[i]->distancetofinish, mean); + + if (disttocluster < bestdist) + { + closesttocluster = dtf_vec[i]; + bestdist = disttocluster; + } + } + } + else + { + out->z = out->y = out->x = 0; + } + + return N; +} + +INT32 K_CountNeighboringPlayers(player_t *sourcePlayer, fixed_t eps, vector3_t *out) +{ + // Dummy vector so the game doesn't SIGSEGV. + // There's probably a better solution to this. + std::vector dummy = {0}; + + return K_CountNeighboringPlayersByDistance(sourcePlayer, eps, out, &dummy); +} + +} \ No newline at end of file diff --git a/src/k_cluster.hpp b/src/k_cluster.hpp new file mode 100644 index 000000000..874cac269 --- /dev/null +++ b/src/k_cluster.hpp @@ -0,0 +1,18 @@ +#include "doomdef.h" +#include "d_player.h" +#include "m_fixed.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern player_t *closesttocluster; + +INT32 K_CountNeighboringPlayersByDistance(player_t *sourcePlayer, fixed_t eps, vector3_t *out, void *nodevec); +INT32 K_CountNeighboringPlayersByDTF(player_t *sourcePlayer, fixed_t eps, vector3_t *out); +INT32 K_CountNeighboringPlayers(player_t *sourcePlayer, fixed_t eps, vector3_t *out); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/k_collide.c b/src/k_collide.c index 0298c0dff..57441cad0 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -695,8 +695,12 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) boolean t1Condition = false; boolean t2Condition = false; - t1Condition = (t1->player->invincibilitytimer > 0); - t2Condition = (t2->player->invincibilitytimer > 0); + // Rim suggestion: Flipover damage is negligible at best, just cull it from Invincibility as a whole. + if (invintype == KARTINVIN_LEGACY) + { + t1Condition = (t1->player->invincibilitytimer > 0); + t2Condition = (t2->player->invincibilitytimer > 0); + } if ((t1Condition == true || flameT1 == true) && (t2Condition == true || flameT2 == true)) { @@ -704,15 +708,18 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) K_DoInstashield(t2->player); return false; } - else if (t1Condition == true && t2Condition == false) + else if (invintype == KARTINVIN_LEGACY) { - P_DamageMobj(t2, t1, t1, 1, DMG_WIPEOUT); - return true; - } - else if (t1Condition == false && t2Condition == true) - { - P_DamageMobj(t1, t2, t2, 1, DMG_WIPEOUT); - return true; + if (t1Condition == true && t2Condition == false) + { + P_DamageMobj(t2, t1, t1, 1, DMG_WIPEOUT); + return true; + } + else if (t1Condition == false && t2Condition == true) + { + P_DamageMobj(t1, t2, t2, 1, DMG_WIPEOUT); + return true; + } } t1Condition = (t1->scale > t2->scale + (mapobjectscale/8)); diff --git a/src/k_hud.c b/src/k_hud.c index 47c12d40f..ddc698b7d 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1249,6 +1249,22 @@ static void K_drawKartItem(void) else localpatch = kp_nodraw; } + else if ((stplyr->invincibilitytimer) && (invintype == KARTINVIN_ALTERN)) + { + itembar = stplyr->invincibilitytimer; + maxl = max(1, stplyr->maxinvincibilitytime); + + if (stplyr->invincibilitycancel > 0) + { + flamebar = stplyr->invincibilitycancel; + flamemaxl = 26; + } + + if (leveltime & 1) + localpatch = localinv; + else + localpatch = kp_nodraw; + } else if (K_GetShieldFromPlayer(stplyr) == KSHIELD_BUBBLE) { localpatch = kp_bubbleshield[offset]; @@ -1392,7 +1408,7 @@ static void K_drawKartItem(void) //V_ClearClipRect(); - // Extensible meter, currently used by Grow, Rocket Sneakers and Flame Shield + // Extensible meter, currently used by Invincibilty, Grow, Rocket Sneakers and Flame Shield if (itembar) { const INT32 fill = ((itembar*barlength)/maxl); @@ -1758,6 +1774,15 @@ static void K_DrawKartPositionNum(INT32 num) } } +static UINT32 K_InvincibilityHUDVisibility(UINT16 t) +{ + UINT32 alphalevel = st_translucency; + + alphalevel = min(10, FixedMul(alphalevel, K_InvincibilityGradient(t))); + + return min(9, 10 - alphalevel)<color) { colormap = R_GetTranslationColormap(players[rankplayer[i]].skin, players[rankplayer[i]].mo->color, GTC_CACHE); + if (players[rankplayer[i]].mo->colorized) colormap = R_GetTranslationColormap(TC_RAINBOW, players[rankplayer[i]].mo->color, GTC_CACHE); else @@ -1862,6 +1889,14 @@ static boolean K_drawKartPositionFaces(void) V_DrawMappedPatch(FACE_X, Y, V_HUDTRANS|V_SNAPTOLEFT, faceprefix[players[rankplayer[i]].skin][FACE_RANK], colormap); + if ((players[rankplayer[i]].invincibilitytimer) && (invintype == KARTINVIN_ALTERN)) + { + colormap = R_GetTranslationColormap(TC_RAINBOW, K_RainbowColor(leveltime / 2), GTC_CACHE); + invinchudtrans = K_InvincibilityHUDVisibility(players[rankplayer[i]].invincibilitytimer); + + V_DrawMappedPatch(FACE_X, Y, invinchudtrans|V_SNAPTOLEFT|V_ADD, faceprefix[players[rankplayer[i]].skin][FACE_RANK], colormap); + } + if (LUA_HudEnabled(hud_battlebumpers)) { if ((gametyperules & GTR_BUMPERS) && players[rankplayer[i]].bumper > 0) @@ -3438,6 +3473,20 @@ static void K_drawKartMinimapWaypoint(waypoint_t *wp, INT32 hudx, INT32 hudy, IN K_drawKartMinimapDot(wp->mobj->x, wp->mobj->y, hudx, hudy, flags | V_NOSCALESTART, pal, size); } +static void K_drawKartMinimapCluster(INT32 hudx, INT32 hudy, INT32 flags) +{ + UINT8 pal = 180; // Strong pink color. + UINT8 size = 6; + + if (!(flags & V_NOSCALESTART)) + { + hudx *= vid.dupx; + hudy *= vid.dupy; + } + + K_drawKartMinimapDot(clusterpoint.x, clusterpoint.y, hudx, hudy, flags | V_NOSCALESTART, pal, size); +} + #define ICON_DOT_RADIUS (cv_minihead.value && !cv_showminimapnames.value) ? 8 : 10 typedef struct @@ -3487,6 +3536,8 @@ static void K_drawKartMinimap(void) mobj_t *mobj, *next; // for SPB drawing (or any other item(s) we may wanna draw, I dunno!) fixed_t interpx, interpy; spbdraw_t spb; + UINT16 usecolor; + boolean colorizeplayer; // Draw the HUD only when playing in a level. // hu_stuff needs this, unlike st_stuff. @@ -3533,6 +3584,8 @@ static void K_drawKartMinimap(void) if (!minimaptrans) return; + colorizeplayer = false; + minimaptrans = ((10-minimaptrans)<colorized; + + if ((players[i].invincibilitytimer) && ((K_InvincibilityGradient(players[i].invincibilitytimer) > (FRACUNIT/2)) || (invintype == KARTINVIN_LEGACY))) + { + usecolor = (K_RainbowColor(leveltime / 2)); + colorizeplayer = true; + } + else + { + usecolor = players[i].mo->color; + } + if (players[i].mo->color) { - if (players[i].mo->colorized) - colormap = R_GetTranslationColormap(TC_RAINBOW, players[i].mo->color, GTC_CACHE); + if (colorizeplayer) + colormap = R_GetTranslationColormap(TC_RAINBOW, usecolor, GTC_CACHE); else - colormap = R_GetTranslationColormap(skin, players[i].mo->color, GTC_CACHE); + colormap = R_GetTranslationColormap(skin, usecolor, GTC_CACHE); } else colormap = NULL; @@ -3769,12 +3834,26 @@ static void K_drawKartMinimap(void) workingPic = faceprefix[skin][FACE_MINIMAP]; + colorizeplayer = players[localplayers[i]].mo->colorized; + + if ((players[localplayers[i]].invincibilitytimer) && + ((K_InvincibilityGradient(players[localplayers[i]].invincibilitytimer) > + (FRACUNIT / 2)) || (invintype == KARTINVIN_LEGACY))) + { + usecolor = (K_RainbowColor(leveltime / 2)); + colorizeplayer = true; + } + else + { + usecolor = players[localplayers[i]].mo->color; + } + if (players[localplayers[i]].mo->color) { - if (players[localplayers[i]].mo->colorized) - colormap = R_GetTranslationColormap(TC_RAINBOW, players[localplayers[i]].mo->color, GTC_CACHE); + if (colorizeplayer) + colormap = R_GetTranslationColormap(TC_RAINBOW, usecolor, GTC_CACHE); else - colormap = R_GetTranslationColormap(skin, players[localplayers[i]].mo->color, GTC_CACHE); + colormap = R_GetTranslationColormap(skin, usecolor, GTC_CACHE); } else colormap = NULL; @@ -3841,6 +3920,11 @@ static void K_drawKartMinimap(void) K_drawKartMinimapWaypoint(stplyr->nextwaypoint, x, y, splitflags); } } + + if (cv_kartdebugcluster.value != 0) + { + K_drawKartMinimapCluster(x, y, splitflags); + } } @@ -4772,11 +4856,12 @@ static void K_drawDistributionDebugger(void) INT32 amount; if (K_UsingLegacyCheckpoints()) - itemodds = K_KartGetLegacyItemOdds(useodds, item, 0, spbrush); + itemodds = K_KartGetLegacyItemOdds(useodds, item, stplyr->distancefromcluster, 0, spbrush); else itemodds = K_KartGetItemOdds( useodds, item, stplyr->distancetofinish, + stplyr->distancefromcluster, 0, spbrush, stplyr->bot, (stplyr->bot && stplyr->botvars.rival) ); @@ -4852,6 +4937,28 @@ static void K_DrawWaypointDebugger(void) V_DrawString(8, 176, 0, va("Finishline Distance: %d", stplyr->distancetofinish)); } +static void K_DrawClusterDebugger(void) +{ + if (cv_kartdebugcluster.value == 0) + return; + + if (stplyr != &players[displayplayers[0]]) // only for p1 + return; + + INT32 vflags = V_6WIDTHSPACE|V_ALLOWLOWERCASE; + + if (K_UsingLegacyCheckpoints() && !(gametype == GT_BATTLE)) + { + V_DrawThinString(8, 136, vflags, va("Cluster player: %s", player_names[clusterid])); + V_DrawThinString(8, 146, vflags, va("X: %f, Y: %f, Z: %f, Dist. from cluster: %d", FIXED_TO_FLOAT(clusterpoint.x), FIXED_TO_FLOAT(clusterpoint.y), FIXED_TO_FLOAT(clusterpoint.z), stplyr->distancefromcluster)); + } + else + { + V_DrawThinString(8, 136, vflags, va("Cluster DtF: %d, Your DtF: %d", clusterdtf.x, stplyr->distancetofinish)); + V_DrawThinString(8, 146, vflags, va("Distance from Cluster: %d", stplyr->distancefromcluster)); + } +} + static void K_DrawBotDebugger(void) { if (!cv_kartdebugbot.value || !stplyr->bot) @@ -5111,5 +5218,6 @@ void K_drawKartHUD(void) K_DrawWaypointDebugger(); K_DrawBotDebugger(); + K_DrawClusterDebugger(); K_DrawDirectorDebugger(); } diff --git a/src/k_kart.c b/src/k_kart.c index d6430ca73..e5aee809c 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -51,6 +51,7 @@ #include "lua_hook.h" // For MobjDamage and ShouldDamage #include "m_cheat.h" // objectplacing #include "p_spec.h" +#include "m_easing.h" // Invincibility gradienting #include "k_stats.h" @@ -62,6 +63,7 @@ #include "k_collide.h" #include "k_follower.h" #include "k_grandprix.h" +#include "k_cluster.hpp" #include "blan/b_soc.h" @@ -83,6 +85,13 @@ consvar_t cv_saltyhop = CVAR_INIT ("hardcodehop", "Off", CV_SAVE, CV_OnOff, NULL // indirectitemcooldown is timer before anyone's allowed another Shrink/SPB // mapreset is set when enough players fill an empty server +// Cluster point. +// During a legacy race, this is an actual 3D vector. +// During a waypointed race, this is simply a storage point for the "cluster distance"; +// the distance to finish with the most active number of players. +boolean clusterplayer[MAXPLAYERS]; +vector3_t clusterpoint, clusterdtf; + void K_TimerInit(void) { UINT8 i; @@ -267,6 +276,7 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartdebugwaypoints); CV_RegisterVar(&cv_kartdebuglap); CV_RegisterVar(&cv_kartdebugbot); + CV_RegisterVar(&cv_kartdebugcluster); CV_RegisterVar(&cv_kartdebugcheckpoint); CV_RegisterVar(&cv_kartdebugnodes); @@ -301,8 +311,10 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartstacking_sneaker_maxgrade); CV_RegisterVar(&cv_kartstacking_sneaker_stackable); - CV_RegisterVar(&cv_kartstacking_invincibility_speedboost); - CV_RegisterVar(&cv_kartstacking_invincibility_accelboost); + CV_RegisterVar(&cv_kartstacking_invincibility_legacyspeedboost); + CV_RegisterVar(&cv_kartstacking_invincibility_legacyaccelboost); + CV_RegisterVar(&cv_kartstacking_invincibility_alternatespeedboost); + CV_RegisterVar(&cv_kartstacking_invincibility_alternateaccelboost); CV_RegisterVar(&cv_kartstacking_invincibility_stackable); CV_RegisterVar(&cv_kartstacking_grow_speedboost); @@ -341,6 +353,13 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartslipdash); + CV_RegisterVar(&cv_kartinvintype); + CV_RegisterVar(&cv_kartinvindist); + CV_RegisterVar(&cv_kartinvindistmul); + + CV_RegisterVar(&cv_kartinvin_maxtime); + CV_RegisterVar(&cv_kartinvin_midtime); + CV_RegisterVar(&cv_kartdriftsounds); CV_RegisterVar(&cv_kartdriftefx); CV_RegisterVar(&cv_driftsparkpulse); @@ -427,10 +446,10 @@ consvar_t *KartItemCVars[NUMKARTRESULTS-1] = &cv_dualjawz }; -#define NUMKARTODDS 80 +#define NUMKARTODDS (MAXODDS*10) // Less ugly 2D arrays -static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = +static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][MAXODDS] = { //B C D E F G H I { 0, 0, 3, 3, 2, 0, 0, 0 }, // Sneaker @@ -687,6 +706,38 @@ UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush) return distance; } +#define INVODDS 4 +#define INVINDESPERATION 4 + +static INT32 K_KartGetInvincibilityOdds(UINT32 dist) +{ + if (dist < (INVINDIST/2)) + return 0; + + INT32 finodds = 0; + fixed_t fac = (min(32000, (fixed_t)dist) * FRACUNIT) / (INVINDIST); + + if (fac > FRACUNIT) + { + // Desperation! Climb exponentially until Invincibility is practically guaranteed. + fac = (((min(32000, (fixed_t)dist) * FRACUNIT) / (INVINDIST)) - FRACUNIT) >> 1; + finodds = Easing_InCubic(fac, INVODDS, 20 * INVINDESPERATION); + } + else + { + if (fac <= FRACHALF) + { + // Invincibility is practically useless at lower distances. + // At below half, remove it from the item pool. + return 0; + } + // Basic linear climb to "reasonable" odds. + finodds = FixedMul(INVODDS, fac); + } + + return min(20 * INVINDESPERATION, finodds); +} + /** \brief Item Roulette for Kart \param player player object passed from P_KartPlayerThink @@ -697,6 +748,7 @@ UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush) INT32 K_KartGetItemOdds( UINT8 pos, SINT8 item, UINT32 ourDist, + UINT32 clusterDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival) { @@ -747,7 +799,7 @@ INT32 K_KartGetItemOdds( } else if (gametyperules & GTR_RACEODDS) { - I_Assert(pos < 8); // Ditto + I_Assert(pos < MAXODDS); // Ditto newodds = K_KartItemOddsRace[item-1][pos]; } else @@ -833,6 +885,19 @@ INT32 K_KartGetItemOdds( notNearEnd = true; break; case KITEM_INVINCIBILITY: + if (invintype == KARTINVIN_ALTERN) + { + // It's a power item, yes, but we don't want mashing to lessen + // its chances, so we lie to the game's face. + // Nonetheless, apply the start cooldown. + cooldownOnStart = true; + + // Unique odds for Invincibility. + newodds = K_KartGetInvincibilityOdds(clusterDist); + + newodds *= 4; + break; + } case KITEM_MINE: case KITEM_GROW: case KITEM_BUBBLESHIELD: @@ -943,7 +1008,7 @@ INT32 K_KartGetItemOdds( return newodds; } -INT32 K_KartGetLegacyItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush) +INT32 K_KartGetLegacyItemOdds(UINT8 pos, SINT8 item, fixed_t clusterDist, fixed_t mashed, boolean spbrush) { INT32 newodds; INT32 i; @@ -1023,6 +1088,19 @@ INT32 K_KartGetLegacyItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spb powerItem = true; break; case KITEM_INVINCIBILITY: + if (invintype == KARTINVIN_ALTERN) + { + // It's a power item, yes, but we don't want mashing to lessen + // its chances, so we lie to the game's face. + // Nonetheless, apply the start cooldown. + cooldownOnStart = true; + + // Unique odds for Invincibility. + newodds = K_KartGetInvincibilityOdds(clusterDist); + + newodds *= 4; + break; + } case KITEM_MINE: case KITEM_GROW: case KITEM_BUBBLESHIELD: @@ -1149,6 +1227,7 @@ UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbum if (K_KartGetItemOdds( i, j, player->distancetofinish, + player->distancefromcluster, mashed, spbrush, player->bot, (player->bot && player->botvars.rival) ) > 0) @@ -1249,7 +1328,7 @@ INT32 K_FindLegacyUseodds(player_t *player, fixed_t mashed, INT32 pingame, INT32 for (j = 1; j < NUMKARTRESULTS; j++) { - if (K_KartGetLegacyItemOdds(i, j, mashed, spbrush) > 0) + if (K_KartGetLegacyItemOdds(i, j, player->distancefromcluster, mashed, spbrush) > 0) { available = true; break; @@ -1771,7 +1850,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) { if (K_UsingLegacyCheckpoints()) { - spawnchance[i] = (totalspawnchance += K_KartGetLegacyItemOdds(useodds, i, mashed, spbrush)); + spawnchance[i] = (totalspawnchance += K_KartGetLegacyItemOdds(useodds, i, player->distancefromcluster, mashed, spbrush)); } else @@ -1779,6 +1858,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( useodds, i, player->distancetofinish, + player->distancefromcluster, mashed, spbrush, player->bot, (player->bot && player->botvars.rival)) ); @@ -1895,6 +1975,13 @@ fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against) return weight; } +// Checks if the bump interaction was a flip-over. +static boolean K_CheckMobjFlippedOver(mobj_t* mobj1, mobj_t* mobj2) +{ + return ((mobj1->player && (mobj1->player->pflags & PF_JUSTFLIPPED)) || + (mobj2->player && (mobj2->player->pflags & PF_JUSTFLIPPED))); +} + boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid) { mobj_t *fx; @@ -1953,6 +2040,19 @@ boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean sol return false; } + // Don't bump if you're flipping over + if (mobj1->player && mobj1->player->flipovertimer && (!(mobj1->player->pflags & PF_JUSTFLIPPED))) + { + mobj1->player->justbumped = bumptime; + return false; + } + + if (mobj2->player && mobj2->player->flipovertimer && (!(mobj2->player->pflags & PF_JUSTFLIPPED))) + { + mobj2->player->justbumped = bumptime; + return false; + } + mass1 = K_GetMobjWeight(mobj1, mobj2); if (solid == true && mass1 > 0) @@ -2039,7 +2139,7 @@ boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean sol // Do the bump fx when we've CONFIRMED we can bump. if ((mobj1->player && mobj1->player->itemtype == KITEM_BUBBLESHIELD) || (mobj2->player && mobj2->player->itemtype == KITEM_BUBBLESHIELD)) S_StartSound(mobj1, sfx_s3k44); - else + else if (!K_CheckMobjFlippedOver(mobj1, mobj2)) S_StartSound(mobj1, sfx_s3k49); fx = P_SpawnMobj(mobj1->x/2 + mobj2->x/2, mobj1->y/2 + mobj2->y/2, mobj1->z/2 + mobj2->z/2, MT_BUMP); @@ -2064,6 +2164,15 @@ boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean sol P_PlayerRingBurst(mobj1->player, 1); } + if (mobj1->player->pflags & PF_JUSTFLIPPED) + { + S_StartSound(mobj2, sfx_mdse8); + mobj1->player->flipoverangle = R_PointToAngle2(0,0,mobj1->player->rmomx,mobj1->player->rmomy); + P_InstaThrust(mobj1, mobj1->player->flipoverangle, FixedMul(FLIPOVERSPEED, mobj1->scale)); + mobj1->momz = FixedMul(FlipOverZMomentum(gravity), mobj1->scale); + mobj1->player->pflags &= ~PF_JUSTFLIPPED; + } + if (mobj1->player->spinouttimer) { mobj1->player->wipeoutslow = wipeoutslowtime+1; @@ -2090,6 +2199,15 @@ boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean sol mobj2->player->spinouttimer = max(wipeoutslowtime+1, mobj2->player->spinouttimer); //mobj2->player->spinouttype = KSPIN_WIPEOUT; // Enforce type } + + if (mobj2->player->pflags & PF_JUSTFLIPPED) + { + S_StartSound(mobj1, sfx_mdse8); + mobj2->player->flipoverangle = R_PointToAngle2(0,0,mobj2->player->rmomx,mobj2->player->rmomy); + P_InstaThrust(mobj2, mobj2->player->flipoverangle, FixedMul(FLIPOVERSPEED, mobj2->scale)); + mobj2->momz = FixedMul(FlipOverZMomentum(gravity), mobj2->scale); + mobj2->player->pflags &= ~PF_JUSTFLIPPED; + } } return true; @@ -2158,6 +2276,15 @@ static fixed_t K_CheckOffroadCollide(mobj_t *mo) return 0; // couldn't find any offroad } +static fixed_t K_OffroadGradient(player_t *player, fixed_t offroad) +{ + // At 50% or lower Invincibility, offroad creeps up on you. + fixed_t invinoffroad = (invintype == KARTINVIN_LEGACY) ? ((player->invincibilitytimer) ? FRACUNIT : 0) : min(FRACUNIT, K_InvincibilityGradient(player->invincibilitytimer) << 1); + fixed_t fac = CLAMP(FRACUNIT - invinoffroad, 0, FRACUNIT); + + return FixedMul(offroad, fac); +} + /** \brief Updates the Player's offroad value once per frame \param player player object passed from K_KartPlayerThink @@ -2179,6 +2306,9 @@ static void K_UpdateOffroad(player_t *player) offroadstrength = K_CheckOffroadCollide(player->mo); } + // Gradient our offroad strength. + offroadstrength = K_OffroadGradient(player, offroadstrength); + // If you are in offroad, a timer starts. if (offroadstrength) { @@ -3204,7 +3334,7 @@ boolean K_ApplyOffroad(player_t *player) if (modeattacking != ATTACKING_NONE) sneakertimer = player->sneakertimer > 0; - if (player->invincibilitytimer || player->hyudorotimer || sneakertimer) + if (player->hyudorotimer || sneakertimer) return false; return true; } @@ -3523,6 +3653,74 @@ static inline fixed_t K_GetSneakerBoostSpeed(void) } } +// Used to determine the speed and power of Invincibility. +fixed_t K_InvincibilityGradient(UINT16 time) +{ + return (min(936, (fixed_t)time) * FRACUNIT / BASEINVINTIME); +} + +static fixed_t K_InvincibilityEasing(fixed_t x) +{ + fixed_t u = max(FRACUNIT / 4, (min(32000, x) * FRACUNIT) / INVINDIST); + + if (x < INVINDIST) + return u; + + u = max(0, (u - FRACUNIT)); + + if (u < FRACUNIT) + return Easing_InCubic(u, FRACUNIT, (INVINMIDTIME/10)); + + return Easing_OutCubic( + min(FRACUNIT, FixedMul(u - FRACUNIT, FRACUNIT/4)), (INVINMIDTIME/10), (INVINMAXTIME/10)); +} + + +UINT16 K_GetInvincibilityTime(player_t *player) +{ + UINT32 i, pingame; + + if (invintype == KARTINVIN_LEGACY) + return BASEINVINTIME; + + pingame = 0; + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (!(gametyperules & GTR_BUMPERS) || players[i].bumper) + pingame++; + + if (pingame > 1) // We only want to see if one player is playing. + continue; + } + + if (pingame <= 1) + return BASEINVINTIME; + + fixed_t clustermul = K_InvincibilityEasing(player->distancefromcluster); + return FixedMul(BASEINVINTIME, clustermul); +} + +static fixed_t K_GetInvincibilitySpeed(UINT16 time) +{ + if (invintype == KARTINVIN_LEGACY) + return INVINSPEEDBOOSTLGC; + + fixed_t t = min(FRACUNIT, K_InvincibilityGradient(time)); + return Easing_OutCubic(t, 0, INVINSPEEDBOOSTALT); +} + +static fixed_t K_GetInvincibilityAccel(UINT16 time) +{ + if (invintype == KARTINVIN_LEGACY) + return INVINACCELBOOSTLGC; + + fixed_t t = min(FRACUNIT, K_InvincibilityGradient(time)); + return Easing_OutCubic(t, 0, INVINACCELBOOSTALT); +} + static fixed_t diminish(fixed_t speedboost) { return FixedSqrt(speedboost + DIMINISHPARAM) - FixedSqrt(DIMINISHPARAM); @@ -3622,7 +3820,9 @@ static void K_GetKartBoostPower(player_t *player) if (player->invincibilitytimer) // Invincibility { - K_DoBoost(player, INVINSPEEDBOOST, INVINACCELBOOST, INVINSTACKABLE, INVINSTACKABLE); // + 37.5% top speed, + 300% acceleration + fixed_t invspeedboost = K_GetInvincibilitySpeed(player->invincibilitytimer); + fixed_t invaccelboost = K_GetInvincibilityAccel(player->invincibilitytimer); + K_DoBoost(player, invspeedboost, invaccelboost, INVINSTACKABLE, INVINSTACKABLE); // + 37.5% top speed, + 300% acceleration } if (player->growshrinktimer > 0) // Grow @@ -3877,7 +4077,10 @@ SINT8 K_GetForwardMove(player_t *player) } } - if ((player->exiting || mapreset) || player->pflags & PF_STASIS || player->spinouttimer) // pw_introcam? + if ((player->exiting || mapreset) || + player->pflags & PF_STASIS || + player->spinouttimer || + player->flipovertimer) // pw_introcam? { forwardmove = 0; if (player->sneakertimer) @@ -4087,6 +4290,19 @@ void K_SpinPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 typ P_SetPlayerMobjState(player->mo, S_KART_SPINOUT); } +void K_FlipPlayer(player_t *player, mobj_t *inflictor, mobj_t *source) +{ + (void)inflictor; + (void)source; + + K_DirectorFollowAttack(player, inflictor, source); + + K_StatPlayerHit(player, source ? source->player : NULL); + player->flipovertimer = 1; + player->pflags |= PF_JUSTFLIPPED; + P_SetPlayerMobjState(player->mo, S_KART_SPINOUT); +} + static void K_RemoveGrowShrink(player_t *player) { if (player->mo && !P_MobjWasRemoved(player->mo)) @@ -4193,6 +4409,11 @@ INT32 K_ExplodePlayer(player_t *player, mobj_t *inflictor, mobj_t *source) // A return ringburst; } +boolean K_IsPlayerDamaged(player_t *player) +{ + return ((player->spinouttimer > 0) || (player->flipovertimer > 0)); +} + void K_HandleBumperChanges(player_t *player, UINT8 prevBumpers) { if (!(gametyperules & GTR_BUMPERS)) @@ -5741,9 +5962,32 @@ void K_DoInvincibility(player_t *player, tic_t time) P_SetTarget(&overlay->target, player->mo); overlay->destscale = player->mo->scale; P_SetScale(overlay, player->mo->scale); + + if (invintype == KARTINVIN_ALTERN) + { + mobj_t *aura = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_OVERLAY); + P_SetTarget(&aura->target, player->mo); + aura->destscale = player->mo->scale; + P_SetScale(aura, player->mo->scale); + aura->extravalue2 = 1; + } } - player->invincibilitytimer = time; + if (invintype == KARTINVIN_ALTERN) + { + // Rim suggestion: Don't allow already invincible players to chain. + if (K_InvincibilityGradient(player->invincibilitytimer) < (FRACUNIT/2)) + { + // Be nice to players at half-power or less. + player->invincibilitytimer = max(player->invincibilitytimer, time); + } + } + else + { + player->invincibilitytimer = time; + } + + player->maxinvincibilitytime = time; if (P_IsLocalPlayer(player) == true) { @@ -6038,6 +6282,7 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( useodds, i, UINT32_MAX, + 0, 0, false, false, false ) @@ -7397,7 +7642,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) K_UpdateEngineSounds(player); // Thanks, VAda! // update boost angle if not spun out - if (!player->spinouttimer && !player->wipeoutslow) + if (!K_IsPlayerDamaged(player) && !player->wipeoutslow) player->boostangle = player->mo->angle; K_GetKartBoostPower(player); @@ -7472,7 +7717,8 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) // Make ABSOLUTELY SURE that your flashing tics don't get set WHILE you're still in hit animations. if (player->spinouttimer != 0 || player->wipeoutslow != 0 - || player->squishedtimer != 0) + || player->squishedtimer != 0 + || player->flipovertimer != 0) { player->flashing = K_GetKartFlashing(player); } @@ -7481,6 +7727,22 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->flashing--; } + if (player->flipovertimer) + { + player->flipovertimer++; + + // Kill off any extra damage values. + player->spinouttimer = 0; + player->wipeoutslow = 0; + + if (P_IsObjectOnGround(player->mo) && + ((INT32)(player->flipovertimer - 1) > 2)) + { + player->flipovertimer = 0; + player->mo->rollangle = 0; + } + } + if (player->spinouttimer) { if ((P_IsObjectOnGround(player->mo) @@ -7573,7 +7835,34 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->startboost = K_ChainOrDeincrementTime(player, player->startboost, 1, false); if (player->invincibilitytimer) - player->invincibilitytimer--; + { + INT16 invinfac = 1; + if ((invintype == KARTINVIN_ALTERN) && ((INT16)(player->invincibilitybottleneck) != -1) && (player->invincibilitytimer > 2)) + { + if (player->distancefromcluster < (INVINDIST >> 2)) + { + player->invincibilitybottleneck = min(256, player->invincibilitybottleneck + 4); + invinfac = FixedMul(8, max(0, min(FRACUNIT, FRACUNIT - (player->distancefromcluster / (INVINDIST >> 2))))); + } + else + { + player->invincibilitybottleneck = max(0, (INT32)(player->invincibilitybottleneck) - 2); + } + + player->invincibilitytimer = (UINT16)(max(2, (INT32)(player->invincibilitytimer) - max(1, FixedMul((UINT16)invinfac, player->invincibilitybottleneck << 8)))); + } + else + { + player->invincibilitytimer--; + player->invincibilitybottleneck = 0; + } + } + else + { + player->invincibilitybottleneck = 0; // No need for bottlenecking. + player->maxinvincibilitytime = 0; + player->invincibilitycancel = -1; + } if (player->checkskip) player->checkskip--; @@ -7880,11 +8169,15 @@ void K_KartResetPlayerColor(player_t *player) { boolean skip = false; - fullbright = true; + if (invintype == KARTINVIN_LEGACY) + { + fullbright = true; - player->mo->color = K_RainbowColor(leveltime / 2); - player->mo->colorized = true; - skip = true; + player->mo->color = K_RainbowColor(leveltime / 2); + player->mo->colorized = true; + + skip = true; + } if (skip) { @@ -9603,6 +9896,149 @@ INT32 K_GetDriftAngleOffset(player_t *player) return a; } +static fixed_t K_Distance3D(fixed_t x1, fixed_t y1, fixed_t z1, fixed_t x2, fixed_t y2, fixed_t z2) +{ + fixed_t dist_xy = R_PointToDist2(x1,y1,x2,y2); + + return R_PointToDist2(0,z1,dist_xy,z2); +} + +static fixed_t K_PlayerDistance3D(player_t *source, player_t *destination) +{ + if ((!source->mo) || (!destination->mo)) + return INT32_MAX; // Return a garbage value. + + return K_Distance3D(source->mo->x,source->mo->y,source->mo->z,destination->mo->x,destination->mo->y,destination->mo->z); +} + +#define MINCLUSTERPLAYERS 6 + +static UINT32 K_UpdateDistanceFromCluster(player_t *player) +{ + player_t *cluster_p; + UINT32 i, pingame, first, second, tinypoptarget; + INT32 divmul; + + pingame = 0; + + first = -1; + second = -1; + tinypoptarget = -1; + + divmul = 1; + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (!(gametyperules & GTR_BUMPERS) || players[i].bumper) + pingame++; + + if (players[i].mo && gametype == GT_RACE) + { + if (players[i].position == 1 && first == -1) + first = i; + else if (players[i].position == 2 && second == -1) + second = i; + } + } + + if (pingame <= 1) + { + // There's only us around. + player->invincibilitybottleneck = (UINT16)(-1); // No bottlenecking! + return 0; + } + else if ((pingame > 3) && (pingame < MINCLUSTERPLAYERS)) + { + // "Second place" in this case is actually second to last. + // In very small lobbies, it's harder for players to cluster together. + second = -1; + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (players[i].mo && gametype == GT_RACE) + { + if (players[i].position == (pingame - 1) && second == -1) + { + second = i; + break; + } + } + } + } + + tinypoptarget = (pingame < 3) ? first : second; + + if (K_UsingLegacyCheckpoints() && !(gametype == GT_BATTLE)) + { + // Compare yourself against the cluster player to determine the distance. + + if ((pingame < MINCLUSTERPLAYERS) && (tinypoptarget != -1)) + { + // In very small lobbies, the cluster player is first place/second to last. + // Half the cluster distance so that the losing player + // doesn't get overly pitied. + cluster_p = &players[tinypoptarget]; + divmul = 2; + } + else if (clusterid != -1) + { + cluster_p = &players[clusterid]; + } + + if ((clusterid == -1) || (!cluster_p)) + return 0; // No cluster player; not good! + + if (player == cluster_p) + return 0; // We ARE the cluster player. + + if (player->position <= cluster_p->position) + return 0; // Ahead, or tying. + + // Return the 3D distance from the cluster player. + return K_PlayerDistance3D(player, cluster_p) / (FRACUNIT*divmul); + } + else + { + if ((pingame < MINCLUSTERPLAYERS) && (tinypoptarget != -1)) + { + // In very small lobbies, compare against first place/second to last's + // distance to finish. + // Half the cluster distance so that the losing player + // doesn't get overly pitied. + + // In a 1v1, this is theoretically impossible; but what do I know? + if (player->distancetofinish <= players[tinypoptarget].distancetofinish) + return 0; // Ahead, or tying. + + return (player->distancetofinish - players[tinypoptarget].distancetofinish) / 2; + } + + UINT32 targetdtf; + + if (clusterid != -1) + { + // Use the cluster player's DtF. + targetdtf = players[clusterid].distancetofinish; + } + else + { + targetdtf = (UINT32)(clusterdtf.x); + } + + if (player->distancetofinish <= targetdtf) + return 0; // Ahead, or tying. + + // Return the difference between us and the cluster distance. + return (player->distancetofinish - targetdtf); + } +} + +#undef MINCLUSTERPLAYERS + // // K_KartUpdatePosition // @@ -9780,10 +10216,99 @@ void K_KartLegacyUpdatePosition(player_t *player) player->position = position; } +// Brute-force finds the area on the course with the highest player density in a given radius. +// Based on DBSCAN, so it "chain-scans" neighboring players as well for a more accurate result. +UINT32 clusterid = 0; + +static vector3_t *K_FindPlayerCluster(fixed_t eps, INT32 (*func)(player_t *, fixed_t, vector3_t *), vector3_t *out) +{ + INT32 bestdensity = 0; // Cluster counter + vector3_t tempclusterpoint; + player_t *findme; + player_t *player; + + INT32 i; + for (i = 0; i < MAXPLAYERS; i++) + { + clusterplayer[i] = false; + + if (!playeringame[i]) + continue; + + player = &players[i]; + + if (player->spectator) + continue; // spectator + + if (!player->mo) + continue; + + // Find neighbors + INT32 N = func(player, eps, &tempclusterpoint); + + // Double-remove the clusterplayer flag. Bad hack, I know... + clusterplayer[i] = false; + + // Density check + if (N > bestdensity) + { + bestdensity = N; + + findme = closesttocluster; + + out->x = tempclusterpoint.x; + out->y = tempclusterpoint.y; + out->z = tempclusterpoint.z; + continue; + } + } + + if (bestdensity) + { + // Only find the cluster player if a cluster's populated. + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + + player = &players[i]; + + if (player == findme) + { + clusterid = i; + break; + } + } + } + + return out; +} + +#define CLUSTER_EPSILON (160*FRACUNIT) +#define K_FindPlayerClusterLegacy() (K_FindPlayerCluster(CLUSTER_EPSILON, K_CountNeighboringPlayers, &clusterpoint)) +#define K_FindPlayerClusterDTF() (K_FindPlayerCluster((CLUSTER_EPSILON / FRACUNIT), K_CountNeighboringPlayersByDTF, &clusterdtf)) + +void K_UpdateClusterPoints(void) +{ + // Get the player cluster. + if (K_UsingLegacyCheckpoints() && !(gametype == GT_BATTLE)) + { + K_FindPlayerClusterLegacy(); + } + else + { + if (cv_kartdebugcluster.value) + { + K_FindPlayerClusterLegacy(); + } + + K_FindPlayerClusterDTF(); + } +} + void K_UpdateAllPlayerPositions(void) { INT32 i; - if (!K_UsingLegacyCheckpoints()) { // First loop: Ensure all players' distance to the finish line are all accurate @@ -9814,6 +10339,9 @@ void K_UpdateAllPlayerPositions(void) K_KartLegacyUpdatePosition(&players[i]); else K_KartUpdatePosition(&players[i]); + + // Get the cluster distance. + players[i].distancefromcluster = K_UpdateDistanceFromCluster(&players[i]); } } } @@ -9853,6 +10381,8 @@ void K_StripOther(player_t *player) player->roulettetype = KROULETTETYPE_NORMAL; player->invincibilitytimer = 0; + player->invincibilitycancel = -1; + if (player->growshrinktimer) { K_RemoveGrowShrink(player); @@ -10008,12 +10538,12 @@ static void K_AdjustPlayerFriction(player_t *player) player->mo->movefactor = 32; } - // Wipeout slowdown - if (player->speed > 0 && player->spinouttimer && player->wipeoutslow) + // Wipeout slowdown, or getting flipped over + if ((player->speed > 0 && player->spinouttimer && player->wipeoutslow) || (player->flipovertimer)) { if (player->offroad) player->mo->friction -= 4912; - if (player->wipeoutslow == 1) + if ((player->wipeoutslow == 1) || (player->flipovertimer)) player->mo->friction -= 9824; } } @@ -10037,6 +10567,14 @@ void K_UnsetItemOut(player_t *player) player->bananadrag = 0; } +static boolean K_InvincibilitySlotHogging(player_t *player) +{ + if (invintype == KARTINVIN_LEGACY) + return false; + + return ((player->invincibilitytimer) != 0); +} + // // K_MoveKartPlayer // @@ -10247,6 +10785,32 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->growcancel = 0; } } + // Invincibility + else if (K_InvincibilitySlotHogging(player)) + { + // (Alternate) Cancel Invincibility + if (player->invincibilitycancel >= 0) + { + if (buttons & BT_ATTACK) + { + player->invincibilitycancel++; + if (player->invincibilitycancel > 25) + { + // Don't fully cancel due to how the music handling works. + player->invincibilitytimer = 1; + } + } + else + player->invincibilitycancel = 0; + } + else + { + if ((buttons & BT_ATTACK) || (player->oldcmd.buttons & BT_ATTACK)) + player->invincibilitycancel = -1; + else + player->invincibilitycancel = 0; + } + } else if (player->itemamount == 0) { K_UnsetItemOut(player); @@ -10299,9 +10863,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } break; case KITEM_INVINCIBILITY: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) // Doesn't hold your item slot hostage normally, so you're free to waste it if you have multiple + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) // Doesn't hold your item slot hostage in Legacy, so you're free to waste it if you have multiple { - K_DoInvincibility(player, 10 * TICRATE); + K_DoInvincibility(player, K_GetInvincibilityTime(player)); K_PlayPowerGloatSound(player->mo); player->itemamount--; player->botvars.itemconfirm = 0; diff --git a/src/k_kart.h b/src/k_kart.h index f3d42d726..33b1b6c17 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -32,11 +32,51 @@ Make sure this matches the actual number of states #define GROW_SCALE ((3*FRACUNIT)/2) #define SHRINK_SCALE ((6*FRACUNIT)/8) +#define BASEINVINTIME (10 * TICRATE) + #define AUTORESPAWN_TIME (10 * TICRATE) #define AUTORESPAWN_THRESHOLD (7 * TICRATE) #define FLAMESTOREMAX TICRATE*2 +// Fixed distance of a flipover. +#define FLIPOVERDIST (336<>1) + +#define FLIPOVER_90DEGTIME ((FLIPOVERTICS>>2)+1) // 0.1666 * FRACUNIT + +// Angle movement for a flipover +// ANGLE_90 / FLIPOVER_90DEGTIME +// Precalculating it here for some slight optimization. +#define FLIPOVERANG 0x0CCCCCCC + +#define FLIPOVERSPEED (FLIPOVERDIST / FLIPOVERTICS) + +#define FLIPOVERHEIGHT (32 << FRACBITS) + +// By the middle of a jump arc, you'll reach the apex. +#define FlipOverZMomentum(grav) \ + ((FLIPOVERHEIGHT + \ + ((grav >> 1) * (FLIPOVERHALFTICS * FLIPOVERHALFTICS))) / \ + FLIPOVERHALFTICS) + +extern boolean clusterplayer[MAXPLAYERS]; +extern UINT32 clusterid; // ID of the "cluster player", the one closest to the cluster point. +extern vector3_t clusterpoint, clusterdtf; + +// :) +#define MAXODDS 8 + +// Invincibility-related constants +#define INVINDIST K_RAGuard(cv_kartinvindist) +#define INVINDISTMUL K_RAGuard(cv_kartinvindistmul) +#define INVINMIDTIME K_RAGuard(cv_kartinvin_midtime) +#define INVINMAXTIME K_RAGuard(cv_kartinvin_maxtime) + // Precalculated constants for stacked boost diminishing // *Somewhat* matches old calc but doesn't use arrays, which makes it faster and more memory efficent #define DIMINISHPARAM K_RAGuard(cv_kartstacking_diminishparam) @@ -53,8 +93,10 @@ Make sure this matches the actual number of states #define SNEAKERACCELBOOST K_RAGuard(cv_kartstacking_sneaker_accelboost) #define MAXSNEAKERSTACK K_RAGuard(cv_kartstacking_sneaker_maxgrade) #define SNEAKERSTACKABLE K_RAGuard(cv_kartstacking_sneaker_stackable) -#define INVINSPEEDBOOST K_RAGuard(cv_kartstacking_invincibility_speedboost) -#define INVINACCELBOOST K_RAGuard(cv_kartstacking_invincibility_accelboost) +#define INVINSPEEDBOOSTLGC K_RAGuard(cv_kartstacking_invincibility_legacyspeedboost) +#define INVINACCELBOOSTLGC K_RAGuard(cv_kartstacking_invincibility_legacyaccelboost) +#define INVINSPEEDBOOSTALT K_RAGuard(cv_kartstacking_invincibility_alternatespeedboost) +#define INVINACCELBOOSTALT K_RAGuard(cv_kartstacking_invincibility_alternateaccelboost) #define INVINSTACKABLE K_RAGuard(cv_kartstacking_invincibility_stackable) #define GROWSPEEDBOOST K_RAGuard(cv_kartstacking_grow_speedboost) #define GROWACCELBOOST K_RAGuard(cv_kartstacking_grow_accelboost) @@ -124,8 +166,8 @@ UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbum INT32 K_FindLegacyUseodds(player_t *player, fixed_t mashed, INT32 pingame, INT32 bestbumper, boolean spbrush, boolean dontforcespb); fixed_t K_ItemOddsScale(UINT8 numPlayers, boolean spbrush); UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush); -INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival); -INT32 K_KartGetLegacyItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush); +INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, UINT32 clusterDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival); +INT32 K_KartGetLegacyItemOdds(UINT8 pos, SINT8 item, fixed_t clusterDist, fixed_t mashed, boolean spbrush); INT32 K_GetRollingRouletteItem(player_t *player); INT32 K_GetShieldFromPlayer(player_t *player); INT32 K_GetShieldFromItem(INT32 item); @@ -153,7 +195,9 @@ void K_AwardPlayerRings(player_t *player, UINT16 rings, boolean overload); void K_DoInstashield(player_t *player); void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UINT8 bumpersRemoved); void K_SpinPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 type); +void K_FlipPlayer(player_t *player, mobj_t *inflictor, mobj_t *source); INT32 K_ExplodePlayer(player_t *player, mobj_t *inflictor, mobj_t *source); +boolean K_IsPlayerDamaged(player_t *player); void K_HandleBumperChanges(player_t *player, UINT8 prevBumpers); void K_DestroyBumpers(player_t *player, UINT8 amount); void K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount); @@ -178,6 +222,8 @@ typedef enum void K_DoSneaker(player_t *player, INT32 type); void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound); +fixed_t K_InvincibilityGradient(UINT16 time); +UINT16 K_GetInvincibilityTime(player_t *player); void K_DoInvincibility(player_t *player, tic_t time); void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source); void K_UpdateHnextList(player_t *player, boolean clean); @@ -197,6 +243,7 @@ void K_SpawnDriftElectricSparks(player_t *player); INT32 K_GetDriftAngleOffset(player_t *player); void K_KartUpdatePosition(player_t *player); void K_KartLegacyUpdatePosition(player_t *player); +void K_UpdateClusterPoints(void); void K_UpdateAllPlayerPositions(void); mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT8 amount); void K_DropItems(player_t *player); diff --git a/src/k_stats.c b/src/k_stats.c index 8ddcffa6a..bfa177167 100644 --- a/src/k_stats.c +++ b/src/k_stats.c @@ -31,7 +31,9 @@ void K_StatTicker(void) if (p->position == spbplace) kartstats.spbtargettime++; - if (max(p->spinouttimer, p->wipeoutslow) > 0) + if (p->flipovertimer > 0) + kartstats.spinouttime++; + else if (max(p->spinouttimer, p->wipeoutslow) > 0) kartstats.spinouttime++; } } diff --git a/src/m_fixed.h b/src/m_fixed.h index 1c147c401..8bc49ca5a 100644 --- a/src/m_fixed.h +++ b/src/m_fixed.h @@ -32,6 +32,7 @@ extern "C" { \brief units of the fraction */ #define FRACUNIT (1<>1)) #define FRACMASK (FRACUNIT -1) /** \brief Redefinition of INT32 as fixed_t unit used as fixed_t diff --git a/src/p_enemy.c b/src/p_enemy.c index 9f4fa86c6..532eba9c1 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -11324,7 +11324,7 @@ void A_RandomShadowFrame(mobj_t *actor) if (actor->target && !actor->target->player->flashing && !actor->target->player->invincibilitytimer && !actor->target->player->growshrinktimer - && !actor->target->player->spinouttimer + && !K_IsPlayerDamaged(actor->target->player) && P_IsObjectOnGround(actor->target) && actor->z == actor->target->z) { @@ -11369,7 +11369,7 @@ void A_RoamingShadowThinker(mobj_t *actor) if (actor->target && !actor->target->player->flashing && !actor->target->player->invincibilitytimer && !actor->target->player->growshrinktimer - && !actor->target->player->spinouttimer) + && !K_IsPlayerDamaged(actor->target->player)) { // send them flying and spawn the WIND! P_InstaThrust(actor->target, 0, 0); @@ -11611,7 +11611,7 @@ void A_ReaperThinker(mobj_t *actor) if (!(actor->target == targetplayermo && actor->target && !actor->target->player->flashing && !actor->target->player->invincibilitytimer && !actor->target->player->growshrinktimer - && !actor->target->player->spinouttimer)) + && !K_IsPlayerDamaged(actor->target->player))) P_SetTarget(&actor->target, actor->hnext); // if the above isn't correct, then we should go back to targetting waypoints or something. } diff --git a/src/p_inter.c b/src/p_inter.c index 4e2af4a9c..695874c08 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -127,7 +127,7 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon) { // Invulnerable if (player->flashing > 0 - || player->spinouttimer > 0 + || K_IsPlayerDamaged(player) || player->squishedtimer > 0 || player->invincibilitytimer > 0 || player->growshrinktimer > 0 @@ -146,6 +146,7 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon) if (player->stealingtimer || player->stolentimer || player->rocketsneakertimer || player->eggmanexplode + || ((invintype == KARTINVIN_ALTERN) && (player->invincibilitytimer)) || (player->growshrinktimer > 0) || player->flametimer) return false; @@ -2055,7 +2056,7 @@ static UINT8 P_ShouldHookDamage(mobj_t *target, mobj_t *inflictor, mobj_t *sourc status = shouldDamage; - if (shouldSpin > 0 && ((type == DMG_NORMAL) || (type == DMG_WIPEOUT))) + if (shouldSpin > 0 && ((type == DMG_NORMAL) || (type == DMG_WIPEOUT) || (type == DMG_FLIPOVER))) status = shouldSpin; else if (shouldExplode > 0 && ((type == DMG_EXPLODE) || (type == DMG_KARMA))) status = shouldExplode; @@ -2266,7 +2267,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da K_DestroyBumpers(player, 1); } - if (!(type == DMG_NORMAL || type == DMG_WIPEOUT || type == DMG_VOLTAGE)) + if (!(type == DMG_NORMAL || type == DMG_WIPEOUT || type == DMG_VOLTAGE || type == DMG_FLIPOVER)) { player->sneakertimer = 0; player->mo->flags2 &= ~MF2_WATERRUN; @@ -2345,6 +2346,12 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da K_SquishPlayer(player, inflictor, source); LUA_HookPlayerSquish(target, inflictor, source, damage, damagetype); break; + case DMG_FLIPOVER: + if (P_IsDisplayPlayer(player)) + P_StartQuake(32<flags & MF_NOCLIPTHING) || !(thing->flags & (MF_SOLID|MF_SPECIAL|MF_PAIN|MF_SHOOTABLE|MF_SPRING))) return BMIT_CONTINUE; + // Get rid of the "just flipped" flag. + // We only use that to confirm flipover hits. + if (thing->player) + thing->player->pflags &= ~PF_JUSTFLIPPED; + + if (g_tm.thing->player) + g_tm.thing->player->pflags &= ~PF_JUSTFLIPPED; + blockdist = thing->radius + g_tm.thing->radius; if (abs(thing->x - g_tm.x) >= blockdist || abs(thing->y - g_tm.y) >= blockdist) @@ -1267,7 +1275,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) } // no interaction - if (g_tm.thing->player->flashing > 0 || g_tm.thing->player->hyudorotimer > 0 || g_tm.thing->player->spinouttimer > 0) + if (g_tm.thing->player->flashing > 0 || g_tm.thing->player->hyudorotimer > 0 || K_IsPlayerDamaged(g_tm.thing->player)) return BMIT_CONTINUE; // collide diff --git a/src/p_mobj.c b/src/p_mobj.c index e5053ac82..86bd2f962 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -2765,7 +2765,7 @@ void P_PlayerZMovement(mobj_t *mo) K_UpdateMobjTerrain(mo, (mo->eflags & MFE_VERTICALFLIP ? g_tm.ceilingpic : g_tm.floorpic)); // Get up if you fell. - if (mo->player->panim == PA_HURT && mo->player->spinouttimer == 0 && mo->player->squishedtimer == 0) + if (mo->player->panim == PA_HURT && !K_IsPlayerDamaged(mo->player) && mo->player->squishedtimer == 0) { P_SetPlayerMobjState(mo, S_KART_STILL); } @@ -6715,6 +6715,75 @@ static void P_RemoveOverlay(mobj_t *thing) } } +static UINT32 P_GetTranslucencyForInvincibility(mobj_t *mo) +{ + if (!mo->player) + { + // Get out. + return 0; + } + + return max(0, min(9, 10 - FixedMul(10, K_InvincibilityGradient(mo->player->invincibilitytimer))))<target != NULL); + I_Assert(thing->target->player != NULL); + + if (!thing->target->player->invincibilitytimer) + { + P_SetTarget(&thing->target, NULL); + + I_Assert(thing->target == NULL); + return; + } + + thing->destscale = thing->target->scale; + + if (thing->target->eflags & MFE_VERTICALFLIP) + { + thing->eflags |= MFE_VERTICALFLIP; + thing->z += thing->target->height - thing->height; + } + + thing->color = K_RainbowColor(leveltime / 2); + thing->colorized = true; + thing->dispoffset = min(2, thing->target->dispoffset + 1); + + thing->angle = (thing->target->player ? thing->target->player->drawangle : thing->target->angle); + + thing->roll = thing->target->roll; + thing->pitch = thing->target->pitch; + thing->sloperoll = thing->target->sloperoll; + thing->slopepitch = thing->target->slopepitch; + + thing->sprite = thing->target->sprite; + thing->sprite2 = thing->target->sprite2; + thing->frame = thing->target->frame | FF_FULLBRIGHT | FF_ADD; + thing->tics = -1; + thing->renderflags = (thing->target->renderflags & ~RF_TRANSMASK)|P_GetTranslucencyForInvincibility(thing->target); + thing->skin = thing->target->skin; + thing->standingslope = thing->target->standingslope; + + thing->sprxoff = thing->target->sprxoff; + thing->spryoff = thing->target->spryoff; + thing->sprzoff = thing->target->sprzoff; + thing->rollangle = thing->target->rollangle; + + thing->spritexscale = thing->target->spritexscale; + thing->spriteyscale = thing->target->spriteyscale; + thing->spritexoffset = thing->target->spritexoffset; + thing->spriteyoffset = thing->target->spriteyoffset; + + if (thing->target->flags2 & MF2_OBJECTFLIP) + thing->flags2 |= MF2_OBJECTFLIP; + + if (!(thing->target->flags & MF_DONTENCOREMAP)) + thing->flags &= ~MF_DONTENCOREMAP; +} + static void P_MobjScaleThink(mobj_t *mobj) { fixed_t oldheight = mobj->height; @@ -7051,7 +7120,14 @@ static void P_MobjSceneryThink(mobj_t *mobj) return; } else + { P_AddOverlay(mobj); + if ((mobj->extravalue2) && (mobj->target->player)) + { + // Could be overlaying an invincible player. + P_PlayerInvincibilityOverlay(mobj); + } + } break; case MT_WATERDROP: P_SceneryCheckWater(mobj); diff --git a/src/p_saveg.c b/src/p_saveg.c index 3db594de2..53f436a82 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -256,6 +256,8 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT16(save->p, players[i].flashing); WRITEUINT16(save->p, players[i].spinouttimer); WRITEUINT8(save->p, players[i].spinouttype); + WRITEUINT16(save->p, players[i].flipovertimer); + WRITEANGLE(save->p, players[i].flipoverangle); WRITEINT16(save->p, players[i].squishedtimer); WRITEUINT8(save->p, players[i].instashield); WRITEUINT8(save->p, players[i].wipeoutslow); @@ -341,8 +343,13 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEINT16(save->p, players[i].growshrinktimer); WRITEINT16(save->p, players[i].growcancel); + WRITEUINT16(save->p, players[i].rocketsneakertimer); + WRITEUINT16(save->p, players[i].invincibilitytimer); + WRITEUINT16(save->p, players[i].maxinvincibilitytime); + WRITEUINT16(save->p, players[i].invincibilitybottleneck); + WRITEINT16(save->p, players[i].invincibilitycancel); WRITEUINT8(save->p, players[i].eggmanexplode); WRITESINT8(save->p, players[i].eggmanblame); @@ -595,6 +602,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].flashing = READUINT16(save->p); players[i].spinouttimer = READUINT16(save->p); players[i].spinouttype = READUINT8(save->p); + players[i].flipoverangle = READUINT16(save->p); + players[i].flipovertimer = READANGLE(save->p); players[i].squishedtimer = READINT16(save->p); players[i].instashield = READUINT8(save->p); players[i].wipeoutslow = READUINT8(save->p); @@ -680,8 +689,13 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].growshrinktimer = READINT16(save->p); players[i].growcancel = READINT16(save->p); + players[i].rocketsneakertimer = READUINT16(save->p); + players[i].invincibilitytimer = READUINT16(save->p); + players[i].maxinvincibilitytime = READUINT16(save->p); + players[i].invincibilitybottleneck = READUINT16(save->p); + players[i].invincibilitycancel = READINT16(save->p); players[i].eggmanexplode = READUINT8(save->p); players[i].eggmanblame = READSINT8(save->p); diff --git a/src/p_setup.c b/src/p_setup.c index 59ab8e0a8..9c1f5bbad 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7961,6 +7961,8 @@ static void P_InitLevelSettings(boolean reloadinggamestate) if (cv_kartslipdash.value) slipdashactive = true; + invintype = (UINT8)cv_kartinvintype.value; + // emerald hunt hunt1 = hunt2 = hunt3 = NULL; @@ -8293,6 +8295,7 @@ static void P_InitPlayers(void) G_SpawnPlayer(i, false); } } + K_UpdateClusterPoints(); K_UpdateAllPlayerPositions(); } diff --git a/src/p_tick.c b/src/p_tick.c index 0640a4dbe..04f58cc33 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -734,6 +734,7 @@ void P_Ticker(boolean run) ps_playerthink_time = I_GetPreciseTime(); + K_UpdateClusterPoints(); K_UpdateAllPlayerPositions(); // OK! Now that we got all of that sorted, players can think! diff --git a/src/p_user.c b/src/p_user.c index 1af4784e7..7c5c009df 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -464,7 +464,7 @@ UINT8 P_FindHighestLap(void) // boolean P_PlayerInPain(player_t *player) { - if (player->spinouttimer || player->squishedtimer) + if (K_IsPlayerDamaged(player) || player->squishedtimer) return true; return false; @@ -1374,7 +1374,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean fromAir) // Cut momentum in half when you hit the ground and // aren't pressing any controls. if (!(player->cmd.forwardmove || player->cmd.sidemove) && !player->cmomx && !player->cmomy - && !(player->spinouttimer)) + && !K_IsPlayerDamaged(player)) { player->mo->momx = player->mo->momx/2; player->mo->momy = player->mo->momy/2; @@ -1806,7 +1806,7 @@ static void P_3dMovement(player_t *player) { movepushangle = player->mo->angle - (ANGLE_45/5) * player->drift; } - else if (player->spinouttimer || player->wipeoutslow) // if spun out, use the boost angle + else if (K_IsPlayerDamaged(player) || player->wipeoutslow) // if spun out, use the boost angle { movepushangle = (angle_t)player->boostangle; } @@ -1896,13 +1896,13 @@ static void P_3dMovement(player_t *player) totalthrust.x += P_ReturnThrustX(player->mo, movepushangle, movepushforward); totalthrust.y += P_ReturnThrustY(player->mo, movepushangle, movepushforward); } - else if (!(player->spinouttimer)) + else if (!K_IsPlayerDamaged(player)) { K_MomentumToFacing(player); } // Sideways movement - if (cmd->sidemove != 0 && !((player->exiting || mapreset) || player->spinouttimer)) + if (cmd->sidemove != 0 && !((player->exiting || mapreset) || K_IsPlayerDamaged(player))) { if (cmd->sidemove > 0) movepushside = (cmd->sidemove * FRACUNIT/128) + FixedDiv(player->speed, K_GetKartSpeed(player, true, true)); @@ -2169,6 +2169,10 @@ void P_MovePlayer(player_t *player) player->glanceDir = 0; player->pflags &= ~PF_GAINAX; } + else if (player->flipovertimer > 0) + { + player->mo->rollangle = (angle_t)(player->flipovertimer - 1) * FLIPOVERANG; + } else if ((player->spinouttimer > 0)) { UINT16 speed = player->spinouttimer/8; @@ -2237,8 +2241,13 @@ void P_MovePlayer(player_t *player) && onground && (leveltime & 1)) K_SpawnBoostTrail(player); - if (player->invincibilitytimer > 0) + if ((player->invincibilitytimer > 0) && + (((leveltime % + max(1, 10 - FixedMul(9, K_InvincibilityGradient(player->invincibilitytimer)))) == 0) || + (invintype == KARTINVIN_LEGACY))) + { K_SpawnSparkleTrail(player->mo); + } if (player->wipeoutslow > 1 && (leveltime & 1)) K_SpawnWipeoutTrail(player->mo, false);