Merge pull request 'Add an alternative Invincibility type' (#69) from Anonimus/blankart:invincerework into blankart-dev

Reviewed-on: https://codeberg.org/NepDisk/blankart/pulls/69
This commit is contained in:
NepDisk 2025-06-30 01:06:14 +02:00
commit 4eeb3a5d2a
28 changed files with 1292 additions and 81 deletions

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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},

View file

@ -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;

View file

@ -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",

View file

@ -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?

View file

@ -786,6 +786,9 @@ _(kc6c)
_(kc6d)
_(kc6e)
// MKDS sounds
_(mdse8)
// SRB2kart
_(slip)
_(screec)

263
src/k_cluster.cpp Normal file
View file

@ -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 <vector>
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::vector<player_t *>clusternodes;
std::vector<player_t *> *realvec;
if (nodevec != nullptr)
realvec = static_cast<std::vector<player_t *> *>(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<player_t *> 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<player_t *> dummy = {0};
return K_CountNeighboringPlayersByDistance(sourcePlayer, eps, out, &dummy);
}
}

18
src/k_cluster.hpp Normal file
View file

@ -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

View file

@ -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));

View file

@ -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)<<V_ALPHASHIFT;
}
static boolean K_drawKartPositionFaces(void)
{
// FACE_X = 15; // 15
@ -1769,6 +1794,7 @@ static boolean K_drawKartPositionFaces(void)
INT32 rankplayer[MAXPLAYERS];
INT32 bumperx, numplayersingame = 0;
UINT8 *colormap;
UINT32 invinchudtrans;
ranklines = 0;
memset(completed, 0, sizeof (completed));
@ -1855,6 +1881,7 @@ static boolean K_drawKartPositionFaces(void)
if (players[rankplayer[i]].mo->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)<<FF_TRANSSHIFT);
if (encoremode)
@ -3618,12 +3671,24 @@ static void K_drawKartMinimap(void)
workingPic = faceprefix[skin][FACE_MINIMAP];
colorizeplayer = players[i].mo->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();
}

View file

@ -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;

View file

@ -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<<FRACBITS)
// Time (in tics) of a flipover.
// (TICRATE * (0.56666 * FRACUNIT))
#define FLIPOVERTICS 19
#define FLIPOVERHALFTICS (FLIPOVERTICS>>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);

View file

@ -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++;
}
}

View file

@ -32,6 +32,7 @@ extern "C" {
\brief units of the fraction
*/
#define FRACUNIT (1<<FRACBITS)
#define FRACHALF (1<<(FRACBITS>>1))
#define FRACMASK (FRACUNIT -1)
/** \brief Redefinition of INT32 as fixed_t
unit used as fixed_t

View file

@ -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.
}

View file

@ -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<<FRACBITS, 5);
K_FlipPlayer(player, inflictor, source);
LUA_HookPlayerSpin(target, inflictor, source, damage, damagetype);
K_KartPainEnergyFling(player);
case DMG_WIPEOUT:
if (P_IsDisplayPlayer(player))
P_StartQuake(32<<FRACBITS, 5);

View file

@ -570,12 +570,13 @@ struct BasicFF_t
/* Damage/death types, for P_DamageMobj and related */
//// Damage types
#define DMG_NORMAL 0x00
#define DMG_WIPEOUT 0x01 // Normal, but with extra flashy effects
#define DMG_EXPLODE 0x02
#define DMG_SQUISH 0x03
#define DMG_VOLTAGE 0x04 // Normal but for killing the spb
#define DMG_KARMA 0x05 // Karma Bomb explosion -- works like DMG_EXPLODE, but steals half of their bumpers & deletes the rest
#define DMG_NORMAL 0x00
#define DMG_WIPEOUT 0x01 // Normal, but with extra flashy effects
#define DMG_EXPLODE 0x02
#define DMG_SQUISH 0x03
#define DMG_VOLTAGE 0x04 // Normal but for killing the spb
#define DMG_KARMA 0x05 // Karma Bomb explosion -- works like DMG_EXPLODE, but steals half of their bumpers & deletes the rest
#define DMG_FLIPOVER 0x06 // Makes a player flip around in the air at a fixed speed.
//// Death types - cannot be combined with damage types
#define DMG_INSTAKILL 0x80
#define DMG_DEATHPIT 0x81

View file

@ -495,6 +495,14 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
if ((thing->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

View file

@ -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))))<<RF_TRANSSHIFT;
}
// Called only when MT_OVERLAY thinks.
static void P_PlayerInvincibilityOverlay(mobj_t *thing)
{
I_Assert(thing->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);

View file

@ -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);

View file

@ -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();
}

View file

@ -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!

View file

@ -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);