diff --git a/.circleci/config.yml b/.circleci/config.yml index d8ac4b995..a9e773ea5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -49,15 +49,15 @@ jobs: paths: - /var/cache/apt/archives - checkout - - run: - name: Compile without network support - command: make -C src LINUX=1 ERRORMODE=1 -k NONET=1 - - run: - name: wipe build - command: make -C src LINUX=1 cleandep - - run: - name: rebuild depend - command: make -C src LINUX=1 clean + #- run: + # name: Compile without network support + # command: make -C src LINUX=1 ERRORMODE=1 -k NONET=1 + #- run: + # name: wipe build + # command: make -C src LINUX=1 cleandep + #- run: + # name: rebuild depend + # command: make -C src LINUX=1 clean - restore_cache: keys: - v1-SRB2-{{ .Branch }}-{{ checksum "objs/Linux/SDL/Release/depend.dep" }} diff --git a/src/command.c b/src/command.c index 9e659f8ad..dd09846a9 100644 --- a/src/command.c +++ b/src/command.c @@ -1024,15 +1024,17 @@ static void COM_Help_f(void) /** Toggles a console variable. Useful for on/off values. * - * This works on on/off, yes/no values only + * This works on on/off, yes/no values by default. Given + * a list of values, cycles between them. */ static void COM_Toggle_f(void) { consvar_t *cvar; - if (COM_Argc() != 2) + if (COM_Argc() == 1 || COM_Argc() == 3) { CONS_Printf(M_GetText("Toggle : Toggle the value of a cvar\n")); + CONS_Printf("Toggle ...: Cycle along a set of values\n"); return; } cvar = CV_FindVar(COM_Argv(1)); @@ -1042,15 +1044,44 @@ static void COM_Toggle_f(void) return; } - if (!(cvar->PossibleValue == CV_YesNo || cvar->PossibleValue == CV_OnOff)) + if (COM_Argc() == 2) { - CONS_Alert(CONS_NOTICE, M_GetText("%s is not a boolean value\n"), COM_Argv(1)); - return; + if (!(cvar->PossibleValue == CV_YesNo || cvar->PossibleValue == CV_OnOff)) + { + CONS_Alert(CONS_NOTICE, M_GetText("%s is not a boolean value\n"), COM_Argv(1)); + return; + } } // netcvar don't change imediately cvar->flags |= CV_SHOWMODIFONETIME; - CV_AddValue(cvar, +1); + + if (COM_Argc() == 2) + { + CV_AddValue(cvar, +1); + } + else + { + size_t i; + + for (i = 2; i < COM_Argc() - 1; ++i) + { + const char *str = COM_Argv(i); + INT32 val; + + if (CV_CompleteValue(cvar, &str, &val)) + { + if (str ? !stricmp(cvar->string, str) + : cvar->value == val) + { + CV_Set(cvar, COM_Argv(i + 1)); + return; + } + } + } + + CV_Set(cvar, COM_Argv(2)); + } } /** Command variant of CV_AddValue @@ -1424,26 +1455,27 @@ const char *CV_CompleteVar(char *partial, INT32 skips) return NULL; } -/** Sets a value to a variable with less checking. Only for internal use. - * - * \param var Variable to set. - * \param valstr String value for the variable. - */ -static void Setvalue(consvar_t *var, const char *valstr, boolean stealth) +boolean CV_CompleteValue(consvar_t *var, const char **valstrp, INT32 *intval) { - boolean override = false; + const char *valstr = *valstrp; + INT32 overrideval = 0; - // If we want messages informing us if cheats have been enabled or disabled, - // we need to rework the consvars a little bit. This call crashes the game - // on load because not all variables will be registered at that time. -/* boolean prevcheats = false; - if (var->flags & CV_CHEAT) - prevcheats = CV_CheatsEnabled(); */ + INT32 v; + + if (var == &cv_forceskin) + { + v = R_SkinAvailable(valstr); + + if (!R_SkinUsable(-1, v)) + v = -1; + + goto finish; + } if (var->PossibleValue) { - INT32 v; + INT32 i; if (var->flags & CV_FLOAT) { @@ -1464,7 +1496,6 @@ static void Setvalue(consvar_t *var, const char *valstr, boolean stealth) { #define MINVAL 0 #define MAXVAL 1 - INT32 i; #ifdef PARANOIA if (!var->PossibleValue[MAXVAL].strvalue) I_Error("Bounded cvar \"%s\" without maximum!\n", var->name); @@ -1473,52 +1504,26 @@ static void Setvalue(consvar_t *var, const char *valstr, boolean stealth) // search for other for (i = MAXVAL+1; var->PossibleValue[i].strvalue; i++) if (v == var->PossibleValue[i].value || !stricmp(var->PossibleValue[i].strvalue, valstr)) - { - if (client && execversion_enabled) - { - if (var->revert.allocated) - { - Z_Free(var->revert.v.string); - var->revert.allocated = false; // the below value is not allocated in zone memory, don't try to free it! - } - - var->revert.v.const_munge = var->PossibleValue[i].strvalue; - - return; - } - - // free the old value string - Z_Free(var->zstring); - var->zstring = NULL; - - var->value = var->PossibleValue[i].value; - var->string = var->PossibleValue[i].strvalue; - goto finish; - } + goto found; if ((v != INT32_MIN && v < var->PossibleValue[MINVAL].value) || !stricmp(valstr, "MIN")) { - v = var->PossibleValue[MINVAL].value; - valstr = var->PossibleValue[MINVAL].strvalue; - override = true; - overrideval = v; + i = MINVAL; + goto found; } else if ((v != INT32_MIN && v > var->PossibleValue[MAXVAL].value) || !stricmp(valstr, "MAX")) { - v = var->PossibleValue[MAXVAL].value; - valstr = var->PossibleValue[MAXVAL].strvalue; - override = true; - overrideval = v; + i = MAXVAL; + goto found; } if (v == INT32_MIN) goto badinput; + valstr = NULL; // not a preset value #undef MINVAL #undef MAXVAL } else { - INT32 i; - // check first strings for (i = 0; var->PossibleValue[i].strvalue; i++) if (!stricmp(var->PossibleValue[i].strvalue, valstr)) @@ -1550,27 +1555,69 @@ static void Setvalue(consvar_t *var, const char *valstr, boolean stealth) // ...or not. goto badinput; found: - if (client && execversion_enabled) - { - var->revert.v.const_munge = var->PossibleValue[i].strvalue; - return; - } + v = var->PossibleValue[i].value; + valstr = var->PossibleValue[i].strvalue; + } - var->value = var->PossibleValue[i].value; - var->string = var->PossibleValue[i].strvalue; - goto finish; +finish: + if (intval) + *intval = v; + + *valstrp = valstr; + + return true; + } + +// landing point for possiblevalue failures +badinput: + return false; +} + +/** Sets a value to a variable with less checking. Only for internal use. + * + * \param var Variable to set. + * \param valstr String value for the variable. + */ +static void Setvalue(consvar_t *var, const char *valstr, boolean stealth) +{ + boolean override = false; + INT32 overrideval = 0; + + // If we want messages informing us if cheats have been enabled or disabled, + // we need to rework the consvars a little bit. This call crashes the game + // on load because not all variables will be registered at that time. +/* boolean prevcheats = false; + if (var->flags & CV_CHEAT) + prevcheats = CV_CheatsEnabled(); */ + + const char *overridestr = valstr; + + if (CV_CompleteValue(var, &overridestr, &overrideval)) + { + if (overridestr) + { + valstr = overridestr; + override = true; } } + else if (var->PossibleValue) + goto badinput; if (client && execversion_enabled) { if (var->revert.allocated) { Z_Free(var->revert.v.string); + // Z_StrDup creates a new zone memory block, so we can keep the allocated flag on + if (override) + var->revert.allocated = false; // the below value is not allocated in zone memory, don't try to free it! } - var->revert.v.string = Z_StrDup(valstr); + if (override) + var->revert.v.const_munge = valstr; + else + var->revert.v.string = Z_StrDup(valstr); return; } @@ -1578,28 +1625,25 @@ found: // free the old value string Z_Free(var->zstring); - var->string = var->zstring = Z_StrDup(valstr); - if (override) - var->value = overrideval; - else if (var->flags & CV_FLOAT) { - double d = atof(var->string); - var->value = (INT32)(d * FRACUNIT); + var->zstring = NULL; + var->value = overrideval; + var->string = valstr; } else { - if (var == &cv_forceskin) + var->string = var->zstring = Z_StrDup(valstr); + + if (var->flags & CV_FLOAT) { - var->value = R_SkinAvailable(var->string); - if (!R_SkinUsable(-1, var->value)) - var->value = -1; + double d = atof(var->string); + var->value = (INT32)(d * FRACUNIT); } else var->value = atoi(var->string); } -finish: // See the note above. /* if (var->flags & CV_CHEAT) { diff --git a/src/command.h b/src/command.h index 876dce67f..31e372006 100644 --- a/src/command.h +++ b/src/command.h @@ -194,6 +194,12 @@ void CV_ClearChangedFlags(void); // returns the name of the nearest console variable name found const char *CV_CompleteVar(char *partial, INT32 skips); +// Returns true if valstrp is within the PossibleValues of +// var. If an exact string value exists, it is returned in +// valstrp. An integer value is returned in intval if it +// is not NULL. +boolean CV_CompleteValue(consvar_t *var, const char **valstrp, INT32 *intval); + // equivalent to " " typed at the console void CV_Set(consvar_t *var, const char *value); diff --git a/src/d_player.h b/src/d_player.h index ad7b75b86..c31837757 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -58,11 +58,11 @@ typedef enum // typedef enum { - // True if button down last tic. - PF_ATTACKDOWN = 1, - PF_ACCELDOWN = 1<<1, - PF_BRAKEDOWN = 1<<2, - PF_LOOKDOWN = 1<<3, + // free: 1<<0 to 1<<2 + + // Look back VFX has been spawned + // TODO: Is there a better way to track this? + PF_GAINAX = 1<<3, // Accessibility and cheats PF_KICKSTARTACCEL = 1<<4, // Is accelerate in kickstart mode? @@ -330,6 +330,7 @@ typedef struct player_s // Caveat: ticcmd_t is ATTRPACK! Be careful what precedes it. ticcmd_t cmd; + ticcmd_t oldcmd; // from the previous tic playerstate_t playerstate; @@ -427,6 +428,9 @@ typedef struct player_s UINT8 driftboost; // (0 to 125) - Boost you get from drifting UINT8 strongdriftboost; // (0 to 125) - While active, boost from drifting gives a stronger speed increase + UINT16 gateBoost; // Juicebox Manta Ring boosts + UINT8 gateSound; // Sound effect combo + SINT8 aizdriftstrat; // (-1 to 1) - Let go of your drift while boosting? Helper for the SICK STRATZ (sliptiding!) you have just unlocked INT32 aizdrifttilt; INT32 aizdriftturn; diff --git a/src/deh_tables.c b/src/deh_tables.c index 7e34deacc..48b87fdd8 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -311,7 +311,6 @@ actionpointer_t actionpointers[] = {{A_ItemPop}, "A_ITEMPOP"}, {{A_JawzChase}, "A_JAWZCHASE"}, {{A_JawzExplode}, "A_JAWZEXPLODE"}, - {{A_SPBChase}, "A_SPBCHASE"}, {{A_SSMineSearch}, "A_SSMINESEARCH"}, {{A_SSMineExplode}, "A_SSMINEEXPLODE"}, {{A_LandMineExplode}, "A_LANDMINEEXPLODE"}, @@ -3641,6 +3640,10 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_SPB20", "S_SPB_DEAD", + // Juicebox for SPB + "S_MANTA1", + "S_MANTA2", + // Lightning Shield "S_LIGHTNINGSHIELD1", "S_LIGHTNINGSHIELD2", @@ -5345,6 +5348,7 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_SPB", // Self-Propelled Bomb "MT_SPBEXPLOSION", + "MT_MANTARING", // Juicebox for SPB "MT_LIGHTNINGSHIELD", // Shields "MT_BUBBLESHIELD", @@ -5714,11 +5718,14 @@ const char *const MAPTHINGFLAG_LIST[4] = { }; const char *const PLAYERFLAG_LIST[] = { - // True if button down last tic. - "ATTACKDOWN", - "ACCELDOWN", - "BRAKEDOWN", - "LOOKDOWN", + // free: 1<<0 to 1<<2 (name un-matchable) + "\x01", + "\x01", + "\x01", + + // Look back VFX has been spawned + // TODO: Is there a better way to track this? + "GAINAX", // Accessibility and cheats "KICKSTARTACCEL", // Is accelerate in kickstart mode? diff --git a/src/doomstat.h b/src/doomstat.h index 0c3eafa5e..9758172e8 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -664,7 +664,7 @@ extern boolean comeback; extern SINT8 battlewanted[4]; extern tic_t wantedcalcdelay; -extern tic_t indirectitemcooldown; +extern tic_t itemCooldowns[NUMKARTITEMS - 1]; extern tic_t mapreset; extern boolean thwompsactive; extern UINT8 lastLowestLap; diff --git a/src/g_game.c b/src/g_game.c index 1defa4369..4c6d85efa 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -307,7 +307,7 @@ SINT8 pickedvote; // What vote the host rolls // Server-sided, synched variables SINT8 battlewanted[4]; // WANTED players in battle, worth x2 points tic_t wantedcalcdelay; // Time before it recalculates WANTED -tic_t indirectitemcooldown; // Cooldown before any more Shrink, SPB, or any other item that works indirectly is awarded +tic_t itemCooldowns[NUMKARTITEMS - 1]; // Cooldowns to prevent item spawning tic_t mapreset; // Map reset delay when enough players have joined an empty game boolean thwompsactive; // Thwomps activate on lap 2 UINT8 lastLowestLap; // Last lowest lap, for activating race lap executors @@ -2439,11 +2439,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) // ^ Not necessary anyway since it will be respawned regardless considering it doesn't exist anymore. - // Don't do anything immediately - p->pflags |= PF_BRAKEDOWN; - p->pflags |= PF_ATTACKDOWN; - p->pflags |= PF_ACCELDOWN; - p->playerstate = PST_LIVE; p->panim = PA_STILL; // standing animation diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c index 570771fc3..b4917751b 100644 --- a/src/hardware/hw_draw.c +++ b/src/hardware/hw_draw.c @@ -31,6 +31,7 @@ #include "../p_local.h" // stplyr #include "../g_game.h" // players #include "../k_hud.h" +#include "../r_plane.h" // R_FlatDimensionsFromLumpSize #include #include "../i_video.h" // for rendermode != render_glide @@ -482,42 +483,17 @@ void HWR_DrawPic(INT32 x, INT32 y, lumpnum_t lumpnum) // -------------------------------------------------------------------------- void HWR_DrawFlatFill (INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatlumpnum) { - FOutVector v[4]; - double dflatsize; - INT32 flatflag; const size_t len = W_LumpLength(flatlumpnum); - switch (len) - { - case 4194304: // 2048x2048 lump - dflatsize = 2048.0f; - flatflag = 2047; - break; - case 1048576: // 1024x1024 lump - dflatsize = 1024.0f; - flatflag = 1023; - break; - case 262144:// 512x512 lump - dflatsize = 512.0f; - flatflag = 511; - break; - case 65536: // 256x256 lump - dflatsize = 256.0f; - flatflag = 255; - break; - case 16384: // 128x128 lump - dflatsize = 128.0f; - flatflag = 127; - break; - case 1024: // 32x32 lump - dflatsize = 32.0f; - flatflag = 31; - break; - default: // 64x64 lump - dflatsize = 64.0f; - flatflag = 63; - break; - } + size_t sflatsize; + double dflatsize; + INT32 flatflag; + + FOutVector v[4]; + + sflatsize = R_FlatDimensionsFromLumpSize(len); + dflatsize = (double)sflatsize; + flatflag = sflatsize - 1; // 3--2 // | /| diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 3a4a0687f..91aa8b00e 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -46,6 +46,7 @@ // SRB2Kart #include "../k_kart.h" // HITLAGJITTERS #include "../r_fps.h" +#include "../r_plane.h" // R_FlatDimensionsFromLumpSize #ifdef NEWCLIP #include "hw_clip.h" @@ -393,31 +394,9 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool if (levelflat->type == LEVELFLAT_FLAT) { size_t len = W_LumpLength(levelflat->u.flat.lumpnum); - switch (len) - { - case 4194304: // 2048x2048 lump - fflatwidth = fflatheight = 2048.0f; - break; - case 1048576: // 1024x1024 lump - fflatwidth = fflatheight = 1024.0f; - break; - case 262144:// 512x512 lump - fflatwidth = fflatheight = 512.0f; - break; - case 65536: // 256x256 lump - fflatwidth = fflatheight = 256.0f; - break; - case 16384: // 128x128 lump - fflatwidth = fflatheight = 128.0f; - break; - case 1024: // 32x32 lump - fflatwidth = fflatheight = 32.0f; - break; - default: // 64x64 lump - fflatwidth = fflatheight = 64.0f; - break; - } - flatflag = ((INT32)fflatwidth)-1; + size_t sflatsize = R_FlatDimensionsFromLumpSize(len); + fflatwidth = fflatheight = (double)sflatsize; + flatflag = sflatsize-1; } else { @@ -2756,31 +2735,9 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, if (levelflat->type == LEVELFLAT_FLAT) { size_t len = W_LumpLength(levelflat->u.flat.lumpnum); - switch (len) - { - case 4194304: // 2048x2048 lump - fflatwidth = fflatheight = 2048.0f; - break; - case 1048576: // 1024x1024 lump - fflatwidth = fflatheight = 1024.0f; - break; - case 262144:// 512x512 lump - fflatwidth = fflatheight = 512.0f; - break; - case 65536: // 256x256 lump - fflatwidth = fflatheight = 256.0f; - break; - case 16384: // 128x128 lump - fflatwidth = fflatheight = 128.0f; - break; - case 1024: // 32x32 lump - fflatwidth = fflatheight = 32.0f; - break; - default: // 64x64 lump - fflatwidth = fflatheight = 64.0f; - break; - } - flatflag = ((INT32)fflatwidth)-1; + size_t sflatsize = R_FlatDimensionsFromLumpSize(len); + fflatwidth = fflatheight = (double)sflatsize; + flatflag = sflatsize-1; } else { diff --git a/src/info.c b/src/info.c index c14582b98..947b06701 100644 --- a/src/info.c +++ b/src/info.c @@ -565,6 +565,8 @@ char sprnames[NUMSPRITES + 1][5] = "BHOG", // Ballhog "BHBM", // Ballhog BOOM "SPBM", // Self-Propelled Bomb + "TRIS", // SPB Manta Ring start + "TRNQ", // SPB Manta Ring loop "THNS", // Lightning Shield "BUBS", // Bubble Shield (not Bubs) "BWVE", // Bubble Shield waves @@ -4199,28 +4201,31 @@ state_t states[NUMSTATES] = {SPR_BHBM, FF_FULLBRIGHT|14, 1, {NULL}, 0, 0, S_BALLHOGBOOM16}, // S_BALLHOGBOOM15 {SPR_BHBM, FF_FULLBRIGHT|15, 1, {NULL}, 0, 0, S_NULL}, // S_BALLHOGBOOM16 - {SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB2}, // S_SPB1 - {SPR_SPBM, 1, 1, {A_SPBChase}, 0, 0, S_SPB3}, // S_SPB2 - {SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB4}, // S_SPB3 - {SPR_SPBM, 2, 1, {A_SPBChase}, 0, 0, S_SPB5}, // S_SPB4 - {SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB6}, // S_SPB5 - {SPR_SPBM, 3, 1, {A_SPBChase}, 0, 0, S_SPB7}, // S_SPB6 - {SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB8}, // S_SPB7 - {SPR_SPBM, 4, 1, {A_SPBChase}, 0, 0, S_SPB9}, // S_SPB8 - {SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB10}, // S_SPB9 - {SPR_SPBM, 5, 1, {A_SPBChase}, 0, 0, S_SPB11}, // S_SPB10 - {SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB12}, // S_SPB11 - {SPR_SPBM, 6, 1, {A_SPBChase}, 0, 0, S_SPB13}, // S_SPB12 - {SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB14}, // S_SPB13 - {SPR_SPBM, 7, 1, {A_SPBChase}, 0, 0, S_SPB15}, // S_SPB14 - {SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB16}, // S_SPB15 - {SPR_SPBM, 8, 1, {A_SPBChase}, 0, 0, S_SPB17}, // S_SPB16 - {SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB18}, // S_SPB17 - {SPR_SPBM, 8, 1, {A_SPBChase}, 0, 0, S_SPB19}, // S_SPB18 - {SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB20}, // S_SPB19 - {SPR_SPBM, 8, 1, {A_SPBChase}, 0, 0, S_SPB1}, // S_SPB20 + {SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB2}, // S_SPB1 + {SPR_SPBM, 1, 1, {NULL}, 0, 0, S_SPB3}, // S_SPB2 + {SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB4}, // S_SPB3 + {SPR_SPBM, 2, 1, {NULL}, 0, 0, S_SPB5}, // S_SPB4 + {SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB6}, // S_SPB5 + {SPR_SPBM, 3, 1, {NULL}, 0, 0, S_SPB7}, // S_SPB6 + {SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB8}, // S_SPB7 + {SPR_SPBM, 4, 1, {NULL}, 0, 0, S_SPB9}, // S_SPB8 + {SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB10}, // S_SPB9 + {SPR_SPBM, 5, 1, {NULL}, 0, 0, S_SPB11}, // S_SPB10 + {SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB12}, // S_SPB11 + {SPR_SPBM, 6, 1, {NULL}, 0, 0, S_SPB13}, // S_SPB12 + {SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB14}, // S_SPB13 + {SPR_SPBM, 7, 1, {NULL}, 0, 0, S_SPB15}, // S_SPB14 + {SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB16}, // S_SPB15 + {SPR_SPBM, 8, 1, {NULL}, 0, 0, S_SPB17}, // S_SPB16 + {SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB18}, // S_SPB17 + {SPR_SPBM, 8, 1, {NULL}, 0, 0, S_SPB19}, // S_SPB18 + {SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB20}, // S_SPB19 + {SPR_SPBM, 8, 1, {NULL}, 0, 0, S_SPB1}, // S_SPB20 {SPR_SPBM, 8, 175, {NULL}, 0, 0, S_NULL}, // S_SPB_DEAD + {SPR_TRIS, FF_FULLBRIGHT|FF_ANIMATE|FF_PAPERSPRITE|FF_ADD, 9, {NULL}, 2, 3, S_MANTA2}, // S_MANTA1 + {SPR_TRNQ, FF_FULLBRIGHT|FF_ANIMATE|FF_PAPERSPRITE|FF_ADD, -1, {NULL}, 7, 1, S_NULL}, // S_MANTA2 + {SPR_THNS, FF_FULLBRIGHT|9, 2, {NULL}, 0, 0, S_LIGHTNINGSHIELD2}, // S_LIGHTNINGSHIELD1 {SPR_THNS, FF_FULLBRIGHT|10, 2, {NULL}, 0, 0, S_LIGHTNINGSHIELD3}, // S_LIGHTNINGSHIELD2 {SPR_THNS, FF_FULLBRIGHT|11, 2, {NULL}, 0, 0, S_LIGHTNINGSHIELD4}, // S_LIGHTNINGSHIELD3 @@ -23906,6 +23911,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_MANTARING + -1, // doomednum + S_MANTA1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 64*FRACUNIT, // radius + 64*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + { // MT_LIGHTNINGSHIELD -1, // doomednum S_LIGHTNINGSHIELD1, // spawnstate @@ -28704,13 +28736,13 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL, // xdeathstate sfx_None, // deathsound 0, // speed - 16<oldcmd.buttons & BT_ATTACK); +} + /*-------------------------------------------------- static boolean K_BotUseItemNearPlayer(player_t *player, ticcmd_t *cmd, fixed_t radius) @@ -45,7 +62,7 @@ static boolean K_BotUseItemNearPlayer(player_t *player, ticcmd_t *cmd, fixed_t r { UINT8 i; - if (player->pflags & PF_ATTACKDOWN) + if (K_ItemButtonWasDown(player) == true) { return false; } @@ -327,7 +344,7 @@ static void K_ItemConfirmForTarget(player_t *bot, player_t *target, UINT16 amoun --------------------------------------------------*/ static boolean K_BotGenericPressItem(player_t *player, ticcmd_t *cmd, SINT8 dir) { - if (player->pflags & PF_ATTACKDOWN) + if (K_ItemButtonWasDown(player) == true) { return false; } @@ -352,7 +369,7 @@ static boolean K_BotGenericPressItem(player_t *player, ticcmd_t *cmd, SINT8 dir) --------------------------------------------------*/ static void K_BotItemGenericTap(player_t *player, ticcmd_t *cmd) { - if (!(player->pflags & PF_ATTACKDOWN)) + if (K_ItemButtonWasDown(player) == false) { cmd->buttons |= BT_ATTACK; player->botvars.itemconfirm = 0; @@ -475,7 +492,7 @@ static void K_BotItemSneaker(player_t *player, ticcmd_t *cmd) || player->speedboost > (FRACUNIT/8) // Have another type of boost (tethering) || player->botvars.itemconfirm > 4*TICRATE) // Held onto it for too long { - if (!player->sneakertimer && !(player->pflags & PF_ATTACKDOWN)) + if (player->sneakertimer == 0 && K_ItemButtonWasDown(player) == false) { cmd->buttons |= BT_ATTACK; player->botvars.itemconfirm = 2*TICRATE; @@ -503,7 +520,7 @@ static void K_BotItemRocketSneaker(player_t *player, ticcmd_t *cmd) { if (player->botvars.itemconfirm > TICRATE) { - if (!player->sneakertimer && !(player->pflags & PF_ATTACKDOWN)) + if (player->sneakertimer == 0 && K_ItemButtonWasDown(player) == false) { cmd->buttons |= BT_ATTACK; player->botvars.itemconfirm = 0; @@ -1193,7 +1210,7 @@ static void K_BotItemRouletteMash(player_t *player, ticcmd_t *cmd) { boolean mash = false; - if (player->pflags & PF_ATTACKDOWN) + if (K_ItemButtonWasDown(player) == true) { return; } diff --git a/src/k_hud.c b/src/k_hud.c index 9c3c8ba2b..14e526dd2 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -695,7 +695,7 @@ const char *K_GetItemPatch(UINT8 item, boolean tiny) } } -static patch_t **K_GetItemPatchTable(INT32 item) +static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset) { patch_t **kp[1 + NUMKARTITEMS] = { kp_sadface, @@ -723,8 +723,8 @@ static patch_t **K_GetItemPatchTable(INT32 item) kp_droptarget, }; - if (item >= KITEM_SAD && item < NUMKARTITEMS) - return kp[item - KITEM_SAD]; + if (item == KITEM_SAD || (item > KITEM_NONE && item < NUMKARTITEMS)) + return kp[item - KITEM_SAD][offset]; else return NULL; } @@ -1146,7 +1146,7 @@ static void K_drawKartItem(void) break; default: - localpatch = K_GetItemPatchTable(item)[offset]; + localpatch = K_GetCachedItemPatch(item, offset); } } else @@ -1224,7 +1224,7 @@ static void K_drawKartItem(void) /*FALLTHRU*/ default: - localpatch = K_GetItemPatchTable(stplyr->itemtype)[offset]; + localpatch = K_GetCachedItemPatch(stplyr->itemtype, offset); if (localpatch == NULL) localpatch = kp_nodraw; // diagnose underflows @@ -4444,7 +4444,6 @@ static void K_drawDistributionDebugger(void) kp_jawz[1], kp_mine[1], kp_landmine[1], - kp_droptarget[1], kp_ballhog[1], kp_selfpropelledbomb[1], kp_grow[1], @@ -4456,6 +4455,7 @@ static void K_drawDistributionDebugger(void) kp_pogospring[1], kp_superring[1], kp_kitchensink[1], + kp_droptarget[1], kp_sneaker[1], kp_sneaker[1], @@ -4470,11 +4470,17 @@ static void K_drawDistributionDebugger(void) UINT32 pdis = 0; INT32 i; INT32 x = -9, y = -9; - boolean spbrush = false; if (stplyr != &players[displayplayers[0]]) // only for p1 return; + if (K_ForcedSPB(stplyr) == true) + { + V_DrawScaledPatch(x, y, V_SNAPTOTOP, items[KITEM_SPB]); + V_DrawThinString(x+11, y+31, V_ALLOWLOWERCASE|V_SNAPTOTOP, "EX"); + return; + } + // The only code duplication from the Kart, just to avoid the actual item function from calculating pingame twice for (i = 0; i < MAXPLAYERS; i++) { @@ -4497,14 +4503,7 @@ static void K_drawDistributionDebugger(void) } } - if (spbplace != -1 && stplyr->position == spbplace+1) - { - // SPB Rush Mode: It's 2nd place's job to catch-up items and make 1st place's job hell - pdis = (3 * pdis) / 2; - spbrush = true; - } - - pdis = K_ScaleItemDistance(pdis, pingame, spbrush); + pdis = K_ScaleItemDistance(pdis, pingame); if (stplyr->bot && stplyr->botvars.rival) { @@ -4512,7 +4511,7 @@ static void K_drawDistributionDebugger(void) pdis = (15 * pdis) / 14; } - useodds = K_FindUseodds(stplyr, 0, pdis, bestbumper, spbrush); + useodds = K_FindUseodds(stplyr, 0, pdis, bestbumper); for (i = 1; i < NUMKARTRESULTS; i++) { @@ -4520,38 +4519,21 @@ static void K_drawDistributionDebugger(void) useodds, i, stplyr->distancetofinish, 0, - spbrush, stplyr->bot, (stplyr->bot && stplyr->botvars.rival) + stplyr->bot, (stplyr->bot && stplyr->botvars.rival) ); + INT32 amount = 1; if (itemodds <= 0) continue; - V_DrawScaledPatch(x, y, V_HUDTRANS|V_SLIDEIN|V_SNAPTOTOP, items[i]); - V_DrawThinString(x+11, y+31, V_HUDTRANS|V_SLIDEIN|V_SNAPTOTOP, va("%d", itemodds)); + V_DrawScaledPatch(x, y, V_SNAPTOTOP, items[i]); + V_DrawThinString(x+11, y+31, V_SNAPTOTOP, va("%d", itemodds)); // Display amount for multi-items - if (i >= NUMKARTITEMS) + amount = K_ItemResultToAmount(i); + if (amount > 1) { - INT32 amount; - switch (i) - { - case KRITEM_TENFOLDBANANA: - amount = 10; - break; - case KRITEM_QUADORBINAUT: - amount = 4; - break; - case KRITEM_DUALJAWZ: - amount = 2; - break; - case KRITEM_DUALSNEAKER: - amount = 2; - break; - default: - amount = 3; - break; - } - V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|V_HUDTRANS|V_SLIDEIN|V_SNAPTOTOP, va("x%d", amount)); + V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("x%d", amount)); } x += 32; @@ -4562,7 +4544,7 @@ static void K_drawDistributionDebugger(void) } } - V_DrawString(0, 0, V_HUDTRANS|V_SLIDEIN|V_SNAPTOTOP, va("USEODDS %d", useodds)); + V_DrawString(0, 0, V_SNAPTOTOP, va("USEODDS %d", useodds)); } static void K_drawCheckpointDebugger(void) diff --git a/src/k_kart.c b/src/k_kart.c index b2aefc397..5d9faf36f 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -47,7 +47,6 @@ // encoremode is Encore Mode (duh), bool // comeback is Battle Mode's karma comeback, also bool // battlewanted is an array of the WANTED player nums, -1 for no player in that slot -// indirectitemcooldown is timer before anyone's allowed another Shrink/SPB // mapreset is set when enough players fill an empty server void K_TimerReset(void) @@ -326,7 +325,6 @@ consvar_t *KartItemCVars[NUMKARTRESULTS-1] = &cv_jawz, &cv_mine, &cv_landmine, - &cv_droptarget, &cv_ballhog, &cv_selfpropelledbomb, &cv_grow, @@ -338,6 +336,7 @@ consvar_t *KartItemCVars[NUMKARTRESULTS-1] = &cv_pogospring, &cv_superring, &cv_kitchensink, + &cv_droptarget, &cv_dualsneaker, &cv_triplesneaker, &cv_triplebanana, @@ -363,7 +362,7 @@ static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = /*Mine*/ { 0, 3, 3, 1, 0, 0, 0, 0 }, // Mine /*Land Mine*/ { 3, 0, 0, 0, 0, 0, 0, 0 }, // Land Mine /*Ballhog*/ { 0, 0, 2, 2, 0, 0, 0, 0 }, // Ballhog - /*Self-Propelled Bomb*/ { 0, 0, 0, 0, 0, 2, 4, 0 }, // Self-Propelled Bomb + /*Self-Propelled Bomb*/ { 0, 0, 0, 0, 0, 0, 0, 0 }, // Self-Propelled Bomb /*Grow*/ { 0, 0, 0, 1, 2, 3, 0, 0 }, // Grow /*Shrink*/ { 0, 0, 0, 0, 0, 1, 3, 2 }, // Shrink /*Lightning Shield*/ { 1, 0, 0, 0, 0, 0, 0, 0 }, // Lightning Shield @@ -417,8 +416,8 @@ static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = }; #define DISTVAR (2048) // Magic number distance for use with item roulette tiers -#define SPBSTARTDIST (5*DISTVAR) // Distance when SPB is forced onto 2nd place -#define SPBFORCEDIST (15*DISTVAR) // Distance when SPB is forced onto 2nd place +#define SPBSTARTDIST (6*DISTVAR) // Distance when SPB can start appearing +#define SPBFORCEDIST (12*DISTVAR) // Distance when SPB is forced onto the next person who rolls an item #define ENDDIST (12*DISTVAR) // Distance when the game stops giving you bananas // Array of states to pick the starting point of the animation, based on the actual time left for invincibility. @@ -448,6 +447,111 @@ INT32 K_GetShieldFromItem(INT32 item) } } +SINT8 K_ItemResultToType(SINT8 getitem) +{ + if (getitem <= 0 || getitem >= NUMKARTRESULTS) // Sad (Fallback) + { + if (getitem != 0) + { + CONS_Printf("ERROR: K_GetItemResultToItemType - Item roulette gave bad item (%d) :(\n", getitem); + } + + return KITEM_SAD; + } + + if (getitem >= NUMKARTITEMS) + { + switch (getitem) + { + case KRITEM_DUALSNEAKER: + case KRITEM_TRIPLESNEAKER: + return KITEM_SNEAKER; + + case KRITEM_TRIPLEBANANA: + case KRITEM_TENFOLDBANANA: + return KITEM_BANANA; + + case KRITEM_TRIPLEORBINAUT: + case KRITEM_QUADORBINAUT: + return KITEM_ORBINAUT; + + case KRITEM_DUALJAWZ: + return KITEM_JAWZ; + + default: + I_Error("Bad item cooldown redirect for result %d\n", getitem); + break; + } + } + + return getitem; +} + +UINT8 K_ItemResultToAmount(SINT8 getitem) +{ + switch (getitem) + { + case KRITEM_DUALSNEAKER: + case KRITEM_DUALJAWZ: + return 2; + + case KRITEM_TRIPLESNEAKER: + case KRITEM_TRIPLEBANANA: + case KRITEM_TRIPLEORBINAUT: + return 3; + + case KRITEM_QUADORBINAUT: + return 4; + + case KITEM_BALLHOG: // Not a special result, but has a special amount + return 5; + + case KRITEM_TENFOLDBANANA: + return 10; + + default: + return 1; + } +} + +tic_t K_GetItemCooldown(SINT8 itemResult) +{ + SINT8 itemType = K_ItemResultToType(itemResult); + + if (itemType < 1 || itemType >= NUMKARTITEMS) + { + return 0; + } + + return itemCooldowns[itemType - 1]; +} + +void K_SetItemCooldown(SINT8 itemResult, tic_t time) +{ + SINT8 itemType = K_ItemResultToType(itemResult); + + if (itemType < 1 || itemType >= NUMKARTITEMS) + { + return; + } + + itemCooldowns[itemType - 1] = max(itemCooldowns[itemType - 1], time); +} + +void K_RunItemCooldowns(void) +{ + size_t i; + + for (i = 0; i < NUMKARTITEMS-1; i++) + { + if (itemCooldowns[i] > 0) + { + itemCooldowns[i]--; + } + } +} + + /** \brief Item Roulette for Kart \param player player @@ -457,70 +561,31 @@ INT32 K_GetShieldFromItem(INT32 item) */ static void K_KartGetItemResult(player_t *player, SINT8 getitem) { - if (getitem == KITEM_SPB || getitem == KITEM_SHRINK) // Indirect items - indirectitemcooldown = 20*TICRATE; + if (getitem == KITEM_SPB || getitem == KITEM_SHRINK) + { + K_SetItemCooldown(getitem, 20*TICRATE); + } player->botvars.itemdelay = TICRATE; player->botvars.itemconfirm = 0; - switch (getitem) - { - // Special roulettes first, then the generic ones are handled by default - case KRITEM_DUALSNEAKER: // Sneaker x2 - player->itemtype = KITEM_SNEAKER; - player->itemamount = 2; - break; - case KRITEM_TRIPLESNEAKER: // Sneaker x3 - player->itemtype = KITEM_SNEAKER; - player->itemamount = 3; - break; - case KRITEM_TRIPLEBANANA: // Banana x3 - player->itemtype = KITEM_BANANA; - player->itemamount = 3; - break; - case KRITEM_TENFOLDBANANA: // Banana x10 - player->itemtype = KITEM_BANANA; - player->itemamount = 10; - break; - case KRITEM_TRIPLEORBINAUT: // Orbinaut x3 - player->itemtype = KITEM_ORBINAUT; - player->itemamount = 3; - break; - case KRITEM_QUADORBINAUT: // Orbinaut x4 - player->itemtype = KITEM_ORBINAUT; - player->itemamount = 4; - break; - case KRITEM_DUALJAWZ: // Jawz x2 - player->itemtype = KITEM_JAWZ; - player->itemamount = 2; - break; - case KITEM_BALLHOG: // Ballhog x5 - player->itemtype = KITEM_BALLHOG; - player->itemamount = 5; - break; - default: - if (getitem <= 0 || getitem >= NUMKARTRESULTS) // Sad (Fallback) - { - if (getitem != 0) - CONS_Printf("ERROR: P_KartGetItemResult - Item roulette gave bad item (%d) :(\n", getitem); - player->itemtype = KITEM_SAD; - } - else - player->itemtype = getitem; - player->itemamount = 1; - break; - } + player->itemtype = K_ItemResultToType(getitem); + player->itemamount = K_ItemResultToAmount(getitem); } -fixed_t K_ItemOddsScale(UINT8 numPlayers, boolean spbrush) +fixed_t K_ItemOddsScale(UINT8 playerCount) { const UINT8 basePlayer = 8; // The player count we design most of the game around. - UINT8 playerCount = (spbrush ? 2 : numPlayers); fixed_t playerScaling = 0; + if (playerCount < 2) + { + // Cap to 1v1 scaling + playerCount = 2; + } + // Then, it multiplies it further if the player count isn't equal to basePlayer. // This is done to make low player count races more interesting and high player count rates more fair. - // (If you're in SPB mode and in 2nd place, it acts like it's a 1v1, so the catch-up game is not weakened.) if (playerCount < basePlayer) { // Less than basePlayer: increase odds significantly. @@ -537,12 +602,12 @@ fixed_t K_ItemOddsScale(UINT8 numPlayers, boolean spbrush) return playerScaling; } -UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush) +UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers) { if (mapobjectscale != FRACUNIT) { // Bring back to normal scale. - distance = FixedDiv(distance * FRACUNIT, mapobjectscale) / FRACUNIT; + distance = FixedDiv(distance, mapobjectscale); } if (franticitems == true) @@ -551,14 +616,11 @@ UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush) distance = (15 * distance) / 14; } - if (numPlayers > 0) - { - // Items get crazier with the fewer players that you have. - distance = FixedMul( - distance * FRACUNIT, - FRACUNIT + (K_ItemOddsScale(numPlayers, spbrush) / 2) - ) / FRACUNIT; - } + // Items get crazier with the fewer players that you have. + distance = FixedMul( + distance, + FRACUNIT + (K_ItemOddsScale(numPlayers) / 2) + ); return distance; } @@ -574,20 +636,23 @@ INT32 K_KartGetItemOdds( UINT8 pos, SINT8 item, UINT32 ourDist, fixed_t mashed, - boolean spbrush, boolean bot, boolean rival) + boolean bot, boolean rival) { INT32 newodds; INT32 i; UINT8 pingame = 0, pexiting = 0; - SINT8 first = -1, second = -1; + player_t *first = NULL; + player_t *second = NULL; + UINT32 firstDist = UINT32_MAX; - UINT32 secondToFirst = UINT32_MAX; + UINT32 secondDist = UINT32_MAX; + UINT32 secondToFirst = 0; + boolean isFirst = false; boolean powerItem = false; boolean cooldownOnStart = false; - boolean indirectItem = false; boolean notNearEnd = false; INT32 shieldtype = KSHIELD_NONE; @@ -596,7 +661,15 @@ INT32 K_KartGetItemOdds( I_Assert(KartItemCVars[NUMKARTRESULTS-2] != NULL); // Make sure this exists if (!KartItemCVars[item-1]->value && !modeattacking) + { return 0; + } + + if (K_GetItemCooldown(item) > 0) + { + // Cooldown is still running, don't give another. + return 0; + } /* if (bot) @@ -649,28 +722,32 @@ INT32 K_KartGetItemOdds( return 0; } - if (players[i].mo && gametype == GT_RACE) + if (players[i].position == 1) { - if (players[i].position == 1 && first == -1) - first = i; - if (players[i].position == 2 && second == -1) - second = i; + first = &players[i]; + } + + if (players[i].position == 2) + { + second = &players[i]; } } - if (first != -1 && second != -1) // calculate 2nd's distance from 1st, for SPB + if (first != NULL) // calculate 2nd's distance from 1st, for SPB { - firstDist = players[first].distancetofinish; + firstDist = first->distancetofinish; + isFirst = (ourDist <= firstDist); + } - if (mapobjectscale != FRACUNIT) - { - firstDist = FixedDiv(firstDist * FRACUNIT, mapobjectscale) / FRACUNIT; - } + if (second != NULL) + { + secondDist = second->distancetofinish; + } - secondToFirst = K_ScaleItemDistance( - players[second].distancetofinish - players[first].distancetofinish, - pingame, spbrush - ); + if (first != NULL && second != NULL) + { + secondToFirst = secondDist - firstDist; + secondToFirst = K_ScaleItemDistance(secondToFirst, 16 - pingame); // Reversed scaling, so 16P is like 1v1, and 1v1 is like 16P } switch (item) @@ -680,6 +757,7 @@ INT32 K_KartGetItemOdds( case KITEM_SUPERRING: notNearEnd = true; break; + case KITEM_ROCKETSNEAKER: case KITEM_JAWZ: case KITEM_LANDMINE: @@ -692,11 +770,13 @@ INT32 K_KartGetItemOdds( case KRITEM_DUALJAWZ: powerItem = true; break; + case KRITEM_TRIPLEBANANA: case KRITEM_TENFOLDBANANA: powerItem = true; notNearEnd = true; break; + case KITEM_INVINCIBILITY: case KITEM_MINE: case KITEM_GROW: @@ -705,40 +785,46 @@ INT32 K_KartGetItemOdds( cooldownOnStart = true; powerItem = true; break; + case KITEM_SPB: cooldownOnStart = true; - indirectItem = true; notNearEnd = true; - if (firstDist < ENDDIST) // No SPB near the end of the race + if (firstDist < ENDDIST*2 // No SPB when 1st is almost done + || isFirst == true) // No SPB for 1st ever { newodds = 0; } else { - const INT32 distFromStart = max(0, (INT32)secondToFirst - SPBSTARTDIST); - const INT32 distRange = SPBFORCEDIST - SPBSTARTDIST; - const INT32 mulMax = 3; - - INT32 multiplier = (distFromStart * mulMax) / distRange; + const UINT32 dist = max(0, ((signed)secondToFirst) - SPBSTARTDIST); + const UINT32 distRange = SPBFORCEDIST - SPBSTARTDIST; + const UINT8 maxOdds = 20; + fixed_t multiplier = (dist * FRACUNIT) / distRange; if (multiplier < 0) + { multiplier = 0; - if (multiplier > mulMax) - multiplier = mulMax; + } - newodds *= multiplier; + if (multiplier > FRACUNIT) + { + multiplier = FRACUNIT; + } + + newodds = FixedMul(maxOdds * 4, multiplier); } break; + case KITEM_SHRINK: cooldownOnStart = true; powerItem = true; - indirectItem = true; notNearEnd = true; if (pingame-1 <= pexiting) newodds = 0; break; + case KITEM_LIGHTNINGSHIELD: cooldownOnStart = true; powerItem = true; @@ -746,6 +832,7 @@ INT32 K_KartGetItemOdds( if (spbplace != -1) newodds = 0; break; + default: break; } @@ -756,12 +843,8 @@ INT32 K_KartGetItemOdds( return newodds; } - if ((indirectItem == true) && (indirectitemcooldown > 0)) - { - // Too many items that act indirectly in a match can feel kind of bad. - newodds = 0; - } - else if ((cooldownOnStart == true) && (leveltime < (30*TICRATE)+starttime)) + + if ((cooldownOnStart == true) && (leveltime < (30*TICRATE)+starttime)) { // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) newodds = 0; @@ -788,7 +871,7 @@ INT32 K_KartGetItemOdds( fracOdds *= 2; } - fracOdds = FixedMul(fracOdds, FRACUNIT + K_ItemOddsScale(pingame, spbrush)); + fracOdds = FixedMul(fracOdds, FRACUNIT + K_ItemOddsScale(pingame)); if (mashed > 0) { @@ -804,7 +887,7 @@ INT32 K_KartGetItemOdds( //{ SRB2kart Roulette Code - Distance Based, yes waypoints -UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush) +UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper) { UINT8 i; UINT8 useodds = 0; @@ -832,7 +915,7 @@ UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbum i, j, player->distancetofinish, mashed, - spbrush, player->bot, (player->bot && player->botvars.rival) + player->bot, (player->bot && player->botvars.rival) ) > 0) { available = true; @@ -947,6 +1030,80 @@ INT32 K_GetRollingRouletteItem(player_t *player) return translation[(player->itemroulette % roulette_size) / 3]; } +boolean K_ForcedSPB(player_t *player) +{ + player_t *first = NULL; + player_t *second = NULL; + UINT32 secondToFirst = UINT32_MAX; + UINT8 pingame = 0; + UINT8 i; + + if (!cv_selfpropelledbomb.value) + { + return false; + } + + if (!(gametyperules & GTR_CIRCUIT)) + { + return false; + } + + if (player->position <= 1) + { + return false; + } + + if (spbplace != -1) + { + return false; + } + + if (itemCooldowns[KITEM_SPB - 1] > 0) + { + return false; + } + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + { + continue; + } + + if (players[i].exiting) + { + return false; + } + + pingame++; + + if (players[i].position == 1) + { + first = &players[i]; + } + + if (players[i].position == 2) + { + second = &players[i]; + } + } + +#if 0 + if (pingame <= 2) + { + return false; + } +#endif + + if (first != NULL && second != NULL) + { + secondToFirst = second->distancetofinish - first->distancetofinish; + secondToFirst = K_ScaleItemDistance(secondToFirst, 16 - pingame); + } + + return (secondToFirst >= SPBFORCEDIST); +} + static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) { INT32 i; @@ -958,8 +1115,6 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) INT32 totalspawnchance = 0; UINT8 bestbumper = 0; fixed_t mashed = 0; - boolean dontforcespb = false; - boolean spbrush = false; // This makes the roulette cycle through items - if this is 0, you shouldn't be here. if (!player->itemroulette) @@ -971,17 +1126,13 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) { if (!playeringame[i] || players[i].spectator) continue; + pingame++; - if (players[i].exiting) - dontforcespb = true; + if (players[i].bumpers > bestbumper) bestbumper = players[i].bumpers; } - // No forced SPB in 1v1s, it has to be randomly rolled - if (pingame <= 2) - dontforcespb = true; - // This makes the roulette produce the random noises. if ((player->itemroulette % 3) == 1 && P_IsDisplayPlayer(player) && !demo.freecam) { @@ -1008,9 +1159,6 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) else if (!(player->itemroulette >= (TICRATE*3))) return; - if (cmd->buttons & BT_ATTACK) - player->pflags |= PF_ATTACKDOWN; - for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && !players[i].spectator @@ -1032,14 +1180,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) } } - if (spbplace != -1 && player->position == spbplace+1) - { - // SPB Rush Mode: It's 2nd place's job to catch-up items and make 1st place's job hell - pdis = (3 * pdis) / 2; - spbrush = true; - } - - pdis = K_ScaleItemDistance(pdis, pingame, spbrush); + pdis = K_ScaleItemDistance(pdis, pingame); if (player->bot && player->botvars.rival) { @@ -1153,10 +1294,8 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) } // SPECIAL CASE No. 5: - // Force SPB onto 2nd if they get too far behind - if ((gametyperules & GTR_CIRCUIT) && player->position == 2 && pdis > SPBFORCEDIST - && spbplace == -1 && !indirectitemcooldown && !dontforcespb - && cv_selfpropelledbomb.value) + // Force SPB if 2nd is way too far behind + if (K_ForcedSPB(player) == true) { K_KartGetItemResult(player, KITEM_SPB); player->karthud[khud_itemblink] = TICRATE; @@ -1174,7 +1313,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) spawnchance[i] = 0; // Split into another function for a debug function below - useodds = K_FindUseodds(player, mashed, pdis, bestbumper, spbrush); + useodds = K_FindUseodds(player, mashed, pdis, bestbumper); for (i = 1; i < NUMKARTRESULTS; i++) { @@ -1182,7 +1321,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) useodds, i, player->distancetofinish, mashed, - spbrush, player->bot, (player->bot && player->botvars.rival)) + player->bot, (player->bot && player->botvars.rival)) ); } @@ -2540,7 +2679,7 @@ void K_KartMoveAnimation(player_t *player) if (!lookback) { - player->pflags &= ~PF_LOOKDOWN; + player->pflags &= ~PF_GAINAX; // Uses turning over steering -- it's important to show player feedback immediately. if (player->cmd.turning < -minturn) @@ -2564,7 +2703,7 @@ void K_KartMoveAnimation(player_t *player) destGlanceDir = -(2*intsign(player->aizdriftturn)); player->glanceDir = destGlanceDir; drift = turndir = 0; - player->pflags &= ~PF_LOOKDOWN; + player->pflags &= ~PF_GAINAX; } else if (player->aizdriftturn) { @@ -2610,14 +2749,14 @@ void K_KartMoveAnimation(player_t *player) gainaxstate = S_GAINAX_MID1; } - if (destGlanceDir && !(player->pflags & PF_LOOKDOWN)) + if (destGlanceDir && !(player->pflags & PF_GAINAX)) { mobj_t *gainax = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_GAINAX); gainax->movedir = (destGlanceDir < 0) ? (ANGLE_270-ANG10) : (ANGLE_90+ANG10); P_SetTarget(&gainax->target, player->mo); P_SetMobjState(gainax, gainaxstate); gainax->flags2 |= MF2_AMBUSH; - player->pflags |= PF_LOOKDOWN; + player->pflags |= PF_GAINAX; } } else if (K_GetForwardMove(player) < 0 && destGlanceDir == 0) @@ -2874,7 +3013,7 @@ void K_KartMoveAnimation(player_t *player) } if (!player->glanceDir) - player->pflags &= ~PF_LOOKDOWN; + player->pflags &= ~PF_GAINAX; // Update lastspeed value -- we use to display slow driving frames instead of fast driving when slowing down. player->lastspeed = player->speed; @@ -3233,7 +3372,7 @@ static void K_GetKartBoostPower(player_t *player) if (player->startboost) // Startup Boost { - ADDBOOST(FRACUNIT, 4*FRACUNIT, sliptidehandling/2); // + 100% top speed, + 400% acceleration, +25% handling + ADDBOOST(FRACUNIT, 4*FRACUNIT, sliptidehandling); // + 100% top speed, + 400% acceleration, +50% handling } if (player->driftboost) // Drift Boost @@ -3261,9 +3400,14 @@ static void K_GetKartBoostPower(player_t *player) ADDBOOST(player->trickboostpower, 5*FRACUNIT, 0); // % speed, 500% accel, 0% handling } + if (player->gateBoost) // SPB Juicebox boost + { + ADDBOOST(3*FRACUNIT/4, 4*FRACUNIT, sliptidehandling/2); // + 75% top speed, + 400% acceleration, +25% handling + } + if (player->ringboost) // Ring Boost { - ADDBOOST(FRACUNIT/5, 4*FRACUNIT, 0); // + 20% top speed, + 400% acceleration, +0% handling + ADDBOOST(FRACUNIT/4, 4*FRACUNIT, 0); // + 20% top speed, + 400% acceleration, +0% handling } if (player->eggmanexplode) // Ready-to-explode @@ -3861,10 +4005,38 @@ angle_t K_StumbleSlope(angle_t angle, angle_t pitch, angle_t roll) return slope; } +static void K_StumblePlayer(player_t *player) +{ + P_ResetPlayer(player); + +#if 0 + // Single, medium bounce + player->tumbleBounces = TUMBLEBOUNCES; + player->tumbleHeight = 30; +#else + // Two small bounces + player->tumbleBounces = TUMBLEBOUNCES-1; + player->tumbleHeight = 20; +#endif + + player->pflags &= ~PF_TUMBLESOUND; + S_StartSound(player->mo, sfx_s3k9b); + + // and then modulate momz like that... + player->mo->momz = K_TumbleZ(player->mo, player->tumbleHeight * FRACUNIT); + + P_SetPlayerMobjState(player->mo, S_KART_SPINOUT); + + if (P_IsDisplayPlayer(player)) + P_StartQuake(64<mo->pitch = player->mo->roll = 0; +} + boolean K_CheckStumble(player_t *player, angle_t oldPitch, angle_t oldRoll, boolean fromAir) { angle_t steepVal = ANGLE_MAX; - fixed_t gravityadjust; angle_t oldSlope, newSlope; angle_t slopeDelta; @@ -3914,36 +4086,7 @@ boolean K_CheckStumble(player_t *player, angle_t oldPitch, angle_t oldRoll, bool // Oh jeez, you landed on your side. // You get to tumble. - P_ResetPlayer(player); - -#if 0 - // Single, medium bounce - player->tumbleBounces = TUMBLEBOUNCES; - player->tumbleHeight = 30; -#else - // Two small bounces - player->tumbleBounces = TUMBLEBOUNCES-1; - player->tumbleHeight = 20; -#endif - - player->pflags &= ~PF_TUMBLESOUND; - S_StartSound(player->mo, sfx_s3k9b); - - gravityadjust = P_GetMobjGravity(player->mo)/2; // so we'll halve it for our calculations. - - if (player->mo->eflags & MFE_UNDERWATER) - gravityadjust /= 2; // halve "gravity" underwater - - // and then modulate momz like that... - player->mo->momz = -gravityadjust * player->tumbleHeight; - - P_SetPlayerMobjState(player->mo, S_KART_SPINOUT); - - if (P_IsDisplayPlayer(player)) - P_StartQuake(64<mo->pitch = player->mo->roll = 0; + K_StumblePlayer(player); return true; } @@ -4177,23 +4320,45 @@ void K_ApplyTripWire(player_t *player, tripwirestate_t state) INT32 K_ExplodePlayer(player_t *player, mobj_t *inflictor, mobj_t *source) // A bit of a hack, we just throw the player up higher here and extend their spinout timer { INT32 ringburst = 10; + fixed_t spbMultiplier = FRACUNIT; (void)source; K_DirectorFollowAttack(player, inflictor, source); - player->mo->momz = 18*mapobjectscale*P_MobjFlip(player->mo); // please stop forgetting mobjflip checks!!!! + if (inflictor != NULL && P_MobjWasRemoved(inflictor) == false) + { + if (inflictor->type == MT_SPBEXPLOSION && inflictor->movefactor) + { + spbMultiplier = inflictor->movefactor; + + if (spbMultiplier <= 0) + { + // Convert into stumble. + K_StumblePlayer(player); + return 0; + } + else if (spbMultiplier < FRACUNIT) + { + spbMultiplier = FRACUNIT; + } + } + } + + player->mo->momz = 18 * mapobjectscale * P_MobjFlip(player->mo); // please stop forgetting mobjflip checks!!!! player->mo->momx = player->mo->momy = 0; player->spinouttype = KSPIN_EXPLOSION; player->spinouttimer = (3*TICRATE/2)+2; - if (inflictor && !P_MobjWasRemoved(inflictor)) + if (spbMultiplier != FRACUNIT) { - if (inflictor->type == MT_SPBEXPLOSION && inflictor->extravalue1) + player->mo->momz = FixedMul(player->mo->momz, spbMultiplier); + player->spinouttimer = FixedMul(player->spinouttimer, spbMultiplier + ((spbMultiplier - FRACUNIT) / 2)); + + ringburst = FixedMul(ringburst * FRACUNIT, spbMultiplier) / FRACUNIT; + if (ringburst > 20) { - player->spinouttimer = ((5*player->spinouttimer)/2)+1; - player->mo->momz *= 2; ringburst = 20; } } @@ -4763,9 +4928,10 @@ static void K_SpawnDriftElectricity(player_t *player) } } -void K_SpawnDriftElectricSparks(player_t *player) +void K_SpawnDriftElectricSparks(player_t *player, int color, boolean shockwave) { SINT8 hdir, vdir, i; + int shockscale = shockwave ? 2 : 1; mobj_t *mo = player->mo; angle_t momangle = K_MomentumAngle(mo) + ANGLE_180; @@ -4775,12 +4941,7 @@ void K_SpawnDriftElectricSparks(player_t *player) fixed_t z = FixedDiv(mo->height, 2 * mo->scale); // P_SpawnMobjFromMobj will rescale fixed_t sparkspeed = mobjinfo[MT_DRIFTELECTRICSPARK].speed; - fixed_t sparkradius = 2 * mobjinfo[MT_DRIFTELECTRICSPARK].radius; - UINT16 color = K_DriftSparkColor(player, player->driftcharge); - - // if the sparks are spawned from first blood rather than drift boost, color will be SKINCOLOR_NONE. ew! - if (color == SKINCOLOR_NONE) - color = SKINCOLOR_SILVER; + fixed_t sparkradius = 2 * shockscale * mobjinfo[MT_DRIFTELECTRICSPARK].radius; for (hdir = -1; hdir <= 1; hdir += 2) { @@ -4803,6 +4964,11 @@ void K_SpawnDriftElectricSparks(player_t *player) spark->momx += mo->momx; // copy player speed spark->momy += mo->momy; spark->momz += P_GetMobjZMovement(mo); + spark->destscale = shockscale * spark->scale; + P_SetScale(spark, shockscale * spark->scale); + + if (shockwave) + spark->frame |= FF_ADD; sparkangle += ANGLE_90; } @@ -5899,11 +6065,6 @@ void K_DoSneaker(player_t *player, INT32 type) } } - if (type != 0) - { - player->pflags |= PF_ATTACKDOWN; - } - player->sneakertimer = sneakertime; // set angle for spun out players: @@ -5912,40 +6073,45 @@ void K_DoSneaker(player_t *player, INT32 type) static void K_DoShrink(player_t *user) { - mobj_t *mobj, *next; - S_StartSound(user->mo, sfx_kc46); // Sound the BANG! - user->pflags |= PF_ATTACKDOWN; Obj_CreateShrinkPohbees(user); - // kill everything in the kitem list while we're at it: - for (mobj = kitemcap; mobj; mobj = next) +#if 0 { - next = mobj->itnext; + mobj_t *mobj, *next; - // check if the item is being held by a player behind us before removing it. - // check if the item is a "shield" first, bc i'm p sure thrown items keep the player that threw em as target anyway - - if (mobj->type == MT_BANANA_SHIELD || mobj->type == MT_JAWZ_SHIELD || - mobj->type == MT_SSMINE_SHIELD || mobj->type == MT_EGGMANITEM_SHIELD || - mobj->type == MT_SINK_SHIELD || mobj->type == MT_ORBINAUT_SHIELD || - mobj->type == MT_DROPTARGET_SHIELD) + // kill everything in the kitem list while we're at it: + for (mobj = kitemcap; mobj; mobj = next) { - if (mobj->target && mobj->target->player) + next = mobj->itnext; + + if (mobj->type == MT_SPB) { - if (mobj->target->player->position > user->position) - continue; // this guy's behind us, don't take his stuff away! + continue; } + + // check if the item is being held by a player behind us before removing it. + // check if the item is a "shield" first, bc i'm p sure thrown items keep the player that threw em as target anyway + + if (mobj->type == MT_BANANA_SHIELD || mobj->type == MT_JAWZ_SHIELD || + mobj->type == MT_SSMINE_SHIELD || mobj->type == MT_EGGMANITEM_SHIELD || + mobj->type == MT_SINK_SHIELD || mobj->type == MT_ORBINAUT_SHIELD || + mobj->type == MT_DROPTARGET_SHIELD) + { + if (mobj->target && mobj->target->player) + { + if (mobj->target->player->position > user->position) + continue; // this guy's behind us, don't take his stuff away! + } + } + + mobj->destscale = 0; + mobj->flags &= ~(MF_SOLID|MF_SHOOTABLE|MF_SPECIAL); + mobj->flags |= MF_NOCLIPTHING; // Just for safety } - - mobj->destscale = 0; - mobj->flags &= ~(MF_SOLID|MF_SHOOTABLE|MF_SPECIAL); - mobj->flags |= MF_NOCLIPTHING; // Just for safety - - if (mobj->type == MT_SPB) - spbplace = -1; } +#endif } void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound) @@ -6363,8 +6529,7 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 useodds, i, UINT32_MAX, 0, - false, false, false - ) + false, false) ); } @@ -6380,46 +6545,8 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 // K_KartGetItemResult requires a player // but item roulette will need rewritten to change this - switch (i) - { - // Special roulettes first, then the generic ones are handled by default - case KRITEM_DUALSNEAKER: // Sneaker x2 - newType = KITEM_SNEAKER; - newAmount = 2; - break; - case KRITEM_TRIPLESNEAKER: // Sneaker x3 - newType = KITEM_SNEAKER; - newAmount = 3; - break; - case KRITEM_TRIPLEBANANA: // Banana x3 - newType = KITEM_BANANA; - newAmount = 3; - break; - case KRITEM_TENFOLDBANANA: // Banana x10 - newType = KITEM_BANANA; - newAmount = 10; - break; - case KRITEM_TRIPLEORBINAUT: // Orbinaut x3 - newType = KITEM_ORBINAUT; - newAmount = 3; - break; - case KRITEM_QUADORBINAUT: // Orbinaut x4 - newType = KITEM_ORBINAUT; - newAmount = 4; - break; - case KRITEM_DUALJAWZ: // Jawz x2 - newType = KITEM_JAWZ; - newAmount = 2; - break; - case KITEM_BALLHOG: // Ballhog x5 - newType = KITEM_BALLHOG; - newAmount = 5; - break; - default: - newType = i; - newAmount = 1; - break; - } + newType = K_ItemResultToType(i); + newAmount = K_ItemResultToAmount(i); if (newAmount > 1) { @@ -7605,7 +7732,8 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) // Speed lines if (player->sneakertimer || player->ringboost || player->driftboost || player->startboost - || player->eggmanexplode || player->trickboost) + || player->eggmanexplode || player->trickboost + || player->gateBoost) { #if 0 if (player->invincibilitytimer) @@ -7855,6 +7983,9 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->strongdriftboost) player->strongdriftboost--; + if (player->gateBoost) + player->gateBoost--; + if (player->startboost > 0 && onground == true) { player->startboost--; @@ -8958,7 +9089,7 @@ static void K_KartDrift(player_t *player, boolean onground) player->strongdriftboost = 85; K_SpawnDriftBoostExplosion(player, 3); - K_SpawnDriftElectricSparks(player); + K_SpawnDriftElectricSparks(player, K_DriftSparkColor(player, player->driftcharge), false); } else if (player->driftcharge >= dsfour) { @@ -8972,7 +9103,7 @@ static void K_KartDrift(player_t *player, boolean onground) player->strongdriftboost = 125; K_SpawnDriftBoostExplosion(player, 4); - K_SpawnDriftElectricSparks(player); + K_SpawnDriftElectricSparks(player, K_DriftSparkColor(player, player->driftcharge), false); } } @@ -9329,7 +9460,7 @@ static INT32 K_FlameShieldMax(player_t *player) } disttofinish = player->distancetofinish - disttofinish; - distv = FixedMul(distv * FRACUNIT, mapobjectscale) / FRACUNIT; + distv = FixedMul(distv, mapobjectscale); return min(16, 1 + (disttofinish / distv)); } @@ -9935,7 +10066,7 @@ void K_UnsetItemOut(player_t *player) void K_MoveKartPlayer(player_t *player, boolean onground) { ticcmd_t *cmd = &player->cmd; - boolean ATTACK_IS_DOWN = ((cmd->buttons & BT_ATTACK) && !(player->pflags & PF_ATTACKDOWN)); + boolean ATTACK_IS_DOWN = ((cmd->buttons & BT_ATTACK) && !(player->oldcmd.buttons & BT_ATTACK)); boolean HOLDING_ITEM = (player->pflags & (PF_ITEMOUT|PF_EGGMANOUT)); boolean NO_HYUDORO = (player->stealingtimer == 0); @@ -9970,11 +10101,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->pflags &= ~PF_USERINGS; } - if ((player->pflags & PF_ATTACKDOWN) && !(cmd->buttons & BT_ATTACK)) - player->pflags &= ~PF_ATTACKDOWN; - else if (cmd->buttons & BT_ATTACK) - player->pflags |= PF_ATTACKDOWN; - if (player && player->mo && player->mo->health > 0 && !player->spectator && !P_PlayerInPain(player) && !mapreset && leveltime > introtime) { // First, the really specific, finicky items that function without the item being directly in your item slot. @@ -9982,7 +10108,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) // Ring boosting if (player->pflags & PF_USERINGS) { - if ((player->pflags & PF_ATTACKDOWN) && !player->ringdelay && player->rings > 0) + if ((cmd->buttons & BT_ATTACK) && !player->ringdelay && player->rings > 0) { mobj_t *ring = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_RING); P_SetMobjState(ring, S_FASTRING1); @@ -10642,9 +10768,10 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->pflags &= ~PF_RINGLOCK; // reset ring lock if (player->itemtype == KITEM_SPB - || player->itemtype == KITEM_SHRINK - || player->growshrinktimer < 0) - indirectitemcooldown = 20*TICRATE; + || player->itemtype == KITEM_SHRINK) + { + K_SetItemCooldown(player->itemtype, 20*TICRATE); + } if (player->hyudorotimer > 0) { @@ -10740,6 +10867,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->trickpanel = 0; K_trickPanelTimingVisual(player, momz); // fail trick visual P_SetPlayerMobjState(player->mo, S_KART_SPINOUT); + if (player->pflags & (PF_ITEMOUT|PF_EGGMANOUT)) + K_DropHnextList(player, true); } else if (!(player->pflags & PF_TRICKDELAY)) // don't allow tricking at the same frame you tumble obv diff --git a/src/k_kart.h b/src/k_kart.h index 98a372188..a3f7dae0c 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -44,12 +44,18 @@ fixed_t K_GetKartGameSpeedScalar(SINT8 value); extern consvar_t *KartItemCVars[NUMKARTRESULTS-1]; -UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush); -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); +UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper); +fixed_t K_ItemOddsScale(UINT8 numPlayers); +UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers); +INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, fixed_t mashed, boolean bot, boolean rival); INT32 K_GetRollingRouletteItem(player_t *player); +boolean K_ForcedSPB(player_t *player); INT32 K_GetShieldFromItem(INT32 item); +SINT8 K_ItemResultToType(SINT8 getitem); +UINT8 K_ItemResultToAmount(SINT8 getitem); +tic_t K_GetItemCooldown(SINT8 itemResult); +void K_SetItemCooldown(SINT8 itemResult, tic_t time); +void K_RunItemCooldowns(void); fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against); boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2); boolean K_KartSolidBounce(mobj_t *bounceMobj, mobj_t *solidMobj); @@ -119,7 +125,7 @@ INT32 K_GetKartDriftSparkValue(player_t *player); INT32 K_StairJankFlip(INT32 value); INT32 K_GetKartDriftSparkValueForStage(player_t *player, UINT8 stage); void K_SpawnDriftBoostExplosion(player_t *player, int stage); -void K_SpawnDriftElectricSparks(player_t *player); +void K_SpawnDriftElectricSparks(player_t *player, int color, boolean shockwave); void K_KartUpdatePosition(player_t *player); 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_menu.h b/src/k_menu.h index 982d0bcf9..a55756788 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -545,7 +545,7 @@ extern struct menutransition_s { extern boolean menuwipe; extern consvar_t cv_showfocuslost; -extern consvar_t cv_chooseskin, cv_serversort; +extern consvar_t cv_chooseskin, cv_serversort, cv_menujam_update; void M_SetMenuDelay(UINT8 i); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index cd5fcbaf1..9e18e181f 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -3386,34 +3386,14 @@ void M_DrawItemToggles(void) cv = KartItemCVars[currentMenu->menuitems[thisitem].mvar1-1]; translucent = (cv->value ? 0 : V_TRANSLUCENT); - switch (currentMenu->menuitems[thisitem].mvar1) - { - case KRITEM_DUALSNEAKER: - case KRITEM_DUALJAWZ: - drawnum = 2; - break; - case KRITEM_TRIPLESNEAKER: - case KRITEM_TRIPLEBANANA: - case KRITEM_TRIPLEORBINAUT: - drawnum = 3; - break; - case KRITEM_QUADORBINAUT: - drawnum = 4; - break; - case KRITEM_TENFOLDBANANA: - drawnum = 10; - break; - default: - drawnum = 0; - break; - } + drawnum = K_ItemResultToAmount(currentMenu->menuitems[thisitem].mvar1); if (cv->value) V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBG", PU_CACHE)); else V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBGD", PU_CACHE)); - if (drawnum != 0) + if (drawnum > 1) { V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISMUL", PU_CACHE)); V_DrawScaledPatch(x, y, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[thisitem].mvar1, true), PU_CACHE)); @@ -3453,30 +3433,14 @@ void M_DrawItemToggles(void) cv = KartItemCVars[currentMenu->menuitems[itemOn].mvar1-1]; translucent = (cv->value ? 0 : V_TRANSLUCENT); - switch (currentMenu->menuitems[itemOn].mvar1) - { - case KRITEM_DUALSNEAKER: - case KRITEM_DUALJAWZ: - drawnum = 2; - break; - case KRITEM_TRIPLESNEAKER: - case KRITEM_TRIPLEBANANA: - drawnum = 3; - break; - case KRITEM_TENFOLDBANANA: - drawnum = 10; - break; - default: - drawnum = 0; - break; - } + drawnum = K_ItemResultToAmount(currentMenu->menuitems[itemOn].mvar1); if (cv->value) V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBG", PU_CACHE)); else V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBGD", PU_CACHE)); - if (drawnum != 0) + if (drawnum > 1) { V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITMUL", PU_CACHE)); V_DrawScaledPatch(onx-1, ony-2, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[itemOn].mvar1, false), PU_CACHE)); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index a1b8505cc..9d57fa6ea 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -143,6 +143,10 @@ consvar_t cv_showfocuslost = CVAR_INIT ("showfocuslost", "Yes", CV_SAVE, CV_YesN static CV_PossibleValue_t skins_cons_t[MAXSKINS+1] = {{1, DEFAULTSKIN}}; consvar_t cv_chooseskin = CVAR_INIT ("chooseskin", DEFAULTSKIN, CV_HIDDEN, skins_cons_t, NULL); +consvar_t cv_menujam_update = CVAR_INIT ("menujam_update", "Off", CV_SAVE, CV_OnOff, NULL); +static CV_PossibleValue_t menujam_cons_t[] = {{0, "menu"}, {1, "menu2"}, {2, "menu3"}, {0, NULL}}; +static consvar_t cv_menujam = CVAR_INIT ("menujam", "0", CV_SAVE, menujam_cons_t, NULL); + // This gametype list is integral for many different reasons. // When you add gametypes here, don't forget to update them in dehacked.c and doomstat.h! CV_PossibleValue_t gametype_cons_t[NUMGAMETYPES+1]; @@ -945,7 +949,13 @@ void M_StartControlPanel(void) paused = false; CON_ToggleOff(); - S_ChangeMusicInternal("menu", true); + if (cv_menujam_update.value) + { + CV_AddValue(&cv_menujam, 1); + CV_SetValue(&cv_menujam_update, 0); + } + + S_ChangeMusicInternal(cv_menujam.string, true); } menuactive = true; @@ -1686,6 +1696,14 @@ void M_Init(void) CV_RegisterVar(&cv_chooseskin); CV_RegisterVar(&cv_autorecord); + // don't lose your position in the jam cycle + CV_RegisterVar(&cv_menujam_update); + CV_RegisterVar(&cv_menujam); + +#ifndef NONET + CV_RegisterVar(&cv_serversort); +#endif + if (dedicated) return; @@ -1712,20 +1730,6 @@ void M_Init(void) CV_RegisterVar(&cv_dummyaddonsearch); M_UpdateMenuBGImage(true); - -#if 0 -#ifdef HWRENDER - // Permanently hide some options based on render mode - if (rendermode == render_soft) - OP_VideoOptionsMenu[op_video_ogl].status = - OP_VideoOptionsMenu[op_video_kartman].status = - OP_VideoOptionsMenu[op_video_md2] .status = IT_DISABLED; -#endif -#endif - -#ifndef NONET - CV_RegisterVar(&cv_serversort); -#endif } // ================================================== diff --git a/src/k_objects.h b/src/k_objects.h index 775710c1a..d39da4247 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -20,4 +20,13 @@ void Obj_SpawnItemDebrisEffects(mobj_t *collectible, mobj_t *collector); void Obj_ItemDebrisThink(mobj_t *debris); fixed_t Obj_ItemDebrisBounce(mobj_t *debris, fixed_t momz); +/* SPB */ +void Obj_SPBThink(mobj_t *spb); +void Obj_SPBExplode(mobj_t *spb); +void Obj_SPBTouch(mobj_t *spb, mobj_t *toucher); + +/* SPB Juicebox Rings */ +void Obj_MantaRingThink(mobj_t *manta); +mobj_t *Obj_MantaRingCreate(mobj_t *spb, mobj_t *owner, mobj_t *chase); + #endif/*k_objects_H*/ diff --git a/src/k_respawn.c b/src/k_respawn.c index 9a8cc38e8..94bb04e18 100644 --- a/src/k_respawn.c +++ b/src/k_respawn.c @@ -151,6 +151,7 @@ void K_DoIngameRespawn(player_t *player) player->ringboost = 0; player->driftboost = player->strongdriftboost = 0; + player->gateBoost = 0; K_TumbleInterrupt(player); P_ResetPlayer(player); diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index cfab3696e..85e73a12f 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -192,6 +192,8 @@ static int player_get(lua_State *L) LUA_PushUserdata(L, plr->mo, META_MOBJ); else if (fastcmp(field,"cmd")) LUA_PushUserdata(L, &plr->cmd, META_TICCMD); + else if (fastcmp(field,"oldcmd")) + LUA_PushUserdata(L, &plr->oldcmd, META_TICCMD); else if (fastcmp(field,"respawn")) LUA_PushUserdata(L, &plr->respawn, META_RESPAWN); else if (fastcmp(field,"playerstate")) @@ -250,6 +252,10 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->driftboost); else if (fastcmp(field,"strongdriftboost")) lua_pushinteger(L, plr->strongdriftboost); + else if (fastcmp(field,"gateBoost")) + lua_pushinteger(L, plr->gateBoost); + else if (fastcmp(field,"gateSound")) + lua_pushinteger(L, plr->gateSound); else if (fastcmp(field,"aizdriftstraft")) lua_pushinteger(L, plr->aizdriftstrat); else if (fastcmp(field,"aizdrifttilt")) @@ -526,6 +532,8 @@ static int player_set(lua_State *L) } else if (fastcmp(field,"cmd")) return NOSET; + else if (fastcmp(field,"oldcmd")) + return NOSET; else if (fastcmp(field,"respawn")) return NOSET; else if (fastcmp(field,"playerstate")) @@ -612,6 +620,12 @@ static int player_set(lua_State *L) plr->driftcharge = luaL_checkinteger(L, 3); else if (fastcmp(field,"driftboost")) plr->driftboost = luaL_checkinteger(L, 3); + else if (fastcmp(field,"strongdriftboost")) + plr->strongdriftboost = luaL_checkinteger(L, 3); + else if (fastcmp(field,"gateBoost")) + plr->gateBoost = luaL_checkinteger(L, 3); + else if (fastcmp(field,"gateSound")) + plr->gateSound = luaL_checkinteger(L, 3); else if (fastcmp(field,"aizdriftstraft")) plr->aizdriftstrat = luaL_checkinteger(L, 3); else if (fastcmp(field,"aizdrifttilt")) diff --git a/src/lua_script.c b/src/lua_script.c index a9eb964cd..f8fc119fd 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -357,9 +357,6 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"wantedcalcdelay")) { lua_pushinteger(L, wantedcalcdelay); return 1; - } else if (fastcmp(word,"indirectitemcooldown")) { - lua_pushinteger(L, indirectitemcooldown); - return 1; } else if (fastcmp(word,"thwompsactive")) { lua_pushboolean(L, thwompsactive); return 1; @@ -445,8 +442,6 @@ int LUA_WriteGlobals(lua_State *L, const char *word) racecountdown = (tic_t)luaL_checkinteger(L, 2); else if (fastcmp(word,"exitcountdown")) exitcountdown = (tic_t)luaL_checkinteger(L, 2); - else if (fastcmp(word,"indirectitemcooldown")) - indirectitemcooldown = (tic_t)luaL_checkinteger(L, 2); else return 0; diff --git a/src/m_cheat.c b/src/m_cheat.c index 5eddbce4a..fbf7e0bcb 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -1053,11 +1053,11 @@ void OP_ObjectplaceMovement(player_t *player) } - if (player->pflags & PF_ATTACKDOWN) + if (player->pflags & PF_STASIS) { // Are ANY objectplace buttons pressed? If no, remove flag. if (!(cmd->buttons & (BT_ATTACK|BT_DRIFT))) - player->pflags &= ~PF_ATTACKDOWN; + player->pflags &= ~PF_STASIS; // Do nothing. return; @@ -1066,12 +1066,12 @@ void OP_ObjectplaceMovement(player_t *player) /*if (cmd->buttons & BT_FORWARD) { OP_CycleThings(-1); - player->pflags |= PF_ATTACKDOWN; + player->pflags |= PF_STASIS; } else*/ if (cmd->buttons & BT_DRIFT) { OP_CycleThings(1); - player->pflags |= PF_ATTACKDOWN; + player->pflags |= PF_STASIS; } // Place an object and add it to the maplist @@ -1082,7 +1082,7 @@ void OP_ObjectplaceMovement(player_t *player) mobjtype_t spawnthing = op_currentdoomednum; boolean ceiling; - player->pflags |= PF_ATTACKDOWN; + player->pflags |= PF_STASIS; if (cv_mapthingnum.value > 0 && cv_mapthingnum.value < 4096) { diff --git a/src/objects/Sourcefile b/src/objects/Sourcefile index d1e801471..84e263f29 100644 --- a/src/objects/Sourcefile +++ b/src/objects/Sourcefile @@ -1,3 +1,5 @@ hyudoro.c shrink.c item-debris.c +spb.c +manta-ring.c diff --git a/src/objects/manta-ring.c b/src/objects/manta-ring.c new file mode 100644 index 000000000..69f98fc06 --- /dev/null +++ b/src/objects/manta-ring.c @@ -0,0 +1,307 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2022 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2022 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file manta-ring.c +/// \brief SPB Juicebox rings. See spb.c for their spawning. + +#include "../doomdef.h" +#include "../doomstat.h" +#include "../info.h" +#include "../k_kart.h" +#include "../k_objects.h" +#include "../m_random.h" +#include "../p_local.h" +#include "../r_main.h" +#include "../s_sound.h" +#include "../g_game.h" +#include "../z_zone.h" +#include "../k_waypoint.h" +#include "../k_respawn.h" + +#define MANTA_RACETIME (90) +#define MANTA_MINTIME (15) +#define MANTA_SPRINTTIME (60) + +#define MANTA_ALIVEGATE (0) +#define MANTA_DEADGATE (FF_TRANS80) + +#define MANTA_SIZE (2 * FRACUNIT) +#define MANTA_SIZEUP (10) +#define MANTA_SIZESTRENGTH (1500) +#define MANTA_MAXRAMP (80) + +#define MANTA_COLLIDE (80 * FRACUNIT) + +#define MANTA_TURBO (40) +#define MANTA_FASTRAMP (17) +#define MANTA_MINPWR (10) + +#define manta_delay(o) ((o)->fuse) +#define manta_timealive(o) ((o)->movecount) +#define manta_boostval(o) ((o)->extravalue1) +#define manta_laps(o) ((o)->extravalue2) +#define manta_touched(o) ((o)->cusval) + +#define manta_owner(o) ((o)->target) +#define manta_chase(o) ((o)->tracer) + +static boolean MantaAlreadyTouched(mobj_t *manta, player_t *player) +{ + INT32 touchFlag = 0; + + if (manta_chase(manta) != NULL && P_MobjWasRemoved(manta_chase(manta)) == false + && player->mo == manta_chase(manta)) + { + return true; + } + +#if 0 + if (manta_laps(manta) < player->laps) + { + return true; + } +#endif + + touchFlag = 1 << (player - players); + return (manta_touched(manta) & touchFlag); +} + +static void Obj_MantaCollide(mobj_t *manta, mobj_t *other) +{ + // Could hook this into actual mobj collide if desired. + fixed_t distance = INT32_MAX; + fixed_t size = INT32_MAX; + + INT32 addBoost = 0; + INT32 touchFlag = 0; + + size_t i; + + distance = P_AproxDistance(P_AproxDistance( + other->x - manta->x, + other->y - manta->y), + other->z - manta->z) - other->radius - manta->radius; + + size = FixedMul(MANTA_COLLIDE, mapobjectscale); + + if (distance > size) + { + return; + } + + if (other->player != NULL) // Just in case other objects should be added? + { + if (MantaAlreadyTouched(manta, other->player)) + { + return; + } + + touchFlag = 1 << (other->player - players); + } + + addBoost = manta_boostval(manta); + + if (manta_timealive(manta) < MANTA_FASTRAMP) + { + // Ramp up to max power. + addBoost = FixedMul(addBoost * FRACUNIT, (manta_timealive(manta) * FRACUNIT) / MANTA_FASTRAMP); + + // Convert to integer + addBoost = (addBoost + (FRACUNIT/2)) / FRACUNIT; + + // Cap it + addBoost = max(MANTA_MINPWR, addBoost); + } + + if (other->player != NULL) + { + UINT8 snd = 0; + + if (other->player->speedboost > FRACUNIT/4) + { + snd = other->player->gateSound; + other->player->gateSound++; + + if (other->player->gateSound > 4) + { + other->player->gateSound = 4; + } + } + else + { + other->player->gateSound = 0; + } + + K_SpawnDriftBoostExplosion(other->player, 3); + K_SpawnDriftElectricSparks(other->player, SKINCOLOR_CRIMSON, true); + + for (i = 0; i < 5; i++) + { + S_StopSoundByID(other, sfx_gate01 + i); + } + + S_StartSound(other, sfx_gate01 + snd); + other->player->gateBoost += addBoost/2; + + if (P_IsDisplayPlayer(other->player) == true) + { + P_StartQuake(12 << FRACBITS, 6); + } + } + + if (touchFlag > 0) + { + manta_touched(manta) |= touchFlag; + } +} + +static void RunMantaCollide(mobj_t *manta) +{ + INT32 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *player = NULL; + + if (playeringame[i] == false) + { + // Invalid + continue; + } + + player = &players[i]; + if (player->spectator == true) + { + // Not playing. + continue; + } + + if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true) + { + // Invalid object + continue; + } + + if (player->mo == manta_chase(manta)) + { + // Don't allow the person being chased to touch this. + continue; + } + + Obj_MantaCollide(manta, player->mo); + } +} + +static void RunMantaVisual(mobj_t *manta) +{ + INT32 i; + + if (manta->fuse < 5*TICRATE) + { + if (leveltime & 1) + { + manta->renderflags |= RF_DONTDRAW; + } + else + { + manta->renderflags &= ~RF_DONTDRAW; + } + } + + for (i = 0; i <= r_splitscreen; i++) + { + const UINT8 pID = displayplayers[i]; + player_t *player = &players[pID]; + + if (MantaAlreadyTouched(manta, player) == false) + { + break; + } + } + + if (i > r_splitscreen) + { + manta->frame = (manta->frame & ~FF_TRANSMASK) | MANTA_DEADGATE; + } + else + { + manta->frame = (manta->frame & ~FF_TRANSMASK) | MANTA_ALIVEGATE; + } +} + +void Obj_MantaRingThink(mobj_t *manta) +{ + RunMantaVisual(manta); + + if (manta_delay(manta) % MANTA_SIZEUP == 0) + { + manta->destscale += FixedMul(MANTA_SIZESTRENGTH, mapobjectscale); + manta_boostval(manta) = min(MANTA_MAXRAMP, manta_boostval(manta) + 1); + } + + manta_timealive(manta)++; + + RunMantaCollide(manta); +} + +mobj_t *Obj_MantaRingCreate(mobj_t *spb, mobj_t *owner, mobj_t *chase) +{ + mobj_t *manta = NULL; + INT32 delay = 0; + + manta = P_SpawnMobjFromMobj(spb, 0, 0, 0, MT_MANTARING); + + manta->color = SKINCOLOR_KETCHUP; + + manta->destscale = FixedMul(MANTA_SIZE, spb->scale); + P_SetScale(manta, manta->destscale); + + manta->angle = R_PointToAngle2(0, 0, spb->momx, spb->momy) + ANGLE_90; + + // Set boost value + manta_boostval(manta) = MANTA_TURBO; + + // Set despawn delay + delay = max(MANTA_MINTIME, MANTA_RACETIME / mapheaderinfo[gamemap - 1]->numlaps); + + if (mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) + { + delay = MANTA_SPRINTTIME; + } + + manta_delay(manta) = delay * TICRATE; + + // Default if neither object exists + manta_laps(manta) = INT32_MAX; + + // Set owner + if (owner != NULL && P_MobjWasRemoved(owner) == false) + { + P_SetTarget(&manta_owner(manta), owner); + + if (owner->player != NULL) + { + // Default if chaser doesn't exist + manta_laps(manta) = owner->player->laps; + } + } + + // Set chaser + if (chase != NULL && P_MobjWasRemoved(chase) == false) + { + P_SetTarget(&manta_chase(manta), chase); + + if (chase->player != NULL) + { + manta_laps(manta) = chase->player->laps; + } + } + + return manta; +} diff --git a/src/objects/shrink.c b/src/objects/shrink.c index 241164aa7..bd68fa3d4 100644 --- a/src/objects/shrink.c +++ b/src/objects/shrink.c @@ -439,6 +439,8 @@ void Obj_PohbeeThinker(mobj_t *pohbee) { mobj_t *gun = NULL; + K_SetItemCooldown(KITEM_SHRINK, 20*TICRATE); + pohbee->momx = pohbee->momy = pohbee->momz = 0; pohbee->spritexscale = pohbee->spriteyscale = 2*FRACUNIT; @@ -482,6 +484,8 @@ void Obj_PohbeeRemoved(mobj_t *pohbee) P_RemoveMobj(gun); gun = nextGun; } + + P_SetTarget(&pohbee_guns(pohbee), NULL); } void Obj_ShrinkGunRemoved(mobj_t *gun) @@ -493,6 +497,8 @@ void Obj_ShrinkGunRemoved(mobj_t *gun) P_RemoveMobj(gun_laser(gun)); } + P_SetTarget(&gun_laser(gun), NULL); + chain = gun_chains(gun); while (chain != NULL && P_MobjWasRemoved(chain) == false) { @@ -500,6 +506,8 @@ void Obj_ShrinkGunRemoved(mobj_t *gun) P_RemoveMobj(chain); chain = nextChain; } + + P_SetTarget(&gun_chains(gun), NULL); } boolean Obj_ShrinkLaserCollide(mobj_t *gun, mobj_t *victim) diff --git a/src/objects/spb.c b/src/objects/spb.c new file mode 100644 index 000000000..e939b1b31 --- /dev/null +++ b/src/objects/spb.c @@ -0,0 +1,1024 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2022 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2022 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file spb.c +/// \brief Self Propelled Bomb item code. + +#include "../doomdef.h" +#include "../doomstat.h" +#include "../info.h" +#include "../k_kart.h" +#include "../k_objects.h" +#include "../m_random.h" +#include "../p_local.h" +#include "../r_main.h" +#include "../s_sound.h" +#include "../g_game.h" +#include "../z_zone.h" +#include "../k_waypoint.h" +#include "../k_respawn.h" + +// #define SPB_SEEKTEST + +#define SPB_SLIPTIDEDELTA (ANG1 * 3) +#define SPB_STEERDELTA (ANGLE_90 - ANG10) +#define SPB_DEFAULTSPEED (FixedMul(mapobjectscale, K_GetKartSpeedFromStat(9) * 2)) +#define SPB_ACTIVEDIST (1024 * FRACUNIT) + +#define SPB_HOTPOTATO (2*TICRATE) +#define SPB_MAXSWAPS (2) +#define SPB_FLASHING (TICRATE) + +#define SPB_CHASETIMESCALE (60*TICRATE) +#define SPB_CHASETIMEMUL (3*FRACUNIT) + +#define SPB_SEEKTURN (FRACUNIT/8) +#define SPB_CHASETURN (FRACUNIT/4) + +#define SPB_MANTA_SPACING (2750 * FRACUNIT) + +#define SPB_MANTA_VSTART (150) +#define SPB_MANTA_VRATE (60) +#define SPB_MANTA_VMAX (100) + +enum +{ + SPB_MODE_SEEK, + SPB_MODE_CHASE, + SPB_MODE_WAIT, +}; + +#define spb_mode(o) ((o)->extravalue1) +#define spb_modetimer(o) ((o)->extravalue2) + +#define spb_nothink(o) ((o)->threshold) +#define spb_intangible(o) ((o)->cvmem) + +#define spb_lastplayer(o) ((o)->lastlook) +#define spb_speed(o) ((o)->movefactor) +#define spb_pitch(o) ((o)->movedir) + +#define spb_chasetime(o) ((o)->watertop) // running out of variables here... +#define spb_swapcount(o) ((o)->health) + +#define spb_curwaypoint(o) ((o)->cusval) + +#define spb_manta_vscale(o) ((o)->movecount) +#define spb_manta_totaldist(o) ((o)->reactiontime) + +#define spb_owner(o) ((o)->target) +#define spb_chase(o) ((o)->tracer) + +static void SPBMantaRings(mobj_t *spb) +{ + fixed_t vScale = INT32_MAX; + fixed_t spacing = INT32_MAX; + fixed_t finalDist = INT32_MAX; + + const fixed_t floatHeight = 24 * spb->scale; + fixed_t floorDist = INT32_MAX; + + if (leveltime % SPB_MANTA_VRATE == 0) + { + spb_manta_vscale(spb) = max(spb_manta_vscale(spb) - 1, SPB_MANTA_VMAX); + } + + spacing = FixedMul(SPB_MANTA_SPACING, spb->scale); + spacing = FixedMul(spacing, K_GetKartGameSpeedScalar(gamespeed)); + + vScale = FixedDiv(spb_manta_vscale(spb) * FRACUNIT, 100 * FRACUNIT); + finalDist = FixedMul(spacing, vScale); + + floorDist = abs(P_GetMobjFeet(spb) - P_GetMobjGround(spb)); + + spb_manta_totaldist(spb) += P_AproxDistance(spb->momx, spb->momy); + + if (spb_manta_totaldist(spb) > finalDist + && floorDist <= floatHeight) + { + spb_manta_totaldist(spb) = 0; + + Obj_MantaRingCreate( + spb, + spb_owner(spb), +#ifdef SPB_SEEKTEST + NULL +#else + spb_chase(spb) +#endif + ); + } +} + +static void SpawnSPBDust(mobj_t *spb) +{ + // The easiest way to spawn a V shaped cone of dust from the SPB is simply to spawn 2 particles, and to both move them to the sides in opposite direction. + mobj_t *dust; + fixed_t sx; + fixed_t sy; + fixed_t sz = spb->floorz; + angle_t sa = spb->angle - ANG1*60; + INT32 i; + + if (spb->eflags & MFE_VERTICALFLIP) + { + sz = spb->ceilingz; + } + + if ((leveltime & 1) && abs(spb->z - sz) < FRACUNIT*64) // Only every other frame. Also don't spawn it if we're way above the ground. + { + // Determine spawning position next to the SPB: + for (i = 0; i < 2; i++) + { + sx = 96 * FINECOSINE(sa >> ANGLETOFINESHIFT); + sy = 96 * FINESINE(sa >> ANGLETOFINESHIFT); + + dust = P_SpawnMobjFromMobj(spb, sx, sy, 0, MT_SPBDUST); + dust->z = sz; + + dust->momx = spb->momx/2; + dust->momy = spb->momy/2; + dust->momz = spb->momz/2; // Give some of the momentum to the dust + + P_SetScale(dust, spb->scale * 2); + + dust->color = SKINCOLOR_RED; + dust->colorized = true; + + dust->angle = spb->angle - FixedAngle(FRACUNIT*90 - FRACUNIT*180*i); // The first one will spawn to the right of the spb, the second one to the left. + P_Thrust(dust, dust->angle, 6*dust->scale); + + K_MatchGenericExtraFlags(dust, spb); + + sa += ANG1*120; // Add 120 degrees to get to mo->angle + ANG1*60 + } + } +} + +// Spawns SPB slip tide. To be used when the SPB is turning. +// Modified version of K_SpawnAIZDust. Maybe we could merge those to be cleaner? + +// dir should be either 1 or -1 to determine where to spawn the dust. + +static void SpawnSPBSliptide(mobj_t *spb, SINT8 dir) +{ + fixed_t newx; + fixed_t newy; + mobj_t *spark; + angle_t travelangle; + fixed_t sz = spb->floorz; + + if (spb->eflags & MFE_VERTICALFLIP) + { + sz = spb->ceilingz; + } + + travelangle = K_MomentumAngle(spb); + + if ((leveltime & 1) && abs(spb->z - sz) < FRACUNIT*64) + { + newx = P_ReturnThrustX(spb, travelangle - (dir*ANGLE_45), 24*FRACUNIT); + newy = P_ReturnThrustY(spb, travelangle - (dir*ANGLE_45), 24*FRACUNIT); + + spark = P_SpawnMobjFromMobj(spb, newx, newy, 0, MT_SPBDUST); + spark->z = sz; + + P_SetMobjState(spark, S_KARTAIZDRIFTSTRAT); + P_SetTarget(&spark->target, spb); + + spark->colorized = true; + spark->color = SKINCOLOR_RED; + + spark->angle = travelangle + (dir * ANGLE_90); + P_SetScale(spark, (spark->destscale = spb->scale*3/2)); + + spark->momx = (6*spb->momx)/5; + spark->momy = (6*spb->momy)/5; + + K_MatchGenericExtraFlags(spark, spb); + } +} + +// Used for seeking and when SPB is trailing its target from way too close! +static void SpawnSPBSpeedLines(mobj_t *spb) +{ + mobj_t *fast = P_SpawnMobjFromMobj(spb, + P_RandomRange(-24, 24) * FRACUNIT, + P_RandomRange(-24, 24) * FRACUNIT, + (spb->info->height / 2) + (P_RandomRange(-24, 24) * FRACUNIT), + MT_FASTLINE + ); + + P_SetTarget(&fast->target, spb); + fast->angle = K_MomentumAngle(spb); + + fast->color = SKINCOLOR_RED; + fast->colorized = true; + + K_MatchGenericExtraFlags(fast, spb); +} + +static fixed_t SPBDist(mobj_t *a, mobj_t *b) +{ + return P_AproxDistance(P_AproxDistance( + a->x - b->x, + a->y - b->y), + a->z - b->z + ); +} + +static void SPBTurn( + fixed_t destSpeed, angle_t destAngle, + fixed_t *editSpeed, angle_t *editAngle, + fixed_t lerp, SINT8 *returnSliptide) +{ + INT32 delta = AngleDeltaSigned(destAngle, *editAngle); + fixed_t dampen = FRACUNIT; + + // Slow down when turning; it looks better and makes U-turns not unfair + dampen = FixedDiv((180 * FRACUNIT) - AngleFixed(abs(delta)), 180 * FRACUNIT); + *editSpeed = FixedMul(destSpeed, dampen); + + delta = FixedMul(delta, lerp); + + // Calculate sliptide effect during seeking. + if (returnSliptide != NULL) + { + const boolean isSliptiding = (abs(delta) >= SPB_SLIPTIDEDELTA); + SINT8 sliptide = 0; + + if (isSliptiding == true) + { + if (delta < 0) + { + sliptide = -1; + } + else + { + sliptide = 1; + } + } + + *returnSliptide = sliptide; + } + + *editAngle += delta; +} + +static void SetSPBSpeed(mobj_t *spb, fixed_t xySpeed, fixed_t zSpeed) +{ + spb->momx = FixedMul(FixedMul( + xySpeed, + FINECOSINE(spb->angle >> ANGLETOFINESHIFT)), + FINECOSINE(spb_pitch(spb) >> ANGLETOFINESHIFT) + ); + + spb->momy = FixedMul(FixedMul( + xySpeed, + FINESINE(spb->angle >> ANGLETOFINESHIFT)), + FINECOSINE(spb_pitch(spb) >> ANGLETOFINESHIFT) + ); + + spb->momz = FixedMul( + zSpeed, + FINESINE(spb_pitch(spb) >> ANGLETOFINESHIFT) + ); +} + +static boolean SPBSeekSoundPlaying(mobj_t *spb) +{ + return (S_SoundPlaying(spb, sfx_spbska) + || S_SoundPlaying(spb, sfx_spbskb) + || S_SoundPlaying(spb, sfx_spbskc)); +} + +static void SPBSeek(mobj_t *spb, player_t *bestPlayer) +{ + const fixed_t desiredSpeed = SPB_DEFAULTSPEED; + + waypoint_t *curWaypoint = NULL; + waypoint_t *destWaypoint = NULL; + + fixed_t dist = INT32_MAX; + fixed_t activeDist = INT32_MAX; + + fixed_t destX = spb->x; + fixed_t destY = spb->y; + fixed_t destZ = spb->z; + angle_t destAngle = spb->angle; + angle_t destPitch = 0U; + + fixed_t xySpeed = desiredSpeed; + fixed_t zSpeed = desiredSpeed; + SINT8 sliptide = 0; + + fixed_t steerDist = INT32_MAX; + mobj_t *steerMobj = NULL; + + boolean circling = false; + + size_t i; + + spb_lastplayer(spb) = -1; // Just make sure this is reset + + if (bestPlayer == NULL + || bestPlayer->mo == NULL + || P_MobjWasRemoved(bestPlayer->mo) == true + || bestPlayer->mo->health <= 0 + || (bestPlayer->respawn.state != RESPAWNST_NONE)) + { + // No one there? Completely STOP. + spb->momx = spb->momy = spb->momz = 0; + + if (bestPlayer == NULL) + { + spbplace = -1; + } + + return; + } + + // Found someone, now get close enough to initiate the slaughter... + P_SetTarget(&spb_chase(spb), bestPlayer->mo); + spbplace = bestPlayer->position; + + dist = SPBDist(spb, spb_chase(spb)); + activeDist = FixedMul(SPB_ACTIVEDIST, spb_chase(spb)->scale); + + if (spb_swapcount(spb) > SPB_MAXSWAPS + 1) + { + // Too much hot potato. + // Go past our target and explode instead. + if (spb->fuse == 0) + { + spb->fuse = 2*TICRATE; + } + } +#ifndef SPB_SEEKTEST // Easy debug switch + else + { + if (dist <= activeDist) + { + S_StopSound(spb); + S_StartSound(spb, spb->info->attacksound); + + spb_mode(spb) = SPB_MODE_CHASE; // TARGET ACQUIRED + spb_swapcount(spb)++; + + spb_modetimer(spb) = SPB_HOTPOTATO; + spb_intangible(spb) = SPB_FLASHING; + + spb_speed(spb) = desiredSpeed; + return; + } + } +#endif + + if (SPBSeekSoundPlaying(spb) == false) + { + if (dist <= activeDist * 3) + { + S_StartSound(spb, sfx_spbskc); + } + else if (dist <= activeDist * 6) + { + S_StartSound(spb, sfx_spbskb); + } + else + { + S_StartSound(spb, sfx_spbska); + } + } + + // Move along the waypoints until you get close enough + if (spb_curwaypoint(spb) == -1) + { + // Determine first waypoint. + curWaypoint = K_GetBestWaypointForMobj(spb); + spb_curwaypoint(spb) = (INT32)K_GetWaypointHeapIndex(curWaypoint); + } + else + { + curWaypoint = K_GetWaypointFromIndex( (size_t)spb_curwaypoint(spb) ); + } + + destWaypoint = bestPlayer->nextwaypoint; + + if (curWaypoint != NULL) + { + fixed_t waypointDist = INT32_MAX; + fixed_t waypointRad = INT32_MAX; + + destX = curWaypoint->mobj->x; + destY = curWaypoint->mobj->y; + destZ = curWaypoint->mobj->z; + + waypointDist = R_PointToDist2(spb->x, spb->y, destX, destY) / mapobjectscale; + waypointRad = max(curWaypoint->mobj->radius / mapobjectscale, DEFAULT_WAYPOINT_RADIUS); + + if (waypointDist <= waypointRad) + { + boolean pathfindsuccess = false; + + if (destWaypoint != NULL) + { + // Go to next waypoint. + const boolean useshortcuts = K_GetWaypointIsShortcut(destWaypoint); // If the player is on a shortcut, use shortcuts. No escape. + boolean huntbackwards = false; + path_t pathtoplayer = {0}; + + pathfindsuccess = K_PathfindToWaypoint( + curWaypoint, destWaypoint, + &pathtoplayer, + useshortcuts, huntbackwards + ); + + if (pathfindsuccess == true) + { +#ifdef SPB_SEEKTEST + if (pathtoplayer.numnodes > 1) + { + // Go to the next waypoint. + curWaypoint = (waypoint_t *)pathtoplayer.array[1].nodedata; + } + else if (destWaypoint->numnextwaypoints > 0) + { + // Run ahead. + curWaypoint = destWaypoint->nextwaypoints[0]; + } + else + { + // Sort of wait at the player's dest waypoint. + circling = true; + curWaypoint = destWaypoint; + } +#else + path_t reversepath = {0}; + boolean reversesuccess = false; + + huntbackwards = true; + reversesuccess = K_PathfindToWaypoint( + curWaypoint, destWaypoint, + &reversepath, + useshortcuts, huntbackwards + ); + + if (reversesuccess == true + && reversepath.totaldist < pathtoplayer.totaldist) + { + // It's faster to go backwards than to chase forward. + // Keep curWaypoint the same, so the SPB waits around for them. + circling = true; + } + else if (pathtoplayer.numnodes > 1) + { + // Go to the next waypoint. + curWaypoint = (waypoint_t *)pathtoplayer.array[1].nodedata; + } + else if (spb->fuse > 0 && destWaypoint->numnextwaypoints > 0) + { + // Run ahead. + curWaypoint = destWaypoint->nextwaypoints[0]; + } + else + { + // Sort of wait at the player's dest waypoint. + circling = true; + curWaypoint = destWaypoint; + } + + if (reversesuccess == true) + { + Z_Free(reversepath.array); + } +#endif + + Z_Free(pathtoplayer.array); + } + } + + if (pathfindsuccess == true && curWaypoint != NULL) + { + // Update again + spb_curwaypoint(spb) = (INT32)K_GetWaypointHeapIndex(curWaypoint); + destX = curWaypoint->mobj->x; + destY = curWaypoint->mobj->y; + destZ = curWaypoint->mobj->z; + } + else + { + spb_curwaypoint(spb) = -1; + destX = spb_chase(spb)->x; + destY = spb_chase(spb)->y; + destZ = spb_chase(spb)->z; + } + } + } + else + { + spb_curwaypoint(spb) = -1; + destX = spb_chase(spb)->x; + destY = spb_chase(spb)->y; + destZ = spb_chase(spb)->z; + } + + destAngle = R_PointToAngle2(spb->x, spb->y, destX, destY); + destPitch = R_PointToAngle2(0, spb->z, P_AproxDistance(spb->x - destX, spb->y - destY), destZ); + + SPBTurn(desiredSpeed, destAngle, &xySpeed, &spb->angle, SPB_SEEKTURN, &sliptide); + SPBTurn(desiredSpeed, destPitch, &zSpeed, &spb_pitch(spb), SPB_SEEKTURN, NULL); + + SetSPBSpeed(spb, xySpeed, zSpeed); + + // see if a player is near us, if they are, try to hit them by slightly thrusting towards them, otherwise, bleh! + steerDist = 1536 * mapobjectscale; + + for (i = 0; i < MAXPLAYERS; i++) + { + fixed_t ourDist = INT32_MAX; + INT32 ourDelta = INT32_MAX; + + if (playeringame[i] == false || players[i].spectator == true) + { + // Not in-game + continue; + } + + if (players[i].mo == NULL || P_MobjWasRemoved(players[i].mo) == true) + { + // Invalid mobj + continue; + } + + ourDelta = AngleDelta(spb->angle, R_PointToAngle2(spb->x, spb->y, players[i].mo->x, players[i].mo->y)); + if (ourDelta > SPB_STEERDELTA) + { + // Check if the angle wouldn't make us LOSE speed. + continue; + } + + ourDist = R_PointToDist2(spb->x, spb->y, players[i].mo->x, players[i].mo->y); + if (ourDist < steerDist) + { + steerDist = ourDist; + steerMobj = players[i].mo; // it doesn't matter if we override this guy now. + } + } + + // different player from our main target, try and ram into em~! + if (steerMobj != NULL && steerMobj != spb_chase(spb)) + { + P_Thrust(spb, R_PointToAngle2(spb->x, spb->y, steerMobj->x, steerMobj->y), spb_speed(spb) / 4); + } + + if (sliptide != 0) + { + // 1 if turning left, -1 if turning right. + // Angles work counterclockwise, remember! + SpawnSPBSliptide(spb, sliptide); + } + else + { + // if we're mostly going straight, then spawn the V dust cone! + SpawnSPBDust(spb); + } + + // Always spawn speed lines while seeking + SpawnSPBSpeedLines(spb); + + // Don't run this while we're circling around one waypoint intentionally. + if (circling == false) + { + // Spawn a trail of rings behind the SPB! + SPBMantaRings(spb); + } +} + +static void SPBChase(mobj_t *spb, player_t *bestPlayer) +{ + fixed_t baseSpeed = 0; + fixed_t maxSpeed = 0; + fixed_t desiredSpeed = 0; + + fixed_t range = INT32_MAX; + fixed_t cx = 0, cy = 0; + + fixed_t dist = INT32_MAX; + angle_t destAngle = spb->angle; + angle_t destPitch = 0U; + fixed_t xySpeed = 0; + fixed_t zSpeed = 0; + + mobj_t *chase = NULL; + player_t *chasePlayer = NULL; + + spb_curwaypoint(spb) = -1; // Reset waypoint + + chase = spb_chase(spb); + + if (chase == NULL || P_MobjWasRemoved(chase) == true || chase->health <= 0) + { + P_SetTarget(&spb_chase(spb), NULL); + spb_mode(spb) = SPB_MODE_WAIT; + spb_modetimer(spb) = 55; // Slightly over the respawn timer length + return; + } + + if (chase->hitlag) + { + // If the player is frozen, the SPB should be too. + spb->hitlag = max(spb->hitlag, chase->hitlag); + return; + } + + // Increment chase time + spb_chasetime(spb)++; + + baseSpeed = SPB_DEFAULTSPEED; + range = (160 * chase->scale); + + // Play the intimidating gurgle + if (S_SoundPlaying(spb, spb->info->activesound) == false) + { + S_StartSound(spb, spb->info->activesound); + } + + // Maybe we want SPB to target an object later? IDK lol + chasePlayer = chase->player; + if (chasePlayer != NULL) + { + UINT8 fracmax = 32; + UINT8 spark = ((10 - chasePlayer->kartspeed) + chasePlayer->kartweight) / 2; + fixed_t easiness = ((chasePlayer->kartspeed + (10 - spark)) << FRACBITS) / 2; + + spb_lastplayer(spb) = chasePlayer - players; // Save the player num for death scumming... + spbplace = chasePlayer->position; + + chasePlayer->pflags |= PF_RINGLOCK; // set ring lock + + if (P_IsObjectOnGround(chase) == false) + { + // In the air you have no control; basically don't hit unless you make a near complete stop + baseSpeed = (7 * chasePlayer->speed) / 8; + } + else + { + // 7/8ths max speed for Knuckles, 3/4ths max speed for min accel, exactly max speed for max accel + baseSpeed = FixedMul( + ((fracmax+1) << FRACBITS) - easiness, + K_GetKartSpeed(chasePlayer, false, false) + ) / fracmax; + } + + if (chasePlayer->carry == CR_SLIDING) + { + baseSpeed = chasePlayer->speed/2; + } + + // Be fairer on conveyors + cx = chasePlayer->cmomx; + cy = chasePlayer->cmomy; + + // Switch targets if you're no longer 1st for long enough + if (bestPlayer != NULL && chasePlayer->position <= bestPlayer->position) + { + spb_modetimer(spb) = SPB_HOTPOTATO; + } + else + { + if (spb_modetimer(spb) > 0) + { + spb_modetimer(spb)--; + } + + if (spb_modetimer(spb) <= 0) + { + spb_mode(spb) = SPB_MODE_SEEK; // back to SEEKING + spb_intangible(spb) = SPB_FLASHING; + } + } + } + + dist = P_AproxDistance(P_AproxDistance(spb->x - chase->x, spb->y - chase->y), spb->z - chase->z); + + desiredSpeed = FixedMul(baseSpeed, FRACUNIT + FixedDiv(dist - range, range)); + + if (desiredSpeed < baseSpeed) + { + desiredSpeed = baseSpeed; + } + + maxSpeed = (baseSpeed * 3) / 2; + if (desiredSpeed > maxSpeed) + { + desiredSpeed = maxSpeed; + } + + if (desiredSpeed < 20 * chase->scale) + { + desiredSpeed = 20 * chase->scale; + } + + if (chasePlayer != NULL && chasePlayer->carry == CR_SLIDING) + { + // Hack for current sections to make them fair. + desiredSpeed = min(desiredSpeed, chasePlayer->speed / 2); + } + + destAngle = R_PointToAngle2(spb->x, spb->y, chase->x, chase->y); + destPitch = R_PointToAngle2(0, spb->z, P_AproxDistance(spb->x - chase->x, spb->y - chase->y), chase->z); + + // Modify stored speed + if (desiredSpeed > spb_speed(spb)) + { + spb_speed(spb) += (desiredSpeed - spb_speed(spb)) / TICRATE; + } + else + { + spb_speed(spb) = desiredSpeed; + } + + SPBTurn(spb_speed(spb), destAngle, &xySpeed, &spb->angle, SPB_CHASETURN, NULL); + SPBTurn(spb_speed(spb), destPitch, &zSpeed, &spb_pitch(spb), SPB_CHASETURN, NULL); + + SetSPBSpeed(spb, xySpeed, zSpeed); + spb->momx += cx; + spb->momy += cy; + + // Spawn a trail of rings behind the SPB! + SPBMantaRings(spb); + + // Red speed lines for when it's gaining on its target. A tell for when you're starting to lose too much speed! + if (R_PointToDist2(0, 0, spb->momx, spb->momy) > (16 * R_PointToDist2(0, 0, chase->momx, chase->momy)) / 15 // Going faster than the target + && xySpeed > 20 * mapobjectscale) // Don't display speedup lines at pitifully low speeds + { + SpawnSPBSpeedLines(spb); + } +} + +static void SPBWait(mobj_t *spb) +{ + player_t *oldPlayer = NULL; + + spb->momx = spb->momy = spb->momz = 0; // Stoooop + spb_curwaypoint(spb) = -1; // Reset waypoint + + if (spb_lastplayer(spb) != -1 + && playeringame[spb_lastplayer(spb)] == true) + { + oldPlayer = &players[spb_lastplayer(spb)]; + } + + if (oldPlayer != NULL + && oldPlayer->spectator == false + && oldPlayer->exiting > 0) + { + spbplace = oldPlayer->position; + oldPlayer->pflags |= PF_RINGLOCK; + } + + if (spb_modetimer(spb) > 0) + { + spb_modetimer(spb)--; + } + + if (spb_modetimer(spb) <= 0) + { + if (oldPlayer != NULL) + { + if (oldPlayer->mo != NULL && P_MobjWasRemoved(oldPlayer->mo) == false) + { + P_SetTarget(&spb_chase(spb), oldPlayer->mo); + spb_mode(spb) = SPB_MODE_CHASE; + spb_modetimer(spb) = SPB_HOTPOTATO; + spb_intangible(spb) = SPB_FLASHING; + spb_speed(spb) = SPB_DEFAULTSPEED; + } + } + else + { + spb_mode(spb) = SPB_MODE_SEEK; + spb_modetimer(spb) = 0; + spb_intangible(spb) = SPB_FLASHING; + spbplace = -1; + } + } +} + +void Obj_SPBThink(mobj_t *spb) +{ + mobj_t *ghost = NULL; + player_t *bestPlayer = NULL; + UINT8 bestRank = UINT8_MAX; + size_t i; + + if (spb->health <= 0) + { + return; + } + + K_SetItemCooldown(KITEM_SPB, 20*TICRATE); + + ghost = P_SpawnGhostMobj(spb); + ghost->fuse = 3; + + if (spb_owner(spb) != NULL && P_MobjWasRemoved(spb_owner(spb)) == false && spb_owner(spb)->player != NULL) + { + ghost->color = spb_owner(spb)->player->skincolor; + ghost->colorized = true; + } + + if (spb_nothink(spb) > 0) + { + // Init values, don't think yet. + spb_lastplayer(spb) = -1; + spb_curwaypoint(spb) = -1; + spb_chasetime(spb) = 0; + spbplace = -1; + + spb_manta_totaldist(spb) = 0; // 30000? + spb_manta_vscale(spb) = SPB_MANTA_VSTART; + + P_InstaThrust(spb, spb->angle, SPB_DEFAULTSPEED); + + spb_nothink(spb)--; + } + else + { + // Find the player with the best rank + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *player = NULL; + + if (playeringame[i] == false) + { + // Not valid + continue; + } + + player = &players[i]; + + if (player->spectator == true || player->exiting > 0) + { + // Not playing + continue; + } + + /* + if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true) + { + // No mobj + continue; + } + + if (player->mo <= 0) + { + // Dead + continue; + } + + if (player->respawn.state != RESPAWNST_NONE) + { + // Respawning + continue; + } + */ + + if (player->position < bestRank) + { + bestRank = player->position; + bestPlayer = player; + } + } + + switch (spb_mode(spb)) + { + case SPB_MODE_SEEK: + default: + SPBSeek(spb, bestPlayer); + break; + + case SPB_MODE_CHASE: + SPBChase(spb, bestPlayer); + break; + + case SPB_MODE_WAIT: + SPBWait(spb); + break; + } + } + + // Flash on/off when intangible. + if (spb_intangible(spb) > 0) + { + spb_intangible(spb)--; + + if (spb_intangible(spb) & 1) + { + spb->renderflags |= RF_DONTDRAW; + } + else + { + spb->renderflags &= ~RF_DONTDRAW; + } + } + + // Flash white when about to explode! + if (spb->fuse > 0) + { + if (spb->fuse & 1) + { + spb->color = SKINCOLOR_INVINCFLASH; + spb->colorized = true; + } + else + { + spb->color = SKINCOLOR_NONE; + spb->colorized = false; + } + } + + // Clamp within level boundaries. + if (spb->z < spb->floorz) + { + spb->z = spb->floorz; + } + else if (spb->z > spb->ceilingz - spb->height) + { + spb->z = spb->ceilingz - spb->height; + } +} + +void Obj_SPBExplode(mobj_t *spb) +{ + mobj_t *spbExplode = NULL; + + // Don't continue playing the gurgle or the siren + S_StopSound(spb); + + spbExplode = P_SpawnMobjFromMobj(spb, 0, 0, 0, MT_SPBEXPLOSION); + + if (spb_owner(spb) != NULL && P_MobjWasRemoved(spb_owner(spb)) == false) + { + P_SetTarget(&spbExplode->target, spb_owner(spb)); + } + + // Tell the explosion to use alternate knockback. + spbExplode->movefactor = ((SPB_CHASETIMESCALE - spb_chasetime(spb)) * SPB_CHASETIMEMUL) / SPB_CHASETIMESCALE; + + P_RemoveMobj(spb); +} + +void Obj_SPBTouch(mobj_t *spb, mobj_t *toucher) +{ + player_t *player = toucher->player; + + if (spb_intangible(spb) > 0) + { + return; + } + + if ((spb_owner(spb) == toucher || spb_owner(spb) == toucher->target) + && (spb_nothink(spb) > 0)) + { + return; + } + + if (spb->health <= 0 || toucher->health <= 0) + { + return; + } + + if (player->spectator == true) + { + return; + } + + if (player->bubbleblowup > 0) + { + // Stun the SPB, and remove the shield. + K_DropHnextList(player, false); + spb_mode(spb) = SPB_MODE_WAIT; + spb_modetimer(spb) = 55; // Slightly over the respawn timer length + return; + } + + if (spb_chase(spb) != NULL && P_MobjWasRemoved(spb_chase(spb)) == false + && toucher == spb_chase(spb)) + { + // Cause the explosion. + Obj_SPBExplode(spb); + return; + } + else + { + // Regular spinout, please. + P_DamageMobj(toucher, spb, spb_owner(spb), 1, DMG_NORMAL); + } +} diff --git a/src/p_enemy.c b/src/p_enemy.c index 66a76b8a1..025ff5231 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -312,7 +312,6 @@ void A_ChangeHeight(mobj_t *actor); void A_ItemPop(mobj_t *actor); void A_JawzChase(mobj_t *actor); void A_JawzExplode(mobj_t *actor); -void A_SPBChase(mobj_t *actor); void A_SSMineSearch(mobj_t *actor); void A_SSMineExplode(mobj_t *actor); void A_LandMineExplode(mobj_t *actor); @@ -13188,7 +13187,6 @@ void A_ItemPop(mobj_t *actor) void A_JawzChase(mobj_t *actor) { - const fixed_t currentspeed = R_PointToDist2(0, 0, actor->momx, actor->momy); player_t *player; fixed_t thrustamount = 0; fixed_t frictionsafety = (actor->friction == 0) ? 1 : actor->friction; @@ -13273,30 +13271,29 @@ void A_JawzChase(mobj_t *actor) P_SetTarget(&actor->tracer, NULL); } - if (!P_IsObjectOnGround(actor)) - { - // No friction in the air - frictionsafety = FRACUNIT; - } - - if (currentspeed >= topspeed) - { - // Thrust as if you were at top speed, slow down naturally - thrustamount = FixedDiv(topspeed, frictionsafety) - topspeed; - } - else - { - const fixed_t beatfriction = FixedDiv(currentspeed, frictionsafety) - currentspeed; - // Thrust to immediately get to top speed - thrustamount = beatfriction + FixedDiv(topspeed - currentspeed, frictionsafety); - } - if (!actor->tracer) { actor->angle = K_MomentumAngle(actor); } - P_Thrust(actor, actor->angle, thrustamount); + if (P_IsObjectOnGround(actor)) + { + const fixed_t currentspeed = R_PointToDist2(0, 0, actor->momx, actor->momy); + + if (currentspeed >= topspeed) + { + // Thrust as if you were at top speed, slow down naturally + thrustamount = FixedDiv(topspeed, frictionsafety) - topspeed; + } + else + { + const fixed_t beatfriction = FixedDiv(currentspeed, frictionsafety) - currentspeed; + // Thrust to immediately get to top speed + thrustamount = beatfriction + FixedDiv(topspeed - currentspeed, frictionsafety); + } + + P_Thrust(actor, actor->angle, thrustamount); + } if ((actor->tracer != NULL) && (actor->tracer->health > 0)) return; @@ -13350,516 +13347,6 @@ void A_JawzExplode(mobj_t *actor) return; } -static void SpawnSPBTrailRings(mobj_t *actor) -{ - I_Assert(actor != NULL); - - if (leveltime % 6 == 0) - { - if (leveltime % (actor->extravalue1 == 2 ? 6 : 3) == 0) // Extravalue1 == 2 is seeking mode. Because the SPB is about twice as fast as normal in that mode, also spawn the rings twice as often to make up for it! - { - mobj_t *ring = P_SpawnMobj(actor->x - actor->momx, actor->y - actor->momy, - actor->z - actor->momz + (24*mapobjectscale), MT_RING); - ring->threshold = 10; - ring->fuse = 35*TICRATE; - ring->colorized = true; - ring->color = SKINCOLOR_RED; - } - } -} - -// Spawns the V shaped dust. To be used when the SPB is going mostly forward. -static void SpawnSPBDust(mobj_t *mo) -{ - // The easiest way to spawn a V shaped cone of dust from the SPB is simply to spawn 2 particles, and to both move them to the sides in opposite direction. - mobj_t *dust; - fixed_t sx; - fixed_t sy; - fixed_t sz = mo->floorz; - angle_t sa = mo->angle - ANG1*60; - INT32 i; - - if (mo->eflags & MFE_VERTICALFLIP) - sz = mo->ceilingz; - - if (leveltime & 1 && abs(mo->z - sz) < FRACUNIT*64) // Only ever other frame. Also don't spawn it if we're way above the ground. - { - // Determine spawning position next to the SPB: - for (i=0; i < 2; i++) - { - sx = mo->x + FixedMul((mo->scale*96), FINECOSINE((sa)>>ANGLETOFINESHIFT)); - sy = mo->y + FixedMul((mo->scale*96), FINESINE((sa)>>ANGLETOFINESHIFT)); - - dust = P_SpawnMobj(sx, sy, sz, MT_SPBDUST); - dust->momx = mo->momx/2; - dust->momy = mo->momy/2; - dust->momz = mo->momz/2; // Give some of the momentum to the dust - P_SetScale(dust, mo->scale*2); - dust->colorized = true; - dust->color = SKINCOLOR_RED; - P_InitAngle(dust, mo->angle - FixedAngle(FRACUNIT*90 - FRACUNIT*180*i)); // The first one will spawn to the right of the spb, the second one to the left. - P_Thrust(dust, dust->angle, 6*dust->scale); - - K_MatchGenericExtraFlags(dust, mo); - - sa += ANG1*120; // Add 120 degrees to get to mo->angle + ANG1*60 - } - } -} - -// Spawns SPB slip tide. To be used when the SPB is turning. -// Modified version of K_SpawnAIZDust. Maybe we could merge those to be cleaner? - -// dir should be either 1 or -1 to determine where to spawn the dust. - -static void SpawnSPBAIZDust(mobj_t *mo, INT32 dir) -{ - fixed_t newx; - fixed_t newy; - mobj_t *spark; - angle_t travelangle; - fixed_t sz = mo->floorz; - - if (mo->eflags & MFE_VERTICALFLIP) - sz = mo->ceilingz; - - travelangle = K_MomentumAngle(mo); - if (leveltime & 1 && abs(mo->z - sz) < FRACUNIT*64) - { - newx = mo->x + P_ReturnThrustX(mo, travelangle - (dir*ANGLE_45), FixedMul(24*FRACUNIT, mo->scale)); - newy = mo->y + P_ReturnThrustY(mo, travelangle - (dir*ANGLE_45), FixedMul(24*FRACUNIT, mo->scale)); - spark = P_SpawnMobj(newx, newy, sz, MT_AIZDRIFTSTRAT); - spark->colorized = true; - spark->color = SKINCOLOR_RED; - spark->flags = MF_NOGRAVITY|MF_PAIN; - P_SetTarget(&spark->target, mo); - - P_InitAngle(spark, travelangle+(dir*ANGLE_90)); - P_SetScale(spark, (spark->destscale = mo->scale*3/2)); - - spark->momx = (6*mo->momx)/5; - spark->momy = (6*mo->momy)/5; - - K_MatchGenericExtraFlags(spark, mo); - } -} - -// Used for seeking and when SPB is trailing its target from way too close! -static void SpawnSPBSpeedLines(mobj_t *actor) -{ - mobj_t *fast = P_SpawnMobj(actor->x + (P_RandomRange(-24,24) * actor->scale), - actor->y + (P_RandomRange(-24,24) * actor->scale), - actor->z + (actor->height/2) + (P_RandomRange(-24,24) * actor->scale), - MT_FASTLINE); - - P_SetTarget(&fast->target, actor); - P_InitAngle(fast, K_MomentumAngle(actor)); - fast->color = SKINCOLOR_RED; - fast->colorized = true; - K_MatchGenericExtraFlags(fast, actor); -} - - -void A_SPBChase(mobj_t *actor) -{ - player_t *player = NULL; - player_t *scplayer = NULL; // secondary target for seeking - UINT8 i; - UINT8 bestrank = UINT8_MAX; - fixed_t dist; - angle_t hang, vang; - fixed_t wspeed, xyspeed, zspeed; - fixed_t pdist = 1536<threshold) // Just fired, go straight. - { - actor->lastlook = -1; - actor->cusval = -1; - spbplace = -1; - P_InstaThrust(actor, actor->angle, wspeed); - return; - } - - // Find the player with the best rank - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator || players[i].exiting) - continue; // not in-game - - /*if (!players[i].mo) - continue; // no mobj - - if (players[i].mo->health <= 0) - continue; // dead - - if (players[i].respawn.state != RESPAWNST_NONE) - continue;*/ // respawning - - if (players[i].position < bestrank) - { - bestrank = players[i].position; - player = &players[i]; - } - } - - // lastlook = last player num targetted - // cvmem = stored speed - // cusval = next waypoint heap index - // extravalue1 = SPB movement mode - // extravalue2 = mode misc option - - if (actor->extravalue1 == 1) // MODE: TARGETING - { - actor->cusval = -1; // Reset waypoint - - if (actor->tracer && actor->tracer->health) - { - fixed_t defspeed = wspeed; - fixed_t range = (160*actor->tracer->scale); - fixed_t cx = 0, cy =0; - - // Play the intimidating gurgle - if (!S_SoundPlaying(actor, actor->info->activesound)) - S_StartSound(actor, actor->info->activesound); - - // Maybe we want SPB to target an object later? IDK lol - if (actor->tracer->player) - { - UINT8 fracmax = 32; - UINT8 spark = ((10-actor->tracer->player->kartspeed) + actor->tracer->player->kartweight) / 2; - fixed_t easiness = ((actor->tracer->player->kartspeed + (10-spark)) << FRACBITS) / 2; - - actor->lastlook = actor->tracer->player-players; // Save the player num for death scumming... - actor->tracer->player->pflags |= PF_RINGLOCK; // set ring lock - - if (actor->tracer->hitlag) - { - // If the player is frozen through no fault of their own, the SPB should be too. - actor->hitlag = actor->tracer->hitlag; - } - - if (!P_IsObjectOnGround(actor->tracer)) - { - // In the air you have no control; basically don't hit unless you make a near complete stop - defspeed = (7 * actor->tracer->player->speed) / 8; - } - else - { - // 7/8ths max speed for Knuckles, 3/4ths max speed for min accel, exactly max speed for max accel - defspeed = FixedMul(((fracmax+1)<tracer->player, false, false)) / fracmax; - } - - // Be fairer on conveyors - cx = actor->tracer->player->cmomx; - cy = actor->tracer->player->cmomy; - - // Switch targets if you're no longer 1st for long enough - if (actor->tracer->player->position <= bestrank) - actor->extravalue2 = 7*TICRATE; - else if (actor->extravalue2-- <= 0) - actor->extravalue1 = 0; // back to SEEKING - - spbplace = actor->tracer->player->position; - } - - dist = P_AproxDistance(P_AproxDistance(actor->x-actor->tracer->x, actor->y-actor->tracer->y), actor->z-actor->tracer->z); - - wspeed = FixedMul(defspeed, FRACUNIT + FixedDiv(dist-range, range)); - if (wspeed < defspeed) - wspeed = defspeed; - if (wspeed > (3*defspeed)/2) - wspeed = (3*defspeed)/2; - if (wspeed < 20*actor->tracer->scale) - wspeed = 20*actor->tracer->scale; - if (actor->tracer->player->carry == CR_SLIDING) - wspeed = actor->tracer->player->speed/2; - // ^^^^ current section: These are annoying, and grand metropolis in particular needs this. - - hang = R_PointToAngle2(actor->x, actor->y, actor->tracer->x, actor->tracer->y); - vang = R_PointToAngle2(0, actor->z, dist, actor->tracer->z); - - // Modify stored speed - if (wspeed > actor->cvmem) - actor->cvmem += (wspeed - actor->cvmem) / TICRATE; - else - actor->cvmem = wspeed; - - { - // Smoothly rotate horz angle - angle_t input = hang - actor->angle; - boolean invert = (input > ANGLE_180); - if (invert) - input = InvAngle(input); - - // Slow down when turning; it looks better and makes U-turns not unfair - xyspeed = FixedMul(actor->cvmem, max(0, (((180<angle += input; - - // Smoothly rotate vert angle - input = vang - actor->movedir; - invert = (input > ANGLE_180); - if (invert) - input = InvAngle(input); - - // Slow down when turning; might as well do it for momz, since we do it above too - zspeed = FixedMul(actor->cvmem, max(0, (((180<movedir += input; - } - - actor->momx = cx + FixedMul(FixedMul(xyspeed, FINECOSINE(actor->angle>>ANGLETOFINESHIFT)), FINECOSINE(actor->movedir>>ANGLETOFINESHIFT)); - actor->momy = cy + FixedMul(FixedMul(xyspeed, FINESINE(actor->angle>>ANGLETOFINESHIFT)), FINECOSINE(actor->movedir>>ANGLETOFINESHIFT)); - actor->momz = FixedMul(zspeed, FINESINE(actor->movedir>>ANGLETOFINESHIFT)); - - // Spawn a trail of rings behind the SPB! - SpawnSPBTrailRings(actor); - - // Red speed lines for when it's gaining on its target. A tell for when you're starting to lose too much speed! - if (R_PointToDist2(0, 0, actor->momx, actor->momy) > (actor->tracer->player ? (16*actor->tracer->player->speed)/15 - : (16*R_PointToDist2(0, 0, actor->tracer->momx, actor->tracer->momy))/15) // Going faster than the target - && xyspeed > K_GetKartSpeed(actor->tracer->player, false, false) / 4) // Don't display speedup lines at pitifully low speeds - SpawnSPBSpeedLines(actor); - - return; - } - else // Target's gone, return to SEEKING - { - P_SetTarget(&actor->tracer, NULL); - actor->extravalue1 = 2; // WAIT... - actor->extravalue2 = 52; // Slightly over the respawn timer length - return; - } - } - else if (actor->extravalue1 == 2) // MODE: WAIT... - { - actor->momx = actor->momy = actor->momz = 0; // Stoooop - actor->cusval = -1; // Reset waypoint - - if (actor->lastlook != -1 - && playeringame[actor->lastlook] - && !players[actor->lastlook].spectator - && !players[actor->lastlook].exiting) - { - spbplace = players[actor->lastlook].position; - players[actor->lastlook].pflags |= PF_RINGLOCK; - if (actor->extravalue2-- <= 0 && players[actor->lastlook].mo) - { - P_SetTarget(&actor->tracer, players[actor->lastlook].mo); - actor->extravalue1 = 1; // TARGET ACQUIRED - actor->extravalue2 = 7*TICRATE; - actor->cvmem = wspeed; - } - } - else - { - actor->extravalue1 = 0; // SEEKING - actor->extravalue2 = 0; - spbplace = -1; - } - } - else // MODE: SEEKING - { - waypoint_t *lastwaypoint = NULL; - waypoint_t *bestwaypoint = NULL; - waypoint_t *nextwaypoint = NULL; - waypoint_t *tempwaypoint = NULL; - - actor->lastlook = -1; // Just make sure this is reset - - if (!player || !player->mo || player->mo->health <= 0 || (player->respawn.state != RESPAWNST_NONE)) - { - // No one there? Completely STOP. - actor->momx = actor->momy = actor->momz = 0; - if (!player) - spbplace = -1; - return; - } - - // Found someone, now get close enough to initiate the slaughter... - P_SetTarget(&actor->tracer, player->mo); - spbplace = bestrank; - dist = P_AproxDistance(P_AproxDistance(actor->x-actor->tracer->x, actor->y-actor->tracer->y), actor->z-actor->tracer->z); - - /* - K_GetBestWaypointForMobj returns the waypoint closest to the object that isn't its current waypoint. While this is usually good enough, - in cases where the track overlaps, this means that the SPB will sometimes target a waypoint on an earlier/later portion of the track instead of following along. - For this reason, we're going to try and make sure to avoid these situations. - */ - - // Move along the waypoints until you get close enough - if (actor->cusval > -1 && actor->extravalue2 > 0) - { - // Previously set nextwaypoint - lastwaypoint = K_GetWaypointFromIndex((size_t)actor->cusval); - tempwaypoint = K_GetBestWaypointForMobj(actor); - // check if the tempwaypoint corresponds to lastwaypoint's next ID at least; - // This is to avoid situations where the SPB decides to suicide jump down a bridge because it found a COMPLETELY unrelated waypoint down there. - - if (K_GetWaypointID(tempwaypoint) == K_GetWaypointNextID(lastwaypoint) || K_GetWaypointID(tempwaypoint) == K_GetWaypointID(lastwaypoint)) - // either our previous or curr waypoint ID, sure, take it - bestwaypoint = tempwaypoint; - else - bestwaypoint = K_GetWaypointFromIndex((size_t)actor->extravalue2); // keep going from the PREVIOUS wp. - } - else - bestwaypoint = K_GetBestWaypointForMobj(actor); - - if (bestwaypoint == NULL && lastwaypoint == NULL) - { - // We have invalid waypoints all around, so use closest to try and make it non-NULL. - bestwaypoint = K_GetClosestWaypointToMobj(actor); - } - - if (bestwaypoint != NULL) - { - const boolean huntbackwards = false; - boolean useshortcuts = false; - - // If the player is on a shortcut, use shortcuts. No escape. - if (K_GetWaypointIsShortcut(player->nextwaypoint)) - { - useshortcuts = true; - } - - nextwaypoint = K_GetNextWaypointToDestination( - bestwaypoint, player->nextwaypoint, useshortcuts, huntbackwards); - } - - if (nextwaypoint == NULL && lastwaypoint != NULL) - { - // Restore to the last nextwaypoint - nextwaypoint = lastwaypoint; - } - - if (nextwaypoint != NULL) - { - const fixed_t xywaypointdist = P_AproxDistance( - actor->x - nextwaypoint->mobj->x, actor->y - nextwaypoint->mobj->y); - - hang = R_PointToAngle2(actor->x, actor->y, nextwaypoint->mobj->x, nextwaypoint->mobj->y); - vang = R_PointToAngle2(0, actor->z, xywaypointdist, nextwaypoint->mobj->z); - - actor->cusval = (INT32)K_GetWaypointHeapIndex(nextwaypoint); - actor->extravalue2 = (INT32)K_GetWaypointHeapIndex(bestwaypoint); // save our last best, used above. - } - else - { - // continue straight ahead... Shouldn't happen. - hang = actor->angle; - vang = 0U; - } - - { - // Smoothly rotate horz angle - angle_t input = hang - actor->angle; - boolean invert = (input > ANGLE_180); - INT32 turnangle; - - if (invert) - input = InvAngle(input); - - input = FixedAngle(AngleFixed(input)/8); - - // Slow down when turning; it looks better and makes U-turns not unfair - xyspeed = FixedMul(wspeed, max(0, (((180<angle += input; - - // If input is small enough, spawn dust. Otherwise, spawn a slip tide! - turnangle = AngleFixed(input)/FRACUNIT; - - // The SPB is really turning if that value is >= 3 and <= 357. This looks pretty bad check-wise so feel free to change it for something that isn't as terrible. - if (turnangle >= 3 && turnangle <= 357) - SpawnSPBAIZDust(actor, turnangle < 180 ? 1 : -1); // 1 if turning left, -1 if turning right. Angles work counterclockwise, remember! - else - SpawnSPBDust(actor); // if we're mostly going straight, then spawn the V dust cone! - - SpawnSPBSpeedLines(actor); // Always spawn speed lines while seeking - - // Smoothly rotate vert angle - input = vang - actor->movedir; - invert = (input > ANGLE_180); - if (invert) - input = InvAngle(input); - - input = FixedAngle(AngleFixed(input)/8); - - // Slow down when turning; might as well do it for momz, since we do it above too - zspeed = FixedMul(wspeed, max(0, (((180<movedir += input; - } - - actor->momx = FixedMul(FixedMul(xyspeed, FINECOSINE(actor->angle>>ANGLETOFINESHIFT)), FINECOSINE(actor->movedir>>ANGLETOFINESHIFT)); - actor->momy = FixedMul(FixedMul(xyspeed, FINESINE(actor->angle>>ANGLETOFINESHIFT)), FINECOSINE(actor->movedir>>ANGLETOFINESHIFT)); - actor->momz = FixedMul(zspeed, FINESINE(actor->movedir>>ANGLETOFINESHIFT)); - - // see if a player is near us, if they are, try to hit them by slightly thrusting towards them, otherwise, bleh! - for (i=0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator || players[i].exiting) - continue; // not in-game - - if (R_PointToDist2(actor->x, actor->y, players[i].mo->x, players[i].mo->y) < pdist) - { - pdist = R_PointToDist2(actor->x, actor->y, players[i].mo->x, players[i].mo->y); - scplayer = &players[i]; // it doesn't matter if we override this guy now. - } - } - - // different player from our main target, try and ram into em~! - if (scplayer && scplayer != player) - { - pangle = actor->angle - R_PointToAngle2(actor->x, actor->y,scplayer->mo->x, scplayer->mo->y); - // check if the angle wouldn't make us LOSE speed... - if ((INT32)pangle/ANG1 >= -80 && (INT32)pangle/ANG1 <= 80) // allow for around 80 degrees - { - // Thrust us towards the guy, try to screw em up! - P_Thrust(actor, R_PointToAngle2(actor->x, actor->y, scplayer->mo->x, scplayer->mo->y), actor->movefactor/4); // not too fast though. - } - } - - // Spawn a trail of rings behind the SPB! - SpawnSPBTrailRings(actor); - - if (dist <= (1024*actor->tracer->scale) && !(actor->flags2 & MF2_AMBUSH)) // Close enough to target? Use Ambush flag to disable targetting so I can have an easier time testing stuff... - { - S_StartSound(actor, actor->info->attacksound); - actor->extravalue1 = 1; // TARGET ACQUIRED - actor->extravalue2 = 7*TICRATE; - actor->cvmem = wspeed; - } - } - - // Finally, no matter what, the spb should not be able to be under the ground, or above the ceiling; - if (actor->z < actor->floorz) - actor->z = actor->floorz; - else if (actor->z > actor->ceilingz - actor->height) - actor->z = actor->ceilingz - actor->height; - - return; -} - void A_SSMineSearch(mobj_t *actor) { fixed_t dis = INT32_MAX; diff --git a/src/p_inter.c b/src/p_inter.c index d21fb1195..b11c269e0 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -352,41 +352,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } return; case MT_SPB: - if ((special->target == toucher || special->target == toucher->target) && (special->threshold > 0)) - return; - - if (special->health <= 0 || toucher->health <= 0) - return; - - if (player->spectator) - return; - - if (special->tracer && !P_MobjWasRemoved(special->tracer) && toucher == special->tracer) { - mobj_t *spbexplode; - - if (player->bubbleblowup > 0) - { - K_DropHnextList(player, false); - special->extravalue1 = 2; // WAIT... - special->extravalue2 = 52; // Slightly over the respawn timer length - return; - } - - S_StopSound(special); // Don't continue playing the gurgle or the siren - - spbexplode = P_SpawnMobj(toucher->x, toucher->y, toucher->z, MT_SPBEXPLOSION); - spbexplode->extravalue1 = 1; // Tell K_ExplodePlayer to use extra knockback - if (special->target && !P_MobjWasRemoved(special->target)) - P_SetTarget(&spbexplode->target, special->target); - - P_RemoveMobj(special); + Obj_SPBTouch(special, toucher); + return; } - else - { - P_DamageMobj(player->mo, special, special->target, 1, DMG_NORMAL); - } - return; case MT_EMERALD: if (!P_CanPickupItem(player, 0)) return; @@ -2060,9 +2029,10 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da player->sneakertimer = player->numsneakers = 0; player->driftboost = player->strongdriftboost = 0; + player->gateBoost = 0; player->ringboost = 0; player->glanceDir = 0; - player->pflags &= ~PF_LOOKDOWN; + player->pflags &= ~PF_GAINAX; switch (type) { diff --git a/src/p_mobj.c b/src/p_mobj.c index d735d8f5e..626a4280a 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1594,21 +1594,7 @@ void P_XYMovement(mobj_t *mo) else if (P_MobjWasRemoved(mo)) return; - //{ SRB2kart - Jawz - if (mo->type == MT_JAWZ || mo->type == MT_JAWZ_DUD) - { - if (mo->health == 1) - { - // This Item Damage - S_StartSound(mo, mo->info->deathsound); - P_KillMobj(mo, NULL, NULL, DMG_NORMAL); - - P_SetObjectMomZ(mo, 8*FRACUNIT, false); - P_InstaThrust(mo, R_PointToAngle2(mo->x, mo->y, mo->x + xmove, mo->y + ymove)+ANGLE_90, 16*FRACUNIT); - } - } - //} - else if (mo->flags & MF_MISSILE) + if (mo->flags & MF_MISSILE) { // explode a missile if (P_CheckSkyHit(mo)) @@ -1671,7 +1657,7 @@ void P_XYMovement(mobj_t *mo) { boolean walltransferred = false; - if (player || mo->flags & MF_SLIDEME) + //if (player || mo->flags & MF_SLIDEME) { // try to slide along it // Wall transfer part 1. pslope_t *transferslope = NULL; @@ -1745,23 +1731,32 @@ void P_XYMovement(mobj_t *mo) fx->scale = mo->scale; } - if (mo->type == MT_ORBINAUT) // Orbinaut speed decreasing + switch (mo->type) { - if (mo->health > 1) - { - S_StartSound(mo, mo->info->attacksound); - mo->health--; - mo->threshold = 0; - } - else if (mo->health == 1) - { - // This Item Damage - S_StartSound(mo, mo->info->deathsound); - P_KillMobj(mo, NULL, NULL, DMG_NORMAL); + case MT_ORBINAUT: // Orbinaut speed decreasing + if (mo->health > 1) + { + S_StartSound(mo, mo->info->attacksound); + mo->health--; + mo->threshold = 0; + } + /*FALLTHRU*/ - P_SetObjectMomZ(mo, 8*FRACUNIT, false); - P_InstaThrust(mo, R_PointToAngle2(mo->x, mo->y, mo->x + xmove, mo->y + ymove)+ANGLE_90, 16*FRACUNIT); - } + case MT_JAWZ: + case MT_JAWZ_DUD: + if (mo->health == 1) + { + // This Item Damage + S_StartSound(mo, mo->info->deathsound); + P_KillMobj(mo, NULL, NULL, DMG_NORMAL); + + P_SetObjectMomZ(mo, 8*FRACUNIT, false); + P_InstaThrust(mo, R_PointToAngle2(mo->x, mo->y, mo->x + xmove, mo->y + ymove)+ANGLE_90, 16*FRACUNIT); + } + break; + + default: + break; } // Bubble bounce @@ -6666,7 +6661,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) break; case KITEM_SPB: case KITEM_SHRINK: - indirectitemcooldown = 20*TICRATE; + K_SetItemCooldown(mobj->threshold, 20*TICRATE); /* FALLTHRU */ default: mobj->sprite = SPR_ITEM; @@ -6739,40 +6734,45 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } else { - fixed_t finalspeed = mobj->movefactor; - const fixed_t currentspeed = R_PointToDist2(0, 0, mobj->momx, mobj->momy); - fixed_t thrustamount = 0; - fixed_t frictionsafety = (mobj->friction == 0) ? 1 : mobj->friction; mobj_t *ghost = P_SpawnGhostMobj(mobj); ghost->colorized = true; // already has color! - if (!grounded) - { - // No friction in the air - frictionsafety = FRACUNIT; - } - mobj->angle = K_MomentumAngle(mobj); - if (mobj->health <= 5) - { - INT32 i; - for (i = 5; i >= mobj->health; i--) - finalspeed = FixedMul(finalspeed, FRACUNIT-FRACUNIT/4); - } - if (currentspeed >= finalspeed) + if (P_IsObjectOnGround(mobj)) { - // Thrust as if you were at top speed, slow down naturally - thrustamount = FixedDiv(finalspeed, frictionsafety) - finalspeed; - } - else - { - const fixed_t beatfriction = FixedDiv(currentspeed, frictionsafety) - currentspeed; - // Thrust to immediately get to top speed - thrustamount = beatfriction + FixedDiv(finalspeed - currentspeed, frictionsafety); - } + fixed_t finalspeed = mobj->movefactor; + const fixed_t currentspeed = R_PointToDist2(0, 0, mobj->momx, mobj->momy); + fixed_t thrustamount = 0; + fixed_t frictionsafety = (mobj->friction == 0) ? 1 : mobj->friction; - P_Thrust(mobj, mobj->angle, thrustamount); + if (!grounded) + { + // No friction in the air + frictionsafety = FRACUNIT; + } + + if (mobj->health <= 5) + { + INT32 i; + for (i = 5; i >= mobj->health; i--) + finalspeed = FixedMul(finalspeed, FRACUNIT-FRACUNIT/4); + } + + if (currentspeed >= finalspeed) + { + // Thrust as if you were at top speed, slow down naturally + thrustamount = FixedDiv(finalspeed, frictionsafety) - finalspeed; + } + else + { + const fixed_t beatfriction = FixedDiv(currentspeed, frictionsafety) - currentspeed; + // Thrust to immediately get to top speed + thrustamount = beatfriction + FixedDiv(finalspeed - currentspeed, frictionsafety); + } + + P_Thrust(mobj, mobj->angle, thrustamount); + } if (P_MobjTouchingSectorSpecial(mobj, 3, 1, true)) K_DoPogoSpring(mobj, 0, 1); @@ -6911,8 +6911,15 @@ static boolean P_MobjRegularThink(mobj_t *mobj) mobj->threshold--; break; case MT_SPB: - indirectitemcooldown = 20*TICRATE; - /* FALLTHRU */ + { + Obj_SPBThink(mobj); + break; + } + case MT_MANTARING: + { + Obj_MantaRingThink(mobj); + break; + } case MT_BALLHOG: { mobj_t *ghost = P_SpawnGhostMobj(mobj); @@ -9274,6 +9281,11 @@ static boolean P_FuseThink(mobj_t *mobj) P_RemoveMobj(mobj); return false; } + case MT_SPB: + { + Obj_SPBExplode(mobj); + break; + } case MT_PLAYER: break; // don't remove default: @@ -9310,6 +9322,8 @@ void P_MobjThinker(mobj_t *mobj) P_SetTarget(&mobj->hnext, NULL); if (mobj->hprev && P_MobjWasRemoved(mobj->hprev)) P_SetTarget(&mobj->hprev, NULL); + if (mobj->itnext && P_MobjWasRemoved(mobj->itnext)) + P_SetTarget(&mobj->itnext, NULL); if (mobj->flags & MF_NOTHINK) return; @@ -10710,6 +10724,8 @@ void P_RemoveMobj(mobj_t *mobj) } } + P_SetTarget(&mobj->itnext, NULL); + // DBG: set everything in mobj_t to 0xFF instead of leaving it. debug memory error. #ifdef SCRAMBLE_REMOVED // Invalidate mobj_t data to cause crashes if accessed! diff --git a/src/p_saveg.c b/src/p_saveg.c index 337efd5a8..b638b03c9 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -269,6 +269,9 @@ static void P_NetArchivePlayers(void) WRITEUINT8(save_p, players[i].driftboost); WRITEUINT8(save_p, players[i].strongdriftboost); + WRITEUINT16(save_p, players[i].gateBoost); + WRITEUINT8(save_p, players[i].gateSound); + WRITESINT8(save_p, players[i].aizdriftstrat); WRITEINT32(save_p, players[i].aizdrifttilt); WRITEINT32(save_p, players[i].aizdriftturn); @@ -563,6 +566,9 @@ static void P_NetUnArchivePlayers(void) players[i].driftboost = READUINT8(save_p); players[i].strongdriftboost = READUINT8(save_p); + players[i].gateBoost = READUINT16(save_p); + players[i].gateSound = READUINT8(save_p); + players[i].aizdriftstrat = READSINT8(save_p); players[i].aizdrifttilt = READINT32(save_p); players[i].aizdriftturn = READINT32(save_p); @@ -4553,7 +4559,8 @@ static void P_NetArchiveMisc(boolean resending) WRITEFIXED(save_p, battleovertime.z); WRITEUINT32(save_p, wantedcalcdelay); - WRITEUINT32(save_p, indirectitemcooldown); + for (i = 0; i < NUMKARTITEMS-1; i++) + WRITEUINT32(save_p, itemCooldowns[i]); WRITEUINT32(save_p, mapreset); WRITEUINT8(save_p, spectateGriefed); @@ -4711,7 +4718,8 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading) battleovertime.z = READFIXED(save_p); wantedcalcdelay = READUINT32(save_p); - indirectitemcooldown = READUINT32(save_p); + for (i = 0; i < NUMKARTITEMS-1; i++) + itemCooldowns[i] = READUINT32(save_p); mapreset = READUINT32(save_p); spectateGriefed = READUINT8(save_p); diff --git a/src/p_setup.c b/src/p_setup.c index 9d60e49d8..2bdbd14f9 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -3846,6 +3846,8 @@ static void P_InitPlayers(void) static void P_InitGametype(void) { + size_t i; + spectateGriefed = 0; K_CashInPowerLevels(); // Pushes power level changes even if intermission was skipped @@ -3870,7 +3872,10 @@ static void P_InitGametype(void) } wantedcalcdelay = wantedfrequency*2; - indirectitemcooldown = 0; + + for (i = 0; i < NUMKARTITEMS-1; i++) + itemCooldowns[i] = 0; + mapreset = 0; thwompsactive = false; @@ -3898,6 +3903,9 @@ static void P_InitGametype(void) G_RecordDemo(buf); } + + // Started a game? Move on to the next jam when you go back to the title screen + CV_SetValue(&cv_menujam_update, 1); } /** Loads a level from a lump or external wad. diff --git a/src/p_spec.c b/src/p_spec.c index 859b35fe1..d24e3fbe8 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1925,7 +1925,7 @@ static void K_HandleLapIncrement(player_t *player) player->startboost = 125; K_SpawnDriftBoostExplosion(player, 4); - K_SpawnDriftElectricSparks(player); + K_SpawnDriftElectricSparks(player, SKINCOLOR_SILVER, false); rainbowstartavailable = false; } diff --git a/src/p_tick.c b/src/p_tick.c index 58b11406f..708ed6ca8 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -683,8 +683,7 @@ void P_Ticker(boolean run) if (exitcountdown > 1) exitcountdown--; - if (indirectitemcooldown > 0) - indirectitemcooldown--; + K_RunItemCooldowns(); K_BossInfoTicker(); @@ -768,6 +767,11 @@ void P_Ticker(boolean run) K_TimerInit(); } + for (i = 0; i < MAXPLAYERS; i++) + { + G_CopyTiccmd(&players[i].oldcmd, &players[i].cmd, 1); + } + // Z_CheckMemCleanup(); } diff --git a/src/p_user.c b/src/p_user.c index 53014038f..6ac150a6a 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2219,7 +2219,7 @@ void P_MovePlayer(player_t *player) player->drawangle -= ANGLE_22h; player->mo->rollangle = 0; player->glanceDir = 0; - player->pflags &= ~PF_LOOKDOWN; + player->pflags &= ~PF_GAINAX; } else if ((player->pflags & PF_FAULT) || (player->spinouttimer > 0)) { @@ -2593,7 +2593,7 @@ void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius) if (mo->type == MT_SPB) // If you destroy a SPB, you don't get the luxury of a cooldown. { spbplace = -1; - indirectitemcooldown = 0; + itemCooldowns[KITEM_SPB - 1] = 0; } if (mo->flags & MF_BOSS) //don't OHKO bosses nor players! @@ -4187,7 +4187,7 @@ void P_PlayerThink(player_t *player) } else if (cmd->buttons & BT_ACCELERATE) { - if (!player->exiting && !(player->pflags & PF_ACCELDOWN)) + if (!player->exiting && !(player->oldcmd.buttons & BT_ACCELERATE)) { player->kickstartaccel = 0; } @@ -4388,19 +4388,6 @@ void P_PlayerThink(player_t *player) P_DoBubbleBreath(player); // Spawn Sonic's bubbles P_CheckInvincibilityTimer(player); // Spawn Invincibility Sparkles - // check for buttons - if (cmd->buttons & BT_ACCELERATE) - player->pflags |= PF_ACCELDOWN; - else - player->pflags &= ~PF_ACCELDOWN; - - if (cmd->buttons & BT_BRAKE) - player->pflags |= PF_BRAKEDOWN; - else - player->pflags &= ~PF_BRAKEDOWN; - - // PF_LOOKDOWN handled in K_KartMoveAnimation - // Counters, time dependent power ups. // Time Bonus & Ring Bonus count settings diff --git a/src/r_plane.h b/src/r_plane.h index 61bac1968..f0a32d969 100644 --- a/src/r_plane.h +++ b/src/r_plane.h @@ -89,6 +89,7 @@ visplane_t *R_CheckPlane(visplane_t *pl, INT32 start, INT32 stop); void R_ExpandPlane(visplane_t *pl, INT32 start, INT32 stop); void R_PlaneBounds(visplane_t *plane); +size_t R_FlatDimensionsFromLumpSize(size_t size); void R_CheckFlatLength(size_t size); boolean R_CheckPowersOfTwo(void); diff --git a/src/r_textures.c b/src/r_textures.c index 39976a3c8..35d79e872 100644 --- a/src/r_textures.c +++ b/src/r_textures.c @@ -656,6 +656,44 @@ boolean R_CheckPowersOfTwo(void) return ds_powersoftwo; } +// +// R_FlatDimensionsFromLumpSize +// +// Returns the flat's square size from its lump length. +// +size_t R_FlatDimensionsFromLumpSize(size_t size) +{ + switch (size) + { + case 4194304: // 2048x2048 lump + return 2048; + + case 1048576: // 1024x1024 lump + return 1024; + + case 262144:// 512x512 lump + return 512; + + case 65536: // 256x256 lump + return 256; + + case 16384: // 128x128 lump + return 128; + + case 1024: // 32x32 lump + return 32; + + case 256: // 16x16 lump + return 16; + + case 64: // 8x8 lump + return 8; + + default: // 64x64 lump + return 64; + } +} + // // R_CheckFlatLength // @@ -707,6 +745,20 @@ void R_CheckFlatLength(size_t size) nflatshiftup = 11; ds_flatwidth = ds_flatheight = 32; break; + case 256: // 16x16 lump + nflatmask = 0xF0; + nflatxshift = 28; + nflatyshift = 24; + nflatshiftup = 12; + ds_flatwidth = ds_flatheight = 16; + break; + case 64: // 8x8 lump + nflatmask = 0x38; + nflatxshift = 29; + nflatyshift = 26; + nflatshiftup = 13; + ds_flatwidth = ds_flatheight = 8; + break; default: // 64x64 lump nflatmask = 0xFC0; nflatxshift = 26; @@ -774,30 +826,7 @@ Rloadflats (INT32 i, INT32 w) W_ReadLumpHeaderPwad(wadnum, lumpnum, header, sizeof header, 0); lumplength = W_LumpLengthPwad(wadnum, lumpnum); - switch (lumplength) - { - case 4194304: // 2048x2048 lump - flatsize = 2048; - break; - case 1048576: // 1024x1024 lump - flatsize = 1024; - break; - case 262144:// 512x512 lump - flatsize = 512; - break; - case 65536: // 256x256 lump - flatsize = 256; - break; - case 16384: // 128x128 lump - flatsize = 128; - break; - case 1024: // 32x32 lump - flatsize = 32; - break; - default: // 64x64 lump - flatsize = 64; - break; - } + flatsize = R_FlatDimensionsFromLumpSize(lumplength); //CONS_Printf("\n\"%s\" is a flat, dimensions %d x %d",W_CheckNameForNumPwad((UINT16)w,texstart+j),flatsize,flatsize); texture = textures[i] = Z_Calloc(sizeof(texture_t) + sizeof(texpatch_t), PU_STATIC, NULL); diff --git a/src/sounds.c b/src/sounds.c index 1cff40633..5abb9504b 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -1118,6 +1118,18 @@ sfxinfo_t S_sfx[NUMSFX] = // Shrink laser beam {"beam01", false, 32, 64, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + // SPB seeking + {"spbska", false, 32, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + {"spbskb", false, 32, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + {"spbskc", false, 32, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + + // Juicebox for SPB + {"gate01", false, 32, 64, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + {"gate02", false, 32, 64, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + {"gate03", false, 32, 64, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + {"gate04", false, 32, 64, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + {"gate05", false, 32, 64, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + // SRB2Kart - Engine sounds // Engine class A {"krta00", false, 48, 65, -1, NULL, 0, -1, -1, LUMPERROR, ""}, diff --git a/src/sounds.h b/src/sounds.h index cb9d3b671..5194d1114 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -1183,6 +1183,18 @@ typedef enum // Shrink laser sfx_beam01, + // SPB seeking + sfx_spbska, + sfx_spbskb, + sfx_spbskc, + + // Juicebox for SPB + sfx_gate01, + sfx_gate02, + sfx_gate03, + sfx_gate04, + sfx_gate05, + // Next up: UNIQUE ENGINE SOUNDS! Hoooooo boy... // Engine class A - Low Speed, Low Weight sfx_krta00, diff --git a/src/v_video.c b/src/v_video.c index 193339574..c2d3e3c4c 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -1236,19 +1236,19 @@ void V_DrawFlatFill(INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatnum) { case 4194304: // 2048x2048 lump lflatsize = 2048; - flatshift = 10; + flatshift = 11; break; case 1048576: // 1024x1024 lump lflatsize = 1024; - flatshift = 9; + flatshift = 10; break; case 262144:// 512x512 lump lflatsize = 512; - flatshift = 8; + flatshift = 9; break; case 65536: // 256x256 lump lflatsize = 256; - flatshift = 7; + flatshift = 8; break; case 16384: // 128x128 lump lflatsize = 128; @@ -1258,6 +1258,14 @@ void V_DrawFlatFill(INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatnum) lflatsize = 32; flatshift = 5; break; + case 256: // 16x16 lump + lflatsize = 16; + flatshift = 4; + break; + case 64: // 8x8 lump + lflatsize = 8; + flatshift = 3; + break; default: // 64x64 lump lflatsize = 64; flatshift = 6;